@happyvertical/smrt-core 0.49.3 → 0.49.4

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 (156) hide show
  1. package/README.md +4 -0
  2. package/agents/change-feed.md +19 -0
  3. package/agents/collection-reads.md +21 -0
  4. package/agents/generators.md +5 -0
  5. package/agents/memory.md +23 -3
  6. package/agents/null-equal-indexes.md +88 -0
  7. package/agents/object-runtime.md +85 -0
  8. package/agents/schema-paths.md +7 -7
  9. package/dist/browser.js +2 -2
  10. package/dist/cascade.d.ts.map +1 -1
  11. package/dist/cascade.js +13 -10
  12. package/dist/cascade.js.map +1 -1
  13. package/dist/change-feed.d.ts +22 -0
  14. package/dist/change-feed.d.ts.map +1 -1
  15. package/dist/change-feed.js +147 -6
  16. package/dist/change-feed.js.map +1 -1
  17. package/dist/class.js +1 -1
  18. package/dist/collection-cache.d.ts +10 -1
  19. package/dist/collection-cache.d.ts.map +1 -1
  20. package/dist/collection-cache.js +65 -10
  21. package/dist/collection-cache.js.map +1 -1
  22. package/dist/collection.d.ts +69 -2
  23. package/dist/collection.d.ts.map +1 -1
  24. package/dist/collection.js +142 -9
  25. package/dist/collection.js.map +1 -1
  26. package/dist/consumer-plugin/artifact-publication.d.ts +27 -0
  27. package/dist/consumer-plugin/artifact-publication.d.ts.map +1 -0
  28. package/dist/consumer-plugin/artifact-publication.js +80 -0
  29. package/dist/consumer-plugin/artifact-publication.js.map +1 -0
  30. package/dist/consumer-plugin/index.d.ts.map +1 -1
  31. package/dist/consumer-plugin/index.js +37 -4
  32. package/dist/consumer-plugin/index.js.map +1 -1
  33. package/dist/db-errors.js +1 -1
  34. package/dist/db-errors.js.map +1 -1
  35. package/dist/embedded-write-queue.d.ts +1 -1
  36. package/dist/embedded-write-queue.d.ts.map +1 -1
  37. package/dist/embedded-write-queue.js +16 -6
  38. package/dist/embedded-write-queue.js.map +1 -1
  39. package/dist/embeddings/storage.d.ts +16 -0
  40. package/dist/embeddings/storage.d.ts.map +1 -1
  41. package/dist/embeddings/storage.js +40 -0
  42. package/dist/embeddings/storage.js.map +1 -1
  43. package/dist/index.d.ts +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -3
  46. package/dist/interceptors.d.ts +22 -0
  47. package/dist/interceptors.d.ts.map +1 -1
  48. package/dist/interceptors.js +12 -0
  49. package/dist/interceptors.js.map +1 -1
  50. package/dist/junction.d.ts +12 -3
  51. package/dist/junction.d.ts.map +1 -1
  52. package/dist/junction.js +34 -4
  53. package/dist/junction.js.map +1 -1
  54. package/dist/knowledge-config.d.ts +6 -0
  55. package/dist/knowledge-config.d.ts.map +1 -0
  56. package/dist/knowledge-config.js +48 -0
  57. package/dist/knowledge-config.js.map +1 -0
  58. package/dist/manifest/static-manifest.d.ts.map +1 -1
  59. package/dist/manifest/static-manifest.js +91 -1
  60. package/dist/manifest/static-manifest.js.map +1 -1
  61. package/dist/manifest/store.js +1 -1
  62. package/dist/manifest/store.js.map +1 -1
  63. package/dist/manifest.json +104 -1
  64. package/dist/migrations/differ.d.ts.map +1 -1
  65. package/dist/migrations/differ.js +2 -0
  66. package/dist/migrations/differ.js.map +1 -1
  67. package/dist/migrations/generator.d.ts.map +1 -1
  68. package/dist/migrations/generator.js +2 -0
  69. package/dist/migrations/generator.js.map +1 -1
  70. package/dist/migrations/index.d.ts +1 -0
  71. package/dist/migrations/index.d.ts.map +1 -1
  72. package/dist/migrations/index.js +2 -1
  73. package/dist/migrations/null-equal-indexes.d.ts +37 -0
  74. package/dist/migrations/null-equal-indexes.d.ts.map +1 -0
  75. package/dist/migrations/null-equal-indexes.js +133 -0
  76. package/dist/migrations/null-equal-indexes.js.map +1 -0
  77. package/dist/migrations/tracker.d.ts.map +1 -1
  78. package/dist/migrations/tracker.js +6 -0
  79. package/dist/migrations/tracker.js.map +1 -1
  80. package/dist/migrations.js +2 -1
  81. package/dist/object.d.ts +33 -0
  82. package/dist/object.d.ts.map +1 -1
  83. package/dist/object.js +316 -46
  84. package/dist/object.js.map +1 -1
  85. package/dist/plain-json.d.ts +6 -0
  86. package/dist/plain-json.d.ts.map +1 -0
  87. package/dist/plain-json.js +41 -0
  88. package/dist/plain-json.js.map +1 -0
  89. package/dist/postgres-permissions.d.ts.map +1 -1
  90. package/dist/postgres-permissions.js +6 -2
  91. package/dist/postgres-permissions.js.map +1 -1
  92. package/dist/prebuild/index.d.ts.map +1 -1
  93. package/dist/prebuild/index.js +3 -3
  94. package/dist/prebuild/index.js.map +1 -1
  95. package/dist/registry/class-registration.d.ts.map +1 -1
  96. package/dist/registry/class-registration.js +4 -1
  97. package/dist/registry/class-registration.js.map +1 -1
  98. package/dist/registry/schema-builder.d.ts.map +1 -1
  99. package/dist/registry/schema-builder.js +4 -3
  100. package/dist/registry/schema-builder.js.map +1 -1
  101. package/dist/registry/validator.d.ts +1 -0
  102. package/dist/registry/validator.d.ts.map +1 -1
  103. package/dist/registry/validator.js +8 -1
  104. package/dist/registry/validator.js.map +1 -1
  105. package/dist/registry.js +1 -1
  106. package/dist/scanner/manifest-generator.d.ts.map +1 -1
  107. package/dist/scanner/manifest-generator.js +21 -0
  108. package/dist/scanner/manifest-generator.js.map +1 -1
  109. package/dist/scanner/types.d.ts +2 -0
  110. package/dist/scanner/types.d.ts.map +1 -1
  111. package/dist/scanner/types.js.map +1 -1
  112. package/dist/schema/conflict-target.d.ts +8 -2
  113. package/dist/schema/conflict-target.d.ts.map +1 -1
  114. package/dist/schema/conflict-target.js +11 -3
  115. package/dist/schema/conflict-target.js.map +1 -1
  116. package/dist/schema/ddl/null-equal-index.d.ts +5 -0
  117. package/dist/schema/ddl/null-equal-index.d.ts.map +1 -0
  118. package/dist/schema/ddl/null-equal-index.js +17 -0
  119. package/dist/schema/ddl/null-equal-index.js.map +1 -0
  120. package/dist/schema/ddl/postgres-strategy.d.ts +2 -1
  121. package/dist/schema/ddl/postgres-strategy.d.ts.map +1 -1
  122. package/dist/schema/ddl/postgres-strategy.js +7 -0
  123. package/dist/schema/ddl/postgres-strategy.js.map +1 -1
  124. package/dist/schema/generator.d.ts.map +1 -1
  125. package/dist/schema/generator.js +8 -3
  126. package/dist/schema/generator.js.map +1 -1
  127. package/dist/schema/manifest-schema.d.ts +2 -1
  128. package/dist/schema/manifest-schema.d.ts.map +1 -1
  129. package/dist/schema/manifest-schema.js +12 -4
  130. package/dist/schema/manifest-schema.js.map +1 -1
  131. package/dist/schema/schema-manager.d.ts.map +1 -1
  132. package/dist/schema/schema-manager.js +1 -1
  133. package/dist/schema/schema-manager.js.map +1 -1
  134. package/dist/schema/types.d.ts +2 -0
  135. package/dist/schema/types.d.ts.map +1 -1
  136. package/dist/scripts/browser-plain-json-regression.mjs +85 -0
  137. package/dist/smrt-knowledge.json +73 -15
  138. package/dist/system/schema.d.ts +14 -3
  139. package/dist/system/schema.d.ts.map +1 -1
  140. package/dist/system/schema.js +120 -2
  141. package/dist/system/schema.js.map +1 -1
  142. package/dist/vite-plugin/api-client-entries.d.ts.map +1 -1
  143. package/dist/vite-plugin/api-client-entries.js +9 -7
  144. package/dist/vite-plugin/api-client-entries.js.map +1 -1
  145. package/dist/vite-plugin/index.d.ts +24 -0
  146. package/dist/vite-plugin/index.d.ts.map +1 -1
  147. package/dist/vite-plugin/index.js +47 -64
  148. package/dist/vite-plugin/index.js.map +1 -1
  149. package/dist/vite-plugin/sveltekit-generator.d.ts.map +1 -1
  150. package/dist/vite-plugin/sveltekit-generator.js +2 -2
  151. package/dist/vite-plugin/sveltekit-generator.js.map +1 -1
  152. package/dist/vite-plugin/web-collections.d.ts +10 -0
  153. package/dist/vite-plugin/web-collections.d.ts.map +1 -1
  154. package/dist/vite-plugin/web-collections.js +14 -2
  155. package/dist/vite-plugin/web-collections.js.map +1 -1
  156. package/package.json +5 -4
@@ -14,8 +14,11 @@ import { createLogger } from "@happyvertical/logger";
14
14
  * - SMRT owns every mutation path (`save()`/`delete()` back
15
15
  * `collection.create()`, `getOrUpsert()`, junction attach/detach), so all
16
16
  * writes invalidate the affected table's entries in-process automatically.
17
- * - Entries are scoped per database identity (`db.url`) and per table, so
18
- * multi-DB processes and STI siblings (which share a table) stay coherent.
17
+ * - Entries and flights are partitioned by the concrete DatabaseInterface
18
+ * object, final SQL and parameters. Distinct transaction/connection handles
19
+ * never share rows or failures, even when their database URL is identical.
20
+ * - Invalidation remains URL/table scoped, so a write clears every handle's
21
+ * entries and STI siblings (which share a table) stay coherent.
19
22
  * - Caches are per-process. With multiple replicas, a local invalidation
20
23
  * leaves peers stale until TTL unless cross-process invalidation is opted
21
24
  * into (`crossProcess: true`), which broadcasts over the database
@@ -67,6 +70,51 @@ var PROCESS_ID = crypto.randomUUID();
67
70
  */
68
71
  var store = /* @__PURE__ */ new Map();
69
72
  /**
73
+ * Reads currently populating a cache entry, scoped the same way as cached
74
+ * rows. A shared promise prevents concurrent identical cache misses from
75
+ * exhausting the connection pool with duplicate SELECTs.
76
+ */
77
+ var inFlightReads = /* @__PURE__ */ new Map();
78
+ /**
79
+ * Share an in-progress cache miss with callers for the same database, table,
80
+ * and final query key. Rejected reads are removed too, so a later caller can
81
+ * retry rather than inheriting a permanently failed promise.
82
+ */
83
+ function getOrCreateInFlightRead(dbKey, tableName, queryKey, generation, read) {
84
+ let tables = inFlightReads.get(dbKey);
85
+ if (!tables) {
86
+ tables = /* @__PURE__ */ new Map();
87
+ inFlightReads.set(dbKey, tables);
88
+ }
89
+ let entries = tables.get(tableName);
90
+ if (!entries) {
91
+ entries = /* @__PURE__ */ new Map();
92
+ tables.set(tableName, entries);
93
+ }
94
+ let generations = entries.get(queryKey);
95
+ if (!generations) {
96
+ generations = /* @__PURE__ */ new Map();
97
+ entries.set(queryKey, generations);
98
+ }
99
+ const existing = generations.get(generation);
100
+ if (existing) return existing;
101
+ let inFlight;
102
+ inFlight = read().finally(() => {
103
+ if (generations.get(generation) === inFlight) {
104
+ generations.delete(generation);
105
+ if (generations.size === 0 && entries.get(queryKey) === generations) {
106
+ entries.delete(queryKey);
107
+ if (entries.size === 0 && tables.get(tableName) === entries) {
108
+ tables.delete(tableName);
109
+ if (tables.size === 0 && inFlightReads.get(dbKey) === tables) inFlightReads.delete(dbKey);
110
+ }
111
+ }
112
+ }
113
+ });
114
+ generations.set(generation, inFlight);
115
+ return inFlight;
116
+ }
117
+ /**
70
118
  * Monotonic invalidation generation per `dbKey\0tableName`, bumped on every
71
119
  * invalidation. A read captures the generation *before* its DB round-trip and
72
120
  * passes it to `setCachedRows`; if an invalidating write landed during the
@@ -87,10 +135,10 @@ function getCacheGeneration(dbKey, tableName) {
87
135
  return generations.get(generationKey(dbKey, tableName)) ?? 0;
88
136
  }
89
137
  /**
90
- * Fallback identities for database instances that expose no URL
91
- * (each such instance gets its own scope, never shared).
138
+ * Concrete executor identities. URL equality does not imply transaction or
139
+ * snapshot equality; each public database-interface object has its own scope.
92
140
  */
93
- var fallbackDbKeys = /* @__PURE__ */ new WeakMap();
141
+ var dbInstanceKeys = /* @__PURE__ */ new WeakMap();
94
142
  /**
95
143
  * Resolve a stable cache scope for a database instance.
96
144
  *
@@ -104,10 +152,13 @@ function resolveDbCacheKey(db) {
104
152
  const dbWithConfig = db;
105
153
  const url = db.url || dbWithConfig.config?.url;
106
154
  if (url && url !== ":memory:") return url;
107
- let key = fallbackDbKeys.get(db);
155
+ return resolveDbInstanceKey(db);
156
+ }
157
+ function resolveDbInstanceKey(db) {
158
+ let key = dbInstanceKeys.get(db);
108
159
  if (!key) {
109
160
  key = `smrt-db:${crypto.randomUUID()}`;
110
- fallbackDbKeys.set(db, key);
161
+ dbInstanceKeys.set(db, key);
111
162
  }
112
163
  return key;
113
164
  }
@@ -115,9 +166,12 @@ function resolveDbCacheKey(db) {
115
166
  * Build the cache key for a query. The final SQL and bound parameters fully
116
167
  * normalize the query shape — they already include STI discriminator
117
168
  * filters, interceptor-injected tenant filters, ORDER BY, LIMIT and OFFSET.
169
+ * Collection reads also supply their concrete executor so neither pending
170
+ * reads nor completed rows cross connection/transaction boundaries. The
171
+ * surrounding URL/table scope remains shared for invalidation generations.
118
172
  */
119
- function buildQueryCacheKey(sql, params) {
120
- return `${sql}\0${JSON.stringify(params)}`;
173
+ function buildQueryCacheKey(sql, params, db) {
174
+ return `${db ? `${resolveDbInstanceKey(db)}\0` : ""}${sql}\0${JSON.stringify(params)}`;
121
175
  }
122
176
  /**
123
177
  * Read cached rows for a query, or undefined on miss/expiry.
@@ -180,6 +234,7 @@ function invalidateCollectionCache(dbKey, tableName) {
180
234
  */
181
235
  function resetCollectionCache() {
182
236
  store.clear();
237
+ inFlightReads.clear();
183
238
  generations.clear();
184
239
  crossProcessInterest.clear();
185
240
  stopCacheInvalidationListeners();
@@ -308,6 +363,6 @@ function safeParse(value) {
308
363
  }
309
364
  }
310
365
  //#endregion
311
- export { CACHE_INVALIDATION_CHANNEL, PROCESS_ID, broadcastCacheInvalidation, buildQueryCacheKey, ensureCacheInvalidationListener, getCacheGeneration, getCachedRows, getNotifications, hasCrossProcessCacheInterest, invalidateCollectionCache, registerCrossProcessCacheInterest, resetCollectionCache, resolveDbCacheKey, setCachedRows, stopCacheInvalidationListeners };
366
+ export { CACHE_INVALIDATION_CHANNEL, PROCESS_ID, broadcastCacheInvalidation, buildQueryCacheKey, ensureCacheInvalidationListener, getCacheGeneration, getCachedRows, getNotifications, getOrCreateInFlightRead, hasCrossProcessCacheInterest, invalidateCollectionCache, registerCrossProcessCacheInterest, resetCollectionCache, resolveDbCacheKey, setCachedRows, stopCacheInvalidationListeners };
312
367
 
313
368
  //# sourceMappingURL=collection-cache.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection-cache.js","names":[],"sources":["../src/collection-cache.ts"],"sourcesContent":["/**\n * Opt-in read-through cache for collection reads (issue #1498).\n *\n * SSR-rendered SMRT apps re-query read-heavy / write-rare collections on\n * every request, and the per-query round-trip dominates wall time when a\n * page needs many collections. This module memoizes `list()`/`get()` row\n * sets keyed by the final SQL + parameters, for an opt-in TTL.\n *\n * Correctness model:\n * - Caching is OFF by default. It is enabled per call\n * (`list({ cache: { ttl } })`) or per model (`@smrt({ cache: { ttl } })`).\n * - SMRT owns every mutation path (`save()`/`delete()` back\n * `collection.create()`, `getOrUpsert()`, junction attach/detach), so all\n * writes invalidate the affected table's entries in-process automatically.\n * - Entries are scoped per database identity (`db.url`) and per table, so\n * multi-DB processes and STI siblings (which share a table) stay coherent.\n * - Caches are per-process. With multiple replicas, a local invalidation\n * leaves peers stale until TTL unless cross-process invalidation is opted\n * into (`crossProcess: true`), which broadcasts over the database\n * adapter's notification capability (e.g. Postgres LISTEN/NOTIFY) when\n * the adapter provides one.\n *\n * Cached values are raw result rows, not hydrated instances — hydration and\n * read interceptors (tenancy, audit) run on every call, cached or not, and\n * each caller receives isolated row copies.\n *\n * Known limitations:\n * - Invalidation fires when a mutation's SQL executes, not when its\n * surrounding transaction commits. A write inside an uncommitted\n * transaction invalidates (and may broadcast) immediately; a concurrent\n * reader could repopulate the cache from the pre-commit snapshot, and a\n * rollback leaves the cache invalidated for a write that never landed.\n * Caching targets read-heavy / write-rare data where this is rare; for\n * models mutated inside multi-statement transactions, coherence is still\n * bounded by TTL. Per-call `cache: false` forces a fresh read where it\n * matters.\n * - Writes that bypass the framework's mutation paths (raw `db.query`\n * issued outside `collection.query()`, external processes without\n * `crossProcess`) are only bounded by TTL.\n *\n * @see https://github.com/happyvertical/smrt/issues/1498\n * @packageDocumentation\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Read-through cache configuration for collection reads.\n *\n * Used both per call (`collection.list({ cache: { ttl: 60_000 } })`) and per\n * model (`@smrt({ cache: { ttl: 60_000 } })`).\n */\nexport interface CollectionCacheConfig {\n /**\n * Time-to-live for cached query results, in milliseconds. Must be > 0.\n */\n ttl: number;\n\n /**\n * Broadcast invalidations to peer processes through the database\n * adapter's notification capability (`db.notifications`, e.g. Postgres\n * LISTEN/NOTIFY). Without this, peer replicas serve stale rows until TTL.\n *\n * Model-level config (`@smrt({ cache })`) is the reliable opt-in: every\n * process that writes the model knows to broadcast. As a per-call option,\n * writes broadcast only from processes that have already performed a\n * `crossProcess` cached read of the same table (typical for homogeneous\n * replicas running the same routes); a process that only writes never\n * learns about the per-call opt-in, so its peers fall back to TTL expiry.\n *\n * Ignored (with a one-time warning) when the adapter does not expose\n * notifications.\n */\n crossProcess?: boolean;\n}\n\ninterface CacheEntry {\n expiresAt: number;\n rows: Record<string, unknown>[];\n}\n\n/**\n * Per-table entry cap. A table accumulating more distinct query shapes than\n * this within one TTL window evicts its oldest entries (insertion order).\n * Guards against unbounded growth from high-cardinality WHERE values.\n */\nconst MAX_ENTRIES_PER_TABLE = 500;\n\n/**\n * Notification channel used for cross-process invalidation broadcasts.\n */\nexport const CACHE_INVALIDATION_CHANNEL = 'smrt_collection_cache';\n\n/**\n * Identifies this process in broadcast payloads so a replica can skip\n * notifications it published itself (it already invalidated locally).\n *\n * Exported so sibling cross-replica buses (the #1763 change-signal bus) share\n * one per-process identity, keeping echo-avoidance consistent across channels.\n */\nexport const PROCESS_ID = crypto.randomUUID();\n\n/**\n * dbKey → tableName → (queryKey → entry)\n */\nconst store = new Map<string, Map<string, Map<string, CacheEntry>>>();\n\n/**\n * Monotonic invalidation generation per `dbKey\\0tableName`, bumped on every\n * invalidation. A read captures the generation *before* its DB round-trip and\n * passes it to `setCachedRows`; if an invalidating write landed during the\n * round-trip, the generation no longer matches and the (now-stale) result is\n * dropped instead of cached. This closes the read-miss/concurrent-write race\n * where an in-flight SELECT would otherwise repopulate the cache with\n * pre-write rows for the full TTL.\n */\nconst generations = new Map<string, number>();\n\nfunction generationKey(dbKey: string, tableName: string): string {\n return `${dbKey}\\0${tableName}`;\n}\n\n/**\n * Current invalidation generation for a table (0 if never invalidated).\n * Capture this before a DB read and pass it to `setCachedRows`.\n */\nexport function getCacheGeneration(dbKey: string, tableName: string): number {\n return generations.get(generationKey(dbKey, tableName)) ?? 0;\n}\n\n/**\n * Fallback identities for database instances that expose no URL\n * (each such instance gets its own scope, never shared).\n */\nconst fallbackDbKeys = new WeakMap<object, string>();\n\n/**\n * Resolve a stable cache scope for a database instance.\n *\n * Mirrors table-verifier's identity resolution (`db.url || config.url`).\n * `:memory:` databases share that URL string while being entirely separate\n * databases, so they (and URL-less instances) are scoped per instance —\n * serving one in-memory database's rows for another would be a correctness\n * bug, not just a stale read.\n */\nexport function resolveDbCacheKey(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseInterface & {\n config?: { url?: string };\n };\n const url = db.url || dbWithConfig.config?.url;\n if (url && url !== ':memory:') return url;\n\n let key = fallbackDbKeys.get(db);\n if (!key) {\n key = `smrt-db:${crypto.randomUUID()}`;\n fallbackDbKeys.set(db, key);\n }\n return key;\n}\n\n/**\n * Build the cache key for a query. The final SQL and bound parameters fully\n * normalize the query shape — they already include STI discriminator\n * filters, interceptor-injected tenant filters, ORDER BY, LIMIT and OFFSET.\n */\nexport function buildQueryCacheKey(sql: string, params: unknown[]): string {\n return `${sql}\\0${JSON.stringify(params)}`;\n}\n\n/**\n * Read cached rows for a query, or undefined on miss/expiry.\n *\n * Returns a structured clone so callers can never mutate the cached copy\n * (hydration writes into row objects).\n */\nexport function getCachedRows(\n dbKey: string,\n tableName: string,\n queryKey: string,\n): Record<string, unknown>[] | undefined {\n const entry = store.get(dbKey)?.get(tableName)?.get(queryKey);\n if (!entry) return undefined;\n if (entry.expiresAt <= Date.now()) {\n store.get(dbKey)?.get(tableName)?.delete(queryKey);\n return undefined;\n }\n return structuredClone(entry.rows);\n}\n\n/**\n * Store rows for a query under the table's cache scope.\n *\n * `expectedGeneration` is the value {@link getCacheGeneration} returned before\n * the DB read. If an invalidation bumped the table's generation while the read\n * was in flight, the result is stale and is dropped rather than cached.\n */\nexport function setCachedRows(\n dbKey: string,\n tableName: string,\n queryKey: string,\n rows: Record<string, unknown>[],\n ttl: number,\n expectedGeneration?: number,\n): void {\n if (!(ttl > 0)) return;\n\n // Drop a result that a concurrent write invalidated mid-flight.\n if (\n expectedGeneration !== undefined &&\n getCacheGeneration(dbKey, tableName) !== expectedGeneration\n ) {\n return;\n }\n\n let tables = store.get(dbKey);\n if (!tables) {\n tables = new Map();\n store.set(dbKey, tables);\n }\n let entries = tables.get(tableName);\n if (!entries) {\n entries = new Map();\n tables.set(tableName, entries);\n }\n\n // Evict oldest entries (Map preserves insertion order) at the cap. Skip when\n // refreshing an existing key — `set` updates it in place without growing the\n // map, so evicting first would drop a live entry and shrink the effective cap.\n if (!entries.has(queryKey)) {\n while (entries.size >= MAX_ENTRIES_PER_TABLE) {\n const oldest = entries.keys().next().value;\n if (oldest === undefined) break;\n entries.delete(oldest);\n }\n }\n\n entries.set(queryKey, {\n expiresAt: Date.now() + ttl,\n rows: structuredClone(rows),\n });\n}\n\n/**\n * Drop every cached entry for a table. Called by the framework after any\n * successful mutation against that table — this is the write-invalidation\n * guarantee an app-side cache can't make.\n */\nexport function invalidateCollectionCache(\n dbKey: string,\n tableName: string,\n): void {\n store.get(dbKey)?.delete(tableName);\n // Bump the generation so any read whose DB round-trip is still in flight\n // discards its (now-stale) result instead of repopulating the table.\n const gkey = generationKey(dbKey, tableName);\n generations.set(gkey, (generations.get(gkey) ?? 0) + 1);\n}\n\n/**\n * Clear all cached collection reads and stop cross-process listeners.\n * Call in test setup to ensure isolation between test files.\n */\nexport function resetCollectionCache(): void {\n store.clear();\n generations.clear();\n crossProcessInterest.clear();\n stopCacheInvalidationListeners();\n}\n\n// ============================================================================\n// Per-call crossProcess interest (write-side broadcast decision)\n// ============================================================================\n\n/**\n * dbKey → tables this process has cached with a per-call `crossProcess`\n * opt-in. Writes consult this so per-call usage broadcasts too — model-level\n * config can't be the only trigger when the opt-in lives at the call site.\n */\nconst crossProcessInterest = new Map<string, Set<string>>();\n\n/**\n * Record that a per-call `crossProcess` cached read happened for a table.\n */\nexport function registerCrossProcessCacheInterest(\n dbKey: string,\n tableName: string,\n): void {\n let tables = crossProcessInterest.get(dbKey);\n if (!tables) {\n tables = new Set();\n crossProcessInterest.set(dbKey, tables);\n }\n tables.add(tableName);\n}\n\n/**\n * Whether any per-call `crossProcess` cached read has touched this table.\n */\nexport function hasCrossProcessCacheInterest(\n dbKey: string,\n tableName: string,\n): boolean {\n return crossProcessInterest.get(dbKey)?.has(tableName) ?? false;\n}\n\n// ============================================================================\n// Cross-process invalidation (opt-in, via db.notifications)\n// ============================================================================\n\ninterface ListenerHandle {\n iterator: AsyncIterator<unknown> | null;\n stopped: boolean;\n}\n\nconst listeners = new Map<string, ListenerHandle>();\nconst warnedNoNotifications = new Set<string>();\n\n/**\n * The database adapter's optional notification capability (e.g. Postgres\n * LISTEN/NOTIFY), or `undefined` when the adapter exposes none. Duck-typed so\n * core never depends on a concrete adapter shape. Exported so sibling\n * cross-replica buses (the #1763 change-signal bus) resolve it identically.\n */\nexport function getNotifications(db: DatabaseInterface) {\n return (db as DatabaseInterface & { notifications?: unknown })\n .notifications as\n | {\n notify(channel: string, payload: unknown): Promise<number>;\n listen(\n channel: string,\n options?: Record<string, unknown>,\n ): AsyncIterable<{ channel: string; payload: unknown }>;\n }\n | undefined;\n}\n\nfunction warnOnceNoNotifications(dbKey: string, context: string): void {\n if (warnedNoNotifications.has(dbKey)) return;\n warnedNoNotifications.add(dbKey);\n logger.warn(\n `Collection cache: crossProcess invalidation requested but the database ` +\n `adapter exposes no notification capability (${context}). Peer ` +\n `replicas will serve stale rows until TTL.`,\n );\n}\n\n/**\n * Broadcast a table invalidation to peer processes.\n *\n * Fire-and-forget from mutation paths: a broadcast failure must never fail\n * the write that triggered it.\n */\nexport async function broadcastCacheInvalidation(\n db: DatabaseInterface,\n tableName: string,\n): Promise<void> {\n const notifications = getNotifications(db);\n const dbKey = resolveDbCacheKey(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey, 'broadcast');\n return;\n }\n try {\n await notifications.notify(CACHE_INVALIDATION_CHANNEL, {\n table: tableName,\n source: PROCESS_ID,\n });\n } catch (error) {\n logger.warn(\n `Collection cache: failed to broadcast invalidation for ${tableName}`,\n { error: error instanceof Error ? error.message : error },\n );\n }\n}\n\n/**\n * Ensure a background listener consumes invalidation broadcasts for this\n * database and drops matching local cache entries.\n *\n * Started lazily by the first cached read that opted into `crossProcess`.\n * Notifications published by this process are skipped — the local\n * invalidation already happened synchronously on the write path.\n */\nexport function ensureCacheInvalidationListener(db: DatabaseInterface): void {\n const dbKey = resolveDbCacheKey(db);\n if (listeners.has(dbKey)) return;\n\n const notifications = getNotifications(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey, 'listen');\n return;\n }\n\n const handle: ListenerHandle = { iterator: null, stopped: false };\n listeners.set(dbKey, handle);\n\n void (async () => {\n try {\n const iterable = notifications.listen(CACHE_INVALIDATION_CHANNEL);\n const iterator = iterable[Symbol.asyncIterator]();\n handle.iterator = iterator;\n\n while (!handle.stopped) {\n const { value, done } = await iterator.next();\n if (done || handle.stopped) break;\n\n const notification = value as { payload?: unknown };\n const payload: unknown =\n typeof notification?.payload === 'string'\n ? safeParse(notification.payload)\n : notification?.payload;\n\n if (!payload || typeof payload !== 'object') continue;\n const record = payload as { table?: unknown; source?: unknown };\n if (typeof record.table !== 'string') continue;\n if (record.source === PROCESS_ID) continue;\n\n invalidateCollectionCache(dbKey, record.table);\n }\n } catch (error) {\n if (!handle.stopped) {\n logger.warn(\n 'Collection cache: invalidation listener terminated unexpectedly; ' +\n 'cross-process invalidation is inactive for this database ' +\n '(local TTL still bounds staleness)',\n { error: error instanceof Error ? error.message : error },\n );\n }\n } finally {\n // Only retract our own handle. A concurrent stop+restart for the same\n // dbKey may have already installed a replacement; deleting unconditionally\n // would orphan it and leak/duplicate the live listener.\n if (listeners.get(dbKey) === handle) {\n listeners.delete(dbKey);\n }\n }\n })();\n}\n\n/**\n * Stop all cross-process invalidation listeners. Used by tests and during\n * shutdown; safe to call when none are active.\n */\nexport function stopCacheInvalidationListeners(): void {\n for (const handle of listeners.values()) {\n handle.stopped = true;\n void handle.iterator?.return?.(undefined);\n }\n listeners.clear();\n warnedNoNotifications.clear();\n}\n\nfunction safeParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"collection-cache.js","names":[],"sources":["../src/collection-cache.ts"],"sourcesContent":["/**\n * Opt-in read-through cache for collection reads (issue #1498).\n *\n * SSR-rendered SMRT apps re-query read-heavy / write-rare collections on\n * every request, and the per-query round-trip dominates wall time when a\n * page needs many collections. This module memoizes `list()`/`get()` row\n * sets keyed by the final SQL + parameters, for an opt-in TTL.\n *\n * Correctness model:\n * - Caching is OFF by default. It is enabled per call\n * (`list({ cache: { ttl } })`) or per model (`@smrt({ cache: { ttl } })`).\n * - SMRT owns every mutation path (`save()`/`delete()` back\n * `collection.create()`, `getOrUpsert()`, junction attach/detach), so all\n * writes invalidate the affected table's entries in-process automatically.\n * - Entries and flights are partitioned by the concrete DatabaseInterface\n * object, final SQL and parameters. Distinct transaction/connection handles\n * never share rows or failures, even when their database URL is identical.\n * - Invalidation remains URL/table scoped, so a write clears every handle's\n * entries and STI siblings (which share a table) stay coherent.\n * - Caches are per-process. With multiple replicas, a local invalidation\n * leaves peers stale until TTL unless cross-process invalidation is opted\n * into (`crossProcess: true`), which broadcasts over the database\n * adapter's notification capability (e.g. Postgres LISTEN/NOTIFY) when\n * the adapter provides one.\n *\n * Cached values are raw result rows, not hydrated instances — hydration and\n * read interceptors (tenancy, audit) run on every call, cached or not, and\n * each caller receives isolated row copies.\n *\n * Known limitations:\n * - Invalidation fires when a mutation's SQL executes, not when its\n * surrounding transaction commits. A write inside an uncommitted\n * transaction invalidates (and may broadcast) immediately; a concurrent\n * reader could repopulate the cache from the pre-commit snapshot, and a\n * rollback leaves the cache invalidated for a write that never landed.\n * Caching targets read-heavy / write-rare data where this is rare; for\n * models mutated inside multi-statement transactions, coherence is still\n * bounded by TTL. Per-call `cache: false` forces a fresh read where it\n * matters.\n * - Writes that bypass the framework's mutation paths (raw `db.query`\n * issued outside `collection.query()`, external processes without\n * `crossProcess`) are only bounded by TTL.\n *\n * @see https://github.com/happyvertical/smrt/issues/1498\n * @packageDocumentation\n */\n\nimport { createLogger } from '@happyvertical/logger';\nimport type { DatabaseInterface } from '@happyvertical/sql';\n\nconst logger = createLogger({ level: 'info' });\n\n/**\n * Read-through cache configuration for collection reads.\n *\n * Used both per call (`collection.list({ cache: { ttl: 60_000 } })`) and per\n * model (`@smrt({ cache: { ttl: 60_000 } })`).\n */\nexport interface CollectionCacheConfig {\n /**\n * Time-to-live for cached query results, in milliseconds. Must be > 0.\n */\n ttl: number;\n\n /**\n * Broadcast invalidations to peer processes through the database\n * adapter's notification capability (`db.notifications`, e.g. Postgres\n * LISTEN/NOTIFY). Without this, peer replicas serve stale rows until TTL.\n *\n * Model-level config (`@smrt({ cache })`) is the reliable opt-in: every\n * process that writes the model knows to broadcast. As a per-call option,\n * writes broadcast only from processes that have already performed a\n * `crossProcess` cached read of the same table (typical for homogeneous\n * replicas running the same routes); a process that only writes never\n * learns about the per-call opt-in, so its peers fall back to TTL expiry.\n *\n * Ignored (with a one-time warning) when the adapter does not expose\n * notifications.\n */\n crossProcess?: boolean;\n}\n\ninterface CacheEntry {\n expiresAt: number;\n rows: Record<string, unknown>[];\n}\n\n/**\n * Per-table entry cap. A table accumulating more distinct query shapes than\n * this within one TTL window evicts its oldest entries (insertion order).\n * Guards against unbounded growth from high-cardinality WHERE values.\n */\nconst MAX_ENTRIES_PER_TABLE = 500;\n\n/**\n * Notification channel used for cross-process invalidation broadcasts.\n */\nexport const CACHE_INVALIDATION_CHANNEL = 'smrt_collection_cache';\n\n/**\n * Identifies this process in broadcast payloads so a replica can skip\n * notifications it published itself (it already invalidated locally).\n *\n * Exported so sibling cross-replica buses (the #1763 change-signal bus) share\n * one per-process identity, keeping echo-avoidance consistent across channels.\n */\nexport const PROCESS_ID = crypto.randomUUID();\n\n/**\n * dbKey → tableName → (queryKey → entry)\n */\nconst store = new Map<string, Map<string, Map<string, CacheEntry>>>();\n\n/**\n * Reads currently populating a cache entry, scoped the same way as cached\n * rows. A shared promise prevents concurrent identical cache misses from\n * exhausting the connection pool with duplicate SELECTs.\n */\nconst inFlightReads = new Map<\n string,\n Map<string, Map<string, Map<number, Promise<Record<string, unknown>[]>>>>\n>();\n\n/**\n * Share an in-progress cache miss with callers for the same database, table,\n * and final query key. Rejected reads are removed too, so a later caller can\n * retry rather than inheriting a permanently failed promise.\n */\nexport function getOrCreateInFlightRead(\n dbKey: string,\n tableName: string,\n queryKey: string,\n generation: number,\n read: () => Promise<Record<string, unknown>[]>,\n): Promise<Record<string, unknown>[]> {\n let tables = inFlightReads.get(dbKey);\n if (!tables) {\n tables = new Map();\n inFlightReads.set(dbKey, tables);\n }\n let entries = tables.get(tableName);\n if (!entries) {\n entries = new Map();\n tables.set(tableName, entries);\n }\n\n let generations = entries.get(queryKey);\n if (!generations) {\n generations = new Map();\n entries.set(queryKey, generations);\n }\n const existing = generations.get(generation);\n if (existing) return existing;\n\n let inFlight: Promise<Record<string, unknown>[]>;\n inFlight = read().finally(() => {\n if (generations.get(generation) === inFlight) {\n generations.delete(generation);\n if (generations.size === 0 && entries.get(queryKey) === generations) {\n entries.delete(queryKey);\n if (entries.size === 0 && tables.get(tableName) === entries) {\n tables.delete(tableName);\n if (tables.size === 0 && inFlightReads.get(dbKey) === tables) {\n inFlightReads.delete(dbKey);\n }\n }\n }\n }\n });\n generations.set(generation, inFlight);\n return inFlight;\n}\n\n/**\n * Monotonic invalidation generation per `dbKey\\0tableName`, bumped on every\n * invalidation. A read captures the generation *before* its DB round-trip and\n * passes it to `setCachedRows`; if an invalidating write landed during the\n * round-trip, the generation no longer matches and the (now-stale) result is\n * dropped instead of cached. This closes the read-miss/concurrent-write race\n * where an in-flight SELECT would otherwise repopulate the cache with\n * pre-write rows for the full TTL.\n */\nconst generations = new Map<string, number>();\n\nfunction generationKey(dbKey: string, tableName: string): string {\n return `${dbKey}\\0${tableName}`;\n}\n\n/**\n * Current invalidation generation for a table (0 if never invalidated).\n * Capture this before a DB read and pass it to `setCachedRows`.\n */\nexport function getCacheGeneration(dbKey: string, tableName: string): number {\n return generations.get(generationKey(dbKey, tableName)) ?? 0;\n}\n\n/**\n * Concrete executor identities. URL equality does not imply transaction or\n * snapshot equality; each public database-interface object has its own scope.\n */\nconst dbInstanceKeys = new WeakMap<object, string>();\n\n/**\n * Resolve a stable cache scope for a database instance.\n *\n * Mirrors table-verifier's identity resolution (`db.url || config.url`).\n * `:memory:` databases share that URL string while being entirely separate\n * databases, so they (and URL-less instances) are scoped per instance —\n * serving one in-memory database's rows for another would be a correctness\n * bug, not just a stale read.\n */\nexport function resolveDbCacheKey(db: DatabaseInterface): string {\n const dbWithConfig = db as DatabaseInterface & {\n config?: { url?: string };\n };\n const url = db.url || dbWithConfig.config?.url;\n if (url && url !== ':memory:') return url;\n\n return resolveDbInstanceKey(db);\n}\n\nfunction resolveDbInstanceKey(db: DatabaseInterface): string {\n let key = dbInstanceKeys.get(db);\n if (!key) {\n key = `smrt-db:${crypto.randomUUID()}`;\n dbInstanceKeys.set(db, key);\n }\n return key;\n}\n\n/**\n * Build the cache key for a query. The final SQL and bound parameters fully\n * normalize the query shape — they already include STI discriminator\n * filters, interceptor-injected tenant filters, ORDER BY, LIMIT and OFFSET.\n * Collection reads also supply their concrete executor so neither pending\n * reads nor completed rows cross connection/transaction boundaries. The\n * surrounding URL/table scope remains shared for invalidation generations.\n */\nexport function buildQueryCacheKey(\n sql: string,\n params: unknown[],\n db?: DatabaseInterface,\n): string {\n const executor = db ? `${resolveDbInstanceKey(db)}\\0` : '';\n return `${executor}${sql}\\0${JSON.stringify(params)}`;\n}\n\n/**\n * Read cached rows for a query, or undefined on miss/expiry.\n *\n * Returns a structured clone so callers can never mutate the cached copy\n * (hydration writes into row objects).\n */\nexport function getCachedRows(\n dbKey: string,\n tableName: string,\n queryKey: string,\n): Record<string, unknown>[] | undefined {\n const entry = store.get(dbKey)?.get(tableName)?.get(queryKey);\n if (!entry) return undefined;\n if (entry.expiresAt <= Date.now()) {\n store.get(dbKey)?.get(tableName)?.delete(queryKey);\n return undefined;\n }\n return structuredClone(entry.rows);\n}\n\n/**\n * Store rows for a query under the table's cache scope.\n *\n * `expectedGeneration` is the value {@link getCacheGeneration} returned before\n * the DB read. If an invalidation bumped the table's generation while the read\n * was in flight, the result is stale and is dropped rather than cached.\n */\nexport function setCachedRows(\n dbKey: string,\n tableName: string,\n queryKey: string,\n rows: Record<string, unknown>[],\n ttl: number,\n expectedGeneration?: number,\n): void {\n if (!(ttl > 0)) return;\n\n // Drop a result that a concurrent write invalidated mid-flight.\n if (\n expectedGeneration !== undefined &&\n getCacheGeneration(dbKey, tableName) !== expectedGeneration\n ) {\n return;\n }\n\n let tables = store.get(dbKey);\n if (!tables) {\n tables = new Map();\n store.set(dbKey, tables);\n }\n let entries = tables.get(tableName);\n if (!entries) {\n entries = new Map();\n tables.set(tableName, entries);\n }\n\n // Evict oldest entries (Map preserves insertion order) at the cap. Skip when\n // refreshing an existing key — `set` updates it in place without growing the\n // map, so evicting first would drop a live entry and shrink the effective cap.\n if (!entries.has(queryKey)) {\n while (entries.size >= MAX_ENTRIES_PER_TABLE) {\n const oldest = entries.keys().next().value;\n if (oldest === undefined) break;\n entries.delete(oldest);\n }\n }\n\n entries.set(queryKey, {\n expiresAt: Date.now() + ttl,\n rows: structuredClone(rows),\n });\n}\n\n/**\n * Drop every cached entry for a table. Called by the framework after any\n * successful mutation against that table — this is the write-invalidation\n * guarantee an app-side cache can't make.\n */\nexport function invalidateCollectionCache(\n dbKey: string,\n tableName: string,\n): void {\n store.get(dbKey)?.delete(tableName);\n // Bump the generation so any read whose DB round-trip is still in flight\n // discards its (now-stale) result instead of repopulating the table.\n const gkey = generationKey(dbKey, tableName);\n generations.set(gkey, (generations.get(gkey) ?? 0) + 1);\n}\n\n/**\n * Clear all cached collection reads and stop cross-process listeners.\n * Call in test setup to ensure isolation between test files.\n */\nexport function resetCollectionCache(): void {\n store.clear();\n inFlightReads.clear();\n generations.clear();\n crossProcessInterest.clear();\n stopCacheInvalidationListeners();\n}\n\n// ============================================================================\n// Per-call crossProcess interest (write-side broadcast decision)\n// ============================================================================\n\n/**\n * dbKey → tables this process has cached with a per-call `crossProcess`\n * opt-in. Writes consult this so per-call usage broadcasts too — model-level\n * config can't be the only trigger when the opt-in lives at the call site.\n */\nconst crossProcessInterest = new Map<string, Set<string>>();\n\n/**\n * Record that a per-call `crossProcess` cached read happened for a table.\n */\nexport function registerCrossProcessCacheInterest(\n dbKey: string,\n tableName: string,\n): void {\n let tables = crossProcessInterest.get(dbKey);\n if (!tables) {\n tables = new Set();\n crossProcessInterest.set(dbKey, tables);\n }\n tables.add(tableName);\n}\n\n/**\n * Whether any per-call `crossProcess` cached read has touched this table.\n */\nexport function hasCrossProcessCacheInterest(\n dbKey: string,\n tableName: string,\n): boolean {\n return crossProcessInterest.get(dbKey)?.has(tableName) ?? false;\n}\n\n// ============================================================================\n// Cross-process invalidation (opt-in, via db.notifications)\n// ============================================================================\n\ninterface ListenerHandle {\n iterator: AsyncIterator<unknown> | null;\n stopped: boolean;\n}\n\nconst listeners = new Map<string, ListenerHandle>();\nconst warnedNoNotifications = new Set<string>();\n\n/**\n * The database adapter's optional notification capability (e.g. Postgres\n * LISTEN/NOTIFY), or `undefined` when the adapter exposes none. Duck-typed so\n * core never depends on a concrete adapter shape. Exported so sibling\n * cross-replica buses (the #1763 change-signal bus) resolve it identically.\n */\nexport function getNotifications(db: DatabaseInterface) {\n return (db as DatabaseInterface & { notifications?: unknown })\n .notifications as\n | {\n notify(channel: string, payload: unknown): Promise<number>;\n listen(\n channel: string,\n options?: Record<string, unknown>,\n ): AsyncIterable<{ channel: string; payload: unknown }>;\n }\n | undefined;\n}\n\nfunction warnOnceNoNotifications(dbKey: string, context: string): void {\n if (warnedNoNotifications.has(dbKey)) return;\n warnedNoNotifications.add(dbKey);\n logger.warn(\n `Collection cache: crossProcess invalidation requested but the database ` +\n `adapter exposes no notification capability (${context}). Peer ` +\n `replicas will serve stale rows until TTL.`,\n );\n}\n\n/**\n * Broadcast a table invalidation to peer processes.\n *\n * Fire-and-forget from mutation paths: a broadcast failure must never fail\n * the write that triggered it.\n */\nexport async function broadcastCacheInvalidation(\n db: DatabaseInterface,\n tableName: string,\n): Promise<void> {\n const notifications = getNotifications(db);\n const dbKey = resolveDbCacheKey(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey, 'broadcast');\n return;\n }\n try {\n await notifications.notify(CACHE_INVALIDATION_CHANNEL, {\n table: tableName,\n source: PROCESS_ID,\n });\n } catch (error) {\n logger.warn(\n `Collection cache: failed to broadcast invalidation for ${tableName}`,\n { error: error instanceof Error ? error.message : error },\n );\n }\n}\n\n/**\n * Ensure a background listener consumes invalidation broadcasts for this\n * database and drops matching local cache entries.\n *\n * Started lazily by the first cached read that opted into `crossProcess`.\n * Notifications published by this process are skipped — the local\n * invalidation already happened synchronously on the write path.\n */\nexport function ensureCacheInvalidationListener(db: DatabaseInterface): void {\n const dbKey = resolveDbCacheKey(db);\n if (listeners.has(dbKey)) return;\n\n const notifications = getNotifications(db);\n if (!notifications) {\n warnOnceNoNotifications(dbKey, 'listen');\n return;\n }\n\n const handle: ListenerHandle = { iterator: null, stopped: false };\n listeners.set(dbKey, handle);\n\n void (async () => {\n try {\n const iterable = notifications.listen(CACHE_INVALIDATION_CHANNEL);\n const iterator = iterable[Symbol.asyncIterator]();\n handle.iterator = iterator;\n\n while (!handle.stopped) {\n const { value, done } = await iterator.next();\n if (done || handle.stopped) break;\n\n const notification = value as { payload?: unknown };\n const payload: unknown =\n typeof notification?.payload === 'string'\n ? safeParse(notification.payload)\n : notification?.payload;\n\n if (!payload || typeof payload !== 'object') continue;\n const record = payload as { table?: unknown; source?: unknown };\n if (typeof record.table !== 'string') continue;\n if (record.source === PROCESS_ID) continue;\n\n invalidateCollectionCache(dbKey, record.table);\n }\n } catch (error) {\n if (!handle.stopped) {\n logger.warn(\n 'Collection cache: invalidation listener terminated unexpectedly; ' +\n 'cross-process invalidation is inactive for this database ' +\n '(local TTL still bounds staleness)',\n { error: error instanceof Error ? error.message : error },\n );\n }\n } finally {\n // Only retract our own handle. A concurrent stop+restart for the same\n // dbKey may have already installed a replacement; deleting unconditionally\n // would orphan it and leak/duplicate the live listener.\n if (listeners.get(dbKey) === handle) {\n listeners.delete(dbKey);\n }\n }\n })();\n}\n\n/**\n * Stop all cross-process invalidation listeners. Used by tests and during\n * shutdown; safe to call when none are active.\n */\nexport function stopCacheInvalidationListeners(): void {\n for (const handle of listeners.values()) {\n handle.stopped = true;\n void handle.iterator?.return?.(undefined);\n }\n listeners.clear();\n warnedNoNotifications.clear();\n}\n\nfunction safeParse(value: string): unknown {\n try {\n return JSON.parse(value);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -236,6 +236,9 @@ export interface SmrtCollectionOptions extends SmrtClassOptions {
236
236
  */
237
237
  maxListLimit?: number;
238
238
  }
239
+ /** Only provider/configuration unavailability permits a caller's text fallback. */
240
+ export declare class EmbeddingUnavailableError extends Error {
241
+ }
239
242
  export type SmrtCollectionItemClass<ModelType extends SmrtObject> = (new (options: any) => ModelType) & {
240
243
  create(options: any): ModelType | Promise<ModelType>;
241
244
  };
@@ -290,7 +293,7 @@ export declare class SmrtCollection<ModelType extends SmrtObject> extends SmrtCl
290
293
  private _defaultListLimit;
291
294
  private _maxListLimit;
292
295
  private getRegisteredItemClass;
293
- private getResolvedItemClassName;
296
+ protected getResolvedItemClassName(): string;
294
297
  private getResolvedItemQualifiedName;
295
298
  /** Apply and validate this collection's owned STI discriminator predicate. */
296
299
  private applyStiReadScope;
@@ -600,7 +603,8 @@ export declare class SmrtCollection<ModelType extends SmrtObject> extends SmrtCl
600
603
  /**
601
604
  * Execute a SELECT, optionally through the collection read cache.
602
605
  *
603
- * The cache key is the final SQL + bound parameters — computed after
606
+ * The cache key includes the concrete database-interface object and final
607
+ * SQL + bound parameters — computed after
604
608
  * interceptors (tenancy filters) and STI discriminators are applied, so
605
609
  * differently-scoped queries can never share an entry. Cached values are
606
610
  * raw rows; callers still run their normal post-query formatting path.
@@ -802,6 +806,10 @@ export declare class SmrtCollection<ModelType extends SmrtObject> extends SmrtCl
802
806
  * @see {@link getOrUpsert} to avoid duplicates by finding-or-creating
803
807
  */
804
808
  create(options: SmrtCreateInput<ModelType>): Promise<ModelType>;
809
+ /** @internal Test creation overrides inside the owning protected boundary. */
810
+ static hasBaseCreateLifecycle<T extends SmrtObject>(collection: SmrtCollection<T>): boolean;
811
+ /** Initialize using the same owning path as create(), without persistence. */
812
+ protected createUnsaved(options: SmrtCreateInput<ModelType>): Promise<ModelType>;
805
813
  /**
806
814
  * Creates an instance of the correct subclass for STI polymorphic queries
807
815
  *
@@ -1265,6 +1273,65 @@ export declare class SmrtCollection<ModelType extends SmrtObject> extends SmrtCl
1265
1273
  }): Promise<Array<ModelType & {
1266
1274
  _similarity: number;
1267
1275
  }>>;
1276
+ /**
1277
+ * Prepare normal list authorization for a subclass's bounded hydrated SQL read.
1278
+ * Call finish exactly once on the final page, after query hooks/annotations,
1279
+ * outside provider fallback catches. It preserves the beforeList context and
1280
+ * applies the normal afterList pipeline without fetching replacement rows.
1281
+ */
1282
+ protected resolveListReadPredicate(where?: SmrtListWhereClause<ModelType>): Promise<{
1283
+ sql: string;
1284
+ values: unknown[];
1285
+ finish(instances: ModelType[]): Promise<ModelType[]>;
1286
+ }>;
1287
+ /**
1288
+ * Search text without hydrating objects. All read predicates (including
1289
+ * `where`, tenancy and STI) apply BEFORE exact cosine ranking. Unlike the
1290
+ * legacy semanticSearch API, a nonmatching high score cannot consume limit.
1291
+ */
1292
+ semanticSearchIds(query: string, options?: {
1293
+ field?: string;
1294
+ limit?: number;
1295
+ minSimilarity?: number;
1296
+ where?: SmrtListWhereClause<ModelType>;
1297
+ }): Promise<Array<{
1298
+ id: string;
1299
+ similarity: number;
1300
+ }>>;
1301
+ /**
1302
+ * Provider-origin availability for bounded subclass reads. Only missing
1303
+ * configuration or provider.embed failure returns unavailable; all option,
1304
+ * authorization, ranking and database failures propagate unchanged.
1305
+ */
1306
+ protected semanticSearchIdsWithAvailability(query: string, options?: {
1307
+ field?: string;
1308
+ limit?: number;
1309
+ minSimilarity?: number;
1310
+ where?: SmrtListWhereClause<ModelType>;
1311
+ }): Promise<{
1312
+ available: true;
1313
+ matches: Array<{
1314
+ id: string;
1315
+ similarity: number;
1316
+ }>;
1317
+ } | {
1318
+ available: false;
1319
+ error: EmbeddingUnavailableError;
1320
+ }>;
1321
+ /**
1322
+ * Return scored IDs using bounded embedding batches and scalar SQL scope
1323
+ * masks. Application rows are never fetched; separate system/app databases
1324
+ * are supported. Equal scores sort by object ID for stable pagination.
1325
+ */
1326
+ findSimilarIdsToEmbedding(embedding: number[], options?: {
1327
+ field?: string;
1328
+ limit?: number;
1329
+ minSimilarity?: number;
1330
+ where?: SmrtListWhereClause<ModelType>;
1331
+ }): Promise<Array<{
1332
+ id: string;
1333
+ similarity: number;
1334
+ }>>;
1268
1335
  /**
1269
1336
  * Find objects similar to a given object
1270
1337
  *
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { chunkArray } from "./utils/chunk.js";
2
2
  import { classnameToTablename, toSnakeCase } from "./utils/naming.js";
3
- import { buildQueryCacheKey, ensureCacheInvalidationListener, getCacheGeneration, getCachedRows, invalidateCollectionCache, registerCrossProcessCacheInterest, resolveDbCacheKey, setCachedRows } from "./collection-cache.js";
3
+ import { buildQueryCacheKey, ensureCacheInvalidationListener, getCacheGeneration, getCachedRows, getOrCreateInFlightRead, invalidateCollectionCache, registerCrossProcessCacheInterest, resolveDbCacheKey, setCachedRows } from "./collection-cache.js";
4
4
  import { toSafeInteger } from "./utils/safe-integer.js";
5
5
  import { isTenantScopedClassResolved, resolveDispatchTenantScope } from "./dispatch/tenant-resolver.js";
6
6
  import { GlobalInterceptors, createInterceptorContext, resolveGetStringFilter } from "./interceptors.js";
@@ -183,6 +183,8 @@ function getSearchableEmbeddingFields(config) {
183
183
  if (!combinedName || config.fields.includes(combinedName)) return config.fields;
184
184
  return [...config.fields, combinedName];
185
185
  }
186
+ /** Only provider/configuration unavailability permits a caller's text fallback. */
187
+ var EmbeddingUnavailableError = class extends Error {};
186
188
  /**
187
189
  * Typed CRUD collection for a specific `SmrtObject` subclass.
188
190
  *
@@ -1033,7 +1035,8 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
1033
1035
  /**
1034
1036
  * Execute a SELECT, optionally through the collection read cache.
1035
1037
  *
1036
- * The cache key is the final SQL + bound parameters — computed after
1038
+ * The cache key includes the concrete database-interface object and final
1039
+ * SQL + bound parameters — computed after
1037
1040
  * interceptors (tenancy filters) and STI discriminators are applied, so
1038
1041
  * differently-scoped queries can never share an entry. Cached values are
1039
1042
  * raw rows; callers still run their normal post-query formatting path.
@@ -1041,7 +1044,7 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
1041
1044
  async queryRowsWithCache(sql, params, cacheConfig, describeUuidOutputs) {
1042
1045
  if (!cacheConfig) return await this.queryDuckDbCanonicalSelectRows(sql, params, describeUuidOutputs);
1043
1046
  const dbKey = resolveDbCacheKey(this.db);
1044
- const queryKey = buildQueryCacheKey(sql, params);
1047
+ const queryKey = buildQueryCacheKey(sql, params, this.db);
1045
1048
  if (cacheConfig.crossProcess) {
1046
1049
  ensureCacheInvalidationListener(this.db);
1047
1050
  registerCrossProcessCacheInterest(dbKey, this.tableName);
@@ -1049,9 +1052,12 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
1049
1052
  const cached = getCachedRows(dbKey, this.tableName, queryKey);
1050
1053
  if (cached) return cached;
1051
1054
  const generation = getCacheGeneration(dbKey, this.tableName);
1052
- const rows = await this.queryDuckDbCanonicalSelectRows(sql, params, describeUuidOutputs);
1053
- setCachedRows(dbKey, this.tableName, queryKey, rows, cacheConfig.ttl, generation);
1054
- return rows;
1055
+ const rows = await getOrCreateInFlightRead(dbKey, this.tableName, queryKey, generation, async () => {
1056
+ const rows = await this.queryDuckDbCanonicalSelectRows(sql, params, describeUuidOutputs);
1057
+ setCachedRows(dbKey, this.tableName, queryKey, rows, cacheConfig.ttl, generation);
1058
+ return rows;
1059
+ });
1060
+ return structuredClone(rows);
1055
1061
  }
1056
1062
  /**
1057
1063
  * Describe a native DuckDB SELECT before executing it, then cast UUID output
@@ -1371,6 +1377,16 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
1371
1377
  * @see {@link getOrUpsert} to avoid duplicates by finding-or-creating
1372
1378
  */
1373
1379
  async create(options) {
1380
+ const instance = await this.createUnsaved(options);
1381
+ await instance.save();
1382
+ return instance;
1383
+ }
1384
+ /** @internal Test creation overrides inside the owning protected boundary. */
1385
+ static hasBaseCreateLifecycle(collection) {
1386
+ return collection.create === SmrtCollection.prototype.create && collection.createUnsaved === SmrtCollection.prototype.createUnsaved;
1387
+ }
1388
+ /** Initialize using the same owning path as create(), without persistence. */
1389
+ async createUnsaved(options) {
1374
1390
  let itemClassName = this.getResolvedItemClassName();
1375
1391
  await ObjectRegistry.ensureManifestLoaded(itemClassName);
1376
1392
  itemClassName = this.getResolvedItemClassName();
@@ -1380,7 +1396,6 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
1380
1396
  const instance = await this.createPolymorphic(options._meta_type, options);
1381
1397
  if (!instance.id) instance._id = crypto.randomUUID();
1382
1398
  if (options._insertOnly === true) instance.requireInsertOnSave();
1383
- await instance.save();
1384
1399
  return instance;
1385
1400
  }
1386
1401
  const params = {
@@ -1394,7 +1409,6 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
1394
1409
  if (tableStrategy === "sti") instance._meta_type = itemQualifiedName;
1395
1410
  if (!instance.id) instance._id = crypto.randomUUID();
1396
1411
  if (options._insertOnly === true) instance.requireInsertOnSave();
1397
- await instance.save();
1398
1412
  return instance;
1399
1413
  }
1400
1414
  /**
@@ -2252,6 +2266,125 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
2252
2266
  });
2253
2267
  }
2254
2268
  /**
2269
+ * Prepare normal list authorization for a subclass's bounded hydrated SQL read.
2270
+ * Call finish exactly once on the final page, after query hooks/annotations,
2271
+ * outside provider fallback catches. It preserves the beforeList context and
2272
+ * applies the normal afterList pipeline without fetching replacement rows.
2273
+ */
2274
+ async resolveListReadPredicate(where = {}) {
2275
+ await this.ensureStorageReady();
2276
+ const className = this.getResolvedItemClassName();
2277
+ const context = createInterceptorContext(className, "list", this.constructor.name, void 0, this.getResolvedItemQualifiedName());
2278
+ const options = await GlobalInterceptors.executeBeforeList(className, { where }, context);
2279
+ const scoped = resolveMetaTypeInWhere(this.applyStiReadScope(options.where, void 0));
2280
+ const predicate = buildWhere(this.convertWhereKeys(scoped || {}));
2281
+ return {
2282
+ sql: predicate.sql.trim().replace(/^WHERE\s+/i, "").replace(/\$\d+/g, "?") || "1 = 1",
2283
+ values: predicate.values,
2284
+ finish: (instances) => GlobalInterceptors.executeAfterList(className, instances, context)
2285
+ };
2286
+ }
2287
+ /**
2288
+ * Search text without hydrating objects. All read predicates (including
2289
+ * `where`, tenancy and STI) apply BEFORE exact cosine ranking. Unlike the
2290
+ * legacy semanticSearch API, a nonmatching high score cannot consume limit.
2291
+ */
2292
+ async semanticSearchIds(query, options = {}) {
2293
+ const result = await this.semanticSearchIdsWithAvailability(query, options);
2294
+ if (!result.available) throw result.error;
2295
+ return result.matches;
2296
+ }
2297
+ /**
2298
+ * Provider-origin availability for bounded subclass reads. Only missing
2299
+ * configuration or provider.embed failure returns unavailable; all option,
2300
+ * authorization, ranking and database failures propagate unchanged.
2301
+ */
2302
+ async semanticSearchIdsWithAvailability(query, options = {}) {
2303
+ const config = ObjectRegistry.resolveEmbeddingConfig(this._itemClass.name);
2304
+ if (!config) return {
2305
+ available: false,
2306
+ error: new EmbeddingUnavailableError(`No embedding configuration found for ${this._itemClass.name}.`)
2307
+ };
2308
+ const field = options.field || config.fields[0];
2309
+ if (!getSearchableEmbeddingFields(config).includes(field)) throw new Error(`Field '${field}' is not configured for embeddings on ${this._itemClass.name}.`);
2310
+ const provider = new EmbeddingProvider({
2311
+ dimensions: config.dimensions,
2312
+ provider: config.provider,
2313
+ localModel: config.localModel,
2314
+ aiModel: config.aiModel,
2315
+ fallbackToAI: config.fallbackToAI
2316
+ }, this.ai);
2317
+ let embedding;
2318
+ try {
2319
+ [embedding] = await provider.embed(query);
2320
+ } catch (cause) {
2321
+ return {
2322
+ available: false,
2323
+ error: new EmbeddingUnavailableError("Query embedding is unavailable", { cause })
2324
+ };
2325
+ }
2326
+ return {
2327
+ available: true,
2328
+ matches: await this.findSimilarIdsToEmbedding(embedding, {
2329
+ ...options,
2330
+ field
2331
+ })
2332
+ };
2333
+ }
2334
+ /**
2335
+ * Return scored IDs using bounded embedding batches and scalar SQL scope
2336
+ * masks. Application rows are never fetched; separate system/app databases
2337
+ * are supported. Equal scores sort by object ID for stable pagination.
2338
+ */
2339
+ async findSimilarIdsToEmbedding(embedding, options = {}) {
2340
+ const { limit = 10, minSimilarity = 0 } = options;
2341
+ if (!Number.isSafeInteger(limit) || limit < 0) throw new Error("Semantic ID search limit must be a nonnegative safe integer");
2342
+ if (!Number.isFinite(minSimilarity) || minSimilarity < -1 || minSimilarity > 1) throw new Error("Semantic ID search minSimilarity must be between -1 and 1");
2343
+ await this.ensureStorageReady();
2344
+ const itemClassName = this.getResolvedItemClassName();
2345
+ const intercepted = await GlobalInterceptors.executeBeforeList(itemClassName, { where: options.where || {} }, createInterceptorContext(itemClassName, "list", this.constructor.name, void 0, this.getResolvedItemQualifiedName()));
2346
+ const scopedWhere = resolveMetaTypeInWhere(this.applyStiReadScope(intercepted.where, void 0));
2347
+ const { sql: whereSql, values: whereValues } = buildWhere(this.convertWhereKeys(scopedWhere || {}));
2348
+ const config = ObjectRegistry.resolveEmbeddingConfig(this._itemClass.name);
2349
+ if (!config) throw new Error(`No embedding configuration found for ${this._itemClass.name}.`);
2350
+ const field = options.field || config.fields[0];
2351
+ if (!getSearchableEmbeddingFields(config).includes(field)) throw new Error(`Field '${field}' is not configured for embeddings on ${this._itemClass.name}.`);
2352
+ const provider = new EmbeddingProvider({
2353
+ dimensions: config.dimensions,
2354
+ provider: config.provider,
2355
+ localModel: config.localModel,
2356
+ aiModel: config.aiModel,
2357
+ fallbackToAI: config.fallbackToAI
2358
+ }, this.ai);
2359
+ if (limit === 0) return [];
2360
+ return (await EmbeddingStorage.searchSimilarBatched(this.systemDb, this._itemClass.name, embedding, {
2361
+ field,
2362
+ model: provider.getModelName(),
2363
+ limit,
2364
+ minSimilarity,
2365
+ eligible: async (ids) => {
2366
+ const chunkSize = Math.max(1, 999 - whereValues.length);
2367
+ const eligible = [];
2368
+ for (let offset = 0; offset < ids.length; offset += chunkSize) {
2369
+ const chunk = ids.slice(offset, offset + chunkSize);
2370
+ const cases = chunk.map((_, index) => `CASE WHEN EXISTS (SELECT 1 FROM eligible_scope
2371
+ WHERE eligible_id = $${whereValues.length + index + 1})
2372
+ THEN '1' ELSE '0' END`);
2373
+ const { rows } = await this.db.query(`WITH eligible_scope AS (
2374
+ SELECT id AS eligible_id FROM ${this.tableName} ${whereSql}
2375
+ ) SELECT ${cases.join(" || ")} AS eligibility`, ...whereValues, ...chunk);
2376
+ const mask = rows[0]?.eligibility;
2377
+ if (typeof mask !== "string" || !/^[01]+$/.test(mask) || mask.length !== chunk.length) throw new Error("Invalid semantic search eligibility result");
2378
+ eligible.push(...[...mask].map((bit) => bit === "1"));
2379
+ }
2380
+ return eligible;
2381
+ }
2382
+ })).map(({ objectId, similarity }) => ({
2383
+ id: objectId,
2384
+ similarity
2385
+ }));
2386
+ }
2387
+ /**
2255
2388
  * Find objects similar to a given object
2256
2389
  *
2257
2390
  * Uses stored embeddings to find objects most similar to the provided object.
@@ -2428,6 +2561,6 @@ var SmrtCollection = class SmrtCollection extends SmrtClass {
2428
2561
  }
2429
2562
  };
2430
2563
  //#endregion
2431
- export { DEFAULT_FACET_LIMIT, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_STI_READ_SCOPE_TYPES, SmrtCollection };
2564
+ export { DEFAULT_FACET_LIMIT, EmbeddingUnavailableError, MAX_FACET_FIELDS, MAX_FACET_LIMIT, MAX_STI_READ_SCOPE_TYPES, SmrtCollection };
2432
2565
 
2433
2566
  //# sourceMappingURL=collection.js.map