@cerbos/orm-prisma 3.2.1 → 4.0.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 (45) hide show
  1. package/README.md +91 -3
  2. package/lib/index.d.ts +29 -2
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +1792 -150
  5. package/lib/index.js.map +1 -1
  6. package/package.json +24 -11
  7. package/src/adversarial.test.ts +586 -0
  8. package/src/generated/prisma/client.ts +3 -1
  9. package/src/generated/prisma/commonInputTypes.ts +26 -0
  10. package/src/generated/prisma/internal/class.ts +22 -10
  11. package/src/generated/prisma/internal/prismaNamespace.ts +89 -19
  12. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +2 -1
  13. package/src/generated/prisma/models/Category.ts +6 -1
  14. package/src/generated/prisma/models/Label.ts +6 -1
  15. package/src/generated/prisma/models/NestedResource.ts +6 -1
  16. package/src/generated/prisma/models/NextLevelNestedResource.ts +6 -1
  17. package/src/generated/prisma/models/Resource.ts +67 -2
  18. package/src/generated/prisma/models/SubCategory.ts +6 -1
  19. package/src/generated/prisma/models/Tag.ts +6 -1
  20. package/src/generated/prisma/models/User.ts +6 -1
  21. package/src/generated/prisma-adversarial/browser.ts +44 -0
  22. package/src/generated/prisma-adversarial/client.ts +66 -0
  23. package/src/generated/prisma-adversarial/commonInputTypes.ts +352 -0
  24. package/src/generated/prisma-adversarial/enums.ts +15 -0
  25. package/src/generated/prisma-adversarial/internal/class.ts +244 -0
  26. package/src/generated/prisma-adversarial/internal/prismaNamespace.ts +1156 -0
  27. package/src/generated/prisma-adversarial/internal/prismaNamespaceBrowser.ts +140 -0
  28. package/src/generated/prisma-adversarial/models/AdversarialCategory.ts +1401 -0
  29. package/src/generated/prisma-adversarial/models/AdversarialLabel.ts +1272 -0
  30. package/src/generated/prisma-adversarial/models/AdversarialResource.ts +1665 -0
  31. package/src/generated/prisma-adversarial/models/AdversarialSubCategory.ts +1401 -0
  32. package/src/generated/prisma-adversarial/models/AdversarialTag.ts +1307 -0
  33. package/src/generated/prisma-adversarial/models.ts +16 -0
  34. package/src/generated/prisma-adversarial-v6/client.d.ts +1 -0
  35. package/src/generated/prisma-adversarial-v6/default.d.ts +1 -0
  36. package/src/generated/prisma-adversarial-v6/edge.d.ts +1 -0
  37. package/src/generated/prisma-adversarial-v6/index.d.ts +8640 -0
  38. package/src/generated/prisma-adversarial-v6/runtime/index-browser.d.ts +370 -0
  39. package/src/generated/prisma-adversarial-v6/runtime/library.d.ts +3982 -0
  40. package/src/generated/prisma-adversarial-v6/wasm.d.ts +1 -0
  41. package/src/generated/prisma-v6/index.d.ts +90 -3
  42. package/src/index.test.ts +3611 -241
  43. package/src/index.ts +2544 -174
  44. package/src/test-setup.adversarial.v6.ts +5 -0
  45. package/src/test-setup.adversarial.v7.ts +12 -0
package/README.md CHANGED
@@ -16,9 +16,35 @@ An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanRe
16
16
 
17
17
  - One-to-one: `is`, `isNot`
18
18
  - One-to-many/Many-to-many: `some`, `none`, `every`
19
- - Collection operators: `exists`, `exists_one`, `all`, `filter`, `except`
19
+ - Collection operators: `exists`, `all`, `except` (`exists_one` requires counting matches,
20
+ which Prisma where-filters cannot express — it throws rather than silently degrading to
21
+ `exists`; `filter` only appears inside other expressions)
20
22
  - Set operations: `hasIntersection`
21
23
 
24
+ #### Arithmetic
25
+
26
+ - `add`, `sub`, `mult`, `div` with a constant side are solved algebraically to a plain column
27
+ comparison: `R.attr.aNumber + 1 > 2` → `{ aNumber: { gt: 1 } }` (Prisma where-filters cannot
28
+ express column arithmetic). Multiplying/dividing by a negative constant mirrors directional
29
+ operators. Arithmetic on both sides of a comparison, division BY a column, and equality or
30
+ inequality over fractional addition throw. The latter cannot be solved reversibly in IEEE-754
31
+ arithmetic.
32
+ - String concatenation solving: `P.attr.context == "projects:" + R.attr.id` → `{ id: { equals: "123" } }`
33
+
34
+ #### Field-to-field comparisons
35
+
36
+ Comparisons between two columns of the same model compile to
37
+ [Prisma field references](https://www.prisma.io/docs/orm/reference/prisma-client-reference#compare-columns-in-the-same-table).
38
+ Pass the Prisma model name via the `model` option (root columns) or `relation.model` in the
39
+ mapper (columns of a related model inside a collection expression). Comparisons across models
40
+ throw — Prisma only supports references between fields of the same model.
41
+
42
+ #### Hierarchy Operators
43
+
44
+ - `hierarchy(string)`, `hierarchy(string, delimiter)`, `hierarchy([segments])`
45
+ - `overlaps`: segment-wise prefix comparison between two hierarchies
46
+ - `ancestorOf` / `descendentOf`: strict prefix relationship between hierarchies
47
+
22
48
  #### Advanced Features
23
49
 
24
50
  - Deep nested relations support
@@ -26,6 +52,65 @@ An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanRe
26
52
  - Collection mapping and filtering
27
53
  - Complex condition combinations
28
54
  - Type-safe field mappings
55
+ - Timestamp comparisons against Prisma `DateTime` columns. Mark the field mapping with
56
+ `valueType: "dateTime"`; applying `timestamp()` to an untyped/string mapping throws.
57
+ Literals must be strict RFC 3339 instants in CEL's supported year 0001–9999 range and
58
+ exactly representable at millisecond precision (fractional digits after the third may
59
+ only be zero). The mapped column/database must preserve that precision.
60
+ - Outer-column references inside collection expressions (e.g.
61
+ `R.attr.tags.exists(t, t.name == "x" && R.attr.aBool)`) are hoisted or case-split so every
62
+ filter lands on the model it belongs to
63
+ - Three-valued-logic guards for nullable element columns: mark a relation field as
64
+ `nullable: true` in the mapper and collection macros (`all`, negated `exists`,
65
+ `hasIntersection` over `map`) exclude rows whose elements hold `NULL` in that column,
66
+ matching Cerbos's treatment of a missing attribute as a deny
67
+
68
+ #### Known limitations (loud failures, never silently-wrong filters)
69
+
70
+ - LIKE wildcards: Prisma emits `LIKE` without an `ESCAPE` clause, so `contains`/`startsWith`/
71
+ `endsWith` with a needle containing `%` or `_`, or with a column-valued needle, throws.
72
+ (A constant *receiver* with a column needle — `"a-b".startsWith(R.attr.x)` — is translated
73
+ exactly by enumerating candidate needles into an `in` filter.)
74
+ - Hierarchy prefixes: `ancestorOf`, `descendentOf` and `overlaps` narrow a column with a
75
+ `startsWith`, so they throw when the constant hierarchy contains `%`, `_` or `[`. `[` is
76
+ rejected as well as the two LIKE wildcards because SQL Server opens a character class on
77
+ `[` even when an `ESCAPE` clause is declared, so it cannot be matched literally at all.
78
+ - Counting: `exists_one`, `size()` thresholds other than empty/non-empty, and string-length
79
+ comparisons throw (`_count` is not supported inside Prisma `where`).
80
+ - Cross-model column comparisons throw (Prisma field references are same-model only).
81
+ This includes membership between an outer scalar column and a related collection column.
82
+
83
+ #### Database collation is an authorization invariant
84
+
85
+ Cerbos string comparisons are case-sensitive. Prisma delegates comparison semantics to the
86
+ database collation, so a case-insensitive or accent-insensitive collation can make a generated
87
+ authorization filter return rows that Cerbos would deny. Treat the database collation used by
88
+ mapped authorization columns as part of the policy contract:
89
+
90
+ - PostgreSQL: use a deterministic, case-sensitive collation and avoid `citext` or an
91
+ insensitive Prisma query mode for mapped fields.
92
+ - MySQL/MariaDB: choose a case-sensitive (`_cs`) or binary collation rather than the common
93
+ case-insensitive (`_ci`) defaults.
94
+ - SQL Server: use a case-sensitive (`_CS_`) collation rather than a case-insensitive (`_CI_`)
95
+ collation.
96
+ - SQLite: do not apply `COLLATE NOCASE` to mapped fields, and verify the exact comparison
97
+ behavior of any database configuration or extension used in production.
98
+
99
+ The adapter cannot override a column's collation in a Prisma `where` filter. See Prisma's
100
+ [case-sensitivity documentation](https://docs.prisma.io/docs/orm/v6/prisma-client/queries/case-sensitivity)
101
+ for provider-specific details.
102
+
103
+ ### Conformance contract
104
+
105
+ The adapter is differentially tested against Cerbos PDP 0.54.0 `checkResource` decisions using 20 hostile seed rows and both Prisma 6 and 7. The Spring Data adapter defines the reference semantics for this compatibility snapshot.
106
+
107
+ | Classification | Coverage |
108
+ | --- | --- |
109
+ | Oracle-tested | 83 reference actions |
110
+ | Fail-closed | 31 reference actions plus the 3 reference-unsupported shapes (34 actions total) |
111
+ | Known planner divergence | `has()` on a missing attribute is folded by the Cerbos planner to `ALWAYS_ALLOWED`, while `checkResource` denies the missing-attribute rows. Until the planner is fixed, use `R.attr.x != null` for database-backed attributes instead of `has(R.attr.x)` |
112
+
113
+ The fail-closed set consists of literal `LIKE` cases Prisma cannot escape safely, cross-model field references, arbitrary relation counts and string lengths, `exists_one`, unsolved column arithmetic, sub-millisecond `now()` thresholds, and the reference probes for regex, ordered indexing, and `timestamp()` over a string field. Supported timestamp plans require a mapper entry with `valueType: "dateTime"` and a strict, millisecond-exact RFC 3339 literal in CEL's supported instant range. These shapes throw instead of producing a broader authorization filter.
29
114
 
30
115
  ## Requirements
31
116
 
@@ -35,7 +120,7 @@ An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanRe
35
120
 
36
121
  ## System Requirements
37
122
 
38
- - Node.js >= 20.0.
123
+ - Node.js >= 22.0.
39
124
  - Prisma CLI & Client >= 6.0 (v7 supported)
40
125
  - A database supported by Prisma (SQLite/PostgreSQL/MySQL/etc.) so the Prisma client can communicate with stored data
41
126
 
@@ -421,9 +506,12 @@ The mapper configuration is also fully typed:
421
506
  ```ts
422
507
  type MapperConfig = {
423
508
  field?: string;
509
+ valueType?: "dateTime";
510
+ nullable?: boolean;
424
511
  relation?: {
425
512
  name: string;
426
513
  type: "one" | "many";
514
+ model?: string;
427
515
  field?: string;
428
516
  fields?: {
429
517
  [key: string]: MapperConfig; // Recursive for nested fields
@@ -463,4 +551,4 @@ A complete example application using this adapter can be found at [https://githu
463
551
 
464
552
  ## License
465
553
 
466
- Apache 2.0 - See [LICENSE](../LICENSE) for more information.
554
+ Apache 2.0.
package/lib/index.d.ts CHANGED
@@ -1,11 +1,32 @@
1
- import { PlanResourcesResponse, PlanKind } from "@cerbos/core";
1
+ import { PlanKind } from "@cerbos/core";
2
+ import type { PlanResourcesResponse } from "@cerbos/core";
2
3
  export { PlanKind };
3
4
  export type PrismaFilter = Record<string, any>;
4
5
  export type MapperConfig = {
5
6
  field?: string;
7
+ /**
8
+ * Declares the database value kind when the Cerbos expression wraps a field in a typed
9
+ * constructor. Date-time metadata is required for timestamp(field) so a string column is
10
+ * never silently treated as a temporal column merely because it happens to contain ISO text.
11
+ */
12
+ valueType?: "dateTime";
13
+ /**
14
+ * Marks the mapped column as nullable in the database. Cerbos treats a missing attribute as
15
+ * an evaluation error (deny), which matches SQL three-valued logic for simple predicates —
16
+ * but relation subqueries (some/every/none) collapse UNKNOWN to false at the EXISTS boundary,
17
+ * so collection macros over elements with NULL fields need an explicit guard to stay
18
+ * deny-aligned. Declaring nullability here enables those guards.
19
+ */
20
+ nullable?: boolean;
6
21
  relation?: {
7
22
  name: string;
8
23
  type: "one" | "many";
24
+ /**
25
+ * The Prisma model name of the related record (e.g. "Tag"). Required only for
26
+ * field-to-field comparisons between two columns of the related model, which compile to
27
+ * Prisma field references and need the model name as their container.
28
+ */
29
+ model?: string;
9
30
  field?: string;
10
31
  fields?: Record<string, MapperConfig>;
11
32
  };
@@ -14,6 +35,12 @@ export type Mapper = Record<string, MapperConfig> | ((key: string) => MapperConf
14
35
  export interface QueryPlanToPrismaArgs {
15
36
  queryPlan: PlanResourcesResponse;
16
37
  mapper?: Mapper;
38
+ /**
39
+ * The Prisma model name the generated filter targets (e.g. "Resource"). Required only for
40
+ * field-to-field comparisons between two root columns, which compile to Prisma field
41
+ * references and need the model name as their container.
42
+ */
43
+ model?: string;
17
44
  }
18
45
  export type QueryPlanToPrismaResult = {
19
46
  kind: PlanKind.ALWAYS_ALLOWED | PlanKind.ALWAYS_DENIED;
@@ -24,5 +51,5 @@ export type QueryPlanToPrismaResult = {
24
51
  /**
25
52
  * Converts a Cerbos query plan to a Prisma filter.
26
53
  */
27
- export declare function queryPlanToPrisma({ queryPlan, mapper, }: QueryPlanToPrismaArgs): QueryPlanToPrismaResult;
54
+ export declare function queryPlanToPrisma({ queryPlan, mapper, model, }: QueryPlanToPrismaArgs): QueryPlanToPrismaResult;
28
55
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EAErB,QAAQ,EAET,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,CAAC;AAGpB,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;KACvC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAC5B,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,IAAI,EAAE,QAAQ,CAAC,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC;CACxD,GACD;IACE,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC;IAC3B,OAAO,EAAE,YAAY,CAAC;CACvB,CAAC;AA2FN;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,MAAW,GACZ,EAAE,qBAAqB,GAAG,uBAAuB,CAiBjD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,CAAC;AAqGpB,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;QACrB;;;;WAIG;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;KACvC,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAC5B,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC;AAEpC,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,qBAAqB,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,IAAI,EAAE,QAAQ,CAAC,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC;CACxD,GACD;IACE,IAAI,EAAE,QAAQ,CAAC,WAAW,CAAC;IAC3B,OAAO,EAAE,YAAY,CAAC;CACvB,CAAC;AAoZN;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,MAAW,EACX,KAAK,GACN,EAAE,qBAAqB,GAAG,uBAAuB,CAgCjD"}