@happyvertical/smrt-core 0.54.2 → 0.54.3

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 (138) hide show
  1. package/AGENTS.md +6 -1
  2. package/README.md +8 -0
  3. package/agents/change-feed.md +7 -12
  4. package/agents/generators.md +13 -0
  5. package/agents/object-runtime.md +8 -0
  6. package/agents/schema-paths.md +14 -0
  7. package/dist/audit-context.d.ts +4 -0
  8. package/dist/audit-context.d.ts.map +1 -0
  9. package/dist/audit-context.js +16 -0
  10. package/dist/audit-context.js.map +1 -0
  11. package/dist/audit.d.ts +34 -0
  12. package/dist/audit.d.ts.map +1 -0
  13. package/dist/audit.js +63 -0
  14. package/dist/audit.js.map +1 -0
  15. package/dist/browser.d.ts +2 -1
  16. package/dist/browser.d.ts.map +1 -1
  17. package/dist/browser.js +4 -3
  18. package/dist/cascade.d.ts +1 -1
  19. package/dist/cascade.d.ts.map +1 -1
  20. package/dist/cascade.js +10 -7
  21. package/dist/cascade.js.map +1 -1
  22. package/dist/change-feed.js +1 -1
  23. package/dist/collection.d.ts +9 -2
  24. package/dist/collection.d.ts.map +1 -1
  25. package/dist/collection.js +59 -4
  26. package/dist/collection.js.map +1 -1
  27. package/dist/embedded-write-queue.d.ts +2 -0
  28. package/dist/embedded-write-queue.d.ts.map +1 -1
  29. package/dist/embedded-write-queue.js +12 -5
  30. package/dist/embedded-write-queue.js.map +1 -1
  31. package/dist/generators/changes-route.js +1 -1
  32. package/dist/generators/mcp-runtime-template.d.ts +2 -0
  33. package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
  34. package/dist/generators/mcp-runtime-template.js +18 -16
  35. package/dist/generators/mcp-runtime-template.js.map +1 -1
  36. package/dist/generators/mcp-tool-name.d.ts +6 -0
  37. package/dist/generators/mcp-tool-name.d.ts.map +1 -1
  38. package/dist/generators/mcp-tool-name.js.map +1 -1
  39. package/dist/generators/mcp.d.ts +34 -1
  40. package/dist/generators/mcp.d.ts.map +1 -1
  41. package/dist/generators/mcp.js +148 -110
  42. package/dist/generators/mcp.js.map +1 -1
  43. package/dist/generators/rest.d.ts.map +1 -1
  44. package/dist/generators/rest.js +16 -7
  45. package/dist/generators/rest.js.map +1 -1
  46. package/dist/index.d.ts +3 -1
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +9 -7
  49. package/dist/junction.d.ts +29 -10
  50. package/dist/junction.d.ts.map +1 -1
  51. package/dist/junction.js +31 -12
  52. package/dist/junction.js.map +1 -1
  53. package/dist/knowledge.js +1 -1
  54. package/dist/knowledge.js.map +1 -1
  55. package/dist/manifest/manifest-loader.d.ts.map +1 -1
  56. package/dist/manifest/manifest-loader.js +1 -1
  57. package/dist/manifest/manifest-loader.js.map +1 -1
  58. package/dist/manifest/static-manifest.d.ts.map +1 -1
  59. package/dist/manifest/static-manifest.js +106 -9
  60. package/dist/manifest/static-manifest.js.map +1 -1
  61. package/dist/manifest/store.d.ts +8 -0
  62. package/dist/manifest/store.d.ts.map +1 -1
  63. package/dist/manifest/store.js +12 -2
  64. package/dist/manifest/store.js.map +1 -1
  65. package/dist/manifest.json +119 -9
  66. package/dist/migrations/differ.d.ts +3 -3
  67. package/dist/migrations/differ.d.ts.map +1 -1
  68. package/dist/migrations/differ.js +8 -4
  69. package/dist/migrations/differ.js.map +1 -1
  70. package/dist/object.d.ts +1 -0
  71. package/dist/object.d.ts.map +1 -1
  72. package/dist/object.js +19 -7
  73. package/dist/object.js.map +1 -1
  74. package/dist/registry/class-registration.d.ts.map +1 -1
  75. package/dist/registry/class-registration.js +75 -10
  76. package/dist/registry/class-registration.js.map +1 -1
  77. package/dist/registry/collection-resolution.d.ts +2 -2
  78. package/dist/registry/collection-resolution.d.ts.map +1 -1
  79. package/dist/registry/collection-resolution.js +2 -1
  80. package/dist/registry/collection-resolution.js.map +1 -1
  81. package/dist/registry/collision-policy.d.ts +8 -0
  82. package/dist/registry/collision-policy.d.ts.map +1 -1
  83. package/dist/registry/collision-policy.js +2 -2
  84. package/dist/registry/collision-policy.js.map +1 -1
  85. package/dist/registry/framework-base-classes.d.ts.map +1 -1
  86. package/dist/registry/framework-base-classes.js +2 -1
  87. package/dist/registry/framework-base-classes.js.map +1 -1
  88. package/dist/registry/schema-builder.d.ts.map +1 -1
  89. package/dist/registry/schema-builder.js +33 -4
  90. package/dist/registry/schema-builder.js.map +1 -1
  91. package/dist/registry/shared-state.d.ts +6 -0
  92. package/dist/registry/shared-state.d.ts.map +1 -1
  93. package/dist/registry/shared-state.js +12 -1
  94. package/dist/registry/shared-state.js.map +1 -1
  95. package/dist/registry/types.d.ts +7 -1
  96. package/dist/registry/types.d.ts.map +1 -1
  97. package/dist/registry.d.ts +7 -1
  98. package/dist/registry.d.ts.map +1 -1
  99. package/dist/registry.js +25 -3
  100. package/dist/registry.js.map +1 -1
  101. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  102. package/dist/scanner/manifest-generator.js +5 -1
  103. package/dist/scanner/manifest-generator.js.map +1 -1
  104. package/dist/scanner/types.d.ts +8 -0
  105. package/dist/scanner/types.d.ts.map +1 -1
  106. package/dist/scanner/types.js.map +1 -1
  107. package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
  108. package/dist/schema/ddl/duckdb-strategy.js +1 -0
  109. package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
  110. package/dist/schema/ddl/null-equal-index.d.ts.map +1 -1
  111. package/dist/schema/ddl/null-equal-index.js +4 -2
  112. package/dist/schema/ddl/null-equal-index.js.map +1 -1
  113. package/dist/schema/generator.d.ts +3 -0
  114. package/dist/schema/generator.d.ts.map +1 -1
  115. package/dist/schema/generator.js +38 -0
  116. package/dist/schema/generator.js.map +1 -1
  117. package/dist/smrt-knowledge.json +74 -20
  118. package/dist/sync/apply.d.ts +2 -0
  119. package/dist/sync/apply.d.ts.map +1 -1
  120. package/dist/sync/apply.js +7 -3
  121. package/dist/sync/apply.js.map +1 -1
  122. package/dist/utils/scanner-module.d.ts +9 -0
  123. package/dist/utils/scanner-module.d.ts.map +1 -1
  124. package/dist/vite-plugin/index.d.ts +14 -0
  125. package/dist/vite-plugin/index.d.ts.map +1 -1
  126. package/dist/vite-plugin/index.js +58 -18
  127. package/dist/vite-plugin/index.js.map +1 -1
  128. package/dist/vite-plugin/package-stamp.d.ts +41 -0
  129. package/dist/vite-plugin/package-stamp.d.ts.map +1 -0
  130. package/dist/vite-plugin/package-stamp.js +33 -0
  131. package/dist/vite-plugin/package-stamp.js.map +1 -0
  132. package/dist/vite-plugin/sveltekit-generator.js +5 -3
  133. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  134. package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
  135. package/dist/vite-plugin/sync-apply-route.js +10 -0
  136. package/dist/vite-plugin/sync-apply-route.js.map +1 -1
  137. package/dist/vite-plugin.js +2 -2
  138. package/package.json +10 -10
package/AGENTS.md CHANGED
@@ -104,7 +104,12 @@ and repository rules.
104
104
  (`decorator-different-packages-qualified-coexist`); bundled output needs
105
105
  a resolved table that differs first (#3106). Stack
106
106
  attribution skips smrt-core (including source-mapped installed
107
- `smrt-core/src` frames, #3109), decorator-helper, and module-runner frames.
107
+ `smrt-core/src` frames, #3109), decorator-helper, and module-runner frames,
108
+ and drops a frame's Vite `?v=` query (#3490). Library builds stamp each
109
+ decorated class with `static __smrtPackage__` (own property, read at
110
+ decoration as its explicit package) and publish `stampsConstructors`; an
111
+ unstamped class never takes a stamping package's entry by name and table,
112
+ in either order (#3490, `issue-3490-*.test.ts`, `package-stamp.test.ts`).
108
113
  Qualified names are `@scope/pkg:Class`; the scope is a deliberate
109
114
  discriminator, so manifest generation (`generateManifest`,
110
115
  `applyGenerationPasses`, hence `pnpm build`) throws
package/README.md CHANGED
@@ -774,3 +774,11 @@ restrictions. Manifests should qualify cross-package
774
774
  `extends` names. The deepest subtype wins, including parent replay and late
775
775
  intermediate manifests. Unrelated classes and sibling subtypes do not choose a
776
776
  winner by registration order: sharing their table still raises a collision.
777
+
778
+ ## Consumer integration fixtures
779
+
780
+ Consumer integration tests create disposable applications in
781
+ `src/consumer-plugin/__test-consumer-*`. These applications have their own
782
+ compiler configuration and aliases. Core's production, test typecheck, and
783
+ declaration builds exclude these directories, including leftovers from an
784
+ interrupted test run; the authored consumer integration tests remain included.
@@ -81,18 +81,13 @@ emitted), so no call site needs a special case. A wildcard `If-None-Match: *`
81
81
  still 304s; the read paths evaluate it only after the payload is built, so it
82
82
  distinguishes nothing an unconditional request would not.
83
83
 
84
- Registration derives the declared name from config and manifest; the writer uses
85
- `instance.tableName`, which resolves through the STI base's schema and then the
86
- class's own. Where those two derivations disagree the declaration lands on a
87
- name nothing writes under, and the real table keeps appending. STI is *not* such
88
- a case today — `@smrt()` already resolves an STI child to its base's table, so a
89
- child declaring `sensitive` declares `<base>` (verified) — but the manifest-stub
90
- and manifest-merge paths derive the name differently again, so registration
91
- declares every candidate name. The authoritative check is at the write path:
92
- `isChangeFeedSensitiveWrite()` asks the registry about the instance's own class
93
- through a resolver hook on the leaf module (so `change-feed.ts` never imports
94
- the registry), sees the exact name being recorded with no derivation to keep in
95
- sync, and declares it — closing the read path and signal bus for that table.
84
+ STI sensitivity is table-wide. Manifest generation and registration refuse a
85
+ hierarchy mixing classes declaring `sensitive: true` with classes omitting it or
86
+ declaring false (`CONFIG_STI_MIXED_SENSITIVITY`, #3456). Mark every class sensitive
87
+ or put the sensitive and public models on separate tables. This preserves the
88
+ historical-read and live-signal protections without introducing a discriminator
89
+ into old feed rows. A uniformly sensitive hierarchy declares its shared table;
90
+ the authoritative write-path check still verifies the instance's actual table.
96
91
 
97
92
  Scope note: the generated `_changes`/`_events` routes — both the SvelteKit
98
93
  routes and the runtime REST generator's — authorize on an authenticated
@@ -268,6 +268,19 @@ uses it for class allow-lists, tenant checks, and compatible workflow guards;
268
268
  those consumers must not reverse protocol aliases. The alias remains the sole
269
269
  wire tool name and is still the key for public patterns and generic tool policy.
270
270
 
271
+ `MCPConfig.classNames` scopes generation (simple names, case-insensitive, or
272
+ qualified `@scope/pkg:Class`); omitted, every registered class is a candidate.
273
+ A simple name matching more than one registered class throws (scope it by
274
+ qualified name). Each tool keeps its class's registry key, and configuration,
275
+ fields, methods, dispatch, tasks and collections resolve through that key —
276
+ never through the first same-named class. Generated servers (single-file and
277
+ modular) carry it as `registryKey` in their tool targets and task actions and
278
+ use it for every emitted collection, policy, class and STI-base lookup;
279
+ `objectName` stays the wire/display name.
280
+ A host with an allow-list passes it so the duplicate-name check runs over the
281
+ published set only: an unrelated collision among classes it never lists must
282
+ not fail its catalog or calls (#3490). App MCP always scopes its generators.
283
+
271
284
  ## Generated route collection access (#3416)
272
285
 
273
286
  There is no generated route-access module. Every generated SvelteKit route
@@ -2,6 +2,14 @@
2
2
 
3
3
  `constructor(options)` → `initialize()` → ready for `save()`/`delete()`/`loadFromId()`
4
4
 
5
+ Models declaring `audit: true` use transactional collection CRUD with trusted
6
+ `SmrtCollectionOptions.auditTrail` identity and writer. `collection.update()` checks
7
+ registered mutable fields; audited creates refuse natural-key overwrites.
8
+ Direct object writes and custom overrides need explicit service recording.
9
+ Cascade reuse of a no-savepoint transaction requires an active framework-owned
10
+ transaction handle; raw caller transactions retain the ordinary delete limitation.
11
+ See [audit contract](../../../docs/content/audit-trail.md).
12
+
5
13
  - `initialize()`: loads field initializers, applies option values (options override initializers), loads from DB if id/slug provided
6
14
  - `save()`: upsert with STI validation, interceptor execution, auto-embeddings. Persisted objects (`isPersisted` — set by DB hydration and successful saves) upsert on `['id']` so natural-key edits (e.g. slug renames) update in place; new objects upsert on the natural-key conflict columns for ingestion-style dedup (#1472)
7
15
  - Persisted `save()` uses loaded `updated_at` in its `UPDATE`; zero rows throws
@@ -896,3 +896,17 @@ connection. This mode is not atomic. Unfinished indexes are `failed`, not
896
896
  allows reruns to resume index work without replaying committed DDL. Inspect
897
897
  `pg_index.indisvalid` and drop INVALID indexes before rebuild (`pg_indexes`
898
898
  alone cannot detect them). Operational commands: `packages/cli/AGENTS.md`.
899
+
900
+ ### Subclass conflict identities (#3456)
901
+
902
+ An STI child's explicit `conflictColumns` owns a partial unique index restricted
903
+ to its qualified discriminator. The root conflict index excludes these children;
904
+ registry and manifest paths emit the same predicates. Natural-key lookups include
905
+ that scope, PostgreSQL atomic inserts include the index predicate, and adopted
906
+ rows update by id. Bulk writes use per-item guards for these targets. Normal
907
+ migrations replace the root index by name and add the child index; repair any
908
+ existing duplicate child natural keys before migrating. Stop old writers for this
909
+ index migration and load the complete consumer manifest on every shared-table
910
+ writer before resuming: a provider-only manifest lacks the child predicates.
911
+ DuckDB/JSON reject
912
+ these schemas because partial unique conflict identities cannot be enforced.
@@ -0,0 +1,4 @@
1
+ import type { CollectionAuditOptions } from './audit.js';
2
+ /** Bind trusted identity/sink to this async request, including generated CRUD. */
3
+ export declare function withAuditContext<T>(options: CollectionAuditOptions, operation: () => T): T;
4
+ //# sourceMappingURL=audit-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-context.d.ts","sourceRoot":"","sources":["../src/audit-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAWzD,kFAAkF;AAClF,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,OAAO,EAAE,sBAAsB,EAC/B,SAAS,EAAE,MAAM,CAAC,GACjB,CAAC,CAEH"}
@@ -0,0 +1,16 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region src/audit-context.ts
3
+ var STORAGE = Symbol.for("smrt.audit.storage");
4
+ var CONTEXT = Symbol.for("smrt.audit.context");
5
+ var shared = globalThis;
6
+ if (!shared[STORAGE]) shared[STORAGE] = new AsyncLocalStorage();
7
+ var storage = shared[STORAGE];
8
+ shared[CONTEXT] = () => storage.getStore();
9
+ /** Bind trusted identity/sink to this async request, including generated CRUD. */
10
+ function withAuditContext(options, operation) {
11
+ return storage.run(Object.freeze({ ...options }), operation);
12
+ }
13
+ //#endregion
14
+ export { withAuditContext };
15
+
16
+ //# sourceMappingURL=audit-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-context.js","names":[],"sources":["../src/audit-context.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { CollectionAuditOptions } from './audit.js';\n\nconst STORAGE = Symbol.for('smrt.audit.storage');\nconst CONTEXT = Symbol.for('smrt.audit.context');\nconst shared = globalThis as unknown as Record<symbol, unknown>;\nif (!shared[STORAGE]) {\n shared[STORAGE] = new AsyncLocalStorage<CollectionAuditOptions>();\n}\nconst storage = shared[STORAGE] as AsyncLocalStorage<CollectionAuditOptions>;\nshared[CONTEXT] = () => storage.getStore();\n\n/** Bind trusted identity/sink to this async request, including generated CRUD. */\nexport function withAuditContext<T>(\n options: CollectionAuditOptions,\n operation: () => T,\n): T {\n return storage.run(Object.freeze({ ...options }), operation);\n}\n"],"mappings":";;AAGA,IAAM,UAAU,OAAO,IAAI,oBAAoB;AAC/C,IAAM,UAAU,OAAO,IAAI,oBAAoB;AAC/C,IAAM,SAAS;AACf,IAAI,CAAC,OAAO,UACV,OAAO,WAAW,IAAI,kBAA0C;AAElE,IAAM,UAAU,OAAO;AACvB,OAAO,iBAAiB,QAAQ,SAAS;;AAGzC,SAAgB,iBACd,SACA,WACG;CACH,OAAO,QAAQ,IAAI,OAAO,OAAO,EAAE,GAAG,QAAQ,CAAC,GAAG,SAAS;AAC7D"}
@@ -0,0 +1,34 @@
1
+ import type { DatabaseInterface } from '@happyvertical/sql';
2
+ import type { SmrtObject } from './object.js';
3
+ /** Caller identity comes from trusted server context, never a request body. */
4
+ export interface AuditContext {
5
+ actorId: string;
6
+ reason?: string;
7
+ source?: 'web' | 'cli' | 'ci' | 'webhook' | 'mcp';
8
+ onBehalfOfId?: string | null;
9
+ }
10
+ export interface AuditChange {
11
+ before: unknown;
12
+ after: unknown;
13
+ }
14
+ export interface AuditEntry extends AuditContext {
15
+ action: string;
16
+ resourceType: string;
17
+ resourceId: string;
18
+ tenantId: string | null;
19
+ changes: Record<string, AuditChange>;
20
+ }
21
+ /** Writers MUST use this exact transaction handle and propagate failures. */
22
+ export type AuditWriter = (entry: AuditEntry, db: DatabaseInterface) => Promise<void>;
23
+ export interface CollectionAuditOptions extends AuditContext {
24
+ writer: AuditWriter;
25
+ }
26
+ /** Dependency-inverted lookup; the Node-only context module owns async scope. */
27
+ export declare function getActiveAuditOptions(): CollectionAuditOptions | undefined;
28
+ export declare function isAudited(instance: SmrtObject): boolean;
29
+ /** Snapshot the public projection: credentials and permission-gated fields are omitted. */
30
+ export declare function auditSnapshot(instance: SmrtObject): Record<string, unknown>;
31
+ export declare function auditChanges(before: Record<string, unknown>, after: Record<string, unknown>): Record<string, AuditChange>;
32
+ export declare function requireAuditOptions(options: CollectionAuditOptions | undefined, context?: Partial<AuditContext>): CollectionAuditOptions;
33
+ export declare function writeAudit(instance: SmrtObject, action: string, before: Record<string, unknown>, after: Record<string, unknown>, options: CollectionAuditOptions): Promise<void>;
34
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,UAAW,SAAQ,YAAY;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CACtC;AAED,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,CACxB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,iBAAiB,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,sBAAuB,SAAQ,YAAY;IAC1D,MAAM,EAAE,WAAW,CAAC;CACrB;AAID,iFAAiF;AACjF,wBAAgB,qBAAqB,IAAI,sBAAsB,GAAG,SAAS,CAG1E;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CASvD;AAED,2FAA2F;AAC3F,wBAAgB,aAAa,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO3E;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAQ7B;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,sBAAsB,GAAG,SAAS,EAC3C,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,sBAAsB,CAaxB;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,UAAU,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAoCf"}
package/dist/audit.js ADDED
@@ -0,0 +1,63 @@
1
+ import { toSnakeCase } from "./utils/naming.js";
2
+ import { ObjectRegistry } from "./registry.js";
3
+ //#region src/audit.ts
4
+ var CONTEXT = Symbol.for("smrt.audit.context");
5
+ /** Dependency-inverted lookup; the Node-only context module owns async scope. */
6
+ function getActiveAuditOptions() {
7
+ const resolver = globalThis[CONTEXT];
8
+ return typeof resolver === "function" ? resolver() : void 0;
9
+ }
10
+ function isAudited(instance) {
11
+ const registered = ObjectRegistry.getClassByConstructor(instance.constructor);
12
+ const name = registered?.qualifiedName || registered?.name || instance.constructor.name;
13
+ return [name, ...ObjectRegistry.getInheritanceChain(name)].some((key) => ObjectRegistry.getConfig(key).audit === true);
14
+ }
15
+ /** Snapshot the public projection: credentials and permission-gated fields are omitted. */
16
+ function auditSnapshot(instance) {
17
+ const snapshot = JSON.parse(JSON.stringify(instance.toPublicJSON()));
18
+ delete snapshot.created_at;
19
+ delete snapshot.updated_at;
20
+ return snapshot;
21
+ }
22
+ function auditChanges(before, after) {
23
+ const changes = {};
24
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) changes[key] = {
25
+ before: before[key] ?? null,
26
+ after: after[key] ?? null
27
+ };
28
+ return changes;
29
+ }
30
+ function requireAuditOptions(options, context) {
31
+ const defaults = options ?? getActiveAuditOptions();
32
+ const resolved = defaults && {
33
+ ...defaults,
34
+ ...context
35
+ };
36
+ if (!resolved || typeof resolved.writer !== "function" || !resolved.actorId?.trim()) throw new Error("Audited mutation requires a writer and an authenticated actorId");
37
+ return resolved;
38
+ }
39
+ async function writeAudit(instance, action, before, after, options) {
40
+ const registered = ObjectRegistry.getClassByConstructor(instance.constructor);
41
+ const resourceType = registered?.qualifiedName || registered?.name || instance.constructor.name;
42
+ const runtimeColumns = ObjectRegistry.getRuntimeOwnershipColumns(resourceType);
43
+ if (runtimeColumns.size > 1) throw new Error("Audited mutation has ambiguous runtime tenant ownership");
44
+ const tenantColumn = runtimeColumns.size === 1 ? runtimeColumns.values().next().value : ObjectRegistry.getOwnershipTenantColumn(resourceType);
45
+ const fields = await ObjectRegistry.getAllFields(resourceType);
46
+ const tenantField = tenantColumn ? [...fields.keys()].find((key) => toSnakeCase(key) === tenantColumn) : void 0;
47
+ const tenantId = tenantField ? instance[tenantField] : null;
48
+ await options.writer({
49
+ actorId: options.actorId,
50
+ reason: options.reason,
51
+ source: options.source,
52
+ onBehalfOfId: options.onBehalfOfId,
53
+ action,
54
+ resourceType,
55
+ resourceId: instance.id,
56
+ tenantId: typeof tenantId === "string" ? tenantId : null,
57
+ changes: auditChanges(before, after)
58
+ }, instance.db);
59
+ }
60
+ //#endregion
61
+ export { auditChanges, auditSnapshot, getActiveAuditOptions, isAudited, requireAuditOptions, writeAudit };
62
+
63
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","names":[],"sources":["../src/audit.ts"],"sourcesContent":["import type { DatabaseInterface } from '@happyvertical/sql';\nimport type { SmrtObject } from './object.js';\nimport type { SmrtObjectConstructor } from './registry/types.js';\nimport { ObjectRegistry } from './registry.js';\nimport { toSnakeCase } from './utils/naming.js';\n\n/** Caller identity comes from trusted server context, never a request body. */\nexport interface AuditContext {\n actorId: string;\n reason?: string;\n source?: 'web' | 'cli' | 'ci' | 'webhook' | 'mcp';\n onBehalfOfId?: string | null;\n}\n\nexport interface AuditChange {\n before: unknown;\n after: unknown;\n}\n\nexport interface AuditEntry extends AuditContext {\n action: string;\n resourceType: string;\n resourceId: string;\n tenantId: string | null;\n changes: Record<string, AuditChange>;\n}\n\n/** Writers MUST use this exact transaction handle and propagate failures. */\nexport type AuditWriter = (\n entry: AuditEntry,\n db: DatabaseInterface,\n) => Promise<void>;\n\nexport interface CollectionAuditOptions extends AuditContext {\n writer: AuditWriter;\n}\n\nconst CONTEXT = Symbol.for('smrt.audit.context');\n\n/** Dependency-inverted lookup; the Node-only context module owns async scope. */\nexport function getActiveAuditOptions(): CollectionAuditOptions | undefined {\n const resolver = (globalThis as unknown as Record<symbol, unknown>)[CONTEXT];\n return typeof resolver === 'function' ? resolver() : undefined;\n}\n\nexport function isAudited(instance: SmrtObject): boolean {\n const registered = ObjectRegistry.getClassByConstructor(\n instance.constructor as SmrtObjectConstructor,\n );\n const name =\n registered?.qualifiedName || registered?.name || instance.constructor.name;\n return [name, ...ObjectRegistry.getInheritanceChain(name)].some(\n (key) => ObjectRegistry.getConfig(key).audit === true,\n );\n}\n\n/** Snapshot the public projection: credentials and permission-gated fields are omitted. */\nexport function auditSnapshot(instance: SmrtObject): Record<string, unknown> {\n const snapshot = JSON.parse(\n JSON.stringify(instance.toPublicJSON()),\n ) as Record<string, unknown>;\n delete snapshot.created_at;\n delete snapshot.updated_at;\n return snapshot;\n}\n\nexport function auditChanges(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): Record<string, AuditChange> {\n const changes: Record<string, AuditChange> = {};\n for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {\n if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) {\n changes[key] = { before: before[key] ?? null, after: after[key] ?? null };\n }\n }\n return changes;\n}\n\nexport function requireAuditOptions(\n options: CollectionAuditOptions | undefined,\n context?: Partial<AuditContext>,\n): CollectionAuditOptions {\n const defaults = options ?? getActiveAuditOptions();\n const resolved = defaults && { ...defaults, ...context };\n if (\n !resolved ||\n typeof resolved.writer !== 'function' ||\n !resolved.actorId?.trim()\n ) {\n throw new Error(\n 'Audited mutation requires a writer and an authenticated actorId',\n );\n }\n return resolved;\n}\n\nexport async function writeAudit(\n instance: SmrtObject,\n action: string,\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n options: CollectionAuditOptions,\n): Promise<void> {\n const registered = ObjectRegistry.getClassByConstructor(\n instance.constructor as SmrtObjectConstructor,\n );\n const resourceType =\n registered?.qualifiedName || registered?.name || instance.constructor.name;\n const runtimeColumns =\n ObjectRegistry.getRuntimeOwnershipColumns(resourceType);\n if (runtimeColumns.size > 1) {\n throw new Error('Audited mutation has ambiguous runtime tenant ownership');\n }\n const tenantColumn =\n runtimeColumns.size === 1\n ? runtimeColumns.values().next().value\n : ObjectRegistry.getOwnershipTenantColumn(resourceType);\n const fields = await ObjectRegistry.getAllFields(resourceType);\n const tenantField = tenantColumn\n ? [...fields.keys()].find((key) => toSnakeCase(key) === tenantColumn)\n : undefined;\n const tenantId = tenantField\n ? (instance as unknown as Record<string, unknown>)[tenantField]\n : null;\n await options.writer(\n {\n actorId: options.actorId,\n reason: options.reason,\n source: options.source,\n onBehalfOfId: options.onBehalfOfId,\n action,\n resourceType,\n resourceId: instance.id as string,\n tenantId: typeof tenantId === 'string' ? tenantId : null,\n changes: auditChanges(before, after),\n },\n instance.db,\n );\n}\n"],"mappings":";;;AAqCA,IAAM,UAAU,OAAO,IAAI,oBAAoB;;AAG/C,SAAgB,wBAA4D;CAC1E,MAAM,WAAY,WAAkD;CACpE,OAAO,OAAO,aAAa,aAAa,SAAS,IAAI,KAAA;AACvD;AAEA,SAAgB,UAAU,UAA+B;CACvD,MAAM,aAAa,eAAe,sBAChC,SAAS,WACX;CACA,MAAM,OACJ,YAAY,iBAAiB,YAAY,QAAQ,SAAS,YAAY;CACxE,OAAO,CAAC,MAAM,GAAG,eAAe,oBAAoB,IAAI,CAAC,CAAC,CAAC,MACxD,QAAQ,eAAe,UAAU,GAAG,CAAC,CAAC,UAAU,IACnD;AACF;;AAGA,SAAgB,cAAc,UAA+C;CAC3E,MAAM,WAAW,KAAK,MACpB,KAAK,UAAU,SAAS,aAAa,CAAC,CACxC;CACA,OAAO,SAAS;CAChB,OAAO,SAAS;CAChB,OAAO;AACT;AAEA,SAAgB,aACd,QACA,OAC6B;CAC7B,MAAM,UAAuC,CAAC;CAC9C,KAAK,MAAM,uBAAO,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,CAAC,CAAC,GACvE,IAAI,KAAK,UAAU,OAAO,IAAI,MAAM,KAAK,UAAU,MAAM,IAAI,GAC3D,QAAQ,OAAO;EAAE,QAAQ,OAAO,QAAQ;EAAM,OAAO,MAAM,QAAQ;CAAK;CAG5E,OAAO;AACT;AAEA,SAAgB,oBACd,SACA,SACwB;CACxB,MAAM,WAAW,WAAW,sBAAsB;CAClD,MAAM,WAAW,YAAY;EAAE,GAAG;EAAU,GAAG;CAAQ;CACvD,IACE,CAAC,YACD,OAAO,SAAS,WAAW,cAC3B,CAAC,SAAS,SAAS,KAAK,GAExB,MAAM,IAAI,MACR,iEACF;CAEF,OAAO;AACT;AAEA,eAAsB,WACpB,UACA,QACA,QACA,OACA,SACe;CACf,MAAM,aAAa,eAAe,sBAChC,SAAS,WACX;CACA,MAAM,eACJ,YAAY,iBAAiB,YAAY,QAAQ,SAAS,YAAY;CACxE,MAAM,iBACJ,eAAe,2BAA2B,YAAY;CACxD,IAAI,eAAe,OAAO,GACxB,MAAM,IAAI,MAAM,yDAAyD;CAE3E,MAAM,eACJ,eAAe,SAAS,IACpB,eAAe,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,QAC/B,eAAe,yBAAyB,YAAY;CAC1D,MAAM,SAAS,MAAM,eAAe,aAAa,YAAY;CAC7D,MAAM,cAAc,eAChB,CAAC,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,QAAQ,YAAY,GAAG,MAAM,YAAY,IAClE,KAAA;CACJ,MAAM,WAAW,cACZ,SAAgD,eACjD;CACJ,MAAM,QAAQ,OACZ;EACE,SAAS,QAAQ;EACjB,QAAQ,QAAQ;EAChB,QAAQ,QAAQ;EAChB,cAAc,QAAQ;EACtB;EACA;EACA,YAAY,SAAS;EACrB,UAAU,OAAO,aAAa,WAAW,WAAW;EACpD,SAAS,aAAa,QAAQ,KAAK;CACrC,GACA,SAAS,EACX;AACF"}
package/dist/browser.d.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  * import { ... } from '@happyvertical/smrt-core';
16
16
  */
17
17
  export * from './adapters/index';
18
+ export * from './audit.js';
18
19
  export * from './class';
19
20
  export * from './collection';
20
21
  export type { AiUsageConfig, GlobalSignalConfig, MetricsConfig, PubSubConfig, } from './config';
@@ -23,7 +24,7 @@ export { classifyDatabaseError, classifyDialectMessage, type DatabaseErrorClassi
23
24
  export * from './decisions';
24
25
  export * from './errors';
25
26
  export { type HierarchyView, SmrtHierarchical } from './hierarchical';
26
- export { type JunctionAttachOptions, type JunctionFilterOptions, SmrtJunction, } from './junction';
27
+ export { type JunctionAttachOptions, type JunctionFilterOptions, SmrtJunction, SmrtJunctionBase, } from './junction';
27
28
  export * from './object';
28
29
  export { SmrtPolymorphicAssociation, type SmrtPolymorphicAssociationOptions, } from './polymorphic-association';
29
30
  export * from './registry';
@@ -1 +1 @@
1
- {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,cAAc,kBAAkB,CAAC;AAEjC,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAOlC,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAIrB,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,KAAK,iCAAiC,GACvC,MAAM,2BAA2B,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AAElD,cAAc,iBAAiB,CAAC;AAEhC,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAE3B,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,aAAa,EACb,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAOlC,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,yBAAyB,EACzB,4BAA4B,EAC5B,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AAIrB,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,KAAK,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,YAAY,EACZ,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,cAAc,UAAU,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,KAAK,iCAAiC,GACvC,MAAM,2BAA2B,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,YAAY,CAAC;AAElD,cAAc,iBAAiB,CAAC;AAEhC,cAAc,gBAAgB,CAAC;AAC/B,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,cAAc,eAAe,CAAC"}
package/dist/browser.js CHANGED
@@ -13,15 +13,16 @@ import { SignalBus } from "./signals/bus.js";
13
13
  import { assertPostgresSystemTimestampsCurrent, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, getDatabaseEngine, migratePostgresSystemTimestamps, planPostgresSystemTimestampMigrations, tableExists } from "./system/compatibility.js";
14
14
  import { ensureSystemTables } from "./system/bootstrap.js";
15
15
  import { SmrtClass } from "./class.js";
16
+ import { DEFAULT_FACET_LIMIT, EmbeddingUnavailableError, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_STI_READ_SCOPE_TYPES, SmrtCollection } from "./collection.js";
16
17
  import { convertTypeToJsonSchema, generateToolFromMethod, generateToolManifest, shouldIncludeMethod } from "./tools/tool-generator.js";
17
18
  import { executeToolCall, executeToolCalls, formatToolResults, validateToolCall } from "./tools/tool-executor.js";
18
19
  import { SmrtObject } from "./object.js";
19
20
  import { isFrameworkBaseClass } from "./registry/framework-base-classes.js";
20
21
  import { SMRT_COLLECTION_BASE_NAMES, isSmrtCollectionExtendsName } from "./registry/collection-resolution.js";
21
22
  import { ObjectRegistry, smrt } from "./registry.js";
22
- import { DEFAULT_FACET_LIMIT, EmbeddingUnavailableError, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_STI_READ_SCOPE_TYPES, SmrtCollection } from "./collection.js";
23
+ import { auditChanges, auditSnapshot, getActiveAuditOptions, isAudited, requireAuditOptions, writeAudit } from "./audit.js";
23
24
  import { SmrtHierarchical } from "./hierarchical.js";
24
- import { SmrtJunction } from "./junction.js";
25
+ import { SmrtJunction, SmrtJunctionBase } from "./junction.js";
25
26
  import { SmrtPolymorphicAssociation } from "./polymorphic-association.js";
26
27
  import "./signals/index.js";
27
28
  import { DIAGNOSTICS_DEFAULT_LIMIT, SYSTEM_DIAGNOSTICS_TABLES, readDispatchHealth, readJobHealth, readMigrationStatus, readRecentChanges, readRegistryDrift, readScheduleHealth, readSystemDiagnostics } from "./system/diagnostics.js";
@@ -29,4 +30,4 @@ import { BOOTED_PROVENANCE, assertPlainJson, sanitizeMessagePaths, snapshotRegis
29
30
  import { DEFAULT_RETENTION_POLICY, clearRetentionTasks, getRetentionTasks, pruneAiUsage, pruneExpiredContexts, pruneRunOnceClaims, registerRetentionTask, runRetentionSweep, unregisterRetentionTask } from "./system/retention.js";
30
31
  import "./system/index.js";
31
32
  import "./tools/index.js";
32
- export { AIError, AiUsageCollector, AiUsagePersistenceHandler, BOOTED_PROVENANCE, ConfigurationError, DEFAULT_AI_COST_RATES, DEFAULT_FACET_LIMIT, DEFAULT_RETENTION_POLICY, DIAGNOSTICS_DEFAULT_LIMIT, DatabaseError, EmbeddingUnavailableError, ErrorUtils, FilesystemError, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_STI_READ_SCOPE_TYPES, MetricsAdapter, NetworkError, ObjectRegistry, PubSubAdapter, RunOnceClaimError, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SMRT_SCHEMA_VERSION, SYSTEM_DIAGNOSTICS_TABLES, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, assertFiniteUnitInterval, assertPlainJson, assertPostgresSystemTimestampsCurrent, classifyDatabaseError, classifyDialectMessage, clearRetentionTasks, config, convertTypeToJsonSchema, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, executeDecision, executeToolCall, executeToolCalls, formatToolResults, generateToolFromMethod, generateToolManifest, getDatabaseEngine, getRetentionTasks, isAbortedTransactionError, isDeterministicDatabaseError, isFrameworkBaseClass, isNotNullViolationError, isSmrtCollectionExtendsName, isTransientDatabaseError, isUniqueViolationError, migratePostgresSystemTimestamps, planPostgresSystemTimestampMigrations, pruneAiUsage, pruneExpiredContexts, pruneRunOnceClaims, readDispatchHealth, readJobHealth, readMigrationStatus, readRecentChanges, readRegistryDrift, readScheduleHealth, readSystemDiagnostics, registerRetentionTask, runRetentionSweep, sanitizeMessagePaths, shouldIncludeMethod, smrt, smrt as smrtRegistry, snapshotRegistry, tableExists, unregisterRetentionTask, validateToolCall };
33
+ export { AIError, AiUsageCollector, AiUsagePersistenceHandler, BOOTED_PROVENANCE, ConfigurationError, DEFAULT_AI_COST_RATES, DEFAULT_FACET_LIMIT, DEFAULT_RETENTION_POLICY, DIAGNOSTICS_DEFAULT_LIMIT, DatabaseError, EmbeddingUnavailableError, ErrorUtils, FilesystemError, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_STI_READ_SCOPE_TYPES, MetricsAdapter, NetworkError, ObjectRegistry, PubSubAdapter, RunOnceClaimError, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SMRT_SCHEMA_VERSION, SYSTEM_DIAGNOSTICS_TABLES, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtJunctionBase, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, assertFiniteUnitInterval, assertPlainJson, assertPostgresSystemTimestampsCurrent, auditChanges, auditSnapshot, classifyDatabaseError, classifyDialectMessage, clearRetentionTasks, config, convertTypeToJsonSchema, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, executeDecision, executeToolCall, executeToolCalls, formatToolResults, generateToolFromMethod, generateToolManifest, getActiveAuditOptions, getDatabaseEngine, getRetentionTasks, isAbortedTransactionError, isAudited, isDeterministicDatabaseError, isFrameworkBaseClass, isNotNullViolationError, isSmrtCollectionExtendsName, isTransientDatabaseError, isUniqueViolationError, migratePostgresSystemTimestamps, planPostgresSystemTimestampMigrations, pruneAiUsage, pruneExpiredContexts, pruneRunOnceClaims, readDispatchHealth, readJobHealth, readMigrationStatus, readRecentChanges, readRegistryDrift, readScheduleHealth, readSystemDiagnostics, registerRetentionTask, requireAuditOptions, runRetentionSweep, sanitizeMessagePaths, shouldIncludeMethod, smrt, smrt as smrtRegistry, snapshotRegistry, tableExists, unregisterRetentionTask, validateToolCall, writeAudit };
package/dist/cascade.d.ts CHANGED
@@ -64,7 +64,7 @@
64
64
  * @see https://github.com/happyvertical/smrt/issues/2371
65
65
  * @module
66
66
  */
67
- import type { DatabaseInterface } from '@happyvertical/sql';
67
+ import { type DatabaseInterface } from '@happyvertical/sql';
68
68
  import type { ObjectRegistry } from './registry.js';
69
69
  import type { ForeignKeyAction } from './schema/types.js';
70
70
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAK5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAM1D;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAsB9C,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,cAAc,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,mEAAmE;AACnE,MAAM,WAAW,2BAA2B;IAC1C,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,mEAAmE;IACnE,WAAW,EAAE,2BAA2B,EAAE,CAAC;IAC3C,yEAAyE;IACzE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,OAAO,cAAc,EACnB,oBAAoB,GACpB,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,uBAAuB,GACvB,UAAU,GACV,YAAY,GACZ,gBAAgB,CACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAE5E;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAC7B,SAAS,EAAE,MAAM,GAChB,WAAW,CAkJb;AAqZD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;;OAIG;IACH,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9D,OAAO,CAAC,aAAa,CAAC,CAwBxB;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE;IACN,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC/B,EACD,UAAU,EAAE,CAAC,EAAE,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACnD,OAAO,CAAC,aAAa,CAAC,CAuDxB"}
1
+ {"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAGH,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAWxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAM1D;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAsB9C,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,MAAM,EAAE,cAAc,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,mEAAmE;AACnE,MAAM,WAAW,2BAA2B;IAC1C,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,4EAA4E;IAC5E,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,mEAAmE;IACnE,WAAW,EAAE,2BAA2B,EAAE,CAAC;IAC3C,yEAAyE;IACzE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,+EAA+E;IAC/E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,OAAO,cAAc,EACnB,oBAAoB,GACpB,WAAW,GACX,oBAAoB,GACpB,cAAc,GACd,uBAAuB,GACvB,UAAU,GACV,YAAY,GACZ,gBAAgB,CACnB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAE5E;AASD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,mBAAmB,EAC7B,SAAS,EAAE,MAAM,GAChB,WAAW,CAkJb;AAyZD,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B;;;;OAIG;IACH,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9D,OAAO,CAAC,aAAa,CAAC,CAwBxB;AASD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,iBAAiB,EACrB,QAAQ,EAAE,mBAAmB,EAC7B,MAAM,EAAE;IACN,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC/B,EACD,UAAU,EAAE,CAAC,EAAE,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,GACnD,OAAO,CAAC,aAAa,CAAC,CAyDxB"}
package/dist/cascade.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { classifyDatabaseError } from "./db-errors.js";
2
+ import { isEmbeddedDatabase, isFrameworkTransactionHandle, isPostgresDatabase, withEmbeddedWriteTransaction } from "./embedded-write-queue.js";
2
3
  import { ConfigurationError, DatabaseError } from "./errors.js";
3
4
  import { normalizeForeignKeyAction, resolveForeignKeyDeleteAction } from "./schema/foreign-key-policy.js";
4
5
  import { chunkArray } from "./utils/chunk.js";
5
6
  import { toSnakeCase } from "./utils/naming.js";
6
7
  import { createLogger } from "@happyvertical/logger";
8
+ import { buildWhere } from "@happyvertical/sql";
7
9
  //#region src/cascade.ts
8
10
  /**
9
11
  * App-side referential integrity for `SmrtObject.delete()` (#2371).
@@ -283,7 +285,10 @@ async function tolerateMissingSystemTable(operation, fallback, context) {
283
285
  async function selectIds(db, tableName, column, ids) {
284
286
  const found = [];
285
287
  for (const batch of chunkArray(ids, 900)) {
286
- const rows = await tolerateMissingTable(() => db.list(tableName, idPredicate(column, batch)), [], {
288
+ const dialect = isPostgresDatabase(db) ? "postgres" : "sqlite";
289
+ const where = buildWhere(idPredicate(column, batch), 1, dialect);
290
+ const sql = `SELECT CAST("id" AS VARCHAR) AS "id" FROM ${`"${tableName.replaceAll("\"", "\"\"")}"`} ${where.sql}`;
291
+ const rows = await tolerateMissingTable(() => db.query(sql, ...where.values).then((result) => result.rows), [], {
287
292
  table: tableName,
288
293
  action: "CASCADE select"
289
294
  });
@@ -449,9 +454,8 @@ async function runCascadeDelete(db, registry, target, deleteSelf) {
449
454
  affectedTableClasses: /* @__PURE__ */ new Map()
450
455
  };
451
456
  };
452
- const transaction = db.transaction;
453
- if (typeof transaction !== "function") return runWithoutReferences(db);
454
- return transaction.call(db, runWithoutReferences);
457
+ if (typeof db.transaction !== "function") return runWithoutReferences(db);
458
+ return withEmbeddedWriteTransaction(db, isEmbeddedDatabase(db), runWithoutReferences, isFrameworkTransactionHandle(db));
455
459
  }
456
460
  const run = async (bound) => {
457
461
  const result = await cascadeReferencesTo(bound, registry, {
@@ -462,9 +466,8 @@ async function runCascadeDelete(db, registry, target, deleteSelf) {
462
466
  await deleteSelf(bound);
463
467
  return result;
464
468
  };
465
- const transaction = db.transaction;
466
- if (typeof transaction !== "function") return run(db);
467
- return transaction.call(db, run);
469
+ if (typeof db.transaction !== "function") return run(db);
470
+ return withEmbeddedWriteTransaction(db, isEmbeddedDatabase(db), run, isFrameworkTransactionHandle(db));
468
471
  }
469
472
  //#endregion
470
473
  export { buildCascadePlan, cascadeReferencesTo, normalizeOnDelete, runCascadeDelete };