@objectstack/metadata 17.3.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/CHANGELOG.md +532 -0
- package/README.md +6 -4
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +299 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +227 -60
- package/dist/index.d.ts +227 -60
- package/dist/index.js +294 -35
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.cjs +104 -22
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +40 -1
- package/dist/migrations/index.d.ts +40 -1
- package/dist/migrations/index.js +107 -22
- package/dist/migrations/index.js.map +1 -1
- package/dist/node.cjs +299 -36
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +24 -1
- package/dist/node.d.ts +24 -1
- package/dist/node.js +294 -35
- package/dist/node.js.map +1 -1
- package/dist/view-container.cjs +37 -0
- package/dist/view-container.cjs.map +1 -0
- package/dist/view-container.d.cts +76 -0
- package/dist/view-container.d.ts +76 -0
- package/dist/view-container.js +12 -0
- package/dist/view-container.js.map +1 -0
- package/package.json +18 -8
package/dist/index.cjs
CHANGED
|
@@ -197,6 +197,9 @@ var init_hmr_routes = __esm({
|
|
|
197
197
|
// src/index.ts
|
|
198
198
|
var index_exports = {};
|
|
199
199
|
__export(index_exports, {
|
|
200
|
+
AMBIGUOUS_METADATA_STEM_CODE: () => AMBIGUOUS_METADATA_STEM_CODE,
|
|
201
|
+
AMBIGUOUS_METADATA_STEM_STATUS: () => AMBIGUOUS_METADATA_STEM_STATUS,
|
|
202
|
+
AmbiguousMetadataStemError: () => AmbiguousMetadataStemError,
|
|
200
203
|
DatabaseLoader: () => DatabaseLoader,
|
|
201
204
|
HistoryCleanupManager: () => HistoryCleanupManager,
|
|
202
205
|
JSONSerializer: () => JSONSerializer,
|
|
@@ -212,7 +215,8 @@ __export(index_exports, {
|
|
|
212
215
|
calculateChecksum: () => calculateChecksum,
|
|
213
216
|
deriveViewContainerObject: () => deriveViewContainerObject,
|
|
214
217
|
generateDiffSummary: () => generateDiffSummary,
|
|
215
|
-
generateSimpleDiff: () => generateSimpleDiff
|
|
218
|
+
generateSimpleDiff: () => generateSimpleDiff,
|
|
219
|
+
isAmbiguousMetadataStemError: () => isAmbiguousMetadataStemError
|
|
216
220
|
});
|
|
217
221
|
module.exports = __toCommonJS(index_exports);
|
|
218
222
|
|
|
@@ -640,7 +644,7 @@ async function _columnExists(exec, table, column) {
|
|
|
640
644
|
// src/loaders/database-loader.ts
|
|
641
645
|
function canonicalIsoInstant(value) {
|
|
642
646
|
if (value === null || value === void 0) return void 0;
|
|
643
|
-
if (value instanceof Date) return value.toISOString();
|
|
647
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? void 0 : value.toISOString();
|
|
644
648
|
if (typeof value === "string") return value;
|
|
645
649
|
return String(value);
|
|
646
650
|
}
|
|
@@ -696,7 +700,7 @@ var DatabaseLoader = class {
|
|
|
696
700
|
if (cacheEnabled) {
|
|
697
701
|
const lruOpts = {
|
|
698
702
|
maxSize: cacheOpts?.maxSize ?? 500,
|
|
699
|
-
ttl: cacheOpts?.
|
|
703
|
+
ttl: cacheOpts?.ttlMs ?? 6e4
|
|
700
704
|
};
|
|
701
705
|
this.loadCache = new LRUCache(lruOpts);
|
|
702
706
|
this.loadManyCache = new LRUCache(lruOpts);
|
|
@@ -1546,6 +1550,33 @@ function generateId() {
|
|
|
1546
1550
|
return `meta_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
1547
1551
|
}
|
|
1548
1552
|
|
|
1553
|
+
// src/loaders/ambiguous-metadata-stem.ts
|
|
1554
|
+
var AMBIGUOUS_METADATA_STEM_CODE = "AMBIGUOUS_METADATA_STEM";
|
|
1555
|
+
var AMBIGUOUS_METADATA_STEM_STATUS = 500;
|
|
1556
|
+
var AMBIGUOUS_METADATA_STEM_BRAND = /* @__PURE__ */ Symbol.for("objectstack.metadata.ambiguousStem");
|
|
1557
|
+
var _a, _b;
|
|
1558
|
+
var AmbiguousMetadataStemError = class extends (_b = Error, _a = AMBIGUOUS_METADATA_STEM_BRAND, _b) {
|
|
1559
|
+
constructor(type, stem, paths) {
|
|
1560
|
+
const sorted = [...paths].sort();
|
|
1561
|
+
super(
|
|
1562
|
+
`Ambiguous metadata name \`${stem}\` for type \`${type}\`: ${sorted.length} files resolve to the same name \u2014 ${sorted.map((p) => `\`${p}\``).join(", ")}. Only the first would ever be served (extension precedence: .json, .yaml, .yml, .ts, .js), so the others are listed and unreachable. Delete or rename all but one.`
|
|
1563
|
+
);
|
|
1564
|
+
/** Brand — see the module doc on why this is not `instanceof`. */
|
|
1565
|
+
this[_a] = true;
|
|
1566
|
+
/** ADR-0112 wire code. */
|
|
1567
|
+
this.code = AMBIGUOUS_METADATA_STEM_CODE;
|
|
1568
|
+
/** HTTP status a transport should answer. */
|
|
1569
|
+
this.status = AMBIGUOUS_METADATA_STEM_STATUS;
|
|
1570
|
+
this.name = "AmbiguousMetadataStemError";
|
|
1571
|
+
this.type = type;
|
|
1572
|
+
this.stem = stem;
|
|
1573
|
+
this.paths = sorted;
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
function isAmbiguousMetadataStemError(err) {
|
|
1577
|
+
return typeof err === "object" && err !== null && err[AMBIGUOUS_METADATA_STEM_BRAND] === true;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1549
1580
|
// src/endpoint-matcher.ts
|
|
1550
1581
|
var import_api = require("@objectstack/spec/api");
|
|
1551
1582
|
|
|
@@ -1696,6 +1727,8 @@ var EndpointMatcher = class {
|
|
|
1696
1727
|
// src/view-container-expansion.ts
|
|
1697
1728
|
var import_spec2 = require("@objectstack/spec");
|
|
1698
1729
|
var import_shared2 = require("@objectstack/spec/shared");
|
|
1730
|
+
|
|
1731
|
+
// src/view-container.ts
|
|
1699
1732
|
function deriveViewContainerObject(container) {
|
|
1700
1733
|
if (!container || typeof container !== "object") return void 0;
|
|
1701
1734
|
const c = container;
|
|
@@ -1703,6 +1736,8 @@ function deriveViewContainerObject(container) {
|
|
|
1703
1736
|
const byName = typeof c.name === "string" && c.name ? c.name : void 0;
|
|
1704
1737
|
return own ?? c?.list?.data?.object ?? c?.form?.data?.object ?? byName;
|
|
1705
1738
|
}
|
|
1739
|
+
|
|
1740
|
+
// src/view-container-expansion.ts
|
|
1706
1741
|
function expandRuntimeViewContainer(data) {
|
|
1707
1742
|
if (!(0, import_spec2.isAggregatedViewContainer)(data)) return [];
|
|
1708
1743
|
const container = data;
|
|
@@ -2408,6 +2443,9 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2408
2443
|
await this.admitLoaderItems(loader, type, items);
|
|
2409
2444
|
this.reportLoaderReadRecovered(loader.contract.name);
|
|
2410
2445
|
} catch (e) {
|
|
2446
|
+
if (isAmbiguousMetadataStemError(e)) {
|
|
2447
|
+
throw e;
|
|
2448
|
+
}
|
|
2411
2449
|
degraded = true;
|
|
2412
2450
|
errors.push(`${loader.contract.name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2413
2451
|
this.reportLoaderReadFailure(loader.contract.name, type, e);
|
|
@@ -2745,6 +2783,30 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2745
2783
|
}
|
|
2746
2784
|
/**
|
|
2747
2785
|
* List all names of metadata items of a given type
|
|
2786
|
+
*
|
|
2787
|
+
* ## [#14423] One loader's fault does not take the whole enumeration down
|
|
2788
|
+
*
|
|
2789
|
+
* This loop used to be bare — `const result = await loader.list(type)` with
|
|
2790
|
+
* no `try`, while the two sibling plural reads (`list()` via
|
|
2791
|
+
* {@link admitLoaderItems}, and {@link loadMany}) have carried a per-loader
|
|
2792
|
+
* `catch` since #5108. That asymmetry is the defect, independent of any one
|
|
2793
|
+
* caller: the SAME storage outage was swallowed by one plural read and
|
|
2794
|
+
* thrown out of the other, so which answer a caller got depended only on
|
|
2795
|
+
* which method it happened to call. A caller reading both — the action
|
|
2796
|
+
* governance audit is one — saw `loadMany` report a short-but-successful
|
|
2797
|
+
* set and `listNames` throw, and had no way to tell that one fact was
|
|
2798
|
+
* behind both.
|
|
2799
|
+
*
|
|
2800
|
+
* Same shape as `loadMany`'s, deliberately, down to the helpers: the outage
|
|
2801
|
+
* is spoken once per loader through {@link reportLoaderReadFailure} and
|
|
2802
|
+
* un-said through {@link reportLoaderReadRecovered}. ⛔ Not a third spelling
|
|
2803
|
+
* for "a loader faulted" — a second vocabulary for one event is how the two
|
|
2804
|
+
* reads drifted apart in the first place.
|
|
2805
|
+
*
|
|
2806
|
+
* The degradation is the same one `list()` documents and is graded the same
|
|
2807
|
+
* way (AGENTS.md → "Degradation log levels"): the caller still gets an
|
|
2808
|
+
* array, nothing 500s, and the set is quietly short — so it is reported at
|
|
2809
|
+
* `error`, by the shared helper, rather than being re-graded here.
|
|
2748
2810
|
*/
|
|
2749
2811
|
async listNames(type) {
|
|
2750
2812
|
type = (0, import_core.canonicalMetadataServiceType)(type);
|
|
@@ -2756,8 +2818,16 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2756
2818
|
}
|
|
2757
2819
|
}
|
|
2758
2820
|
for (const loader of this.loaders.values()) {
|
|
2759
|
-
|
|
2760
|
-
|
|
2821
|
+
try {
|
|
2822
|
+
const result = await loader.list(type);
|
|
2823
|
+
result.forEach((item) => names.add(item));
|
|
2824
|
+
this.reportLoaderReadRecovered(loader.contract.name);
|
|
2825
|
+
} catch (e) {
|
|
2826
|
+
if (isAmbiguousMetadataStemError(e)) {
|
|
2827
|
+
throw e;
|
|
2828
|
+
}
|
|
2829
|
+
this.reportLoaderReadFailure(loader.contract.name, type, e);
|
|
2830
|
+
}
|
|
2761
2831
|
}
|
|
2762
2832
|
return Array.from(names);
|
|
2763
2833
|
}
|
|
@@ -3617,6 +3687,99 @@ var _MetadataManager = class _MetadataManager {
|
|
|
3617
3687
|
}
|
|
3618
3688
|
return results;
|
|
3619
3689
|
}
|
|
3690
|
+
/**
|
|
3691
|
+
* [#14423] {@link loadMany}, read under the identity the STORE holds each
|
|
3692
|
+
* item by — the keyed plural read, beside the unkeyed one.
|
|
3693
|
+
*
|
|
3694
|
+
* ## Why a second method and not a widened `loadMany`
|
|
3695
|
+
*
|
|
3696
|
+
* `loadMany` keys nothing: it returns bodies, and every consumer that needs
|
|
3697
|
+
* an identity reads `body.name` off them. #14205 already ruled what identity
|
|
3698
|
+
* IS — the key the store holds the item under (`register(type, name, data)`
|
|
3699
|
+
* takes it as the ARGUMENT, and a body is not required to name itself) — so
|
|
3700
|
+
* `body.name` is a guess that happens to be right for most items and drops
|
|
3701
|
+
* the rest ENTIRELY: an item whose body carries no `name` is served by
|
|
3702
|
+
* `load(type, name)` and is not nameable from `loadMany`'s answer at all.
|
|
3703
|
+
*
|
|
3704
|
+
* Widening `loadMany`'s return would fix that and break every consumer of a
|
|
3705
|
+
* published shape (the ones counted on this card all read `body.name` as the
|
|
3706
|
+
* identity). So this is additive: `loadMany`'s return shape is untouched,
|
|
3707
|
+
* and a caller that needs the key asks for the key.
|
|
3708
|
+
*
|
|
3709
|
+
* ## What it reads — the same population `loadMany` reads
|
|
3710
|
+
*
|
|
3711
|
+
* Loaders only, deliberately, so this is `loadMany` keyed and nothing more.
|
|
3712
|
+
* It is NOT `list()`/{@link listNames}, which also merge the in-memory
|
|
3713
|
+
* `register()` registry; a caller wanting that set has those. Reading the
|
|
3714
|
+
* loaders alone is also what makes this the enumerable twin of
|
|
3715
|
+
* {@link loadDiagnosed}, which walks the same loaders by name — that pairing
|
|
3716
|
+
* is the point on the audit side of #14423, where an enumeration and a
|
|
3717
|
+
* by-name read that disagree about a population make one subsystem accuse
|
|
3718
|
+
* another of a defect neither has.
|
|
3719
|
+
*
|
|
3720
|
+
* ## Delegate first, fall back second — and why that order is not a style
|
|
3721
|
+
*
|
|
3722
|
+
* Per loader: {@link MetadataLoader.loadManyKeyed} where the loader offers
|
|
3723
|
+
* one, else its `list()` + a per-name `load()`. Measured, on
|
|
3724
|
+
* `DatabaseLoader`: the keyed method shares `loadMany`'s single query
|
|
3725
|
+
* (`{find:1, findOne:0}` — zero extra cost), while enumerate-then-read-each
|
|
3726
|
+
* on that same loader is a real N+1 (`{find:1, findOne:5}` for five items).
|
|
3727
|
+
* The fallback exists for loaders that cannot produce keys at all
|
|
3728
|
+
* (`RemoteLoader`'s wire format carries bodies only), and it recovers the
|
|
3729
|
+
* nameless item the pre-#14205 `loadMany`-and-key-by-`body.name` fallback
|
|
3730
|
+
* drops — which is why it is `list()` + `load()` and not `loadMany()`.
|
|
3731
|
+
*
|
|
3732
|
+
* ## Failure posture
|
|
3733
|
+
*
|
|
3734
|
+
* Per-loader `try`/`catch`, the same seam and the same helpers as
|
|
3735
|
+
* {@link loadMany} and `list()` — one loader's outage does not take the
|
|
3736
|
+
* enumeration down, and it is reported once through
|
|
3737
|
+
* {@link reportLoaderReadFailure} rather than in a third vocabulary.
|
|
3738
|
+
* Earlier loaders win a key collision, mirroring `list()`.
|
|
3739
|
+
*/
|
|
3740
|
+
async loadManyKeyed(type, options) {
|
|
3741
|
+
const items = /* @__PURE__ */ new Map();
|
|
3742
|
+
for (const loader of this.loaders.values()) {
|
|
3743
|
+
try {
|
|
3744
|
+
await this.admitKeyedLoaderItems(loader, type, items, options);
|
|
3745
|
+
this.reportLoaderReadRecovered(loader.contract.name);
|
|
3746
|
+
} catch (e) {
|
|
3747
|
+
this.reportLoaderReadFailure(loader.contract.name, type, e);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
return Array.from(items, ([name, data]) => ({ name, data }));
|
|
3751
|
+
}
|
|
3752
|
+
/**
|
|
3753
|
+
* Merge ONE loader's answer for `type` into `items`, keyed by that loader's
|
|
3754
|
+
* own key for each item — {@link loadManyKeyed}'s per-loader body.
|
|
3755
|
+
*
|
|
3756
|
+
* Distinct from {@link admitLoaderItems} on exactly one axis, and that axis
|
|
3757
|
+
* is the whole of #14423: the fallback for a loader with no
|
|
3758
|
+
* `loadManyKeyed`. `admitLoaderItems` falls back to `loadMany` keyed by
|
|
3759
|
+
* `data.name` — the pre-#14205 behaviour, verbatim, which drops a nameless
|
|
3760
|
+
* body. Here the fallback is `list()` + a per-name `load()`, so a loader
|
|
3761
|
+
* that cannot enumerate keys and bodies together still answers with both.
|
|
3762
|
+
*
|
|
3763
|
+
* Read failures are NOT caught here — the caller owns that verdict, as in
|
|
3764
|
+
* {@link admitLoaderItems}.
|
|
3765
|
+
*/
|
|
3766
|
+
async admitKeyedLoaderItems(loader, type, items, options) {
|
|
3767
|
+
if (typeof loader.loadManyKeyed === "function") {
|
|
3768
|
+
const keyed = await loader.loadManyKeyed(type, options);
|
|
3769
|
+
for (const entry of keyed) {
|
|
3770
|
+
if (!entry || typeof entry.name !== "string" || entry.name === "") continue;
|
|
3771
|
+
if (items.has(entry.name)) continue;
|
|
3772
|
+
items.set(entry.name, entry.data);
|
|
3773
|
+
}
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
for (const name of await loader.list(type)) {
|
|
3777
|
+
if (typeof name !== "string" || name === "" || items.has(name)) continue;
|
|
3778
|
+
const result = await loader.load(type, name, options);
|
|
3779
|
+
if (result?.data == null) continue;
|
|
3780
|
+
items.set(name, result.data);
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3620
3783
|
/**
|
|
3621
3784
|
* Save metadata item to a loader
|
|
3622
3785
|
*/
|
|
@@ -4249,33 +4412,34 @@ var _FilesystemLoader = class _FilesystemLoader {
|
|
|
4249
4412
|
const globPatterns = patterns.map(
|
|
4250
4413
|
(pattern) => path.join(typeDir, pattern)
|
|
4251
4414
|
);
|
|
4415
|
+
const files = [];
|
|
4252
4416
|
for (const pattern of globPatterns) {
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
const content = await fs.readFile(file, "utf-8");
|
|
4263
|
-
const format = this.detectFormat(file);
|
|
4264
|
-
const serializer = this.getSerializer(format);
|
|
4265
|
-
if (serializer) {
|
|
4266
|
-
const data = serializer.deserialize(content);
|
|
4267
|
-
items.push({ file, data });
|
|
4268
|
-
}
|
|
4269
|
-
} catch (error) {
|
|
4270
|
-
this.logger?.warn("Failed to load file", {
|
|
4271
|
-
file,
|
|
4272
|
-
error: error instanceof Error ? error.message : String(error)
|
|
4273
|
-
});
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
4417
|
+
files.push(
|
|
4418
|
+
...await (0, import_glob.glob)(pattern, {
|
|
4419
|
+
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*", "**/*[*]*"],
|
|
4420
|
+
nodir: true
|
|
4421
|
+
})
|
|
4422
|
+
);
|
|
4423
|
+
}
|
|
4424
|
+
this.resolvableNames(type, typeDir, files);
|
|
4425
|
+
for (const file of files) {
|
|
4276
4426
|
if (limit && items.length >= limit) {
|
|
4277
4427
|
break;
|
|
4278
4428
|
}
|
|
4429
|
+
try {
|
|
4430
|
+
const content = await fs.readFile(file, "utf-8");
|
|
4431
|
+
const format = this.detectFormat(file);
|
|
4432
|
+
const serializer = this.getSerializer(format);
|
|
4433
|
+
if (serializer) {
|
|
4434
|
+
const data = serializer.deserialize(content);
|
|
4435
|
+
items.push({ file, data });
|
|
4436
|
+
}
|
|
4437
|
+
} catch (error) {
|
|
4438
|
+
this.logger?.warn("Failed to load file", {
|
|
4439
|
+
file,
|
|
4440
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4441
|
+
});
|
|
4442
|
+
}
|
|
4279
4443
|
}
|
|
4280
4444
|
return items;
|
|
4281
4445
|
} catch (error) {
|
|
@@ -4344,13 +4508,13 @@ var _FilesystemLoader = class _FilesystemLoader {
|
|
|
4344
4508
|
*/
|
|
4345
4509
|
async list(type) {
|
|
4346
4510
|
const typeDir = path.join(this.rootDir, type);
|
|
4511
|
+
let files;
|
|
4347
4512
|
try {
|
|
4348
|
-
|
|
4513
|
+
files = await (0, import_glob.glob)("**/*", {
|
|
4349
4514
|
cwd: typeDir,
|
|
4350
4515
|
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
|
|
4351
4516
|
nodir: true
|
|
4352
4517
|
});
|
|
4353
|
-
return files.map((file) => this.resolvableNameForPath(typeDir, path.join(typeDir, file))).filter((name) => name !== null);
|
|
4354
4518
|
} catch (error) {
|
|
4355
4519
|
this.logger?.error("Failed to list", void 0, {
|
|
4356
4520
|
type,
|
|
@@ -4358,6 +4522,7 @@ var _FilesystemLoader = class _FilesystemLoader {
|
|
|
4358
4522
|
});
|
|
4359
4523
|
return [];
|
|
4360
4524
|
}
|
|
4525
|
+
return this.resolvableNames(type, typeDir, files.map((file) => path.join(typeDir, file)));
|
|
4361
4526
|
}
|
|
4362
4527
|
async save(type, name, data, options) {
|
|
4363
4528
|
const startTime = Date.now();
|
|
@@ -4482,6 +4647,49 @@ var _FilesystemLoader = class _FilesystemLoader {
|
|
|
4482
4647
|
}
|
|
4483
4648
|
return _FilesystemLoader.nameFromFilename(rel);
|
|
4484
4649
|
}
|
|
4650
|
+
/**
|
|
4651
|
+
* [#14921] The names this loader reports for `files` — and the ONE place an
|
|
4652
|
+
* ambiguous stem is refused.
|
|
4653
|
+
*
|
|
4654
|
+
* Shared by {@link list} and {@link loadManyEntries} so the two can never
|
|
4655
|
+
* disagree about which trees are admissible: a stem that `list()` refuses
|
|
4656
|
+
* must not still be walked and returned as two bodies by `loadMany()`, which
|
|
4657
|
+
* is exactly the split this card measured.
|
|
4658
|
+
*
|
|
4659
|
+
* Refuses on the FIRST colliding name in sorted order, so a tree holding more
|
|
4660
|
+
* than one collision always names the same one — a refusal that moves
|
|
4661
|
+
* between runs reads as flakiness rather than as the fixed authoring error it
|
|
4662
|
+
* is. Paths are deduplicated because two overlapping `patterns` legitimately
|
|
4663
|
+
* match one file twice, and counting that as a collision would refuse a
|
|
4664
|
+
* perfectly good tree.
|
|
4665
|
+
*
|
|
4666
|
+
* ⛔ Not a precedence resolver. Picking a winner here is what the ruling
|
|
4667
|
+
* declined (option 2, keep the precedence and log): the loser would stay
|
|
4668
|
+
* unreachable and the listed set would stay different from the addressable
|
|
4669
|
+
* one.
|
|
4670
|
+
*/
|
|
4671
|
+
resolvableNames(type, typeDir, files) {
|
|
4672
|
+
const byName = /* @__PURE__ */ new Map();
|
|
4673
|
+
for (const file of files) {
|
|
4674
|
+
const name = this.resolvableNameForPath(typeDir, file);
|
|
4675
|
+
if (name === null) {
|
|
4676
|
+
continue;
|
|
4677
|
+
}
|
|
4678
|
+
let paths = byName.get(name);
|
|
4679
|
+
if (!paths) {
|
|
4680
|
+
paths = /* @__PURE__ */ new Set();
|
|
4681
|
+
byName.set(name, paths);
|
|
4682
|
+
}
|
|
4683
|
+
paths.add(file);
|
|
4684
|
+
}
|
|
4685
|
+
for (const name of [...byName.keys()].sort()) {
|
|
4686
|
+
const paths = byName.get(name);
|
|
4687
|
+
if (paths.size > 1) {
|
|
4688
|
+
throw new AmbiguousMetadataStemError(type, name, [...paths]);
|
|
4689
|
+
}
|
|
4690
|
+
}
|
|
4691
|
+
return [...byName.keys()];
|
|
4692
|
+
}
|
|
4485
4693
|
/**
|
|
4486
4694
|
* Find file for a given type and name
|
|
4487
4695
|
*/
|
|
@@ -5569,9 +5777,43 @@ var RemoteLoader = class {
|
|
|
5569
5777
|
format: "json"
|
|
5570
5778
|
};
|
|
5571
5779
|
}
|
|
5780
|
+
/**
|
|
5781
|
+
* [#15037] Report only the names that ARE names.
|
|
5782
|
+
*
|
|
5783
|
+
* This read used to be `loadMany<{ name: string }>(type)` mapped straight to
|
|
5784
|
+
* `items.map(i => i.name)`. That type argument is an ASSERTION about bodies
|
|
5785
|
+
* that arrived over HTTP, and nothing checked it: a body with no top-level
|
|
5786
|
+
* `name` yielded `undefined`, which went into an array this signature
|
|
5787
|
+
* declares as `string[]` and reached consumers through
|
|
5788
|
+
* `MetadataManager.listNames()` — a runtime violation of a declared type,
|
|
5789
|
+
* not an untidy entry. A consumer that keys by it, lower-cases it, or feeds
|
|
5790
|
+
* it back to a by-name `load()` gets `undefined` where the type says it
|
|
5791
|
+
* cannot be.
|
|
5792
|
+
*
|
|
5793
|
+
* The guard is `DatabaseLoader.list()`'s, one file away: same cast-then-map
|
|
5794
|
+
* spelling, one `typeof` filter behind it. Silently dropping is the landed
|
|
5795
|
+
* direction, not a preference — `DatabaseLoader` drops rather than throws,
|
|
5796
|
+
* and `FilesystemLoader`'s narrowing carries a maintainer ruling (via the
|
|
5797
|
+
* director seat on #14486, 2026-09-02) that chose narrowing (A) over
|
|
5798
|
+
* refusing loudly (B), because a name in the list that the door answers
|
|
5799
|
+
* `null` for is the silent failure an author reads as their own typo. An
|
|
5800
|
+
* `undefined` here is the extreme form of that name.
|
|
5801
|
+
*
|
|
5802
|
+
* ⛔ NOT copied from the siblings: `MemoryLoader` answers with its store
|
|
5803
|
+
* keys, and #14205 ruled that identity is the key the store holds an item
|
|
5804
|
+
* under rather than `body.name`. This loader reads over HTTP and holds no
|
|
5805
|
+
* store key, so `body.name` is the only identity it has — the list is
|
|
5806
|
+
* narrowed to agree with the door instead. `loadMany()` is deliberately
|
|
5807
|
+
* untouched: it keys nothing, so a nameless body is still served there.
|
|
5808
|
+
*
|
|
5809
|
+
* The predicate is spelled as a type guard, and the mapped element type left
|
|
5810
|
+
* `unknown`, so `tsc` PROVES the declared `string[]` instead of a cast
|
|
5811
|
+
* asserting it — otherwise the compiler reads the filter as always-true and
|
|
5812
|
+
* a later reader deletes it as dead.
|
|
5813
|
+
*/
|
|
5572
5814
|
async list(type) {
|
|
5573
5815
|
const items = await this.loadMany(type);
|
|
5574
|
-
return items.map((
|
|
5816
|
+
return items.map((item) => item.name).filter((name) => typeof name === "string");
|
|
5575
5817
|
}
|
|
5576
5818
|
async save(type, name, data, _options) {
|
|
5577
5819
|
const response = await fetch(`${this.baseUrl}/${type}/${name}`, {
|
|
@@ -5611,9 +5853,9 @@ var HistoryCleanupManager = class {
|
|
|
5611
5853
|
return;
|
|
5612
5854
|
}
|
|
5613
5855
|
const intervalMs = (this.policy.cleanupIntervalHours ?? 24) * 60 * 60 * 1e3;
|
|
5614
|
-
void this
|
|
5856
|
+
void runCleanupAndReport(this);
|
|
5615
5857
|
this.cleanupTimer = setInterval(() => {
|
|
5616
|
-
void this
|
|
5858
|
+
void runCleanupAndReport(this);
|
|
5617
5859
|
}, intervalMs);
|
|
5618
5860
|
}
|
|
5619
5861
|
/**
|
|
@@ -5640,7 +5882,7 @@ var HistoryCleanupManager = class {
|
|
|
5640
5882
|
try {
|
|
5641
5883
|
if (this.policy.maxAgeDays) {
|
|
5642
5884
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
5643
|
-
cutoffDate.
|
|
5885
|
+
cutoffDate.setUTCDate(cutoffDate.getUTCDate() - this.policy.maxAgeDays);
|
|
5644
5886
|
const cutoffISO = cutoffDate.toISOString();
|
|
5645
5887
|
const filter = {
|
|
5646
5888
|
recorded_at: { $lt: cutoffISO }
|
|
@@ -5760,7 +6002,7 @@ var HistoryCleanupManager = class {
|
|
|
5760
6002
|
if (organizationId) baseWhere.organization_id = organizationId;
|
|
5761
6003
|
if (this.policy.maxAgeDays) {
|
|
5762
6004
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
5763
|
-
cutoffDate.
|
|
6005
|
+
cutoffDate.setUTCDate(cutoffDate.getUTCDate() - this.policy.maxAgeDays);
|
|
5764
6006
|
const cutoffISO = cutoffDate.toISOString();
|
|
5765
6007
|
const filter = {
|
|
5766
6008
|
recorded_at: { $lt: cutoffISO },
|
|
@@ -5807,6 +6049,23 @@ var HistoryCleanupManager = class {
|
|
|
5807
6049
|
};
|
|
5808
6050
|
}
|
|
5809
6051
|
};
|
|
6052
|
+
async function runCleanupAndReport(manager) {
|
|
6053
|
+
let outcome;
|
|
6054
|
+
try {
|
|
6055
|
+
outcome = await manager.runCleanup();
|
|
6056
|
+
} catch (error) {
|
|
6057
|
+
console.error(
|
|
6058
|
+
"History cleanup: the run did not complete, so no history row past the retention policy was deleted and the table keeps growing while the system reports healthy. Fix: the cause below comes from the configured data driver, not from the retention policy; call `runCleanup()` directly to reproduce it. Cause:",
|
|
6059
|
+
error
|
|
6060
|
+
);
|
|
6061
|
+
return;
|
|
6062
|
+
}
|
|
6063
|
+
if (outcome.errors > 0) {
|
|
6064
|
+
console.error(
|
|
6065
|
+
`History cleanup: ${outcome.errors} delete operation(s) failed and ${outcome.deleted} row(s) were deleted. The history rows those deletes were meant to remove are still in the table, nothing retries them, and the table grows past the retention policy while the system keeps reporting healthy. Fix: check the data driver delete path for the metadata history table. The per-failure causes are not carried out of \`runCleanup()\`, so reproduce them against the driver directly.`
|
|
6066
|
+
);
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
5810
6069
|
|
|
5811
6070
|
// src/migration/index.ts
|
|
5812
6071
|
var migration_exports = {};
|
|
@@ -5865,6 +6124,9 @@ var MigrationExecutor = class {
|
|
|
5865
6124
|
};
|
|
5866
6125
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5867
6126
|
0 && (module.exports = {
|
|
6127
|
+
AMBIGUOUS_METADATA_STEM_CODE,
|
|
6128
|
+
AMBIGUOUS_METADATA_STEM_STATUS,
|
|
6129
|
+
AmbiguousMetadataStemError,
|
|
5868
6130
|
DatabaseLoader,
|
|
5869
6131
|
HistoryCleanupManager,
|
|
5870
6132
|
JSONSerializer,
|
|
@@ -5880,6 +6142,7 @@ var MigrationExecutor = class {
|
|
|
5880
6142
|
calculateChecksum,
|
|
5881
6143
|
deriveViewContainerObject,
|
|
5882
6144
|
generateDiffSummary,
|
|
5883
|
-
generateSimpleDiff
|
|
6145
|
+
generateSimpleDiff,
|
|
6146
|
+
isAmbiguousMetadataStemError
|
|
5884
6147
|
});
|
|
5885
6148
|
//# sourceMappingURL=index.cjs.map
|