@objectstack/metadata 4.1.1 → 5.0.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 +441 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -4
- package/dist/index.d.ts +89 -4
- package/dist/index.js +442 -23
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +441 -23
- 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 +442 -23
- package/dist/node.js.map +1 -1
- package/package.json +7 -5
package/dist/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,6 +30,160 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
30
|
));
|
|
28
31
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
32
|
|
|
33
|
+
// src/routes/hmr-routes.ts
|
|
34
|
+
var hmr_routes_exports = {};
|
|
35
|
+
__export(hmr_routes_exports, {
|
|
36
|
+
registerMetadataHmrRoutes: () => registerMetadataHmrRoutes
|
|
37
|
+
});
|
|
38
|
+
function registerMetadataHmrRoutes(app, manager, options = {}) {
|
|
39
|
+
const routePath = options.path ?? "/api/v1/dev/metadata-events";
|
|
40
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
41
|
+
const broadcast = (evt) => {
|
|
42
|
+
for (const l of listeners) {
|
|
43
|
+
try {
|
|
44
|
+
l(evt);
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
let fsHookInstalled = false;
|
|
50
|
+
const installFsHooks = async () => {
|
|
51
|
+
if (fsHookInstalled) return;
|
|
52
|
+
const mgr = manager;
|
|
53
|
+
if (typeof mgr.subscribe !== "function") {
|
|
54
|
+
fsHookInstalled = true;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const types = await manager.getRegisteredTypes();
|
|
58
|
+
for (const type of types) {
|
|
59
|
+
mgr.subscribe(type, (evt) => {
|
|
60
|
+
const ts = typeof evt.timestamp === "string" ? Date.parse(evt.timestamp) : evt.timestamp ?? Date.now();
|
|
61
|
+
broadcast({
|
|
62
|
+
kind: "metadata-change",
|
|
63
|
+
type: evt.type ?? "changed",
|
|
64
|
+
metadataType: evt.metadataType ?? type,
|
|
65
|
+
name: evt.name ?? "",
|
|
66
|
+
path: evt.path,
|
|
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 } : {}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
fsHookInstalled = true;
|
|
77
|
+
};
|
|
78
|
+
installFsHooks().catch(() => {
|
|
79
|
+
});
|
|
80
|
+
let onPostReload = null;
|
|
81
|
+
app.get(routePath, async (c) => {
|
|
82
|
+
await installFsHooks().catch(() => {
|
|
83
|
+
});
|
|
84
|
+
const types = await manager.getRegisteredTypes().catch(() => []);
|
|
85
|
+
const stream = new ReadableStream({
|
|
86
|
+
async start(controller) {
|
|
87
|
+
const enc = new TextEncoder();
|
|
88
|
+
let closed = false;
|
|
89
|
+
const safeEnqueue = (chunk) => {
|
|
90
|
+
if (closed) return;
|
|
91
|
+
try {
|
|
92
|
+
controller.enqueue(enc.encode(chunk));
|
|
93
|
+
} catch {
|
|
94
|
+
closed = true;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const listener = (evt) => {
|
|
98
|
+
if (closed) return;
|
|
99
|
+
const eventName = evt.kind === "reload" ? "reload" : "metadata-change";
|
|
100
|
+
safeEnqueue(`event: ${eventName}
|
|
101
|
+
data: ${JSON.stringify(evt)}
|
|
102
|
+
|
|
103
|
+
`);
|
|
104
|
+
};
|
|
105
|
+
listeners.add(listener);
|
|
106
|
+
safeEnqueue(`event: ready
|
|
107
|
+
data: ${JSON.stringify({ types, timestamp: Date.now() })}
|
|
108
|
+
|
|
109
|
+
`);
|
|
110
|
+
const heartbeat = setInterval(() => {
|
|
111
|
+
safeEnqueue(`: ping ${Date.now()}
|
|
112
|
+
|
|
113
|
+
`);
|
|
114
|
+
}, 15e3);
|
|
115
|
+
const cleanup = () => {
|
|
116
|
+
if (closed) return;
|
|
117
|
+
closed = true;
|
|
118
|
+
clearInterval(heartbeat);
|
|
119
|
+
listeners.delete(listener);
|
|
120
|
+
try {
|
|
121
|
+
controller.close();
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const signal = c.req?.raw?.signal;
|
|
126
|
+
if (signal) {
|
|
127
|
+
if (signal.aborted) cleanup();
|
|
128
|
+
else signal.addEventListener("abort", cleanup, { once: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
return new Response(stream, {
|
|
133
|
+
status: 200,
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
136
|
+
"Cache-Control": "no-cache, no-transform",
|
|
137
|
+
"Connection": "keep-alive",
|
|
138
|
+
"X-Accel-Buffering": "no"
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
app.post(routePath, async (c) => {
|
|
143
|
+
let body = {};
|
|
144
|
+
try {
|
|
145
|
+
const ct = c.req?.header?.("content-type") ?? "";
|
|
146
|
+
if (typeof c.req?.json === "function" && ct.includes("json")) {
|
|
147
|
+
body = await c.req.json();
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
if (onPostReload) await onPostReload(body);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
return new Response(
|
|
155
|
+
JSON.stringify({ ok: false, error: e?.message ?? "reload handler failed" }),
|
|
156
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const reason = body.reason ?? "manual-trigger";
|
|
160
|
+
broadcast({
|
|
161
|
+
kind: "reload",
|
|
162
|
+
reason,
|
|
163
|
+
changed: body.changed,
|
|
164
|
+
timestamp: Date.now()
|
|
165
|
+
});
|
|
166
|
+
return new Response(
|
|
167
|
+
JSON.stringify({ ok: true, listeners: listeners.size, reason }),
|
|
168
|
+
{ status: 200, headers: { "Content-Type": "application/json" } }
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
broadcastReload(reason, changed) {
|
|
173
|
+
broadcast({ kind: "reload", reason, changed, timestamp: Date.now() });
|
|
174
|
+
},
|
|
175
|
+
setOnPostReload(fn) {
|
|
176
|
+
onPostReload = fn;
|
|
177
|
+
},
|
|
178
|
+
listenerCount: () => listeners.size
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
var init_hmr_routes = __esm({
|
|
182
|
+
"src/routes/hmr-routes.ts"() {
|
|
183
|
+
"use strict";
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
30
187
|
// src/index.ts
|
|
31
188
|
var index_exports = {};
|
|
32
189
|
__export(index_exports, {
|
|
@@ -451,7 +608,7 @@ var DatabaseLoader = class {
|
|
|
451
608
|
this.tableName = options.tableName ?? "sys_metadata";
|
|
452
609
|
this.historyTableName = options.historyTableName ?? "sys_metadata_history";
|
|
453
610
|
this.organizationId = options.organizationId;
|
|
454
|
-
|
|
611
|
+
void options.projectId;
|
|
455
612
|
this.trackHistory = options.trackHistory !== false;
|
|
456
613
|
const cacheOpts = options.cache;
|
|
457
614
|
const cacheEnabled = cacheOpts?.enabled !== false;
|
|
@@ -541,6 +698,26 @@ var DatabaseLoader = class {
|
|
|
541
698
|
}
|
|
542
699
|
return this.driver.delete(table, id);
|
|
543
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Compute the next per-org `event_seq` for `sys_metadata_history`.
|
|
703
|
+
* Reads `MAX(event_seq) + 1` for the configured `organization_id`.
|
|
704
|
+
* Legacy path — not transactional, so concurrent writes can collide.
|
|
705
|
+
* The canonical (transactional) producer is `SysMetadataRepository`.
|
|
706
|
+
*/
|
|
707
|
+
async nextEventSeq() {
|
|
708
|
+
const where = this.organizationId ? { organization_id: this.organizationId } : {};
|
|
709
|
+
try {
|
|
710
|
+
const rows = await this._find(this.historyTableName, { where });
|
|
711
|
+
let max = 0;
|
|
712
|
+
for (const row of rows) {
|
|
713
|
+
const v = typeof row.event_seq === "number" ? row.event_seq : 0;
|
|
714
|
+
if (v > max) max = v;
|
|
715
|
+
}
|
|
716
|
+
return max + 1;
|
|
717
|
+
} catch {
|
|
718
|
+
return 1;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
544
721
|
/**
|
|
545
722
|
* Ensure the metadata table exists.
|
|
546
723
|
* Uses IDataDriver.syncSchema with the SysMetadataObject definition
|
|
@@ -605,8 +782,9 @@ var DatabaseLoader = class {
|
|
|
605
782
|
}
|
|
606
783
|
/**
|
|
607
784
|
* Build base filter conditions for queries.
|
|
608
|
-
* Filters by organizationId when configured
|
|
609
|
-
*
|
|
785
|
+
* Filters by organizationId when configured. `projectId` is accepted
|
|
786
|
+
* for back-compat but no longer constrains the query — see
|
|
787
|
+
* ADR-0008 §0 (branch/project removal).
|
|
610
788
|
*/
|
|
611
789
|
baseFilter(type, name) {
|
|
612
790
|
const filter = { type };
|
|
@@ -616,7 +794,6 @@ var DatabaseLoader = class {
|
|
|
616
794
|
if (this.organizationId) {
|
|
617
795
|
filter.organization_id = this.organizationId;
|
|
618
796
|
}
|
|
619
|
-
filter.project_id = this.projectId ?? null;
|
|
620
797
|
return filter;
|
|
621
798
|
}
|
|
622
799
|
/**
|
|
@@ -642,6 +819,7 @@ var DatabaseLoader = class {
|
|
|
642
819
|
}
|
|
643
820
|
const historyId = generateId();
|
|
644
821
|
const metadataJson = JSON.stringify(metadata);
|
|
822
|
+
const eventSeq = await this.nextEventSeq();
|
|
645
823
|
const historyRecord = {
|
|
646
824
|
id: historyId,
|
|
647
825
|
metadataId,
|
|
@@ -655,12 +833,12 @@ var DatabaseLoader = class {
|
|
|
655
833
|
changeNote,
|
|
656
834
|
recordedBy,
|
|
657
835
|
recordedAt: now,
|
|
658
|
-
...this.organizationId ? { organizationId: this.organizationId } : {}
|
|
659
|
-
...this.projectId !== void 0 ? { projectId: this.projectId } : {}
|
|
836
|
+
...this.organizationId ? { organizationId: this.organizationId } : {}
|
|
660
837
|
};
|
|
661
838
|
try {
|
|
662
839
|
await this._create(this.historyTableName, {
|
|
663
840
|
id: historyRecord.id,
|
|
841
|
+
event_seq: eventSeq,
|
|
664
842
|
metadata_id: historyRecord.metadataId,
|
|
665
843
|
name: historyRecord.name,
|
|
666
844
|
type: historyRecord.type,
|
|
@@ -672,8 +850,8 @@ var DatabaseLoader = class {
|
|
|
672
850
|
change_note: historyRecord.changeNote,
|
|
673
851
|
recorded_by: historyRecord.recordedBy,
|
|
674
852
|
recorded_at: historyRecord.recordedAt,
|
|
675
|
-
|
|
676
|
-
...this.
|
|
853
|
+
source: "database-loader",
|
|
854
|
+
...this.organizationId ? { organization_id: this.organizationId } : {}
|
|
677
855
|
});
|
|
678
856
|
} catch (error) {
|
|
679
857
|
console.error(`Failed to create history record for ${type}/${name}:`, error);
|
|
@@ -860,7 +1038,6 @@ var DatabaseLoader = class {
|
|
|
860
1038
|
if (this.organizationId) {
|
|
861
1039
|
filter.organization_id = this.organizationId;
|
|
862
1040
|
}
|
|
863
|
-
filter.project_id = this.projectId ?? null;
|
|
864
1041
|
const row = await this._findOne(this.historyTableName, {
|
|
865
1042
|
where: filter
|
|
866
1043
|
});
|
|
@@ -877,7 +1054,6 @@ var DatabaseLoader = class {
|
|
|
877
1054
|
previousChecksum: row.previous_checksum,
|
|
878
1055
|
changeNote: row.change_note,
|
|
879
1056
|
organizationId: row.organization_id,
|
|
880
|
-
projectId: row.project_id,
|
|
881
1057
|
recordedBy: row.recorded_by,
|
|
882
1058
|
recordedAt: row.recorded_at
|
|
883
1059
|
};
|
|
@@ -895,7 +1071,6 @@ var DatabaseLoader = class {
|
|
|
895
1071
|
await this.ensureHistorySchema();
|
|
896
1072
|
const filter = { type, name };
|
|
897
1073
|
if (this.organizationId) filter.organization_id = this.organizationId;
|
|
898
|
-
filter.project_id = this.projectId ?? null;
|
|
899
1074
|
const metadataRecord = await this._findOne(this.tableName, { where: filter });
|
|
900
1075
|
if (!metadataRecord) {
|
|
901
1076
|
return { records: [], total: 0, hasMore: false };
|
|
@@ -904,7 +1079,6 @@ var DatabaseLoader = class {
|
|
|
904
1079
|
metadata_id: metadataRecord.id
|
|
905
1080
|
};
|
|
906
1081
|
if (this.organizationId) historyFilter.organization_id = this.organizationId;
|
|
907
|
-
historyFilter.project_id = this.projectId ?? null;
|
|
908
1082
|
if (options?.operationType) historyFilter.operation_type = options.operationType;
|
|
909
1083
|
if (options?.since) historyFilter.recorded_at = { $gte: options.since };
|
|
910
1084
|
if (options?.until) {
|
|
@@ -943,7 +1117,6 @@ var DatabaseLoader = class {
|
|
|
943
1117
|
previousChecksum: row.previous_checksum,
|
|
944
1118
|
changeNote: row.change_note,
|
|
945
1119
|
organizationId: row.organization_id,
|
|
946
|
-
projectId: row.project_id,
|
|
947
1120
|
recordedBy: row.recorded_by,
|
|
948
1121
|
recordedAt: row.recorded_at
|
|
949
1122
|
};
|
|
@@ -1050,7 +1223,6 @@ var DatabaseLoader = class {
|
|
|
1050
1223
|
version: 1,
|
|
1051
1224
|
source: "database",
|
|
1052
1225
|
...this.organizationId ? { organization_id: this.organizationId } : {},
|
|
1053
|
-
...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
|
|
1054
1226
|
created_at: now,
|
|
1055
1227
|
updated_at: now
|
|
1056
1228
|
});
|
|
@@ -1123,6 +1295,7 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1123
1295
|
// Invalidated on every `register()` / `unregister()` to keep CRUD writes
|
|
1124
1296
|
// visible to subsequent reads.
|
|
1125
1297
|
this.listCache = /* @__PURE__ */ new Map();
|
|
1298
|
+
this.repoWatchClosed = false;
|
|
1126
1299
|
this.config = config;
|
|
1127
1300
|
this.logger = (0, import_core.createLogger)({ level: "info", format: "pretty" });
|
|
1128
1301
|
this.serializers = /* @__PURE__ */ new Map();
|
|
@@ -1818,6 +1991,21 @@ var _MetadataManager = class _MetadataManager {
|
|
|
1818
1991
|
unsubscribe: () => this.removeWatchCallback(type, wrappedCallback)
|
|
1819
1992
|
};
|
|
1820
1993
|
}
|
|
1994
|
+
/**
|
|
1995
|
+
* Subscribe to raw metadata watch events for a given type.
|
|
1996
|
+
*
|
|
1997
|
+
* Unlike `watchService` (which maps to the IMetadataService contract and
|
|
1998
|
+
* drops fields like `path`/`timestamp`), this returns the raw
|
|
1999
|
+
* `MetadataWatchEvent` produced by the underlying watcher — useful for
|
|
2000
|
+
* developer-facing tooling such as the HMR SSE endpoint that wants the
|
|
2001
|
+
* source file path and original timestamp.
|
|
2002
|
+
*
|
|
2003
|
+
* @returns An unsubscribe function.
|
|
2004
|
+
*/
|
|
2005
|
+
subscribe(type, callback) {
|
|
2006
|
+
this.addWatchCallback(type, callback);
|
|
2007
|
+
return () => this.removeWatchCallback(type, callback);
|
|
2008
|
+
}
|
|
1821
2009
|
// ==========================================
|
|
1822
2010
|
// Import / Export
|
|
1823
2011
|
// ==========================================
|
|
@@ -2115,6 +2303,110 @@ var _MetadataManager = class _MetadataManager {
|
|
|
2115
2303
|
*/
|
|
2116
2304
|
async stopWatching() {
|
|
2117
2305
|
}
|
|
2306
|
+
// ─── ADR-0008 PR-6: Repository wiring ───────────────────────────────
|
|
2307
|
+
/**
|
|
2308
|
+
* Attach a {@link MetadataRepository} as a supplementary event source.
|
|
2309
|
+
*
|
|
2310
|
+
* The manager subscribes to `repo.watch({})` and re-emits each event
|
|
2311
|
+
* through {@link notifyWatchers} as a legacy `MetadataWatchEvent`.
|
|
2312
|
+
* Each event also invalidates the in-memory registry entry and the
|
|
2313
|
+
* `list()` cache for the affected type so subsequent reads see fresh
|
|
2314
|
+
* data.
|
|
2315
|
+
*
|
|
2316
|
+
* No write-through. `register()` / `unregister()` / `save()` are
|
|
2317
|
+
* untouched in this PR (deferred to ADR-0008 M0 PR-10).
|
|
2318
|
+
*
|
|
2319
|
+
* Call {@link dispose} (or {@link stopRepositoryWatch}) to detach.
|
|
2320
|
+
*/
|
|
2321
|
+
setRepository(repo) {
|
|
2322
|
+
if (this.repository === repo) return;
|
|
2323
|
+
if (this.repository) {
|
|
2324
|
+
void this.stopRepositoryWatch();
|
|
2325
|
+
}
|
|
2326
|
+
this.repository = repo;
|
|
2327
|
+
this.repoWatchClosed = false;
|
|
2328
|
+
void this.startRepositoryWatch();
|
|
2329
|
+
}
|
|
2330
|
+
/** Return the attached repository, if any. */
|
|
2331
|
+
getRepository() {
|
|
2332
|
+
return this.repository;
|
|
2333
|
+
}
|
|
2334
|
+
/** Stop the active repo.watch() loop (best-effort). */
|
|
2335
|
+
async stopRepositoryWatch() {
|
|
2336
|
+
this.repoWatchClosed = true;
|
|
2337
|
+
const iter = this.repoWatchIter;
|
|
2338
|
+
this.repoWatchIter = void 0;
|
|
2339
|
+
if (iter && typeof iter.return === "function") {
|
|
2340
|
+
try {
|
|
2341
|
+
await iter.return(void 0);
|
|
2342
|
+
} catch {
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Best-effort cleanup. Stops the FS watcher (if any), drains the
|
|
2348
|
+
* repository watch loop, and clears registry caches. Safe to call
|
|
2349
|
+
* multiple times.
|
|
2350
|
+
*/
|
|
2351
|
+
async dispose() {
|
|
2352
|
+
await this.stopWatching().catch(() => void 0);
|
|
2353
|
+
await this.stopRepositoryWatch().catch(() => void 0);
|
|
2354
|
+
this.listCache.clear();
|
|
2355
|
+
}
|
|
2356
|
+
async startRepositoryWatch() {
|
|
2357
|
+
const repo = this.repository;
|
|
2358
|
+
if (!repo) return;
|
|
2359
|
+
const iterable = repo.watch({});
|
|
2360
|
+
const iter = iterable[Symbol.asyncIterator]();
|
|
2361
|
+
this.repoWatchIter = iter;
|
|
2362
|
+
try {
|
|
2363
|
+
while (!this.repoWatchClosed) {
|
|
2364
|
+
const { value, done } = await iter.next();
|
|
2365
|
+
if (done) break;
|
|
2366
|
+
try {
|
|
2367
|
+
this.applyRepoEvent(value);
|
|
2368
|
+
} catch (err) {
|
|
2369
|
+
this.logger.warn("[MetadataManager] repo event handler failed", {
|
|
2370
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2371
|
+
});
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
} catch (err) {
|
|
2375
|
+
if (!this.repoWatchClosed) {
|
|
2376
|
+
this.logger.warn("[MetadataManager] repository watch loop exited unexpectedly", {
|
|
2377
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
} finally {
|
|
2381
|
+
if (this.repoWatchIter === iter) this.repoWatchIter = void 0;
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
/** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
|
|
2385
|
+
applyRepoEvent(evt) {
|
|
2386
|
+
const ref = evt.ref;
|
|
2387
|
+
const type = ref.type;
|
|
2388
|
+
const name = ref.name;
|
|
2389
|
+
const typeStore = this.registry.get(type);
|
|
2390
|
+
if (typeStore) {
|
|
2391
|
+
typeStore.delete(name);
|
|
2392
|
+
if (typeStore.size === 0) this.registry.delete(type);
|
|
2393
|
+
}
|
|
2394
|
+
this.listCache.delete(type);
|
|
2395
|
+
const legacyType = evt.op === "create" ? "added" : evt.op === "delete" ? "deleted" : "changed";
|
|
2396
|
+
const legacyEvent = {
|
|
2397
|
+
type: legacyType,
|
|
2398
|
+
metadataType: type,
|
|
2399
|
+
name,
|
|
2400
|
+
path: "",
|
|
2401
|
+
// Repo events carry the hash only; the body is fetched on demand
|
|
2402
|
+
// via manager.get(type, name). HMR consumers don't read `data` so
|
|
2403
|
+
// this is fine for M0. (See ADR-0008 §12 open question 1.)
|
|
2404
|
+
data: void 0,
|
|
2405
|
+
timestamp: evt.ts
|
|
2406
|
+
};
|
|
2407
|
+
legacyEvent.seq = evt.seq;
|
|
2408
|
+
this.notifyWatchers(type, legacyEvent);
|
|
2409
|
+
}
|
|
2118
2410
|
notifyWatchers(type, event) {
|
|
2119
2411
|
const callbacks = this.watchCallbacks.get(type);
|
|
2120
2412
|
if (!callbacks) return;
|
|
@@ -2590,7 +2882,13 @@ var NodeMetadataManager = class extends MetadataManager {
|
|
|
2590
2882
|
this.watcher = (0, import_chokidar.watch)(rootDir, {
|
|
2591
2883
|
ignored,
|
|
2592
2884
|
persistent,
|
|
2593
|
-
ignoreInitial: true
|
|
2885
|
+
ignoreInitial: true,
|
|
2886
|
+
// Use polling to avoid `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2887
|
+
// Recursive watch over a project root would otherwise wire native
|
|
2888
|
+
// watches across the entire tree, easily exhausting the FD pool.
|
|
2889
|
+
usePolling: true,
|
|
2890
|
+
interval: 1e3,
|
|
2891
|
+
binaryInterval: 2e3
|
|
2594
2892
|
});
|
|
2595
2893
|
this.watcher.on("add", async (filePath) => {
|
|
2596
2894
|
await this.handleFileEvent("added", filePath);
|
|
@@ -2725,6 +3023,7 @@ var queryableMetadataObjects = [
|
|
|
2725
3023
|
import_metadata2.SysMetadataObject,
|
|
2726
3024
|
import_metadata2.SysMetadataHistoryObject
|
|
2727
3025
|
];
|
|
3026
|
+
var REPO_SUBDIR = ".objectstack/metadata";
|
|
2728
3027
|
var ARTIFACT_FIELD_TO_TYPE = {
|
|
2729
3028
|
objects: "object",
|
|
2730
3029
|
objectExtensions: "object_extension",
|
|
@@ -2822,6 +3121,31 @@ var MetadataPlugin = class {
|
|
|
2822
3121
|
await this._loadFromFileSystem(ctx);
|
|
2823
3122
|
}
|
|
2824
3123
|
}
|
|
3124
|
+
const bootstrapMode = this.options.config?.bootstrap ?? "eager";
|
|
3125
|
+
if (bootstrapMode !== "artifact-only") {
|
|
3126
|
+
try {
|
|
3127
|
+
const path3 = await import("path");
|
|
3128
|
+
const { FileSystemRepository } = await import("@objectstack/metadata-fs");
|
|
3129
|
+
const rootDir = this.options.rootDir || process.cwd();
|
|
3130
|
+
const repoRoot = path3.join(rootDir, REPO_SUBDIR);
|
|
3131
|
+
const repo = new FileSystemRepository({
|
|
3132
|
+
root: repoRoot,
|
|
3133
|
+
org: this.options.organizationId ?? "system",
|
|
3134
|
+
disableWatch: this.options.watch === false
|
|
3135
|
+
});
|
|
3136
|
+
await repo.start();
|
|
3137
|
+
this.repository = repo;
|
|
3138
|
+
this.manager.setRepository(repo);
|
|
3139
|
+
ctx.logger.info("[MetadataPlugin] FileSystemRepository attached", {
|
|
3140
|
+
repoRoot,
|
|
3141
|
+
watch: this.options.watch !== false
|
|
3142
|
+
});
|
|
3143
|
+
} catch (e) {
|
|
3144
|
+
ctx.logger.warn("[MetadataPlugin] Failed to attach FileSystemRepository", {
|
|
3145
|
+
error: e?.message
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
2825
3149
|
try {
|
|
2826
3150
|
const realtimeService = ctx.getService("realtime");
|
|
2827
3151
|
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
@@ -2833,6 +3157,102 @@ var MetadataPlugin = class {
|
|
|
2833
3157
|
error: e.message
|
|
2834
3158
|
});
|
|
2835
3159
|
}
|
|
3160
|
+
try {
|
|
3161
|
+
const httpServer = ctx.getService("http-server") ?? ctx.getService("http.server");
|
|
3162
|
+
if (httpServer && typeof httpServer.getRawApp === "function") {
|
|
3163
|
+
const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
|
|
3164
|
+
const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
|
|
3165
|
+
hub.setOnPostReload(async (body = {}) => {
|
|
3166
|
+
const src3 = this.options.artifactSource;
|
|
3167
|
+
if (src3?.mode === "local-file") {
|
|
3168
|
+
try {
|
|
3169
|
+
await this._loadFromLocalFile(ctx, src3.path, src3.fetchTimeoutMs);
|
|
3170
|
+
ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
|
|
3171
|
+
path: src3.path,
|
|
3172
|
+
reason: body?.reason
|
|
3173
|
+
});
|
|
3174
|
+
} catch (e) {
|
|
3175
|
+
ctx.logger.warn("[MetadataPlugin] artifact reload failed", { error: e?.message });
|
|
3176
|
+
throw e;
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
});
|
|
3180
|
+
const src2 = this.options.artifactSource;
|
|
3181
|
+
const wantArtifactWatch = this.options.artifactWatch ?? src2?.mode === "local-file";
|
|
3182
|
+
if (src2?.mode === "local-file" && wantArtifactWatch && !/^https?:\/\//i.test(src2.path)) {
|
|
3183
|
+
try {
|
|
3184
|
+
const { watch: chokidarWatch2 } = await import("chokidar");
|
|
3185
|
+
const w = chokidarWatch2(src2.path, {
|
|
3186
|
+
ignoreInitial: true,
|
|
3187
|
+
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 },
|
|
3188
|
+
persistent: true,
|
|
3189
|
+
// Use polling to avoid `fs.watch` exhausting the
|
|
3190
|
+
// process file-descriptor limit on macOS (chokidar
|
|
3191
|
+
// recursively wires watches on the parent
|
|
3192
|
+
// directory tree which can trip EMFILE on busy
|
|
3193
|
+
// dev hosts). 500ms polling is fast enough for
|
|
3194
|
+
// HMR (a recompile takes ~400ms anyway).
|
|
3195
|
+
usePolling: true,
|
|
3196
|
+
interval: 500,
|
|
3197
|
+
binaryInterval: 1e3
|
|
3198
|
+
});
|
|
3199
|
+
let pending = false;
|
|
3200
|
+
const reload = async () => {
|
|
3201
|
+
if (pending) return;
|
|
3202
|
+
pending = true;
|
|
3203
|
+
try {
|
|
3204
|
+
await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
|
|
3205
|
+
hub.broadcastReload("artifact-file-changed", [src2.path]);
|
|
3206
|
+
ctx.logger.info("[MetadataPlugin] artifact auto-reloaded (file watcher)", {
|
|
3207
|
+
path: src2.path
|
|
3208
|
+
});
|
|
3209
|
+
} catch (e) {
|
|
3210
|
+
ctx.logger.warn("[MetadataPlugin] artifact auto-reload failed", { error: e?.message });
|
|
3211
|
+
} finally {
|
|
3212
|
+
pending = false;
|
|
3213
|
+
}
|
|
3214
|
+
};
|
|
3215
|
+
w.on("change", () => {
|
|
3216
|
+
void reload();
|
|
3217
|
+
});
|
|
3218
|
+
w.on("add", () => {
|
|
3219
|
+
void reload();
|
|
3220
|
+
});
|
|
3221
|
+
this.artifactWatcher = { close: () => w.close() };
|
|
3222
|
+
console.log("[MetadataPlugin] artifact file watcher attached", src2.path);
|
|
3223
|
+
} catch (e) {
|
|
3224
|
+
ctx.logger.warn("[MetadataPlugin] artifact watcher failed to start", { error: e?.message });
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
|
|
3228
|
+
} else {
|
|
3229
|
+
console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
|
|
3230
|
+
}
|
|
3231
|
+
} catch (e) {
|
|
3232
|
+
console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
|
|
3233
|
+
}
|
|
3234
|
+
};
|
|
3235
|
+
this.stop = async (ctx) => {
|
|
3236
|
+
if (this.artifactWatcher) {
|
|
3237
|
+
try {
|
|
3238
|
+
await this.artifactWatcher.close();
|
|
3239
|
+
} catch {
|
|
3240
|
+
}
|
|
3241
|
+
this.artifactWatcher = void 0;
|
|
3242
|
+
}
|
|
3243
|
+
try {
|
|
3244
|
+
await this.manager.dispose();
|
|
3245
|
+
} catch (e) {
|
|
3246
|
+
ctx.logger.warn("[MetadataPlugin] manager.dispose() failed", { error: e?.message });
|
|
3247
|
+
}
|
|
3248
|
+
const repo = this.repository;
|
|
3249
|
+
if (repo && typeof repo.close === "function") {
|
|
3250
|
+
try {
|
|
3251
|
+
await repo.close();
|
|
3252
|
+
} catch {
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
this.repository = void 0;
|
|
2836
3256
|
};
|
|
2837
3257
|
this.options = {
|
|
2838
3258
|
watch: true,
|
|
@@ -2910,7 +3330,12 @@ var MetadataPlugin = class {
|
|
|
2910
3330
|
const items = metadata[field];
|
|
2911
3331
|
if (!Array.isArray(items) || items.length === 0) continue;
|
|
2912
3332
|
for (const item of items) {
|
|
2913
|
-
|
|
3333
|
+
let name = item?.name;
|
|
3334
|
+
if (!name) {
|
|
3335
|
+
if (metaType === "view") {
|
|
3336
|
+
name = item?.list?.data?.object ?? item?.form?.data?.object;
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
2914
3339
|
if (!name) continue;
|
|
2915
3340
|
if (manifestPackageId && item._packageId === void 0) {
|
|
2916
3341
|
item._packageId = manifestPackageId;
|
|
@@ -3254,7 +3679,6 @@ var HistoryCleanupManager = class {
|
|
|
3254
3679
|
const driver = this.dbLoader.driver;
|
|
3255
3680
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3256
3681
|
const organizationId = this.dbLoader.organizationId;
|
|
3257
|
-
const projectId = this.dbLoader.projectId;
|
|
3258
3682
|
let deleted = 0;
|
|
3259
3683
|
let errors = 0;
|
|
3260
3684
|
try {
|
|
@@ -3268,9 +3692,6 @@ var HistoryCleanupManager = class {
|
|
|
3268
3692
|
if (organizationId) {
|
|
3269
3693
|
filter.organization_id = organizationId;
|
|
3270
3694
|
}
|
|
3271
|
-
if (projectId !== void 0) {
|
|
3272
|
-
filter.project_id = projectId;
|
|
3273
|
-
}
|
|
3274
3695
|
try {
|
|
3275
3696
|
const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
|
|
3276
3697
|
deleted += result.deleted;
|
|
@@ -3283,7 +3704,6 @@ var HistoryCleanupManager = class {
|
|
|
3283
3704
|
try {
|
|
3284
3705
|
const baseWhere = {};
|
|
3285
3706
|
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3286
|
-
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3287
3707
|
const metadataIds = await driver.find(historyTableName, {
|
|
3288
3708
|
object: historyTableName,
|
|
3289
3709
|
where: baseWhere,
|
|
@@ -3371,13 +3791,11 @@ var HistoryCleanupManager = class {
|
|
|
3371
3791
|
const driver = this.dbLoader.driver;
|
|
3372
3792
|
const historyTableName = this.dbLoader.historyTableName;
|
|
3373
3793
|
const organizationId = this.dbLoader.organizationId;
|
|
3374
|
-
const projectId = this.dbLoader.projectId;
|
|
3375
3794
|
let recordsByAge = 0;
|
|
3376
3795
|
let recordsByCount = 0;
|
|
3377
3796
|
try {
|
|
3378
3797
|
const baseWhere = {};
|
|
3379
3798
|
if (organizationId) baseWhere.organization_id = organizationId;
|
|
3380
|
-
if (projectId !== void 0) baseWhere.project_id = projectId;
|
|
3381
3799
|
if (this.policy.maxAgeDays) {
|
|
3382
3800
|
const cutoffDate = /* @__PURE__ */ new Date();
|
|
3383
3801
|
cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);
|