@rpcbase/db 0.11.0 → 0.13.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
@@ -350,10 +350,92 @@ const zUUID = (ref) => {
350
350
  };
351
351
  return output;
352
352
  };
353
- const zI18nString = () => z.record(
354
- z.string().regex(/^[A-Za-z]{2}$/i, { message: "Expected a country code (ISO 3166-1 alpha-2)." }),
355
- z.string()
356
- );
353
+ const LANGUAGE_CODE_REGEX = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
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
+ };
438
+ const zI18nString = zLocalizedString;
357
439
  function zodSchema(schema, options) {
358
440
  const definition = parseObject(schema);
359
441
  return new Schema(definition, options);
@@ -525,6 +607,28 @@ function parseField(field, required = true, def, refinement) {
525
607
  if (zmAssert$1.any(field)) {
526
608
  return parseMixed(required, def);
527
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
+ }
528
632
  if (zmAssert$1.mapOrRecord(field)) {
529
633
  const mapValueType = field.valueType ?? field.valueSchema;
530
634
  const mapKeyType = field.keyType ?? field.keySchema;
@@ -715,17 +819,22 @@ const loadModel = async (modelName, ctx) => {
715
819
  return modelConnection.models[modelName];
716
820
  };
717
821
  export {
822
+ LANGUAGE_CODE_REGEX,
718
823
  RBTenantSchema,
719
824
  RBUserSchema,
720
825
  ZRBTenant,
721
826
  ZRBUser,
827
+ buildLocaleFallbackChain,
722
828
  extendZod,
723
829
  getRegisteredModels,
724
830
  loadModel,
725
831
  registerModels,
832
+ resolveLocalizedString,
833
+ withLocalizedStringFallback,
726
834
  z2 as z,
727
835
  zI18nString,
728
836
  zId,
837
+ zLocalizedString,
729
838
  zUUID,
730
839
  zodSchema,
731
840
  zodSchemaRaw
@@ -62,7 +62,17 @@ export type TzmUUID = ReturnType<typeof createUUID> & {
62
62
  };
63
63
  declare const createUUID: () => z.ZodUnion<[z.ZodUUID, z.ZodCustom<Types.UUID, Types.UUID>]>;
64
64
  export declare const zUUID: (ref?: string) => TzmUUID;
65
- export type I18nStringRecord = Record<string, string>;
65
+ export type LanguageCode = string;
66
+ export type LocalizedString = Record<LanguageCode, string>;
67
+ export type I18nStringRecord = LocalizedString;
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;
75
+ export declare const zLocalizedString: () => z.ZodRecord<z.ZodString, z.ZodString>;
66
76
  export declare const zI18nString: () => z.ZodRecord<z.ZodString, z.ZodString>;
67
77
  export {};
68
78
  //# sourceMappingURL=extension.d.ts.map
@@ -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,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;AAErD,eAAO,MAAM,WAAW,QAMjB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,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.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"