@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.
- package/AGENTS.md +6 -1
- package/README.md +8 -0
- package/agents/change-feed.md +7 -12
- package/agents/generators.md +13 -0
- package/agents/object-runtime.md +8 -0
- package/agents/schema-paths.md +14 -0
- package/dist/audit-context.d.ts +4 -0
- package/dist/audit-context.d.ts.map +1 -0
- package/dist/audit-context.js +16 -0
- package/dist/audit-context.js.map +1 -0
- package/dist/audit.d.ts +34 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +63 -0
- package/dist/audit.js.map +1 -0
- package/dist/browser.d.ts +2 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +4 -3
- package/dist/cascade.d.ts +1 -1
- package/dist/cascade.d.ts.map +1 -1
- package/dist/cascade.js +10 -7
- package/dist/cascade.js.map +1 -1
- package/dist/change-feed.js +1 -1
- package/dist/collection.d.ts +9 -2
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +59 -4
- package/dist/collection.js.map +1 -1
- package/dist/embedded-write-queue.d.ts +2 -0
- package/dist/embedded-write-queue.d.ts.map +1 -1
- package/dist/embedded-write-queue.js +12 -5
- package/dist/embedded-write-queue.js.map +1 -1
- package/dist/generators/changes-route.js +1 -1
- package/dist/generators/mcp-runtime-template.d.ts +2 -0
- package/dist/generators/mcp-runtime-template.d.ts.map +1 -1
- package/dist/generators/mcp-runtime-template.js +18 -16
- package/dist/generators/mcp-runtime-template.js.map +1 -1
- package/dist/generators/mcp-tool-name.d.ts +6 -0
- package/dist/generators/mcp-tool-name.d.ts.map +1 -1
- package/dist/generators/mcp-tool-name.js.map +1 -1
- package/dist/generators/mcp.d.ts +34 -1
- package/dist/generators/mcp.d.ts.map +1 -1
- package/dist/generators/mcp.js +148 -110
- package/dist/generators/mcp.js.map +1 -1
- package/dist/generators/rest.d.ts.map +1 -1
- package/dist/generators/rest.js +16 -7
- package/dist/generators/rest.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -7
- package/dist/junction.d.ts +29 -10
- package/dist/junction.d.ts.map +1 -1
- package/dist/junction.js +31 -12
- package/dist/junction.js.map +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/knowledge.js.map +1 -1
- package/dist/manifest/manifest-loader.d.ts.map +1 -1
- package/dist/manifest/manifest-loader.js +1 -1
- package/dist/manifest/manifest-loader.js.map +1 -1
- package/dist/manifest/static-manifest.d.ts.map +1 -1
- package/dist/manifest/static-manifest.js +106 -9
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.d.ts +8 -0
- package/dist/manifest/store.d.ts.map +1 -1
- package/dist/manifest/store.js +12 -2
- package/dist/manifest/store.js.map +1 -1
- package/dist/manifest.json +119 -9
- package/dist/migrations/differ.d.ts +3 -3
- package/dist/migrations/differ.d.ts.map +1 -1
- package/dist/migrations/differ.js +8 -4
- package/dist/migrations/differ.js.map +1 -1
- package/dist/object.d.ts +1 -0
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +19 -7
- package/dist/object.js.map +1 -1
- package/dist/registry/class-registration.d.ts.map +1 -1
- package/dist/registry/class-registration.js +75 -10
- package/dist/registry/class-registration.js.map +1 -1
- package/dist/registry/collection-resolution.d.ts +2 -2
- package/dist/registry/collection-resolution.d.ts.map +1 -1
- package/dist/registry/collection-resolution.js +2 -1
- package/dist/registry/collection-resolution.js.map +1 -1
- package/dist/registry/collision-policy.d.ts +8 -0
- package/dist/registry/collision-policy.d.ts.map +1 -1
- package/dist/registry/collision-policy.js +2 -2
- package/dist/registry/collision-policy.js.map +1 -1
- package/dist/registry/framework-base-classes.d.ts.map +1 -1
- package/dist/registry/framework-base-classes.js +2 -1
- package/dist/registry/framework-base-classes.js.map +1 -1
- package/dist/registry/schema-builder.d.ts.map +1 -1
- package/dist/registry/schema-builder.js +33 -4
- package/dist/registry/schema-builder.js.map +1 -1
- package/dist/registry/shared-state.d.ts +6 -0
- package/dist/registry/shared-state.d.ts.map +1 -1
- package/dist/registry/shared-state.js +12 -1
- package/dist/registry/shared-state.js.map +1 -1
- package/dist/registry/types.d.ts +7 -1
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry.d.ts +7 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +25 -3
- package/dist/registry.js.map +1 -1
- package/dist/scanner/manifest-generator.d.ts.map +1 -1
- package/dist/scanner/manifest-generator.js +5 -1
- package/dist/scanner/manifest-generator.js.map +1 -1
- package/dist/scanner/types.d.ts +8 -0
- package/dist/scanner/types.d.ts.map +1 -1
- package/dist/scanner/types.js.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.d.ts.map +1 -1
- package/dist/schema/ddl/duckdb-strategy.js +1 -0
- package/dist/schema/ddl/duckdb-strategy.js.map +1 -1
- package/dist/schema/ddl/null-equal-index.d.ts.map +1 -1
- package/dist/schema/ddl/null-equal-index.js +4 -2
- package/dist/schema/ddl/null-equal-index.js.map +1 -1
- package/dist/schema/generator.d.ts +3 -0
- package/dist/schema/generator.d.ts.map +1 -1
- package/dist/schema/generator.js +38 -0
- package/dist/schema/generator.js.map +1 -1
- package/dist/smrt-knowledge.json +74 -20
- package/dist/sync/apply.d.ts +2 -0
- package/dist/sync/apply.d.ts.map +1 -1
- package/dist/sync/apply.js +7 -3
- package/dist/sync/apply.js.map +1 -1
- package/dist/utils/scanner-module.d.ts +9 -0
- package/dist/utils/scanner-module.d.ts.map +1 -1
- package/dist/vite-plugin/index.d.ts +14 -0
- package/dist/vite-plugin/index.d.ts.map +1 -1
- package/dist/vite-plugin/index.js +58 -18
- package/dist/vite-plugin/index.js.map +1 -1
- package/dist/vite-plugin/package-stamp.d.ts +41 -0
- package/dist/vite-plugin/package-stamp.d.ts.map +1 -0
- package/dist/vite-plugin/package-stamp.js +33 -0
- package/dist/vite-plugin/package-stamp.js.map +1 -0
- package/dist/vite-plugin/sveltekit-generator.js +5 -3
- package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
- package/dist/vite-plugin/sync-apply-route.d.ts.map +1 -1
- package/dist/vite-plugin/sync-apply-route.js +10 -0
- package/dist/vite-plugin/sync-apply-route.js.map +1 -1
- package/dist/vite-plugin.js +2 -2
- 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.
|
package/agents/change-feed.md
CHANGED
|
@@ -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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
package/agents/generators.md
CHANGED
|
@@ -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
|
package/agents/object-runtime.md
CHANGED
|
@@ -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
|
package/agents/schema-paths.md
CHANGED
|
@@ -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"}
|
package/dist/audit.d.ts
ADDED
|
@@ -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';
|
package/dist/browser.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,cAAc,kBAAkB,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 {
|
|
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
|
|
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
|
/**
|
package/dist/cascade.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAGH,OAAO,KAAK,
|
|
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
|
|
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
|
-
|
|
453
|
-
|
|
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
|
-
|
|
466
|
-
|
|
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 };
|