@opensaas/stack-core 0.20.1 → 0.21.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 (105) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +72 -0
  3. package/CLAUDE.md +18 -2
  4. package/dist/access/access-filter.d.ts +29 -0
  5. package/dist/access/access-filter.d.ts.map +1 -0
  6. package/dist/access/access-filter.js +68 -0
  7. package/dist/access/access-filter.js.map +1 -0
  8. package/dist/access/engine.d.ts +15 -48
  9. package/dist/access/engine.d.ts.map +1 -1
  10. package/dist/access/engine.js +14 -280
  11. package/dist/access/engine.js.map +1 -1
  12. package/dist/access/field-access.d.ts +44 -0
  13. package/dist/access/field-access.d.ts.map +1 -0
  14. package/dist/access/field-access.js +123 -0
  15. package/dist/access/field-access.js.map +1 -0
  16. package/dist/access/field-access.test.d.ts +2 -0
  17. package/dist/access/field-access.test.d.ts.map +1 -0
  18. package/dist/access/{engine.test.js → field-access.test.js} +2 -2
  19. package/dist/access/field-access.test.js.map +1 -0
  20. package/dist/access/field-visibility.d.ts +13 -0
  21. package/dist/access/field-visibility.d.ts.map +1 -0
  22. package/dist/access/field-visibility.js +155 -0
  23. package/dist/access/field-visibility.js.map +1 -0
  24. package/dist/access/index.d.ts +4 -1
  25. package/dist/access/index.d.ts.map +1 -1
  26. package/dist/access/index.js +8 -1
  27. package/dist/access/index.js.map +1 -1
  28. package/dist/config/index.d.ts +1 -1
  29. package/dist/config/index.d.ts.map +1 -1
  30. package/dist/config/types.d.ts +45 -4
  31. package/dist/config/types.d.ts.map +1 -1
  32. package/dist/context/hook-pipeline.d.ts +49 -0
  33. package/dist/context/hook-pipeline.d.ts.map +1 -0
  34. package/dist/context/hook-pipeline.js +75 -0
  35. package/dist/context/hook-pipeline.js.map +1 -0
  36. package/dist/context/index.d.ts.map +1 -1
  37. package/dist/context/index.js +30 -462
  38. package/dist/context/index.js.map +1 -1
  39. package/dist/context/nested-operations.d.ts.map +1 -1
  40. package/dist/context/nested-operations.js +72 -68
  41. package/dist/context/nested-operations.js.map +1 -1
  42. package/dist/context/write-pipeline.d.ts +158 -0
  43. package/dist/context/write-pipeline.d.ts.map +1 -0
  44. package/dist/context/write-pipeline.js +306 -0
  45. package/dist/context/write-pipeline.js.map +1 -0
  46. package/dist/extend.d.ts +3 -0
  47. package/dist/extend.d.ts.map +1 -0
  48. package/dist/extend.js +10 -0
  49. package/dist/extend.js.map +1 -0
  50. package/dist/fields/index.d.ts +1 -0
  51. package/dist/fields/index.d.ts.map +1 -1
  52. package/dist/fields/index.js +213 -2
  53. package/dist/fields/index.js.map +1 -1
  54. package/dist/hooks/index.d.ts +20 -0
  55. package/dist/hooks/index.d.ts.map +1 -1
  56. package/dist/hooks/index.js +202 -0
  57. package/dist/hooks/index.js.map +1 -1
  58. package/dist/index.d.ts +5 -9
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +19 -10
  61. package/dist/index.js.map +1 -1
  62. package/dist/internal.d.ts +8 -0
  63. package/dist/internal.d.ts.map +1 -0
  64. package/dist/internal.js +16 -0
  65. package/dist/internal.js.map +1 -0
  66. package/dist/validation/field-config.d.ts +55 -0
  67. package/dist/validation/field-config.d.ts.map +1 -0
  68. package/dist/validation/field-config.js +100 -0
  69. package/dist/validation/field-config.js.map +1 -0
  70. package/dist/validation/field-config.test.d.ts +2 -0
  71. package/dist/validation/field-config.test.d.ts.map +1 -0
  72. package/dist/validation/field-config.test.js +159 -0
  73. package/dist/validation/field-config.test.js.map +1 -0
  74. package/package.json +11 -3
  75. package/src/access/access-filter.ts +97 -0
  76. package/src/access/engine.ts +13 -396
  77. package/src/access/{engine.test.ts → field-access.test.ts} +1 -1
  78. package/src/access/field-access.ts +159 -0
  79. package/src/access/field-visibility.ts +247 -0
  80. package/src/access/index.ts +7 -4
  81. package/src/config/index.ts +1 -0
  82. package/src/config/types.ts +51 -4
  83. package/src/context/hook-pipeline.ts +160 -0
  84. package/src/context/index.ts +29 -667
  85. package/src/context/nested-operations.ts +142 -111
  86. package/src/context/write-pipeline.ts +543 -0
  87. package/src/extend.ts +14 -0
  88. package/src/fields/index.ts +310 -2
  89. package/src/hooks/index.ts +227 -0
  90. package/src/index.ts +27 -90
  91. package/src/internal.ts +49 -0
  92. package/src/validation/field-config.test.ts +199 -0
  93. package/src/validation/field-config.ts +145 -0
  94. package/tests/access-relationships.test.ts +4 -4
  95. package/tests/access.test.ts +1 -1
  96. package/tests/field-hooks.test.ts +410 -0
  97. package/tests/field-types.test.ts +1 -1
  98. package/tests/hook-pipeline.test.ts +233 -0
  99. package/tests/nested-operation-registry.test.ts +206 -0
  100. package/tests/write-pipeline.test.ts +588 -0
  101. package/tsconfig.tsbuildinfo +1 -1
  102. package/vitest.config.ts +43 -1
  103. package/dist/access/engine.test.d.ts +0 -2
  104. package/dist/access/engine.test.d.ts.map +0 -1
  105. package/dist/access/engine.test.js.map +0 -1
@@ -1,4 +1,4 @@
1
1
 
2
- > @opensaas/stack-core@0.20.1 build /home/runner/work/stack/stack/packages/core
2
+ > @opensaas/stack-core@0.21.0 build /home/runner/work/stack/stack/packages/core
3
3
  > tsc
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # @opensaas/stack-core
2
2
 
3
+ ## 0.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#415](https://github.com/OpenSaasAU/stack/pull/415) [`8980ff3`](https://github.com/OpenSaasAU/stack/commit/8980ff36ffb0879d8f4409740493dd940572cc9d) Thanks [@borisno2](https://github.com/borisno2)! - Curate the `@opensaas/stack-core` public surface into clearly-scoped entry points
8
+
9
+ The root entry point now exposes only the everyday consumer surface — `config`,
10
+ `list`, `getContext`, the naming helpers (`getDbKey`, `getUrlKey`,
11
+ `getListKeyFromUrl`), `ValidationError`, and the config/access types you annotate
12
+ with. Plugin and field authoring contracts move to a new `/extend` path, and the
13
+ plumbing shared with sibling packages and generated code moves to `/internal`.
14
+
15
+ ```typescript
16
+ // Everyday usage (unchanged)
17
+ import { config, list, getContext } from '@opensaas/stack-core'
18
+
19
+ // Authoring a plugin or a third-party field package
20
+ import type { Plugin, BaseFieldConfig, TypeInfo } from '@opensaas/stack-core/extend'
21
+ ```
22
+
23
+ `@opensaas/stack-core/internal` carries no semver guarantees; application code
24
+ should never import from it. `Session` stays on the root entry point because it is
25
+ the module-augmentation target.
26
+
27
+ Removed from the public surface (zero callers): the nine `*HookArgs` types and the
28
+ callerless typed-query runtime types. The other `@opensaas/*` packages and the CLI
29
+ generator are updated to import from the new paths.
30
+
31
+ - [#416](https://github.com/OpenSaasAU/stack/pull/416) [`841a836`](https://github.com/OpenSaasAU/stack/commit/841a836494e2647f390ae19a8c4121d38ebd2fa4) Thanks [@borisno2](https://github.com/borisno2)! - Move field-config types to `@opensaas/stack-core/fields`, beside their builders
32
+
33
+ The concrete field-config types (`TextField`, `IntegerField`, `CheckboxField`,
34
+ `TimestampField`, `PasswordField`, `SelectField`, `RelationshipField`,
35
+ `JsonField`, `VirtualField`, plus `DecimalField`, `CalendarDayField`, and
36
+ `PrismaRelationResult`) now live on the `/fields` entry point alongside the
37
+ builders that produce them, instead of the root barrel. One concept, one import
38
+ path:
39
+
40
+ ```typescript
41
+ import { text, decimal } from '@opensaas/stack-core/fields'
42
+ import type { TextField, DecimalField } from '@opensaas/stack-core/fields'
43
+ ```
44
+
45
+ `DecimalField` and `CalendarDayField` were previously defined but exported from
46
+ nowhere — they are now public, and the CLI's lists generator maps `decimal`/
47
+ `calendarDay` fields to their precise types instead of the generic
48
+ `BaseFieldConfig` fallback. The umbrella `FieldConfig` stays on the root entry
49
+ point and `BaseFieldConfig` stays on `/extend`.
50
+
51
+ ### Patch Changes
52
+
53
+ - [#441](https://github.com/OpenSaasAU/stack/pull/441) [`bc20bf4`](https://github.com/OpenSaasAU/stack/commit/bc20bf447cf724bd0ee153ea9a69d54cc26a6bb2) Thanks [@borisno2](https://github.com/borisno2)! - Validate field self-containment at config load instead of failing deep in generation
54
+
55
+ Core now exports `validateFieldConfig(field, fieldKey, listKey?)` and `validateConfigFields(config)` (plus the `FieldConfigValidationError` type). They check each field implements its generation contract — `getPrismaType`, `getTypeScriptType`, and `getZodSchema` (or `getPrismaRelation` for relationships; virtual fields skip `getPrismaType`) — and return structured per-field errors. `opensaas generate` runs this first and fails fast with a clear message naming the list, field, and missing method, rather than throwing an opaque stack trace mid-generation.
56
+
57
+ - [#428](https://github.com/OpenSaasAU/stack/pull/428) [`50371ea`](https://github.com/OpenSaasAU/stack/commit/50371ea3dd134f6b3718f347fed2c0d3b7dc63ce) Thanks [@borisno2](https://github.com/borisno2)! - Fix outdated SQLite adapter guidance to match the installed `@prisma/adapter-better-sqlite3` API (`PrismaBetterSqlite3` constructed with `{ url }`), so copied examples actually run. Updates the CLI "missing adapter" error message and the migration config it generates, plus the `prismaClientConstructor` JSDoc example.
58
+
59
+ - [#440](https://github.com/OpenSaasAU/stack/pull/440) [`70b4f53`](https://github.com/OpenSaasAU/stack/commit/70b4f538d380bbf546af50a985d29b48a71d3b4d) Thanks [@borisno2](https://github.com/borisno2)! - Refactor nested-operation dispatch into a handler registry (internal, no behaviour change)
60
+
61
+ - [#397](https://github.com/OpenSaasAU/stack/pull/397) [`8e394ab`](https://github.com/OpenSaasAU/stack/commit/8e394abe9df2da53ba23b93836853516bb4e25d5) Thanks [@borisno2](https://github.com/borisno2)! - Move relationship Prisma schema generation into the relationship field builder
62
+
63
+ The relationship field now exposes a `getPrismaRelation()` method that returns its complete Prisma schema contribution (FK line, relation line, synthetic back-relation). The Prisma generator delegates to this method instead of special-casing relationships, keeping it a neutral coordinator. Generated schemas are unchanged.
64
+
65
+ - [#455](https://github.com/OpenSaasAU/stack/pull/455) [`d3fdf2a`](https://github.com/OpenSaasAU/stack/commit/d3fdf2a2e5374302bc7fe1fe814cb0f567a349df) Thanks [@borisno2](https://github.com/borisno2)! - Exclude `**/dist/**` from Vitest test discovery and gate coverage on `src/access`, `src/context`, and `src/validation` via per-file thresholds.
66
+
67
+ - [#403](https://github.com/OpenSaasAU/stack/pull/403) [`0f9c644`](https://github.com/OpenSaasAU/stack/commit/0f9c644a115ad747e338e6138b4762b4a48a9144) Thanks [@borisno2](https://github.com/borisno2)! - Split the access engine into named two-phase-read modules: Access Filter (pre-query), Field Visibility (post-query), and a shared field-access evaluator. No behaviour or public API change.
68
+
69
+ - [#411](https://github.com/OpenSaasAU/stack/pull/411) [`96258b0`](https://github.com/OpenSaasAU/stack/commit/96258b00bb762d9e38cfb83eacae65ce670b161f) Thanks [@borisno2](https://github.com/borisno2)! - Deduplicate field-level hook execution helpers by promoting them to `hooks/index.ts`, and remove a stray `console.log` that ran on every create/update.
70
+
71
+ - [#439](https://github.com/OpenSaasAU/stack/pull/439) [`898e477`](https://github.com/OpenSaasAU/stack/commit/898e47747abc02e457a54e2a78939450d16da5fb) Thanks [@borisno2](https://github.com/borisno2)! - Internal refactor: extract the write transform+validate span into a single Hook Pipeline that the Write Pipeline delegates to. No behaviour change.
72
+
73
+ - [#438](https://github.com/OpenSaasAU/stack/pull/438) [`29966b2`](https://github.com/OpenSaasAU/stack/commit/29966b23597199bcf4233298b1d0de6401b91acd) Thanks [@borisno2](https://github.com/borisno2)! - Refactor the write path into a single Write Pipeline. The canonical secured write sequence (hooks, validation, access, writable-field filtering, nested operations, persistence, after-hooks, Field Visibility) now lives in one module; create/update/delete are thin adapters over it parameterised by a per-operation strategy. Internal refactor only — no public API or behaviour change.
74
+
3
75
  ## 0.20.1
4
76
 
5
77
  ## 0.20.0
package/CLAUDE.md CHANGED
@@ -6,6 +6,18 @@ Core stack providing config system, access control engine, hooks, field types, a
6
6
 
7
7
  The foundation of OpenSaas Stack. Defines the config DSL, executes access control, runs hooks, and generates Prisma schema and TypeScript types from config.
8
8
 
9
+ ## Entry Points
10
+
11
+ The package exposes a curated surface across several import paths. Use the narrowest one that fits:
12
+
13
+ - **`@opensaas/stack-core`** (root) — the everyday consumer surface: `config`, `list`, `getContext`, the naming helpers (`getDbKey`, `getUrlKey`, `getListKeyFromUrl`), `ValidationError`, and the config/access types you annotate with (`OpenSaasConfig`, `ListConfig`, `FieldConfig`, `AccessControl`, `FieldAccess`, `Session`, `AccessContext`, `PrismaFilter`, `OperationAccess`).
14
+ - **`@opensaas/stack-core/fields`** — field builder functions (`text()`, `integer()`, …) and their config types (`TextField`, `IntegerField`, `DecimalField`, `CalendarDayField`, …, plus `PrismaRelationResult`). The builders and the types they produce live together here.
15
+ - **`@opensaas/stack-core/extend`** — authoring contracts: implement these to build a plugin (`Plugin`, `PluginContext`, `GeneratedFiles`) or a third-party field package (`BaseFieldConfig`, `TypeInfo`, `TypeDescriptor`).
16
+ - **`@opensaas/stack-core/mcp`** — MCP runtime handlers.
17
+ - **`@opensaas/stack-core/internal`** — `@internal` plumbing shared between the `@opensaas/*` packages and generated `.opensaas/` code. **No semver guarantees**; application code should never import from here.
18
+
19
+ `Session` deliberately stays on the root entry point because it is the module-augmentation target (`declare module '@opensaas/stack-core'`).
20
+
9
21
  ## Key Files & Exports
10
22
 
11
23
  ### Config (`src/config/`)
@@ -115,12 +127,16 @@ Hook types:
115
127
 
116
128
  Run via CLI: `pnpm generate`
117
129
 
118
- ### Utilities (`src/utils.ts`)
130
+ ### Utilities (`src/lib/case-utils.ts`)
131
+
132
+ Public naming helpers (exported from the root entry point):
119
133
 
120
134
  - `getDbKey(listKey)` - PascalCase → camelCase (e.g., `BlogPost` → `blogPost`)
121
135
  - `getUrlKey(listKey)` - PascalCase → kebab-case (e.g., `BlogPost` → `blog-post`)
122
136
  - `getListKeyFromUrl(urlKey)` - kebab-case → PascalCase (e.g., `blog-post` → `BlogPost`)
123
137
 
138
+ The lower-level converters (`pascalToCamel`, `pascalToKebab`, `kebabToPascal`, `kebabToCamel`) are internal plumbing on `@opensaas/stack-core/internal`.
139
+
124
140
  ## Architecture Patterns
125
141
 
126
142
  ### Field Self-Containment
@@ -206,7 +222,7 @@ const context = createContext<typeof prisma>(config, prisma, session)
206
222
 
207
223
  ### With Third-Party Field Packages
208
224
 
209
- - Packages export field builders implementing `BaseFieldConfig`
225
+ - Packages export field builders implementing `BaseFieldConfig` (imported from `@opensaas/stack-core/extend`)
210
226
  - No changes needed to core - fields are self-contained
211
227
  - Example: `@opensaas/stack-tiptap` provides `richText()` field
212
228
 
@@ -0,0 +1,29 @@
1
+ import type { Session, AccessContext, PrismaFilter } from './types.js';
2
+ import type { OpenSaasConfig, FieldConfig } from '../config/types.js';
3
+ /**
4
+ * Access Filter — phase 1 of the two-phase read (pre-query).
5
+ *
6
+ * This module scopes which rows and relationships the database is allowed to
7
+ * return, before the query runs. It evaluates *operation-level* `query` access
8
+ * on related lists and turns the results into a Prisma `include`/`where` clause,
9
+ * so denied rows and relations never leave the database.
10
+ *
11
+ * Phase 2 (post-query field stripping + `resolveOutput` + virtual computation)
12
+ * lives in `field-visibility.ts`. The two phases cannot be merged: virtual
13
+ * fields are computed in JavaScript and post-query field access can depend on
14
+ * the fetched row, neither of which is expressible in SQL. See
15
+ * `docs/adr/0001-access-control-is-a-two-phase-read.md` and the access-control
16
+ * glossary in `CONTEXT.md`.
17
+ */
18
+ /**
19
+ * Build Prisma include object with access control filters
20
+ * This allows us to filter relationships at the database level instead of in memory
21
+ */
22
+ export declare function buildIncludeWithAccessControl(fieldConfigs: Record<string, FieldConfig>, args: {
23
+ session: Session | null;
24
+ context: AccessContext;
25
+ }, config: OpenSaasConfig, depth?: number): Promise<Record<string, boolean | {
26
+ where?: PrismaFilter;
27
+ include?: Record<string, boolean | /*elided*/ any>;
28
+ }> | undefined>;
29
+ //# sourceMappingURL=access-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-filter.d.ts","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAGrE;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACzC,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,OAAO,EAAE,aAAa,CAAA;CACvB,EACD,MAAM,EAAE,cAAc,EACtB,KAAK,GAAE,MAAU;YAeuB,YAAY;cAAY,MAAM,CAAC,MAAM,2BAAe;gBAkD7F"}
@@ -0,0 +1,68 @@
1
+ import { checkAccess, getRelatedListConfig } from './engine.js';
2
+ /**
3
+ * Access Filter — phase 1 of the two-phase read (pre-query).
4
+ *
5
+ * This module scopes which rows and relationships the database is allowed to
6
+ * return, before the query runs. It evaluates *operation-level* `query` access
7
+ * on related lists and turns the results into a Prisma `include`/`where` clause,
8
+ * so denied rows and relations never leave the database.
9
+ *
10
+ * Phase 2 (post-query field stripping + `resolveOutput` + virtual computation)
11
+ * lives in `field-visibility.ts`. The two phases cannot be merged: virtual
12
+ * fields are computed in JavaScript and post-query field access can depend on
13
+ * the fetched row, neither of which is expressible in SQL. See
14
+ * `docs/adr/0001-access-control-is-a-two-phase-read.md` and the access-control
15
+ * glossary in `CONTEXT.md`.
16
+ */
17
+ /**
18
+ * Build Prisma include object with access control filters
19
+ * This allows us to filter relationships at the database level instead of in memory
20
+ */
21
+ export async function buildIncludeWithAccessControl(fieldConfigs, args, config, depth = 0) {
22
+ const MAX_DEPTH = 5;
23
+ if (depth >= MAX_DEPTH) {
24
+ return undefined;
25
+ }
26
+ // Skip auto-including relationships when inside a resolveOutput hook
27
+ // This prevents infinite loops when hooks make DB queries that include
28
+ // relationships back to the same entity (e.g., User virtual field queries Posts
29
+ // which includes author back to User, triggering the virtual field again)
30
+ if (args.context._resolveOutputCounter.depth > 0) {
31
+ return undefined;
32
+ }
33
+ const include = {};
34
+ let hasRelationships = false;
35
+ for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
36
+ if (fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && fieldConfig.ref) {
37
+ hasRelationships = true;
38
+ const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
39
+ if (relatedConfig) {
40
+ // Check query access for the related list
41
+ const queryAccess = relatedConfig.listConfig.access?.operation?.query;
42
+ const accessResult = await checkAccess(queryAccess, {
43
+ session: args.session,
44
+ context: args.context,
45
+ });
46
+ // If access is completely denied, exclude this relationship
47
+ if (accessResult === false) {
48
+ continue;
49
+ }
50
+ // Build the include entry
51
+ const includeEntry = {};
52
+ // If access returns a filter, add it to the where clause
53
+ if (typeof accessResult === 'object') {
54
+ includeEntry.where = accessResult;
55
+ }
56
+ // Recursively build nested includes
57
+ const nestedInclude = await buildIncludeWithAccessControl(relatedConfig.listConfig.fields, args, config, depth + 1);
58
+ if (nestedInclude && Object.keys(nestedInclude).length > 0) {
59
+ includeEntry.include = nestedInclude;
60
+ }
61
+ // Add to include object
62
+ include[fieldName] = Object.keys(includeEntry).length > 0 ? includeEntry : true;
63
+ }
64
+ }
65
+ }
66
+ return hasRelationships ? include : undefined;
67
+ }
68
+ //# sourceMappingURL=access-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-filter.js","sourceRoot":"","sources":["../../src/access/access-filter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE/D;;;;;;;;;;;;;;GAcG;AAEH;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,YAAyC,EACzC,IAGC,EACD,MAAsB,EACtB,QAAgB,CAAC;IAEjB,MAAM,SAAS,GAAG,CAAC,CAAA;IACnB,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;QACvB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,qEAAqE;IACrE,uEAAuE;IACvE,gFAAgF;IAChF,0EAA0E;IAC1E,IAAI,IAAI,CAAC,OAAO,CAAC,qBAAqB,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QACjD,OAAO,SAAS,CAAA;IAClB,CAAC;IAID,MAAM,OAAO,GAAiC,EAAE,CAAA;IAChD,IAAI,gBAAgB,GAAG,KAAK,CAAA;IAE5B,KAAK,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACpE,IAAI,WAAW,EAAE,IAAI,KAAK,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI,WAAW,CAAC,GAAG,EAAE,CAAC;YACpF,gBAAgB,GAAG,IAAI,CAAA;YACvB,MAAM,aAAa,GAAG,oBAAoB,CAAC,WAAW,CAAC,GAAa,EAAE,MAAM,CAAC,CAAA;YAE7E,IAAI,aAAa,EAAE,CAAC;gBAClB,0CAA0C;gBAC1C,MAAM,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAA;gBACrE,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE;oBAClD,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;iBACtB,CAAC,CAAA;gBAEF,4DAA4D;gBAC5D,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;oBAC3B,SAAQ;gBACV,CAAC;gBAED,0BAA0B;gBAC1B,MAAM,YAAY,GAA4B,EAAE,CAAA;gBAEhD,yDAAyD;gBACzD,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;oBACrC,YAAY,CAAC,KAAK,GAAG,YAAY,CAAA;gBACnC,CAAC;gBAED,oCAAoC;gBACpC,MAAM,aAAa,GAAG,MAAM,6BAA6B,CACvD,aAAa,CAAC,UAAU,CAAC,MAAM,EAC/B,IAAI,EACJ,MAAM,EACN,KAAK,GAAG,CAAC,CACV,CAAA;gBAED,IAAI,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC3D,YAAY,CAAC,OAAO,GAAG,aAAa,CAAA;gBACtC,CAAC;gBAED,wBAAwB;gBACxB,OAAO,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAA;YACjF,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AAC/C,CAAC"}
@@ -1,6 +1,19 @@
1
1
  import type { AccessControl, Session, AccessContext, PrismaFilter } from './types.js';
2
- import type { FieldAccess } from './types.js';
3
- import type { OpenSaasConfig, ListConfig, FieldConfig } from '../config/types.js';
2
+ import type { OpenSaasConfig, ListConfig } from '../config/types.js';
3
+ /**
4
+ * Access engine — operation-level access control and shared helpers.
5
+ *
6
+ * This module holds the *operation-level* (list-level) access primitives and
7
+ * the ref-parsing helper shared across both phases of the two-phase read:
8
+ *
9
+ * - Phase 1, Access Filter (pre-query row/relation scoping): `access-filter.ts`
10
+ * - Phase 2, Field Visibility (post-query field stripping + resolveOutput +
11
+ * virtual fields): `field-visibility.ts`
12
+ *
13
+ * Field-level access evaluation is centralized in `field-access.ts`
14
+ * (`checkFieldAccess`). See `docs/adr/0001-access-control-is-a-two-phase-read.md`
15
+ * and the access-control glossary in `CONTEXT.md`.
16
+ */
4
17
  /**
5
18
  * Check if access control result is a boolean
6
19
  */
@@ -33,50 +46,4 @@ export declare function checkAccess<T = Record<string, unknown>>(accessControl:
33
46
  * Merge user filter with access control filter
34
47
  */
35
48
  export declare function mergeFilters(userFilter: PrismaFilter | undefined, accessFilter: boolean | PrismaFilter): PrismaFilter | null;
36
- /**
37
- * Check field-level access for a specific operation
38
- */
39
- export declare function checkFieldAccess(fieldAccess: FieldAccess | undefined, operation: 'read' | 'create' | 'update', args: {
40
- session: Session | null;
41
- item?: Record<string, unknown>;
42
- context: AccessContext & {
43
- _isSudo?: boolean;
44
- };
45
- inputData?: Record<string, unknown>;
46
- }): Promise<boolean>;
47
- /**
48
- * Build Prisma include object with access control filters
49
- * This allows us to filter relationships at the database level instead of in memory
50
- */
51
- export declare function buildIncludeWithAccessControl(fieldConfigs: Record<string, FieldConfig>, args: {
52
- session: Session | null;
53
- context: AccessContext;
54
- }, config: OpenSaasConfig, depth?: number): Promise<Record<string, boolean | {
55
- where?: PrismaFilter;
56
- include?: Record<string, boolean | /*elided*/ any>;
57
- }> | undefined>;
58
- /**
59
- * Filter fields from an object based on read access
60
- * Recursively applies access control to nested relationships
61
- */
62
- export declare function filterReadableFields<T extends Record<string, unknown>>(item: T, fieldConfigs: Record<string, FieldConfig>, args: {
63
- session: Session | null;
64
- context: AccessContext & {
65
- _isSudo?: boolean;
66
- };
67
- }, config?: OpenSaasConfig, depth?: number, listKey?: string): Promise<Partial<T>>;
68
- /**
69
- * Filter fields from input data based on write access (create/update)
70
- */
71
- export declare function filterWritableFields<T extends Record<string, unknown>>(data: T, fieldConfigs: Record<string, {
72
- access?: FieldAccess;
73
- type?: string;
74
- }>, operation: 'create' | 'update', args: {
75
- session: Session | null;
76
- item?: Record<string, unknown>;
77
- context: AccessContext & {
78
- _isSudo?: boolean;
79
- };
80
- inputData?: Record<string, unknown>;
81
- }): Promise<Partial<T>>;
82
49
  //# sourceMappingURL=engine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpE;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,OAAO,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAEpE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,cAAc,GAErB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAA;CAAE,GAAG,IAAI,CAe1D;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS,EAC3C,IAAI,EAAE;IACJ,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,EAAE,aAAa,CAAA;CACvB,GACA,OAAO,CAAC,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAUpC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,YAAY,EAAE,OAAO,GAAG,YAAY,GACnC,YAAY,GAAG,IAAI,CAoBrB"}
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Access engine — operation-level access control and shared helpers.
3
+ *
4
+ * This module holds the *operation-level* (list-level) access primitives and
5
+ * the ref-parsing helper shared across both phases of the two-phase read:
6
+ *
7
+ * - Phase 1, Access Filter (pre-query row/relation scoping): `access-filter.ts`
8
+ * - Phase 2, Field Visibility (post-query field stripping + resolveOutput +
9
+ * virtual fields): `field-visibility.ts`
10
+ *
11
+ * Field-level access evaluation is centralized in `field-access.ts`
12
+ * (`checkFieldAccess`). See `docs/adr/0001-access-control-is-a-two-phase-read.md`
13
+ * and the access-control glossary in `CONTEXT.md`.
14
+ */
1
15
  /**
2
16
  * Check if access control result is a boolean
3
17
  */
@@ -64,284 +78,4 @@ export function mergeFilters(userFilter, accessFilter) {
64
78
  AND: [accessFilter, userFilter],
65
79
  };
66
80
  }
67
- /**
68
- * Check field-level access for a specific operation
69
- */
70
- export async function checkFieldAccess(fieldAccess, operation, args) {
71
- // Skip access check in sudo mode
72
- if (args.context._isSudo) {
73
- return true;
74
- }
75
- if (!fieldAccess) {
76
- return true; // No field access means allow
77
- }
78
- const accessControl = fieldAccess[operation];
79
- if (!accessControl) {
80
- return true; // No specific access control means allow
81
- }
82
- const result = await accessControl({
83
- session: args.session,
84
- item: args.item,
85
- context: args.context,
86
- inputData: args.inputData,
87
- operation,
88
- });
89
- // If result is false, deny access
90
- if (result === false) {
91
- return false;
92
- }
93
- // If result is true, allow access
94
- if (result === true) {
95
- return true;
96
- }
97
- // Default to allowing access if we can't determine
98
- return true;
99
- }
100
- /**
101
- * Simple filter matching for field-level access
102
- * Checks if an item matches a Prisma-like filter object
103
- */
104
- function matchesFilter(item, filter) {
105
- for (const [key, condition] of Object.entries(filter)) {
106
- if (typeof condition === 'object' && condition !== null) {
107
- // Handle nested conditions like { equals: value }
108
- if ('equals' in condition) {
109
- if (item[key] !== condition.equals) {
110
- return false;
111
- }
112
- }
113
- else if ('not' in condition) {
114
- if (item[key] === condition.not) {
115
- return false;
116
- }
117
- }
118
- // Add more condition types as needed
119
- }
120
- else {
121
- // Direct equality check
122
- if (item[key] !== condition) {
123
- return false;
124
- }
125
- }
126
- }
127
- return true;
128
- }
129
- /**
130
- * Build Prisma include object with access control filters
131
- * This allows us to filter relationships at the database level instead of in memory
132
- */
133
- export async function buildIncludeWithAccessControl(fieldConfigs, args, config, depth = 0) {
134
- const MAX_DEPTH = 5;
135
- if (depth >= MAX_DEPTH) {
136
- return undefined;
137
- }
138
- // Skip auto-including relationships when inside a resolveOutput hook
139
- // This prevents infinite loops when hooks make DB queries that include
140
- // relationships back to the same entity (e.g., User virtual field queries Posts
141
- // which includes author back to User, triggering the virtual field again)
142
- if (args.context._resolveOutputCounter.depth > 0) {
143
- return undefined;
144
- }
145
- const include = {};
146
- let hasRelationships = false;
147
- for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
148
- if (fieldConfig?.type === 'relationship' && 'ref' in fieldConfig && fieldConfig.ref) {
149
- hasRelationships = true;
150
- const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
151
- if (relatedConfig) {
152
- // Check query access for the related list
153
- const queryAccess = relatedConfig.listConfig.access?.operation?.query;
154
- const accessResult = await checkAccess(queryAccess, {
155
- session: args.session,
156
- context: args.context,
157
- });
158
- // If access is completely denied, exclude this relationship
159
- if (accessResult === false) {
160
- continue;
161
- }
162
- // Build the include entry
163
- const includeEntry = {};
164
- // If access returns a filter, add it to the where clause
165
- if (typeof accessResult === 'object') {
166
- includeEntry.where = accessResult;
167
- }
168
- // Recursively build nested includes
169
- const nestedInclude = await buildIncludeWithAccessControl(relatedConfig.listConfig.fields, args, config, depth + 1);
170
- if (nestedInclude && Object.keys(nestedInclude).length > 0) {
171
- includeEntry.include = nestedInclude;
172
- }
173
- // Add to include object
174
- include[fieldName] = Object.keys(includeEntry).length > 0 ? includeEntry : true;
175
- }
176
- }
177
- }
178
- return hasRelationships ? include : undefined;
179
- }
180
- /**
181
- * Filter fields from an object based on read access
182
- * Recursively applies access control to nested relationships
183
- */
184
- export async function filterReadableFields(item, fieldConfigs, args, config, depth = 0, listKey) {
185
- const filtered = {};
186
- const MAX_DEPTH = 5; // Prevent infinite recursion
187
- // Process existing fields from the database result
188
- for (const [fieldName, value] of Object.entries(item)) {
189
- const fieldConfig = fieldConfigs[fieldName];
190
- // Always include id, createdAt, updatedAt
191
- if (['id', 'createdAt', 'updatedAt'].includes(fieldName)) {
192
- filtered[fieldName] = value;
193
- continue;
194
- }
195
- // Check field access (checkFieldAccess already handles sudo mode)
196
- const canRead = await checkFieldAccess(fieldConfig?.access, 'read', {
197
- ...args,
198
- item,
199
- });
200
- if (!canRead) {
201
- continue;
202
- }
203
- // Handle relationship fields - recursively filter fields within related items
204
- // Note: Access control filtering is now done at database level via buildIncludeWithAccessControl
205
- // This only handles field-level access (hiding sensitive fields)
206
- if (config &&
207
- fieldConfig?.type === 'relationship' &&
208
- 'ref' in fieldConfig &&
209
- fieldConfig.ref &&
210
- value !== null &&
211
- value !== undefined &&
212
- depth < MAX_DEPTH) {
213
- const relatedConfig = getRelatedListConfig(fieldConfig.ref, config);
214
- if (relatedConfig) {
215
- // For many relationships (arrays) - recursively filter fields in each item
216
- // The recursive call already handles applying resolveOutput hooks
217
- if (Array.isArray(value)) {
218
- filtered[fieldName] = await Promise.all(value.map((relatedItem) => filterReadableFields(relatedItem, relatedConfig.listConfig.fields, args, config, depth + 1, relatedConfig.listName)));
219
- }
220
- // For single relationships (objects) - recursively filter fields
221
- // The recursive call already handles applying resolveOutput hooks
222
- else if (typeof value === 'object') {
223
- filtered[fieldName] = await filterReadableFields(value, relatedConfig.listConfig.fields, args, config, depth + 1, relatedConfig.listName);
224
- }
225
- }
226
- else {
227
- // Related config not found, include the value as-is
228
- filtered[fieldName] = value;
229
- }
230
- }
231
- else {
232
- // Non-relationship field or no config provided - apply resolveOutput hook if present
233
- if (fieldConfig?.hooks?.resolveOutput && listKey) {
234
- // Cast to runtime type for generic execution
235
- // At runtime, the hook will receive the correct value type for the field
236
- const hook = fieldConfig.hooks.resolveOutput;
237
- // Increment depth counter to prevent infinite loops from hooks making DB queries
238
- // that include relationships back to the same entity
239
- args.context._resolveOutputCounter.depth++;
240
- try {
241
- // Use Promise.resolve() to handle both sync and async hooks
242
- filtered[fieldName] = await Promise.resolve(hook({
243
- value,
244
- operation: 'query',
245
- fieldName,
246
- listKey,
247
- item,
248
- context: args.context,
249
- }));
250
- }
251
- finally {
252
- args.context._resolveOutputCounter.depth--;
253
- }
254
- }
255
- else {
256
- filtered[fieldName] = value;
257
- }
258
- }
259
- }
260
- // Process virtual fields - compute values from other fields
261
- // Virtual fields don't exist in the database result, so we need to compute them separately
262
- for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
263
- // Skip if already processed (from database result)
264
- if (fieldName in filtered) {
265
- continue;
266
- }
267
- // Only process virtual fields
268
- if (!fieldConfig.virtual) {
269
- continue;
270
- }
271
- // Check field access
272
- const canRead = await checkFieldAccess(fieldConfig.access, 'read', {
273
- ...args,
274
- item,
275
- });
276
- if (!canRead) {
277
- continue;
278
- }
279
- // Virtual fields must have resolveOutput hook to compute their value
280
- if (fieldConfig.hooks?.resolveOutput && listKey) {
281
- const hook = fieldConfig.hooks.resolveOutput;
282
- // Increment depth counter to prevent infinite loops from hooks making DB queries
283
- // that include relationships back to the same entity
284
- args.context._resolveOutputCounter.depth++;
285
- try {
286
- // Use Promise.resolve() to handle both sync and async hooks
287
- filtered[fieldName] = await Promise.resolve(hook({
288
- value: undefined, // Virtual fields don't have a database value
289
- operation: 'query',
290
- fieldName,
291
- listKey,
292
- item: filtered, // Pass filtered item so virtual field can access other fields
293
- context: args.context,
294
- }));
295
- }
296
- finally {
297
- args.context._resolveOutputCounter.depth--;
298
- }
299
- }
300
- }
301
- return filtered;
302
- }
303
- /**
304
- * Filter fields from input data based on write access (create/update)
305
- */
306
- export async function filterWritableFields(data, fieldConfigs, operation, args) {
307
- const filtered = {};
308
- // Build a set of foreign key field names to exclude
309
- // Foreign keys should not be in the data when using Prisma's relation syntax
310
- const foreignKeyFields = new Set();
311
- for (const [fieldName, fieldConfig] of Object.entries(fieldConfigs)) {
312
- if (fieldConfig.type === 'relationship') {
313
- // For non-many relationships, Prisma creates a foreign key field named `${fieldName}Id`
314
- const relConfig = fieldConfig;
315
- if (!relConfig.many) {
316
- foreignKeyFields.add(`${fieldName}Id`);
317
- }
318
- }
319
- }
320
- for (const [fieldName, value] of Object.entries(data)) {
321
- const fieldConfig = fieldConfigs[fieldName];
322
- // Skip system fields
323
- if (['id', 'createdAt', 'updatedAt'].includes(fieldName)) {
324
- continue;
325
- }
326
- // Skip virtual fields - they don't store in database
327
- // Virtual fields with resolveInput hooks handle side effects separately
328
- if (fieldConfig && 'virtual' in fieldConfig && fieldConfig.virtual) {
329
- continue;
330
- }
331
- // Skip foreign key fields (e.g., authorId) when their corresponding relationship field exists
332
- // This prevents conflicts when using Prisma's relation syntax (e.g., author: { connect: { id } })
333
- if (foreignKeyFields.has(fieldName)) {
334
- continue;
335
- }
336
- // Check field access (checkFieldAccess already handles sudo mode)
337
- const canWrite = await checkFieldAccess(fieldConfig?.access, operation, {
338
- ...args,
339
- inputData: args.inputData,
340
- });
341
- if (canWrite) {
342
- filtered[fieldName] = value;
343
- }
344
- }
345
- return filtered;
346
- }
347
81
  //# sourceMappingURL=engine.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/access/engine.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,SAAS,CAAA;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAClC,eAAuB,EACvB,MAAsB;IAGtB,uDAAuD;IACvD,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7C,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;IACzB,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAEzC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAA;AACjC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,aAA2C,EAC3C,IAIC;IAED,0CAA0C;IAC1C,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO,KAAK,CAAA;IACd,CAAC;IAED,sCAAsC;IACtC,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAA;IAExC,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,UAAoC,EACpC,YAAoC;IAEpC,mCAAmC;IACnC,IAAI,YAAY,KAAK,KAAK,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,8CAA8C;IAC9C,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,OAAO,UAAU,IAAI,EAAE,CAAA;IACzB,CAAC;IAED,uCAAuC;IACvC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,YAAY,CAAA;IACrB,CAAC;IAED,2BAA2B;IAC3B,OAAO;QACL,GAAG,EAAE,CAAC,YAAY,EAAE,UAAU,CAAC;KAChC,CAAA;AACH,CAAC"}