ai-database 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-test.log +102 -0
  3. package/README.md +381 -68
  4. package/TESTING.md +410 -0
  5. package/TEST_SUMMARY.md +250 -0
  6. package/TODO.md +128 -0
  7. package/dist/ai-promise-db.d.ts +370 -0
  8. package/dist/ai-promise-db.d.ts.map +1 -0
  9. package/dist/ai-promise-db.js +839 -0
  10. package/dist/ai-promise-db.js.map +1 -0
  11. package/dist/authorization.d.ts +531 -0
  12. package/dist/authorization.d.ts.map +1 -0
  13. package/dist/authorization.js +632 -0
  14. package/dist/authorization.js.map +1 -0
  15. package/dist/durable-clickhouse.d.ts +193 -0
  16. package/dist/durable-clickhouse.d.ts.map +1 -0
  17. package/dist/durable-clickhouse.js +422 -0
  18. package/dist/durable-clickhouse.js.map +1 -0
  19. package/dist/durable-promise.d.ts +182 -0
  20. package/dist/durable-promise.d.ts.map +1 -0
  21. package/dist/durable-promise.js +409 -0
  22. package/dist/durable-promise.js.map +1 -0
  23. package/dist/execution-queue.d.ts +239 -0
  24. package/dist/execution-queue.d.ts.map +1 -0
  25. package/dist/execution-queue.js +400 -0
  26. package/dist/execution-queue.js.map +1 -0
  27. package/dist/index.d.ts +50 -191
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -462
  30. package/dist/index.js.map +1 -0
  31. package/dist/linguistic.d.ts +115 -0
  32. package/dist/linguistic.d.ts.map +1 -0
  33. package/dist/linguistic.js +379 -0
  34. package/dist/linguistic.js.map +1 -0
  35. package/dist/memory-provider.d.ts +304 -0
  36. package/dist/memory-provider.d.ts.map +1 -0
  37. package/dist/memory-provider.js +785 -0
  38. package/dist/memory-provider.js.map +1 -0
  39. package/dist/schema.d.ts +899 -0
  40. package/dist/schema.d.ts.map +1 -0
  41. package/dist/schema.js +1165 -0
  42. package/dist/schema.js.map +1 -0
  43. package/dist/tests.d.ts +107 -0
  44. package/dist/tests.d.ts.map +1 -0
  45. package/dist/tests.js +568 -0
  46. package/dist/tests.js.map +1 -0
  47. package/dist/types.d.ts +972 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +126 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +37 -37
  52. package/src/ai-promise-db.ts +1243 -0
  53. package/src/authorization.ts +1102 -0
  54. package/src/durable-clickhouse.ts +596 -0
  55. package/src/durable-promise.ts +582 -0
  56. package/src/execution-queue.ts +608 -0
  57. package/src/index.test.ts +868 -0
  58. package/src/index.ts +337 -0
  59. package/src/linguistic.ts +404 -0
  60. package/src/memory-provider.test.ts +1036 -0
  61. package/src/memory-provider.ts +1119 -0
  62. package/src/schema.test.ts +1254 -0
  63. package/src/schema.ts +2296 -0
  64. package/src/tests.ts +725 -0
  65. package/src/types.ts +1177 -0
  66. package/test/README.md +153 -0
  67. package/test/edge-cases.test.ts +646 -0
  68. package/test/provider-resolution.test.ts +402 -0
  69. package/tsconfig.json +9 -0
  70. package/vitest.config.ts +19 -0
  71. package/dist/index.d.mts +0 -195
  72. package/dist/index.mjs +0 -430
@@ -0,0 +1,646 @@
1
+ /**
2
+ * Tests for edge cases and error handling
3
+ *
4
+ * Covers unusual inputs, boundary conditions, and error scenarios.
5
+ */
6
+
7
+ import { describe, it, expect, beforeEach } from 'vitest'
8
+ import { DB, setProvider, createMemoryProvider } from '../src/index.js'
9
+ import type { DatabaseSchema } from '../src/index.js'
10
+
11
+ describe('edge cases', () => {
12
+ beforeEach(() => {
13
+ setProvider(createMemoryProvider())
14
+ })
15
+
16
+ describe('empty and minimal schemas', () => {
17
+ it('handles empty schema', () => {
18
+ const schema: DatabaseSchema = {}
19
+
20
+ const { db } = DB(schema)
21
+
22
+ expect(db.$schema.entities.size).toBe(0)
23
+ expect(typeof db.get).toBe('function')
24
+ expect(typeof db.search).toBe('function')
25
+ })
26
+
27
+ it('handles entity with no fields', () => {
28
+ const schema: DatabaseSchema = {
29
+ Empty: {},
30
+ }
31
+
32
+ const { db } = DB(schema)
33
+
34
+ expect(db.Empty).toBeDefined()
35
+ expect(typeof db.Empty.create).toBe('function')
36
+ })
37
+
38
+ it('creates entity with no data fields', async () => {
39
+ const schema: DatabaseSchema = {
40
+ Marker: {},
41
+ }
42
+
43
+ const { db } = DB(schema)
44
+
45
+ const marker = await db.Marker.create('mark1', {})
46
+
47
+ expect(marker.$id).toBe('mark1')
48
+ expect(marker.$type).toBe('Marker')
49
+ })
50
+ })
51
+
52
+ describe('special characters in IDs', () => {
53
+ const schema = {
54
+ User: { name: 'string' },
55
+ } as const
56
+
57
+ it('handles IDs with hyphens', async () => {
58
+ const { db } = DB(schema)
59
+
60
+ const user = await db.User.create('user-123', { name: 'Test' })
61
+ expect(user.$id).toBe('user-123')
62
+
63
+ const retrieved = await db.User.get('user-123')
64
+ expect(retrieved?.$id).toBe('user-123')
65
+ })
66
+
67
+ it('handles IDs with underscores', async () => {
68
+ const { db } = DB(schema)
69
+
70
+ const user = await db.User.create('user_123', { name: 'Test' })
71
+ expect(user.$id).toBe('user_123')
72
+ })
73
+
74
+ it('handles IDs with dots', async () => {
75
+ const { db } = DB(schema)
76
+
77
+ const user = await db.User.create('user.123', { name: 'Test' })
78
+ expect(user.$id).toBe('user.123')
79
+ })
80
+
81
+ it('handles IDs with slashes (path-like)', async () => {
82
+ const { db } = DB(schema)
83
+
84
+ const user = await db.User.create('org/user/123', { name: 'Test' })
85
+ expect(user.$id).toBe('org/user/123')
86
+ })
87
+
88
+ it('handles UUID-style IDs', async () => {
89
+ const { db } = DB(schema)
90
+
91
+ const uuid = '550e8400-e29b-41d4-a716-446655440000'
92
+ const user = await db.User.create(uuid, { name: 'Test' })
93
+ expect(user.$id).toBe(uuid)
94
+ })
95
+ })
96
+
97
+ describe('special characters in data', () => {
98
+ const schema = {
99
+ User: {
100
+ name: 'string',
101
+ bio: 'string?',
102
+ },
103
+ } as const
104
+
105
+ it('handles unicode characters', async () => {
106
+ const { db } = DB(schema)
107
+
108
+ const user = await db.User.create('user1', {
109
+ name: '日本語',
110
+ bio: '🚀 Emoji test',
111
+ })
112
+
113
+ expect(user.name).toBe('日本語')
114
+ expect(user.bio).toBe('🚀 Emoji test')
115
+ })
116
+
117
+ it('handles newlines in data', async () => {
118
+ const { db } = DB(schema)
119
+
120
+ const user = await db.User.create('user1', {
121
+ name: 'John',
122
+ bio: 'Line 1\nLine 2\nLine 3',
123
+ })
124
+
125
+ expect(user.bio).toContain('\n')
126
+ })
127
+
128
+ it('handles special JSON characters', async () => {
129
+ const { db } = DB(schema)
130
+
131
+ const user = await db.User.create('user1', {
132
+ name: 'Test "quoted" name',
133
+ bio: 'Backslash: \\ and more: \t\n',
134
+ })
135
+
136
+ expect(user.name).toContain('"')
137
+ expect(user.bio).toContain('\\')
138
+ })
139
+ })
140
+
141
+ describe('large data', () => {
142
+ const schema = {
143
+ Document: {
144
+ title: 'string',
145
+ content: 'markdown',
146
+ },
147
+ } as const
148
+
149
+ it('handles large strings', async () => {
150
+ const { db } = DB(schema)
151
+
152
+ const largeContent = 'x'.repeat(100000) // 100KB string
153
+
154
+ const doc = await db.Document.create('doc1', {
155
+ title: 'Large',
156
+ content: largeContent,
157
+ })
158
+
159
+ expect(doc.content.length).toBe(100000)
160
+ })
161
+
162
+ it('handles many entities', async () => {
163
+ const { db } = DB(schema)
164
+
165
+ const count = 1000
166
+ const promises = []
167
+
168
+ for (let i = 0; i < count; i++) {
169
+ promises.push(
170
+ db.Document.create(`doc${i}`, {
171
+ title: `Document ${i}`,
172
+ content: `Content ${i}`,
173
+ })
174
+ )
175
+ }
176
+
177
+ await Promise.all(promises)
178
+
179
+ const docs = await db.Document.list()
180
+ expect(docs.length).toBe(count)
181
+ })
182
+
183
+ it('handles large result sets', async () => {
184
+ const { db } = DB(schema)
185
+
186
+ // Create 100 documents
187
+ for (let i = 0; i < 100; i++) {
188
+ await db.Document.create(`doc${i}`, {
189
+ title: `Document ${i}`,
190
+ content: `Content for document ${i}`,
191
+ })
192
+ }
193
+
194
+ const all = await db.Document.list()
195
+ expect(all.length).toBe(100)
196
+ })
197
+ })
198
+
199
+ describe('concurrent operations', () => {
200
+ const schema = {
201
+ Counter: {
202
+ value: 'number',
203
+ },
204
+ } as const
205
+
206
+ it('handles concurrent creates', async () => {
207
+ const { db } = DB(schema)
208
+
209
+ const promises = []
210
+ for (let i = 0; i < 10; i++) {
211
+ promises.push(
212
+ db.Counter.create(`counter${i}`, { value: i })
213
+ )
214
+ }
215
+
216
+ const results = await Promise.all(promises)
217
+
218
+ expect(results).toHaveLength(10)
219
+ expect(new Set(results.map(r => r.$id)).size).toBe(10)
220
+ })
221
+
222
+ it('handles concurrent updates', async () => {
223
+ const { db } = DB(schema)
224
+
225
+ await db.Counter.create('counter1', { value: 0 })
226
+
227
+ const promises = []
228
+ for (let i = 1; i <= 5; i++) {
229
+ promises.push(
230
+ db.Counter.update('counter1', { value: i })
231
+ )
232
+ }
233
+
234
+ await Promise.all(promises)
235
+
236
+ const counter = await db.Counter.get('counter1')
237
+ expect(counter?.value).toBeDefined()
238
+ })
239
+
240
+ it('handles concurrent list operations', async () => {
241
+ const { db } = DB(schema)
242
+
243
+ await db.Counter.create('counter1', { value: 1 })
244
+ await db.Counter.create('counter2', { value: 2 })
245
+
246
+ const promises = []
247
+ for (let i = 0; i < 10; i++) {
248
+ promises.push(db.Counter.list())
249
+ }
250
+
251
+ const results = await Promise.all(promises)
252
+
253
+ results.forEach(result => {
254
+ expect(result.length).toBe(2)
255
+ })
256
+ })
257
+ })
258
+
259
+ describe('optional fields', () => {
260
+ const schema = {
261
+ User: {
262
+ name: 'string',
263
+ email: 'string?',
264
+ age: 'number?',
265
+ bio: 'string?',
266
+ },
267
+ } as const
268
+
269
+ it('creates entity with missing optional fields', async () => {
270
+ const { db } = DB(schema)
271
+
272
+ const user = await db.User.create('user1', {
273
+ name: 'John',
274
+ })
275
+
276
+ expect(user.name).toBe('John')
277
+ expect(user.email).toBeUndefined()
278
+ expect(user.age).toBeUndefined()
279
+ })
280
+
281
+ it('creates entity with some optional fields', async () => {
282
+ const { db } = DB(schema)
283
+
284
+ const user = await db.User.create('user1', {
285
+ name: 'John',
286
+ email: 'john@example.com',
287
+ })
288
+
289
+ expect(user.email).toBe('john@example.com')
290
+ expect(user.age).toBeUndefined()
291
+ })
292
+
293
+ it('updates to set optional field', async () => {
294
+ const { db } = DB(schema)
295
+
296
+ await db.User.create('user1', { name: 'John' })
297
+
298
+ const updated = await db.User.update('user1', {
299
+ email: 'john@example.com',
300
+ })
301
+
302
+ expect(updated.email).toBe('john@example.com')
303
+ })
304
+
305
+ it('updates to unset optional field (set to undefined)', async () => {
306
+ const { db } = DB(schema)
307
+
308
+ await db.User.create('user1', {
309
+ name: 'John',
310
+ email: 'john@example.com',
311
+ })
312
+
313
+ const updated = await db.User.update('user1', {
314
+ email: undefined,
315
+ })
316
+
317
+ expect(updated.email).toBeUndefined()
318
+ })
319
+ })
320
+
321
+ describe('array fields', () => {
322
+ const schema = {
323
+ Post: {
324
+ title: 'string',
325
+ tags: 'string[]',
326
+ scores: 'number[]',
327
+ },
328
+ } as const
329
+
330
+ it('creates entity with empty arrays', async () => {
331
+ const { db } = DB(schema)
332
+
333
+ const post = await db.Post.create('post1', {
334
+ title: 'Test',
335
+ tags: [],
336
+ scores: [],
337
+ })
338
+
339
+ expect(post.tags).toEqual([])
340
+ expect(post.scores).toEqual([])
341
+ })
342
+
343
+ it('creates entity with array values', async () => {
344
+ const { db } = DB(schema)
345
+
346
+ const post = await db.Post.create('post1', {
347
+ title: 'Test',
348
+ tags: ['typescript', 'javascript'],
349
+ scores: [1, 2, 3],
350
+ })
351
+
352
+ expect(post.tags).toEqual(['typescript', 'javascript'])
353
+ expect(post.scores).toEqual([1, 2, 3])
354
+ })
355
+
356
+ it('updates array fields', async () => {
357
+ const { db } = DB(schema)
358
+
359
+ await db.Post.create('post1', {
360
+ title: 'Test',
361
+ tags: ['old'],
362
+ scores: [1],
363
+ })
364
+
365
+ const updated = await db.Post.update('post1', {
366
+ tags: ['new', 'tags'],
367
+ })
368
+
369
+ expect(updated.tags).toEqual(['new', 'tags'])
370
+ })
371
+ })
372
+
373
+ describe('URL parsing edge cases', () => {
374
+ const schema = {
375
+ User: { name: 'string' },
376
+ } as const
377
+
378
+ it('parses full HTTPS URL', async () => {
379
+ const { db } = DB(schema)
380
+
381
+ await db.User.create('john', { name: 'John' })
382
+
383
+ const user = await db.get('https://example.com/User/john')
384
+ expect(user).toBeDefined()
385
+ })
386
+
387
+ it('parses HTTP URL', async () => {
388
+ const { db } = DB(schema)
389
+
390
+ await db.User.create('john', { name: 'John' })
391
+
392
+ const user = await db.get('http://example.com/User/john')
393
+ expect(user).toBeDefined()
394
+ })
395
+
396
+ it('parses type/id path', async () => {
397
+ const { db } = DB(schema)
398
+
399
+ await db.User.create('john', { name: 'John' })
400
+
401
+ const user = await db.get('User/john')
402
+ expect(user).toBeDefined()
403
+ })
404
+
405
+ it('parses nested path IDs', async () => {
406
+ const { db } = DB(schema)
407
+
408
+ await db.User.create('org/team/john', { name: 'John' })
409
+
410
+ const user = await db.get('User/org/team/john')
411
+ expect(user).toBeDefined()
412
+ })
413
+
414
+ it('handles URL with query parameters', async () => {
415
+ const { db } = DB(schema)
416
+
417
+ await db.User.create('john', { name: 'John' })
418
+
419
+ // Should ignore query params
420
+ const user = await db.get('https://example.com/User/john?param=value')
421
+ expect(user).toBeDefined()
422
+ })
423
+
424
+ it('handles URL with hash', async () => {
425
+ const { db } = DB(schema)
426
+
427
+ await db.User.create('john', { name: 'John' })
428
+
429
+ // Should ignore hash
430
+ const user = await db.get('https://example.com/User/john#section')
431
+ expect(user).toBeDefined()
432
+ })
433
+ })
434
+
435
+ describe('relation edge cases', () => {
436
+ const schema = {
437
+ User: {
438
+ name: 'string',
439
+ manager: 'User.reports?',
440
+ },
441
+ } as const
442
+
443
+ it('handles self-referential optional relation', async () => {
444
+ const { db } = DB(schema)
445
+
446
+ const user = await db.User.create('john', {
447
+ name: 'John',
448
+ })
449
+
450
+ // Verify the user was created correctly
451
+ expect(user.$id).toBe('john')
452
+ expect(user.name).toBe('John')
453
+ })
454
+
455
+ it('handles circular relations through provider', async () => {
456
+ const { db } = DB(schema)
457
+ const provider = createMemoryProvider()
458
+ setProvider(provider)
459
+
460
+ await db.User.create('alice', { name: 'Alice' })
461
+ await db.User.create('bob', { name: 'Bob' })
462
+
463
+ // Alice manages Bob, Bob manages Alice (circular)
464
+ await provider.relate('User', 'alice', 'reports', 'User', 'bob')
465
+ await provider.relate('User', 'bob', 'reports', 'User', 'alice')
466
+
467
+ const aliceReports = await provider.related('User', 'alice', 'reports')
468
+ const bobReports = await provider.related('User', 'bob', 'reports')
469
+
470
+ expect(aliceReports).toHaveLength(1)
471
+ expect(bobReports).toHaveLength(1)
472
+ })
473
+ })
474
+
475
+ describe('search edge cases', () => {
476
+ const schema = {
477
+ Post: {
478
+ title: 'string',
479
+ content: 'markdown',
480
+ },
481
+ } as const
482
+
483
+ it('searches for empty string', async () => {
484
+ const { db } = DB(schema)
485
+
486
+ await db.Post.create('post1', {
487
+ title: 'Test',
488
+ content: 'Content',
489
+ })
490
+
491
+ const results = await db.Post.search('')
492
+
493
+ // Empty search might return all or none - implementation dependent
494
+ expect(Array.isArray(results)).toBe(true)
495
+ })
496
+
497
+ it('searches for special regex characters', async () => {
498
+ const { db } = DB(schema)
499
+
500
+ await db.Post.create('post1', {
501
+ title: 'Test [brackets]',
502
+ content: 'Content with (parens)',
503
+ })
504
+
505
+ // Should not throw regex error
506
+ const results = await db.Post.search('[brackets]')
507
+ expect(Array.isArray(results)).toBe(true)
508
+ })
509
+
510
+ it('searches for very long query', async () => {
511
+ const { db } = DB(schema)
512
+
513
+ await db.Post.create('post1', {
514
+ title: 'Test',
515
+ content: 'Content',
516
+ })
517
+
518
+ const longQuery = 'word '.repeat(1000)
519
+ const results = await db.Post.search(longQuery)
520
+
521
+ expect(Array.isArray(results)).toBe(true)
522
+ })
523
+
524
+ it('searches with minScore 0', async () => {
525
+ const { db } = DB(schema)
526
+
527
+ await db.Post.create('post1', {
528
+ title: 'Test TypeScript',
529
+ content: 'Content',
530
+ })
531
+
532
+ const results = await db.Post.search('TypeScript', {
533
+ minScore: 0,
534
+ })
535
+
536
+ expect(results.length).toBeGreaterThan(0)
537
+ })
538
+
539
+ it('searches with minScore 1', async () => {
540
+ const { db } = DB(schema)
541
+
542
+ await db.Post.create('post1', {
543
+ title: 'Test',
544
+ content: 'Content',
545
+ })
546
+
547
+ const results = await db.Post.search('Test', {
548
+ minScore: 1,
549
+ })
550
+
551
+ // Very high threshold might return only exact matches
552
+ expect(Array.isArray(results)).toBe(true)
553
+ })
554
+ })
555
+
556
+ describe('pagination edge cases', () => {
557
+ const schema = {
558
+ Item: { value: 'number' },
559
+ } as const
560
+
561
+ beforeEach(async () => {
562
+ const { db } = DB(schema)
563
+ for (let i = 0; i < 10; i++) {
564
+ await db.Item.create(`item${i}`, { value: i })
565
+ }
566
+ })
567
+
568
+ it('handles offset beyond result count', async () => {
569
+ const { db } = DB(schema)
570
+
571
+ const results = await db.Item.list({
572
+ offset: 100,
573
+ })
574
+
575
+ expect(results).toEqual([])
576
+ })
577
+
578
+ it('handles limit of 0', async () => {
579
+ const { db } = DB(schema)
580
+
581
+ const results = await db.Item.list({
582
+ limit: 0,
583
+ })
584
+
585
+ // Limit 0 might return empty or all - implementation dependent
586
+ expect(Array.isArray(results)).toBe(true)
587
+ })
588
+
589
+ it('handles negative limit (treated as invalid)', async () => {
590
+ const { db } = DB(schema)
591
+
592
+ const results = await db.Item.list({
593
+ limit: -1,
594
+ })
595
+
596
+ // Negative limit should be handled gracefully
597
+ expect(Array.isArray(results)).toBe(true)
598
+ })
599
+
600
+ it('handles very large limit', async () => {
601
+ const { db } = DB(schema)
602
+
603
+ const results = await db.Item.list({
604
+ limit: 1000000,
605
+ })
606
+
607
+ expect(results.length).toBeLessThanOrEqual(10)
608
+ })
609
+ })
610
+
611
+ describe('type coercion', () => {
612
+ const schema = {
613
+ Mixed: {
614
+ str: 'string',
615
+ num: 'number',
616
+ bool: 'boolean',
617
+ },
618
+ } as const
619
+
620
+ it('stores values as provided', async () => {
621
+ const { db } = DB(schema)
622
+
623
+ const item = await db.Mixed.create('item1', {
624
+ str: 'hello',
625
+ num: 42,
626
+ bool: true,
627
+ })
628
+
629
+ expect(typeof item.str).toBe('string')
630
+ expect(typeof item.num).toBe('number')
631
+ expect(typeof item.bool).toBe('boolean')
632
+ })
633
+
634
+ it('handles null values', async () => {
635
+ const { db } = DB(schema)
636
+
637
+ const item = await db.Mixed.create('item1', {
638
+ str: 'test',
639
+ num: 1,
640
+ bool: false,
641
+ } as any)
642
+
643
+ expect(item.str).toBe('test')
644
+ })
645
+ })
646
+ })