Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

@imqueue/pg-prisma

Build Statusnpm versionLicense

A Prisma Next (8.x) / Postgres toolkit for Node.js & TypeScript back-ends — the persistence helpers behind @imqueue framework services. It bundles a set of Prisma Next query middlewares (soft-delete, authorship stamping, audit trail, row-level access scope) that rewrite the statement before it is lowered to SQL, plus Postgres operational helpers (row archiving, change-notify triggers, SQL log formatting).

Their per-model configuration is derived from the emitted contract.json rather than generated: Prisma Next has no custom-generator protocol and needs none, since the contract already names every model, field and physical column.

Documentation: full guides, tutorial and API reference at imqueue.org. Commercial licensing & support for closed-source products at imqueue.com.

Using an AI assistant? Point it at imqueue.org/llms.txt for a machine-readable index of the docs, or see AGENTS.md. Current version, licence and Node floor for every package: imqueue.org/status.json.

Related packages:

  • @imqueue/core - Fast JSON message queue over Redis for inter-service communication.
  • @imqueue/rpc - RPC-like client/service implementation over @imqueue/core.
  • @imqueue/validation - Zod-backed decorator validation (used by the generated model classes).

Features

  • Soft delete and authorship — a DELETE becomes a deletedAt stamp, stamped rows disappear from reads, and every write records who made it.
  • Access scope — every read, update and delete is narrowed to the rows the caller may see, in the data layer rather than at each call site.
  • Audit trail — every write to a nominated table recorded with the actor, the action and the row as the database returned it.
  • Row archiving — aged rows moved into a mirror archive schema on a pg_cron schedule.
  • Change-notify triggers — Postgres NOTIFY on every row change.

Filtering applies across the whole statement, not just its outermost FROM. Prisma Next compiles a relation read into one statement holding several selects, so a filter on the root alone would return soft-deleted and out-of-scope rows through any include.

Requirements

  • Node.js >= 22.12
  • prisma 8.x and @prisma/orm-postgres (peer dependency)
  • PostgreSQL 15 or newer

Install

npm i @imqueue/pg-prisma

Usage

The data layer, in one call

import{dataLayer}from'@imqueue/pg-prisma';importpostgresfrom'@prisma/orm-postgres/runtime';importtype{Contract}from'./prisma/contract.d.ts';importcontractJsonfrom'./prisma/contract.json'with{type: 'json'};constlayer=dataLayer({contract: contractJson,scope: {Portfolio: {portfolio: ['id']}},resolvers: {portfolio: ()=>currentPortfolioIds()},getActorId: currentActorId,audit: {connectionString: process.env.DATABASE_URL!,config: {table: 'AuditLog',columns: {/* ... */}},getPrincipal: currentPrincipal,},});exportconstdb=postgres<Contract>({
contractJson,url: process.env.DATABASE_URL!,middleware: layer.middleware,});

dataLayer returns the middlewares already composed. That is the point: a caller never orders them, and so cannot order them wrongly. Call layer.close() on shutdown to release the audit pool.

Access scope

Scope is the one thing that cannot be derived from the contract — Prisma Next has no schema-level annotation to carry it — so it is declared where dataLayer is called, keyed by model and field:

scope: {Portfolio: {portfolio: ['id']},User: {user: ['createdBy','id']},}

Columns within one level are OR-ed; levels are AND-ed together. A resolver returning undefined leaves its level inactive, a value or array restricts, and null or an empty array denies everything. Get the composition backwards and the failure is a data leak rather than an error, so a scope naming a model the contract does not define is a throw, not a silent no-op.

Emitting the RPC model classes

Prisma Next emits contract.d.ts, which carries the types but not the decorated classes. @classType/@property are what the @imqueue/rpc client generator reads, and an undecorated type is dropped from the generated client with no error — so the DTO classes are emitted here, from the same contract:

import{emitModels,parseImportMap}from'@imqueue/pg-prisma';awaitwriteFile('src/generated/models.ts',emitModels({ contract }));

Redirecting the runtime imports

By default the emitted file imports @imqueue/rpc directly. Pass imports to point it somewhere else:

emitModels({
contract,imports: parseImportMap('@imqueue/rpc=@my-org/runtime'),});
// beforeimport{classType,property}from'@imqueue/rpc';// afterimport{classType,property}from'@my-org/runtime';

Why this exists. The decorators are only meaningful to the registry that defined them, so @imqueue/rpc, @imqueue/validation and zod each have to be a single copy shared with the service. A second copy fails silently rather than loudly — a second decorator registry nothing reads, or a ZodError that fails instanceof. The reliable way to guarantee one copy is for one package to own the dependency and re-export it, with every service taking it from there; redirecting the emitted imports is what makes that possible.

Redirecting several runtimes at one package merges them into a single statement, rather than emitting the same specifier three times:

parseImportMap('zod=@base, @imqueue/rpc=@base, @imqueue/validation=@base',);// import { classType, property, validatable, validate, z } from '@base';

Redirecting a module the generator never emits throws rather than being ignored, because the alternative is believing a redirection was applied while the generated files still point at the original.

Composing it yourself

stamp, accessScope and audit are exported individually for cases dataLayer does not cover, and deriveDataLayer produces the config they take. The middlewares commute — stamp merges what were two order-dependent Prisma 7 extensions — so there is no required order between them.

Running Unit Tests

Tests run on the native Node.js test runner (node:test) with node:assert and no external test framework:

git clone git@github.com:imqueue/pg-prisma.git
cd pg-prisma
npm install
npm test

To produce a coverage report use:

npm run test-coverage # prints coverage summary to the console
npm run test-lcov # writes coverage/lcov.info

License

This project is licensed under the GNU General Public License v3.0. See the LICENSE

About

Prisma/Postgres toolkit for @imqueue framework services — extensions, archiving, migrations and an @imqueue/rpc model generator

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages