moost 0.7.2 → 0.7.3

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
@@ -48,7 +48,7 @@ let stamped = false;
48
48
  if (stamped) return;
49
49
  stamped = true;
50
50
  stampModuleIdentity({
51
- version: "0.7.2",
51
+ version: "0.7.3",
52
52
  path: typeof __filename === "string" ? __filename : require("url").pathToFileURL(__filename).href
53
53
  });
54
54
  }
@@ -658,7 +658,7 @@ function formatNewInstanceArg(a) {
658
658
  //#endregion
659
659
  //#region packages/moost/src/composables/global-key.ts
660
660
  var _g;
661
- const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
661
+ const registry = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
662
662
  /**
663
663
  * Creates a typed event-context key that stays a single instance across duplicate
664
664
  * moost loads (dual ESM/CJS, or duplicate installs). Use this for every
@@ -666,8 +666,8 @@ const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PU
666
666
  *
667
667
  * @param name - Debug label AND the registry lookup key; must be unique per slot.
668
668
  */ function globalKey(name) {
669
- if (!registry$1.has(name)) registry$1.set(name, (0, _wooksjs_event_core.key)(name));
670
- return registry$1.get(name);
669
+ if (!registry.has(name)) registry.set(name, (0, _wooksjs_event_core.key)(name));
670
+ return registry.get(name);
671
671
  }
672
672
 
673
673
  //#endregion
@@ -3184,47 +3184,217 @@ function _define_property(obj, key, value) {
3184
3184
 
3185
3185
  //#endregion
3186
3186
  //#region packages/moost/src/i18n.ts
3187
- const TRANSLATOR = Symbol.for("moost.translator");
3188
- const registry = globalThis;
3189
- /**
3190
- * Registers the app's translator (process-wide), or removes it with `undefined`.
3191
- * It takes effect where code calls {@link useTranslate} — your own code and packages that opt
3192
- * in to translating the text they serialise; with no translator registered, text stays as
3193
- * authored.
3187
+ var _globalThis, _STATE, _state, _state1, _state2, _state3, _state4;
3188
+ const STATE = Symbol.for("moost.i18n");
3189
+ const NO_LOCALES = Object.freeze([]);
3190
+ const state = (_globalThis = globalThis)[_STATE = STATE] ?? (_globalThis[_STATE] = {});
3191
+ (_state = state).locales ?? (_state.locales = NO_LOCALES);
3192
+ (_state1 = state).generation ?? (_state1.generation = 0);
3193
+ (_state2 = state).catalogs ?? (_state2.catalogs = /* @__PURE__ */ new WeakSet());
3194
+ (_state3 = state).templates ?? (_state3.templates = /* @__PURE__ */ new Map());
3195
+ (_state4 = state).memo ?? (_state4.memo = /* @__PURE__ */ new Map());
3196
+ /**
3197
+ * Registers the app's translator (process-wide), or removes it with `undefined`: a
3198
+ * {@link TTranslate} function or a {@link TTranslations} map. Load your languages before the
3199
+ * server listens, or call `setTranslator` again once they are loaded.
3200
+ *
3201
+ * `opts.locales` lists the locales the app serves, the first being the default (`MoostHttp`
3202
+ * negotiates `Accept-Language` against them). For a map they default to its keys — put the
3203
+ * source language first: `{ en: {}, de: {…} }`. Give a function translator `locales`, or
3204
+ * requests get a locale only from your own `resolveLocale` / `useLocale().set()`.
3194
3205
  *
3195
3206
  * @example
3196
3207
  * ```ts
3197
- * const messages = { de: { 'Name taken': 'Name vergeben' } }
3198
- * setTranslator((text, params, { locale }) => messages[locale ?? '']?.[text])
3208
+ * setTranslator({ en: {}, de: { NAME_TAKEN: 'Name {name} ist vergeben' } })
3209
+ *
3210
+ * setTranslator(
3211
+ * (id, params, { locale }) =>
3212
+ * locale && i18next.exists(id, { lng: locale }) ? i18next.t(id, { ...params, lng: locale }) : undefined,
3213
+ * { locales: ['en', 'de'] },
3214
+ * )
3199
3215
  * ```
3200
- */ function setTranslator(translate) {
3201
- registry[TRANSLATOR] = translate;
3202
- }
3203
- /** The registered translator, or `undefined` (e.g. to skip a per-locale variant entirely). */ function getTranslator() {
3204
- return registry[TRANSLATOR];
3216
+ */ function setTranslator(translate, opts) {
3217
+ const isMap = typeof translate === "object";
3218
+ state.translate = isMap ? mapTranslator(translate) : translate;
3219
+ const locales = opts?.locales ?? (isMap ? Object.keys(translate) : void 0);
3220
+ state.locales = translate && locales?.length ? Object.freeze([...locales]) : NO_LOCALES;
3221
+ state.failureLogged = false;
3222
+ newGeneration();
3223
+ }
3224
+ /** The registered translator (a map wrapped as a function), or `undefined`. */ function getTranslator() {
3225
+ return state.translate;
3226
+ }
3227
+ /** The locales of the registered translator, the default first (empty without a translator). */ function getTranslatorLocales() {
3228
+ return state.locales;
3229
+ }
3230
+ /**
3231
+ * Registers message templates by code (`{ MIN_LENGTH: 'At least {min} characters' }`),
3232
+ * process-wide: the English text `useTranslate()` falls back to for the code, and the templates
3233
+ * {@link TTranslation.messages} serves translated. Registering the same catalog object again
3234
+ * does nothing; a later catalog wins for a code both define. A call that adds or changes a
3235
+ * template starts a new generation, like {@link setTranslator} ({@link TTranslation.key}
3236
+ * changes).
3237
+ */ function registerMessages(catalog) {
3238
+ if (state.catalogs.has(catalog)) return;
3239
+ state.catalogs.add(catalog);
3240
+ let changed = false;
3241
+ for (const code of Object.keys(catalog)) if (state.templates.get(code) !== catalog[code]) {
3242
+ state.templates.set(code, catalog[code]);
3243
+ changed = true;
3244
+ }
3245
+ if (changed) newGeneration();
3246
+ }
3247
+ /** The only invalidation: drops everything derived from the translator and the templates. */ function newGeneration() {
3248
+ state.generation++;
3249
+ state.memo.clear();
3250
+ }
3251
+ const PLACEHOLDER = /\{(\w+)\}/g;
3252
+ /**
3253
+ * Fills `{name}` placeholders with `params` in one pass (arrays: items joined with `, `).
3254
+ * A placeholder without a param stays as it is; filled-in values are never filled again.
3255
+ */ function formatMessage(template, params) {
3256
+ if (!params || !template.includes("{")) return template;
3257
+ return template.replace(PLACEHOLDER, (match, name) => {
3258
+ const value = Object.hasOwn(params, name) ? params[name] : void 0;
3259
+ if (value === void 0) return match;
3260
+ return Array.isArray(value) ? value.map(String).join(", ") : String(value);
3261
+ });
3205
3262
  }
3206
3263
  /**
3207
- * Returns `t(text, params?)` for the current event: the registered translator's result in the
3208
- * event's locale (`useLocale().get()`), else `text` unchanged — never a missing-key marker.
3209
- * With no translator registered, `t` returns `text` (params are not interpolated).
3264
+ * Returns `t(id, params?, fallback?)` for the current event (or `ctx`), in the event's locale
3265
+ * (`useLocale().get()`). `id` is a code or a source text. `t` returns:
3266
+ * - the translator's result, as is (it is final text: params are not filled again);
3267
+ * - else `fallback` as is — it is final too, never filled: pass already-filled English text
3268
+ * (e.g. an error's message), or pass no fallback and {@link registerMessages} a template;
3269
+ * - else the code's registered template ({@link registerMessages}), or `id` itself, with
3270
+ * `params` filled in ({@link formatMessage}).
3210
3271
  *
3211
3272
  * @example
3212
3273
  * ```ts
3213
3274
  * const t = useTranslate()
3214
- * throw new HttpError(409, { message: t('Name taken'), code: 'NAME_TAKEN' })
3275
+ * throw new HttpError(409, { message: t('NAME_TAKEN', { name }, `Name ${name} is taken`), code: 'NAME_TAKEN', params: { name } })
3215
3276
  * ```
3216
3277
  */ function useTranslate(ctx) {
3217
3278
  const event = ctx ?? (0, _wooksjs_event_core.current)();
3218
3279
  let translateCtx;
3219
- return (text, params) => {
3220
- const translate = getTranslator();
3221
- if (!translate) return text;
3222
- translateCtx ?? (translateCtx = {
3280
+ return (id, params, fallback) => {
3281
+ const translate = state.translate;
3282
+ return (translate ? callTranslator(translate, id, params, translateCtx ?? (translateCtx = {
3223
3283
  locale: (0, _wooksjs_event_core.useLocale)(event).get(),
3224
3284
  event
3225
- });
3226
- return translate(text, params, translateCtx) ?? text;
3285
+ })) : void 0) ?? untranslated(id, params, fallback);
3286
+ };
3287
+ }
3288
+ /** `t`'s text without a translation: `fallback` as is, else the filled template (or id). */ function untranslated(id, params, fallback) {
3289
+ return fallback ?? formatMessage(state.templates.get(id) ?? id, params);
3290
+ }
3291
+ /**
3292
+ * The translation of the current event (or `ctx`): its locale, a memo key, `t` and the
3293
+ * translated message templates. `undefined` when no translator is registered or the event has
3294
+ * no locale — serve the source texts then.
3295
+ */ function useTranslation(ctx) {
3296
+ const translate = state.translate;
3297
+ if (!translate) return;
3298
+ const event = ctx ?? (0, _wooksjs_event_core.current)();
3299
+ const locale = (0, _wooksjs_event_core.useLocale)(event).get();
3300
+ if (locale === void 0) return;
3301
+ const translateCtx = {
3302
+ locale,
3303
+ event
3304
+ };
3305
+ const generation = state.generation;
3306
+ const key = state.locales.includes(locale) ? `${generation}:${locale}` : void 0;
3307
+ const t = (id, params, fallback) => callTranslator(translate, id, params, translateCtx) ?? untranslated(id, params, fallback);
3308
+ const messages = () => {
3309
+ if (key === void 0 || state.generation !== generation) return translateTemplates(translate, translateCtx);
3310
+ let out = state.memo.get(key);
3311
+ if (!out) {
3312
+ out = translateTemplates(translate, translateCtx);
3313
+ state.memo.set(key, out);
3314
+ }
3315
+ return out;
3227
3316
  };
3317
+ return {
3318
+ locale,
3319
+ key,
3320
+ t,
3321
+ messages
3322
+ };
3323
+ }
3324
+ /**
3325
+ * Returns a translated copy of an error body (`{ message, code?, params?, errors? }`) in the
3326
+ * event's locale — the body itself when there is no translation ({@link useTranslation}).
3327
+ *
3328
+ * - each entry of `errors` (and of its entries' `details`): by its `code` (unless `custom`),
3329
+ * else by its message text, its English `message` being the fallback;
3330
+ * - the top `message`: by `code` when the body has one; else, when it is the first entry's
3331
+ * message (`path: message`), the first entry's translation; else by its text.
3332
+ *
3333
+ * `MoostHttp` applies it to every error body when it creates the HTTP app.
3334
+ */ function translateErrorBody(body, ctx) {
3335
+ const translation = useTranslation(ctx);
3336
+ if (!translation) return body;
3337
+ const { t } = translation;
3338
+ const source = Array.isArray(body.errors) ? body.errors : void 0;
3339
+ const errors = source && translateEntries(source, t);
3340
+ let message;
3341
+ if (typeof body.code === "string" && body.code) message = t(body.code, body.params, body.message);
3342
+ else if (source && errors && isEntry(source[0]) && body.message === topMessage(source[0])) message = topMessage(errors[0]);
3343
+ else message = t(body.message, body.params, body.message);
3344
+ return errors ? {
3345
+ ...body,
3346
+ message,
3347
+ errors
3348
+ } : {
3349
+ ...body,
3350
+ message
3351
+ };
3352
+ }
3353
+ function mapTranslator(map) {
3354
+ return (id, params, { locale }) => {
3355
+ const texts = locale !== void 0 && Object.hasOwn(map, locale) ? map[locale] : void 0;
3356
+ return texts && Object.hasOwn(texts, id) ? formatMessage(texts[id], params) : void 0;
3357
+ };
3358
+ }
3359
+ /** The translator's text, `undefined` if it has none or throws (logged once per `setTranslator`). */ function callTranslator(translate, id, params, ctx) {
3360
+ try {
3361
+ const out = translate(id, params, ctx);
3362
+ return typeof out === "string" ? out : void 0;
3363
+ } catch (error) {
3364
+ if (!state.failureLogged) {
3365
+ state.failureLogged = true;
3366
+ (0, _wooksjs_event_core.useLogger)("i18n", ctx.event).error(`The translator threw for "${id}" — using the untranslated text (logged once)`, error);
3367
+ }
3368
+ return;
3369
+ }
3370
+ }
3371
+ /** The registered templates translated with placeholder params, if they differ. */ function translateTemplates(translate, ctx) {
3372
+ const out = {};
3373
+ for (const [code, template] of state.templates) {
3374
+ let params;
3375
+ for (const [placeholder, name] of template.matchAll(PLACEHOLDER)) (params ?? (params = {}))[name] = placeholder;
3376
+ const translated = callTranslator(translate, code, params, ctx);
3377
+ if (translated !== void 0 && translated !== template) out[code] = translated;
3378
+ }
3379
+ return Object.freeze(out);
3380
+ }
3381
+ function isEntry(value) {
3382
+ return typeof value === "object" && value !== null && typeof value.message === "string";
3383
+ }
3384
+ function translateEntries(entries, t) {
3385
+ return entries.map((entry) => {
3386
+ if (!isEntry(entry)) return entry;
3387
+ const id = typeof entry.code === "string" && entry.code && entry.custom !== true ? entry.code : entry.message;
3388
+ const out = {
3389
+ ...entry,
3390
+ message: t(id, entry.params, entry.message)
3391
+ };
3392
+ if (Array.isArray(entry.details)) out.details = translateEntries(entry.details, t);
3393
+ return out;
3394
+ });
3395
+ }
3396
+ function topMessage(entry) {
3397
+ return typeof entry.path === "string" && entry.path ? `${entry.path}: ${entry.message}` : entry.message;
3228
3398
  }
3229
3399
 
3230
3400
  //#endregion
@@ -3337,6 +3507,7 @@ Object.defineProperty(exports, 'eventTypeKey', {
3337
3507
  }
3338
3508
  });
3339
3509
  exports.forkEventContext = forkEventContext;
3510
+ exports.formatMessage = formatMessage;
3340
3511
  Object.defineProperty(exports, 'getClassKey', {
3341
3512
  enumerable: true,
3342
3513
  get: function () {
@@ -3370,6 +3541,7 @@ exports.getMoostInfact = getMoostInfact;
3370
3541
  exports.getMoostMate = getMoostMate;
3371
3542
  exports.getNewMoostInfact = getNewMoostInfact;
3372
3543
  exports.getTranslator = getTranslator;
3544
+ exports.getTranslatorLocales = getTranslatorLocales;
3373
3545
  exports.globalKey = globalKey;
3374
3546
  Object.defineProperty(exports, 'isConstructor', {
3375
3547
  enumerable: true,
@@ -3387,6 +3559,7 @@ Object.defineProperty(exports, 'key', {
3387
3559
  exports.loggerConsoleTransport = loggerConsoleTransport;
3388
3560
  exports.mergeSorted = mergeSorted;
3389
3561
  exports.registerEventScope = registerEventScope;
3562
+ exports.registerMessages = registerMessages;
3390
3563
  Object.defineProperty(exports, 'replaceContextInjector', {
3391
3564
  enumerable: true,
3392
3565
  get: function () {
@@ -3414,6 +3587,7 @@ exports.setOvertake = setOvertake;
3414
3587
  exports.setTranslator = setTranslator;
3415
3588
  exports.stampModuleIdentity = stampModuleIdentity;
3416
3589
  exports.stampOnce = stampOnce;
3590
+ exports.translateErrorBody = translateErrorBody;
3417
3591
  exports.useControllerContext = useControllerContext;
3418
3592
  exports.useHandlerPaths = useHandlerPaths;
3419
3593
  exports.useInterceptOriginal = useInterceptOriginal;
@@ -3433,4 +3607,5 @@ Object.defineProperty(exports, 'useLogger', {
3433
3607
  exports.useOvertake = useOvertake;
3434
3608
  exports.useScopeId = useScopeId;
3435
3609
  exports.useTranslate = useTranslate;
3610
+ exports.useTranslation = useTranslation;
3436
3611
  exports.withControllerContext = withControllerContext;
package/dist/index.d.ts CHANGED
@@ -1779,46 +1779,126 @@ interface TTranslateContext {
1779
1779
  event: EventContext;
1780
1780
  }
1781
1781
  /**
1782
- * The app's translator. `text` is a source text (gettext-style message id) or an error/message
1783
- * code (`WF_EXPIRED`, `NAME_TAKEN`, …); `params` are the values it refers to. Return the
1784
- * translation, or `undefined` to keep `text` as is.
1782
+ * The app's translator. `text` is a message id: an error/message code (`WF_EXPIRED`,
1783
+ * `MIN_LENGTH`, …) or a source text (gettext-style); `params` are the values it refers to.
1784
+ * Return the final text (with `params` filled in), or `undefined` when there is no translation.
1785
1785
  *
1786
- * Moost never translates on its own: plug in any i18n library (vue-i18n, i18next, FormatJS)
1787
- * or a plain map here.
1786
+ * Moost never translates on its own: plug in any i18n library (i18next, FormatJS, …) or pass a
1787
+ * plain {@link TTranslations} map to {@link setTranslator}.
1788
1788
  */
1789
1789
  type TTranslate = (text: string, params: Record<string, unknown> | undefined, ctx: TTranslateContext) => string | undefined;
1790
1790
  /**
1791
- * Registers the app's translator (process-wide), or removes it with `undefined`.
1792
- * It takes effect where code calls {@link useTranslate} — your own code and packages that opt
1793
- * in to translating the text they serialise; with no translator registered, text stays as
1794
- * authored.
1791
+ * Translations as a plain map: `{ [locale]: { [id]: template } }`. Templates use `{name}`
1792
+ * placeholders, filled with {@link formatMessage}.
1793
+ */
1794
+ type TTranslations = Record<string, Record<string, string>>;
1795
+ /** `t(id, params?, fallback?)` — see {@link useTranslate}. */
1796
+ type TTranslateFn = (id: string, params?: Record<string, unknown>, fallback?: string) => string;
1797
+ /** The translation of one event's locale — see {@link useTranslation}. */
1798
+ interface TTranslation {
1799
+ /** The event's locale. */
1800
+ locale: string;
1801
+ /**
1802
+ * A memo key for anything derived from this translation: changes with every
1803
+ * {@link setTranslator} call and every {@link registerMessages} call that adds or changes a
1804
+ * template. `undefined` for a locale outside the translator's `locales` — don't memoize then.
1805
+ */
1806
+ key: string | undefined;
1807
+ /** {@link useTranslate}'s `t`, bound to this translation. */
1808
+ t: TTranslateFn;
1809
+ /**
1810
+ * The registered message templates ({@link registerMessages}) translated to `locale`, with
1811
+ * their `{name}` placeholders kept — only the ones that differ from the English template.
1812
+ * Memoized per {@link TTranslation.key}.
1813
+ */
1814
+ messages: () => Readonly<Record<string, string>>;
1815
+ }
1816
+ /**
1817
+ * Registers the app's translator (process-wide), or removes it with `undefined`: a
1818
+ * {@link TTranslate} function or a {@link TTranslations} map. Load your languages before the
1819
+ * server listens, or call `setTranslator` again once they are loaded.
1820
+ *
1821
+ * `opts.locales` lists the locales the app serves, the first being the default (`MoostHttp`
1822
+ * negotiates `Accept-Language` against them). For a map they default to its keys — put the
1823
+ * source language first: `{ en: {}, de: {…} }`. Give a function translator `locales`, or
1824
+ * requests get a locale only from your own `resolveLocale` / `useLocale().set()`.
1795
1825
  *
1796
1826
  * @example
1797
1827
  * ```ts
1798
- * const messages = { de: { 'Name taken': 'Name vergeben' } }
1799
- * setTranslator((text, params, { locale }) => messages[locale ?? '']?.[text])
1828
+ * setTranslator({ en: {}, de: { NAME_TAKEN: 'Name {name} ist vergeben' } })
1829
+ *
1830
+ * setTranslator(
1831
+ * (id, params, { locale }) =>
1832
+ * locale && i18next.exists(id, { lng: locale }) ? i18next.t(id, { ...params, lng: locale }) : undefined,
1833
+ * { locales: ['en', 'de'] },
1834
+ * )
1800
1835
  * ```
1801
1836
  */
1802
- declare function setTranslator(translate: TTranslate | undefined): void;
1803
- /** The registered translator, or `undefined` (e.g. to skip a per-locale variant entirely). */
1837
+ declare function setTranslator(translate: TTranslate | TTranslations | undefined, opts?: {
1838
+ locales?: readonly string[];
1839
+ }): void;
1840
+ /** The registered translator (a map wrapped as a function), or `undefined`. */
1804
1841
  declare function getTranslator(): TTranslate | undefined;
1805
- /**
1806
- * Returns `t(text, params?)` for the current event: the registered translator's result in the
1807
- * event's locale (`useLocale().get()`), else `text` unchanged — never a missing-key marker.
1808
- * With no translator registered, `t` returns `text` (params are not interpolated).
1842
+ /** The locales of the registered translator, the default first (empty without a translator). */
1843
+ declare function getTranslatorLocales(): readonly string[];
1844
+ /**
1845
+ * Registers message templates by code (`{ MIN_LENGTH: 'At least {min} characters' }`),
1846
+ * process-wide: the English text `useTranslate()` falls back to for the code, and the templates
1847
+ * {@link TTranslation.messages} serves translated. Registering the same catalog object again
1848
+ * does nothing; a later catalog wins for a code both define. A call that adds or changes a
1849
+ * template starts a new generation, like {@link setTranslator} ({@link TTranslation.key}
1850
+ * changes).
1851
+ */
1852
+ declare function registerMessages(catalog: Readonly<Record<string, string>>): void;
1853
+ /**
1854
+ * Fills `{name}` placeholders with `params` in one pass (arrays: items joined with `, `).
1855
+ * A placeholder without a param stays as it is; filled-in values are never filled again.
1856
+ */
1857
+ declare function formatMessage(template: string, params?: Record<string, unknown>): string;
1858
+ /**
1859
+ * Returns `t(id, params?, fallback?)` for the current event (or `ctx`), in the event's locale
1860
+ * (`useLocale().get()`). `id` is a code or a source text. `t` returns:
1861
+ * - the translator's result, as is (it is final text: params are not filled again);
1862
+ * - else `fallback` as is — it is final too, never filled: pass already-filled English text
1863
+ * (e.g. an error's message), or pass no fallback and {@link registerMessages} a template;
1864
+ * - else the code's registered template ({@link registerMessages}), or `id` itself, with
1865
+ * `params` filled in ({@link formatMessage}).
1809
1866
  *
1810
1867
  * @example
1811
1868
  * ```ts
1812
1869
  * const t = useTranslate()
1813
- * throw new HttpError(409, { message: t('Name taken'), code: 'NAME_TAKEN' })
1870
+ * throw new HttpError(409, { message: t('NAME_TAKEN', { name }, `Name ${name} is taken`), code: 'NAME_TAKEN', params: { name } })
1814
1871
  * ```
1815
1872
  */
1816
- declare function useTranslate(ctx?: EventContext): (text: string, params?: Record<string, unknown>) => string;
1873
+ declare function useTranslate(ctx?: EventContext): TTranslateFn;
1874
+ /**
1875
+ * The translation of the current event (or `ctx`): its locale, a memo key, `t` and the
1876
+ * translated message templates. `undefined` when no translator is registered or the event has
1877
+ * no locale — serve the source texts then.
1878
+ */
1879
+ declare function useTranslation(ctx?: EventContext): TTranslation | undefined;
1880
+ /**
1881
+ * Returns a translated copy of an error body (`{ message, code?, params?, errors? }`) in the
1882
+ * event's locale — the body itself when there is no translation ({@link useTranslation}).
1883
+ *
1884
+ * - each entry of `errors` (and of its entries' `details`): by its `code` (unless `custom`),
1885
+ * else by its message text, its English `message` being the fallback;
1886
+ * - the top `message`: by `code` when the body has one; else, when it is the first entry's
1887
+ * message (`path: message`), the first entry's translation; else by its text.
1888
+ *
1889
+ * `MoostHttp` applies it to every error body when it creates the HTTP app.
1890
+ */
1891
+ declare function translateErrorBody<T extends {
1892
+ message: string;
1893
+ code?: string;
1894
+ params?: Record<string, unknown>;
1895
+ errors?: unknown;
1896
+ }>(body: T, ctx?: EventContext): T;
1817
1897
 
1818
1898
  /** Creates a new `ProstoLogger` instance with sensible defaults (level 4, colored console). */
1819
1899
  declare function createLogger(opts?: Partial<TProstoLoggerOptions>): ProstoLogger;
1820
1900
  /** Default colored console transport used by Moost loggers. */
1821
1901
  declare const loggerConsoleTransport: _prostojs_logger.TProstoLoggerTransportFn<any>;
1822
1902
 
1823
- export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, Locale, LoggerTopic, MOOST_ADAPTER_BRAND, MOOST_MODULE_IDENTITY_KEY, Moost, MoostDispose, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, createLogger, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, detachedKind, disposeInstances, forkEventContext, getHandlerPaths, getInfactScopeVars, getInfactSingletonInstances, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, getTranslator, globalKey, isThenable, loggerConsoleTransport, mergeSorted, registerEventScope, resolvePipe, setControllerContext, setInfactLoggingOptions, setInterceptOriginal, setInterceptResult, setOvertake, setTranslator, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptOriginal, useInterceptResult, useOvertake, useScopeId, useTranslate, withControllerContext };
1824
- export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TControllerRegistration, TControllersGroup, TDisposeError, TDisposeOptions, TDisposeResult, TEmpty, TForkEventContextOptions, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TIsolatedSlot, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostModuleIdentity, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives, TRegistryMergeOptions, TRunDetachedOptions, TTranslate, TTranslateContext, TWithControllerContextOptions };
1903
+ export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, Locale, LoggerTopic, MOOST_ADAPTER_BRAND, MOOST_MODULE_IDENTITY_KEY, Moost, MoostDispose, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, createLogger, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, detachedKind, disposeInstances, forkEventContext, formatMessage, getHandlerPaths, getInfactScopeVars, getInfactSingletonInstances, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, getTranslator, getTranslatorLocales, globalKey, isThenable, loggerConsoleTransport, mergeSorted, registerEventScope, registerMessages, resolvePipe, setControllerContext, setInfactLoggingOptions, setInterceptOriginal, setInterceptResult, setOvertake, setTranslator, stampModuleIdentity, stampOnce, translateErrorBody, useControllerContext, useHandlerPaths, useInterceptOriginal, useInterceptResult, useOvertake, useScopeId, useTranslate, useTranslation, withControllerContext };
1904
+ export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TControllerRegistration, TControllersGroup, TDisposeError, TDisposeOptions, TDisposeResult, TEmpty, TForkEventContextOptions, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TIsolatedSlot, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostModuleIdentity, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives, TRegistryMergeOptions, TRunDetachedOptions, TTranslate, TTranslateContext, TTranslation, TTranslations, TWithControllerContextOptions };
package/dist/index.mjs CHANGED
@@ -47,7 +47,7 @@ let stamped = false;
47
47
  if (stamped) return;
48
48
  stamped = true;
49
49
  stampModuleIdentity({
50
- version: "0.7.2",
50
+ version: "0.7.3",
51
51
  path: typeof __filename === "string" ? __filename : import.meta.url
52
52
  });
53
53
  }
@@ -657,7 +657,7 @@ function formatNewInstanceArg(a) {
657
657
  //#endregion
658
658
  //#region packages/moost/src/composables/global-key.ts
659
659
  var _g;
660
- const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
660
+ const registry = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
661
661
  /**
662
662
  * Creates a typed event-context key that stays a single instance across duplicate
663
663
  * moost loads (dual ESM/CJS, or duplicate installs). Use this for every
@@ -665,8 +665,8 @@ const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PU
665
665
  *
666
666
  * @param name - Debug label AND the registry lookup key; must be unique per slot.
667
667
  */ function globalKey(name) {
668
- if (!registry$1.has(name)) registry$1.set(name, key$1(name));
669
- return registry$1.get(name);
668
+ if (!registry.has(name)) registry.set(name, key$1(name));
669
+ return registry.get(name);
670
670
  }
671
671
 
672
672
  //#endregion
@@ -3183,48 +3183,218 @@ function _define_property(obj, key, value) {
3183
3183
 
3184
3184
  //#endregion
3185
3185
  //#region packages/moost/src/i18n.ts
3186
- const TRANSLATOR = Symbol.for("moost.translator");
3187
- const registry = globalThis;
3188
- /**
3189
- * Registers the app's translator (process-wide), or removes it with `undefined`.
3190
- * It takes effect where code calls {@link useTranslate} — your own code and packages that opt
3191
- * in to translating the text they serialise; with no translator registered, text stays as
3192
- * authored.
3186
+ var _globalThis, _STATE, _state, _state1, _state2, _state3, _state4;
3187
+ const STATE = Symbol.for("moost.i18n");
3188
+ const NO_LOCALES = Object.freeze([]);
3189
+ const state = (_globalThis = globalThis)[_STATE = STATE] ?? (_globalThis[_STATE] = {});
3190
+ (_state = state).locales ?? (_state.locales = NO_LOCALES);
3191
+ (_state1 = state).generation ?? (_state1.generation = 0);
3192
+ (_state2 = state).catalogs ?? (_state2.catalogs = /* @__PURE__ */ new WeakSet());
3193
+ (_state3 = state).templates ?? (_state3.templates = /* @__PURE__ */ new Map());
3194
+ (_state4 = state).memo ?? (_state4.memo = /* @__PURE__ */ new Map());
3195
+ /**
3196
+ * Registers the app's translator (process-wide), or removes it with `undefined`: a
3197
+ * {@link TTranslate} function or a {@link TTranslations} map. Load your languages before the
3198
+ * server listens, or call `setTranslator` again once they are loaded.
3199
+ *
3200
+ * `opts.locales` lists the locales the app serves, the first being the default (`MoostHttp`
3201
+ * negotiates `Accept-Language` against them). For a map they default to its keys — put the
3202
+ * source language first: `{ en: {}, de: {…} }`. Give a function translator `locales`, or
3203
+ * requests get a locale only from your own `resolveLocale` / `useLocale().set()`.
3193
3204
  *
3194
3205
  * @example
3195
3206
  * ```ts
3196
- * const messages = { de: { 'Name taken': 'Name vergeben' } }
3197
- * setTranslator((text, params, { locale }) => messages[locale ?? '']?.[text])
3207
+ * setTranslator({ en: {}, de: { NAME_TAKEN: 'Name {name} ist vergeben' } })
3208
+ *
3209
+ * setTranslator(
3210
+ * (id, params, { locale }) =>
3211
+ * locale && i18next.exists(id, { lng: locale }) ? i18next.t(id, { ...params, lng: locale }) : undefined,
3212
+ * { locales: ['en', 'de'] },
3213
+ * )
3198
3214
  * ```
3199
- */ function setTranslator(translate) {
3200
- registry[TRANSLATOR] = translate;
3201
- }
3202
- /** The registered translator, or `undefined` (e.g. to skip a per-locale variant entirely). */ function getTranslator() {
3203
- return registry[TRANSLATOR];
3215
+ */ function setTranslator(translate, opts) {
3216
+ const isMap = typeof translate === "object";
3217
+ state.translate = isMap ? mapTranslator(translate) : translate;
3218
+ const locales = opts?.locales ?? (isMap ? Object.keys(translate) : void 0);
3219
+ state.locales = translate && locales?.length ? Object.freeze([...locales]) : NO_LOCALES;
3220
+ state.failureLogged = false;
3221
+ newGeneration();
3222
+ }
3223
+ /** The registered translator (a map wrapped as a function), or `undefined`. */ function getTranslator() {
3224
+ return state.translate;
3225
+ }
3226
+ /** The locales of the registered translator, the default first (empty without a translator). */ function getTranslatorLocales() {
3227
+ return state.locales;
3228
+ }
3229
+ /**
3230
+ * Registers message templates by code (`{ MIN_LENGTH: 'At least {min} characters' }`),
3231
+ * process-wide: the English text `useTranslate()` falls back to for the code, and the templates
3232
+ * {@link TTranslation.messages} serves translated. Registering the same catalog object again
3233
+ * does nothing; a later catalog wins for a code both define. A call that adds or changes a
3234
+ * template starts a new generation, like {@link setTranslator} ({@link TTranslation.key}
3235
+ * changes).
3236
+ */ function registerMessages(catalog) {
3237
+ if (state.catalogs.has(catalog)) return;
3238
+ state.catalogs.add(catalog);
3239
+ let changed = false;
3240
+ for (const code of Object.keys(catalog)) if (state.templates.get(code) !== catalog[code]) {
3241
+ state.templates.set(code, catalog[code]);
3242
+ changed = true;
3243
+ }
3244
+ if (changed) newGeneration();
3245
+ }
3246
+ /** The only invalidation: drops everything derived from the translator and the templates. */ function newGeneration() {
3247
+ state.generation++;
3248
+ state.memo.clear();
3249
+ }
3250
+ const PLACEHOLDER = /\{(\w+)\}/g;
3251
+ /**
3252
+ * Fills `{name}` placeholders with `params` in one pass (arrays: items joined with `, `).
3253
+ * A placeholder without a param stays as it is; filled-in values are never filled again.
3254
+ */ function formatMessage(template, params) {
3255
+ if (!params || !template.includes("{")) return template;
3256
+ return template.replace(PLACEHOLDER, (match, name) => {
3257
+ const value = Object.hasOwn(params, name) ? params[name] : void 0;
3258
+ if (value === void 0) return match;
3259
+ return Array.isArray(value) ? value.map(String).join(", ") : String(value);
3260
+ });
3204
3261
  }
3205
3262
  /**
3206
- * Returns `t(text, params?)` for the current event: the registered translator's result in the
3207
- * event's locale (`useLocale().get()`), else `text` unchanged — never a missing-key marker.
3208
- * With no translator registered, `t` returns `text` (params are not interpolated).
3263
+ * Returns `t(id, params?, fallback?)` for the current event (or `ctx`), in the event's locale
3264
+ * (`useLocale().get()`). `id` is a code or a source text. `t` returns:
3265
+ * - the translator's result, as is (it is final text: params are not filled again);
3266
+ * - else `fallback` as is — it is final too, never filled: pass already-filled English text
3267
+ * (e.g. an error's message), or pass no fallback and {@link registerMessages} a template;
3268
+ * - else the code's registered template ({@link registerMessages}), or `id` itself, with
3269
+ * `params` filled in ({@link formatMessage}).
3209
3270
  *
3210
3271
  * @example
3211
3272
  * ```ts
3212
3273
  * const t = useTranslate()
3213
- * throw new HttpError(409, { message: t('Name taken'), code: 'NAME_TAKEN' })
3274
+ * throw new HttpError(409, { message: t('NAME_TAKEN', { name }, `Name ${name} is taken`), code: 'NAME_TAKEN', params: { name } })
3214
3275
  * ```
3215
3276
  */ function useTranslate(ctx) {
3216
3277
  const event = ctx ?? current$1();
3217
3278
  let translateCtx;
3218
- return (text, params) => {
3219
- const translate = getTranslator();
3220
- if (!translate) return text;
3221
- translateCtx ?? (translateCtx = {
3279
+ return (id, params, fallback) => {
3280
+ const translate = state.translate;
3281
+ return (translate ? callTranslator(translate, id, params, translateCtx ?? (translateCtx = {
3222
3282
  locale: useLocale$1(event).get(),
3223
3283
  event
3224
- });
3225
- return translate(text, params, translateCtx) ?? text;
3284
+ })) : void 0) ?? untranslated(id, params, fallback);
3285
+ };
3286
+ }
3287
+ /** `t`'s text without a translation: `fallback` as is, else the filled template (or id). */ function untranslated(id, params, fallback) {
3288
+ return fallback ?? formatMessage(state.templates.get(id) ?? id, params);
3289
+ }
3290
+ /**
3291
+ * The translation of the current event (or `ctx`): its locale, a memo key, `t` and the
3292
+ * translated message templates. `undefined` when no translator is registered or the event has
3293
+ * no locale — serve the source texts then.
3294
+ */ function useTranslation(ctx) {
3295
+ const translate = state.translate;
3296
+ if (!translate) return;
3297
+ const event = ctx ?? current$1();
3298
+ const locale = useLocale$1(event).get();
3299
+ if (locale === void 0) return;
3300
+ const translateCtx = {
3301
+ locale,
3302
+ event
3303
+ };
3304
+ const generation = state.generation;
3305
+ const key = state.locales.includes(locale) ? `${generation}:${locale}` : void 0;
3306
+ const t = (id, params, fallback) => callTranslator(translate, id, params, translateCtx) ?? untranslated(id, params, fallback);
3307
+ const messages = () => {
3308
+ if (key === void 0 || state.generation !== generation) return translateTemplates(translate, translateCtx);
3309
+ let out = state.memo.get(key);
3310
+ if (!out) {
3311
+ out = translateTemplates(translate, translateCtx);
3312
+ state.memo.set(key, out);
3313
+ }
3314
+ return out;
3226
3315
  };
3316
+ return {
3317
+ locale,
3318
+ key,
3319
+ t,
3320
+ messages
3321
+ };
3322
+ }
3323
+ /**
3324
+ * Returns a translated copy of an error body (`{ message, code?, params?, errors? }`) in the
3325
+ * event's locale — the body itself when there is no translation ({@link useTranslation}).
3326
+ *
3327
+ * - each entry of `errors` (and of its entries' `details`): by its `code` (unless `custom`),
3328
+ * else by its message text, its English `message` being the fallback;
3329
+ * - the top `message`: by `code` when the body has one; else, when it is the first entry's
3330
+ * message (`path: message`), the first entry's translation; else by its text.
3331
+ *
3332
+ * `MoostHttp` applies it to every error body when it creates the HTTP app.
3333
+ */ function translateErrorBody(body, ctx) {
3334
+ const translation = useTranslation(ctx);
3335
+ if (!translation) return body;
3336
+ const { t } = translation;
3337
+ const source = Array.isArray(body.errors) ? body.errors : void 0;
3338
+ const errors = source && translateEntries(source, t);
3339
+ let message;
3340
+ if (typeof body.code === "string" && body.code) message = t(body.code, body.params, body.message);
3341
+ else if (source && errors && isEntry(source[0]) && body.message === topMessage(source[0])) message = topMessage(errors[0]);
3342
+ else message = t(body.message, body.params, body.message);
3343
+ return errors ? {
3344
+ ...body,
3345
+ message,
3346
+ errors
3347
+ } : {
3348
+ ...body,
3349
+ message
3350
+ };
3351
+ }
3352
+ function mapTranslator(map) {
3353
+ return (id, params, { locale }) => {
3354
+ const texts = locale !== void 0 && Object.hasOwn(map, locale) ? map[locale] : void 0;
3355
+ return texts && Object.hasOwn(texts, id) ? formatMessage(texts[id], params) : void 0;
3356
+ };
3357
+ }
3358
+ /** The translator's text, `undefined` if it has none or throws (logged once per `setTranslator`). */ function callTranslator(translate, id, params, ctx) {
3359
+ try {
3360
+ const out = translate(id, params, ctx);
3361
+ return typeof out === "string" ? out : void 0;
3362
+ } catch (error) {
3363
+ if (!state.failureLogged) {
3364
+ state.failureLogged = true;
3365
+ useLogger$1("i18n", ctx.event).error(`The translator threw for "${id}" — using the untranslated text (logged once)`, error);
3366
+ }
3367
+ return;
3368
+ }
3369
+ }
3370
+ /** The registered templates translated with placeholder params, if they differ. */ function translateTemplates(translate, ctx) {
3371
+ const out = {};
3372
+ for (const [code, template] of state.templates) {
3373
+ let params;
3374
+ for (const [placeholder, name] of template.matchAll(PLACEHOLDER)) (params ?? (params = {}))[name] = placeholder;
3375
+ const translated = callTranslator(translate, code, params, ctx);
3376
+ if (translated !== void 0 && translated !== template) out[code] = translated;
3377
+ }
3378
+ return Object.freeze(out);
3379
+ }
3380
+ function isEntry(value) {
3381
+ return typeof value === "object" && value !== null && typeof value.message === "string";
3382
+ }
3383
+ function translateEntries(entries, t) {
3384
+ return entries.map((entry) => {
3385
+ if (!isEntry(entry)) return entry;
3386
+ const id = typeof entry.code === "string" && entry.code && entry.custom !== true ? entry.code : entry.message;
3387
+ const out = {
3388
+ ...entry,
3389
+ message: t(id, entry.params, entry.message)
3390
+ };
3391
+ if (Array.isArray(entry.details)) out.details = translateEntries(entry.details, t);
3392
+ return out;
3393
+ });
3394
+ }
3395
+ function topMessage(entry) {
3396
+ return typeof entry.path === "string" && entry.path ? `${entry.path}: ${entry.message}` : entry.message;
3227
3397
  }
3228
3398
 
3229
3399
  //#endregion
3230
- export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, ContextInjector, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, Locale, LoggerTopic, MOOST_ADAPTER_BRAND, MOOST_MODULE_IDENTITY_KEY, Moost, MoostDispose, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, ProstoLogger, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, cached, clearGlobalWooks, createEventContext, createLogger, createProvideRegistry, createReplaceRegistry, current, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, detachedKind, disposeInstances, eventTypeKey, forkEventContext, getClassKey, getConstructor, getContextInjector, getGlobalWooks, getHandlerPaths, getInfactScopeVars, getInfactSingletonInstances, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, getTranslator, globalKey, isConstructor, isThenable, key, loggerConsoleTransport, mergeSorted, registerEventScope, replaceContextInjector, resetContextInjector, resolvePipe, run, setControllerContext, setInfactLoggingOptions, setInterceptOriginal, setInterceptResult, setOvertake, setTranslator, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptOriginal, useInterceptResult, useLocale, useLogger, useOvertake, useScopeId, useTranslate, withControllerContext };
3400
+ export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, ContextInjector, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, Locale, LoggerTopic, MOOST_ADAPTER_BRAND, MOOST_MODULE_IDENTITY_KEY, Moost, MoostDispose, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, ProstoLogger, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, cached, clearGlobalWooks, createEventContext, createLogger, createProvideRegistry, createReplaceRegistry, current, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, detachedKind, disposeInstances, eventTypeKey, forkEventContext, formatMessage, getClassKey, getConstructor, getContextInjector, getGlobalWooks, getHandlerPaths, getInfactScopeVars, getInfactSingletonInstances, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, getTranslator, getTranslatorLocales, globalKey, isConstructor, isThenable, key, loggerConsoleTransport, mergeSorted, registerEventScope, registerMessages, replaceContextInjector, resetContextInjector, resolvePipe, run, setControllerContext, setInfactLoggingOptions, setInterceptOriginal, setInterceptResult, setOvertake, setTranslator, stampModuleIdentity, stampOnce, translateErrorBody, useControllerContext, useHandlerPaths, useInterceptOriginal, useInterceptResult, useLocale, useLogger, useOvertake, useScopeId, useTranslate, useTranslation, withControllerContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moost",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "moost",
5
5
  "keywords": [
6
6
  "composables",
@@ -41,15 +41,15 @@
41
41
  "@prostojs/infact": "^0.6.1",
42
42
  "@prostojs/logger": "^0.4.3",
43
43
  "@prostojs/mate": "^0.5.1",
44
- "@wooksjs/event-core": "^0.8.0",
44
+ "@wooksjs/event-core": "^0.8.1",
45
45
  "hookable": "^5.5.3",
46
- "wooks": "^0.8.0"
46
+ "wooks": "^0.8.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@wooksjs/event-http": "^0.8.0",
50
- "@wooksjs/http-body": "^0.8.0",
49
+ "@wooksjs/event-http": "^0.8.1",
50
+ "@wooksjs/http-body": "^0.8.1",
51
51
  "vitest": "4.1.12",
52
- "@moostjs/event-http": "^0.7.2"
52
+ "@moostjs/event-http": "^0.7.3"
53
53
  },
54
54
  "scripts": {
55
55
  "pub": "pnpm publish --access public",