@objectstack/metadata 4.1.0 → 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/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,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/index.ts
31
183
  var index_exports = {};
32
184
  __export(index_exports, {
@@ -1818,6 +1970,21 @@ var _MetadataManager = class _MetadataManager {
1818
1970
  unsubscribe: () => this.removeWatchCallback(type, wrappedCallback)
1819
1971
  };
1820
1972
  }
1973
+ /**
1974
+ * Subscribe to raw metadata watch events for a given type.
1975
+ *
1976
+ * Unlike `watchService` (which maps to the IMetadataService contract and
1977
+ * drops fields like `path`/`timestamp`), this returns the raw
1978
+ * `MetadataWatchEvent` produced by the underlying watcher — useful for
1979
+ * developer-facing tooling such as the HMR SSE endpoint that wants the
1980
+ * source file path and original timestamp.
1981
+ *
1982
+ * @returns An unsubscribe function.
1983
+ */
1984
+ subscribe(type, callback) {
1985
+ this.addWatchCallback(type, callback);
1986
+ return () => this.removeWatchCallback(type, callback);
1987
+ }
1821
1988
  // ==========================================
1822
1989
  // Import / Export
1823
1990
  // ==========================================
@@ -2833,6 +3000,33 @@ var MetadataPlugin = class {
2833
3000
  error: e.message
2834
3001
  });
2835
3002
  }
3003
+ try {
3004
+ const httpServer = ctx.getService("http-server") ?? ctx.getService("http.server");
3005
+ if (httpServer && typeof httpServer.getRawApp === "function") {
3006
+ const { registerMetadataHmrRoutes: registerMetadataHmrRoutes2 } = await Promise.resolve().then(() => (init_hmr_routes(), hmr_routes_exports));
3007
+ const hub = registerMetadataHmrRoutes2(httpServer.getRawApp(), this.manager);
3008
+ hub.setOnPostReload(async (body = {}) => {
3009
+ const src2 = this.options.artifactSource;
3010
+ if (src2?.mode === "local-file") {
3011
+ try {
3012
+ await this._loadFromLocalFile(ctx, src2.path, src2.fetchTimeoutMs);
3013
+ ctx.logger.info("[MetadataPlugin] artifact reloaded via HMR POST", {
3014
+ path: src2.path,
3015
+ reason: body?.reason
3016
+ });
3017
+ } catch (e) {
3018
+ ctx.logger.warn("[MetadataPlugin] artifact reload failed", { error: e?.message });
3019
+ throw e;
3020
+ }
3021
+ }
3022
+ });
3023
+ console.log("[MetadataPlugin] HMR endpoint registered at /api/v1/dev/metadata-events");
3024
+ } else {
3025
+ console.log("[MetadataPlugin] HTTP server with getRawApp() not available \u2014 skipping HMR endpoint");
3026
+ }
3027
+ } catch (e) {
3028
+ console.warn("[MetadataPlugin] Failed to register HMR endpoint", e?.message);
3029
+ }
2836
3030
  };
2837
3031
  this.options = {
2838
3032
  watch: true,