@xfcfam/xf-sql 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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/index.d.ts +26 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +27 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/api/A.d.ts +17 -0
  8. package/dist/src/api/A.d.ts.map +1 -0
  9. package/dist/src/api/A.js +17 -0
  10. package/dist/src/api/A.js.map +1 -0
  11. package/dist/src/business/B.d.ts +17 -0
  12. package/dist/src/business/B.d.ts.map +1 -0
  13. package/dist/src/business/B.js +17 -0
  14. package/dist/src/business/B.js.map +1 -0
  15. package/dist/src/repository/R.d.ts +17 -0
  16. package/dist/src/repository/R.d.ts.map +1 -0
  17. package/dist/src/repository/R.js +17 -0
  18. package/dist/src/repository/R.js.map +1 -0
  19. package/dist/src/repository/base/DatabaseRepository.d.ts +184 -0
  20. package/dist/src/repository/base/DatabaseRepository.d.ts.map +1 -0
  21. package/dist/src/repository/base/DatabaseRepository.js +216 -0
  22. package/dist/src/repository/base/DatabaseRepository.js.map +1 -0
  23. package/dist/src/repository/base/TransactionalDatabaseRepository.d.ts +86 -0
  24. package/dist/src/repository/base/TransactionalDatabaseRepository.d.ts.map +1 -0
  25. package/dist/src/repository/base/TransactionalDatabaseRepository.js +104 -0
  26. package/dist/src/repository/base/TransactionalDatabaseRepository.js.map +1 -0
  27. package/dist/src/repository/general/DatabaseRepository.d.ts +192 -0
  28. package/dist/src/repository/general/DatabaseRepository.d.ts.map +1 -0
  29. package/dist/src/repository/general/DatabaseRepository.js +224 -0
  30. package/dist/src/repository/general/DatabaseRepository.js.map +1 -0
  31. package/dist/src/repository/general/TransactionalDatabaseRepository.d.ts +91 -0
  32. package/dist/src/repository/general/TransactionalDatabaseRepository.d.ts.map +1 -0
  33. package/dist/src/repository/general/TransactionalDatabaseRepository.js +114 -0
  34. package/dist/src/repository/general/TransactionalDatabaseRepository.js.map +1 -0
  35. package/dist/src/repository/structs/CheckViolationException.d.ts +19 -0
  36. package/dist/src/repository/structs/CheckViolationException.d.ts.map +1 -0
  37. package/dist/src/repository/structs/CheckViolationException.js +22 -0
  38. package/dist/src/repository/structs/CheckViolationException.js.map +1 -0
  39. package/dist/src/repository/structs/ConnectionException.d.ts +15 -0
  40. package/dist/src/repository/structs/ConnectionException.d.ts.map +1 -0
  41. package/dist/src/repository/structs/ConnectionException.js +16 -0
  42. package/dist/src/repository/structs/ConnectionException.js.map +1 -0
  43. package/dist/src/repository/structs/DatabaseException.d.ts +14 -0
  44. package/dist/src/repository/structs/DatabaseException.d.ts.map +1 -0
  45. package/dist/src/repository/structs/DatabaseException.js +15 -0
  46. package/dist/src/repository/structs/DatabaseException.js.map +1 -0
  47. package/dist/src/repository/structs/DeadlockException.d.ts +16 -0
  48. package/dist/src/repository/structs/DeadlockException.d.ts.map +1 -0
  49. package/dist/src/repository/structs/DeadlockException.js +17 -0
  50. package/dist/src/repository/structs/DeadlockException.js.map +1 -0
  51. package/dist/src/repository/structs/ForeignKeyViolationException.d.ts +20 -0
  52. package/dist/src/repository/structs/ForeignKeyViolationException.d.ts.map +1 -0
  53. package/dist/src/repository/structs/ForeignKeyViolationException.js +23 -0
  54. package/dist/src/repository/structs/ForeignKeyViolationException.js.map +1 -0
  55. package/dist/src/repository/structs/NotNullViolationException.d.ts +20 -0
  56. package/dist/src/repository/structs/NotNullViolationException.d.ts.map +1 -0
  57. package/dist/src/repository/structs/NotNullViolationException.js +23 -0
  58. package/dist/src/repository/structs/NotNullViolationException.js.map +1 -0
  59. package/dist/src/repository/structs/UniqueViolationException.d.ts +36 -0
  60. package/dist/src/repository/structs/UniqueViolationException.d.ts.map +1 -0
  61. package/dist/src/repository/structs/UniqueViolationException.js +40 -0
  62. package/dist/src/repository/structs/UniqueViolationException.js.map +1 -0
  63. package/dist/src/repository/transfers/CheckViolationException.d.ts +19 -0
  64. package/dist/src/repository/transfers/CheckViolationException.d.ts.map +1 -0
  65. package/dist/src/repository/transfers/CheckViolationException.js +22 -0
  66. package/dist/src/repository/transfers/CheckViolationException.js.map +1 -0
  67. package/dist/src/repository/transfers/ConnectionException.d.ts +15 -0
  68. package/dist/src/repository/transfers/ConnectionException.d.ts.map +1 -0
  69. package/dist/src/repository/transfers/ConnectionException.js +16 -0
  70. package/dist/src/repository/transfers/ConnectionException.js.map +1 -0
  71. package/dist/src/repository/transfers/DatabaseException.d.ts +14 -0
  72. package/dist/src/repository/transfers/DatabaseException.d.ts.map +1 -0
  73. package/dist/src/repository/transfers/DatabaseException.js +15 -0
  74. package/dist/src/repository/transfers/DatabaseException.js.map +1 -0
  75. package/dist/src/repository/transfers/DeadlockException.d.ts +16 -0
  76. package/dist/src/repository/transfers/DeadlockException.d.ts.map +1 -0
  77. package/dist/src/repository/transfers/DeadlockException.js +17 -0
  78. package/dist/src/repository/transfers/DeadlockException.js.map +1 -0
  79. package/dist/src/repository/transfers/ForeignKeyViolationException.d.ts +20 -0
  80. package/dist/src/repository/transfers/ForeignKeyViolationException.d.ts.map +1 -0
  81. package/dist/src/repository/transfers/ForeignKeyViolationException.js +23 -0
  82. package/dist/src/repository/transfers/ForeignKeyViolationException.js.map +1 -0
  83. package/dist/src/repository/transfers/NotNullViolationException.d.ts +20 -0
  84. package/dist/src/repository/transfers/NotNullViolationException.d.ts.map +1 -0
  85. package/dist/src/repository/transfers/NotNullViolationException.js +23 -0
  86. package/dist/src/repository/transfers/NotNullViolationException.js.map +1 -0
  87. package/dist/src/repository/transfers/UniqueViolationException.d.ts +36 -0
  88. package/dist/src/repository/transfers/UniqueViolationException.d.ts.map +1 -0
  89. package/dist/src/repository/transfers/UniqueViolationException.js +40 -0
  90. package/dist/src/repository/transfers/UniqueViolationException.js.map +1 -0
  91. package/package.json +52 -0
@@ -0,0 +1,224 @@
1
+ import { Repository, NotInitializedException } from '@xfcfam/xf';
2
+ import { Kysely } from 'kysely';
3
+ /**
4
+ * Base Generalization for the Access Layer when the underlying
5
+ * external system is a SQL database.
6
+ *
7
+ * Encapsulates the [Kysely](https://kysely.dev) query builder behind
8
+ * a single XF-canonical class. The implementer's concrete Logical
9
+ * extends this (or a dialect-specific subclass such as
10
+ * `PostgresDatabaseRepository`) and exposes domain-meaningful methods
11
+ * that compose queries against `this.db`, a `Kysely<Schema>` bound
12
+ * to the implementer's typed schema.
13
+ *
14
+ * Concrete dialect support lives in adapter packages — install one of
15
+ * `@xfcfam/xf-sql-postgres`, `@xfcfam/xf-sql-mysql`, etc., or supply
16
+ * any Kysely-compatible `Dialect` directly.
17
+ *
18
+ * ──────────────────────────────────────────────────────────────────
19
+ * IMPORTANT — Required configuration for subclasses
20
+ * ──────────────────────────────────────────────────────────────────
21
+ * The underlying Kysely instance is held in a static private WeakMap
22
+ * and created inside {@link init}. If a subclass overrides `init()` /
23
+ * `terminate()`, it MUST chain through `super`:
24
+ *
25
+ * async init() { await super.init(); // own setup }
26
+ * async terminate() { // own teardown; await super.terminate() }
27
+ *
28
+ * Forgetting `super.init()` leaves `this.db` uninitialised and every
29
+ * query will throw {@link NotInitializedException}.
30
+ *
31
+ * ──────────────────────────────────────────────────────────────────
32
+ * Overridable observation hooks
33
+ * ──────────────────────────────────────────────────────────────────
34
+ * - {@link onConnected} ← after `init()` creates the Kysely instance
35
+ * - {@link onDisconnected} ← after `terminate()` destroys it
36
+ * - {@link onQuery} ← for every query Kysely executes
37
+ * - {@link onError} ← for every {@link exec}-wrapped operation that rejects
38
+ *
39
+ * Transaction hooks ({@link onTransactionStart} / Commit / Rollback)
40
+ * live on {@link TransactionalDatabaseRepository}, the subclass that
41
+ * exposes explicit transaction control.
42
+ *
43
+ * ──────────────────────────────────────────────────────────────────
44
+ * Error translation
45
+ * ──────────────────────────────────────────────────────────────────
46
+ * Dialect-specific errors (driver Error objects) are not translated
47
+ * by this class — it does not know about Postgres SQLSTATEs, MySQL
48
+ * error numbers, etc. Use a dialect adapter subclass (such as
49
+ * `PostgresDatabaseRepository`) or override {@link translateError}
50
+ * yourself to map errors to the typed Exceptions exported from this
51
+ * package (`UniqueViolationException`, etc.).
52
+ *
53
+ * @typeParam Schema Implementer-defined TypeScript interface mapping
54
+ * table names to their column shapes. See the
55
+ * Kysely docs for the conventions on
56
+ * `Generated`, `ColumnType`, etc.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * import { DatabaseRepository } from '@xfcfam/xf-sql'
61
+ * import { PostgresDialect } from 'kysely'
62
+ * import { Pool } from 'pg'
63
+ *
64
+ * interface Schema {
65
+ * users: { id: number; name: string; email: string }
66
+ * }
67
+ *
68
+ * export class UsersDb extends DatabaseRepository<Schema> {
69
+ * constructor() {
70
+ * super({
71
+ * dialect: new PostgresDialect({
72
+ * pool: new Pool({ connectionString: process.env.DATABASE_URL }),
73
+ * }),
74
+ * })
75
+ * }
76
+ * async init() { await super.init() }
77
+ * async terminate() { await super.terminate() }
78
+ *
79
+ * getUser(id: number) {
80
+ * return this.db
81
+ * .selectFrom('users')
82
+ * .where('id', '=', id)
83
+ * .selectAll()
84
+ * .executeTakeFirstOrThrow()
85
+ * }
86
+ * }
87
+ * ```
88
+ */
89
+ export class DatabaseRepository extends Repository {
90
+ static state = new WeakMap();
91
+ /** Options provided at construction time. */
92
+ options;
93
+ constructor(options) {
94
+ super(null);
95
+ this.options = options;
96
+ }
97
+ /**
98
+ * Typed Kysely instance bound to `Schema`. Use it to compose queries:
99
+ * `this.db.selectFrom('users')…`, `this.db.insertInto('users')…`.
100
+ * Throws if {@link init} has not been called.
101
+ */
102
+ get db() {
103
+ const s = DatabaseRepository.state.get(this);
104
+ if (s === undefined) {
105
+ throw new NotInitializedException('DatabaseRepository: init() was not called (or super.init() was skipped)');
106
+ }
107
+ return s.db;
108
+ }
109
+ /**
110
+ * Subclasses that override this method MUST call `await super.init()`
111
+ * **first**; otherwise {@link db} will throw on use.
112
+ */
113
+ async init() {
114
+ const db = new Kysely({
115
+ dialect: this.options.dialect,
116
+ log: (event) => {
117
+ if (event.level === 'query') {
118
+ this.onQuery(event.query.sql, event.query.parameters);
119
+ }
120
+ else if (event.level === 'error') {
121
+ // Kysely-emitted query errors are also routed through onError
122
+ // for symmetry with exec() failures.
123
+ this.onError('query', event.error);
124
+ }
125
+ },
126
+ });
127
+ DatabaseRepository.state.set(this, { db });
128
+ await this.onConnected();
129
+ }
130
+ /**
131
+ * Subclasses that override this method MUST call `await super.terminate()`
132
+ * **last** to release the connection pool held by the Kysely instance.
133
+ */
134
+ async terminate() {
135
+ const s = DatabaseRepository.state.get(this);
136
+ if (s !== undefined) {
137
+ await s.db.destroy();
138
+ DatabaseRepository.state.delete(this);
139
+ await this.onDisconnected();
140
+ }
141
+ }
142
+ /**
143
+ * Hook for dialect-specific error translation. Default
144
+ * implementation is identity (returns the input unchanged).
145
+ *
146
+ * Dialect adapter subclasses (such as `PostgresDatabaseRepository`)
147
+ * override this to map driver errors to the typed Exceptions
148
+ * exported from this package.
149
+ *
150
+ * Called automatically by {@link exec}; not called for direct
151
+ * `this.db.…` usage, where the implementer is responsible for
152
+ * wrapping queries in `this.exec(...)`.
153
+ */
154
+ translateError(err) {
155
+ return err;
156
+ }
157
+ /**
158
+ * Execute an operation against the database, translating any
159
+ * dialect-specific errors to xf-sql Exception types via
160
+ * {@link translateError}.
161
+ *
162
+ * Use this whenever you need automatic error translation. For raw
163
+ * Kysely access without translation, use {@link db} directly.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * async createUser(input: UserInput) {
168
+ * return this.exec(() =>
169
+ * this.db.insertInto('users').values(input).returningAll().executeTakeFirstOrThrow()
170
+ * )
171
+ * }
172
+ * ```
173
+ */
174
+ async exec(op) {
175
+ try {
176
+ return await op();
177
+ }
178
+ catch (err) {
179
+ await this.onError('exec', err);
180
+ throw this.translateError(err);
181
+ }
182
+ }
183
+ // ─── Overridable observation hooks ────────────────────────
184
+ /**
185
+ * Invoked after `init()` has created the Kysely instance. Default
186
+ * no-op. Override for connection-open telemetry, schema migrations,
187
+ * connection warmup, etc.
188
+ */
189
+ async onConnected() { }
190
+ /**
191
+ * Invoked after `terminate()` has destroyed the Kysely instance.
192
+ * Default no-op. Override for connection-close telemetry.
193
+ */
194
+ async onDisconnected() { }
195
+ /**
196
+ * Invoked for every query Kysely executes (both standalone and
197
+ * inside transactions). Receives the compiled SQL and the bound
198
+ * parameters. Default no-op.
199
+ *
200
+ * Use for audit logging or query profiling. The hook runs
201
+ * synchronously inside Kysely's pipeline — keep it cheap and avoid
202
+ * blocking work.
203
+ */
204
+ onQuery(_sql, _params) { }
205
+ /**
206
+ * Invoked when an `exec()`-wrapped operation rejects, or when
207
+ * Kysely emits a query-level error. The `operation` label
208
+ * distinguishes the source (`'exec'`, `'query'`, or the value
209
+ * supplied by a subclass). Default no-op.
210
+ *
211
+ * The hook fires before {@link translateError}; the (possibly
212
+ * untranslated) error is still re-thrown to the caller.
213
+ *
214
+ * The hook may be `async` — the `exec()` call site `await`s it, so
215
+ * an async override is fully observed before the error is rethrown.
216
+ * Note: the Kysely log callback (`'query'` source) is synchronous by
217
+ * Kysely's API contract and cannot `await` the hook; async overrides
218
+ * that target only that source will execute fire-and-forget from that
219
+ * path. If reliable async observability of query-level Kysely errors
220
+ * is required, wrap the hook body with its own error handling.
221
+ */
222
+ onError(_operation, _error) { }
223
+ }
224
+ //# sourceMappingURL=DatabaseRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatabaseRepository.js","sourceRoot":"","sources":["../../../../src/repository/general/DatabaseRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { type Transaction } from 'kysely';
2
+ import { DatabaseRepository } from './DatabaseRepository.js';
3
+ /**
4
+ * Generalization for SQL Access Layer components that need explicit
5
+ * transaction control on top of {@link DatabaseRepository}.
6
+ *
7
+ * Extends `DatabaseRepository<Schema>` and adds {@link transaction},
8
+ * a helper that runs a callback inside a Kysely transaction:
9
+ * commits on success, rolls back on throw.
10
+ *
11
+ * Error translation flows through {@link DatabaseRepository.translateError}
12
+ * — overriding it once on a dialect subclass covers both `exec` and
13
+ * `transaction`.
14
+ *
15
+ * ──────────────────────────────────────────────────────────────────
16
+ * Overridable transaction hooks
17
+ * ──────────────────────────────────────────────────────────────────
18
+ * - {@link onTransactionStart} ← before the callback runs
19
+ * - {@link onTransactionCommit} ← after the callback resolves
20
+ * - {@link onTransactionRollback} ← when the callback throws
21
+ *
22
+ * Each hook receives a `txId` — a short opaque identifier that
23
+ * correlates the three events of the same transaction. The id is
24
+ * generated per-call and has no meaning to the database (it's a
25
+ * sibling of telemetry trace ids, not of SQL `SAVEPOINT` names).
26
+ *
27
+ * @typeParam Schema See {@link DatabaseRepository}.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { TransactionalDatabaseRepository } from '@xfcfam/xf-sql'
32
+ *
33
+ * export class OrdersDb extends TransactionalDatabaseRepository<Schema> {
34
+ * constructor() { super({ dialect: ... }) }
35
+ * async init() { await super.init() }
36
+ * async terminate() { await super.terminate() }
37
+ *
38
+ * async checkout(order: Order) {
39
+ * return this.transaction(async (trx) => {
40
+ * await trx.insertInto('orders').values(order).execute()
41
+ * await trx.updateTable('inventory')
42
+ * .set((eb) => ({ stock: eb('stock', '-', order.qty) }))
43
+ * .where('sku', '=', order.sku)
44
+ * .execute()
45
+ * return order.id
46
+ * })
47
+ * }
48
+ * }
49
+ * ```
50
+ */
51
+ export declare abstract class TransactionalDatabaseRepository<Schema = unknown> extends DatabaseRepository<Schema> {
52
+ /**
53
+ * Run `callback` inside a database transaction. Commits if the
54
+ * callback resolves; rolls back if it throws.
55
+ *
56
+ * The argument passed to `callback` is a `Transaction<Schema>` —
57
+ * a Kysely-typed transaction handle. Use it (not `this.db`) for
58
+ * queries inside the transaction so they participate in the same
59
+ * unit of work.
60
+ *
61
+ * Errors thrown inside the callback are translated through
62
+ * {@link DatabaseRepository.translateError} before being rethrown.
63
+ */
64
+ protected transaction<R>(callback: (trx: Transaction<Schema>) => Promise<R>): Promise<R>;
65
+ /**
66
+ * Invoked just before the transaction callback runs. Default no-op.
67
+ * `txId` is a short opaque identifier that correlates the three
68
+ * events of the same transaction.
69
+ */
70
+ protected onTransactionStart(_txId: string): void;
71
+ /**
72
+ * Invoked after the transaction callback resolves successfully and
73
+ * the commit completes. `durationMs` measures the full lifetime of
74
+ * the transaction (including commit latency). Default no-op.
75
+ */
76
+ protected onTransactionCommit(_txId: string, _durationMs: number): void;
77
+ /**
78
+ * Invoked when the transaction callback throws and the rollback
79
+ * completes. `reason` is the original error (before
80
+ * {@link DatabaseRepository.translateError} is applied to the
81
+ * rethrow). Default no-op.
82
+ *
83
+ * The hook may be `async` — it is `await`ed before the original
84
+ * error is rethrown. If the hook itself throws, the exception is
85
+ * swallowed so the original transaction error is always the one
86
+ * propagated to the caller.
87
+ */
88
+ protected onTransactionRollback(_txId: string, _reason: unknown): void | Promise<void>;
89
+ private static makeTxId;
90
+ }
91
+ //# sourceMappingURL=TransactionalDatabaseRepository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransactionalDatabaseRepository.d.ts","sourceRoot":"","sources":["../../../../src/repository/general/TransactionalDatabaseRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,8BAAsB,+BAA+B,CAAC,MAAM,GAAG,OAAO,CACpE,SAAQ,kBAAkB,CAAC,MAAM,CAAC;IAElC;;;;;;;;;;;OAWG;cACa,WAAW,CAAC,CAAC,EAC3B,QAAQ,EAAE,CAAC,GAAG,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,OAAO,CAAC,CAAC,CAAC;IAoBb;;;;OAIG;IACH,SAAS,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAEjD;;;;OAIG;IACH,SAAS,CAAC,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAEvE;;;;;;;;;;OAUG;IACH,SAAS,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAItF,OAAO,CAAC,MAAM,CAAC,QAAQ;CAKxB"}
@@ -0,0 +1,114 @@
1
+ import { DatabaseRepository } from './DatabaseRepository.js';
2
+ /**
3
+ * Generalization for SQL Access Layer components that need explicit
4
+ * transaction control on top of {@link DatabaseRepository}.
5
+ *
6
+ * Extends `DatabaseRepository<Schema>` and adds {@link transaction},
7
+ * a helper that runs a callback inside a Kysely transaction:
8
+ * commits on success, rolls back on throw.
9
+ *
10
+ * Error translation flows through {@link DatabaseRepository.translateError}
11
+ * — overriding it once on a dialect subclass covers both `exec` and
12
+ * `transaction`.
13
+ *
14
+ * ──────────────────────────────────────────────────────────────────
15
+ * Overridable transaction hooks
16
+ * ──────────────────────────────────────────────────────────────────
17
+ * - {@link onTransactionStart} ← before the callback runs
18
+ * - {@link onTransactionCommit} ← after the callback resolves
19
+ * - {@link onTransactionRollback} ← when the callback throws
20
+ *
21
+ * Each hook receives a `txId` — a short opaque identifier that
22
+ * correlates the three events of the same transaction. The id is
23
+ * generated per-call and has no meaning to the database (it's a
24
+ * sibling of telemetry trace ids, not of SQL `SAVEPOINT` names).
25
+ *
26
+ * @typeParam Schema See {@link DatabaseRepository}.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { TransactionalDatabaseRepository } from '@xfcfam/xf-sql'
31
+ *
32
+ * export class OrdersDb extends TransactionalDatabaseRepository<Schema> {
33
+ * constructor() { super({ dialect: ... }) }
34
+ * async init() { await super.init() }
35
+ * async terminate() { await super.terminate() }
36
+ *
37
+ * async checkout(order: Order) {
38
+ * return this.transaction(async (trx) => {
39
+ * await trx.insertInto('orders').values(order).execute()
40
+ * await trx.updateTable('inventory')
41
+ * .set((eb) => ({ stock: eb('stock', '-', order.qty) }))
42
+ * .where('sku', '=', order.sku)
43
+ * .execute()
44
+ * return order.id
45
+ * })
46
+ * }
47
+ * }
48
+ * ```
49
+ */
50
+ export class TransactionalDatabaseRepository extends DatabaseRepository {
51
+ /**
52
+ * Run `callback` inside a database transaction. Commits if the
53
+ * callback resolves; rolls back if it throws.
54
+ *
55
+ * The argument passed to `callback` is a `Transaction<Schema>` —
56
+ * a Kysely-typed transaction handle. Use it (not `this.db`) for
57
+ * queries inside the transaction so they participate in the same
58
+ * unit of work.
59
+ *
60
+ * Errors thrown inside the callback are translated through
61
+ * {@link DatabaseRepository.translateError} before being rethrown.
62
+ */
63
+ async transaction(callback) {
64
+ const txId = TransactionalDatabaseRepository.makeTxId();
65
+ const startedAt = Date.now();
66
+ this.onTransactionStart(txId);
67
+ try {
68
+ const result = await this.db.transaction().execute(callback);
69
+ this.onTransactionCommit(txId, Date.now() - startedAt);
70
+ return result;
71
+ }
72
+ catch (err) {
73
+ try {
74
+ await this.onTransactionRollback(txId, err);
75
+ }
76
+ catch {
77
+ // A throwing rollback hook must not mask the original error.
78
+ }
79
+ throw this.translateError(err);
80
+ }
81
+ }
82
+ // ─── Overridable transaction hooks ────────────────────────
83
+ /**
84
+ * Invoked just before the transaction callback runs. Default no-op.
85
+ * `txId` is a short opaque identifier that correlates the three
86
+ * events of the same transaction.
87
+ */
88
+ onTransactionStart(_txId) { }
89
+ /**
90
+ * Invoked after the transaction callback resolves successfully and
91
+ * the commit completes. `durationMs` measures the full lifetime of
92
+ * the transaction (including commit latency). Default no-op.
93
+ */
94
+ onTransactionCommit(_txId, _durationMs) { }
95
+ /**
96
+ * Invoked when the transaction callback throws and the rollback
97
+ * completes. `reason` is the original error (before
98
+ * {@link DatabaseRepository.translateError} is applied to the
99
+ * rethrow). Default no-op.
100
+ *
101
+ * The hook may be `async` — it is `await`ed before the original
102
+ * error is rethrown. If the hook itself throws, the exception is
103
+ * swallowed so the original transaction error is always the one
104
+ * propagated to the caller.
105
+ */
106
+ onTransactionRollback(_txId, _reason) { }
107
+ // ─── Internals ────────────────────────────────────────────
108
+ static makeTxId() {
109
+ const t = Date.now().toString(36);
110
+ const r = Math.floor(Math.random() * 0x100000).toString(36).padStart(4, '0');
111
+ return `tx-${t}-${r}`;
112
+ }
113
+ }
114
+ //# sourceMappingURL=TransactionalDatabaseRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransactionalDatabaseRepository.js","sourceRoot":"","sources":["../../../../src/repository/general/TransactionalDatabaseRepository.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,OAAgB,+BACpB,SAAQ,kBAA0B;IAElC;;;;;;;;;;;OAWG;IACO,KAAK,CAAC,WAAW,CACzB,QAAkD;QAElD,MAAM,IAAI,GAAG,+BAA+B,CAAC,QAAQ,EAAE,CAAA;QACvD,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC5B,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;YAC5D,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAA;YACtD,OAAO,MAAM,CAAA;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;YAC7C,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;YAC/D,CAAC;YACD,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;QAChC,CAAC;IACH,CAAC;IAED,6DAA6D;IAE7D;;;;OAIG;IACO,kBAAkB,CAAC,KAAa,IAAS,CAAC;IAEpD;;;;OAIG;IACO,mBAAmB,CAAC,KAAa,EAAE,WAAmB,IAAS,CAAC;IAE1E;;;;;;;;;;OAUG;IACO,qBAAqB,CAAC,KAAa,EAAE,OAAgB,IAAyB,CAAC;IAEzF,6DAA6D;IAErD,MAAM,CAAC,QAAQ;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;QACjC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QAC5E,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAA;IACvB,CAAC;CACF"}
@@ -0,0 +1,19 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when an INSERT or UPDATE violates a CHECK constraint.
4
+ *
5
+ * The constraint name (when exposed by the driver) often matches the
6
+ * named CHECK in the schema, e.g. `users_age_positive_check`.
7
+ */
8
+ export declare class CheckViolationException extends DatabaseException {
9
+ /** Name of the violated CHECK constraint, if known. */
10
+ readonly constraint?: string;
11
+ /** Table where the violation occurred, if known. */
12
+ readonly table?: string;
13
+ constructor(message: string, details?: {
14
+ constraint?: string;
15
+ table?: string;
16
+ cause?: unknown;
17
+ });
18
+ }
19
+ //# sourceMappingURL=CheckViolationException.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckViolationException.d.ts","sourceRoot":"","sources":["../../../../src/repository/structs/CheckViolationException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,iBAAiB;IAC5D,uDAAuD;IACvD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;gBAGrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAOzE"}
@@ -0,0 +1,22 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when an INSERT or UPDATE violates a CHECK constraint.
4
+ *
5
+ * The constraint name (when exposed by the driver) often matches the
6
+ * named CHECK in the schema, e.g. `users_age_positive_check`.
7
+ */
8
+ export class CheckViolationException extends DatabaseException {
9
+ /** Name of the violated CHECK constraint, if known. */
10
+ constraint;
11
+ /** Table where the violation occurred, if known. */
12
+ table;
13
+ constructor(message, details = {}) {
14
+ super(message, { cause: details.cause });
15
+ this.name = 'CheckViolationException';
16
+ if (details.constraint !== undefined)
17
+ this.constraint = details.constraint;
18
+ if (details.table !== undefined)
19
+ this.table = details.table;
20
+ }
21
+ }
22
+ //# sourceMappingURL=CheckViolationException.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CheckViolationException.js","sourceRoot":"","sources":["../../../../src/repository/structs/CheckViolationException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,OAAO,uBAAwB,SAAQ,iBAAiB;IAC5D,uDAAuD;IAC9C,UAAU,CAAS;IAC5B,oDAAoD;IAC3C,KAAK,CAAS;IAEvB,YACE,OAAe,EACf,UAAoE,EAAE;QAEtE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;QACxC,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAA;QACrC,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QAC1E,IAAI,OAAO,CAAC,KAAK,KAAU,SAAS;YAAE,IAAI,CAAC,KAAK,GAAQ,OAAO,CAAC,KAAK,CAAA;IACvE,CAAC;CACF"}
@@ -0,0 +1,15 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when the database is unreachable — DNS failure, TCP refused,
4
+ * TLS handshake failure, statement / connection timeout, etc.
5
+ *
6
+ * The original transport error is preserved as the standard
7
+ * `Error.cause`. Use this alongside `RetryableRepository.withRetry`
8
+ * to back off and retry on transient transport failures.
9
+ */
10
+ export declare class ConnectionException extends DatabaseException {
11
+ constructor(message: string, options?: {
12
+ cause?: unknown;
13
+ });
14
+ }
15
+ //# sourceMappingURL=ConnectionException.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionException.d.ts","sourceRoot":"","sources":["../../../../src/repository/structs/ConnectionException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;;;GAOG;AACH,qBAAa,mBAAoB,SAAQ,iBAAiB;gBAC5C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D"}
@@ -0,0 +1,16 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when the database is unreachable — DNS failure, TCP refused,
4
+ * TLS handshake failure, statement / connection timeout, etc.
5
+ *
6
+ * The original transport error is preserved as the standard
7
+ * `Error.cause`. Use this alongside `RetryableRepository.withRetry`
8
+ * to back off and retry on transient transport failures.
9
+ */
10
+ export class ConnectionException extends DatabaseException {
11
+ constructor(message, options) {
12
+ super(message, options);
13
+ this.name = 'ConnectionException';
14
+ }
15
+ }
16
+ //# sourceMappingURL=ConnectionException.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConnectionException.js","sourceRoot":"","sources":["../../../../src/repository/structs/ConnectionException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;;;GAOG;AACH,MAAM,OAAO,mBAAoB,SAAQ,iBAAiB;IACxD,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAA;IACnC,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Base type for every database-related error raised through xf-sql.
3
+ *
4
+ * Every concrete dialect-specific Exception in `@xfarch/xf-sql` is a
5
+ * subclass of `DatabaseException`. Consumers can branch on the
6
+ * specific subclass (e.g. `UniqueViolationException`) or fall back to
7
+ * `instanceof DatabaseException` to catch any DB-originated error.
8
+ */
9
+ export declare class DatabaseException extends Error {
10
+ constructor(message: string, options?: {
11
+ cause?: unknown;
12
+ });
13
+ }
14
+ //# sourceMappingURL=DatabaseException.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatabaseException.d.ts","sourceRoot":"","sources":["../../../../src/repository/structs/DatabaseException.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Base type for every database-related error raised through xf-sql.
3
+ *
4
+ * Every concrete dialect-specific Exception in `@xfarch/xf-sql` is a
5
+ * subclass of `DatabaseException`. Consumers can branch on the
6
+ * specific subclass (e.g. `UniqueViolationException`) or fall back to
7
+ * `instanceof DatabaseException` to catch any DB-originated error.
8
+ */
9
+ export class DatabaseException extends Error {
10
+ constructor(message, options) {
11
+ super(message, options);
12
+ this.name = 'DatabaseException';
13
+ }
14
+ }
15
+ //# sourceMappingURL=DatabaseException.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DatabaseException.js","sourceRoot":"","sources":["../../../../src/repository/structs/DatabaseException.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when the database detects a deadlock and aborts one of the
4
+ * conflicting transactions.
5
+ *
6
+ * Deadlocks are typically retryable — the transaction can be re-run
7
+ * and will usually succeed once the conflicting party has finished.
8
+ * Pair with `RetryableRepository.withRetry` or
9
+ * `RetryRestRepository`-style policies in your Logical.
10
+ */
11
+ export declare class DeadlockException extends DatabaseException {
12
+ constructor(message: string, options?: {
13
+ cause?: unknown;
14
+ });
15
+ }
16
+ //# sourceMappingURL=DeadlockException.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeadlockException.d.ts","sourceRoot":"","sources":["../../../../src/repository/structs/DeadlockException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;;;;GAQG;AACH,qBAAa,iBAAkB,SAAQ,iBAAiB;gBAC1C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAI3D"}
@@ -0,0 +1,17 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when the database detects a deadlock and aborts one of the
4
+ * conflicting transactions.
5
+ *
6
+ * Deadlocks are typically retryable — the transaction can be re-run
7
+ * and will usually succeed once the conflicting party has finished.
8
+ * Pair with `RetryableRepository.withRetry` or
9
+ * `RetryRestRepository`-style policies in your Logical.
10
+ */
11
+ export class DeadlockException extends DatabaseException {
12
+ constructor(message, options) {
13
+ super(message, options);
14
+ this.name = 'DeadlockException';
15
+ }
16
+ }
17
+ //# sourceMappingURL=DeadlockException.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeadlockException.js","sourceRoot":"","sources":["../../../../src/repository/structs/DeadlockException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;;;;GAQG;AACH,MAAM,OAAO,iBAAkB,SAAQ,iBAAiB;IACtD,YAAY,OAAe,EAAE,OAA6B;QACxD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;IACjC,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import { DatabaseException } from './DatabaseException.js';
2
+ /**
3
+ * Thrown when an INSERT, UPDATE or DELETE violates a FOREIGN KEY
4
+ * constraint.
5
+ *
6
+ * Common scenarios: inserting a row that references a non-existent
7
+ * parent; deleting a row that is still referenced by children.
8
+ */
9
+ export declare class ForeignKeyViolationException extends DatabaseException {
10
+ /** Name of the violated foreign-key constraint, if known. */
11
+ readonly constraint?: string;
12
+ /** Table where the violation occurred, if known. */
13
+ readonly table?: string;
14
+ constructor(message: string, details?: {
15
+ constraint?: string;
16
+ table?: string;
17
+ cause?: unknown;
18
+ });
19
+ }
20
+ //# sourceMappingURL=ForeignKeyViolationException.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ForeignKeyViolationException.d.ts","sourceRoot":"","sources":["../../../../src/repository/structs/ForeignKeyViolationException.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAE1D;;;;;;GAMG;AACH,qBAAa,4BAA6B,SAAQ,iBAAiB;IACjE,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;gBAGrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAOzE"}