@ddd-ts-kit/drizzle 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/dist/postgres/outbox/drizzle-outbox-store.d.ts +2 -1
- package/dist/postgres/outbox/drizzle-outbox-store.d.ts.map +1 -1
- package/dist/postgres/outbox/drizzle-outbox-store.js +9 -0
- package/dist/postgres/outbox/drizzle-outbox-store.js.map +1 -1
- package/dist/postgres/outbox/outbox-columns.d.ts +20 -0
- package/dist/postgres/outbox/outbox-columns.d.ts.map +1 -0
- package/dist/postgres/outbox/outbox-columns.js +25 -0
- package/dist/postgres/outbox/outbox-columns.js.map +1 -0
- package/dist/postgres/outbox/outbox-table.d.ts +2 -3
- package/dist/postgres/outbox/outbox-table.d.ts.map +1 -1
- package/dist/postgres/outbox/outbox-table.js +3 -21
- package/dist/postgres/outbox/outbox-table.js.map +1 -1
- package/dist/postgres/outbox/tenant-outbox-table.d.ts +271 -0
- package/dist/postgres/outbox/tenant-outbox-table.d.ts.map +1 -0
- package/dist/postgres/outbox/tenant-outbox-table.js +16 -0
- package/dist/postgres/outbox/tenant-outbox-table.js.map +1 -0
- package/dist/postgres/read-model/drizzle-read-model-options.d.ts +9 -0
- package/dist/postgres/read-model/drizzle-read-model-options.d.ts.map +1 -1
- package/dist/postgres/read-model/drizzle-read-model-table.d.ts.map +1 -1
- package/dist/postgres/read-model/drizzle-read-model-table.js +11 -2
- package/dist/postgres/read-model/drizzle-read-model-table.js.map +1 -1
- package/dist/postgres/read-model/drizzle-stamped-rows.d.ts +8 -1
- package/dist/postgres/read-model/drizzle-stamped-rows.d.ts.map +1 -1
- package/dist/postgres/read-model/drizzle-stamped-rows.js +23 -4
- package/dist/postgres/read-model/drizzle-stamped-rows.js.map +1 -1
- package/dist/postgres/read-model/read-model-tenant.d.ts +8 -0
- package/dist/postgres/read-model/read-model-tenant.d.ts.map +1 -0
- package/dist/postgres/read-model/read-model-tenant.js +2 -0
- package/dist/postgres/read-model/read-model-tenant.js.map +1 -0
- package/dist/postgres/tenancy/force-row-level-security.d.ts +8 -0
- package/dist/postgres/tenancy/force-row-level-security.d.ts.map +1 -0
- package/dist/postgres/tenancy/force-row-level-security.js +12 -0
- package/dist/postgres/tenancy/force-row-level-security.js.map +1 -0
- package/dist/postgres/tenancy/own-tenant-condition.d.ts +6 -0
- package/dist/postgres/tenancy/own-tenant-condition.d.ts.map +1 -0
- package/dist/postgres/tenancy/own-tenant-condition.js +7 -0
- package/dist/postgres/tenancy/own-tenant-condition.js.map +1 -0
- package/dist/postgres/tenancy/reach-condition.d.ts +5 -0
- package/dist/postgres/tenancy/reach-condition.d.ts.map +1 -0
- package/dist/postgres/tenancy/reach-condition.js +14 -0
- package/dist/postgres/tenancy/reach-condition.js.map +1 -0
- package/dist/postgres/tenancy/sql-executor.d.ts +6 -0
- package/dist/postgres/tenancy/sql-executor.d.ts.map +1 -0
- package/dist/postgres/tenancy/sql-executor.js +2 -0
- package/dist/postgres/tenancy/sql-executor.js.map +1 -0
- package/dist/postgres/tenancy/tenant-condition.d.ts +12 -0
- package/dist/postgres/tenancy/tenant-condition.d.ts.map +1 -0
- package/dist/postgres/tenancy/tenant-condition.js +13 -0
- package/dist/postgres/tenancy/tenant-condition.js.map +1 -0
- package/dist/postgres/tenancy/tenant-policies.d.ts +12 -0
- package/dist/postgres/tenancy/tenant-policies.d.ts.map +1 -0
- package/dist/postgres/tenancy/tenant-policies.js +27 -0
- package/dist/postgres/tenancy/tenant-policies.js.map +1 -0
- package/dist/postgres/tenancy/tenant-session.d.ts +10 -0
- package/dist/postgres/tenancy/tenant-session.d.ts.map +1 -0
- package/dist/postgres/tenancy/tenant-session.js +15 -0
- package/dist/postgres/tenancy/tenant-session.js.map +1 -0
- package/dist/postgres/unit-of-work/create-drizzle-unit-of-work.d.ts +6 -1
- package/dist/postgres/unit-of-work/create-drizzle-unit-of-work.d.ts.map +1 -1
- package/dist/postgres/unit-of-work/create-drizzle-unit-of-work.js +7 -2
- package/dist/postgres/unit-of-work/create-drizzle-unit-of-work.js.map +1 -1
- package/dist/postgres.d.ts +8 -0
- package/dist/postgres.d.ts.map +1 -1
- package/dist/postgres.js +8 -0
- package/dist/postgres.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -378,6 +378,43 @@ The rules these examples hold, and that the tests of this package check on a rea
|
|
|
378
378
|
- **An offset page answers `total`**, a cursor page `nextCursor`. Offer the offset form for
|
|
379
379
|
small, browsable lists and the cursor form for anything that grows.
|
|
380
380
|
|
|
381
|
+
## Tenant isolation
|
|
382
|
+
|
|
383
|
+
Two strategies on drizzle, both proven by `tenantIsolationContract` of
|
|
384
|
+
`@ddd-ts-kit/testing/contracts`; schema per tenant and database per tenant are shipped for knex
|
|
385
|
+
only ([Multitenancy](../../docs/concepts/multitenancy.md) has the table).
|
|
386
|
+
|
|
387
|
+
**Shared tables.** A `tenant_id` column (`tenantColumn()`) and the condition of the kit in every
|
|
388
|
+
statement instead of one written by hand:
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
// reads: the tenant of the work, or the tenants of an elevation
|
|
392
|
+
.where(and(eq(invoices.id, id), tenantCondition(invoices.tenantId, this.scope)))
|
|
393
|
+
// updates and deletes: the one tenant of the work, which an elevation never widens
|
|
394
|
+
.where(and(eq(invoices.id, state.id), eq(invoices.version, expected), ownTenantCondition(invoices.tenantId, this.scope)))
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Inserts spread `tenantScopeOf(this.scope)`. A read model of tenant data passes `tenant: { scope,
|
|
398
|
+
column: table.tenantId }` to `DrizzleReadModelTable` and `DrizzleReadModelStore`.
|
|
399
|
+
|
|
400
|
+
**Row-level security**, the second line: the policies (one per command: a select sees the reach of
|
|
401
|
+
the work, an insert, an update and a delete only its one tenant) are declared with the table, so
|
|
402
|
+
drizzle-kit generates them, and the unit of work sets the tenant of each transaction.
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
export const invoices = billing
|
|
406
|
+
.table('invoices', { /* ... */ ...tenantColumn() }, (table) => [...tenantPolicies(table.tenantId)])
|
|
407
|
+
.enableRLS();
|
|
408
|
+
|
|
409
|
+
const unitOfWork = createDrizzleUnitOfWork(db, undefined, { begin: tenantSession(executionScope) });
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
drizzle-kit does not force the policies on the owner of the table: `forceRowLevelSecurity(invoices)`
|
|
413
|
+
is the statement of a custom migration (`drizzle-kit generate --custom`), or the application
|
|
414
|
+
connects with a role that does not own the tables. In every case that role is not a superuser and
|
|
415
|
+
has no `BYPASSRLS`, and every statement runs inside the unit of work: one outside of a transaction
|
|
416
|
+
carries no setting and sees nothing.
|
|
417
|
+
|
|
381
418
|
## Criteria
|
|
382
419
|
|
|
383
420
|
`criteriaToDrizzle(plan, columns)` turns a criteria plan of the kernel into the parts of a
|
|
@@ -460,6 +497,10 @@ and on PostgreSQL 18.
|
|
|
460
497
|
- `DrizzleProcessedEventStore.markProcessed` is an `INSERT ... ON CONFLICT DO NOTHING`. Run
|
|
461
498
|
the consumer inside the unit of work: the mark then commits with the effects of the handler,
|
|
462
499
|
and disappears with them if it fails.
|
|
500
|
+
- A multi-tenant project declares `tenantOutboxTable('messaging')` instead of `outboxTable`: the
|
|
501
|
+
same table and a nullable `tenant_id`, which the store, given that table, fills from the tenant
|
|
502
|
+
of each event, so that its consumers run in that tenant. Switching an existing project is one
|
|
503
|
+
generated migration that adds the column.
|
|
463
504
|
|
|
464
505
|
## Testing an adapter
|
|
465
506
|
|
|
@@ -5,7 +5,7 @@ import type { OutboxRecord, OutboxStats, OutboxStore } from '@ddd-ts-kit/events'
|
|
|
5
5
|
import type { DrizzleContext } from '../unit-of-work/drizzle-context.js';
|
|
6
6
|
import { type OutboxTable } from './outbox-table.js';
|
|
7
7
|
export interface DrizzleOutboxStoreOptions {
|
|
8
|
-
/** Default `outboxTable()`, in the `public` schema. */
|
|
8
|
+
/** Default `outboxTable()`, in the `public` schema. `tenantOutboxTable()` keeps the tenant of each event. */
|
|
9
9
|
readonly table?: OutboxTable;
|
|
10
10
|
/** How long a claimed record stays invisible to other claims. Default 30000. */
|
|
11
11
|
readonly leaseMs?: number;
|
|
@@ -19,6 +19,7 @@ export declare class DrizzleOutboxStore<Q extends PgQueryResultHKT, F extends Re
|
|
|
19
19
|
private readonly context;
|
|
20
20
|
private readonly outbox;
|
|
21
21
|
private readonly leaseDurationMs;
|
|
22
|
+
private readonly tenant;
|
|
22
23
|
constructor(context: DrizzleContext<Q, F, S>, options?: DrizzleOutboxStoreOptions);
|
|
23
24
|
append(events: readonly IntegrationEvent[]): Promise<void>;
|
|
24
25
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle-outbox-store.d.ts","sourceRoot":"","sources":["../../../src/postgres/outbox/drizzle-outbox-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,yBAAyB;IACxC,
|
|
1
|
+
{"version":3,"file":"drizzle-outbox-store.d.ts","sourceRoot":"","sources":["../../../src/postgres/outbox/drizzle-outbox-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAElE,MAAM,WAAW,yBAAyB;IACxC,6GAA6G;IAC7G,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAOD;;;;GAIG;AACH,qBAAa,kBAAkB,CAC7B,CAAC,SAAS,gBAAgB,EAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,sBAAsB,CAChC,YAAW,WAAW;IAEtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAElD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IAErC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAEzC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IAEjC,YAAY,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,GAAE,yBAA8B,EAKpF;IAEK,MAAM,CAAC,MAAM,EAAE,SAAS,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB/D;IAED;;;;;OAKG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA4BlE;IAEK,aAAa,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAUnE;IAEK,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,aAAa,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAUxG;IAEK,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAalE;IAEK,cAAc,CAAC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAQlD;IAEK,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC,CAiBlC;CACF"}
|
|
@@ -11,10 +11,12 @@ export class DrizzleOutboxStore {
|
|
|
11
11
|
context;
|
|
12
12
|
outbox;
|
|
13
13
|
leaseDurationMs;
|
|
14
|
+
tenant;
|
|
14
15
|
constructor(context, options = {}) {
|
|
15
16
|
this.context = context;
|
|
16
17
|
this.outbox = options.table ?? outboxTable();
|
|
17
18
|
this.leaseDurationMs = options.leaseMs ?? 30_000;
|
|
19
|
+
this.tenant = 'tenantId' in this.outbox;
|
|
18
20
|
}
|
|
19
21
|
async append(events) {
|
|
20
22
|
if (events.length === 0) {
|
|
@@ -31,6 +33,7 @@ export class DrizzleOutboxStore {
|
|
|
31
33
|
payload: event.payload,
|
|
32
34
|
traceparent: event.traceparent ?? null,
|
|
33
35
|
correlationId: event.correlationId ?? null,
|
|
36
|
+
...(this.tenant ? { tenantId: event.tenantId ?? null } : {}),
|
|
34
37
|
nextAttemptAt: DUE_IMMEDIATELY,
|
|
35
38
|
})));
|
|
36
39
|
}
|
|
@@ -128,6 +131,7 @@ function toRecord(row) {
|
|
|
128
131
|
payload: row.payload,
|
|
129
132
|
...(row.traceparent === null ? {} : { traceparent: row.traceparent }),
|
|
130
133
|
...(row.correlationId === null ? {} : { correlationId: row.correlationId }),
|
|
134
|
+
...tenantOf(row),
|
|
131
135
|
},
|
|
132
136
|
status: row.status,
|
|
133
137
|
attempts: row.attempts,
|
|
@@ -136,4 +140,9 @@ function toRecord(row) {
|
|
|
136
140
|
...(row.publishedAt === null ? {} : { publishedAt: row.publishedAt }),
|
|
137
141
|
};
|
|
138
142
|
}
|
|
143
|
+
/** The tenant a `tenantOutboxTable` row carries; a row of `outboxTable` has none. */
|
|
144
|
+
function tenantOf(row) {
|
|
145
|
+
const tenantId = Reflect.get(row, 'tenantId');
|
|
146
|
+
return typeof tenantId === 'string' ? { tenantId } : {};
|
|
147
|
+
}
|
|
139
148
|
//# sourceMappingURL=drizzle-outbox-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle-outbox-store.js","sourceRoot":"","sources":["../../../src/postgres/outbox/drizzle-outbox-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAA+B,MAAM,aAAa,CAAC;AAK9F,OAAO,EAAE,WAAW,EAAoB,MAAM,mBAAmB,CAAC;AAWlE,mEAAmE;AACnE,MAAM,eAAe,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;AAEpC;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IAMZ,OAAO,CAA0B;IAEjC,MAAM,CAAc;IAEpB,eAAe,CAAS;
|
|
1
|
+
{"version":3,"file":"drizzle-outbox-store.js","sourceRoot":"","sources":["../../../src/postgres/outbox/drizzle-outbox-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OutboxStatus } from '@ddd-ts-kit/events';
|
|
2
|
+
/** The columns of every outbox, with or without the tenant. */
|
|
3
|
+
export declare function outboxColumns(): {
|
|
4
|
+
id: import("drizzle-orm").IsPrimaryKey<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"id", [string, ...string[]]>>>;
|
|
5
|
+
/** Insertion order, the tie-breaker of the claim. */
|
|
6
|
+
sequence: import("drizzle-orm").IsIdentity<import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"sequence">, "always">;
|
|
7
|
+
type: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"type", [string, ...string[]]>>;
|
|
8
|
+
version: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"version">>;
|
|
9
|
+
occurredAt: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilderInitial<"occurred_at", [string, ...string[]]>>;
|
|
10
|
+
payload: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"payload">;
|
|
11
|
+
traceparent: import("drizzle-orm/pg-core").PgTextBuilderInitial<"traceparent", [string, ...string[]]>;
|
|
12
|
+
correlationId: import("drizzle-orm/pg-core").PgTextBuilderInitial<"correlation_id", [string, ...string[]]>;
|
|
13
|
+
status: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm").$Type<import("drizzle-orm/pg-core").PgTextBuilderInitial<"status", [string, ...string[]]>, OutboxStatus>>>;
|
|
14
|
+
attempts: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgIntegerBuilderInitial<"attempts">>>;
|
|
15
|
+
nextAttemptAt: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>;
|
|
16
|
+
lastErrorCode: import("drizzle-orm/pg-core").PgTextBuilderInitial<"last_error_code", [string, ...string[]]>;
|
|
17
|
+
publishedAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>;
|
|
18
|
+
createdAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<string>>>;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=outbox-columns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-columns.d.ts","sourceRoot":"","sources":["../../../src/postgres/outbox/outbox-columns.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD,+DAA+D;AAC/D,wBAAgB,aAAa;IAEzB,EAAE;IACF,qDAAqD;IACrD,QAAQ;IACR,IAAI;IACJ,OAAO;IAEP,UAAU;IAEV,OAAO;IACP,WAAW;IACX,aAAa;IACb,MAAM;IACN,QAAQ;IACR,aAAa;IACb,aAAa;IACb,WAAW;IACX,SAAS;EAEZ"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { bigint, integer, jsonb, text } from 'drizzle-orm/pg-core';
|
|
2
|
+
import { instant } from '../columns/instant.js';
|
|
3
|
+
/** The columns of every outbox, with or without the tenant. */
|
|
4
|
+
export function outboxColumns() {
|
|
5
|
+
return {
|
|
6
|
+
id: text('id').primaryKey(),
|
|
7
|
+
/** Insertion order, the tie-breaker of the claim. */
|
|
8
|
+
sequence: bigint('sequence', { mode: 'number' }).generatedAlwaysAsIdentity(),
|
|
9
|
+
type: text('type').notNull(),
|
|
10
|
+
version: integer('version').notNull(),
|
|
11
|
+
// Text, not timestamptz: the envelope must come back exactly as published, offset included.
|
|
12
|
+
occurredAt: text('occurred_at').notNull(),
|
|
13
|
+
// Nullable because JSON `null` is a legal payload and is stored as SQL NULL.
|
|
14
|
+
payload: jsonb('payload'),
|
|
15
|
+
traceparent: text('traceparent'),
|
|
16
|
+
correlationId: text('correlation_id'),
|
|
17
|
+
status: text('status').$type().notNull().default('pending'),
|
|
18
|
+
attempts: integer('attempts').notNull().default(0),
|
|
19
|
+
nextAttemptAt: instant('next_attempt_at').notNull(),
|
|
20
|
+
lastErrorCode: text('last_error_code'),
|
|
21
|
+
publishedAt: instant('published_at'),
|
|
22
|
+
createdAt: instant('created_at').notNull().defaultNow(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=outbox-columns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox-columns.js","sourceRoot":"","sources":["../../../src/postgres/outbox/outbox-columns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,+DAA+D;AAC/D,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE;QAC3B,qDAAqD;QACrD,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,yBAAyB,EAAE;QAC5E,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE;QAC5B,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;QACrC,4FAA4F;QAC5F,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE;QACzC,6EAA6E;QAC7E,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;QACzB,WAAW,EAAE,IAAI,CAAC,aAAa,CAAC;QAChC,aAAa,EAAE,IAAI,CAAC,gBAAgB,CAAC;QACrC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAgB,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;QACzE,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAClD,aAAa,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO,EAAE;QACnD,aAAa,EAAE,IAAI,CAAC,iBAAiB,CAAC;QACtC,WAAW,EAAE,OAAO,CAAC,cAAc,CAAC;QACpC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE;KACxD,CAAC;AACJ,CAAC"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { OutboxStatus } from '@ddd-ts-kit/events';
|
|
2
1
|
export type OutboxTable = ReturnType<typeof outboxTable>;
|
|
3
2
|
/**
|
|
4
3
|
* The outbox table, to include in the schema files drizzle-kit reads so that it is created by
|
|
@@ -149,7 +148,7 @@ export declare function outboxTable(schemaName?: string): import("drizzle-orm/pg
|
|
|
149
148
|
tableName: "outbox";
|
|
150
149
|
dataType: "string";
|
|
151
150
|
columnType: "PgText";
|
|
152
|
-
data: OutboxStatus;
|
|
151
|
+
data: import("@ddd-ts-kit/events").OutboxStatus;
|
|
153
152
|
driverParam: string;
|
|
154
153
|
notNull: true;
|
|
155
154
|
hasDefault: true;
|
|
@@ -161,7 +160,7 @@ export declare function outboxTable(schemaName?: string): import("drizzle-orm/pg
|
|
|
161
160
|
identity: undefined;
|
|
162
161
|
generated: undefined;
|
|
163
162
|
}, {}, {
|
|
164
|
-
$type: OutboxStatus;
|
|
163
|
+
$type: import("@ddd-ts-kit/events").OutboxStatus;
|
|
165
164
|
}>;
|
|
166
165
|
attempts: import("drizzle-orm/pg-core").PgColumn<{
|
|
167
166
|
name: "attempts";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox-table.d.ts","sourceRoot":"","sources":["../../../src/postgres/outbox/outbox-table.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbox-table.d.ts","sourceRoot":"","sources":["../../../src/postgres/outbox/outbox-table.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAEzD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,UAAU,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAK9C"}
|
|
@@ -1,31 +1,13 @@
|
|
|
1
1
|
import { sql } from 'drizzle-orm';
|
|
2
|
-
import {
|
|
3
|
-
import { instant } from '../columns/instant.js';
|
|
2
|
+
import { index } from 'drizzle-orm/pg-core';
|
|
4
3
|
import { tableIn } from '../schema/table-in.js';
|
|
4
|
+
import { outboxColumns } from './outbox-columns.js';
|
|
5
5
|
/**
|
|
6
6
|
* The outbox table, to include in the schema files drizzle-kit reads so that it is created by
|
|
7
7
|
* the migrations of the consuming project. Without a schema name it lives in `public`.
|
|
8
8
|
*/
|
|
9
9
|
export function outboxTable(schemaName) {
|
|
10
|
-
return tableIn(schemaName)('outbox',
|
|
11
|
-
id: text('id').primaryKey(),
|
|
12
|
-
/** Insertion order, the tie-breaker of the claim. */
|
|
13
|
-
sequence: bigint('sequence', { mode: 'number' }).generatedAlwaysAsIdentity(),
|
|
14
|
-
type: text('type').notNull(),
|
|
15
|
-
version: integer('version').notNull(),
|
|
16
|
-
// Text, not timestamptz: the envelope must come back exactly as published, offset included.
|
|
17
|
-
occurredAt: text('occurred_at').notNull(),
|
|
18
|
-
// Nullable because JSON `null` is a legal payload and is stored as SQL NULL.
|
|
19
|
-
payload: jsonb('payload'),
|
|
20
|
-
traceparent: text('traceparent'),
|
|
21
|
-
correlationId: text('correlation_id'),
|
|
22
|
-
status: text('status').$type().notNull().default('pending'),
|
|
23
|
-
attempts: integer('attempts').notNull().default(0),
|
|
24
|
-
nextAttemptAt: instant('next_attempt_at').notNull(),
|
|
25
|
-
lastErrorCode: text('last_error_code'),
|
|
26
|
-
publishedAt: instant('published_at'),
|
|
27
|
-
createdAt: instant('created_at').notNull().defaultNow(),
|
|
28
|
-
}, (table) => [
|
|
10
|
+
return tableIn(schemaName)('outbox', outboxColumns(), (table) => [
|
|
29
11
|
// Partial: the relay only ever scans what is pending, and published rows dominate.
|
|
30
12
|
index('outbox_pending_idx').on(table.nextAttemptAt, table.sequence).where(sql `${table.status} = 'pending'`),
|
|
31
13
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbox-table.js","sourceRoot":"","sources":["../../../src/postgres/outbox/outbox-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"outbox-table.js","sourceRoot":"","sources":["../../../src/postgres/outbox/outbox-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAIpD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,UAAmB;IAC7C,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QAC/D,mFAAmF;QACnF,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAA,GAAG,KAAK,CAAC,MAAM,cAAc,CAAC;KAC5G,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outbox of a multi-tenant project: `outboxTable` and `tenant_id`, nullable, which
|
|
3
|
+
* `DrizzleOutboxStore` fills from the tenant of each event so that its consumers run in that
|
|
4
|
+
* tenant. A project that becomes multi-tenant swaps it in and generates the migration that adds
|
|
5
|
+
* the column.
|
|
6
|
+
*/
|
|
7
|
+
export declare function tenantOutboxTable(schemaName?: string): import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
8
|
+
name: "outbox";
|
|
9
|
+
schema: string | undefined;
|
|
10
|
+
columns: {
|
|
11
|
+
id: import("drizzle-orm/pg-core").PgColumn<{
|
|
12
|
+
name: "id";
|
|
13
|
+
tableName: "outbox";
|
|
14
|
+
dataType: "string";
|
|
15
|
+
columnType: "PgText";
|
|
16
|
+
data: string;
|
|
17
|
+
driverParam: string;
|
|
18
|
+
notNull: true;
|
|
19
|
+
hasDefault: false;
|
|
20
|
+
isPrimaryKey: true;
|
|
21
|
+
isAutoincrement: false;
|
|
22
|
+
hasRuntimeDefault: false;
|
|
23
|
+
enumValues: [string, ...string[]];
|
|
24
|
+
baseColumn: never;
|
|
25
|
+
identity: undefined;
|
|
26
|
+
generated: undefined;
|
|
27
|
+
}, {}, {}>;
|
|
28
|
+
sequence: import("drizzle-orm/pg-core").PgColumn<{
|
|
29
|
+
name: "sequence";
|
|
30
|
+
tableName: "outbox";
|
|
31
|
+
dataType: "number";
|
|
32
|
+
columnType: "PgBigInt53";
|
|
33
|
+
data: number;
|
|
34
|
+
driverParam: string | number;
|
|
35
|
+
notNull: true;
|
|
36
|
+
hasDefault: true;
|
|
37
|
+
isPrimaryKey: false;
|
|
38
|
+
isAutoincrement: false;
|
|
39
|
+
hasRuntimeDefault: false;
|
|
40
|
+
enumValues: undefined;
|
|
41
|
+
baseColumn: never;
|
|
42
|
+
identity: "always";
|
|
43
|
+
generated: undefined;
|
|
44
|
+
}, {}, {}>;
|
|
45
|
+
type: import("drizzle-orm/pg-core").PgColumn<{
|
|
46
|
+
name: "type";
|
|
47
|
+
tableName: "outbox";
|
|
48
|
+
dataType: "string";
|
|
49
|
+
columnType: "PgText";
|
|
50
|
+
data: string;
|
|
51
|
+
driverParam: string;
|
|
52
|
+
notNull: true;
|
|
53
|
+
hasDefault: false;
|
|
54
|
+
isPrimaryKey: false;
|
|
55
|
+
isAutoincrement: false;
|
|
56
|
+
hasRuntimeDefault: false;
|
|
57
|
+
enumValues: [string, ...string[]];
|
|
58
|
+
baseColumn: never;
|
|
59
|
+
identity: undefined;
|
|
60
|
+
generated: undefined;
|
|
61
|
+
}, {}, {}>;
|
|
62
|
+
version: import("drizzle-orm/pg-core").PgColumn<{
|
|
63
|
+
name: "version";
|
|
64
|
+
tableName: "outbox";
|
|
65
|
+
dataType: "number";
|
|
66
|
+
columnType: "PgInteger";
|
|
67
|
+
data: number;
|
|
68
|
+
driverParam: string | number;
|
|
69
|
+
notNull: true;
|
|
70
|
+
hasDefault: false;
|
|
71
|
+
isPrimaryKey: false;
|
|
72
|
+
isAutoincrement: false;
|
|
73
|
+
hasRuntimeDefault: false;
|
|
74
|
+
enumValues: undefined;
|
|
75
|
+
baseColumn: never;
|
|
76
|
+
identity: undefined;
|
|
77
|
+
generated: undefined;
|
|
78
|
+
}, {}, {}>;
|
|
79
|
+
occurredAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
80
|
+
name: "occurred_at";
|
|
81
|
+
tableName: "outbox";
|
|
82
|
+
dataType: "string";
|
|
83
|
+
columnType: "PgText";
|
|
84
|
+
data: string;
|
|
85
|
+
driverParam: string;
|
|
86
|
+
notNull: true;
|
|
87
|
+
hasDefault: false;
|
|
88
|
+
isPrimaryKey: false;
|
|
89
|
+
isAutoincrement: false;
|
|
90
|
+
hasRuntimeDefault: false;
|
|
91
|
+
enumValues: [string, ...string[]];
|
|
92
|
+
baseColumn: never;
|
|
93
|
+
identity: undefined;
|
|
94
|
+
generated: undefined;
|
|
95
|
+
}, {}, {}>;
|
|
96
|
+
payload: import("drizzle-orm/pg-core").PgColumn<{
|
|
97
|
+
name: "payload";
|
|
98
|
+
tableName: "outbox";
|
|
99
|
+
dataType: "json";
|
|
100
|
+
columnType: "PgJsonb";
|
|
101
|
+
data: unknown;
|
|
102
|
+
driverParam: unknown;
|
|
103
|
+
notNull: false;
|
|
104
|
+
hasDefault: false;
|
|
105
|
+
isPrimaryKey: false;
|
|
106
|
+
isAutoincrement: false;
|
|
107
|
+
hasRuntimeDefault: false;
|
|
108
|
+
enumValues: undefined;
|
|
109
|
+
baseColumn: never;
|
|
110
|
+
identity: undefined;
|
|
111
|
+
generated: undefined;
|
|
112
|
+
}, {}, {}>;
|
|
113
|
+
traceparent: import("drizzle-orm/pg-core").PgColumn<{
|
|
114
|
+
name: "traceparent";
|
|
115
|
+
tableName: "outbox";
|
|
116
|
+
dataType: "string";
|
|
117
|
+
columnType: "PgText";
|
|
118
|
+
data: string;
|
|
119
|
+
driverParam: string;
|
|
120
|
+
notNull: false;
|
|
121
|
+
hasDefault: false;
|
|
122
|
+
isPrimaryKey: false;
|
|
123
|
+
isAutoincrement: false;
|
|
124
|
+
hasRuntimeDefault: false;
|
|
125
|
+
enumValues: [string, ...string[]];
|
|
126
|
+
baseColumn: never;
|
|
127
|
+
identity: undefined;
|
|
128
|
+
generated: undefined;
|
|
129
|
+
}, {}, {}>;
|
|
130
|
+
correlationId: import("drizzle-orm/pg-core").PgColumn<{
|
|
131
|
+
name: "correlation_id";
|
|
132
|
+
tableName: "outbox";
|
|
133
|
+
dataType: "string";
|
|
134
|
+
columnType: "PgText";
|
|
135
|
+
data: string;
|
|
136
|
+
driverParam: string;
|
|
137
|
+
notNull: false;
|
|
138
|
+
hasDefault: false;
|
|
139
|
+
isPrimaryKey: false;
|
|
140
|
+
isAutoincrement: false;
|
|
141
|
+
hasRuntimeDefault: false;
|
|
142
|
+
enumValues: [string, ...string[]];
|
|
143
|
+
baseColumn: never;
|
|
144
|
+
identity: undefined;
|
|
145
|
+
generated: undefined;
|
|
146
|
+
}, {}, {}>;
|
|
147
|
+
status: import("drizzle-orm/pg-core").PgColumn<{
|
|
148
|
+
name: "status";
|
|
149
|
+
tableName: "outbox";
|
|
150
|
+
dataType: "string";
|
|
151
|
+
columnType: "PgText";
|
|
152
|
+
data: import("@ddd-ts-kit/events").OutboxStatus;
|
|
153
|
+
driverParam: string;
|
|
154
|
+
notNull: true;
|
|
155
|
+
hasDefault: true;
|
|
156
|
+
isPrimaryKey: false;
|
|
157
|
+
isAutoincrement: false;
|
|
158
|
+
hasRuntimeDefault: false;
|
|
159
|
+
enumValues: [string, ...string[]];
|
|
160
|
+
baseColumn: never;
|
|
161
|
+
identity: undefined;
|
|
162
|
+
generated: undefined;
|
|
163
|
+
}, {}, {
|
|
164
|
+
$type: import("@ddd-ts-kit/events").OutboxStatus;
|
|
165
|
+
}>;
|
|
166
|
+
attempts: import("drizzle-orm/pg-core").PgColumn<{
|
|
167
|
+
name: "attempts";
|
|
168
|
+
tableName: "outbox";
|
|
169
|
+
dataType: "number";
|
|
170
|
+
columnType: "PgInteger";
|
|
171
|
+
data: number;
|
|
172
|
+
driverParam: string | number;
|
|
173
|
+
notNull: true;
|
|
174
|
+
hasDefault: true;
|
|
175
|
+
isPrimaryKey: false;
|
|
176
|
+
isAutoincrement: false;
|
|
177
|
+
hasRuntimeDefault: false;
|
|
178
|
+
enumValues: undefined;
|
|
179
|
+
baseColumn: never;
|
|
180
|
+
identity: undefined;
|
|
181
|
+
generated: undefined;
|
|
182
|
+
}, {}, {}>;
|
|
183
|
+
nextAttemptAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
184
|
+
name: string;
|
|
185
|
+
tableName: "outbox";
|
|
186
|
+
dataType: "date";
|
|
187
|
+
columnType: "PgTimestamp";
|
|
188
|
+
data: Date;
|
|
189
|
+
driverParam: string;
|
|
190
|
+
notNull: true;
|
|
191
|
+
hasDefault: false;
|
|
192
|
+
isPrimaryKey: false;
|
|
193
|
+
isAutoincrement: false;
|
|
194
|
+
hasRuntimeDefault: false;
|
|
195
|
+
enumValues: undefined;
|
|
196
|
+
baseColumn: never;
|
|
197
|
+
identity: undefined;
|
|
198
|
+
generated: undefined;
|
|
199
|
+
}, {}, {}>;
|
|
200
|
+
lastErrorCode: import("drizzle-orm/pg-core").PgColumn<{
|
|
201
|
+
name: "last_error_code";
|
|
202
|
+
tableName: "outbox";
|
|
203
|
+
dataType: "string";
|
|
204
|
+
columnType: "PgText";
|
|
205
|
+
data: string;
|
|
206
|
+
driverParam: string;
|
|
207
|
+
notNull: false;
|
|
208
|
+
hasDefault: false;
|
|
209
|
+
isPrimaryKey: false;
|
|
210
|
+
isAutoincrement: false;
|
|
211
|
+
hasRuntimeDefault: false;
|
|
212
|
+
enumValues: [string, ...string[]];
|
|
213
|
+
baseColumn: never;
|
|
214
|
+
identity: undefined;
|
|
215
|
+
generated: undefined;
|
|
216
|
+
}, {}, {}>;
|
|
217
|
+
publishedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
218
|
+
name: string;
|
|
219
|
+
tableName: "outbox";
|
|
220
|
+
dataType: "date";
|
|
221
|
+
columnType: "PgTimestamp";
|
|
222
|
+
data: Date;
|
|
223
|
+
driverParam: string;
|
|
224
|
+
notNull: false;
|
|
225
|
+
hasDefault: false;
|
|
226
|
+
isPrimaryKey: false;
|
|
227
|
+
isAutoincrement: false;
|
|
228
|
+
hasRuntimeDefault: false;
|
|
229
|
+
enumValues: undefined;
|
|
230
|
+
baseColumn: never;
|
|
231
|
+
identity: undefined;
|
|
232
|
+
generated: undefined;
|
|
233
|
+
}, {}, {}>;
|
|
234
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
235
|
+
name: string;
|
|
236
|
+
tableName: "outbox";
|
|
237
|
+
dataType: "date";
|
|
238
|
+
columnType: "PgTimestamp";
|
|
239
|
+
data: Date;
|
|
240
|
+
driverParam: string;
|
|
241
|
+
notNull: true;
|
|
242
|
+
hasDefault: true;
|
|
243
|
+
isPrimaryKey: false;
|
|
244
|
+
isAutoincrement: false;
|
|
245
|
+
hasRuntimeDefault: false;
|
|
246
|
+
enumValues: undefined;
|
|
247
|
+
baseColumn: never;
|
|
248
|
+
identity: undefined;
|
|
249
|
+
generated: undefined;
|
|
250
|
+
}, {}, {}>;
|
|
251
|
+
tenantId: import("drizzle-orm/pg-core").PgColumn<{
|
|
252
|
+
name: "tenant_id";
|
|
253
|
+
tableName: "outbox";
|
|
254
|
+
dataType: "string";
|
|
255
|
+
columnType: "PgText";
|
|
256
|
+
data: string;
|
|
257
|
+
driverParam: string;
|
|
258
|
+
notNull: false;
|
|
259
|
+
hasDefault: false;
|
|
260
|
+
isPrimaryKey: false;
|
|
261
|
+
isAutoincrement: false;
|
|
262
|
+
hasRuntimeDefault: false;
|
|
263
|
+
enumValues: [string, ...string[]];
|
|
264
|
+
baseColumn: never;
|
|
265
|
+
identity: undefined;
|
|
266
|
+
generated: undefined;
|
|
267
|
+
}, {}, {}>;
|
|
268
|
+
};
|
|
269
|
+
dialect: 'pg';
|
|
270
|
+
}>;
|
|
271
|
+
//# sourceMappingURL=tenant-outbox-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-outbox-table.d.ts","sourceRoot":"","sources":["../../../src/postgres/outbox/tenant-outbox-table.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAIpD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { index, text } from 'drizzle-orm/pg-core';
|
|
3
|
+
import { tableIn } from '../schema/table-in.js';
|
|
4
|
+
import { outboxColumns } from './outbox-columns.js';
|
|
5
|
+
/**
|
|
6
|
+
* The outbox of a multi-tenant project: `outboxTable` and `tenant_id`, nullable, which
|
|
7
|
+
* `DrizzleOutboxStore` fills from the tenant of each event so that its consumers run in that
|
|
8
|
+
* tenant. A project that becomes multi-tenant swaps it in and generates the migration that adds
|
|
9
|
+
* the column.
|
|
10
|
+
*/
|
|
11
|
+
export function tenantOutboxTable(schemaName) {
|
|
12
|
+
return tableIn(schemaName)('outbox', { ...outboxColumns(), tenantId: text('tenant_id') }, (table) => [
|
|
13
|
+
index('outbox_pending_idx').on(table.nextAttemptAt, table.sequence).where(sql `${table.status} = 'pending'`),
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=tenant-outbox-table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-outbox-table.js","sourceRoot":"","sources":["../../../src/postgres/outbox/tenant-outbox-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAAmB;IACnD,OAAO,OAAO,CAAC,UAAU,CAAC,CAAC,QAAQ,EAAE,EAAE,GAAG,aAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC;QACnG,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,CAAA,GAAG,KAAK,CAAC,MAAM,cAAc,CAAC;KAC5G,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PgColumn, PgTable } from 'drizzle-orm/pg-core';
|
|
2
|
+
import type { ReadModelTenant } from './read-model-tenant.js';
|
|
2
3
|
export interface DrizzleReadModelOptions<Row> {
|
|
3
4
|
/** The table of the read model, declared with `...readModelColumns()`. */
|
|
4
5
|
readonly table: PgTable;
|
|
@@ -6,5 +7,13 @@ export interface DrizzleReadModelOptions<Row> {
|
|
|
6
7
|
readonly key: PgColumn;
|
|
7
8
|
/** The column of every field of the row: `{ startsAt: appointments.startsAt }`. */
|
|
8
9
|
readonly columns: Readonly<Record<keyof Row & string, PgColumn>>;
|
|
10
|
+
/**
|
|
11
|
+
* For a read model of tenant data: every row is written with the tenant of the work in
|
|
12
|
+
* progress, in `column`, and every read and write is confined to it, an elevation widening the
|
|
13
|
+
* reads only. Keys stay unique across tenants (the conflict of an insert is on the key alone):
|
|
14
|
+
* a key that two tenants could share (a day, a slug) is a generated id, or carries the tenant
|
|
15
|
+
* (`acme:2026-03-01`). Absent: one read model for every tenant.
|
|
16
|
+
*/
|
|
17
|
+
readonly tenant?: ReadModelTenant;
|
|
9
18
|
}
|
|
10
19
|
//# sourceMappingURL=drizzle-read-model-options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle-read-model-options.d.ts","sourceRoot":"","sources":["../../../src/postgres/read-model/drizzle-read-model-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"drizzle-read-model-options.d.ts","sourceRoot":"","sources":["../../../src/postgres/read-model/drizzle-read-model-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,WAAW,uBAAuB,CAAC,GAAG;IAC1C,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;IACvB,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACjE;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC;CACnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drizzle-read-model-table.d.ts","sourceRoot":"","sources":["../../../src/postgres/read-model/drizzle-read-model-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,0BAA0B,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACzG,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"drizzle-read-model-table.d.ts","sourceRoot":"","sources":["../../../src/postgres/read-model/drizzle-read-model-table.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,0BAA0B,EAAE,KAAK,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACzG,OAAO,EAAkB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAGzE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAM/E;;;;GAIG;AACH,qBAAa,qBAAqB,CAChC,GAAG,SAAS,MAAM,EAClB,CAAC,SAAS,gBAAgB,EAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EACzD,CAAC,SAAS,sBAAsB,GAAG,0BAA0B,CAAC,CAAC,CAAC,CAChE,YAAW,cAAc,CAAC,GAAG,CAAC;IAW5B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IAEtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmC;IAExD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmC;IAExD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IAEnD,YACmB,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAChC,OAAO,EAAE,uBAAuB,CAAC,GAAG,CAAC,EAoBvD;IAED,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,CAEtD;IAED,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAE/C;IAED,OAAO,CAAC,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAElE;IAEK,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAmB3D;IAED,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAE7B;IAED,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhC;IAED,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAE5B;IAED,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,CAE3B;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAErD;IAED,MAAM,CAAC,IAAI,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAEjD;IAED,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;IAED,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAEvB;IAED,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAE9B;IAED,gHAAgH;YAClG,GAAG;CAelB"}
|