@objectstack/metadata 4.2.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +292 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +77 -5
- package/dist/index.d.ts +77 -5
- package/dist/index.js +292 -64
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +292 -64
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -0
- package/dist/node.d.ts +1 -0
- package/dist/node.js +292 -64
- package/dist/node.js.map +1 -1
- package/package.json +7 -5
package/dist/node.cjs
CHANGED
|
@@ -64,7 +64,12 @@ function registerMetadataHmrRoutes(app, manager, options = {}) {
|
|
|
64
64
|
metadataType: evt.metadataType ?? type,
|
|
65
65
|
name: evt.name ?? "",
|
|
66
66
|
path: evt.path,
|
|
67
|
-
timestamp: Number.isFinite(ts) ? ts : Date.now()
|
|
67
|
+
timestamp: Number.isFinite(ts) ? ts : Date.now(),
|
|
68
|
+
// Forward the canonical server-side sequence number when the
|
|
69
|
+
// event originated from a MetadataRepository (ADR-0008). Legacy
|
|
70
|
+
// chokidar-driven events have no seq — clients fall back to
|
|
71
|
+
// their local counter in that case.
|
|
72
|
+
...typeof evt.seq === "number" ? { seq: evt.seq } : {}
|
|
68
73
|
});
|
|
69
74
|
});
|
|
70
75
|
}
|
|
@@ -605,7 +610,7 @@ var DatabaseLoader = class {
|
|
|
605
610
|
this.tableName = options.tableName ?? "sys_metadata";
|
|
606
611
|
this.historyTableName = options.historyTableName ?? "sys_metadata_history";
|
|
607
612
|
this.organizationId = options.organizationId;
|
|
608
|
-
|
|
613
|
+
void options.projectId;
|
|
609
614
|
this.trackHistory = options.trackHistory !== false;
|
|
610
615
|
const cacheOpts = options.cache;
|
|
611
616
|
const cacheEnabled = cacheOpts?.enabled !== false;
|
|
@@ -695,6 +700,26 @@ var DatabaseLoader = class {
|
|
|
695
700
|
}
|
|
696
701
|
return this.driver.delete(table, id);
|
|
697
702
|
}
|
|
703
|
+
/**
|
|
704
|
+
* Compute the next per-org `event_seq` for `sys_metadata_history`.
|
|
705
|
+
* Reads `MAX(event_seq) + 1` for the configured `organization_id`.
|
|
706
|
+
* Legacy path — not transactional, so concurrent writes can collide.
|
|
707
|
+
* The canonical (transactional) producer is `SysMetadataRepository`.
|
|
708
|
+
*/
|
|
709
|
+
async nextEventSeq() {
|
|
710
|
+
const where = this.organizationId ? { organization_id: this.organizationId } : {};
|
|
711
|
+
try {
|
|
712
|
+
const rows = await this._find(this.historyTableName, { where });
|
|
713
|
+
let max = 0;
|
|
714
|
+
for (const row of rows) {
|
|
715
|
+
const v = typeof row.event_seq === "number" ? row.event_seq : 0;
|
|
716
|
+
if (v > max) max = v;
|
|
717
|
+
}
|
|
718
|
+
return max + 1;
|
|
719
|
+
} catch {
|
|
720
|
+
return 1;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
698
723
|
/**
|
|
699
724
|
* Ensure the metadata table exists.
|
|
700
725
|
* Uses IDataDriver.syncSchema with the SysMetadataObject definition
|
|
@@ -759,8 +784,9 @@ var DatabaseLoader = class {
|
|
|
759
784
|
}
|
|
760
785
|
/**
|
|
761
786
|
* Build base filter conditions for queries.
|
|
762
|
-
* Filters by organizationId when configured
|
|
763
|
-
*
|
|
787
|
+
* Filters by organizationId when configured. `projectId` is accepted
|
|
788
|
+
* for back-compat but no longer constrains the query — see
|
|
789
|
+
* ADR-0008 §0 (branch/project removal).
|
|
764
790
|
*/
|
|
765
791
|
baseFilter(type, name) {
|
|
766
792
|
const filter = { type };
|
|
@@ -770,13 +796,11 @@ var DatabaseLoader = class {
|
|
|
770
796
|
if (this.organizationId) {
|
|
771
797
|
filter.organization_id = this.organizationId;
|
|
772
798
|
}
|
|
773
|
-
filter.project_id = this.projectId ?? null;
|
|
774
799
|
return filter;
|
|
775
800
|
}
|
|
776
801
|
/**
|
|
777
802
|
* Create a history record for a metadata change.
|
|
778
803
|
*
|
|
779
|
-
* @param metadataId - The metadata record ID
|
|
780
804
|
* @param type - Metadata type
|
|
781
805
|
* @param name - Metadata name
|
|
782
806
|
* @param version - Version number
|
|
@@ -786,7 +810,7 @@ var DatabaseLoader = class {
|
|
|
786
810
|
* @param changeNote - Optional change description
|
|
787
811
|
* @param recordedBy - Optional user who made the change
|
|
788
812
|
*/
|
|
789
|
-
async createHistoryRecord(
|
|
813
|
+
async createHistoryRecord(type, name, version, metadata, operationType, previousChecksum, changeNote, recordedBy) {
|
|
790
814
|
if (!this.trackHistory) return;
|
|
791
815
|
await this.ensureHistorySchema();
|
|
792
816
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -796,9 +820,9 @@ var DatabaseLoader = class {
|
|
|
796
820
|
}
|
|
797
821
|
const historyId = generateId();
|
|
798
822
|
const metadataJson = JSON.stringify(metadata);
|
|
823
|
+
const eventSeq = await this.nextEventSeq();
|
|
799
824
|
const historyRecord = {
|
|
800
825
|
id: historyId,
|
|
801
|
-
metadataId,
|
|
802
826
|
name,
|
|
803
827
|
type,
|
|
804
828
|
version,
|
|
@@ -809,13 +833,12 @@ var DatabaseLoader = class {
|
|
|
809
833
|
changeNote,
|
|
810
834
|
recordedBy,
|
|
811
835
|
recordedAt: now,
|
|
812
|
-
...this.organizationId ? { organizationId: this.organizationId } : {}
|
|
813
|
-
...this.projectId !== void 0 ? { projectId: this.projectId } : {}
|
|
836
|
+
...this.organizationId ? { organizationId: this.organizationId } : {}
|
|
814
837
|
};
|
|
815
838
|
try {
|
|
816
839
|
await this._create(this.historyTableName, {
|
|
817
840
|
id: historyRecord.id,
|
|
818
|
-
|
|
841
|
+
event_seq: eventSeq,
|
|
819
842
|
name: historyRecord.name,
|
|
820
843
|
type: historyRecord.type,
|
|
821
844
|
version: historyRecord.version,
|
|
@@ -826,8 +849,8 @@ var DatabaseLoader = class {
|
|
|
826
849
|
change_note: historyRecord.changeNote,
|
|
827
850
|
recorded_by: historyRecord.recordedBy,
|
|
828
851
|
recorded_at: historyRecord.recordedAt,
|
|
829
|
-
|
|
830
|
-
...this.
|
|
852
|
+
source: "database-loader",
|
|
853
|
+
...this.organizationId ? { organization_id: this.organizationId } : {}
|
|
831
854
|
});
|
|
832
855
|
} catch (error) {
|
|
833
856
|
console.error(`Failed to create history record for ${type}/${name}:`, error);
|
|
@@ -1003,25 +1026,20 @@ var DatabaseLoader = class {
|
|
|
1003
1026
|
async getHistoryRecord(type, name, version) {
|
|
1004
1027
|
if (!this.trackHistory) return null;
|
|
1005
1028
|
await this.ensureHistorySchema();
|
|
1006
|
-
const metadataRow = await this._findOne(this.tableName, {
|
|
1007
|
-
where: this.baseFilter(type, name)
|
|
1008
|
-
});
|
|
1009
|
-
if (!metadataRow) return null;
|
|
1010
1029
|
const filter = {
|
|
1011
|
-
|
|
1030
|
+
type,
|
|
1031
|
+
name,
|
|
1012
1032
|
version
|
|
1013
1033
|
};
|
|
1014
1034
|
if (this.organizationId) {
|
|
1015
1035
|
filter.organization_id = this.organizationId;
|
|
1016
1036
|
}
|
|
1017
|
-
filter.project_id = this.projectId ?? null;
|
|
1018
1037
|
const row = await this._findOne(this.historyTableName, {
|
|
1019
1038
|
where: filter
|
|
1020
1039
|
});
|
|
1021
1040
|
if (!row) return null;
|
|
1022
1041
|
return {
|
|
1023
1042
|
id: row.id,
|
|
1024
|
-
metadataId: row.metadata_id,
|
|
1025
1043
|
name: row.name,
|
|
1026
1044
|
type: row.type,
|
|
1027
1045
|
version: row.version,
|
|
@@ -1031,7 +1049,6 @@ var DatabaseLoader = class {
|
|
|
1031
1049
|
previousChecksum: row.previous_checksum,
|
|
1032
1050
|
changeNote: row.change_note,
|
|
1033
1051
|
organizationId: row.organization_id,
|
|
1034
|
-
projectId: row.project_id,
|
|
1035
1052
|
recordedBy: row.recorded_by,
|
|
1036
1053
|
recordedAt: row.recorded_at
|
|
1037
1054
|
};
|
|
@@ -1047,18 +1064,11 @@ var DatabaseLoader = class {
|
|
|
1047
1064
|
}
|
|
1048
1065
|
await this.ensureSchema();
|
|
1049
1066
|
await this.ensureHistorySchema();
|
|
1050
|
-
const filter = { type, name };
|
|
1051
|
-
if (this.organizationId) filter.organization_id = this.organizationId;
|
|
1052
|
-
filter.project_id = this.projectId ?? null;
|
|
1053
|
-
const metadataRecord = await this._findOne(this.tableName, { where: filter });
|
|
1054
|
-
if (!metadataRecord) {
|
|
1055
|
-
return { records: [], total: 0, hasMore: false };
|
|
1056
|
-
}
|
|
1057
1067
|
const historyFilter = {
|
|
1058
|
-
|
|
1068
|
+
type,
|
|
1069
|
+
name
|
|
1059
1070
|
};
|
|
1060
1071
|
if (this.organizationId) historyFilter.organization_id = this.organizationId;
|
|
1061
|
-
historyFilter.project_id = this.projectId ?? null;
|
|
1062
1072
|
if (options?.operationType) historyFilter.operation_type = options.operationType;
|
|
1063
1073
|
if (options?.since) historyFilter.recorded_at = { $gte: options.since };
|
|
1064
1074
|
if (options?.until) {
|
|
@@ -1087,7 +1097,6 @@ var DatabaseLoader = class {
|
|
|
1087
1097
|
const parsedMetadata = typeof row.metadata === "string" ? JSON.parse(row.metadata) : row.metadata;
|
|
1088
1098
|
return {
|
|
1089
1099
|
id: row.id,
|
|
1090
|
-
metadataId: row.metadata_id,
|
|
1091
1100
|
name: row.name,
|
|
1092
1101
|
type: row.type,
|
|
1093
1102
|
version: row.version,
|
|
@@ -1097,7 +1106,6 @@ var DatabaseLoader = class {
|
|
|
1097
1106
|
previousChecksum: row.previous_checksum,
|
|
1098
1107
|
changeNote: row.change_note,
|
|
1099
1108
|
organizationId: row.organization_id,
|
|
1100
|
-
projectId: row.project_id,
|
|
1101
1109
|
recordedBy: row.recorded_by,
|
|
1102
1110
|
recordedAt: row.recorded_at
|
|
1103
1111
|
};
|
|
@@ -1133,7 +1141,6 @@ var DatabaseLoader = class {
|
|
|
1133
1141
|
});
|
|
1134
1142
|
this.invalidate(type, name);
|
|
1135
1143
|
await this.createHistoryRecord(
|
|
1136
|
-
existing.id,
|
|
1137
1144
|
type,
|
|
1138
1145
|
name,
|
|
1139
1146
|
newVersion,
|
|
@@ -1175,7 +1182,6 @@ var DatabaseLoader = class {
|
|
|
1175
1182
|
});
|
|
1176
1183
|
this.invalidate(type, name);
|
|
1177
1184
|
await this.createHistoryRecord(
|
|
1178
|
-
existing.id,
|
|
1179
1185
|
type,
|
|
1180
1186
|
name,
|
|
1181
1187
|
version,
|
|
@@ -1204,13 +1210,11 @@ var DatabaseLoader = class {
|
|
|
1204
1210
|
version: 1,
|
|
1205
1211
|
source: "database",
|
|
1206
1212
|
...this.organizationId ? { organization_id: this.organizationId } : {},
|
|
1207
|
-
...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
|
|
1208
1213
|
created_at: now,
|
|
1209
1214
|
updated_at: now
|
|
1210
1215
|
});
|
|
1211
1216
|
this.invalidate(type, name);
|
|
1212
1217
|
await this.createHistoryRecord(
|
|
1213
|
-
id,
|
|
1214
1218
|
type,
|
|
1215
1219
|
name,
|
|
1216
1220
|
1,
|
|
@@ -1277,6 +1281,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1277
1281
|
// Invalidated on every `register()` / `unregister()` to keep CRUD writes
|
|
1278
1282
|
// visible to subsequent reads.
|
|
1279
1283
|
this.listCache = /* @__PURE__ */ new Map();
|
|
1284
|
+
this.repoWatchClosed = false;
|
|
1280
1285
|
this.config = config;
|
|
1281
1286
|
this.logger = (0, import_core.createLogger)({ level: "info", format: "pretty" });
|
|
1282
1287
|
this.serializers = /* @__PURE__ */ new Map();
|
|
@@ -2284,6 +2289,110 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2284
2289
|
*/
|
|
2285
2290
|
async stopWatching() {
|
|
2286
2291
|
}
|
|
2292
|
+
// ─── ADR-0008 PR-6: Repository wiring ───────────────────────────────
|
|
2293
|
+
/**
|
|
2294
|
+
* Attach a {@link MetadataRepository} as a supplementary event source.
|
|
2295
|
+
*
|
|
2296
|
+
* The manager subscribes to `repo.watch({})` and re-emits each event
|
|
2297
|
+
* through {@link notifyWatchers} as a legacy `MetadataWatchEvent`.
|
|
2298
|
+
* Each event also invalidates the in-memory registry entry and the
|
|
2299
|
+
* `list()` cache for the affected type so subsequent reads see fresh
|
|
2300
|
+
* data.
|
|
2301
|
+
*
|
|
2302
|
+
* No write-through. `register()` / `unregister()` / `save()` are
|
|
2303
|
+
* untouched in this PR (deferred to ADR-0008 M0 PR-10).
|
|
2304
|
+
*
|
|
2305
|
+
* Call {@link dispose} (or {@link stopRepositoryWatch}) to detach.
|
|
2306
|
+
*/
|
|
2307
|
+
setRepository(repo) {
|
|
2308
|
+
if (this.repository === repo) return;
|
|
2309
|
+
if (this.repository) {
|
|
2310
|
+
void this.stopRepositoryWatch();
|
|
2311
|
+
}
|
|
2312
|
+
this.repository = repo;
|
|
2313
|
+
this.repoWatchClosed = false;
|
|
2314
|
+
void this.startRepositoryWatch();
|
|
2315
|
+
}
|
|
2316
|
+
/** Return the attached repository, if any. */
|
|
2317
|
+
getRepository() {
|
|
2318
|
+
return this.repository;
|
|
2319
|
+
}
|
|
2320
|
+
/** Stop the active repo.watch() loop (best-effort). */
|
|
2321
|
+
async stopRepositoryWatch() {
|
|
2322
|
+
this.repoWatchClosed = true;
|
|
2323
|
+
const iter = this.repoWatchIter;
|
|
2324
|
+
this.repoWatchIter = void 0;
|
|
2325
|
+
if (iter && typeof iter.return === "function") {
|
|
2326
|
+
try {
|
|
2327
|
+
await iter.return(void 0);
|
|
2328
|
+
} catch {
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
/**
|
|
2333
|
+
* Best-effort cleanup. Stops the FS watcher (if any), drains the
|
|
2334
|
+
* repository watch loop, and clears registry caches. Safe to call
|
|
2335
|
+
* multiple times.
|
|
2336
|
+
*/
|
|
2337
|
+
async dispose() {
|
|
2338
|
+
await this.stopWatching().catch(() => void 0);
|
|
2339
|
+
await this.stopRepositoryWatch().catch(() => void 0);
|
|
2340
|
+
this.listCache.clear();
|
|
2341
|
+
}
|
|
2342
|
+
async startRepositoryWatch() {
|
|
2343
|
+
const repo = this.repository;
|
|
2344
|
+
if (!repo) return;
|
|
2345
|
+
const iterable = repo.watch({});
|
|
2346
|
+
const iter = iterable[Symbol.asyncIterator]();
|
|
2347
|
+
this.repoWatchIter = iter;
|
|
2348
|
+
try {
|
|
2349
|
+
while (!this.repoWatchClosed) {
|
|
2350
|
+
const { value, done } = await iter.next();
|
|
2351
|
+
if (done) break;
|
|
2352
|
+
try {
|
|
2353
|
+
this.applyRepoEvent(value);
|
|
2354
|
+
} catch (err) {
|
|
2355
|
+
this.logger.warn("[MetadataManager] repo event handler failed", {
|
|
2356
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2357
|
+
});
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
} catch (err) {
|
|
2361
|
+
if (!this.repoWatchClosed) {
|
|
2362
|
+
this.logger.warn("[MetadataManager] repository watch loop exited unexpectedly", {
|
|
2363
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
} finally {
|
|
2367
|
+
if (this.repoWatchIter === iter) this.repoWatchIter = void 0;
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
/** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
|
|
2371
|
+
applyRepoEvent(evt) {
|
|
2372
|
+
const ref = evt.ref;
|
|
2373
|
+
const type = ref.type;
|
|
2374
|
+
const name = ref.name;
|
|
2375
|
+
const typeStore = this.registry.get(type);
|
|
2376
|
+
if (typeStore) {
|
|
2377
|
+
typeStore.delete(name);
|
|
2378
|
+
if (typeStore.size === 0) this.registry.delete(type);
|
|
2379
|
+
}
|
|
2380
|
+
this.listCache.delete(type);
|
|
2381
|
+
const legacyType = evt.op === "create" ? "added" : evt.op === "delete" ? "deleted" : "changed";
|
|
2382
|
+
const legacyEvent = {
|
|
2383
|
+
type: legacyType,
|
|
2384
|
+
metadataType: type,
|
|
2385
|
+
name,
|
|
2386
|
+
path: "",
|
|
2387
|
+
// Repo events carry the hash only; the body is fetched on demand
|
|
2388
|
+
// via manager.get(type, name). HMR consumers don't read `data` so
|
|
2389
|
+
// this is fine for M0. (See ADR-0008 §12 open question 1.)
|
|
2390
|
+
data: void 0,
|
|
2391
|
+
timestamp: evt.ts
|
|
2392
|
+
};
|
|
2393
|
+
legacyEvent.seq = evt.seq;
|
|
2394
|
+
this.notifyWatchers(type, legacyEvent);
|
|
2395
|
+
}
|
|
2287
2396
|
notifyWatchers(type, event) {
|
|
2288
2397
|
const callbacks = this.watchCallbacks.get(type);
|
|
2289
2398
|
if (!callbacks) return;
|
|
@@ -2759,7 +2868,13 @@ var NodeMetadataManager = class extends MetadataManager {
|
|
|
2759
2868
|
this.watcher = (0, import_chokidar.watch)(rootDir, {
|
|
2760
2869
|
ignored,
|
|
2761
2870
|
persistent,
|
|
2762
|
-
ignoreInitial: true
|
|
2871
|
+
ignoreInitial: true,
|
|
2872
|
+
// Use polling to avoid `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2873
|
+
// Recursive watch over a project root would otherwise wire native
|
|
2874
|
+
// watches across the entire tree, easily exhausting the FD pool.
|
|
2875
|
+
usePolling: true,
|
|
2876
|
+
interval: 1e3,
|
|
2877
|
+
binaryInterval: 2e3
|
|
2763
2878
|
});
|
|
2764
2879
|
this.watcher.on("add", async (filePath) => {
|
|
2765
2880
|
await this.handleFileEvent("added", filePath);
|
|
@@ -2894,6 +3009,7 @@ var queryableMetadataObjects = [
|
|
|
2894
3009
|
import_metadata2.SysMetadataObject,
|
|
2895
3010
|
import_metadata2.SysMetadataHistoryObject
|
|
2896
3011
|
];
|
|
3012
|
+
var REPO_SUBDIR = ".objectstack/metadata";
|
|
2897
3013
|
var ARTIFACT_FIELD_TO_TYPE = {
|
|
2898
3014
|
objects: "object",
|
|
2899
3015
|
objectExtensions: "object_extension",
|
|
@@ -2991,6 +3107,31 @@ var MetadataPlugin = class {
|
|
|
2991
3107
|
await this._loadFromFileSystem(ctx);
|
|
2992
3108
|
}
|
|
2993
3109
|
}
|
|
3110
|
+
const bootstrapMode = this.options.config?.bootstrap ?? "eager";
|
|
3111
|
+
if (bootstrapMode !== "artifact-only") {
|
|
3112
|
+
try {
|
|
3113
|
+
const path3 = await import("path");
|
|
3114
|
+
const { FileSystemRepository } = await import("@objectstack/metadata-fs");
|
|
3115
|
+
const rootDir = this.options.rootDir || process.cwd();
|
|
3116
|
+
const repoRoot = path3.join(rootDir, REPO_SUBDIR);
|
|
3117
|
+
const repo = new FileSystemRepository({
|
|
3118
|
+
root: repoRoot,
|
|
3119
|
+
org: this.options.organizationId ?? "system",
|
|
3120
|
+
disableWatch: this.options.watch === false
|
|
3121
|
+
});
|
|
3122
|
+
await repo.start();
|
|
3123
|
+
this.repository = repo;
|
|
3124
|
+
this.manager.setRepository(repo);
|
|
3125
|
+
ctx.logger.info("[MetadataPlugin] FileSystemRepository attached", {
|
|
3126
|
+
repoRoot,
|
|
3127
|
+
watch: this.options.watch !== false
|
|
3128
|
+
});
|
|
3129
|
+
} catch (e) {
|
|
3130
|
+
ctx.logger.warn("[MetadataPlugin] Failed to attach FileSystemRepository", {
|
|
3131
|
+
error: e?.message
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
2994
3135
|
try {
|
|
2995
3136
|
const realtimeService = ctx.getService("realtime");
|
|
2996
3137
|
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
@@ -3008,12 +3149,12 @@ var MetadataPlugin = class {
|
|
|
3008
3149
|
const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
|
|
3009
3150
|
const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
|
|
3010
3151
|
hub.setOnPostReload(async (body = {}) => {
|
|
3011
|
-
const
|
|
3012
|
-
if (
|
|
3152
|
+
const src3 = this.options.artifactSource;
|
|
3153
|
+
if (src3?.mode === "local-file") {
|
|
3013
3154
|
try {
|
|
3014
|
-
await this._loadFromLocalFile(ctx,
|
|
3155
|
+
await this._loadFromLocalFile(ctx, src3.path, src3.fetchTimeoutMs);
|
|
3015
3156
|
ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
|
|
3016
|
-
path:
|
|
3157
|
+
path: src3.path,
|
|
3017
3158
|
reason: body?.reason
|
|
3018
3159
|
});
|
|
3019
3160
|
} catch (e) {
|
|
@@ -3022,6 +3163,53 @@ var MetadataPlugin = class {
|
|
|
3022
3163
|
}
|
|
3023
3164
|
}
|
|
3024
3165
|
});
|
|
3166
|
+
const src2 = this.options.artifactSource;
|
|
3167
|
+
const wantArtifactWatch = this.options.artifactWatch ?? src2?.mode === "local-file";
|
|
3168
|
+
if (src2?.mode === "local-file" && wantArtifactWatch && !/^https?:\/\//i.test(src2.path)) {
|
|
3169
|
+
try {
|
|
3170
|
+
const { watch: chokidarWatch2 } = await import("chokidar");
|
|
3171
|
+
const w = chokidarWatch2(src2.path, {
|
|
3172
|
+
ignoreInitial: true,
|
|
3173
|
+
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 },
|
|
3174
|
+
persistent: true,
|
|
3175
|
+
// Use polling to avoid `fs.watch` exhausting the
|
|
3176
|
+
// process file-descriptor limit on macOS (chokidar
|
|
3177
|
+
// recursively wires watches on the parent
|
|
3178
|
+
// directory tree which can trip EMFILE on busy
|
|
3179
|
+
// dev hosts). 500ms polling is fast enough for
|
|
3180
|
+
// HMR (a recompile takes ~400ms anyway).
|
|
3181
|
+
usePolling: true,
|
|
3182
|
+
interval: 500,
|
|
3183
|
+
binaryInterval: 1e3
|
|
3184
|
+
});
|
|
3185
|
+
let pending = false;
|
|
3186
|
+
const reload = async () => {
|
|
3187
|
+
if (pending) return;
|
|
3188
|
+
pending = true;
|
|
3189
|
+
try {
|
|
3190
|
+
await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
|
|
3191
|
+
hub.broadcastReload("artifact-file-changed", [src2.path]);
|
|
3192
|
+
ctx.logger.info("[MetadataPlugin] artifact auto-reloaded (file watcher)", {
|
|
3193
|
+
path: src2.path
|
|
3194
|
+
});
|
|
3195
|
+
} catch (e) {
|
|
3196
|
+
ctx.logger.warn("[MetadataPlugin] artifact auto-reload failed", { error: e?.message });
|
|
3197
|
+
} finally {
|
|
3198
|
+
pending = false;
|
|
3199
|
+
}
|
|
3200
|
+
};
|
|
3201
|
+
w.on("change", () => {
|
|
3202
|
+
void reload();
|
|
3203
|
+
});
|
|
3204
|
+
w.on("add", () => {
|
|
3205
|
+
void reload();
|
|
3206
|
+
});
|
|
3207
|
+
this.artifactWatcher = { close: () => w.close() };
|
|
3208
|
+
console.log("[MetadataPlugin] artifact file watcher attached", src2.path);
|
|
3209
|
+
} catch (e) {
|
|
3210
|
+
ctx.logger.warn("[MetadataPlugin] artifact watcher failed to start", { error: e?.message });
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3025
3213
|
console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
|
|
3026
3214
|
} else {
|
|
3027
3215
|
console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
|
|
@@ -3030,6 +3218,28 @@ var MetadataPlugin = class {
|
|
|
3030
3218
|
console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
|
|
3031
3219
|
}
|
|
3032
3220
|
};
|
|
3221
|
+
this.stop = async (ctx) => {
|
|
3222
|
+
if (this.artifactWatcher) {
|
|
3223
|
+
try {
|
|
3224
|
+
await this.artifactWatcher.close();
|
|
3225
|
+
} catch {
|
|
3226
|
+
}
|
|
3227
|
+
this.artifactWatcher = void 0;
|
|
3228
|
+
}
|
|
3229
|
+
try {
|
|
3230
|
+
await this.manager.dispose();
|
|
3231
|
+
} catch (e) {
|
|
3232
|
+
ctx.logger.warn("[MetadataPlugin] manager.dispose() failed", { error: e?.message });
|
|
3233
|
+
}
|
|
3234
|
+
const repo = this.repository;
|
|
3235
|
+
if (repo && typeof repo.close === "function") {
|
|
3236
|
+
try {
|
|
3237
|
+
await repo.close();
|
|
3238
|
+
} catch {
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
this.repository = void 0;
|
|
3242
|
+
};
|
|
3033
3243
|
this.options = {
|
|
3034
3244
|
watch: true,
|
|
3035
3245
|
...options
|
|
@@ -3106,7 +3316,12 @@ var MetadataPlugin = class {
|
|
|
3106
3316
|
const items = metadata[field];
|
|
3107
3317
|
if (!Array.isArray(items) || items.length === 0) continue;
|
|
3108
3318
|
for (const item of items) {
|
|
3109
|
-
|
|
3319
|
+
let name = item?.name;
|
|
3320
|
+
if (!name) {
|
|
3321
|
+
if (metaType === "view") {
|
|
3322
|
+
name = item?.list?.data?.object ?? item?.form?.data?.object;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3110
3325
|
if (!name) continue;
|
|
3111
3326
|
if (manifestPackageId && item._packageId === void 0) {
|
|
3112
3327
|
item._packageId = manifestPackageId;
|
|
@@ -3415,6 +3630,10 @@ function registerMetadataHistoryRoutes(app, metadataService) {
|
|
|
3415
3630
|
}
|
|
3416
3631
|
|
|
3417
3632
|
// src/utils/history-cleanup.ts
|
|
3633
|
+
var import_kernel2 = require("@objectstack/spec/kernel");
|
|
3634
|
+
function executionPinnedTypes() {
|
|
3635
|
+
return import_kernel2.DEFAULT_METADATA_TYPE_REGISTRY.filter((entry) => entry.executionPinned).map((entry) => entry.type);
|
|
3636
|
+
}
|
|
3418
3637
|
var HistoryCleanupManager = class {
|
|
3419
3638
|
constructor(policy, dbLoader) {
|
|
3420
3639
|
this.policy = policy;
|
|
@@ -3450,9 +3669,10 @@ var HistoryCleanupManager = class {
|
|
|
3450
3669
|
const driver = this.dbLoader.driver;
|
|
3451
3670
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3452
3671
|
const organizationId = this.dbLoader.organizationId;
|
|
3453
|
-
const projectId = this.dbLoader.projectId;
|
|
3454
3672
|
let deleted = 0;
|
|
3455
3673
|
let errors = 0;
|
|
3674
|
+
const pinnedTypes = executionPinnedTypes();
|
|
3675
|
+
const isPinned = (t) => !!t && pinnedTypes.includes(t);
|
|
3456
3676
|
try {
|
|
3457
3677
|
if (this.policy.maxAgeDays) {
|
|
3458
3678
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
@@ -3464,8 +3684,8 @@ var HistoryCleanupManager = class {
|
|
|
3464
3684
|
if (organizationId) {
|
|
3465
3685
|
filter.organization_id = organizationId;
|
|
3466
3686
|
}
|
|
3467
|
-
if (
|
|
3468
|
-
filter.
|
|
3687
|
+
if (pinnedTypes.length > 0) {
|
|
3688
|
+
filter.type = { $nin: pinnedTypes };
|
|
3469
3689
|
}
|
|
3470
3690
|
try {
|
|
3471
3691
|
const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
|
|
@@ -3479,20 +3699,22 @@ var HistoryCleanupManager = class {
|
|
|
3479
3699
|
try {
|
|
3480
3700
|
const baseWhere = {};
|
|
3481
3701
|
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3482
|
-
|
|
3483
|
-
const metadataIds = await driver.find(historyTableName, {
|
|
3702
|
+
const metaItems = await driver.find(historyTableName, {
|
|
3484
3703
|
object: historyTableName,
|
|
3485
3704
|
where: baseWhere,
|
|
3486
|
-
fields: ["
|
|
3705
|
+
fields: ["type", "name"]
|
|
3487
3706
|
});
|
|
3488
|
-
const
|
|
3489
|
-
for (const record of
|
|
3490
|
-
|
|
3491
|
-
|
|
3707
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
3708
|
+
for (const record of metaItems) {
|
|
3709
|
+
const t = record.type;
|
|
3710
|
+
const n = record.name;
|
|
3711
|
+
if (t && n && !isPinned(t)) {
|
|
3712
|
+
uniqueKeys.add(`${t}${n}`);
|
|
3492
3713
|
}
|
|
3493
3714
|
}
|
|
3494
|
-
for (const
|
|
3495
|
-
const
|
|
3715
|
+
for (const key of uniqueKeys) {
|
|
3716
|
+
const [type, name] = key.split("");
|
|
3717
|
+
const filter = { type, name, ...baseWhere };
|
|
3496
3718
|
try {
|
|
3497
3719
|
const historyRecords = await driver.find(historyTableName, {
|
|
3498
3720
|
object: historyTableName,
|
|
@@ -3567,13 +3789,13 @@ var HistoryCleanupManager = class {
|
|
|
3567
3789
|
const driver = this.dbLoader.driver;
|
|
3568
3790
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3569
3791
|
const organizationId = this.dbLoader.organizationId;
|
|
3570
|
-
const projectId = this.dbLoader.projectId;
|
|
3571
3792
|
let recordsByAge = 0;
|
|
3572
3793
|
let recordsByCount = 0;
|
|
3794
|
+
const pinnedTypes = executionPinnedTypes();
|
|
3795
|
+
const isPinned = (t) => !!t && pinnedTypes.includes(t);
|
|
3573
3796
|
try {
|
|
3574
3797
|
const baseWhere = {};
|
|
3575
3798
|
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3576
|
-
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3577
3799
|
if (this.policy.maxAgeDays) {
|
|
3578
3800
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
3579
3801
|
cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
|
|
@@ -3582,25 +3804,31 @@ var HistoryCleanupManager = class {
|
|
|
3582
3804
|
recorded_at: { $lt: cutoffISO },
|
|
3583
3805
|
...baseWhere
|
|
3584
3806
|
};
|
|
3807
|
+
if (pinnedTypes.length > 0) {
|
|
3808
|
+
filter.type = { $nin: pinnedTypes };
|
|
3809
|
+
}
|
|
3585
3810
|
recordsByAge = await driver.count(historyTableName, {
|
|
3586
3811
|
object: historyTableName,
|
|
3587
3812
|
where: filter
|
|
3588
3813
|
});
|
|
3589
3814
|
}
|
|
3590
3815
|
if (this.policy.maxVersions) {
|
|
3591
|
-
const
|
|
3816
|
+
const metaItems = await driver.find(historyTableName, {
|
|
3592
3817
|
object: historyTableName,
|
|
3593
3818
|
where: baseWhere,
|
|
3594
|
-
fields: ["
|
|
3819
|
+
fields: ["type", "name"]
|
|
3595
3820
|
});
|
|
3596
|
-
const
|
|
3597
|
-
for (const record of
|
|
3598
|
-
|
|
3599
|
-
|
|
3821
|
+
const uniqueKeys = /* @__PURE__ */ new Set();
|
|
3822
|
+
for (const record of metaItems) {
|
|
3823
|
+
const t = record.type;
|
|
3824
|
+
const n = record.name;
|
|
3825
|
+
if (t && n && !isPinned(t)) {
|
|
3826
|
+
uniqueKeys.add(`${t}${n}`);
|
|
3600
3827
|
}
|
|
3601
3828
|
}
|
|
3602
|
-
for (const
|
|
3603
|
-
const
|
|
3829
|
+
for (const key of uniqueKeys) {
|
|
3830
|
+
const [type, name] = key.split("");
|
|
3831
|
+
const filter = { type, name, ...baseWhere };
|
|
3604
3832
|
const count = await driver.count(historyTableName, {
|
|
3605
3833
|
object: historyTableName,
|
|
3606
3834
|
where: filter
|