@happyvertical/smrt-core 0.60.9 → 0.60.10

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 (67) hide show
  1. package/agents/browser-entry.md +10 -0
  2. package/agents/change-feed.md +33 -0
  3. package/agents/object-runtime.md +8 -0
  4. package/agents/schema-paths.md +6 -0
  5. package/dist/browser.d.ts +2 -1
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +3 -2
  8. package/dist/cascade.d.ts +17 -3
  9. package/dist/cascade.d.ts.map +1 -1
  10. package/dist/cascade.js +30 -15
  11. package/dist/cascade.js.map +1 -1
  12. package/dist/collection.d.ts.map +1 -1
  13. package/dist/collection.js +8 -4
  14. package/dist/collection.js.map +1 -1
  15. package/dist/embedded-write-queue.d.ts +18 -7
  16. package/dist/embedded-write-queue.d.ts.map +1 -1
  17. package/dist/embedded-write-queue.js +40 -10
  18. package/dist/embedded-write-queue.js.map +1 -1
  19. package/dist/index.d.ts +2 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -2
  22. package/dist/manifest/static-manifest.d.ts.map +1 -1
  23. package/dist/manifest/static-manifest.js +25 -7
  24. package/dist/manifest/static-manifest.js.map +1 -1
  25. package/dist/manifest/store.js +1 -1
  26. package/dist/manifest/store.js.map +1 -1
  27. package/dist/manifest.json +39 -7
  28. package/dist/migrations/differ.d.ts +4 -0
  29. package/dist/migrations/differ.d.ts.map +1 -1
  30. package/dist/migrations/differ.js +25 -9
  31. package/dist/migrations/differ.js.map +1 -1
  32. package/dist/migrations/orchestrate.d.ts +1 -1
  33. package/dist/migrations/orchestrate.d.ts.map +1 -1
  34. package/dist/migrations/orchestrate.js +5 -14
  35. package/dist/migrations/orchestrate.js.map +1 -1
  36. package/dist/native-duckdb.d.ts.map +1 -1
  37. package/dist/native-duckdb.js +5 -1
  38. package/dist/native-duckdb.js.map +1 -1
  39. package/dist/object.d.ts +26 -0
  40. package/dist/object.d.ts.map +1 -1
  41. package/dist/object.js +215 -55
  42. package/dist/object.js.map +1 -1
  43. package/dist/schema/generator.d.ts.map +1 -1
  44. package/dist/schema/generator.js +8 -4
  45. package/dist/schema/generator.js.map +1 -1
  46. package/dist/scripts/browser-entry-evaluation.mjs +63 -8
  47. package/dist/scripts/browser-unbundled-dev.mjs +5 -2
  48. package/dist/scripts/fixtures/browser-pglite-cascade.ts +107 -0
  49. package/dist/scripts/fixtures/pglite-peer/package.json +9 -0
  50. package/dist/scripts/fixtures/pglite-peer/peer.js +1 -0
  51. package/dist/scripts/fixtures/pglite-peer/pnpm-lock.yaml +22 -0
  52. package/dist/scripts/fixtures/pglite-peer/pnpm-workspace.yaml +1 -0
  53. package/dist/smrt-knowledge.json +19 -11
  54. package/dist/smrt-knowledge.okf.md +181 -0
  55. package/dist/sync-device.d.ts +6 -0
  56. package/dist/sync-device.d.ts.map +1 -0
  57. package/dist/sync-device.js +18 -0
  58. package/dist/sync-device.js.map +1 -0
  59. package/dist/sync-tombstones.d.ts +30 -0
  60. package/dist/sync-tombstones.d.ts.map +1 -0
  61. package/dist/sync-tombstones.js +112 -0
  62. package/dist/sync-tombstones.js.map +1 -0
  63. package/dist/system/schema.d.ts +5 -1
  64. package/dist/system/schema.d.ts.map +1 -1
  65. package/dist/system/schema.js +21 -4
  66. package/dist/system/schema.js.map +1 -1
  67. package/package.json +11 -11
@@ -0,0 +1,181 @@
1
+ ---
2
+ type: "Component"
3
+ title: "@happyvertical/smrt-core"
4
+ description: "Core SMRT framework for business objects, persistence, and generated REST, CLI, MCP, and AI interfaces"
5
+ status: "stable"
6
+ generated:
7
+ by: "smrt/scripts/generate-knowledge-okf.ts"
8
+ at: "2026-11-05T20:27:03Z"
9
+ audience: "internal"
10
+ evidence_level: "source-inspected"
11
+ source_revision: "sha256:53b8a0911a425be4d61221228978265f10d7550b534098ed74d32c2ef13327fd"
12
+ sources:
13
+ - id: "manifest"
14
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/package.json"
15
+ title: "@happyvertical/smrt-core package manifest"
16
+ - id: "agents"
17
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/AGENTS.md"
18
+ title: "@happyvertical/smrt-core packages/core/AGENTS.md"
19
+ - id: "agents-2"
20
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/browser-entry.md"
21
+ title: "@happyvertical/smrt-core packages/core/agents/browser-entry.md"
22
+ - id: "agents-3"
23
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/build-knowledge.md"
24
+ title: "@happyvertical/smrt-core packages/core/agents/build-knowledge.md"
25
+ - id: "agents-4"
26
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/change-feed.md"
27
+ title: "@happyvertical/smrt-core packages/core/agents/change-feed.md"
28
+ - id: "agents-5"
29
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/change-signals.md"
30
+ title: "@happyvertical/smrt-core packages/core/agents/change-signals.md"
31
+ - id: "agents-6"
32
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/collection-reads.md"
33
+ title: "@happyvertical/smrt-core packages/core/agents/collection-reads.md"
34
+ - id: "agents-7"
35
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/cookbook-engine.md"
36
+ title: "@happyvertical/smrt-core packages/core/agents/cookbook-engine.md"
37
+ - id: "agents-8"
38
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/cookbook.md"
39
+ title: "@happyvertical/smrt-core packages/core/agents/cookbook.md"
40
+ - id: "agents-9"
41
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/data-query.md"
42
+ title: "@happyvertical/smrt-core packages/core/agents/data-query.md"
43
+ - id: "agents-10"
44
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/deployment-phases.md"
45
+ title: "@happyvertical/smrt-core packages/core/agents/deployment-phases.md"
46
+ - id: "agents-11"
47
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/generators.md"
48
+ title: "@happyvertical/smrt-core packages/core/agents/generators.md"
49
+ - id: "agents-12"
50
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/memory.md"
51
+ title: "@happyvertical/smrt-core packages/core/agents/memory.md"
52
+ - id: "agents-13"
53
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/model-moves.md"
54
+ title: "@happyvertical/smrt-core packages/core/agents/model-moves.md"
55
+ - id: "agents-14"
56
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/object-runtime.md"
57
+ title: "@happyvertical/smrt-core packages/core/agents/object-runtime.md"
58
+ - id: "agents-15"
59
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/query-bounds.md"
60
+ title: "@happyvertical/smrt-core packages/core/agents/query-bounds.md"
61
+ - id: "agents-16"
62
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/recipes.md"
63
+ title: "@happyvertical/smrt-core packages/core/agents/recipes.md"
64
+ - id: "agents-17"
65
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/registry-snapshot.md"
66
+ title: "@happyvertical/smrt-core packages/core/agents/registry-snapshot.md"
67
+ - id: "agents-18"
68
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/revision-guard.md"
69
+ title: "@happyvertical/smrt-core packages/core/agents/revision-guard.md"
70
+ - id: "agents-19"
71
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/run-once.md"
72
+ title: "@happyvertical/smrt-core packages/core/agents/run-once.md"
73
+ - id: "agents-20"
74
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/schema-paths.md"
75
+ title: "@happyvertical/smrt-core packages/core/agents/schema-paths.md"
76
+ - id: "agents-21"
77
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/system-diagnostics.md"
78
+ title: "@happyvertical/smrt-core packages/core/agents/system-diagnostics.md"
79
+ - id: "agents-22"
80
+ resource: "https://github.com/happyvertical/smrt/blob/main/packages/core/agents/test-types.md"
81
+ title: "@happyvertical/smrt-core packages/core/agents/test-types.md"
82
+ ---
83
+
84
+ # @happyvertical/smrt-core
85
+
86
+ ## Package
87
+
88
+ - Directory: `packages/core`
89
+ - Private: no
90
+ - Object source: `domain-artifact`
91
+ - Evidence level: `source-inspected`
92
+
93
+ ## Documentation
94
+
95
+ - `packages/core/AGENTS.md`
96
+ - `packages/core/agents/browser-entry.md`
97
+ - `packages/core/agents/build-knowledge.md`
98
+ - `packages/core/agents/change-feed.md`
99
+ - `packages/core/agents/change-signals.md`
100
+ - `packages/core/agents/collection-reads.md`
101
+ - `packages/core/agents/cookbook-engine.md`
102
+ - `packages/core/agents/cookbook.md`
103
+ - `packages/core/agents/data-query.md`
104
+ - `packages/core/agents/deployment-phases.md`
105
+ - `packages/core/agents/generators.md`
106
+ - `packages/core/agents/memory.md`
107
+ - `packages/core/agents/model-moves.md`
108
+ - `packages/core/agents/object-runtime.md`
109
+ - `packages/core/agents/query-bounds.md`
110
+ - `packages/core/agents/recipes.md`
111
+ - `packages/core/agents/registry-snapshot.md`
112
+ - `packages/core/agents/revision-guard.md`
113
+ - `packages/core/agents/run-once.md`
114
+ - `packages/core/agents/schema-paths.md`
115
+ - `packages/core/agents/system-diagnostics.md`
116
+ - `packages/core/agents/test-types.md`
117
+
118
+ ## Framework dependencies
119
+
120
+ - [smrt-config](/components/smrt-config.md)
121
+ - [smrt-scanner](/components/smrt-scanner.md)
122
+ - [smrt-types](/components/smrt-types.md)
123
+
124
+ ## SDK dependencies
125
+
126
+ - `@happyvertical/ai`
127
+ - `@happyvertical/files`
128
+ - `@happyvertical/json`
129
+ - `@happyvertical/logger`
130
+ - `@happyvertical/sql`
131
+ - `@happyvertical/utils`
132
+
133
+ ## Exports
134
+
135
+ - `.`
136
+ - `./byo-inference`
137
+ - `./consumer-plugin`
138
+ - `./cookbook`
139
+ - `./cookbook/engine`
140
+ - `./cookbook/v1.schema.json`
141
+ - `./filesystem`
142
+ - `./generators`
143
+ - `./generators/custom-action`
144
+ - `./generators/mcp`
145
+ - `./generators/rest`
146
+ - `./generators/swagger`
147
+ - `./knowledge`
148
+ - `./manifest`
149
+ - `./manifest.json`
150
+ - `./manifest/discover-base-classes`
151
+ - `./manifest/discover-smrt-packages`
152
+ - `./mcp-serializer`
153
+ - `./migrations`
154
+ - `./prebuild`
155
+ - `./prebuild/cli`
156
+ - `./recipe-demo`
157
+ - `./runtime`
158
+ - `./scanner`
159
+ - `./scanner/types`
160
+ - `./schema`
161
+ - `./schema/utils`
162
+ - `./smrt-error`
163
+ - `./smrt-knowledge.json`
164
+ - `./table-cache`
165
+ - `./testing`
166
+ - `./ui-metadata`
167
+ - `./utils`
168
+ - `./utils/database-url`
169
+ - `./utils/import-workspace-module`
170
+ - `./vite`
171
+ - `./vite-plugin`
172
+
173
+ ## Models
174
+
175
+ - `SmrtClass` (collection `smrtclasses`)
176
+ - `SmrtCollection` (collection `smrtcollections`, extends `SmrtClass`)
177
+ - `SmrtHierarchical` (collection `smrthierarchicals`, extends `SmrtObject`)
178
+ - `SmrtJunction` (collection `smrtjunctions`, extends `SmrtJunctionBase`)
179
+ - `SmrtJunctionBase` (collection `smrtjunctionbases`, extends `SmrtCollection`)
180
+ - `SmrtObject` (collection `smrtobjects`, extends `SmrtClass`)
181
+ - `SmrtPolymorphicAssociation` (collection `smrtpolymorphicassociations`, extends `SmrtObject`)
@@ -0,0 +1,6 @@
1
+ import type { DatabaseInterface } from '@happyvertical/sql';
2
+ /** Configure the identity used for local tracked writes on this database handle. */
3
+ export declare function setDatabaseSyncDeviceId(db: DatabaseInterface, deviceId: string): void;
4
+ export declare function getDatabaseSyncDeviceId(db: DatabaseInterface): string | undefined;
5
+ export declare function inheritDatabaseSyncDeviceId(db: DatabaseInterface, bound: DatabaseInterface): void;
6
+ //# sourceMappingURL=sync-device.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-device.d.ts","sourceRoot":"","sources":["../src/sync-device.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,oFAAoF;AACpF,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,MAAM,GACf,IAAI,CAUN;AAED,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,iBAAiB,GACpB,MAAM,GAAG,SAAS,CAEpB;AAED,wBAAgB,2BAA2B,CACzC,EAAE,EAAE,iBAAiB,EACrB,KAAK,EAAE,iBAAiB,GACvB,IAAI,CAGN"}
@@ -0,0 +1,18 @@
1
+ //#region src/sync-device.ts
2
+ var deviceIds = /* @__PURE__ */ new WeakMap();
3
+ /** Configure the identity used for local tracked writes on this database handle. */
4
+ function setDatabaseSyncDeviceId(db, deviceId) {
5
+ if (typeof deviceId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(deviceId)) throw new TypeError("The sync device ID must be a UUID");
6
+ deviceIds.set(db, deviceId.toLowerCase());
7
+ }
8
+ function getDatabaseSyncDeviceId(db) {
9
+ return deviceIds.get(db);
10
+ }
11
+ function inheritDatabaseSyncDeviceId(db, bound) {
12
+ const deviceId = getDatabaseSyncDeviceId(db);
13
+ if (deviceId !== void 0) deviceIds.set(bound, deviceId);
14
+ }
15
+ //#endregion
16
+ export { getDatabaseSyncDeviceId, inheritDatabaseSyncDeviceId, setDatabaseSyncDeviceId };
17
+
18
+ //# sourceMappingURL=sync-device.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-device.js","names":[],"sources":["../src/sync-device.ts"],"sourcesContent":["import type { DatabaseInterface } from '@happyvertical/sql';\n\nconst deviceIds = new WeakMap<DatabaseInterface, string>();\n\n/** Configure the identity used for local tracked writes on this database handle. */\nexport function setDatabaseSyncDeviceId(\n db: DatabaseInterface,\n deviceId: string,\n): void {\n if (\n typeof deviceId !== 'string' ||\n !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(\n deviceId,\n )\n ) {\n throw new TypeError('The sync device ID must be a UUID');\n }\n deviceIds.set(db, deviceId.toLowerCase());\n}\n\nexport function getDatabaseSyncDeviceId(\n db: DatabaseInterface,\n): string | undefined {\n return deviceIds.get(db);\n}\n\nexport function inheritDatabaseSyncDeviceId(\n db: DatabaseInterface,\n bound: DatabaseInterface,\n): void {\n const deviceId = getDatabaseSyncDeviceId(db);\n if (deviceId !== undefined) deviceIds.set(bound, deviceId);\n}\n"],"mappings":";AAEA,IAAM,4BAAY,IAAI,QAAmC;;AAGzD,SAAgB,wBACd,IACA,UACM;CACN,IACE,OAAO,aAAa,YACpB,CAAC,kEAAkE,KACjE,QACF,GAEA,MAAM,IAAI,UAAU,mCAAmC;CAEzD,UAAU,IAAI,IAAI,SAAS,YAAY,CAAC;AAC1C;AAEA,SAAgB,wBACd,IACoB;CACpB,OAAO,UAAU,IAAI,EAAE;AACzB;AAEA,SAAgB,4BACd,IACA,OACM;CACN,MAAM,WAAW,wBAAwB,EAAE;CAC3C,IAAI,aAAa,KAAA,GAAW,UAAU,IAAI,OAAO,QAAQ;AAC3D"}
@@ -0,0 +1,30 @@
1
+ import type { DatabaseInterface } from '@happyvertical/sql';
2
+ import { RuntimeError } from './errors.js';
3
+ export declare const SYNC_TOMBSTONES_TABLE = "_smrt_sync_tombstones";
4
+ export interface SyncRevision {
5
+ originDeviceId: string;
6
+ revision: number;
7
+ }
8
+ export interface SyncRowIdentity {
9
+ tableName: string;
10
+ model: string;
11
+ tenantColumn?: string;
12
+ sti?: boolean;
13
+ }
14
+ /** DuckDB binds JavaScript integer numbers as INT32, even for BIGINT columns. */
15
+ export declare function bindSyncRevision(revision: number, nativeDuckDb: boolean): number | bigint;
16
+ export declare function compareSyncRevisions(left: SyncRevision, right: SyncRevision): number;
17
+ export declare function validateSyncRevision(originDeviceId: unknown, revision: unknown): SyncRevision;
18
+ export declare function syncRevisionConflict(id: unknown): RuntimeError;
19
+ export declare function lockSyncRowIdentity(db: DatabaseInterface, tableName: string, rowId: string): Promise<void>;
20
+ export declare function putSyncTombstone(db: DatabaseInterface, identity: SyncRowIdentity, rowId: string, model: string, tenantId: string | null, revision: SyncRevision): Promise<void>;
21
+ /** Must run inside the same transaction as the enclosing delete/cascade. */
22
+ export declare function deleteSyncRows(db: DatabaseInterface, identity: SyncRowIdentity, filter: Record<string, unknown>, didDelete: (count: number) => void, incoming?: {
23
+ rowId: string;
24
+ tenantId: string | null;
25
+ revision: SyncRevision;
26
+ }): Promise<{
27
+ affected: number;
28
+ }>;
29
+ export declare function nullSyncReference(db: DatabaseInterface, tableName: string, column: string, filter: Record<string, unknown>, didUpdate: (count: number) => void): Promise<void>;
30
+ //# sourceMappingURL=sync-tombstones.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-tombstones.d.ts","sourceRoot":"","sources":["../src/sync-tombstones.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAK5D,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAQ5D,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAUD,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,OAAO,mBAEvE;AAMD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,UAS3E;AAED,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,OAAO,EACvB,QAAQ,EAAE,OAAO,GAChB,YAAY,CAed;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,OAAO,gBAM/C;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAMf;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,eAAe,EACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,QAAQ,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED,4EAA4E;AAC5E,wBAAsB,cAAc,CAClC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,EAClC,QAAQ,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,YAAY,CAAA;CAAE,GAC5E,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6D/B;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,iBAAiB,EACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GACjC,OAAO,CAAC,IAAI,CAAC,CAiCf"}
@@ -0,0 +1,112 @@
1
+ import { buildWhere } from "./host.js";
2
+ import { isJsonAdapterHandle, isKnownNativeDuckDbHandle } from "./native-duckdb.js";
3
+ import { getDatabaseSyncDeviceId } from "./sync-device.js";
4
+ import { isPostgresDatabase, usesPostgresDialect } from "./embedded-write-queue.js";
5
+ import { RuntimeError, ValidationError } from "./errors.js";
6
+ //#region src/sync-tombstones.ts
7
+ var SYNC_TOMBSTONES_TABLE = "_smrt_sync_tombstones";
8
+ function dialect(db) {
9
+ return usesPostgresDialect(db) ? "postgres" : isKnownNativeDuckDbHandle(db) ? "duckdb" : "sqlite";
10
+ }
11
+ /** DuckDB binds JavaScript integer numbers as INT32, even for BIGINT columns. */
12
+ function bindSyncRevision(revision, nativeDuckDb) {
13
+ return nativeDuckDb ? BigInt(revision) : revision;
14
+ }
15
+ function quote(name) {
16
+ return `"${name.replaceAll("\"", "\"\"")}"`;
17
+ }
18
+ function compareSyncRevisions(left, right) {
19
+ return left.revision - right.revision || (left.originDeviceId > right.originDeviceId ? 1 : left.originDeviceId < right.originDeviceId ? -1 : 0);
20
+ }
21
+ function validateSyncRevision(originDeviceId, revision) {
22
+ if (typeof originDeviceId !== "string" || !originDeviceId.trim() || originDeviceId.length > 255 || typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 1) throw new ValidationError("Invalid sync revision or missing local sync device", "VALIDATION_SYNC_METADATA");
23
+ return {
24
+ originDeviceId,
25
+ revision
26
+ };
27
+ }
28
+ function syncRevisionConflict(id) {
29
+ return new RuntimeError("Incoming sync revision does not advance the row", "RUNTIME_SYNC_REVISION_CONFLICT", { id });
30
+ }
31
+ async function lockSyncRowIdentity(db, tableName, rowId) {
32
+ if (!isPostgresDatabase(db)) return;
33
+ await db.query("SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))", JSON.stringify([
34
+ SYNC_TOMBSTONES_TABLE,
35
+ tableName,
36
+ rowId
37
+ ]));
38
+ }
39
+ async function putSyncTombstone(db, identity, rowId, model, tenantId, revision) {
40
+ const filter = {
41
+ table_name: identity.tableName,
42
+ row_id: rowId
43
+ };
44
+ const old = await db.get(SYNC_TOMBSTONES_TABLE, filter);
45
+ if (old) {
46
+ if (old.model !== model || (old.tenant_id ?? null) !== tenantId || compareSyncRevisions(revision, validateSyncRevision(old.origin_device_id, Number(old.revision))) <= 0) throw syncRevisionConflict(rowId);
47
+ await db.update(SYNC_TOMBSTONES_TABLE, filter, {
48
+ origin_device_id: revision.originDeviceId,
49
+ revision: bindSyncRevision(revision.revision, isKnownNativeDuckDbHandle(db)),
50
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString()
51
+ });
52
+ return;
53
+ }
54
+ await db.insert(SYNC_TOMBSTONES_TABLE, {
55
+ ...filter,
56
+ model,
57
+ tenant_id: tenantId,
58
+ origin_device_id: revision.originDeviceId,
59
+ revision: bindSyncRevision(revision.revision, isKnownNativeDuckDbHandle(db)),
60
+ deleted_at: (/* @__PURE__ */ new Date()).toISOString()
61
+ });
62
+ }
63
+ /** Must run inside the same transaction as the enclosing delete/cascade. */
64
+ async function deleteSyncRows(db, identity, filter, didDelete, incoming) {
65
+ if (isJsonAdapterHandle(db)) {
66
+ if (incoming) throw RuntimeError.invalidState("Sync tombstones require a SQL database");
67
+ const result = await db.delete(identity.tableName, filter);
68
+ didDelete(result.affected);
69
+ return result;
70
+ }
71
+ const { sql, values } = buildWhere(filter, 1, dialect(db));
72
+ const tenant = identity.tenantColumn ? ", CAST(" + quote(identity.tenantColumn) + " AS VARCHAR) AS _sync_deleted_tenant" : "";
73
+ const discriminator = identity.sti ? ", _meta_type" : "";
74
+ const result = await db.query("DELETE FROM " + quote(identity.tableName) + " " + sql + " RETURNING CAST(id AS VARCHAR) AS _sync_deleted_id, _sync_origin_device_id, _sync_revision" + tenant + discriminator, ...values);
75
+ didDelete(result.rows.length);
76
+ for (const row of result.rows) {
77
+ if (!incoming && row._sync_origin_device_id == null) continue;
78
+ const rowId = String(row._sync_deleted_id);
79
+ const model = typeof row._meta_type === "string" && row._meta_type.includes(":") ? row._meta_type : identity.model;
80
+ const tenantId = row._sync_deleted_tenant ?? null;
81
+ if (incoming && (rowId !== incoming.rowId || model !== identity.model || tenantId !== incoming.tenantId)) throw syncRevisionConflict(rowId);
82
+ const prior = row._sync_origin_device_id == null ? void 0 : validateSyncRevision(row._sync_origin_device_id, Number(row._sync_revision));
83
+ const revision = incoming?.revision ?? validateSyncRevision(getDatabaseSyncDeviceId(db), (prior?.revision ?? 0) + 1);
84
+ if (prior && compareSyncRevisions(revision, prior) <= 0) throw syncRevisionConflict(rowId);
85
+ await putSyncTombstone(db, identity, rowId, model, tenantId, revision);
86
+ }
87
+ return { affected: result.rows.length };
88
+ }
89
+ async function nullSyncReference(db, tableName, column, filter, didUpdate) {
90
+ if (isJsonAdapterHandle(db)) {
91
+ didUpdate((await db.update(tableName, filter, { [column]: null })).affected);
92
+ return;
93
+ }
94
+ const engine = dialect(db);
95
+ const parameter = (index) => engine === "postgres" ? `$${index}` : "?";
96
+ const { sql, values } = buildWhere(filter, 3, engine);
97
+ const device = getDatabaseSyncDeviceId(db);
98
+ const tracked = "_sync_origin_device_id IS NOT NULL";
99
+ const nextTimestamp = engine === "sqlite" ? `max(${parameter(1)}, coalesce(strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds'), ''))` : `greatest(CAST(${parameter(1)} AS ${engine === "postgres" ? "TIMESTAMPTZ" : "TIMESTAMP"}), updated_at + INTERVAL '1 millisecond')`;
100
+ const result = await db.query(`UPDATE ${quote(tableName)}
101
+ SET ${quote(column)} = NULL,
102
+ updated_at = CASE WHEN ${tracked} THEN ${nextTimestamp} ELSE updated_at END,
103
+ _sync_origin_device_id = CASE WHEN ${tracked} THEN COALESCE(${parameter(2)}, _sync_origin_device_id) ELSE NULL END,
104
+ _sync_revision = CASE WHEN ${tracked} THEN _sync_revision + 1 ELSE _sync_revision END
105
+ ${sql} RETURNING _sync_origin_device_id, _sync_revision`, (/* @__PURE__ */ new Date()).toISOString(), device ?? null, ...values);
106
+ didUpdate(result.rows.length);
107
+ for (const row of result.rows) if (row._sync_origin_device_id != null) validateSyncRevision(device, Number(row._sync_revision));
108
+ }
109
+ //#endregion
110
+ export { SYNC_TOMBSTONES_TABLE, bindSyncRevision, compareSyncRevisions, deleteSyncRows, lockSyncRowIdentity, nullSyncReference, putSyncTombstone, syncRevisionConflict, validateSyncRevision };
111
+
112
+ //# sourceMappingURL=sync-tombstones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-tombstones.js","names":[],"sources":["../src/sync-tombstones.ts"],"sourcesContent":["import type { DatabaseInterface } from '@happyvertical/sql';\nimport {\n isPostgresDatabase,\n usesPostgresDialect,\n} from './embedded-write-queue.js';\nimport { RuntimeError, ValidationError } from './errors.js';\nimport { buildWhere } from './host.js';\nimport {\n isJsonAdapterHandle,\n isKnownNativeDuckDbHandle,\n} from './native-duckdb.js';\nimport { getDatabaseSyncDeviceId } from './sync-device.js';\n\nexport const SYNC_TOMBSTONES_TABLE = '_smrt_sync_tombstones';\n\nexport interface SyncRevision {\n originDeviceId: string;\n revision: number;\n}\n\nexport interface SyncRowIdentity {\n tableName: string;\n model: string;\n tenantColumn?: string;\n sti?: boolean;\n}\n\nfunction dialect(db: DatabaseInterface) {\n return usesPostgresDialect(db)\n ? 'postgres'\n : isKnownNativeDuckDbHandle(db)\n ? 'duckdb'\n : 'sqlite';\n}\n\n/** DuckDB binds JavaScript integer numbers as INT32, even for BIGINT columns. */\nexport function bindSyncRevision(revision: number, nativeDuckDb: boolean) {\n return nativeDuckDb ? BigInt(revision) : revision;\n}\n\nfunction quote(name: string): string {\n return `\"${name.replaceAll('\"', '\"\"')}\"`;\n}\n\nexport function compareSyncRevisions(left: SyncRevision, right: SyncRevision) {\n return (\n left.revision - right.revision ||\n (left.originDeviceId > right.originDeviceId\n ? 1\n : left.originDeviceId < right.originDeviceId\n ? -1\n : 0)\n );\n}\n\nexport function validateSyncRevision(\n originDeviceId: unknown,\n revision: unknown,\n): SyncRevision {\n if (\n typeof originDeviceId !== 'string' ||\n !originDeviceId.trim() ||\n originDeviceId.length > 255 ||\n typeof revision !== 'number' ||\n !Number.isSafeInteger(revision) ||\n revision < 1\n ) {\n throw new ValidationError(\n 'Invalid sync revision or missing local sync device',\n 'VALIDATION_SYNC_METADATA',\n );\n }\n return { originDeviceId, revision };\n}\n\nexport function syncRevisionConflict(id: unknown) {\n return new RuntimeError(\n 'Incoming sync revision does not advance the row',\n 'RUNTIME_SYNC_REVISION_CONFLICT',\n { id },\n );\n}\n\nexport async function lockSyncRowIdentity(\n db: DatabaseInterface,\n tableName: string,\n rowId: string,\n): Promise<void> {\n if (!isPostgresDatabase(db)) return;\n await db.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))',\n JSON.stringify([SYNC_TOMBSTONES_TABLE, tableName, rowId]),\n );\n}\n\nexport async function putSyncTombstone(\n db: DatabaseInterface,\n identity: SyncRowIdentity,\n rowId: string,\n model: string,\n tenantId: string | null,\n revision: SyncRevision,\n): Promise<void> {\n const filter = { table_name: identity.tableName, row_id: rowId };\n const old = await db.get(SYNC_TOMBSTONES_TABLE, filter);\n if (old) {\n if (\n old.model !== model ||\n (old.tenant_id ?? null) !== tenantId ||\n compareSyncRevisions(\n revision,\n validateSyncRevision(old.origin_device_id, Number(old.revision)),\n ) <= 0\n ) {\n throw syncRevisionConflict(rowId);\n }\n await db.update(SYNC_TOMBSTONES_TABLE, filter, {\n origin_device_id: revision.originDeviceId,\n revision: bindSyncRevision(\n revision.revision,\n isKnownNativeDuckDbHandle(db),\n ),\n deleted_at: new Date().toISOString(),\n });\n return;\n }\n await db.insert(SYNC_TOMBSTONES_TABLE, {\n ...filter,\n model,\n tenant_id: tenantId,\n origin_device_id: revision.originDeviceId,\n revision: bindSyncRevision(\n revision.revision,\n isKnownNativeDuckDbHandle(db),\n ),\n deleted_at: new Date().toISOString(),\n });\n}\n\n/** Must run inside the same transaction as the enclosing delete/cascade. */\nexport async function deleteSyncRows(\n db: DatabaseInterface,\n identity: SyncRowIdentity,\n filter: Record<string, unknown>,\n didDelete: (count: number) => void,\n incoming?: { rowId: string; tenantId: string | null; revision: SyncRevision },\n): Promise<{ affected: number }> {\n if (isJsonAdapterHandle(db)) {\n if (incoming)\n throw RuntimeError.invalidState('Sync tombstones require a SQL database');\n const result = await db.delete(identity.tableName, filter);\n didDelete(result.affected);\n return result;\n }\n const { sql, values } = buildWhere(filter, 1, dialect(db));\n const tenant = identity.tenantColumn\n ? ', CAST(' +\n quote(identity.tenantColumn) +\n ' AS VARCHAR) AS _sync_deleted_tenant'\n : '';\n const discriminator = identity.sti ? ', _meta_type' : '';\n const result = await db.query(\n 'DELETE FROM ' +\n quote(identity.tableName) +\n ' ' +\n sql +\n ' RETURNING CAST(id AS VARCHAR) AS _sync_deleted_id, _sync_origin_device_id, _sync_revision' +\n tenant +\n discriminator,\n ...values,\n );\n didDelete(result.rows.length);\n for (const row of result.rows) {\n if (!incoming && row._sync_origin_device_id == null) continue;\n const rowId = String(row._sync_deleted_id);\n const model =\n typeof row._meta_type === 'string' && row._meta_type.includes(':')\n ? row._meta_type\n : identity.model;\n const tenantId = (row._sync_deleted_tenant ?? null) as string | null;\n if (\n incoming &&\n (rowId !== incoming.rowId ||\n model !== identity.model ||\n tenantId !== incoming.tenantId)\n ) {\n throw syncRevisionConflict(rowId);\n }\n const prior =\n row._sync_origin_device_id == null\n ? undefined\n : validateSyncRevision(\n row._sync_origin_device_id,\n Number(row._sync_revision),\n );\n const revision =\n incoming?.revision ??\n validateSyncRevision(\n getDatabaseSyncDeviceId(db),\n (prior?.revision ?? 0) + 1,\n );\n if (prior && compareSyncRevisions(revision, prior) <= 0) {\n throw syncRevisionConflict(rowId);\n }\n await putSyncTombstone(db, identity, rowId, model, tenantId, revision);\n }\n return { affected: result.rows.length };\n}\n\nexport async function nullSyncReference(\n db: DatabaseInterface,\n tableName: string,\n column: string,\n filter: Record<string, unknown>,\n didUpdate: (count: number) => void,\n): Promise<void> {\n if (isJsonAdapterHandle(db)) {\n const result = await db.update(tableName, filter, { [column]: null });\n didUpdate(result.affected);\n return;\n }\n const engine = dialect(db);\n const parameter = (index: number) =>\n engine === 'postgres' ? `$${index}` : '?';\n const { sql, values } = buildWhere(filter, 3, engine);\n const device = getDatabaseSyncDeviceId(db);\n const tracked = '_sync_origin_device_id IS NOT NULL';\n const nextTimestamp =\n engine === 'sqlite'\n ? `max(${parameter(1)}, coalesce(strftime('%Y-%m-%dT%H:%M:%fZ', updated_at, '+0.001 seconds'), ''))`\n : `greatest(CAST(${parameter(1)} AS ${engine === 'postgres' ? 'TIMESTAMPTZ' : 'TIMESTAMP'}), updated_at + INTERVAL '1 millisecond')`;\n const result = await db.query(\n `UPDATE ${quote(tableName)}\n SET ${quote(column)} = NULL,\n updated_at = CASE WHEN ${tracked} THEN ${nextTimestamp} ELSE updated_at END,\n _sync_origin_device_id = CASE WHEN ${tracked} THEN COALESCE(${parameter(2)}, _sync_origin_device_id) ELSE NULL END,\n _sync_revision = CASE WHEN ${tracked} THEN _sync_revision + 1 ELSE _sync_revision END\n ${sql} RETURNING _sync_origin_device_id, _sync_revision`,\n new Date().toISOString(),\n device ?? null,\n ...values,\n );\n didUpdate(result.rows.length);\n for (const row of result.rows) {\n if (row._sync_origin_device_id != null) {\n validateSyncRevision(device, Number(row._sync_revision));\n }\n }\n}\n"],"mappings":"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"}
@@ -260,6 +260,8 @@ export declare const RUN_ONCE_CLAIMS_TABLE = "_smrt_run_once_claims";
260
260
  * can always run again.
261
261
  */
262
262
  export declare const CREATE_SMRT_RUN_ONCE_CLAIMS_TABLE = "\nCREATE TABLE IF NOT EXISTS _smrt_run_once_claims (\n claim_key TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n actor TEXT NOT NULL,\n content_digest TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'in_progress',\n result TEXT,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n completed_at TIMESTAMP\n);\n\nCREATE INDEX IF NOT EXISTS idx_smrt_run_once_claims_tenant\n ON _smrt_run_once_claims(tenant_id, created_at);\n\n-- Retention predicate for pruneRunOnceClaims() (#3080): completed claims are\n-- aged out on completed_at, scoped to status = 'completed' so an in_progress\n-- claim (completed_at IS NULL) is never matched by the sweep.\nCREATE INDEX IF NOT EXISTS idx_smrt_run_once_claims_status_completed\n ON _smrt_run_once_claims(status, completed_at);\n";
263
+ /** Durable sync deletion revisions, separate from live natural keys. */
264
+ export declare const CREATE_SMRT_SYNC_TOMBSTONES_TABLE = "\nCREATE TABLE IF NOT EXISTS _smrt_sync_tombstones (\n table_name TEXT NOT NULL,\n row_id TEXT NOT NULL,\n model TEXT NOT NULL,\n tenant_id TEXT,\n origin_device_id TEXT NOT NULL,\n revision INTEGER NOT NULL,\n deleted_at TIMESTAMP NOT NULL,\n PRIMARY KEY (table_name, row_id)\n);\n";
263
265
  /**
264
266
  * All system table creation statements
265
267
  */
@@ -371,8 +373,10 @@ export declare function getSystemTableDDLForEngine(ddl: string, engine: Database
371
373
  * 1.11.1 adds `idx_smrt_run_once_claims_status_completed` (#3080), the
372
374
  * `(status, completed_at)` index backing `pruneRunOnceClaims()`'s retention
373
375
  * predicate (`system/retention.ts`).
376
+ *
377
+ * 1.12.0 adds the durable tracked-deletion ledger.
374
378
  */
375
- export declare const SMRT_SCHEMA_VERSION = "1.11.1";
379
+ export declare const SMRT_SCHEMA_VERSION = "1.12.0";
376
380
  /**
377
381
  * Canonical form of the system DDL that {@link SMRT_SCHEMA_DDL_CHECKSUMS} covers.
378
382
  *
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/system/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/system/schema.ts"],"names":[],"mappings":"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"}
@@ -1087,6 +1087,19 @@ CREATE INDEX IF NOT EXISTS idx_smrt_run_once_claims_tenant
1087
1087
  CREATE INDEX IF NOT EXISTS idx_smrt_run_once_claims_status_completed
1088
1088
  ON _smrt_run_once_claims(status, completed_at);
1089
1089
  `;
1090
+ /** Durable sync deletion revisions, separate from live natural keys. */
1091
+ var CREATE_SMRT_SYNC_TOMBSTONES_TABLE = `
1092
+ CREATE TABLE IF NOT EXISTS _smrt_sync_tombstones (
1093
+ table_name TEXT NOT NULL,
1094
+ row_id TEXT NOT NULL,
1095
+ model TEXT NOT NULL,
1096
+ tenant_id TEXT,
1097
+ origin_device_id TEXT NOT NULL,
1098
+ revision INTEGER NOT NULL,
1099
+ deleted_at TIMESTAMP NOT NULL,
1100
+ PRIMARY KEY (table_name, row_id)
1101
+ );
1102
+ `;
1090
1103
  /**
1091
1104
  * All system table creation statements
1092
1105
  */
@@ -1100,7 +1113,8 @@ var ALL_SYSTEM_TABLES = [
1100
1113
  CREATE_SMRT_DISPATCH_TABLE,
1101
1114
  CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE,
1102
1115
  CREATE_SMRT_AI_USAGE_TABLE,
1103
- CREATE_SMRT_CHANGES_TABLE
1116
+ CREATE_SMRT_CHANGES_TABLE,
1117
+ CREATE_SMRT_SYNC_TOMBSTONES_TABLE
1104
1118
  ];
1105
1119
  /**
1106
1120
  * Framework-owned tables that are backed by `@smrt()` models rather than by
@@ -1233,14 +1247,17 @@ function toPostgresTenantUuid(ddl) {
1233
1247
  * 1.11.1 adds `idx_smrt_run_once_claims_status_completed` (#3080), the
1234
1248
  * `(status, completed_at)` index backing `pruneRunOnceClaims()`'s retention
1235
1249
  * predicate (`system/retention.ts`).
1250
+ *
1251
+ * 1.12.0 adds the durable tracked-deletion ledger.
1236
1252
  */
1237
- var SMRT_SCHEMA_VERSION = "1.11.1";
1253
+ var SMRT_SCHEMA_VERSION = "1.12.0";
1238
1254
  Object.freeze({
1239
1255
  "1.10.1": "f796ee3b3f7ab8b9dc659ecaa68884ec01277408c3dd6edea540853fce369c16",
1240
1256
  "1.11.0": "c51351a76c96beb64fa7e5922f78d776367af882ed1ff37229f9e9ffacee8372",
1241
- "1.11.1": "98112937a3bbb3883c3db1fd5e836751f91a479c1c5da64d4410263a6581ef1f"
1257
+ "1.11.1": "98112937a3bbb3883c3db1fd5e836751f91a479c1c5da64d4410263a6581ef1f",
1258
+ "1.12.0": "6aa42e368e7d57191de9b0c040c289edf38a1ffa7ae0097f57188ecd5a5b8f9c"
1242
1259
  });
1243
1260
  //#endregion
1244
- export { ALL_SYSTEM_TABLES, CREATE_POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION, CREATE_POSTGRES_SMRT_CHANGES_PENDING_TABLE, CREATE_POSTGRES_SMRT_CHANGES_TABLE, CREATE_SMRT_AI_USAGE_TABLE, CREATE_SMRT_BACKFILLS_TABLE, CREATE_SMRT_CHANGES_TABLE, CREATE_SMRT_CONTEXTS_TABLE, CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, CREATE_SMRT_DISPATCH_TABLE, CREATE_SMRT_EMBEDDINGS_TABLE, CREATE_SMRT_MIGRATIONS_TABLE, CREATE_SMRT_RUN_ONCE_CLAIMS_TABLE, CREATE_SMRT_SCHEMA_MIGRATIONS_TABLE, ENSURE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, ENSURE_POSTGRES_CHANGE_FEED_SCHEMA, FRAMEWORK_OPERATIONAL_TABLES, LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_NAME, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_NAME, POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_BATCH, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME, POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER, POSTGRES_CHANGE_FEED_HELPER_MARKER, POSTGRES_CHANGE_FEED_PENDING_TABLE, REPLACE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, RETIRED_SYSTEM_TABLES, RUN_ONCE_CLAIMS_TABLE, SMRT_SCHEMA_VERSION, SYSTEM_TABLE_TENANT_UUID_COLUMNS, getSystemTableDDL, getSystemTableDDLForEngine };
1261
+ export { ALL_SYSTEM_TABLES, CREATE_POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION, CREATE_POSTGRES_CHANGE_FEED_DRAIN_FUNCTION, CREATE_POSTGRES_SMRT_CHANGES_PENDING_TABLE, CREATE_POSTGRES_SMRT_CHANGES_TABLE, CREATE_SMRT_AI_USAGE_TABLE, CREATE_SMRT_BACKFILLS_TABLE, CREATE_SMRT_CHANGES_TABLE, CREATE_SMRT_CONTEXTS_TABLE, CREATE_SMRT_DISPATCH_SUBSCRIPTIONS_TABLE, CREATE_SMRT_DISPATCH_TABLE, CREATE_SMRT_EMBEDDINGS_TABLE, CREATE_SMRT_MIGRATIONS_TABLE, CREATE_SMRT_RUN_ONCE_CLAIMS_TABLE, CREATE_SMRT_SCHEMA_MIGRATIONS_TABLE, CREATE_SMRT_SYNC_TOMBSTONES_TABLE, ENSURE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, ENSURE_POSTGRES_CHANGE_FEED_SCHEMA, FRAMEWORK_OPERATIONAL_TABLES, LEGACY_POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_BATCH_FUNCTION_NAME, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_APPEND_FUNCTION_NAME, POSTGRES_CHANGE_FEED_COMPAT_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_BATCH, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_IDENTITY, POSTGRES_CHANGE_FEED_DRAIN_FUNCTION_NAME, POSTGRES_CHANGE_FEED_HELPER_COMPAT_MARKER, POSTGRES_CHANGE_FEED_HELPER_MARKER, POSTGRES_CHANGE_FEED_PENDING_TABLE, REPLACE_POSTGRES_CHANGE_FEED_APPEND_FUNCTION, RETIRED_SYSTEM_TABLES, RUN_ONCE_CLAIMS_TABLE, SMRT_SCHEMA_VERSION, SYSTEM_TABLE_TENANT_UUID_COLUMNS, getSystemTableDDL, getSystemTableDDLForEngine };
1245
1262
 
1246
1263
  //# sourceMappingURL=schema.js.map