@moostjs/otel 0.6.49 → 0.6.51

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
@@ -1,7 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  let _opentelemetry_api = require("@opentelemetry/api");
3
3
  let moost = require("moost");
4
+ let _opentelemetry_core = require("@opentelemetry/core");
4
5
  let _moostjs_event_http = require("@moostjs/event-http");
6
+ let net = require("net");
5
7
  let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
6
8
 
7
9
  //#region packages/otel/src/context.ts
@@ -184,6 +186,56 @@ function readHandlerOtelMeta(controller, method) {
184
186
  }
185
187
  return meta;
186
188
  }
189
+ /** Root spans already owned by a Moost event — never attached to (renamed, re-attributed) twice. */ const claimedSpans = /* @__PURE__ */ new WeakSet();
190
+ const otelHostSpanKey = (0, moost.globalKey)("otel.hostSpan");
191
+ /**
192
+ * The server span of the http instrumentation (`@opentelemetry/instrumentation-http`) for this
193
+ * network request, when no Moost event owns it yet.
194
+ *
195
+ * The instrumentation publishes it as the request's RPC metadata, so it is found even when a host
196
+ * app's middleware span (Express instrumentation) is active, not the server span itself. Without
197
+ * RPC metadata only an active SERVER span qualifies — anything else active (a lifecycle span of a
198
+ * running event, a client span) is a parent, not the request's own span. An in-process request
199
+ * (`fetch()` / `request()` on the HTTP app) runs on a fake socket and never attaches: it is not
200
+ * the network request the server span stands for.
201
+ */ function getHostServerSpan(req) {
202
+ if (!(req?.socket instanceof net.Socket)) return;
203
+ const rpc = (0, _opentelemetry_core.getRPCMetadata)(_opentelemetry_api.context.active());
204
+ if (rpc?.type === _opentelemetry_core.RPCType.HTTP && rpc.span) return claimedSpans.has(rpc.span) ? void 0 : {
205
+ span: rpc.span,
206
+ rpc,
207
+ mount: getMountRoute(req, rpc)
208
+ };
209
+ const active = _opentelemetry_api.trace.getActiveSpan();
210
+ return active?.kind === _opentelemetry_api.SpanKind.SERVER && !claimedSpans.has(active) ? {
211
+ span: active,
212
+ mount: ""
213
+ } : void 0;
214
+ }
215
+ /**
216
+ * The route template a host app mounted Moost under, when it stripped that path from `req.url`
217
+ * (`app.use('/api', moostMiddleware)`): the route the host's own instrumentation reported
218
+ * for the request so far. Empty when Moost sees the whole path.
219
+ */ function getMountRoute(req, rpc) {
220
+ const path = (url) => url?.split("?")[0];
221
+ if (!req.originalUrl || path(req.originalUrl) === path(req.url) || !rpc.route) return "";
222
+ return rpc.route.replace(/\/+$/, "");
223
+ }
224
+ /**
225
+ * The controller context of the current event, or `undefined` while no handler is resolved yet
226
+ * (an unmatched route reports `Handler:not_found` before any controller context exists).
227
+ */ function readControllerContext() {
228
+ const cc = (0, moost.useControllerContext)();
229
+ try {
230
+ return {
231
+ controller: cc.getController(),
232
+ method: cc.getMethod(),
233
+ route: cc.getRoute()
234
+ };
235
+ } catch {
236
+ return;
237
+ }
238
+ }
187
239
  /** Context injector that wraps Moost lifecycle hooks with OpenTelemetry spans and records metrics. */ var SpanInjector = class extends moost.ContextInjector {
188
240
  with(name, attributes, cb) {
189
241
  const fn = typeof attributes === "function" ? attributes : cb;
@@ -206,45 +258,46 @@ function readHandlerOtelMeta(controller, method) {
206
258
  const res = this.getResponse();
207
259
  if (res) {
208
260
  const originalWriteHead = res.writeHead;
209
- Object.assign(res, { writeHead: (arg0, arg1, arg2) => {
210
- res._statusCode = arg0;
211
- const headers = typeof arg2 === "object" ? arg2 : typeof arg1 === "object" ? arg1 : void 0;
212
- res._contentLength = headers?.["content-length"] ? Number(headers["content-length"]) : 0;
213
- return originalWriteHead.apply(res, [
214
- arg0,
215
- arg1,
216
- arg2
217
- ]);
218
- } });
261
+ res.writeHead = (...args) => {
262
+ res._statusCode = args[0];
263
+ return originalWriteHead.apply(res, args);
264
+ };
219
265
  }
220
266
  }
221
267
  startEvent(eventType, cb) {
222
268
  if (eventType === "init") return cb();
223
269
  const { registerSpan } = useOtelContext();
224
- let span = _opentelemetry_api.trace.getActiveSpan();
225
- if (eventType === _moostjs_event_http.httpKind.kind) this.patchRsponse();
226
- else span = tracer.startSpan(`${eventType} Event`);
227
- if (span) {
228
- registerSpan(span);
229
- return this.withSpan(span, cb, {
230
- withMetrics: true,
231
- endSpan: eventType !== _moostjs_event_http.httpKind.kind
232
- });
270
+ const isHttp = eventType === _moostjs_event_http.httpKind.name;
271
+ const req = isHttp ? this.getRequest() : void 0;
272
+ if (isHttp) this.patchRsponse();
273
+ const host = getHostServerSpan(req);
274
+ const active = _opentelemetry_api.trace.getActiveSpan();
275
+ const span = host?.span ?? tracer.startSpan(isHttp ? req?.method || "HTTP" : `${eventType} Event`, isHttp && !active ? { kind: _opentelemetry_api.SpanKind.SERVER } : void 0);
276
+ claimedSpans.add(span);
277
+ registerSpan(span);
278
+ if (host) (0, moost.current)().set(otelHostSpanKey, host);
279
+ const opts = {
280
+ withMetrics: true,
281
+ endSpan: !host
282
+ };
283
+ if (host && active && active !== host.span) {
284
+ const outer = _opentelemetry_api.context.active();
285
+ return this.withSpan(span, () => _opentelemetry_api.context.with(outer, cb), opts);
233
286
  }
234
- return cb();
287
+ return this.withSpan(span, cb, opts);
235
288
  }
236
289
  getEventType() {
237
290
  const ctx = (0, moost.current)();
238
291
  return ctx.has(moost.eventTypeKey) ? ctx.get(moost.eventTypeKey) : void 0;
239
292
  }
240
293
  getIgnoreSpan() {
241
- const { getController, getMethod } = (0, moost.useControllerContext)();
242
- return readHandlerOtelMeta(getController(), getMethod()).ignoreSpan;
294
+ const cc = readControllerContext();
295
+ return readHandlerOtelMeta(cc?.controller, cc?.method).ignoreSpan;
243
296
  }
244
297
  getControllerHandlerMeta() {
245
- const { getMethod, getController, getRoute } = (0, moost.useControllerContext)();
246
- const methodName = getMethod();
247
- const meta = readHandlerOtelMeta(getController(), methodName);
298
+ const cc = readControllerContext();
299
+ const methodName = cc?.method;
300
+ const meta = readHandlerOtelMeta(cc?.controller, methodName);
248
301
  return {
249
302
  ignoreMeter: meta.ignoreMeter,
250
303
  ignoreSpan: meta.ignoreSpan,
@@ -255,7 +308,7 @@ function readHandlerOtelMeta(controller, method) {
255
308
  "moost.handler_label": meta.label,
256
309
  "moost.handler_id": meta.id,
257
310
  "moost.ignore": meta.ignoreSpan,
258
- "moost.route": getRoute(),
311
+ "moost.route": cc?.route,
259
312
  "moost.event_type": this.getEventType()
260
313
  }
261
314
  };
@@ -273,10 +326,7 @@ function readHandlerOtelMeta(controller, method) {
273
326
  const chm = this.getControllerHandlerMeta();
274
327
  const span = getSpan();
275
328
  if (span) {
276
- if (this.getEventType() === _moostjs_event_http.httpKind.kind) {
277
- const req = this.getRequest();
278
- span.updateName(`${req?.method || ""} ${req?.url}`);
279
- }
329
+ if (this.getEventType() === _moostjs_event_http.httpKind.name) span.updateName(this.getRequest()?.method || "HTTP");
280
330
  }
281
331
  this.startEventMetrics(chm.attrs, route);
282
332
  } else if (name === "Controller:registered") {
@@ -286,8 +336,15 @@ function readHandlerOtelMeta(controller, method) {
286
336
  const span = getSpan();
287
337
  if (span) {
288
338
  span.setAttributes(chm.attrs);
289
- if (chm.attrs["moost.event_type"] === _moostjs_event_http.httpKind.kind) span.updateName(`${this.getRequest()?.method || ""} ${_route || "<unresolved>"}`);
290
- else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
339
+ if (chm.attrs["moost.event_type"] === _moostjs_event_http.httpKind.name) {
340
+ const host = ctx.has(otelHostSpanKey) ? ctx.get(otelHostSpanKey) : void 0;
341
+ const httpRoute = _route ? `${host?.mount || ""}${_route}` : void 0;
342
+ span.updateName(`${this.getRequest()?.method || ""} ${httpRoute || "<unresolved>"}`);
343
+ if (httpRoute) {
344
+ span.setAttribute("http.route", httpRoute);
345
+ if (host?.rpc) host.rpc.route = httpRoute;
346
+ }
347
+ } else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
291
348
  }
292
349
  }
293
350
  if (name !== "Controller:registered") ctx.set(otelRouteKey, route);
@@ -314,6 +371,7 @@ function readHandlerOtelMeta(controller, method) {
314
371
  const ctx = (0, moost.current)();
315
372
  const route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
316
373
  const startTime = ctx.has(otelStartTimeKey) ? ctx.get(otelStartTimeKey) : void 0;
374
+ if (startTime === void 0 && route === void 0 && a["moost.event_type"] === _moostjs_event_http.httpKind.name) return;
317
375
  const duration = Date.now() - (startTime || Date.now() - 1);
318
376
  const attrs = {
319
377
  ...ctx.has(customMetricAttrsKey) ? ctx.get(customMetricAttrsKey) : {},
@@ -321,8 +379,7 @@ function readHandlerOtelMeta(controller, method) {
321
379
  "moost.event_type": a["moost.event_type"],
322
380
  "moost.is_error": error ? 1 : 0
323
381
  };
324
- if (a["moost.event_type"] === _moostjs_event_http.httpKind.kind) {
325
- if (!attrs.route) attrs.route = this.getRequest()?.url || "";
382
+ if (a["moost.event_type"] === _moostjs_event_http.httpKind.name) {
326
383
  attrs["http.status_code"] = this.getResponse()?._statusCode || 0;
327
384
  attrs["moost.is_error"] = attrs["moost.is_error"] || attrs["http.status_code"] > 399 ? 1 : 0;
328
385
  }
package/dist/index.d.ts CHANGED
@@ -4,8 +4,7 @@ import * as moost from 'moost';
4
4
  import { Mate, TMoostMetadata, TMateParamMeta, ContextInjector, TContextInjectorHook } from 'moost';
5
5
  import { EventContext } from '@wooksjs/event-core';
6
6
  import { BatchSpanProcessor, ReadableSpan, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
7
- import * as http from 'http';
8
- import { ServerResponse } from 'http';
7
+ import { IncomingMessage, ServerResponse } from 'http';
9
8
 
10
9
  /** Event context key storing the root OpenTelemetry span. */
11
10
  declare const otelSpanKey: moost.Key<Span | undefined>;
@@ -135,7 +134,7 @@ declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
135
134
  'moost.handler_label': string | undefined;
136
135
  'moost.handler_id': string | undefined;
137
136
  'moost.ignore': boolean | undefined;
138
- 'moost.route': string;
137
+ 'moost.route': string | undefined;
139
138
  'moost.event_type': string | undefined;
140
139
  };
141
140
  };
@@ -146,10 +145,9 @@ declare class SpanInjector extends ContextInjector<TContextInjectorHook> {
146
145
  }): T;
147
146
  startEventMetrics(a: Record<string, string | number | boolean | undefined>, route?: string): void;
148
147
  endEventMetrics(a: Record<string, string | number | boolean | undefined>, error?: Error): void;
149
- getRequest(): http.IncomingMessage | undefined;
150
- getResponse(): (ServerResponse<http.IncomingMessage> & {
148
+ getRequest(): IncomingMessage | undefined;
149
+ getResponse(): (ServerResponse<IncomingMessage> & {
151
150
  _statusCode?: number;
152
- _contentLength?: number;
153
151
  }) | undefined;
154
152
  }
155
153
 
package/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { SpanKind, SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
2
2
  import { ContextInjector, current, eventTypeKey, getConstructor, getMoostMate, globalKey, replaceContextInjector, useControllerContext } from "moost";
3
+ import { RPCType, getRPCMetadata } from "@opentelemetry/core";
3
4
  import { httpKind } from "@moostjs/event-http";
5
+ import { Socket } from "net";
4
6
  import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
5
7
 
6
8
  //#region packages/otel/src/context.ts
@@ -183,6 +185,56 @@ function readHandlerOtelMeta(controller, method) {
183
185
  }
184
186
  return meta;
185
187
  }
188
+ /** Root spans already owned by a Moost event — never attached to (renamed, re-attributed) twice. */ const claimedSpans = /* @__PURE__ */ new WeakSet();
189
+ const otelHostSpanKey = globalKey("otel.hostSpan");
190
+ /**
191
+ * The server span of the http instrumentation (`@opentelemetry/instrumentation-http`) for this
192
+ * network request, when no Moost event owns it yet.
193
+ *
194
+ * The instrumentation publishes it as the request's RPC metadata, so it is found even when a host
195
+ * app's middleware span (Express instrumentation) is active, not the server span itself. Without
196
+ * RPC metadata only an active SERVER span qualifies — anything else active (a lifecycle span of a
197
+ * running event, a client span) is a parent, not the request's own span. An in-process request
198
+ * (`fetch()` / `request()` on the HTTP app) runs on a fake socket and never attaches: it is not
199
+ * the network request the server span stands for.
200
+ */ function getHostServerSpan(req) {
201
+ if (!(req?.socket instanceof Socket)) return;
202
+ const rpc = getRPCMetadata(context.active());
203
+ if (rpc?.type === RPCType.HTTP && rpc.span) return claimedSpans.has(rpc.span) ? void 0 : {
204
+ span: rpc.span,
205
+ rpc,
206
+ mount: getMountRoute(req, rpc)
207
+ };
208
+ const active = trace.getActiveSpan();
209
+ return active?.kind === SpanKind.SERVER && !claimedSpans.has(active) ? {
210
+ span: active,
211
+ mount: ""
212
+ } : void 0;
213
+ }
214
+ /**
215
+ * The route template a host app mounted Moost under, when it stripped that path from `req.url`
216
+ * (`app.use('/api', moostMiddleware)`): the route the host's own instrumentation reported
217
+ * for the request so far. Empty when Moost sees the whole path.
218
+ */ function getMountRoute(req, rpc) {
219
+ const path = (url) => url?.split("?")[0];
220
+ if (!req.originalUrl || path(req.originalUrl) === path(req.url) || !rpc.route) return "";
221
+ return rpc.route.replace(/\/+$/, "");
222
+ }
223
+ /**
224
+ * The controller context of the current event, or `undefined` while no handler is resolved yet
225
+ * (an unmatched route reports `Handler:not_found` before any controller context exists).
226
+ */ function readControllerContext() {
227
+ const cc = useControllerContext();
228
+ try {
229
+ return {
230
+ controller: cc.getController(),
231
+ method: cc.getMethod(),
232
+ route: cc.getRoute()
233
+ };
234
+ } catch {
235
+ return;
236
+ }
237
+ }
186
238
  /** Context injector that wraps Moost lifecycle hooks with OpenTelemetry spans and records metrics. */ var SpanInjector = class extends ContextInjector {
187
239
  with(name, attributes, cb) {
188
240
  const fn = typeof attributes === "function" ? attributes : cb;
@@ -205,45 +257,46 @@ function readHandlerOtelMeta(controller, method) {
205
257
  const res = this.getResponse();
206
258
  if (res) {
207
259
  const originalWriteHead = res.writeHead;
208
- Object.assign(res, { writeHead: (arg0, arg1, arg2) => {
209
- res._statusCode = arg0;
210
- const headers = typeof arg2 === "object" ? arg2 : typeof arg1 === "object" ? arg1 : void 0;
211
- res._contentLength = headers?.["content-length"] ? Number(headers["content-length"]) : 0;
212
- return originalWriteHead.apply(res, [
213
- arg0,
214
- arg1,
215
- arg2
216
- ]);
217
- } });
260
+ res.writeHead = (...args) => {
261
+ res._statusCode = args[0];
262
+ return originalWriteHead.apply(res, args);
263
+ };
218
264
  }
219
265
  }
220
266
  startEvent(eventType, cb) {
221
267
  if (eventType === "init") return cb();
222
268
  const { registerSpan } = useOtelContext();
223
- let span = trace.getActiveSpan();
224
- if (eventType === httpKind.kind) this.patchRsponse();
225
- else span = tracer.startSpan(`${eventType} Event`);
226
- if (span) {
227
- registerSpan(span);
228
- return this.withSpan(span, cb, {
229
- withMetrics: true,
230
- endSpan: eventType !== httpKind.kind
231
- });
269
+ const isHttp = eventType === httpKind.name;
270
+ const req = isHttp ? this.getRequest() : void 0;
271
+ if (isHttp) this.patchRsponse();
272
+ const host = getHostServerSpan(req);
273
+ const active = trace.getActiveSpan();
274
+ const span = host?.span ?? tracer.startSpan(isHttp ? req?.method || "HTTP" : `${eventType} Event`, isHttp && !active ? { kind: SpanKind.SERVER } : void 0);
275
+ claimedSpans.add(span);
276
+ registerSpan(span);
277
+ if (host) current().set(otelHostSpanKey, host);
278
+ const opts = {
279
+ withMetrics: true,
280
+ endSpan: !host
281
+ };
282
+ if (host && active && active !== host.span) {
283
+ const outer = context.active();
284
+ return this.withSpan(span, () => context.with(outer, cb), opts);
232
285
  }
233
- return cb();
286
+ return this.withSpan(span, cb, opts);
234
287
  }
235
288
  getEventType() {
236
289
  const ctx = current();
237
290
  return ctx.has(eventTypeKey) ? ctx.get(eventTypeKey) : void 0;
238
291
  }
239
292
  getIgnoreSpan() {
240
- const { getController, getMethod } = useControllerContext();
241
- return readHandlerOtelMeta(getController(), getMethod()).ignoreSpan;
293
+ const cc = readControllerContext();
294
+ return readHandlerOtelMeta(cc?.controller, cc?.method).ignoreSpan;
242
295
  }
243
296
  getControllerHandlerMeta() {
244
- const { getMethod, getController, getRoute } = useControllerContext();
245
- const methodName = getMethod();
246
- const meta = readHandlerOtelMeta(getController(), methodName);
297
+ const cc = readControllerContext();
298
+ const methodName = cc?.method;
299
+ const meta = readHandlerOtelMeta(cc?.controller, methodName);
247
300
  return {
248
301
  ignoreMeter: meta.ignoreMeter,
249
302
  ignoreSpan: meta.ignoreSpan,
@@ -254,7 +307,7 @@ function readHandlerOtelMeta(controller, method) {
254
307
  "moost.handler_label": meta.label,
255
308
  "moost.handler_id": meta.id,
256
309
  "moost.ignore": meta.ignoreSpan,
257
- "moost.route": getRoute(),
310
+ "moost.route": cc?.route,
258
311
  "moost.event_type": this.getEventType()
259
312
  }
260
313
  };
@@ -272,10 +325,7 @@ function readHandlerOtelMeta(controller, method) {
272
325
  const chm = this.getControllerHandlerMeta();
273
326
  const span = getSpan();
274
327
  if (span) {
275
- if (this.getEventType() === httpKind.kind) {
276
- const req = this.getRequest();
277
- span.updateName(`${req?.method || ""} ${req?.url}`);
278
- }
328
+ if (this.getEventType() === httpKind.name) span.updateName(this.getRequest()?.method || "HTTP");
279
329
  }
280
330
  this.startEventMetrics(chm.attrs, route);
281
331
  } else if (name === "Controller:registered") {
@@ -285,8 +335,15 @@ function readHandlerOtelMeta(controller, method) {
285
335
  const span = getSpan();
286
336
  if (span) {
287
337
  span.setAttributes(chm.attrs);
288
- if (chm.attrs["moost.event_type"] === httpKind.kind) span.updateName(`${this.getRequest()?.method || ""} ${_route || "<unresolved>"}`);
289
- else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
338
+ if (chm.attrs["moost.event_type"] === httpKind.name) {
339
+ const host = ctx.has(otelHostSpanKey) ? ctx.get(otelHostSpanKey) : void 0;
340
+ const httpRoute = _route ? `${host?.mount || ""}${_route}` : void 0;
341
+ span.updateName(`${this.getRequest()?.method || ""} ${httpRoute || "<unresolved>"}`);
342
+ if (httpRoute) {
343
+ span.setAttribute("http.route", httpRoute);
344
+ if (host?.rpc) host.rpc.route = httpRoute;
345
+ }
346
+ } else span.updateName(`${chm.attrs["moost.event_type"]} ${_route || "<unresolved>"}`);
290
347
  }
291
348
  }
292
349
  if (name !== "Controller:registered") ctx.set(otelRouteKey, route);
@@ -313,6 +370,7 @@ function readHandlerOtelMeta(controller, method) {
313
370
  const ctx = current();
314
371
  const route = ctx.has(otelRouteKey) ? ctx.get(otelRouteKey) : void 0;
315
372
  const startTime = ctx.has(otelStartTimeKey) ? ctx.get(otelStartTimeKey) : void 0;
373
+ if (startTime === void 0 && route === void 0 && a["moost.event_type"] === httpKind.name) return;
316
374
  const duration = Date.now() - (startTime || Date.now() - 1);
317
375
  const attrs = {
318
376
  ...ctx.has(customMetricAttrsKey) ? ctx.get(customMetricAttrsKey) : {},
@@ -320,8 +378,7 @@ function readHandlerOtelMeta(controller, method) {
320
378
  "moost.event_type": a["moost.event_type"],
321
379
  "moost.is_error": error ? 1 : 0
322
380
  };
323
- if (a["moost.event_type"] === httpKind.kind) {
324
- if (!attrs.route) attrs.route = this.getRequest()?.url || "";
381
+ if (a["moost.event_type"] === httpKind.name) {
325
382
  attrs["http.status_code"] = this.getResponse()?._statusCode || 0;
326
383
  attrs["moost.is_error"] = attrs["moost.is_error"] || attrs["http.status_code"] > 399 ? 1 : 0;
327
384
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moostjs/otel",
3
- "version": "0.6.49",
3
+ "version": "0.6.51",
4
4
  "description": "@moostjs/otel",
5
5
  "keywords": [
6
6
  "composables",
@@ -39,15 +39,16 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@opentelemetry/api": "^1.9.1",
42
+ "@opentelemetry/core": "^2.10.0",
42
43
  "@opentelemetry/sdk-trace-base": "^2.10.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "vitest": "3.2.7"
46
47
  },
47
48
  "peerDependencies": {
48
- "@wooksjs/event-core": "^0.7.28",
49
- "@moostjs/event-http": "^0.6.49",
50
- "moost": "^0.6.49"
49
+ "@wooksjs/event-core": "^0.7.29",
50
+ "@moostjs/event-http": "^0.6.51",
51
+ "moost": "^0.6.51"
51
52
  },
52
53
  "scripts": {
53
54
  "pub": "pnpm publish --access public",