@rpcbase/db 0.36.0 → 0.37.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
@@ -13,8 +13,32 @@ const ZUser = z.object({
13
13
  })
14
14
  ```
15
15
 
16
+ ### `LocalizedString` / `zI18nString()`
17
+
18
+ `zI18nString()` (alias of `zLocalizedString()`) describes a localized string as an object keyed by *language codes* (BCP47):
19
+
20
+ ```ts
21
+ import { z, zI18nString } from "@rpcbase/db"
22
+
23
+ const ZPost = z.object({
24
+ title: zI18nString(), // { "en": "...", "fr": "...", "fr-FR": "..." }
25
+ })
26
+ ```
27
+
16
28
  ### Mongoose re-export
17
29
 
18
30
  ```ts
19
31
  import { Schema, model, mongoose } from "@rpcbase/db"
20
32
  ```
33
+
34
+ ### Mongoose localized fallback (optional)
35
+
36
+ When storing localized strings as plain objects, `mongooseLocalizedStringField()` adds a getter that returns a proxy with fallback behavior:
37
+
38
+ ```ts
39
+ import { Schema, mongooseLocalizedStringField } from "@rpcbase/db"
40
+
41
+ const PostSchema = new Schema({
42
+ title: mongooseLocalizedStringField(),
43
+ })
44
+ ```
@@ -1,5 +1,90 @@
1
1
  import { z } from "zod";
2
2
  import { z as z2 } from "zod";
3
+ const LANGUAGE_CODE_REGEX = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
4
+ const LOCALIZED_STRING_PROXY_CACHE = /* @__PURE__ */ new WeakMap();
5
+ function normalizeLocale(locale) {
6
+ const trimmed = locale.trim();
7
+ if (!trimmed) return "";
8
+ const getCanonicalLocales = Intl?.getCanonicalLocales;
9
+ if (typeof getCanonicalLocales !== "function") return trimmed;
10
+ try {
11
+ return getCanonicalLocales(trimmed)[0] ?? trimmed;
12
+ } catch {
13
+ return trimmed;
14
+ }
15
+ }
16
+ function buildLocaleFallbackChain(locale, fallbacks) {
17
+ const base = locale.trim();
18
+ const canonical = normalizeLocale(base);
19
+ const extra = typeof fallbacks === "string" ? [fallbacks] : fallbacks ?? [];
20
+ const output = [];
21
+ const seen = /* @__PURE__ */ new Set();
22
+ const push = (value) => {
23
+ if (!value) return;
24
+ if (seen.has(value)) return;
25
+ seen.add(value);
26
+ output.push(value);
27
+ };
28
+ const addChain = (value) => {
29
+ if (!value) return;
30
+ const parts = value.split("-").filter(Boolean);
31
+ while (parts.length > 0) {
32
+ push(parts.join("-"));
33
+ parts.pop();
34
+ }
35
+ };
36
+ addChain(base);
37
+ addChain(canonical);
38
+ for (const fallback of extra) addChain(normalizeLocale(fallback));
39
+ return output;
40
+ }
41
+ function resolveLocalizedString(value, locale, options) {
42
+ if (!value) return void 0;
43
+ const chain = buildLocaleFallbackChain(locale, options?.fallbacks);
44
+ if (chain.length === 0) return void 0;
45
+ const record = value;
46
+ for (const key of chain) {
47
+ if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
48
+ return record[key];
49
+ }
50
+ return void 0;
51
+ }
52
+ function withLocalizedStringFallback(value) {
53
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
54
+ if (value instanceof Map) return value;
55
+ const cached = LOCALIZED_STRING_PROXY_CACHE.get(value);
56
+ if (cached) return cached;
57
+ const proxy = withFallbackRecord(value);
58
+ LOCALIZED_STRING_PROXY_CACHE.set(value, proxy);
59
+ return proxy;
60
+ }
61
+ function withFallbackRecord(record) {
62
+ const getExact = (key) => record[key];
63
+ const hasExact = (key) => Object.prototype.hasOwnProperty.call(record, key);
64
+ return new Proxy(record, {
65
+ get(target, prop) {
66
+ if (prop === "getExact") return getExact;
67
+ if (prop === "hasExact") return hasExact;
68
+ if (prop === "get") return (key) => resolveLocalizedString(record, key);
69
+ if (typeof prop === "string" && !(prop in target)) {
70
+ return resolveLocalizedString(record, prop);
71
+ }
72
+ return target[prop];
73
+ },
74
+ set(target, prop, next) {
75
+ target[prop] = next;
76
+ return true;
77
+ }
78
+ });
79
+ }
80
+ const zLocalizedString = () => {
81
+ const schema = z.record(
82
+ z.string().regex(LANGUAGE_CODE_REGEX, { message: "Expected a language code (BCP 47, e.g. en or fr-FR)." }),
83
+ z.string()
84
+ );
85
+ return schema;
86
+ };
87
+ const zI18nString = zLocalizedString;
3
88
  let zodExtended = false;
4
89
  function extendZod(zod) {
5
90
  if (zodExtended) return;
@@ -18,5 +103,12 @@ function extendZod(zod) {
18
103
  }
19
104
  extendZod(z);
20
105
  export {
21
- z2 as z
106
+ LANGUAGE_CODE_REGEX,
107
+ buildLocaleFallbackChain,
108
+ extendZod,
109
+ resolveLocalizedString,
110
+ withLocalizedStringFallback,
111
+ z2 as z,
112
+ zI18nString,
113
+ zLocalizedString
22
114
  };
package/dist/index.d.ts CHANGED
@@ -4,7 +4,6 @@ export * from './mongoose';
4
4
  export * from './pagination';
5
5
  export * from './zod';
6
6
  export * from './createModels';
7
- export * from './extendMongooseSchema';
8
7
  export * from './modelsApi';
9
8
  export * from './tenantFilesystemDb';
10
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAA;AACrB,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,OAAO,CAAA;AAErB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,wBAAwB,CAAA;AACtC,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAA;AACrB,cAAc,UAAU,CAAA;AACxB,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,OAAO,CAAA;AAErB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA"}
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ import { default as default2 } from "mongoose";
5
5
  import { z } from "zod";
6
6
  import { z as z2 } from "zod";
7
7
  import { timingSafeEqual, createHmac } from "node:crypto";
8
- import "./index.browser.js";
8
+ import { withLocalizedStringFallback } from "./index.browser.js";
9
+ import { LANGUAGE_CODE_REGEX, buildLocaleFallbackChain, extendZod, resolveLocalizedString, zI18nString, zLocalizedString } from "./index.browser.js";
9
10
  import assert from "assert";
10
11
  import { accessibleBy, accessibleRecordsPlugin } from "@casl/mongoose";
11
12
  import "@casl/ability";
@@ -432,6 +433,24 @@ const frameworkSchemas = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.de
432
433
  ZRBUploadSessionStatus,
433
434
  ZRBUser
434
435
  }, Symbol.toStringTag, { value: "Module" }));
436
+ function extendMongooseSchema(baseSchema, ...extensions) {
437
+ const schema = baseSchema.clone();
438
+ extensions.forEach((extension) => schema.add(extension));
439
+ return schema;
440
+ }
441
+ function omitMongooseSchemaPaths(schema, paths) {
442
+ const clone = schema.clone();
443
+ paths.forEach((path) => clone.remove(path));
444
+ return clone;
445
+ }
446
+ function localizedStringField(options) {
447
+ const userGet = options?.get;
448
+ return {
449
+ ...options,
450
+ type: Schema$1.Types.Mixed,
451
+ get: (value) => withLocalizedStringFallback(userGet ? userGet(value) : value)
452
+ };
453
+ }
435
454
  const { Schema, model } = mongoose;
436
455
  class PaginationValidationError extends Error {
437
456
  code = "invalid_pagination";
@@ -1178,16 +1197,6 @@ const createModels = (modules) => {
1178
1197
  getGlobal
1179
1198
  };
1180
1199
  };
1181
- function extendMongooseSchema(baseSchema, ...extensions) {
1182
- const schema = baseSchema.clone();
1183
- extensions.forEach((extension) => schema.add(extension));
1184
- return schema;
1185
- }
1186
- function omitSchemaPaths(schema, paths) {
1187
- const clone = schema.clone();
1188
- paths.forEach((path) => clone.remove(path));
1189
- return clone;
1190
- }
1191
1200
  const getAppName = () => {
1192
1201
  const appName = process.env.APP_NAME?.trim();
1193
1202
  assert(appName, "Missing APP_NAME");
@@ -1206,6 +1215,7 @@ const getTenantFilesystemDbFromCtx = async (ctx) => {
1206
1215
  return getTenantFilesystemDb(tenantId);
1207
1216
  };
1208
1217
  export {
1218
+ LANGUAGE_CODE_REGEX,
1209
1219
  PaginationValidationError,
1210
1220
  RBNotificationPolicy,
1211
1221
  RBNotificationSchema,
@@ -1243,9 +1253,11 @@ export {
1243
1253
  ZRBUser,
1244
1254
  b as buildAbility,
1245
1255
  d as buildAbilityFromSession,
1256
+ buildLocaleFallbackChain,
1246
1257
  f as can,
1247
1258
  createModels,
1248
1259
  extendMongooseSchema,
1260
+ extendZod,
1249
1261
  e as getAccessibleByQuery,
1250
1262
  g as getRegisteredPolicies,
1251
1263
  getTenantFilesystemDb,
@@ -1253,12 +1265,17 @@ export {
1253
1265
  getTenantFilesystemDbName,
1254
1266
  c as getTenantRolesFromSessionUser,
1255
1267
  isPaginationValidationError,
1268
+ localizedStringField,
1256
1269
  model,
1257
1270
  models,
1258
1271
  mongoPaginationPlugin,
1259
1272
  default2 as mongoose,
1260
- omitSchemaPaths,
1273
+ omitMongooseSchemaPaths,
1261
1274
  registerPoliciesFromModules,
1262
1275
  r as registerPolicy,
1263
- z2 as z
1276
+ resolveLocalizedString,
1277
+ withLocalizedStringFallback,
1278
+ z2 as z,
1279
+ zI18nString,
1280
+ zLocalizedString
1264
1281
  };
@@ -0,0 +1,6 @@
1
+ import { Schema } from '../../../vite/node_modules/mongoose';
2
+ type MongooseSchemaExtension = Parameters<Schema["add"]>[0];
3
+ export declare function extendMongooseSchema<TSchema extends Schema>(baseSchema: TSchema, ...extensions: MongooseSchemaExtension[]): TSchema;
4
+ export declare function omitMongooseSchemaPaths<TSchema extends Schema>(schema: TSchema, paths: string[]): TSchema;
5
+ export {};
6
+ //# sourceMappingURL=extendMongooseSchema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extendMongooseSchema.d.ts","sourceRoot":"","sources":["../../src/mongoose/extendMongooseSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,KAAK,uBAAuB,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE3D,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,MAAM,EACzD,UAAU,EAAE,OAAO,EACnB,GAAG,UAAU,EAAE,uBAAuB,EAAE,GACvC,OAAO,CAMT;AAED,wBAAgB,uBAAuB,CAAC,OAAO,SAAS,MAAM,EAC5D,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAIT"}
@@ -1,4 +1,6 @@
1
- import { default as mongoose } from '../../../vite/node_modules/mongoose';
1
+ import { default as mongoose, InferSchemaType } from '../../../vite/node_modules/mongoose';
2
2
  declare const Schema: typeof mongoose.Schema, model: typeof mongoose.model;
3
- export { Schema, model, mongoose };
3
+ export { Schema, model, mongoose, type InferSchemaType };
4
+ export * from './extendMongooseSchema';
5
+ export * from './localizedStringField';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mongoose/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAG/B,QAAA,MAAQ,MAAM,0BAAE,KAAK,uBAAa,CAAA;AAElC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mongoose/index.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAG/C,QAAA,MAAQ,MAAM,0BAAE,KAAK,uBAAa,CAAA;AAElC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,CAAA;AACxD,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { SchemaTypeOptions } from '../../../vite/node_modules/mongoose';
2
+ import { LocalizedString } from '../zod/localizedString';
3
+ export declare function localizedStringField(options?: Omit<SchemaTypeOptions<LocalizedString>, "type" | "get">): SchemaTypeOptions<LocalizedString>;
4
+ //# sourceMappingURL=localizedStringField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localizedStringField.d.ts","sourceRoot":"","sources":["../../src/mongoose/localizedStringField.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAI7D,wBAAgB,oBAAoB,CAClC,OAAO,CAAC,EAAE,IAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,GACjE,iBAAiB,CAAC,eAAe,CAAC,CAQpC"}
@@ -1,3 +1,5 @@
1
1
  import { z, ZodError } from '../../../vite/node_modules/zod';
2
- export { z, ZodError };
2
+ import { extendZod } from './extension';
3
+ export * from './localizedString';
4
+ export { extendZod, z, ZodError };
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/zod/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,KAAK,QAAQ,EAAE,MAAM,KAAK,CAAA;AAOtC,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/zod/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,KAAK,QAAQ,EAAE,MAAM,KAAK,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAGvC,cAAc,mBAAmB,CAAA;AAKjC,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAA"}
@@ -0,0 +1,14 @@
1
+ import { z } from '../../../vite/node_modules/zod';
2
+ export type LanguageCode = string;
3
+ export type LocalizedString = Record<LanguageCode, string>;
4
+ export type I18nStringRecord = LocalizedString;
5
+ export type I18nString = LocalizedString;
6
+ export declare const LANGUAGE_CODE_REGEX: RegExp;
7
+ export declare function buildLocaleFallbackChain(locale: string, fallbacks?: string | string[]): string[];
8
+ export declare function resolveLocalizedString(value: LocalizedString | null | undefined, locale: string, options?: {
9
+ fallbacks?: string | string[];
10
+ }): string | undefined;
11
+ export declare function withLocalizedStringFallback<T extends object>(value: T): T;
12
+ export declare const zLocalizedString: () => z.ZodRecord<z.ZodString, z.ZodString>;
13
+ export declare const zI18nString: () => z.ZodRecord<z.ZodString, z.ZodString>;
14
+ //# sourceMappingURL=localizedString.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localizedString.d.ts","sourceRoot":"","sources":["../../src/zod/localizedString.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,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;AAExC,eAAO,MAAM,mBAAmB,QAAuC,CAAA;AAgBvE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CA6BhG;AAED,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EACzC,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAAE,GAC1C,MAAM,GAAG,SAAS,CAWpB;AAED,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CASzE;AAwBD,eAAO,MAAM,gBAAgB,6CAS5B,CAAA;AAED,eAAO,MAAM,WAAW,6CAAmB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/db",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -1,6 +0,0 @@
1
- import { Schema } from '../../vite/node_modules/mongoose';
2
- type MongooseSchemaExtension = Parameters<Schema["add"]>[0];
3
- export declare function extendMongooseSchema<TSchema extends Schema>(baseSchema: TSchema, ...extensions: MongooseSchemaExtension[]): TSchema;
4
- export declare function omitSchemaPaths<TSchema extends Schema>(schema: TSchema, paths: string[]): TSchema;
5
- export {};
6
- //# sourceMappingURL=extendMongooseSchema.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extendMongooseSchema.d.ts","sourceRoot":"","sources":["../src/extendMongooseSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAGjC,KAAK,uBAAuB,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAE3D,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,MAAM,EACzD,UAAU,EAAE,OAAO,EACnB,GAAG,UAAU,EAAE,uBAAuB,EAAE,GACvC,OAAO,CAMT;AAED,wBAAgB,eAAe,CAAC,OAAO,SAAS,MAAM,EACpD,MAAM,EAAE,OAAO,EACf,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAIT"}