@zmdb/orm 1.0.0-beta.1

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 (87) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +30 -0
  3. package/dist/cache/index.d.ts +34 -0
  4. package/dist/cache/index.d.ts.map +1 -0
  5. package/dist/cache/index.js +149 -0
  6. package/dist/cache/index.js.map +1 -0
  7. package/dist/drivers/transactional.d.ts +6 -0
  8. package/dist/drivers/transactional.d.ts.map +1 -0
  9. package/dist/drivers/transactional.js +2 -0
  10. package/dist/drivers/transactional.js.map +1 -0
  11. package/dist/dto/index.d.ts +41 -0
  12. package/dist/dto/index.d.ts.map +1 -0
  13. package/dist/dto/index.js +334 -0
  14. package/dist/dto/index.js.map +1 -0
  15. package/dist/entity-modeling/index.d.ts +18 -0
  16. package/dist/entity-modeling/index.d.ts.map +1 -0
  17. package/dist/entity-modeling/index.js +26 -0
  18. package/dist/entity-modeling/index.js.map +1 -0
  19. package/dist/filters/index.d.ts +62 -0
  20. package/dist/filters/index.d.ts.map +1 -0
  21. package/dist/filters/index.js +163 -0
  22. package/dist/filters/index.js.map +1 -0
  23. package/dist/index.d.ts +413 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +2003 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/loaders/index.d.ts +38 -0
  28. package/dist/loaders/index.d.ts.map +1 -0
  29. package/dist/loaders/index.js +164 -0
  30. package/dist/loaders/index.js.map +1 -0
  31. package/dist/outbox/index.d.ts +59 -0
  32. package/dist/outbox/index.d.ts.map +1 -0
  33. package/dist/outbox/index.js +323 -0
  34. package/dist/outbox/index.js.map +1 -0
  35. package/dist/outbox/sql.d.ts +52 -0
  36. package/dist/outbox/sql.d.ts.map +1 -0
  37. package/dist/outbox/sql.js +115 -0
  38. package/dist/outbox/sql.js.map +1 -0
  39. package/dist/relations/index.d.ts +38 -0
  40. package/dist/relations/index.d.ts.map +1 -0
  41. package/dist/relations/index.js +164 -0
  42. package/dist/relations/index.js.map +1 -0
  43. package/dist/replicas/index.d.ts +10 -0
  44. package/dist/replicas/index.d.ts.map +1 -0
  45. package/dist/replicas/index.js +42 -0
  46. package/dist/replicas/index.js.map +1 -0
  47. package/dist/seeding/index.d.ts +18 -0
  48. package/dist/seeding/index.d.ts.map +1 -0
  49. package/dist/seeding/index.js +66 -0
  50. package/dist/seeding/index.js.map +1 -0
  51. package/dist/streaming/index.d.ts +7 -0
  52. package/dist/streaming/index.d.ts.map +1 -0
  53. package/dist/streaming/index.js +67 -0
  54. package/dist/streaming/index.js.map +1 -0
  55. package/dist/testing/official-dialects.fixture.d.ts +16 -0
  56. package/dist/testing/official-dialects.fixture.d.ts.map +1 -0
  57. package/dist/testing/official-dialects.fixture.js +21 -0
  58. package/dist/testing/official-dialects.fixture.js.map +1 -0
  59. package/dist/testing/repository.fixture.d.ts +11 -0
  60. package/dist/testing/repository.fixture.d.ts.map +1 -0
  61. package/dist/testing/repository.fixture.js +33 -0
  62. package/dist/testing/repository.fixture.js.map +1 -0
  63. package/dist/transactions/index.d.ts +40 -0
  64. package/dist/transactions/index.d.ts.map +1 -0
  65. package/dist/transactions/index.js +249 -0
  66. package/dist/transactions/index.js.map +1 -0
  67. package/dist/typed-populate/nested.fixtures.d.ts +34 -0
  68. package/dist/typed-populate/nested.fixtures.d.ts.map +1 -0
  69. package/dist/typed-populate/nested.fixtures.js +4 -0
  70. package/dist/typed-populate/nested.fixtures.js.map +1 -0
  71. package/package.json +78 -0
  72. package/src/cache/index.ts +188 -0
  73. package/src/drivers/transactional.ts +6 -0
  74. package/src/dto/index.ts +379 -0
  75. package/src/entity-modeling/index.ts +38 -0
  76. package/src/filters/index.ts +267 -0
  77. package/src/index.ts +2757 -0
  78. package/src/loaders/index.ts +256 -0
  79. package/src/outbox/index.ts +409 -0
  80. package/src/outbox/sql.ts +179 -0
  81. package/src/relations/index.ts +221 -0
  82. package/src/replicas/index.ts +51 -0
  83. package/src/seeding/index.ts +72 -0
  84. package/src/streaming/index.ts +67 -0
  85. package/src/testing/repository.fixture.ts +44 -0
  86. package/src/transactions/index.ts +315 -0
  87. package/src/typed-populate/nested.fixtures.ts +59 -0
@@ -0,0 +1,249 @@
1
+ // Transactions — implementation (#36 transaction context primitive).
2
+ // createTransactionalDb.transaction() issues BEGIN/COMMIT/ROLLBACK and
3
+ // tx.savepoint() issues SAVEPOINT / RELEASE / ROLLBACK TO SAVEPOINT.
4
+ import { dialectTraits } from '@zmdb/sql';
5
+ import {} from '../index.js';
6
+ export function markTransactionClosed(tx) {
7
+ const stream = typeof tx.stream === 'function' ? tx.stream : undefined;
8
+ return {
9
+ _state: 'closed',
10
+ ...(tx.dialect === undefined ? {} : { dialect: tx.dialect }),
11
+ execute: (query, opts) => tx.execute(query, opts),
12
+ ...(stream === undefined
13
+ ? {}
14
+ : {
15
+ stream: (query, opts) => stream.call(tx, query, opts),
16
+ }),
17
+ savepoint: (fn) => tx.savepoint(innerTx => fn(markTransactionClosed(innerTx))),
18
+ };
19
+ }
20
+ function nonNegativeInteger(name, value) {
21
+ if (!Number.isSafeInteger(value) || value < 0) {
22
+ throw new TypeError(`${name} must be a non-negative safe integer`);
23
+ }
24
+ }
25
+ function retryPolicy(options) {
26
+ const policy = options?.retry;
27
+ if (policy === undefined)
28
+ return undefined;
29
+ const baseDelayMs = policy.baseDelayMs ?? 10;
30
+ const maxDelayMs = policy.maxDelayMs ?? 1000;
31
+ nonNegativeInteger('retry.maxRetries', policy.maxRetries);
32
+ nonNegativeInteger('retry.baseDelayMs', baseDelayMs);
33
+ nonNegativeInteger('retry.maxDelayMs', maxDelayMs);
34
+ return { maxRetries: policy.maxRetries, baseDelayMs, maxDelayMs };
35
+ }
36
+ function errorCode(error) {
37
+ if (error === null || typeof error !== 'object')
38
+ return undefined;
39
+ const code = Reflect.get(error, 'code');
40
+ return typeof code === 'string' ? code : undefined;
41
+ }
42
+ function canRetry(retryableCodes, error, retries, policy) {
43
+ if (retryableCodes === undefined || policy === undefined || retries >= policy.maxRetries)
44
+ return false;
45
+ const code = errorCode(error);
46
+ return code !== undefined && retryableCodes.includes(code);
47
+ }
48
+ function backoff(policy, retry) {
49
+ return Math.min(policy.baseDelayMs * 2 ** (retry - 1), policy.maxDelayMs);
50
+ }
51
+ async function wait(milliseconds) {
52
+ if (milliseconds === 0)
53
+ return;
54
+ await new Promise(resolve => {
55
+ setTimeout(resolve, milliseconds);
56
+ });
57
+ }
58
+ function transactionClosedError() {
59
+ return new Error('cannot continue a stream after its transaction scope has closed');
60
+ }
61
+ function trackedTransactionStream(source, active, isOpen) {
62
+ let started = false;
63
+ let closed = false;
64
+ let iterator;
65
+ let closing;
66
+ const stream = {
67
+ close() {
68
+ if (closing !== undefined)
69
+ return closing;
70
+ if (closed)
71
+ return Promise.resolve();
72
+ closed = true;
73
+ active.delete(stream);
74
+ const current = iterator;
75
+ iterator = undefined;
76
+ closing = (async () => {
77
+ if (current?.return !== undefined)
78
+ await current.return();
79
+ })();
80
+ return closing;
81
+ },
82
+ };
83
+ return {
84
+ [Symbol.asyncIterator]() {
85
+ if (started)
86
+ throw new Error('transaction stream is single-shot');
87
+ if (!isOpen())
88
+ throw transactionClosedError();
89
+ started = true;
90
+ iterator = source[Symbol.asyncIterator]();
91
+ active.add(stream);
92
+ return {
93
+ async next() {
94
+ if (!isOpen())
95
+ throw transactionClosedError();
96
+ const current = iterator;
97
+ if (current === undefined)
98
+ return { done: true, value: undefined };
99
+ try {
100
+ const result = await current.next();
101
+ if (!isOpen()) {
102
+ await stream.close();
103
+ throw transactionClosedError();
104
+ }
105
+ if (result.done) {
106
+ closed = true;
107
+ iterator = undefined;
108
+ active.delete(stream);
109
+ }
110
+ return result;
111
+ }
112
+ catch (error) {
113
+ await stream.close();
114
+ throw error;
115
+ }
116
+ },
117
+ async return() {
118
+ await stream.close();
119
+ return { done: true, value: undefined };
120
+ },
121
+ async throw(error) {
122
+ const current = iterator;
123
+ try {
124
+ if (current?.throw !== undefined)
125
+ return await current.throw(error);
126
+ throw error;
127
+ }
128
+ finally {
129
+ await stream.close();
130
+ }
131
+ },
132
+ };
133
+ },
134
+ };
135
+ }
136
+ export function createTransactionalDb(conn) {
137
+ let savepointSeq = 0;
138
+ const retryableCodes = conn.dialect === undefined ? undefined : dialectTraits(conn.dialect).retryableCodes;
139
+ const makeContext = () => {
140
+ let open = true;
141
+ const activeStreams = new Set();
142
+ const connectionStream = typeof conn.stream === 'function' ? conn.stream : undefined;
143
+ const assertOpen = () => {
144
+ if (!open)
145
+ throw transactionClosedError();
146
+ };
147
+ const close = async () => {
148
+ if (!open)
149
+ return;
150
+ open = false;
151
+ let firstFailure;
152
+ let failed = false;
153
+ for (const stream of activeStreams) {
154
+ try {
155
+ await stream.close();
156
+ }
157
+ catch (error) {
158
+ if (!failed)
159
+ firstFailure = error;
160
+ failed = true;
161
+ }
162
+ }
163
+ if (failed)
164
+ throw firstFailure;
165
+ };
166
+ const context = {
167
+ ...(conn.dialect === undefined ? {} : { dialect: conn.dialect }),
168
+ execute: (query, opts) => {
169
+ assertOpen();
170
+ return conn.execute(query, opts);
171
+ },
172
+ ...(connectionStream === undefined
173
+ ? {}
174
+ : {
175
+ stream: (query, opts) => {
176
+ assertOpen();
177
+ return trackedTransactionStream(connectionStream.call(conn, query, opts), activeStreams, () => open);
178
+ },
179
+ }),
180
+ savepoint: async (fn) => {
181
+ assertOpen();
182
+ const name = `s${++savepointSeq}`;
183
+ await conn.raw(`SAVEPOINT ${name}`);
184
+ const nested = makeContext();
185
+ try {
186
+ const result = await fn(nested.context);
187
+ await nested.close();
188
+ await conn.raw(`RELEASE SAVEPOINT ${name}`);
189
+ return result;
190
+ }
191
+ catch (error) {
192
+ let failure = error;
193
+ try {
194
+ await nested.close();
195
+ }
196
+ catch (closeError) {
197
+ failure = closeError;
198
+ }
199
+ await conn.raw(`ROLLBACK TO SAVEPOINT ${name}`);
200
+ throw failure;
201
+ }
202
+ },
203
+ };
204
+ return { context, close };
205
+ };
206
+ return {
207
+ async transaction(fn, options) {
208
+ const policy = retryPolicy(options);
209
+ let retries = 0;
210
+ while (true) {
211
+ savepointSeq = 0;
212
+ await conn.raw('BEGIN');
213
+ const scope = makeContext();
214
+ try {
215
+ const result = await fn(scope.context);
216
+ await scope.close();
217
+ await conn.raw('COMMIT');
218
+ return result;
219
+ }
220
+ catch (error) {
221
+ let failure = error;
222
+ try {
223
+ await scope.close();
224
+ }
225
+ catch (closeError) {
226
+ failure = closeError;
227
+ }
228
+ await conn.raw('ROLLBACK');
229
+ if (!canRetry(retryableCodes, failure, retries, policy))
230
+ throw failure;
231
+ retries++;
232
+ await wait(backoff(policy, retries));
233
+ }
234
+ }
235
+ },
236
+ };
237
+ }
238
+ // #39 — explicit write-batching helper. Runs the given operations inside a
239
+ // single transaction / one flush: all-or-nothing. Each op receives the tx
240
+ // context and performs its own execute(s).
241
+ export function batch(db, ops) {
242
+ return db.transaction(async (tx) => {
243
+ const results = [];
244
+ for (const op of ops)
245
+ results.push(await op(tx));
246
+ return results;
247
+ });
248
+ }
249
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transactions/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { type ManyToMany, type ManyToOne, type OneToMany, type OneToOne, type Physical, type PrimaryKey, type References, type Sql, type Table } from '@zmdb/schema/tags';
2
+ export interface NestedUser extends Table<'nested_users'>, Physical<'app_users'> {
3
+ tenantId: number & Sql<'integer'> & PrimaryKey & Physical<'tenant_id'>;
4
+ id: number & Sql<'integer'> & PrimaryKey;
5
+ name: string & Sql<'text'>;
6
+ posts?: NestedPost[] & OneToMany<'nested_posts', 'tenantId,userId'>;
7
+ profile?: NestedProfile & OneToOne<'nested_profiles', 'tenantId,userId'>;
8
+ groups?: NestedUser[] & ManyToMany<'nested_users', 'user_groups'>;
9
+ }
10
+ export interface NestedPost extends Table<'nested_posts'>, Physical<'blog_posts'> {
11
+ tenantId: number & Sql<'integer'> & PrimaryKey & References<'nested_users.tenantId'> & Physical<'tenant_id'>;
12
+ id: number & Sql<'integer'> & PrimaryKey;
13
+ userId: number & Sql<'integer'> & References<'nested_users.id'> & Physical<'user_id'>;
14
+ title: string & Sql<'text'>;
15
+ comments?: NestedComment[] & OneToMany<'nested_comments', 'tenantId,postId'>;
16
+ }
17
+ export interface NestedComment extends Table<'nested_comments'>, Physical<'post_comments'> {
18
+ tenantId: number & Sql<'integer'> & PrimaryKey & References<'nested_users.tenantId'> & Physical<'tenant_id'>;
19
+ id: number & Sql<'integer'> & PrimaryKey;
20
+ postId: number & Sql<'integer'> & Physical<'post_id'>;
21
+ authorId: (number & Sql<'integer'> & References<'nested_users.id'> & Physical<'author_id'>) | null;
22
+ body: string & Sql<'text'>;
23
+ hidden: number & Sql<'integer'>;
24
+ author?: NestedUser & ManyToOne<'nested_users', 'tenantId,authorId'>;
25
+ }
26
+ export interface NestedProfile extends Table<'nested_profiles'>, Physical<'user_profiles'> {
27
+ tenantId: number & Sql<'integer'> & PrimaryKey & References<'nested_users.tenantId'> & Physical<'tenant_id'>;
28
+ id: number & Sql<'integer'> & PrimaryKey;
29
+ userId: number & Sql<'integer'> & References<'nested_users.id'> & Physical<'user_id'>;
30
+ bio: string & Sql<'text'>;
31
+ user?: NestedUser & ManyToOne<'nested_users', 'tenantId,userId'>;
32
+ }
33
+ export declare const NestedUserSchema: import("@zmdb/schema").TaggedSchema<NestedUser>, NestedPostSchema: import("@zmdb/schema").TaggedSchema<NestedPost>, NestedCommentSchema: import("@zmdb/schema").TaggedSchema<NestedComment>, NestedProfileSchema: import("@zmdb/schema").TaggedSchema<NestedProfile>;
34
+ //# sourceMappingURL=nested.fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nested.fixtures.d.ts","sourceRoot":"","sources":["../../src/typed-populate/nested.fixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { schemasFrom } from '@zmdb/compiler/testing';
2
+ import {} from '@zmdb/schema/tags';
3
+ export const { NestedUser: NestedUserSchema, NestedPost: NestedPostSchema, NestedComment: NestedCommentSchema, NestedProfile: NestedProfileSchema, } = schemasFrom(import.meta.url, ['NestedUser', 'NestedPost', 'NestedComment', 'NestedProfile']);
4
+ //# sourceMappingURL=nested.fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nested.fixtures.js","sourceRoot":"","sources":["../../src/typed-populate/nested.fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAUN,MAAM,mBAAmB,CAAC;AAqC3B,MAAM,CAAC,MAAM,EACX,UAAU,EAAE,gBAAgB,EAC5B,UAAU,EAAE,gBAAgB,EAC5B,aAAa,EAAE,mBAAmB,EAClC,aAAa,EAAE,mBAAmB,GACnC,GAAG,WAAW,CAKZ,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@zmdb/orm",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "Auto-validating CRUD repository over a zmdb schema: transactions, populate, read-replicas, lifecycle events, and seeding. No proxies, no identity map.",
5
+ "keywords": [
6
+ "crud",
7
+ "data-layer",
8
+ "repository",
9
+ "transactions",
10
+ "typescript",
11
+ "zmdb"
12
+ ],
13
+ "homepage": "https://github.com/ambasta/zmdb#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/ambasta/zmdb/issues"
16
+ },
17
+ "license": "GPL-3.0-or-later",
18
+ "author": "zmdb contributors",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/ambasta/zmdb.git",
22
+ "directory": "packages/orm"
23
+ },
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ },
31
+ "./dto": {
32
+ "types": "./dist/dto/index.d.ts",
33
+ "import": "./dist/dto/index.js"
34
+ },
35
+ "./relations": {
36
+ "types": "./dist/relations/index.d.ts",
37
+ "import": "./dist/relations/index.js"
38
+ },
39
+ "./seeding": {
40
+ "types": "./dist/seeding/index.d.ts",
41
+ "import": "./dist/seeding/index.js"
42
+ },
43
+ "./transactions": {
44
+ "types": "./dist/transactions/index.d.ts",
45
+ "import": "./dist/transactions/index.js"
46
+ },
47
+ "./outbox": {
48
+ "types": "./dist/outbox/index.d.ts",
49
+ "import": "./dist/outbox/index.js"
50
+ },
51
+ "./replicas": {
52
+ "types": "./dist/replicas/index.d.ts",
53
+ "import": "./dist/replicas/index.js"
54
+ },
55
+ "./entity-modeling": {
56
+ "types": "./dist/entity-modeling/index.d.ts",
57
+ "import": "./dist/entity-modeling/index.js"
58
+ }
59
+ },
60
+ "publishConfig": {
61
+ "access": "public",
62
+ "tag": "beta"
63
+ },
64
+ "scripts": {
65
+ "build": "node ../../scripts/build-package.mjs",
66
+ "test": "vitest run"
67
+ },
68
+ "dependencies": {
69
+ "@zmdb/schema": "1.0.0-beta.1",
70
+ "@zmdb/sql": "1.0.0-beta.1",
71
+ "@zmdb/validator": "1.0.0-beta.1"
72
+ },
73
+ "engines": {
74
+ "node": ">=26"
75
+ },
76
+ "main": "./dist/index.js",
77
+ "types": "./dist/index.d.ts"
78
+ }
@@ -0,0 +1,188 @@
1
+ import { isRecord } from '@zmdb/schema';
2
+ import { type SchemaIR } from '@zmdb/schema/ir';
3
+ import { dialectName, type CompiledQuery, type DialectTarget } from '@zmdb/sql';
4
+
5
+ export interface CacheStore {
6
+ get(key: string): Promise<unknown | undefined>;
7
+ set(key: string, value: unknown, ttlMs: number, tags: readonly string[]): Promise<void>;
8
+ invalidateTags(tags: readonly string[]): Promise<void>;
9
+ }
10
+
11
+ export interface CacheOptions {
12
+ readonly ttlMs: number;
13
+ readonly tags?: readonly string[];
14
+ }
15
+
16
+ export interface CacheInvalidationOptions {
17
+ readonly invalidateTags?: readonly string[];
18
+ }
19
+
20
+ interface MemoryEntry {
21
+ readonly value: unknown;
22
+ readonly expiresAt: number;
23
+ readonly tags: readonly string[];
24
+ }
25
+
26
+ const DEFAULT_MAX_ENTRIES = 1_000;
27
+
28
+ function positiveInteger(value: number, name: string): number {
29
+ if (!Number.isSafeInteger(value) || value <= 0) {
30
+ throw new RangeError(`${name} must be a positive safe integer`);
31
+ }
32
+ return value;
33
+ }
34
+
35
+ function positiveDuration(value: number): number {
36
+ if (!Number.isFinite(value) || value <= 0) {
37
+ throw new RangeError('cache ttlMs must be a positive finite number');
38
+ }
39
+ return value;
40
+ }
41
+
42
+ /**
43
+ * A process-local TTL store with bounded least-recently-used eviction.
44
+ *
45
+ * The default is deliberately finite. Expired entries are removed on access;
46
+ * writes evict the oldest live-or-expired entry until the bound is restored.
47
+ */
48
+ export function memoryStore(options?: { readonly maxEntries?: number }): CacheStore {
49
+ const maxEntries = positiveInteger(options?.maxEntries ?? DEFAULT_MAX_ENTRIES, 'cache maxEntries');
50
+ const entries = new Map<string, MemoryEntry>();
51
+
52
+ return {
53
+ async get(key) {
54
+ const entry = entries.get(key);
55
+ if (entry === undefined) return undefined;
56
+ if (entry.expiresAt <= Date.now()) {
57
+ entries.delete(key);
58
+ return undefined;
59
+ }
60
+
61
+ entries.delete(key);
62
+ entries.set(key, entry);
63
+ return entry.value;
64
+ },
65
+
66
+ async set(key, value, ttlMs, tags) {
67
+ entries.delete(key);
68
+ entries.set(key, {
69
+ value,
70
+ expiresAt: Date.now() + positiveDuration(ttlMs),
71
+ tags: [...tags],
72
+ });
73
+
74
+ while (entries.size > maxEntries) {
75
+ const oldest = entries.keys().next().value;
76
+ if (oldest === undefined) break;
77
+ entries.delete(oldest);
78
+ }
79
+ },
80
+
81
+ async invalidateTags(tags) {
82
+ if (tags.length === 0) return;
83
+ const invalidated = new Set(tags);
84
+ for (const [key, entry] of entries) {
85
+ if (entry.tags.some(tag => invalidated.has(tag))) entries.delete(key);
86
+ }
87
+ },
88
+ };
89
+ }
90
+
91
+ function frame(tag: string, payload = ''): string {
92
+ return `${tag}:${String(payload.length)}:${payload}`;
93
+ }
94
+
95
+ function binary(kind: string, value: Uint8Array): string {
96
+ const encoded = [...value].map(byte => byte.toString(16).padStart(2, '0')).join('');
97
+ return frame('x', frame('t', kind) + frame('v', encoded));
98
+ }
99
+
100
+ function taggedValue(value: unknown, ancestors: Set<object>): string {
101
+ if (value === null) return frame('z');
102
+ if (value === undefined) return frame('u');
103
+ if (typeof value === 'string') return frame('s', value);
104
+ if (typeof value === 'number') return frame('n', String(value));
105
+ if (typeof value === 'bigint') return frame('i', String(value));
106
+ if (typeof value === 'boolean') return frame('b', String(value));
107
+ if (typeof value === 'symbol' || typeof value === 'function') {
108
+ throw new TypeError(`cache keys cannot contain ${typeof value} values`);
109
+ }
110
+
111
+ if (value instanceof Date) return frame('d', String(value.getTime()));
112
+ if (value instanceof ArrayBuffer) return binary('ArrayBuffer', new Uint8Array(value));
113
+ if (ArrayBuffer.isView(value)) {
114
+ return binary(
115
+ Object.prototype.toString.call(value),
116
+ new Uint8Array(value.buffer, value.byteOffset, value.byteLength),
117
+ );
118
+ }
119
+ if (ancestors.has(value)) throw new TypeError('cache keys cannot contain cyclic values');
120
+ ancestors.add(value);
121
+
122
+ try {
123
+ if (Array.isArray(value)) {
124
+ return frame('a', value.map(item => frame('e', taggedValue(item, ancestors))).join(''));
125
+ }
126
+ if (value instanceof Map) {
127
+ const pairs = [...value].map(
128
+ ([key, item]) => frame('k', taggedValue(key, ancestors)) + frame('v', taggedValue(item, ancestors)),
129
+ );
130
+ return frame('m', pairs.toSorted().join(''));
131
+ }
132
+ if (value instanceof Set) {
133
+ return frame(
134
+ 'q',
135
+ [...value]
136
+ .map(item => taggedValue(item, ancestors))
137
+ .toSorted()
138
+ .map(item => frame('e', item))
139
+ .join(''),
140
+ );
141
+ }
142
+
143
+ const kind = Object.prototype.toString.call(value);
144
+ const properties = Object.keys(value)
145
+ .toSorted()
146
+ .map(key => frame('k', taggedValue(key, ancestors)) + frame('v', taggedValue(Reflect.get(value, key), ancestors)))
147
+ .join('');
148
+ return frame('o', frame('t', kind) + properties);
149
+ } finally {
150
+ ancestors.delete(value);
151
+ }
152
+ }
153
+
154
+ function stableValue(value: unknown): string {
155
+ return taggedValue(value, new Set());
156
+ }
157
+
158
+ function segment(name: string, value: unknown): string {
159
+ return frame(name, stableValue(value));
160
+ }
161
+
162
+ export function resultCacheKey(input: {
163
+ readonly dialect: DialectTarget;
164
+ readonly schema: SchemaIR;
165
+ readonly table: string;
166
+ readonly filters?: readonly string[];
167
+ readonly query: CompiledQuery;
168
+ }): string {
169
+ return [
170
+ 'z1',
171
+ segment('dialect', dialectName(input.dialect)),
172
+ segment('fingerprint', input.schema),
173
+ segment('table', input.table),
174
+ segment('filters', input.filters ?? []),
175
+ segment('text', input.query.text),
176
+ segment('params', input.query.parameters),
177
+ ].join('|');
178
+ }
179
+
180
+ export function cacheTags(table: string, callerTags: readonly string[] = []): readonly string[] {
181
+ return [...new Set([`table:${table}`, ...callerTags])];
182
+ }
183
+
184
+ /** Fresh row and array identities without the cost or promise of a deep clone. */
185
+ export function copyCachedRows(value: unknown): unknown {
186
+ if (!Array.isArray(value)) return value;
187
+ return value.map(row => (isRecord(row) ? { ...row } : row));
188
+ }
@@ -0,0 +1,6 @@
1
+ import { type Driver } from '../index.js';
2
+
3
+ /** A driver that can pin every query in a callback to one database transaction. */
4
+ export interface TransactionalDriver extends Driver {
5
+ transaction<Result>(run: (driver: Driver) => Promise<Result>): Promise<Result>;
6
+ }