@happyvertical/smrt-core 0.51.31 → 0.51.33
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/build-knowledge.md +13 -0
- package/dist/__tests__/fixtures/collection-coverage-fixtures.d.ts +8 -0
- package/dist/__tests__/fixtures/inheritance-resolver-fixtures.d.ts +9 -0
- package/dist/__tests__/fixtures/inheritance-test-classes.d.ts +5 -0
- package/dist/__tests__/fixtures/object-ai-memory-fixtures.d.ts +10 -0
- package/dist/__typechecks__/collection-read-plan.d.ts +2 -2
- package/dist/adapters/ai-usage.d.ts +5 -2
- package/dist/adapters/cost-rates.d.ts +1 -1
- package/dist/adapters/metrics.d.ts +7 -1
- package/dist/adapters/pubsub.d.ts +7 -1
- package/dist/browser.d.ts +1 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -1
- package/dist/cascade.d.ts +69 -3
- package/dist/change-feed-authz.d.ts +85 -2
- package/dist/change-feed.d.ts +133 -2
- package/dist/change-signals.d.ts +66 -1
- package/dist/child-accessors.d.ts +39 -1
- package/dist/class.d.ts +17 -8
- package/dist/class.d.ts.map +1 -1
- package/dist/class.js +22 -0
- package/dist/class.js.map +1 -1
- package/dist/collection-cache.d.ts +47 -1
- package/dist/collection-read-plan.d.ts +3 -3
- package/dist/collection.d.ts +4 -3
- package/dist/config.d.ts +15 -6
- package/dist/consumer-plugin/index.d.ts +7 -3
- package/dist/consumer-plugin/index.d.ts.map +1 -1
- package/dist/consumer-plugin/index.js +2 -0
- package/dist/consumer-plugin/index.js.map +1 -1
- package/dist/data-query.d.ts +12 -1
- package/dist/database.d.ts +10 -2
- package/dist/decisions.d.ts +24 -2
- package/dist/decisions.d.ts.map +1 -1
- package/dist/decisions.js +71 -1
- package/dist/decisions.js.map +1 -1
- package/dist/decorators/index.d.ts +11 -5
- package/dist/dispatch/bus.d.ts +27 -2
- package/dist/dispatch/collections/DispatchSubscriptions.d.ts +7 -2
- package/dist/dispatch/collections/Dispatches.d.ts +8 -3
- package/dist/dispatch/models/Dispatch.d.ts +7 -1
- package/dist/dispatch/types.d.ts +7 -2
- package/dist/embedded-write-queue.d.ts +1 -1
- package/dist/embeddings/provider.d.ts +8 -1
- package/dist/embeddings/storage.d.ts +8 -2
- package/dist/filesystem-loader.d.ts +16 -1
- package/dist/generators/changes-route.d.ts +34 -1
- package/dist/generators/custom-action.d.ts +10 -2
- package/dist/generators/events-route.d.ts +41 -4
- package/dist/generators/mcp-runtime-template.d.ts +13 -2
- package/dist/generators/mcp.d.ts +6 -1
- package/dist/generators/rest.d.ts +10 -5
- package/dist/generators/tool-schema.d.ts +1 -1
- package/dist/hierarchical.d.ts +37 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/interceptors.d.ts +45 -1
- package/dist/junction.d.ts +24 -0
- package/dist/knowledge-config.d.ts +1 -1
- package/dist/knowledge-discovery.d.ts +2 -25
- package/dist/knowledge-discovery.d.ts.map +1 -1
- package/dist/knowledge-discovery.js +1 -53
- package/dist/knowledge-graph.d.ts +2 -94
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +1 -267
- package/dist/knowledge.d.ts +4 -23
- package/dist/knowledge.d.ts.map +1 -1
- package/dist/knowledge.js +3 -52
- package/dist/knowledge.js.map +1 -1
- package/dist/learning/memory.d.ts +21 -1
- package/dist/manifest/generator.d.ts +5 -1
- package/dist/manifest/index.d.ts +5 -1
- package/dist/manifest/manager.d.ts +1 -1
- package/dist/manifest/manifest-loader.d.ts +24 -2
- package/dist/manifest/sources/composite.d.ts +17 -1
- package/dist/manifest/sources/embedded.d.ts +10 -1
- package/dist/manifest/sources/explicit-paths.d.ts +13 -1
- package/dist/manifest/sources/fallback.d.ts +15 -1
- package/dist/manifest/sources/local-test.d.ts +10 -1
- package/dist/manifest/sources/static.d.ts +12 -1
- package/dist/manifest/sources/test.d.ts +9 -1
- package/dist/manifest/sources/types.d.ts +7 -1
- package/dist/manifest/static-manifest.d.ts +6 -1
- package/dist/manifest/static-manifest.js +1 -1
- package/dist/manifest/static-manifest.js.map +1 -1
- package/dist/manifest/store.d.ts +22 -3
- package/dist/manifest/store.js +1 -1
- package/dist/manifest.json +1 -1
- package/dist/migrations/backfill-tracker.d.ts +15 -1
- package/dist/migrations/differ.d.ts +8 -2
- package/dist/migrations/framework-base-tables.d.ts +57 -2
- package/dist/migrations/generator.d.ts +8 -2
- package/dist/migrations/integer-width.d.ts +10 -2
- package/dist/migrations/minor-units.d.ts +40 -1
- package/dist/migrations/null-equal-indexes.d.ts +3 -2
- package/dist/migrations/orchestrate.d.ts +15 -3
- package/dist/migrations/sqlite-rebuild.d.ts +67 -2
- package/dist/migrations/tracker.d.ts +9 -3
- package/dist/migrations/types.d.ts +7 -2
- package/dist/object.d.ts +7 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +6 -17
- package/dist/object.js.map +1 -1
- package/dist/polymorphic-association.d.ts +44 -1
- package/dist/postgres-permissions.d.ts +1 -1
- package/dist/prebuild/index.d.ts +5 -1
- package/dist/query-bounds.d.ts +26 -0
- package/dist/registry/cache-config.d.ts +10 -1
- package/dist/registry/class-registration.d.ts +10 -2
- package/dist/registry/collection-resolution.d.ts +1 -1
- package/dist/registry/embedding-manager.d.ts +9 -1
- package/dist/registry/inheritance-resolver.d.ts +8 -3
- package/dist/registry/manifest-field-merge.d.ts +2 -2
- package/dist/registry/name-resolver.d.ts +22 -2
- package/dist/registry/relationship-graph.d.ts +9 -1
- package/dist/registry/schema-builder.d.ts +8 -3
- package/dist/registry/shared-state.d.ts +12 -2
- package/dist/registry/types.d.ts +15 -7
- package/dist/registry/validator.d.ts +10 -2
- package/dist/registry.d.ts +40 -11
- package/dist/relationship-loader.d.ts +1 -1
- package/dist/run-once.d.ts +110 -1
- package/dist/runtime/client.d.ts +4 -1
- package/dist/runtime/server.d.ts +5 -2
- package/dist/scanner/import-scanner.d.ts +1 -1
- package/dist/scanner/manifest-generator.d.ts +4 -1
- package/dist/scanner/types.d.ts +4 -1
- package/dist/schema/code-generator.d.ts +6 -2
- package/dist/schema/column-data-probes.d.ts +27 -2
- package/dist/schema/ddl/base-strategy.d.ts +8 -2
- package/dist/schema/ddl/duckdb-strategy.d.ts +14 -2
- package/dist/schema/ddl/index.d.ts +2 -2
- package/dist/schema/ddl/json-duckdb-strategy.d.ts +10 -2
- package/dist/schema/ddl/materialize-manifest.d.ts +1 -1
- package/dist/schema/ddl/null-equal-index.d.ts +1 -1
- package/dist/schema/ddl/postgres-strategy.d.ts +12 -2
- package/dist/schema/ddl/sqlite-strategy.d.ts +12 -2
- package/dist/schema/ddl/types.d.ts +10 -1
- package/dist/schema/foreign-key-ddl.d.ts +2 -2
- package/dist/schema/foreign-key-orphan-report.d.ts +25 -3
- package/dist/schema/foreign-key-planner.d.ts +2 -2
- package/dist/schema/foreign-key-policy.d.ts +1 -1
- package/dist/schema/generator.d.ts +7 -3
- package/dist/schema/index-utils.d.ts +11 -2
- package/dist/schema/live-parity.d.ts +27 -3
- package/dist/schema/manifest-schema.d.ts +21 -2
- package/dist/schema/schema-aggregator.d.ts +35 -2
- package/dist/schema/schema-manager.d.ts +13 -4
- package/dist/schema/system-table-shapes.d.ts +20 -1
- package/dist/schema/table-verifier.d.ts +1 -1
- package/dist/schema/text-cast-probe.d.ts +24 -1
- package/dist/schema/utils.d.ts +13 -4
- package/dist/schema/uuid-convergence.d.ts +32 -1
- package/dist/signals/bus.d.ts +8 -2
- package/dist/signals/sanitizer.d.ts +7 -1
- package/dist/smrt-knowledge.json +8 -9
- package/dist/system/bootstrap.d.ts +2 -1
- package/dist/system/compatibility.d.ts +1 -1
- package/dist/system/diagnostics.d.ts +17 -2
- package/dist/system/retention.d.ts +46 -1
- package/dist/system/schema.d.ts +7 -1
- package/dist/system-fields.d.ts +9 -1
- package/dist/test-utils.d.ts +7 -1
- package/dist/testing/database.d.ts +18 -1
- package/dist/testing/notifications-stub.d.ts +15 -1
- package/dist/tools/tool-executor.d.ts +6 -1
- package/dist/tools/tool-generator.d.ts +8 -2
- package/dist/utils/json.d.ts +9 -1
- package/dist/utils/qualified-names.d.ts +10 -1
- package/dist/utils/scanner-module.d.ts +2 -2
- package/dist/utils.d.ts +1 -1
- package/dist/vite-plugin/api-client-entries.d.ts +9 -1
- package/dist/vite-plugin/changes-route.d.ts +33 -2
- package/dist/vite-plugin/dev-plane-route.d.ts +12 -1
- package/dist/vite-plugin/events-route.d.ts +37 -2
- package/dist/vite-plugin/generated-client.d.ts +9 -1
- package/dist/vite-plugin/index.d.ts +9 -5
- package/dist/vite-plugin/resources-route.d.ts +82 -1
- package/dist/vite-plugin/sveltekit-generator.d.ts +8 -4
- package/dist/vite-plugin/sveltekit-route-coordinator.d.ts +3 -3
- package/dist/vite-plugin/sync-apply-route.d.ts +19 -2
- package/dist/vite-plugin/web-collections.d.ts +25 -2
- package/dist/vite-plugin/worker-registration.d.ts +21 -1
- package/package.json +7 -8
- package/dist/knowledge-discovery.js.map +0 -1
- package/dist/knowledge-graph.js.map +0 -1
|
@@ -33,6 +33,10 @@ importing one fails at worker startup. A failure fails the build.
|
|
|
33
33
|
|
|
34
34
|
## Lightweight discovery
|
|
35
35
|
|
|
36
|
+
Filesystem-only discovery, module-doc parsing, and graph freshness are owned by
|
|
37
|
+
`@happyvertical/smrt-scanner/knowledge`. Core's knowledge exports remain compatible
|
|
38
|
+
reexports. Runtime manifest projection and custom-action semantics stay in core.
|
|
39
|
+
|
|
36
40
|
`src/knowledge-discovery.ts`, exported through `smrt-core/knowledge`, enumerates
|
|
37
41
|
installed scope directories and reads canonical AGENTS/legacy CLAUDE docs without
|
|
38
42
|
loading package code, artifacts, or scanning objects. CLI snapshots and MCP share
|
|
@@ -119,3 +123,12 @@ digests; each plugin selects its own view. Reuse mode fails closed on
|
|
|
119
123
|
byte/provenance/path/content drift, skips scans and manifest writes, and still
|
|
120
124
|
generates routes, types, registration, and virtual modules. Omit it for normal
|
|
121
125
|
local development and watch mode.
|
|
126
|
+
|
|
127
|
+
## Clean consumer type checks (TypeScript 6)
|
|
128
|
+
|
|
129
|
+
The consumer config hook writes `.smrt/register.js` alongside generated types
|
|
130
|
+
after route preflight succeeds. Generated SvelteKit registration imports that
|
|
131
|
+
module, so it must exist during `svelte-kit sync`, before Vite's `buildStart`.
|
|
132
|
+
Both hosted routes and the legacy `svelteKit: true` mode follow this order.
|
|
133
|
+
The clean-consumer regression fixture ships real provider declarations and
|
|
134
|
+
checks the generated imports with `svelte-check`.
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixtures for collection.ts coverage tests (convenience finders, count,
|
|
3
|
+
* getDiff, getOrUpsert, and collection-scoped memory).
|
|
4
|
+
*
|
|
5
|
+
* Separate file so the @smrt() classes land in the test manifest.
|
|
6
|
+
*
|
|
7
|
+
* @see src/collection.ts (issue #1500 coverage, ORM group)
|
|
8
|
+
*/
|
|
1
9
|
import { SmrtCollection } from '../../collection.js';
|
|
2
10
|
import { SmrtObject } from '../../object.js';
|
|
3
11
|
export declare class CovWidget extends SmrtObject {
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixture classes for inheritance-resolver coverage tests.
|
|
3
|
+
*
|
|
4
|
+
* Kept in a separate file (not the test file) so they are picked up by
|
|
5
|
+
* test-manifest generation. Mirrors the convention used by
|
|
6
|
+
* inheritance-test-classes.ts.
|
|
7
|
+
*
|
|
8
|
+
* @see src/registry/inheritance-resolver.ts (issue #1500 coverage, ORM group)
|
|
9
|
+
*/
|
|
1
10
|
import { SmrtHierarchical } from '../../hierarchical.js';
|
|
2
11
|
import { SmrtObject } from '../../object.js';
|
|
3
12
|
export declare class IRBase extends SmrtObject {
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test fixture classes for inheritance tests
|
|
3
|
+
* These classes must be in a separate file (not in the test file itself)
|
|
4
|
+
* so they can be included in the test manifest generation
|
|
5
|
+
*/
|
|
1
6
|
import { SmrtObject } from '../../object.js';
|
|
2
7
|
export declare class InheritanceTestContent extends SmrtObject {
|
|
3
8
|
title: string;
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixtures for object.ts AI + memory coverage tests.
|
|
3
|
+
*
|
|
4
|
+
* Separate file so the @smrt() classes are picked up by test-manifest
|
|
5
|
+
* generation. The AI client is injectable so is()/do()/describe() can run
|
|
6
|
+
* against a stub without a real provider (mocking AI is the only mocking
|
|
7
|
+
* allowed by the testing rules).
|
|
8
|
+
*
|
|
9
|
+
* @see src/object.ts (issue #1500 coverage, ORM group)
|
|
10
|
+
*/
|
|
1
11
|
import { SmrtObject } from '../../object.js';
|
|
2
12
|
/** Minimal stub matching the slice of AIClient that is()/do()/describe() use. */
|
|
3
13
|
export interface StubAi {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SmrtCollectionReadPlan, SmrtCollectionReadPlanEntry, SmrtCollectionReadPlanResult } from '../collection-read-plan';
|
|
2
|
-
import { SmrtObject } from '../object';
|
|
1
|
+
import { type SmrtCollectionReadPlan, type SmrtCollectionReadPlanEntry, type SmrtCollectionReadPlanResult } from '../collection-read-plan';
|
|
2
|
+
import type { SmrtObject } from '../object';
|
|
3
3
|
type Equal<Left, Right> = (<Value>() => Value extends Left ? 1 : 2) extends <Value>() => Value extends Right ? 1 : 2 ? true : false;
|
|
4
4
|
type Expect<Value extends true> = Value;
|
|
5
5
|
type ReadPlanTypeProbe = SmrtObject & {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* In-memory AI usage tracking adapters.
|
|
3
|
+
*/
|
|
4
|
+
import type { AiUsageHandler, AiUsageSnapshot, SmrtAiUsageEvent } from '@happyvertical/smrt-types';
|
|
5
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
3
6
|
/**
|
|
4
7
|
* Collects normalized AI usage events in memory for quick inspection.
|
|
5
8
|
*/
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Adapter for Signal System
|
|
3
|
+
*
|
|
4
|
+
* Tracks execution metrics for SMRT objects in Prometheus-compatible format.
|
|
5
|
+
* Collects counts, durations, error rates, and success rates.
|
|
6
|
+
*/
|
|
7
|
+
import type { Signal, SignalAdapter } from '@happyvertical/smrt-types';
|
|
2
8
|
/**
|
|
3
9
|
* Execution metrics for a specific method
|
|
4
10
|
*/
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Pub/Sub Adapter for Signal System
|
|
3
|
+
*
|
|
4
|
+
* Broadcasts signals to subscribers for real-time updates.
|
|
5
|
+
* Supports multiple subscriber types (callbacks, WebSocket, SSE).
|
|
6
|
+
*/
|
|
7
|
+
import type { Signal, SignalAdapter } from '@happyvertical/smrt-types';
|
|
2
8
|
/**
|
|
3
9
|
* Signal filter function
|
|
4
10
|
*
|
package/dist/browser.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export * from './collection';
|
|
|
20
20
|
export type { AiUsageConfig, GlobalSignalConfig, MetricsConfig, PubSubConfig, } from './config';
|
|
21
21
|
export { config } from './config';
|
|
22
22
|
export { classifyDatabaseError, classifyDialectMessage, type DatabaseErrorClassification, type DatabaseErrorKind, isAbortedTransactionError, isDeterministicDatabaseError, isNotNullViolationError, isTransientDatabaseError, isUniqueViolationError, } from './db-errors';
|
|
23
|
+
export * from './decisions';
|
|
23
24
|
export * from './errors';
|
|
24
25
|
export { type HierarchyView, SmrtHierarchical } from './hierarchical';
|
|
25
26
|
export { type JunctionAttachOptions, type JunctionFilterOptions, SmrtJunction, } from './junction';
|
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;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;
|
|
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"}
|
package/dist/browser.js
CHANGED
|
@@ -7,6 +7,7 @@ import { classifyDatabaseError, classifyDialectMessage, isAbortedTransactionErro
|
|
|
7
7
|
import { AIError, ConfigurationError, DatabaseError, ErrorUtils, FilesystemError, NetworkError, RunOnceClaimError, RuntimeError, SmrtError, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils } from "./errors.js";
|
|
8
8
|
import { SMRT_SCHEMA_VERSION } from "./system/schema.js";
|
|
9
9
|
import { config } from "./config.js";
|
|
10
|
+
import { assertFiniteUnitInterval, executeDecision } from "./decisions.js";
|
|
10
11
|
import { SignalSanitizer } from "./signals/sanitizer.js";
|
|
11
12
|
import { SignalBus } from "./signals/bus.js";
|
|
12
13
|
import { assertPostgresSystemTimestampsCurrent, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, getDatabaseEngine, migratePostgresSystemTimestamps, planPostgresSystemTimestampMigrations, tableExists } from "./system/compatibility.js";
|
|
@@ -28,4 +29,4 @@ import { BOOTED_PROVENANCE, assertPlainJson, sanitizeMessagePaths, snapshotRegis
|
|
|
28
29
|
import { DEFAULT_RETENTION_POLICY, clearRetentionTasks, getRetentionTasks, pruneAiUsage, pruneExpiredContexts, pruneRunOnceClaims, registerRetentionTask, runRetentionSweep, unregisterRetentionTask } from "./system/retention.js";
|
|
29
30
|
import "./system/index.js";
|
|
30
31
|
import "./tools/index.js";
|
|
31
|
-
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, assertPlainJson, assertPostgresSystemTimestampsCurrent, classifyDatabaseError, classifyDialectMessage, clearRetentionTasks, config, convertTypeToJsonSchema, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, 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 };
|
|
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 };
|
package/dist/cascade.d.ts
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* App-side referential integrity for `SmrtObject.delete()` (#2371).
|
|
3
|
+
*
|
|
4
|
+
* Same-package `@foreignKey` fields now emit database constraints where the
|
|
5
|
+
* engine can enforce the declared policy. Cross-package references and older
|
|
6
|
+
* schemas still rely on this application-side path. Before this module,
|
|
7
|
+
* `delete()` removed the object's own row and nothing else: junction rows,
|
|
8
|
+
* polymorphic association rows, `_smrt_embeddings` and `_smrt_contexts` entries
|
|
9
|
+
* were all left pointing at an id that no longer resolved, and
|
|
10
|
+
* `@foreignKey(..., { onDelete })` was metadata nobody read.
|
|
11
|
+
*
|
|
12
|
+
* This module implements those semantics in the application layer. It mirrors
|
|
13
|
+
* what a DB-level constraint would do, deliberately including the parts that
|
|
14
|
+
* make it *unlike* a model-layer delete:
|
|
15
|
+
*
|
|
16
|
+
* - Cascaded rows are removed with set-based statements. Their `beforeDelete` /
|
|
17
|
+
* `afterDelete` hooks and interceptors do **not** run, and no change-feed
|
|
18
|
+
* tombstone is written for them — exactly as `ON DELETE CASCADE` behaves.
|
|
19
|
+
* Only the object `delete()` was called on runs the full lifecycle.
|
|
20
|
+
* - Every delete runs inside a single transaction when the adapter exposes
|
|
21
|
+
* one, including cleanup of framework-owned context and embedding rows, so
|
|
22
|
+
* a partial cascade cannot survive a failure. A `metaType` column can point
|
|
23
|
+
* at any class at runtime, so a polymorphic association class is always
|
|
24
|
+
* plausibly relevant.
|
|
25
|
+
*
|
|
26
|
+
* ## Which references are followed
|
|
27
|
+
*
|
|
28
|
+
* A reference is any `@foreignKey` / `@crossPackageRef` field on a registered
|
|
29
|
+
* class whose target resolves to the deleted object's class (or one of its
|
|
30
|
+
* registered STI ancestors), plus the polymorphic `(metaType, metaId)` pair on
|
|
31
|
+
* {@link SmrtPolymorphicAssociation} subclasses.
|
|
32
|
+
*
|
|
33
|
+
* The action comes from the field's declared `onDelete`. When it is not
|
|
34
|
+
* declared, the default is derived from the referencing class's natural key:
|
|
35
|
+
*
|
|
36
|
+
* | Reference | Default when `onDelete` is absent |
|
|
37
|
+
* |---|---|
|
|
38
|
+
* | Column is part of the referencing class's `conflictColumns`, and is not a `@tenantId()` field | `CASCADE` |
|
|
39
|
+
* | Polymorphic `(metaType, metaId)` association row | `CASCADE` |
|
|
40
|
+
* | Ordinary same-package reference | `NO ACTION` (deletion is refused while references remain) |
|
|
41
|
+
* | Every `@tenantId()` field | Excluded: tenant scope is not an ownership edge |
|
|
42
|
+
*
|
|
43
|
+
* The natural-key rule is what makes junction rows work without any
|
|
44
|
+
* per-package annotation: a junction declares
|
|
45
|
+
* `@smrt({ conflictColumns: ['content_id', 'asset_id', 'relationship'] })`, so
|
|
46
|
+
* `content_id` identifies the row and the row cannot outlive the content it
|
|
47
|
+
* links. An ordinary child (`Order.customerId`) is keyed by `(slug, context)`,
|
|
48
|
+
* so it defaults to immediate `NO ACTION` unless it opts in with
|
|
49
|
+
* `@foreignKey(Customer, { onDelete: 'CASCADE' })`.
|
|
50
|
+
*
|
|
51
|
+
* `@tenantId()` fields are excluded from the natural-key rule even though
|
|
52
|
+
* `@happyvertical/smrt-tenancy` leads a tenant-scoped class's *default*
|
|
53
|
+
* `conflictColumns` with the tenant column (#2360): the tenant column scopes
|
|
54
|
+
* ownership, it does not identify the row the way a junction's foreign key
|
|
55
|
+
* does, and it targets a class (`Tenant`) that is virtually always
|
|
56
|
+
* referenced. Without this exclusion, deleting one `Tenant` row would
|
|
57
|
+
* recursively CASCADE through every tenant-scoped table that has not
|
|
58
|
+
* declared its own `conflictColumns` — the overwhelming majority. The field
|
|
59
|
+
* is detected via the `__tenancy.isTenantIdField` marker `@tenantId()`
|
|
60
|
+
* attaches to its own registration (`FieldMeta.__tenancy`, read structurally
|
|
61
|
+
* so `smrt-core` never depends on `smrt-tenancy`). `@tenantId()` exposes no
|
|
62
|
+
* `onDelete` option today, so this cannot currently be overridden per field.
|
|
63
|
+
*
|
|
64
|
+
* @see https://github.com/happyvertical/smrt/issues/2371
|
|
65
|
+
* @module
|
|
66
|
+
*/
|
|
67
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
68
|
+
import type { ObjectRegistry } from './registry.js';
|
|
69
|
+
import type { ForeignKeyAction } from './schema/types.js';
|
|
4
70
|
/**
|
|
5
71
|
* Referential action applied to rows pointing at a deleted object.
|
|
6
72
|
*
|
|
@@ -1,5 +1,88 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Change-feed table/row authorization seam (issue #3020).
|
|
3
|
+
*
|
|
4
|
+
* The generated `_changes`/`_events` routes authorize on "an authenticated
|
|
5
|
+
* principal" plus tenant scope (`getTenantScopedChangesSince`,
|
|
6
|
+
* `resolveDispatchTenantScope`) — see `change-feed-sensitivity.ts`'s scope
|
|
7
|
+
* note. That is enough to keep one tenant from seeing another's rows, but it
|
|
8
|
+
* cannot express "table X is readable by some principals of the tenant but
|
|
9
|
+
* not others" or "principal P only sees their own rows of table X" (a
|
|
10
|
+
* bay-tablet station versus the back office). `?tables=` was a client-chosen
|
|
11
|
+
* filter, never enforcement: a table the client did not ask for was withheld
|
|
12
|
+
* only because it did not ask, not because it could not have.
|
|
13
|
+
*
|
|
14
|
+
* This module is the consumer-supplied seam that closes that gap, evaluated
|
|
15
|
+
* identically by **every** transport — the generated SvelteKit `_changes`/
|
|
16
|
+
* `_events` routes AND the runtime REST generator's `_changes`/`_events`
|
|
17
|
+
* (`generators/changes-route.ts`, `generators/events-route.ts`) — so no
|
|
18
|
+
* transport can leak what another denies. Two independent hooks,
|
|
19
|
+
* dependency-inverted onto `globalThis` exactly like
|
|
20
|
+
* {@link resolveDispatchTenantScope} (`dispatch/tenant-resolver.ts`) so a
|
|
21
|
+
* consumer app registers them once — wherever it already calls
|
|
22
|
+
* `enableTenancy()`, or its `hooks.server.ts` — with **no change to the
|
|
23
|
+
* generated "DO NOT EDIT" route files' imports**:
|
|
24
|
+
*
|
|
25
|
+
* - `authorizeChangeFeed({ locals, request, tables }) => allowedTables` —
|
|
26
|
+
* table-level. Its result is INTERSECTED with the client's `?tables=`
|
|
27
|
+
* filter (never unioned): a table the hook does not name is never queried,
|
|
28
|
+
* let alone returned, regardless of what the client asked for.
|
|
29
|
+
* - `isChangeFeedEntryVisible({ locals, request, entry }) => boolean` —
|
|
30
|
+
* row-level, for scope narrower than a whole table (a station sees only its
|
|
31
|
+
* own rows). Applied per entry, after the table-level filter, before
|
|
32
|
+
* anything is serialized to the client — including a live `_events` signal,
|
|
33
|
+
* which never carries a payload but still carries the row id and write
|
|
34
|
+
* timing the row hook exists to withhold.
|
|
35
|
+
*
|
|
36
|
+
* ## `locals` vs. `request`
|
|
37
|
+
*
|
|
38
|
+
* SvelteKit routes pass both `event.locals` and `event.request`. The REST
|
|
39
|
+
* generator has no `locals` concept, so it passes `locals: undefined` and the
|
|
40
|
+
* `authMiddleware`-processed `Request` (whatever properties/headers the
|
|
41
|
+
* middleware attached) as `request` — a REST-hosting consumer identifies the
|
|
42
|
+
* principal from `request`, not `locals`. Every real caller supplies
|
|
43
|
+
* `request`; it is typed optional only so a low-level unit test can omit it.
|
|
44
|
+
*
|
|
45
|
+
* Both hooks are optional and independent. When neither is registered, every
|
|
46
|
+
* transport behaves exactly as before (#1540 posture — authenticated +
|
|
47
|
+
* tenant scoped only, no per-table or per-row check): every call in this
|
|
48
|
+
* module is a synchronous no-op pass-through until a consumer opts in.
|
|
49
|
+
*
|
|
50
|
+
* ## Fail-closed
|
|
51
|
+
*
|
|
52
|
+
* A hook that throws, or returns something other than the documented shape,
|
|
53
|
+
* is treated as authorizing nothing for that call — the same posture
|
|
54
|
+
* {@link resolveDispatchTenantScope} takes for a throwing tenant resolver
|
|
55
|
+
* (fail closed rather than fail the request), and the same *shape* of answer
|
|
56
|
+
* (200, empty `changes`, cursor still advances) the change-feed sensitivity
|
|
57
|
+
* filter (#2937) already gives a request naming only tables it may not see.
|
|
58
|
+
* A 5xx here would tell a caller its own hook is broken, which is
|
|
59
|
+
* indistinguishable from "your hook denied me" from the caller's side and
|
|
60
|
+
* would leak that distinction; the contract that matters — never an
|
|
61
|
+
* unfiltered feed — holds identically either way.
|
|
62
|
+
*
|
|
63
|
+
* ## Cursor correctness
|
|
64
|
+
*
|
|
65
|
+
* Every filter here narrows the *returned* `changes` array; nothing here ever
|
|
66
|
+
* touches `cursor`/`resyncRequired`/`resyncCursor`. This mirrors #2937's
|
|
67
|
+
* sensitivity filter (`change-feed.ts`, "Filters ... affect which rows are
|
|
68
|
+
* returned, never how the cursor advances") and for the same reason: a
|
|
69
|
+
* client must advance past a denied entry without re-polling it forever, and
|
|
70
|
+
* the entry's row id/timing must never be inferable from a cursor that stalls
|
|
71
|
+
* on it. Table denial is implemented via `getChangesSince`'s explicit
|
|
72
|
+
* `denyAllTables` option rather than an empty `tables` array —
|
|
73
|
+
* `getChangesSince` treats an omitted/empty `tables` as "no filter", so an
|
|
74
|
+
* empty array would (perversely) widen the read to everything, and an
|
|
75
|
+
* earlier version of this module instead passed a guaranteed-no-match
|
|
76
|
+
* sentinel table name through the ordinary `table_name IN (...)` path; that
|
|
77
|
+
* broke on PostgreSQL, which rejects a NUL byte in a text parameter, turning
|
|
78
|
+
* a denied request into a 500 instead of the required 200 empty page. A
|
|
79
|
+
* `denyAllTables` request skips the `IN (...)` clause entirely — no
|
|
80
|
+
* synthesized value ever reaches the driver — while still running the same
|
|
81
|
+
* horizon/pruned-cursor computation any other filter does, so the cursor
|
|
82
|
+
* still advances correctly; it just matches zero rows.
|
|
83
|
+
*/
|
|
84
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
85
|
+
import { type ChangeFeedPage, type GetChangesOptions } from './change-feed.js';
|
|
3
86
|
/**
|
|
4
87
|
* Minimal shape shared by a durable {@link ChangeFeedEntry} (`change-feed.ts`)
|
|
5
88
|
* and a live `ChangeSignal` (`change-signals.ts`) — both carry exactly
|
package/dist/change-feed.d.ts
CHANGED
|
@@ -1,5 +1,136 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Adapter-agnostic change feed — the framework's change-observation spine
|
|
3
|
+
* (issue #1758, parent PRD #1755).
|
|
4
|
+
*
|
|
5
|
+
* Every framework `save()`/`delete()` appends exactly one row to the
|
|
6
|
+
* `_smrt_changes` system table (monotonic per-database sequence, table name,
|
|
7
|
+
* row id, operation, tenant id, timestamp). Deletes are recorded as
|
|
8
|
+
* tombstones (`operation: 'delete'`), distinguishable from updates. One read
|
|
9
|
+
* interface — {@link getChangesSince} — returns changes after a cursor,
|
|
10
|
+
* filterable by table and tenant, and serves three eventual consumers:
|
|
11
|
+
* client delta pull, the SSE push channel, and the per-table version source
|
|
12
|
+
* backing ETags.
|
|
13
|
+
*
|
|
14
|
+
* ## Cursor semantics (the precise guarantee)
|
|
15
|
+
*
|
|
16
|
+
* Sequences are allocated *inside* the append statement as
|
|
17
|
+
* `COALESCE(MAX(seq), 0) + 1` over the feed table itself, with a retry on
|
|
18
|
+
* primary-key conflict. `MAX(seq)` only observes committed rows, so a row
|
|
19
|
+
* with sequence `N` can only be inserted while every row with sequence
|
|
20
|
+
* `< N` is already committed (a conflicting in-flight allocation of the same
|
|
21
|
+
* value blocks, then retries). Committed rows therefore always form a
|
|
22
|
+
* contiguous run ending at `MAX(seq)` — the **committed horizon**. Sequence
|
|
23
|
+
* order equals commit order; out-of-order commit visibility (the classic
|
|
24
|
+
* MVCC race that makes native identity/serial columns unsafe as cursors
|
|
25
|
+
* under concurrent writers) cannot occur.
|
|
26
|
+
*
|
|
27
|
+
* {@link getChangesSince} reads the committed horizon `H = MAX(seq)`, then
|
|
28
|
+
* returns matching rows with `since < seq <= H` (bounded by `limit`), and a
|
|
29
|
+
* `cursor` that is either `H` (page exhaustive) or the last returned `seq`
|
|
30
|
+
* (page limited). Because no change can ever commit at or below an observed
|
|
31
|
+
* horizon after it was observed, polling with returned cursors misses no
|
|
32
|
+
* committed change and never returns the same change twice — under any
|
|
33
|
+
* number of concurrent writers, identically on SQLite, Postgres and DuckDB.
|
|
34
|
+
* This is the design reason the allocator is `MAX+1` rather than a native
|
|
35
|
+
* AUTOINCREMENT/identity column: identity values are allocated before
|
|
36
|
+
* commit, so a reader on Postgres could observe seq 101 while seq 100 is
|
|
37
|
+
* still uncommitted and advance its cursor past it. (No shared
|
|
38
|
+
* auto-increment mechanism exists in the system-table schema path either;
|
|
39
|
+
* see `system/schema.ts`.)
|
|
40
|
+
*
|
|
41
|
+
* ## Staged appends inside caller transactions (PostgreSQL, #2649)
|
|
42
|
+
*
|
|
43
|
+
* `MAX+1` allocation costs a *wait*: two appends that pick the same value
|
|
44
|
+
* conflict on the primary key, and the loser waits for the winner's
|
|
45
|
+
* transaction to end. Under autocommit that is one statement. Inside a
|
|
46
|
+
* caller-managed transaction it is the whole transaction — and a long write
|
|
47
|
+
* transaction that appends and then keeps taking row locks forms a genuine
|
|
48
|
+
* lock cycle with any writer that took those row locks first and then
|
|
49
|
+
* appended. PostgreSQL detects it (`40P01`) and aborts one side, so an
|
|
50
|
+
* ordinary concurrent request could abort a legitimate long write.
|
|
51
|
+
*
|
|
52
|
+
* PostgreSQL appends issued inside a caller transaction are therefore
|
|
53
|
+
* **staged**: `_smrt_append_change` inserts into `_smrt_changes_pending`,
|
|
54
|
+
* whose identity key conflicts with nothing, so the append never waits on
|
|
55
|
+
* another transaction and the cycle cannot form. The staged row is still
|
|
56
|
+
* fate-shared with the caller (a rollback removes it). {@link drainChangeFeed}
|
|
57
|
+
* — run by {@link getChangesSince} before it reads, and (throttled) by the
|
|
58
|
+
* append path itself — moves *committed* staged rows into `_smrt_changes`
|
|
59
|
+
* under a try-only advisory lock, numbering them `MAX(seq) + row_number()` in
|
|
60
|
+
* staged order. Draining is driven from JavaScript rather than inside the
|
|
61
|
+
* append helper so that every sequenced entry also gets its live `_events`
|
|
62
|
+
* signal: an entry sequenced invisibly server-side would be skipped for good
|
|
63
|
+
* by a subscriber whose `Last-Event-ID` came from a later, higher sequence.
|
|
64
|
+
* Signals for drained entries are published only once the drain is proven
|
|
65
|
+
* committed, so a rollback can never advertise a sequence another appender
|
|
66
|
+
* then reuses.
|
|
67
|
+
*
|
|
68
|
+
* The cursor guarantee is unchanged, because sequences are still allocated by
|
|
69
|
+
* exactly one `MAX+1` writer at a time and only ever for already-committed
|
|
70
|
+
* work: committed sequences stay contiguous, and no entry can appear at or
|
|
71
|
+
* below a horizon a reader already observed. What a staged append gives up is
|
|
72
|
+
* *promptness*, not durability or order: its entry becomes visible one drain
|
|
73
|
+
* after its transaction commits, and its position in the log is its drain
|
|
74
|
+
* order rather than its statement order. An autocommit append drains before
|
|
75
|
+
* allocating whenever its throttle allows, so staged work usually keeps its
|
|
76
|
+
* place ahead of later writes.
|
|
77
|
+
*
|
|
78
|
+
* SQLite and DuckDB keep the direct `MAX+1` insert unchanged — SQLite
|
|
79
|
+
* serializes writers outright, so the defect is unreachable there.
|
|
80
|
+
*
|
|
81
|
+
* Contention note: appends serialize on the head of the log. Each append is
|
|
82
|
+
* one small INSERT (issued from the write path *after* the user's row was
|
|
83
|
+
* written), so the serialization window is one statement; conflicts resolve
|
|
84
|
+
* with a bounded retry loop and are impossible on single-writer engines
|
|
85
|
+
* (SQLite). PostgreSQL invokes that INSERT through the framework-owned
|
|
86
|
+
* `_smrt_append_change` function so failure isolation remains one statement.
|
|
87
|
+
*
|
|
88
|
+
* ## Failure semantics
|
|
89
|
+
*
|
|
90
|
+
* A feed-write failure must never fail the user's write. The interceptor
|
|
91
|
+
* wraps the append in a try/catch: on failure it logs a warning (deduped per
|
|
92
|
+
* database) and continues. The trade-off is availability of the user's
|
|
93
|
+
* write over completeness of the feed — consumers already need a
|
|
94
|
+
* full-resync path for cursors older than the retention window, and the
|
|
95
|
+
* same path covers a (rare) dropped feed row. PostgreSQL runs the INSERT in a
|
|
96
|
+
* PL/pgSQL exception subtransaction and returns a caught SQLSTATE as data;
|
|
97
|
+
* JavaScript only throws/logs after PostgreSQL has restored the caller's
|
|
98
|
+
* transaction, so a swallowed append failure cannot surface later as 25P02.
|
|
99
|
+
* The append still joins a caller-managed transaction on the same handle and
|
|
100
|
+
* shares its fate (a rollback removes the staged row with the data row).
|
|
101
|
+
*
|
|
102
|
+
* ## Known gaps (documented in the PRD)
|
|
103
|
+
*
|
|
104
|
+
* - Writes that bypass framework mutation paths (raw SQL) are invisible to
|
|
105
|
+
* the feed — the same accepted gap as the #1499 collection cache.
|
|
106
|
+
* {@link bumpChangeFeed} is the manual escape hatch: out-of-band writers
|
|
107
|
+
* append a synthetic change row for the affected table.
|
|
108
|
+
* - **Spurious `update` entries**: `SmrtObject.save()` has no dirty-check,
|
|
109
|
+
* so a field-unchanged `.save()` still appends an `update` row. This is
|
|
110
|
+
* by design — the writer observes writes, not diffs (it has no old-row
|
|
111
|
+
* access), so the feed faithfully mirrors the write path. Diff-aware
|
|
112
|
+
* paths (`getOrUpsert()`'s diff guard, the sync-apply endpoint's no-op
|
|
113
|
+
* detection) short-circuit before `save()` and append nothing.
|
|
114
|
+
* Subscribers must tolerate spurious entries; they are convergent — a
|
|
115
|
+
* re-fetch returns identical data.
|
|
116
|
+
*
|
|
117
|
+
* ## Retention
|
|
118
|
+
*
|
|
119
|
+
* The log is append-only and grows with write volume. {@link pruneChangeFeed}
|
|
120
|
+
* bounds it by age (`maxAgeMs`) and/or row count (`maxRows`); call it from a
|
|
121
|
+
* scheduled job sized so the retention window comfortably exceeds the
|
|
122
|
+
* slowest consumer's polling interval. Pruning deletes oldest-first and
|
|
123
|
+
* always retains the newest entry, so retained sequences stay a contiguous
|
|
124
|
+
* `[floor..horizon]` run — which is how {@link getChangesSince} *detects* a
|
|
125
|
+
* consumer whose cursor predates the retained window and answers it with
|
|
126
|
+
* `resyncRequired: true` plus a fresh resume cursor instead of silently
|
|
127
|
+
* skipping the pruned changes.
|
|
128
|
+
*
|
|
129
|
+
* @see https://github.com/happyvertical/smrt/issues/1758
|
|
130
|
+
* @packageDocumentation
|
|
131
|
+
*/
|
|
132
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
133
|
+
import type { SmrtObject } from './object.js';
|
|
3
134
|
/** Name of the append-only change-feed system table. */
|
|
4
135
|
export declare const CHANGE_FEED_TABLE = "_smrt_changes";
|
|
5
136
|
/**
|
package/dist/change-signals.d.ts
CHANGED
|
@@ -1,4 +1,69 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Change-signal bus — the live push spine for the generated `_events` SSE
|
|
3
|
+
* route (issue #1763, parent PRD #1755).
|
|
4
|
+
*
|
|
5
|
+
* The change feed (#1758) is a durable, cursor-addressable log; this bus is
|
|
6
|
+
* its ephemeral companion. Every framework `save()`/`delete()` that appends a
|
|
7
|
+
* feed row also publishes a coarse {@link ChangeSignal} here, which fans out
|
|
8
|
+
* synchronously to in-process subscribers (the SSE controllers of connected
|
|
9
|
+
* `_events` clients) and, when the database adapter exposes a notification
|
|
10
|
+
* capability, to peer replicas over the same channel. Absence of that
|
|
11
|
+
* capability degrades gracefully — no cross-replica push, never an error, and
|
|
12
|
+
* it never blocks the write.
|
|
13
|
+
*
|
|
14
|
+
* ## What a signal carries (and deliberately does not)
|
|
15
|
+
*
|
|
16
|
+
* A signal is `{ table, operation, rowId, tenantId, seq }` — never any row
|
|
17
|
+
* payload. Authorization stays entirely on the read path: a subscriber learns
|
|
18
|
+
* *that* something changed and its cursor (`seq`), then re-reads through the
|
|
19
|
+
* authorized collection routes to catch up. This is why the bus can broadcast
|
|
20
|
+
* a tenant's writes to peer replicas without leaking data across a trust
|
|
21
|
+
* boundary. The `seq` is the same monotonic cursor dimension the change feed
|
|
22
|
+
* allocates, so a reconnecting client can resume via `getChangesSince`.
|
|
23
|
+
*
|
|
24
|
+
* ## Delivery model
|
|
25
|
+
*
|
|
26
|
+
* - Local delivery is a synchronous fan-out ({@link deliverLocally}) into each
|
|
27
|
+
* listener callback, wrapped in a try/catch **per listener** so one throwing
|
|
28
|
+
* listener (e.g. a closed SSE controller's `enqueue`) never blocks the
|
|
29
|
+
* others. There is no per-subscriber queue — backpressure is delegated to
|
|
30
|
+
* each platform `ReadableStream`.
|
|
31
|
+
* - Cross-replica delivery reuses the same {@link deliverLocally} helper on
|
|
32
|
+
* receipt, so locally-published and peer-received signals travel one code
|
|
33
|
+
* path. Notifications this process published are skipped by `source` id
|
|
34
|
+
* (echo-avoidance), exactly as the collection cache does.
|
|
35
|
+
*
|
|
36
|
+
* This mirrors `collection-cache.ts`'s notify/listen structure; study that
|
|
37
|
+
* module for the shared cross-process conventions (`resolveDbCacheKey`,
|
|
38
|
+
* `getNotifications`, the lazy listener and finally-retract).
|
|
39
|
+
*
|
|
40
|
+
* ## Known gaps
|
|
41
|
+
*
|
|
42
|
+
* - **Subscriber cap lives at the route boundary**: the bus tracks subscribers,
|
|
43
|
+
* but generated `_events` routes decide whether to reject a new connection
|
|
44
|
+
* before opening a stream (#1860).
|
|
45
|
+
* - **Raw-SQL writes are invisible**: signals originate from the framework
|
|
46
|
+
* write path (same accepted gap as the #1758 feed and #1498 cache). A
|
|
47
|
+
* `bumpChangeFeed` escape-hatch write appends a feed row but does not
|
|
48
|
+
* publish a signal.
|
|
49
|
+
* - **Caller-managed transactions signal late, not early** (#2649): a
|
|
50
|
+
* PostgreSQL append made inside a caller transaction that has already
|
|
51
|
+
* written is *staged*, so it has no sequence to signal with at
|
|
52
|
+
* `afterSave`/`afterDelete` time. Its signal is published instead by the
|
|
53
|
+
* drain that sequences it, which is the first post-commit moment — closing
|
|
54
|
+
* the older hazard where a pre-commit signal could describe a write a later
|
|
55
|
+
* rollback undid, and where the rolled-back seq was reused by the next
|
|
56
|
+
* append. The cost is latency: live subscribers see a transaction-wrapped
|
|
57
|
+
* write one drain after it commits, and on engines without staging
|
|
58
|
+
* (SQLite, DuckDB) the pre-commit caveat still applies. The autocommit
|
|
59
|
+
* default path — save()/delete() as independent statements — is exact and
|
|
60
|
+
* unchanged. Clients reconcile via cursor catch-up either way, so
|
|
61
|
+
* convergence holds.
|
|
62
|
+
*
|
|
63
|
+
* @see https://github.com/happyvertical/smrt/issues/1763
|
|
64
|
+
* @packageDocumentation
|
|
65
|
+
*/
|
|
66
|
+
import type { DatabaseInterface } from '@happyvertical/sql';
|
|
2
67
|
/**
|
|
3
68
|
* Operation carried by a change signal. Kept as a local string union — this
|
|
4
69
|
* module deliberately does NOT import `./change-feed.js` (which imports this
|
|
@@ -1,4 +1,42 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* R10 — consolidated child-accessor API for `@oneToMany` relationships.
|
|
3
|
+
*
|
|
4
|
+
* Before R10 every model hand-rolled its own child accessor (`getTerms`,
|
|
5
|
+
* `getChildren`, `getForProfile`, …) with inconsistent names and shapes. This
|
|
6
|
+
* module installs ONE canonical accessor per `@oneToMany` field at class
|
|
7
|
+
* registration time: a `@oneToMany('Child') items` field gains a `getItems()`
|
|
8
|
+
* instance method that delegates to the existing `loadRelatedMany('items')`
|
|
9
|
+
* resolver — the same code path used for lazy loading and `include:` eager
|
|
10
|
+
* loading, which itself queries through the child collection (the queryable
|
|
11
|
+
* primitive).
|
|
12
|
+
*
|
|
13
|
+
* Two invariants make this safe to apply to every registered class:
|
|
14
|
+
*
|
|
15
|
+
* - **Additive.** If a method of the computed name already exists anywhere on
|
|
16
|
+
* the prototype chain — a hand-rolled accessor like
|
|
17
|
+
* `ProfileRelationship.getTerms()`, a differently-shaped `Profile.getMetadata()`,
|
|
18
|
+
* or a `SmrtObject` base method — it is left untouched. The generated
|
|
19
|
+
* accessor never renames or shadows an existing public signature.
|
|
20
|
+
* - **Runtime-only.** Accessors are attached to the prototype at registration;
|
|
21
|
+
* they are invisible to the build-time manifest, so they never leak into the
|
|
22
|
+
* generated REST/CLI/MCP surface (those read `classInfo.tools`/scanned
|
|
23
|
+
* methods, not the runtime prototype). They are a developer-ergonomics
|
|
24
|
+
* convenience layered on top of `loadRelatedMany`.
|
|
25
|
+
*
|
|
26
|
+
* Multiplicity note: when the child class declares more than one foreign key
|
|
27
|
+
* back to the parent (e.g. `ProfileRelationship.fromProfileId` /
|
|
28
|
+
* `toProfileId`), annotate the `@oneToMany` with `{ foreignKey: 'fromProfileId' }`
|
|
29
|
+
* so `loadRelatedMany` resolves the correct inverse side. Without it the
|
|
30
|
+
* resolver falls back to the first matching foreign key (legacy behavior). An
|
|
31
|
+
* explicit `foreignKey` that matches no inverse FK is treated as an error.
|
|
32
|
+
*
|
|
33
|
+
* STI note: a child class inherits the base's generated accessor through the
|
|
34
|
+
* prototype chain. `loadRelatedMany` resolves the inverse FK against the
|
|
35
|
+
* instance's class AND its registered ancestors (see
|
|
36
|
+
* `ObjectRegistry.getInverseRelationshipsForSelf`), so calling the accessor on
|
|
37
|
+
* an STI subclass instance resolves a `@oneToMany` declared on its base.
|
|
38
|
+
*/
|
|
39
|
+
import type { RelationshipMetadata } from './registry/types';
|
|
2
40
|
/**
|
|
3
41
|
* Compute the canonical child-accessor method name for a `@oneToMany` field.
|
|
4
42
|
*
|
package/dist/class.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { AIClient, AIClientOptions } from '@happyvertical/ai';
|
|
2
|
-
import { FilesystemAdapter, FilesystemAdapterOptions } from '@happyvertical/files';
|
|
3
|
-
import { LoggerConfig } from '@happyvertical/logger';
|
|
4
|
-
import { AiUsageListOptions, AiUsageSnapshot, AiUsageStats, AiUsageSummaryOptions, SignalAdapter, SmrtAiUsageRecord } from '@happyvertical/smrt-types';
|
|
5
|
-
import { DatabaseInterface } from '@happyvertical/sql';
|
|
6
|
-
import { AiUsageConfig, MetricsConfig, PubSubConfig } from './config.js';
|
|
7
|
-
import { DatabaseConfig } from './database.js';
|
|
8
|
-
import { DecisionClient, DecisionConfig } from './decisions.js';
|
|
1
|
+
import type { AIClient, AIClientOptions } from '@happyvertical/ai';
|
|
2
|
+
import type { FilesystemAdapter, FilesystemAdapterOptions } from '@happyvertical/files';
|
|
3
|
+
import { type LoggerConfig } from '@happyvertical/logger';
|
|
4
|
+
import type { AiUsageListOptions, AiUsageSnapshot, AiUsageStats, AiUsageSummaryOptions, SignalAdapter, SmrtAiUsageRecord } from '@happyvertical/smrt-types';
|
|
5
|
+
import { type DatabaseInterface } from '@happyvertical/sql';
|
|
6
|
+
import type { AiUsageConfig, MetricsConfig, PubSubConfig } from './config.js';
|
|
7
|
+
import type { DatabaseConfig } from './database.js';
|
|
8
|
+
import { type DecisionClient, type DecisionConfig, type DecisionOptions, type DecisionRequest, type DecisionResult } from './decisions.js';
|
|
9
9
|
import { SignalBus } from './signals/bus.js';
|
|
10
10
|
/**
|
|
11
11
|
* Configuration options for the SmrtClass
|
|
@@ -217,6 +217,15 @@ export declare class SmrtClass {
|
|
|
217
217
|
* the decision provider.
|
|
218
218
|
*/
|
|
219
219
|
protected getDecisionClient(): Promise<DecisionClient | undefined>;
|
|
220
|
+
/**
|
|
221
|
+
* Attempt one typed decision batch against caller-curated state.
|
|
222
|
+
*
|
|
223
|
+
* `undefined` is the explicit legacy-route signal: no decision client was
|
|
224
|
+
* configured. Configured capability, provider, and response errors always
|
|
225
|
+
* propagate. Object methods with registered tools must retain their
|
|
226
|
+
* generation route and therefore do not call this helper.
|
|
227
|
+
*/
|
|
228
|
+
protected attemptDecision(request: DecisionRequest, options?: DecisionOptions): Promise<DecisionResult | undefined>;
|
|
220
229
|
/** Resolve project, global, and instance decision options in that order. */
|
|
221
230
|
protected getDecisionConfig(): DecisionConfig | undefined;
|
|
222
231
|
/**
|