ai-database 0.0.0-development → 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 (79) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/.turbo/turbo-test.log +102 -0
  3. package/README.md +402 -47
  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 +54 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -0
  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 -23
  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/LICENSE +0 -21
  72. package/dist/types/database.d.ts +0 -46
  73. package/dist/types/document.d.ts +0 -15
  74. package/dist/types/index.d.ts +0 -5
  75. package/dist/types/mdxdb/embedding.d.ts +0 -7
  76. package/dist/types/mdxdb/types.d.ts +0 -59
  77. package/dist/types/synthetic.d.ts +0 -9
  78. package/dist/types/tools.d.ts +0 -10
  79. package/dist/types/vector.d.ts +0 -16
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Linguistic Helpers
3
+ *
4
+ * Utilities for verb conjugation, noun pluralization, and linguistic inference.
5
+ * Used for auto-generating forms, events, and semantic metadata.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import { Verbs, type Noun, type Verb, type TypeMeta } from './types.js'
11
+
12
+ // =============================================================================
13
+ // Internal Helpers
14
+ // =============================================================================
15
+
16
+ function capitalize(s: string): string {
17
+ return s.charAt(0).toUpperCase() + s.slice(1)
18
+ }
19
+
20
+ function preserveCase(original: string, replacement: string): string {
21
+ if (original[0] === original[0]?.toUpperCase()) {
22
+ return capitalize(replacement)
23
+ }
24
+ return replacement
25
+ }
26
+
27
+ function isVowel(char: string | undefined): boolean {
28
+ return char ? 'aeiou'.includes(char.toLowerCase()) : false
29
+ }
30
+
31
+ function splitCamelCase(s: string): string[] {
32
+ return s.replace(/([a-z])([A-Z])/g, '$1 $2').split(' ')
33
+ }
34
+
35
+ /** Check if we should double the final consonant (CVC pattern) */
36
+ function shouldDoubleConsonant(verb: string): boolean {
37
+ if (verb.length < 2) return false
38
+ const last = verb[verb.length - 1]!
39
+ const secondLast = verb[verb.length - 2]!
40
+ // Don't double w, x, y
41
+ if ('wxy'.includes(last)) return false
42
+ // Must end in consonant preceded by vowel
43
+ if (isVowel(last) || !isVowel(secondLast)) return false
44
+ // Common verbs that double the final consonant
45
+ const doublingVerbs = ['submit', 'commit', 'permit', 'omit', 'admit', 'emit', 'transmit', 'refer', 'prefer', 'defer', 'occur', 'recur', 'begin', 'stop', 'drop', 'shop', 'plan', 'scan', 'ban', 'run', 'gun', 'stun', 'cut', 'shut', 'hit', 'sit', 'fit', 'spit', 'quit', 'knit', 'get', 'set', 'pet', 'wet', 'bet', 'let', 'put', 'drag', 'brag', 'flag', 'tag', 'bag', 'nag', 'wag', 'hug', 'bug', 'mug', 'tug', 'rub', 'scrub', 'grab', 'stab', 'rob', 'sob', 'throb', 'nod', 'prod', 'plod', 'plot', 'rot', 'blot', 'spot', 'knot', 'trot', 'chat', 'pat', 'bat', 'mat', 'rat', 'slap', 'clap', 'flap', 'tap', 'wrap', 'snap', 'trap', 'cap', 'map', 'nap', 'zap', 'tip', 'sip', 'dip', 'rip', 'zip', 'slip', 'trip', 'drip', 'chip', 'clip', 'flip', 'grip', 'ship', 'skip', 'whip', 'strip', 'equip', 'hop', 'pop', 'mop', 'cop', 'chop', 'crop', 'prop', 'flop', 'swim', 'trim', 'slim', 'skim', 'dim', 'rim', 'brim', 'grim', 'hem', 'stem', 'jam', 'cram', 'ram', 'slam', 'dam', 'ham', 'scam', 'spam', 'tram', 'hum', 'drum', 'strum', 'sum', 'gum', 'chum', 'plum']
46
+ // Short words (3 letters) almost always double
47
+ if (verb.length <= 3) return true
48
+ // Check if verb matches any known doubling pattern
49
+ return doublingVerbs.some(v => verb === v || verb.endsWith(v))
50
+ }
51
+
52
+ /** Convert verb to past participle (create → created, publish → published) */
53
+ function toPastParticiple(verb: string): string {
54
+ if (verb.endsWith('e')) return verb + 'd'
55
+ if (verb.endsWith('y') && !isVowel(verb[verb.length - 2])) {
56
+ return verb.slice(0, -1) + 'ied'
57
+ }
58
+ if (shouldDoubleConsonant(verb)) {
59
+ return verb + verb[verb.length - 1] + 'ed'
60
+ }
61
+ return verb + 'ed'
62
+ }
63
+
64
+ /** Convert verb to actor noun (create → creator, publish → publisher) */
65
+ function toActor(verb: string): string {
66
+ if (verb.endsWith('e')) return verb + 'r'
67
+ if (verb.endsWith('y') && !isVowel(verb[verb.length - 2])) {
68
+ return verb.slice(0, -1) + 'ier'
69
+ }
70
+ if (shouldDoubleConsonant(verb)) {
71
+ return verb + verb[verb.length - 1] + 'er'
72
+ }
73
+ return verb + 'er'
74
+ }
75
+
76
+ /** Convert verb to present 3rd person (create → creates, publish → publishes) */
77
+ function toPresent(verb: string): string {
78
+ if (verb.endsWith('y') && !isVowel(verb[verb.length - 2])) {
79
+ return verb.slice(0, -1) + 'ies'
80
+ }
81
+ if (verb.endsWith('s') || verb.endsWith('x') || verb.endsWith('z') ||
82
+ verb.endsWith('ch') || verb.endsWith('sh')) {
83
+ return verb + 'es'
84
+ }
85
+ return verb + 's'
86
+ }
87
+
88
+ /** Convert verb to gerund (create → creating, publish → publishing) */
89
+ function toGerund(verb: string): string {
90
+ if (verb.endsWith('ie')) return verb.slice(0, -2) + 'ying'
91
+ if (verb.endsWith('e') && !verb.endsWith('ee')) return verb.slice(0, -1) + 'ing'
92
+ if (shouldDoubleConsonant(verb)) {
93
+ return verb + verb[verb.length - 1] + 'ing'
94
+ }
95
+ return verb + 'ing'
96
+ }
97
+
98
+ /** Convert verb to result noun (create → creation, publish → publication) */
99
+ function toResult(verb: string): string {
100
+ // Common -ate → -ation
101
+ if (verb.endsWith('ate')) return verb.slice(0, -1) + 'ion'
102
+ // Common -ify → -ification
103
+ if (verb.endsWith('ify')) return verb.slice(0, -1) + 'ication'
104
+ // Common -ize → -ization
105
+ if (verb.endsWith('ize')) return verb.slice(0, -1) + 'ation'
106
+ // Common -e → -ion (but not always correct)
107
+ if (verb.endsWith('e')) return verb.slice(0, -1) + 'ion'
108
+ // Default: just add -ion
109
+ return verb + 'ion'
110
+ }
111
+
112
+ // =============================================================================
113
+ // Public API
114
+ // =============================================================================
115
+
116
+ /**
117
+ * Auto-conjugate a verb from just the base form
118
+ *
119
+ * Given just "publish", generates all forms:
120
+ * - actor: publisher
121
+ * - act: publishes
122
+ * - activity: publishing
123
+ * - result: publication
124
+ * - reverse: { at: publishedAt, by: publishedBy, ... }
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * conjugate('publish')
129
+ * // => { action: 'publish', actor: 'publisher', act: 'publishes', activity: 'publishing', ... }
130
+ *
131
+ * conjugate('create')
132
+ * // => { action: 'create', actor: 'creator', act: 'creates', activity: 'creating', ... }
133
+ * ```
134
+ */
135
+ export function conjugate(action: string): Verb {
136
+ // Check if it's a known verb first
137
+ if (action in Verbs) {
138
+ return Verbs[action as keyof typeof Verbs]
139
+ }
140
+
141
+ const base = action.toLowerCase()
142
+ const pastParticiple = toPastParticiple(base)
143
+
144
+ return {
145
+ action: base,
146
+ actor: toActor(base),
147
+ act: toPresent(base),
148
+ activity: toGerund(base),
149
+ result: toResult(base),
150
+ reverse: {
151
+ at: `${pastParticiple}At`,
152
+ by: `${pastParticiple}By`,
153
+ in: `${pastParticiple}In`,
154
+ for: `${pastParticiple}For`,
155
+ },
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Auto-pluralize a noun
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * pluralize('post') // => 'posts'
165
+ * pluralize('category') // => 'categories'
166
+ * pluralize('person') // => 'people'
167
+ * pluralize('child') // => 'children'
168
+ * ```
169
+ */
170
+ export function pluralize(singular: string): string {
171
+ const lower = singular.toLowerCase()
172
+
173
+ // Irregular plurals
174
+ const irregulars: Record<string, string> = {
175
+ person: 'people',
176
+ child: 'children',
177
+ man: 'men',
178
+ woman: 'women',
179
+ foot: 'feet',
180
+ tooth: 'teeth',
181
+ goose: 'geese',
182
+ mouse: 'mice',
183
+ ox: 'oxen',
184
+ leaf: 'leaves',
185
+ life: 'lives',
186
+ knife: 'knives',
187
+ wife: 'wives',
188
+ half: 'halves',
189
+ self: 'selves',
190
+ calf: 'calves',
191
+ analysis: 'analyses',
192
+ crisis: 'crises',
193
+ thesis: 'theses',
194
+ datum: 'data',
195
+ medium: 'media',
196
+ criterion: 'criteria',
197
+ phenomenon: 'phenomena',
198
+ }
199
+
200
+ if (irregulars[lower]) {
201
+ return preserveCase(singular, irregulars[lower])
202
+ }
203
+
204
+ // Rules for regular plurals
205
+ if (lower.endsWith('y') && !isVowel(lower[lower.length - 2])) {
206
+ return singular.slice(0, -1) + 'ies'
207
+ }
208
+ // Words ending in z that double: quiz → quizzes, fez → fezzes
209
+ if (lower.endsWith('z') && !lower.endsWith('zz')) {
210
+ return singular + 'zes'
211
+ }
212
+ if (lower.endsWith('s') || lower.endsWith('x') || lower.endsWith('zz') ||
213
+ lower.endsWith('ch') || lower.endsWith('sh')) {
214
+ return singular + 'es'
215
+ }
216
+ if (lower.endsWith('f')) {
217
+ return singular.slice(0, -1) + 'ves'
218
+ }
219
+ if (lower.endsWith('fe')) {
220
+ return singular.slice(0, -2) + 'ves'
221
+ }
222
+
223
+ return singular + 's'
224
+ }
225
+
226
+ /**
227
+ * Auto-singularize a noun (reverse of pluralize)
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * singularize('posts') // => 'post'
232
+ * singularize('categories') // => 'category'
233
+ * singularize('people') // => 'person'
234
+ * ```
235
+ */
236
+ export function singularize(plural: string): string {
237
+ const lower = plural.toLowerCase()
238
+
239
+ // Irregular singulars
240
+ const irregulars: Record<string, string> = {
241
+ people: 'person',
242
+ children: 'child',
243
+ men: 'man',
244
+ women: 'woman',
245
+ feet: 'foot',
246
+ teeth: 'tooth',
247
+ geese: 'goose',
248
+ mice: 'mouse',
249
+ oxen: 'ox',
250
+ leaves: 'leaf',
251
+ lives: 'life',
252
+ knives: 'knife',
253
+ wives: 'wife',
254
+ halves: 'half',
255
+ selves: 'self',
256
+ calves: 'calf',
257
+ analyses: 'analysis',
258
+ crises: 'crisis',
259
+ theses: 'thesis',
260
+ data: 'datum',
261
+ media: 'medium',
262
+ criteria: 'criterion',
263
+ phenomena: 'phenomenon',
264
+ }
265
+
266
+ if (irregulars[lower]) {
267
+ return preserveCase(plural, irregulars[lower])
268
+ }
269
+
270
+ // Rules for regular singulars
271
+ if (lower.endsWith('ies')) {
272
+ return plural.slice(0, -3) + 'y'
273
+ }
274
+ if (lower.endsWith('ves')) {
275
+ return plural.slice(0, -3) + 'f'
276
+ }
277
+ if (lower.endsWith('es') && (
278
+ lower.endsWith('sses') || lower.endsWith('xes') || lower.endsWith('zes') ||
279
+ lower.endsWith('ches') || lower.endsWith('shes')
280
+ )) {
281
+ return plural.slice(0, -2)
282
+ }
283
+ if (lower.endsWith('s') && !lower.endsWith('ss')) {
284
+ return plural.slice(0, -1)
285
+ }
286
+
287
+ return plural
288
+ }
289
+
290
+ /**
291
+ * Infer a complete Noun from just a type name
292
+ *
293
+ * @example
294
+ * ```ts
295
+ * inferNoun('BlogPost')
296
+ * // => { singular: 'blog post', plural: 'blog posts', ... }
297
+ *
298
+ * inferNoun('Category')
299
+ * // => { singular: 'category', plural: 'categories', ... }
300
+ * ```
301
+ */
302
+ export function inferNoun(typeName: string): Noun {
303
+ const words = splitCamelCase(typeName)
304
+ const singular = words.join(' ').toLowerCase()
305
+ const plural = words.slice(0, -1).concat(pluralize(words[words.length - 1]!)).join(' ').toLowerCase()
306
+
307
+ return {
308
+ singular,
309
+ plural,
310
+ actions: ['create', 'update', 'delete'],
311
+ events: ['created', 'updated', 'deleted'],
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Create TypeMeta from a type name - all linguistic forms auto-inferred
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * const meta = createTypeMeta('BlogPost')
321
+ * meta.singular // 'blog post'
322
+ * meta.plural // 'blog posts'
323
+ * meta.slug // 'blog-post'
324
+ * meta.created // 'BlogPost.created'
325
+ * meta.createdAt // 'createdAt'
326
+ * meta.creator // 'creator'
327
+ * ```
328
+ */
329
+ export function createTypeMeta(typeName: string): TypeMeta {
330
+ const noun = inferNoun(typeName)
331
+ const slug = noun.singular.replace(/\s+/g, '-')
332
+ const slugPlural = noun.plural.replace(/\s+/g, '-')
333
+
334
+ return {
335
+ name: typeName,
336
+ singular: noun.singular,
337
+ plural: noun.plural,
338
+ slug,
339
+ slugPlural,
340
+
341
+ // From Verbs.create
342
+ creator: 'creator',
343
+ createdAt: 'createdAt',
344
+ createdBy: 'createdBy',
345
+ updatedAt: 'updatedAt',
346
+ updatedBy: 'updatedBy',
347
+
348
+ // Event types
349
+ created: `${typeName}.created`,
350
+ updated: `${typeName}.updated`,
351
+ deleted: `${typeName}.deleted`,
352
+ }
353
+ }
354
+
355
+ /** Cache of TypeMeta by type name */
356
+ const typeMetaCache = new Map<string, TypeMeta>()
357
+
358
+ /**
359
+ * Get or create TypeMeta for a type name (cached)
360
+ */
361
+ export function getTypeMeta(typeName: string): TypeMeta {
362
+ let meta = typeMetaCache.get(typeName)
363
+ if (!meta) {
364
+ meta = createTypeMeta(typeName)
365
+ typeMetaCache.set(typeName, meta)
366
+ }
367
+ return meta
368
+ }
369
+
370
+ /**
371
+ * Type proxy - provides dynamic access to type metadata
372
+ *
373
+ * @example
374
+ * ```ts
375
+ * const Post = Type('Post')
376
+ * Post.singular // 'post'
377
+ * Post.plural // 'posts'
378
+ * Post.created // 'Post.created'
379
+ *
380
+ * // In event handlers:
381
+ * on.create(thing => {
382
+ * console.log(thing.$type.plural) // 'posts'
383
+ * })
384
+ * ```
385
+ */
386
+ export function Type(name: string): TypeMeta {
387
+ return getTypeMeta(name)
388
+ }
389
+
390
+ /**
391
+ * Get reverse property names for a verb action
392
+ *
393
+ * @example
394
+ * ```ts
395
+ * getVerbFields('create')
396
+ * // => { at: 'createdAt', by: 'createdBy', in: 'createdIn', for: 'createdFor' }
397
+ *
398
+ * getVerbFields('publish')
399
+ * // => { at: 'publishedAt', by: 'publishedBy' }
400
+ * ```
401
+ */
402
+ export function getVerbFields(action: keyof typeof Verbs): Record<string, string> {
403
+ return Verbs[action]?.reverse ?? {}
404
+ }