@utilarium/overcontext 0.0.4-dev.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 (87) hide show
  1. package/LICENSE +65 -0
  2. package/README.md +286 -0
  3. package/dist/api/builder.d.ts +26 -0
  4. package/dist/api/builder.js +24 -0
  5. package/dist/api/builder.js.map +1 -0
  6. package/dist/api/context.d.ts +90 -0
  7. package/dist/api/context.js +94 -0
  8. package/dist/api/context.js.map +1 -0
  9. package/dist/api/index.d.ts +6 -0
  10. package/dist/api/query-builder.d.ts +51 -0
  11. package/dist/api/query-builder.js +95 -0
  12. package/dist/api/query-builder.js.map +1 -0
  13. package/dist/api/query.d.ts +32 -0
  14. package/dist/api/search.d.ts +20 -0
  15. package/dist/api/search.js +112 -0
  16. package/dist/api/search.js.map +1 -0
  17. package/dist/api/slug.d.ts +10 -0
  18. package/dist/api/slug.js +55 -0
  19. package/dist/api/slug.js.map +1 -0
  20. package/dist/cli/builder.d.ts +74 -0
  21. package/dist/cli/builder.js +42 -0
  22. package/dist/cli/builder.js.map +1 -0
  23. package/dist/cli/commands.d.ts +53 -0
  24. package/dist/cli/commands.js +57 -0
  25. package/dist/cli/commands.js.map +1 -0
  26. package/dist/cli/formatters.d.ts +15 -0
  27. package/dist/cli/formatters.js +50 -0
  28. package/dist/cli/formatters.js.map +1 -0
  29. package/dist/cli/index.d.ts +3 -0
  30. package/dist/discovery/context-root.d.ts +31 -0
  31. package/dist/discovery/context-root.js +48 -0
  32. package/dist/discovery/context-root.js.map +1 -0
  33. package/dist/discovery/hierarchical-provider.d.ts +13 -0
  34. package/dist/discovery/hierarchical-provider.js +102 -0
  35. package/dist/discovery/hierarchical-provider.js.map +1 -0
  36. package/dist/discovery/index.d.ts +18 -0
  37. package/dist/discovery/index.js +47 -0
  38. package/dist/discovery/index.js.map +1 -0
  39. package/dist/discovery/walker.d.ts +36 -0
  40. package/dist/discovery/walker.js +87 -0
  41. package/dist/discovery/walker.js.map +1 -0
  42. package/dist/index.cjs +1763 -0
  43. package/dist/index.cjs.map +1 -0
  44. package/dist/index.d.ts +9 -0
  45. package/dist/index.js +24 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/namespace/index.d.ts +3 -0
  48. package/dist/namespace/multi-context.d.ts +40 -0
  49. package/dist/namespace/multi-context.js +72 -0
  50. package/dist/namespace/multi-context.js.map +1 -0
  51. package/dist/namespace/resolver.d.ts +33 -0
  52. package/dist/namespace/resolver.js +85 -0
  53. package/dist/namespace/resolver.js.map +1 -0
  54. package/dist/namespace/types.d.ts +41 -0
  55. package/dist/overcontext.d.ts +7 -0
  56. package/dist/schema/base.d.ts +29 -0
  57. package/dist/schema/base.js +24 -0
  58. package/dist/schema/base.js.map +1 -0
  59. package/dist/schema/builder.d.ts +28 -0
  60. package/dist/schema/builder.js +39 -0
  61. package/dist/schema/builder.js.map +1 -0
  62. package/dist/schema/index.d.ts +5 -0
  63. package/dist/schema/inference.d.ts +49 -0
  64. package/dist/schema/inference.js +15 -0
  65. package/dist/schema/inference.js.map +1 -0
  66. package/dist/schema/registry.d.ts +74 -0
  67. package/dist/schema/registry.js +117 -0
  68. package/dist/schema/registry.js.map +1 -0
  69. package/dist/schema/validation.d.ts +26 -0
  70. package/dist/schema/validation.js +51 -0
  71. package/dist/schema/validation.js.map +1 -0
  72. package/dist/storage/errors.d.ts +35 -0
  73. package/dist/storage/errors.js +58 -0
  74. package/dist/storage/errors.js.map +1 -0
  75. package/dist/storage/events.d.ts +50 -0
  76. package/dist/storage/filesystem.d.ts +10 -0
  77. package/dist/storage/filesystem.js +284 -0
  78. package/dist/storage/filesystem.js.map +1 -0
  79. package/dist/storage/index.d.ts +6 -0
  80. package/dist/storage/interface.d.ts +109 -0
  81. package/dist/storage/memory.d.ts +7 -0
  82. package/dist/storage/memory.js +128 -0
  83. package/dist/storage/memory.js.map +1 -0
  84. package/dist/storage/observable.d.ts +6 -0
  85. package/dist/storage/observable.js +98 -0
  86. package/dist/storage/observable.js.map +1 -0
  87. package/package.json +85 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,1763 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const zod = require('zod');
6
+ const fs = require('node:fs/promises');
7
+ const node_fs = require('node:fs');
8
+ const path = require('node:path');
9
+ const yaml = require('js-yaml');
10
+
11
+ function _interopNamespaceDefault(e) {
12
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: 'Module' } });
13
+ if (e) {
14
+ for (const k in e) {
15
+ if (k !== 'default') {
16
+ const d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: () => e[k]
20
+ });
21
+ }
22
+ }
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ const fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
29
+ const path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
30
+ const yaml__namespace = /*#__PURE__*/_interopNamespaceDefault(yaml);
31
+
32
+ /**
33
+ * Metadata that overcontext manages automatically.
34
+ * Consumers don't need to define these.
35
+ */ const EntityMetadataSchema = zod.z.object({
36
+ createdAt: zod.z.date().optional(),
37
+ updatedAt: zod.z.date().optional(),
38
+ createdBy: zod.z.string().optional(),
39
+ namespace: zod.z.string().optional(),
40
+ source: zod.z.string().optional()
41
+ });
42
+ /**
43
+ * The minimal contract every entity must satisfy.
44
+ * Consuming libraries extend this with their own fields.
45
+ */ const BaseEntitySchema = zod.z.object({
46
+ /** Unique identifier within the entity type (used as filename) */ id: zod.z.string().min(1),
47
+ /** Human-readable name (used for display and search) */ name: zod.z.string().min(1),
48
+ /** Entity type discriminator (must be a string literal in extensions) */ type: zod.z.string().min(1),
49
+ /** Optional notes - common enough to include in base */ notes: zod.z.string().optional()
50
+ }).merge(EntityMetadataSchema);
51
+
52
+ /**
53
+ * Helper to create a properly typed entity schema.
54
+ * Ensures the schema extends BaseEntitySchema.
55
+ */ const createEntitySchema = (typeName, extension)=>{
56
+ return BaseEntitySchema.extend({
57
+ type: zod.z.literal(typeName),
58
+ ...extension
59
+ });
60
+ };
61
+
62
+ /**
63
+ * Validate that an object satisfies at least the base entity contract.
64
+ */ const validateBaseEntity = (data)=>{
65
+ const result = BaseEntitySchema.safeParse(data);
66
+ if (result.success) {
67
+ return {
68
+ success: true,
69
+ data: result.data
70
+ };
71
+ }
72
+ return {
73
+ success: false,
74
+ errors: result.error.issues.map((e)=>({
75
+ path: e.path.join('.'),
76
+ message: e.message
77
+ }))
78
+ };
79
+ };
80
+ /**
81
+ * Validate data against a specific schema.
82
+ */ const validateEntity = (schema, data)=>{
83
+ const result = schema.safeParse(data);
84
+ if (result.success) {
85
+ return {
86
+ success: true,
87
+ data: result.data
88
+ };
89
+ }
90
+ return {
91
+ success: false,
92
+ errors: result.error.issues.map((e)=>({
93
+ path: e.path.join('.'),
94
+ message: e.message
95
+ }))
96
+ };
97
+ };
98
+ /**
99
+ * Check if data extends the base entity (has id, name, type).
100
+ */ const isBaseEntity = (data)=>{
101
+ return validateBaseEntity(data).success;
102
+ };
103
+ /**
104
+ * Format Zod errors into a readable message.
105
+ */ const formatValidationErrors = (errors)=>{
106
+ return errors.issues.map((e)=>`${e.path.join('.')}: ${e.message}`).join('; ');
107
+ };
108
+
109
+ /**
110
+ * Default plural name derivation.
111
+ */ const derivePluralName = (type)=>{
112
+ // Simple pluralization rules
113
+ if (type.endsWith('y')) {
114
+ return type.slice(0, -1) + 'ies';
115
+ }
116
+ if (type.endsWith('s') || type.endsWith('x') || type.endsWith('ch') || type.endsWith('sh')) {
117
+ return type + 'es';
118
+ }
119
+ return type + 's';
120
+ };
121
+ /**
122
+ * Create a new schema registry.
123
+ */ const createSchemaRegistry = ()=>{
124
+ const schemas = new Map();
125
+ const directoryToType = new Map();
126
+ const register = (options)=>{
127
+ const { type, schema, pluralName, customValidator } = options;
128
+ const plural = pluralName || derivePluralName(type);
129
+ const registered = {
130
+ type,
131
+ schema,
132
+ pluralName: plural,
133
+ customValidator
134
+ };
135
+ schemas.set(type, registered);
136
+ directoryToType.set(plural, type);
137
+ };
138
+ const registerAll = (schemaMap)=>{
139
+ for (const [type, schema] of Object.entries(schemaMap)){
140
+ register({
141
+ type,
142
+ schema
143
+ });
144
+ }
145
+ };
146
+ const get = (type)=>{
147
+ return schemas.get(type);
148
+ };
149
+ const has = (type)=>{
150
+ return schemas.has(type);
151
+ };
152
+ const types = ()=>{
153
+ return Array.from(schemas.keys());
154
+ };
155
+ const getDirectoryName = (type)=>{
156
+ var _schemas_get;
157
+ return (_schemas_get = schemas.get(type)) === null || _schemas_get === void 0 ? void 0 : _schemas_get.pluralName;
158
+ };
159
+ const getTypeFromDirectory = (directory)=>{
160
+ return directoryToType.get(directory);
161
+ };
162
+ const validate = (entity)=>{
163
+ const registered = schemas.get(entity.type);
164
+ if (!registered) {
165
+ return {
166
+ success: false,
167
+ errors: [
168
+ {
169
+ path: 'type',
170
+ message: `Unknown entity type: ${entity.type}`
171
+ }
172
+ ]
173
+ };
174
+ }
175
+ // Schema validation
176
+ const schemaResult = validateEntity(registered.schema, entity);
177
+ if (!schemaResult.success) {
178
+ return schemaResult;
179
+ }
180
+ // Custom validation
181
+ if (registered.customValidator) {
182
+ return registered.customValidator(entity);
183
+ }
184
+ return {
185
+ success: true,
186
+ data: entity
187
+ };
188
+ };
189
+ const validateAs = (type, data)=>{
190
+ const registered = schemas.get(type);
191
+ if (!registered) {
192
+ return {
193
+ success: false,
194
+ errors: [
195
+ {
196
+ path: 'type',
197
+ message: `Unknown entity type: ${type}`
198
+ }
199
+ ]
200
+ };
201
+ }
202
+ // Add type to data if missing (for convenience when loading from files)
203
+ const withType = typeof data === 'object' && data !== null ? {
204
+ ...data,
205
+ type
206
+ } : data;
207
+ return validateEntity(registered.schema, withType);
208
+ };
209
+ return {
210
+ register,
211
+ registerAll,
212
+ get,
213
+ has,
214
+ types,
215
+ getDirectoryName,
216
+ getTypeFromDirectory,
217
+ validate,
218
+ validateAs
219
+ };
220
+ };
221
+
222
+ /**
223
+ * Create a schema map with proper type inference.
224
+ *
225
+ * @example
226
+ * const { schemas, types } = defineSchemas({
227
+ * person: PersonSchema,
228
+ * project: ProjectSchema,
229
+ * });
230
+ *
231
+ * type Person = typeof types.person; // Inferred from PersonSchema
232
+ */ const defineSchemas = (schemas)=>{
233
+ return {
234
+ schemas,
235
+ types: {}
236
+ };
237
+ };
238
+ /**
239
+ * Helper to check if a schema extends BaseEntity properly.
240
+ */ const isValidEntitySchema = (schema)=>{
241
+ try {
242
+ // BaseEntitySchema allows extra fields, so we just need id, name, type
243
+ const result = schema.safeParse({
244
+ id: 'test',
245
+ name: 'Test',
246
+ type: 'test'
247
+ });
248
+ // If it fails, it might be because type is a literal
249
+ // Try without type and check if it has the base structure
250
+ if (!result.success) {
251
+ return false;
252
+ }
253
+ return true;
254
+ } catch {
255
+ return false;
256
+ }
257
+ };
258
+
259
+ function _define_property$1(obj, key, value) {
260
+ if (key in obj) {
261
+ Object.defineProperty(obj, key, {
262
+ value: value,
263
+ enumerable: true,
264
+ configurable: true,
265
+ writable: true
266
+ });
267
+ } else {
268
+ obj[key] = value;
269
+ }
270
+ return obj;
271
+ }
272
+ class StorageError extends Error {
273
+ constructor(message, code, cause){
274
+ super(message), _define_property$1(this, "code", void 0), _define_property$1(this, "cause", void 0), this.code = code, this.cause = cause;
275
+ this.name = 'StorageError';
276
+ }
277
+ }
278
+ class EntityNotFoundError extends StorageError {
279
+ constructor(entityType, entityId, namespace){
280
+ super(`Entity not found: ${entityType}/${entityId}${namespace ? ` in ${namespace}` : ''}`, 'ENTITY_NOT_FOUND'), _define_property$1(this, "entityType", void 0), _define_property$1(this, "entityId", void 0), _define_property$1(this, "namespace", void 0), this.entityType = entityType, this.entityId = entityId, this.namespace = namespace;
281
+ this.name = 'EntityNotFoundError';
282
+ }
283
+ }
284
+ class SchemaNotRegisteredError extends StorageError {
285
+ constructor(entityType){
286
+ super(`Schema not registered for type: ${entityType}`, 'SCHEMA_NOT_REGISTERED'), _define_property$1(this, "entityType", void 0), this.entityType = entityType;
287
+ this.name = 'SchemaNotRegisteredError';
288
+ }
289
+ }
290
+ class ValidationError extends StorageError {
291
+ constructor(message, validationErrors){
292
+ super(message, 'VALIDATION_ERROR'), _define_property$1(this, "validationErrors", void 0), this.validationErrors = validationErrors;
293
+ this.name = 'ValidationError';
294
+ }
295
+ }
296
+ class StorageAccessError extends StorageError {
297
+ constructor(message, cause){
298
+ super(message, 'STORAGE_ACCESS_ERROR', cause);
299
+ this.name = 'StorageAccessError';
300
+ }
301
+ }
302
+ class ReadonlyStorageError extends StorageError {
303
+ constructor(){
304
+ super('Storage is readonly', 'READONLY_STORAGE');
305
+ this.name = 'ReadonlyStorageError';
306
+ }
307
+ }
308
+ class NamespaceNotFoundError extends StorageError {
309
+ constructor(namespace){
310
+ super(`Namespace not found: ${namespace}`, 'NAMESPACE_NOT_FOUND'), _define_property$1(this, "namespace", void 0), this.namespace = namespace;
311
+ this.name = 'NamespaceNotFoundError';
312
+ }
313
+ }
314
+
315
+ /**
316
+ * Wrap any StorageProvider to make it observable.
317
+ */ const createObservableProvider = (provider)=>{
318
+ const handlers = new Set();
319
+ const emit = (event)=>{
320
+ handlers.forEach((handler)=>{
321
+ try {
322
+ handler(event);
323
+ } catch (error) {
324
+ // eslint-disable-next-line no-console
325
+ console.error('Storage event handler error:', error);
326
+ }
327
+ });
328
+ };
329
+ return {
330
+ ...provider,
331
+ subscribe (handler) {
332
+ handlers.add(handler);
333
+ return ()=>handlers.delete(handler);
334
+ },
335
+ async initialize () {
336
+ await provider.initialize();
337
+ emit({
338
+ type: 'storage:initialized',
339
+ timestamp: new Date()
340
+ });
341
+ },
342
+ async dispose () {
343
+ emit({
344
+ type: 'storage:disposed',
345
+ timestamp: new Date()
346
+ });
347
+ await provider.dispose();
348
+ },
349
+ async save (entity, namespace) {
350
+ const existing = await provider.get(entity.type, entity.id, namespace);
351
+ const saved = await provider.save(entity, namespace);
352
+ if (existing) {
353
+ emit({
354
+ type: 'entity:updated',
355
+ timestamp: new Date(),
356
+ namespace,
357
+ entityType: entity.type,
358
+ entityId: entity.id,
359
+ entity: saved,
360
+ previousEntity: existing
361
+ });
362
+ } else {
363
+ emit({
364
+ type: 'entity:created',
365
+ timestamp: new Date(),
366
+ namespace,
367
+ entityType: entity.type,
368
+ entityId: entity.id,
369
+ entity: saved
370
+ });
371
+ }
372
+ return saved;
373
+ },
374
+ async delete (type, id, namespace) {
375
+ const deleted = await provider.delete(type, id, namespace);
376
+ if (deleted) {
377
+ emit({
378
+ type: 'entity:deleted',
379
+ timestamp: new Date(),
380
+ namespace,
381
+ entityType: type,
382
+ entityId: id
383
+ });
384
+ }
385
+ return deleted;
386
+ },
387
+ async saveBatch (entities, namespace) {
388
+ const saved = await provider.saveBatch(entities, namespace);
389
+ emit({
390
+ type: 'batch:saved',
391
+ timestamp: new Date(),
392
+ namespace,
393
+ entities: saved
394
+ });
395
+ return saved;
396
+ },
397
+ async deleteBatch (refs, namespace) {
398
+ const count = await provider.deleteBatch(refs, namespace);
399
+ emit({
400
+ type: 'batch:deleted',
401
+ timestamp: new Date(),
402
+ namespace,
403
+ refs,
404
+ deletedCount: count
405
+ });
406
+ return count;
407
+ }
408
+ };
409
+ };
410
+
411
+ const createFileSystemProvider = async (options)=>{
412
+ const { basePath, registry, createIfMissing = true, extension = '.yaml', readonly = false, defaultNamespace } = options;
413
+ // --- Helper Functions ---
414
+ const getEntityDir = (type, namespace)=>{
415
+ const dirName = registry.getDirectoryName(type);
416
+ if (!dirName) {
417
+ throw new SchemaNotRegisteredError(type);
418
+ }
419
+ if (namespace) {
420
+ return path__namespace.join(basePath, namespace, dirName);
421
+ }
422
+ return path__namespace.join(basePath, dirName);
423
+ };
424
+ const getEntityPath = (type, id, namespace)=>{
425
+ return path__namespace.join(getEntityDir(type, namespace), `${id}${extension}`);
426
+ };
427
+ const ensureDir = async (dir)=>{
428
+ if (!node_fs.existsSync(dir) && createIfMissing && !readonly) {
429
+ await fs__namespace.mkdir(dir, {
430
+ recursive: true
431
+ });
432
+ }
433
+ };
434
+ const readEntity = async (filePath, type)=>{
435
+ try {
436
+ const content = await fs__namespace.readFile(filePath, 'utf-8');
437
+ let parsed;
438
+ try {
439
+ parsed = yaml__namespace.load(content);
440
+ } catch (yamlError) {
441
+ // eslint-disable-next-line no-console
442
+ console.warn(`Invalid YAML at ${filePath}:`, yamlError);
443
+ return undefined;
444
+ }
445
+ if (!parsed || typeof parsed !== 'object') {
446
+ return undefined;
447
+ }
448
+ // Validate against registered schema
449
+ const result = registry.validateAs(type, {
450
+ ...parsed,
451
+ source: filePath
452
+ });
453
+ if (!result.success) {
454
+ // eslint-disable-next-line no-console
455
+ console.warn(`Invalid entity at ${filePath}:`, result.errors);
456
+ return undefined;
457
+ }
458
+ return result.data;
459
+ } catch (error) {
460
+ if (error.code === 'ENOENT') {
461
+ return undefined;
462
+ }
463
+ throw new StorageAccessError(`Failed to read ${filePath}`, error);
464
+ }
465
+ };
466
+ const writeEntity = async (entity, namespace)=>{
467
+ if (readonly) {
468
+ throw new ReadonlyStorageError();
469
+ }
470
+ // Validate against schema
471
+ const validationResult = registry.validate(entity);
472
+ if (!validationResult.success) {
473
+ throw new ValidationError('Entity validation failed', validationResult.errors || []);
474
+ }
475
+ const dir = getEntityDir(entity.type, namespace);
476
+ await ensureDir(dir);
477
+ const filePath = getEntityPath(entity.type, entity.id, namespace);
478
+ // Remove framework-managed fields from saved YAML
479
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
480
+ const { type: _type, source: _source, ...entityToSave } = entity;
481
+ // Update metadata
482
+ const now = new Date();
483
+ const toSave = {
484
+ ...entityToSave,
485
+ updatedAt: now,
486
+ createdAt: entityToSave.createdAt || now
487
+ };
488
+ const content = yaml__namespace.dump(toSave, {
489
+ lineWidth: -1,
490
+ sortKeys: false
491
+ });
492
+ await fs__namespace.writeFile(filePath, content, 'utf-8');
493
+ return {
494
+ ...entity,
495
+ ...toSave,
496
+ type: entity.type,
497
+ source: filePath
498
+ };
499
+ };
500
+ const listDirectoryTypes = async (basePath)=>{
501
+ const types = [];
502
+ try {
503
+ const entries = await fs__namespace.readdir(basePath, {
504
+ withFileTypes: true
505
+ });
506
+ for (const entry of entries){
507
+ if (entry.isDirectory()) {
508
+ const type = registry.getTypeFromDirectory(entry.name);
509
+ if (type) {
510
+ types.push(type);
511
+ }
512
+ }
513
+ }
514
+ } catch {
515
+ // Directory doesn't exist
516
+ }
517
+ return types;
518
+ };
519
+ // --- StorageProvider Implementation ---
520
+ const provider = {
521
+ name: 'filesystem',
522
+ location: basePath,
523
+ registry,
524
+ async initialize () {
525
+ if (createIfMissing && !readonly) {
526
+ await ensureDir(basePath);
527
+ }
528
+ if (!node_fs.existsSync(basePath)) {
529
+ throw new StorageAccessError(`Context path does not exist: ${basePath}`);
530
+ }
531
+ },
532
+ async dispose () {
533
+ // No cleanup needed for filesystem
534
+ },
535
+ async isAvailable () {
536
+ try {
537
+ const stat = node_fs.statSync(basePath);
538
+ return stat.isDirectory();
539
+ } catch {
540
+ return false;
541
+ }
542
+ },
543
+ async get (type, id, namespace) {
544
+ const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
545
+ const filePath = getEntityPath(type, id, ns);
546
+ return readEntity(filePath, type);
547
+ },
548
+ async getAll (type, namespace) {
549
+ const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
550
+ let dir;
551
+ try {
552
+ dir = getEntityDir(type, ns);
553
+ } catch (error) {
554
+ if (error instanceof SchemaNotRegisteredError) {
555
+ return [];
556
+ }
557
+ throw error;
558
+ }
559
+ if (!node_fs.existsSync(dir)) {
560
+ return [];
561
+ }
562
+ const files = await fs__namespace.readdir(dir);
563
+ const entities = [];
564
+ for (const file of files){
565
+ if (!file.endsWith('.yaml') && !file.endsWith('.yml')) {
566
+ continue;
567
+ }
568
+ const entity = await readEntity(path__namespace.join(dir, file), type);
569
+ if (entity) {
570
+ entities.push(entity);
571
+ }
572
+ }
573
+ return entities;
574
+ },
575
+ async find (filter) {
576
+ var _filter_namespace;
577
+ var _filter_ids;
578
+ let results = [];
579
+ const types = filter.type ? Array.isArray(filter.type) ? filter.type : [
580
+ filter.type
581
+ ] : registry.types();
582
+ const namespace = (_filter_namespace = filter.namespace) !== null && _filter_namespace !== void 0 ? _filter_namespace : defaultNamespace;
583
+ for (const type of types){
584
+ const entities = await this.getAll(type, namespace);
585
+ results = results.concat(entities);
586
+ }
587
+ // Apply ID filter
588
+ if ((_filter_ids = filter.ids) === null || _filter_ids === void 0 ? void 0 : _filter_ids.length) {
589
+ results = results.filter((e)=>filter.ids.includes(e.id));
590
+ }
591
+ // Apply search filter
592
+ if (filter.search) {
593
+ const searchLower = filter.search.toLowerCase();
594
+ results = results.filter((e)=>e.name.toLowerCase().includes(searchLower));
595
+ }
596
+ // Apply pagination
597
+ if (filter.offset) {
598
+ results = results.slice(filter.offset);
599
+ }
600
+ if (filter.limit) {
601
+ results = results.slice(0, filter.limit);
602
+ }
603
+ return results;
604
+ },
605
+ async exists (type, id, namespace) {
606
+ const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
607
+ try {
608
+ const filePath = getEntityPath(type, id, ns);
609
+ return node_fs.existsSync(filePath);
610
+ } catch {
611
+ return false;
612
+ }
613
+ },
614
+ async count (filter) {
615
+ const results = await this.find(filter);
616
+ return results.length;
617
+ },
618
+ async save (entity, namespace) {
619
+ const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
620
+ return writeEntity(entity, ns);
621
+ },
622
+ async delete (type, id, namespace) {
623
+ if (readonly) {
624
+ throw new ReadonlyStorageError();
625
+ }
626
+ const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
627
+ try {
628
+ const filePath = getEntityPath(type, id, ns);
629
+ await fs__namespace.unlink(filePath);
630
+ return true;
631
+ } catch (error) {
632
+ if (error.code === 'ENOENT') {
633
+ return false;
634
+ }
635
+ throw error;
636
+ }
637
+ },
638
+ async saveBatch (entities, namespace) {
639
+ const saved = [];
640
+ for (const entity of entities){
641
+ saved.push(await this.save(entity, namespace));
642
+ }
643
+ return saved;
644
+ },
645
+ async deleteBatch (refs, namespace) {
646
+ let count = 0;
647
+ for (const ref of refs){
648
+ if (await this.delete(ref.type, ref.id, namespace)) {
649
+ count++;
650
+ }
651
+ }
652
+ return count;
653
+ },
654
+ async listNamespaces () {
655
+ const namespaces = [];
656
+ if (!node_fs.existsSync(basePath)) {
657
+ return namespaces;
658
+ }
659
+ const entries = await fs__namespace.readdir(basePath, {
660
+ withFileTypes: true
661
+ });
662
+ for (const entry of entries){
663
+ if (!entry.isDirectory()) continue;
664
+ // Check if it's a namespace (contains entity type directories)
665
+ // vs being an entity type directory itself
666
+ const subPath = path__namespace.join(basePath, entry.name);
667
+ const subTypes = await listDirectoryTypes(subPath);
668
+ if (subTypes.length > 0) {
669
+ namespaces.push(entry.name);
670
+ }
671
+ }
672
+ return namespaces;
673
+ },
674
+ async namespaceExists (namespace) {
675
+ const namespacePath = path__namespace.join(basePath, namespace);
676
+ return node_fs.existsSync(namespacePath);
677
+ },
678
+ async listTypes (namespace) {
679
+ const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
680
+ const searchPath = ns ? path__namespace.join(basePath, ns) : basePath;
681
+ return listDirectoryTypes(searchPath);
682
+ }
683
+ };
684
+ return provider;
685
+ };
686
+
687
+ const createMemoryProvider = (options)=>{
688
+ const { registry, readonly = false, defaultNamespace, initialData = [] } = options;
689
+ // Storage: namespace -> type -> id -> entity
690
+ const store = new Map();
691
+ const getNamespaceStore = (namespace)=>{
692
+ if (!store.has(namespace)) {
693
+ store.set(namespace, new Map());
694
+ }
695
+ return store.get(namespace);
696
+ };
697
+ const getTypeStore = (namespace, type)=>{
698
+ const nsStore = getNamespaceStore(namespace);
699
+ if (!nsStore.has(type)) {
700
+ nsStore.set(type, new Map());
701
+ }
702
+ return nsStore.get(type);
703
+ };
704
+ const resolveNamespace = (ns)=>{
705
+ var _ref;
706
+ return (_ref = ns !== null && ns !== void 0 ? ns : defaultNamespace) !== null && _ref !== void 0 ? _ref : '_default';
707
+ };
708
+ // Initialize with any provided data
709
+ for (const entity of initialData){
710
+ const ns = resolveNamespace(entity.namespace);
711
+ getTypeStore(ns, entity.type).set(entity.id, entity);
712
+ }
713
+ return {
714
+ name: 'memory',
715
+ location: 'in-memory',
716
+ registry,
717
+ async initialize () {},
718
+ async dispose () {
719
+ store.clear();
720
+ },
721
+ async isAvailable () {
722
+ return true;
723
+ },
724
+ async get (type, id, namespace) {
725
+ const ns = resolveNamespace(namespace);
726
+ return getTypeStore(ns, type).get(id);
727
+ },
728
+ async getAll (type, namespace) {
729
+ const ns = resolveNamespace(namespace);
730
+ return Array.from(getTypeStore(ns, type).values());
731
+ },
732
+ async find (filter) {
733
+ var _filter_ids;
734
+ let results = [];
735
+ const types = filter.type ? Array.isArray(filter.type) ? filter.type : [
736
+ filter.type
737
+ ] : registry.types();
738
+ const ns = resolveNamespace(filter.namespace);
739
+ for (const type of types){
740
+ results = results.concat(await this.getAll(type, ns));
741
+ }
742
+ if ((_filter_ids = filter.ids) === null || _filter_ids === void 0 ? void 0 : _filter_ids.length) {
743
+ results = results.filter((e)=>filter.ids.includes(e.id));
744
+ }
745
+ if (filter.search) {
746
+ const s = filter.search.toLowerCase();
747
+ results = results.filter((e)=>e.name.toLowerCase().includes(s));
748
+ }
749
+ if (filter.offset) results = results.slice(filter.offset);
750
+ if (filter.limit) results = results.slice(0, filter.limit);
751
+ return results;
752
+ },
753
+ async exists (type, id, namespace) {
754
+ const ns = resolveNamespace(namespace);
755
+ return getTypeStore(ns, type).has(id);
756
+ },
757
+ async count (filter) {
758
+ return (await this.find(filter)).length;
759
+ },
760
+ async save (entity, namespace) {
761
+ if (readonly) {
762
+ throw new ReadonlyStorageError();
763
+ }
764
+ const validationResult = registry.validate(entity);
765
+ if (!validationResult.success) {
766
+ throw new ValidationError('Validation failed', validationResult.errors || []);
767
+ }
768
+ const ns = resolveNamespace(namespace);
769
+ const now = new Date();
770
+ const existing = getTypeStore(ns, entity.type).get(entity.id);
771
+ const saved = {
772
+ ...entity,
773
+ namespace: ns,
774
+ createdAt: (existing === null || existing === void 0 ? void 0 : existing.createdAt) || now,
775
+ updatedAt: now
776
+ };
777
+ getTypeStore(ns, entity.type).set(entity.id, saved);
778
+ return saved;
779
+ },
780
+ async delete (type, id, namespace) {
781
+ if (readonly) {
782
+ throw new ReadonlyStorageError();
783
+ }
784
+ const ns = resolveNamespace(namespace);
785
+ return getTypeStore(ns, type).delete(id);
786
+ },
787
+ async saveBatch (entities, namespace) {
788
+ return Promise.all(entities.map((e)=>this.save(e, namespace)));
789
+ },
790
+ async deleteBatch (refs, namespace) {
791
+ let count = 0;
792
+ for (const ref of refs){
793
+ if (await this.delete(ref.type, ref.id, namespace)) count++;
794
+ }
795
+ return count;
796
+ },
797
+ async listNamespaces () {
798
+ return Array.from(store.keys()).filter((ns)=>ns !== '_default');
799
+ },
800
+ async namespaceExists (namespace) {
801
+ return store.has(namespace);
802
+ },
803
+ async listTypes (namespace) {
804
+ const ns = resolveNamespace(namespace);
805
+ const nsStore = store.get(ns);
806
+ return nsStore ? Array.from(nsStore.keys()) : [];
807
+ }
808
+ };
809
+ };
810
+
811
+ /**
812
+ * Generate a slug from a name.
813
+ * "John Doe" -> "john-doe"
814
+ * "API Reference" -> "api-reference"
815
+ */ const slugify = (name)=>{
816
+ const cleaned = name.toLowerCase().trim().replace(/[^\w\s-]/g, '') // Remove non-word chars
817
+ .replace(/[\s_]+/g, '-'); // Replace spaces/underscores
818
+ // Single-pass algorithm to collapse hyphens and trim (avoid ReDoS)
819
+ const result = [];
820
+ let prevWasHyphen = false;
821
+ let startIdx = 0;
822
+ let endIdx = cleaned.length;
823
+ // Skip leading hyphens
824
+ while(startIdx < cleaned.length && cleaned[startIdx] === '-'){
825
+ startIdx++;
826
+ }
827
+ // Skip trailing hyphens
828
+ while(endIdx > startIdx && cleaned[endIdx - 1] === '-'){
829
+ endIdx--;
830
+ }
831
+ // Build result, collapsing consecutive hyphens
832
+ for(let i = startIdx; i < endIdx; i++){
833
+ const char = cleaned[i];
834
+ if (char === '-') {
835
+ if (!prevWasHyphen) {
836
+ result.push('-');
837
+ prevWasHyphen = true;
838
+ }
839
+ } else {
840
+ result.push(char);
841
+ prevWasHyphen = false;
842
+ }
843
+ }
844
+ return result.join('');
845
+ };
846
+ /**
847
+ * Generate a unique ID, appending a number if needed.
848
+ */ const generateUniqueId = async (baseName, exists)=>{
849
+ const baseId = slugify(baseName);
850
+ if (!await exists(baseId)) {
851
+ return baseId;
852
+ }
853
+ // Try appending numbers
854
+ for(let i = 2; i <= 100; i++){
855
+ const candidateId = `${baseId}-${i}`;
856
+ if (!await exists(candidateId)) {
857
+ return candidateId;
858
+ }
859
+ }
860
+ // Fallback to timestamp
861
+ return `${baseId}-${Date.now()}`;
862
+ };
863
+
864
+ const createSearchEngine = (options)=>{
865
+ const { provider, registry, defaultNamespace } = options;
866
+ const textMatch = (text, query, caseSensitive)=>{
867
+ if (!text) return false;
868
+ const a = caseSensitive ? text : text.toLowerCase();
869
+ const b = caseSensitive ? query : query.toLowerCase();
870
+ return a.includes(b);
871
+ };
872
+ const matchesSearch = (entity, search, searchFields, caseSensitive)=>{
873
+ // Always search name
874
+ if (textMatch(entity.name, search, caseSensitive)) {
875
+ return true;
876
+ }
877
+ // Search additional fields
878
+ for (const field of searchFields){
879
+ const value = entity[field];
880
+ if (typeof value === 'string' && textMatch(value, search, caseSensitive)) {
881
+ return true;
882
+ }
883
+ // Handle arrays of strings (like sounds_like)
884
+ if (Array.isArray(value)) {
885
+ for (const item of value){
886
+ if (typeof item === 'string' && textMatch(item, search, caseSensitive)) {
887
+ return true;
888
+ }
889
+ }
890
+ }
891
+ }
892
+ return false;
893
+ };
894
+ const sortEntities = (entities, sort)=>{
895
+ return [
896
+ ...entities
897
+ ].sort((a, b)=>{
898
+ for (const { field, direction } of sort){
899
+ const aVal = a[field];
900
+ const bVal = b[field];
901
+ if (aVal === bVal) continue;
902
+ if (aVal === undefined || aVal === null) return direction === 'asc' ? 1 : -1;
903
+ if (bVal === undefined || bVal === null) return direction === 'asc' ? -1 : 1;
904
+ let cmp;
905
+ if (typeof aVal === 'string' && typeof bVal === 'string') {
906
+ cmp = aVal.localeCompare(bVal);
907
+ } else if (aVal instanceof Date && bVal instanceof Date) {
908
+ cmp = aVal.getTime() - bVal.getTime();
909
+ } else {
910
+ cmp = aVal < bVal ? -1 : 1;
911
+ }
912
+ return direction === 'asc' ? cmp : -cmp;
913
+ }
914
+ return 0;
915
+ });
916
+ };
917
+ return {
918
+ async search (options) {
919
+ const { type, namespace, ids, search, searchFields = [], caseSensitive = false, limit, offset = 0, sort = [
920
+ {
921
+ field: 'name',
922
+ direction: 'asc'
923
+ }
924
+ ] } = options;
925
+ // Determine which types to search
926
+ const types = type ? Array.isArray(type) ? type : [
927
+ type
928
+ ] : registry.types();
929
+ // Determine which namespaces to search
930
+ const namespaces = namespace ? Array.isArray(namespace) ? namespace : [
931
+ namespace
932
+ ] : [
933
+ defaultNamespace
934
+ ];
935
+ // Collect entities
936
+ let allEntities = [];
937
+ for (const t of types){
938
+ for (const ns of namespaces){
939
+ const entities = await provider.getAll(t, ns);
940
+ allEntities = allEntities.concat(entities);
941
+ }
942
+ }
943
+ // Apply ID filter
944
+ if (ids === null || ids === void 0 ? void 0 : ids.length) {
945
+ allEntities = allEntities.filter((e)=>ids.includes(e.id));
946
+ }
947
+ // Apply search filter
948
+ if (search) {
949
+ allEntities = allEntities.filter((e)=>matchesSearch(e, search, searchFields, caseSensitive));
950
+ }
951
+ // Apply sorting
952
+ allEntities = sortEntities(allEntities, sort);
953
+ // Get total before pagination
954
+ const total = allEntities.length;
955
+ // Apply pagination
956
+ const paginated = allEntities.slice(offset, limit ? offset + limit : undefined);
957
+ return {
958
+ items: paginated,
959
+ total,
960
+ hasMore: limit ? offset + limit < total : false,
961
+ query: options
962
+ };
963
+ },
964
+ async quickSearch (query, options = {}) {
965
+ const result = await this.search({
966
+ ...options,
967
+ search: query
968
+ });
969
+ return result.items;
970
+ }
971
+ };
972
+ };
973
+
974
+ const createContext = (options)=>{
975
+ const { provider, registry, defaultNamespace } = options;
976
+ const resolveNamespace = (ns)=>ns !== null && ns !== void 0 ? ns : defaultNamespace;
977
+ const searchEngine = createSearchEngine({
978
+ provider,
979
+ registry,
980
+ defaultNamespace
981
+ });
982
+ const api = {
983
+ provider,
984
+ registry,
985
+ defaultNamespace,
986
+ async get (type, id, namespace) {
987
+ return provider.get(type, id, resolveNamespace(namespace));
988
+ },
989
+ async getAll (type, namespace) {
990
+ return provider.getAll(type, resolveNamespace(namespace));
991
+ },
992
+ async exists (type, id, namespace) {
993
+ return provider.exists(type, id, resolveNamespace(namespace));
994
+ },
995
+ async create (type, data, options = {}) {
996
+ var _options_generateUniqueId;
997
+ const namespace = resolveNamespace(options.namespace);
998
+ const shouldGenerateUnique = (_options_generateUniqueId = options.generateUniqueId) !== null && _options_generateUniqueId !== void 0 ? _options_generateUniqueId : true;
999
+ let id;
1000
+ if (options.id) {
1001
+ id = options.id;
1002
+ } else if (shouldGenerateUnique) {
1003
+ id = await generateUniqueId(data.name, (testId)=>provider.exists(type, testId, namespace));
1004
+ } else {
1005
+ id = slugify(data.name);
1006
+ }
1007
+ const entity = {
1008
+ ...data,
1009
+ id,
1010
+ type,
1011
+ createdAt: new Date(),
1012
+ updatedAt: new Date()
1013
+ };
1014
+ return provider.save(entity, namespace);
1015
+ },
1016
+ async update (type, id, updates, namespace) {
1017
+ const ns = resolveNamespace(namespace);
1018
+ const existing = await provider.get(type, id, ns);
1019
+ if (!existing) {
1020
+ throw new EntityNotFoundError(type, id, ns);
1021
+ }
1022
+ const updated = {
1023
+ ...existing,
1024
+ ...updates,
1025
+ id,
1026
+ type,
1027
+ updatedAt: new Date()
1028
+ };
1029
+ return provider.save(updated, ns);
1030
+ },
1031
+ async upsert (type, entity, namespace) {
1032
+ const ns = resolveNamespace(namespace);
1033
+ const existing = await provider.get(type, entity.id, ns);
1034
+ const now = new Date();
1035
+ const toSave = {
1036
+ ...existing,
1037
+ ...entity,
1038
+ type,
1039
+ createdAt: (existing === null || existing === void 0 ? void 0 : existing.createdAt) || now,
1040
+ updatedAt: now
1041
+ };
1042
+ return provider.save(toSave, ns);
1043
+ },
1044
+ async delete (type, id, namespace) {
1045
+ return provider.delete(type, id, resolveNamespace(namespace));
1046
+ },
1047
+ types () {
1048
+ return registry.types();
1049
+ },
1050
+ withNamespace (namespace) {
1051
+ return createContext({
1052
+ ...options,
1053
+ defaultNamespace: namespace
1054
+ });
1055
+ },
1056
+ search: searchEngine.search.bind(searchEngine),
1057
+ quickSearch: searchEngine.quickSearch.bind(searchEngine)
1058
+ };
1059
+ return api;
1060
+ };
1061
+
1062
+ const createTypedAPI = (options)=>{
1063
+ const { schemas, provider, defaultNamespace, pluralNames = {} } = options;
1064
+ // Create registry from schemas
1065
+ const registry = createSchemaRegistry();
1066
+ for (const [type, schema] of Object.entries(schemas)){
1067
+ registry.register({
1068
+ type,
1069
+ schema: schema,
1070
+ pluralName: pluralNames[type]
1071
+ });
1072
+ }
1073
+ return createContext({
1074
+ provider,
1075
+ registry,
1076
+ schemas,
1077
+ defaultNamespace
1078
+ });
1079
+ };
1080
+
1081
+ function _define_property(obj, key, value) {
1082
+ if (key in obj) {
1083
+ Object.defineProperty(obj, key, {
1084
+ value: value,
1085
+ enumerable: true,
1086
+ configurable: true,
1087
+ writable: true
1088
+ });
1089
+ } else {
1090
+ obj[key] = value;
1091
+ }
1092
+ return obj;
1093
+ }
1094
+ /**
1095
+ * Fluent query builder for constructing search queries.
1096
+ */ class QueryBuilder {
1097
+ /**
1098
+ * Filter by entity type(s).
1099
+ */ type(type) {
1100
+ this.options.type = type;
1101
+ return this;
1102
+ }
1103
+ /**
1104
+ * Filter by namespace(s).
1105
+ */ namespace(ns) {
1106
+ this.options.namespace = ns;
1107
+ return this;
1108
+ }
1109
+ /**
1110
+ * Filter by specific IDs.
1111
+ */ ids(ids) {
1112
+ this.options.ids = ids;
1113
+ return this;
1114
+ }
1115
+ /**
1116
+ * Add text search.
1117
+ */ search(query, fields) {
1118
+ this.options.search = query;
1119
+ if (fields) this.options.searchFields = fields;
1120
+ return this;
1121
+ }
1122
+ /**
1123
+ * Enable case-sensitive search.
1124
+ */ caseSensitive(enabled = true) {
1125
+ this.options.caseSensitive = enabled;
1126
+ return this;
1127
+ }
1128
+ /**
1129
+ * Add sort field.
1130
+ */ sortBy(field, direction = 'asc') {
1131
+ this.options.sort = [
1132
+ ...this.options.sort || [],
1133
+ {
1134
+ field,
1135
+ direction
1136
+ }
1137
+ ];
1138
+ return this;
1139
+ }
1140
+ /**
1141
+ * Set result limit.
1142
+ */ limit(n) {
1143
+ this.options.limit = n;
1144
+ return this;
1145
+ }
1146
+ /**
1147
+ * Set result offset.
1148
+ */ offset(n) {
1149
+ this.options.offset = n;
1150
+ return this;
1151
+ }
1152
+ /**
1153
+ * Set page (calculates offset from limit).
1154
+ */ page(pageNum, pageSize) {
1155
+ this.options.limit = pageSize;
1156
+ this.options.offset = (pageNum - 1) * pageSize;
1157
+ return this;
1158
+ }
1159
+ /**
1160
+ * Build the query options.
1161
+ */ build() {
1162
+ return {
1163
+ ...this.options
1164
+ };
1165
+ }
1166
+ constructor(){
1167
+ _define_property(this, "options", {});
1168
+ }
1169
+ }
1170
+ /**
1171
+ * Start building a query.
1172
+ */ const query = ()=>new QueryBuilder();
1173
+
1174
+ const createNamespaceResolver = (options)=>{
1175
+ const { provider, defaultNamespace = '_default' } = options;
1176
+ const configuredNamespaces = new Map();
1177
+ // Load initial configurations
1178
+ if (options.namespaces) {
1179
+ for (const ns of options.namespaces){
1180
+ configuredNamespaces.set(ns.id, ns);
1181
+ }
1182
+ }
1183
+ return {
1184
+ async resolve (requested) {
1185
+ let namespaceIds;
1186
+ if (!requested) {
1187
+ namespaceIds = [
1188
+ defaultNamespace
1189
+ ];
1190
+ } else if (typeof requested === 'string') {
1191
+ namespaceIds = [
1192
+ requested
1193
+ ];
1194
+ } else {
1195
+ namespaceIds = requested;
1196
+ }
1197
+ const references = [];
1198
+ const readable = [];
1199
+ let primary;
1200
+ for(let i = 0; i < namespaceIds.length; i++){
1201
+ const nsId = namespaceIds[i];
1202
+ const config = configuredNamespaces.get(nsId);
1203
+ // Include all requested namespaces, even if they don't exist yet
1204
+ const ref = {
1205
+ namespace: nsId,
1206
+ priority: namespaceIds.length - i,
1207
+ searchable: (config === null || config === void 0 ? void 0 : config.active) !== false,
1208
+ writable: i === 0
1209
+ };
1210
+ references.push(ref);
1211
+ readable.push(nsId);
1212
+ if (!primary) {
1213
+ primary = nsId;
1214
+ }
1215
+ }
1216
+ if (!primary) {
1217
+ primary = defaultNamespace;
1218
+ readable.push(defaultNamespace);
1219
+ }
1220
+ return {
1221
+ primary,
1222
+ readable,
1223
+ references
1224
+ };
1225
+ },
1226
+ async listAll () {
1227
+ const discovered = await provider.listNamespaces();
1228
+ const all = new Map();
1229
+ // Add configured namespaces
1230
+ for (const [id, config] of configuredNamespaces){
1231
+ all.set(id, config);
1232
+ }
1233
+ // Add discovered namespaces
1234
+ for (const nsId of discovered){
1235
+ if (!all.has(nsId)) {
1236
+ all.set(nsId, {
1237
+ id: nsId,
1238
+ name: nsId,
1239
+ active: true
1240
+ });
1241
+ }
1242
+ }
1243
+ return Array.from(all.values());
1244
+ },
1245
+ register (config) {
1246
+ configuredNamespaces.set(config.id, config);
1247
+ },
1248
+ getPrimary () {
1249
+ return defaultNamespace;
1250
+ },
1251
+ async exists (namespace) {
1252
+ return configuredNamespaces.has(namespace) || await provider.namespaceExists(namespace);
1253
+ }
1254
+ };
1255
+ };
1256
+
1257
+ const createMultiNamespaceContext = async (options, namespaces)=>{
1258
+ const { api, resolver } = options;
1259
+ const { provider } = api;
1260
+ const resolution = await resolver.resolve(namespaces);
1261
+ return {
1262
+ // Forward base API methods
1263
+ provider: api.provider,
1264
+ registry: api.registry,
1265
+ defaultNamespace: resolution.primary,
1266
+ get: (type, id, namespace)=>api.get(type, id, namespace !== null && namespace !== void 0 ? namespace : resolution.primary),
1267
+ getAll: (type, namespace)=>api.getAll(type, namespace !== null && namespace !== void 0 ? namespace : resolution.primary),
1268
+ exists: (type, id, namespace)=>api.exists(type, id, namespace !== null && namespace !== void 0 ? namespace : resolution.primary),
1269
+ create: (type, data, opts)=>{
1270
+ var _ref;
1271
+ return api.create(type, data, {
1272
+ ...opts,
1273
+ namespace: (_ref = opts === null || opts === void 0 ? void 0 : opts.namespace) !== null && _ref !== void 0 ? _ref : resolution.primary
1274
+ });
1275
+ },
1276
+ update: (type, id, updates, namespace)=>api.update(type, id, updates, namespace !== null && namespace !== void 0 ? namespace : resolution.primary),
1277
+ upsert: (type, entity, namespace)=>api.upsert(type, entity, namespace !== null && namespace !== void 0 ? namespace : resolution.primary),
1278
+ delete: (type, id, namespace)=>api.delete(type, id, namespace !== null && namespace !== void 0 ? namespace : resolution.primary),
1279
+ types: ()=>api.types(),
1280
+ search: (opts)=>api.search(opts),
1281
+ quickSearch: (q, opts)=>api.quickSearch(q, opts),
1282
+ withNamespace: (ns)=>api.withNamespace(ns),
1283
+ // Multi-namespace methods
1284
+ async getFromAny (type, id) {
1285
+ for (const ns of resolution.readable){
1286
+ const entity = await provider.get(type, id, ns);
1287
+ if (entity) {
1288
+ return {
1289
+ entity: entity,
1290
+ namespace: ns
1291
+ };
1292
+ }
1293
+ }
1294
+ return undefined;
1295
+ },
1296
+ async getAllMerged (type) {
1297
+ const byId = new Map();
1298
+ // Process in reverse priority order so higher priority wins
1299
+ const reversed = [
1300
+ ...resolution.readable
1301
+ ].reverse();
1302
+ for (const ns of reversed){
1303
+ const entities = await provider.getAll(type, ns);
1304
+ for (const entity of entities){
1305
+ byId.set(entity.id, entity);
1306
+ }
1307
+ }
1308
+ return Array.from(byId.values());
1309
+ },
1310
+ async locateEntity (type, id) {
1311
+ for (const ns of resolution.readable){
1312
+ if (await provider.exists(type, id, ns)) {
1313
+ return ns;
1314
+ }
1315
+ }
1316
+ return undefined;
1317
+ },
1318
+ getResolution () {
1319
+ return resolution;
1320
+ },
1321
+ async withNamespaces (ns) {
1322
+ return createMultiNamespaceContext(options, ns);
1323
+ }
1324
+ };
1325
+ };
1326
+
1327
+ const createDirectoryWalker = (options)=>{
1328
+ const { startDir, contextDirName, maxLevels, stopAt, stopMarkers = [], registry } = options;
1329
+ const shouldStop = async (dir)=>{
1330
+ if (stopAt && dir === stopAt) return true;
1331
+ for (const marker of stopMarkers){
1332
+ if (node_fs.existsSync(path__namespace.join(dir, marker))) {
1333
+ return true;
1334
+ }
1335
+ }
1336
+ const parent = path__namespace.dirname(dir);
1337
+ return parent === dir; // Root
1338
+ };
1339
+ const getNamespacesAndTypes = async (contextDir)=>{
1340
+ const namespaces = [];
1341
+ const types = [];
1342
+ try {
1343
+ const entries = await fs__namespace.readdir(contextDir, {
1344
+ withFileTypes: true
1345
+ });
1346
+ for (const entry of entries){
1347
+ if (!entry.isDirectory()) continue;
1348
+ const subPath = path__namespace.join(contextDir, entry.name);
1349
+ const subEntries = await fs__namespace.readdir(subPath, {
1350
+ withFileTypes: true
1351
+ });
1352
+ // Check if this is a type directory (has .yaml files)
1353
+ const hasYamlFiles = subEntries.some((sub)=>sub.isFile() && (sub.name.endsWith('.yaml') || sub.name.endsWith('.yml')));
1354
+ // Check if this looks like a type directory (known to registry)
1355
+ const isTypeDir = registry ? !!registry.getTypeFromDirectory(entry.name) : hasYamlFiles;
1356
+ if (isTypeDir) {
1357
+ const typeName = (registry === null || registry === void 0 ? void 0 : registry.getTypeFromDirectory(entry.name)) || entry.name;
1358
+ if (!types.includes(typeName)) {
1359
+ types.push(typeName);
1360
+ }
1361
+ } else {
1362
+ // Check if it's a namespace (contains type directories)
1363
+ const hasTypeDirs = subEntries.some((sub)=>{
1364
+ if (!sub.isDirectory()) return false;
1365
+ return registry ? !!registry.getTypeFromDirectory(sub.name) : true; // Assume any subdir could be a type
1366
+ });
1367
+ if (hasTypeDirs) {
1368
+ namespaces.push(entry.name);
1369
+ }
1370
+ }
1371
+ }
1372
+ } catch {
1373
+ // Directory doesn't exist or can't be read
1374
+ }
1375
+ return {
1376
+ namespaces,
1377
+ types
1378
+ };
1379
+ };
1380
+ return {
1381
+ async discover () {
1382
+ const discovered = [];
1383
+ let currentDir = path__namespace.resolve(startDir);
1384
+ let level = 0;
1385
+ while(level < maxLevels){
1386
+ const contextDir = path__namespace.join(currentDir, contextDirName);
1387
+ if (node_fs.existsSync(contextDir)) {
1388
+ const { namespaces, types } = await getNamespacesAndTypes(contextDir);
1389
+ discovered.push({
1390
+ path: contextDir,
1391
+ level,
1392
+ namespaces,
1393
+ types
1394
+ });
1395
+ }
1396
+ if (await shouldStop(currentDir)) break;
1397
+ currentDir = path__namespace.dirname(currentDir);
1398
+ level++;
1399
+ }
1400
+ return discovered;
1401
+ },
1402
+ async hasContext (dir) {
1403
+ return node_fs.existsSync(path__namespace.join(dir, contextDirName));
1404
+ }
1405
+ };
1406
+ };
1407
+
1408
+ const discoverContextRoot = async (options = {})=>{
1409
+ var _directories_;
1410
+ const { startDir = process.cwd(), contextDirName = 'context', maxLevels = 10, projectMarkers = [
1411
+ '.git',
1412
+ 'package.json'
1413
+ ], registry } = options;
1414
+ const walker = createDirectoryWalker({
1415
+ startDir,
1416
+ contextDirName,
1417
+ maxLevels,
1418
+ stopMarkers: projectMarkers,
1419
+ registry
1420
+ });
1421
+ const directories = await walker.discover();
1422
+ // Collect all namespaces and types (deduped)
1423
+ const namespaceSet = new Set();
1424
+ const typeSet = new Set();
1425
+ for (const dir of directories){
1426
+ for (const ns of dir.namespaces)namespaceSet.add(ns);
1427
+ for (const type of dir.types)typeSet.add(type);
1428
+ }
1429
+ return {
1430
+ directories,
1431
+ primary: (_directories_ = directories[0]) === null || _directories_ === void 0 ? void 0 : _directories_.path,
1432
+ contextPaths: directories.map((d)=>d.path),
1433
+ allNamespaces: Array.from(namespaceSet),
1434
+ allTypes: Array.from(typeSet)
1435
+ };
1436
+ };
1437
+ /**
1438
+ * Create or ensure a context directory exists.
1439
+ */ const ensureContextRoot = async (projectDir, contextDirName = 'context')=>{
1440
+ const contextPath = path__namespace.join(projectDir, contextDirName);
1441
+ if (!node_fs.existsSync(contextPath)) {
1442
+ const fs = await import('node:fs/promises');
1443
+ await fs.mkdir(contextPath, {
1444
+ recursive: true
1445
+ });
1446
+ }
1447
+ return contextPath;
1448
+ };
1449
+
1450
+ /**
1451
+ * Storage provider that reads from multiple context directories
1452
+ * and writes to the primary (closest) directory.
1453
+ */ const createHierarchicalProvider = async (options)=>{
1454
+ const { contextRoot, registry, readonly = false } = options;
1455
+ if (contextRoot.contextPaths.length === 0) {
1456
+ throw new Error('No context directories found');
1457
+ }
1458
+ // Create read-only providers for each context directory
1459
+ const readProviders = [];
1460
+ for (const contextPath of contextRoot.contextPaths){
1461
+ const fsProvider = await createFileSystemProvider({
1462
+ basePath: contextPath,
1463
+ registry,
1464
+ createIfMissing: false,
1465
+ readonly: true
1466
+ });
1467
+ await fsProvider.initialize();
1468
+ readProviders.push(fsProvider);
1469
+ }
1470
+ // Primary provider for writes
1471
+ const primaryProvider = await createFileSystemProvider({
1472
+ basePath: contextRoot.primary,
1473
+ registry,
1474
+ createIfMissing: true,
1475
+ readonly
1476
+ });
1477
+ await primaryProvider.initialize();
1478
+ const findEntities = async (filter)=>{
1479
+ const byId = new Map();
1480
+ for (const p of [
1481
+ ...readProviders
1482
+ ].reverse()){
1483
+ const results = await p.find(filter);
1484
+ for (const entity of results){
1485
+ byId.set(entity.id, entity);
1486
+ }
1487
+ }
1488
+ let results = Array.from(byId.values());
1489
+ if (filter.offset) results = results.slice(filter.offset);
1490
+ if (filter.limit) results = results.slice(0, filter.limit);
1491
+ return results;
1492
+ };
1493
+ return {
1494
+ name: 'hierarchical',
1495
+ location: contextRoot.primary,
1496
+ registry,
1497
+ async initialize () {},
1498
+ async dispose () {
1499
+ for (const p of readProviders)await p.dispose();
1500
+ await primaryProvider.dispose();
1501
+ },
1502
+ async isAvailable () {
1503
+ return primaryProvider.isAvailable();
1504
+ },
1505
+ // Read operations search all providers (closest first)
1506
+ async get (type, id, namespace) {
1507
+ for (const p of readProviders){
1508
+ const entity = await p.get(type, id, namespace);
1509
+ if (entity) return entity;
1510
+ }
1511
+ return undefined;
1512
+ },
1513
+ async getAll (type, namespace) {
1514
+ const byId = new Map();
1515
+ // Process in reverse order so closest overwrites
1516
+ for (const p of [
1517
+ ...readProviders
1518
+ ].reverse()){
1519
+ const entities = await p.getAll(type, namespace);
1520
+ for (const entity of entities){
1521
+ byId.set(entity.id, entity);
1522
+ }
1523
+ }
1524
+ return Array.from(byId.values());
1525
+ },
1526
+ find: findEntities,
1527
+ async exists (type, id, namespace) {
1528
+ for (const p of readProviders){
1529
+ if (await p.exists(type, id, namespace)) return true;
1530
+ }
1531
+ return false;
1532
+ },
1533
+ async count (filter) {
1534
+ const results = await findEntities(filter);
1535
+ return results.length;
1536
+ },
1537
+ // Write operations go to primary
1538
+ save: (entity, namespace)=>primaryProvider.save(entity, namespace),
1539
+ delete: (type, id, namespace)=>primaryProvider.delete(type, id, namespace),
1540
+ saveBatch: (entities, namespace)=>primaryProvider.saveBatch(entities, namespace),
1541
+ deleteBatch: (refs, namespace)=>primaryProvider.deleteBatch(refs, namespace),
1542
+ listNamespaces: ()=>Promise.resolve(contextRoot.allNamespaces),
1543
+ namespaceExists: (ns)=>Promise.resolve(contextRoot.allNamespaces.includes(ns)),
1544
+ listTypes: ()=>Promise.resolve(contextRoot.allTypes)
1545
+ };
1546
+ };
1547
+
1548
+ /**
1549
+ * Discover context directories and create an API.
1550
+ */ const discoverOvercontext = async (options)=>{
1551
+ const { schemas, pluralNames = {}, readonly, ...rootOptions } = options;
1552
+ // Create registry
1553
+ const registry = createSchemaRegistry();
1554
+ for (const [type, schema] of Object.entries(schemas)){
1555
+ registry.register({
1556
+ type,
1557
+ schema: schema,
1558
+ pluralName: pluralNames[type]
1559
+ });
1560
+ }
1561
+ // Discover context directories
1562
+ const contextRoot = await discoverContextRoot({
1563
+ ...rootOptions,
1564
+ registry
1565
+ });
1566
+ if (!contextRoot.primary) {
1567
+ throw new Error('No context directory found');
1568
+ }
1569
+ // Create hierarchical provider
1570
+ const provider = await createHierarchicalProvider({
1571
+ contextRoot,
1572
+ registry,
1573
+ readonly
1574
+ });
1575
+ // Create context API
1576
+ return createContext({
1577
+ provider,
1578
+ registry,
1579
+ schemas,
1580
+ defaultNamespace: undefined
1581
+ });
1582
+ };
1583
+
1584
+ /**
1585
+ * Format entities for CLI output.
1586
+ */ const formatEntities = (entities, options)=>{
1587
+ const { format, fields, noHeaders } = options;
1588
+ if (format === 'json') {
1589
+ return JSON.stringify(entities, null, 2);
1590
+ }
1591
+ if (format === 'yaml') {
1592
+ return yaml__namespace.dump(entities);
1593
+ }
1594
+ // Table format
1595
+ if (entities.length === 0) {
1596
+ return 'No entities found.';
1597
+ }
1598
+ const displayFields = fields || [
1599
+ 'id',
1600
+ 'name',
1601
+ 'type'
1602
+ ];
1603
+ const rows = [];
1604
+ if (!noHeaders) {
1605
+ rows.push(displayFields.map((f)=>f.toUpperCase()));
1606
+ }
1607
+ for (const entity of entities){
1608
+ const row = displayFields.map((f)=>{
1609
+ const value = entity[f];
1610
+ if (value === undefined || value === null) return '-';
1611
+ if (typeof value === 'object') return JSON.stringify(value);
1612
+ return String(value);
1613
+ });
1614
+ rows.push(row);
1615
+ }
1616
+ // Calculate column widths
1617
+ const widths = displayFields.map((_, i)=>Math.max(...rows.map((row)=>row[i].length)));
1618
+ // Format rows
1619
+ return rows.map((row)=>row.map((cell, i)=>cell.padEnd(widths[i])).join(' ')).join('\n');
1620
+ };
1621
+ /**
1622
+ * Format a single entity for display.
1623
+ */ const formatEntity = (entity, options)=>{
1624
+ if (options.format === 'json') {
1625
+ return JSON.stringify(entity, null, 2);
1626
+ }
1627
+ return yaml__namespace.dump(entity);
1628
+ };
1629
+
1630
+ /**
1631
+ * Build a list command handler.
1632
+ */ const listCommand = async (ctx, options)=>{
1633
+ const result = await ctx.api.search({
1634
+ type: options.type,
1635
+ namespace: ctx.namespace,
1636
+ search: options.search,
1637
+ limit: options.limit || 20
1638
+ });
1639
+ return formatEntities(result.items, {
1640
+ format: ctx.outputFormat,
1641
+ fields: options.fields
1642
+ });
1643
+ };
1644
+ /**
1645
+ * Build a get command handler.
1646
+ */ const getCommand = async (ctx, options)=>{
1647
+ const entity = await ctx.api.get(options.type, options.id, ctx.namespace);
1648
+ if (!entity) {
1649
+ throw new Error(`Entity not found: ${options.type}/${options.id}`);
1650
+ }
1651
+ return formatEntity(entity, {
1652
+ format: ctx.outputFormat
1653
+ });
1654
+ };
1655
+ /**
1656
+ * Build a create command handler.
1657
+ */ const createCommand = async (ctx, options)=>{
1658
+ const entity = await ctx.api.create(options.type, {
1659
+ name: options.name,
1660
+ ...options.data
1661
+ }, {
1662
+ id: options.id,
1663
+ namespace: ctx.namespace
1664
+ });
1665
+ return `Created ${options.type}: ${entity.id}`;
1666
+ };
1667
+ /**
1668
+ * Build an update command handler.
1669
+ */ const updateCommand = async (ctx, options)=>{
1670
+ await ctx.api.update(options.type, options.id, options.data, ctx.namespace);
1671
+ return `Updated ${options.type}: ${options.id}`;
1672
+ };
1673
+ /**
1674
+ * Build a delete command handler.
1675
+ */ const deleteCommand = async (ctx, options)=>{
1676
+ const deleted = await ctx.api.delete(options.type, options.id, ctx.namespace);
1677
+ if (!deleted) {
1678
+ throw new Error(`Entity not found: ${options.type}/${options.id}`);
1679
+ }
1680
+ return `Deleted ${options.type}: ${options.id}`;
1681
+ };
1682
+
1683
+ /**
1684
+ * CLI builder for consumers to create their own CLIs.
1685
+ *
1686
+ * @example
1687
+ * const cli = createCLIBuilder({ api });
1688
+ *
1689
+ * // In your CLI tool:
1690
+ * const result = await cli.list({ type: 'person' });
1691
+ * console.log(result);
1692
+ */ const createCLIBuilder = (options)=>{
1693
+ const { api, defaultFormat = 'table' } = options;
1694
+ const createContext = (format, namespace)=>({
1695
+ api,
1696
+ outputFormat: format || defaultFormat,
1697
+ namespace
1698
+ });
1699
+ return {
1700
+ /**
1701
+ * List entities.
1702
+ */ list: (opts)=>listCommand(createContext(opts.format, opts.namespace), opts),
1703
+ /**
1704
+ * Get a single entity.
1705
+ */ get: (opts)=>getCommand(createContext(opts.format, opts.namespace), opts),
1706
+ /**
1707
+ * Create an entity.
1708
+ */ create: (opts)=>createCommand(createContext(opts.format, opts.namespace), opts),
1709
+ /**
1710
+ * Update an entity.
1711
+ */ update: (opts)=>updateCommand(createContext(opts.format, opts.namespace), opts),
1712
+ /**
1713
+ * Delete an entity.
1714
+ */ delete: (opts)=>deleteCommand(createContext(opts.format, opts.namespace), opts),
1715
+ /**
1716
+ * List available entity types.
1717
+ */ types: ()=>api.types()
1718
+ };
1719
+ };
1720
+
1721
+ exports.BaseEntitySchema = BaseEntitySchema;
1722
+ exports.EntityMetadataSchema = EntityMetadataSchema;
1723
+ exports.EntityNotFoundError = EntityNotFoundError;
1724
+ exports.NamespaceNotFoundError = NamespaceNotFoundError;
1725
+ exports.QueryBuilder = QueryBuilder;
1726
+ exports.ReadonlyStorageError = ReadonlyStorageError;
1727
+ exports.SchemaNotRegisteredError = SchemaNotRegisteredError;
1728
+ exports.StorageAccessError = StorageAccessError;
1729
+ exports.StorageError = StorageError;
1730
+ exports.ValidationError = ValidationError;
1731
+ exports.createCLIBuilder = createCLIBuilder;
1732
+ exports.createCommand = createCommand;
1733
+ exports.createContext = createContext;
1734
+ exports.createDirectoryWalker = createDirectoryWalker;
1735
+ exports.createEntitySchema = createEntitySchema;
1736
+ exports.createFileSystemProvider = createFileSystemProvider;
1737
+ exports.createHierarchicalProvider = createHierarchicalProvider;
1738
+ exports.createMemoryProvider = createMemoryProvider;
1739
+ exports.createMultiNamespaceContext = createMultiNamespaceContext;
1740
+ exports.createNamespaceResolver = createNamespaceResolver;
1741
+ exports.createObservableProvider = createObservableProvider;
1742
+ exports.createSchemaRegistry = createSchemaRegistry;
1743
+ exports.createSearchEngine = createSearchEngine;
1744
+ exports.createTypedAPI = createTypedAPI;
1745
+ exports.defineSchemas = defineSchemas;
1746
+ exports.deleteCommand = deleteCommand;
1747
+ exports.discoverContextRoot = discoverContextRoot;
1748
+ exports.discoverOvercontext = discoverOvercontext;
1749
+ exports.ensureContextRoot = ensureContextRoot;
1750
+ exports.formatEntities = formatEntities;
1751
+ exports.formatEntity = formatEntity;
1752
+ exports.formatValidationErrors = formatValidationErrors;
1753
+ exports.generateUniqueId = generateUniqueId;
1754
+ exports.getCommand = getCommand;
1755
+ exports.isBaseEntity = isBaseEntity;
1756
+ exports.isValidEntitySchema = isValidEntitySchema;
1757
+ exports.listCommand = listCommand;
1758
+ exports.query = query;
1759
+ exports.slugify = slugify;
1760
+ exports.updateCommand = updateCommand;
1761
+ exports.validateBaseEntity = validateBaseEntity;
1762
+ exports.validateEntity = validateEntity;
1763
+ //# sourceMappingURL=index.cjs.map