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.
- package/CHANGELOG.md +43 -0
- package/dist/actions.d.ts +247 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +260 -0
- package/dist/actions.js.map +1 -0
- package/dist/ai-promise-db.d.ts +34 -2
- package/dist/ai-promise-db.d.ts.map +1 -1
- package/dist/ai-promise-db.js +511 -66
- package/dist/ai-promise-db.js.map +1 -1
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/events.d.ts +153 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +154 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/memory-provider.d.ts +144 -2
- package/dist/memory-provider.d.ts.map +1 -1
- package/dist/memory-provider.js +569 -13
- package/dist/memory-provider.js.map +1 -1
- package/dist/schema/cascade.d.ts +96 -0
- package/dist/schema/cascade.d.ts.map +1 -0
- package/dist/schema/cascade.js +528 -0
- package/dist/schema/cascade.js.map +1 -0
- package/dist/schema/index.d.ts +197 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +1211 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/schema/parse.d.ts +225 -0
- package/dist/schema/parse.d.ts.map +1 -0
- package/dist/schema/parse.js +732 -0
- package/dist/schema/parse.js.map +1 -0
- package/dist/schema/provider.d.ts +176 -0
- package/dist/schema/provider.d.ts.map +1 -0
- package/dist/schema/provider.js +258 -0
- package/dist/schema/provider.js.map +1 -0
- package/dist/schema/resolve.d.ts +87 -0
- package/dist/schema/resolve.d.ts.map +1 -0
- package/dist/schema/resolve.js +474 -0
- package/dist/schema/resolve.js.map +1 -0
- package/dist/schema/semantic.d.ts +53 -0
- package/dist/schema/semantic.d.ts.map +1 -0
- package/dist/schema/semantic.js +247 -0
- package/dist/schema/semantic.js.map +1 -0
- package/dist/schema/types.d.ts +528 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +9 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema.d.ts +24 -867
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +41 -1124
- package/dist/schema.js.map +1 -1
- package/dist/semantic.d.ts +175 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +338 -0
- package/dist/semantic.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +13 -4
- package/.turbo/turbo-build.log +0 -5
- package/TESTING.md +0 -410
- package/TEST_SUMMARY.md +0 -250
- package/TODO.md +0 -128
- package/src/ai-promise-db.ts +0 -1243
- package/src/authorization.ts +0 -1102
- package/src/durable-clickhouse.ts +0 -596
- package/src/durable-promise.ts +0 -582
- package/src/execution-queue.ts +0 -608
- package/src/index.test.ts +0 -868
- package/src/index.ts +0 -337
- package/src/linguistic.ts +0 -404
- package/src/memory-provider.test.ts +0 -1036
- package/src/memory-provider.ts +0 -1119
- package/src/schema.test.ts +0 -1254
- package/src/schema.ts +0 -2296
- package/src/tests.ts +0 -725
- package/src/types.ts +0 -1177
- package/test/README.md +0 -153
- package/test/edge-cases.test.ts +0 -646
- package/test/provider-resolution.test.ts +0 -402
- package/tsconfig.json +0 -9
- package/vitest.config.ts +0 -19
package/src/memory-provider.ts
DELETED
|
@@ -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
|
-
}
|