@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.js CHANGED
@@ -1,9 +1,167 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
2
6
  var __export = (target, all) => {
3
7
  for (var name in all)
4
8
  __defProp(target, name, { get: all[name], enumerable: true });
5
9
  };
6
10
 
11
+ // src/routes/hmr-routes.ts
12
+ var hmr_routes_exports = {};
13
+ __export(hmr_routes_exports, {
14
+ registerMetadataHmrRoutes: () => registerMetadataHmrRoutes
15
+ });
16
+ function registerMetadataHmrRoutes(app, manager, options = {}) {
17
+ const routePath = options.path ?? "/api/v1/dev/metadata-events";
18
+ const listeners = /* @__PURE__ */ new Set();
19
+ const broadcast = (evt) => {
20
+ for (const l of listeners) {
21
+ try {
22
+ l(evt);
23
+ } catch {
24
+ }
25
+ }
26
+ };
27
+ let fsHookInstalled = false;
28
+ const installFsHooks = async () => {
29
+ if (fsHookInstalled) return;
30
+ const mgr = manager;
31
+ if (typeof mgr.subscribe !== "function") {
32
+ fsHookInstalled = true;
33
+ return;
34
+ }
35
+ const types = await manager.getRegisteredTypes();
36
+ for (const type of types) {
37
+ mgr.subscribe(type, (evt) => {
38
+ const ts = typeof evt.timestamp === "string" ? Date.parse(evt.timestamp) : evt.timestamp ?? Date.now();
39
+ broadcast({
40
+ kind: "metadata-change",
41
+ type: evt.type ?? "changed",
42
+ metadataType: evt.metadataType ?? type,
43
+ name: evt.name ?? "",
44
+ path: evt.path,
45
+ timestamp: Number.isFinite(ts) ? ts : Date.now(),
46
+ // Forward the canonical server-side sequence number when the
47
+ // event originated from a MetadataRepository (ADR-0008). Legacy
48
+ // chokidar-driven events have no seq — clients fall back to
49
+ // their local counter in that case.
50
+ ...typeof evt.seq === "number" ? { seq: evt.seq } : {}
51
+ });
52
+ });
53
+ }
54
+ fsHookInstalled = true;
55
+ };
56
+ installFsHooks().catch(() => {
57
+ });
58
+ let onPostReload = null;
59
+ app.get(routePath, async (c) => {
60
+ await installFsHooks().catch(() => {
61
+ });
62
+ const types = await manager.getRegisteredTypes().catch(() => []);
63
+ const stream = new ReadableStream({
64
+ async start(controller) {
65
+ const enc = new TextEncoder();
66
+ let closed = false;
67
+ const safeEnqueue = (chunk) => {
68
+ if (closed) return;
69
+ try {
70
+ controller.enqueue(enc.encode(chunk));
71
+ } catch {
72
+ closed = true;
73
+ }
74
+ };
75
+ const listener = (evt) => {
76
+ if (closed) return;
77
+ const eventName = evt.kind === "reload" ? "reload" : "metadata-change";
78
+ safeEnqueue(`event: ${eventName}
79
+ data: ${JSON.stringify(evt)}
80
+
81
+ `);
82
+ };
83
+ listeners.add(listener);
84
+ safeEnqueue(`event: ready
85
+ data: ${JSON.stringify({ types, timestamp: Date.now() })}
86
+
87
+ `);
88
+ const heartbeat = setInterval(() => {
89
+ safeEnqueue(`: ping ${Date.now()}
90
+
91
+ `);
92
+ }, 15e3);
93
+ const cleanup = () => {
94
+ if (closed) return;
95
+ closed = true;
96
+ clearInterval(heartbeat);
97
+ listeners.delete(listener);
98
+ try {
99
+ controller.close();
100
+ } catch {
101
+ }
102
+ };
103
+ const signal = c.req?.raw?.signal;
104
+ if (signal) {
105
+ if (signal.aborted) cleanup();
106
+ else signal.addEventListener("abort", cleanup, { once: true });
107
+ }
108
+ }
109
+ });
110
+ return new Response(stream, {
111
+ status: 200,
112
+ headers: {
113
+ "Content-Type": "text/event-stream; charset=utf-8",
114
+ "Cache-Control": "no-cache, no-transform",
115
+ "Connection": "keep-alive",
116
+ "X-Accel-Buffering": "no"
117
+ }
118
+ });
119
+ });
120
+ app.post(routePath, async (c) => {
121
+ let body = {};
122
+ try {
123
+ const ct = c.req?.header?.("content-type") ?? "";
124
+ if (typeof c.req?.json === "function" && ct.includes("json")) {
125
+ body = await c.req.json();
126
+ }
127
+ } catch {
128
+ }
129
+ try {
130
+ if (onPostReload) await onPostReload(body);
131
+ } catch (e) {
132
+ return new Response(
133
+ JSON.stringify({ ok: false, error: e?.message ?? "reload handler failed" }),
134
+ { status: 500, headers: { "Content-Type": "application/json" } }
135
+ );
136
+ }
137
+ const reason = body.reason ?? "manual-trigger";
138
+ broadcast({
139
+ kind: "reload",
140
+ reason,
141
+ changed: body.changed,
142
+ timestamp: Date.now()
143
+ });
144
+ return new Response(
145
+ JSON.stringify({ ok: true, listeners: listeners.size, reason }),
146
+ { status: 200, headers: { "Content-Type": "application/json" } }
147
+ );
148
+ });
149
+ return {
150
+ broadcastReload(reason, changed) {
151
+ broadcast({ kind: "reload", reason, changed, timestamp: Date.now() });
152
+ },
153
+ setOnPostReload(fn) {
154
+ onPostReload = fn;
155
+ },
156
+ listenerCount: () => listeners.size
157
+ };
158
+ }
159
+ var init_hmr_routes = __esm({
160
+ "src/routes/hmr-routes.ts"() {
161
+ "use strict";
162
+ }
163
+ });
164
+
7
165
  // src/metadata-manager.ts
8
166
  import { createLogger } from "@objectstack/core";
9
167
 
@@ -406,7 +564,7 @@ var DatabaseLoader = class {
406
564
  this.tableName = options.tableName ?? "sys_metadata";
407
565
  this.historyTableName = options.historyTableName ?? "sys_metadata_history";
408
566
  this.organizationId = options.organizationId;
409
- this.projectId = options.projectId;
567
+ void options.projectId;
410
568
  this.trackHistory = options.trackHistory !== false;
411
569
  const cacheOpts = options.cache;
412
570
  const cacheEnabled = cacheOpts?.enabled !== false;
@@ -496,6 +654,26 @@ var DatabaseLoader = class {
496
654
  }
497
655
  return this.driver.delete(table, id);
498
656
  }
657
+ /**
658
+ * Compute the next per-org `event_seq` for `sys_metadata_history`.
659
+ * Reads `MAX(event_seq) + 1` for the configured `organization_id`.
660
+ * Legacy path — not transactional, so concurrent writes can collide.
661
+ * The canonical (transactional) producer is `SysMetadataRepository`.
662
+ */
663
+ async nextEventSeq() {
664
+ const where = this.organizationId ? { organization_id: this.organizationId } : {};
665
+ try {
666
+ const rows = await this._find(this.historyTableName, { where });
667
+ let max = 0;
668
+ for (const row of rows) {
669
+ const v = typeof row.event_seq === "number" ? row.event_seq : 0;
670
+ if (v > max) max = v;
671
+ }
672
+ return max + 1;
673
+ } catch {
674
+ return 1;
675
+ }
676
+ }
499
677
  /**
500
678
  * Ensure the metadata table exists.
501
679
  * Uses IDataDriver.syncSchema with the SysMetadataObject definition
@@ -560,8 +738,9 @@ var DatabaseLoader = class {
560
738
  }
561
739
  /**
562
740
  * Build base filter conditions for queries.
563
- * Filters by organizationId when configured; project_id when projectId is set,
564
- * or null (platform-global) when not set.
741
+ * Filters by organizationId when configured. `projectId` is accepted
742
+ * for back-compat but no longer constrains the query — see
743
+ * ADR-0008 §0 (branch/project removal).
565
744
  */
566
745
  baseFilter(type, name) {
567
746
  const filter = { type };
@@ -571,7 +750,6 @@ var DatabaseLoader = class {
571
750
  if (this.organizationId) {
572
751
  filter.organization_id = this.organizationId;
573
752
  }
574
- filter.project_id = this.projectId ?? null;
575
753
  return filter;
576
754
  }
577
755
  /**
@@ -597,6 +775,7 @@ var DatabaseLoader = class {
597
775
  }
598
776
  const historyId = generateId();
599
777
  const metadataJson = JSON.stringify(metadata);
778
+ const eventSeq = await this.nextEventSeq();
600
779
  const historyRecord = {
601
780
  id: historyId,
602
781
  metadataId,
@@ -610,12 +789,12 @@ var DatabaseLoader = class {
610
789
  changeNote,
611
790
  recordedBy,
612
791
  recordedAt: now,
613
- ...this.organizationId ? { organizationId: this.organizationId } : {},
614
- ...this.projectId !== void 0 ? { projectId: this.projectId } : {}
792
+ ...this.organizationId ? { organizationId: this.organizationId } : {}
615
793
  };
616
794
  try {
617
795
  await this._create(this.historyTableName, {
618
796
  id: historyRecord.id,
797
+ event_seq: eventSeq,
619
798
  metadata_id: historyRecord.metadataId,
620
799
  name: historyRecord.name,
621
800
  type: historyRecord.type,
@@ -627,8 +806,8 @@ var DatabaseLoader = class {
627
806
  change_note: historyRecord.changeNote,
628
807
  recorded_by: historyRecord.recordedBy,
629
808
  recorded_at: historyRecord.recordedAt,
630
- ...this.organizationId ? { organization_id: this.organizationId } : {},
631
- ...this.projectId !== void 0 ? { project_id: this.projectId } : {}
809
+ source: "database-loader",
810
+ ...this.organizationId ? { organization_id: this.organizationId } : {}
632
811
  });
633
812
  } catch (error) {
634
813
  console.error(`Failed to create history record for ${type}/${name}:`, error);
@@ -815,7 +994,6 @@ var DatabaseLoader = class {
815
994
  if (this.organizationId) {
816
995
  filter.organization_id = this.organizationId;
817
996
  }
818
- filter.project_id = this.projectId ?? null;
819
997
  const row = await this._findOne(this.historyTableName, {
820
998
  where: filter
821
999
  });
@@ -832,7 +1010,6 @@ var DatabaseLoader = class {
832
1010
  previousChecksum: row.previous_checksum,
833
1011
  changeNote: row.change_note,
834
1012
  organizationId: row.organization_id,
835
- projectId: row.project_id,
836
1013
  recordedBy: row.recorded_by,
837
1014
  recordedAt: row.recorded_at
838
1015
  };
@@ -850,7 +1027,6 @@ var DatabaseLoader = class {
850
1027
  await this.ensureHistorySchema();
851
1028
  const filter = { type, name };
852
1029
  if (this.organizationId) filter.organization_id = this.organizationId;
853
- filter.project_id = this.projectId ?? null;
854
1030
  const metadataRecord = await this._findOne(this.tableName, { where: filter });
855
1031
  if (!metadataRecord) {
856
1032
  return { records: [], total: 0, hasMore: false };
@@ -859,7 +1035,6 @@ var DatabaseLoader = class {
859
1035
  metadata_id: metadataRecord.id
860
1036
  };
861
1037
  if (this.organizationId) historyFilter.organization_id = this.organizationId;
862
- historyFilter.project_id = this.projectId ?? null;
863
1038
  if (options?.operationType) historyFilter.operation_type = options.operationType;
864
1039
  if (options?.since) historyFilter.recorded_at = { $gte: options.since };
865
1040
  if (options?.until) {
@@ -898,7 +1073,6 @@ var DatabaseLoader = class {
898
1073
  previousChecksum: row.previous_checksum,
899
1074
  changeNote: row.change_note,
900
1075
  organizationId: row.organization_id,
901
- projectId: row.project_id,
902
1076
  recordedBy: row.recorded_by,
903
1077
  recordedAt: row.recorded_at
904
1078
  };
@@ -1005,7 +1179,6 @@ var DatabaseLoader = class {
1005
1179
  version: 1,
1006
1180
  source: "database",
1007
1181
  ...this.organizationId ? { organization_id: this.organizationId } : {},
1008
- ...this.projectId !== void 0 ? { project_id: this.projectId } : { project_id: null },
1009
1182
  created_at: now,
1010
1183
  updated_at: now
1011
1184
  });
@@ -1078,6 +1251,7 @@ var _MetadataManager = class _MetadataManager {
1078
1251
  // Invalidated on every `register()` / `unregister()` to keep CRUD writes
1079
1252
  // visible to subsequent reads.
1080
1253
  this.listCache = /* @__PURE__ */ new Map();
1254
+ this.repoWatchClosed = false;
1081
1255
  this.config = config;
1082
1256
  this.logger = createLogger({ level: "info", format: "pretty" });
1083
1257
  this.serializers = /* @__PURE__ */ new Map();
@@ -1773,6 +1947,21 @@ var _MetadataManager = class _MetadataManager {
1773
1947
  unsubscribe: () => this.removeWatchCallback(type, wrappedCallback)
1774
1948
  };
1775
1949
  }
1950
+ /**
1951
+ * Subscribe to raw metadata watch events for a given type.
1952
+ *
1953
+ * Unlike `watchService` (which maps to the IMetadataService contract and
1954
+ * drops fields like `path`/`timestamp`), this returns the raw
1955
+ * `MetadataWatchEvent` produced by the underlying watcher — useful for
1956
+ * developer-facing tooling such as the HMR SSE endpoint that wants the
1957
+ * source file path and original timestamp.
1958
+ *
1959
+ * @returns An unsubscribe function.
1960
+ */
1961
+ subscribe(type, callback) {
1962
+ this.addWatchCallback(type, callback);
1963
+ return () => this.removeWatchCallback(type, callback);
1964
+ }
1776
1965
  // ==========================================
1777
1966
  // Import / Export
1778
1967
  // ==========================================
@@ -2070,6 +2259,110 @@ var _MetadataManager = class _MetadataManager {
2070
2259
  */
2071
2260
  async stopWatching() {
2072
2261
  }
2262
+ // ─── ADR-0008 PR-6: Repository wiring ───────────────────────────────
2263
+ /**
2264
+ * Attach a {@link MetadataRepository} as a supplementary event source.
2265
+ *
2266
+ * The manager subscribes to `repo.watch({})` and re-emits each event
2267
+ * through {@link notifyWatchers} as a legacy `MetadataWatchEvent`.
2268
+ * Each event also invalidates the in-memory registry entry and the
2269
+ * `list()` cache for the affected type so subsequent reads see fresh
2270
+ * data.
2271
+ *
2272
+ * No write-through. `register()` / `unregister()` / `save()` are
2273
+ * untouched in this PR (deferred to ADR-0008 M0 PR-10).
2274
+ *
2275
+ * Call {@link dispose} (or {@link stopRepositoryWatch}) to detach.
2276
+ */
2277
+ setRepository(repo) {
2278
+ if (this.repository === repo) return;
2279
+ if (this.repository) {
2280
+ void this.stopRepositoryWatch();
2281
+ }
2282
+ this.repository = repo;
2283
+ this.repoWatchClosed = false;
2284
+ void this.startRepositoryWatch();
2285
+ }
2286
+ /** Return the attached repository, if any. */
2287
+ getRepository() {
2288
+ return this.repository;
2289
+ }
2290
+ /** Stop the active repo.watch() loop (best-effort). */
2291
+ async stopRepositoryWatch() {
2292
+ this.repoWatchClosed = true;
2293
+ const iter = this.repoWatchIter;
2294
+ this.repoWatchIter = void 0;
2295
+ if (iter && typeof iter.return === "function") {
2296
+ try {
2297
+ await iter.return(void 0);
2298
+ } catch {
2299
+ }
2300
+ }
2301
+ }
2302
+ /**
2303
+ * Best-effort cleanup. Stops the FS watcher (if any), drains the
2304
+ * repository watch loop, and clears registry caches. Safe to call
2305
+ * multiple times.
2306
+ */
2307
+ async dispose() {
2308
+ await this.stopWatching().catch(() => void 0);
2309
+ await this.stopRepositoryWatch().catch(() => void 0);
2310
+ this.listCache.clear();
2311
+ }
2312
+ async startRepositoryWatch() {
2313
+ const repo = this.repository;
2314
+ if (!repo) return;
2315
+ const iterable = repo.watch({});
2316
+ const iter = iterable[Symbol.asyncIterator]();
2317
+ this.repoWatchIter = iter;
2318
+ try {
2319
+ while (!this.repoWatchClosed) {
2320
+ const { value, done } = await iter.next();
2321
+ if (done) break;
2322
+ try {
2323
+ this.applyRepoEvent(value);
2324
+ } catch (err) {
2325
+ this.logger.warn("[MetadataManager] repo event handler failed", {
2326
+ error: err instanceof Error ? err.message : String(err)
2327
+ });
2328
+ }
2329
+ }
2330
+ } catch (err) {
2331
+ if (!this.repoWatchClosed) {
2332
+ this.logger.warn("[MetadataManager] repository watch loop exited unexpectedly", {
2333
+ error: err instanceof Error ? err.message : String(err)
2334
+ });
2335
+ }
2336
+ } finally {
2337
+ if (this.repoWatchIter === iter) this.repoWatchIter = void 0;
2338
+ }
2339
+ }
2340
+ /** Translate a repo event to the legacy MetadataWatchEvent + invalidate caches. */
2341
+ applyRepoEvent(evt) {
2342
+ const ref = evt.ref;
2343
+ const type = ref.type;
2344
+ const name = ref.name;
2345
+ const typeStore = this.registry.get(type);
2346
+ if (typeStore) {
2347
+ typeStore.delete(name);
2348
+ if (typeStore.size === 0) this.registry.delete(type);
2349
+ }
2350
+ this.listCache.delete(type);
2351
+ const legacyType = evt.op === "create" ? "added" : evt.op === "delete" ? "deleted" : "changed";
2352
+ const legacyEvent = {
2353
+ type: legacyType,
2354
+ metadataType: type,
2355
+ name,
2356
+ path: "",
2357
+ // Repo events carry the hash only; the body is fetched on demand
2358
+ // via manager.get(type, name). HMR consumers don't read `data` so
2359
+ // this is fine for M0. (See ADR-0008 §12 open question 1.)
2360
+ data: void 0,
2361
+ timestamp: evt.ts
2362
+ };
2363
+ legacyEvent.seq = evt.seq;
2364
+ this.notifyWatchers(type, legacyEvent);
2365
+ }
2073
2366
  notifyWatchers(type, event) {
2074
2367
  const callbacks = this.watchCallbacks.get(type);
2075
2368
  if (!callbacks) return;
@@ -2545,7 +2838,13 @@ var NodeMetadataManager = class extends MetadataManager {
2545
2838
  this.watcher = chokidarWatch(rootDir, {
2546
2839
  ignored,
2547
2840
  persistent,
2548
- ignoreInitial: true
2841
+ ignoreInitial: true,
2842
+ // Use polling to avoid `fs.watch` EMFILE on macOS / busy dev hosts.
2843
+ // Recursive watch over a project root would otherwise wire native
2844
+ // watches across the entire tree, easily exhausting the FD pool.
2845
+ usePolling: true,
2846
+ interval: 1e3,
2847
+ binaryInterval: 2e3
2549
2848
  });
2550
2849
  this.watcher.on("add", async (filePath) => {
2551
2850
  await this.handleFileEvent("added", filePath);
@@ -2683,6 +2982,7 @@ var queryableMetadataObjects = [
2683
2982
  SysMetadataObject2,
2684
2983
  SysMetadataHistoryObject2
2685
2984
  ];
2985
+ var REPO_SUBDIR = ".objectstack/metadata";
2686
2986
  var ARTIFACT_FIELD_TO_TYPE = {
2687
2987
  objects: "object",
2688
2988
  objectExtensions: "object_extension",
@@ -2780,6 +3080,31 @@ var MetadataPlugin = class {
2780
3080
  await this._loadFromFileSystem(ctx);
2781
3081
  }
2782
3082
  }
3083
+ const bootstrapMode = this.options.config?.bootstrap ?? "eager";
3084
+ if (bootstrapMode !== "artifact-only") {
3085
+ try {
3086
+ const path3 = await import("path");
3087
+ const { FileSystemRepository } = await import("@objectstack/metadata-fs");
3088
+ const rootDir = this.options.rootDir || process.cwd();
3089
+ const repoRoot = path3.join(rootDir, REPO_SUBDIR);
3090
+ const repo = new FileSystemRepository({
3091
+ root: repoRoot,
3092
+ org: this.options.organizationId ?? "system",
3093
+ disableWatch: this.options.watch === false
3094
+ });
3095
+ await repo.start();
3096
+ this.repository = repo;
3097
+ this.manager.setRepository(repo);
3098
+ ctx.logger.info("[MetadataPlugin] FileSystemRepository attached", {
3099
+ repoRoot,
3100
+ watch: this.options.watch !== false
3101
+ });
3102
+ } catch (e) {
3103
+ ctx.logger.warn("[MetadataPlugin] Failed to attach FileSystemRepository", {
3104
+ error: e?.message
3105
+ });
3106
+ }
3107
+ }
2783
3108
  try {
2784
3109
  const realtimeService = ctx.getService("realtime");
2785
3110
  if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
@@ -2791,6 +3116,102 @@ var MetadataPlugin = class {
2791
3116
  error: e.message
2792
3117
  });
2793
3118
  }
3119
+ try {
3120
+ const httpServer = ctx.getService("http-server") ?? ctx.getService("http.server");
3121
+ if (httpServer && typeof httpServer.getRawApp === "function") {
3122
+ const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
3123
+ const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
3124
+ hub.setOnPostReload(async (body = {}) => {
3125
+ const src3 = this.options.artifactSource;
3126
+ if (src3?.mode === "local-file") {
3127
+ try {
3128
+ await this._loadFromLocalFile(ctx, src3.path, src3.fetchTimeoutMs);
3129
+ ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
3130
+ path: src3.path,
3131
+ reason: body?.reason
3132
+ });
3133
+ } catch (e) {
3134
+ ctx.logger.warn("[MetadataPlugin] artifact reload failed", { error: e?.message });
3135
+ throw e;
3136
+ }
3137
+ }
3138
+ });
3139
+ const src2 = this.options.artifactSource;
3140
+ const wantArtifactWatch = this.options.artifactWatch ?? src2?.mode === "local-file";
3141
+ if (src2?.mode === "local-file" && wantArtifactWatch && !/^https?:\/\//i.test(src2.path)) {
3142
+ try {
3143
+ const { watch: chokidarWatch2 } = await import("chokidar");
3144
+ const w = chokidarWatch2(src2.path, {
3145
+ ignoreInitial: true,
3146
+ awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 20 },
3147
+ persistent: true,
3148
+ // Use polling to avoid `fs.watch` exhausting the
3149
+ // process file-descriptor limit on macOS (chokidar
3150
+ // recursively wires watches on the parent
3151
+ // directory tree which can trip EMFILE on busy
3152
+ // dev hosts). 500ms polling is fast enough for
3153
+ // HMR (a recompile takes ~400ms anyway).
3154
+ usePolling: true,
3155
+ interval: 500,
3156
+ binaryInterval: 1e3
3157
+ });
3158
+ let pending = false;
3159
+ const reload = async () => {
3160
+ if (pending) return;
3161
+ pending = true;
3162
+ try {
3163
+ await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
3164
+ hub.broadcastReload("artifact-file-changed", [src2.path]);
3165
+ ctx.logger.info("[MetadataPlugin] artifact auto-reloaded (file watcher)", {
3166
+ path: src2.path
3167
+ });
3168
+ } catch (e) {
3169
+ ctx.logger.warn("[MetadataPlugin] artifact auto-reload failed", { error: e?.message });
3170
+ } finally {
3171
+ pending = false;
3172
+ }
3173
+ };
3174
+ w.on("change", () => {
3175
+ void reload();
3176
+ });
3177
+ w.on("add", () => {
3178
+ void reload();
3179
+ });
3180
+ this.artifactWatcher = { close: () => w.close() };
3181
+ console.log("[MetadataPlugin] artifact file watcher attached", src2.path);
3182
+ } catch (e) {
3183
+ ctx.logger.warn("[MetadataPlugin] artifact watcher failed to start", { error: e?.message });
3184
+ }
3185
+ }
3186
+ console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
3187
+ } else {
3188
+ console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
3189
+ }
3190
+ } catch (e) {
3191
+ console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
3192
+ }
3193
+ };
3194
+ this.stop = async (ctx) => {
3195
+ if (this.artifactWatcher) {
3196
+ try {
3197
+ await this.artifactWatcher.close();
3198
+ } catch {
3199
+ }
3200
+ this.artifactWatcher = void 0;
3201
+ }
3202
+ try {
3203
+ await this.manager.dispose();
3204
+ } catch (e) {
3205
+ ctx.logger.warn("[MetadataPlugin] manager.dispose() failed", { error: e?.message });
3206
+ }
3207
+ const repo = this.repository;
3208
+ if (repo && typeof repo.close === "function") {
3209
+ try {
3210
+ await repo.close();
3211
+ } catch {
3212
+ }
3213
+ }
3214
+ this.repository = void 0;
2794
3215
  };
2795
3216
  this.options = {
2796
3217
  watch: true,
@@ -2868,7 +3289,12 @@ var MetadataPlugin = class {
2868
3289
  const items = metadata[field];
2869
3290
  if (!Array.isArray(items) || items.length === 0) continue;
2870
3291
  for (const item of items) {
2871
- const name = item?.name;
3292
+ let name = item?.name;
3293
+ if (!name) {
3294
+ if (metaType === "view") {
3295
+ name = item?.list?.data?.object ?? item?.form?.data?.object;
3296
+ }
3297
+ }
2872
3298
  if (!name) continue;
2873
3299
  if (manifestPackageId && item._packageId === void 0) {
2874
3300
  item._packageId = manifestPackageId;
@@ -3212,7 +3638,6 @@ var HistoryCleanupManager = class {
3212
3638
  const driver = this.dbLoader.driver;
3213
3639
  const historyTableName = this.dbLoader.historyTableName;
3214
3640
  const organizationId = this.dbLoader.organizationId;
3215
- const projectId = this.dbLoader.projectId;
3216
3641
  let deleted = 0;
3217
3642
  let errors = 0;
3218
3643
  try {
@@ -3226,9 +3651,6 @@ var HistoryCleanupManager = class {
3226
3651
  if (organizationId) {
3227
3652
  filter.organization_id = organizationId;
3228
3653
  }
3229
- if (projectId !== void 0) {
3230
- filter.project_id = projectId;
3231
- }
3232
3654
  try {
3233
3655
  const result = await this.bulkDeleteByFilter(driver, historyTableName, filter);
3234
3656
  deleted += result.deleted;
@@ -3241,7 +3663,6 @@ var HistoryCleanupManager = class {
3241
3663
  try {
3242
3664
  const baseWhere = {};
3243
3665
  if (organizationId) baseWhere.organization_id = organizationId;
3244
- if (projectId !== void 0) baseWhere.project_id = projectId;
3245
3666
  const metadataIds = await driver.find(historyTableName, {
3246
3667
  object: historyTableName,
3247
3668
  where: baseWhere,
@@ -3329,13 +3750,11 @@ var HistoryCleanupManager = class {
3329
3750
  const driver = this.dbLoader.driver;
3330
3751
  const historyTableName = this.dbLoader.historyTableName;
3331
3752
  const organizationId = this.dbLoader.organizationId;
3332
- const projectId = this.dbLoader.projectId;
3333
3753
  let recordsByAge = 0;
3334
3754
  let recordsByCount = 0;
3335
3755
  try {
3336
3756
  const baseWhere = {};
3337
3757
  if (organizationId) baseWhere.organization_id = organizationId;
3338
- if (projectId !== void 0) baseWhere.project_id = projectId;
3339
3758
  if (this.policy.maxAgeDays) {
3340
3759
  const cutoffDate = /* @__PURE__ */ new Date();
3341
3760
  cutoffDate.setDate(cutoffDate.getDate() - this.policy.maxAgeDays);