@rpcbase/db 0.35.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.
Files changed (52) hide show
  1. package/README.md +30 -11
  2. package/dist/acl/index.js +11 -0
  3. package/dist/can-urGFf45M.js +131 -0
  4. package/dist/index.browser.d.ts +2 -0
  5. package/dist/index.browser.d.ts.map +1 -0
  6. package/dist/index.browser.js +114 -0
  7. package/dist/index.d.ts +4 -3
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +319 -1130
  10. package/dist/models/{Tenant.d.ts → RBTenant.d.ts} +1 -1
  11. package/dist/models/RBTenant.d.ts.map +1 -0
  12. package/dist/models/{User.d.ts → RBUser.d.ts} +1 -1
  13. package/dist/models/RBUser.d.ts.map +1 -0
  14. package/dist/models/index.d.ts +2 -2
  15. package/dist/models/index.d.ts.map +1 -1
  16. package/dist/mongoose/extendMongooseSchema.d.ts +6 -0
  17. package/dist/mongoose/extendMongooseSchema.d.ts.map +1 -0
  18. package/dist/mongoose/index.d.ts +6 -0
  19. package/dist/mongoose/index.d.ts.map +1 -0
  20. package/dist/mongoose/localizedStringField.d.ts +4 -0
  21. package/dist/mongoose/localizedStringField.d.ts.map +1 -0
  22. package/dist/zod/extension.d.ts +17 -0
  23. package/dist/zod/extension.d.ts.map +1 -0
  24. package/dist/zod/index.d.ts +5 -0
  25. package/dist/zod/index.d.ts.map +1 -0
  26. package/dist/zod/localizedString.d.ts +14 -0
  27. package/dist/zod/localizedString.d.ts.map +1 -0
  28. package/package.json +5 -4
  29. package/dist/acl.d.ts +0 -2
  30. package/dist/acl.d.ts.map +0 -1
  31. package/dist/models/Tenant.d.ts.map +0 -1
  32. package/dist/models/User.d.ts.map +0 -1
  33. package/dist/schema/assertions/assertions.d.ts +0 -20
  34. package/dist/schema/assertions/assertions.d.ts.map +0 -1
  35. package/dist/schema/assertions/constructor.d.ts +0 -10
  36. package/dist/schema/assertions/constructor.d.ts.map +0 -1
  37. package/dist/schema/assertions/custom.d.ts +0 -6
  38. package/dist/schema/assertions/custom.d.ts.map +0 -1
  39. package/dist/schema/assertions/instanceOf.d.ts +0 -10
  40. package/dist/schema/assertions/instanceOf.d.ts.map +0 -1
  41. package/dist/schema/assertions/staticNames.d.ts +0 -10
  42. package/dist/schema/assertions/staticNames.d.ts.map +0 -1
  43. package/dist/schema/assertions/types.d.ts +0 -17
  44. package/dist/schema/assertions/types.d.ts.map +0 -1
  45. package/dist/schema/extension.d.ts +0 -78
  46. package/dist/schema/extension.d.ts.map +0 -1
  47. package/dist/schema/index.d.ts +0 -73
  48. package/dist/schema/index.d.ts.map +0 -1
  49. package/dist/schema/mongoose.types.d.ts +0 -93
  50. package/dist/schema/mongoose.types.d.ts.map +0 -1
  51. package/dist/setupFile.d.ts +0 -2
  52. package/dist/setupFile.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,25 +1,44 @@
1
1
  ## `@rpcbase/db`
2
2
 
3
+ ### Zod extensions
4
+
5
+ This package provides a small Zod extension that adds `.unique()` and `.sparse()` to `z.string()`, `z.number()`, and `z.date()` (for chaining/compatibility).
6
+
7
+ ```ts
8
+ import { z } from "@rpcbase/db"
9
+
10
+ const ZUser = z.object({
11
+ email: z.string().email().unique().sparse(),
12
+ createdAt: z.date().sparse(),
13
+ })
14
+ ```
15
+
3
16
  ### `LocalizedString` / `zI18nString()`
4
17
 
5
- `zI18nString()` (alias of `zLocalizedString()`) describes a localized string as an object, keyed by *language codes* (BCP47):
18
+ `zI18nString()` (alias of `zLocalizedString()`) describes a localized string as an object keyed by *language codes* (BCP47):
6
19
 
7
20
  ```ts
8
- import { z } from "zod"
9
- import { zI18nString, zodSchema } from "@rpcbase/db"
21
+ import { z, zI18nString } from "@rpcbase/db"
10
22
 
11
23
  const ZPost = z.object({
12
24
  title: zI18nString(), // { "en": "...", "fr": "...", "fr-FR": "..." }
13
25
  })
26
+ ```
14
27
 
15
- const schema = zodSchema(ZPost)
28
+ ### Mongoose re-export
29
+
30
+ ```ts
31
+ import { Schema, model, mongoose } from "@rpcbase/db"
16
32
  ```
17
33
 
18
- **Mongoose-side (important)**
34
+ ### Mongoose localized fallback (optional)
19
35
 
20
- - The field is stored as an object and typed as `SchemaTypes.Mixed` (not a `Map`).
21
- - On read, the Mongoose getter returns a “proxy” object with automatic fallback: `fr-FR` falls back to `fr` if `fr-FR` doesn’t exist.
22
- - `doc.title["fr-FR"]` → fallback
23
- - `doc.title.get("fr-FR")` fallback (read helper)
24
- - `doc.title.getExact("fr-FR")` → exact (no fallback)
25
- - On write, because it’s `Mixed`, avoid silent deep mutations: prefer replacing the object (`doc.title = { ...doc.title, fr: "Salut" }`) or call `doc.markModified("title")`.
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
+ ```
@@ -0,0 +1,11 @@
1
+ import { b, d, f, e, g, c, a, r } from "../can-urGFf45M.js";
2
+ export {
3
+ b as buildAbility,
4
+ d as buildAbilityFromSession,
5
+ f as can,
6
+ e as getAccessibleByQuery,
7
+ g as getRegisteredPolicies,
8
+ c as getTenantRolesFromSessionUser,
9
+ a as registerPoliciesFromModules,
10
+ r as registerPolicy
11
+ };
@@ -0,0 +1,131 @@
1
+ import { AbilityBuilder, createMongoAbility, subject } from "@casl/ability";
2
+ import { accessibleBy } from "@casl/mongoose";
3
+ const POLICIES_GLOBAL_KEY = /* @__PURE__ */ Symbol.for("@rpcbase/db/acl/policiesBySubject");
4
+ const getGlobalPoliciesMap = () => {
5
+ const store = globalThis;
6
+ const existing = store[POLICIES_GLOBAL_KEY];
7
+ if (existing instanceof Map) {
8
+ return existing;
9
+ }
10
+ const created = /* @__PURE__ */ new Map();
11
+ store[POLICIES_GLOBAL_KEY] = created;
12
+ return created;
13
+ };
14
+ const policiesBySubject = getGlobalPoliciesMap();
15
+ const isPolicy = (value) => {
16
+ if (!value || typeof value !== "object") return false;
17
+ const maybe = value;
18
+ return typeof maybe.subject === "string" && typeof maybe.define === "function";
19
+ };
20
+ const registerPolicy = (policy) => {
21
+ const set = policiesBySubject.get(policy.subject) ?? /* @__PURE__ */ new Set();
22
+ set.add(policy.define);
23
+ policiesBySubject.set(policy.subject, set);
24
+ };
25
+ const registerPoliciesFromModules = (modules) => {
26
+ for (const [exportName, value] of Object.entries(modules)) {
27
+ if (!isPolicy(value)) continue;
28
+ if (!exportName.endsWith("Policy")) {
29
+ throw new Error(
30
+ `Invalid policy export name "${exportName}". Policies must be exported as "<ModelName>Policy".`
31
+ );
32
+ }
33
+ const expectedSubject = exportName.slice(0, -"Policy".length);
34
+ if (value.subject !== expectedSubject) {
35
+ throw new Error(
36
+ `Invalid policy "${exportName}": expected subject "${expectedSubject}", got "${value.subject}".`
37
+ );
38
+ }
39
+ registerPolicy(value);
40
+ }
41
+ };
42
+ const getRegisteredPolicies = () => {
43
+ const out = [];
44
+ for (const set of policiesBySubject.values()) {
45
+ for (const fn of set) out.push(fn);
46
+ }
47
+ return out;
48
+ };
49
+ const buildAbility = (ctx) => {
50
+ const builder = new AbilityBuilder(createMongoAbility);
51
+ const { can: can2 } = builder;
52
+ if (ctx.roles.includes("owner") || ctx.roles.includes("admin")) {
53
+ can2("manage", "all");
54
+ }
55
+ for (const define of getRegisteredPolicies()) {
56
+ define(builder, ctx);
57
+ }
58
+ return builder.build();
59
+ };
60
+ const normalizeRole = (raw) => {
61
+ if (typeof raw !== "string") return null;
62
+ const normalized = raw.trim();
63
+ return normalized ? normalized : null;
64
+ };
65
+ const normalizeRoles = (raw) => {
66
+ if (Array.isArray(raw)) {
67
+ return raw.map(normalizeRole).filter((r) => Boolean(r));
68
+ }
69
+ const role = normalizeRole(raw);
70
+ return role ? [role] : [];
71
+ };
72
+ const normalizeRolesByTenantId = (raw) => {
73
+ if (!raw || typeof raw !== "object") return null;
74
+ if (raw instanceof Map) {
75
+ return Object.fromEntries(Array.from(raw.entries()).map(([key, value]) => [String(key), normalizeRoles(value)]));
76
+ }
77
+ const obj = raw;
78
+ const out = {};
79
+ for (const [key, value] of Object.entries(obj)) {
80
+ const tenantId = String(key).trim();
81
+ if (!tenantId) continue;
82
+ out[tenantId] = normalizeRoles(value);
83
+ }
84
+ return out;
85
+ };
86
+ const getTenantRolesFromSessionUser = (user, tenantId) => {
87
+ if (!user || typeof user !== "object") return [];
88
+ const rolesByTenantId = normalizeRolesByTenantId(user.tenantRoles);
89
+ if (!rolesByTenantId) return [];
90
+ return rolesByTenantId[tenantId]?.filter(Boolean) ?? [];
91
+ };
92
+ const buildAbilityFromSession = ({
93
+ tenantId,
94
+ session,
95
+ claims
96
+ }) => {
97
+ const user = session?.user;
98
+ const userId = typeof user?.id === "string" ? user.id.trim() : "";
99
+ const rolesByTenantId = normalizeRolesByTenantId(user?.tenantRoles);
100
+ const hasExplicitTenantEntry = Boolean(
101
+ rolesByTenantId && Object.prototype.hasOwnProperty.call(rolesByTenantId, tenantId)
102
+ );
103
+ const signedInTenantsRaw = user?.signedInTenants;
104
+ const signedInTenants = Array.isArray(signedInTenantsRaw) ? signedInTenantsRaw.map(String) : [];
105
+ const roles = hasExplicitTenantEntry ? rolesByTenantId[tenantId] ?? [] : userId && signedInTenants.includes(tenantId) ? ["owner"] : getTenantRolesFromSessionUser(user, tenantId);
106
+ return buildAbility({
107
+ tenantId,
108
+ userId: userId || null,
109
+ roles,
110
+ claims
111
+ });
112
+ };
113
+ const getAccessibleByQuery = (ability, action, subject2) => {
114
+ return accessibleBy(ability, action).ofType(subject2);
115
+ };
116
+ const can = (ability, action, subjectType, object) => {
117
+ if (object && typeof object === "object") {
118
+ return ability.can(action, subject(subjectType, object));
119
+ }
120
+ return ability.can(action, subjectType);
121
+ };
122
+ export {
123
+ registerPoliciesFromModules as a,
124
+ buildAbility as b,
125
+ getTenantRolesFromSessionUser as c,
126
+ buildAbilityFromSession as d,
127
+ getAccessibleByQuery as e,
128
+ can as f,
129
+ getRegisteredPolicies as g,
130
+ registerPolicy as r
131
+ };
@@ -0,0 +1,2 @@
1
+ export * from './zod';
2
+ //# sourceMappingURL=index.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAA"}
@@ -0,0 +1,114 @@
1
+ import { z } from "zod";
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;
88
+ let zodExtended = false;
89
+ function extendZod(zod) {
90
+ if (zodExtended) return;
91
+ zodExtended = true;
92
+ const supported = [zod.ZodString, zod.ZodNumber, zod.ZodDate];
93
+ for (const type of supported) {
94
+ const proto = type?.prototype;
95
+ if (!proto) continue;
96
+ proto.unique = function unique(_flag = true) {
97
+ return this;
98
+ };
99
+ proto.sparse = function sparse(_flag = true) {
100
+ return this;
101
+ };
102
+ }
103
+ }
104
+ extendZod(z);
105
+ export {
106
+ LANGUAGE_CODE_REGEX,
107
+ buildLocaleFallbackChain,
108
+ extendZod,
109
+ resolveLocalizedString,
110
+ withLocalizedStringFallback,
111
+ z2 as z,
112
+ zI18nString,
113
+ zLocalizedString
114
+ };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
+ export * from './acl';
1
2
  export * from './models';
2
- export * from './schema';
3
+ export * from './mongoose';
4
+ export * from './pagination';
5
+ export * from './zod';
3
6
  export * from './createModels';
4
7
  export * from './modelsApi';
5
8
  export * from './tenantFilesystemDb';
6
- export * from './acl';
7
- export * from './pagination';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,UAAU,CAAA;AACxB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,OAAO,CAAA;AACrB,cAAc,cAAc,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"}