@objectstack/metadata 17.2.0 → 17.4.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.
package/dist/node.d.cts CHANGED
@@ -1,6 +1,7 @@
1
- import { MetadataManager, MetadataManagerOptions, MetadataLoader, MetadataSerializer } from './index.cjs';
2
- export { DatabaseLoader, DatabaseLoaderOptions, HistoryCleanupManager, JSONSerializer, MemoryLoader, MetadataPlugin, Migration, RemoteLoader, SerializeOptions, TypeScriptSerializer, WatchCallback, YAMLSerializer, calculateChecksum, generateDiffSummary, generateSimpleDiff } from './index.cjs';
1
+ import { MetadataManager, MetadataManagerOptions, MetadataLoader, MetadataSerializer, MetadataKeyedItem } from './index.cjs';
2
+ export { AMBIGUOUS_METADATA_STEM_CODE, AMBIGUOUS_METADATA_STEM_STATUS, AmbiguousMetadataStemError, DatabaseLoader, DatabaseLoaderOptions, HistoryCleanupManager, JSONSerializer, MemoryLoader, MetadataPlugin, Migration, RemoteLoader, SerializeOptions, TypeScriptSerializer, WatchCallback, YAMLSerializer, calculateChecksum, generateDiffSummary, generateSimpleDiff, isAmbiguousMetadataStemError } from './index.cjs';
3
3
  export { HistoryOptions, MetaRef, MetadataEvent, MetadataItem, MetadataItemHeader, MetadataRepository, SysMetadataHistoryObject, SysMetadataObject, WatchFilter } from '@objectstack/metadata-core';
4
+ export { deriveViewContainerObject } from './view-container.cjs';
4
5
  import { MetadataLoaderContract, MetadataFormat, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult } from '@objectstack/spec/system';
5
6
  export { MetadataCollectionInfo, MetadataDiffResult, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
6
7
  export { IMetadataService, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
@@ -37,10 +38,162 @@ declare class FilesystemLoader implements MetadataLoader {
37
38
  constructor(rootDir: string, serializers: Map<MetadataFormat, MetadataSerializer>, logger?: Logger | undefined);
38
39
  load(type: string, name: string, options?: MetadataLoadOptions): Promise<MetadataLoadResult>;
39
40
  loadMany<T = any>(type: string, options?: MetadataLoadOptions): Promise<T[]>;
41
+ /**
42
+ * [#14341] The keyed half of {@link loadMany} — see {@link MetadataKeyedItem}
43
+ * for why the store's key travels BESIDE the body instead of being folded
44
+ * into it.
45
+ *
46
+ * THE RULE, in one sentence: an item is keyed by this loader's own
47
+ * name-to-path derivation — {@link nameFromFilename}, the very basename
48
+ * derivation `list()` reports — ONLY where that derivation is a bijection for
49
+ * the file (it sits directly under `ROOT/TYPE/` and carries one of the
50
+ * extensions {@link findFile} tries, so `findFile(type, key)` resolves back to
51
+ * this same file); every other shape keeps the pre-#14205 behaviour verbatim,
52
+ * keyed by `body.name` when it has one and dropped when it has none.
53
+ *
54
+ * Why the rule stops there (PM ruling on #14341, 2026-09-02, knowingly over
55
+ * triage's "a nested path keeps whatever `list()` reports for it today"):
56
+ * `list()` and `findFile()` DISAGREE outside that shape. For
57
+ * `ROOT/TYPE/crm/account.json`, `list()` reports the bare `account`, but
58
+ * `findFile()` resolves that name against `ROOT/TYPE/account.json` and finds
59
+ * nothing — the only name reaching the file is `crm/account`, which nothing
60
+ * reports. An extension-less file is read by `loadMany()` and reported by
61
+ * `list()`, and `findFile()` resolves neither. Keying by either side would
62
+ * mint a name some other door cannot open, and two directories holding the
63
+ * same basename would collide in silence
64
+ * (`MetadataManager.admitLoaderItems()` keeps the first and says nothing).
65
+ * The card's own fence: "keying items under names nothing else uses … is
66
+ * worse than today's honest drop". So the drop stays exactly where the key is
67
+ * unsettled, and is pinned as a RECORD in
68
+ * `filesystem-loader-keyed-items.test.ts`.
69
+ *
70
+ * [#14486, partial] `list()` and {@link findFile} have since converged on
71
+ * {@link resolvableNameForPath} — the derivation this method already used —
72
+ * so a nested or extension-less file is now neither listed nor resolvable.
73
+ * What did NOT change is the WALK behind this method: `loadManyEntries()`
74
+ * still READS those files, so `loadMany()` still returns their bodies and
75
+ * this method still falls back to `body.name` for them. That half of the
76
+ * #14486 ruling ("nothing unlisted is returned by `loadMany()` either") is
77
+ * deliberately NOT taken here: it would invert the three landed #14341 pins
78
+ * in `filesystem-loader-keyed-items.test.ts:113,167,187` and the
79
+ * `loadMany()` CONTROL at `:196`, and that file was under a concurrent
80
+ * claim (PR #14627) when this landed. The remaining divergence — listed ⊂
81
+ * loaded — is pinned as a RECORD in
82
+ * `filesystem-loader-list-reachability.test.ts` rather than left implicit.
83
+ *
84
+ * One consequence, deliberate: a flat file whose `body.name` DISAGREES with
85
+ * its basename is now keyed by the BASENAME. That is #14205's rule (identity
86
+ * is the key the store holds an item under, not `body.name`) applied to this
87
+ * loader, and it aligns `MetadataManager.list()` with `listNames()` for that
88
+ * shape.
89
+ *
90
+ * The body is handed back by reference, unchanged: nothing is written into a
91
+ * body that deliberately has no `name`. `limit` bounds the items LOADED,
92
+ * exactly as `loadMany()` does — an entry the key rule drops has still been
93
+ * read and still counts against it.
94
+ */
95
+ loadManyKeyed<T = any>(type: string, options?: MetadataLoadOptions): Promise<MetadataKeyedItem<T>[]>;
96
+ /**
97
+ * The single walk behind {@link loadMany} and {@link loadManyKeyed}: one glob,
98
+ * one serializer pass, one `limit`. Shared so the two can never answer with
99
+ * different bodies for the same file — {@link MetadataLoader.loadManyKeyed}
100
+ * requires `data` to be "the same body `loadMany()` would return for the
101
+ * item", and a second copy of this walk is how that would quietly stop being
102
+ * true.
103
+ */
104
+ private loadManyEntries;
40
105
  exists(type: string, name: string): Promise<boolean>;
41
106
  stat(type: string, name: string): Promise<MetadataStats | null>;
107
+ /**
108
+ * [#14486] The names this loader can be asked for, and ONLY those: a file
109
+ * directly under `ROOT/TYPE/` carrying an extension one of this instance's
110
+ * REGISTERED serializers claims. Every name it reports resolves back through
111
+ * {@link findFile}, so `listNames()` and `get()` give the same answer.
112
+ *
113
+ * It used to report `path.basename(file, ext)` for every file the glob found,
114
+ * nested or not, extension or not — and {@link findFile} resolves neither
115
+ * shape. `ROOT/TYPE/crm/account.json` was listed as `account`, which resolves
116
+ * against `ROOT/TYPE/account.json` and finds nothing; an extension-less
117
+ * `ROOT/TYPE/noext` was listed as `noext`, which resolves under no appended
118
+ * extension at all. A name in the list that `get()` answers `null` for is the
119
+ * silent failure an author (human or AI) reads as their own typo, so they
120
+ * retry the same word: the list and the door now agree instead.
121
+ *
122
+ * Ruling (maintainer, via the director seat on #14486, 2026-09-02): narrow
123
+ * the list — direction A, over B (reverse-unify: report `crm/account` and
124
+ * teach `findFile()` path-shaped names), which would have made a slash inside
125
+ * a metadata name every consumer's permanent obligation with no measured
126
+ * demand for it. The two-segment layout follows ADR-0008 §10, which
127
+ * `metadata-fs`'s `parseItemPath()` already enforces for its own store; the
128
+ * EXTENSION set deliberately does NOT follow §10's `.json`-only rule — see
129
+ * {@link resolvableExtensions} for why.
130
+ */
42
131
  list(type: string): Promise<string[]>;
43
132
  save(type: string, name: string, data: any, options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
133
+ /**
134
+ * The inverse of {@link detectFormat}: which file extensions carry which
135
+ * format. Fixed ORDER, because it is also {@link findFile}'s precedence when
136
+ * two files under one type directory share a stem — registration order must
137
+ * not be able to change which file `ROOT/TYPE/NAME` opens.
138
+ */
139
+ private static readonly EXTENSIONS_BY_FORMAT;
140
+ /**
141
+ * [#14486] The extensions a name can be resolved under, for THIS instance:
142
+ * the ones belonging to the serializer set it was constructed with. Shared by
143
+ * {@link findFile}, {@link resolvableNameForPath} and therefore {@link list},
144
+ * so the set a name can be RESOLVED under cannot drift from the set that is
145
+ * LISTED or the set {@link loadManyKeyed} is willing to KEY by.
146
+ *
147
+ * Registered, not hard-coded, and deliberately not ADR-0008 §10's `.json`
148
+ * only. §10 governs the `metadata-fs` store; applying it verbatim here would
149
+ * drop `.yaml` and `.ts` metadata out of `listNames()` — a breakage this card
150
+ * never asked for. Under the manager's DEFAULT format set
151
+ * (`typescript` / `json` / `yaml`, `metadata-manager.ts`) that leaves `.js`
152
+ * out, which is the card's row-4 membership mismatch closing for free: a `.js`
153
+ * file was listed and resolvable while `loadMany()` could never return it and
154
+ * `load()` threw `No serializer found for format: javascript`. Register
155
+ * `javascript` and it is listed, resolvable and loadable together.
156
+ */
157
+ private resolvableExtensions;
158
+ /**
159
+ * The metadata name this loader reports for a file: the basename with its
160
+ * extension stripped. One derivation, shared by {@link list} and
161
+ * {@link loadManyKeyed}, so the two cannot drift for the shape where they
162
+ * agree — `dotted.config.json` is `dotted.config` for both.
163
+ */
164
+ private static nameFromFilename;
165
+ /**
166
+ * The key for a file IF this loader's name-to-path mapping is a bijection for
167
+ * it: a file directly under `ROOT/TYPE/` carrying an extension
168
+ * {@link findFile} tries, so `findFile(type, key)` resolves back to this very
169
+ * file. `null` for every other shape — a nested path, an extension-less file,
170
+ * an extension spelled in a case `findFile()` does not compose — which is why
171
+ * {@link loadManyKeyed} falls back to `body.name` there rather than minting a
172
+ * key no other door can open.
173
+ */
174
+ private resolvableNameForPath;
175
+ /**
176
+ * [#14921] The names this loader reports for `files` — and the ONE place an
177
+ * ambiguous stem is refused.
178
+ *
179
+ * Shared by {@link list} and {@link loadManyEntries} so the two can never
180
+ * disagree about which trees are admissible: a stem that `list()` refuses
181
+ * must not still be walked and returned as two bodies by `loadMany()`, which
182
+ * is exactly the split this card measured.
183
+ *
184
+ * Refuses on the FIRST colliding name in sorted order, so a tree holding more
185
+ * than one collision always names the same one — a refusal that moves
186
+ * between runs reads as flakiness rather than as the fixed authoring error it
187
+ * is. Paths are deduplicated because two overlapping `patterns` legitimately
188
+ * match one file twice, and counting that as a collision would refuse a
189
+ * perfectly good tree.
190
+ *
191
+ * ⛔ Not a precedence resolver. Picking a winner here is what the ruling
192
+ * declined (option 2, keep the precedence and log): the loser would stay
193
+ * unreachable and the listed set would stay different from the addressable
194
+ * one.
195
+ */
196
+ private resolvableNames;
44
197
  /**
45
198
  * Find file for a given type and name
46
199
  */
@@ -61,4 +214,4 @@ declare class FilesystemLoader implements MetadataLoader {
61
214
  private generateETag;
62
215
  }
63
216
 
64
- export { FilesystemLoader, MetadataLoader, MetadataManager, MetadataManagerOptions, MetadataSerializer, NodeMetadataManager };
217
+ export { FilesystemLoader, MetadataKeyedItem, MetadataLoader, MetadataManager, MetadataManagerOptions, MetadataSerializer, NodeMetadataManager };
package/dist/node.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { MetadataManager, MetadataManagerOptions, MetadataLoader, MetadataSerializer } from './index.js';
2
- export { DatabaseLoader, DatabaseLoaderOptions, HistoryCleanupManager, JSONSerializer, MemoryLoader, MetadataPlugin, Migration, RemoteLoader, SerializeOptions, TypeScriptSerializer, WatchCallback, YAMLSerializer, calculateChecksum, generateDiffSummary, generateSimpleDiff } from './index.js';
1
+ import { MetadataManager, MetadataManagerOptions, MetadataLoader, MetadataSerializer, MetadataKeyedItem } from './index.js';
2
+ export { AMBIGUOUS_METADATA_STEM_CODE, AMBIGUOUS_METADATA_STEM_STATUS, AmbiguousMetadataStemError, DatabaseLoader, DatabaseLoaderOptions, HistoryCleanupManager, JSONSerializer, MemoryLoader, MetadataPlugin, Migration, RemoteLoader, SerializeOptions, TypeScriptSerializer, WatchCallback, YAMLSerializer, calculateChecksum, generateDiffSummary, generateSimpleDiff, isAmbiguousMetadataStemError } from './index.js';
3
3
  export { HistoryOptions, MetaRef, MetadataEvent, MetadataItem, MetadataItemHeader, MetadataRepository, SysMetadataHistoryObject, SysMetadataObject, WatchFilter } from '@objectstack/metadata-core';
4
+ export { deriveViewContainerObject } from './view-container.js';
4
5
  import { MetadataLoaderContract, MetadataFormat, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult } from '@objectstack/spec/system';
5
6
  export { MetadataCollectionInfo, MetadataDiffResult, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
6
7
  export { IMetadataService, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
@@ -37,10 +38,162 @@ declare class FilesystemLoader implements MetadataLoader {
37
38
  constructor(rootDir: string, serializers: Map<MetadataFormat, MetadataSerializer>, logger?: Logger | undefined);
38
39
  load(type: string, name: string, options?: MetadataLoadOptions): Promise<MetadataLoadResult>;
39
40
  loadMany<T = any>(type: string, options?: MetadataLoadOptions): Promise<T[]>;
41
+ /**
42
+ * [#14341] The keyed half of {@link loadMany} — see {@link MetadataKeyedItem}
43
+ * for why the store's key travels BESIDE the body instead of being folded
44
+ * into it.
45
+ *
46
+ * THE RULE, in one sentence: an item is keyed by this loader's own
47
+ * name-to-path derivation — {@link nameFromFilename}, the very basename
48
+ * derivation `list()` reports — ONLY where that derivation is a bijection for
49
+ * the file (it sits directly under `ROOT/TYPE/` and carries one of the
50
+ * extensions {@link findFile} tries, so `findFile(type, key)` resolves back to
51
+ * this same file); every other shape keeps the pre-#14205 behaviour verbatim,
52
+ * keyed by `body.name` when it has one and dropped when it has none.
53
+ *
54
+ * Why the rule stops there (PM ruling on #14341, 2026-09-02, knowingly over
55
+ * triage's "a nested path keeps whatever `list()` reports for it today"):
56
+ * `list()` and `findFile()` DISAGREE outside that shape. For
57
+ * `ROOT/TYPE/crm/account.json`, `list()` reports the bare `account`, but
58
+ * `findFile()` resolves that name against `ROOT/TYPE/account.json` and finds
59
+ * nothing — the only name reaching the file is `crm/account`, which nothing
60
+ * reports. An extension-less file is read by `loadMany()` and reported by
61
+ * `list()`, and `findFile()` resolves neither. Keying by either side would
62
+ * mint a name some other door cannot open, and two directories holding the
63
+ * same basename would collide in silence
64
+ * (`MetadataManager.admitLoaderItems()` keeps the first and says nothing).
65
+ * The card's own fence: "keying items under names nothing else uses … is
66
+ * worse than today's honest drop". So the drop stays exactly where the key is
67
+ * unsettled, and is pinned as a RECORD in
68
+ * `filesystem-loader-keyed-items.test.ts`.
69
+ *
70
+ * [#14486, partial] `list()` and {@link findFile} have since converged on
71
+ * {@link resolvableNameForPath} — the derivation this method already used —
72
+ * so a nested or extension-less file is now neither listed nor resolvable.
73
+ * What did NOT change is the WALK behind this method: `loadManyEntries()`
74
+ * still READS those files, so `loadMany()` still returns their bodies and
75
+ * this method still falls back to `body.name` for them. That half of the
76
+ * #14486 ruling ("nothing unlisted is returned by `loadMany()` either") is
77
+ * deliberately NOT taken here: it would invert the three landed #14341 pins
78
+ * in `filesystem-loader-keyed-items.test.ts:113,167,187` and the
79
+ * `loadMany()` CONTROL at `:196`, and that file was under a concurrent
80
+ * claim (PR #14627) when this landed. The remaining divergence — listed ⊂
81
+ * loaded — is pinned as a RECORD in
82
+ * `filesystem-loader-list-reachability.test.ts` rather than left implicit.
83
+ *
84
+ * One consequence, deliberate: a flat file whose `body.name` DISAGREES with
85
+ * its basename is now keyed by the BASENAME. That is #14205's rule (identity
86
+ * is the key the store holds an item under, not `body.name`) applied to this
87
+ * loader, and it aligns `MetadataManager.list()` with `listNames()` for that
88
+ * shape.
89
+ *
90
+ * The body is handed back by reference, unchanged: nothing is written into a
91
+ * body that deliberately has no `name`. `limit` bounds the items LOADED,
92
+ * exactly as `loadMany()` does — an entry the key rule drops has still been
93
+ * read and still counts against it.
94
+ */
95
+ loadManyKeyed<T = any>(type: string, options?: MetadataLoadOptions): Promise<MetadataKeyedItem<T>[]>;
96
+ /**
97
+ * The single walk behind {@link loadMany} and {@link loadManyKeyed}: one glob,
98
+ * one serializer pass, one `limit`. Shared so the two can never answer with
99
+ * different bodies for the same file — {@link MetadataLoader.loadManyKeyed}
100
+ * requires `data` to be "the same body `loadMany()` would return for the
101
+ * item", and a second copy of this walk is how that would quietly stop being
102
+ * true.
103
+ */
104
+ private loadManyEntries;
40
105
  exists(type: string, name: string): Promise<boolean>;
41
106
  stat(type: string, name: string): Promise<MetadataStats | null>;
107
+ /**
108
+ * [#14486] The names this loader can be asked for, and ONLY those: a file
109
+ * directly under `ROOT/TYPE/` carrying an extension one of this instance's
110
+ * REGISTERED serializers claims. Every name it reports resolves back through
111
+ * {@link findFile}, so `listNames()` and `get()` give the same answer.
112
+ *
113
+ * It used to report `path.basename(file, ext)` for every file the glob found,
114
+ * nested or not, extension or not — and {@link findFile} resolves neither
115
+ * shape. `ROOT/TYPE/crm/account.json` was listed as `account`, which resolves
116
+ * against `ROOT/TYPE/account.json` and finds nothing; an extension-less
117
+ * `ROOT/TYPE/noext` was listed as `noext`, which resolves under no appended
118
+ * extension at all. A name in the list that `get()` answers `null` for is the
119
+ * silent failure an author (human or AI) reads as their own typo, so they
120
+ * retry the same word: the list and the door now agree instead.
121
+ *
122
+ * Ruling (maintainer, via the director seat on #14486, 2026-09-02): narrow
123
+ * the list — direction A, over B (reverse-unify: report `crm/account` and
124
+ * teach `findFile()` path-shaped names), which would have made a slash inside
125
+ * a metadata name every consumer's permanent obligation with no measured
126
+ * demand for it. The two-segment layout follows ADR-0008 §10, which
127
+ * `metadata-fs`'s `parseItemPath()` already enforces for its own store; the
128
+ * EXTENSION set deliberately does NOT follow §10's `.json`-only rule — see
129
+ * {@link resolvableExtensions} for why.
130
+ */
42
131
  list(type: string): Promise<string[]>;
43
132
  save(type: string, name: string, data: any, options?: MetadataSaveOptions): Promise<MetadataSaveResult>;
133
+ /**
134
+ * The inverse of {@link detectFormat}: which file extensions carry which
135
+ * format. Fixed ORDER, because it is also {@link findFile}'s precedence when
136
+ * two files under one type directory share a stem — registration order must
137
+ * not be able to change which file `ROOT/TYPE/NAME` opens.
138
+ */
139
+ private static readonly EXTENSIONS_BY_FORMAT;
140
+ /**
141
+ * [#14486] The extensions a name can be resolved under, for THIS instance:
142
+ * the ones belonging to the serializer set it was constructed with. Shared by
143
+ * {@link findFile}, {@link resolvableNameForPath} and therefore {@link list},
144
+ * so the set a name can be RESOLVED under cannot drift from the set that is
145
+ * LISTED or the set {@link loadManyKeyed} is willing to KEY by.
146
+ *
147
+ * Registered, not hard-coded, and deliberately not ADR-0008 §10's `.json`
148
+ * only. §10 governs the `metadata-fs` store; applying it verbatim here would
149
+ * drop `.yaml` and `.ts` metadata out of `listNames()` — a breakage this card
150
+ * never asked for. Under the manager's DEFAULT format set
151
+ * (`typescript` / `json` / `yaml`, `metadata-manager.ts`) that leaves `.js`
152
+ * out, which is the card's row-4 membership mismatch closing for free: a `.js`
153
+ * file was listed and resolvable while `loadMany()` could never return it and
154
+ * `load()` threw `No serializer found for format: javascript`. Register
155
+ * `javascript` and it is listed, resolvable and loadable together.
156
+ */
157
+ private resolvableExtensions;
158
+ /**
159
+ * The metadata name this loader reports for a file: the basename with its
160
+ * extension stripped. One derivation, shared by {@link list} and
161
+ * {@link loadManyKeyed}, so the two cannot drift for the shape where they
162
+ * agree — `dotted.config.json` is `dotted.config` for both.
163
+ */
164
+ private static nameFromFilename;
165
+ /**
166
+ * The key for a file IF this loader's name-to-path mapping is a bijection for
167
+ * it: a file directly under `ROOT/TYPE/` carrying an extension
168
+ * {@link findFile} tries, so `findFile(type, key)` resolves back to this very
169
+ * file. `null` for every other shape — a nested path, an extension-less file,
170
+ * an extension spelled in a case `findFile()` does not compose — which is why
171
+ * {@link loadManyKeyed} falls back to `body.name` there rather than minting a
172
+ * key no other door can open.
173
+ */
174
+ private resolvableNameForPath;
175
+ /**
176
+ * [#14921] The names this loader reports for `files` — and the ONE place an
177
+ * ambiguous stem is refused.
178
+ *
179
+ * Shared by {@link list} and {@link loadManyEntries} so the two can never
180
+ * disagree about which trees are admissible: a stem that `list()` refuses
181
+ * must not still be walked and returned as two bodies by `loadMany()`, which
182
+ * is exactly the split this card measured.
183
+ *
184
+ * Refuses on the FIRST colliding name in sorted order, so a tree holding more
185
+ * than one collision always names the same one — a refusal that moves
186
+ * between runs reads as flakiness rather than as the fixed authoring error it
187
+ * is. Paths are deduplicated because two overlapping `patterns` legitimately
188
+ * match one file twice, and counting that as a collision would refuse a
189
+ * perfectly good tree.
190
+ *
191
+ * ⛔ Not a precedence resolver. Picking a winner here is what the ruling
192
+ * declined (option 2, keep the precedence and log): the loser would stay
193
+ * unreachable and the listed set would stay different from the addressable
194
+ * one.
195
+ */
196
+ private resolvableNames;
44
197
  /**
45
198
  * Find file for a given type and name
46
199
  */
@@ -61,4 +214,4 @@ declare class FilesystemLoader implements MetadataLoader {
61
214
  private generateETag;
62
215
  }
63
216
 
64
- export { FilesystemLoader, MetadataLoader, MetadataManager, MetadataManagerOptions, MetadataSerializer, NodeMetadataManager };
217
+ export { FilesystemLoader, MetadataKeyedItem, MetadataLoader, MetadataManager, MetadataManagerOptions, MetadataSerializer, NodeMetadataManager };