@objectstack/metadata 17.1.0 → 17.3.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 +1541 -0
- package/dist/errors.cjs +1 -85
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +1 -22
- package/dist/errors.d.ts +1 -22
- package/dist/errors.js +2 -84
- package/dist/errors.js.map +1 -1
- package/dist/index.cjs +752 -272
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +360 -28
- package/dist/index.d.ts +360 -28
- package/dist/index.js +756 -270
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.cjs +72 -46
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +57 -14
- package/dist/migrations/index.d.ts +57 -14
- package/dist/migrations/index.js +72 -46
- package/dist/migrations/index.js.map +1 -1
- package/dist/node.cjs +752 -272
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +133 -3
- package/dist/node.d.ts +133 -3
- package/dist/node.js +756 -270
- package/dist/node.js.map +1 -1
- package/package.json +42 -21
package/dist/index.cjs
CHANGED
|
@@ -38,9 +38,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
38
38
|
// src/routes/hmr-routes.ts
|
|
39
39
|
var hmr_routes_exports = {};
|
|
40
40
|
__export(hmr_routes_exports, {
|
|
41
|
+
isDevMetadataEndpointEnabled: () => isDevMetadataEndpointEnabled,
|
|
41
42
|
registerMetadataHmrRoutes: () => registerMetadataHmrRoutes
|
|
42
43
|
});
|
|
44
|
+
function isDevMetadataEndpointEnabled(env = process.env) {
|
|
45
|
+
return (env.NODE_ENV ?? "").trim().toLowerCase() === "development";
|
|
46
|
+
}
|
|
43
47
|
function registerMetadataHmrRoutes(app, manager, options = {}) {
|
|
48
|
+
if (!isDevMetadataEndpointEnabled()) return null;
|
|
44
49
|
const routePath = options.path ?? "/api/v1/dev/metadata-events";
|
|
45
50
|
const listeners = /* @__PURE__ */ new Set();
|
|
46
51
|
const broadcast = (evt) => {
|
|
@@ -200,11 +205,12 @@ __export(index_exports, {
|
|
|
200
205
|
MetadataPlugin: () => MetadataPlugin,
|
|
201
206
|
Migration: () => migration_exports,
|
|
202
207
|
RemoteLoader: () => RemoteLoader,
|
|
203
|
-
SysMetadataHistoryObject: () =>
|
|
204
|
-
SysMetadataObject: () =>
|
|
208
|
+
SysMetadataHistoryObject: () => import_metadata_core4.SysMetadataHistoryObject,
|
|
209
|
+
SysMetadataObject: () => import_metadata_core4.SysMetadataObject,
|
|
205
210
|
TypeScriptSerializer: () => TypeScriptSerializer,
|
|
206
211
|
YAMLSerializer: () => YAMLSerializer,
|
|
207
212
|
calculateChecksum: () => calculateChecksum,
|
|
213
|
+
deriveViewContainerObject: () => deriveViewContainerObject,
|
|
208
214
|
generateDiffSummary: () => generateDiffSummary,
|
|
209
215
|
generateSimpleDiff: () => generateSimpleDiff
|
|
210
216
|
});
|
|
@@ -384,7 +390,7 @@ export default metadata;
|
|
|
384
390
|
};
|
|
385
391
|
|
|
386
392
|
// src/loaders/database-loader.ts
|
|
387
|
-
var
|
|
393
|
+
var import_metadata_core2 = require("@objectstack/metadata-core");
|
|
388
394
|
var import_spec = require("@objectstack/spec");
|
|
389
395
|
var import_shared = require("@objectstack/spec/shared");
|
|
390
396
|
|
|
@@ -552,141 +558,49 @@ var LRUCache = class {
|
|
|
552
558
|
}
|
|
553
559
|
};
|
|
554
560
|
|
|
555
|
-
// src/
|
|
561
|
+
// src/loaders/database-loader.ts
|
|
556
562
|
var import_types = require("@objectstack/types");
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
"42710",
|
|
565
|
-
// duplicate_object — index / constraint already exists
|
|
566
|
-
// MySQL / MariaDB (mysql2 puts the symbolic name on `code`)
|
|
567
|
-
"ER_TABLE_EXISTS_ERROR",
|
|
568
|
-
// 1050
|
|
569
|
-
"ER_DUP_FIELDNAME",
|
|
570
|
-
// 1060
|
|
571
|
-
"ER_DUP_KEYNAME"
|
|
572
|
-
// 1061
|
|
573
|
-
]),
|
|
574
|
-
errnos: /* @__PURE__ */ new Set([1050, 1060, 1061]),
|
|
575
|
-
/**
|
|
576
|
-
* Message fallback for drivers that carry no machine-readable code —
|
|
577
|
-
* notably SQLite, whose `code` is the undifferentiated `SQLITE_ERROR` for
|
|
578
|
-
* every DDL failure, so the message is the only signal available:
|
|
579
|
-
* - `table sys_metadata already exists`
|
|
580
|
-
* - `duplicate column name: environment_id`
|
|
581
|
-
* - `index idx_x already exists`
|
|
582
|
-
* Postgres phrases its own as `relation "x" already exists` /
|
|
583
|
-
* `column "x" of relation "y" already exists`, which matches the same test.
|
|
584
|
-
*/
|
|
585
|
-
message: /already exists|duplicate column name|duplicate key name/i
|
|
586
|
-
};
|
|
587
|
-
var MISSING_TABLE = {
|
|
588
|
-
codes: /* @__PURE__ */ new Set([
|
|
589
|
-
"42P01",
|
|
590
|
-
// PostgreSQL undefined_table
|
|
591
|
-
"ER_NO_SUCH_TABLE"
|
|
592
|
-
// MySQL / MariaDB 1146
|
|
593
|
-
]),
|
|
594
|
-
errnos: /* @__PURE__ */ new Set([1146]),
|
|
595
|
-
/**
|
|
596
|
-
* - SQLite / libsql: `no such table: sys_metadata_history`
|
|
597
|
-
* - PostgreSQL: `relation "sys_metadata_history" does not exist`
|
|
598
|
-
* - MySQL/MariaDB: `Table 'app.sys_metadata_history' doesn't exist`
|
|
599
|
-
*/
|
|
600
|
-
message: /no such table|relation ["'`][^"'`]+["'`] does not exist|table ["'`][^"'`]+["'`] doesn'?t exist|unknown table/i,
|
|
601
|
-
excludes: {
|
|
602
|
-
/**
|
|
603
|
-
* Exactly the three SQLSTATEs the docblock above already names as
|
|
604
|
-
* must-stay-loud neighbours of `does not exist`. They are listed here
|
|
605
|
-
* rather than merely trusted to miss the message test, because two of
|
|
606
|
-
* them (42703 columns, 42704 constraints/triggers) have a phrasing that
|
|
607
|
-
* *does* hit it, and because a code is a fact where prose is a guess.
|
|
608
|
-
*
|
|
609
|
-
* Postgres-shaped on purpose: measured, neither MySQL
|
|
610
|
-
* (`Unknown column 'label' in 'field list'`) nor SQLite
|
|
611
|
-
* (`no such column: bogus`, `table t has no column named label`)
|
|
612
|
-
* phrases a sub-object failure so that a missing-table phrase falls out
|
|
613
|
-
* of it, so there is nothing there to exclude. Adding their codes would
|
|
614
|
-
* be surface with no defect behind it.
|
|
615
|
-
*/
|
|
616
|
-
codes: /* @__PURE__ */ new Set([
|
|
617
|
-
"42703",
|
|
618
|
-
// undefined_column
|
|
619
|
-
"42704",
|
|
620
|
-
// undefined_object — constraint, trigger, role, type, …
|
|
621
|
-
"3D000"
|
|
622
|
-
// invalid_catalog_name — `database "x" does not exist`
|
|
623
|
-
]),
|
|
624
|
-
/**
|
|
625
|
-
* `«sub-object» "x" of relation "y" …` — Postgres' phrasing for a
|
|
626
|
-
* failure about something *inside* a relation, which therefore says the
|
|
627
|
-
* relation itself is present. The two in-repo siblings that carry this
|
|
628
|
-
* phrase are `mapDataError` (`packages/rest`, #5352) and
|
|
629
|
-
* `service-analytics`'s missing-column subtraction (#6035/PR #6346).
|
|
630
|
-
*
|
|
631
|
-
* [#6615] All three now read one home — `@objectstack/types` — instead
|
|
632
|
-
* of three hand-kept copies, so the phrase can no longer be taught to
|
|
633
|
-
* the repo a fourth time or drift in one package only. The **width**
|
|
634
|
-
* difference that used to justify the copy is preserved and is the
|
|
635
|
-
* reason the home exports two functions rather than one: those two
|
|
636
|
-
* *extract* the column name to phrase a better error, so a miss costs a
|
|
637
|
-
* vaguer message; this one *excludes*, so a miss restores the
|
|
638
|
-
* corruption. {@link isRelationSubObjectPhrase} is therefore the wider
|
|
639
|
-
* question — it drops their `column`/`[a-z0-9_]+`/`does not exist`
|
|
640
|
-
* anchors: any sub-object, any quoted identifier, any verdict.
|
|
641
|
-
* Over-matching here only ever converts a benign verdict into a loud
|
|
642
|
-
* one, which is the direction this whole module already errs in.
|
|
643
|
-
*/
|
|
644
|
-
matchesMessage: import_types.isRelationSubObjectPhrase
|
|
563
|
+
|
|
564
|
+
// src/migrations/driver-exec.ts
|
|
565
|
+
function resolveDriverExec(driver) {
|
|
566
|
+
const candidate = driver;
|
|
567
|
+
if (!candidate) return void 0;
|
|
568
|
+
if (typeof candidate.execute === "function") {
|
|
569
|
+
return (sql, bindings) => candidate.execute(sql, bindings ? [...bindings] : []);
|
|
645
570
|
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (typeof error === "string") {
|
|
651
|
-
if (signature.excludes?.matchesMessage(error)) return false;
|
|
652
|
-
return signature.message.test(error);
|
|
653
|
-
}
|
|
654
|
-
if (typeof error !== "object") return false;
|
|
655
|
-
const err = error;
|
|
656
|
-
const excludes = signature.excludes;
|
|
657
|
-
if (excludes) {
|
|
658
|
-
if (typeof err.code === "string" && excludes.codes.has(err.code)) return false;
|
|
659
|
-
if (typeof err.message === "string" && excludes.matchesMessage(err.message)) return false;
|
|
660
|
-
}
|
|
661
|
-
if (typeof err.code === "string" && signature.codes.has(err.code)) return true;
|
|
662
|
-
if (typeof err.errno === "number" && signature.errnos.has(err.errno)) return true;
|
|
663
|
-
if (typeof err.message === "string" && signature.message.test(err.message)) return true;
|
|
664
|
-
return matchesDriverError(err.cause, signature, depth + 1);
|
|
665
|
-
}
|
|
666
|
-
function isSchemaAlreadyExistsError(error, depth = 0) {
|
|
667
|
-
return matchesDriverError(error, ALREADY_EXISTS, depth);
|
|
571
|
+
if (typeof candidate.raw === "function") {
|
|
572
|
+
return (sql, bindings) => candidate.raw(sql, bindings ? [...bindings] : []);
|
|
573
|
+
}
|
|
574
|
+
return void 0;
|
|
668
575
|
}
|
|
669
|
-
function
|
|
670
|
-
return
|
|
576
|
+
function driverExecRefusal(helper) {
|
|
577
|
+
return `${helper}: driver must expose an .execute(sql, bindings?) or .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) exposes .execute(), as does its SqliteWasmDriver subclass; cloud-side TursoDriver also conforms.`;
|
|
671
578
|
}
|
|
672
579
|
|
|
673
580
|
// src/migrations/migrate-project-id-to-environment-id.ts
|
|
674
|
-
var
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
];
|
|
581
|
+
var import_metadata_core = require("@objectstack/metadata-core");
|
|
582
|
+
var SOURCE_COLUMN = "project_id";
|
|
583
|
+
var TARGET_COLUMN = "environment_id";
|
|
584
|
+
var CANDIDATE_OBJECTS = [import_metadata_core.SysMetadataObject, import_metadata_core.SysMetadataHistoryObject];
|
|
585
|
+
function declaresColumn(object, column) {
|
|
586
|
+
return Object.prototype.hasOwnProperty.call(object.fields ?? {}, column);
|
|
587
|
+
}
|
|
588
|
+
var CANDIDATE_TABLES = CANDIDATE_OBJECTS.map((o) => o.name);
|
|
589
|
+
var AFFECTED_TABLES = CANDIDATE_OBJECTS.filter((o) => declaresColumn(o, TARGET_COLUMN)).map((o) => o.name);
|
|
678
590
|
async function migrateProjectIdToEnvironmentId(driver) {
|
|
679
|
-
const
|
|
680
|
-
if (
|
|
681
|
-
throw new Error(
|
|
682
|
-
"migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. migrateProjectIdToEnvironmentId: driver must expose a .raw(sql, bindings?) method. SqlDriver (better-sqlite3/knex) supports this; cloud-side TursoDriver also conforms."
|
|
683
|
-
);
|
|
591
|
+
const exec = resolveDriverExec(driver);
|
|
592
|
+
if (!exec) {
|
|
593
|
+
throw new Error(driverExecRefusal("migrateProjectIdToEnvironmentId"));
|
|
684
594
|
}
|
|
685
595
|
const results = [];
|
|
686
|
-
for (const table of
|
|
596
|
+
for (const table of CANDIDATE_TABLES) {
|
|
597
|
+
if (!AFFECTED_TABLES.includes(table)) {
|
|
598
|
+
results.push({ table, status: "skipped_not_declared" });
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
687
601
|
try {
|
|
688
|
-
const hasColumn = await _columnExists(
|
|
689
|
-
const alreadyMigrated = await _columnExists(
|
|
602
|
+
const hasColumn = await _columnExists(exec, table, SOURCE_COLUMN);
|
|
603
|
+
const alreadyMigrated = await _columnExists(exec, table, TARGET_COLUMN);
|
|
690
604
|
if (alreadyMigrated && !hasColumn) {
|
|
691
605
|
results.push({ table, status: "already_done" });
|
|
692
606
|
continue;
|
|
@@ -695,8 +609,8 @@ async function migrateProjectIdToEnvironmentId(driver) {
|
|
|
695
609
|
results.push({ table, status: "table_missing" });
|
|
696
610
|
continue;
|
|
697
611
|
}
|
|
698
|
-
await
|
|
699
|
-
`ALTER TABLE "${table}" RENAME COLUMN
|
|
612
|
+
await exec(
|
|
613
|
+
`ALTER TABLE "${table}" RENAME COLUMN ${SOURCE_COLUMN} TO ${TARGET_COLUMN}`
|
|
700
614
|
);
|
|
701
615
|
results.push({ table, status: "renamed" });
|
|
702
616
|
} catch (err) {
|
|
@@ -705,14 +619,14 @@ async function migrateProjectIdToEnvironmentId(driver) {
|
|
|
705
619
|
}
|
|
706
620
|
return results;
|
|
707
621
|
}
|
|
708
|
-
async function _columnExists(
|
|
622
|
+
async function _columnExists(exec, table, column) {
|
|
709
623
|
try {
|
|
710
|
-
const rows = await
|
|
624
|
+
const rows = await exec(`PRAGMA table_info("${table}")`);
|
|
711
625
|
if (Array.isArray(rows) && rows.length > 0) {
|
|
712
626
|
const list2 = Array.isArray(rows[0]) ? rows[0] : rows;
|
|
713
627
|
return list2.some((r) => r?.name === column);
|
|
714
628
|
}
|
|
715
|
-
const result = await
|
|
629
|
+
const result = await exec(
|
|
716
630
|
`SELECT column_name FROM information_schema.columns WHERE table_name = ? AND column_name = ?`,
|
|
717
631
|
[table, column]
|
|
718
632
|
);
|
|
@@ -724,6 +638,16 @@ async function _columnExists(driver, table, column) {
|
|
|
724
638
|
}
|
|
725
639
|
|
|
726
640
|
// src/loaders/database-loader.ts
|
|
641
|
+
function canonicalIsoInstant(value) {
|
|
642
|
+
if (value === null || value === void 0) return void 0;
|
|
643
|
+
if (value instanceof Date) return value.toISOString();
|
|
644
|
+
if (typeof value === "string") return value;
|
|
645
|
+
return String(value);
|
|
646
|
+
}
|
|
647
|
+
function isoFromValidDate(value) {
|
|
648
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
727
651
|
var DatabaseLoader = class {
|
|
728
652
|
constructor(options) {
|
|
729
653
|
this.contract = {
|
|
@@ -860,9 +784,11 @@ var DatabaseLoader = class {
|
|
|
860
784
|
}
|
|
861
785
|
return this.driver.create(table, data);
|
|
862
786
|
}
|
|
787
|
+
// `null` is the driver path's not-found answer (`IDataDriver.update()`,
|
|
788
|
+
// #13878); both callers here resolve the row first and discard the result.
|
|
863
789
|
async _update(table, id, data) {
|
|
864
790
|
if (this.engine) {
|
|
865
|
-
return this.engine.update(table, {
|
|
791
|
+
return this.engine.update(table, { ...data, id });
|
|
866
792
|
}
|
|
867
793
|
return this.driver.update(table, id, data);
|
|
868
794
|
}
|
|
@@ -907,7 +833,7 @@ var DatabaseLoader = class {
|
|
|
907
833
|
}
|
|
908
834
|
return max + 1;
|
|
909
835
|
} catch (error) {
|
|
910
|
-
if (isMissingTableError(error)) return 1;
|
|
836
|
+
if ((0, import_types.isMissingTableError)(error, this.historyTableName)) return 1;
|
|
911
837
|
throw error;
|
|
912
838
|
}
|
|
913
839
|
}
|
|
@@ -945,11 +871,11 @@ var DatabaseLoader = class {
|
|
|
945
871
|
}
|
|
946
872
|
try {
|
|
947
873
|
await this.driver.syncSchema(this.tableName, {
|
|
948
|
-
...
|
|
874
|
+
...import_metadata_core2.SysMetadataObject,
|
|
949
875
|
name: this.tableName
|
|
950
876
|
});
|
|
951
877
|
} catch (error) {
|
|
952
|
-
if (!isSchemaAlreadyExistsError(error)) {
|
|
878
|
+
if (!(0, import_types.isSchemaAlreadyExistsError)(error)) {
|
|
953
879
|
if (!this.schemaFailureReported) {
|
|
954
880
|
this.schemaFailureReported = true;
|
|
955
881
|
console.error(
|
|
@@ -984,7 +910,7 @@ var DatabaseLoader = class {
|
|
|
984
910
|
}
|
|
985
911
|
try {
|
|
986
912
|
await this.driver.syncSchema(this.historyTableName, {
|
|
987
|
-
...
|
|
913
|
+
...import_metadata_core2.SysMetadataHistoryObject,
|
|
988
914
|
name: this.historyTableName
|
|
989
915
|
});
|
|
990
916
|
if (this.historySchemaFailureReported) {
|
|
@@ -995,7 +921,7 @@ var DatabaseLoader = class {
|
|
|
995
921
|
}
|
|
996
922
|
this.historySchemaReady = true;
|
|
997
923
|
} catch (error) {
|
|
998
|
-
if (isSchemaAlreadyExistsError(error)) {
|
|
924
|
+
if ((0, import_types.isSchemaAlreadyExistsError)(error)) {
|
|
999
925
|
this.historySchemaReady = true;
|
|
1000
926
|
return;
|
|
1001
927
|
}
|
|
@@ -1153,9 +1079,9 @@ var DatabaseLoader = class {
|
|
|
1153
1079
|
source: row.source,
|
|
1154
1080
|
tags: row.tags ? typeof row.tags === "string" ? JSON.parse(row.tags) : row.tags : void 0,
|
|
1155
1081
|
createdBy: row.created_by,
|
|
1156
|
-
createdAt: row.created_at,
|
|
1082
|
+
createdAt: isoFromValidDate(row.created_at),
|
|
1157
1083
|
updatedBy: row.updated_by,
|
|
1158
|
-
updatedAt: row.updated_at
|
|
1084
|
+
updatedAt: isoFromValidDate(row.updated_at)
|
|
1159
1085
|
};
|
|
1160
1086
|
}
|
|
1161
1087
|
// ==========================================
|
|
@@ -1206,7 +1132,7 @@ var DatabaseLoader = class {
|
|
|
1206
1132
|
* with its empty value.
|
|
1207
1133
|
*/
|
|
1208
1134
|
rethrowUnlessTableUnprovisioned(error) {
|
|
1209
|
-
if (isMissingTableError(error)) return;
|
|
1135
|
+
if ((0, import_types.isMissingTableError)(error, this.tableName)) return;
|
|
1210
1136
|
throw error;
|
|
1211
1137
|
}
|
|
1212
1138
|
// ==========================================
|
|
@@ -1256,17 +1182,38 @@ var DatabaseLoader = class {
|
|
|
1256
1182
|
};
|
|
1257
1183
|
}
|
|
1258
1184
|
}
|
|
1259
|
-
|
|
1185
|
+
/**
|
|
1186
|
+
* The one type-wide read both plural readers share: every row of `type`, each
|
|
1187
|
+
* body paired with the `name` COLUMN it was stored under.
|
|
1188
|
+
*
|
|
1189
|
+
* [#14205] `name` is `null` only for a row whose key column does not hold a
|
|
1190
|
+
* string. Such a row is still a body {@link loadMany} must return — dropping
|
|
1191
|
+
* it would change what consumers see today — but it has no usable identity,
|
|
1192
|
+
* so {@link loadManyKeyed} filters it out rather than invent one.
|
|
1193
|
+
*
|
|
1194
|
+
* One query and one cache entry serve both methods: `loadMany()` used to own
|
|
1195
|
+
* them, and splitting them would have made every keyed `list()` read miss the
|
|
1196
|
+
* cache and re-hit the database.
|
|
1197
|
+
*/
|
|
1198
|
+
async readTypeRows(type) {
|
|
1260
1199
|
await this.ensureSchema();
|
|
1261
1200
|
if (this.loadManyCache) {
|
|
1262
1201
|
const cached = this.loadManyCache.get(type);
|
|
1263
|
-
if (cached !== void 0)
|
|
1202
|
+
if (cached !== void 0) {
|
|
1203
|
+
return cached;
|
|
1204
|
+
}
|
|
1264
1205
|
}
|
|
1265
1206
|
try {
|
|
1266
1207
|
const rows = await this._find(this.tableName, {
|
|
1267
1208
|
where: this.baseFilter(type)
|
|
1268
1209
|
});
|
|
1269
|
-
const result =
|
|
1210
|
+
const result = [];
|
|
1211
|
+
for (const row of rows) {
|
|
1212
|
+
const data = this.rowToData(row);
|
|
1213
|
+
if (data === null) continue;
|
|
1214
|
+
const name = row.name;
|
|
1215
|
+
result.push({ name: typeof name === "string" && name !== "" ? name : null, data });
|
|
1216
|
+
}
|
|
1270
1217
|
this.loadManyCache?.set(type, result);
|
|
1271
1218
|
return result;
|
|
1272
1219
|
} catch (error) {
|
|
@@ -1274,6 +1221,29 @@ var DatabaseLoader = class {
|
|
|
1274
1221
|
return [];
|
|
1275
1222
|
}
|
|
1276
1223
|
}
|
|
1224
|
+
async loadMany(type, _options) {
|
|
1225
|
+
return (await this.readTypeRows(type)).map((entry) => entry.data);
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* [#14205] The keyed half of {@link loadMany} — see
|
|
1229
|
+
* {@link MetadataKeyedItem} for why the row key travels beside the body
|
|
1230
|
+
* instead of inside it.
|
|
1231
|
+
*
|
|
1232
|
+
* `DatabaseLoader` is where the defect was measured: an aggregated view
|
|
1233
|
+
* container is written by `register('view', OBJECT, container)` and stored
|
|
1234
|
+
* verbatim, so its `sys_metadata` row carries the identity in the `name`
|
|
1235
|
+
* COLUMN and the body has none. {@link rowToData} returns that body without
|
|
1236
|
+
* folding the column in — deliberately, and unchanged here.
|
|
1237
|
+
*/
|
|
1238
|
+
async loadManyKeyed(type, _options) {
|
|
1239
|
+
const entries = await this.readTypeRows(type);
|
|
1240
|
+
const keyed = [];
|
|
1241
|
+
for (const entry of entries) {
|
|
1242
|
+
if (entry.name === null) continue;
|
|
1243
|
+
keyed.push({ name: entry.name, data: entry.data });
|
|
1244
|
+
}
|
|
1245
|
+
return keyed;
|
|
1246
|
+
}
|
|
1277
1247
|
async exists(type, name) {
|
|
1278
1248
|
await this.ensureSchema();
|
|
1279
1249
|
if (this.loadCache) {
|
|
@@ -1309,7 +1279,7 @@ var DatabaseLoader = class {
|
|
|
1309
1279
|
const metadataStr = typeof row.metadata === "string" ? row.metadata : JSON.stringify(row.metadata);
|
|
1310
1280
|
const stats = {
|
|
1311
1281
|
size: metadataStr.length,
|
|
1312
|
-
mtime: record.updatedAt ?? record.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1282
|
+
mtime: canonicalIsoInstant(record.updatedAt ?? record.createdAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1313
1283
|
format: "json",
|
|
1314
1284
|
etag: record.checksum
|
|
1315
1285
|
};
|
|
@@ -1370,7 +1340,7 @@ var DatabaseLoader = class {
|
|
|
1370
1340
|
changeNote: row.change_note,
|
|
1371
1341
|
organizationId: row.organization_id,
|
|
1372
1342
|
recordedBy: row.recorded_by,
|
|
1373
|
-
recordedAt: row.recorded_at
|
|
1343
|
+
recordedAt: isoFromValidDate(row.recorded_at)
|
|
1374
1344
|
};
|
|
1375
1345
|
}
|
|
1376
1346
|
/**
|
|
@@ -1427,7 +1397,7 @@ var DatabaseLoader = class {
|
|
|
1427
1397
|
changeNote: row.change_note,
|
|
1428
1398
|
organizationId: row.organization_id,
|
|
1429
1399
|
recordedBy: row.recorded_by,
|
|
1430
|
-
recordedAt: row.recorded_at
|
|
1400
|
+
recordedAt: isoFromValidDate(row.recorded_at)
|
|
1431
1401
|
};
|
|
1432
1402
|
});
|
|
1433
1403
|
return { records: result, total, hasMore };
|
|
@@ -1723,6 +1693,32 @@ var EndpointMatcher = class {
|
|
|
1723
1693
|
}
|
|
1724
1694
|
};
|
|
1725
1695
|
|
|
1696
|
+
// src/view-container-expansion.ts
|
|
1697
|
+
var import_spec2 = require("@objectstack/spec");
|
|
1698
|
+
var import_shared2 = require("@objectstack/spec/shared");
|
|
1699
|
+
function deriveViewContainerObject(container) {
|
|
1700
|
+
if (!container || typeof container !== "object") return void 0;
|
|
1701
|
+
const c = container;
|
|
1702
|
+
const own = typeof c.object === "string" && c.object ? c.object : void 0;
|
|
1703
|
+
const byName = typeof c.name === "string" && c.name ? c.name : void 0;
|
|
1704
|
+
return own ?? c?.list?.data?.object ?? c?.form?.data?.object ?? byName;
|
|
1705
|
+
}
|
|
1706
|
+
function expandRuntimeViewContainer(data) {
|
|
1707
|
+
if (!(0, import_spec2.isAggregatedViewContainer)(data)) return [];
|
|
1708
|
+
const container = data;
|
|
1709
|
+
const viewObject = deriveViewContainerObject(container);
|
|
1710
|
+
if (!viewObject) return [];
|
|
1711
|
+
const out = [];
|
|
1712
|
+
for (const vi of (0, import_spec2.expandViewContainer)(viewObject, container)) {
|
|
1713
|
+
(0, import_shared2.applyProtection)(vi, {
|
|
1714
|
+
packageId: container._packageId,
|
|
1715
|
+
packageVersion: container._packageVersion
|
|
1716
|
+
});
|
|
1717
|
+
out.push(vi);
|
|
1718
|
+
}
|
|
1719
|
+
return out;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1726
1722
|
// src/metadata-manager.ts
|
|
1727
1723
|
var WRITABLE_LOADER_METHODS = ["save", "delete"];
|
|
1728
1724
|
var WRITABLE_LOADER_METHOD_SIGNATURE = {
|
|
@@ -1762,8 +1758,6 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1762
1758
|
this.watchCallbacks = /* @__PURE__ */ new Map();
|
|
1763
1759
|
// In-memory metadata registry: type -> name -> data
|
|
1764
1760
|
this.registry = /* @__PURE__ */ new Map();
|
|
1765
|
-
// Overlay storage: "type:name:scope" -> MetadataOverlay
|
|
1766
|
-
this.overlays = /* @__PURE__ */ new Map();
|
|
1767
1761
|
// Type registry for metadata type info
|
|
1768
1762
|
this.typeRegistry = [];
|
|
1769
1763
|
// Dependency tracking: "type:name" -> dependencies
|
|
@@ -2338,6 +2332,67 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2338
2332
|
* result may be memoized depends on what happened to the read's registration
|
|
2339
2333
|
* while it ran, which only `list()` can see.
|
|
2340
2334
|
*/
|
|
2335
|
+
/**
|
|
2336
|
+
* Merge one loader's answer for `type` into `items`, under the identity that
|
|
2337
|
+
* loader holds each item by.
|
|
2338
|
+
*
|
|
2339
|
+
* ## [#14205] The identity of a loader-held item is its ROW KEY
|
|
2340
|
+
*
|
|
2341
|
+
* Both plural readers used to key a loader's items by `body.name`, and admit
|
|
2342
|
+
* an item only when the body carried a string one:
|
|
2343
|
+
*
|
|
2344
|
+
* ```ts
|
|
2345
|
+
* if (itemAny && typeof itemAny.name === 'string' && !items.has(itemAny.name))
|
|
2346
|
+
* ```
|
|
2347
|
+
*
|
|
2348
|
+
* A body is not required to name itself. `register(type, name, data)` takes
|
|
2349
|
+
* the key as its ARGUMENT, and `assertMetadataRegisterContract` says in as
|
|
2350
|
+
* many words that "A document with NO `name` of its own is fine — the argument
|
|
2351
|
+
* is the key". An aggregated `defineView` container is exactly that: no own
|
|
2352
|
+
* `name` by design, identity carried in the row's `name` column.
|
|
2353
|
+
*
|
|
2354
|
+
* So the old gate dropped every such item the moment the registry went cold
|
|
2355
|
+
* and only the loader could answer — a persisted view container vanished from
|
|
2356
|
+
* `list('view')` after a restart, and `listDiagnosed()` called the short
|
|
2357
|
+
* answer complete because no loader had thrown. Same gate, same effect, in
|
|
2358
|
+
* `listForIndex()`: a nameless `api` row fell out of the endpoint index, where
|
|
2359
|
+
* a miss reads as "nothing declares this route".
|
|
2360
|
+
*
|
|
2361
|
+
* The repair is to ask the loader for the key instead of guessing it from the
|
|
2362
|
+
* body ({@link MetadataLoader.loadManyKeyed}), and to keep the key BESIDE the
|
|
2363
|
+
* body: nothing is written into a body that deliberately has none, so the
|
|
2364
|
+
* register contract's refusal of a disagreeing `data.name` still means what it
|
|
2365
|
+
* says.
|
|
2366
|
+
*
|
|
2367
|
+
* Nothing consumers see today changes shape. For any item that went through
|
|
2368
|
+
* `register()`, a `data.name` that exists is required to EQUAL the key, so the
|
|
2369
|
+
* keyed merge produces the identical map entry; what is new is only the
|
|
2370
|
+
* entries the old gate refused. The `loadMany()` fallback below is the
|
|
2371
|
+
* pre-#14205 behaviour verbatim, for loaders that cannot produce keys
|
|
2372
|
+
* (`RemoteLoader`'s wire format carries bodies only).
|
|
2373
|
+
*
|
|
2374
|
+
* Read failures are NOT caught here: `readListUncached` warns-and-continues,
|
|
2375
|
+
* `listForIndex` deliberately throws, and that difference is each caller's to
|
|
2376
|
+
* keep.
|
|
2377
|
+
*/
|
|
2378
|
+
async admitLoaderItems(loader, type, items) {
|
|
2379
|
+
if (typeof loader.loadManyKeyed === "function") {
|
|
2380
|
+
const keyed = await loader.loadManyKeyed(type);
|
|
2381
|
+
for (const entry of keyed) {
|
|
2382
|
+
if (!entry || typeof entry.name !== "string" || entry.name === "") continue;
|
|
2383
|
+
if (items.has(entry.name)) continue;
|
|
2384
|
+
items.set(entry.name, entry.data);
|
|
2385
|
+
}
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
const loaderItems = await loader.loadMany(type);
|
|
2389
|
+
for (const item of loaderItems) {
|
|
2390
|
+
const itemAny = item;
|
|
2391
|
+
if (itemAny && typeof itemAny.name === "string" && !items.has(itemAny.name)) {
|
|
2392
|
+
items.set(itemAny.name, item);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2341
2396
|
async readListUncached(type) {
|
|
2342
2397
|
const items = /* @__PURE__ */ new Map();
|
|
2343
2398
|
const typeStore = this.registry.get(type);
|
|
@@ -2350,13 +2405,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2350
2405
|
const errors = [];
|
|
2351
2406
|
for (const loader of this.loaders.values()) {
|
|
2352
2407
|
try {
|
|
2353
|
-
|
|
2354
|
-
for (const item of loaderItems) {
|
|
2355
|
-
const itemAny = item;
|
|
2356
|
-
if (itemAny && typeof itemAny.name === "string" && !items.has(itemAny.name)) {
|
|
2357
|
-
items.set(itemAny.name, item);
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2408
|
+
await this.admitLoaderItems(loader, type, items);
|
|
2360
2409
|
this.reportLoaderReadRecovered(loader.contract.name);
|
|
2361
2410
|
} catch (e) {
|
|
2362
2411
|
degraded = true;
|
|
@@ -2509,13 +2558,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2509
2558
|
}
|
|
2510
2559
|
}
|
|
2511
2560
|
for (const loader of this.loaders.values()) {
|
|
2512
|
-
|
|
2513
|
-
for (const item of loaderItems) {
|
|
2514
|
-
const itemAny = item;
|
|
2515
|
-
if (itemAny && typeof itemAny.name === "string" && !items.has(itemAny.name)) {
|
|
2516
|
-
items.set(itemAny.name, item);
|
|
2517
|
-
}
|
|
2518
|
-
}
|
|
2561
|
+
await this.admitLoaderItems(loader, type, items);
|
|
2519
2562
|
}
|
|
2520
2563
|
return Array.from(items.values());
|
|
2521
2564
|
}
|
|
@@ -2760,12 +2803,54 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2760
2803
|
* Runtime-authored `shared` / `personal` views (`sys_view_definition`) are
|
|
2761
2804
|
* merged in by the REST layer; this method returns the `package` layer that
|
|
2762
2805
|
* was registered from source.
|
|
2806
|
+
*
|
|
2807
|
+
* ## [#13913] Aggregated containers are expanded inline, per read
|
|
2808
|
+
*
|
|
2809
|
+
* `this.list('view')` is `MetadataManager`'s OWN loader-based store — the
|
|
2810
|
+
* in-memory registry plus every registered loader — and is a completely
|
|
2811
|
+
* different store from the `sys_metadata` rows `getMetaItems` reads. #13407
|
|
2812
|
+
* taught `getMetaItems` to expand a runtime-authored aggregated container
|
|
2813
|
+
* inline; this exit never called it and had no equivalent step, so a
|
|
2814
|
+
* container that `GET /meta/view?object=` now serves still answered **empty**
|
|
2815
|
+
* here.
|
|
2816
|
+
*
|
|
2817
|
+
* Merely getting the container into the store would not have helped: the
|
|
2818
|
+
* filter also requires `viewKind`, and a container has none. Loosening that
|
|
2819
|
+
* requirement is NOT the repair — it would answer with the container itself
|
|
2820
|
+
* as a view, the behaviour #7163 ruled wrong — so what is added below is the
|
|
2821
|
+
* container's **expansion**, whose items each carry the `viewKind` + `object`
|
|
2822
|
+
* pair this filter has always tested. The filter itself is untouched: it
|
|
2823
|
+
* reads the top-level `object`, exactly as `ViewSchema.object` declares.
|
|
2824
|
+
*
|
|
2825
|
+
* Registry-free and per-read, mirroring #13407's choice at the other exit and
|
|
2826
|
+
* for the same reason — the registry is process-wide, so a read must not
|
|
2827
|
+
* graft rows into it (see `view-container-expansion.ts`'s header, which also
|
|
2828
|
+
* records why the protocol's copy of this logic cannot be imported).
|
|
2829
|
+
*
|
|
2830
|
+
* Already-present items win: an expansion contributes only names the store
|
|
2831
|
+
* does not already hold, so a container whose expanded ViewItems were
|
|
2832
|
+
* registered by a source registrar (the ObjectQL boot loop, the artifact/HMR
|
|
2833
|
+
* loader) still answers with those registered, fully-enriched items and this
|
|
2834
|
+
* step adds nothing.
|
|
2763
2835
|
*/
|
|
2764
2836
|
async getViewsByObject(object) {
|
|
2765
2837
|
const views = await this.list("view");
|
|
2766
|
-
|
|
2838
|
+
const matches = views.filter(
|
|
2767
2839
|
(v) => v && typeof v === "object" && v.viewKind && v.object === object
|
|
2768
|
-
)
|
|
2840
|
+
);
|
|
2841
|
+
const known = /* @__PURE__ */ new Set();
|
|
2842
|
+
for (const v of views) {
|
|
2843
|
+
if (v && typeof v === "object" && typeof v.name === "string") known.add(v.name);
|
|
2844
|
+
}
|
|
2845
|
+
for (const v of views) {
|
|
2846
|
+
for (const item of expandRuntimeViewContainer(v)) {
|
|
2847
|
+
if (!item.viewKind || item.object !== object) continue;
|
|
2848
|
+
if (known.has(item.name)) continue;
|
|
2849
|
+
known.add(item.name);
|
|
2850
|
+
matches.push(item);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
return matches.sort(
|
|
2769
2854
|
(a, b) => (a.order ?? 0) - (b.order ?? 0) || String(a.name).localeCompare(String(b.name))
|
|
2770
2855
|
);
|
|
2771
2856
|
}
|
|
@@ -3183,66 +3268,18 @@ var _MetadataManager = class _MetadataManager {
|
|
|
3183
3268
|
};
|
|
3184
3269
|
}
|
|
3185
3270
|
// ==========================================
|
|
3186
|
-
// Overlay / Customization Management
|
|
3271
|
+
// Overlay / Customization Management — REMOVED (#13135, ADR-0049)
|
|
3187
3272
|
// ==========================================
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
* Save/update an overlay for a metadata item
|
|
3199
|
-
*/
|
|
3200
|
-
async saveOverlay(overlay) {
|
|
3201
|
-
if (this.config.persistence?.overlayWritable === false) {
|
|
3202
|
-
const msg = `MetadataManager overlays are read-only (persistence.overlayWritable=false); refusing to save overlay for ${overlay.baseType}/${overlay.baseName}`;
|
|
3203
|
-
if (this.config.validation?.throwOnError) {
|
|
3204
|
-
throw new Error(msg);
|
|
3205
|
-
}
|
|
3206
|
-
this.logger.warn(msg);
|
|
3207
|
-
return;
|
|
3208
|
-
}
|
|
3209
|
-
const key = this.overlayKey(overlay.baseType, overlay.baseName, overlay.scope);
|
|
3210
|
-
this.overlays.set(key, overlay);
|
|
3211
|
-
}
|
|
3212
|
-
/**
|
|
3213
|
-
* Remove an overlay, reverting to the base definition
|
|
3214
|
-
*/
|
|
3215
|
-
async removeOverlay(type, name, scope) {
|
|
3216
|
-
this.overlays.delete(this.overlayKey(type, name, scope ?? "platform"));
|
|
3217
|
-
}
|
|
3218
|
-
/**
|
|
3219
|
-
* Get the effective (merged) metadata after applying all overlays.
|
|
3220
|
-
* Resolution order: system ← merge(platform) ← merge(user)
|
|
3221
|
-
*/
|
|
3222
|
-
async getEffective(type, name, context) {
|
|
3223
|
-
const base = await this.get(type, name);
|
|
3224
|
-
if (!base) return void 0;
|
|
3225
|
-
let effective = { ...base };
|
|
3226
|
-
const platformOverlay = await this.getOverlay(type, name, "platform");
|
|
3227
|
-
if (platformOverlay?.active && platformOverlay.patch) {
|
|
3228
|
-
effective = { ...effective, ...platformOverlay.patch };
|
|
3229
|
-
}
|
|
3230
|
-
if (context?.userId) {
|
|
3231
|
-
const userOverlayKey = this.overlayKey(type, name, "user") + `:${context.userId}`;
|
|
3232
|
-
const userOverlay = this.overlays.get(userOverlayKey) ?? await this.getOverlay(type, name, "user");
|
|
3233
|
-
if (userOverlay?.active && userOverlay.patch) {
|
|
3234
|
-
if (!userOverlay.owner || userOverlay.owner === context.userId) {
|
|
3235
|
-
effective = { ...effective, ...userOverlay.patch };
|
|
3236
|
-
}
|
|
3237
|
-
}
|
|
3238
|
-
} else {
|
|
3239
|
-
const userOverlay = await this.getOverlay(type, name, "user");
|
|
3240
|
-
if (userOverlay?.active && userOverlay.patch && !userOverlay.owner) {
|
|
3241
|
-
effective = { ...effective, ...userOverlay.patch };
|
|
3242
|
-
}
|
|
3243
|
-
}
|
|
3244
|
-
return effective;
|
|
3245
|
-
}
|
|
3273
|
+
//
|
|
3274
|
+
// The in-memory overlay limb (`getOverlay` / `saveOverlay` / `removeOverlay`
|
|
3275
|
+
// / `getEffective`, keyed `type:name:scope`) implemented the paper
|
|
3276
|
+
// metadata-customization protocol removed from `@objectstack/spec` in the
|
|
3277
|
+
// same change: no route ever served the paper `.../overlay` or
|
|
3278
|
+
// `.../effective` endpoints, and the only callers of these methods were this
|
|
3279
|
+
// package's own unit tests. ADR-0126 supersedes the protocol on the record.
|
|
3280
|
+
// The org-scoped customization that actually ships is ADR-0005's
|
|
3281
|
+
// `sys_metadata` overlay (`getMetaItemLayered` in metadata-protocol), which
|
|
3282
|
+
// never lived here.
|
|
3246
3283
|
// ==========================================
|
|
3247
3284
|
// Watch / Subscribe (IMetadataService)
|
|
3248
3285
|
// ==========================================
|
|
@@ -4016,6 +4053,7 @@ var MetadataManager = _MetadataManager;
|
|
|
4016
4053
|
// src/plugin.ts
|
|
4017
4054
|
var import_promises = require("fs/promises");
|
|
4018
4055
|
var import_node_crypto2 = require("crypto");
|
|
4056
|
+
var import_core2 = require("@objectstack/core");
|
|
4019
4057
|
|
|
4020
4058
|
// src/node-metadata-manager.ts
|
|
4021
4059
|
var path2 = __toESM(require("path"), 1);
|
|
@@ -4026,7 +4064,11 @@ var fs = __toESM(require("fs/promises"), 1);
|
|
|
4026
4064
|
var path = __toESM(require("path"), 1);
|
|
4027
4065
|
var import_glob = require("glob");
|
|
4028
4066
|
var import_node_crypto = require("crypto");
|
|
4029
|
-
|
|
4067
|
+
function ownNameOf(data) {
|
|
4068
|
+
const own = data?.name;
|
|
4069
|
+
return typeof own === "string" && own !== "" ? own : null;
|
|
4070
|
+
}
|
|
4071
|
+
var _FilesystemLoader = class _FilesystemLoader {
|
|
4030
4072
|
constructor(rootDir, serializers, logger) {
|
|
4031
4073
|
this.rootDir = rootDir;
|
|
4032
4074
|
this.serializers = serializers;
|
|
@@ -4124,6 +4166,82 @@ var FilesystemLoader = class {
|
|
|
4124
4166
|
}
|
|
4125
4167
|
}
|
|
4126
4168
|
async loadMany(type, options) {
|
|
4169
|
+
return (await this.loadManyEntries(type, options)).map((entry) => entry.data);
|
|
4170
|
+
}
|
|
4171
|
+
/**
|
|
4172
|
+
* [#14341] The keyed half of {@link loadMany} — see {@link MetadataKeyedItem}
|
|
4173
|
+
* for why the store's key travels BESIDE the body instead of being folded
|
|
4174
|
+
* into it.
|
|
4175
|
+
*
|
|
4176
|
+
* THE RULE, in one sentence: an item is keyed by this loader's own
|
|
4177
|
+
* name-to-path derivation — {@link nameFromFilename}, the very basename
|
|
4178
|
+
* derivation `list()` reports — ONLY where that derivation is a bijection for
|
|
4179
|
+
* the file (it sits directly under `ROOT/TYPE/` and carries one of the
|
|
4180
|
+
* extensions {@link findFile} tries, so `findFile(type, key)` resolves back to
|
|
4181
|
+
* this same file); every other shape keeps the pre-#14205 behaviour verbatim,
|
|
4182
|
+
* keyed by `body.name` when it has one and dropped when it has none.
|
|
4183
|
+
*
|
|
4184
|
+
* Why the rule stops there (PM ruling on #14341, 2026-09-02, knowingly over
|
|
4185
|
+
* triage's "a nested path keeps whatever `list()` reports for it today"):
|
|
4186
|
+
* `list()` and `findFile()` DISAGREE outside that shape. For
|
|
4187
|
+
* `ROOT/TYPE/crm/account.json`, `list()` reports the bare `account`, but
|
|
4188
|
+
* `findFile()` resolves that name against `ROOT/TYPE/account.json` and finds
|
|
4189
|
+
* nothing — the only name reaching the file is `crm/account`, which nothing
|
|
4190
|
+
* reports. An extension-less file is read by `loadMany()` and reported by
|
|
4191
|
+
* `list()`, and `findFile()` resolves neither. Keying by either side would
|
|
4192
|
+
* mint a name some other door cannot open, and two directories holding the
|
|
4193
|
+
* same basename would collide in silence
|
|
4194
|
+
* (`MetadataManager.admitLoaderItems()` keeps the first and says nothing).
|
|
4195
|
+
* The card's own fence: "keying items under names nothing else uses … is
|
|
4196
|
+
* worse than today's honest drop". So the drop stays exactly where the key is
|
|
4197
|
+
* unsettled, and is pinned as a RECORD in
|
|
4198
|
+
* `filesystem-loader-keyed-items.test.ts`.
|
|
4199
|
+
*
|
|
4200
|
+
* [#14486, partial] `list()` and {@link findFile} have since converged on
|
|
4201
|
+
* {@link resolvableNameForPath} — the derivation this method already used —
|
|
4202
|
+
* so a nested or extension-less file is now neither listed nor resolvable.
|
|
4203
|
+
* What did NOT change is the WALK behind this method: `loadManyEntries()`
|
|
4204
|
+
* still READS those files, so `loadMany()` still returns their bodies and
|
|
4205
|
+
* this method still falls back to `body.name` for them. That half of the
|
|
4206
|
+
* #14486 ruling ("nothing unlisted is returned by `loadMany()` either") is
|
|
4207
|
+
* deliberately NOT taken here: it would invert the three landed #14341 pins
|
|
4208
|
+
* in `filesystem-loader-keyed-items.test.ts:113,167,187` and the
|
|
4209
|
+
* `loadMany()` CONTROL at `:196`, and that file was under a concurrent
|
|
4210
|
+
* claim (PR #14627) when this landed. The remaining divergence — listed ⊂
|
|
4211
|
+
* loaded — is pinned as a RECORD in
|
|
4212
|
+
* `filesystem-loader-list-reachability.test.ts` rather than left implicit.
|
|
4213
|
+
*
|
|
4214
|
+
* One consequence, deliberate: a flat file whose `body.name` DISAGREES with
|
|
4215
|
+
* its basename is now keyed by the BASENAME. That is #14205's rule (identity
|
|
4216
|
+
* is the key the store holds an item under, not `body.name`) applied to this
|
|
4217
|
+
* loader, and it aligns `MetadataManager.list()` with `listNames()` for that
|
|
4218
|
+
* shape.
|
|
4219
|
+
*
|
|
4220
|
+
* The body is handed back by reference, unchanged: nothing is written into a
|
|
4221
|
+
* body that deliberately has no `name`. `limit` bounds the items LOADED,
|
|
4222
|
+
* exactly as `loadMany()` does — an entry the key rule drops has still been
|
|
4223
|
+
* read and still counts against it.
|
|
4224
|
+
*/
|
|
4225
|
+
async loadManyKeyed(type, options) {
|
|
4226
|
+
const typeDir = path.join(this.rootDir, type);
|
|
4227
|
+
const keyed = [];
|
|
4228
|
+
for (const entry of await this.loadManyEntries(type, options)) {
|
|
4229
|
+
const name = this.resolvableNameForPath(typeDir, entry.file) ?? ownNameOf(entry.data);
|
|
4230
|
+
if (name) {
|
|
4231
|
+
keyed.push({ name, data: entry.data });
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
return keyed;
|
|
4235
|
+
}
|
|
4236
|
+
/**
|
|
4237
|
+
* The single walk behind {@link loadMany} and {@link loadManyKeyed}: one glob,
|
|
4238
|
+
* one serializer pass, one `limit`. Shared so the two can never answer with
|
|
4239
|
+
* different bodies for the same file — {@link MetadataLoader.loadManyKeyed}
|
|
4240
|
+
* requires `data` to be "the same body `loadMany()` would return for the
|
|
4241
|
+
* item", and a second copy of this walk is how that would quietly stop being
|
|
4242
|
+
* true.
|
|
4243
|
+
*/
|
|
4244
|
+
async loadManyEntries(type, options) {
|
|
4127
4245
|
const { patterns = ["**/*"], recursive: _recursive = true, limit } = options || {};
|
|
4128
4246
|
const typeDir = path.join(this.rootDir, type);
|
|
4129
4247
|
const items = [];
|
|
@@ -4146,7 +4264,7 @@ var FilesystemLoader = class {
|
|
|
4146
4264
|
const serializer = this.getSerializer(format);
|
|
4147
4265
|
if (serializer) {
|
|
4148
4266
|
const data = serializer.deserialize(content);
|
|
4149
|
-
items.push(data);
|
|
4267
|
+
items.push({ file, data });
|
|
4150
4268
|
}
|
|
4151
4269
|
} catch (error) {
|
|
4152
4270
|
this.logger?.warn("Failed to load file", {
|
|
@@ -4200,6 +4318,30 @@ var FilesystemLoader = class {
|
|
|
4200
4318
|
return null;
|
|
4201
4319
|
}
|
|
4202
4320
|
}
|
|
4321
|
+
/**
|
|
4322
|
+
* [#14486] The names this loader can be asked for, and ONLY those: a file
|
|
4323
|
+
* directly under `ROOT/TYPE/` carrying an extension one of this instance's
|
|
4324
|
+
* REGISTERED serializers claims. Every name it reports resolves back through
|
|
4325
|
+
* {@link findFile}, so `listNames()` and `get()` give the same answer.
|
|
4326
|
+
*
|
|
4327
|
+
* It used to report `path.basename(file, ext)` for every file the glob found,
|
|
4328
|
+
* nested or not, extension or not — and {@link findFile} resolves neither
|
|
4329
|
+
* shape. `ROOT/TYPE/crm/account.json` was listed as `account`, which resolves
|
|
4330
|
+
* against `ROOT/TYPE/account.json` and finds nothing; an extension-less
|
|
4331
|
+
* `ROOT/TYPE/noext` was listed as `noext`, which resolves under no appended
|
|
4332
|
+
* extension at all. A name in the list that `get()` answers `null` for is the
|
|
4333
|
+
* silent failure an author (human or AI) reads as their own typo, so they
|
|
4334
|
+
* retry the same word: the list and the door now agree instead.
|
|
4335
|
+
*
|
|
4336
|
+
* Ruling (maintainer, via the director seat on #14486, 2026-09-02): narrow
|
|
4337
|
+
* the list — direction A, over B (reverse-unify: report `crm/account` and
|
|
4338
|
+
* teach `findFile()` path-shaped names), which would have made a slash inside
|
|
4339
|
+
* a metadata name every consumer's permanent obligation with no measured
|
|
4340
|
+
* demand for it. The two-segment layout follows ADR-0008 §10, which
|
|
4341
|
+
* `metadata-fs`'s `parseItemPath()` already enforces for its own store; the
|
|
4342
|
+
* EXTENSION set deliberately does NOT follow §10's `.json`-only rule — see
|
|
4343
|
+
* {@link resolvableExtensions} for why.
|
|
4344
|
+
*/
|
|
4203
4345
|
async list(type) {
|
|
4204
4346
|
const typeDir = path.join(this.rootDir, type);
|
|
4205
4347
|
try {
|
|
@@ -4208,11 +4350,7 @@ var FilesystemLoader = class {
|
|
|
4208
4350
|
ignore: ["**/node_modules/**", "**/*.test.*", "**/*.spec.*"],
|
|
4209
4351
|
nodir: true
|
|
4210
4352
|
});
|
|
4211
|
-
return files.map((file) =>
|
|
4212
|
-
const ext = path.extname(file);
|
|
4213
|
-
const basename3 = path.basename(file, ext);
|
|
4214
|
-
return basename3;
|
|
4215
|
-
});
|
|
4353
|
+
return files.map((file) => this.resolvableNameForPath(typeDir, path.join(typeDir, file))).filter((name) => name !== null);
|
|
4216
4354
|
} catch (error) {
|
|
4217
4355
|
this.logger?.error("Failed to list", void 0, {
|
|
4218
4356
|
type,
|
|
@@ -4290,12 +4428,66 @@ var FilesystemLoader = class {
|
|
|
4290
4428
|
throw error;
|
|
4291
4429
|
}
|
|
4292
4430
|
}
|
|
4431
|
+
/**
|
|
4432
|
+
* [#14486] The extensions a name can be resolved under, for THIS instance:
|
|
4433
|
+
* the ones belonging to the serializer set it was constructed with. Shared by
|
|
4434
|
+
* {@link findFile}, {@link resolvableNameForPath} and therefore {@link list},
|
|
4435
|
+
* so the set a name can be RESOLVED under cannot drift from the set that is
|
|
4436
|
+
* LISTED or the set {@link loadManyKeyed} is willing to KEY by.
|
|
4437
|
+
*
|
|
4438
|
+
* Registered, not hard-coded, and deliberately not ADR-0008 §10's `.json`
|
|
4439
|
+
* only. §10 governs the `metadata-fs` store; applying it verbatim here would
|
|
4440
|
+
* drop `.yaml` and `.ts` metadata out of `listNames()` — a breakage this card
|
|
4441
|
+
* never asked for. Under the manager's DEFAULT format set
|
|
4442
|
+
* (`typescript` / `json` / `yaml`, `metadata-manager.ts`) that leaves `.js`
|
|
4443
|
+
* out, which is the card's row-4 membership mismatch closing for free: a `.js`
|
|
4444
|
+
* file was listed and resolvable while `loadMany()` could never return it and
|
|
4445
|
+
* `load()` threw `No serializer found for format: javascript`. Register
|
|
4446
|
+
* `javascript` and it is listed, resolvable and loadable together.
|
|
4447
|
+
*/
|
|
4448
|
+
resolvableExtensions() {
|
|
4449
|
+
const extensions = [];
|
|
4450
|
+
for (const [format, formatExtensions] of _FilesystemLoader.EXTENSIONS_BY_FORMAT) {
|
|
4451
|
+
if (this.serializers.has(format)) {
|
|
4452
|
+
extensions.push(...formatExtensions);
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
return extensions;
|
|
4456
|
+
}
|
|
4457
|
+
/**
|
|
4458
|
+
* The metadata name this loader reports for a file: the basename with its
|
|
4459
|
+
* extension stripped. One derivation, shared by {@link list} and
|
|
4460
|
+
* {@link loadManyKeyed}, so the two cannot drift for the shape where they
|
|
4461
|
+
* agree — `dotted.config.json` is `dotted.config` for both.
|
|
4462
|
+
*/
|
|
4463
|
+
static nameFromFilename(file) {
|
|
4464
|
+
return path.basename(file, path.extname(file));
|
|
4465
|
+
}
|
|
4466
|
+
/**
|
|
4467
|
+
* The key for a file IF this loader's name-to-path mapping is a bijection for
|
|
4468
|
+
* it: a file directly under `ROOT/TYPE/` carrying an extension
|
|
4469
|
+
* {@link findFile} tries, so `findFile(type, key)` resolves back to this very
|
|
4470
|
+
* file. `null` for every other shape — a nested path, an extension-less file,
|
|
4471
|
+
* an extension spelled in a case `findFile()` does not compose — which is why
|
|
4472
|
+
* {@link loadManyKeyed} falls back to `body.name` there rather than minting a
|
|
4473
|
+
* key no other door can open.
|
|
4474
|
+
*/
|
|
4475
|
+
resolvableNameForPath(typeDir, file) {
|
|
4476
|
+
const rel = path.relative(typeDir, file);
|
|
4477
|
+
if (rel === "" || rel.split(path.sep).length !== 1) {
|
|
4478
|
+
return null;
|
|
4479
|
+
}
|
|
4480
|
+
if (!this.resolvableExtensions().includes(path.extname(rel))) {
|
|
4481
|
+
return null;
|
|
4482
|
+
}
|
|
4483
|
+
return _FilesystemLoader.nameFromFilename(rel);
|
|
4484
|
+
}
|
|
4293
4485
|
/**
|
|
4294
4486
|
* Find file for a given type and name
|
|
4295
4487
|
*/
|
|
4296
4488
|
async findFile(type, name) {
|
|
4297
4489
|
const typeDir = path.join(this.rootDir, type);
|
|
4298
|
-
const extensions =
|
|
4490
|
+
const extensions = this.resolvableExtensions();
|
|
4299
4491
|
for (const ext of extensions) {
|
|
4300
4492
|
const filePath = path.join(typeDir, `${name}${ext}`);
|
|
4301
4493
|
try {
|
|
@@ -4341,6 +4533,19 @@ var FilesystemLoader = class {
|
|
|
4341
4533
|
return `"${hash}"`;
|
|
4342
4534
|
}
|
|
4343
4535
|
};
|
|
4536
|
+
/**
|
|
4537
|
+
* The inverse of {@link detectFormat}: which file extensions carry which
|
|
4538
|
+
* format. Fixed ORDER, because it is also {@link findFile}'s precedence when
|
|
4539
|
+
* two files under one type directory share a stem — registration order must
|
|
4540
|
+
* not be able to change which file `ROOT/TYPE/NAME` opens.
|
|
4541
|
+
*/
|
|
4542
|
+
_FilesystemLoader.EXTENSIONS_BY_FORMAT = [
|
|
4543
|
+
["json", [".json"]],
|
|
4544
|
+
["yaml", [".yaml", ".yml"]],
|
|
4545
|
+
["typescript", [".ts"]],
|
|
4546
|
+
["javascript", [".js"]]
|
|
4547
|
+
];
|
|
4548
|
+
var FilesystemLoader = _FilesystemLoader;
|
|
4344
4549
|
|
|
4345
4550
|
// src/node-metadata-manager.ts
|
|
4346
4551
|
var NodeMetadataManager = class extends MetadataManager {
|
|
@@ -4465,6 +4670,20 @@ var MemoryLoader = class {
|
|
|
4465
4670
|
if (!typeStore) return [];
|
|
4466
4671
|
return Array.from(typeStore.values());
|
|
4467
4672
|
}
|
|
4673
|
+
/**
|
|
4674
|
+
* [#14205] The keyed half of {@link loadMany}. The storage map is already
|
|
4675
|
+
* `Type -> Name -> Data`, so the key this loader holds an item under is the
|
|
4676
|
+
* map key — `loadMany()` was simply discarding it, which dropped every
|
|
4677
|
+
* nameless body out of `MetadataManager.list()` and out of the endpoint index.
|
|
4678
|
+
*
|
|
4679
|
+
* The body is handed back by reference, unchanged: the key travels beside it,
|
|
4680
|
+
* never folded into it.
|
|
4681
|
+
*/
|
|
4682
|
+
async loadManyKeyed(type, _options) {
|
|
4683
|
+
const typeStore = this.storage.get(type);
|
|
4684
|
+
if (!typeStore) return [];
|
|
4685
|
+
return Array.from(typeStore, ([name, data]) => ({ name, data }));
|
|
4686
|
+
}
|
|
4468
4687
|
async exists(type, name) {
|
|
4469
4688
|
return this.storage.get(type)?.has(name) ?? false;
|
|
4470
4689
|
}
|
|
@@ -4511,20 +4730,20 @@ var MemoryLoader = class {
|
|
|
4511
4730
|
|
|
4512
4731
|
// src/plugin.ts
|
|
4513
4732
|
var import_kernel2 = require("@objectstack/spec/kernel");
|
|
4514
|
-
var
|
|
4515
|
-
var
|
|
4516
|
-
var import_spec2 = require("@objectstack/spec");
|
|
4733
|
+
var import_shared3 = require("@objectstack/spec/shared");
|
|
4734
|
+
var import_metadata_core3 = require("@objectstack/metadata-core");
|
|
4517
4735
|
var import_spec3 = require("@objectstack/spec");
|
|
4736
|
+
var import_spec4 = require("@objectstack/spec");
|
|
4518
4737
|
var queryableMetadataObjects = [
|
|
4519
|
-
|
|
4520
|
-
|
|
4738
|
+
import_metadata_core3.SysMetadataObject,
|
|
4739
|
+
import_metadata_core3.SysMetadataHistoryObject,
|
|
4521
4740
|
// ADR-0067 commit log — sibling of sys_metadata_history (see note above).
|
|
4522
|
-
|
|
4523
|
-
|
|
4741
|
+
import_metadata_core3.SysMetadataCommitObject,
|
|
4742
|
+
import_metadata_core3.SysMetadataAuditObject,
|
|
4524
4743
|
// Runtime view storage (shared / personal). Must always be provisioned so
|
|
4525
4744
|
// end-user view creation via the generic data API has a place to write —
|
|
4526
4745
|
// mirroring why sys_metadata is always provisioned for PUT /meta.
|
|
4527
|
-
|
|
4746
|
+
import_metadata_core3.SysViewDefinitionObject
|
|
4528
4747
|
];
|
|
4529
4748
|
var REPO_SUBDIR = ".objectstack/metadata";
|
|
4530
4749
|
var ARTIFACT_FIELD_TO_TYPE = {
|
|
@@ -4536,7 +4755,10 @@ var ARTIFACT_FIELD_TO_TYPE = {
|
|
|
4536
4755
|
dashboards: "dashboard",
|
|
4537
4756
|
reports: "report",
|
|
4538
4757
|
actions: "action",
|
|
4539
|
-
themes:
|
|
4758
|
+
// `themes: 'theme'` removed at #10485 (ADR-0049): the carrier key is
|
|
4759
|
+
// retired, so a parsed artifact can no longer carry the field — and the
|
|
4760
|
+
// ingest half of the dead pipeline (items stored, read by nothing) goes
|
|
4761
|
+
// with the authoring half rather than surviving it as drift.
|
|
4540
4762
|
workflows: "workflow",
|
|
4541
4763
|
flows: "flow",
|
|
4542
4764
|
// ADR-0090 D3: stacks declare `positions` (stack.zod.ts); the retired
|
|
@@ -4544,8 +4766,37 @@ var ARTIFACT_FIELD_TO_TYPE = {
|
|
|
4544
4766
|
// positions from artifact ingestion.
|
|
4545
4767
|
positions: "position",
|
|
4546
4768
|
permissions: "permission",
|
|
4769
|
+
// [ADR-0066 D1] `capabilities` reaches the door at #12892 step 1, the
|
|
4770
|
+
// maintainer's `option 1` ruling ("the door owns the registration
|
|
4771
|
+
// route" for the five artifact security collections). Until #12894
|
|
4772
|
+
// measured it, `AppPlugin`'s `SECURITY_FIELDS` block
|
|
4773
|
+
// (packages/runtime/src/app-plugin.ts) was this collection's SOLE
|
|
4774
|
+
// registrar on an artifact boot — the one security collection the door
|
|
4775
|
+
// could not reach — so a declared capability was registered from bytes
|
|
4776
|
+
// nothing strict-parses, with no schema default and no ADR-0010
|
|
4777
|
+
// provenance. Measured on the two-reader harness, the door's copy adds
|
|
4778
|
+
// exactly four keys the raw copy lacks: `scope` (the schema default)
|
|
4779
|
+
// and `_packageId` / `_packageVersion` / `_provenance`.
|
|
4780
|
+
//
|
|
4781
|
+
// ⚠️ This entry makes the door a SECOND writer, not yet the only one:
|
|
4782
|
+
// `AppPlugin` still registers `capabilities`, and it runs last, so the
|
|
4783
|
+
// raw copy still wins a real artifact boot. Step 2 of the ruling (that
|
|
4784
|
+
// block stops registering these five on the artifact path, after a
|
|
4785
|
+
// census of the non-artifact boot paths) is what makes this the only
|
|
4786
|
+
// copy. Until then the divergence is the interim reality the ruling
|
|
4787
|
+
// explicitly permits, and #12878's pins are what keep it visible.
|
|
4788
|
+
capabilities: "capability",
|
|
4547
4789
|
sharingRules: "sharing_rule",
|
|
4548
|
-
policies:
|
|
4790
|
+
// `policies: 'policy'` removed at #12894: the stack schema is a
|
|
4791
|
+
// `strictObject` that declares no top-level `policies` key, so a
|
|
4792
|
+
// definition carrying one is refused by the strict parse a few lines
|
|
4793
|
+
// below — the entry could never match, and nothing was ever registered
|
|
4794
|
+
// under `policy` from this map. The word is real, but it lives ONE LEVEL
|
|
4795
|
+
// DOWN: on a permission set it is an alias for `rowLevelSecurity`
|
|
4796
|
+
// (`PERMISSION_SET_KEY_ALIASES`, packages/spec/src/security/permission.zod.ts)
|
|
4797
|
+
// — a key on an ITEM, never a collection. Third retirement of this exact
|
|
4798
|
+
// shape in this map (`themes` and `roles` above); the reasons are kept
|
|
4799
|
+
// in place because the first two are what made this one findable.
|
|
4549
4800
|
apis: "api",
|
|
4550
4801
|
webhooks: "webhook",
|
|
4551
4802
|
agents: "agent",
|
|
@@ -4588,14 +4839,29 @@ var MetadataPlugin = class {
|
|
|
4588
4839
|
* degrades on purpose (objects are discovered via the legacy fallback).
|
|
4589
4840
|
*/
|
|
4590
4841
|
this.optionalDependencies = ["com.objectstack.engine.objectql"];
|
|
4842
|
+
/**
|
|
4843
|
+
* Once-per-process dedupe for the summaries the versioned artifact window
|
|
4844
|
+
* emits. The artifact watcher replays `_parseAndRegisterArtifact` on every
|
|
4845
|
+
* file change, so without this a dev loop over a legacy artifact would
|
|
4846
|
+
* re-announce the same finding on every reload — the same shape
|
|
4847
|
+
* `Protocol.storedConversionWarned` guards on the stored-row pass, which
|
|
4848
|
+
* this surfacing is modeled on.
|
|
4849
|
+
*
|
|
4850
|
+
* Two key families share the set, because they share the replay:
|
|
4851
|
+
* `<conversionId>|<label>` for a forward-conversion summary (#12772), and
|
|
4852
|
+
* `unbound-form-predicate-root|<label>` for the unbound-root notice
|
|
4853
|
+
* (#12915) — one line per artifact there, not one per conversion, since
|
|
4854
|
+
* the notice already aggregates every finding it made.
|
|
4855
|
+
*/
|
|
4856
|
+
this.artifactConversionWarned = /* @__PURE__ */ new Set();
|
|
4591
4857
|
this.init = async (ctx) => {
|
|
4858
|
+
this.initCtx = ctx;
|
|
4592
4859
|
ctx.logger.info("Initializing Metadata Manager", {
|
|
4593
4860
|
root: this.options.rootDir || process.cwd(),
|
|
4594
4861
|
watch: this.options.watch,
|
|
4595
4862
|
artifactSource: this.options.artifactSource?.mode
|
|
4596
4863
|
});
|
|
4597
4864
|
ctx.registerService("metadata", this.manager);
|
|
4598
|
-
console.log("[MetadataPlugin] Registered metadata service, has getRegisteredTypes:", typeof this.manager.getRegisteredTypes);
|
|
4599
4865
|
const registerSysObjects = this.options.registerSystemObjects !== false;
|
|
4600
4866
|
if (registerSysObjects) {
|
|
4601
4867
|
try {
|
|
@@ -4617,7 +4883,7 @@ var MetadataPlugin = class {
|
|
|
4617
4883
|
}
|
|
4618
4884
|
ctx.logger.info("MetadataPlugin providing metadata service (primary mode)", {
|
|
4619
4885
|
mode: this.options.artifactSource?.mode ?? "file-system",
|
|
4620
|
-
features: ["watch", "multi-format", "query", "
|
|
4886
|
+
features: ["watch", "multi-format", "query", "type-registry"]
|
|
4621
4887
|
});
|
|
4622
4888
|
};
|
|
4623
4889
|
this.start = async (ctx) => {
|
|
@@ -4700,7 +4966,7 @@ var MetadataPlugin = class {
|
|
|
4700
4966
|
if (httpServer && typeof httpServer.getRawApp === "function") {
|
|
4701
4967
|
const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
|
|
4702
4968
|
const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
|
|
4703
|
-
hub
|
|
4969
|
+
hub?.setOnPostReload(async (body = {}) => {
|
|
4704
4970
|
const src3 = this.options.artifactSource;
|
|
4705
4971
|
if (src3?.mode === "local-file") {
|
|
4706
4972
|
try {
|
|
@@ -4740,7 +5006,7 @@ var MetadataPlugin = class {
|
|
|
4740
5006
|
pending = true;
|
|
4741
5007
|
try {
|
|
4742
5008
|
await this._reloadAndAnnounce(ctx, src2, [src2.path]);
|
|
4743
|
-
hub
|
|
5009
|
+
hub?.broadcastReload("artifact-file-changed", [src2.path]);
|
|
4744
5010
|
ctx.logger.info("[MetadataPlugin] artifact auto-reloaded (file watcher)", {
|
|
4745
5011
|
path: src2.path
|
|
4746
5012
|
});
|
|
@@ -4762,7 +5028,13 @@ var MetadataPlugin = class {
|
|
|
4762
5028
|
ctx.logger.warn("[MetadataPlugin] artifact watcher failed to start", { error: e?.message });
|
|
4763
5029
|
}
|
|
4764
5030
|
}
|
|
4765
|
-
|
|
5031
|
+
if (hub) {
|
|
5032
|
+
console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
|
|
5033
|
+
} else {
|
|
5034
|
+
console.log(
|
|
5035
|
+
`[MetadataPlugin] dev metadata-HMR endpoints NOT mounted \u2014 they require NODE_ENV=development (this process: ${process.env.NODE_ENV ? `NODE_ENV=${process.env.NODE_ENV}` : "NODE_ENV unset, treated as production"})`
|
|
5036
|
+
);
|
|
5037
|
+
}
|
|
4766
5038
|
} else {
|
|
4767
5039
|
console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
|
|
4768
5040
|
}
|
|
@@ -4770,7 +5042,22 @@ var MetadataPlugin = class {
|
|
|
4770
5042
|
console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
|
|
4771
5043
|
}
|
|
4772
5044
|
};
|
|
4773
|
-
|
|
5045
|
+
/**
|
|
5046
|
+
* Teardown — the kernel's ONLY teardown hook.
|
|
5047
|
+
*
|
|
5048
|
+
* [#10772] This body used to be spelled `stop(ctx)`. `Plugin`
|
|
5049
|
+
* (`@objectstack/core`'s `types.ts`) declares `init()`, `start?(ctx)` and
|
|
5050
|
+
* `destroy?()` and no `stop()`, and `ObjectKernel.performShutdown()` /
|
|
5051
|
+
* `LiteKernel.destroy()` walk the plugins in reverse calling
|
|
5052
|
+
* `plugin.destroy()` — so the artifact watcher, the manager and the
|
|
5053
|
+
* repository were all still held after `await kernel.shutdown()` had
|
|
5054
|
+
* RESOLVED. The `start`/`stop` pair read symmetric to a reviewer because
|
|
5055
|
+
* `start()` really is on the interface; only one half was ever called.
|
|
5056
|
+
*
|
|
5057
|
+
* Idempotent: every handle is cleared as it is released, so a second
|
|
5058
|
+
* teardown is a no-op rather than a second close.
|
|
5059
|
+
*/
|
|
5060
|
+
this.destroy = async () => {
|
|
4774
5061
|
if (this.artifactWatcher) {
|
|
4775
5062
|
try {
|
|
4776
5063
|
await this.artifactWatcher.close();
|
|
@@ -4781,7 +5068,7 @@ var MetadataPlugin = class {
|
|
|
4781
5068
|
try {
|
|
4782
5069
|
await this.manager.dispose();
|
|
4783
5070
|
} catch (e) {
|
|
4784
|
-
|
|
5071
|
+
this.initCtx?.logger?.warn?.("[MetadataPlugin] manager.dispose() failed", { error: e?.message });
|
|
4785
5072
|
}
|
|
4786
5073
|
const repo = this.repository;
|
|
4787
5074
|
if (repo && typeof repo.close === "function") {
|
|
@@ -4792,6 +5079,18 @@ var MetadataPlugin = class {
|
|
|
4792
5079
|
}
|
|
4793
5080
|
this.repository = void 0;
|
|
4794
5081
|
};
|
|
5082
|
+
/**
|
|
5083
|
+
* Retained alias for {@link destroy}. Kept because it is public API of an
|
|
5084
|
+
* exported class: an embedder may have learned to call it directly
|
|
5085
|
+
* precisely BECAUSE the kernel never did, and deleting it would break them.
|
|
5086
|
+
* Still an arrow property, so a detached `const { stop } = plugin` call
|
|
5087
|
+
* keeps working too. The parameter is now optional and ignored —
|
|
5088
|
+
* `destroy()` takes no context, so teardown logs through the context
|
|
5089
|
+
* captured in `init()`.
|
|
5090
|
+
*/
|
|
5091
|
+
this.stop = async (_ctx) => {
|
|
5092
|
+
await this.destroy();
|
|
5093
|
+
};
|
|
4795
5094
|
this.options = {
|
|
4796
5095
|
...options,
|
|
4797
5096
|
watch: options.watch ?? false
|
|
@@ -4831,6 +5130,106 @@ var MetadataPlugin = class {
|
|
|
4831
5130
|
if (timer) clearTimeout(timer);
|
|
4832
5131
|
}
|
|
4833
5132
|
}
|
|
5133
|
+
/**
|
|
5134
|
+
* Versioned ADR-0087 forward conversion at the artifact-ingestion door
|
|
5135
|
+
* (#12772) — runs BEFORE the strict schema parse below, because the parse
|
|
5136
|
+
* is the refusal point.
|
|
5137
|
+
*
|
|
5138
|
+
* A compiled artifact is data at rest with a version stamp: built by
|
|
5139
|
+
* released tooling, then unchanged while the platform moves on. When a
|
|
5140
|
+
* spec release retires an authorable key inside a protocol line (spec
|
|
5141
|
+
* 17.1 → 17.2 retired the `allowRestore`/`allowPurge` permission bits),
|
|
5142
|
+
* every already-built artifact carrying the key becomes unbootable at the
|
|
5143
|
+
* tombstone — with no operator remedy, since `os migrate meta` targets
|
|
5144
|
+
* sources, not built artifacts. The stored-row read path already replays
|
|
5145
|
+
* the conversion chain for exactly this reason
|
|
5146
|
+
* (`applyConversionsToStoredItem`, ADR-0087 addendum); this is the same
|
|
5147
|
+
* policy at the artifact door, **keyed off the artifact's own declared
|
|
5148
|
+
* `engines.protocol` floor**: an artifact authored below the running spec
|
|
5149
|
+
* version converts forward, an artifact authored at the current (or a
|
|
5150
|
+
* newer) surface converts nothing and answers to the strict parse,
|
|
5151
|
+
* tombstones included. The version key is what keeps this a conversion
|
|
5152
|
+
* rather than an amnesty — the retired keys return with the M2 lifecycle
|
|
5153
|
+
* batch (#1883), and artifacts authored against that surface must never
|
|
5154
|
+
* have them stripped by history.
|
|
5155
|
+
*
|
|
5156
|
+
* Notices surface the way the stored-row pass's do — operator-visible and
|
|
5157
|
+
* deduped — as one summary line per conversion per artifact rather than
|
|
5158
|
+
* one per rewritten path (a real 17.1 artifact carried 150 strips of the
|
|
5159
|
+
* same two keys; 150 identical warn lines would bury the boot log).
|
|
5160
|
+
*/
|
|
5161
|
+
_convertArtifactForward(ctx, definition, label) {
|
|
5162
|
+
const result = (0, import_metadata_core3.applyArtifactForwardConversions)(definition);
|
|
5163
|
+
this._warnUnboundFormPredicateRoots(ctx, result, label);
|
|
5164
|
+
if (result.notices.length === 0) return result.definition;
|
|
5165
|
+
const byConversion = /* @__PURE__ */ new Map();
|
|
5166
|
+
for (const n of result.notices) {
|
|
5167
|
+
const existing = byConversion.get(n.conversionId);
|
|
5168
|
+
if (existing) existing.count += 1;
|
|
5169
|
+
else byConversion.set(n.conversionId, { count: 1, firstPath: n.path, message: n.message });
|
|
5170
|
+
}
|
|
5171
|
+
for (const [conversionId, agg] of byConversion) {
|
|
5172
|
+
const key = `${conversionId}|${label}`;
|
|
5173
|
+
if (this.artifactConversionWarned.has(key)) continue;
|
|
5174
|
+
this.artifactConversionWarned.add(key);
|
|
5175
|
+
ctx.logger.warn(
|
|
5176
|
+
`[MetadataPlugin] artifact '${label}' predates this runtime's spec (authored engines.protocol floor ${result.authoredFloor ?? "<undeclared>"}, runtime spec ${result.runtimeSpecVersion}) \u2014 converted ${agg.count} site(s) forward via ADR-0087 conversion '${conversionId}' (first at ${agg.firstPath}). ${agg.message} The artifact file itself is unchanged \u2014 rebuild it with current tooling ('os build') to persist the canonical shape.`
|
|
5177
|
+
);
|
|
5178
|
+
}
|
|
5179
|
+
return result.definition;
|
|
5180
|
+
}
|
|
5181
|
+
/**
|
|
5182
|
+
* Operator-facing boot notice for form-view predicates that fault OPEN on
|
|
5183
|
+
* this runtime (#12915 scope C — maintainer ruling 2026-08-28, 「同意C」).
|
|
5184
|
+
*
|
|
5185
|
+
* A form-view predicate binds `record` / `previous` / `parent` (runtime
|
|
5186
|
+
* record forms) or `data` (metadata-editing forms) — and a FIELD-level one
|
|
5187
|
+
* also binds `current_user` and its ADR-0068 aliases (objectui#6010),
|
|
5188
|
+
* which a SECTION-level one does not. The contract states beside that
|
|
5189
|
+
* vocabulary that a bare identifier is UNBOUND and the predicate faults,
|
|
5190
|
+
* and `visibleWhen`'s fault fallback is `true`. On a real
|
|
5191
|
+
* 17.1-built artifact that combination dead-ends record creation in the
|
|
5192
|
+
* console: the conditionally hidden field renders, and its unconditional
|
|
5193
|
+
* `required: true` — authored to be gated by the visibility that no longer
|
|
5194
|
+
* applies — blocks every submit, while the same payload POSTs 201 through
|
|
5195
|
+
* REST. Nothing refused, nothing logged, and only the operator can fix it
|
|
5196
|
+
* (by rebuilding the artifact), so this is the channel the ruling picked:
|
|
5197
|
+
* service startup, server-side, never a console surface — the person at
|
|
5198
|
+
* the form cannot act on "your artifact is stale".
|
|
5199
|
+
*
|
|
5200
|
+
* **Detection only.** No refusal, no rewrite, no behaviour change: the
|
|
5201
|
+
* predicate keeps faulting open exactly as before. Rewriting a bare root to
|
|
5202
|
+
* `record.` is the ADR-0087 conversion (#12915 scope A), deferred by the
|
|
5203
|
+
* same ruling with an explicit start line.
|
|
5204
|
+
*
|
|
5205
|
+
* **Same versioned window as the conversion replay above** — and read off
|
|
5206
|
+
* that pass's own verdict rather than recomputed, so the two can never
|
|
5207
|
+
* disagree about which artifacts are "old". An artifact declaring the
|
|
5208
|
+
* current (or a newer) floor answers to the strict parse and gets nothing
|
|
5209
|
+
* from here even when it does carry bare roots; that boundary is what keeps
|
|
5210
|
+
* a notice about legacy artifacts out of contract territory. An undeclared
|
|
5211
|
+
* range is treated as old data at rest, matching the grandfathering posture
|
|
5212
|
+
* the window already takes (`converted-undeclared`).
|
|
5213
|
+
*/
|
|
5214
|
+
_warnUnboundFormPredicateRoots(ctx, result, label) {
|
|
5215
|
+
if (result.verdict !== "converted-forward" && result.verdict !== "converted-undeclared") return;
|
|
5216
|
+
const findings = (0, import_metadata_core3.detectUnboundFormViewPredicateRoots)(result.definition);
|
|
5217
|
+
if (findings.length === 0) return;
|
|
5218
|
+
const key = `unbound-form-predicate-root|${label}`;
|
|
5219
|
+
if (this.artifactConversionWarned.has(key)) return;
|
|
5220
|
+
this.artifactConversionWarned.add(key);
|
|
5221
|
+
const views = [...new Set(findings.map((f) => f.view))];
|
|
5222
|
+
const roots = [...new Set(findings.map((f) => f.root))];
|
|
5223
|
+
const quote = (list) => list.map((v) => `'${v}'`).join(", ");
|
|
5224
|
+
const surfaces = new Set(findings.map((f) => f.surface));
|
|
5225
|
+
const vocabulary = [
|
|
5226
|
+
surfaces.has("field") ? `on a form FIELD: ${quote(import_metadata_core3.BOUND_FORM_FIELD_PREDICATE_ROOTS)}` : null,
|
|
5227
|
+
surfaces.has("section") ? `on a form SECTION: ${quote(import_metadata_core3.BOUND_FORM_VIEW_PREDICATE_ROOTS)}` : null
|
|
5228
|
+
].filter(Boolean).join("; ");
|
|
5229
|
+
ctx.logger.warn(
|
|
5230
|
+
`[MetadataPlugin] artifact '${label}' predates this runtime's spec (authored engines.protocol floor ${result.authoredFloor ?? "<undeclared>"}, runtime spec ${result.runtimeSpecVersion}) and carries ${findings.length} form-view predicate(s) whose root identifier is NOT bound where it evaluates \u2014 ${quote(roots)} (bound roots ${vocabulary}) \u2014 across ${views.length} view(s): ${views.join(", ")} (first at ${findings[0].path}). Each one faults at evaluation and visibility fails OPEN, so a field the predicate was authored to HIDE renders anyway \u2014 and an unconditional 'required: true' on such a field dead-ends record creation in the console while the REST door still accepts it. Nothing was rewritten and no behaviour changed here; rebuild the artifact with current tooling ('os build') so its predicates carry a bound root ('record.<field>').`
|
|
5231
|
+
);
|
|
5232
|
+
}
|
|
4834
5233
|
/**
|
|
4835
5234
|
* Parse raw artifact JSON (envelope or bare definition) and register all
|
|
4836
5235
|
* metadata items into the MetadataManager.
|
|
@@ -4849,16 +5248,22 @@ var MetadataPlugin = class {
|
|
|
4849
5248
|
let metadata;
|
|
4850
5249
|
const obj = raw;
|
|
4851
5250
|
if (obj?.schemaVersion && obj?.commitId && obj?.metadata !== void 0) {
|
|
4852
|
-
const artifact = EnvironmentArtifactSchema.parse(
|
|
5251
|
+
const artifact = EnvironmentArtifactSchema.parse({
|
|
5252
|
+
...obj,
|
|
5253
|
+
metadata: this._convertArtifactForward(ctx, obj.metadata, label)
|
|
5254
|
+
});
|
|
4853
5255
|
metadata = artifact.metadata;
|
|
4854
5256
|
} else if (obj?.success && obj?.data?.metadata) {
|
|
4855
|
-
const artifact = EnvironmentArtifactSchema.parse(
|
|
5257
|
+
const artifact = EnvironmentArtifactSchema.parse({
|
|
5258
|
+
...obj.data,
|
|
5259
|
+
metadata: this._convertArtifactForward(ctx, obj.data.metadata, label)
|
|
5260
|
+
});
|
|
4856
5261
|
metadata = artifact.metadata;
|
|
4857
5262
|
} else {
|
|
4858
|
-
const def = ObjectStackDefinitionSchema.parse(obj);
|
|
5263
|
+
const def = ObjectStackDefinitionSchema.parse(this._convertArtifactForward(ctx, obj, label));
|
|
4859
5264
|
const canonical = JSON.stringify(def, Object.keys(def).sort());
|
|
4860
5265
|
const checksum = (0, import_node_crypto2.createHash)("sha256").update(canonical).digest("hex");
|
|
4861
|
-
const environmentId = this.options.environmentId ?? "
|
|
5266
|
+
const environmentId = this.options.environmentId ?? "env_local";
|
|
4862
5267
|
EnvironmentArtifactSchema.parse({
|
|
4863
5268
|
schemaVersion: "0.1",
|
|
4864
5269
|
environmentId,
|
|
@@ -4872,53 +5277,127 @@ var MetadataPlugin = class {
|
|
|
4872
5277
|
const memLoader = new MemoryLoader();
|
|
4873
5278
|
const manifestPackageId = metadata?.manifest?.id ?? metadata?.id ?? void 0;
|
|
4874
5279
|
const manifestVersion = metadata?.manifest?.version ?? metadata?.version ?? void 0;
|
|
5280
|
+
const carriesPackages = Array.isArray(metadata?.packages);
|
|
5281
|
+
const bodies = (0, import_core2.resolveArtifactPackageOrder)(metadata);
|
|
5282
|
+
const ownedByPackage = /* @__PURE__ */ new Map();
|
|
5283
|
+
const claim = (type, name) => {
|
|
5284
|
+
let names = ownedByPackage.get(type);
|
|
5285
|
+
if (!names) ownedByPackage.set(type, names = /* @__PURE__ */ new Set());
|
|
5286
|
+
names.add(name);
|
|
5287
|
+
};
|
|
5288
|
+
const claimed = (type, name) => ownedByPackage.get(type)?.has(name) === true;
|
|
5289
|
+
let totalRegistered = 0;
|
|
5290
|
+
for (const body of bodies) {
|
|
5291
|
+
totalRegistered += await this._registerArtifactBodyCollections(
|
|
5292
|
+
ctx,
|
|
5293
|
+
memLoader,
|
|
5294
|
+
body,
|
|
5295
|
+
carriesPackages ? {
|
|
5296
|
+
packageId: (0, import_core2.artifactPackageId)(body),
|
|
5297
|
+
packageVersion: body?.version ?? void 0
|
|
5298
|
+
} : { packageId: manifestPackageId, packageVersion: manifestVersion },
|
|
5299
|
+
{ claim: carriesPackages ? claim : void 0 }
|
|
5300
|
+
);
|
|
5301
|
+
}
|
|
5302
|
+
if (carriesPackages) {
|
|
5303
|
+
const residual = await this._registerArtifactBodyCollections(
|
|
5304
|
+
ctx,
|
|
5305
|
+
memLoader,
|
|
5306
|
+
metadata,
|
|
5307
|
+
{ packageId: manifestPackageId, packageVersion: manifestVersion },
|
|
5308
|
+
{ skip: claimed }
|
|
5309
|
+
);
|
|
5310
|
+
totalRegistered += residual;
|
|
5311
|
+
if (residual > 0) {
|
|
5312
|
+
ctx.logger.warn(
|
|
5313
|
+
`[MetadataPlugin] artifact '${label}' carries ${residual} top-level metadata item(s) that none of its ${bodies.length} package bodies declare. They were registered under the artifact's own manifest id ('${manifestPackageId ?? "<none>"}') because no package in the artifact claims them, so every door will report that id as their owner. Rebuild the artifact so each collection it ships is carried by the package that owns it.`
|
|
5314
|
+
);
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
this.manager.registerLoader(memLoader);
|
|
5318
|
+
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", { source: label, totalRegistered });
|
|
5319
|
+
return totalRegistered;
|
|
5320
|
+
}
|
|
5321
|
+
/**
|
|
5322
|
+
* Register ONE artifact body's collections into the MetadataManager.
|
|
5323
|
+
*
|
|
5324
|
+
* A "body" is either the whole artifact (the single-package branch, where
|
|
5325
|
+
* the artifact and its one package are the same object) or one entry of
|
|
5326
|
+
* `packages[]` (ADR-0130 D4), which is an assembled
|
|
5327
|
+
* `{ ...manifest, ...collections }` payload carrying the same collection
|
|
5328
|
+
* keys the top level does. The loop is identical for both — that is the
|
|
5329
|
+
* point: there is one ingestion of a collection here, not one per shape.
|
|
5330
|
+
*
|
|
5331
|
+
* @param provenance - The `(packageId, packageVersion)` every item found in
|
|
5332
|
+
* this body is stamped with (ADR-0010 §3.7, via `applyProtection`). It is
|
|
5333
|
+
* the body's OWN identity, never the enclosing artifact's, which is what
|
|
5334
|
+
* makes a multi-package artifact's items agree with the registry and with
|
|
5335
|
+
* `GET /api/v1/packages` about who owns them.
|
|
5336
|
+
* @param slots.claim - Called with every `(type, name)` this pass
|
|
5337
|
+
* registered. Passed when reading package bodies; the residual sweep uses
|
|
5338
|
+
* what it recorded.
|
|
5339
|
+
* @param slots.skip - Consulted before registering each `(type, name)`.
|
|
5340
|
+
* Passed ONLY by the residual sweep, so a package body's copy is never
|
|
5341
|
+
* overwritten by the flattened top-level copy of the same definition —
|
|
5342
|
+
* the overwrite that re-attributed the item to the artifact's manifest.
|
|
5343
|
+
* ⛔ It is never passed while reading the bodies themselves: two items of
|
|
5344
|
+
* one name inside one body still register as they always have (last
|
|
5345
|
+
* wins), because suppressing that would be a behaviour change on the
|
|
5346
|
+
* single-package branch D7 pins.
|
|
5347
|
+
* @returns How many items this body registered.
|
|
5348
|
+
*/
|
|
5349
|
+
async _registerArtifactBodyCollections(ctx, memLoader, body, provenance, slots = {}) {
|
|
5350
|
+
const { packageId, packageVersion } = provenance;
|
|
4875
5351
|
let totalRegistered = 0;
|
|
4876
5352
|
for (const [field, metaType] of Object.entries(ARTIFACT_FIELD_TO_TYPE)) {
|
|
4877
|
-
const items =
|
|
5353
|
+
const items = body[field];
|
|
4878
5354
|
if (!Array.isArray(items) || items.length === 0) continue;
|
|
4879
5355
|
for (const item of items) {
|
|
4880
|
-
if (metaType === "view" && (0,
|
|
4881
|
-
const viewObject = item
|
|
5356
|
+
if (metaType === "view" && (0, import_spec4.isAggregatedViewContainer)(item)) {
|
|
5357
|
+
const viewObject = deriveViewContainerObject(item);
|
|
4882
5358
|
if (!viewObject) continue;
|
|
4883
|
-
(
|
|
4884
|
-
|
|
4885
|
-
|
|
5359
|
+
if (slots.skip?.("view", viewObject)) continue;
|
|
5360
|
+
(0, import_shared3.applyProtection)(item, {
|
|
5361
|
+
packageId,
|
|
5362
|
+
packageVersion
|
|
4886
5363
|
});
|
|
4887
5364
|
await memLoader.save("view", viewObject, item);
|
|
4888
5365
|
await this.manager.register("view", viewObject, item, { notify: false });
|
|
4889
5366
|
totalRegistered++;
|
|
4890
|
-
|
|
5367
|
+
slots.claim?.("view", viewObject);
|
|
5368
|
+
for (const vi of (0, import_spec4.expandViewContainer)(viewObject, item)) {
|
|
4891
5369
|
for (const w of vi._diagnostics?.warnings ?? []) {
|
|
4892
5370
|
ctx.logger.warn(`[MetadataPlugin] View expansion warning for '${vi.name}': ${w.message}`);
|
|
4893
5371
|
}
|
|
4894
|
-
(0,
|
|
4895
|
-
packageId
|
|
4896
|
-
packageVersion
|
|
5372
|
+
(0, import_shared3.applyProtection)(vi, {
|
|
5373
|
+
packageId,
|
|
5374
|
+
packageVersion
|
|
4897
5375
|
});
|
|
4898
5376
|
await memLoader.save("view", vi.name, vi);
|
|
4899
5377
|
await this.manager.register("view", vi.name, vi, { notify: false });
|
|
4900
5378
|
totalRegistered++;
|
|
5379
|
+
slots.claim?.("view", vi.name);
|
|
4901
5380
|
}
|
|
4902
5381
|
continue;
|
|
4903
5382
|
}
|
|
4904
5383
|
let name = item?.name;
|
|
4905
5384
|
if (!name) {
|
|
4906
5385
|
if (metaType === "view") {
|
|
4907
|
-
name = item
|
|
5386
|
+
name = deriveViewContainerObject(item);
|
|
4908
5387
|
}
|
|
4909
5388
|
}
|
|
4910
5389
|
if (!name) continue;
|
|
4911
|
-
(
|
|
4912
|
-
|
|
4913
|
-
|
|
5390
|
+
if (slots.skip?.(metaType, name)) continue;
|
|
5391
|
+
(0, import_shared3.applyProtection)(item, {
|
|
5392
|
+
packageId,
|
|
5393
|
+
packageVersion
|
|
4914
5394
|
});
|
|
4915
5395
|
await memLoader.save(metaType, name, item);
|
|
4916
5396
|
await this.manager.register(metaType, name, item, { notify: false });
|
|
4917
5397
|
totalRegistered++;
|
|
5398
|
+
slots.claim?.(metaType, name);
|
|
4918
5399
|
}
|
|
4919
5400
|
}
|
|
4920
|
-
this.manager.registerLoader(memLoader);
|
|
4921
|
-
ctx.logger.info("[MetadataPlugin] Artifact metadata loaded", { source: label, totalRegistered });
|
|
4922
5401
|
return totalRegistered;
|
|
4923
5402
|
}
|
|
4924
5403
|
/**
|
|
@@ -4994,7 +5473,7 @@ var MetadataPlugin = class {
|
|
|
4994
5473
|
for (const item of items) {
|
|
4995
5474
|
const meta = item;
|
|
4996
5475
|
if (meta?.name) {
|
|
4997
|
-
(0,
|
|
5476
|
+
(0, import_shared3.applyProtection)(meta, {
|
|
4998
5477
|
packageId: this.options.packageId
|
|
4999
5478
|
});
|
|
5000
5479
|
await this.manager.register(entry.type, meta.name, item, { notify: false });
|
|
@@ -5112,7 +5591,7 @@ var RemoteLoader = class {
|
|
|
5112
5591
|
};
|
|
5113
5592
|
|
|
5114
5593
|
// src/index.ts
|
|
5115
|
-
var
|
|
5594
|
+
var import_metadata_core4 = require("@objectstack/metadata-core");
|
|
5116
5595
|
|
|
5117
5596
|
// src/utils/history-cleanup.ts
|
|
5118
5597
|
var import_kernel3 = require("@objectstack/spec/kernel");
|
|
@@ -5399,6 +5878,7 @@ var MigrationExecutor = class {
|
|
|
5399
5878
|
TypeScriptSerializer,
|
|
5400
5879
|
YAMLSerializer,
|
|
5401
5880
|
calculateChecksum,
|
|
5881
|
+
deriveViewContainerObject,
|
|
5402
5882
|
generateDiffSummary,
|
|
5403
5883
|
generateSimpleDiff
|
|
5404
5884
|
});
|