@rpcbase/db 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/index.js +110 -4
- package/dist/schema/extension.d.ts +6 -0
- package/dist/schema/extension.d.ts.map +1 -1
- package/dist/schema/index.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
## `@rpcbase/db`
|
|
2
|
+
|
|
3
|
+
### `LocalizedString` / `zI18nString()`
|
|
4
|
+
|
|
5
|
+
`zI18nString()` (alias de `zLocalizedString()`) décrit une string localisée sous forme d’objet, avec des clés en *language codes* (BCP47) :
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { z } from "zod"
|
|
9
|
+
import { zI18nString, zodSchema } from "@rpcbase/db"
|
|
10
|
+
|
|
11
|
+
const ZPost = z.object({
|
|
12
|
+
title: zI18nString(), // { "en": "...", "fr": "...", "fr-FR": "..." }
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const schema = zodSchema(ZPost)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Côté Mongoose (important)**
|
|
19
|
+
|
|
20
|
+
- Le champ est stocké comme un objet et typé `SchemaTypes.Mixed` (pas un `Map`).
|
|
21
|
+
- À la lecture, le getter Mongoose renvoie un objet “proxy” avec fallback automatique : `fr-FR` retombe sur `fr` si `fr-FR` n’existe pas.
|
|
22
|
+
- `doc.title["fr-FR"]` → fallback
|
|
23
|
+
- `doc.title.get("fr-FR")` → fallback (helper de lecture)
|
|
24
|
+
- `doc.title.getExact("fr-FR")` → exact (sans fallback)
|
|
25
|
+
- À l’écriture, comme c’est du `Mixed`, évite les mutations profondes silencieuses : préfère remplacer l’objet (`doc.title = { ...doc.title, fr: "Salut" }`) ou appelle `doc.markModified("title")`.
|
package/dist/index.js
CHANGED
|
@@ -351,10 +351,90 @@ const zUUID = (ref) => {
|
|
|
351
351
|
return output;
|
|
352
352
|
};
|
|
353
353
|
const LANGUAGE_CODE_REGEX = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
);
|
|
354
|
+
const LOCALIZED_STRING_PROXY_CACHE = /* @__PURE__ */ new WeakMap();
|
|
355
|
+
function normalizeLocale(locale) {
|
|
356
|
+
const trimmed = locale.trim();
|
|
357
|
+
if (!trimmed) return "";
|
|
358
|
+
const getCanonicalLocales = Intl?.getCanonicalLocales;
|
|
359
|
+
if (typeof getCanonicalLocales !== "function") return trimmed;
|
|
360
|
+
try {
|
|
361
|
+
return getCanonicalLocales(trimmed)[0] ?? trimmed;
|
|
362
|
+
} catch {
|
|
363
|
+
return trimmed;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function buildLocaleFallbackChain(locale, fallbacks) {
|
|
367
|
+
const base = locale.trim();
|
|
368
|
+
const canonical = normalizeLocale(base);
|
|
369
|
+
const extra = typeof fallbacks === "string" ? [fallbacks] : fallbacks ?? [];
|
|
370
|
+
const output = [];
|
|
371
|
+
const seen = /* @__PURE__ */ new Set();
|
|
372
|
+
const push = (value) => {
|
|
373
|
+
if (!value) return;
|
|
374
|
+
if (seen.has(value)) return;
|
|
375
|
+
seen.add(value);
|
|
376
|
+
output.push(value);
|
|
377
|
+
};
|
|
378
|
+
const addChain = (value) => {
|
|
379
|
+
if (!value) return;
|
|
380
|
+
const parts = value.split("-").filter(Boolean);
|
|
381
|
+
while (parts.length > 0) {
|
|
382
|
+
push(parts.join("-"));
|
|
383
|
+
parts.pop();
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
addChain(base);
|
|
387
|
+
addChain(canonical);
|
|
388
|
+
for (const fallback of extra) addChain(normalizeLocale(fallback));
|
|
389
|
+
return output;
|
|
390
|
+
}
|
|
391
|
+
function resolveLocalizedString(value, locale, options) {
|
|
392
|
+
if (!value) return void 0;
|
|
393
|
+
const chain = buildLocaleFallbackChain(locale, options?.fallbacks);
|
|
394
|
+
if (chain.length === 0) return void 0;
|
|
395
|
+
const record = value;
|
|
396
|
+
for (const key of chain) {
|
|
397
|
+
if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
398
|
+
return record[key];
|
|
399
|
+
}
|
|
400
|
+
return void 0;
|
|
401
|
+
}
|
|
402
|
+
function withLocalizedStringFallback(value) {
|
|
403
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
404
|
+
if (value instanceof Map) return value;
|
|
405
|
+
const cached = LOCALIZED_STRING_PROXY_CACHE.get(value);
|
|
406
|
+
if (cached) return cached;
|
|
407
|
+
const proxy = withFallbackRecord(value);
|
|
408
|
+
LOCALIZED_STRING_PROXY_CACHE.set(value, proxy);
|
|
409
|
+
return proxy;
|
|
410
|
+
}
|
|
411
|
+
function withFallbackRecord(record) {
|
|
412
|
+
const getExact = (key) => record[key];
|
|
413
|
+
const hasExact = (key) => Object.prototype.hasOwnProperty.call(record, key);
|
|
414
|
+
return new Proxy(record, {
|
|
415
|
+
get(target, prop) {
|
|
416
|
+
if (prop === "getExact") return getExact;
|
|
417
|
+
if (prop === "hasExact") return hasExact;
|
|
418
|
+
if (prop === "get") return (key) => resolveLocalizedString(record, key);
|
|
419
|
+
if (typeof prop === "string" && !(prop in target)) {
|
|
420
|
+
return resolveLocalizedString(record, prop);
|
|
421
|
+
}
|
|
422
|
+
return target[prop];
|
|
423
|
+
},
|
|
424
|
+
set(target, prop, next) {
|
|
425
|
+
target[prop] = next;
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
const zLocalizedString = () => {
|
|
431
|
+
const schema = z.record(
|
|
432
|
+
z.string().regex(LANGUAGE_CODE_REGEX, { message: "Expected a language code (BCP 47, e.g. en or fr-FR)." }),
|
|
433
|
+
z.string()
|
|
434
|
+
);
|
|
435
|
+
schema.__rpcbase_localizedString = true;
|
|
436
|
+
return schema;
|
|
437
|
+
};
|
|
358
438
|
const zI18nString = zLocalizedString;
|
|
359
439
|
function zodSchema(schema, options) {
|
|
360
440
|
const definition = parseObject(schema);
|
|
@@ -527,6 +607,28 @@ function parseField(field, required = true, def, refinement) {
|
|
|
527
607
|
if (zmAssert$1.any(field)) {
|
|
528
608
|
return parseMixed(required, def);
|
|
529
609
|
}
|
|
610
|
+
if (field.__rpcbase_localizedString) {
|
|
611
|
+
return {
|
|
612
|
+
type: SchemaTypes.Mixed,
|
|
613
|
+
default: def,
|
|
614
|
+
required,
|
|
615
|
+
validate: {
|
|
616
|
+
validator: (value) => {
|
|
617
|
+
if (value == null) return true;
|
|
618
|
+
if (typeof value !== "object" || Array.isArray(value)) return false;
|
|
619
|
+
const proto = Object.getPrototypeOf(value);
|
|
620
|
+
if (proto !== Object.prototype && proto !== null) return false;
|
|
621
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
622
|
+
if (!LANGUAGE_CODE_REGEX.test(key)) return false;
|
|
623
|
+
if (typeof entry !== "string") return false;
|
|
624
|
+
}
|
|
625
|
+
return true;
|
|
626
|
+
},
|
|
627
|
+
message: "Expected a localized string record."
|
|
628
|
+
},
|
|
629
|
+
get: (value) => value == null ? value : withLocalizedStringFallback(value)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
530
632
|
if (zmAssert$1.mapOrRecord(field)) {
|
|
531
633
|
const mapValueType = field.valueType ?? field.valueSchema;
|
|
532
634
|
const mapKeyType = field.keyType ?? field.keySchema;
|
|
@@ -717,14 +819,18 @@ const loadModel = async (modelName, ctx) => {
|
|
|
717
819
|
return modelConnection.models[modelName];
|
|
718
820
|
};
|
|
719
821
|
export {
|
|
822
|
+
LANGUAGE_CODE_REGEX,
|
|
720
823
|
RBTenantSchema,
|
|
721
824
|
RBUserSchema,
|
|
722
825
|
ZRBTenant,
|
|
723
826
|
ZRBUser,
|
|
827
|
+
buildLocaleFallbackChain,
|
|
724
828
|
extendZod,
|
|
725
829
|
getRegisteredModels,
|
|
726
830
|
loadModel,
|
|
727
831
|
registerModels,
|
|
832
|
+
resolveLocalizedString,
|
|
833
|
+
withLocalizedStringFallback,
|
|
728
834
|
z2 as z,
|
|
729
835
|
zI18nString,
|
|
730
836
|
zId,
|
|
@@ -66,6 +66,12 @@ export type LanguageCode = string;
|
|
|
66
66
|
export type LocalizedString = Record<LanguageCode, string>;
|
|
67
67
|
export type I18nStringRecord = LocalizedString;
|
|
68
68
|
export type I18nString = LocalizedString;
|
|
69
|
+
export declare const LANGUAGE_CODE_REGEX: RegExp;
|
|
70
|
+
export declare function buildLocaleFallbackChain(locale: string, fallbacks?: string | string[]): string[];
|
|
71
|
+
export declare function resolveLocalizedString(value: LocalizedString | null | undefined, locale: string, options?: {
|
|
72
|
+
fallbacks?: string | string[];
|
|
73
|
+
}): string | undefined;
|
|
74
|
+
export declare function withLocalizedStringFallback<T extends object>(value: T): T;
|
|
69
75
|
export declare const zLocalizedString: () => z.ZodRecord<z.ZodString, z.ZodString>;
|
|
70
76
|
export declare const zI18nString: () => z.ZodRecord<z.ZodString, z.ZodString>;
|
|
71
77
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../src/schema/extension.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAmB,MAAM,UAAU,CAAA;AACjD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,QAAQ,KAAK,CAAC;IACnB,UAAU,SAAS;QACjB,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,SAAS,CAAA;QACpC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,SAAS,CAAA;QACpC,WAAW,EAAE,OAAO,CAAA;QACpB,WAAW,EAAE,OAAO,CAAA;KACrB;IAED,UAAU,SAAS;QACjB,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,SAAS,CAAA;QACpC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,SAAS,CAAA;QACpC,WAAW,EAAE,OAAO,CAAA;QACpB,WAAW,EAAE,OAAO,CAAA;KACrB;IAED,UAAU,OAAO;QACf,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;QAClC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;QAClC,WAAW,EAAE,OAAO,CAAA;QACpB,WAAW,EAAE,OAAO,CAAA;KACrB;CAEF;AA+CD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,QA2DtC;AAED,MAAM,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,GAAG;IAChD,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,KAAK,CAAA;IAChC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,KAAK,CAAA;IAChC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,KAAK,CAAA;IAC3B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,KAAK,CAAA;CAChC,CAAA;AAED,QAAA,MAAM,QAAQ,8EAKb,CAAA;AAED,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,KAAG,KAoDlC,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG;IACpD,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;IAClC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;IAClC,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;IAC7B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CAClC,CAAA;AAED,QAAA,MAAM,UAAU,oEAEf,CAAA;AAED,eAAO,MAAM,KAAK,GAAI,MAAM,MAAM,KAAG,OAoDpC,CAAA;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAA;AAEjC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;AAE1D,MAAM,MAAM,gBAAgB,GAAG,eAAe,CAAA;AAE9C,MAAM,MAAM,UAAU,GAAG,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../src/schema/extension.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAe,MAAM,UAAU,CAAA;AAClE,OAAO,EAEL,SAAS,EACT,WAAW,EAGX,CAAC,EACF,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAe,MAAM,UAAU,CAAA;AAClE,OAAO,EAEL,SAAS,EACT,WAAW,EAGX,CAAC,EACF,MAAM,KAAK,CAAA;AAIZ,OAAO,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAG3C,cAAc,aAAa,CAAA;AAC3B,cAAc,KAAK,CAAA;AAEnB,OAAO,EAAE,CAAC,EAAE,CAAA;AAEZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,WAAW,EAC7C,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,GAC3B,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC,CAGhC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,WAAW,EAChD,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,GACnB,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAEf;AAqbD,eAAe,SAAS,CAAA"}
|