ai-database 0.1.0 → 2.0.1

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 (72) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +381 -68
  4. package/TESTING.md +410 -0
  5. package/TEST_SUMMARY.md +250 -0
  6. package/TODO.md +128 -0
  7. package/dist/ai-promise-db.d.ts +370 -0
  8. package/dist/ai-promise-db.d.ts.map +1 -0
  9. package/dist/ai-promise-db.js +839 -0
  10. package/dist/ai-promise-db.js.map +1 -0
  11. package/dist/authorization.d.ts +531 -0
  12. package/dist/authorization.d.ts.map +1 -0
  13. package/dist/authorization.js +632 -0
  14. package/dist/authorization.js.map +1 -0
  15. package/dist/durable-clickhouse.d.ts +193 -0
  16. package/dist/durable-clickhouse.d.ts.map +1 -0
  17. package/dist/durable-clickhouse.js +422 -0
  18. package/dist/durable-clickhouse.js.map +1 -0
  19. package/dist/durable-promise.d.ts +182 -0
  20. package/dist/durable-promise.d.ts.map +1 -0
  21. package/dist/durable-promise.js +409 -0
  22. package/dist/durable-promise.js.map +1 -0
  23. package/dist/execution-queue.d.ts +239 -0
  24. package/dist/execution-queue.d.ts.map +1 -0
  25. package/dist/execution-queue.js +400 -0
  26. package/dist/execution-queue.js.map +1 -0
  27. package/dist/index.d.ts +50 -191
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -462
  30. package/dist/index.js.map +1 -0
  31. package/dist/linguistic.d.ts +115 -0
  32. package/dist/linguistic.d.ts.map +1 -0
  33. package/dist/linguistic.js +379 -0
  34. package/dist/linguistic.js.map +1 -0
  35. package/dist/memory-provider.d.ts +304 -0
  36. package/dist/memory-provider.d.ts.map +1 -0
  37. package/dist/memory-provider.js +785 -0
  38. package/dist/memory-provider.js.map +1 -0
  39. package/dist/schema.d.ts +899 -0
  40. package/dist/schema.d.ts.map +1 -0
  41. package/dist/schema.js +1165 -0
  42. package/dist/schema.js.map +1 -0
  43. package/dist/tests.d.ts +107 -0
  44. package/dist/tests.d.ts.map +1 -0
  45. package/dist/tests.js +568 -0
  46. package/dist/tests.js.map +1 -0
  47. package/dist/types.d.ts +972 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +126 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +37 -37
  52. package/src/ai-promise-db.ts +1243 -0
  53. package/src/authorization.ts +1102 -0
  54. package/src/durable-clickhouse.ts +596 -0
  55. package/src/durable-promise.ts +582 -0
  56. package/src/execution-queue.ts +608 -0
  57. package/src/index.test.ts +868 -0
  58. package/src/index.ts +337 -0
  59. package/src/linguistic.ts +404 -0
  60. package/src/memory-provider.test.ts +1036 -0
  61. package/src/memory-provider.ts +1119 -0
  62. package/src/schema.test.ts +1254 -0
  63. package/src/schema.ts +2296 -0
  64. package/src/tests.ts +725 -0
  65. package/src/types.ts +1177 -0
  66. package/test/README.md +153 -0
  67. package/test/edge-cases.test.ts +646 -0
  68. package/test/provider-resolution.test.ts +402 -0
  69. package/tsconfig.json +9 -0
  70. package/vitest.config.ts +19 -0
  71. package/dist/index.d.mts +0 -195
  72. package/dist/index.mjs +0 -430
@@ -0,0 +1,785 @@
1
+ /**
2
+ * In-memory Database Provider
3
+ *
4
+ * Simple provider implementation for testing and development.
5
+ * Includes concurrency control via Semaphore for rate limiting.
6
+ */
7
+ // =============================================================================
8
+ // Semaphore for Concurrency Control
9
+ // =============================================================================
10
+ /**
11
+ * Simple semaphore for concurrency control
12
+ * Used to limit parallel operations (e.g., embedding, generation)
13
+ */
14
+ export class Semaphore {
15
+ concurrency;
16
+ queue = [];
17
+ running = 0;
18
+ constructor(concurrency) {
19
+ this.concurrency = concurrency;
20
+ }
21
+ /**
22
+ * Acquire a slot. Returns a release function.
23
+ */
24
+ async acquire() {
25
+ if (this.running < this.concurrency) {
26
+ this.running++;
27
+ return () => this.release();
28
+ }
29
+ return new Promise((resolve) => {
30
+ this.queue.push(() => {
31
+ this.running++;
32
+ resolve(() => this.release());
33
+ });
34
+ });
35
+ }
36
+ release() {
37
+ this.running--;
38
+ const next = this.queue.shift();
39
+ if (next)
40
+ next();
41
+ }
42
+ /**
43
+ * Run a function with concurrency control
44
+ */
45
+ async run(fn) {
46
+ const release = await this.acquire();
47
+ try {
48
+ return await fn();
49
+ }
50
+ finally {
51
+ release();
52
+ }
53
+ }
54
+ /**
55
+ * Run multiple functions with concurrency control
56
+ */
57
+ async map(items, fn) {
58
+ return Promise.all(items.map((item) => this.run(() => fn(item))));
59
+ }
60
+ get pending() {
61
+ return this.queue.length;
62
+ }
63
+ get active() {
64
+ return this.running;
65
+ }
66
+ }
67
+ // =============================================================================
68
+ // Generate ID
69
+ // =============================================================================
70
+ function generateId() {
71
+ return crypto.randomUUID();
72
+ }
73
+ // =============================================================================
74
+ // Verb Conjugation (Linguistic Helpers)
75
+ // =============================================================================
76
+ /**
77
+ * Conjugate a verb to get all forms
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * conjugateVerb('create')
82
+ * // => { action: 'create', act: 'creates', activity: 'creating' }
83
+ *
84
+ * conjugateVerb('publish')
85
+ * // => { action: 'publish', act: 'publishes', activity: 'publishing' }
86
+ * ```
87
+ */
88
+ function conjugateVerb(verb) {
89
+ const base = verb.toLowerCase();
90
+ // Known verbs with pre-defined conjugations
91
+ const known = {
92
+ create: { act: 'creates', activity: 'creating' },
93
+ update: { act: 'updates', activity: 'updating' },
94
+ delete: { act: 'deletes', activity: 'deleting' },
95
+ publish: { act: 'publishes', activity: 'publishing' },
96
+ archive: { act: 'archives', activity: 'archiving' },
97
+ generate: { act: 'generates', activity: 'generating' },
98
+ process: { act: 'processes', activity: 'processing' },
99
+ sync: { act: 'syncs', activity: 'syncing' },
100
+ import: { act: 'imports', activity: 'importing' },
101
+ export: { act: 'exports', activity: 'exporting' },
102
+ run: { act: 'runs', activity: 'running' },
103
+ execute: { act: 'executes', activity: 'executing' },
104
+ send: { act: 'sends', activity: 'sending' },
105
+ fetch: { act: 'fetches', activity: 'fetching' },
106
+ build: { act: 'builds', activity: 'building' },
107
+ deploy: { act: 'deploys', activity: 'deploying' },
108
+ };
109
+ if (known[base]) {
110
+ return { action: base, ...known[base] };
111
+ }
112
+ // Auto-conjugate unknown verbs
113
+ return {
114
+ action: base,
115
+ act: toPresent(base),
116
+ activity: toGerund(base),
117
+ };
118
+ }
119
+ /** Check if character is a vowel */
120
+ function isVowel(char) {
121
+ return char ? 'aeiou'.includes(char.toLowerCase()) : false;
122
+ }
123
+ /** Check if we should double the final consonant */
124
+ function shouldDoubleConsonant(verb) {
125
+ if (verb.length < 2)
126
+ return false;
127
+ const last = verb[verb.length - 1];
128
+ const secondLast = verb[verb.length - 2];
129
+ if ('wxy'.includes(last))
130
+ return false;
131
+ if (isVowel(last) || !isVowel(secondLast))
132
+ return false;
133
+ // Short words (3 letters) almost always double
134
+ if (verb.length <= 3)
135
+ return true;
136
+ return false;
137
+ }
138
+ /** Convert verb to present 3rd person (create → creates) */
139
+ function toPresent(verb) {
140
+ if (verb.endsWith('y') && !isVowel(verb[verb.length - 2])) {
141
+ return verb.slice(0, -1) + 'ies';
142
+ }
143
+ if (verb.endsWith('s') || verb.endsWith('x') || verb.endsWith('z') ||
144
+ verb.endsWith('ch') || verb.endsWith('sh')) {
145
+ return verb + 'es';
146
+ }
147
+ return verb + 's';
148
+ }
149
+ /** Convert verb to gerund (create → creating) */
150
+ function toGerund(verb) {
151
+ if (verb.endsWith('ie'))
152
+ return verb.slice(0, -2) + 'ying';
153
+ if (verb.endsWith('e') && !verb.endsWith('ee'))
154
+ return verb.slice(0, -1) + 'ing';
155
+ if (shouldDoubleConsonant(verb)) {
156
+ return verb + verb[verb.length - 1] + 'ing';
157
+ }
158
+ return verb + 'ing';
159
+ }
160
+ // =============================================================================
161
+ // In-memory Provider
162
+ // =============================================================================
163
+ /**
164
+ * In-memory storage for entities, relationships, events, actions, and artifacts
165
+ */
166
+ export class MemoryProvider {
167
+ // Things: type -> id -> entity
168
+ entities = new Map();
169
+ // Relationships: from:relation -> Set<to>
170
+ relations = new Map();
171
+ // Events: chronological log
172
+ events = [];
173
+ eventHandlers = new Map();
174
+ // Actions: id -> action
175
+ actions = new Map();
176
+ // Artifacts: url:type -> artifact
177
+ artifacts = new Map();
178
+ // Concurrency control
179
+ semaphore;
180
+ constructor(options = {}) {
181
+ this.semaphore = new Semaphore(options.concurrency ?? 10);
182
+ }
183
+ // ===========================================================================
184
+ // Things (Records)
185
+ // ===========================================================================
186
+ getTypeStore(type) {
187
+ if (!this.entities.has(type)) {
188
+ this.entities.set(type, new Map());
189
+ }
190
+ return this.entities.get(type);
191
+ }
192
+ async get(type, id) {
193
+ const store = this.getTypeStore(type);
194
+ const entity = store.get(id);
195
+ return entity ? { ...entity, $id: id, $type: type } : null;
196
+ }
197
+ async list(type, options) {
198
+ const store = this.getTypeStore(type);
199
+ let results = [];
200
+ for (const [id, entity] of store) {
201
+ const full = { ...entity, $id: id, $type: type };
202
+ // Apply where filter
203
+ if (options?.where) {
204
+ let matches = true;
205
+ for (const [key, value] of Object.entries(options.where)) {
206
+ if (full[key] !== value) {
207
+ matches = false;
208
+ break;
209
+ }
210
+ }
211
+ if (!matches)
212
+ continue;
213
+ }
214
+ results.push(full);
215
+ }
216
+ // Sort
217
+ if (options?.orderBy) {
218
+ const field = options.orderBy;
219
+ const dir = options.order === 'desc' ? -1 : 1;
220
+ results.sort((a, b) => {
221
+ const aVal = a[field];
222
+ const bVal = b[field];
223
+ if (aVal === undefined && bVal === undefined)
224
+ return 0;
225
+ if (aVal === undefined)
226
+ return dir;
227
+ if (bVal === undefined)
228
+ return -dir;
229
+ if (aVal < bVal)
230
+ return -dir;
231
+ if (aVal > bVal)
232
+ return dir;
233
+ return 0;
234
+ });
235
+ }
236
+ // Paginate
237
+ if (options?.offset) {
238
+ results = results.slice(options.offset);
239
+ }
240
+ if (options?.limit) {
241
+ results = results.slice(0, options.limit);
242
+ }
243
+ return results;
244
+ }
245
+ async search(type, query, options) {
246
+ const all = await this.list(type, options);
247
+ const queryLower = query.toLowerCase();
248
+ const fields = options?.fields || ['$all'];
249
+ const scored = [];
250
+ for (const entity of all) {
251
+ let searchText;
252
+ if (fields.includes('$all')) {
253
+ searchText = JSON.stringify(entity).toLowerCase();
254
+ }
255
+ else {
256
+ searchText = fields
257
+ .map((f) => String(entity[f] || ''))
258
+ .join(' ')
259
+ .toLowerCase();
260
+ }
261
+ if (searchText.includes(queryLower)) {
262
+ const index = searchText.indexOf(queryLower);
263
+ const score = 1 - index / searchText.length;
264
+ if (!options?.minScore || score >= options.minScore) {
265
+ scored.push({ entity, score });
266
+ }
267
+ }
268
+ }
269
+ scored.sort((a, b) => b.score - a.score);
270
+ return scored.map((s) => s.entity);
271
+ }
272
+ async create(type, id, data) {
273
+ const store = this.getTypeStore(type);
274
+ const entityId = id || generateId();
275
+ if (store.has(entityId)) {
276
+ throw new Error(`Entity already exists: ${type}/${entityId}`);
277
+ }
278
+ const entity = {
279
+ ...data,
280
+ createdAt: new Date().toISOString(),
281
+ updatedAt: new Date().toISOString(),
282
+ };
283
+ store.set(entityId, entity);
284
+ // Emit event
285
+ await this.emit(`${type}.created`, { $id: entityId, $type: type, ...entity });
286
+ return { ...entity, $id: entityId, $type: type };
287
+ }
288
+ async update(type, id, data) {
289
+ const store = this.getTypeStore(type);
290
+ const existing = store.get(id);
291
+ if (!existing) {
292
+ throw new Error(`Entity not found: ${type}/${id}`);
293
+ }
294
+ const updated = {
295
+ ...existing,
296
+ ...data,
297
+ updatedAt: new Date().toISOString(),
298
+ };
299
+ store.set(id, updated);
300
+ // Emit event
301
+ await this.emit(`${type}.updated`, { $id: id, $type: type, ...updated });
302
+ // Invalidate artifacts when data changes
303
+ await this.invalidateArtifacts(`${type}/${id}`);
304
+ return { ...updated, $id: id, $type: type };
305
+ }
306
+ async delete(type, id) {
307
+ const store = this.getTypeStore(type);
308
+ if (!store.has(id)) {
309
+ return false;
310
+ }
311
+ store.delete(id);
312
+ // Emit event
313
+ await this.emit(`${type}.deleted`, { $id: id, $type: type });
314
+ // Clean up relations
315
+ for (const [key, targets] of this.relations) {
316
+ if (key.startsWith(`${type}:${id}:`)) {
317
+ this.relations.delete(key);
318
+ }
319
+ targets.delete(`${type}:${id}`);
320
+ }
321
+ // Clean up artifacts
322
+ await this.deleteArtifact(`${type}/${id}`);
323
+ return true;
324
+ }
325
+ // ===========================================================================
326
+ // Relationships
327
+ // ===========================================================================
328
+ relationKey(fromType, fromId, relation) {
329
+ return `${fromType}:${fromId}:${relation}`;
330
+ }
331
+ async related(type, id, relation) {
332
+ const key = this.relationKey(type, id, relation);
333
+ const targets = this.relations.get(key);
334
+ if (!targets)
335
+ return [];
336
+ const results = [];
337
+ for (const target of targets) {
338
+ const [targetType, targetId] = target.split(':');
339
+ const entity = await this.get(targetType, targetId);
340
+ if (entity) {
341
+ results.push(entity);
342
+ }
343
+ }
344
+ return results;
345
+ }
346
+ async relate(fromType, fromId, relation, toType, toId) {
347
+ const key = this.relationKey(fromType, fromId, relation);
348
+ if (!this.relations.has(key)) {
349
+ this.relations.set(key, new Set());
350
+ }
351
+ this.relations.get(key).add(`${toType}:${toId}`);
352
+ // Emit event
353
+ await this.emit('Relation.created', {
354
+ from: `${fromType}/${fromId}`,
355
+ type: relation,
356
+ to: `${toType}/${toId}`,
357
+ });
358
+ }
359
+ async unrelate(fromType, fromId, relation, toType, toId) {
360
+ const key = this.relationKey(fromType, fromId, relation);
361
+ const targets = this.relations.get(key);
362
+ if (targets) {
363
+ targets.delete(`${toType}:${toId}`);
364
+ // Emit event
365
+ await this.emit('Relation.deleted', {
366
+ from: `${fromType}/${fromId}`,
367
+ type: relation,
368
+ to: `${toType}/${toId}`,
369
+ });
370
+ }
371
+ }
372
+ // ===========================================================================
373
+ // Events (Actor-Event-Object-Result pattern)
374
+ // ===========================================================================
375
+ /**
376
+ * Emit an event using Actor-Event-Object-Result pattern
377
+ *
378
+ * @example
379
+ * ```ts
380
+ * // New pattern
381
+ * await provider.emit({
382
+ * actor: 'user:john',
383
+ * event: 'Post.created',
384
+ * object: 'Post/hello-world',
385
+ * objectData: { title: 'Hello World' },
386
+ * })
387
+ *
388
+ * // Legacy pattern (still supported)
389
+ * await provider.emit('Post.created', { title: 'Hello World' })
390
+ * ```
391
+ */
392
+ async emit(eventOrType, data) {
393
+ let event;
394
+ if (typeof eventOrType === 'string') {
395
+ // Legacy pattern: emit('Post.created', { ... })
396
+ event = {
397
+ id: generateId(),
398
+ actor: 'system',
399
+ event: eventOrType,
400
+ objectData: data,
401
+ timestamp: new Date(),
402
+ // Legacy fields
403
+ type: eventOrType,
404
+ data,
405
+ };
406
+ }
407
+ else {
408
+ // New pattern: emit({ actor, event, object, ... })
409
+ event = {
410
+ id: generateId(),
411
+ actor: eventOrType.actor ?? 'system',
412
+ actorData: eventOrType.actorData,
413
+ event: eventOrType.event,
414
+ object: eventOrType.object,
415
+ objectData: eventOrType.objectData,
416
+ result: eventOrType.result,
417
+ resultData: eventOrType.resultData,
418
+ meta: eventOrType.meta,
419
+ timestamp: new Date(),
420
+ // Legacy fields
421
+ type: eventOrType.event,
422
+ };
423
+ }
424
+ this.events.push(event);
425
+ // Trigger handlers (with concurrency control)
426
+ const handlers = this.getEventHandlers(event.event);
427
+ await this.semaphore.map(handlers, (handler) => Promise.resolve(handler(event)));
428
+ return event;
429
+ }
430
+ getEventHandlers(type) {
431
+ const handlers = [];
432
+ for (const [pattern, patternHandlers] of this.eventHandlers) {
433
+ if (this.matchesPattern(type, pattern)) {
434
+ handlers.push(...patternHandlers);
435
+ }
436
+ }
437
+ return handlers;
438
+ }
439
+ matchesPattern(type, pattern) {
440
+ if (pattern === type)
441
+ return true;
442
+ if (pattern === '*')
443
+ return true;
444
+ if (pattern.endsWith('.*')) {
445
+ const prefix = pattern.slice(0, -2);
446
+ return type.startsWith(prefix + '.');
447
+ }
448
+ if (pattern.startsWith('*.')) {
449
+ const suffix = pattern.slice(2);
450
+ return type.endsWith('.' + suffix);
451
+ }
452
+ return false;
453
+ }
454
+ on(pattern, handler) {
455
+ if (!this.eventHandlers.has(pattern)) {
456
+ this.eventHandlers.set(pattern, []);
457
+ }
458
+ this.eventHandlers.get(pattern).push(handler);
459
+ // Return unsubscribe function
460
+ return () => {
461
+ const handlers = this.eventHandlers.get(pattern);
462
+ if (handlers) {
463
+ const index = handlers.indexOf(handler);
464
+ if (index !== -1)
465
+ handlers.splice(index, 1);
466
+ }
467
+ };
468
+ }
469
+ async listEvents(options) {
470
+ let results = [...this.events];
471
+ // Filter by event pattern
472
+ const eventPattern = options?.event ?? options?.type;
473
+ if (eventPattern) {
474
+ results = results.filter((e) => this.matchesPattern(e.event, eventPattern));
475
+ }
476
+ if (options?.actor) {
477
+ results = results.filter((e) => e.actor === options.actor);
478
+ }
479
+ if (options?.object) {
480
+ results = results.filter((e) => e.object === options.object);
481
+ }
482
+ if (options?.since) {
483
+ results = results.filter((e) => e.timestamp >= options.since);
484
+ }
485
+ if (options?.until) {
486
+ results = results.filter((e) => e.timestamp <= options.until);
487
+ }
488
+ if (options?.limit) {
489
+ results = results.slice(-options.limit);
490
+ }
491
+ return results;
492
+ }
493
+ async replayEvents(options) {
494
+ const events = await this.listEvents({
495
+ event: options.event ?? options.type,
496
+ actor: options.actor,
497
+ since: options.since,
498
+ });
499
+ for (const event of events) {
500
+ await this.semaphore.run(() => Promise.resolve(options.handler(event)));
501
+ }
502
+ }
503
+ // ===========================================================================
504
+ // Actions (Linguistic Verb Pattern)
505
+ // ===========================================================================
506
+ /**
507
+ * Create an action with automatic verb conjugation
508
+ *
509
+ * @example
510
+ * ```ts
511
+ * // New pattern with verb conjugation
512
+ * const action = await provider.createAction({
513
+ * actor: 'system',
514
+ * action: 'generate', // auto-conjugates to act='generates', activity='generating'
515
+ * object: 'Post',
516
+ * objectData: { count: 100 },
517
+ * total: 100,
518
+ * })
519
+ *
520
+ * // Legacy pattern (still supported)
521
+ * const action = await provider.createAction({
522
+ * type: 'generate',
523
+ * data: { count: 100 },
524
+ * total: 100,
525
+ * })
526
+ * ```
527
+ */
528
+ async createAction(data) {
529
+ // Get base verb from action or legacy type
530
+ const baseVerb = data.action ?? data.type ?? 'process';
531
+ // Auto-conjugate verb forms
532
+ const conjugated = conjugateVerb(baseVerb);
533
+ const action = {
534
+ id: generateId(),
535
+ actor: data.actor ?? 'system',
536
+ actorData: data.actorData,
537
+ act: conjugated.act,
538
+ action: conjugated.action,
539
+ activity: conjugated.activity,
540
+ object: data.object,
541
+ objectData: data.objectData ?? data.data,
542
+ status: 'pending',
543
+ progress: 0,
544
+ total: data.total,
545
+ meta: data.meta,
546
+ createdAt: new Date(),
547
+ // Legacy fields
548
+ type: baseVerb,
549
+ data: data.data,
550
+ };
551
+ this.actions.set(action.id, action);
552
+ await this.emit({
553
+ actor: action.actor,
554
+ actorData: action.actorData,
555
+ event: 'Action.created',
556
+ object: action.id,
557
+ objectData: { action: action.action, object: action.object },
558
+ });
559
+ return action;
560
+ }
561
+ async getAction(id) {
562
+ return this.actions.get(id) ?? null;
563
+ }
564
+ async updateAction(id, updates) {
565
+ const action = this.actions.get(id);
566
+ if (!action) {
567
+ throw new Error(`Action not found: ${id}`);
568
+ }
569
+ Object.assign(action, updates);
570
+ if (updates.status === 'active' && !action.startedAt) {
571
+ action.startedAt = new Date();
572
+ await this.emit({
573
+ actor: action.actor,
574
+ event: 'Action.started',
575
+ object: action.id,
576
+ objectData: { action: action.action, activity: action.activity },
577
+ });
578
+ }
579
+ if (updates.status === 'completed') {
580
+ action.completedAt = new Date();
581
+ await this.emit({
582
+ actor: action.actor,
583
+ event: 'Action.completed',
584
+ object: action.id,
585
+ objectData: { action: action.action },
586
+ result: action.object,
587
+ resultData: action.result,
588
+ });
589
+ }
590
+ if (updates.status === 'failed') {
591
+ action.completedAt = new Date();
592
+ await this.emit({
593
+ actor: action.actor,
594
+ event: 'Action.failed',
595
+ object: action.id,
596
+ objectData: { action: action.action, error: action.error },
597
+ });
598
+ }
599
+ if (updates.status === 'cancelled') {
600
+ action.completedAt = new Date();
601
+ await this.emit({
602
+ actor: action.actor,
603
+ event: 'Action.cancelled',
604
+ object: action.id,
605
+ objectData: { action: action.action },
606
+ });
607
+ }
608
+ return action;
609
+ }
610
+ async listActions(options) {
611
+ let results = Array.from(this.actions.values());
612
+ if (options?.status) {
613
+ results = results.filter((a) => a.status === options.status);
614
+ }
615
+ // Filter by action or legacy type
616
+ const actionFilter = options?.action ?? options?.type;
617
+ if (actionFilter) {
618
+ results = results.filter((a) => a.action === actionFilter);
619
+ }
620
+ if (options?.actor) {
621
+ results = results.filter((a) => a.actor === options.actor);
622
+ }
623
+ if (options?.object) {
624
+ results = results.filter((a) => a.object === options.object);
625
+ }
626
+ if (options?.since) {
627
+ results = results.filter((a) => a.createdAt >= options.since);
628
+ }
629
+ if (options?.until) {
630
+ results = results.filter((a) => a.createdAt <= options.until);
631
+ }
632
+ if (options?.limit) {
633
+ results = results.slice(0, options.limit);
634
+ }
635
+ return results;
636
+ }
637
+ async retryAction(id) {
638
+ const action = this.actions.get(id);
639
+ if (!action) {
640
+ throw new Error(`Action not found: ${id}`);
641
+ }
642
+ if (action.status !== 'failed') {
643
+ throw new Error(`Can only retry failed actions: ${id}`);
644
+ }
645
+ action.status = 'pending';
646
+ action.error = undefined;
647
+ action.startedAt = undefined;
648
+ action.completedAt = undefined;
649
+ await this.emit({
650
+ actor: action.actor,
651
+ event: 'Action.retried',
652
+ object: action.id,
653
+ objectData: { action: action.action },
654
+ });
655
+ return action;
656
+ }
657
+ async cancelAction(id) {
658
+ const action = this.actions.get(id);
659
+ if (!action) {
660
+ throw new Error(`Action not found: ${id}`);
661
+ }
662
+ if (action.status === 'completed' || action.status === 'failed' || action.status === 'cancelled') {
663
+ throw new Error(`Cannot cancel finished action: ${id}`);
664
+ }
665
+ action.status = 'cancelled';
666
+ action.completedAt = new Date();
667
+ await this.emit({
668
+ actor: action.actor,
669
+ event: 'Action.cancelled',
670
+ object: action.id,
671
+ objectData: { action: action.action },
672
+ });
673
+ }
674
+ // ===========================================================================
675
+ // Artifacts
676
+ // ===========================================================================
677
+ artifactKey(url, type) {
678
+ return `${url}:${type}`;
679
+ }
680
+ async getArtifact(url, type) {
681
+ return this.artifacts.get(this.artifactKey(url, type)) ?? null;
682
+ }
683
+ async setArtifact(url, type, data) {
684
+ const artifact = {
685
+ url,
686
+ type,
687
+ sourceHash: data.sourceHash,
688
+ content: data.content,
689
+ metadata: data.metadata,
690
+ createdAt: new Date(),
691
+ };
692
+ this.artifacts.set(this.artifactKey(url, type), artifact);
693
+ }
694
+ async deleteArtifact(url, type) {
695
+ if (type) {
696
+ this.artifacts.delete(this.artifactKey(url, type));
697
+ }
698
+ else {
699
+ // Delete all artifacts for this URL
700
+ for (const key of this.artifacts.keys()) {
701
+ if (key.startsWith(`${url}:`)) {
702
+ this.artifacts.delete(key);
703
+ }
704
+ }
705
+ }
706
+ }
707
+ async invalidateArtifacts(url) {
708
+ // Keep embedding artifact but mark others for regeneration
709
+ for (const [key, artifact] of this.artifacts) {
710
+ if (key.startsWith(`${url}:`) && artifact.type !== 'embedding') {
711
+ this.artifacts.delete(key);
712
+ }
713
+ }
714
+ }
715
+ async listArtifacts(url) {
716
+ const results = [];
717
+ for (const [key, artifact] of this.artifacts) {
718
+ if (key.startsWith(`${url}:`)) {
719
+ results.push(artifact);
720
+ }
721
+ }
722
+ return results;
723
+ }
724
+ // ===========================================================================
725
+ // Utilities
726
+ // ===========================================================================
727
+ /**
728
+ * Run an operation with concurrency control
729
+ */
730
+ async withConcurrency(fn) {
731
+ return this.semaphore.run(fn);
732
+ }
733
+ /**
734
+ * Run multiple operations with concurrency control
735
+ */
736
+ async mapWithConcurrency(items, fn) {
737
+ return this.semaphore.map(items, fn);
738
+ }
739
+ /**
740
+ * Clear all data (useful for testing)
741
+ */
742
+ clear() {
743
+ this.entities.clear();
744
+ this.relations.clear();
745
+ this.events.length = 0;
746
+ this.actions.clear();
747
+ this.artifacts.clear();
748
+ this.eventHandlers.clear();
749
+ }
750
+ /**
751
+ * Get stats
752
+ */
753
+ stats() {
754
+ let entityCount = 0;
755
+ for (const store of this.entities.values()) {
756
+ entityCount += store.size;
757
+ }
758
+ let relationCount = 0;
759
+ for (const targets of this.relations.values()) {
760
+ relationCount += targets.size;
761
+ }
762
+ const actionStats = { pending: 0, active: 0, completed: 0, failed: 0, cancelled: 0 };
763
+ for (const action of this.actions.values()) {
764
+ actionStats[action.status]++;
765
+ }
766
+ return {
767
+ entities: entityCount,
768
+ relations: relationCount,
769
+ events: this.events.length,
770
+ actions: actionStats,
771
+ artifacts: this.artifacts.size,
772
+ concurrency: {
773
+ active: this.semaphore.active,
774
+ pending: this.semaphore.pending,
775
+ },
776
+ };
777
+ }
778
+ }
779
+ /**
780
+ * Create an in-memory provider
781
+ */
782
+ export function createMemoryProvider(options) {
783
+ return new MemoryProvider(options);
784
+ }
785
+ //# sourceMappingURL=memory-provider.js.map