ai-database 2.1.1 → 2.3.0

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 (268) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/README.md +1063 -186
  3. package/dist/actions.d.ts +2 -2
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +1 -1
  6. package/dist/actions.js.map +1 -1
  7. package/dist/ai-promise-db.d.ts +52 -23
  8. package/dist/ai-promise-db.d.ts.map +1 -1
  9. package/dist/ai-promise-db.js +185 -164
  10. package/dist/ai-promise-db.js.map +1 -1
  11. package/dist/authorization.d.ts.map +1 -1
  12. package/dist/authorization.js +38 -30
  13. package/dist/authorization.js.map +1 -1
  14. package/dist/cascade-orchestrator.d.ts +404 -0
  15. package/dist/cascade-orchestrator.d.ts.map +1 -0
  16. package/dist/cascade-orchestrator.js +828 -0
  17. package/dist/cascade-orchestrator.js.map +1 -0
  18. package/dist/cascade-write-strategy.d.ts +584 -0
  19. package/dist/cascade-write-strategy.d.ts.map +1 -0
  20. package/dist/cascade-write-strategy.js +590 -0
  21. package/dist/cascade-write-strategy.js.map +1 -0
  22. package/dist/ch-adapter.d.ts +358 -0
  23. package/dist/ch-adapter.d.ts.map +1 -0
  24. package/dist/ch-adapter.js +929 -0
  25. package/dist/ch-adapter.js.map +1 -0
  26. package/dist/client/index.d.ts +42 -0
  27. package/dist/client/index.d.ts.map +1 -0
  28. package/dist/client/index.js +43 -0
  29. package/dist/client/index.js.map +1 -0
  30. package/dist/client.d.ts +266 -0
  31. package/dist/client.d.ts.map +1 -0
  32. package/dist/client.js +81 -0
  33. package/dist/client.js.map +1 -0
  34. package/dist/constants.d.ts +64 -1
  35. package/dist/constants.d.ts.map +1 -1
  36. package/dist/constants.js +52 -2
  37. package/dist/constants.js.map +1 -1
  38. package/dist/dataloader.d.ts +99 -0
  39. package/dist/dataloader.d.ts.map +1 -0
  40. package/dist/dataloader.js +225 -0
  41. package/dist/dataloader.js.map +1 -0
  42. package/dist/db-provider-port.d.ts +501 -0
  43. package/dist/db-provider-port.d.ts.map +1 -0
  44. package/dist/db-provider-port.js +113 -0
  45. package/dist/db-provider-port.js.map +1 -0
  46. package/dist/digital-objects-provider.d.ts +49 -0
  47. package/dist/digital-objects-provider.d.ts.map +1 -0
  48. package/dist/digital-objects-provider.js +55 -0
  49. package/dist/digital-objects-provider.js.map +1 -0
  50. package/dist/do-sqlite-adapter.d.ts +402 -0
  51. package/dist/do-sqlite-adapter.d.ts.map +1 -0
  52. package/dist/do-sqlite-adapter.js +745 -0
  53. package/dist/do-sqlite-adapter.js.map +1 -0
  54. package/dist/docs-rels/custom-types.d.ts +134 -0
  55. package/dist/docs-rels/custom-types.d.ts.map +1 -0
  56. package/dist/docs-rels/custom-types.js +70 -0
  57. package/dist/docs-rels/custom-types.js.map +1 -0
  58. package/dist/docs-rels/index.d.ts +16 -0
  59. package/dist/docs-rels/index.d.ts.map +1 -0
  60. package/dist/docs-rels/index.js +16 -0
  61. package/dist/docs-rels/index.js.map +1 -0
  62. package/dist/docs-rels/migrations/index.d.ts +30 -0
  63. package/dist/docs-rels/migrations/index.d.ts.map +1 -0
  64. package/dist/docs-rels/migrations/index.js +128 -0
  65. package/dist/docs-rels/migrations/index.js.map +1 -0
  66. package/dist/docs-rels/schema.d.ts +2961 -0
  67. package/dist/docs-rels/schema.d.ts.map +1 -0
  68. package/dist/docs-rels/schema.js +244 -0
  69. package/dist/docs-rels/schema.js.map +1 -0
  70. package/dist/durable-clickhouse.d.ts.map +1 -1
  71. package/dist/durable-clickhouse.js +16 -13
  72. package/dist/durable-clickhouse.js.map +1 -1
  73. package/dist/durable-promise.d.ts.map +1 -1
  74. package/dist/durable-promise.js +34 -15
  75. package/dist/durable-promise.js.map +1 -1
  76. package/dist/errors.d.ts +127 -0
  77. package/dist/errors.d.ts.map +1 -0
  78. package/dist/errors.js +210 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/eventbridge.d.ts +117 -0
  81. package/dist/eventbridge.d.ts.map +1 -0
  82. package/dist/eventbridge.js +238 -0
  83. package/dist/eventbridge.js.map +1 -0
  84. package/dist/events.d.ts +2 -2
  85. package/dist/events.d.ts.map +1 -1
  86. package/dist/events.js +1 -1
  87. package/dist/events.js.map +1 -1
  88. package/dist/execution-queue.d.ts.map +1 -1
  89. package/dist/execution-queue.js +4 -5
  90. package/dist/execution-queue.js.map +1 -1
  91. package/dist/index.d.ts +37 -8
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +112 -6
  94. package/dist/index.js.map +1 -1
  95. package/dist/linguistic.d.ts +3 -108
  96. package/dist/linguistic.d.ts.map +1 -1
  97. package/dist/linguistic.js +3 -372
  98. package/dist/linguistic.js.map +1 -1
  99. package/dist/logger.d.ts +132 -0
  100. package/dist/logger.d.ts.map +1 -0
  101. package/dist/logger.js +137 -0
  102. package/dist/logger.js.map +1 -0
  103. package/dist/memory-provider.d.ts +129 -0
  104. package/dist/memory-provider.d.ts.map +1 -1
  105. package/dist/memory-provider.js +592 -257
  106. package/dist/memory-provider.js.map +1 -1
  107. package/dist/pg-adapter.d.ts +424 -0
  108. package/dist/pg-adapter.d.ts.map +1 -0
  109. package/dist/pg-adapter.js +921 -0
  110. package/dist/pg-adapter.js.map +1 -0
  111. package/dist/pipelines-iceberg-emitter.d.ts +327 -0
  112. package/dist/pipelines-iceberg-emitter.d.ts.map +1 -0
  113. package/dist/pipelines-iceberg-emitter.js +351 -0
  114. package/dist/pipelines-iceberg-emitter.js.map +1 -0
  115. package/dist/provider-capabilities.d.ts +146 -0
  116. package/dist/provider-capabilities.d.ts.map +1 -0
  117. package/dist/provider-capabilities.js +214 -0
  118. package/dist/provider-capabilities.js.map +1 -0
  119. package/dist/rdb-provider-adapter.d.ts +195 -0
  120. package/dist/rdb-provider-adapter.d.ts.map +1 -0
  121. package/dist/rdb-provider-adapter.js +291 -0
  122. package/dist/rdb-provider-adapter.js.map +1 -0
  123. package/dist/schema/cascade.d.ts +49 -10
  124. package/dist/schema/cascade.d.ts.map +1 -1
  125. package/dist/schema/cascade.js +491 -273
  126. package/dist/schema/cascade.js.map +1 -1
  127. package/dist/schema/definition-caches.d.ts +24 -0
  128. package/dist/schema/definition-caches.d.ts.map +1 -0
  129. package/dist/schema/definition-caches.js +26 -0
  130. package/dist/schema/definition-caches.js.map +1 -0
  131. package/dist/schema/dependency-graph.d.ts +45 -0
  132. package/dist/schema/dependency-graph.d.ts.map +1 -0
  133. package/dist/schema/dependency-graph.js +47 -0
  134. package/dist/schema/dependency-graph.js.map +1 -0
  135. package/dist/schema/diff.d.ts +103 -0
  136. package/dist/schema/diff.d.ts.map +1 -0
  137. package/dist/schema/diff.js +329 -0
  138. package/dist/schema/diff.js.map +1 -0
  139. package/dist/schema/entity-operations.d.ts +99 -0
  140. package/dist/schema/entity-operations.d.ts.map +1 -0
  141. package/dist/schema/entity-operations.js +818 -0
  142. package/dist/schema/entity-operations.js.map +1 -0
  143. package/dist/schema/generation-context.d.ts +202 -0
  144. package/dist/schema/generation-context.d.ts.map +1 -0
  145. package/dist/schema/generation-context.js +393 -0
  146. package/dist/schema/generation-context.js.map +1 -0
  147. package/dist/schema/index.d.ts +32 -34
  148. package/dist/schema/index.d.ts.map +1 -1
  149. package/dist/schema/index.js +462 -519
  150. package/dist/schema/index.js.map +1 -1
  151. package/dist/schema/migration.d.ts +205 -0
  152. package/dist/schema/migration.d.ts.map +1 -0
  153. package/dist/schema/migration.js +327 -0
  154. package/dist/schema/migration.js.map +1 -0
  155. package/dist/schema/nl-query-generator.d.ts +68 -0
  156. package/dist/schema/nl-query-generator.d.ts.map +1 -0
  157. package/dist/schema/nl-query-generator.js +362 -0
  158. package/dist/schema/nl-query-generator.js.map +1 -0
  159. package/dist/schema/nl-query.d.ts +65 -0
  160. package/dist/schema/nl-query.d.ts.map +1 -0
  161. package/dist/schema/nl-query.js +178 -0
  162. package/dist/schema/nl-query.js.map +1 -0
  163. package/dist/schema/parse.d.ts.map +1 -1
  164. package/dist/schema/parse.js +152 -89
  165. package/dist/schema/parse.js.map +1 -1
  166. package/dist/schema/provider.d.ts +38 -0
  167. package/dist/schema/provider.d.ts.map +1 -1
  168. package/dist/schema/provider.js +15 -7
  169. package/dist/schema/provider.js.map +1 -1
  170. package/dist/schema/resolve.d.ts +46 -5
  171. package/dist/schema/resolve.d.ts.map +1 -1
  172. package/dist/schema/resolve.js +334 -117
  173. package/dist/schema/resolve.js.map +1 -1
  174. package/dist/schema/search-utils.d.ts +76 -0
  175. package/dist/schema/search-utils.d.ts.map +1 -0
  176. package/dist/schema/search-utils.js +86 -0
  177. package/dist/schema/search-utils.js.map +1 -0
  178. package/dist/schema/seed.d.ts +53 -0
  179. package/dist/schema/seed.d.ts.map +1 -0
  180. package/dist/schema/seed.js +94 -0
  181. package/dist/schema/seed.js.map +1 -0
  182. package/dist/schema/semantic.d.ts +11 -0
  183. package/dist/schema/semantic.d.ts.map +1 -1
  184. package/dist/schema/semantic.js +262 -68
  185. package/dist/schema/semantic.js.map +1 -1
  186. package/dist/schema/sub-apis.d.ts +52 -0
  187. package/dist/schema/sub-apis.d.ts.map +1 -0
  188. package/dist/schema/sub-apis.js +216 -0
  189. package/dist/schema/sub-apis.js.map +1 -0
  190. package/dist/schema/system-entities.d.ts +42 -0
  191. package/dist/schema/system-entities.d.ts.map +1 -0
  192. package/dist/schema/system-entities.js +101 -0
  193. package/dist/schema/system-entities.js.map +1 -0
  194. package/dist/schema/types.d.ts +91 -9
  195. package/dist/schema/types.d.ts.map +1 -1
  196. package/dist/schema/union-fallback.d.ts +219 -0
  197. package/dist/schema/union-fallback.d.ts.map +1 -0
  198. package/dist/schema/union-fallback.js +331 -0
  199. package/dist/schema/union-fallback.js.map +1 -0
  200. package/dist/schema/value-generators/ai.d.ts +54 -0
  201. package/dist/schema/value-generators/ai.d.ts.map +1 -0
  202. package/dist/schema/value-generators/ai.js +136 -0
  203. package/dist/schema/value-generators/ai.js.map +1 -0
  204. package/dist/schema/value-generators/index.d.ts +126 -0
  205. package/dist/schema/value-generators/index.d.ts.map +1 -0
  206. package/dist/schema/value-generators/index.js +219 -0
  207. package/dist/schema/value-generators/index.js.map +1 -0
  208. package/dist/schema/value-generators/placeholder.d.ts +52 -0
  209. package/dist/schema/value-generators/placeholder.d.ts.map +1 -0
  210. package/dist/schema/value-generators/placeholder.js +328 -0
  211. package/dist/schema/value-generators/placeholder.js.map +1 -0
  212. package/dist/schema/value-generators/types.d.ts +116 -0
  213. package/dist/schema/value-generators/types.d.ts.map +1 -0
  214. package/dist/schema/value-generators/types.js +11 -0
  215. package/dist/schema/value-generators/types.js.map +1 -0
  216. package/dist/schema/verb-derivation.d.ts +167 -0
  217. package/dist/schema/verb-derivation.d.ts.map +1 -0
  218. package/dist/schema/verb-derivation.js +281 -0
  219. package/dist/schema/verb-derivation.js.map +1 -0
  220. package/dist/schema/version.d.ts +111 -0
  221. package/dist/schema/version.d.ts.map +1 -0
  222. package/dist/schema/version.js +190 -0
  223. package/dist/schema/version.js.map +1 -0
  224. package/dist/schema.d.ts +1095 -23
  225. package/dist/schema.d.ts.map +1 -1
  226. package/dist/schema.js +2854 -38
  227. package/dist/schema.js.map +1 -1
  228. package/dist/semantic-vectors.d.ts +39 -0
  229. package/dist/semantic-vectors.d.ts.map +1 -0
  230. package/dist/semantic-vectors.js +334 -0
  231. package/dist/semantic-vectors.js.map +1 -0
  232. package/dist/semantic.d.ts +29 -1
  233. package/dist/semantic.d.ts.map +1 -1
  234. package/dist/semantic.js +26 -16
  235. package/dist/semantic.js.map +1 -1
  236. package/dist/telemetry.d.ts +128 -0
  237. package/dist/telemetry.d.ts.map +1 -0
  238. package/dist/telemetry.js +305 -0
  239. package/dist/telemetry.js.map +1 -0
  240. package/dist/tests.d.ts.map +1 -1
  241. package/dist/tests.js +30 -22
  242. package/dist/tests.js.map +1 -1
  243. package/dist/type-guards.d.ts +212 -0
  244. package/dist/type-guards.d.ts.map +1 -0
  245. package/dist/type-guards.js +318 -0
  246. package/dist/type-guards.js.map +1 -0
  247. package/dist/types.d.ts +33 -245
  248. package/dist/types.d.ts.map +1 -1
  249. package/dist/types.js +62 -72
  250. package/dist/types.js.map +1 -1
  251. package/dist/validation.d.ts +165 -0
  252. package/dist/validation.d.ts.map +1 -0
  253. package/dist/validation.js +639 -0
  254. package/dist/validation.js.map +1 -0
  255. package/dist/worker/db-provider.d.ts +168 -0
  256. package/dist/worker/db-provider.d.ts.map +1 -0
  257. package/dist/worker/db-provider.js +277 -0
  258. package/dist/worker/db-provider.js.map +1 -0
  259. package/dist/worker/index.d.ts +35 -0
  260. package/dist/worker/index.d.ts.map +1 -0
  261. package/dist/worker/index.js +37 -0
  262. package/dist/worker/index.js.map +1 -0
  263. package/dist/worker.d.ts +779 -0
  264. package/dist/worker.d.ts.map +1 -0
  265. package/dist/worker.js +2786 -0
  266. package/dist/worker.js.map +1 -0
  267. package/package.json +38 -8
  268. package/src/docs-rels/migrations/0001-init.sql +125 -0
@@ -21,6 +21,8 @@
21
21
  * @packageDocumentation
22
22
  */
23
23
  import { Semaphore } from './memory-provider.js';
24
+ import { isEntityArray, extractEntityId, extractMarkerType, isPlainObject, hasRelationElements, asCallback, asPredicate, asComparator, getSymbolProperty, asItem, } from './type-guards.js';
25
+ import { EntityNotFoundError } from './errors.js';
24
26
  // Provider resolver - will be set by schema.ts
25
27
  let providerResolver = null;
26
28
  /**
@@ -146,7 +148,7 @@ export class DBPromise {
146
148
  map(callback) {
147
149
  const parentPromise = this;
148
150
  return new DBPromise({
149
- type: this._options.type,
151
+ ...(this._options.type !== undefined && { type: this._options.type }),
150
152
  executor: async () => {
151
153
  // Resolve the parent array
152
154
  const items = await parentPromise.resolve();
@@ -165,8 +167,9 @@ export class DBPromise {
165
167
  // Create a recording proxy for this item
166
168
  const recordingProxy = createRecordingProxy(item, recording);
167
169
  // Execute callback with recording proxy to discover accesses
170
+ // Use asCallback to convert the callback to accept unknown items
168
171
  try {
169
- callback(recordingProxy, i);
172
+ asCallback(callback)(recordingProxy, i);
170
173
  }
171
174
  catch {
172
175
  // Ignore errors during recording phase - they'll surface in Phase 3
@@ -182,9 +185,11 @@ export class DBPromise {
182
185
  enrichedItems.push(enrichItemWithLoadedRelations(items[i], loadedRelations));
183
186
  }
184
187
  // Execute callback again with enriched data
188
+ // Use asCallback to convert the callback to accept unknown items
185
189
  const results = [];
190
+ const typedCallback = asCallback(callback);
186
191
  for (let i = 0; i < enrichedItems.length; i++) {
187
- results.push(callback(enrichedItems[i], i));
192
+ results.push(typedCallback(enrichedItems[i], i));
188
193
  }
189
194
  return results;
190
195
  },
@@ -196,13 +201,14 @@ export class DBPromise {
196
201
  filter(predicate) {
197
202
  const parentPromise = this;
198
203
  return new DBPromise({
199
- type: this._options.type,
204
+ ...(this._options.type !== undefined && { type: this._options.type }),
200
205
  executor: async () => {
201
206
  const items = await parentPromise.resolve();
202
207
  if (!Array.isArray(items)) {
203
208
  return items;
204
209
  }
205
- return items.filter(predicate);
210
+ // Use asPredicate to convert the predicate to accept unknown items
211
+ return items.filter(asPredicate(predicate));
206
212
  },
207
213
  });
208
214
  }
@@ -212,13 +218,14 @@ export class DBPromise {
212
218
  sort(compareFn) {
213
219
  const parentPromise = this;
214
220
  return new DBPromise({
215
- type: this._options.type,
221
+ ...(this._options.type !== undefined && { type: this._options.type }),
216
222
  executor: async () => {
217
223
  const items = await parentPromise.resolve();
218
224
  if (!Array.isArray(items)) {
219
225
  return items;
220
226
  }
221
- return [...items].sort(compareFn);
227
+ // Use asComparator to convert the compareFn to accept unknown items
228
+ return [...items].sort(asComparator(compareFn));
222
229
  },
223
230
  });
224
231
  }
@@ -228,7 +235,7 @@ export class DBPromise {
228
235
  limit(n) {
229
236
  const parentPromise = this;
230
237
  return new DBPromise({
231
- type: this._options.type,
238
+ ...(this._options.type !== undefined && { type: this._options.type }),
232
239
  executor: async () => {
233
240
  const items = await parentPromise.resolve();
234
241
  if (!Array.isArray(items)) {
@@ -244,7 +251,7 @@ export class DBPromise {
244
251
  first() {
245
252
  const parentPromise = this;
246
253
  return new DBPromise({
247
- type: this._options.type,
254
+ ...(this._options.type !== undefined && { type: this._options.type }),
248
255
  executor: async () => {
249
256
  const items = await parentPromise.resolve();
250
257
  if (Array.isArray(items)) {
@@ -300,7 +307,7 @@ export class DBPromise {
300
307
  // Persistence state
301
308
  let processedIds = new Set();
302
309
  let persistCounter = 0;
303
- const getItemId = (item) => item?.$id ?? item?.id ?? String(item);
310
+ const getItemId = (item) => extractEntityId(item) ?? String(item);
304
311
  // Get actions API from options (injected by wrapEntityOperations)
305
312
  const actionsAPI = this._options.actionsAPI;
306
313
  // Initialize persistence if enabled
@@ -319,7 +326,7 @@ export class DBPromise {
319
326
  await actionsAPI.update(actionId, { status: 'active' });
320
327
  }
321
328
  else {
322
- throw new Error(`Action ${resume} not found`);
329
+ throw new EntityNotFoundError('Action', resume, 'resume');
323
330
  }
324
331
  }
325
332
  else {
@@ -355,7 +362,7 @@ export class DBPromise {
355
362
  skipped,
356
363
  current,
357
364
  elapsed,
358
- remaining,
365
+ ...(remaining !== undefined && { remaining }),
359
366
  rate,
360
367
  };
361
368
  };
@@ -377,10 +384,10 @@ export class DBPromise {
377
384
  const getRetryDelay = (attempt) => {
378
385
  return typeof retryDelay === 'function' ? retryDelay(attempt) : retryDelay;
379
386
  };
380
- // Helper to handle error
387
+ // Helper to handle error - use asItem for type conversion
381
388
  const handleError = async (error, item, attempt) => {
382
389
  if (typeof onError === 'function') {
383
- return onError(error, item, attempt);
390
+ return onError(error, asItem(item), attempt);
384
391
  }
385
392
  return onError;
386
393
  };
@@ -399,24 +406,25 @@ export class DBPromise {
399
406
  let attempt = 0;
400
407
  while (true) {
401
408
  try {
402
- // Create timeout wrapper if needed
409
+ // Create timeout wrapper if needed - use asCallback for type conversion
403
410
  let result;
411
+ const typedCallback = asCallback(callback);
404
412
  if (timeout) {
405
413
  const timeoutPromise = new Promise((_, reject) => {
406
414
  setTimeout(() => reject(new Error(`Timeout after ${timeout}ms`)), timeout);
407
415
  });
408
416
  result = await Promise.race([
409
- Promise.resolve(callback(item, index)),
417
+ Promise.resolve(typedCallback(item, index)),
410
418
  timeoutPromise,
411
419
  ]);
412
420
  }
413
421
  else {
414
- result = await callback(item, index);
422
+ result = await typedCallback(item, index);
415
423
  }
416
424
  // Success
417
425
  completed++;
418
426
  await persistProgress(itemId);
419
- await onComplete?.(item, result, index);
427
+ await onComplete?.(asItem(item), result, index);
420
428
  onProgress?.(getProgress(index, item));
421
429
  return;
422
430
  }
@@ -504,13 +512,21 @@ export class DBPromise {
504
512
  cancelled,
505
513
  actionId,
506
514
  };
507
- await actionsAPI.update(actionId, {
515
+ const updatePayload = {
508
516
  status: cancelled ? 'failed' : 'completed',
509
517
  progress: completed + failed + skipped,
510
518
  data: { processedIds: Array.from(processedIds) },
511
519
  result: finalResult,
512
- error: cancelled ? 'Cancelled' : errors.length > 0 ? `${errors.length} items failed` : undefined,
513
- });
520
+ };
521
+ const errorMessage = cancelled
522
+ ? 'Cancelled'
523
+ : errors.length > 0
524
+ ? `${errors.length} items failed`
525
+ : undefined;
526
+ if (errorMessage !== undefined) {
527
+ updatePayload.error = errorMessage;
528
+ }
529
+ await actionsAPI.update(actionId, updatePayload);
514
530
  }
515
531
  }
516
532
  return {
@@ -521,11 +537,14 @@ export class DBPromise {
521
537
  elapsed: Date.now() - startTime,
522
538
  errors,
523
539
  cancelled,
524
- actionId,
540
+ ...(actionId !== undefined && { actionId }),
525
541
  };
526
542
  }
527
543
  /**
528
544
  * Async iteration
545
+ *
546
+ * The yield casts use a local type alias because TypeScript cannot narrow
547
+ * the conditional type `T extends (infer I)[] ? I : T` at runtime.
529
548
  */
530
549
  async *[Symbol.asyncIterator]() {
531
550
  const items = await this.resolve();
@@ -579,9 +598,50 @@ export class DBPromise {
579
598
  // =============================================================================
580
599
  // Proxy Handlers
581
600
  // =============================================================================
601
+ /**
602
+ * Known DBPromise methods that need proxy handling.
603
+ */
604
+ const DBPROMISE_METHODS = new Set([
605
+ 'map',
606
+ 'filter',
607
+ 'sort',
608
+ 'limit',
609
+ 'first',
610
+ 'forEach',
611
+ 'resolve',
612
+ 'then',
613
+ 'catch',
614
+ 'finally',
615
+ ]);
616
+ /**
617
+ * Known internal properties that need proxy handling.
618
+ */
619
+ const INTERNAL_PROPS = new Set(['accessedProps', 'path', 'isResolved']);
620
+ /**
621
+ * Access a property on DBPromise by name, with type safety.
622
+ */
623
+ function getDBPromiseProperty(target, prop) {
624
+ return target[prop];
625
+ }
626
+ /**
627
+ * Get and bind a method from DBPromise by name.
628
+ */
629
+ function bindDBPromiseMethod(target, prop) {
630
+ const method = getDBPromiseProperty(target, prop);
631
+ if (typeof method === 'function') {
632
+ return method.bind(target);
633
+ }
634
+ throw new Error(`${prop} is not a method on DBPromise`);
635
+ }
636
+ /**
637
+ * Proxy handlers for DBPromise property access tracking.
638
+ *
639
+ * Uses type-safe helper functions to access class methods and properties
640
+ * from within the ProxyHandler where the target type is DBPromise<unknown>.
641
+ */
582
642
  const DB_PROXY_HANDLERS = {
583
- get(target, prop, receiver) {
584
- // Handle symbols
643
+ get(target, prop) {
644
+ // Handle symbols - access internal symbol-keyed properties
585
645
  if (typeof prop === 'symbol') {
586
646
  if (prop === DB_PROMISE_SYMBOL)
587
647
  return true;
@@ -589,25 +649,24 @@ const DB_PROXY_HANDLERS = {
589
649
  return target;
590
650
  if (prop === Symbol.asyncIterator)
591
651
  return target[Symbol.asyncIterator].bind(target);
592
- return target[prop];
652
+ // Use getSymbolProperty for type-safe symbol access
653
+ return getSymbolProperty(target, prop);
593
654
  }
594
- // Handle promise methods
595
- if (prop === 'then' || prop === 'catch' || prop === 'finally') {
596
- return target[prop].bind(target);
655
+ // Handle promise and DBPromise methods
656
+ if (DBPROMISE_METHODS.has(prop)) {
657
+ return bindDBPromiseMethod(target, prop);
597
658
  }
598
- // Handle DBPromise methods
599
- if (['map', 'filter', 'sort', 'limit', 'first', 'forEach', 'resolve'].includes(prop)) {
600
- return target[prop].bind(target);
601
- }
602
- // Handle internal properties
603
- if (prop.startsWith('_') || ['accessedProps', 'path', 'isResolved'].includes(prop)) {
604
- return target[prop];
659
+ // Handle internal properties - private properties and getters
660
+ if (prop.startsWith('_') || INTERNAL_PROPS.has(prop)) {
661
+ return getDBPromiseProperty(target, prop);
605
662
  }
606
663
  // Track property access
607
664
  target.accessedProps.add(prop);
608
665
  // Return a new DBPromise for the property path
666
+ const internals = target;
667
+ const typeValue = internals._options?.type;
609
668
  return new DBPromise({
610
- type: target['_options']?.type,
669
+ ...(typeValue !== undefined && { type: typeValue }),
611
670
  parent: target,
612
671
  propertyPath: [...target.path, prop],
613
672
  executor: async () => {
@@ -651,9 +710,11 @@ function getNestedValue(obj, path) {
651
710
  function createBatchLoadingArray(items) {
652
711
  // Create a new array with all the original items
653
712
  const batchArray = [...items];
654
- // Override the map method to do batch loading
713
+ // Override the map method to do batch loading only when needed
714
+ // Returns synchronously when no relations are accessed (preserving native Array behavior)
715
+ // Returns a Promise when relations need to be batch-loaded
655
716
  Object.defineProperty(batchArray, 'map', {
656
- value: async function (callback) {
717
+ value: function (callback) {
657
718
  const items = this;
658
719
  // Phase 1: Record what the callback accesses using placeholder proxies
659
720
  const recordings = [];
@@ -674,20 +735,33 @@ function createBatchLoadingArray(items) {
674
735
  }
675
736
  recordings.push(recording);
676
737
  }
677
- // Phase 2: Analyze recordings and batch-load relations
678
- const batchLoads = analyzeBatchLoads(recordings, items);
679
- const loadedRelations = await executeBatchLoads(batchLoads, recordings);
680
- // Phase 3: Re-run callback with enriched items that have loaded relations
681
- const enrichedItems = [];
682
- for (let i = 0; i < items.length; i++) {
683
- enrichedItems.push(enrichItemWithLoadedRelations(items[i], loadedRelations));
684
- }
685
- // Execute callback again with enriched data
686
- const results = [];
687
- for (let i = 0; i < enrichedItems.length; i++) {
688
- results.push(callback(enrichedItems[i], i));
738
+ // Check if any relations were accessed
739
+ const hasRelations = recordings.some((r) => r.relations.size > 0);
740
+ // If no relations were accessed, run synchronously like native Array.map
741
+ if (!hasRelations) {
742
+ try {
743
+ return Array.prototype.map.call(items, (item, i) => callback(item, i));
744
+ }
745
+ catch (error) {
746
+ return Promise.reject(error);
747
+ }
689
748
  }
690
- return results;
749
+ // Phase 2 & 3: Async batch loading path (returns a Promise)
750
+ return (async () => {
751
+ const batchLoads = analyzeBatchLoads(recordings, items);
752
+ const loadedRelations = await executeBatchLoads(batchLoads, recordings);
753
+ // Re-run callback with enriched items that have loaded relations
754
+ const enrichedItems = [];
755
+ for (let i = 0; i < items.length; i++) {
756
+ enrichedItems.push(enrichItemWithLoadedRelations(items[i], loadedRelations));
757
+ }
758
+ // Execute callback again with enriched data
759
+ const results = [];
760
+ for (let i = 0; i < enrichedItems.length; i++) {
761
+ results.push(callback(enrichedItems[i], i));
762
+ }
763
+ return results;
764
+ })();
691
765
  },
692
766
  writable: true,
693
767
  configurable: true,
@@ -779,14 +853,15 @@ function createRecordingProxy(item, recording) {
779
853
  return new Proxy(item, {
780
854
  get(target, prop) {
781
855
  if (typeof prop === 'symbol') {
782
- return target[prop];
856
+ // Use getSymbolProperty for type-safe symbol access
857
+ return getSymbolProperty(target, prop);
783
858
  }
784
859
  recording.paths.add(prop);
785
860
  const value = target[prop];
786
861
  // If accessing a relation (identified by $type marker from hydration)
787
862
  // Note: proxies may not expose $type in 'has' trap, so check via property access
788
- const maybeType = value && typeof value === 'object' ? value.$type : undefined;
789
- if (maybeType && typeof maybeType === 'string') {
863
+ const maybeType = extractMarkerType(value);
864
+ if (maybeType) {
790
865
  const relationType = maybeType;
791
866
  // Get or create the relation recording
792
867
  let relationRecording = recording.relations.get(prop);
@@ -805,16 +880,17 @@ function createRecordingProxy(item, recording) {
805
880
  // Handle arrays with potential relation elements (like members: ['->User'])
806
881
  if (Array.isArray(value)) {
807
882
  // Check if the array itself is a relation array (has $type marker from thenableArray)
808
- const arrayType = value.$type;
809
- const isArrayRelation = arrayType !== undefined || value.$isArrayRelation;
883
+ const arrayMarker = isEntityArray(value) ? value : null;
884
+ const arrayType = arrayMarker?.$type;
885
+ const isArrayRelationFlag = arrayMarker !== null;
810
886
  // Also check if array contains relation proxies (for backwards compatibility)
811
- const hasRelationElements = !isArrayRelation && value.some(v => v && typeof v === 'object' && v.$type !== undefined);
812
- if (isArrayRelation || hasRelationElements) {
887
+ const hasRelationElementsFlag = !isArrayRelationFlag && hasRelationElements(value);
888
+ if (isArrayRelationFlag || hasRelationElementsFlag) {
813
889
  // Get the type from the array $type or first element
814
890
  let relationType = arrayType;
815
891
  if (!relationType) {
816
- const firstRelation = value.find(v => v && typeof v === 'object' && v.$type !== undefined);
817
- relationType = firstRelation ? firstRelation.$type : 'unknown';
892
+ const firstRelation = value.find((v) => extractMarkerType(v) !== undefined);
893
+ relationType = firstRelation ? extractMarkerType(firstRelation) ?? 'unknown' : 'unknown';
818
894
  }
819
895
  let relationRecording = recording.relations.get(prop);
820
896
  if (!relationRecording) {
@@ -852,7 +928,7 @@ function createRecordingProxy(item, recording) {
852
928
  return createRelationRecordingProxy(relationRecording);
853
929
  }
854
930
  return Reflect.get(arrayTarget, arrayProp);
855
- }
931
+ },
856
932
  });
857
933
  }
858
934
  // Regular array - wrap for recording
@@ -889,36 +965,17 @@ function analyzeBatchLoads(recordings, items) {
889
965
  // Handle array relations (e.g., members: ['id1', 'id2'])
890
966
  if (Array.isArray(relationValue)) {
891
967
  for (const element of relationValue) {
892
- if (typeof element === 'string') {
893
- ids.push(element);
894
- }
895
- else if (element && typeof element === 'object') {
896
- // Try valueOf() for thenable proxies
897
- if (typeof element.valueOf === 'function') {
898
- const val = element.valueOf();
899
- if (typeof val === 'string') {
900
- ids.push(val);
901
- }
902
- }
903
- else if (element.$id) {
904
- ids.push(element.$id);
905
- }
968
+ const elementId = extractEntityId(element);
969
+ if (elementId) {
970
+ ids.push(elementId);
906
971
  }
907
972
  }
908
973
  }
909
- else if (typeof relationValue === 'string') {
910
- ids.push(relationValue);
911
- }
912
- else if (relationValue && typeof relationValue === 'object') {
913
- // Try valueOf() for thenable proxies (single relation)
914
- if (typeof relationValue.valueOf === 'function') {
915
- const val = relationValue.valueOf();
916
- if (typeof val === 'string') {
917
- ids.push(val);
918
- }
919
- }
920
- else if (relationValue.$id) {
921
- ids.push(relationValue.$id);
974
+ else {
975
+ // Handle single relations - string ID or proxy object
976
+ const relationId = extractEntityId(relationValue);
977
+ if (relationId) {
978
+ ids.push(relationId);
922
979
  }
923
980
  }
924
981
  }
@@ -983,12 +1040,12 @@ async function executeBatchLoads(batchLoads, recordings) {
983
1040
  // Deduplicate IDs
984
1041
  const uniqueIds = [...new Set(ids)];
985
1042
  // Fetch all entities in parallel
986
- const entities = await Promise.all(uniqueIds.map(id => provider.get(type, id)));
1043
+ const entities = await Promise.all(uniqueIds.map((id) => provider.get(type, id)));
987
1044
  // Map results by ID
988
1045
  for (let i = 0; i < uniqueIds.length; i++) {
989
1046
  const entity = entities[i];
990
1047
  if (entity) {
991
- const entityId = (entity.$id || entity.id);
1048
+ const entityId = (entity['$id'] || entity['id']);
992
1049
  relationResults.set(entityId, entity);
993
1050
  }
994
1051
  }
@@ -1004,7 +1061,7 @@ async function executeBatchLoads(batchLoads, recordings) {
1004
1061
  let nestedType;
1005
1062
  for (const entity of relationResults.values()) {
1006
1063
  const entityObj = entity;
1007
- const entityType = entityObj.$type;
1064
+ const entityType = entityObj['$type'];
1008
1065
  const nestedValue = entityObj[nestedPath];
1009
1066
  if (typeof nestedValue === 'string') {
1010
1067
  // It's a string - could be an ID
@@ -1022,17 +1079,15 @@ async function executeBatchLoads(batchLoads, recordings) {
1022
1079
  }
1023
1080
  }
1024
1081
  }
1025
- else if (nestedValue && typeof nestedValue === 'object') {
1082
+ else if (isPlainObject(nestedValue)) {
1026
1083
  // Check if it has a $type marker (for already-hydrated proxies)
1027
- const valueType = nestedValue.$type;
1028
- if (valueType && typeof valueType === 'string') {
1084
+ const valueType = extractMarkerType(nestedValue);
1085
+ if (valueType) {
1029
1086
  nestedType = valueType;
1030
- // Try to get the ID
1031
- if (typeof nestedValue.valueOf === 'function') {
1032
- const val = nestedValue.valueOf();
1033
- if (typeof val === 'string') {
1034
- nestedIds.push(val);
1035
- }
1087
+ // Try to get the ID via valueOf or $id
1088
+ const nestedId = extractEntityId(nestedValue);
1089
+ if (nestedId) {
1090
+ nestedIds.push(nestedId);
1036
1091
  }
1037
1092
  }
1038
1093
  }
@@ -1044,11 +1099,13 @@ async function executeBatchLoads(batchLoads, recordings) {
1044
1099
  // Recursively load nested relations
1045
1100
  if (nestedBatchLoads.size > 0) {
1046
1101
  // Create nested recordings for the next level if available
1102
+ // The key must be the field name (not the type) so that nestedRelationInfo
1103
+ // lookups in the recursive call match by relation field name
1047
1104
  const nestedRecordings = [];
1048
- for (const nestedRecording of nestedInfo.nestedRelations.values()) {
1105
+ for (const [nestedFieldName, nestedRecording] of nestedInfo.nestedRelations) {
1049
1106
  nestedRecordings.push({
1050
1107
  paths: new Set(),
1051
- relations: new Map([[nestedRecording.type, nestedRecording]]),
1108
+ relations: new Map([[nestedFieldName, nestedRecording]]),
1052
1109
  });
1053
1110
  }
1054
1111
  const nestedResults = await executeBatchLoads(nestedBatchLoads, nestedRecordings.length > 0 ? nestedRecordings : undefined);
@@ -1076,22 +1133,7 @@ function enrichItemWithLoadedRelations(item, loadedRelations) {
1076
1133
  if (Array.isArray(relationValue)) {
1077
1134
  const loadedArray = [];
1078
1135
  for (const element of relationValue) {
1079
- let idStr;
1080
- if (typeof element === 'string') {
1081
- idStr = element;
1082
- }
1083
- else if (element && typeof element === 'object') {
1084
- // Try valueOf for thenable proxies
1085
- if (typeof element.valueOf === 'function') {
1086
- const val = element.valueOf();
1087
- if (typeof val === 'string') {
1088
- idStr = val;
1089
- }
1090
- }
1091
- if (!idStr && element.$id) {
1092
- idStr = element.$id;
1093
- }
1094
- }
1136
+ const idStr = extractEntityId(element);
1095
1137
  if (idStr) {
1096
1138
  const loaded = relationData.get(idStr);
1097
1139
  if (loaded) {
@@ -1103,23 +1145,7 @@ function enrichItemWithLoadedRelations(item, loadedRelations) {
1103
1145
  }
1104
1146
  else {
1105
1147
  // Handle single relations - get the ID from the thenable proxy or direct value
1106
- let relationId;
1107
- if (typeof relationValue === 'string') {
1108
- relationId = relationValue;
1109
- }
1110
- else if (relationValue && typeof relationValue === 'object') {
1111
- // Try to get ID from proxy's valueOf or toString
1112
- if ('valueOf' in relationValue && typeof relationValue.valueOf === 'function') {
1113
- const val = relationValue.valueOf();
1114
- if (typeof val === 'string') {
1115
- relationId = val;
1116
- }
1117
- }
1118
- // Also check for $id
1119
- if (!relationId && '$id' in relationValue) {
1120
- relationId = relationValue.$id;
1121
- }
1122
- }
1148
+ const relationId = extractEntityId(relationValue);
1123
1149
  if (relationId) {
1124
1150
  const loaded = relationData.get(relationId);
1125
1151
  if (loaded) {
@@ -1130,13 +1156,6 @@ function enrichItemWithLoadedRelations(item, loadedRelations) {
1130
1156
  }
1131
1157
  return enriched;
1132
1158
  }
1133
- /**
1134
- * Apply batch-loaded results to the mapped results (deprecated, kept for compatibility)
1135
- */
1136
- function applyBatchResults(results, loadedRelations, originalItems) {
1137
- // No longer used - enrichment happens before callback re-run
1138
- return results;
1139
- }
1140
1159
  // =============================================================================
1141
1160
  // Check Functions
1142
1161
  // =============================================================================
@@ -1154,7 +1173,9 @@ export function isDBPromise(value) {
1154
1173
  */
1155
1174
  export function getRawDBPromise(value) {
1156
1175
  if (RAW_DB_PROMISE_SYMBOL in value) {
1157
- return value[RAW_DB_PROMISE_SYMBOL];
1176
+ // We know the symbol exists from the check above, so this cast is safe
1177
+ const raw = value[RAW_DB_PROMISE_SYMBOL];
1178
+ return raw;
1158
1179
  }
1159
1180
  return value;
1160
1181
  }
@@ -1179,9 +1200,6 @@ export function createEntityPromise(type, executor) {
1179
1200
  export function createSearchPromise(type, executor) {
1180
1201
  return new DBPromise({ type, executor });
1181
1202
  }
1182
- // =============================================================================
1183
- // Entity Operations Wrapper
1184
- // =============================================================================
1185
1203
  /**
1186
1204
  * Wrap EntityOperations to return DBPromise
1187
1205
  */
@@ -1191,28 +1209,28 @@ export function wrapEntityOperations(typeName, operations, actionsAPI) {
1191
1209
  return new DBPromise({
1192
1210
  type: typeName,
1193
1211
  executor: () => operations.get(id),
1194
- actionsAPI,
1212
+ ...(actionsAPI !== undefined && { actionsAPI }),
1195
1213
  });
1196
1214
  },
1197
1215
  list(options) {
1198
1216
  return new DBPromise({
1199
1217
  type: typeName,
1200
1218
  executor: () => operations.list(options),
1201
- actionsAPI,
1219
+ ...(actionsAPI !== undefined && { actionsAPI }),
1202
1220
  });
1203
1221
  },
1204
1222
  find(where) {
1205
1223
  return new DBPromise({
1206
1224
  type: typeName,
1207
1225
  executor: () => operations.find(where),
1208
- actionsAPI,
1226
+ ...(actionsAPI !== undefined && { actionsAPI }),
1209
1227
  });
1210
1228
  },
1211
1229
  search(query, options) {
1212
1230
  return new DBPromise({
1213
1231
  type: typeName,
1214
1232
  executor: () => operations.search(query, options),
1215
- actionsAPI,
1233
+ ...(actionsAPI !== undefined && { actionsAPI }),
1216
1234
  });
1217
1235
  },
1218
1236
  first() {
@@ -1222,7 +1240,7 @@ export function wrapEntityOperations(typeName, operations, actionsAPI) {
1222
1240
  const items = await operations.list({ limit: 1 });
1223
1241
  return items[0] ?? null;
1224
1242
  },
1225
- actionsAPI,
1243
+ ...(actionsAPI !== undefined && { actionsAPI }),
1226
1244
  });
1227
1245
  },
1228
1246
  /**
@@ -1243,7 +1261,9 @@ export function wrapEntityOperations(typeName, operations, actionsAPI) {
1243
1261
  */
1244
1262
  async forEach(callbackOrOptions, callbackOrOpts) {
1245
1263
  // Detect which calling style is being used
1246
- const isOptionsFirst = typeof callbackOrOptions === 'object' && callbackOrOptions !== null && !('call' in callbackOrOptions);
1264
+ const isOptionsFirst = typeof callbackOrOptions === 'object' &&
1265
+ callbackOrOptions !== null &&
1266
+ !('call' in callbackOrOptions);
1247
1267
  const callback = isOptionsFirst
1248
1268
  ? callbackOrOpts
1249
1269
  : callbackOrOptions;
@@ -1255,26 +1275,27 @@ export function wrapEntityOperations(typeName, operations, actionsAPI) {
1255
1275
  const listPromise = new DBPromise({
1256
1276
  type: typeName,
1257
1277
  executor: () => operations.list(listOptions),
1258
- actionsAPI,
1278
+ ...(actionsAPI !== undefined && { actionsAPI }),
1259
1279
  });
1260
- return listPromise.forEach(callback, options);
1280
+ return listPromise.forEach(asCallback(callback), options);
1261
1281
  },
1262
- // Semantic search methods
1282
+ // Semantic search methods - delegate to underlying operations
1283
+ // Return empty array if not available for graceful degradation
1263
1284
  semanticSearch(query, options) {
1264
- if (operations.semanticSearch) {
1265
- return operations.semanticSearch(query, options);
1285
+ if (!operations.semanticSearch) {
1286
+ // Return empty array for graceful degradation when semantic search not available
1287
+ return Promise.resolve([]);
1266
1288
  }
1267
- // Fallback: return empty array if not supported
1268
- return Promise.resolve([]);
1289
+ return operations.semanticSearch(query, options);
1269
1290
  },
1270
1291
  hybridSearch(query, options) {
1271
- if (operations.hybridSearch) {
1272
- return operations.hybridSearch(query, options);
1292
+ if (!operations.hybridSearch) {
1293
+ // Return empty array for graceful degradation when hybrid search not available
1294
+ return Promise.resolve([]);
1273
1295
  }
1274
- // Fallback: return empty array if not supported
1275
- return Promise.resolve([]);
1296
+ return operations.hybridSearch(query, options);
1276
1297
  },
1277
- // Mutations don't need wrapping
1298
+ // Mutations - pass through create which supports both (data, options?) and (id, data, options?)
1278
1299
  create: operations.create,
1279
1300
  update: operations.update,
1280
1301
  upsert: operations.upsert,