@ddd-ts-kit/prisma 0.0.0-stage → 0.1.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.
Files changed (118) hide show
  1. package/README.md +629 -2
  2. package/dist/index.d.ts +20 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +22 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/outbox/locked-id-of.d.ts +2 -0
  7. package/dist/outbox/locked-id-of.d.ts.map +1 -0
  8. package/dist/outbox/locked-id-of.js +7 -0
  9. package/dist/outbox/locked-id-of.js.map +1 -0
  10. package/dist/outbox/locking-select.d.ts +4 -0
  11. package/dist/outbox/locking-select.d.ts.map +1 -0
  12. package/dist/outbox/locking-select.js +20 -0
  13. package/dist/outbox/locking-select.js.map +1 -0
  14. package/dist/outbox/payload-field.d.ts +9 -0
  15. package/dist/outbox/payload-field.d.ts.map +1 -0
  16. package/dist/outbox/payload-field.js +12 -0
  17. package/dist/outbox/payload-field.js.map +1 -0
  18. package/dist/outbox/prisma-json-input.d.ts +5 -0
  19. package/dist/outbox/prisma-json-input.d.ts.map +1 -0
  20. package/dist/outbox/prisma-json-input.js +2 -0
  21. package/dist/outbox/prisma-json-input.js.map +1 -0
  22. package/dist/outbox/prisma-outbox-claim.d.ts +25 -0
  23. package/dist/outbox/prisma-outbox-claim.d.ts.map +1 -0
  24. package/dist/outbox/prisma-outbox-claim.js +2 -0
  25. package/dist/outbox/prisma-outbox-claim.js.map +1 -0
  26. package/dist/outbox/prisma-outbox-delegate.d.ts +87 -0
  27. package/dist/outbox/prisma-outbox-delegate.d.ts.map +1 -0
  28. package/dist/outbox/prisma-outbox-delegate.js +2 -0
  29. package/dist/outbox/prisma-outbox-delegate.js.map +1 -0
  30. package/dist/outbox/prisma-outbox-store.d.ts +54 -0
  31. package/dist/outbox/prisma-outbox-store.d.ts.map +1 -0
  32. package/dist/outbox/prisma-outbox-store.js +150 -0
  33. package/dist/outbox/prisma-outbox-store.js.map +1 -0
  34. package/dist/outbox/prisma-processed-event-delegate.d.ts +17 -0
  35. package/dist/outbox/prisma-processed-event-delegate.d.ts.map +1 -0
  36. package/dist/outbox/prisma-processed-event-delegate.js +2 -0
  37. package/dist/outbox/prisma-processed-event-delegate.js.map +1 -0
  38. package/dist/outbox/prisma-processed-event-store.d.ts +19 -0
  39. package/dist/outbox/prisma-processed-event-store.d.ts.map +1 -0
  40. package/dist/outbox/prisma-processed-event-store.js +23 -0
  41. package/dist/outbox/prisma-processed-event-store.js.map +1 -0
  42. package/dist/outbox/prisma-raw-queries.d.ts +5 -0
  43. package/dist/outbox/prisma-raw-queries.d.ts.map +1 -0
  44. package/dist/outbox/prisma-raw-queries.js +2 -0
  45. package/dist/outbox/prisma-raw-queries.js.map +1 -0
  46. package/dist/outbox/to-outbox-record.d.ts +4 -0
  47. package/dist/outbox/to-outbox-record.d.ts.map +1 -0
  48. package/dist/outbox/to-outbox-record.js +25 -0
  49. package/dist/outbox/to-outbox-record.js.map +1 -0
  50. package/dist/read-model/criteria-to-prisma.d.ts +10 -0
  51. package/dist/read-model/criteria-to-prisma.d.ts.map +1 -0
  52. package/dist/read-model/criteria-to-prisma.js +23 -0
  53. package/dist/read-model/criteria-to-prisma.js.map +1 -0
  54. package/dist/read-model/predicate-to-prisma.d.ts +11 -0
  55. package/dist/read-model/predicate-to-prisma.d.ts.map +1 -0
  56. package/dist/read-model/predicate-to-prisma.js +41 -0
  57. package/dist/read-model/predicate-to-prisma.js.map +1 -0
  58. package/dist/read-model/prisma-criteria.d.ts +13 -0
  59. package/dist/read-model/prisma-criteria.d.ts.map +1 -0
  60. package/dist/read-model/prisma-criteria.js +2 -0
  61. package/dist/read-model/prisma-criteria.js.map +1 -0
  62. package/dist/read-model/prisma-model-rows.d.ts +17 -0
  63. package/dist/read-model/prisma-model-rows.d.ts.map +1 -0
  64. package/dist/read-model/prisma-model-rows.js +50 -0
  65. package/dist/read-model/prisma-model-rows.js.map +1 -0
  66. package/dist/read-model/prisma-read-model-delegate.d.ts +21 -0
  67. package/dist/read-model/prisma-read-model-delegate.d.ts.map +1 -0
  68. package/dist/read-model/prisma-read-model-delegate.js +2 -0
  69. package/dist/read-model/prisma-read-model-delegate.js.map +1 -0
  70. package/dist/read-model/prisma-read-model-options.d.ts +13 -0
  71. package/dist/read-model/prisma-read-model-options.d.ts.map +1 -0
  72. package/dist/read-model/prisma-read-model-options.js +2 -0
  73. package/dist/read-model/prisma-read-model-options.js.map +1 -0
  74. package/dist/read-model/prisma-read-model-store.d.ts +17 -0
  75. package/dist/read-model/prisma-read-model-store.d.ts.map +1 -0
  76. package/dist/read-model/prisma-read-model-store.js +13 -0
  77. package/dist/read-model/prisma-read-model-store.js.map +1 -0
  78. package/dist/read-model/prisma-read-model-table.d.ts +32 -0
  79. package/dist/read-model/prisma-read-model-table.d.ts.map +1 -0
  80. package/dist/read-model/prisma-read-model-table.js +84 -0
  81. package/dist/read-model/prisma-read-model-table.js.map +1 -0
  82. package/dist/read-model/prisma-table-rows.d.ts +27 -0
  83. package/dist/read-model/prisma-table-rows.d.ts.map +1 -0
  84. package/dist/read-model/prisma-table-rows.js +59 -0
  85. package/dist/read-model/prisma-table-rows.js.map +1 -0
  86. package/dist/read-model/row.mapper.d.ts +17 -0
  87. package/dist/read-model/row.mapper.d.ts.map +1 -0
  88. package/dist/read-model/row.mapper.js +58 -0
  89. package/dist/read-model/row.mapper.js.map +1 -0
  90. package/dist/read-model/stamped-row-of.d.ts +11 -0
  91. package/dist/read-model/stamped-row-of.d.ts.map +1 -0
  92. package/dist/read-model/stamped-row-of.js +13 -0
  93. package/dist/read-model/stamped-row-of.js.map +1 -0
  94. package/dist/read-model/written-of.d.ts +4 -0
  95. package/dist/read-model/written-of.d.ts.map +1 -0
  96. package/dist/read-model/written-of.js +12 -0
  97. package/dist/read-model/written-of.js.map +1 -0
  98. package/dist/unit-of-work/create-prisma-unit-of-work.d.ts +18 -0
  99. package/dist/unit-of-work/create-prisma-unit-of-work.d.ts.map +1 -0
  100. package/dist/unit-of-work/create-prisma-unit-of-work.js +30 -0
  101. package/dist/unit-of-work/create-prisma-unit-of-work.js.map +1 -0
  102. package/dist/unit-of-work/prisma-context.d.ts +15 -0
  103. package/dist/unit-of-work/prisma-context.d.ts.map +1 -0
  104. package/dist/unit-of-work/prisma-context.js +14 -0
  105. package/dist/unit-of-work/prisma-context.js.map +1 -0
  106. package/dist/unit-of-work/prisma-transaction-client.d.ts +8 -0
  107. package/dist/unit-of-work/prisma-transaction-client.d.ts.map +1 -0
  108. package/dist/unit-of-work/prisma-transaction-client.js +2 -0
  109. package/dist/unit-of-work/prisma-transaction-client.js.map +1 -0
  110. package/dist/unit-of-work/prisma-transaction-host.d.ts +9 -0
  111. package/dist/unit-of-work/prisma-transaction-host.d.ts.map +1 -0
  112. package/dist/unit-of-work/prisma-transaction-host.js +2 -0
  113. package/dist/unit-of-work/prisma-transaction-host.js.map +1 -0
  114. package/package.json +44 -4
  115. package/prisma/outbox.mysql.prisma +40 -0
  116. package/prisma/outbox.postgresql.prisma +43 -0
  117. package/prisma/outbox.sqlite.prisma +42 -0
  118. package/prisma/read-model.postgresql.prisma +26 -0
@@ -0,0 +1,58 @@
1
+ const STAMP_COLUMNS = {
2
+ lastEventId: 'last_event_id',
3
+ lastEventAt: 'last_event_at',
4
+ lastEventVersion: 'last_event_version',
5
+ deleted: 'deleted',
6
+ };
7
+ /**
8
+ * Translates between the row of a read model and what Prisma holds: the model, keyed by
9
+ * field, and the table, keyed by column, which is how the copy of a rebuild is reached since
10
+ * it has no model. A `Decimal` reads back as a string, a `BigInt` as a number.
11
+ */
12
+ export class RowMapper {
13
+ columns;
14
+ constructor(columns) {
15
+ this.columns = columns;
16
+ }
17
+ /** From what the model answered, by field. */
18
+ toRow(found) {
19
+ return Object.fromEntries(Object.keys(this.columns).map((field) => [field, plain(Reflect.get(found, field))]));
20
+ }
21
+ /** From a record of the table, by column. */
22
+ fromRecord(record) {
23
+ return Object.fromEntries(Object.entries(this.columns).map(([field, column]) => [field, plain(record[column])]));
24
+ }
25
+ /** What the model wrote by field, by column: the fields of the row and the four stamps. */
26
+ toRecord(written) {
27
+ const columns = { ...this.columns, ...STAMP_COLUMNS };
28
+ return Object.fromEntries(Object.entries(written).map(([field, value]) => [
29
+ columns[field] ?? field,
30
+ typeof value === 'bigint' ? Number(value) : value,
31
+ ]));
32
+ }
33
+ columnOf(field) {
34
+ const column = Object.hasOwn(this.columns, field) ? this.columns[field] : undefined;
35
+ if (column === undefined) {
36
+ throw new Error(`The field "${field}" is mapped to no column`);
37
+ }
38
+ return column;
39
+ }
40
+ }
41
+ function plain(value) {
42
+ if (value === null || value === undefined) {
43
+ return null;
44
+ }
45
+ if (typeof value === 'bigint') {
46
+ return Number(value);
47
+ }
48
+ return isDecimal(value) ? value.toFixed() : value;
49
+ }
50
+ /** A `Decimal` of Prisma, read as digits without an exponent. */
51
+ function isDecimal(value) {
52
+ return (typeof value === 'object' &&
53
+ value !== null &&
54
+ !(value instanceof Date) &&
55
+ 'toFixed' in value &&
56
+ typeof value.toFixed === 'function');
57
+ }
58
+ //# sourceMappingURL=row.mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"row.mapper.js","sourceRoot":"","sources":["../../src/read-model/row.mapper.ts"],"names":[],"mappings":"AAAA,MAAM,aAAa,GAAG;IACpB,WAAW,EAAE,eAAe;IAC5B,WAAW,EAAE,eAAe;IAC5B,gBAAgB,EAAE,oBAAoB;IACtC,OAAO,EAAE,SAAS;CACV,CAAC;AAEX;;;;GAIG;AACH,MAAM,OAAO,SAAS;IACS,OAAO;IAApC,YAA6B,OAAyC;uBAAzC,OAAO;IAAqC,CAAC;IAE1E,8CAA8C;IAC9C,KAAK,CAAC,KAAa;QACjB,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAC7E,CAAC;IACX,CAAC;IAED,6CAA6C;IAC7C,UAAU,CAAC,MAAyC;QAClD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAC/E,CAAC;IACX,CAAC;IAED,2FAA2F;IAC3F,QAAQ,CAAC,OAA0C;QACjD,MAAM,OAAO,GAAqC,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,GAAG,aAAa,EAAE,CAAC;QAExF,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YAC9C,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK;YACvB,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK;SAClD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEpF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CAAC,cAAc,KAAK,0BAA0B,CAAC,CAAC;QACjE,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAED,SAAS,KAAK,CAAC,KAAc;IAC3B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,iEAAiE;AACjE,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC;QACxB,SAAS,IAAI,KAAK;QAClB,OAAO,KAAK,CAAC,OAAO,KAAK,UAAU,CACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { StampedRow } from '@ddd-ts-kit/kernel';
2
+ interface Read<Row> {
3
+ readonly key: string;
4
+ /** What holds the stamp: the model by field, or the record by column. */
5
+ readonly stamps: readonly [eventId: unknown, occurredAt: unknown, version: unknown, deleted: unknown];
6
+ readonly row: () => Row;
7
+ }
8
+ /** The stamped row of what was read from the model or from the table. */
9
+ export declare function stampedRowOf<Row>({ key, stamps: [eventId, occurredAt, version, deleted], row, }: Read<Row>): StampedRow<Row>;
10
+ export {};
11
+ //# sourceMappingURL=stamped-row-of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamped-row-of.d.ts","sourceRoot":"","sources":["../../src/read-model/stamped-row-of.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,UAAU,IAAI,CAAC,GAAG;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACtG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC;CACzB;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,GAAG,EAAE,EAChC,GAAG,EACH,MAAM,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,EAC/C,GAAG,GACJ,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAU7B"}
@@ -0,0 +1,13 @@
1
+ /** The stamped row of what was read from the model or from the table. */
2
+ export function stampedRowOf({ key, stamps: [eventId, occurredAt, version, deleted], row, }) {
3
+ return {
4
+ key,
5
+ row: deleted === true ? undefined : row(),
6
+ stamp: {
7
+ eventId: String(eventId),
8
+ occurredAt: occurredAt instanceof Date ? occurredAt : new Date(String(occurredAt)),
9
+ ...(version === null || version === undefined ? {} : { version: Number(version) }),
10
+ },
11
+ };
12
+ }
13
+ //# sourceMappingURL=stamped-row-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stamped-row-of.js","sourceRoot":"","sources":["../../src/read-model/stamped-row-of.ts"],"names":[],"mappings":"AASA,yEAAyE;AACzE,MAAM,UAAU,YAAY,CAAM,EAChC,GAAG,EACH,MAAM,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,CAAC,EAC/C,GAAG,GACO;IACV,OAAO;QACL,GAAG;QACH,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE;QACzC,KAAK,EAAE;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;YACxB,UAAU,EAAE,UAAU,YAAY,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAClF,GAAG,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;SACnF;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { StampedRow } from '@ddd-ts-kit/kernel';
2
+ /** What a stamped row writes, by field. A removed row keeps its fields and changes its stamp only. */
3
+ export declare function writtenOf<Row extends object>({ key, row, stamp }: StampedRow<Row>, keyField: string): Record<string, unknown>;
4
+ //# sourceMappingURL=written-of.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"written-of.d.ts","sourceRoot":"","sources":["../../src/read-model/written-of.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,sGAAsG;AACtG,wBAAgB,SAAS,CAAC,GAAG,SAAS,MAAM,EAC1C,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,UAAU,CAAC,GAAG,CAAC,EACpC,QAAQ,EAAE,MAAM,GACf,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB"}
@@ -0,0 +1,12 @@
1
+ /** What a stamped row writes, by field. A removed row keeps its fields and changes its stamp only. */
2
+ export function writtenOf({ key, row, stamp }, keyField) {
3
+ return {
4
+ ...row,
5
+ [keyField]: key,
6
+ lastEventId: stamp.eventId,
7
+ lastEventAt: stamp.occurredAt,
8
+ lastEventVersion: stamp.version === undefined ? null : BigInt(stamp.version),
9
+ deleted: row === undefined,
10
+ };
11
+ }
12
+ //# sourceMappingURL=written-of.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"written-of.js","sourceRoot":"","sources":["../../src/read-model/written-of.ts"],"names":[],"mappings":"AAEA,sGAAsG;AACtG,MAAM,UAAU,SAAS,CACvB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAmB,EACpC,QAAgB;IAEhB,OAAO;QACL,GAAG,GAAG;QACN,CAAC,QAAQ,CAAC,EAAE,GAAG;QACf,WAAW,EAAE,KAAK,CAAC,OAAO;QAC1B,WAAW,EAAE,KAAK,CAAC,UAAU;QAC7B,gBAAgB,EAAE,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5E,OAAO,EAAE,GAAG,KAAK,SAAS;KAC3B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { type UnitOfWork } from '@ddd-ts-kit/kernel';
2
+ import type { PrismaContext } from './prisma-context.js';
3
+ import type { PrismaTransactionHost } from './prisma-transaction-host.js';
4
+ /** One object for both ports: bind it to `UnitOfWork` and to the `PrismaContext` of the project. */
5
+ export interface PrismaUnitOfWork<Tx> extends UnitOfWork, PrismaContext<Tx> {
6
+ }
7
+ /**
8
+ * A unit of work over the interactive `$transaction` of Prisma. The transaction client travels
9
+ * in an AsyncLocalStorage owned by this unit of work, so two clients never see each other's
10
+ * transaction. A nested `run` joins the outer transaction: it commits or rolls back as a whole.
11
+ * Domain events are dispatched at the end of the outermost `run`, before its commit
12
+ * (`withinUnitOfWork`): their handlers run inside the time Prisma allows the transaction.
13
+ *
14
+ * Prisma bounds an interactive transaction in time (`timeout`, 5 seconds by default) and
15
+ * rolls it back beyond: pass `options` to widen it for long commands.
16
+ */
17
+ export declare function createPrismaUnitOfWork<Tx, Options>(client: PrismaTransactionHost<Tx, Options> & NoInfer<Tx>, options?: Options): PrismaUnitOfWork<Tx>;
18
+ //# sourceMappingURL=create-prisma-unit-of-work.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-prisma-unit-of-work.d.ts","sourceRoot":"","sources":["../../src/unit-of-work/create-prisma-unit-of-work.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,oGAAoG;AACpG,MAAM,WAAW,gBAAgB,CAAC,EAAE,CAAE,SAAQ,UAAU,EAAE,aAAa,CAAC,EAAE,CAAC;CAAG;AAE9E;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,OAAO,EAEhD,MAAM,EAAE,qBAAqB,CAAC,EAAE,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,EACxD,OAAO,CAAC,EAAE,OAAO,GAChB,gBAAgB,CAAC,EAAE,CAAC,CAgBtB"}
@@ -0,0 +1,30 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { withinUnitOfWork } from '@ddd-ts-kit/kernel';
3
+ /**
4
+ * A unit of work over the interactive `$transaction` of Prisma. The transaction client travels
5
+ * in an AsyncLocalStorage owned by this unit of work, so two clients never see each other's
6
+ * transaction. A nested `run` joins the outer transaction: it commits or rolls back as a whole.
7
+ * Domain events are dispatched at the end of the outermost `run`, before its commit
8
+ * (`withinUnitOfWork`): their handlers run inside the time Prisma allows the transaction.
9
+ *
10
+ * Prisma bounds an interactive transaction in time (`timeout`, 5 seconds by default) and
11
+ * rolls it back beyond: pass `options` to widen it for long commands.
12
+ */
13
+ export function createPrismaUnitOfWork(
14
+ // NoInfer: `Tx` is what `$transaction` hands out, and the root client must offer at least that.
15
+ client, options) {
16
+ const current = new AsyncLocalStorage();
17
+ return {
18
+ run(work) {
19
+ return withinUnitOfWork(work, (scoped) => {
20
+ if (current.getStore() !== undefined) {
21
+ return scoped();
22
+ }
23
+ return client.$transaction((transaction) => current.run({ transaction }, scoped), options);
24
+ });
25
+ },
26
+ client: () => current.getStore()?.transaction ?? client,
27
+ inTransaction: () => current.getStore() !== undefined,
28
+ };
29
+ }
30
+ //# sourceMappingURL=create-prisma-unit-of-work.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-prisma-unit-of-work.js","sourceRoot":"","sources":["../../src/unit-of-work/create-prisma-unit-of-work.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAmB,MAAM,oBAAoB,CAAC;AAOvE;;;;;;;;;GASG;AACH,MAAM,UAAU,sBAAsB;AACpC,gGAAgG;AAChG,MAAwD,EACxD,OAAiB;IAEjB,MAAM,OAAO,GAAG,IAAI,iBAAiB,EAAgC,CAAC;IAEtE,OAAO;QACL,GAAG,CAAI,IAAsB;YAC3B,OAAO,gBAAgB,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE;gBACvC,IAAI,OAAO,CAAC,QAAQ,EAAE,KAAK,SAAS,EAAE,CAAC;oBACrC,OAAO,MAAM,EAAE,CAAC;gBAClB,CAAC;gBAED,OAAO,MAAM,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;YAC7F,CAAC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,WAAW,IAAI,MAAM;QACvD,aAAa,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,SAAS;KACtD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Gives adapters the right client to run their queries on: the transaction client of the
3
+ * current unit of work when there is one, the root client otherwise. Repositories depend on
4
+ * this and never receive a transaction as a parameter. The root client offers everything a
5
+ * transaction client does, hence the single return type.
6
+ *
7
+ * Generic over the transaction client of the generated client, so it is not an injection token
8
+ * by itself: the project names its database with a class of its own,
9
+ * `export abstract class Database extends PrismaContext<Prisma.TransactionClient> {}`.
10
+ */
11
+ export declare abstract class PrismaContext<Tx> {
12
+ abstract client(): Tx;
13
+ abstract inTransaction(): boolean;
14
+ }
15
+ //# sourceMappingURL=prisma-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-context.d.ts","sourceRoot":"","sources":["../../src/unit-of-work/prisma-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,8BAAsB,aAAa,CAAC,EAAE;IACpC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;IAEtB,QAAQ,CAAC,aAAa,IAAI,OAAO,CAAC;CACnC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Gives adapters the right client to run their queries on: the transaction client of the
3
+ * current unit of work when there is one, the root client otherwise. Repositories depend on
4
+ * this and never receive a transaction as a parameter. The root client offers everything a
5
+ * transaction client does, hence the single return type.
6
+ *
7
+ * Generic over the transaction client of the generated client, so it is not an injection token
8
+ * by itself: the project names its database with a class of its own,
9
+ * `export abstract class Database extends PrismaContext<Prisma.TransactionClient> {}`.
10
+ */
11
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- the parameter is what a project fixes when it extends this class with its own transaction client
12
+ export class PrismaContext {
13
+ }
14
+ //# sourceMappingURL=prisma-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-context.js","sourceRoot":"","sources":["../../src/unit-of-work/prisma-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,iLAAiL;AACjL,MAAM,OAAgB,aAAa;CAIlC"}
@@ -0,0 +1,8 @@
1
+ import type { PrismaTransactionHost } from './prisma-transaction-host.js';
2
+ /**
3
+ * The transaction client of a client, for typing a context without naming the generated
4
+ * type: `PrismaContext<PrismaTransactionClient<typeof prisma>>`. For a plain client it is
5
+ * `Prisma.TransactionClient`; an extended client (`$extends`) has a type of its own.
6
+ */
7
+ export type PrismaTransactionClient<Client> = Client extends PrismaTransactionHost<infer Tx, never> ? Tx : never;
8
+ //# sourceMappingURL=prisma-transaction-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-transaction-client.d.ts","sourceRoot":"","sources":["../../src/unit-of-work/prisma-transaction-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;GAIG;AACH,MAAM,MAAM,uBAAuB,CAAC,MAAM,IAAI,MAAM,SAAS,qBAAqB,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prisma-transaction-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-transaction-client.js","sourceRoot":"","sources":["../../src/unit-of-work/prisma-transaction-client.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The part of a Prisma client the unit of work calls: the interactive form of `$transaction`.
3
+ * Typed structurally, so the package depends on no generated client. `Tx` is the transaction
4
+ * client of your own client (`Prisma.TransactionClient`), `Options` its transaction options.
5
+ */
6
+ export interface PrismaTransactionHost<Tx, Options = never> {
7
+ $transaction<R>(work: (transaction: Tx) => Promise<R>, options?: Options): Promise<R>;
8
+ }
9
+ //# sourceMappingURL=prisma-transaction-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-transaction-host.d.ts","sourceRoot":"","sources":["../../src/unit-of-work/prisma-transaction-host.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,qBAAqB,CAAC,EAAE,EAAE,OAAO,GAAG,KAAK;IACxD,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACvF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prisma-transaction-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prisma-transaction-host.js","sourceRoot":"","sources":["../../src/unit-of-work/prisma-transaction-host.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@ddd-ts-kit/prisma",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Prisma adapters for the kernel ports: unit of work, transactional outbox store, processed-event store.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/gbourgeat/ddd-ts-kit.git",
9
+ "directory": "packages/prisma"
10
+ },
11
+ "homepage": "https://github.com/gbourgeat/ddd-ts-kit/tree/main/packages/prisma#readme",
12
+ "bugs": "https://github.com/gbourgeat/ddd-ts-kit/issues",
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "provenance": true
16
+ },
17
+ "type": "module",
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ }
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "prisma"
28
+ ],
29
+ "dependencies": {
30
+ "@ddd-ts-kit/kernel": "0.1.0",
31
+ "@ddd-ts-kit/events": "0.1.0"
32
+ },
33
+ "peerDependencies": {
34
+ "@prisma/client": "^7.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@prisma/client": "^7.10.0",
38
+ "prisma": "^7.10.0",
39
+ "@prisma/adapter-pg": "^7.10.0",
40
+ "@ddd-ts-kit/testing": "0.1.0",
41
+ "pg": "^8.23.1",
42
+ "@types/pg": "^8.23.1",
43
+ "@electric-sql/pglite": "^0.5.8",
44
+ "@electric-sql/pglite-socket": "^0.2.11"
45
+ }
46
+ }
@@ -0,0 +1,40 @@
1
+ // Models of @ddd-ts-kit/prisma for MySQL 8. Copy them into your schema.prisma; your own
2
+ // `prisma migrate dev` then creates the tables in a migration of your project.
3
+ //
4
+ // Not covered by the tests of the package: the schema is validated by Prisma, the stores are
5
+ // exercised on PostgreSQL only.
6
+ //
7
+ // Keep the field names: the stores of the package are typed against them.
8
+
9
+ model OutboxRecord {
10
+ id String @id @db.VarChar(191)
11
+ /// Insertion order, the tie-breaker of the claim. MySQL requires it to be a key.
12
+ sequence BigInt @unique @default(autoincrement())
13
+ type String @db.Text
14
+ version Int
15
+ /// Text, not a timestamp: the envelope must come back exactly as published, offset included.
16
+ occurredAt String @map("occurred_at") @db.VarChar(64)
17
+ /// Optional because JSON `null` is a legal payload and is stored as SQL NULL.
18
+ payload Json?
19
+ traceparent String? @db.Text
20
+ correlationId String? @map("correlation_id") @db.Text
21
+ status String @default("pending") @db.VarChar(16)
22
+ attempts Int @default(0)
23
+ nextAttemptAt DateTime @map("next_attempt_at") @db.DateTime(3)
24
+ lastErrorCode String? @map("last_error_code") @db.Text
25
+ publishedAt DateTime? @map("published_at") @db.DateTime(3)
26
+ createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
27
+
28
+ @@index([status, nextAttemptAt, sequence])
29
+ @@map("outbox")
30
+ }
31
+
32
+ model ProcessedEvent {
33
+ consumer String @db.VarChar(191)
34
+ eventId String @map("event_id") @db.VarChar(191)
35
+ processedAt DateTime @default(now()) @map("processed_at") @db.DateTime(3)
36
+
37
+ /// The unique (consumer, event_id) the store relies on.
38
+ @@id([consumer, eventId])
39
+ @@map("processed_events")
40
+ }
@@ -0,0 +1,43 @@
1
+ // Models of @ddd-ts-kit/prisma for PostgreSQL. Copy them into your schema.prisma; your own
2
+ // `prisma migrate dev` then creates the tables in a migration of your project.
3
+ //
4
+ // One schema per bounded context: list the schemas in the datasource (`schemas = [...]`), add
5
+ // `@@schema("<context>")` to each model, and give each copy a model name of its own
6
+ // (`BillingOutboxRecord`, ...) when several contexts keep their own outbox.
7
+ //
8
+ // Keep the field names: the stores of the package are typed against them. Table and column
9
+ // names (`@@map`, `@map`) are yours to change, as long as the `skipLocked` option of the outbox
10
+ // store is told the table name.
11
+
12
+ model OutboxRecord {
13
+ id String @id
14
+ /// Insertion order, the tie-breaker of the claim.
15
+ sequence BigInt @unique @default(autoincrement())
16
+ type String
17
+ version Int
18
+ /// Text, not a timestamp: the envelope must come back exactly as published, offset included.
19
+ occurredAt String @map("occurred_at")
20
+ /// Optional because JSON `null` is a legal payload and is stored as SQL NULL.
21
+ payload Json?
22
+ traceparent String?
23
+ correlationId String? @map("correlation_id")
24
+ status String @default("pending")
25
+ attempts Int @default(0)
26
+ nextAttemptAt DateTime @map("next_attempt_at") @db.Timestamptz(3)
27
+ lastErrorCode String? @map("last_error_code")
28
+ publishedAt DateTime? @map("published_at") @db.Timestamptz(3)
29
+ createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
30
+
31
+ @@index([status, nextAttemptAt, sequence])
32
+ @@map("outbox")
33
+ }
34
+
35
+ model ProcessedEvent {
36
+ consumer String
37
+ eventId String @map("event_id")
38
+ processedAt DateTime @default(now()) @map("processed_at") @db.Timestamptz(3)
39
+
40
+ /// The unique (consumer, event_id) the store relies on.
41
+ @@id([consumer, eventId])
42
+ @@map("processed_events")
43
+ }
@@ -0,0 +1,42 @@
1
+ // Models of @ddd-ts-kit/prisma for SQLite. Copy them into your schema.prisma; your own
2
+ // `prisma migrate dev` then creates the tables in a migration of your project.
3
+ //
4
+ // Not covered by the tests of the package: the schema is validated by Prisma, the stores are
5
+ // exercised on PostgreSQL only. SQLite has no `FOR UPDATE SKIP LOCKED` (claim with
6
+ // `compare-and-set`) and Prisma has no `createMany({ skipDuplicates })` there (see the README
7
+ // for the processed-event store).
8
+ //
9
+ // Keep the field names: the stores of the package are typed against them.
10
+
11
+ model OutboxRecord {
12
+ /// SQLite only auto-increments an integer primary key, so the roles are swapped here.
13
+ sequence Int @id @default(autoincrement())
14
+ id String @unique
15
+ type String
16
+ version Int
17
+ /// Text, not a timestamp: the envelope must come back exactly as published, offset included.
18
+ occurredAt String @map("occurred_at")
19
+ /// Optional because JSON `null` is a legal payload and is stored as SQL NULL.
20
+ payload Json?
21
+ traceparent String?
22
+ correlationId String? @map("correlation_id")
23
+ status String @default("pending")
24
+ attempts Int @default(0)
25
+ nextAttemptAt DateTime @map("next_attempt_at")
26
+ lastErrorCode String? @map("last_error_code")
27
+ publishedAt DateTime? @map("published_at")
28
+ createdAt DateTime @default(now()) @map("created_at")
29
+
30
+ @@index([status, nextAttemptAt, sequence])
31
+ @@map("outbox")
32
+ }
33
+
34
+ model ProcessedEvent {
35
+ consumer String
36
+ eventId String @map("event_id")
37
+ processedAt DateTime @default(now()) @map("processed_at")
38
+
39
+ /// The unique (consumer, event_id) the store relies on.
40
+ @@id([consumer, eventId])
41
+ @@map("processed_events")
42
+ }
@@ -0,0 +1,26 @@
1
+ // The model of a read model of @ddd-ts-kit/prisma for PostgreSQL: a template. Copy it into
2
+ // your schema.prisma, name it after your read model, replace the example fields by the fields
3
+ // of your row, and keep the four last fields as they are: `PrismaReadModelStore` is typed
4
+ // against their names, and the rebuild of a projection against their columns.
5
+ //
6
+ // A text field that is sorted or compared by range follows the collation of its column, and
7
+ // criteria order strings by code point: add `COLLATE "C"` to those columns in the migration
8
+ // (`ALTER TABLE ... ALTER COLUMN room TYPE text COLLATE "C"`), which the schema cannot say.
9
+ //
10
+ // A unique index of your own leaves the deleted rows out: `WHERE NOT deleted`.
11
+
12
+ model AppointmentRow {
13
+ id String @id
14
+ status String
15
+ startsAt DateTime @map("starts_at") @db.Timestamptz(3)
16
+ /// The event that last changed the row: an older one changes nothing.
17
+ lastEventId String @map("last_event_id")
18
+ lastEventAt DateTime @map("last_event_at") @db.Timestamptz(3)
19
+ /// The per-key version of that event, when the upstream context gives one.
20
+ lastEventVersion BigInt? @map("last_event_version")
21
+ /// The trace of a removed row, so that a late event does not bring it back.
22
+ deleted Boolean @default(false)
23
+
24
+ @@map("appointment_list")
25
+ @@schema("scheduling")
26
+ }