ai-database 2.0.1 → 2.1.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 (88) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/actions.d.ts +247 -0
  3. package/dist/actions.d.ts.map +1 -0
  4. package/dist/actions.js +260 -0
  5. package/dist/actions.js.map +1 -0
  6. package/dist/ai-promise-db.d.ts +34 -2
  7. package/dist/ai-promise-db.d.ts.map +1 -1
  8. package/dist/ai-promise-db.js +511 -66
  9. package/dist/ai-promise-db.js.map +1 -1
  10. package/dist/constants.d.ts +16 -0
  11. package/dist/constants.d.ts.map +1 -0
  12. package/dist/constants.js +16 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/events.d.ts +153 -0
  15. package/dist/events.d.ts.map +1 -0
  16. package/dist/events.js +154 -0
  17. package/dist/events.js.map +1 -0
  18. package/dist/index.d.ts +8 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +13 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/memory-provider.d.ts +144 -2
  23. package/dist/memory-provider.d.ts.map +1 -1
  24. package/dist/memory-provider.js +569 -13
  25. package/dist/memory-provider.js.map +1 -1
  26. package/dist/schema/cascade.d.ts +96 -0
  27. package/dist/schema/cascade.d.ts.map +1 -0
  28. package/dist/schema/cascade.js +528 -0
  29. package/dist/schema/cascade.js.map +1 -0
  30. package/dist/schema/index.d.ts +197 -0
  31. package/dist/schema/index.d.ts.map +1 -0
  32. package/dist/schema/index.js +1211 -0
  33. package/dist/schema/index.js.map +1 -0
  34. package/dist/schema/parse.d.ts +225 -0
  35. package/dist/schema/parse.d.ts.map +1 -0
  36. package/dist/schema/parse.js +732 -0
  37. package/dist/schema/parse.js.map +1 -0
  38. package/dist/schema/provider.d.ts +176 -0
  39. package/dist/schema/provider.d.ts.map +1 -0
  40. package/dist/schema/provider.js +258 -0
  41. package/dist/schema/provider.js.map +1 -0
  42. package/dist/schema/resolve.d.ts +87 -0
  43. package/dist/schema/resolve.d.ts.map +1 -0
  44. package/dist/schema/resolve.js +474 -0
  45. package/dist/schema/resolve.js.map +1 -0
  46. package/dist/schema/semantic.d.ts +53 -0
  47. package/dist/schema/semantic.d.ts.map +1 -0
  48. package/dist/schema/semantic.js +247 -0
  49. package/dist/schema/semantic.js.map +1 -0
  50. package/dist/schema/types.d.ts +528 -0
  51. package/dist/schema/types.d.ts.map +1 -0
  52. package/dist/schema/types.js +9 -0
  53. package/dist/schema/types.js.map +1 -0
  54. package/dist/schema.d.ts +24 -867
  55. package/dist/schema.d.ts.map +1 -1
  56. package/dist/schema.js +41 -1124
  57. package/dist/schema.js.map +1 -1
  58. package/dist/semantic.d.ts +175 -0
  59. package/dist/semantic.d.ts.map +1 -0
  60. package/dist/semantic.js +338 -0
  61. package/dist/semantic.js.map +1 -0
  62. package/dist/types.d.ts +14 -0
  63. package/dist/types.d.ts.map +1 -1
  64. package/dist/types.js.map +1 -1
  65. package/package.json +13 -4
  66. package/.turbo/turbo-build.log +0 -5
  67. package/TESTING.md +0 -410
  68. package/TEST_SUMMARY.md +0 -250
  69. package/TODO.md +0 -128
  70. package/src/ai-promise-db.ts +0 -1243
  71. package/src/authorization.ts +0 -1102
  72. package/src/durable-clickhouse.ts +0 -596
  73. package/src/durable-promise.ts +0 -582
  74. package/src/execution-queue.ts +0 -608
  75. package/src/index.test.ts +0 -868
  76. package/src/index.ts +0 -337
  77. package/src/linguistic.ts +0 -404
  78. package/src/memory-provider.test.ts +0 -1036
  79. package/src/memory-provider.ts +0 -1119
  80. package/src/schema.test.ts +0 -1254
  81. package/src/schema.ts +0 -2296
  82. package/src/tests.ts +0 -725
  83. package/src/types.ts +0 -1177
  84. package/test/README.md +0 -153
  85. package/test/edge-cases.test.ts +0 -646
  86. package/test/provider-resolution.test.ts +0 -402
  87. package/tsconfig.json +0 -9
  88. package/vitest.config.ts +0 -19
@@ -1,1119 +0,0 @@
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
- import type { DBProvider, ListOptions, SearchOptions } from './schema.js'
9
-
10
- // =============================================================================
11
- // Semaphore for Concurrency Control
12
- // =============================================================================
13
-
14
- /**
15
- * Simple semaphore for concurrency control
16
- * Used to limit parallel operations (e.g., embedding, generation)
17
- */
18
- export class Semaphore {
19
- private queue: Array<() => void> = []
20
- private running = 0
21
-
22
- constructor(private concurrency: number) {}
23
-
24
- /**
25
- * Acquire a slot. Returns a release function.
26
- */
27
- async acquire(): Promise<() => void> {
28
- if (this.running < this.concurrency) {
29
- this.running++
30
- return () => this.release()
31
- }
32
-
33
- return new Promise((resolve) => {
34
- this.queue.push(() => {
35
- this.running++
36
- resolve(() => this.release())
37
- })
38
- })
39
- }
40
-
41
- private release(): void {
42
- this.running--
43
- const next = this.queue.shift()
44
- if (next) next()
45
- }
46
-
47
- /**
48
- * Run a function with concurrency control
49
- */
50
- async run<T>(fn: () => Promise<T>): Promise<T> {
51
- const release = await this.acquire()
52
- try {
53
- return await fn()
54
- } finally {
55
- release()
56
- }
57
- }
58
-
59
- /**
60
- * Run multiple functions with concurrency control
61
- */
62
- async map<T, R>(items: T[], fn: (item: T) => Promise<R>): Promise<R[]> {
63
- return Promise.all(items.map((item) => this.run(() => fn(item))))
64
- }
65
-
66
- get pending(): number {
67
- return this.queue.length
68
- }
69
-
70
- get active(): number {
71
- return this.running
72
- }
73
- }
74
-
75
- // =============================================================================
76
- // Types (Actor-Event-Object-Result pattern)
77
- // =============================================================================
78
-
79
- /**
80
- * Actor metadata for events and actions
81
- */
82
- export interface ActorData {
83
- name?: string
84
- email?: string
85
- org?: string
86
- role?: string
87
- [key: string]: unknown
88
- }
89
-
90
- /**
91
- * Event with Actor-Event-Object-Result pattern
92
- *
93
- * Following ActivityStreams semantics:
94
- * - Actor: Who did it (user, system, agent)
95
- * - Event: What happened (created, updated, published)
96
- * - Object: What it was done to
97
- * - Result: What was the outcome
98
- */
99
- export interface Event {
100
- id: string
101
- /** Actor identifier (user:id, system, agent:name) */
102
- actor: string
103
- /** Actor metadata */
104
- actorData?: ActorData
105
- /** Event type (Entity.action format) */
106
- event: string
107
- /** Object URL/identifier */
108
- object?: string
109
- /** Object data snapshot */
110
- objectData?: Record<string, unknown>
111
- /** Result URL/identifier */
112
- result?: string
113
- /** Result data */
114
- resultData?: Record<string, unknown>
115
- /** Additional metadata */
116
- meta?: Record<string, unknown>
117
- /** When the event occurred */
118
- timestamp: Date
119
-
120
- // Legacy compatibility
121
- /** @deprecated Use 'event' instead */
122
- type?: string
123
- /** @deprecated Use 'object' instead */
124
- url?: string
125
- /** @deprecated Use 'objectData' instead */
126
- data?: unknown
127
- }
128
-
129
- /**
130
- * Action with linguistic verb conjugations
131
- *
132
- * Uses act/action/activity pattern for semantic clarity:
133
- * - act: Present tense 3rd person (creates, publishes)
134
- * - action: Base verb form (create, publish)
135
- * - activity: Gerund/progressive (creating, publishing)
136
- */
137
- export interface Action {
138
- id: string
139
- /** Actor identifier */
140
- actor: string
141
- /** Actor metadata */
142
- actorData?: ActorData
143
- /** Present tense verb (creates, publishes) */
144
- act: string
145
- /** Base verb form (create, publish) */
146
- action: string
147
- /** Gerund form (creating, publishing) */
148
- activity: string
149
- /** Object being acted upon */
150
- object?: string
151
- /** Object data/parameters */
152
- objectData?: Record<string, unknown>
153
- /** Action status */
154
- status: 'pending' | 'active' | 'completed' | 'failed' | 'cancelled'
155
- /** Progress count */
156
- progress?: number
157
- /** Total items */
158
- total?: number
159
- /** Result data */
160
- result?: Record<string, unknown>
161
- /** Error message */
162
- error?: string
163
- /** Additional metadata */
164
- meta?: Record<string, unknown>
165
- /** Created timestamp */
166
- createdAt: Date
167
- /** Started timestamp */
168
- startedAt?: Date
169
- /** Completed timestamp */
170
- completedAt?: Date
171
-
172
- // Legacy compatibility
173
- /** @deprecated Use 'action' instead */
174
- type?: string
175
- /** @deprecated Use 'objectData' instead */
176
- data?: unknown
177
- }
178
-
179
- export interface Artifact {
180
- url: string
181
- type: string
182
- sourceHash: string
183
- content: unknown
184
- metadata?: Record<string, unknown>
185
- createdAt: Date
186
- }
187
-
188
- export interface MemoryProviderOptions {
189
- /** Concurrency limit for operations (default: 10) */
190
- concurrency?: number
191
- }
192
-
193
- // =============================================================================
194
- // Generate ID
195
- // =============================================================================
196
-
197
- function generateId(): string {
198
- return crypto.randomUUID()
199
- }
200
-
201
- // =============================================================================
202
- // Verb Conjugation (Linguistic Helpers)
203
- // =============================================================================
204
-
205
- /**
206
- * Conjugate a verb to get all forms
207
- *
208
- * @example
209
- * ```ts
210
- * conjugateVerb('create')
211
- * // => { action: 'create', act: 'creates', activity: 'creating' }
212
- *
213
- * conjugateVerb('publish')
214
- * // => { action: 'publish', act: 'publishes', activity: 'publishing' }
215
- * ```
216
- */
217
- function conjugateVerb(verb: string): { action: string; act: string; activity: string } {
218
- const base = verb.toLowerCase()
219
-
220
- // Known verbs with pre-defined conjugations
221
- const known: Record<string, { act: string; activity: string }> = {
222
- create: { act: 'creates', activity: 'creating' },
223
- update: { act: 'updates', activity: 'updating' },
224
- delete: { act: 'deletes', activity: 'deleting' },
225
- publish: { act: 'publishes', activity: 'publishing' },
226
- archive: { act: 'archives', activity: 'archiving' },
227
- generate: { act: 'generates', activity: 'generating' },
228
- process: { act: 'processes', activity: 'processing' },
229
- sync: { act: 'syncs', activity: 'syncing' },
230
- import: { act: 'imports', activity: 'importing' },
231
- export: { act: 'exports', activity: 'exporting' },
232
- run: { act: 'runs', activity: 'running' },
233
- execute: { act: 'executes', activity: 'executing' },
234
- send: { act: 'sends', activity: 'sending' },
235
- fetch: { act: 'fetches', activity: 'fetching' },
236
- build: { act: 'builds', activity: 'building' },
237
- deploy: { act: 'deploys', activity: 'deploying' },
238
- }
239
-
240
- if (known[base]) {
241
- return { action: base, ...known[base] }
242
- }
243
-
244
- // Auto-conjugate unknown verbs
245
- return {
246
- action: base,
247
- act: toPresent(base),
248
- activity: toGerund(base),
249
- }
250
- }
251
-
252
- /** Check if character is a vowel */
253
- function isVowel(char: string | undefined): boolean {
254
- return char ? 'aeiou'.includes(char.toLowerCase()) : false
255
- }
256
-
257
- /** Check if we should double the final consonant */
258
- function shouldDoubleConsonant(verb: string): boolean {
259
- if (verb.length < 2) return false
260
- const last = verb[verb.length - 1]!
261
- const secondLast = verb[verb.length - 2]!
262
- if ('wxy'.includes(last)) return false
263
- if (isVowel(last) || !isVowel(secondLast)) return false
264
- // Short words (3 letters) almost always double
265
- if (verb.length <= 3) return true
266
- return false
267
- }
268
-
269
- /** Convert verb to present 3rd person (create → creates) */
270
- function toPresent(verb: string): string {
271
- if (verb.endsWith('y') && !isVowel(verb[verb.length - 2])) {
272
- return verb.slice(0, -1) + 'ies'
273
- }
274
- if (verb.endsWith('s') || verb.endsWith('x') || verb.endsWith('z') ||
275
- verb.endsWith('ch') || verb.endsWith('sh')) {
276
- return verb + 'es'
277
- }
278
- return verb + 's'
279
- }
280
-
281
- /** Convert verb to gerund (create → creating) */
282
- function toGerund(verb: string): string {
283
- if (verb.endsWith('ie')) return verb.slice(0, -2) + 'ying'
284
- if (verb.endsWith('e') && !verb.endsWith('ee')) return verb.slice(0, -1) + 'ing'
285
- if (shouldDoubleConsonant(verb)) {
286
- return verb + verb[verb.length - 1] + 'ing'
287
- }
288
- return verb + 'ing'
289
- }
290
-
291
- // =============================================================================
292
- // In-memory Provider
293
- // =============================================================================
294
-
295
- /**
296
- * In-memory storage for entities, relationships, events, actions, and artifacts
297
- */
298
- export class MemoryProvider implements DBProvider {
299
- // Things: type -> id -> entity
300
- private entities = new Map<string, Map<string, Record<string, unknown>>>()
301
-
302
- // Relationships: from:relation -> Set<to>
303
- private relations = new Map<string, Set<string>>()
304
-
305
- // Events: chronological log
306
- private events: Event[] = []
307
- private eventHandlers = new Map<string, Array<(event: Event) => void | Promise<void>>>()
308
-
309
- // Actions: id -> action
310
- private actions = new Map<string, Action>()
311
-
312
- // Artifacts: url:type -> artifact
313
- private artifacts = new Map<string, Artifact>()
314
-
315
- // Concurrency control
316
- private semaphore: Semaphore
317
-
318
- constructor(options: MemoryProviderOptions = {}) {
319
- this.semaphore = new Semaphore(options.concurrency ?? 10)
320
- }
321
-
322
- // ===========================================================================
323
- // Things (Records)
324
- // ===========================================================================
325
-
326
- private getTypeStore(type: string): Map<string, Record<string, unknown>> {
327
- if (!this.entities.has(type)) {
328
- this.entities.set(type, new Map())
329
- }
330
- return this.entities.get(type)!
331
- }
332
-
333
- async get(
334
- type: string,
335
- id: string
336
- ): Promise<Record<string, unknown> | null> {
337
- const store = this.getTypeStore(type)
338
- const entity = store.get(id)
339
- return entity ? { ...entity, $id: id, $type: type } : null
340
- }
341
-
342
- async list(
343
- type: string,
344
- options?: ListOptions
345
- ): Promise<Record<string, unknown>[]> {
346
- const store = this.getTypeStore(type)
347
- let results: Record<string, unknown>[] = []
348
-
349
- for (const [id, entity] of store) {
350
- const full: Record<string, unknown> = { ...entity, $id: id, $type: type }
351
-
352
- // Apply where filter
353
- if (options?.where) {
354
- let matches = true
355
- for (const [key, value] of Object.entries(options.where)) {
356
- if ((full as Record<string, unknown>)[key] !== value) {
357
- matches = false
358
- break
359
- }
360
- }
361
- if (!matches) continue
362
- }
363
-
364
- results.push(full)
365
- }
366
-
367
- // Sort
368
- if (options?.orderBy) {
369
- const field = options.orderBy
370
- const dir = options.order === 'desc' ? -1 : 1
371
- results.sort((a, b) => {
372
- const aVal = a[field]
373
- const bVal = b[field]
374
- if (aVal === undefined && bVal === undefined) return 0
375
- if (aVal === undefined) return dir
376
- if (bVal === undefined) return -dir
377
- if ((aVal as string | number) < (bVal as string | number)) return -dir
378
- if ((aVal as string | number) > (bVal as string | number)) return dir
379
- return 0
380
- })
381
- }
382
-
383
- // Paginate
384
- if (options?.offset) {
385
- results = results.slice(options.offset)
386
- }
387
- if (options?.limit) {
388
- results = results.slice(0, options.limit)
389
- }
390
-
391
- return results
392
- }
393
-
394
- async search(
395
- type: string,
396
- query: string,
397
- options?: SearchOptions
398
- ): Promise<Record<string, unknown>[]> {
399
- const all = await this.list(type, options)
400
- const queryLower = query.toLowerCase()
401
- const fields = options?.fields || ['$all']
402
-
403
- const scored: Array<{ entity: Record<string, unknown>; score: number }> = []
404
-
405
- for (const entity of all) {
406
- let searchText: string
407
- if (fields.includes('$all')) {
408
- searchText = JSON.stringify(entity).toLowerCase()
409
- } else {
410
- searchText = fields
411
- .map((f) => String(entity[f] || ''))
412
- .join(' ')
413
- .toLowerCase()
414
- }
415
-
416
- if (searchText.includes(queryLower)) {
417
- const index = searchText.indexOf(queryLower)
418
- const score = 1 - index / searchText.length
419
- if (!options?.minScore || score >= options.minScore) {
420
- scored.push({ entity, score })
421
- }
422
- }
423
- }
424
-
425
- scored.sort((a, b) => b.score - a.score)
426
- return scored.map((s) => s.entity)
427
- }
428
-
429
- async create(
430
- type: string,
431
- id: string | undefined,
432
- data: Record<string, unknown>
433
- ): Promise<Record<string, unknown>> {
434
- const store = this.getTypeStore(type)
435
- const entityId = id || generateId()
436
-
437
- if (store.has(entityId)) {
438
- throw new Error(`Entity already exists: ${type}/${entityId}`)
439
- }
440
-
441
- const entity = {
442
- ...data,
443
- createdAt: new Date().toISOString(),
444
- updatedAt: new Date().toISOString(),
445
- }
446
-
447
- store.set(entityId, entity)
448
-
449
- // Emit event
450
- await this.emit(`${type}.created`, { $id: entityId, $type: type, ...entity })
451
-
452
- return { ...entity, $id: entityId, $type: type }
453
- }
454
-
455
- async update(
456
- type: string,
457
- id: string,
458
- data: Record<string, unknown>
459
- ): Promise<Record<string, unknown>> {
460
- const store = this.getTypeStore(type)
461
- const existing = store.get(id)
462
-
463
- if (!existing) {
464
- throw new Error(`Entity not found: ${type}/${id}`)
465
- }
466
-
467
- const updated = {
468
- ...existing,
469
- ...data,
470
- updatedAt: new Date().toISOString(),
471
- }
472
-
473
- store.set(id, updated)
474
-
475
- // Emit event
476
- await this.emit(`${type}.updated`, { $id: id, $type: type, ...updated })
477
-
478
- // Invalidate artifacts when data changes
479
- await this.invalidateArtifacts(`${type}/${id}`)
480
-
481
- return { ...updated, $id: id, $type: type }
482
- }
483
-
484
- async delete(type: string, id: string): Promise<boolean> {
485
- const store = this.getTypeStore(type)
486
-
487
- if (!store.has(id)) {
488
- return false
489
- }
490
-
491
- store.delete(id)
492
-
493
- // Emit event
494
- await this.emit(`${type}.deleted`, { $id: id, $type: type })
495
-
496
- // Clean up relations
497
- for (const [key, targets] of this.relations) {
498
- if (key.startsWith(`${type}:${id}:`)) {
499
- this.relations.delete(key)
500
- }
501
- targets.delete(`${type}:${id}`)
502
- }
503
-
504
- // Clean up artifacts
505
- await this.deleteArtifact(`${type}/${id}`)
506
-
507
- return true
508
- }
509
-
510
- // ===========================================================================
511
- // Relationships
512
- // ===========================================================================
513
-
514
- private relationKey(
515
- fromType: string,
516
- fromId: string,
517
- relation: string
518
- ): string {
519
- return `${fromType}:${fromId}:${relation}`
520
- }
521
-
522
- async related(
523
- type: string,
524
- id: string,
525
- relation: string
526
- ): Promise<Record<string, unknown>[]> {
527
- const key = this.relationKey(type, id, relation)
528
- const targets = this.relations.get(key)
529
-
530
- if (!targets) return []
531
-
532
- const results: Record<string, unknown>[] = []
533
- for (const target of targets) {
534
- const [targetType, targetId] = target.split(':')
535
- const entity = await this.get(targetType!, targetId!)
536
- if (entity) {
537
- results.push(entity)
538
- }
539
- }
540
-
541
- return results
542
- }
543
-
544
- async relate(
545
- fromType: string,
546
- fromId: string,
547
- relation: string,
548
- toType: string,
549
- toId: string
550
- ): Promise<void> {
551
- const key = this.relationKey(fromType, fromId, relation)
552
-
553
- if (!this.relations.has(key)) {
554
- this.relations.set(key, new Set())
555
- }
556
-
557
- this.relations.get(key)!.add(`${toType}:${toId}`)
558
-
559
- // Emit event
560
- await this.emit('Relation.created', {
561
- from: `${fromType}/${fromId}`,
562
- type: relation,
563
- to: `${toType}/${toId}`,
564
- })
565
- }
566
-
567
- async unrelate(
568
- fromType: string,
569
- fromId: string,
570
- relation: string,
571
- toType: string,
572
- toId: string
573
- ): Promise<void> {
574
- const key = this.relationKey(fromType, fromId, relation)
575
- const targets = this.relations.get(key)
576
-
577
- if (targets) {
578
- targets.delete(`${toType}:${toId}`)
579
-
580
- // Emit event
581
- await this.emit('Relation.deleted', {
582
- from: `${fromType}/${fromId}`,
583
- type: relation,
584
- to: `${toType}/${toId}`,
585
- })
586
- }
587
- }
588
-
589
- // ===========================================================================
590
- // Events (Actor-Event-Object-Result pattern)
591
- // ===========================================================================
592
-
593
- /**
594
- * Emit an event using Actor-Event-Object-Result pattern
595
- *
596
- * @example
597
- * ```ts
598
- * // New pattern
599
- * await provider.emit({
600
- * actor: 'user:john',
601
- * event: 'Post.created',
602
- * object: 'Post/hello-world',
603
- * objectData: { title: 'Hello World' },
604
- * })
605
- *
606
- * // Legacy pattern (still supported)
607
- * await provider.emit('Post.created', { title: 'Hello World' })
608
- * ```
609
- */
610
- async emit(
611
- eventOrType: string | {
612
- actor?: string
613
- actorData?: ActorData
614
- event: string
615
- object?: string
616
- objectData?: Record<string, unknown>
617
- result?: string
618
- resultData?: Record<string, unknown>
619
- meta?: Record<string, unknown>
620
- },
621
- data?: unknown
622
- ): Promise<Event> {
623
- let event: Event
624
-
625
- if (typeof eventOrType === 'string') {
626
- // Legacy pattern: emit('Post.created', { ... })
627
- event = {
628
- id: generateId(),
629
- actor: 'system',
630
- event: eventOrType,
631
- objectData: data as Record<string, unknown> | undefined,
632
- timestamp: new Date(),
633
- // Legacy fields
634
- type: eventOrType,
635
- data,
636
- }
637
- } else {
638
- // New pattern: emit({ actor, event, object, ... })
639
- event = {
640
- id: generateId(),
641
- actor: eventOrType.actor ?? 'system',
642
- actorData: eventOrType.actorData,
643
- event: eventOrType.event,
644
- object: eventOrType.object,
645
- objectData: eventOrType.objectData,
646
- result: eventOrType.result,
647
- resultData: eventOrType.resultData,
648
- meta: eventOrType.meta,
649
- timestamp: new Date(),
650
- // Legacy fields
651
- type: eventOrType.event,
652
- }
653
- }
654
-
655
- this.events.push(event)
656
-
657
- // Trigger handlers (with concurrency control)
658
- const handlers = this.getEventHandlers(event.event)
659
- await this.semaphore.map(handlers, (handler) =>
660
- Promise.resolve(handler(event))
661
- )
662
-
663
- return event
664
- }
665
-
666
- private getEventHandlers(type: string): Array<(event: Event) => void | Promise<void>> {
667
- const handlers: Array<(event: Event) => void | Promise<void>> = []
668
-
669
- for (const [pattern, patternHandlers] of this.eventHandlers) {
670
- if (this.matchesPattern(type, pattern)) {
671
- handlers.push(...patternHandlers)
672
- }
673
- }
674
-
675
- return handlers
676
- }
677
-
678
- private matchesPattern(type: string, pattern: string): boolean {
679
- if (pattern === type) return true
680
- if (pattern === '*') return true
681
- if (pattern.endsWith('.*')) {
682
- const prefix = pattern.slice(0, -2)
683
- return type.startsWith(prefix + '.')
684
- }
685
- if (pattern.startsWith('*.')) {
686
- const suffix = pattern.slice(2)
687
- return type.endsWith('.' + suffix)
688
- }
689
- return false
690
- }
691
-
692
- on(pattern: string, handler: (event: Event) => void | Promise<void>): () => void {
693
- if (!this.eventHandlers.has(pattern)) {
694
- this.eventHandlers.set(pattern, [])
695
- }
696
- this.eventHandlers.get(pattern)!.push(handler)
697
-
698
- // Return unsubscribe function
699
- return () => {
700
- const handlers = this.eventHandlers.get(pattern)
701
- if (handlers) {
702
- const index = handlers.indexOf(handler)
703
- if (index !== -1) handlers.splice(index, 1)
704
- }
705
- }
706
- }
707
-
708
- async listEvents(options?: {
709
- event?: string
710
- actor?: string
711
- object?: string
712
- since?: Date
713
- until?: Date
714
- limit?: number
715
- /** @deprecated Use 'event' instead */
716
- type?: string
717
- }): Promise<Event[]> {
718
- let results = [...this.events]
719
-
720
- // Filter by event pattern
721
- const eventPattern = options?.event ?? options?.type
722
- if (eventPattern) {
723
- results = results.filter((e) => this.matchesPattern(e.event, eventPattern))
724
- }
725
- if (options?.actor) {
726
- results = results.filter((e) => e.actor === options.actor)
727
- }
728
- if (options?.object) {
729
- results = results.filter((e) => e.object === options.object)
730
- }
731
- if (options?.since) {
732
- results = results.filter((e) => e.timestamp >= options.since!)
733
- }
734
- if (options?.until) {
735
- results = results.filter((e) => e.timestamp <= options.until!)
736
- }
737
- if (options?.limit) {
738
- results = results.slice(-options.limit)
739
- }
740
-
741
- return results
742
- }
743
-
744
- async replayEvents(options: {
745
- event?: string
746
- actor?: string
747
- since?: Date
748
- handler: (event: Event) => void | Promise<void>
749
- /** @deprecated Use 'event' instead */
750
- type?: string
751
- }): Promise<void> {
752
- const events = await this.listEvents({
753
- event: options.event ?? options.type,
754
- actor: options.actor,
755
- since: options.since,
756
- })
757
-
758
- for (const event of events) {
759
- await this.semaphore.run(() => Promise.resolve(options.handler(event)))
760
- }
761
- }
762
-
763
- // ===========================================================================
764
- // Actions (Linguistic Verb Pattern)
765
- // ===========================================================================
766
-
767
- /**
768
- * Create an action with automatic verb conjugation
769
- *
770
- * @example
771
- * ```ts
772
- * // New pattern with verb conjugation
773
- * const action = await provider.createAction({
774
- * actor: 'system',
775
- * action: 'generate', // auto-conjugates to act='generates', activity='generating'
776
- * object: 'Post',
777
- * objectData: { count: 100 },
778
- * total: 100,
779
- * })
780
- *
781
- * // Legacy pattern (still supported)
782
- * const action = await provider.createAction({
783
- * type: 'generate',
784
- * data: { count: 100 },
785
- * total: 100,
786
- * })
787
- * ```
788
- */
789
- async createAction(data: {
790
- actor?: string
791
- actorData?: ActorData
792
- action?: string
793
- object?: string
794
- objectData?: Record<string, unknown>
795
- total?: number
796
- meta?: Record<string, unknown>
797
- // Legacy
798
- type?: string
799
- data?: unknown
800
- }): Promise<Action> {
801
- // Get base verb from action or legacy type
802
- const baseVerb = data.action ?? data.type ?? 'process'
803
-
804
- // Auto-conjugate verb forms
805
- const conjugated = conjugateVerb(baseVerb)
806
-
807
- const action: Action = {
808
- id: generateId(),
809
- actor: data.actor ?? 'system',
810
- actorData: data.actorData,
811
- act: conjugated.act,
812
- action: conjugated.action,
813
- activity: conjugated.activity,
814
- object: data.object,
815
- objectData: data.objectData ?? (data.data as Record<string, unknown> | undefined),
816
- status: 'pending',
817
- progress: 0,
818
- total: data.total,
819
- meta: data.meta,
820
- createdAt: new Date(),
821
- // Legacy fields
822
- type: baseVerb,
823
- data: data.data,
824
- }
825
-
826
- this.actions.set(action.id, action)
827
-
828
- await this.emit({
829
- actor: action.actor,
830
- actorData: action.actorData,
831
- event: 'Action.created',
832
- object: action.id,
833
- objectData: { action: action.action, object: action.object },
834
- })
835
-
836
- return action
837
- }
838
-
839
- async getAction(id: string): Promise<Action | null> {
840
- return this.actions.get(id) ?? null
841
- }
842
-
843
- async updateAction(
844
- id: string,
845
- updates: Partial<Pick<Action, 'status' | 'progress' | 'result' | 'error'>>
846
- ): Promise<Action> {
847
- const action = this.actions.get(id)
848
- if (!action) {
849
- throw new Error(`Action not found: ${id}`)
850
- }
851
-
852
- Object.assign(action, updates)
853
-
854
- if (updates.status === 'active' && !action.startedAt) {
855
- action.startedAt = new Date()
856
- await this.emit({
857
- actor: action.actor,
858
- event: 'Action.started',
859
- object: action.id,
860
- objectData: { action: action.action, activity: action.activity },
861
- })
862
- }
863
-
864
- if (updates.status === 'completed') {
865
- action.completedAt = new Date()
866
- await this.emit({
867
- actor: action.actor,
868
- event: 'Action.completed',
869
- object: action.id,
870
- objectData: { action: action.action },
871
- result: action.object,
872
- resultData: action.result,
873
- })
874
- }
875
-
876
- if (updates.status === 'failed') {
877
- action.completedAt = new Date()
878
- await this.emit({
879
- actor: action.actor,
880
- event: 'Action.failed',
881
- object: action.id,
882
- objectData: { action: action.action, error: action.error },
883
- })
884
- }
885
-
886
- if (updates.status === 'cancelled') {
887
- action.completedAt = new Date()
888
- await this.emit({
889
- actor: action.actor,
890
- event: 'Action.cancelled',
891
- object: action.id,
892
- objectData: { action: action.action },
893
- })
894
- }
895
-
896
- return action
897
- }
898
-
899
- async listActions(options?: {
900
- status?: Action['status']
901
- action?: string
902
- actor?: string
903
- object?: string
904
- since?: Date
905
- until?: Date
906
- limit?: number
907
- /** @deprecated Use 'action' instead */
908
- type?: string
909
- }): Promise<Action[]> {
910
- let results = Array.from(this.actions.values())
911
-
912
- if (options?.status) {
913
- results = results.filter((a) => a.status === options.status)
914
- }
915
- // Filter by action or legacy type
916
- const actionFilter = options?.action ?? options?.type
917
- if (actionFilter) {
918
- results = results.filter((a) => a.action === actionFilter)
919
- }
920
- if (options?.actor) {
921
- results = results.filter((a) => a.actor === options.actor)
922
- }
923
- if (options?.object) {
924
- results = results.filter((a) => a.object === options.object)
925
- }
926
- if (options?.since) {
927
- results = results.filter((a) => a.createdAt >= options.since!)
928
- }
929
- if (options?.until) {
930
- results = results.filter((a) => a.createdAt <= options.until!)
931
- }
932
- if (options?.limit) {
933
- results = results.slice(0, options.limit)
934
- }
935
-
936
- return results
937
- }
938
-
939
- async retryAction(id: string): Promise<Action> {
940
- const action = this.actions.get(id)
941
- if (!action) {
942
- throw new Error(`Action not found: ${id}`)
943
- }
944
- if (action.status !== 'failed') {
945
- throw new Error(`Can only retry failed actions: ${id}`)
946
- }
947
-
948
- action.status = 'pending'
949
- action.error = undefined
950
- action.startedAt = undefined
951
- action.completedAt = undefined
952
-
953
- await this.emit({
954
- actor: action.actor,
955
- event: 'Action.retried',
956
- object: action.id,
957
- objectData: { action: action.action },
958
- })
959
-
960
- return action
961
- }
962
-
963
- async cancelAction(id: string): Promise<void> {
964
- const action = this.actions.get(id)
965
- if (!action) {
966
- throw new Error(`Action not found: ${id}`)
967
- }
968
- if (action.status === 'completed' || action.status === 'failed' || action.status === 'cancelled') {
969
- throw new Error(`Cannot cancel finished action: ${id}`)
970
- }
971
-
972
- action.status = 'cancelled'
973
- action.completedAt = new Date()
974
-
975
- await this.emit({
976
- actor: action.actor,
977
- event: 'Action.cancelled',
978
- object: action.id,
979
- objectData: { action: action.action },
980
- })
981
- }
982
-
983
- // ===========================================================================
984
- // Artifacts
985
- // ===========================================================================
986
-
987
- private artifactKey(url: string, type: string): string {
988
- return `${url}:${type}`
989
- }
990
-
991
- async getArtifact(url: string, type: string): Promise<Artifact | null> {
992
- return this.artifacts.get(this.artifactKey(url, type)) ?? null
993
- }
994
-
995
- async setArtifact(
996
- url: string,
997
- type: string,
998
- data: { content: unknown; sourceHash: string; metadata?: Record<string, unknown> }
999
- ): Promise<void> {
1000
- const artifact: Artifact = {
1001
- url,
1002
- type,
1003
- sourceHash: data.sourceHash,
1004
- content: data.content,
1005
- metadata: data.metadata,
1006
- createdAt: new Date(),
1007
- }
1008
-
1009
- this.artifacts.set(this.artifactKey(url, type), artifact)
1010
- }
1011
-
1012
- async deleteArtifact(url: string, type?: string): Promise<void> {
1013
- if (type) {
1014
- this.artifacts.delete(this.artifactKey(url, type))
1015
- } else {
1016
- // Delete all artifacts for this URL
1017
- for (const key of this.artifacts.keys()) {
1018
- if (key.startsWith(`${url}:`)) {
1019
- this.artifacts.delete(key)
1020
- }
1021
- }
1022
- }
1023
- }
1024
-
1025
- private async invalidateArtifacts(url: string): Promise<void> {
1026
- // Keep embedding artifact but mark others for regeneration
1027
- for (const [key, artifact] of this.artifacts) {
1028
- if (key.startsWith(`${url}:`) && artifact.type !== 'embedding') {
1029
- this.artifacts.delete(key)
1030
- }
1031
- }
1032
- }
1033
-
1034
- async listArtifacts(url: string): Promise<Artifact[]> {
1035
- const results: Artifact[] = []
1036
- for (const [key, artifact] of this.artifacts) {
1037
- if (key.startsWith(`${url}:`)) {
1038
- results.push(artifact)
1039
- }
1040
- }
1041
- return results
1042
- }
1043
-
1044
- // ===========================================================================
1045
- // Utilities
1046
- // ===========================================================================
1047
-
1048
- /**
1049
- * Run an operation with concurrency control
1050
- */
1051
- async withConcurrency<T>(fn: () => Promise<T>): Promise<T> {
1052
- return this.semaphore.run(fn)
1053
- }
1054
-
1055
- /**
1056
- * Run multiple operations with concurrency control
1057
- */
1058
- async mapWithConcurrency<T, R>(items: T[], fn: (item: T) => Promise<R>): Promise<R[]> {
1059
- return this.semaphore.map(items, fn)
1060
- }
1061
-
1062
- /**
1063
- * Clear all data (useful for testing)
1064
- */
1065
- clear(): void {
1066
- this.entities.clear()
1067
- this.relations.clear()
1068
- this.events.length = 0
1069
- this.actions.clear()
1070
- this.artifacts.clear()
1071
- this.eventHandlers.clear()
1072
- }
1073
-
1074
- /**
1075
- * Get stats
1076
- */
1077
- stats(): {
1078
- entities: number
1079
- relations: number
1080
- events: number
1081
- actions: { pending: number; active: number; completed: number; failed: number; cancelled: number }
1082
- artifacts: number
1083
- concurrency: { active: number; pending: number }
1084
- } {
1085
- let entityCount = 0
1086
- for (const store of this.entities.values()) {
1087
- entityCount += store.size
1088
- }
1089
-
1090
- let relationCount = 0
1091
- for (const targets of this.relations.values()) {
1092
- relationCount += targets.size
1093
- }
1094
-
1095
- const actionStats = { pending: 0, active: 0, completed: 0, failed: 0, cancelled: 0 }
1096
- for (const action of this.actions.values()) {
1097
- actionStats[action.status]++
1098
- }
1099
-
1100
- return {
1101
- entities: entityCount,
1102
- relations: relationCount,
1103
- events: this.events.length,
1104
- actions: actionStats,
1105
- artifacts: this.artifacts.size,
1106
- concurrency: {
1107
- active: this.semaphore.active,
1108
- pending: this.semaphore.pending,
1109
- },
1110
- }
1111
- }
1112
- }
1113
-
1114
- /**
1115
- * Create an in-memory provider
1116
- */
1117
- export function createMemoryProvider(options?: MemoryProviderOptions): MemoryProvider {
1118
- return new MemoryProvider(options)
1119
- }