@cerbos/orm-prisma 3.3.0 → 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 (44) hide show
  1. package/README.md +83 -6
  2. package/lib/index.d.ts +29 -2
  3. package/lib/index.d.ts.map +1 -1
  4. package/lib/index.js +1459 -114
  5. package/lib/index.js.map +1 -1
  6. package/package.json +19 -13
  7. package/src/adversarial.test.ts +586 -0
  8. package/src/generated/prisma/commonInputTypes.ts +26 -0
  9. package/src/generated/prisma/internal/class.ts +9 -9
  10. package/src/generated/prisma/internal/prismaNamespace.ts +89 -19
  11. package/src/generated/prisma/internal/prismaNamespaceBrowser.ts +2 -1
  12. package/src/generated/prisma/models/Category.ts +1 -1
  13. package/src/generated/prisma/models/Label.ts +1 -1
  14. package/src/generated/prisma/models/NestedResource.ts +1 -1
  15. package/src/generated/prisma/models/NextLevelNestedResource.ts +1 -1
  16. package/src/generated/prisma/models/Resource.ts +62 -2
  17. package/src/generated/prisma/models/SubCategory.ts +1 -1
  18. package/src/generated/prisma/models/Tag.ts +1 -1
  19. package/src/generated/prisma/models/User.ts +1 -1
  20. package/src/generated/prisma-adversarial/browser.ts +44 -0
  21. package/src/generated/prisma-adversarial/client.ts +66 -0
  22. package/src/generated/prisma-adversarial/commonInputTypes.ts +352 -0
  23. package/src/generated/prisma-adversarial/enums.ts +15 -0
  24. package/src/generated/prisma-adversarial/internal/class.ts +244 -0
  25. package/src/generated/prisma-adversarial/internal/prismaNamespace.ts +1156 -0
  26. package/src/generated/prisma-adversarial/internal/prismaNamespaceBrowser.ts +140 -0
  27. package/src/generated/prisma-adversarial/models/AdversarialCategory.ts +1401 -0
  28. package/src/generated/prisma-adversarial/models/AdversarialLabel.ts +1272 -0
  29. package/src/generated/prisma-adversarial/models/AdversarialResource.ts +1665 -0
  30. package/src/generated/prisma-adversarial/models/AdversarialSubCategory.ts +1401 -0
  31. package/src/generated/prisma-adversarial/models/AdversarialTag.ts +1307 -0
  32. package/src/generated/prisma-adversarial/models.ts +16 -0
  33. package/src/generated/prisma-adversarial-v6/client.d.ts +1 -0
  34. package/src/generated/prisma-adversarial-v6/default.d.ts +1 -0
  35. package/src/generated/prisma-adversarial-v6/edge.d.ts +1 -0
  36. package/src/generated/prisma-adversarial-v6/index.d.ts +8640 -0
  37. package/src/generated/prisma-adversarial-v6/runtime/index-browser.d.ts +370 -0
  38. package/src/generated/prisma-adversarial-v6/runtime/library.d.ts +3982 -0
  39. package/src/generated/prisma-adversarial-v6/wasm.d.ts +1 -0
  40. package/src/generated/prisma-v6/index.d.ts +90 -3
  41. package/src/index.test.ts +3199 -636
  42. package/src/index.ts +2055 -148
  43. package/src/test-setup.adversarial.v6.ts +5 -0
  44. package/src/test-setup.adversarial.v7.ts +12 -0
package/README.md CHANGED
@@ -16,13 +16,28 @@ 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
 
22
- #### String Concatenation
24
+ #### Arithmetic
23
25
 
24
- - `add` operator for string/numeric concatenation in conditions
25
- - Algebraic solving: `P.attr.context == "projects:" + R.attr.id` → `{ id: { equals: "123" } }`
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.
26
41
 
27
42
  #### Hierarchy Operators
28
43
 
@@ -37,6 +52,65 @@ An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanRe
37
52
  - Collection mapping and filtering
38
53
  - Complex condition combinations
39
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.
40
114
 
41
115
  ## Requirements
42
116
 
@@ -46,7 +120,7 @@ An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanRe
46
120
 
47
121
  ## System Requirements
48
122
 
49
- - Node.js >= 20.0.
123
+ - Node.js >= 22.0.
50
124
  - Prisma CLI & Client >= 6.0 (v7 supported)
51
125
  - A database supported by Prisma (SQLite/PostgreSQL/MySQL/etc.) so the Prisma client can communicate with stored data
52
126
 
@@ -432,9 +506,12 @@ The mapper configuration is also fully typed:
432
506
  ```ts
433
507
  type MapperConfig = {
434
508
  field?: string;
509
+ valueType?: "dateTime";
510
+ nullable?: boolean;
435
511
  relation?: {
436
512
  name: string;
437
513
  type: "one" | "many";
514
+ model?: string;
438
515
  field?: string;
439
516
  fields?: {
440
517
  [key: string]: MapperConfig; // Recursive for nested fields
@@ -474,4 +551,4 @@ A complete example application using this adapter can be found at [https://githu
474
551
 
475
552
  ## License
476
553
 
477
- 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;AAYpB,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;AAmKN;;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"}