@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/node.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/node.ts
31
188
  var node_exports = {};
32
189
  __export(node_exports, {
@@ -453,7 +610,7 @@ var DatabaseLoader = class {
453
610
  this.tableName = options.tableName ?? "sys_metadata";
454
611
  this.historyTableName = options.historyTableName ?? "sys_metadata_history";
455
612
  this.organizationId = options.organizationId;
456
- this.projectId = options.projectId;
613
+ void options.projectId;
457
614
  this.trackHistory = options.trackHistory !== false;
458
615
  const cacheOpts = options.cache;
459
616
  const cacheEnabled = cacheOpts?.enabled !== false;
@@ -543,6 +700,26 @@ var DatabaseLoader = class {
543
700
  }
544
701
  return this.driver.delete(table, id);
545
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
+ }
546
723
  /**
547
724
  * Ensure the metadata table exists.
548
725
  * Uses IDataDriver.syncSchema with the SysMetadataObject definition
@@ -607,8 +784,9 @@ var DatabaseLoader = class {
607
784
  }
608
785
  /**
609
786
  * Build base filter conditions for queries.
610
- * Filters by organizationId when configured; project_id when projectId is set,
611
- * or null (platform-global) when not set.
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).
612
790
  */
613
791
  baseFilter(type, name) {
614
792
  const filter = { type };
@@ -618,7 +796,6 @@ var DatabaseLoader = class {
618
796
  if (this.organizationId) {
619
797
  filter.organization_id = this.organizationId;
620
798
  }
621
- filter.project_id = this.projectId ?? null;
622
799
  return filter;
623
800
  }
624
801
  /**
@@ -644,6 +821,7 @@ var DatabaseLoader = class {
644
821
  }
645
822
  const historyId = generateId();
646
823
  const metadataJson = JSON.stringify(metadata);
824
+ const eventSeq = await this.nextEventSeq();
647
825
  const historyRecord = {
648
826
  id: historyId,
649
827
  metadataId,
@@ -657,12 +835,12 @@ var DatabaseLoader = class {
657
835
  changeNote,
658
836
  recordedBy,
659
837
  recordedAt: now,
660
- ...this.organizationId ? { organizationId: this.organizationId } : {},
661
- ...this.projectId !== void 0 ? { projectId: this.projectId } : {}
838
+ ...this.organizationId ? { organizationId: this.organizationId } : {}
662
839
  };
663
840
  try {
664
841
  await this._create(this.historyTableName, {
665
842
  id: historyRecord.id,
843
+ event_seq: eventSeq,
666
844
  metadata_id: historyRecord.metadataId,
667
845
  name: historyRecord.name,
668
846
  type: historyRecord.type,
@@ -674,8 +852,8 @@ var DatabaseLoader = class {
674
852
  change_note: historyRecord.changeNote,
675
853
  recorded_by: historyRecord.recordedBy,
676
854
  recorded_at: historyRecord.recordedAt,
677
- ...this.organizationId ? { organization_id: this.organizationId } : {},
678
- ...this.projectId !== void 0 ? { project_id: this.projectId } : {}
855
+ source: "database-loader",
856
+ ...this.organizationId ? { organization_id: this.organizationId } : {}
679
857
  });
680
858
  } catch (error) {
681
859
  console.error(`Failed to create history record for ${type}/${name}:`, error);
@@ -862,7 +1040,6 @@ var DatabaseLoader = class {
862
1040
  if (this.organizationId) {
863
1041
  filter.organization_id = this.organizationId;
864
1042
  }
865
- filter.project_id = this.projectId ?? null;
866
1043
  const row = await this._findOne(this.historyTableName, {
867
1044
  where: filter
868
1045
  });
@@ -879,7 +1056,6 @@ var DatabaseLoader = class {
879
1056
  previousChecksum: row.previous_checksum,
880
1057
  changeNote: row.change_note,
881
1058
  organizationId: row.organization_id,
882
- projectId: row.project_id,
883
1059
  recordedBy: row.recorded_by,
884
1060
  recordedAt: row.recorded_at
885
1061
  };
@@ -897,7 +1073,6 @@ var DatabaseLoader = class {
897
1073
  await this.ensureHistorySchema();
898
1074
  const filter = { type, name };
899
1075
  if (this.organizationId) filter.organization_id = this.organizationId;
900
- filter.project_id = this.projectId ?? null;
901
1076
  const metadataRecord = await this._findOne(this.tableName, { where: filter });
902
1077
  if (!metadataRecord) {
903
1078
  return { records: [], total: 0, hasMore: false };
@@ -906,7 +1081,6 @@ var DatabaseLoader = class {
906
1081
  metadata_id: metadataRecord.id
907
1082
  };
908
1083
  if (this.organizationId) historyFilter.organization_id = this.organizationId;
909
- historyFilter.project_id = this.projectId ?? null;
910
1084
  if (options?.operationType) historyFilter.operation_type = options.operationType;
911
1085
  if (options?.since) historyFilter.recorded_at = { $gte: options.since };
912
1086
  if (options?.until) {
@@ -945,7 +1119,6 @@ var DatabaseLoader = class {
945
1119
  previousChecksum: row.previous_checksum,
946
1120
  changeNote: row.change_note,
947
1121
  organizationId: row.organization_id,
948
- projectId: row.project_id,
949
1122
  recordedBy: row.recorded_by,
950
1123
  recordedAt: row.recorded_at
951
1124
  };
@@ -1052,7 +1225,6 @@ var DatabaseLoader = class {
1052
1225
  version: 1,
1053
1226
  source: "database",
1054
1227
  ...this.organizationId ? { organization_id: this.organizationId } : {},
1055
- ...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
1056
1228
  created_at: now,
1057
1229
  updated_at: now
1058
1230
  });
@@ -1125,6 +1297,7 @@ var _MetadataManager = class _MetadataManager {
1125
1297
  // Invalidated on every `register()` / `unregister()` to keep CRUD writes
1126
1298
  // visible to subsequent reads.
1127
1299
  this.listCache = /* @__PURE__ */ new Map();
1300
+ this.repoWatchClosed = false;
1128
1301
  this.config = config;
1129
1302
  this.logger = (0, import_core.createLogger)({ level: "info", format: "pretty" });
1130
1303
  this.serializers = /* @__PURE__ */ new Map();
@@ -1820,6 +1993,21 @@ var _MetadataManager = class _MetadataManager {
1820
1993
  unsubscribe: () => this.removeWatchCallback(type, wrappedCallback)
1821
1994
  };
1822
1995
  }
1996
+ /**
1997
+ * Subscribe to raw metadata watch events for a given type.
1998
+ *
1999
+ * Unlike `watchService` (which maps to the IMetadataService contract and
2000
+ * drops fields like `path`/`timestamp`), this returns the raw
2001
+ * `MetadataWatchEvent` produced by the underlying watcher — useful for
2002
+ * developer-facing tooling such as the HMR SSE endpoint that wants the
2003
+ * source file path and original timestamp.
2004
+ *
2005
+ * @returns An unsubscribe function.
2006
+ */
2007
+ subscribe(type, callback) {
2008
+ this.addWatchCallback(type, callback);
2009
+ return () => this.removeWatchCallback(type, callback);
2010
+ }
1823
2011
  // ==========================================
1824
2012
  // Import / Export
1825
2013
  // ==========================================
@@ -2117,6 +2305,110 @@ var _MetadataManager = class _MetadataManager {
2117
2305
  */
2118
2306
  async stopWatching() {
2119
2307
  }
2308
+ // ─── ADR-0008 PR-6: Repository wiring ───────────────────────────────
2309
+ /**
2310
+ * Attach a {@link MetadataRepository} as a supplementary event source.
2311
+ *
2312
+ * The manager subscribes to `repo.watch({})` and re-emits each event
2313
+ * through {@link notifyWatchers} as a legacy `MetadataWatchEvent`.
2314
+ * Each event also invalidates the in-memory registry entry and the
2315
+ * `list()` cache for the affected type so subsequent reads see fresh
2316
+ * data.
2317
+ *
2318
+ * No write-through. `register()` / `unregister()` / `save()` are
2319
+ * untouched in this PR (deferred to ADR-0008 M0 PR-10).
2320
+ *
2321
+ * Call {@link dispose} (or {@link stopRepositoryWatch}) to detach.
2322
+ */
2323
+ setRepository(repo) {
2324
+ if (this.repository === repo) return;
2325
+ if (this.repository) {
2326
+ void this.stopRepositoryWatch();
2327
+ }
2328
+ this.repository = repo;
2329
+ this.repoWatchClosed = false;
2330
+ void this.startRepositoryWatch();
2331
+ }
2332
+ /** Return the attached repository, if any. */
2333
+ getRepository() {
2334
+ return this.repository;
2335
+ }
2336
+ /** Stop the active repo.watch() loop (best-effort). */
2337
+ async stopRepositoryWatch() {
2338
+ this.repoWatchClosed = true;
2339
+ const iter = this.repoWatchIter;
2340
+ this.repoWatchIter = void 0;
2341
+ if (iter && typeof iter.return === "function") {
2342
+ try {
2343
+ await iter.return(void 0);
2344
+ } catch {
2345
+ }
2346
+ }
2347
+ }
2348
+ /**
2349
+ * Best-effort cleanup. Stops the FS watcher (if any), drains the
2350
+ * repository watch loop, and clears registry caches. Safe to call
2351
+ * multiple times.
2352
+ */
2353
+ async dispose() {
2354
+ await this.stopWatching().catch(() => void 0);
2355
+ await this.stopRepositoryWatch().catch(() => void 0);
2356
+ this.listCache.clear();
2357
+ }
2358
+ async startRepositoryWatch() {
2359
+ const repo = this.repository;
2360
+ if (!repo) return;
2361
+ const iterable = repo.watch({});
2362
+ const iter = iterable[Symbol.asyncIterator]();
2363
+ this.repoWatchIter = iter;
2364
+ try {
2365
+ while (!this.repoWatchClosed) {
2366
+ const { value, done } = await iter.next();
2367
+ if (done) break;
2368
+ try {
2369
+ this.applyRepoEvent(value);
2370
+ } catch (err) {
2371
+ this.logger.warn("[MetadataManager] repo event handler failed", {
2372
+ error: err instanceof Error ? err.message : String(err)
2373
+ });
2374
+ }
2375
+ }
2376
+ } catch (err) {
2377
+ if (!this.repoWatchClosed) {
2378
+ this.logger.warn("[MetadataManager] repository watch loop exited unexpectedly", {
2379
+ error: err instanceof Error ? err.message : String(err)
2380
+ });
2381
+ }
2382
+ } finally {
2383
+ if (this.repoWatchIter === iter) this.repoWatchIter = void 0;
2384
+ }
2385
+ }
2386
+ /** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
2387
+ applyRepoEvent(evt) {
2388
+ const ref = evt.ref;
2389
+ const type = ref.type;
2390
+ const name = ref.name;
2391
+ const typeStore = this.registry.get(type);
2392
+ if (typeStore) {
2393
+ typeStore.delete(name);
2394
+ if (typeStore.size === 0) this.registry.delete(type);
2395
+ }
2396
+ this.listCache.delete(type);
2397
+ const legacyType = evt.op === "create" ? "added" : evt.op === "delete" ? "deleted" : "changed";
2398
+ const legacyEvent = {
2399
+ type: legacyType,
2400
+ metadataType: type,
2401
+ name,
2402
+ path: "",
2403
+ // Repo events carry the hash only; the body is fetched on demand
2404
+ // via manager.get(type, name). HMR consumers don't read `data` so
2405
+ // this is fine for M0. (See ADR-0008 §12 open question 1.)
2406
+ data: void 0,
2407
+ timestamp: evt.ts
2408
+ };
2409
+ legacyEvent.seq = evt.seq;
2410
+ this.notifyWatchers(type, legacyEvent);
2411
+ }
2120
2412
  notifyWatchers(type, event) {
2121
2413
  const callbacks = this.watchCallbacks.get(type);
2122
2414
  if (!callbacks) return;
@@ -2592,7 +2884,13 @@ var NodeMetadataManager = class extends MetadataManager {
2592
2884
  this.watcher = (0, import_chokidar.watch)(rootDir, {
2593
2885
  ignored,
2594
2886
  persistent,
2595
- ignoreInitial: true
2887
+ ignoreInitial: true,
2888
+ // Use polling to avoid `fs.watch` EMFILE on macOS / busy dev hosts.
2889
+ // Recursive watch over a project root would otherwise wire native
2890
+ // watches across the entire tree, easily exhausting the FD pool.
2891
+ usePolling: true,
2892
+ interval: 1e3,
2893
+ binaryInterval: 2e3
2596
2894
  });
2597
2895
  this.watcher.on("add", async (filePath) => {
2598
2896
  await this.handleFileEvent("added", filePath);
@@ -2727,6 +3025,7 @@ var queryableMetadataObjects = [
2727
3025
  import_metadata2.SysMetadataObject,
2728
3026
  import_metadata2.SysMetadataHistoryObject
2729
3027
  ];
3028
+ var REPO_SUBDIR = ".objectstack/metadata";
2730
3029
  var ARTIFACT_FIELD_TO_TYPE = {
2731
3030
  objects: "object",
2732
3031
  objectExtensions: "object_extension",
@@ -2824,6 +3123,31 @@ var MetadataPlugin = class {
2824
3123
  await this._loadFromFileSystem(ctx);
2825
3124
  }
2826
3125
  }
3126
+ const bootstrapMode = this.options.config?.bootstrap ?? "eager";
3127
+ if (bootstrapMode !== "artifact-only") {
3128
+ try {
3129
+ const path3 = await import("path");
3130
+ const { FileSystemRepository } = await import("@objectstack/metadata-fs");
3131
+ const rootDir = this.options.rootDir || process.cwd();
3132
+ const repoRoot = path3.join(rootDir, REPO_SUBDIR);
3133
+ const repo = new FileSystemRepository({
3134
+ root: repoRoot,
3135
+ org: this.options.organizationId ?? "system",
3136
+ disableWatch: this.options.watch === false
3137
+ });
3138
+ await repo.start();
3139
+ this.repository = repo;
3140
+ this.manager.setRepository(repo);
3141
+ ctx.logger.info("[MetadataPlugin] FileSystemRepository attached", {
3142
+ repoRoot,
3143
+ watch: this.options.watch !== false
3144
+ });
3145
+ } catch (e) {
3146
+ ctx.logger.warn("[MetadataPlugin] Failed to attach FileSystemRepository", {
3147
+ error: e?.message
3148
+ });
3149
+ }
3150
+ }
2827
3151
  try {
2828
3152
  const realtimeService = ctx.getService("realtime");
2829
3153
  if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
@@ -2835,6 +3159,102 @@ var MetadataPlugin = class {
2835
3159
  error: e.message
2836
3160
  });
2837
3161
  }
3162
+ try {
3163
+ const httpServer = ctx.getService("http-server") ?? ctx.getService("http.server");
3164
+ if (httpServer && typeof httpServer.getRawApp === "function") {
3165
+ const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
3166
+ const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
3167
+ hub.setOnPostReload(async (body = {}) => {
3168
+ const src3 = this.options.artifactSource;
3169
+ if (src3?.mode === "local-file") {
3170
+ try {
3171
+ await this._loadFromLocalFile(ctx, src3.path, src3.fetchTimeoutMs);
3172
+ ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
3173
+ path: src3.path,
3174
+ reason: body?.reason
3175
+ });
3176
+ } catch (e) {
3177
+ ctx.logger.warn("[MetadataPlugin] artifact reload failed", { error: e?.message });
3178
+ throw e;
3179
+ }
3180
+ }
3181
+ });
3182
+ const src2 = this.options.artifactSource;
3183
+ const wantArtifactWatch = this.options.artifactWatch ?? src2?.mode === "local-file";
3184
+ if (src2?.mode === "local-file" && wantArtifactWatch && !/^https?:\/\//i.test(src2.path)) {
3185
+ try {
3186
+ const { watch: chokidarWatch2 } = await import("chokidar");
3187
+ const w = chokidarWatch2(src2.path, {
3188
+ ignoreInitial: true,
3189
+ awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 },
3190
+ persistent: true,
3191
+ // Use polling to avoid `fs.watch` exhausting the
3192
+ // process file-descriptor limit on macOS (chokidar
3193
+ // recursively wires watches on the parent
3194
+ // directory tree which can trip EMFILE on busy
3195
+ // dev hosts). 500ms polling is fast enough for
3196
+ // HMR (a recompile takes ~400ms anyway).
3197
+ usePolling: true,
3198
+ interval: 500,
3199
+ binaryInterval: 1e3
3200
+ });
3201
+ let pending = false;
3202
+ const reload = async () => {
3203
+ if (pending) return;
3204
+ pending = true;
3205
+ try {
3206
+ await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
3207
+ hub.broadcastReload("artifact-file-changed", [src2.path]);
3208
+ ctx.logger.info("[MetadataPlugin] artifact auto-reloaded (file watcher)", {
3209
+ path: src2.path
3210
+ });
3211
+ } catch (e) {
3212
+ ctx.logger.warn("[MetadataPlugin] artifact auto-reload failed", { error: e?.message });
3213
+ } finally {
3214
+ pending = false;
3215
+ }
3216
+ };
3217
+ w.on("change", () => {
3218
+ void reload();
3219
+ });
3220
+ w.on("add", () => {
3221
+ void reload();
3222
+ });
3223
+ this.artifactWatcher = { close: () => w.close() };
3224
+ console.log("[MetadataPlugin] artifact file watcher attached", src2.path);
3225
+ } catch (e) {
3226
+ ctx.logger.warn("[MetadataPlugin] artifact watcher failed to start", { error: e?.message });
3227
+ }
3228
+ }
3229
+ console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
3230
+ } else {
3231
+ console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
3232
+ }
3233
+ } catch (e) {
3234
+ console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
3235
+ }
3236
+ };
3237
+ this.stop = async (ctx) => {
3238
+ if (this.artifactWatcher) {
3239
+ try {
3240
+ await this.artifactWatcher.close();
3241
+ } catch {
3242
+ }
3243
+ this.artifactWatcher = void 0;
3244
+ }
3245
+ try {
3246
+ await this.manager.dispose();
3247
+ } catch (e) {
3248
+ ctx.logger.warn("[MetadataPlugin] manager.dispose() failed", { error: e?.message });
3249
+ }
3250
+ const repo = this.repository;
3251
+ if (repo && typeof repo.close === "function") {
3252
+ try {
3253
+ await repo.close();
3254
+ } catch {
3255
+ }
3256
+ }
3257
+ this.repository = void 0;
2838
3258
  };
2839
3259
  this.options = {
2840
3260
  watch: true,
@@ -2912,7 +3332,12 @@ var MetadataPlugin = class {
2912
3332
  const items = metadata[field];
2913
3333
  if (!Array.isArray(items) || items.length === 0) continue;
2914
3334
  for (const item of items) {
2915
- const name = item?.name;
3335
+ let name = item?.name;
3336
+ if (!name) {
3337
+ if (metaType === "view") {
3338
+ name = item?.list?.data?.object ?? item?.form?.data?.object;
3339
+ }
3340
+ }
2916
3341
  if (!name) continue;
2917
3342
  if (manifestPackageId && item._packageId === void 0) {
2918
3343
  item._packageId = manifestPackageId;
@@ -3256,7 +3681,6 @@ var HistoryCleanupManager = class {
3256
3681
  const driver = this.dbLoader.driver;
3257
3682
  const historyTableName = this.dbLoader.historyTableName;
3258
3683
  const organizationId = this.dbLoader.organizationId;
3259
- const projectId = this.dbLoader.projectId;
3260
3684
  let deleted = 0;
3261
3685
  let errors = 0;
3262
3686
  try {
@@ -3270,9 +3694,6 @@ var HistoryCleanupManager = class {
3270
3694
  if (organizationId) {
3271
3695
  filter.organization_id = organizationId;
3272
3696
  }
3273
- if (projectId !== void 0) {
3274
- filter.project_id = projectId;
3275
- }
3276
3697
  try {
3277
3698
  const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
3278
3699
  deleted += result.deleted;
@@ -3285,7 +3706,6 @@ var HistoryCleanupManager = class {
3285
3706
  try {
3286
3707
  const baseWhere = {};
3287
3708
  if (organizationId) baseWhere.organization_id = organizationId;
3288
- if (projectId !== void 0) baseWhere.project_id = projectId;
3289
3709
  const metadataIds = await driver.find(historyTableName, {
3290
3710
  object: historyTableName,
3291
3711
  where: baseWhere,
@@ -3373,13 +3793,11 @@ var HistoryCleanupManager = class {
3373
3793
  const driver = this.dbLoader.driver;
3374
3794
  const historyTableName = this.dbLoader.historyTableName;
3375
3795
  const organizationId = this.dbLoader.organizationId;
3376
- const projectId = this.dbLoader.projectId;
3377
3796
  let recordsByAge = 0;
3378
3797
  let recordsByCount = 0;
3379
3798
  try {
3380
3799
  const baseWhere = {};
3381
3800
  if (organizationId) baseWhere.organization_id = organizationId;
3382
- if (projectId !== void 0) baseWhere.project_id = projectId;
3383
3801
  if (this.policy.maxAgeDays) {
3384
3802
  const cutoffDate = /* @__PURE__ */ new Date();
3385
3803
  cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);