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
|
@@ -1,1036 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for in-memory database provider
|
|
3
|
-
*
|
|
4
|
-
* Tests all CRUD operations and relationships in memory.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
|
8
|
-
import { MemoryProvider, createMemoryProvider, Semaphore } from './memory-provider.js'
|
|
9
|
-
|
|
10
|
-
describe('MemoryProvider', () => {
|
|
11
|
-
let provider: MemoryProvider
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
provider = createMemoryProvider()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
describe('create', () => {
|
|
18
|
-
it('creates an entity with generated ID', async () => {
|
|
19
|
-
const result = await provider.create('User', undefined, {
|
|
20
|
-
name: 'John Doe',
|
|
21
|
-
email: 'john@example.com',
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
expect(result.$id).toBeDefined()
|
|
25
|
-
expect(result.$type).toBe('User')
|
|
26
|
-
expect(result.name).toBe('John Doe')
|
|
27
|
-
expect(result.email).toBe('john@example.com')
|
|
28
|
-
expect(result.createdAt).toBeDefined()
|
|
29
|
-
expect(result.updatedAt).toBeDefined()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('creates an entity with provided ID', async () => {
|
|
33
|
-
const result = await provider.create('User', 'john', {
|
|
34
|
-
name: 'John Doe',
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
expect(result.$id).toBe('john')
|
|
38
|
-
expect(result.$type).toBe('User')
|
|
39
|
-
expect(result.name).toBe('John Doe')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
it('throws error if entity already exists', async () => {
|
|
43
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
44
|
-
|
|
45
|
-
await expect(
|
|
46
|
-
provider.create('User', 'john', { name: 'Jane' })
|
|
47
|
-
).rejects.toThrow('Entity already exists: User/john')
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
it('stores createdAt and updatedAt timestamps', async () => {
|
|
51
|
-
const result = await provider.create('User', 'john', { name: 'John' })
|
|
52
|
-
|
|
53
|
-
expect(result.createdAt).toBeDefined()
|
|
54
|
-
expect(result.updatedAt).toBeDefined()
|
|
55
|
-
expect(typeof result.createdAt).toBe('string')
|
|
56
|
-
expect(typeof result.updatedAt).toBe('string')
|
|
57
|
-
// Verify they are valid ISO strings
|
|
58
|
-
expect(() => new Date(result.createdAt as string)).not.toThrow()
|
|
59
|
-
expect(() => new Date(result.updatedAt as string)).not.toThrow()
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
describe('get', () => {
|
|
64
|
-
it('retrieves an existing entity', async () => {
|
|
65
|
-
await provider.create('User', 'john', {
|
|
66
|
-
name: 'John Doe',
|
|
67
|
-
email: 'john@example.com',
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const result = await provider.get('User', 'john')
|
|
71
|
-
|
|
72
|
-
expect(result).toBeDefined()
|
|
73
|
-
expect(result?.$id).toBe('john')
|
|
74
|
-
expect(result?.$type).toBe('User')
|
|
75
|
-
expect(result?.name).toBe('John Doe')
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
it('returns null for non-existent entity', async () => {
|
|
79
|
-
const result = await provider.get('User', 'nonexistent')
|
|
80
|
-
expect(result).toBeNull()
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('returns null for wrong type', async () => {
|
|
84
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
85
|
-
const result = await provider.get('Post', 'john')
|
|
86
|
-
expect(result).toBeNull()
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('update', () => {
|
|
91
|
-
it('updates an existing entity', async () => {
|
|
92
|
-
await provider.create('User', 'john', {
|
|
93
|
-
name: 'John',
|
|
94
|
-
email: 'john@example.com',
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
const result = await provider.update('User', 'john', {
|
|
98
|
-
name: 'John Doe',
|
|
99
|
-
role: 'admin',
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
expect(result.name).toBe('John Doe')
|
|
103
|
-
expect(result.email).toBe('john@example.com')
|
|
104
|
-
expect(result.role).toBe('admin')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('updates updatedAt timestamp', async () => {
|
|
108
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
109
|
-
|
|
110
|
-
// Small delay to ensure timestamp difference
|
|
111
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
112
|
-
|
|
113
|
-
const result = await provider.update('User', 'john', { name: 'Jane' })
|
|
114
|
-
|
|
115
|
-
// Compare timestamps as strings
|
|
116
|
-
expect(result.updatedAt).toBeDefined()
|
|
117
|
-
expect(result.createdAt).toBeDefined()
|
|
118
|
-
// updatedAt should be greater than or equal to createdAt
|
|
119
|
-
expect(new Date(result.updatedAt as string).getTime()).toBeGreaterThanOrEqual(
|
|
120
|
-
new Date(result.createdAt as string).getTime()
|
|
121
|
-
)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('throws error if entity does not exist', async () => {
|
|
125
|
-
await expect(
|
|
126
|
-
provider.update('User', 'nonexistent', { name: 'Jane' })
|
|
127
|
-
).rejects.toThrow('Entity not found: User/nonexistent')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('merges with existing data', async () => {
|
|
131
|
-
await provider.create('User', 'john', {
|
|
132
|
-
name: 'John',
|
|
133
|
-
email: 'john@example.com',
|
|
134
|
-
age: 30,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
const result = await provider.update('User', 'john', {
|
|
138
|
-
age: 31,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
expect(result.name).toBe('John')
|
|
142
|
-
expect(result.email).toBe('john@example.com')
|
|
143
|
-
expect(result.age).toBe(31)
|
|
144
|
-
})
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
describe('delete', () => {
|
|
148
|
-
it('deletes an existing entity', async () => {
|
|
149
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
150
|
-
|
|
151
|
-
const result = await provider.delete('User', 'john')
|
|
152
|
-
expect(result).toBe(true)
|
|
153
|
-
|
|
154
|
-
const retrieved = await provider.get('User', 'john')
|
|
155
|
-
expect(retrieved).toBeNull()
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('returns false for non-existent entity', async () => {
|
|
159
|
-
const result = await provider.delete('User', 'nonexistent')
|
|
160
|
-
expect(result).toBe(false)
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
it('cleans up relations when deleting entity', async () => {
|
|
164
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
165
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
166
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post1')
|
|
167
|
-
|
|
168
|
-
await provider.delete('User', 'john')
|
|
169
|
-
|
|
170
|
-
const related = await provider.related('Post', 'post1', 'author')
|
|
171
|
-
expect(related).toEqual([])
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
describe('list', () => {
|
|
176
|
-
beforeEach(async () => {
|
|
177
|
-
await provider.create('User', 'john', { name: 'John', age: 30 })
|
|
178
|
-
await provider.create('User', 'jane', { name: 'Jane', age: 25 })
|
|
179
|
-
await provider.create('User', 'bob', { name: 'Bob', age: 35 })
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
it('lists all entities of a type', async () => {
|
|
183
|
-
const results = await provider.list('User')
|
|
184
|
-
|
|
185
|
-
expect(results).toHaveLength(3)
|
|
186
|
-
expect(results.map((r) => r.$id)).toContain('john')
|
|
187
|
-
expect(results.map((r) => r.$id)).toContain('jane')
|
|
188
|
-
expect(results.map((r) => r.$id)).toContain('bob')
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('filters by where clause', async () => {
|
|
192
|
-
const results = await provider.list('User', {
|
|
193
|
-
where: { age: 30 },
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
expect(results).toHaveLength(1)
|
|
197
|
-
expect(results[0]?.name).toBe('John')
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('filters by multiple where conditions', async () => {
|
|
201
|
-
await provider.create('User', 'alice', { name: 'Alice', age: 30, active: true })
|
|
202
|
-
await provider.create('User', 'charlie', { name: 'Charlie', age: 30, active: false })
|
|
203
|
-
|
|
204
|
-
const results = await provider.list('User', {
|
|
205
|
-
where: { age: 30, active: true },
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
expect(results).toHaveLength(1)
|
|
209
|
-
expect(results[0]?.name).toBe('Alice')
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
it('sorts by field ascending', async () => {
|
|
213
|
-
const results = await provider.list('User', {
|
|
214
|
-
orderBy: 'age',
|
|
215
|
-
order: 'asc',
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
expect(results.map((r) => r.age)).toEqual([25, 30, 35])
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it('sorts by field descending', async () => {
|
|
222
|
-
const results = await provider.list('User', {
|
|
223
|
-
orderBy: 'age',
|
|
224
|
-
order: 'desc',
|
|
225
|
-
})
|
|
226
|
-
|
|
227
|
-
expect(results.map((r) => r.age)).toEqual([35, 30, 25])
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('limits results', async () => {
|
|
231
|
-
const results = await provider.list('User', {
|
|
232
|
-
limit: 2,
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
expect(results).toHaveLength(2)
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
it('offsets results', async () => {
|
|
239
|
-
const results = await provider.list('User', {
|
|
240
|
-
orderBy: 'name',
|
|
241
|
-
order: 'asc',
|
|
242
|
-
offset: 1,
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
expect(results).toHaveLength(2)
|
|
246
|
-
expect(results[0]?.name).not.toBe('Bob')
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('combines limit and offset', async () => {
|
|
250
|
-
const results = await provider.list('User', {
|
|
251
|
-
orderBy: 'name',
|
|
252
|
-
order: 'asc',
|
|
253
|
-
limit: 1,
|
|
254
|
-
offset: 1,
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
expect(results).toHaveLength(1)
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('returns empty array for non-existent type', async () => {
|
|
261
|
-
const results = await provider.list('NonExistent')
|
|
262
|
-
expect(results).toEqual([])
|
|
263
|
-
})
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
describe('search', () => {
|
|
267
|
-
beforeEach(async () => {
|
|
268
|
-
await provider.create('Post', 'post1', {
|
|
269
|
-
title: 'Introduction to TypeScript',
|
|
270
|
-
content: 'TypeScript is a typed superset of JavaScript',
|
|
271
|
-
})
|
|
272
|
-
await provider.create('Post', 'post2', {
|
|
273
|
-
title: 'Advanced JavaScript',
|
|
274
|
-
content: 'Deep dive into JavaScript patterns',
|
|
275
|
-
})
|
|
276
|
-
await provider.create('Post', 'post3', {
|
|
277
|
-
title: 'Python Guide',
|
|
278
|
-
content: 'Getting started with Python programming',
|
|
279
|
-
})
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
it('searches across all fields by default', async () => {
|
|
283
|
-
const results = await provider.search('Post', 'TypeScript')
|
|
284
|
-
|
|
285
|
-
expect(results).toHaveLength(1)
|
|
286
|
-
expect(results[0]?.title).toBe('Introduction to TypeScript')
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
it('searches case-insensitively', async () => {
|
|
290
|
-
const results = await provider.search('Post', 'javascript')
|
|
291
|
-
|
|
292
|
-
expect(results.length).toBeGreaterThan(0)
|
|
293
|
-
expect(results.map((r) => r.title)).toContain('Advanced JavaScript')
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
it('searches specific fields', async () => {
|
|
297
|
-
const results = await provider.search('Post', 'JavaScript', {
|
|
298
|
-
fields: ['title'],
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
expect(results).toHaveLength(1)
|
|
302
|
-
expect(results[0]?.title).toBe('Advanced JavaScript')
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
it('filters by minScore', async () => {
|
|
306
|
-
const results = await provider.search('Post', 'TypeScript', {
|
|
307
|
-
minScore: 0.9,
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
// High minScore should return fewer results
|
|
311
|
-
expect(results.length).toBeLessThanOrEqual(1)
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
it('sorts by relevance score', async () => {
|
|
315
|
-
const results = await provider.search('Post', 'JavaScript')
|
|
316
|
-
|
|
317
|
-
// Results should be ordered by relevance
|
|
318
|
-
expect(results.length).toBeGreaterThan(0)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('combines search with where clause', async () => {
|
|
322
|
-
await provider.create('Post', 'post4', {
|
|
323
|
-
title: 'TypeScript Tips',
|
|
324
|
-
category: 'tutorial',
|
|
325
|
-
})
|
|
326
|
-
await provider.create('Post', 'post5', {
|
|
327
|
-
title: 'TypeScript News',
|
|
328
|
-
category: 'news',
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
const results = await provider.search('Post', 'TypeScript', {
|
|
332
|
-
where: { category: 'tutorial' },
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
expect(results).toHaveLength(1)
|
|
336
|
-
expect(results[0]?.title).toBe('TypeScript Tips')
|
|
337
|
-
})
|
|
338
|
-
|
|
339
|
-
it('returns empty array for no matches', async () => {
|
|
340
|
-
const results = await provider.search('Post', 'nonexistent')
|
|
341
|
-
expect(results).toEqual([])
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
describe('relationships', () => {
|
|
346
|
-
beforeEach(async () => {
|
|
347
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
348
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
349
|
-
await provider.create('Post', 'post2', { title: 'World' })
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
it('creates a relationship', async () => {
|
|
353
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post1')
|
|
354
|
-
|
|
355
|
-
const related = await provider.related('User', 'john', 'posts')
|
|
356
|
-
expect(related).toHaveLength(1)
|
|
357
|
-
expect(related[0]?.$id).toBe('post1')
|
|
358
|
-
})
|
|
359
|
-
|
|
360
|
-
it('creates multiple relationships', async () => {
|
|
361
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post1')
|
|
362
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post2')
|
|
363
|
-
|
|
364
|
-
const related = await provider.related('User', 'john', 'posts')
|
|
365
|
-
expect(related).toHaveLength(2)
|
|
366
|
-
expect(related.map((r) => r.$id)).toContain('post1')
|
|
367
|
-
expect(related.map((r) => r.$id)).toContain('post2')
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
it('removes a relationship', async () => {
|
|
371
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post1')
|
|
372
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post2')
|
|
373
|
-
|
|
374
|
-
await provider.unrelate('User', 'john', 'posts', 'Post', 'post1')
|
|
375
|
-
|
|
376
|
-
const related = await provider.related('User', 'john', 'posts')
|
|
377
|
-
expect(related).toHaveLength(1)
|
|
378
|
-
expect(related[0]?.$id).toBe('post2')
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
it('returns empty array for no relationships', async () => {
|
|
382
|
-
const related = await provider.related('User', 'john', 'posts')
|
|
383
|
-
expect(related).toEqual([])
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
it('handles different relation types', async () => {
|
|
387
|
-
await provider.create('Tag', 'tag1', { name: 'typescript' })
|
|
388
|
-
|
|
389
|
-
await provider.relate('Post', 'post1', 'tags', 'Tag', 'tag1')
|
|
390
|
-
await provider.relate('Post', 'post1', 'author', 'User', 'john')
|
|
391
|
-
|
|
392
|
-
const tags = await provider.related('Post', 'post1', 'tags')
|
|
393
|
-
const author = await provider.related('Post', 'post1', 'author')
|
|
394
|
-
|
|
395
|
-
expect(tags).toHaveLength(1)
|
|
396
|
-
expect(author).toHaveLength(1)
|
|
397
|
-
})
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
describe('utility methods', () => {
|
|
401
|
-
it('clears all data', async () => {
|
|
402
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
403
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
404
|
-
|
|
405
|
-
provider.clear()
|
|
406
|
-
|
|
407
|
-
const user = await provider.get('User', 'john')
|
|
408
|
-
const post = await provider.get('Post', 'post1')
|
|
409
|
-
|
|
410
|
-
expect(user).toBeNull()
|
|
411
|
-
expect(post).toBeNull()
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
it('returns stats', async () => {
|
|
415
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
416
|
-
await provider.create('User', 'jane', { name: 'Jane' })
|
|
417
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
418
|
-
await provider.relate('User', 'john', 'posts', 'Post', 'post1')
|
|
419
|
-
|
|
420
|
-
const stats = provider.stats()
|
|
421
|
-
|
|
422
|
-
expect(stats.entities).toBe(3)
|
|
423
|
-
expect(stats.relations).toBe(1)
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
it('tracks entity count correctly', async () => {
|
|
427
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
428
|
-
let stats = provider.stats()
|
|
429
|
-
expect(stats.entities).toBe(1)
|
|
430
|
-
|
|
431
|
-
await provider.create('User', 'jane', { name: 'Jane' })
|
|
432
|
-
stats = provider.stats()
|
|
433
|
-
expect(stats.entities).toBe(2)
|
|
434
|
-
|
|
435
|
-
await provider.delete('User', 'john')
|
|
436
|
-
stats = provider.stats()
|
|
437
|
-
expect(stats.entities).toBe(1)
|
|
438
|
-
})
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
describe('createMemoryProvider', () => {
|
|
442
|
-
it('creates a new provider instance', () => {
|
|
443
|
-
const provider1 = createMemoryProvider()
|
|
444
|
-
const provider2 = createMemoryProvider()
|
|
445
|
-
|
|
446
|
-
expect(provider1).toBeInstanceOf(MemoryProvider)
|
|
447
|
-
expect(provider2).toBeInstanceOf(MemoryProvider)
|
|
448
|
-
expect(provider1).not.toBe(provider2)
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
it('creates isolated provider instances', async () => {
|
|
452
|
-
const provider1 = createMemoryProvider()
|
|
453
|
-
const provider2 = createMemoryProvider()
|
|
454
|
-
|
|
455
|
-
await provider1.create('User', 'john', { name: 'John' })
|
|
456
|
-
|
|
457
|
-
const result1 = await provider1.get('User', 'john')
|
|
458
|
-
const result2 = await provider2.get('User', 'john')
|
|
459
|
-
|
|
460
|
-
expect(result1).not.toBeNull()
|
|
461
|
-
expect(result2).toBeNull()
|
|
462
|
-
})
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
describe('events', () => {
|
|
466
|
-
it('emits events on create', async () => {
|
|
467
|
-
const handler = vi.fn()
|
|
468
|
-
provider.on('User.created', handler)
|
|
469
|
-
|
|
470
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
471
|
-
|
|
472
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
473
|
-
expect(handler.mock.calls[0][0].type).toBe('User.created')
|
|
474
|
-
expect(handler.mock.calls[0][0].data.name).toBe('John')
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
it('emits events on update', async () => {
|
|
478
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
479
|
-
|
|
480
|
-
const handler = vi.fn()
|
|
481
|
-
provider.on('User.updated', handler)
|
|
482
|
-
|
|
483
|
-
await provider.update('User', 'john', { name: 'Jane' })
|
|
484
|
-
|
|
485
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
486
|
-
expect(handler.mock.calls[0][0].type).toBe('User.updated')
|
|
487
|
-
})
|
|
488
|
-
|
|
489
|
-
it('emits events on delete', async () => {
|
|
490
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
491
|
-
|
|
492
|
-
const handler = vi.fn()
|
|
493
|
-
provider.on('User.deleted', handler)
|
|
494
|
-
|
|
495
|
-
await provider.delete('User', 'john')
|
|
496
|
-
|
|
497
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
498
|
-
expect(handler.mock.calls[0][0].type).toBe('User.deleted')
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
it('supports wildcard pattern matching with Type.*', async () => {
|
|
502
|
-
const handler = vi.fn()
|
|
503
|
-
provider.on('User.*', handler)
|
|
504
|
-
|
|
505
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
506
|
-
await provider.update('User', 'john', { name: 'Jane' })
|
|
507
|
-
await provider.delete('User', 'john')
|
|
508
|
-
|
|
509
|
-
expect(handler).toHaveBeenCalledTimes(3)
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
it('supports wildcard pattern matching with *.action', async () => {
|
|
513
|
-
const handler = vi.fn()
|
|
514
|
-
provider.on('*.created', handler)
|
|
515
|
-
|
|
516
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
517
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
518
|
-
|
|
519
|
-
expect(handler).toHaveBeenCalledTimes(2)
|
|
520
|
-
})
|
|
521
|
-
|
|
522
|
-
it('supports global wildcard *', async () => {
|
|
523
|
-
const handler = vi.fn()
|
|
524
|
-
provider.on('*', handler)
|
|
525
|
-
|
|
526
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
527
|
-
await provider.update('User', 'john', { name: 'Jane' })
|
|
528
|
-
|
|
529
|
-
expect(handler).toHaveBeenCalledTimes(2)
|
|
530
|
-
})
|
|
531
|
-
|
|
532
|
-
it('allows unsubscribing from events', async () => {
|
|
533
|
-
const handler = vi.fn()
|
|
534
|
-
const unsubscribe = provider.on('User.created', handler)
|
|
535
|
-
|
|
536
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
537
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
538
|
-
|
|
539
|
-
unsubscribe()
|
|
540
|
-
|
|
541
|
-
await provider.create('User', 'jane', { name: 'Jane' })
|
|
542
|
-
expect(handler).toHaveBeenCalledTimes(1)
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
it('lists events with filters', async () => {
|
|
546
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
547
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
548
|
-
await provider.update('User', 'john', { name: 'Jane' })
|
|
549
|
-
|
|
550
|
-
const allEvents = await provider.listEvents()
|
|
551
|
-
expect(allEvents.length).toBeGreaterThanOrEqual(3)
|
|
552
|
-
|
|
553
|
-
const userEvents = await provider.listEvents({ type: 'User.*' })
|
|
554
|
-
expect(userEvents.length).toBe(2)
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
it('replays events', async () => {
|
|
558
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
559
|
-
await provider.create('User', 'jane', { name: 'Jane' })
|
|
560
|
-
|
|
561
|
-
const replayedEvents: string[] = []
|
|
562
|
-
await provider.replayEvents({
|
|
563
|
-
type: 'User.created',
|
|
564
|
-
handler: (event) => {
|
|
565
|
-
replayedEvents.push((event.data as { name: string }).name)
|
|
566
|
-
},
|
|
567
|
-
})
|
|
568
|
-
|
|
569
|
-
expect(replayedEvents).toEqual(['John', 'Jane'])
|
|
570
|
-
})
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
describe('actions', () => {
|
|
574
|
-
it('creates a pending action', async () => {
|
|
575
|
-
const action = await provider.createAction({
|
|
576
|
-
type: 'batch-embed',
|
|
577
|
-
data: { items: ['a', 'b', 'c'] },
|
|
578
|
-
total: 3,
|
|
579
|
-
})
|
|
580
|
-
|
|
581
|
-
expect(action.id).toBeDefined()
|
|
582
|
-
expect(action.status).toBe('pending')
|
|
583
|
-
expect(action.type).toBe('batch-embed')
|
|
584
|
-
expect(action.total).toBe(3)
|
|
585
|
-
expect(action.progress).toBe(0)
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
it('updates action status to active', async () => {
|
|
589
|
-
const action = await provider.createAction({
|
|
590
|
-
type: 'batch-embed',
|
|
591
|
-
data: {},
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
const updated = await provider.updateAction(action.id, {
|
|
595
|
-
status: 'active',
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
expect(updated.status).toBe('active')
|
|
599
|
-
expect(updated.startedAt).toBeDefined()
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
it('updates action progress', async () => {
|
|
603
|
-
const action = await provider.createAction({
|
|
604
|
-
type: 'batch-embed',
|
|
605
|
-
data: {},
|
|
606
|
-
total: 10,
|
|
607
|
-
})
|
|
608
|
-
|
|
609
|
-
const updated = await provider.updateAction(action.id, {
|
|
610
|
-
progress: 5,
|
|
611
|
-
})
|
|
612
|
-
|
|
613
|
-
expect(updated.progress).toBe(5)
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
it('marks action as completed', async () => {
|
|
617
|
-
const action = await provider.createAction({
|
|
618
|
-
type: 'batch-embed',
|
|
619
|
-
data: {},
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
const completed = await provider.updateAction(action.id, {
|
|
623
|
-
status: 'completed',
|
|
624
|
-
result: { success: true },
|
|
625
|
-
})
|
|
626
|
-
|
|
627
|
-
expect(completed.status).toBe('completed')
|
|
628
|
-
expect(completed.completedAt).toBeDefined()
|
|
629
|
-
expect(completed.result).toEqual({ success: true })
|
|
630
|
-
})
|
|
631
|
-
|
|
632
|
-
it('marks action as failed', async () => {
|
|
633
|
-
const action = await provider.createAction({
|
|
634
|
-
type: 'batch-embed',
|
|
635
|
-
data: {},
|
|
636
|
-
})
|
|
637
|
-
|
|
638
|
-
const failed = await provider.updateAction(action.id, {
|
|
639
|
-
status: 'failed',
|
|
640
|
-
error: 'Something went wrong',
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
expect(failed.status).toBe('failed')
|
|
644
|
-
expect(failed.completedAt).toBeDefined()
|
|
645
|
-
expect(failed.error).toBe('Something went wrong')
|
|
646
|
-
})
|
|
647
|
-
|
|
648
|
-
it('retrieves an action by id', async () => {
|
|
649
|
-
const action = await provider.createAction({
|
|
650
|
-
type: 'batch-embed',
|
|
651
|
-
data: { foo: 'bar' },
|
|
652
|
-
})
|
|
653
|
-
|
|
654
|
-
const retrieved = await provider.getAction(action.id)
|
|
655
|
-
|
|
656
|
-
expect(retrieved).not.toBeNull()
|
|
657
|
-
expect(retrieved?.type).toBe('batch-embed')
|
|
658
|
-
expect(retrieved?.data).toEqual({ foo: 'bar' })
|
|
659
|
-
})
|
|
660
|
-
|
|
661
|
-
it('returns null for non-existent action', async () => {
|
|
662
|
-
const retrieved = await provider.getAction('nonexistent')
|
|
663
|
-
expect(retrieved).toBeNull()
|
|
664
|
-
})
|
|
665
|
-
|
|
666
|
-
it('lists actions by status', async () => {
|
|
667
|
-
await provider.createAction({ type: 'task1', data: {} })
|
|
668
|
-
const action2 = await provider.createAction({ type: 'task2', data: {} })
|
|
669
|
-
await provider.updateAction(action2.id, { status: 'completed' })
|
|
670
|
-
|
|
671
|
-
const pending = await provider.listActions({ status: 'pending' })
|
|
672
|
-
const completed = await provider.listActions({ status: 'completed' })
|
|
673
|
-
|
|
674
|
-
expect(pending).toHaveLength(1)
|
|
675
|
-
expect(completed).toHaveLength(1)
|
|
676
|
-
})
|
|
677
|
-
|
|
678
|
-
it('lists actions by type', async () => {
|
|
679
|
-
await provider.createAction({ type: 'embed', data: {} })
|
|
680
|
-
await provider.createAction({ type: 'embed', data: {} })
|
|
681
|
-
await provider.createAction({ type: 'generate', data: {} })
|
|
682
|
-
|
|
683
|
-
const embedActions = await provider.listActions({ type: 'embed' })
|
|
684
|
-
expect(embedActions).toHaveLength(2)
|
|
685
|
-
})
|
|
686
|
-
|
|
687
|
-
it('retries failed actions', async () => {
|
|
688
|
-
const action = await provider.createAction({
|
|
689
|
-
type: 'batch-embed',
|
|
690
|
-
data: {},
|
|
691
|
-
})
|
|
692
|
-
await provider.updateAction(action.id, {
|
|
693
|
-
status: 'failed',
|
|
694
|
-
error: 'Network error',
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
const retried = await provider.retryAction(action.id)
|
|
698
|
-
|
|
699
|
-
expect(retried.status).toBe('pending')
|
|
700
|
-
expect(retried.error).toBeUndefined()
|
|
701
|
-
expect(retried.startedAt).toBeUndefined()
|
|
702
|
-
expect(retried.completedAt).toBeUndefined()
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
it('throws error when retrying non-failed action', async () => {
|
|
706
|
-
const action = await provider.createAction({
|
|
707
|
-
type: 'batch-embed',
|
|
708
|
-
data: {},
|
|
709
|
-
})
|
|
710
|
-
|
|
711
|
-
await expect(provider.retryAction(action.id)).rejects.toThrow(
|
|
712
|
-
'Can only retry failed actions'
|
|
713
|
-
)
|
|
714
|
-
})
|
|
715
|
-
|
|
716
|
-
it('cancels pending action', async () => {
|
|
717
|
-
const action = await provider.createAction({
|
|
718
|
-
type: 'batch-embed',
|
|
719
|
-
data: {},
|
|
720
|
-
})
|
|
721
|
-
|
|
722
|
-
await provider.cancelAction(action.id)
|
|
723
|
-
|
|
724
|
-
const cancelled = await provider.getAction(action.id)
|
|
725
|
-
expect(cancelled?.status).toBe('cancelled')
|
|
726
|
-
// Note: cancelled actions no longer set error message
|
|
727
|
-
})
|
|
728
|
-
|
|
729
|
-
it('throws error when cancelling completed action', async () => {
|
|
730
|
-
const action = await provider.createAction({
|
|
731
|
-
type: 'batch-embed',
|
|
732
|
-
data: {},
|
|
733
|
-
})
|
|
734
|
-
await provider.updateAction(action.id, { status: 'completed' })
|
|
735
|
-
|
|
736
|
-
await expect(provider.cancelAction(action.id)).rejects.toThrow(
|
|
737
|
-
'Cannot cancel finished action'
|
|
738
|
-
)
|
|
739
|
-
})
|
|
740
|
-
|
|
741
|
-
it('emits action events', async () => {
|
|
742
|
-
const handler = vi.fn()
|
|
743
|
-
provider.on('Action.*', handler)
|
|
744
|
-
|
|
745
|
-
const action = await provider.createAction({ type: 'test', data: {} })
|
|
746
|
-
await provider.updateAction(action.id, { status: 'active' })
|
|
747
|
-
await provider.updateAction(action.id, { status: 'completed' })
|
|
748
|
-
|
|
749
|
-
const eventTypes = handler.mock.calls.map(
|
|
750
|
-
(call) => call[0].type
|
|
751
|
-
)
|
|
752
|
-
expect(eventTypes).toContain('Action.created')
|
|
753
|
-
expect(eventTypes).toContain('Action.started')
|
|
754
|
-
expect(eventTypes).toContain('Action.completed')
|
|
755
|
-
})
|
|
756
|
-
})
|
|
757
|
-
|
|
758
|
-
describe('artifacts', () => {
|
|
759
|
-
it('stores an artifact', async () => {
|
|
760
|
-
await provider.setArtifact('User/john', 'embedding', {
|
|
761
|
-
content: [0.1, 0.2, 0.3],
|
|
762
|
-
sourceHash: 'abc123',
|
|
763
|
-
})
|
|
764
|
-
|
|
765
|
-
const artifact = await provider.getArtifact('User/john', 'embedding')
|
|
766
|
-
|
|
767
|
-
expect(artifact).not.toBeNull()
|
|
768
|
-
expect(artifact?.content).toEqual([0.1, 0.2, 0.3])
|
|
769
|
-
expect(artifact?.sourceHash).toBe('abc123')
|
|
770
|
-
})
|
|
771
|
-
|
|
772
|
-
it('stores artifact with metadata', async () => {
|
|
773
|
-
await provider.setArtifact('User/john', 'embedding', {
|
|
774
|
-
content: [0.1, 0.2, 0.3],
|
|
775
|
-
sourceHash: 'abc123',
|
|
776
|
-
metadata: { model: 'gemini-embedding-001', dimensions: 768 },
|
|
777
|
-
})
|
|
778
|
-
|
|
779
|
-
const artifact = await provider.getArtifact('User/john', 'embedding')
|
|
780
|
-
|
|
781
|
-
expect(artifact?.metadata).toEqual({
|
|
782
|
-
model: 'gemini-embedding-001',
|
|
783
|
-
dimensions: 768,
|
|
784
|
-
})
|
|
785
|
-
})
|
|
786
|
-
|
|
787
|
-
it('returns null for non-existent artifact', async () => {
|
|
788
|
-
const artifact = await provider.getArtifact('User/john', 'embedding')
|
|
789
|
-
expect(artifact).toBeNull()
|
|
790
|
-
})
|
|
791
|
-
|
|
792
|
-
it('stores multiple artifact types for same url', async () => {
|
|
793
|
-
await provider.setArtifact('Post/post1', 'embedding', {
|
|
794
|
-
content: [0.1, 0.2],
|
|
795
|
-
sourceHash: 'abc',
|
|
796
|
-
})
|
|
797
|
-
await provider.setArtifact('Post/post1', 'chunks', {
|
|
798
|
-
content: ['chunk1', 'chunk2'],
|
|
799
|
-
sourceHash: 'def',
|
|
800
|
-
})
|
|
801
|
-
|
|
802
|
-
const embedding = await provider.getArtifact('Post/post1', 'embedding')
|
|
803
|
-
const chunks = await provider.getArtifact('Post/post1', 'chunks')
|
|
804
|
-
|
|
805
|
-
expect(embedding?.content).toEqual([0.1, 0.2])
|
|
806
|
-
expect(chunks?.content).toEqual(['chunk1', 'chunk2'])
|
|
807
|
-
})
|
|
808
|
-
|
|
809
|
-
it('deletes specific artifact type', async () => {
|
|
810
|
-
await provider.setArtifact('Post/post1', 'embedding', {
|
|
811
|
-
content: [0.1],
|
|
812
|
-
sourceHash: 'abc',
|
|
813
|
-
})
|
|
814
|
-
await provider.setArtifact('Post/post1', 'chunks', {
|
|
815
|
-
content: ['chunk1'],
|
|
816
|
-
sourceHash: 'def',
|
|
817
|
-
})
|
|
818
|
-
|
|
819
|
-
await provider.deleteArtifact('Post/post1', 'embedding')
|
|
820
|
-
|
|
821
|
-
const embedding = await provider.getArtifact('Post/post1', 'embedding')
|
|
822
|
-
const chunks = await provider.getArtifact('Post/post1', 'chunks')
|
|
823
|
-
|
|
824
|
-
expect(embedding).toBeNull()
|
|
825
|
-
expect(chunks).not.toBeNull()
|
|
826
|
-
})
|
|
827
|
-
|
|
828
|
-
it('deletes all artifacts for url', async () => {
|
|
829
|
-
await provider.setArtifact('Post/post1', 'embedding', {
|
|
830
|
-
content: [0.1],
|
|
831
|
-
sourceHash: 'abc',
|
|
832
|
-
})
|
|
833
|
-
await provider.setArtifact('Post/post1', 'chunks', {
|
|
834
|
-
content: ['chunk1'],
|
|
835
|
-
sourceHash: 'def',
|
|
836
|
-
})
|
|
837
|
-
|
|
838
|
-
await provider.deleteArtifact('Post/post1')
|
|
839
|
-
|
|
840
|
-
const embedding = await provider.getArtifact('Post/post1', 'embedding')
|
|
841
|
-
const chunks = await provider.getArtifact('Post/post1', 'chunks')
|
|
842
|
-
|
|
843
|
-
expect(embedding).toBeNull()
|
|
844
|
-
expect(chunks).toBeNull()
|
|
845
|
-
})
|
|
846
|
-
|
|
847
|
-
it('lists all artifacts for url', async () => {
|
|
848
|
-
await provider.setArtifact('Post/post1', 'embedding', {
|
|
849
|
-
content: [0.1],
|
|
850
|
-
sourceHash: 'abc',
|
|
851
|
-
})
|
|
852
|
-
await provider.setArtifact('Post/post1', 'chunks', {
|
|
853
|
-
content: ['chunk1'],
|
|
854
|
-
sourceHash: 'def',
|
|
855
|
-
})
|
|
856
|
-
await provider.setArtifact('Post/post2', 'embedding', {
|
|
857
|
-
content: [0.2],
|
|
858
|
-
sourceHash: 'ghi',
|
|
859
|
-
})
|
|
860
|
-
|
|
861
|
-
const artifacts = await provider.listArtifacts('Post/post1')
|
|
862
|
-
|
|
863
|
-
expect(artifacts).toHaveLength(2)
|
|
864
|
-
expect(artifacts.map((a) => a.type)).toContain('embedding')
|
|
865
|
-
expect(artifacts.map((a) => a.type)).toContain('chunks')
|
|
866
|
-
})
|
|
867
|
-
|
|
868
|
-
it('invalidates non-embedding artifacts on update', async () => {
|
|
869
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
870
|
-
await provider.setArtifact('User/john', 'embedding', {
|
|
871
|
-
content: [0.1],
|
|
872
|
-
sourceHash: 'abc',
|
|
873
|
-
})
|
|
874
|
-
await provider.setArtifact('User/john', 'summary', {
|
|
875
|
-
content: 'A user named John',
|
|
876
|
-
sourceHash: 'def',
|
|
877
|
-
})
|
|
878
|
-
|
|
879
|
-
await provider.update('User', 'john', { name: 'Jane' })
|
|
880
|
-
|
|
881
|
-
const embedding = await provider.getArtifact('User/john', 'embedding')
|
|
882
|
-
const summary = await provider.getArtifact('User/john', 'summary')
|
|
883
|
-
|
|
884
|
-
// Embedding should be preserved, other artifacts invalidated
|
|
885
|
-
expect(embedding).not.toBeNull()
|
|
886
|
-
expect(summary).toBeNull()
|
|
887
|
-
})
|
|
888
|
-
|
|
889
|
-
it('cleans up artifacts on entity delete', async () => {
|
|
890
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
891
|
-
await provider.setArtifact('User/john', 'embedding', {
|
|
892
|
-
content: [0.1],
|
|
893
|
-
sourceHash: 'abc',
|
|
894
|
-
})
|
|
895
|
-
|
|
896
|
-
await provider.delete('User', 'john')
|
|
897
|
-
|
|
898
|
-
const artifacts = await provider.listArtifacts('User/john')
|
|
899
|
-
expect(artifacts).toHaveLength(0)
|
|
900
|
-
})
|
|
901
|
-
})
|
|
902
|
-
|
|
903
|
-
describe('stats with new primitives', () => {
|
|
904
|
-
it('includes event count', async () => {
|
|
905
|
-
await provider.create('User', 'john', { name: 'John' })
|
|
906
|
-
await provider.create('Post', 'post1', { title: 'Hello' })
|
|
907
|
-
|
|
908
|
-
const stats = provider.stats()
|
|
909
|
-
|
|
910
|
-
expect(stats.events).toBeGreaterThanOrEqual(2)
|
|
911
|
-
})
|
|
912
|
-
|
|
913
|
-
it('includes action stats', async () => {
|
|
914
|
-
await provider.createAction({ type: 'task1', data: {} })
|
|
915
|
-
const action2 = await provider.createAction({ type: 'task2', data: {} })
|
|
916
|
-
await provider.updateAction(action2.id, { status: 'active' })
|
|
917
|
-
const action3 = await provider.createAction({ type: 'task3', data: {} })
|
|
918
|
-
await provider.updateAction(action3.id, { status: 'completed' })
|
|
919
|
-
|
|
920
|
-
const stats = provider.stats()
|
|
921
|
-
|
|
922
|
-
expect(stats.actions.pending).toBe(1)
|
|
923
|
-
expect(stats.actions.active).toBe(1)
|
|
924
|
-
expect(stats.actions.completed).toBe(1)
|
|
925
|
-
})
|
|
926
|
-
|
|
927
|
-
it('includes artifact count', async () => {
|
|
928
|
-
await provider.setArtifact('User/john', 'embedding', {
|
|
929
|
-
content: [0.1],
|
|
930
|
-
sourceHash: 'abc',
|
|
931
|
-
})
|
|
932
|
-
await provider.setArtifact('Post/post1', 'embedding', {
|
|
933
|
-
content: [0.2],
|
|
934
|
-
sourceHash: 'def',
|
|
935
|
-
})
|
|
936
|
-
|
|
937
|
-
const stats = provider.stats()
|
|
938
|
-
|
|
939
|
-
expect(stats.artifacts).toBe(2)
|
|
940
|
-
})
|
|
941
|
-
|
|
942
|
-
it('includes concurrency stats', () => {
|
|
943
|
-
const stats = provider.stats()
|
|
944
|
-
|
|
945
|
-
expect(stats.concurrency.active).toBe(0)
|
|
946
|
-
expect(stats.concurrency.pending).toBe(0)
|
|
947
|
-
})
|
|
948
|
-
})
|
|
949
|
-
})
|
|
950
|
-
|
|
951
|
-
describe('Semaphore', () => {
|
|
952
|
-
it('limits concurrent execution', async () => {
|
|
953
|
-
const semaphore = new Semaphore(2)
|
|
954
|
-
const running: number[] = []
|
|
955
|
-
const maxConcurrent: number[] = []
|
|
956
|
-
|
|
957
|
-
const task = async (id: number): Promise<number> => {
|
|
958
|
-
running.push(id)
|
|
959
|
-
maxConcurrent.push(running.length)
|
|
960
|
-
await new Promise((resolve) => setTimeout(resolve, 50))
|
|
961
|
-
running.splice(running.indexOf(id), 1)
|
|
962
|
-
return id
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
const results = await semaphore.map([1, 2, 3, 4, 5], task)
|
|
966
|
-
|
|
967
|
-
expect(results).toEqual([1, 2, 3, 4, 5])
|
|
968
|
-
expect(Math.max(...maxConcurrent)).toBe(2)
|
|
969
|
-
})
|
|
970
|
-
|
|
971
|
-
it('tracks active and pending counts', async () => {
|
|
972
|
-
const semaphore = new Semaphore(1)
|
|
973
|
-
|
|
974
|
-
expect(semaphore.active).toBe(0)
|
|
975
|
-
expect(semaphore.pending).toBe(0)
|
|
976
|
-
|
|
977
|
-
const release = await semaphore.acquire()
|
|
978
|
-
|
|
979
|
-
expect(semaphore.active).toBe(1)
|
|
980
|
-
expect(semaphore.pending).toBe(0)
|
|
981
|
-
|
|
982
|
-
// Start another acquire that will be queued
|
|
983
|
-
const secondPromise = semaphore.acquire()
|
|
984
|
-
|
|
985
|
-
expect(semaphore.pending).toBe(1)
|
|
986
|
-
|
|
987
|
-
release()
|
|
988
|
-
await secondPromise
|
|
989
|
-
|
|
990
|
-
expect(semaphore.active).toBe(1)
|
|
991
|
-
expect(semaphore.pending).toBe(0)
|
|
992
|
-
})
|
|
993
|
-
|
|
994
|
-
it('runs function with concurrency control', async () => {
|
|
995
|
-
const semaphore = new Semaphore(1)
|
|
996
|
-
const results: number[] = []
|
|
997
|
-
|
|
998
|
-
await Promise.all([
|
|
999
|
-
semaphore.run(async () => {
|
|
1000
|
-
results.push(1)
|
|
1001
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
1002
|
-
}),
|
|
1003
|
-
semaphore.run(async () => {
|
|
1004
|
-
results.push(2)
|
|
1005
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
1006
|
-
}),
|
|
1007
|
-
])
|
|
1008
|
-
|
|
1009
|
-
expect(results).toEqual([1, 2])
|
|
1010
|
-
})
|
|
1011
|
-
|
|
1012
|
-
it('handles errors in run', async () => {
|
|
1013
|
-
const semaphore = new Semaphore(1)
|
|
1014
|
-
|
|
1015
|
-
await expect(
|
|
1016
|
-
semaphore.run(async () => {
|
|
1017
|
-
throw new Error('Test error')
|
|
1018
|
-
})
|
|
1019
|
-
).rejects.toThrow('Test error')
|
|
1020
|
-
|
|
1021
|
-
// Semaphore should be released after error
|
|
1022
|
-
expect(semaphore.active).toBe(0)
|
|
1023
|
-
})
|
|
1024
|
-
|
|
1025
|
-
it('processes items in order', async () => {
|
|
1026
|
-
const semaphore = new Semaphore(1)
|
|
1027
|
-
const order: number[] = []
|
|
1028
|
-
|
|
1029
|
-
await semaphore.map([1, 2, 3], async (num) => {
|
|
1030
|
-
order.push(num)
|
|
1031
|
-
return num
|
|
1032
|
-
})
|
|
1033
|
-
|
|
1034
|
-
expect(order).toEqual([1, 2, 3])
|
|
1035
|
-
})
|
|
1036
|
-
})
|