@utilarium/overcontext 0.0.5 → 0.0.6-dev.20260218224640.3065466

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,6 +126,22 @@ const workPeople = await ctx.getAll('person', 'work');
126
126
  - **Hierarchical**: Multi-level discovery with override behavior
127
127
  - **Custom**: Implement your own
128
128
 
129
+ ### Custom Filenames
130
+
131
+ Control how entity files are named on disk with a filename strategy:
132
+
133
+ ```typescript
134
+ const ctx = await discoverOvercontext({
135
+ schemas: { person: PersonSchema },
136
+ filenameStrategy: (entity) => {
137
+ const slug = (entity as any).slug;
138
+ return slug ? `${entity.id.substring(0, 8)}-${slug}` : entity.id;
139
+ },
140
+ });
141
+ ```
142
+
143
+ This produces files like `d00acdc4-gerald-corson.yaml` instead of `d00acdc4-5678-9abc-def0-111111111111.yaml`. Lookups, existence checks, and deletes work transparently regardless of the filename on disk. See the [Storage Providers](./guide/storage-providers.md#custom-filename-strategy) guide for details.
144
+
129
145
  ## API Overview
130
146
 
131
147
  ### CRUD Operations
@@ -1,10 +1,13 @@
1
1
  import { StorageProvider } from '../storage/interface';
2
+ import { BaseEntity } from '../schema/base';
2
3
  import { SchemaRegistry } from '../schema/registry';
3
4
  import { ContextRoot } from './context-root';
4
5
  export interface HierarchicalProviderOptions {
5
6
  contextRoot: ContextRoot;
6
7
  registry: SchemaRegistry;
7
8
  readonly?: boolean;
9
+ /** Custom filename strategy passed through to filesystem providers */
10
+ filenameStrategy?: (entity: BaseEntity) => string;
8
11
  }
9
12
  /**
10
13
  * Storage provider that reads from multiple context directories
@@ -4,7 +4,7 @@ import { createFileSystemProvider } from '../storage/filesystem.js';
4
4
  * Storage provider that reads from multiple context directories
5
5
  * and writes to the primary (closest) directory.
6
6
  */ const createHierarchicalProvider = async (options)=>{
7
- const { contextRoot, registry, readonly = false } = options;
7
+ const { contextRoot, registry, readonly = false, filenameStrategy } = options;
8
8
  if (contextRoot.contextPaths.length === 0) {
9
9
  throw new Error('No context directories found');
10
10
  }
@@ -15,7 +15,8 @@ import { createFileSystemProvider } from '../storage/filesystem.js';
15
15
  basePath: contextPath,
16
16
  registry,
17
17
  createIfMissing: false,
18
- readonly: true
18
+ readonly: true,
19
+ filenameStrategy
19
20
  });
20
21
  await fsProvider.initialize();
21
22
  readProviders.push(fsProvider);
@@ -25,7 +26,8 @@ import { createFileSystemProvider } from '../storage/filesystem.js';
25
26
  basePath: contextRoot.primary,
26
27
  registry,
27
28
  createIfMissing: true,
28
- readonly
29
+ readonly,
30
+ filenameStrategy
29
31
  });
30
32
  await primaryProvider.initialize();
31
33
  const findEntities = async (filter)=>{
@@ -1 +1 @@
1
- {"version":3,"file":"hierarchical-provider.js","sources":["../../src/discovery/hierarchical-provider.ts"],"sourcesContent":["import { StorageProvider, EntityFilter } from '../storage/interface';\nimport { BaseEntity } from '../schema/base';\nimport { SchemaRegistry } from '../schema/registry';\nimport { createFileSystemProvider } from '../storage/filesystem';\nimport { ContextRoot } from './context-root';\n\nexport interface HierarchicalProviderOptions {\n contextRoot: ContextRoot;\n registry: SchemaRegistry;\n readonly?: boolean;\n}\n\n/**\n * Storage provider that reads from multiple context directories\n * and writes to the primary (closest) directory.\n */\nexport const createHierarchicalProvider = async (\n options: HierarchicalProviderOptions\n): Promise<StorageProvider> => {\n const { contextRoot, registry, readonly = false } = options;\n\n if (contextRoot.contextPaths.length === 0) {\n throw new Error('No context directories found');\n }\n\n // Create read-only providers for each context directory\n const readProviders: StorageProvider[] = [];\n for (const contextPath of contextRoot.contextPaths) {\n const fsProvider = await createFileSystemProvider({\n basePath: contextPath,\n registry,\n createIfMissing: false,\n readonly: true,\n });\n await fsProvider.initialize();\n readProviders.push(fsProvider);\n }\n\n // Primary provider for writes\n const primaryProvider = await createFileSystemProvider({\n basePath: contextRoot.primary!,\n registry,\n createIfMissing: true,\n readonly,\n });\n await primaryProvider.initialize();\n\n const findEntities = async <T extends BaseEntity>(filter: EntityFilter): Promise<T[]> => {\n const byId = new Map<string, T>();\n\n // Create a filter without pagination - we'll apply pagination after merging\n // This prevents double-pagination (sub-providers paginating, then us paginating again)\n const filterWithoutPagination: EntityFilter = {\n type: filter.type,\n namespace: filter.namespace,\n ids: filter.ids,\n search: filter.search,\n };\n\n for (const p of [...readProviders].reverse()) {\n const results = await p.find<T>(filterWithoutPagination);\n for (const entity of results) {\n byId.set(entity.id, entity);\n }\n }\n\n let results = Array.from(byId.values());\n\n // Apply pagination once, after merging all results\n if (filter.offset) results = results.slice(filter.offset);\n if (filter.limit) results = results.slice(0, filter.limit);\n\n return results;\n };\n\n return {\n name: 'hierarchical',\n location: contextRoot.primary!,\n registry,\n\n async initialize() { },\n\n async dispose() {\n for (const p of readProviders) await p.dispose();\n await primaryProvider.dispose();\n },\n\n async isAvailable() {\n return primaryProvider.isAvailable();\n },\n\n // Read operations search all providers (closest first)\n async get<T extends BaseEntity>(type: string, id: string, namespace?: string) {\n for (const p of readProviders) {\n const entity = await p.get<T>(type, id, namespace);\n if (entity) return entity;\n }\n return undefined;\n },\n\n async getAll<T extends BaseEntity>(type: string, namespace?: string) {\n const byId = new Map<string, T>();\n\n // Process in reverse order so closest overwrites\n for (const p of [...readProviders].reverse()) {\n const entities = await p.getAll<T>(type, namespace);\n for (const entity of entities) {\n byId.set(entity.id, entity);\n }\n }\n\n return Array.from(byId.values());\n },\n\n find: findEntities,\n\n async exists(type: string, id: string, namespace?: string) {\n for (const p of readProviders) {\n if (await p.exists(type, id, namespace)) return true;\n }\n return false;\n },\n\n async count(filter: EntityFilter) {\n const results = await findEntities(filter);\n return results.length;\n },\n\n // Write operations go to primary\n save: (entity, namespace) => primaryProvider.save(entity, namespace),\n delete: (type, id, namespace) => primaryProvider.delete(type, id, namespace),\n saveBatch: (entities, namespace) => primaryProvider.saveBatch(entities, namespace),\n deleteBatch: (refs, namespace) => primaryProvider.deleteBatch(refs, namespace),\n\n listNamespaces: () => Promise.resolve(contextRoot.allNamespaces),\n namespaceExists: (ns) => Promise.resolve(contextRoot.allNamespaces.includes(ns)),\n listTypes: () => Promise.resolve(contextRoot.allTypes),\n };\n};\n"],"names":["createHierarchicalProvider","options","contextRoot","registry","readonly","contextPaths","length","Error","readProviders","contextPath","fsProvider","createFileSystemProvider","basePath","createIfMissing","initialize","push","primaryProvider","primary","findEntities","filter","byId","Map","filterWithoutPagination","type","namespace","ids","search","p","reverse","results","find","entity","set","id","Array","from","values","offset","slice","limit","name","location","dispose","isAvailable","get","undefined","getAll","entities","exists","count","save","delete","saveBatch","deleteBatch","refs","listNamespaces","Promise","resolve","allNamespaces","namespaceExists","ns","includes","listTypes","allTypes"],"mappings":"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"}
1
+ {"version":3,"file":"hierarchical-provider.js","sources":["../../src/discovery/hierarchical-provider.ts"],"sourcesContent":["import { StorageProvider, EntityFilter } from '../storage/interface';\nimport { BaseEntity } from '../schema/base';\nimport { SchemaRegistry } from '../schema/registry';\nimport { createFileSystemProvider } from '../storage/filesystem';\nimport { ContextRoot } from './context-root';\n\nexport interface HierarchicalProviderOptions {\n contextRoot: ContextRoot;\n registry: SchemaRegistry;\n readonly?: boolean;\n /** Custom filename strategy passed through to filesystem providers */\n filenameStrategy?: (entity: BaseEntity) => string;\n}\n\n/**\n * Storage provider that reads from multiple context directories\n * and writes to the primary (closest) directory.\n */\nexport const createHierarchicalProvider = async (\n options: HierarchicalProviderOptions\n): Promise<StorageProvider> => {\n const { contextRoot, registry, readonly = false, filenameStrategy } = options;\n\n if (contextRoot.contextPaths.length === 0) {\n throw new Error('No context directories found');\n }\n\n // Create read-only providers for each context directory\n const readProviders: StorageProvider[] = [];\n for (const contextPath of contextRoot.contextPaths) {\n const fsProvider = await createFileSystemProvider({\n basePath: contextPath,\n registry,\n createIfMissing: false,\n readonly: true,\n filenameStrategy,\n });\n await fsProvider.initialize();\n readProviders.push(fsProvider);\n }\n\n // Primary provider for writes\n const primaryProvider = await createFileSystemProvider({\n basePath: contextRoot.primary!,\n registry,\n createIfMissing: true,\n readonly,\n filenameStrategy,\n });\n await primaryProvider.initialize();\n\n const findEntities = async <T extends BaseEntity>(filter: EntityFilter): Promise<T[]> => {\n const byId = new Map<string, T>();\n\n // Create a filter without pagination - we'll apply pagination after merging\n // This prevents double-pagination (sub-providers paginating, then us paginating again)\n const filterWithoutPagination: EntityFilter = {\n type: filter.type,\n namespace: filter.namespace,\n ids: filter.ids,\n search: filter.search,\n };\n\n for (const p of [...readProviders].reverse()) {\n const results = await p.find<T>(filterWithoutPagination);\n for (const entity of results) {\n byId.set(entity.id, entity);\n }\n }\n\n let results = Array.from(byId.values());\n\n // Apply pagination once, after merging all results\n if (filter.offset) results = results.slice(filter.offset);\n if (filter.limit) results = results.slice(0, filter.limit);\n\n return results;\n };\n\n return {\n name: 'hierarchical',\n location: contextRoot.primary!,\n registry,\n\n async initialize() { },\n\n async dispose() {\n for (const p of readProviders) await p.dispose();\n await primaryProvider.dispose();\n },\n\n async isAvailable() {\n return primaryProvider.isAvailable();\n },\n\n // Read operations search all providers (closest first)\n async get<T extends BaseEntity>(type: string, id: string, namespace?: string) {\n for (const p of readProviders) {\n const entity = await p.get<T>(type, id, namespace);\n if (entity) return entity;\n }\n return undefined;\n },\n\n async getAll<T extends BaseEntity>(type: string, namespace?: string) {\n const byId = new Map<string, T>();\n\n // Process in reverse order so closest overwrites\n for (const p of [...readProviders].reverse()) {\n const entities = await p.getAll<T>(type, namespace);\n for (const entity of entities) {\n byId.set(entity.id, entity);\n }\n }\n\n return Array.from(byId.values());\n },\n\n find: findEntities,\n\n async exists(type: string, id: string, namespace?: string) {\n for (const p of readProviders) {\n if (await p.exists(type, id, namespace)) return true;\n }\n return false;\n },\n\n async count(filter: EntityFilter) {\n const results = await findEntities(filter);\n return results.length;\n },\n\n // Write operations go to primary\n save: (entity, namespace) => primaryProvider.save(entity, namespace),\n delete: (type, id, namespace) => primaryProvider.delete(type, id, namespace),\n saveBatch: (entities, namespace) => primaryProvider.saveBatch(entities, namespace),\n deleteBatch: (refs, namespace) => primaryProvider.deleteBatch(refs, namespace),\n\n listNamespaces: () => Promise.resolve(contextRoot.allNamespaces),\n namespaceExists: (ns) => Promise.resolve(contextRoot.allNamespaces.includes(ns)),\n listTypes: () => Promise.resolve(contextRoot.allTypes),\n };\n};\n"],"names":["createHierarchicalProvider","options","contextRoot","registry","readonly","filenameStrategy","contextPaths","length","Error","readProviders","contextPath","fsProvider","createFileSystemProvider","basePath","createIfMissing","initialize","push","primaryProvider","primary","findEntities","filter","byId","Map","filterWithoutPagination","type","namespace","ids","search","p","reverse","results","find","entity","set","id","Array","from","values","offset","slice","limit","name","location","dispose","isAvailable","get","undefined","getAll","entities","exists","count","save","delete","saveBatch","deleteBatch","refs","listNamespaces","Promise","resolve","allNamespaces","namespaceExists","ns","includes","listTypes","allTypes"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { SchemaMap } from '../schema/registry';
2
+ import { BaseEntity } from '../schema/base';
2
3
  import { ContextRootOptions } from './context-root';
3
4
  import { OvercontextAPI } from '../api/context';
4
5
  export * from './walker';
@@ -11,6 +12,12 @@ export interface DiscoverOptions<TSchemas extends SchemaMap> extends ContextRoot
11
12
  pluralNames?: Partial<Record<keyof TSchemas, string>>;
12
13
  /** Whether context is readonly */
13
14
  readonly?: boolean;
15
+ /**
16
+ * Custom filename strategy for entity files.
17
+ * Given an entity, returns the filename stem (without extension).
18
+ * When not provided, entity.id is used as the filename.
19
+ */
20
+ filenameStrategy?: (entity: BaseEntity) => string;
14
21
  }
15
22
  /**
16
23
  * Discover context directories and create an API.
@@ -10,7 +10,7 @@ import { createContext } from '../api/context.js';
10
10
  /**
11
11
  * Discover context directories and create an API.
12
12
  */ const discoverOvercontext = async (options)=>{
13
- const { schemas, pluralNames = {}, readonly, ...rootOptions } = options;
13
+ const { schemas, pluralNames = {}, readonly, filenameStrategy, ...rootOptions } = options;
14
14
  // Create registry
15
15
  const registry = createSchemaRegistry();
16
16
  for (const [type, schema] of Object.entries(schemas)){
@@ -32,7 +32,8 @@ import { createContext } from '../api/context.js';
32
32
  const provider = await createHierarchicalProvider({
33
33
  contextRoot,
34
34
  registry,
35
- readonly
35
+ readonly,
36
+ filenameStrategy
36
37
  });
37
38
  // Create context API
38
39
  return createContext({
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/discovery/index.ts"],"sourcesContent":["export * from './walker';\nexport * from './context-root';\nexport * from './hierarchical-provider';\n\nimport { z } from 'zod';\nimport { SchemaMap, createSchemaRegistry } from '../schema/registry';\nimport { BaseEntity } from '../schema/base';\nimport { discoverContextRoot, ContextRootOptions } from './context-root';\nimport { createHierarchicalProvider } from './hierarchical-provider';\nimport { createContext, OvercontextAPI } from '../api/context';\n\nexport interface DiscoverOptions<TSchemas extends SchemaMap> extends ContextRootOptions {\n /** Schemas to register */\n schemas: TSchemas;\n\n /** Custom plural names for types */\n pluralNames?: Partial<Record<keyof TSchemas, string>>;\n\n /** Whether context is readonly */\n readonly?: boolean;\n}\n\n/**\n * Discover context directories and create an API.\n */\nexport const discoverOvercontext = async <TSchemas extends SchemaMap>(\n options: DiscoverOptions<TSchemas>\n): Promise<OvercontextAPI<TSchemas>> => {\n const { schemas, pluralNames = {}, readonly, ...rootOptions } = options;\n\n // Create registry\n const registry = createSchemaRegistry();\n for (const [type, schema] of Object.entries(schemas)) {\n registry.register({\n type,\n schema: schema as z.ZodType<BaseEntity>,\n pluralName: (pluralNames as Record<string, string>)[type],\n });\n }\n\n // Discover context directories\n const contextRoot = await discoverContextRoot({\n ...rootOptions,\n registry,\n });\n\n if (!contextRoot.primary) {\n throw new Error('No context directory found');\n }\n\n // Create hierarchical provider\n const provider = await createHierarchicalProvider({\n contextRoot,\n registry,\n readonly,\n });\n\n // Create context API\n return createContext({\n provider,\n registry,\n schemas,\n defaultNamespace: undefined,\n });\n};\n"],"names":["discoverOvercontext","options","schemas","pluralNames","readonly","rootOptions","registry","createSchemaRegistry","type","schema","Object","entries","register","pluralName","contextRoot","discoverContextRoot","primary","Error","provider","createHierarchicalProvider","createContext","defaultNamespace","undefined"],"mappings":";;;;;;;;;AAsBA;;IAGO,MAAMA,mBAAAA,GAAsB,OAC/BC,OAAAA,GAAAA;IAEA,MAAM,EAAEC,OAAO,EAAEC,WAAAA,GAAc,EAAE,EAAEC,QAAQ,EAAE,GAAGC,WAAAA,EAAa,GAAGJ,OAAAA;;AAGhE,IAAA,MAAMK,QAAAA,GAAWC,oBAAAA,EAAAA;IACjB,KAAK,MAAM,CAACC,IAAAA,EAAMC,MAAAA,CAAO,IAAIC,MAAAA,CAAOC,OAAO,CAACT,OAAAA,CAAAA,CAAU;AAClDI,QAAAA,QAAAA,CAASM,QAAQ,CAAC;AACdJ,YAAAA,IAAAA;YACAC,MAAAA,EAAQA,MAAAA;YACRI,UAAAA,EAAaV,WAAsC,CAACK,IAAAA;AACxD,SAAA,CAAA;AACJ,IAAA;;IAGA,MAAMM,WAAAA,GAAc,MAAMC,mBAAAA,CAAoB;AAC1C,QAAA,GAAGV,WAAW;AACdC,QAAAA;AACJ,KAAA,CAAA;IAEA,IAAI,CAACQ,WAAAA,CAAYE,OAAO,EAAE;AACtB,QAAA,MAAM,IAAIC,KAAAA,CAAM,4BAAA,CAAA;AACpB,IAAA;;IAGA,MAAMC,QAAAA,GAAW,MAAMC,0BAAAA,CAA2B;AAC9CL,QAAAA,WAAAA;AACAR,QAAAA,QAAAA;AACAF,QAAAA;AACJ,KAAA,CAAA;;AAGA,IAAA,OAAOgB,aAAAA,CAAc;AACjBF,QAAAA,QAAAA;AACAZ,QAAAA,QAAAA;AACAJ,QAAAA,OAAAA;QACAmB,gBAAAA,EAAkBC;AACtB,KAAA,CAAA;AACJ;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../src/discovery/index.ts"],"sourcesContent":["export * from './walker';\nexport * from './context-root';\nexport * from './hierarchical-provider';\n\nimport { z } from 'zod';\nimport { SchemaMap, createSchemaRegistry } from '../schema/registry';\nimport { BaseEntity } from '../schema/base';\nimport { discoverContextRoot, ContextRootOptions } from './context-root';\nimport { createHierarchicalProvider } from './hierarchical-provider';\nimport { createContext, OvercontextAPI } from '../api/context';\n\nexport interface DiscoverOptions<TSchemas extends SchemaMap> extends ContextRootOptions {\n /** Schemas to register */\n schemas: TSchemas;\n\n /** Custom plural names for types */\n pluralNames?: Partial<Record<keyof TSchemas, string>>;\n\n /** Whether context is readonly */\n readonly?: boolean;\n\n /**\n * Custom filename strategy for entity files.\n * Given an entity, returns the filename stem (without extension).\n * When not provided, entity.id is used as the filename.\n */\n filenameStrategy?: (entity: BaseEntity) => string;\n}\n\n/**\n * Discover context directories and create an API.\n */\nexport const discoverOvercontext = async <TSchemas extends SchemaMap>(\n options: DiscoverOptions<TSchemas>\n): Promise<OvercontextAPI<TSchemas>> => {\n const { schemas, pluralNames = {}, readonly, filenameStrategy, ...rootOptions } = options;\n\n // Create registry\n const registry = createSchemaRegistry();\n for (const [type, schema] of Object.entries(schemas)) {\n registry.register({\n type,\n schema: schema as z.ZodType<BaseEntity>,\n pluralName: (pluralNames as Record<string, string>)[type],\n });\n }\n\n // Discover context directories\n const contextRoot = await discoverContextRoot({\n ...rootOptions,\n registry,\n });\n\n if (!contextRoot.primary) {\n throw new Error('No context directory found');\n }\n\n // Create hierarchical provider\n const provider = await createHierarchicalProvider({\n contextRoot,\n registry,\n readonly,\n filenameStrategy,\n });\n\n // Create context API\n return createContext({\n provider,\n registry,\n schemas,\n defaultNamespace: undefined,\n });\n};\n"],"names":["discoverOvercontext","options","schemas","pluralNames","readonly","filenameStrategy","rootOptions","registry","createSchemaRegistry","type","schema","Object","entries","register","pluralName","contextRoot","discoverContextRoot","primary","Error","provider","createHierarchicalProvider","createContext","defaultNamespace","undefined"],"mappings":";;;;;;;;;AA6BA;;IAGO,MAAMA,mBAAAA,GAAsB,OAC/BC,OAAAA,GAAAA;AAEA,IAAA,MAAM,EAAEC,OAAO,EAAEC,WAAAA,GAAc,EAAE,EAAEC,QAAQ,EAAEC,gBAAgB,EAAE,GAAGC,aAAa,GAAGL,OAAAA;;AAGlF,IAAA,MAAMM,QAAAA,GAAWC,oBAAAA,EAAAA;IACjB,KAAK,MAAM,CAACC,IAAAA,EAAMC,MAAAA,CAAO,IAAIC,MAAAA,CAAOC,OAAO,CAACV,OAAAA,CAAAA,CAAU;AAClDK,QAAAA,QAAAA,CAASM,QAAQ,CAAC;AACdJ,YAAAA,IAAAA;YACAC,MAAAA,EAAQA,MAAAA;YACRI,UAAAA,EAAaX,WAAsC,CAACM,IAAAA;AACxD,SAAA,CAAA;AACJ,IAAA;;IAGA,MAAMM,WAAAA,GAAc,MAAMC,mBAAAA,CAAoB;AAC1C,QAAA,GAAGV,WAAW;AACdC,QAAAA;AACJ,KAAA,CAAA;IAEA,IAAI,CAACQ,WAAAA,CAAYE,OAAO,EAAE;AACtB,QAAA,MAAM,IAAIC,KAAAA,CAAM,4BAAA,CAAA;AACpB,IAAA;;IAGA,MAAMC,QAAAA,GAAW,MAAMC,0BAAAA,CAA2B;AAC9CL,QAAAA,WAAAA;AACAR,QAAAA,QAAAA;AACAH,QAAAA,QAAAA;AACAC,QAAAA;AACJ,KAAA,CAAA;;AAGA,IAAA,OAAOgB,aAAAA,CAAc;AACjBF,QAAAA,QAAAA;AACAZ,QAAAA,QAAAA;AACAL,QAAAA,OAAAA;QACAoB,gBAAAA,EAAkBC;AACtB,KAAA,CAAA;AACJ;;;;"}
package/dist/index.cjs CHANGED
@@ -412,7 +412,7 @@ class NamespaceNotFoundError extends StorageError {
412
412
  };
413
413
 
414
414
  const createFileSystemProvider = async (options)=>{
415
- const { basePath, registry, createIfMissing = true, extension = '.yaml', readonly = false, defaultNamespace } = options;
415
+ const { basePath, registry, createIfMissing = true, extension = '.yaml', readonly = false, defaultNamespace, filenameStrategy } = options;
416
416
  // --- Helper Functions ---
417
417
  /**
418
418
  * Sanitize a path component to prevent directory traversal attacks.
@@ -487,6 +487,53 @@ const createFileSystemProvider = async (options)=>{
487
487
  verifyPathWithinBase(fullPath);
488
488
  return fullPath;
489
489
  };
490
+ const getEntityPathForWrite = (entity, namespace)=>{
491
+ if (!filenameStrategy) {
492
+ return getEntityPath(entity.type, entity.id, namespace);
493
+ }
494
+ const filename = filenameStrategy(entity);
495
+ const safeFilename = sanitizePathComponent(filename, 'filename');
496
+ const dir = getEntityDir(entity.type, namespace);
497
+ const fullPath = path__namespace.join(dir, `${safeFilename}${extension}`);
498
+ verifyPathWithinBase(fullPath);
499
+ return fullPath;
500
+ };
501
+ /**
502
+ * Find the actual file path for an entity by id, handling custom filename strategies.
503
+ * Tries direct {id}{ext} first, then scans the directory using id prefix matching.
504
+ */ const findEntityFileById = async (type, id, namespace)=>{
505
+ const directPath = getEntityPath(type, id, namespace);
506
+ if (node_fs.existsSync(directPath)) {
507
+ return directPath;
508
+ }
509
+ if (!filenameStrategy) {
510
+ return undefined;
511
+ }
512
+ let dir;
513
+ try {
514
+ dir = getEntityDir(type, namespace);
515
+ } catch {
516
+ return undefined;
517
+ }
518
+ if (!node_fs.existsSync(dir)) {
519
+ return undefined;
520
+ }
521
+ const prefix = id.substring(0, Math.min(8, id.length));
522
+ const files = await fs__namespace.readdir(dir);
523
+ const candidates = files.filter((f)=>(f.endsWith('.yaml') || f.endsWith('.yml')) && f.startsWith(prefix));
524
+ if (candidates.length === 1) {
525
+ return path__namespace.join(dir, candidates[0]);
526
+ }
527
+ // Multiple prefix matches — read and verify the id field
528
+ for (const file of candidates){
529
+ const filePath = path__namespace.join(dir, file);
530
+ const entity = await readEntity(filePath, type);
531
+ if (entity && entity.id === id) {
532
+ return filePath;
533
+ }
534
+ }
535
+ return undefined;
536
+ };
490
537
  const ensureDir = async (dir)=>{
491
538
  if (!node_fs.existsSync(dir) && createIfMissing && !readonly) {
492
539
  await fs__namespace.mkdir(dir, {
@@ -545,7 +592,16 @@ const createFileSystemProvider = async (options)=>{
545
592
  }
546
593
  const dir = getEntityDir(entity.type, namespace);
547
594
  await ensureDir(dir);
548
- const filePath = getEntityPath(entity.type, entity.id, namespace);
595
+ const filePath = getEntityPathForWrite(entity, namespace);
596
+ // If filenameStrategy is set, clean up any old file with a different name for the same id
597
+ if (filenameStrategy) {
598
+ const oldPath = await findEntityFileById(entity.type, entity.id, namespace);
599
+ if (oldPath && path__namespace.resolve(oldPath) !== path__namespace.resolve(filePath)) {
600
+ try {
601
+ await fs__namespace.unlink(oldPath);
602
+ } catch {}
603
+ }
604
+ }
549
605
  // Remove framework-managed fields from saved YAML
550
606
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
551
607
  const { type: _type, source: _source, ...entityToSave } = entity;
@@ -613,6 +669,11 @@ const createFileSystemProvider = async (options)=>{
613
669
  },
614
670
  async get (type, id, namespace) {
615
671
  const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
672
+ if (filenameStrategy) {
673
+ const filePath = await findEntityFileById(type, id, ns);
674
+ if (!filePath) return undefined;
675
+ return readEntity(filePath, type);
676
+ }
616
677
  const filePath = getEntityPath(type, id, ns);
617
678
  return readEntity(filePath, type);
618
679
  },
@@ -676,6 +737,10 @@ const createFileSystemProvider = async (options)=>{
676
737
  async exists (type, id, namespace) {
677
738
  const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
678
739
  try {
740
+ if (filenameStrategy) {
741
+ const filePath = await findEntityFileById(type, id, ns);
742
+ return filePath !== undefined;
743
+ }
679
744
  const filePath = getEntityPath(type, id, ns);
680
745
  return node_fs.existsSync(filePath);
681
746
  } catch {
@@ -696,6 +761,12 @@ const createFileSystemProvider = async (options)=>{
696
761
  }
697
762
  const ns = namespace !== null && namespace !== void 0 ? namespace : defaultNamespace;
698
763
  try {
764
+ if (filenameStrategy) {
765
+ const filePath = await findEntityFileById(type, id, ns);
766
+ if (!filePath) return false;
767
+ await fs__namespace.unlink(filePath);
768
+ return true;
769
+ }
699
770
  const filePath = getEntityPath(type, id, ns);
700
771
  await fs__namespace.unlink(filePath);
701
772
  return true;
@@ -1595,7 +1666,7 @@ const discoverContextRoot = async (options = {})=>{
1595
1666
  * Storage provider that reads from multiple context directories
1596
1667
  * and writes to the primary (closest) directory.
1597
1668
  */ const createHierarchicalProvider = async (options)=>{
1598
- const { contextRoot, registry, readonly = false } = options;
1669
+ const { contextRoot, registry, readonly = false, filenameStrategy } = options;
1599
1670
  if (contextRoot.contextPaths.length === 0) {
1600
1671
  throw new Error('No context directories found');
1601
1672
  }
@@ -1606,7 +1677,8 @@ const discoverContextRoot = async (options = {})=>{
1606
1677
  basePath: contextPath,
1607
1678
  registry,
1608
1679
  createIfMissing: false,
1609
- readonly: true
1680
+ readonly: true,
1681
+ filenameStrategy
1610
1682
  });
1611
1683
  await fsProvider.initialize();
1612
1684
  readProviders.push(fsProvider);
@@ -1616,7 +1688,8 @@ const discoverContextRoot = async (options = {})=>{
1616
1688
  basePath: contextRoot.primary,
1617
1689
  registry,
1618
1690
  createIfMissing: true,
1619
- readonly
1691
+ readonly,
1692
+ filenameStrategy
1620
1693
  });
1621
1694
  await primaryProvider.initialize();
1622
1695
  const findEntities = async (filter)=>{
@@ -1701,7 +1774,7 @@ const discoverContextRoot = async (options = {})=>{
1701
1774
  /**
1702
1775
  * Discover context directories and create an API.
1703
1776
  */ const discoverOvercontext = async (options)=>{
1704
- const { schemas, pluralNames = {}, readonly, ...rootOptions } = options;
1777
+ const { schemas, pluralNames = {}, readonly, filenameStrategy, ...rootOptions } = options;
1705
1778
  // Create registry
1706
1779
  const registry = createSchemaRegistry();
1707
1780
  for (const [type, schema] of Object.entries(schemas)){
@@ -1723,7 +1796,8 @@ const discoverContextRoot = async (options = {})=>{
1723
1796
  const provider = await createHierarchicalProvider({
1724
1797
  contextRoot,
1725
1798
  registry,
1726
- readonly
1799
+ readonly,
1800
+ filenameStrategy
1727
1801
  });
1728
1802
  // Create context API
1729
1803
  return createContext({