@happyvertical/smrt-core 0.42.6 → 0.42.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/AGENTS.md +13 -41
  2. package/agents/collection-reads.md +40 -0
  3. package/agents/data-query.md +21 -0
  4. package/agents/latest-related.md +35 -0
  5. package/agents/memory.md +16 -0
  6. package/dist/browser.js +2 -2
  7. package/dist/class.d.ts +7 -0
  8. package/dist/class.d.ts.map +1 -1
  9. package/dist/class.js +9 -0
  10. package/dist/class.js.map +1 -1
  11. package/dist/collection.d.ts +164 -3
  12. package/dist/collection.d.ts.map +1 -1
  13. package/dist/collection.js +345 -10
  14. package/dist/collection.js.map +1 -1
  15. package/dist/generators/rest.d.ts.map +1 -1
  16. package/dist/generators/rest.js +10 -2
  17. package/dist/generators/rest.js.map +1 -1
  18. package/dist/generators/tool-schema.d.ts +14 -0
  19. package/dist/generators/tool-schema.d.ts.map +1 -1
  20. package/dist/generators/tool-schema.js +13 -1
  21. package/dist/generators/tool-schema.js.map +1 -1
  22. package/dist/index.js +2 -2
  23. package/dist/interceptors.d.ts +2 -1
  24. package/dist/interceptors.d.ts.map +1 -1
  25. package/dist/interceptors.js.map +1 -1
  26. package/dist/manifest/static-manifest.d.ts.map +1 -1
  27. package/dist/manifest/static-manifest.js +37 -1
  28. package/dist/manifest/static-manifest.js.map +1 -1
  29. package/dist/manifest/store.js +1 -1
  30. package/dist/manifest/store.js.map +1 -1
  31. package/dist/manifest.json +43 -1
  32. package/dist/prebuild/index.d.ts.map +1 -1
  33. package/dist/prebuild/index.js +9 -0
  34. package/dist/prebuild/index.js.map +1 -1
  35. package/dist/runtime/types.d.ts +2 -2
  36. package/dist/runtime/types.d.ts.map +1 -1
  37. package/dist/smrt-knowledge.json +50 -5
  38. package/dist/vite-plugin/index.d.ts.map +1 -1
  39. package/dist/vite-plugin/index.js +12 -3
  40. package/dist/vite-plugin/index.js.map +1 -1
  41. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  42. package/dist/vite-plugin/sveltekit-generator.js +24 -3
  43. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  44. package/dist/vite-plugin/web-collections.d.ts +3 -1
  45. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  46. package/dist/vite-plugin/web-collections.js +24 -4
  47. package/dist/vite-plugin/web-collections.js.map +1 -1
  48. package/package.json +4 -4
package/AGENTS.md CHANGED
@@ -18,7 +18,8 @@ subsystem you are editing. This file keeps what holds across all of them.
18
18
  | `src/change-signals.ts` + the generated `_events` SSE route | the push companion to the change feed — the signal bus, cross-replica fan-out, the SSE route, and its documented gaps | [agents/change-signals.md](agents/change-signals.md) |
19
19
  | `src/generators/` + `src/vite-plugin/web-collections.ts` | REST/CLI/MCP/web-collection generation, the `manifestHash` emission sites, and generated conditional-GET / ETag v2 semantics | [agents/generators.md](agents/generators.md) |
20
20
  | `src/schema/` | the four `SchemaGenerator` entry points, which two reach production, why schema drift stayed invisible, and the #2382 index/tenancy rules | [agents/schema-paths.md](agents/schema-paths.md) |
21
- | `src/data-query.ts` | canonical bounded data-query normalizer: allowlisted fields, deterministic fingerprints, output validation, and transport-neutral envelope (#2444) | |
21
+ | `src/data-query.ts` | canonical bounded data-query normalizer and transport-neutral envelope (#2444) | [agents/data-query.md](agents/data-query.md) |
22
+ | `src/collection.ts` | bounded collection reads, projections, latest-related hydration, facets, counts, and read plans | [agents/collection-reads.md](agents/collection-reads.md) |
22
23
 
23
24
  ## SmrtObject Lifecycle
24
25
 
@@ -37,7 +38,8 @@ subsystem you are editing. This file keeps what holds across all of them.
37
38
  `_smrt_contexts` plus optional injected semantic search. `capture()` reinforces
38
39
  successes and decays failures while updating outcome counters; `recall()`
39
40
  applies confidence, expiry, time-decay, and hierarchical-scope filters and
40
- refreshes `last_used_at`. Keep semantic search behind the
41
+ refreshes `last_used_at`. Detailed persistence and search semantics are in
42
+ [agents/memory.md](agents/memory.md). Keep semantic search behind the
41
43
  `SmrtCollection.semanticSearch`-compatible injection boundary.
42
44
 
43
45
  ## SmrtCollection Query
@@ -49,13 +51,8 @@ await collection.list({
49
51
  });
50
52
  ```
51
53
 
52
- Projection primitive (#1902): pass `select: ['id', 'title', 'tenantId']` to
53
- `list()` when an admin/list workflow needs compact rows. `select` uses SMRT
54
- field names, maps them to DB columns internally, and returns plain objects keyed
55
- by the same SMRT field names without hydrating `SmrtObject` instances. It
56
- composes with `where`, `orderBy`, `limit`, and `offset`; `beforeList`
57
- interceptors still run. It is for column-backed fields only and cannot combine
58
- with `include`/relationship eager loading.
54
+ Projection, latest-related, facets, counts, and bounded read plans are
55
+ documented in [agents/collection-reads.md](agents/collection-reads.md).
59
56
 
60
57
  `list()` and `query()` hydrate model instances serially in result order because
61
58
  an `initialize()` hook may query through the same transaction-bound PostgreSQL
@@ -79,43 +76,18 @@ operator against a database to keep the two in step.
79
76
 
80
77
  STI child collections auto-filter by `_meta_type`. Query bounds — `LIMIT 1` on `get()`, the `limit`/`offset` parser, the `orderBy` whitelist and sensitive/permission refusals, and the deterministic generated-list ordering (#2367) — are in [agents/query-bounds.md](agents/query-bounds.md).
81
78
 
82
- ## Bounded Collection Read Plans
83
-
84
- Use `executeCollectionReadPlan()` when one operation needs several independent
85
- collections. It bounds top-level `collection.list()` concurrency while keeping
86
- all reads on the normal registry/collection path. Callers must choose an
87
- explicit positive `maxConcurrency` and pass their normal shared
88
- `collectionOptions` when database or tenant context matters.
89
-
90
- The executor deliberately does not compose SQL, cache the plan, or change pool
91
- defaults. On failure it stops starting queued entries, drains operations already
92
- in flight, and rethrows the first error.
93
-
94
79
  ## Canonical Bounded Data Queries (#2444)
95
80
 
96
- `normalizeDataQueryRequest()` and `normalizeDataQueryResult()` are the trust
97
- boundary for the transport-neutral table/report/content query envelope. An
98
- authenticated adapter supplies a trusted `DataQuerySchema`; the caller only
99
- gets its declared projectable/sortable/filterable/facetable fields. The helpers
100
- never execute a query or decide tenant/principal access.
101
-
102
- Use `createDataQueryFingerprint()` for cache and result correlation. It omits
103
- the request id and page position, canonicalizes equivalent filter/projection/
104
- facet forms, and adds the identity sort tie-break. Keep data-query values
105
- scalar, requests/pages/facets positive and bounded, results within the schema
106
- byte cap with declared field types preserved. Datetimes must be valid RFC 3339
107
- instants, identity fields must be string/number/datetime-compatible, and JSON
108
- result fields are depth/container/string/byte bounded before cloning. Return
109
- only normalized `DataQueryResult` envelopes to REST, MCP,
110
- WebMCP, and browser consumers. Adapter-specific report/content context wraps
111
- the base envelope; it does not add unsafe fields or SQL-like controls to it.
81
+ The normalizers and fingerprint are the trust boundary for the
82
+ transport-neutral query envelope; full bounds, schema, and output rules live in
83
+ [agents/data-query.md](agents/data-query.md). Adapters own tenant/principal
84
+ access and query execution.
112
85
 
113
86
  ## Object Memory & Semantic Search
114
87
 
115
- Two persistence primitives every `SmrtObject`/`SmrtCollection` inherits load-bearing for learning agents, usable by any object. Full guide: `docs/content/core.md` → "Context Memory System".
116
-
117
- - **Context memory** (`remember`/`recall`/`recallAll`/`forget`/`forgetScope`, table `_smrt_contexts`): stores any JSON value keyed by `(owner_class, owner_id, scope, key, version)` with a `confidence` score (0–1) and a stored `expiresAt` (metadata — `recall()` does **not** filter expired rows; expiry is caller-managed). `recall()` returns the highest-confidence match with an optional `minConfidence` floor and **opt-in** hierarchical scope fallback (`includeAncestors: true` → `'a/b/c' → 'a/b' → 'a' → 'global'`; default off); `recallAll()` returns a `Map`. Typical use: cache a learned strategy (e.g. a working selector per host) and reuse it across sessions. `success_count`/`failure_count` columns exist for outcome-weighting: `SmrtObject.remember()` leaves them untouched, `SmrtCollection.remember()` resets them to zero, and neither recall path updates them. `LearningMemory` is the layer that maintains them (and that does filter expired rows).
118
- - **Semantic search** (on `SmrtCollection`, table `_smrt_embeddings`): `semanticSearch(query)`, `findSimilar(object)`, `findSimilarToEmbedding(vector)` — cosine ranking over embeddings of the fields declared in `@smrt({ embeddings })`. Native pgvector/HNSW when configured, in-memory `CosineSimilarity` fallback otherwise; default local model `Xenova/bge-base-en-v1.5` (768-dim) or AI `text-embedding-3-small`. Hits hydrate via `list({ 'id in': … })`, so `@TenantScoped` isolation applies to results.
88
+ Context memory and semantic search are persistence primitives inherited by
89
+ `SmrtObject`/`SmrtCollection`; their storage, scope, expiry, and tenant
90
+ invariants are in [agents/memory.md](agents/memory.md).
119
91
 
120
92
  ## @smrt() Decorator Options
121
93
 
@@ -0,0 +1,40 @@
1
+ <!-- Module doc for packages/core/AGENTS.md. Linked from the Modules table there. -->
2
+
3
+ # Collection reads
4
+
5
+ This module covers bounded collection reads beyond the basic query contract in
6
+ `packages/core/AGENTS.md`.
7
+
8
+ ## Projections and related rows
9
+
10
+ `list({ select })` uses SMRT field names, maps them to database columns, and
11
+ returns plain rows without hydrating objects. It composes with `where`,
12
+ `orderBy`, `limit`, and `offset`, runs normal `beforeList`/tenant interceptors,
13
+ and is limited to column-backed fields; it cannot combine with `include`.
14
+
15
+ For one child per parent, use
16
+ [`latest-related.md`](latest-related.md). It uses a portable ranked CTE,
17
+ declared primary keys, adapter-specific offset-only syntax, explicit aliases,
18
+ and hydrates only the visible parent page.
19
+
20
+ ## Facets, counts, and read plans
21
+
22
+ `collection.facets({ fields, where })` runs one bounded `GROUP BY` per requested
23
+ field and returns `{ field, values: [{ value, count }] }`. It accepts at most 20
24
+ fields, clamps value limits to 1,000 and the collection ceiling, never hydrates
25
+ objects, and applies the same read/tenant/sensitive-field rails as `select`.
26
+ Stored array/string-list values are grouped as stored; they are not unnested.
27
+ `collection.counts({ where })` returns `{ total, filtered }` through two scoped
28
+ `COUNT(*)` queries. Local coverage is SQLite/DuckDB; optional scalar PostgreSQL
29
+ coverage requires `SMRT_TEST_POSTGRES_URL`.
30
+
31
+ `executeCollectionReadPlan()` bounds concurrent reads across independent
32
+ collections while preserving the normal registry and collection options. The
33
+ caller supplies a positive `maxConcurrency`; the executor does not compose SQL,
34
+ cache, or alter pool defaults, and drains already-started work before returning
35
+ the first error.
36
+
37
+ `where` operators must remain aligned with `@happyvertical/sql`'s `buildWhere`:
38
+ `=`, `>`, `<`, `>=`, `<=`, `!=`, `in`, `not in`, and `like`. Arrays imply `IN`,
39
+ and null values render `IS NULL`/`IS NOT NULL`. `contains` and dot-notation JSON
40
+ paths are intentionally rejected until the SQL layer supports them.
@@ -0,0 +1,21 @@
1
+ <!-- Module doc for packages/core/AGENTS.md. Linked from the Modules table there. -->
2
+
3
+ # Bounded data queries (#2444)
4
+
5
+ `normalizeDataQueryRequest()` and `normalizeDataQueryResult()` define the trust
6
+ boundary for the transport-neutral table/report/content query envelope. A
7
+ trusted adapter supplies `DataQuerySchema`; callers receive only its declared
8
+ projectable, sortable, filterable, and facetable fields. The helpers validate
9
+ and normalize but never execute SQL or decide tenant/principal access.
10
+
11
+ Use `createDataQueryFingerprint()` for cache and result correlation. It omits
12
+ request id and page position, canonicalizes equivalent filter/projection/facet
13
+ forms, and adds the identity sort tie-break. Keep values scalar and request,
14
+ page, and facet sizes positive and bounded. Results must stay within the schema
15
+ byte cap with declared field types preserved; datetimes are RFC 3339 instants,
16
+ identity fields are string/number/datetime-compatible, and JSON values are
17
+ bounded by depth, container count, string size, and bytes before cloning.
18
+
19
+ Only normalized `DataQueryResult` envelopes cross REST, MCP, WebMCP, and browser
20
+ boundaries. Adapter-specific report/content context wraps the base envelope; it
21
+ does not add unsafe fields or SQL-like controls.
@@ -0,0 +1,35 @@
1
+ <!-- Module doc for packages/core/AGENTS.md. Linked from the Modules table there. -->
2
+
3
+ # Latest-related reads (#1903)
4
+
5
+ Use `SmrtCollection.listWithLatestRelated()` when a page needs one row from a
6
+ declared `@oneToMany` relationship without N+1 queries or hydrating unrelated
7
+ children. `latestRelated.orderBy` ranks rows within each parent, `select`
8
+ chooses the returned child fields (defaulting to the related model's declared
9
+ primary key), and an optional `sortBy` orders parents by the winning child
10
+ before the parent `limit`/`offset` are applied. The result is
11
+ `{ parent, latestRelated }`: `parent` is hydrated and `latestRelated` is a
12
+ plain projection or `null` when the parent has no child.
13
+
14
+ The read plan uses a portable `ROW_NUMBER()` CTE with explicit null-last
15
+ ordering and the declared primary keys for every tie-break, join, and result
16
+ map. The parent primary key is never assumed to be `id`; the related primary
17
+ key is the default projection when `select` is omitted. Only the visible
18
+ parent page is hydrated.
19
+
20
+ Pagination is adapter-aware: SQLite uses `LIMIT -1` for offset-only reads,
21
+ while DuckDB and PostgreSQL use `LIMIT ALL`. In-memory DuckDB and JSON adapters
22
+ retain their engine hint even when no URL or database type is present, so the
23
+ generated syntax remains legal for the actual driver.
24
+
25
+ Internal result aliases are bounded `__smrt_lr_N` identifiers. The allocator
26
+ reserves declared and live parent table columns, and parent columns are
27
+ projected explicitly (using the public table-schema API where available) rather
28
+ than `parent.*`, so externally added columns cannot collide or corrupt the
29
+ latest-related projection. JSON adapters without live schema introspection use
30
+ the declared schema as their fallback.
31
+
32
+ The primitive preserves normal read interceptors, tenant and STI scope, and
33
+ does not expose a cache option until cache invalidation is implemented. Tests
34
+ cover custom primary keys, SQLite/DuckDB offset-only pagination, long field
35
+ names, external alias collisions, and cleanup of temporary database files.
@@ -0,0 +1,16 @@
1
+ <!-- Module doc for packages/core/AGENTS.md. Linked from the Modules table there. -->
2
+
3
+ # Object memory and semantic search
4
+
5
+ Context memory (`remember`, `recall`, `recallAll`, `forget`, `forgetScope`) is
6
+ stored in `_smrt_contexts`, keyed by owner, scope, key, and version. Values have
7
+ a 0–1 confidence and optional expiry metadata; `recall()` does not filter
8
+ expired rows. Ancestor fallback is opt-in (`includeAncestors: true`) and walks
9
+ `a/b/c → a/b → a → global`. `LearningMemory` owns outcome counters and expiry
10
+ filtering; object/collection recall does not update them.
11
+
12
+ Semantic search uses `_smrt_embeddings` and cosine ranking over fields declared
13
+ by `@smrt({ embeddings })`, with native pgvector/HNSW or an in-memory fallback.
14
+ Results hydrate through `list({ 'id in': … })`, so normal tenant isolation still
15
+ applies. Keep injected search behind the `SmrtCollection.semanticSearch`
16
+ boundary.
package/dist/browser.js CHANGED
@@ -16,7 +16,7 @@ import { executeToolCall, executeToolCalls, formatToolResults, validateToolCall
16
16
  import { SmrtObject } from "./object.js";
17
17
  import { SMRT_COLLECTION_BASE_NAMES, isSmrtCollectionExtendsName } from "./registry/collection-resolution.js";
18
18
  import { ObjectRegistry, smrt } from "./registry.js";
19
- import { SmrtCollection } from "./collection.js";
19
+ import { DEFAULT_FACET_LIMIT, MAX_FACET_FIELDS, MAX_FACET_LIMIT, SmrtCollection } from "./collection.js";
20
20
  import { SmrtHierarchical } from "./hierarchical.js";
21
21
  import { SmrtJunction } from "./junction.js";
22
22
  import { SmrtPolymorphicAssociation } from "./polymorphic-association.js";
@@ -24,4 +24,4 @@ import "./signals/index.js";
24
24
  import { DEFAULT_RETENTION_POLICY, clearRetentionTasks, getRetentionTasks, pruneAiUsage, pruneExpiredContexts, registerRetentionTask, runRetentionSweep, unregisterRetentionTask } from "./system/retention.js";
25
25
  import "./system/index.js";
26
26
  import "./tools/index.js";
27
- export { AIError, AiUsageCollector, AiUsagePersistenceHandler, ConfigurationError, DEFAULT_AI_COST_RATES, DEFAULT_RETENTION_POLICY, DatabaseError, ErrorUtils, FilesystemError, MetricsAdapter, NetworkError, ObjectRegistry, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, assertPostgresSystemTimestampsCurrent, classifyDatabaseError, classifyDialectMessage, clearRetentionTasks, config, convertTypeToJsonSchema, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, executeToolCall, executeToolCalls, formatToolResults, generateToolFromMethod, generateToolManifest, getDatabaseEngine, getRetentionTasks, isAbortedTransactionError, isDeterministicDatabaseError, isNotNullViolationError, isSmrtCollectionExtendsName, isTransientDatabaseError, isUniqueViolationError, migratePostgresSystemTimestamps, planPostgresSystemTimestampMigrations, pruneAiUsage, pruneExpiredContexts, registerRetentionTask, runRetentionSweep, shouldIncludeMethod, smrt, smrt as smrtRegistry, tableExists, unregisterRetentionTask, validateToolCall };
27
+ export { AIError, AiUsageCollector, AiUsagePersistenceHandler, ConfigurationError, DEFAULT_AI_COST_RATES, DEFAULT_FACET_LIMIT, DEFAULT_RETENTION_POLICY, DatabaseError, ErrorUtils, FilesystemError, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MetricsAdapter, NetworkError, ObjectRegistry, PubSubAdapter, RuntimeError, SMRT_COLLECTION_BASE_NAMES, SignalBus, SignalSanitizer, SmrtClass, SmrtCollection, SmrtError, SmrtHierarchical, SmrtJunction, SmrtObject, SmrtPolymorphicAssociation, TenantIsolationError, ValidationError, ValidationReport, ValidationUtils, assertPostgresSystemTimestampsCurrent, classifyDatabaseError, classifyDialectMessage, clearRetentionTasks, config, convertTypeToJsonSchema, ensureBootstrapSystemTableCompatibility, ensureDeferredSystemTableCompatibility, ensureDispatchSubscriptionsSystemTableCompatibility, ensureDispatchSystemTableCompatibility, ensureJobEventsSystemTableCompatibility, ensureJobsSystemTableCompatibility, ensureLegacySystemTableCompatibility, ensureSystemTables, estimateAiUsageCost, executeToolCall, executeToolCalls, formatToolResults, generateToolFromMethod, generateToolManifest, getDatabaseEngine, getRetentionTasks, isAbortedTransactionError, isDeterministicDatabaseError, isNotNullViolationError, isSmrtCollectionExtendsName, isTransientDatabaseError, isUniqueViolationError, migratePostgresSystemTimestamps, planPostgresSystemTimestampMigrations, pruneAiUsage, pruneExpiredContexts, registerRetentionTask, runRetentionSweep, shouldIncludeMethod, smrt, smrt as smrtRegistry, tableExists, unregisterRetentionTask, validateToolCall };
package/dist/class.d.ts CHANGED
@@ -254,6 +254,13 @@ export declare class SmrtClass {
254
254
  * System tables use _smrt_ prefix to avoid conflicts with user tables
255
255
  */
256
256
  protected get systemDb(): DatabaseInterface;
257
+ /**
258
+ * Return the database engine hint captured from the caller's configuration.
259
+ * Adapters may not expose their original type after construction (notably
260
+ * in-memory DuckDB/JSON connections), so query helpers can combine this
261
+ * hint with their public connection capabilities.
262
+ */
263
+ protected getDatabaseEngineHint(): string | undefined;
257
264
  /**
258
265
  * Initialize signal bus and adapters
259
266
  *
@@ -1 +1 @@
1
- {"version":3,"file":"class.d.ts","sourceRoot":"","sources":["../src/class.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,KAAK,QAAQ,EAAS,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EACV,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,KAAK,EAGV,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,qBAAqB,EACrB,aAAa,EAEb,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,oBAAoB,CAAC;AAOzE,OAAO,KAAK,EAEV,aAAa,EAEb,aAAa,EACb,YAAY,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAkR7C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;OASG;IACH,EAAE,CAAC,EAAE,cAAc,CAAC;IAEpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAE7B;;OAEG;IACH,EAAE,CAAC,EAAE,wBAAwB,CAAC;IAE9B;;OAEG;IACH,EAAE,CAAC,EAAE,eAAe,GAAG,QAAQ,CAAC;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,aAAa,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;IAExE;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,iCAAiC;QACjC,GAAG,CAAC,EAAE,SAAS,CAAC;QAChB,iCAAiC;QACjC,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;KAC5B,CAAC;IAEF;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;IACpB;;OAEG;IACH,SAAS,CAAC,GAAG,EAAG,QAAQ,CAAC;IAEzB;;OAEG;IACH,SAAS,CAAC,GAAG,EAAG,iBAAiB,CAAC;IAElC;;OAEG;IACH,SAAS,CAAC,GAAG,EAAG,iBAAiB,CAAC;IAClC,OAAO,CAAC,aAAa,CAAC,CAAS;IAE/B;;OAEG;IACH,SAAS,CAAC,UAAU,EAAG,MAAM,CAAC;IAE9B;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAuB;IAElD;;OAEG;IACH,OAAO,CAAC,iBAAiB,CAAC,CAAmB;IAE7C;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAwB;IAEhD;;OAEG;IACH,OAAO,CAAC,2BAA2B,CAAS;IAE5C;;OAEG;IACH,OAAO,CAAC,2BAA2B,CAAC,CAAgB;IAEpD;;OAEG;IACI,OAAO,EAAE,gBAAgB,CAAC;IAEjC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB,CAAoC;IAC3E,OAAO,CAAC,MAAM,CAAC,6BAA6B,CAAqB;IAEjE;;;;OAIG;gBACS,OAAO,GAAE,gBAAqB;IAK1C;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAIrC;;;;;;;;OAQG;cACa,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3C;;;;;;OAMG;cACa,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC;IA2GxD;;OAEG;cACa,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4G1D;;OAEG;cACa,gCAAgC,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjE;;OAEG;cACa,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;IAahD;;OAEG;cACa,mBAAmB,IAAI,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;YAKtD,4BAA4B;IA6B1C;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,kBAAkB;IAiDhC;;;;;;;;;;;;;;;;;;;;;;OAsBG;YACW,sCAAsC;YAkCtC,yBAAyB;IAgCvC;;;OAGG;IACH,SAAS,KAAK,QAAQ,IAAI,iBAAiB,CAE1C;IAED;;;;;OAKG;YACW,iBAAiB;IAqB/B;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;;;OAIG;YACW,gBAAgB;IAuC9B;;OAEG;IACH,IAAI,EAAE,sBAEL;IAED;;OAEG;IACH,IAAI,EAAE,sBASL;IAED;;OAEG;IACH,IAAI,EAAE,aAEL;IAED;;OAEG;IACH,kBAAkB,IAAI,eAAe,GAAG,SAAS;IAIjD;;OAEG;IACH,YAAY,IAAI,IAAI;IAIpB;;OAEG;IACG,WAAW,CACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAwD/B;;OAEG;IACG,gBAAgB,CACpB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAyExC;;;;OAIG;IACH,IAAI,SAAS,IAAI,SAAS,GAAG,SAAS,CAErC;IAED;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,IAAI;IAef,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,yBAAyB;YAkBnB,qBAAqB;IAwCnC,OAAO,CAAC,qBAAqB;CAoD9B"}
1
+ {"version":3,"file":"class.d.ts","sourceRoot":"","sources":["../src/class.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,KAAK,QAAQ,EAAS,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EACV,iBAAiB,EACjB,wBAAwB,EACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,KAAK,EAGV,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,qBAAqB,EACrB,aAAa,EAEb,iBAAiB,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,oBAAoB,CAAC;AAOzE,OAAO,KAAK,EAEV,aAAa,EAEb,aAAa,EACb,YAAY,EACb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAkR7C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;OASG;IACH,EAAE,CAAC,EAAE,cAAc,CAAC;IAEpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,cAAc,CAAC;IAE7B;;OAEG;IACH,EAAE,CAAC,EAAE,wBAAwB,CAAC;IAE9B;;OAEG;IACH,EAAE,CAAC,EAAE,eAAe,GAAG,QAAQ,CAAC;IAEhC;;OAEG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC;IAEtB;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB;;OAEG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,aAAa,EAAE,kBAAkB,CAAC,cAAc,CAAC,CAAC;IAExE;;OAEG;IACH,OAAO,CAAC,EAAE;QACR,iCAAiC;QACjC,GAAG,CAAC,EAAE,SAAS,CAAC;QAChB,iCAAiC;QACjC,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;KAC5B,CAAC;IAEF;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;;;;OAMG;IACH,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS;IACpB;;OAEG;IACH,SAAS,CAAC,GAAG,EAAG,QAAQ,CAAC;IAEzB;;OAEG;IACH,SAAS,CAAC,GAAG,EAAG,iBAAiB,CAAC;IAElC;;OAEG;IACH,SAAS,CAAC,GAAG,EAAG,iBAAiB,CAAC;IAClC,OAAO,CAAC,aAAa,CAAC,CAAS;IAE/B;;OAEG;IACH,SAAS,CAAC,UAAU,EAAG,MAAM,CAAC;IAE9B;;OAEG;IACH,SAAS,CAAC,UAAU,CAAC,EAAE,SAAS,CAAC;IAEjC;;OAEG;IACH,OAAO,CAAC,mBAAmB,CAAuB;IAElD;;OAEG;IACH,OAAO,CAAC,iBAAiB,CAAC,CAAmB;IAE7C;;OAEG;IACH,OAAO,CAAC,gBAAgB,CAAwB;IAEhD;;OAEG;IACH,OAAO,CAAC,2BAA2B,CAAS;IAE5C;;OAEG;IACH,OAAO,CAAC,2BAA2B,CAAC,CAAgB;IAEpD;;OAEG;IACI,OAAO,EAAE,gBAAgB,CAAC;IAEjC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,wBAAwB,CAAoC;IAC3E,OAAO,CAAC,MAAM,CAAC,6BAA6B,CAAqB;IAEjE;;;;OAIG;gBACS,OAAO,GAAE,gBAAqB;IAK1C;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAIrC;;;;;;;;OAQG;cACa,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3C;;;;;;OAMG;cACa,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC;IA2GxD;;OAEG;cACa,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4G1D;;OAEG;cACa,gCAAgC,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjE;;OAEG;cACa,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;IAahD;;OAEG;cACa,mBAAmB,IAAI,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;YAKtD,4BAA4B;IA6B1C;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,kBAAkB;IAiDhC;;;;;;;;;;;;;;;;;;;;;;OAsBG;YACW,sCAAsC;YAkCtC,yBAAyB;IAgCvC;;;OAGG;IACH,SAAS,KAAK,QAAQ,IAAI,iBAAiB,CAE1C;IAED;;;;;OAKG;IACH,SAAS,CAAC,qBAAqB,IAAI,MAAM,GAAG,SAAS;IAIrD;;;;;OAKG;YACW,iBAAiB;IAqB/B;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;;;;OAOG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;;;OAIG;YACW,gBAAgB;IAuC9B;;OAEG;IACH,IAAI,EAAE,sBAEL;IAED;;OAEG;IACH,IAAI,EAAE,sBASL;IAED;;OAEG;IACH,IAAI,EAAE,aAEL;IAED;;OAEG;IACH,kBAAkB,IAAI,eAAe,GAAG,SAAS;IAIjD;;OAEG;IACH,YAAY,IAAI,IAAI;IAIpB;;OAEG;IACG,WAAW,CACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAwD/B;;OAEG;IACG,gBAAgB,CACpB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAyExC;;;;OAIG;IACH,IAAI,SAAS,IAAI,SAAS,GAAG,SAAS,CAErC;IAED;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,IAAI;IAef,OAAO,CAAC,kBAAkB;IAsB1B,OAAO,CAAC,yBAAyB;YAkBnB,qBAAqB;IAwCnC,OAAO,CAAC,qBAAqB;CAoD9B"}
package/dist/class.js CHANGED
@@ -480,6 +480,15 @@ var SmrtClass = class SmrtClass {
480
480
  return this._db;
481
481
  }
482
482
  /**
483
+ * Return the database engine hint captured from the caller's configuration.
484
+ * Adapters may not expose their original type after construction (notably
485
+ * in-memory DuckDB/JSON connections), so query helpers can combine this
486
+ * hint with their public connection capabilities.
487
+ */
488
+ getDatabaseEngineHint() {
489
+ return this._dbEngineHint;
490
+ }
491
+ /**
483
492
  * Initialize signal bus and adapters
484
493
  *
485
494
  * Merges global configuration with instance-specific overrides.
package/dist/class.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"class.js","names":[],"sources":["../src/class.ts"],"sourcesContent":["import type { AIClientOptions } from '@happyvertical/ai';\nimport { type AIClient, getAI } from '@happyvertical/ai';\nimport type {\n FilesystemAdapter,\n FilesystemAdapterOptions,\n} from '@happyvertical/files';\nimport { createLogger, type LoggerConfig } from '@happyvertical/logger';\nimport type {\n AiTokenUsage,\n AiUsageHandler,\n AiUsageListOptions,\n AiUsageSnapshot,\n AiUsageStats,\n AiUsageSummaryOptions,\n SignalAdapter,\n SmrtAiUsageEvent,\n SmrtAiUsageRecord,\n} from '@happyvertical/smrt-types';\nimport { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport {\n AiUsageCollector,\n AiUsagePersistenceHandler,\n} from './adapters/ai-usage.js';\nimport { estimateAiUsageCost } from './adapters/cost-rates.js';\nimport { registerChangeFeedWriter } from './change-feed.js';\nimport type {\n AIConfig,\n AiUsageConfig,\n GlobalSignalConfig,\n MetricsConfig,\n PubSubConfig,\n} from './config.js';\nimport { config } from './config.js';\nimport type { DatabaseConfig } from './database.js';\nimport { createFilesystemAdapter } from './filesystem-loader.js';\nimport { applyPostgresRuntimeTimeouts } from './postgres-timeouts.js';\nimport { detectEngine } from './schema/ddl/index.js';\nimport { SignalBus } from './signals/bus.js';\nimport { ensureSystemTables as ensureFrameworkSystemTables } from './system/bootstrap.js';\nimport {\n ensureDeferredSystemTableCompatibility,\n tableExists,\n} from './system/compatibility.js';\nimport { SMRT_SCHEMA_VERSION } from './system/schema.js';\nimport { toSafeInteger } from './utils/safe-integer.js';\n\n/**\n * `_smrt_migrations.version` suffix marking that the deferred (manifest-created)\n * system tables have been through their compatibility pass (issue #2376).\n */\nconst DEFERRED_COMPATIBILITY_VERSION_SUFFIX = '+deferred-compat';\n\nconst logger = createLogger({ level: 'info' });\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\ninterface ResolvedAiUsageConfig {\n enabled: boolean;\n persist: boolean;\n estimateCosts: boolean;\n costRates?: Record<string, { input: number; output: number }>;\n handlers: AiUsageHandler[];\n}\n\ntype AiUsageFilterOptions = Pick<\n AiUsageListOptions,\n | 'since'\n | 'until'\n | 'provider'\n | 'model'\n | 'operation'\n | 'className'\n | 'tenantId'\n>;\n\ninterface AiUsageWhereClause {\n conditions: string[];\n params: unknown[];\n nextParamIndex: number;\n}\n\nfunction firstString(...candidates: unknown[]): string | undefined {\n return candidates.find((candidate): candidate is string => {\n return typeof candidate === 'string';\n });\n}\n\nfunction firstNumber(...candidates: unknown[]): number | undefined {\n return candidates.find((candidate): candidate is number => {\n return typeof candidate === 'number';\n });\n}\n\nfunction getDatabaseUrl(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseWithConfig;\n return db.url || dbWithConfig.config?.url || '';\n}\n\nfunction getDatabaseTypeHint(\n config: DatabaseConfig | undefined,\n): string | undefined {\n if (!config || typeof config === 'string') {\n return undefined;\n }\n\n const configWithType = config as DatabaseWithConfig & {\n client?: unknown;\n };\n\n if (typeof configWithType.type === 'string') {\n return configWithType.type;\n }\n\n if (typeof configWithType.config?.type === 'string') {\n return configWithType.config.type;\n }\n\n if ('query' in configWithType && typeof configWithType.query === 'function') {\n return undefined;\n }\n\n if ('client' in configWithType && configWithType.client) {\n return 'postgres';\n }\n\n return undefined;\n}\n\nfunction normalizeIncomingAiUsageTokens(\n value: unknown,\n): AiTokenUsage | undefined {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n\n const usage = value as Record<string, unknown>;\n const promptTokens =\n typeof usage.promptTokens === 'number'\n ? usage.promptTokens\n : typeof usage.inputTokens === 'number'\n ? usage.inputTokens\n : undefined;\n const completionTokens =\n typeof usage.completionTokens === 'number'\n ? usage.completionTokens\n : typeof usage.outputTokens === 'number'\n ? usage.outputTokens\n : undefined;\n const totalTokens =\n typeof usage.totalTokens === 'number'\n ? usage.totalTokens\n : promptTokens !== undefined || completionTokens !== undefined\n ? (promptTokens ?? 0) + (completionTokens ?? 0)\n : undefined;\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction hydratePersistedAiUsageTokens(row: {\n prompt_tokens?: unknown;\n completion_tokens?: unknown;\n total_tokens?: unknown;\n}): AiTokenUsage | undefined {\n const promptTokens =\n row.prompt_tokens === null || row.prompt_tokens === undefined\n ? undefined\n : toSafeInteger(row.prompt_tokens, 'AI usage prompt tokens');\n const completionTokens =\n row.completion_tokens === null || row.completion_tokens === undefined\n ? undefined\n : toSafeInteger(row.completion_tokens, 'AI usage completion tokens');\n const totalTokens =\n row.total_tokens === null || row.total_tokens === undefined\n ? undefined\n : toSafeInteger(row.total_tokens, 'AI usage total tokens');\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction normalizeAiUsageTags(\n value: unknown,\n): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const tags: Record<string, string> = {};\n for (const [key, tagValue] of Object.entries(value)) {\n if (tagValue === undefined || tagValue === null) continue;\n tags[key] = String(tagValue);\n }\n\n return Object.keys(tags).length > 0 ? tags : undefined;\n}\n\nfunction parseAiUsageTags(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n\n try {\n return normalizeAiUsageTags(JSON.parse(value));\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeAiUsageTimestamp(value: unknown): Date {\n if (value instanceof Date) {\n return value;\n }\n\n if (typeof value === 'string' || typeof value === 'number') {\n const date = new Date(value);\n if (!Number.isNaN(date.getTime())) {\n return date;\n }\n }\n\n return new Date();\n}\n\nfunction getQueryRows(\n result: Awaited<ReturnType<DatabaseInterface['query']>>,\n): Record<string, unknown>[] {\n return Array.isArray(result)\n ? (result as Record<string, unknown>[])\n : ((result as { rows?: Record<string, unknown>[] }).rows ?? []);\n}\n\nfunction buildAiUsageWhereClause(\n options: AiUsageFilterOptions,\n): AiUsageWhereClause {\n const conditions: string[] = [];\n const params: unknown[] = [];\n let nextParamIndex = 1;\n\n if (options.since) {\n conditions.push(`created_at >= $${nextParamIndex++}`);\n params.push(options.since.toISOString());\n }\n\n if (options.until) {\n conditions.push(`created_at <= $${nextParamIndex++}`);\n params.push(options.until.toISOString());\n }\n\n if (options.provider) {\n conditions.push(`provider = $${nextParamIndex++}`);\n params.push(options.provider);\n }\n\n if (options.model) {\n conditions.push(`model = $${nextParamIndex++}`);\n params.push(options.model);\n }\n\n if (options.operation) {\n conditions.push(`operation = $${nextParamIndex++}`);\n params.push(options.operation);\n }\n\n if (options.className) {\n conditions.push(`class_name = $${nextParamIndex++}`);\n params.push(options.className);\n }\n\n if (options.tenantId === null) {\n conditions.push(`tenant_id IS NULL`);\n } else if (options.tenantId) {\n conditions.push(`tenant_id = $${nextParamIndex++}`);\n params.push(options.tenantId);\n }\n\n return {\n conditions,\n params,\n nextParamIndex,\n };\n}\n\n/**\n * Configuration options for the SmrtClass\n */\nexport interface SmrtClassOptions {\n /**\n * Optional custom class name override\n */\n _className?: string;\n\n /**\n * Database configuration - unified approach matching @happyvertical/sql\n *\n * Supports three formats:\n * - String shortcut: 'products.db' (auto-detects database type)\n * - Config object: { type: 'sqlite', url: 'products.db' }\n * - DatabaseInterface instance: await getDatabase(...)\n *\n * @see DatabaseConfig for type definition\n */\n db?: DatabaseConfig;\n\n /**\n * Alias for db option - for backward compatibility with documentation\n *\n * @deprecated Use 'db' instead. This alias exists for backward compatibility.\n */\n persistence?: DatabaseConfig;\n\n /**\n * Filesystem adapter configuration options\n */\n fs?: FilesystemAdapterOptions;\n\n /**\n * AI client configuration options or instance\n */\n ai?: AIClientOptions | AIClient;\n\n /**\n * AI usage tracking configuration (overrides global defaults)\n */\n usage?: AiUsageConfig;\n\n /**\n * Logging configuration (overrides global default)\n */\n logging?: LoggerConfig;\n\n /**\n * Metrics configuration (overrides global default)\n */\n metrics?: MetricsConfig;\n\n /**\n * Pub/Sub configuration (overrides global default)\n */\n pubsub?: PubSubConfig;\n\n /**\n * Sanitization configuration (overrides global default)\n */\n sanitization?: import('./config.js').GlobalSignalConfig['sanitization'];\n\n /**\n * Custom signal configuration (overrides global default)\n */\n signals?: {\n /** Shared signal bus instance */\n bus?: SignalBus;\n /** Additional custom adapters */\n adapters?: SignalAdapter[];\n };\n\n /**\n * Internal flag to reuse an already initialized DatabaseInterface instance.\n *\n * Skips database resolution and system-table setup during lightweight hydration.\n * @internal\n */\n _reuseInitializedDb?: boolean;\n\n /**\n * Internal flag to defer runtime-only services such as signals and AI setup.\n *\n * Lightweight hydration paths set this so plain query reads avoid per-row\n * runtime bootstrap costs.\n * @internal\n */\n _deferRuntimeInitialization?: boolean;\n}\n\n/**\n * Foundation class providing core functionality for the SMRT framework\n *\n * SmrtClass provides unified access to database, filesystem, and AI client\n * interfaces. It serves as the foundation for all other classes in the\n * SMRT framework.\n */\nexport class SmrtClass {\n /**\n * AI client instance for interacting with AI models\n */\n protected _ai!: AIClient;\n\n /**\n * Filesystem adapter for file operations\n */\n protected _fs!: FilesystemAdapter;\n\n /**\n * Database interface for data persistence\n */\n protected _db!: DatabaseInterface;\n private _dbEngineHint?: string;\n\n /**\n * Class name used for identification\n */\n protected _className!: string;\n\n /**\n * Signal bus for method execution tracking\n */\n protected _signalBus?: SignalBus;\n\n /**\n * Adapters registered by this instance (for cleanup)\n */\n private _registeredAdapters: SignalAdapter[] = [];\n\n /**\n * In-memory AI usage collector for quick inspection.\n */\n private _aiUsageCollector?: AiUsageCollector;\n\n /**\n * Registered AI usage handlers for this instance.\n */\n private _aiUsageHandlers: AiUsageHandler[] = [];\n\n /**\n * Tracks whether optional runtime services (signals, AI, fs) are ready.\n */\n private _runtimeServicesInitialized = false;\n\n /**\n * Shared in-flight runtime initialization promise for single-flight setup.\n */\n private _runtimeServicesInitPromise?: Promise<void>;\n\n /**\n * Configuration options provided to the class\n */\n public options: SmrtClassOptions;\n\n /**\n * Track which databases have had system tables initialized\n * - WeakSet for :memory: databases (URL not unique, track by instance)\n * - Set<string> for all others (URL is unique identifier)\n */\n private static _systemTablesInitialized = new WeakSet<DatabaseInterface>();\n private static _systemTablesInitializedByUrl = new Set<string>();\n\n /**\n * Creates a new SmrtClass instance\n *\n * @param options - Configuration options for database, filesystem, and AI clients\n */\n constructor(options: SmrtClassOptions = {}) {\n this.options = options;\n this._className = this.constructor.name;\n }\n\n /**\n * Determines whether this class requires a database to function\n *\n * Override this method in subclasses that require database access\n * to enable early validation during initialization.\n *\n * @returns True if database is required, false otherwise\n * @example\n * ```typescript\n * class MyDataModel extends SmrtClass {\n * protected requiresDatabase(): boolean {\n * return true; // This class needs database access\n * }\n * }\n * ```\n */\n protected requiresDatabase(): boolean {\n return false; // Base class doesn't require database by default\n }\n\n /**\n * Initializes database, filesystem, and AI client connections\n *\n * This method sets up all required services based on the provided options.\n * It should be called before using any of the service interfaces.\n *\n * @returns Promise that resolves to this instance for chaining\n * @throws {Error} If database is required but not provided in options\n */\n protected async initialize(): Promise<this> {\n await this.initializeCoreResources();\n\n if (!this.options._deferRuntimeInitialization) {\n await this.initializeRuntimeServices();\n }\n\n return this;\n }\n\n /**\n * Initialize core resources required for ORM behavior.\n *\n * This setup is shared by both full runtime initialization and lightweight\n * query hydration. Hydrated objects reuse an existing DB connection and skip\n * repeated system-table checks.\n */\n protected async initializeCoreResources(): Promise<void> {\n // Framework init hook for the change feed (#1758): make sure the writer\n // interceptor observes every save/delete before any instance can write.\n // Idempotent and cheap when already registered.\n registerChangeFeedWriter();\n\n // Map persistence to db for backward compatibility\n if (this.options.persistence && !this.options.db) {\n this.options.db = this.options.persistence;\n }\n\n // Validate database configuration if required\n if (this.requiresDatabase() && !this.options.db) {\n throw new Error(\n `${this._className} requires a database configuration. ` +\n `Please provide 'db' in options: { db: { url: '...' } } or { db: 'database.db' }`,\n );\n }\n\n if (this.options.db) {\n this._dbEngineHint = getDatabaseTypeHint(this.options.db);\n\n if (\n this.options._reuseInitializedDb &&\n typeof this.options.db === 'object' &&\n 'query' in this.options.db\n ) {\n this._db = this.options.db as DatabaseInterface;\n this.options.db = this._db;\n } else {\n // Handle four db config formats (in implementation order):\n // 1. String URL: 'products.db' (shortcut)\n // 2. DatabaseInterface instance: already initialized db (has 'query' method)\n // 3. Config with client: { type: 'postgres', client: pgPool } (SvelteKit pattern)\n // 4. Config object: { type: 'sqlite', url: 'products.db' }\n if (typeof this.options.db === 'string') {\n // Format 1: String shortcut - let getDatabase auto-detect type from URL\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const isMemoryDb = this.options.db === ':memory:';\n // PostgreSQL URLs pick up the runtime timeout bounds, and the dbid is\n // derived from the bounded URL so the same rewrite at every call site\n // still resolves to one shared pool (#2377).\n const bounded = applyPostgresRuntimeTimeouts({\n url: this.options.db,\n });\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else if ('query' in this.options.db) {\n // Format 2: Already a DatabaseInterface instance - return as-is\n this._db = this.options.db as DatabaseInterface;\n } else if ('client' in this.options.db && this.options.db.client) {\n // Format 3: Config with pre-created client (e.g., from SvelteKit's $env-based connection)\n // Pass the client to getDatabase which will use it instead of creating a new connection\n const dbConfig = this.options.db as {\n type?: string;\n client: unknown;\n url?: string;\n };\n // `client` is a runtime-only property the postgres adapter reads\n // but the public `getDatabase` option union does not model, so the\n // literal is routed through the function's own parameter type.\n this._db = await getDatabase({\n type: dbConfig.type || 'postgres',\n client: dbConfig.client,\n url: dbConfig.url,\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else {\n // Format 4: Config object - pass to getDatabase (handles all types uniformly)\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const dbConfig = this.options.db as { url?: string; type?: string };\n const dbUrl = dbConfig.url || 'memory';\n const isMemoryDb = dbUrl === ':memory:' || dbUrl === 'memory';\n // Keep the config's own key set: an object with no `url` must stay\n // that way so the adapter applies its own default (#2377).\n const bounded = applyPostgresRuntimeTimeouts({ ...this.options.db });\n // The loose config-object variant of `DatabaseConfig` carries an\n // index signature that the closed `getDatabase` option union does\n // not accept structurally, so route through its parameter type.\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url ?? dbUrl}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n }\n\n /**\n * INTENTIONAL MUTATION: After resolving the database config,\n * we replace options.db with the actual DatabaseInterface instance.\n * This enables child objects to share the same connection via:\n *\n * const child = new ChildObject({ db: parent.options.db });\n *\n * Without this, passing this.options to getCollection() would use the config object\n * which causes a NEW db instance to be created, losing data isolation.\n *\n * See issue #567 for context on why this pattern is necessary.\n */\n this.options.db = this._db;\n\n await this.ensureSystemTables();\n }\n }\n }\n\n /**\n * Initialize optional runtime services that are not required for plain ORM reads.\n */\n protected async initializeRuntimeServices(): Promise<void> {\n if (this._runtimeServicesInitialized) {\n return;\n }\n\n if (!this._runtimeServicesInitPromise) {\n this._runtimeServicesInitPromise = (async () => {\n if (this.options.fs && !this._fs) {\n // Acquired through the boundary in filesystem-loader.ts so the\n // @happyvertical/files SDK (S3/googleapis) never enters\n // provider-neutral consumer bundles (#1977/#1979).\n this._fs = await createFilesystemAdapter(this.options.fs);\n }\n\n // Initialize AI client with environment variable support\n // Priority: instance options > env vars > global config > defaults\n const globalConfig = config.toJSON();\n const usageConfig = this.mergeAiUsageConfig(globalConfig);\n this.initializeAiUsageHandlers(usageConfig);\n\n if (\n !this._ai &&\n (this.options.ai || globalConfig.ai || process.env.SMRT_AI_PROVIDER)\n ) {\n // Check if options.ai is already a client-like object with embed method\n // This allows passing mock AI clients for testing\n const aiOption = this.options.ai as\n | Record<string, unknown>\n | undefined;\n if (\n aiOption &&\n typeof aiOption === 'object' &&\n typeof aiOption.embed === 'function' &&\n !aiOption.provider\n ) {\n this._ai = aiOption as unknown as AIClient;\n } else {\n // CC-8 follow-up: ideally this would route through\n // `@happyvertical/smrt-config` for sanitization parity (e.g. via\n // `getPackageConfig('ai', ...)`), but smrt-config currently only\n // merges file-based config + runtime overrides — it does NOT\n // read from `process.env` with a typed prefix/schema. Until\n // smrt-config grows an env-loader (or wraps `loadEnvConfig`),\n // we continue to use the underlying utility directly. Tracked\n // alongside the CC-8 audit on issue #1199.\n const { loadEnvConfig } = await import('@happyvertical/utils');\n\n // Start with global defaults\n const baseConfig = globalConfig.ai || {};\n\n // Merge with instance options (takes priority over global)\n const userConfig = { ...baseConfig, ...this.options.ai };\n\n // Load environment variables and merge (user options take priority).\n // `AIConfig` carries an index signature, so provider-specific keys\n // (`type`, `onUsage`, `defaultModel`, …) read back as `unknown`.\n const aiConfig = loadEnvConfig<AIConfig>(userConfig, {\n packageName: 'ai',\n prefix: 'SMRT',\n schema: {\n provider: 'string',\n model: 'string',\n apiKey: 'string',\n timeout: 'number',\n maxRetries: 'number',\n temperature: 'number',\n maxTokens: 'number',\n },\n });\n\n const existingOnUsage =\n aiConfig.onUsage ??\n (userConfig as Record<string, unknown>).onUsage ??\n undefined;\n aiConfig.onUsage = async (event: unknown) => {\n if (typeof existingOnUsage === 'function') {\n await (existingOnUsage as (usageEvent: unknown) => unknown)(\n event,\n );\n }\n await this.handleAiUsageCallback(event, aiConfig, usageConfig);\n };\n\n // Only initialize if we have a provider configured\n if (aiConfig.provider || aiConfig.type || aiConfig.apiKey) {\n // Use getAI() factory to support all AI providers (OpenAI, Anthropic, Gemini, etc.)\n // getAI() returns AIInterface, which we narrow to AIClient for\n // backward compatibility. The index-signature `AIConfig` is routed\n // through getAI's own parameter type rather than the closed union.\n this._ai = (await getAI(\n aiConfig as unknown as Parameters<typeof getAI>[0],\n )) as unknown as AIClient;\n }\n }\n }\n\n await this.initializeSignals();\n this._runtimeServicesInitialized = true;\n })();\n }\n\n try {\n await this._runtimeServicesInitPromise;\n } finally {\n this._runtimeServicesInitPromise = undefined;\n }\n }\n\n /**\n * Ensure deferred runtime services are ready before using them.\n */\n protected async ensureRuntimeServicesInitialized(): Promise<void> {\n if (!this._runtimeServicesInitialized) {\n await this.initializeRuntimeServices();\n }\n }\n\n /**\n * Resolve the AI client, initializing deferred runtime services on demand.\n */\n protected async getAiClient(): Promise<AIClient> {\n await this.ensureRuntimeServicesInitialized();\n\n if (!this._ai) {\n throw new Error(\n `${this._className} does not have an AI client configured. ` +\n `Provide 'ai' in options or configure a global SMRT AI provider.`,\n );\n }\n\n return this._ai;\n }\n\n /**\n * Resolve the AI client if one is configured, otherwise return undefined.\n */\n protected async getOptionalAiClient(): Promise<AIClient | undefined> {\n await this.ensureRuntimeServicesInitialized();\n return this._ai;\n }\n\n private async isSystemSchemaVersionApplied(\n db: DatabaseInterface,\n version: string,\n ): Promise<boolean> {\n const engine = detectEngine(getDatabaseUrl(db), this._dbEngineHint);\n\n if (\n engine === 'postgres' &&\n !(await tableExists(db, '_smrt_migrations', this._dbEngineHint))\n ) {\n return false;\n }\n\n try {\n const versionParam = engine === 'postgres' ? '$1' : '?';\n const rows = await db.query(\n `SELECT 1 FROM _smrt_migrations WHERE version = ${versionParam} LIMIT 1`,\n version,\n );\n return getQueryRows(rows).length > 0;\n } catch (error) {\n if (engine === 'postgres') {\n throw error;\n }\n\n return false;\n }\n }\n\n /**\n * Ensure SMRT system tables exist in the database\n *\n * System tables use the _smrt_ prefix and store framework metadata:\n * - _smrt_contexts: Context memory storage for remembered patterns\n * - _smrt_migrations: Schema version tracking\n * - _smrt_schema_migrations / _smrt_backfills: Migration and backfill ledgers\n * - _smrt_embeddings: Embedding vectors for semantic search\n * - _smrt_dispatch / _smrt_dispatch_subscriptions: Inter-agent dispatch queue\n * - _smrt_ai_usage: AI usage telemetry\n * - _smrt_changes: Append-only change feed\n *\n * Note that the _smrt_ prefix alone does NOT mean \"system table\" — ~25\n * `@smrt()` domain tables (feature flags, prompt overrides, subscription\n * plans, …) carry it too and are created by `db:migrate`, not here. The\n * framework's own list is `SYSTEM_TABLE_NAMES` in\n * `schema/system-table-shapes.ts`, derived from this DDL.\n *\n * This method is idempotent and safe to call multiple times.\n * Tables are only created once per database connection.\n */\n private async ensureSystemTables(): Promise<void> {\n if (!this._db) return;\n\n const dbUrl = getDatabaseUrl(this._db);\n\n // Some databases share URLs but are different instances:\n // - :memory: databases (SQLite/DuckDB in-memory)\n // - JSON databases (may have undefined or shared URLs)\n // - Any database with undefined URL\n // Use WeakSet for instance tracking, Set<string> for URL tracking\n const dbConstructorName = this._db.constructor?.name || '';\n const isMemoryDb = dbUrl === ':memory:';\n const isJsonDb = dbConstructorName.toLowerCase().includes('json');\n const hasUndefinedUrl = !dbUrl;\n const useInstanceTracking = isMemoryDb || isJsonDb || hasUndefinedUrl;\n\n if (useInstanceTracking) {\n // Check WeakSet for databases that may share URLs (track by instance)\n if (SmrtClass._systemTablesInitialized.has(this._db)) {\n return;\n }\n } else {\n // Check Set<string> for URL-based databases (track by URL)\n if (SmrtClass._systemTablesInitializedByUrl.has(dbUrl)) {\n return;\n }\n }\n\n try {\n await ensureFrameworkSystemTables(this._db, this._dbEngineHint);\n await this.settleDeferredSystemTableCompatibility(this._db);\n await this.ensureNativeVectorStorage();\n\n // Mark as initialized using appropriate tracking mechanism\n if (useInstanceTracking) {\n SmrtClass._systemTablesInitialized.add(this._db);\n } else {\n SmrtClass._systemTablesInitializedByUrl.add(dbUrl);\n }\n } catch (error) {\n // DO NOT SWALLOW ERRORS - fail loudly so we know what's wrong\n const dbInfo = this._db.constructor?.name || 'unknown database';\n throw new Error(\n `Failed to create system tables for ${dbInfo}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Run the compatibility pass for the system tables `db:migrate` creates, then\n * stamp its own marker in `_smrt_migrations` (issue #2376).\n *\n * Deliberately runs OUTSIDE the bootstrap lock/transaction, and deliberately\n * cannot fail startup:\n *\n * - `_smrt_jobs` / `_smrt_job_events` are created by `db:migrate` from the\n * jobs manifest, which on a fresh install runs AFTER the framework's first\n * bootstrap. Gating this pass on {@link SMRT_SCHEMA_VERSION} would mean the\n * version gets stamped while the tables are still absent and the pass never\n * runs on that database again. It therefore carries its own marker, which\n * is written only once every deferred table exists and has been upgraded.\n * - Its statements are ALTER TABLE / CREATE INDEX against tables the\n * framework does not own. Inside the PostgreSQL bootstrap transaction one\n * failure (a permissions problem, say) would abort the transaction and roll\n * back system-table creation with it. Outside, a failure is a logged\n * warning and the next process start retries — the pass is idempotent and\n * already tolerates concurrent creators.\n *\n * Until the tables exist this costs two catalog probes per process per\n * database; afterwards the marker short-circuits it.\n */\n private async settleDeferredSystemTableCompatibility(\n db: DatabaseInterface,\n ): Promise<void> {\n const marker = `${SMRT_SCHEMA_VERSION}${DEFERRED_COMPATIBILITY_VERSION_SUFFIX}`;\n\n try {\n if (await this.isSystemSchemaVersionApplied(db, marker)) {\n return;\n }\n\n const { settled } = await ensureDeferredSystemTableCompatibility(\n db,\n this._dbEngineHint,\n );\n if (!settled) {\n return;\n }\n\n const id = crypto.randomUUID();\n const description = 'Deferred SMRT system table compatibility';\n await db.execute`\n INSERT INTO _smrt_migrations (id, version, description)\n VALUES (${id}, ${marker}, ${description})\n ON CONFLICT(version) DO NOTHING\n `;\n } catch (error) {\n logger.warn(\n `[smrt] Deferred system table compatibility did not complete; retrying on the next start: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n private async ensureNativeVectorStorage(): Promise<void> {\n if (!this._db) {\n return;\n }\n\n try {\n const { ObjectRegistry } = await import('./registry.js');\n const embeddingConfig = ObjectRegistry.getProjectEmbeddingConfig();\n if (embeddingConfig?.storage === 'native') {\n const { EmbeddingStorage } = await import('./embeddings/storage.js');\n const vector = this._db.vector;\n if (vector) {\n const dimensions = embeddingConfig.dimensions || 768;\n await EmbeddingStorage.ensureVectorStorage(\n this._db,\n dimensions,\n vector,\n );\n } else {\n logger.warn(\n '[smrt] Embedding storage set to \"native\" but database has no vector capability. Falling back to JSON storage.',\n );\n }\n }\n } catch (error) {\n // Don't fail system table initialization for vector setup errors\n logger.warn(\n `[smrt] Failed to initialize vector storage: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Access system tables through standard database interface\n * System tables use _smrt_ prefix to avoid conflicts with user tables\n */\n protected get systemDb(): DatabaseInterface {\n return this._db;\n }\n\n /**\n * Initialize signal bus and adapters\n *\n * Merges global configuration with instance-specific overrides.\n * Registers built-in and custom adapters based on configuration.\n */\n private async initializeSignals(): Promise<void> {\n const globalConfig = config.toJSON();\n const effectiveConfig = this.mergeSignalConfig(globalConfig);\n\n // If a shared bus is provided, always use it (don't create new adapters)\n if (this.options.signals?.bus) {\n this._signalBus = this.options.signals.bus;\n return;\n }\n\n // Otherwise, check if we should initialize signals based on config\n if (!this.shouldInitializeSignals(effectiveConfig)) {\n return;\n }\n\n this._signalBus = new SignalBus({\n sanitization: effectiveConfig.sanitization,\n });\n await this.registerAdapters(effectiveConfig);\n }\n\n /**\n * Merge global and instance signal configuration\n *\n * Instance configuration takes priority over global defaults.\n *\n * @param globalConfig - Global configuration from smrt.configure()\n * @returns Merged configuration\n */\n private mergeSignalConfig(\n globalConfig: GlobalSignalConfig,\n ): GlobalSignalConfig {\n return {\n logging: this.options.logging ?? globalConfig.logging,\n metrics: this.options.metrics ?? globalConfig.metrics,\n pubsub: this.options.pubsub ?? globalConfig.pubsub,\n usage: this.mergeAiUsageConfig(globalConfig),\n sanitization: this.options.sanitization ?? globalConfig.sanitization,\n signals: {\n bus: this.options.signals?.bus ?? globalConfig.signals?.bus,\n adapters: [\n ...(globalConfig.signals?.adapters ?? []),\n ...(this.options.signals?.adapters ?? []),\n ],\n },\n };\n }\n\n /**\n * Check if signals should be initialized\n *\n * Signals are initialized if any adapter is configured.\n *\n * @param config - Effective signal configuration\n * @returns True if signals should be initialized\n */\n private shouldInitializeSignals(config: GlobalSignalConfig): boolean {\n return !!(\n config.logging !== false ||\n config.metrics?.enabled ||\n config.pubsub?.enabled ||\n config.signals?.adapters?.length\n );\n }\n\n /**\n * Register signal adapters based on configuration\n *\n * @param config - Effective signal configuration\n */\n private async registerAdapters(config: GlobalSignalConfig): Promise<void> {\n if (!this._signalBus) return;\n\n // Logging adapter (default: enabled with console)\n if (config.logging !== false) {\n const { createLogger, LoggerAdapter } = await import(\n '@happyvertical/logger'\n );\n const logger = createLogger(config.logging ?? true);\n const adapter = new LoggerAdapter(logger);\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Metrics adapter (default: disabled)\n if (config.metrics?.enabled) {\n const { MetricsAdapter } = await import('./adapters/metrics.js');\n const adapter = new MetricsAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Pub/Sub adapter (default: disabled)\n if (config.pubsub?.enabled) {\n const { PubSubAdapter } = await import('./adapters/pubsub.js');\n const adapter = new PubSubAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Custom adapters\n if (config.signals?.adapters) {\n for (const adapter of config.signals.adapters) {\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n }\n }\n\n /**\n * Gets the filesystem adapter instance\n */\n get fs() {\n return this._fs;\n }\n\n /**\n * Gets the database interface instance\n */\n get db() {\n // Throw helpful error if database is accessed before initialization\n if (!this._db) {\n throw new Error(\n `Database accessed before initialization. ` +\n `Please call await instance.initialize() before accessing the database.`,\n );\n }\n return this._db;\n }\n\n /**\n * Gets the AI client instance\n */\n get ai() {\n return this._ai;\n }\n\n /**\n * Get the in-memory AI usage snapshot for this instance.\n */\n getAiUsageSnapshot(): AiUsageSnapshot | undefined {\n return this._aiUsageCollector?.getSnapshot();\n }\n\n /**\n * Reset the in-memory AI usage collector.\n */\n resetAiUsage(): void {\n this._aiUsageCollector?.reset();\n }\n\n /**\n * List persisted AI usage records.\n */\n async listAiUsage(\n options: AiUsageListOptions = {},\n ): Promise<SmrtAiUsageRecord[]> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n let paramIndex = params.length + 1;\n\n let sql = 'SELECT * FROM _smrt_ai_usage';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ` ORDER BY ${\n options.orderBy === 'timestamp ASC' ? 'created_at ASC' : 'created_at DESC'\n }`;\n\n if (options.limit !== undefined) {\n sql += ` LIMIT $${paramIndex++}`;\n params.push(options.limit);\n }\n\n if (options.offset !== undefined) {\n sql += ` OFFSET $${paramIndex++}`;\n params.push(options.offset);\n }\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n\n return rows.map((row) => ({\n id: String(row.id),\n provider: String(row.provider),\n model: String(row.model),\n operation: String(row.operation),\n usage: hydratePersistedAiUsageTokens(row),\n estimatedCost:\n row.estimated_cost === null || row.estimated_cost === undefined\n ? undefined\n : Number(row.estimated_cost),\n duration: toSafeInteger(row.duration ?? 0, 'AI usage duration'),\n className:\n row.class_name === null || row.class_name === undefined\n ? undefined\n : String(row.class_name),\n tenantId:\n row.tenant_id === undefined\n ? undefined\n : (row.tenant_id as string | null),\n tags: parseAiUsageTags(row.tags),\n timestamp: normalizeAiUsageTimestamp(row.created_at),\n }));\n }\n\n /**\n * Summarize persisted AI usage records by a grouping dimension.\n */\n async summarizeAiUsage(\n options: AiUsageSummaryOptions = {},\n ): Promise<Record<string, AiUsageStats>> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const groupBy = options.groupBy ?? 'model';\n const bucketExpression =\n groupBy === 'provider'\n ? `provider`\n : groupBy === 'model'\n ? `provider || ':' || model`\n : groupBy === 'class'\n ? `COALESCE(class_name, 'unknown')`\n : groupBy === 'tenant'\n ? `COALESCE(tenant_id, 'global')`\n : groupBy === 'operation'\n ? `operation`\n : `substr(CAST(created_at AS TEXT), 1, 10)`;\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n\n let sql = `\n SELECT ${bucketExpression} AS bucket,\n COUNT(*) AS call_count,\n COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,\n COALESCE(SUM(completion_tokens), 0) AS completion_tokens,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(duration), 0) AS total_duration,\n COALESCE(SUM(estimated_cost), 0) AS estimated_cost,\n MAX(created_at) AS last_used\n FROM _smrt_ai_usage\n `;\n\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ' GROUP BY bucket ORDER BY bucket ASC';\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n const summary: Record<string, AiUsageStats> = {};\n\n for (const row of rows) {\n const bucket = String(row.bucket);\n summary[bucket] = {\n callCount: toSafeInteger(row.call_count ?? 0, 'AI usage call count'),\n promptTokens: toSafeInteger(\n row.prompt_tokens ?? 0,\n 'AI usage prompt-token total',\n ),\n completionTokens: toSafeInteger(\n row.completion_tokens ?? 0,\n 'AI usage completion-token total',\n ),\n totalTokens: toSafeInteger(\n row.total_tokens ?? 0,\n 'AI usage token total',\n ),\n totalDuration: toSafeInteger(\n row.total_duration ?? 0,\n 'AI usage duration total',\n ),\n estimatedCost: Number(row.estimated_cost ?? 0),\n lastUsed: row.last_used ? new Date(String(row.last_used)).getTime() : 0,\n };\n }\n\n return summary;\n }\n\n /**\n * Gets the signal bus instance\n *\n * @returns Signal bus if signals are enabled, undefined otherwise\n */\n get signalBus(): SignalBus | undefined {\n return this._signalBus;\n }\n\n /**\n * Cleanup method to prevent memory leaks\n *\n * Unregisters all adapters from the signal bus that were registered\n * by this instance. Call this when the SmrtClass instance is no longer\n * needed to prevent memory leaks.\n *\n * @example\n * ```typescript\n * const product = new Product({ name: 'Widget' });\n * await product.initialize();\n * // ... use product ...\n * product.destroy(); // Clean up when done\n * ```\n */\n destroy(): void {\n // Only unregister adapters if we own the bus (not shared)\n if (this._signalBus && !this.options.signals?.bus) {\n for (const adapter of this._registeredAdapters) {\n this._signalBus.unregister(adapter);\n }\n this._registeredAdapters = [];\n }\n\n // TODO: If SmrtClass grows a broader async teardown lifecycle, move\n // AI usage handler cleanup there alongside other connection-bound state.\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n }\n\n private mergeAiUsageConfig(\n globalConfig: GlobalSignalConfig,\n ): ResolvedAiUsageConfig {\n const globalUsage = globalConfig.usage ?? {};\n const instanceUsage = this.options.usage ?? {};\n\n return {\n enabled: instanceUsage.enabled ?? globalUsage.enabled ?? true,\n persist: instanceUsage.persist ?? globalUsage.persist ?? true,\n estimateCosts:\n instanceUsage.estimateCosts ?? globalUsage.estimateCosts ?? true,\n costRates: {\n ...(globalUsage.costRates ?? {}),\n ...(instanceUsage.costRates ?? {}),\n },\n handlers: [\n ...(globalUsage.handlers ?? []),\n ...(instanceUsage.handlers ?? []),\n ],\n };\n }\n\n private initializeAiUsageHandlers(config: ResolvedAiUsageConfig): void {\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n\n if (!config.enabled) {\n return;\n }\n\n this._aiUsageCollector = new AiUsageCollector();\n this._aiUsageHandlers.push(this._aiUsageCollector);\n\n if (config.persist && this._db) {\n this._aiUsageHandlers.push(new AiUsagePersistenceHandler(this._db));\n }\n\n this._aiUsageHandlers.push(...config.handlers);\n }\n\n private async handleAiUsageCallback(\n event: unknown,\n aiConfig: Record<string, unknown>,\n usageConfig: ResolvedAiUsageConfig,\n ): Promise<void> {\n if (!usageConfig.enabled || this._aiUsageHandlers.length === 0) {\n return;\n }\n\n const normalizedEvent = this.normalizeAiUsageEvent(event, aiConfig);\n if (!normalizedEvent) {\n return;\n }\n\n if (usageConfig.estimateCosts) {\n normalizedEvent.estimatedCost = estimateAiUsageCost(\n normalizedEvent.provider,\n normalizedEvent.model,\n normalizedEvent.usage,\n usageConfig.costRates,\n );\n }\n\n const results = await Promise.allSettled(\n this._aiUsageHandlers.map((handler) => handler.handle(normalizedEvent)),\n );\n\n for (const result of results) {\n if (result.status === 'rejected') {\n logger.warn(\n `[smrt] AI usage handler failed for ${normalizedEvent.provider}:${normalizedEvent.model}: ${\n result.reason instanceof Error\n ? result.reason.message\n : String(result.reason)\n }`,\n );\n }\n }\n }\n\n private normalizeAiUsageEvent(\n event: unknown,\n aiConfig: Record<string, unknown>,\n ): SmrtAiUsageEvent | undefined {\n const raw = (event ?? {}) as Record<string, unknown>;\n const provider = firstString(\n raw.provider,\n raw.type,\n aiConfig.provider,\n aiConfig.type,\n );\n const model = firstString(\n raw.model,\n raw.defaultModel,\n aiConfig.model,\n aiConfig.defaultModel,\n );\n const operation =\n firstString(raw.operation, raw.kind, raw.method) ?? 'unknown';\n const usage =\n normalizeIncomingAiUsageTokens(raw.usage) ??\n normalizeIncomingAiUsageTokens(raw.tokenUsage) ??\n normalizeIncomingAiUsageTokens({\n promptTokens: raw.promptTokens,\n completionTokens: raw.completionTokens,\n totalTokens: raw.totalTokens,\n });\n\n if (!provider || !model) {\n return undefined;\n }\n\n const duration =\n firstNumber(raw.duration, raw.durationMs, raw.latency) ?? 0;\n const tenantId =\n 'tenantId' in this &&\n (this as { tenantId?: string | null }).tenantId !== undefined\n ? ((this as { tenantId?: string | null }).tenantId ?? null)\n : undefined;\n\n return {\n provider,\n model,\n operation,\n usage,\n duration,\n timestamp: normalizeAiUsageTimestamp(raw.timestamp),\n tags: normalizeAiUsageTags(raw.tags),\n className: this._className,\n tenantId,\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"class.js","names":[],"sources":["../src/class.ts"],"sourcesContent":["import type { AIClientOptions } from '@happyvertical/ai';\nimport { type AIClient, getAI } from '@happyvertical/ai';\nimport type {\n FilesystemAdapter,\n FilesystemAdapterOptions,\n} from '@happyvertical/files';\nimport { createLogger, type LoggerConfig } from '@happyvertical/logger';\nimport type {\n AiTokenUsage,\n AiUsageHandler,\n AiUsageListOptions,\n AiUsageSnapshot,\n AiUsageStats,\n AiUsageSummaryOptions,\n SignalAdapter,\n SmrtAiUsageEvent,\n SmrtAiUsageRecord,\n} from '@happyvertical/smrt-types';\nimport { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport {\n AiUsageCollector,\n AiUsagePersistenceHandler,\n} from './adapters/ai-usage.js';\nimport { estimateAiUsageCost } from './adapters/cost-rates.js';\nimport { registerChangeFeedWriter } from './change-feed.js';\nimport type {\n AIConfig,\n AiUsageConfig,\n GlobalSignalConfig,\n MetricsConfig,\n PubSubConfig,\n} from './config.js';\nimport { config } from './config.js';\nimport type { DatabaseConfig } from './database.js';\nimport { createFilesystemAdapter } from './filesystem-loader.js';\nimport { applyPostgresRuntimeTimeouts } from './postgres-timeouts.js';\nimport { detectEngine } from './schema/ddl/index.js';\nimport { SignalBus } from './signals/bus.js';\nimport { ensureSystemTables as ensureFrameworkSystemTables } from './system/bootstrap.js';\nimport {\n ensureDeferredSystemTableCompatibility,\n tableExists,\n} from './system/compatibility.js';\nimport { SMRT_SCHEMA_VERSION } from './system/schema.js';\nimport { toSafeInteger } from './utils/safe-integer.js';\n\n/**\n * `_smrt_migrations.version` suffix marking that the deferred (manifest-created)\n * system tables have been through their compatibility pass (issue #2376).\n */\nconst DEFERRED_COMPATIBILITY_VERSION_SUFFIX = '+deferred-compat';\n\nconst logger = createLogger({ level: 'info' });\n\ntype DatabaseWithConfig = DatabaseInterface & {\n config?: {\n type?: string;\n url?: string;\n };\n type?: string;\n};\n\ninterface ResolvedAiUsageConfig {\n enabled: boolean;\n persist: boolean;\n estimateCosts: boolean;\n costRates?: Record<string, { input: number; output: number }>;\n handlers: AiUsageHandler[];\n}\n\ntype AiUsageFilterOptions = Pick<\n AiUsageListOptions,\n | 'since'\n | 'until'\n | 'provider'\n | 'model'\n | 'operation'\n | 'className'\n | 'tenantId'\n>;\n\ninterface AiUsageWhereClause {\n conditions: string[];\n params: unknown[];\n nextParamIndex: number;\n}\n\nfunction firstString(...candidates: unknown[]): string | undefined {\n return candidates.find((candidate): candidate is string => {\n return typeof candidate === 'string';\n });\n}\n\nfunction firstNumber(...candidates: unknown[]): number | undefined {\n return candidates.find((candidate): candidate is number => {\n return typeof candidate === 'number';\n });\n}\n\nfunction getDatabaseUrl(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseWithConfig;\n return db.url || dbWithConfig.config?.url || '';\n}\n\nfunction getDatabaseTypeHint(\n config: DatabaseConfig | undefined,\n): string | undefined {\n if (!config || typeof config === 'string') {\n return undefined;\n }\n\n const configWithType = config as DatabaseWithConfig & {\n client?: unknown;\n };\n\n if (typeof configWithType.type === 'string') {\n return configWithType.type;\n }\n\n if (typeof configWithType.config?.type === 'string') {\n return configWithType.config.type;\n }\n\n if ('query' in configWithType && typeof configWithType.query === 'function') {\n return undefined;\n }\n\n if ('client' in configWithType && configWithType.client) {\n return 'postgres';\n }\n\n return undefined;\n}\n\nfunction normalizeIncomingAiUsageTokens(\n value: unknown,\n): AiTokenUsage | undefined {\n if (!value || typeof value !== 'object') {\n return undefined;\n }\n\n const usage = value as Record<string, unknown>;\n const promptTokens =\n typeof usage.promptTokens === 'number'\n ? usage.promptTokens\n : typeof usage.inputTokens === 'number'\n ? usage.inputTokens\n : undefined;\n const completionTokens =\n typeof usage.completionTokens === 'number'\n ? usage.completionTokens\n : typeof usage.outputTokens === 'number'\n ? usage.outputTokens\n : undefined;\n const totalTokens =\n typeof usage.totalTokens === 'number'\n ? usage.totalTokens\n : promptTokens !== undefined || completionTokens !== undefined\n ? (promptTokens ?? 0) + (completionTokens ?? 0)\n : undefined;\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction hydratePersistedAiUsageTokens(row: {\n prompt_tokens?: unknown;\n completion_tokens?: unknown;\n total_tokens?: unknown;\n}): AiTokenUsage | undefined {\n const promptTokens =\n row.prompt_tokens === null || row.prompt_tokens === undefined\n ? undefined\n : toSafeInteger(row.prompt_tokens, 'AI usage prompt tokens');\n const completionTokens =\n row.completion_tokens === null || row.completion_tokens === undefined\n ? undefined\n : toSafeInteger(row.completion_tokens, 'AI usage completion tokens');\n const totalTokens =\n row.total_tokens === null || row.total_tokens === undefined\n ? undefined\n : toSafeInteger(row.total_tokens, 'AI usage total tokens');\n\n if (\n promptTokens === undefined &&\n completionTokens === undefined &&\n totalTokens === undefined\n ) {\n return undefined;\n }\n\n return {\n promptTokens,\n completionTokens,\n totalTokens,\n };\n}\n\nfunction normalizeAiUsageTags(\n value: unknown,\n): Record<string, string> | undefined {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return undefined;\n }\n\n const tags: Record<string, string> = {};\n for (const [key, tagValue] of Object.entries(value)) {\n if (tagValue === undefined || tagValue === null) continue;\n tags[key] = String(tagValue);\n }\n\n return Object.keys(tags).length > 0 ? tags : undefined;\n}\n\nfunction parseAiUsageTags(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'string') {\n return undefined;\n }\n\n try {\n return normalizeAiUsageTags(JSON.parse(value));\n } catch {\n return undefined;\n }\n}\n\nfunction normalizeAiUsageTimestamp(value: unknown): Date {\n if (value instanceof Date) {\n return value;\n }\n\n if (typeof value === 'string' || typeof value === 'number') {\n const date = new Date(value);\n if (!Number.isNaN(date.getTime())) {\n return date;\n }\n }\n\n return new Date();\n}\n\nfunction getQueryRows(\n result: Awaited<ReturnType<DatabaseInterface['query']>>,\n): Record<string, unknown>[] {\n return Array.isArray(result)\n ? (result as Record<string, unknown>[])\n : ((result as { rows?: Record<string, unknown>[] }).rows ?? []);\n}\n\nfunction buildAiUsageWhereClause(\n options: AiUsageFilterOptions,\n): AiUsageWhereClause {\n const conditions: string[] = [];\n const params: unknown[] = [];\n let nextParamIndex = 1;\n\n if (options.since) {\n conditions.push(`created_at >= $${nextParamIndex++}`);\n params.push(options.since.toISOString());\n }\n\n if (options.until) {\n conditions.push(`created_at <= $${nextParamIndex++}`);\n params.push(options.until.toISOString());\n }\n\n if (options.provider) {\n conditions.push(`provider = $${nextParamIndex++}`);\n params.push(options.provider);\n }\n\n if (options.model) {\n conditions.push(`model = $${nextParamIndex++}`);\n params.push(options.model);\n }\n\n if (options.operation) {\n conditions.push(`operation = $${nextParamIndex++}`);\n params.push(options.operation);\n }\n\n if (options.className) {\n conditions.push(`class_name = $${nextParamIndex++}`);\n params.push(options.className);\n }\n\n if (options.tenantId === null) {\n conditions.push(`tenant_id IS NULL`);\n } else if (options.tenantId) {\n conditions.push(`tenant_id = $${nextParamIndex++}`);\n params.push(options.tenantId);\n }\n\n return {\n conditions,\n params,\n nextParamIndex,\n };\n}\n\n/**\n * Configuration options for the SmrtClass\n */\nexport interface SmrtClassOptions {\n /**\n * Optional custom class name override\n */\n _className?: string;\n\n /**\n * Database configuration - unified approach matching @happyvertical/sql\n *\n * Supports three formats:\n * - String shortcut: 'products.db' (auto-detects database type)\n * - Config object: { type: 'sqlite', url: 'products.db' }\n * - DatabaseInterface instance: await getDatabase(...)\n *\n * @see DatabaseConfig for type definition\n */\n db?: DatabaseConfig;\n\n /**\n * Alias for db option - for backward compatibility with documentation\n *\n * @deprecated Use 'db' instead. This alias exists for backward compatibility.\n */\n persistence?: DatabaseConfig;\n\n /**\n * Filesystem adapter configuration options\n */\n fs?: FilesystemAdapterOptions;\n\n /**\n * AI client configuration options or instance\n */\n ai?: AIClientOptions | AIClient;\n\n /**\n * AI usage tracking configuration (overrides global defaults)\n */\n usage?: AiUsageConfig;\n\n /**\n * Logging configuration (overrides global default)\n */\n logging?: LoggerConfig;\n\n /**\n * Metrics configuration (overrides global default)\n */\n metrics?: MetricsConfig;\n\n /**\n * Pub/Sub configuration (overrides global default)\n */\n pubsub?: PubSubConfig;\n\n /**\n * Sanitization configuration (overrides global default)\n */\n sanitization?: import('./config.js').GlobalSignalConfig['sanitization'];\n\n /**\n * Custom signal configuration (overrides global default)\n */\n signals?: {\n /** Shared signal bus instance */\n bus?: SignalBus;\n /** Additional custom adapters */\n adapters?: SignalAdapter[];\n };\n\n /**\n * Internal flag to reuse an already initialized DatabaseInterface instance.\n *\n * Skips database resolution and system-table setup during lightweight hydration.\n * @internal\n */\n _reuseInitializedDb?: boolean;\n\n /**\n * Internal flag to defer runtime-only services such as signals and AI setup.\n *\n * Lightweight hydration paths set this so plain query reads avoid per-row\n * runtime bootstrap costs.\n * @internal\n */\n _deferRuntimeInitialization?: boolean;\n}\n\n/**\n * Foundation class providing core functionality for the SMRT framework\n *\n * SmrtClass provides unified access to database, filesystem, and AI client\n * interfaces. It serves as the foundation for all other classes in the\n * SMRT framework.\n */\nexport class SmrtClass {\n /**\n * AI client instance for interacting with AI models\n */\n protected _ai!: AIClient;\n\n /**\n * Filesystem adapter for file operations\n */\n protected _fs!: FilesystemAdapter;\n\n /**\n * Database interface for data persistence\n */\n protected _db!: DatabaseInterface;\n private _dbEngineHint?: string;\n\n /**\n * Class name used for identification\n */\n protected _className!: string;\n\n /**\n * Signal bus for method execution tracking\n */\n protected _signalBus?: SignalBus;\n\n /**\n * Adapters registered by this instance (for cleanup)\n */\n private _registeredAdapters: SignalAdapter[] = [];\n\n /**\n * In-memory AI usage collector for quick inspection.\n */\n private _aiUsageCollector?: AiUsageCollector;\n\n /**\n * Registered AI usage handlers for this instance.\n */\n private _aiUsageHandlers: AiUsageHandler[] = [];\n\n /**\n * Tracks whether optional runtime services (signals, AI, fs) are ready.\n */\n private _runtimeServicesInitialized = false;\n\n /**\n * Shared in-flight runtime initialization promise for single-flight setup.\n */\n private _runtimeServicesInitPromise?: Promise<void>;\n\n /**\n * Configuration options provided to the class\n */\n public options: SmrtClassOptions;\n\n /**\n * Track which databases have had system tables initialized\n * - WeakSet for :memory: databases (URL not unique, track by instance)\n * - Set<string> for all others (URL is unique identifier)\n */\n private static _systemTablesInitialized = new WeakSet<DatabaseInterface>();\n private static _systemTablesInitializedByUrl = new Set<string>();\n\n /**\n * Creates a new SmrtClass instance\n *\n * @param options - Configuration options for database, filesystem, and AI clients\n */\n constructor(options: SmrtClassOptions = {}) {\n this.options = options;\n this._className = this.constructor.name;\n }\n\n /**\n * Determines whether this class requires a database to function\n *\n * Override this method in subclasses that require database access\n * to enable early validation during initialization.\n *\n * @returns True if database is required, false otherwise\n * @example\n * ```typescript\n * class MyDataModel extends SmrtClass {\n * protected requiresDatabase(): boolean {\n * return true; // This class needs database access\n * }\n * }\n * ```\n */\n protected requiresDatabase(): boolean {\n return false; // Base class doesn't require database by default\n }\n\n /**\n * Initializes database, filesystem, and AI client connections\n *\n * This method sets up all required services based on the provided options.\n * It should be called before using any of the service interfaces.\n *\n * @returns Promise that resolves to this instance for chaining\n * @throws {Error} If database is required but not provided in options\n */\n protected async initialize(): Promise<this> {\n await this.initializeCoreResources();\n\n if (!this.options._deferRuntimeInitialization) {\n await this.initializeRuntimeServices();\n }\n\n return this;\n }\n\n /**\n * Initialize core resources required for ORM behavior.\n *\n * This setup is shared by both full runtime initialization and lightweight\n * query hydration. Hydrated objects reuse an existing DB connection and skip\n * repeated system-table checks.\n */\n protected async initializeCoreResources(): Promise<void> {\n // Framework init hook for the change feed (#1758): make sure the writer\n // interceptor observes every save/delete before any instance can write.\n // Idempotent and cheap when already registered.\n registerChangeFeedWriter();\n\n // Map persistence to db for backward compatibility\n if (this.options.persistence && !this.options.db) {\n this.options.db = this.options.persistence;\n }\n\n // Validate database configuration if required\n if (this.requiresDatabase() && !this.options.db) {\n throw new Error(\n `${this._className} requires a database configuration. ` +\n `Please provide 'db' in options: { db: { url: '...' } } or { db: 'database.db' }`,\n );\n }\n\n if (this.options.db) {\n this._dbEngineHint = getDatabaseTypeHint(this.options.db);\n\n if (\n this.options._reuseInitializedDb &&\n typeof this.options.db === 'object' &&\n 'query' in this.options.db\n ) {\n this._db = this.options.db as DatabaseInterface;\n this.options.db = this._db;\n } else {\n // Handle four db config formats (in implementation order):\n // 1. String URL: 'products.db' (shortcut)\n // 2. DatabaseInterface instance: already initialized db (has 'query' method)\n // 3. Config with client: { type: 'postgres', client: pgPool } (SvelteKit pattern)\n // 4. Config object: { type: 'sqlite', url: 'products.db' }\n if (typeof this.options.db === 'string') {\n // Format 1: String shortcut - let getDatabase auto-detect type from URL\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const isMemoryDb = this.options.db === ':memory:';\n // PostgreSQL URLs pick up the runtime timeout bounds, and the dbid is\n // derived from the bounded URL so the same rewrite at every call site\n // still resolves to one shared pool (#2377).\n const bounded = applyPostgresRuntimeTimeouts({\n url: this.options.db,\n });\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else if ('query' in this.options.db) {\n // Format 2: Already a DatabaseInterface instance - return as-is\n this._db = this.options.db as DatabaseInterface;\n } else if ('client' in this.options.db && this.options.db.client) {\n // Format 3: Config with pre-created client (e.g., from SvelteKit's $env-based connection)\n // Pass the client to getDatabase which will use it instead of creating a new connection\n const dbConfig = this.options.db as {\n type?: string;\n client: unknown;\n url?: string;\n };\n // `client` is a runtime-only property the postgres adapter reads\n // but the public `getDatabase` option union does not model, so the\n // literal is routed through the function's own parameter type.\n this._db = await getDatabase({\n type: dbConfig.type || 'postgres',\n client: dbConfig.client,\n url: dbConfig.url,\n } as unknown as Parameters<typeof getDatabase>[0]);\n } else {\n // Format 4: Config object - pass to getDatabase (handles all types uniformly)\n // Preserve connection sharing for file-backed databases while leaving\n // true in-memory databases isolated per instance.\n const dbConfig = this.options.db as { url?: string; type?: string };\n const dbUrl = dbConfig.url || 'memory';\n const isMemoryDb = dbUrl === ':memory:' || dbUrl === 'memory';\n // Keep the config's own key set: an object with no `url` must stay\n // that way so the adapter applies its own default (#2377).\n const bounded = applyPostgresRuntimeTimeouts({ ...this.options.db });\n // The loose config-object variant of `DatabaseConfig` carries an\n // index signature that the closed `getDatabase` option union does\n // not accept structurally, so route through its parameter type.\n this._db = await getDatabase({\n ...bounded,\n ...(isMemoryDb ? {} : { dbid: `smrt:${bounded.url ?? dbUrl}` }),\n } as unknown as Parameters<typeof getDatabase>[0]);\n }\n\n /**\n * INTENTIONAL MUTATION: After resolving the database config,\n * we replace options.db with the actual DatabaseInterface instance.\n * This enables child objects to share the same connection via:\n *\n * const child = new ChildObject({ db: parent.options.db });\n *\n * Without this, passing this.options to getCollection() would use the config object\n * which causes a NEW db instance to be created, losing data isolation.\n *\n * See issue #567 for context on why this pattern is necessary.\n */\n this.options.db = this._db;\n\n await this.ensureSystemTables();\n }\n }\n }\n\n /**\n * Initialize optional runtime services that are not required for plain ORM reads.\n */\n protected async initializeRuntimeServices(): Promise<void> {\n if (this._runtimeServicesInitialized) {\n return;\n }\n\n if (!this._runtimeServicesInitPromise) {\n this._runtimeServicesInitPromise = (async () => {\n if (this.options.fs && !this._fs) {\n // Acquired through the boundary in filesystem-loader.ts so the\n // @happyvertical/files SDK (S3/googleapis) never enters\n // provider-neutral consumer bundles (#1977/#1979).\n this._fs = await createFilesystemAdapter(this.options.fs);\n }\n\n // Initialize AI client with environment variable support\n // Priority: instance options > env vars > global config > defaults\n const globalConfig = config.toJSON();\n const usageConfig = this.mergeAiUsageConfig(globalConfig);\n this.initializeAiUsageHandlers(usageConfig);\n\n if (\n !this._ai &&\n (this.options.ai || globalConfig.ai || process.env.SMRT_AI_PROVIDER)\n ) {\n // Check if options.ai is already a client-like object with embed method\n // This allows passing mock AI clients for testing\n const aiOption = this.options.ai as\n | Record<string, unknown>\n | undefined;\n if (\n aiOption &&\n typeof aiOption === 'object' &&\n typeof aiOption.embed === 'function' &&\n !aiOption.provider\n ) {\n this._ai = aiOption as unknown as AIClient;\n } else {\n // CC-8 follow-up: ideally this would route through\n // `@happyvertical/smrt-config` for sanitization parity (e.g. via\n // `getPackageConfig('ai', ...)`), but smrt-config currently only\n // merges file-based config + runtime overrides — it does NOT\n // read from `process.env` with a typed prefix/schema. Until\n // smrt-config grows an env-loader (or wraps `loadEnvConfig`),\n // we continue to use the underlying utility directly. Tracked\n // alongside the CC-8 audit on issue #1199.\n const { loadEnvConfig } = await import('@happyvertical/utils');\n\n // Start with global defaults\n const baseConfig = globalConfig.ai || {};\n\n // Merge with instance options (takes priority over global)\n const userConfig = { ...baseConfig, ...this.options.ai };\n\n // Load environment variables and merge (user options take priority).\n // `AIConfig` carries an index signature, so provider-specific keys\n // (`type`, `onUsage`, `defaultModel`, …) read back as `unknown`.\n const aiConfig = loadEnvConfig<AIConfig>(userConfig, {\n packageName: 'ai',\n prefix: 'SMRT',\n schema: {\n provider: 'string',\n model: 'string',\n apiKey: 'string',\n timeout: 'number',\n maxRetries: 'number',\n temperature: 'number',\n maxTokens: 'number',\n },\n });\n\n const existingOnUsage =\n aiConfig.onUsage ??\n (userConfig as Record<string, unknown>).onUsage ??\n undefined;\n aiConfig.onUsage = async (event: unknown) => {\n if (typeof existingOnUsage === 'function') {\n await (existingOnUsage as (usageEvent: unknown) => unknown)(\n event,\n );\n }\n await this.handleAiUsageCallback(event, aiConfig, usageConfig);\n };\n\n // Only initialize if we have a provider configured\n if (aiConfig.provider || aiConfig.type || aiConfig.apiKey) {\n // Use getAI() factory to support all AI providers (OpenAI, Anthropic, Gemini, etc.)\n // getAI() returns AIInterface, which we narrow to AIClient for\n // backward compatibility. The index-signature `AIConfig` is routed\n // through getAI's own parameter type rather than the closed union.\n this._ai = (await getAI(\n aiConfig as unknown as Parameters<typeof getAI>[0],\n )) as unknown as AIClient;\n }\n }\n }\n\n await this.initializeSignals();\n this._runtimeServicesInitialized = true;\n })();\n }\n\n try {\n await this._runtimeServicesInitPromise;\n } finally {\n this._runtimeServicesInitPromise = undefined;\n }\n }\n\n /**\n * Ensure deferred runtime services are ready before using them.\n */\n protected async ensureRuntimeServicesInitialized(): Promise<void> {\n if (!this._runtimeServicesInitialized) {\n await this.initializeRuntimeServices();\n }\n }\n\n /**\n * Resolve the AI client, initializing deferred runtime services on demand.\n */\n protected async getAiClient(): Promise<AIClient> {\n await this.ensureRuntimeServicesInitialized();\n\n if (!this._ai) {\n throw new Error(\n `${this._className} does not have an AI client configured. ` +\n `Provide 'ai' in options or configure a global SMRT AI provider.`,\n );\n }\n\n return this._ai;\n }\n\n /**\n * Resolve the AI client if one is configured, otherwise return undefined.\n */\n protected async getOptionalAiClient(): Promise<AIClient | undefined> {\n await this.ensureRuntimeServicesInitialized();\n return this._ai;\n }\n\n private async isSystemSchemaVersionApplied(\n db: DatabaseInterface,\n version: string,\n ): Promise<boolean> {\n const engine = detectEngine(getDatabaseUrl(db), this._dbEngineHint);\n\n if (\n engine === 'postgres' &&\n !(await tableExists(db, '_smrt_migrations', this._dbEngineHint))\n ) {\n return false;\n }\n\n try {\n const versionParam = engine === 'postgres' ? '$1' : '?';\n const rows = await db.query(\n `SELECT 1 FROM _smrt_migrations WHERE version = ${versionParam} LIMIT 1`,\n version,\n );\n return getQueryRows(rows).length > 0;\n } catch (error) {\n if (engine === 'postgres') {\n throw error;\n }\n\n return false;\n }\n }\n\n /**\n * Ensure SMRT system tables exist in the database\n *\n * System tables use the _smrt_ prefix and store framework metadata:\n * - _smrt_contexts: Context memory storage for remembered patterns\n * - _smrt_migrations: Schema version tracking\n * - _smrt_schema_migrations / _smrt_backfills: Migration and backfill ledgers\n * - _smrt_embeddings: Embedding vectors for semantic search\n * - _smrt_dispatch / _smrt_dispatch_subscriptions: Inter-agent dispatch queue\n * - _smrt_ai_usage: AI usage telemetry\n * - _smrt_changes: Append-only change feed\n *\n * Note that the _smrt_ prefix alone does NOT mean \"system table\" — ~25\n * `@smrt()` domain tables (feature flags, prompt overrides, subscription\n * plans, …) carry it too and are created by `db:migrate`, not here. The\n * framework's own list is `SYSTEM_TABLE_NAMES` in\n * `schema/system-table-shapes.ts`, derived from this DDL.\n *\n * This method is idempotent and safe to call multiple times.\n * Tables are only created once per database connection.\n */\n private async ensureSystemTables(): Promise<void> {\n if (!this._db) return;\n\n const dbUrl = getDatabaseUrl(this._db);\n\n // Some databases share URLs but are different instances:\n // - :memory: databases (SQLite/DuckDB in-memory)\n // - JSON databases (may have undefined or shared URLs)\n // - Any database with undefined URL\n // Use WeakSet for instance tracking, Set<string> for URL tracking\n const dbConstructorName = this._db.constructor?.name || '';\n const isMemoryDb = dbUrl === ':memory:';\n const isJsonDb = dbConstructorName.toLowerCase().includes('json');\n const hasUndefinedUrl = !dbUrl;\n const useInstanceTracking = isMemoryDb || isJsonDb || hasUndefinedUrl;\n\n if (useInstanceTracking) {\n // Check WeakSet for databases that may share URLs (track by instance)\n if (SmrtClass._systemTablesInitialized.has(this._db)) {\n return;\n }\n } else {\n // Check Set<string> for URL-based databases (track by URL)\n if (SmrtClass._systemTablesInitializedByUrl.has(dbUrl)) {\n return;\n }\n }\n\n try {\n await ensureFrameworkSystemTables(this._db, this._dbEngineHint);\n await this.settleDeferredSystemTableCompatibility(this._db);\n await this.ensureNativeVectorStorage();\n\n // Mark as initialized using appropriate tracking mechanism\n if (useInstanceTracking) {\n SmrtClass._systemTablesInitialized.add(this._db);\n } else {\n SmrtClass._systemTablesInitializedByUrl.add(dbUrl);\n }\n } catch (error) {\n // DO NOT SWALLOW ERRORS - fail loudly so we know what's wrong\n const dbInfo = this._db.constructor?.name || 'unknown database';\n throw new Error(\n `Failed to create system tables for ${dbInfo}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n }\n\n /**\n * Run the compatibility pass for the system tables `db:migrate` creates, then\n * stamp its own marker in `_smrt_migrations` (issue #2376).\n *\n * Deliberately runs OUTSIDE the bootstrap lock/transaction, and deliberately\n * cannot fail startup:\n *\n * - `_smrt_jobs` / `_smrt_job_events` are created by `db:migrate` from the\n * jobs manifest, which on a fresh install runs AFTER the framework's first\n * bootstrap. Gating this pass on {@link SMRT_SCHEMA_VERSION} would mean the\n * version gets stamped while the tables are still absent and the pass never\n * runs on that database again. It therefore carries its own marker, which\n * is written only once every deferred table exists and has been upgraded.\n * - Its statements are ALTER TABLE / CREATE INDEX against tables the\n * framework does not own. Inside the PostgreSQL bootstrap transaction one\n * failure (a permissions problem, say) would abort the transaction and roll\n * back system-table creation with it. Outside, a failure is a logged\n * warning and the next process start retries — the pass is idempotent and\n * already tolerates concurrent creators.\n *\n * Until the tables exist this costs two catalog probes per process per\n * database; afterwards the marker short-circuits it.\n */\n private async settleDeferredSystemTableCompatibility(\n db: DatabaseInterface,\n ): Promise<void> {\n const marker = `${SMRT_SCHEMA_VERSION}${DEFERRED_COMPATIBILITY_VERSION_SUFFIX}`;\n\n try {\n if (await this.isSystemSchemaVersionApplied(db, marker)) {\n return;\n }\n\n const { settled } = await ensureDeferredSystemTableCompatibility(\n db,\n this._dbEngineHint,\n );\n if (!settled) {\n return;\n }\n\n const id = crypto.randomUUID();\n const description = 'Deferred SMRT system table compatibility';\n await db.execute`\n INSERT INTO _smrt_migrations (id, version, description)\n VALUES (${id}, ${marker}, ${description})\n ON CONFLICT(version) DO NOTHING\n `;\n } catch (error) {\n logger.warn(\n `[smrt] Deferred system table compatibility did not complete; retrying on the next start: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n\n private async ensureNativeVectorStorage(): Promise<void> {\n if (!this._db) {\n return;\n }\n\n try {\n const { ObjectRegistry } = await import('./registry.js');\n const embeddingConfig = ObjectRegistry.getProjectEmbeddingConfig();\n if (embeddingConfig?.storage === 'native') {\n const { EmbeddingStorage } = await import('./embeddings/storage.js');\n const vector = this._db.vector;\n if (vector) {\n const dimensions = embeddingConfig.dimensions || 768;\n await EmbeddingStorage.ensureVectorStorage(\n this._db,\n dimensions,\n vector,\n );\n } else {\n logger.warn(\n '[smrt] Embedding storage set to \"native\" but database has no vector capability. Falling back to JSON storage.',\n );\n }\n }\n } catch (error) {\n // Don't fail system table initialization for vector setup errors\n logger.warn(\n `[smrt] Failed to initialize vector storage: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n /**\n * Access system tables through standard database interface\n * System tables use _smrt_ prefix to avoid conflicts with user tables\n */\n protected get systemDb(): DatabaseInterface {\n return this._db;\n }\n\n /**\n * Return the database engine hint captured from the caller's configuration.\n * Adapters may not expose their original type after construction (notably\n * in-memory DuckDB/JSON connections), so query helpers can combine this\n * hint with their public connection capabilities.\n */\n protected getDatabaseEngineHint(): string | undefined {\n return this._dbEngineHint;\n }\n\n /**\n * Initialize signal bus and adapters\n *\n * Merges global configuration with instance-specific overrides.\n * Registers built-in and custom adapters based on configuration.\n */\n private async initializeSignals(): Promise<void> {\n const globalConfig = config.toJSON();\n const effectiveConfig = this.mergeSignalConfig(globalConfig);\n\n // If a shared bus is provided, always use it (don't create new adapters)\n if (this.options.signals?.bus) {\n this._signalBus = this.options.signals.bus;\n return;\n }\n\n // Otherwise, check if we should initialize signals based on config\n if (!this.shouldInitializeSignals(effectiveConfig)) {\n return;\n }\n\n this._signalBus = new SignalBus({\n sanitization: effectiveConfig.sanitization,\n });\n await this.registerAdapters(effectiveConfig);\n }\n\n /**\n * Merge global and instance signal configuration\n *\n * Instance configuration takes priority over global defaults.\n *\n * @param globalConfig - Global configuration from smrt.configure()\n * @returns Merged configuration\n */\n private mergeSignalConfig(\n globalConfig: GlobalSignalConfig,\n ): GlobalSignalConfig {\n return {\n logging: this.options.logging ?? globalConfig.logging,\n metrics: this.options.metrics ?? globalConfig.metrics,\n pubsub: this.options.pubsub ?? globalConfig.pubsub,\n usage: this.mergeAiUsageConfig(globalConfig),\n sanitization: this.options.sanitization ?? globalConfig.sanitization,\n signals: {\n bus: this.options.signals?.bus ?? globalConfig.signals?.bus,\n adapters: [\n ...(globalConfig.signals?.adapters ?? []),\n ...(this.options.signals?.adapters ?? []),\n ],\n },\n };\n }\n\n /**\n * Check if signals should be initialized\n *\n * Signals are initialized if any adapter is configured.\n *\n * @param config - Effective signal configuration\n * @returns True if signals should be initialized\n */\n private shouldInitializeSignals(config: GlobalSignalConfig): boolean {\n return !!(\n config.logging !== false ||\n config.metrics?.enabled ||\n config.pubsub?.enabled ||\n config.signals?.adapters?.length\n );\n }\n\n /**\n * Register signal adapters based on configuration\n *\n * @param config - Effective signal configuration\n */\n private async registerAdapters(config: GlobalSignalConfig): Promise<void> {\n if (!this._signalBus) return;\n\n // Logging adapter (default: enabled with console)\n if (config.logging !== false) {\n const { createLogger, LoggerAdapter } = await import(\n '@happyvertical/logger'\n );\n const logger = createLogger(config.logging ?? true);\n const adapter = new LoggerAdapter(logger);\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Metrics adapter (default: disabled)\n if (config.metrics?.enabled) {\n const { MetricsAdapter } = await import('./adapters/metrics.js');\n const adapter = new MetricsAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Pub/Sub adapter (default: disabled)\n if (config.pubsub?.enabled) {\n const { PubSubAdapter } = await import('./adapters/pubsub.js');\n const adapter = new PubSubAdapter();\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n\n // Custom adapters\n if (config.signals?.adapters) {\n for (const adapter of config.signals.adapters) {\n this._signalBus.register(adapter);\n this._registeredAdapters.push(adapter);\n }\n }\n }\n\n /**\n * Gets the filesystem adapter instance\n */\n get fs() {\n return this._fs;\n }\n\n /**\n * Gets the database interface instance\n */\n get db() {\n // Throw helpful error if database is accessed before initialization\n if (!this._db) {\n throw new Error(\n `Database accessed before initialization. ` +\n `Please call await instance.initialize() before accessing the database.`,\n );\n }\n return this._db;\n }\n\n /**\n * Gets the AI client instance\n */\n get ai() {\n return this._ai;\n }\n\n /**\n * Get the in-memory AI usage snapshot for this instance.\n */\n getAiUsageSnapshot(): AiUsageSnapshot | undefined {\n return this._aiUsageCollector?.getSnapshot();\n }\n\n /**\n * Reset the in-memory AI usage collector.\n */\n resetAiUsage(): void {\n this._aiUsageCollector?.reset();\n }\n\n /**\n * List persisted AI usage records.\n */\n async listAiUsage(\n options: AiUsageListOptions = {},\n ): Promise<SmrtAiUsageRecord[]> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n let paramIndex = params.length + 1;\n\n let sql = 'SELECT * FROM _smrt_ai_usage';\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ` ORDER BY ${\n options.orderBy === 'timestamp ASC' ? 'created_at ASC' : 'created_at DESC'\n }`;\n\n if (options.limit !== undefined) {\n sql += ` LIMIT $${paramIndex++}`;\n params.push(options.limit);\n }\n\n if (options.offset !== undefined) {\n sql += ` OFFSET $${paramIndex++}`;\n params.push(options.offset);\n }\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n\n return rows.map((row) => ({\n id: String(row.id),\n provider: String(row.provider),\n model: String(row.model),\n operation: String(row.operation),\n usage: hydratePersistedAiUsageTokens(row),\n estimatedCost:\n row.estimated_cost === null || row.estimated_cost === undefined\n ? undefined\n : Number(row.estimated_cost),\n duration: toSafeInteger(row.duration ?? 0, 'AI usage duration'),\n className:\n row.class_name === null || row.class_name === undefined\n ? undefined\n : String(row.class_name),\n tenantId:\n row.tenant_id === undefined\n ? undefined\n : (row.tenant_id as string | null),\n tags: parseAiUsageTags(row.tags),\n timestamp: normalizeAiUsageTimestamp(row.created_at),\n }));\n }\n\n /**\n * Summarize persisted AI usage records by a grouping dimension.\n */\n async summarizeAiUsage(\n options: AiUsageSummaryOptions = {},\n ): Promise<Record<string, AiUsageStats>> {\n if (!this._db) {\n throw new Error(\n `AI usage requires a database configuration. ` +\n `Please call initialize() with a db option before querying usage.`,\n );\n }\n\n const groupBy = options.groupBy ?? 'model';\n const bucketExpression =\n groupBy === 'provider'\n ? `provider`\n : groupBy === 'model'\n ? `provider || ':' || model`\n : groupBy === 'class'\n ? `COALESCE(class_name, 'unknown')`\n : groupBy === 'tenant'\n ? `COALESCE(tenant_id, 'global')`\n : groupBy === 'operation'\n ? `operation`\n : `substr(CAST(created_at AS TEXT), 1, 10)`;\n\n const { conditions, params } = buildAiUsageWhereClause(options);\n\n let sql = `\n SELECT ${bucketExpression} AS bucket,\n COUNT(*) AS call_count,\n COALESCE(SUM(prompt_tokens), 0) AS prompt_tokens,\n COALESCE(SUM(completion_tokens), 0) AS completion_tokens,\n COALESCE(SUM(total_tokens), 0) AS total_tokens,\n COALESCE(SUM(duration), 0) AS total_duration,\n COALESCE(SUM(estimated_cost), 0) AS estimated_cost,\n MAX(created_at) AS last_used\n FROM _smrt_ai_usage\n `;\n\n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`;\n }\n\n sql += ' GROUP BY bucket ORDER BY bucket ASC';\n\n const rows = getQueryRows(await this._db.query(sql, ...params));\n const summary: Record<string, AiUsageStats> = {};\n\n for (const row of rows) {\n const bucket = String(row.bucket);\n summary[bucket] = {\n callCount: toSafeInteger(row.call_count ?? 0, 'AI usage call count'),\n promptTokens: toSafeInteger(\n row.prompt_tokens ?? 0,\n 'AI usage prompt-token total',\n ),\n completionTokens: toSafeInteger(\n row.completion_tokens ?? 0,\n 'AI usage completion-token total',\n ),\n totalTokens: toSafeInteger(\n row.total_tokens ?? 0,\n 'AI usage token total',\n ),\n totalDuration: toSafeInteger(\n row.total_duration ?? 0,\n 'AI usage duration total',\n ),\n estimatedCost: Number(row.estimated_cost ?? 0),\n lastUsed: row.last_used ? new Date(String(row.last_used)).getTime() : 0,\n };\n }\n\n return summary;\n }\n\n /**\n * Gets the signal bus instance\n *\n * @returns Signal bus if signals are enabled, undefined otherwise\n */\n get signalBus(): SignalBus | undefined {\n return this._signalBus;\n }\n\n /**\n * Cleanup method to prevent memory leaks\n *\n * Unregisters all adapters from the signal bus that were registered\n * by this instance. Call this when the SmrtClass instance is no longer\n * needed to prevent memory leaks.\n *\n * @example\n * ```typescript\n * const product = new Product({ name: 'Widget' });\n * await product.initialize();\n * // ... use product ...\n * product.destroy(); // Clean up when done\n * ```\n */\n destroy(): void {\n // Only unregister adapters if we own the bus (not shared)\n if (this._signalBus && !this.options.signals?.bus) {\n for (const adapter of this._registeredAdapters) {\n this._signalBus.unregister(adapter);\n }\n this._registeredAdapters = [];\n }\n\n // TODO: If SmrtClass grows a broader async teardown lifecycle, move\n // AI usage handler cleanup there alongside other connection-bound state.\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n }\n\n private mergeAiUsageConfig(\n globalConfig: GlobalSignalConfig,\n ): ResolvedAiUsageConfig {\n const globalUsage = globalConfig.usage ?? {};\n const instanceUsage = this.options.usage ?? {};\n\n return {\n enabled: instanceUsage.enabled ?? globalUsage.enabled ?? true,\n persist: instanceUsage.persist ?? globalUsage.persist ?? true,\n estimateCosts:\n instanceUsage.estimateCosts ?? globalUsage.estimateCosts ?? true,\n costRates: {\n ...(globalUsage.costRates ?? {}),\n ...(instanceUsage.costRates ?? {}),\n },\n handlers: [\n ...(globalUsage.handlers ?? []),\n ...(instanceUsage.handlers ?? []),\n ],\n };\n }\n\n private initializeAiUsageHandlers(config: ResolvedAiUsageConfig): void {\n this._aiUsageCollector = undefined;\n this._aiUsageHandlers = [];\n\n if (!config.enabled) {\n return;\n }\n\n this._aiUsageCollector = new AiUsageCollector();\n this._aiUsageHandlers.push(this._aiUsageCollector);\n\n if (config.persist && this._db) {\n this._aiUsageHandlers.push(new AiUsagePersistenceHandler(this._db));\n }\n\n this._aiUsageHandlers.push(...config.handlers);\n }\n\n private async handleAiUsageCallback(\n event: unknown,\n aiConfig: Record<string, unknown>,\n usageConfig: ResolvedAiUsageConfig,\n ): Promise<void> {\n if (!usageConfig.enabled || this._aiUsageHandlers.length === 0) {\n return;\n }\n\n const normalizedEvent = this.normalizeAiUsageEvent(event, aiConfig);\n if (!normalizedEvent) {\n return;\n }\n\n if (usageConfig.estimateCosts) {\n normalizedEvent.estimatedCost = estimateAiUsageCost(\n normalizedEvent.provider,\n normalizedEvent.model,\n normalizedEvent.usage,\n usageConfig.costRates,\n );\n }\n\n const results = await Promise.allSettled(\n this._aiUsageHandlers.map((handler) => handler.handle(normalizedEvent)),\n );\n\n for (const result of results) {\n if (result.status === 'rejected') {\n logger.warn(\n `[smrt] AI usage handler failed for ${normalizedEvent.provider}:${normalizedEvent.model}: ${\n result.reason instanceof Error\n ? result.reason.message\n : String(result.reason)\n }`,\n );\n }\n }\n }\n\n private normalizeAiUsageEvent(\n event: unknown,\n aiConfig: Record<string, unknown>,\n ): SmrtAiUsageEvent | undefined {\n const raw = (event ?? {}) as Record<string, unknown>;\n const provider = firstString(\n raw.provider,\n raw.type,\n aiConfig.provider,\n aiConfig.type,\n );\n const model = firstString(\n raw.model,\n raw.defaultModel,\n aiConfig.model,\n aiConfig.defaultModel,\n );\n const operation =\n firstString(raw.operation, raw.kind, raw.method) ?? 'unknown';\n const usage =\n normalizeIncomingAiUsageTokens(raw.usage) ??\n normalizeIncomingAiUsageTokens(raw.tokenUsage) ??\n normalizeIncomingAiUsageTokens({\n promptTokens: raw.promptTokens,\n completionTokens: raw.completionTokens,\n totalTokens: raw.totalTokens,\n });\n\n if (!provider || !model) {\n return undefined;\n }\n\n const duration =\n firstNumber(raw.duration, raw.durationMs, raw.latency) ?? 0;\n const tenantId =\n 'tenantId' in this &&\n (this as { tenantId?: string | null }).tenantId !== undefined\n ? ((this as { tenantId?: string | null }).tenantId ?? null)\n : undefined;\n\n return {\n provider,\n model,\n operation,\n usage,\n duration,\n timestamp: normalizeAiUsageTimestamp(raw.timestamp),\n tags: normalizeAiUsageTags(raw.tags),\n className: this._className,\n tenantId,\n };\n }\n}\n"],"mappings":"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"}