moost 0.7.1 → 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 +286 -31
- package/dist/index.d.ts +107 -21
- package/dist/index.mjs +282 -32
- package/package.json +6 -6
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.
|
|
51
|
+
version: "0.7.3",
|
|
52
52
|
path: typeof __filename === "string" ? __filename : require("url").pathToFileURL(__filename).href
|
|
53
53
|
});
|
|
54
54
|
}
|
|
@@ -136,7 +136,7 @@ function getDefaultLogger(topic) {
|
|
|
136
136
|
|
|
137
137
|
//#endregion
|
|
138
138
|
//#region packages/moost/src/event-scope.ts
|
|
139
|
-
function _define_property$
|
|
139
|
+
function _define_property$2(obj, key, value) {
|
|
140
140
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
141
141
|
value,
|
|
142
142
|
enumerable: true,
|
|
@@ -232,7 +232,7 @@ const NO_ISOLATION = /* @__PURE__ */ new Set();
|
|
|
232
232
|
super({
|
|
233
233
|
logger: parent.logger,
|
|
234
234
|
parent
|
|
235
|
-
}), _define_property$
|
|
235
|
+
}), _define_property$2(this, "isolated", void 0), this.isolated = isolated;
|
|
236
236
|
}
|
|
237
237
|
};
|
|
238
238
|
/**
|
|
@@ -385,6 +385,16 @@ let sourceRefs = [];
|
|
|
385
385
|
|
|
386
386
|
//#endregion
|
|
387
387
|
//#region packages/moost/src/metadata/moost-metadata.ts
|
|
388
|
+
function _define_property$1(obj, key, value) {
|
|
389
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
390
|
+
value,
|
|
391
|
+
enumerable: true,
|
|
392
|
+
configurable: true,
|
|
393
|
+
writable: true
|
|
394
|
+
});
|
|
395
|
+
else obj[key] = value;
|
|
396
|
+
return obj;
|
|
397
|
+
}
|
|
388
398
|
const METADATA_WORKSPACE = "moost";
|
|
389
399
|
/**
|
|
390
400
|
* A param entry is "bare" when it carries nothing but the design-time type
|
|
@@ -394,7 +404,71 @@ const METADATA_WORKSPACE = "moost";
|
|
|
394
404
|
*/ function isBareParam(param) {
|
|
395
405
|
return !param || Object.entries(param).every(([key, value]) => key === "type" || value === void 0);
|
|
396
406
|
}
|
|
397
|
-
|
|
407
|
+
/**
|
|
408
|
+
* List keys that describe behaviour stacked on a class or method. Under
|
|
409
|
+
* `@Inherit()` they ACCUMULATE along `extends` (base entries first, then the
|
|
410
|
+
* subclass's own) instead of following the shallow own-wins merge — a subclass
|
|
411
|
+
* adding a guard must not silently drop the base's error mapping or guard.
|
|
412
|
+
* `authTransports` is written by `@Authenticate` (event-http) next to its
|
|
413
|
+
* interceptor entry and has to travel with it. Route `handlers`, params,
|
|
414
|
+
* `importController` (re-mounting would duplicate routes) and documentation
|
|
415
|
+
* lists (swagger tags/security, …) keep own-wins semantics.
|
|
416
|
+
*/ const ACCUMULATED_KEYS = [
|
|
417
|
+
"interceptors",
|
|
418
|
+
"pipes",
|
|
419
|
+
"authTransports",
|
|
420
|
+
"properties",
|
|
421
|
+
"requiredProps"
|
|
422
|
+
];
|
|
423
|
+
/**
|
|
424
|
+
* The identity an entry is deduplicated by when it appears on both levels: the
|
|
425
|
+
* `handler` reference for interceptor/pipe entries, the value itself otherwise.
|
|
426
|
+
*/ function entryIdentity(entry) {
|
|
427
|
+
return entry?.handler ?? entry;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Inherited entries first, then the own ones. An own entry that re-declares an
|
|
431
|
+
* inherited one (same identity) takes the inherited entry's place.
|
|
432
|
+
*/ function accumulate(inherited, own) {
|
|
433
|
+
const ownById = /* @__PURE__ */ new Map();
|
|
434
|
+
for (const entry of own) {
|
|
435
|
+
const id = entryIdentity(entry);
|
|
436
|
+
if (!ownById.has(id)) ownById.set(id, entry);
|
|
437
|
+
}
|
|
438
|
+
const result = [];
|
|
439
|
+
const placed = /* @__PURE__ */ new Set();
|
|
440
|
+
for (const entry of inherited) {
|
|
441
|
+
const id = entryIdentity(entry);
|
|
442
|
+
if (ownById.has(id)) {
|
|
443
|
+
placed.add(id);
|
|
444
|
+
result.push(ownById.get(id));
|
|
445
|
+
} else result.push(entry);
|
|
446
|
+
}
|
|
447
|
+
for (const entry of own) if (!placed.has(entryIdentity(entry))) result.push(entry);
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
const moostMate = new class MoostMate extends _prostojs_mate.Mate {
|
|
451
|
+
read(target, propKey) {
|
|
452
|
+
const result = super.read(target, propKey);
|
|
453
|
+
const meta = result;
|
|
454
|
+
if (!meta || this.settled.has(meta)) return result;
|
|
455
|
+
this.settled.add(meta);
|
|
456
|
+
const ctor = (0, _prostojs_mate.getConstructor)(target);
|
|
457
|
+
const own = Reflect.getOwnMetadata(this.workspace, typeof propKey === "string" ? ctor.prototype : ctor, propKey);
|
|
458
|
+
if (own && meta !== own) {
|
|
459
|
+
const inherited = this.read(Object.getPrototypeOf(ctor), propKey);
|
|
460
|
+
for (const key of ACCUMULATED_KEYS) {
|
|
461
|
+
const ownList = own[key];
|
|
462
|
+
const inheritedList = inherited?.[key];
|
|
463
|
+
if (Array.isArray(ownList) && Array.isArray(inheritedList) && inheritedList.length > 0) meta[key] = accumulate(inheritedList, ownList);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
constructor(...args) {
|
|
469
|
+
super(...args), _define_property$1(this, "settled", /* @__PURE__ */ new WeakSet());
|
|
470
|
+
}
|
|
471
|
+
}(METADATA_WORKSPACE, {
|
|
398
472
|
readType: true,
|
|
399
473
|
readReturnType: true,
|
|
400
474
|
collectPropKeys: true,
|
|
@@ -584,7 +658,7 @@ function formatNewInstanceArg(a) {
|
|
|
584
658
|
//#endregion
|
|
585
659
|
//#region packages/moost/src/composables/global-key.ts
|
|
586
660
|
var _g;
|
|
587
|
-
const registry
|
|
661
|
+
const registry = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
|
|
588
662
|
/**
|
|
589
663
|
* Creates a typed event-context key that stays a single instance across duplicate
|
|
590
664
|
* moost loads (dual ESM/CJS, or duplicate installs). Use this for every
|
|
@@ -592,8 +666,8 @@ const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PU
|
|
|
592
666
|
*
|
|
593
667
|
* @param name - Debug label AND the registry lookup key; must be unique per slot.
|
|
594
668
|
*/ function globalKey(name) {
|
|
595
|
-
if (!registry
|
|
596
|
-
return registry
|
|
669
|
+
if (!registry.has(name)) registry.set(name, (0, _wooksjs_event_core.key)(name));
|
|
670
|
+
return registry.get(name);
|
|
597
671
|
}
|
|
598
672
|
|
|
599
673
|
//#endregion
|
|
@@ -1366,6 +1440,12 @@ function fillLabel(target, key, index, name) {
|
|
|
1366
1440
|
* overrides, which merge the parent's metadata under their own (so adding
|
|
1367
1441
|
* e.g. a rate-limit decorator to an override does not unbind the parent's
|
|
1368
1442
|
* `@Get`/`@Post` route).
|
|
1443
|
+
* - Exception to "own keys win" (since 0.7.2): interceptors (incl.
|
|
1444
|
+
* `@Authenticate` guards), pipes and prop decorators ACCUMULATE on the class
|
|
1445
|
+
* and on overridden methods, parent first. The same interceptor/pipe
|
|
1446
|
+
* *reference* on both levels runs once; a factory called on each level runs
|
|
1447
|
+
* twice. A subclass can't remove a base class's class-level interceptors.
|
|
1448
|
+
* Routes, params and `@ImportController` still replace.
|
|
1369
1449
|
* - On a method/prop: enables inheritance for just that member.
|
|
1370
1450
|
* - `@Inherit(false)` on an overridden member opts out of the merge for a
|
|
1371
1451
|
* deliberate full replacement (the member keeps only its own metadata).
|
|
@@ -3104,48 +3184,218 @@ function _define_property(obj, key, value) {
|
|
|
3104
3184
|
|
|
3105
3185
|
//#endregion
|
|
3106
3186
|
//#region packages/moost/src/i18n.ts
|
|
3107
|
-
|
|
3108
|
-
const
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
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()`.
|
|
3114
3205
|
*
|
|
3115
3206
|
* @example
|
|
3116
3207
|
* ```ts
|
|
3117
|
-
*
|
|
3118
|
-
*
|
|
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
|
+
* )
|
|
3119
3215
|
* ```
|
|
3120
|
-
*/ function setTranslator(translate) {
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
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
|
+
});
|
|
3125
3262
|
}
|
|
3126
3263
|
/**
|
|
3127
|
-
* Returns `t(
|
|
3128
|
-
*
|
|
3129
|
-
*
|
|
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}).
|
|
3130
3271
|
*
|
|
3131
3272
|
* @example
|
|
3132
3273
|
* ```ts
|
|
3133
3274
|
* const t = useTranslate()
|
|
3134
|
-
* throw new HttpError(409, { message: t('Name taken
|
|
3275
|
+
* throw new HttpError(409, { message: t('NAME_TAKEN', { name }, `Name ${name} is taken`), code: 'NAME_TAKEN', params: { name } })
|
|
3135
3276
|
* ```
|
|
3136
3277
|
*/ function useTranslate(ctx) {
|
|
3137
3278
|
const event = ctx ?? (0, _wooksjs_event_core.current)();
|
|
3138
3279
|
let translateCtx;
|
|
3139
|
-
return (
|
|
3140
|
-
const translate =
|
|
3141
|
-
|
|
3142
|
-
translateCtx ?? (translateCtx = {
|
|
3280
|
+
return (id, params, fallback) => {
|
|
3281
|
+
const translate = state.translate;
|
|
3282
|
+
return (translate ? callTranslator(translate, id, params, translateCtx ?? (translateCtx = {
|
|
3143
3283
|
locale: (0, _wooksjs_event_core.useLocale)(event).get(),
|
|
3144
3284
|
event
|
|
3145
|
-
});
|
|
3146
|
-
|
|
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;
|
|
3316
|
+
};
|
|
3317
|
+
return {
|
|
3318
|
+
locale,
|
|
3319
|
+
key,
|
|
3320
|
+
t,
|
|
3321
|
+
messages
|
|
3147
3322
|
};
|
|
3148
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;
|
|
3398
|
+
}
|
|
3149
3399
|
|
|
3150
3400
|
//#endregion
|
|
3151
3401
|
exports.After = After;
|
|
@@ -3257,6 +3507,7 @@ Object.defineProperty(exports, 'eventTypeKey', {
|
|
|
3257
3507
|
}
|
|
3258
3508
|
});
|
|
3259
3509
|
exports.forkEventContext = forkEventContext;
|
|
3510
|
+
exports.formatMessage = formatMessage;
|
|
3260
3511
|
Object.defineProperty(exports, 'getClassKey', {
|
|
3261
3512
|
enumerable: true,
|
|
3262
3513
|
get: function () {
|
|
@@ -3290,6 +3541,7 @@ exports.getMoostInfact = getMoostInfact;
|
|
|
3290
3541
|
exports.getMoostMate = getMoostMate;
|
|
3291
3542
|
exports.getNewMoostInfact = getNewMoostInfact;
|
|
3292
3543
|
exports.getTranslator = getTranslator;
|
|
3544
|
+
exports.getTranslatorLocales = getTranslatorLocales;
|
|
3293
3545
|
exports.globalKey = globalKey;
|
|
3294
3546
|
Object.defineProperty(exports, 'isConstructor', {
|
|
3295
3547
|
enumerable: true,
|
|
@@ -3307,6 +3559,7 @@ Object.defineProperty(exports, 'key', {
|
|
|
3307
3559
|
exports.loggerConsoleTransport = loggerConsoleTransport;
|
|
3308
3560
|
exports.mergeSorted = mergeSorted;
|
|
3309
3561
|
exports.registerEventScope = registerEventScope;
|
|
3562
|
+
exports.registerMessages = registerMessages;
|
|
3310
3563
|
Object.defineProperty(exports, 'replaceContextInjector', {
|
|
3311
3564
|
enumerable: true,
|
|
3312
3565
|
get: function () {
|
|
@@ -3334,6 +3587,7 @@ exports.setOvertake = setOvertake;
|
|
|
3334
3587
|
exports.setTranslator = setTranslator;
|
|
3335
3588
|
exports.stampModuleIdentity = stampModuleIdentity;
|
|
3336
3589
|
exports.stampOnce = stampOnce;
|
|
3590
|
+
exports.translateErrorBody = translateErrorBody;
|
|
3337
3591
|
exports.useControllerContext = useControllerContext;
|
|
3338
3592
|
exports.useHandlerPaths = useHandlerPaths;
|
|
3339
3593
|
exports.useInterceptOriginal = useInterceptOriginal;
|
|
@@ -3353,4 +3607,5 @@ Object.defineProperty(exports, 'useLogger', {
|
|
|
3353
3607
|
exports.useOvertake = useOvertake;
|
|
3354
3608
|
exports.useScopeId = useScopeId;
|
|
3355
3609
|
exports.useTranslate = useTranslate;
|
|
3610
|
+
exports.useTranslation = useTranslation;
|
|
3356
3611
|
exports.withControllerContext = withControllerContext;
|
package/dist/index.d.ts
CHANGED
|
@@ -1147,6 +1147,12 @@ declare function HandlerPaths(method?: string, opts?: TGetHandlerPathsOptions):
|
|
|
1147
1147
|
* overrides, which merge the parent's metadata under their own (so adding
|
|
1148
1148
|
* e.g. a rate-limit decorator to an override does not unbind the parent's
|
|
1149
1149
|
* `@Get`/`@Post` route).
|
|
1150
|
+
* - Exception to "own keys win" (since 0.7.2): interceptors (incl.
|
|
1151
|
+
* `@Authenticate` guards), pipes and prop decorators ACCUMULATE on the class
|
|
1152
|
+
* and on overridden methods, parent first. The same interceptor/pipe
|
|
1153
|
+
* *reference* on both levels runs once; a factory called on each level runs
|
|
1154
|
+
* twice. A subclass can't remove a base class's class-level interceptors.
|
|
1155
|
+
* Routes, params and `@ImportController` still replace.
|
|
1150
1156
|
* - On a method/prop: enables inheritance for just that member.
|
|
1151
1157
|
* - `@Inherit(false)` on an overridden member opts out of the merge for a
|
|
1152
1158
|
* deliberate full replacement (the member keeps only its own metadata).
|
|
@@ -1773,46 +1779,126 @@ interface TTranslateContext {
|
|
|
1773
1779
|
event: EventContext;
|
|
1774
1780
|
}
|
|
1775
1781
|
/**
|
|
1776
|
-
* The app's translator. `text` is a
|
|
1777
|
-
*
|
|
1778
|
-
*
|
|
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.
|
|
1779
1785
|
*
|
|
1780
|
-
* Moost never translates on its own: plug in any i18n library (
|
|
1781
|
-
*
|
|
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}.
|
|
1782
1788
|
*/
|
|
1783
1789
|
type TTranslate = (text: string, params: Record<string, unknown> | undefined, ctx: TTranslateContext) => string | undefined;
|
|
1784
1790
|
/**
|
|
1785
|
-
*
|
|
1786
|
-
*
|
|
1787
|
-
|
|
1788
|
-
|
|
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()`.
|
|
1789
1825
|
*
|
|
1790
1826
|
* @example
|
|
1791
1827
|
* ```ts
|
|
1792
|
-
*
|
|
1793
|
-
*
|
|
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
|
+
* )
|
|
1794
1835
|
* ```
|
|
1795
1836
|
*/
|
|
1796
|
-
declare function setTranslator(translate: TTranslate | undefined
|
|
1797
|
-
|
|
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`. */
|
|
1798
1841
|
declare function getTranslator(): TTranslate | undefined;
|
|
1799
|
-
/**
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
*
|
|
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}).
|
|
1803
1866
|
*
|
|
1804
1867
|
* @example
|
|
1805
1868
|
* ```ts
|
|
1806
1869
|
* const t = useTranslate()
|
|
1807
|
-
* throw new HttpError(409, { message: t('Name taken
|
|
1870
|
+
* throw new HttpError(409, { message: t('NAME_TAKEN', { name }, `Name ${name} is taken`), code: 'NAME_TAKEN', params: { name } })
|
|
1808
1871
|
* ```
|
|
1809
1872
|
*/
|
|
1810
|
-
declare function useTranslate(ctx?: EventContext):
|
|
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;
|
|
1811
1897
|
|
|
1812
1898
|
/** Creates a new `ProstoLogger` instance with sensible defaults (level 4, colored console). */
|
|
1813
1899
|
declare function createLogger(opts?: Partial<TProstoLoggerOptions>): ProstoLogger;
|
|
1814
1900
|
/** Default colored console transport used by Moost loggers. */
|
|
1815
1901
|
declare const loggerConsoleTransport: _prostojs_logger.TProstoLoggerTransportFn<any>;
|
|
1816
1902
|
|
|
1817
|
-
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 };
|
|
1818
|
-
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.
|
|
50
|
+
version: "0.7.3",
|
|
51
51
|
path: typeof __filename === "string" ? __filename : import.meta.url
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -135,7 +135,7 @@ function getDefaultLogger(topic) {
|
|
|
135
135
|
|
|
136
136
|
//#endregion
|
|
137
137
|
//#region packages/moost/src/event-scope.ts
|
|
138
|
-
function _define_property$
|
|
138
|
+
function _define_property$2(obj, key, value) {
|
|
139
139
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
140
140
|
value,
|
|
141
141
|
enumerable: true,
|
|
@@ -231,7 +231,7 @@ const NO_ISOLATION = /* @__PURE__ */ new Set();
|
|
|
231
231
|
super({
|
|
232
232
|
logger: parent.logger,
|
|
233
233
|
parent
|
|
234
|
-
}), _define_property$
|
|
234
|
+
}), _define_property$2(this, "isolated", void 0), this.isolated = isolated;
|
|
235
235
|
}
|
|
236
236
|
};
|
|
237
237
|
/**
|
|
@@ -384,6 +384,16 @@ let sourceRefs = [];
|
|
|
384
384
|
|
|
385
385
|
//#endregion
|
|
386
386
|
//#region packages/moost/src/metadata/moost-metadata.ts
|
|
387
|
+
function _define_property$1(obj, key, value) {
|
|
388
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
389
|
+
value,
|
|
390
|
+
enumerable: true,
|
|
391
|
+
configurable: true,
|
|
392
|
+
writable: true
|
|
393
|
+
});
|
|
394
|
+
else obj[key] = value;
|
|
395
|
+
return obj;
|
|
396
|
+
}
|
|
387
397
|
const METADATA_WORKSPACE = "moost";
|
|
388
398
|
/**
|
|
389
399
|
* A param entry is "bare" when it carries nothing but the design-time type
|
|
@@ -393,7 +403,71 @@ const METADATA_WORKSPACE = "moost";
|
|
|
393
403
|
*/ function isBareParam(param) {
|
|
394
404
|
return !param || Object.entries(param).every(([key, value]) => key === "type" || value === void 0);
|
|
395
405
|
}
|
|
396
|
-
|
|
406
|
+
/**
|
|
407
|
+
* List keys that describe behaviour stacked on a class or method. Under
|
|
408
|
+
* `@Inherit()` they ACCUMULATE along `extends` (base entries first, then the
|
|
409
|
+
* subclass's own) instead of following the shallow own-wins merge — a subclass
|
|
410
|
+
* adding a guard must not silently drop the base's error mapping or guard.
|
|
411
|
+
* `authTransports` is written by `@Authenticate` (event-http) next to its
|
|
412
|
+
* interceptor entry and has to travel with it. Route `handlers`, params,
|
|
413
|
+
* `importController` (re-mounting would duplicate routes) and documentation
|
|
414
|
+
* lists (swagger tags/security, …) keep own-wins semantics.
|
|
415
|
+
*/ const ACCUMULATED_KEYS = [
|
|
416
|
+
"interceptors",
|
|
417
|
+
"pipes",
|
|
418
|
+
"authTransports",
|
|
419
|
+
"properties",
|
|
420
|
+
"requiredProps"
|
|
421
|
+
];
|
|
422
|
+
/**
|
|
423
|
+
* The identity an entry is deduplicated by when it appears on both levels: the
|
|
424
|
+
* `handler` reference for interceptor/pipe entries, the value itself otherwise.
|
|
425
|
+
*/ function entryIdentity(entry) {
|
|
426
|
+
return entry?.handler ?? entry;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Inherited entries first, then the own ones. An own entry that re-declares an
|
|
430
|
+
* inherited one (same identity) takes the inherited entry's place.
|
|
431
|
+
*/ function accumulate(inherited, own) {
|
|
432
|
+
const ownById = /* @__PURE__ */ new Map();
|
|
433
|
+
for (const entry of own) {
|
|
434
|
+
const id = entryIdentity(entry);
|
|
435
|
+
if (!ownById.has(id)) ownById.set(id, entry);
|
|
436
|
+
}
|
|
437
|
+
const result = [];
|
|
438
|
+
const placed = /* @__PURE__ */ new Set();
|
|
439
|
+
for (const entry of inherited) {
|
|
440
|
+
const id = entryIdentity(entry);
|
|
441
|
+
if (ownById.has(id)) {
|
|
442
|
+
placed.add(id);
|
|
443
|
+
result.push(ownById.get(id));
|
|
444
|
+
} else result.push(entry);
|
|
445
|
+
}
|
|
446
|
+
for (const entry of own) if (!placed.has(entryIdentity(entry))) result.push(entry);
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
const moostMate = new class MoostMate extends Mate {
|
|
450
|
+
read(target, propKey) {
|
|
451
|
+
const result = super.read(target, propKey);
|
|
452
|
+
const meta = result;
|
|
453
|
+
if (!meta || this.settled.has(meta)) return result;
|
|
454
|
+
this.settled.add(meta);
|
|
455
|
+
const ctor = getConstructor$1(target);
|
|
456
|
+
const own = Reflect.getOwnMetadata(this.workspace, typeof propKey === "string" ? ctor.prototype : ctor, propKey);
|
|
457
|
+
if (own && meta !== own) {
|
|
458
|
+
const inherited = this.read(Object.getPrototypeOf(ctor), propKey);
|
|
459
|
+
for (const key of ACCUMULATED_KEYS) {
|
|
460
|
+
const ownList = own[key];
|
|
461
|
+
const inheritedList = inherited?.[key];
|
|
462
|
+
if (Array.isArray(ownList) && Array.isArray(inheritedList) && inheritedList.length > 0) meta[key] = accumulate(inheritedList, ownList);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return result;
|
|
466
|
+
}
|
|
467
|
+
constructor(...args) {
|
|
468
|
+
super(...args), _define_property$1(this, "settled", /* @__PURE__ */ new WeakSet());
|
|
469
|
+
}
|
|
470
|
+
}(METADATA_WORKSPACE, {
|
|
397
471
|
readType: true,
|
|
398
472
|
readReturnType: true,
|
|
399
473
|
collectPropKeys: true,
|
|
@@ -583,7 +657,7 @@ function formatNewInstanceArg(a) {
|
|
|
583
657
|
//#endregion
|
|
584
658
|
//#region packages/moost/src/composables/global-key.ts
|
|
585
659
|
var _g;
|
|
586
|
-
const registry
|
|
660
|
+
const registry = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
|
|
587
661
|
/**
|
|
588
662
|
* Creates a typed event-context key that stays a single instance across duplicate
|
|
589
663
|
* moost loads (dual ESM/CJS, or duplicate installs). Use this for every
|
|
@@ -591,8 +665,8 @@ const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PU
|
|
|
591
665
|
*
|
|
592
666
|
* @param name - Debug label AND the registry lookup key; must be unique per slot.
|
|
593
667
|
*/ function globalKey(name) {
|
|
594
|
-
if (!registry
|
|
595
|
-
return registry
|
|
668
|
+
if (!registry.has(name)) registry.set(name, key$1(name));
|
|
669
|
+
return registry.get(name);
|
|
596
670
|
}
|
|
597
671
|
|
|
598
672
|
//#endregion
|
|
@@ -1365,6 +1439,12 @@ function fillLabel(target, key, index, name) {
|
|
|
1365
1439
|
* overrides, which merge the parent's metadata under their own (so adding
|
|
1366
1440
|
* e.g. a rate-limit decorator to an override does not unbind the parent's
|
|
1367
1441
|
* `@Get`/`@Post` route).
|
|
1442
|
+
* - Exception to "own keys win" (since 0.7.2): interceptors (incl.
|
|
1443
|
+
* `@Authenticate` guards), pipes and prop decorators ACCUMULATE on the class
|
|
1444
|
+
* and on overridden methods, parent first. The same interceptor/pipe
|
|
1445
|
+
* *reference* on both levels runs once; a factory called on each level runs
|
|
1446
|
+
* twice. A subclass can't remove a base class's class-level interceptors.
|
|
1447
|
+
* Routes, params and `@ImportController` still replace.
|
|
1368
1448
|
* - On a method/prop: enables inheritance for just that member.
|
|
1369
1449
|
* - `@Inherit(false)` on an overridden member opts out of the merge for a
|
|
1370
1450
|
* deliberate full replacement (the member keeps only its own metadata).
|
|
@@ -3103,48 +3183,218 @@ function _define_property(obj, key, value) {
|
|
|
3103
3183
|
|
|
3104
3184
|
//#endregion
|
|
3105
3185
|
//#region packages/moost/src/i18n.ts
|
|
3106
|
-
|
|
3107
|
-
const
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
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()`.
|
|
3113
3204
|
*
|
|
3114
3205
|
* @example
|
|
3115
3206
|
* ```ts
|
|
3116
|
-
*
|
|
3117
|
-
*
|
|
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
|
+
* )
|
|
3118
3214
|
* ```
|
|
3119
|
-
*/ function setTranslator(translate) {
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
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
|
+
});
|
|
3124
3261
|
}
|
|
3125
3262
|
/**
|
|
3126
|
-
* Returns `t(
|
|
3127
|
-
*
|
|
3128
|
-
*
|
|
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}).
|
|
3129
3270
|
*
|
|
3130
3271
|
* @example
|
|
3131
3272
|
* ```ts
|
|
3132
3273
|
* const t = useTranslate()
|
|
3133
|
-
* throw new HttpError(409, { message: t('Name taken
|
|
3274
|
+
* throw new HttpError(409, { message: t('NAME_TAKEN', { name }, `Name ${name} is taken`), code: 'NAME_TAKEN', params: { name } })
|
|
3134
3275
|
* ```
|
|
3135
3276
|
*/ function useTranslate(ctx) {
|
|
3136
3277
|
const event = ctx ?? current$1();
|
|
3137
3278
|
let translateCtx;
|
|
3138
|
-
return (
|
|
3139
|
-
const translate =
|
|
3140
|
-
|
|
3141
|
-
translateCtx ?? (translateCtx = {
|
|
3279
|
+
return (id, params, fallback) => {
|
|
3280
|
+
const translate = state.translate;
|
|
3281
|
+
return (translate ? callTranslator(translate, id, params, translateCtx ?? (translateCtx = {
|
|
3142
3282
|
locale: useLocale$1(event).get(),
|
|
3143
3283
|
event
|
|
3144
|
-
});
|
|
3145
|
-
|
|
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;
|
|
3315
|
+
};
|
|
3316
|
+
return {
|
|
3317
|
+
locale,
|
|
3318
|
+
key,
|
|
3319
|
+
t,
|
|
3320
|
+
messages
|
|
3146
3321
|
};
|
|
3147
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;
|
|
3397
|
+
}
|
|
3148
3398
|
|
|
3149
3399
|
//#endregion
|
|
3150
|
-
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.
|
|
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.
|
|
44
|
+
"@wooksjs/event-core": "^0.8.1",
|
|
45
45
|
"hookable": "^5.5.3",
|
|
46
|
-
"wooks": "^0.8.
|
|
46
|
+
"wooks": "^0.8.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@wooksjs/event-http": "^0.8.
|
|
50
|
-
"@wooksjs/http-body": "^0.8.
|
|
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.
|
|
52
|
+
"@moostjs/event-http": "^0.7.3"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"pub": "pnpm publish --access public",
|