@objectstack/metadata 4.1.1 → 4.2.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,155 @@ 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
+ });
69
+ });
70
+ }
71
+ fsHookInstalled = true;
72
+ };
73
+ installFsHooks().catch(() => {
74
+ });
75
+ let onPostReload = null;
76
+ app.get(routePath, async (c) => {
77
+ await installFsHooks().catch(() => {
78
+ });
79
+ const types = await manager.getRegisteredTypes().catch(() => []);
80
+ const stream = new ReadableStream({
81
+ async start(controller) {
82
+ const enc = new TextEncoder();
83
+ let closed = false;
84
+ const safeEnqueue = (chunk) => {
85
+ if (closed) return;
86
+ try {
87
+ controller.enqueue(enc.encode(chunk));
88
+ } catch {
89
+ closed = true;
90
+ }
91
+ };
92
+ const listener = (evt) => {
93
+ if (closed) return;
94
+ const eventName = evt.kind === "reload" ? "reload" : "metadata-change";
95
+ safeEnqueue(`event: ${eventName}
96
+ data: ${JSON.stringify(evt)}
97
+
98
+ `);
99
+ };
100
+ listeners.add(listener);
101
+ safeEnqueue(`event: ready
102
+ data: ${JSON.stringify({ types, timestamp: Date.now() })}
103
+
104
+ `);
105
+ const heartbeat = setInterval(() => {
106
+ safeEnqueue(`: ping ${Date.now()}
107
+
108
+ `);
109
+ }, 15e3);
110
+ const cleanup = () => {
111
+ if (closed) return;
112
+ closed = true;
113
+ clearInterval(heartbeat);
114
+ listeners.delete(listener);
115
+ try {
116
+ controller.close();
117
+ } catch {
118
+ }
119
+ };
120
+ const signal = c.req?.raw?.signal;
121
+ if (signal) {
122
+ if (signal.aborted) cleanup();
123
+ else signal.addEventListener("abort", cleanup, { once: true });
124
+ }
125
+ }
126
+ });
127
+ return new Response(stream, {
128
+ status: 200,
129
+ headers: {
130
+ "Content-Type": "text/event-stream; charset=utf-8",
131
+ "Cache-Control": "no-cache, no-transform",
132
+ "Connection": "keep-alive",
133
+ "X-Accel-Buffering": "no"
134
+ }
135
+ });
136
+ });
137
+ app.post(routePath, async (c) => {
138
+ let body = {};
139
+ try {
140
+ const ct = c.req?.header?.("content-type") ?? "";
141
+ if (typeof c.req?.json === "function" && ct.includes("json")) {
142
+ body = await c.req.json();
143
+ }
144
+ } catch {
145
+ }
146
+ try {
147
+ if (onPostReload) await onPostReload(body);
148
+ } catch (e) {
149
+ return new Response(
150
+ JSON.stringify({ ok: false, error: e?.message ?? "reload handler failed" }),
151
+ { status: 500, headers: { "Content-Type": "application/json" } }
152
+ );
153
+ }
154
+ const reason = body.reason ?? "manual-trigger";
155
+ broadcast({
156
+ kind: "reload",
157
+ reason,
158
+ changed: body.changed,
159
+ timestamp: Date.now()
160
+ });
161
+ return new Response(
162
+ JSON.stringify({ ok: true, listeners: listeners.size, reason }),
163
+ { status: 200, headers: { "Content-Type": "application/json" } }
164
+ );
165
+ });
166
+ return {
167
+ broadcastReload(reason, changed) {
168
+ broadcast({ kind: "reload", reason, changed, timestamp: Date.now() });
169
+ },
170
+ setOnPostReload(fn) {
171
+ onPostReload = fn;
172
+ },
173
+ listenerCount: () => listeners.size
174
+ };
175
+ }
176
+ var init_hmr_routes = __esm({
177
+ "src/routes/hmr-routes.ts"() {
178
+ "use strict";
179
+ }
180
+ });
181
+
30
182
  // src/node.ts
31
183
  var node_exports = {};
32
184
  __export(node_exports, {
@@ -1820,6 +1972,21 @@ var _MetadataManager = class _MetadataManager {
1820
1972
  unsubscribe: () => this.removeWatchCallback(type, wrappedCallback)
1821
1973
  };
1822
1974
  }
1975
+ /**
1976
+ * Subscribe to raw metadata watch events for a given type.
1977
+ *
1978
+ * Unlike `watchService` (which maps to the IMetadataService contract and
1979
+ * drops fields like `path`/`timestamp`), this returns the raw
1980
+ * `MetadataWatchEvent` produced by the underlying watcher — useful for
1981
+ * developer-facing tooling such as the HMR SSE endpoint that wants the
1982
+ * source file path and original timestamp.
1983
+ *
1984
+ * @returns An unsubscribe function.
1985
+ */
1986
+ subscribe(type, callback) {
1987
+ this.addWatchCallback(type, callback);
1988
+ return () => this.removeWatchCallback(type, callback);
1989
+ }
1823
1990
  // ==========================================
1824
1991
  // Import / Export
1825
1992
  // ==========================================
@@ -2835,6 +3002,33 @@ var MetadataPlugin = class {
2835
3002
  error: e.message
2836
3003
  });
2837
3004
  }
3005
+ try {
3006
+ const httpServer = ctx.getService("http-server") ?? ctx.getService("http.server");
3007
+ if (httpServer && typeof httpServer.getRawApp === "function") {
3008
+ const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
3009
+ const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
3010
+ hub.setOnPostReload(async (body = {}) => {
3011
+ const src2 = this.options.artifactSource;
3012
+ if (src2?.mode === "local-file") {
3013
+ try {
3014
+ await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
3015
+ ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
3016
+ path: src2.path,
3017
+ reason: body?.reason
3018
+ });
3019
+ } catch (e) {
3020
+ ctx.logger.warn("[MetadataPlugin] artifact reload failed", { error: e?.message });
3021
+ throw e;
3022
+ }
3023
+ }
3024
+ });
3025
+ console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
3026
+ } else {
3027
+ console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
3028
+ }
3029
+ } catch (e) {
3030
+ console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
3031
+ }
2838
3032
  };
2839
3033
  this.options = {
2840
3034
  watch: true,