@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 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 zLocalizedString = () => z.record(
355
- z.string().regex(LANGUAGE_CODE_REGEX, { message: "Expected a language code (BCP 47, e.g. en or fr-FR)." }),
356
- z.string()
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;AAIxC,eAAO,MAAM,gBAAgB,QAMtB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAA;AAE5C,eAAO,MAAM,WAAW,QAFjB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAEA,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;AAGZ,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;AA6ZD,eAAe,SAAS,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/db",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"