@opensaas/stack-core 0.38.0 → 0.39.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +123 -0
- package/CLAUDE.md +21 -0
- package/dist/access/access-filter.d.ts +46 -1
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +113 -4
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/declared-dependencies.d.ts.map +1 -1
- package/dist/access/declared-dependencies.js +4 -9
- package/dist/access/declared-dependencies.js.map +1 -1
- package/dist/access/engine.d.ts +0 -20
- package/dist/access/engine.d.ts.map +1 -1
- package/dist/access/engine.js +0 -25
- package/dist/access/engine.js.map +1 -1
- package/dist/access/errors.d.ts +49 -0
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +75 -0
- package/dist/access/errors.js.map +1 -1
- package/dist/access/field-access.d.ts +39 -1
- package/dist/access/field-access.d.ts.map +1 -1
- package/dist/access/field-access.js +108 -42
- package/dist/access/field-access.js.map +1 -1
- package/dist/access/field-access.test.js +229 -2
- package/dist/access/field-access.test.js.map +1 -1
- package/dist/access/field-transforms.d.ts +0 -27
- package/dist/access/field-transforms.d.ts.map +1 -1
- package/dist/access/field-visibility.d.ts +0 -4
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +13 -39
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +6 -2
- package/dist/access/index.d.ts.map +1 -1
- package/dist/access/index.js +12 -2
- package/dist/access/index.js.map +1 -1
- package/dist/access/query-validation.d.ts +121 -0
- package/dist/access/query-validation.d.ts.map +1 -0
- package/dist/access/query-validation.js +292 -0
- package/dist/access/query-validation.js.map +1 -0
- package/dist/access/relationship-count.d.ts.map +1 -1
- package/dist/access/relationship-count.js +2 -10
- package/dist/access/relationship-count.js.map +1 -1
- package/dist/access/relationship-label-filter.d.ts +23 -23
- package/dist/access/relationship-label-filter.d.ts.map +1 -1
- package/dist/access/relationship-label-filter.js +5 -76
- package/dist/access/relationship-label-filter.js.map +1 -1
- package/dist/access/relationship-label-filter.test.js +19 -56
- package/dist/access/relationship-label-filter.test.js.map +1 -1
- package/dist/access/transaction-registry.d.ts +45 -0
- package/dist/access/transaction-registry.d.ts.map +1 -0
- package/dist/access/transaction-registry.js +37 -0
- package/dist/access/transaction-registry.js.map +1 -0
- package/dist/access/types.d.ts +55 -59
- package/dist/access/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +9 -7
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -18
- package/dist/config/index.js.map +1 -1
- package/dist/config/label.d.ts +6 -13
- package/dist/config/label.d.ts.map +1 -1
- package/dist/config/label.js +6 -13
- package/dist/config/label.js.map +1 -1
- package/dist/config/nav-count.d.ts +11 -19
- package/dist/config/nav-count.d.ts.map +1 -1
- package/dist/config/nav-count.js +11 -19
- package/dist/config/nav-count.js.map +1 -1
- package/dist/config/plugin-engine.d.ts +0 -10
- package/dist/config/plugin-engine.d.ts.map +1 -1
- package/dist/config/plugin-engine.js +1 -37
- package/dist/config/plugin-engine.js.map +1 -1
- package/dist/config/types.d.ts +94 -109
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/apply-defaults.d.ts +8 -31
- package/dist/context/apply-defaults.d.ts.map +1 -1
- package/dist/context/apply-defaults.js +12 -41
- package/dist/context/apply-defaults.js.map +1 -1
- package/dist/context/hook-pipeline.d.ts +4 -29
- package/dist/context/hook-pipeline.d.ts.map +1 -1
- package/dist/context/hook-pipeline.js +8 -41
- package/dist/context/hook-pipeline.js.map +1 -1
- package/dist/context/index.d.ts +4 -15
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +190 -193
- package/dist/context/index.js.map +1 -1
- package/dist/context/nested-operations.d.ts +7 -26
- package/dist/context/nested-operations.d.ts.map +1 -1
- package/dist/context/nested-operations.js +73 -171
- package/dist/context/nested-operations.js.map +1 -1
- package/dist/context/transaction-boundary.d.ts +36 -47
- package/dist/context/transaction-boundary.d.ts.map +1 -1
- package/dist/context/transaction-boundary.js +92 -79
- package/dist/context/transaction-boundary.js.map +1 -1
- package/dist/context/write-pipeline.d.ts +25 -64
- package/dist/context/write-pipeline.d.ts.map +1 -1
- package/dist/context/write-pipeline.js +81 -111
- package/dist/context/write-pipeline.js.map +1 -1
- package/dist/fields/format-prisma-default.d.ts.map +1 -1
- package/dist/fields/format-prisma-default.js +4 -7
- package/dist/fields/format-prisma-default.js.map +1 -1
- package/dist/fields/index.d.ts +17 -2
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/fields/index.js +136 -147
- package/dist/fields/index.js.map +1 -1
- package/dist/filter/collect.d.ts +26 -3
- package/dist/filter/collect.d.ts.map +1 -1
- package/dist/filter/collect.js +30 -12
- package/dist/filter/collect.js.map +1 -1
- package/dist/filter/filter.test.js +98 -15
- package/dist/filter/filter.test.js.map +1 -1
- package/dist/filter/index.d.ts +1 -0
- package/dist/filter/index.d.ts.map +1 -1
- package/dist/filter/index.js.map +1 -1
- package/dist/filter/map.d.ts.map +1 -1
- package/dist/filter/map.js +0 -5
- package/dist/filter/map.js.map +1 -1
- package/dist/filter/parse.d.ts.map +1 -1
- package/dist/filter/parse.js +2 -9
- package/dist/filter/parse.js.map +1 -1
- package/dist/filter/serialize.d.ts.map +1 -1
- package/dist/filter/serialize.js +2 -4
- package/dist/filter/serialize.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -42
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -59
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -1
- package/dist/lib/case-utils.d.ts +0 -32
- package/dist/lib/case-utils.d.ts.map +1 -1
- package/dist/lib/case-utils.js +0 -32
- package/dist/lib/case-utils.js.map +1 -1
- package/dist/mcp/handler.d.ts +0 -4
- package/dist/mcp/handler.d.ts.map +1 -1
- package/dist/mcp/handler.js +12 -69
- package/dist/mcp/handler.js.map +1 -1
- package/dist/query/index.d.ts +26 -227
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +14 -117
- package/dist/query/index.js.map +1 -1
- package/dist/utils/password.d.ts +0 -68
- package/dist/utils/password.d.ts.map +1 -1
- package/dist/utils/password.js +7 -76
- package/dist/utils/password.js.map +1 -1
- package/dist/validation/field-config.d.ts.map +1 -1
- package/dist/validation/field-config.js +0 -6
- package/dist/validation/field-config.js.map +1 -1
- package/dist/validation/schema.d.ts +0 -7
- package/dist/validation/schema.d.ts.map +1 -1
- package/dist/validation/schema.js +0 -10
- package/dist/validation/schema.js.map +1 -1
- package/package.json +1 -1
- package/src/access/access-filter.ts +152 -5
- package/src/access/declared-dependencies.ts +4 -9
- package/src/access/engine.ts +0 -25
- package/src/access/errors.ts +81 -0
- package/src/access/field-access.test.ts +312 -2
- package/src/access/field-access.ts +118 -42
- package/src/access/field-transforms.ts +8 -39
- package/src/access/field-visibility.ts +16 -47
- package/src/access/index.ts +20 -2
- package/src/access/query-validation.ts +359 -0
- package/src/access/relationship-count.ts +2 -10
- package/src/access/relationship-label-filter.test.ts +19 -68
- package/src/access/relationship-label-filter.ts +27 -104
- package/src/access/transaction-registry.ts +58 -0
- package/src/access/types.ts +83 -86
- package/src/config/index.ts +10 -22
- package/src/config/label.ts +6 -13
- package/src/config/nav-count.ts +11 -19
- package/src/config/plugin-engine.ts +1 -37
- package/src/config/types.ts +95 -110
- package/src/context/apply-defaults.ts +12 -44
- package/src/context/hook-pipeline.ts +12 -67
- package/src/context/index.ts +263 -247
- package/src/context/nested-operations.ts +94 -226
- package/src/context/transaction-boundary.ts +113 -109
- package/src/context/write-pipeline.ts +99 -155
- package/src/fields/format-prisma-default.ts +4 -7
- package/src/fields/index.ts +155 -147
- package/src/filter/collect.ts +48 -12
- package/src/filter/filter.test.ts +153 -15
- package/src/filter/index.ts +1 -0
- package/src/filter/map.ts +0 -5
- package/src/filter/parse.ts +2 -9
- package/src/filter/serialize.ts +2 -4
- package/src/hooks/index.ts +5 -59
- package/src/index.ts +23 -4
- package/src/internal.ts +5 -0
- package/src/lib/case-utils.ts +0 -32
- package/src/mcp/handler.ts +13 -71
- package/src/query/index.ts +29 -235
- package/src/utils/password.ts +7 -76
- package/src/validation/field-config.ts +0 -6
- package/src/validation/schema.ts +0 -10
- package/tests/context.test.ts +700 -1
- package/tests/field-types.test.ts +222 -0
- package/tests/mcp-handler.test.ts +34 -0
- package/tests/transaction-boundary-outermost.test.ts +530 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,66 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Fills a field's `defaultValue` into `resolvedData` for CREATE when the field
|
|
3
|
+
* was omitted, run after resolveInput hooks and before validation. Shared by
|
|
4
|
+
* the top-level create path (Hook Pipeline) and the nested-relation create path.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* fails `isRequired` validation on an omitted input even though a default exists.
|
|
10
|
-
*
|
|
11
|
-
* This helper closes that gap: in the resolve phase (after `resolveInput` hooks,
|
|
12
|
-
* before validation) it fills `resolvedData[field]` with the field's
|
|
13
|
-
* `defaultValue` ONLY when the field was OMITTED (value is `undefined`). It is a
|
|
14
|
-
* SINGLE shared mechanism used by both the top-level create path (Hook Pipeline)
|
|
15
|
-
* and the nested-relation create path.
|
|
16
|
-
*
|
|
17
|
-
* Guard rails (each acceptance-criteria-driven):
|
|
18
|
-
* - CREATE only. Update never injects defaults for omitted fields (the caller
|
|
19
|
-
* only invokes this for `operation === 'create'`).
|
|
20
|
-
* - Explicitly-provided values are preserved. A key present in `resolvedData`
|
|
21
|
-
* — INCLUDING an explicit `null` — is left untouched; only `undefined`
|
|
22
|
-
* (omitted) keys are filled.
|
|
23
|
-
* - Virtual, system (`id`/`createdAt`/`updatedAt`) and relationship fields are
|
|
24
|
-
* skipped — they have no scalar `defaultValue` to inject and relationships
|
|
25
|
-
* carry connect/create payloads rather than literal defaults.
|
|
26
|
-
* - The timestamp `{ kind: 'now' }` sentinel is NOT injected: it is not a
|
|
27
|
-
* literal value but a request for the DB-level `@default(now())`, which still
|
|
28
|
-
* applies at write time. Injecting the sentinel object would corrupt the
|
|
29
|
-
* payload. (A concrete `Date` default is a real literal and IS injected.)
|
|
30
|
-
*
|
|
31
|
-
* The function mutates and returns `resolvedData` (consistent with the other
|
|
32
|
-
* resolve-phase helpers that thread `resolvedData` through the pipeline).
|
|
6
|
+
* Prisma only realises `defaultValue` as `@default(...)` at the database write,
|
|
7
|
+
* which is after this pipeline's validation phase — so without this, a
|
|
8
|
+
* required field with a default (e.g. `select({ validation: { isRequired: true },
|
|
9
|
+
* defaultValue: 'X' })`) fails `isRequired` on an omitted input. See issue #615.
|
|
33
10
|
*/
|
|
34
11
|
export function applyCreateDefaults(resolvedData, fieldConfigs) {
|
|
35
12
|
for (const [fieldKey, fieldConfig] of Object.entries(fieldConfigs)) {
|
|
36
|
-
// Skip virtual fields — not stored in the database.
|
|
37
13
|
if (fieldConfig.virtual)
|
|
38
14
|
continue;
|
|
39
|
-
// Skip system fields — always managed by the framework/DB.
|
|
40
15
|
if (fieldKey === 'id' || fieldKey === 'createdAt' || fieldKey === 'updatedAt')
|
|
41
16
|
continue;
|
|
42
|
-
//
|
|
17
|
+
// Relationships carry connect/create payloads, not literal defaults.
|
|
43
18
|
if (fieldConfig.type === 'relationship')
|
|
44
19
|
continue;
|
|
45
|
-
// No declared default → nothing to inject.
|
|
46
20
|
if (!('defaultValue' in fieldConfig) || fieldConfig.defaultValue === undefined)
|
|
47
21
|
continue;
|
|
48
|
-
// Only fill OMITTED keys
|
|
49
|
-
// `null
|
|
22
|
+
// Only fill OMITTED (`undefined`) keys — an explicitly-provided value,
|
|
23
|
+
// including explicit `null`, must survive untouched.
|
|
50
24
|
if (resolvedData[fieldKey] !== undefined)
|
|
51
25
|
continue;
|
|
52
26
|
const defaultValue = fieldConfig.defaultValue;
|
|
53
|
-
//
|
|
54
|
-
//
|
|
27
|
+
// `{ kind: 'now' }` requests the DB-level `@default(now())`, not a literal
|
|
28
|
+
// value — injecting the sentinel object itself would corrupt the payload.
|
|
55
29
|
if (isNowSentinel(defaultValue))
|
|
56
30
|
continue;
|
|
57
31
|
resolvedData[fieldKey] = defaultValue;
|
|
58
32
|
}
|
|
59
33
|
return resolvedData;
|
|
60
34
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Detect the timestamp `{ kind: 'now' }` default sentinel.
|
|
63
|
-
*/
|
|
64
35
|
function isNowSentinel(value) {
|
|
65
36
|
return (typeof value === 'object' &&
|
|
66
37
|
value !== null &&
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply-defaults.js","sourceRoot":"","sources":["../../src/context/apply-defaults.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"apply-defaults.js","sourceRoot":"","sources":["../../src/context/apply-defaults.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,YAAqC,EACrC,YAAyC;IAEzC,KAAK,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACnE,IAAI,WAAW,CAAC,OAAO;YAAE,SAAQ;QACjC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW;YAAE,SAAQ;QACvF,qEAAqE;QACrE,IAAI,WAAW,CAAC,IAAI,KAAK,cAAc;YAAE,SAAQ;QACjD,IAAI,CAAC,CAAC,cAAc,IAAI,WAAW,CAAC,IAAI,WAAW,CAAC,YAAY,KAAK,SAAS;YAAE,SAAQ;QAExF,uEAAuE;QACvE,qDAAqD;QACrD,IAAI,YAAY,CAAC,QAAQ,CAAC,KAAK,SAAS;YAAE,SAAQ;QAElD,MAAM,YAAY,GAAG,WAAW,CAAC,YAAY,CAAA;QAE7C,2EAA2E;QAC3E,0EAA0E;QAC1E,IAAI,aAAa,CAAC,YAAY,CAAC;YAAE,SAAQ;QAEzC,YAAY,CAAC,QAAQ,CAAC,GAAG,YAAY,CAAA;IACvC,CAAC;IAED,OAAO,YAAY,CAAA;AACrB,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACd,MAAM,IAAI,KAAK;QACd,KAA2B,CAAC,IAAI,KAAK,KAAK,CAC5C,CAAA;AACH,CAAC"}
|
|
@@ -1,49 +1,24 @@
|
|
|
1
1
|
import type { ListConfig } from '../config/types.js';
|
|
2
2
|
import type { AccessContext } from '../access/types.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* It is THE place where input is shaped and validated; it throws
|
|
10
|
-
* {@link ValidationError} on failure exactly as before (validate hooks via
|
|
11
|
-
* `addValidationError`, then `validateFieldRules`) — validation is never silent.
|
|
12
|
-
*
|
|
13
|
-
* Side-effect hooks (`beforeOperation`/`afterOperation`), operation-level access,
|
|
14
|
-
* writable-field filtering, nested operations, persistence and Field Visibility
|
|
15
|
-
* are deliberately OUT of this span — they stay in the Write Pipeline. See the
|
|
16
|
-
* "Hook Pipeline" and "Write Pipeline" glossary terms in CONTEXT.md.
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* Arguments for one transform+validate span. Only the create/update operations
|
|
20
|
-
* run this span (delete skips the input-shaping phases entirely).
|
|
4
|
+
* The transform+validate span of a write. See the "Hook Pipeline" and "Write
|
|
5
|
+
* Pipeline" glossary terms in CONTEXT.md for the full phase order and the
|
|
6
|
+
* boundary with the Write Pipeline (which owns side-effect hooks, access,
|
|
7
|
+
* writable-field filtering, nested operations, persistence, Field Visibility).
|
|
21
8
|
*/
|
|
22
9
|
export interface HookPipelineArgs {
|
|
23
10
|
operation: 'create' | 'update';
|
|
24
11
|
listName: string;
|
|
25
12
|
listConfig: ListConfig<any>;
|
|
26
|
-
/** The original input data for the write. */
|
|
27
13
|
inputData: Record<string, unknown>;
|
|
28
|
-
/** The existing row for update; `undefined` for create. */
|
|
29
14
|
item: Record<string, unknown> | undefined;
|
|
30
15
|
context: AccessContext;
|
|
31
16
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Result of a transform+validate span: the fully-resolved write data after the
|
|
34
|
-
* resolveInput hooks have run and all validation has passed.
|
|
35
|
-
*/
|
|
36
17
|
export interface HookPipelineResult {
|
|
37
18
|
resolvedData: Record<string, unknown>;
|
|
38
19
|
}
|
|
39
|
-
/**
|
|
40
|
-
* The transform+validate span, owning order + `resolvedData` threading.
|
|
41
|
-
*/
|
|
42
20
|
export interface HookPipeline {
|
|
43
21
|
run(args: HookPipelineArgs): Promise<HookPipelineResult>;
|
|
44
22
|
}
|
|
45
|
-
/**
|
|
46
|
-
* The default Hook Pipeline instance used by the Write Pipeline.
|
|
47
|
-
*/
|
|
48
23
|
export declare const hookPipeline: HookPipeline;
|
|
49
24
|
//# sourceMappingURL=hook-pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-pipeline.d.ts","sourceRoot":"","sources":["../../src/context/hook-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAYvD
|
|
1
|
+
{"version":3,"file":"hook-pipeline.d.ts","sourceRoot":"","sources":["../../src/context/hook-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAYvD;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAEhB,UAAU,EAAE,UAAU,CAAC,GAAG,CAAC,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IACzC,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACzD;AAgGD,eAAO,MAAM,YAAY,EAAE,YAE1B,CAAA"}
|
|
@@ -1,30 +1,7 @@
|
|
|
1
1
|
import { executeResolveInput, executeValidate, executeFieldResolveInputHooks, executeFieldValidateHooks, validateFieldRules, splitMultiColumnFields, ValidationError, } from '../hooks/index.js';
|
|
2
2
|
import { applyCreateDefaults } from './apply-defaults.js';
|
|
3
|
-
/**
|
|
4
|
-
* Run the transform+validate span once.
|
|
5
|
-
*
|
|
6
|
-
* Phase order (owned here, in one place):
|
|
7
|
-
* list `resolveInput`
|
|
8
|
-
* → field `resolveInput`
|
|
9
|
-
* → list `validate`
|
|
10
|
-
* → field `validate`
|
|
11
|
-
* → built-in field rules (`validateFieldRules`)
|
|
12
|
-
* → split multi-column fields (`splitMultiColumnFields`)
|
|
13
|
-
*
|
|
14
|
-
* Contract preserved exactly:
|
|
15
|
-
* - `resolvedData` starts as `inputData` and is threaded through each phase;
|
|
16
|
-
* - validate hooks report failures via `addValidationError` → THROW
|
|
17
|
-
* `ValidationError` (never silent);
|
|
18
|
-
* - built-in field rule failures THROW `ValidationError`;
|
|
19
|
-
* - a multi-column field (e.g. storage image()/file() in Keystone-parity
|
|
20
|
-
* mode) is validated under its LOGICAL key BEFORE it is split into
|
|
21
|
-
* physical columns (#789) — an unrecognised value throws instead of being
|
|
22
|
-
* silently split into null/undefined columns;
|
|
23
|
-
* - on success returns the transformed `resolvedData`.
|
|
24
|
-
*/
|
|
25
3
|
async function runHookPipeline(args) {
|
|
26
4
|
const { operation, listName, listConfig, inputData, item, context } = args;
|
|
27
|
-
// ── Phase 1: list-level resolveInput ──────────────────────────────────────
|
|
28
5
|
let resolvedData = await executeResolveInput(listConfig.hooks, operation === 'create'
|
|
29
6
|
? {
|
|
30
7
|
listKey: listName,
|
|
@@ -42,18 +19,13 @@ async function runHookPipeline(args) {
|
|
|
42
19
|
item,
|
|
43
20
|
context,
|
|
44
21
|
});
|
|
45
|
-
// ── Phase 1.5: field-level resolveInput (e.g. hash passwords) ──────────────
|
|
46
22
|
resolvedData = await executeFieldResolveInputHooks(inputData, resolvedData, listConfig.fields, operation, context, listName, item);
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
// and BEFORE validation — but only when the field was OMITTED, so a
|
|
51
|
-
// required-with-default field passes `isRequired` instead of failing it.
|
|
52
|
-
// Update is untouched (no default injection on update).
|
|
23
|
+
// Must run after resolveInput hooks and before validation, so a
|
|
24
|
+
// required-with-default field passes `isRequired` on an omitted input
|
|
25
|
+
// instead of failing it (see applyCreateDefaults, issue #615).
|
|
53
26
|
if (operation === 'create') {
|
|
54
27
|
resolvedData = applyCreateDefaults(resolvedData, listConfig.fields);
|
|
55
28
|
}
|
|
56
|
-
// ── Phase 2: list-level validate ──────────────────────────────────────────
|
|
57
29
|
await executeValidate(listConfig.hooks, operation === 'create'
|
|
58
30
|
? {
|
|
59
31
|
listKey: listName,
|
|
@@ -71,24 +43,19 @@ async function runHookPipeline(args) {
|
|
|
71
43
|
item,
|
|
72
44
|
context,
|
|
73
45
|
});
|
|
74
|
-
// ── Phase 2.5: field-level validate ───────────────────────────────────────
|
|
75
46
|
await executeFieldValidateHooks(inputData, resolvedData, listConfig.fields, operation, context, listName, item);
|
|
76
|
-
//
|
|
77
|
-
//
|
|
47
|
+
// Unlike an access-denied `context.db` operation, a validation failure
|
|
48
|
+
// throws rather than returning null/[] — validation is never silent.
|
|
78
49
|
const validation = validateFieldRules(resolvedData, listConfig.fields, operation);
|
|
79
50
|
if (validation.errors.length > 0) {
|
|
80
51
|
throw new ValidationError(validation.errors, validation.fieldErrors);
|
|
81
52
|
}
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// the field's own write access (see `splitMultiColumnFields`).
|
|
53
|
+
// Runs only once the logical value has passed validation above (#789) — a
|
|
54
|
+
// multi-column field validated after splitting could see an unrecognised
|
|
55
|
+
// value silently become null/undefined columns instead of throwing.
|
|
86
56
|
resolvedData = await splitMultiColumnFields(inputData, resolvedData, listConfig.fields, operation, context, item);
|
|
87
57
|
return { resolvedData };
|
|
88
58
|
}
|
|
89
|
-
/**
|
|
90
|
-
* The default Hook Pipeline instance used by the Write Pipeline.
|
|
91
|
-
*/
|
|
92
59
|
export const hookPipeline = {
|
|
93
60
|
run: runHookPipeline,
|
|
94
61
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook-pipeline.js","sourceRoot":"","sources":["../../src/context/hook-pipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,6BAA6B,EAC7B,yBAAyB,EACzB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"hook-pipeline.js","sourceRoot":"","sources":["../../src/context/hook-pipeline.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,6BAA6B,EAC7B,yBAAyB,EACzB,kBAAkB,EAClB,sBAAsB,EACtB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AA2BzD,KAAK,UAAU,eAAe,CAAC,IAAsB;IACnD,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAE1E,IAAI,YAAY,GAAG,MAAM,mBAAmB,CAC1C,UAAU,CAAC,KAAK,EAChB,SAAS,KAAK,QAAQ;QACpB,CAAC,CAAC;YACE,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,QAAQ;YACnB,SAAS;YACT,YAAY,EAAE,SAAS;YACvB,IAAI,EAAE,SAAS;YACf,OAAO;SACR;QACH,CAAC,CAAC;YACE,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,QAAQ;YACnB,SAAS;YACT,YAAY,EAAE,SAAS;YACvB,IAAI;YACJ,OAAO;SACR,CACN,CAAA;IAED,YAAY,GAAG,MAAM,6BAA6B,CAChD,SAAS,EACT,YAAY,EACZ,UAAU,CAAC,MAAM,EACjB,SAAS,EACT,OAAO,EACP,QAAQ,EACR,IAAI,CACL,CAAA;IAED,gEAAgE;IAChE,sEAAsE;IACtE,+DAA+D;IAC/D,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,YAAY,GAAG,mBAAmB,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,CAAC,CAAA;IACrE,CAAC;IAED,MAAM,eAAe,CACnB,UAAU,CAAC,KAAK,EAChB,SAAS,KAAK,QAAQ;QACpB,CAAC,CAAC;YACE,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,QAAQ;YACnB,SAAS;YACT,YAAY;YACZ,IAAI,EAAE,SAAS;YACf,OAAO;SACR;QACH,CAAC,CAAC;YACE,OAAO,EAAE,QAAQ;YACjB,SAAS,EAAE,QAAQ;YACnB,SAAS;YACT,YAAY;YACZ,IAAI;YACJ,OAAO;SACR,CACN,CAAA;IAED,MAAM,yBAAyB,CAC7B,SAAS,EACT,YAAY,EACZ,UAAU,CAAC,MAAM,EACjB,SAAS,EACT,OAAO,EACP,QAAQ,EACR,IAAI,CACL,CAAA;IAED,uEAAuE;IACvE,qEAAqE;IACrE,MAAM,UAAU,GAAG,kBAAkB,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACjF,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,eAAe,CAAC,UAAU,CAAC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,CAAA;IACtE,CAAC;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,oEAAoE;IACpE,YAAY,GAAG,MAAM,sBAAsB,CACzC,SAAS,EACT,YAAY,EACZ,UAAU,CAAC,MAAM,EACjB,SAAS,EACT,OAAO,EACP,IAAI,CACL,CAAA;IAED,OAAO,EAAE,YAAY,EAAE,CAAA;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAiB;IACxC,GAAG,EAAE,eAAe;CACrB,CAAA"}
|
package/dist/context/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OpenSaasConfig } from '../config/types.js';
|
|
2
2
|
import type { Session, AccessContext, AccessControlledDB, StorageUtils } from '../access/index.js';
|
|
3
3
|
import type { PrismaClientLike } from '../access/types.js';
|
|
4
|
+
import { TransactionRegistry } from '../access/transaction-registry.js';
|
|
4
5
|
export type ServerActionProps = {
|
|
5
6
|
listKey: string;
|
|
6
7
|
action: 'create';
|
|
@@ -51,12 +52,8 @@ export type ServerActionProps = {
|
|
|
51
52
|
selectedIds?: string[];
|
|
52
53
|
};
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* Mirrors Prisma's `TransactionIsolationLevel`. The level passed to
|
|
57
|
-
* {@link StackContext.transaction} is forwarded to the underlying interactive
|
|
58
|
-
* transaction; provider support varies (e.g. `Serializable` is supported by
|
|
59
|
-
* PostgreSQL — required for the concurrency-sensitive capacity-gate pattern).
|
|
55
|
+
* Mirrors Prisma's `TransactionIsolationLevel`. Provider support varies —
|
|
56
|
+
* e.g. `Serializable` requires PostgreSQL.
|
|
60
57
|
*/
|
|
61
58
|
export type TransactionIsolationLevel = 'ReadUncommitted' | 'ReadCommitted' | 'RepeatableRead' | 'Serializable' | 'Snapshot';
|
|
62
59
|
/**
|
|
@@ -111,15 +108,7 @@ export interface StackContext<TPrisma extends PrismaClientLike = PrismaClientLik
|
|
|
111
108
|
sudo: () => StackContext<TPrisma>;
|
|
112
109
|
_isSudo: boolean;
|
|
113
110
|
}
|
|
114
|
-
|
|
115
|
-
* Create an access-controlled context
|
|
116
|
-
*
|
|
117
|
-
* @param config - OpenSaas configuration
|
|
118
|
-
* @param prisma - Your Prisma client instance (pass as generic for type safety)
|
|
119
|
-
* @param session - Current session object (or null if not authenticated)
|
|
120
|
-
* @param storage - Optional storage utilities (uploadFile, uploadImage, deleteFile, deleteImage)
|
|
121
|
-
*/
|
|
122
|
-
export declare function getContext<TConfig extends OpenSaasConfig, TPrisma extends PrismaClientLike = PrismaClientLike>(config: TConfig, prisma: TPrisma, session: Session | null, storage?: StorageUtils, _isSudo?: boolean, _sharedPlugins?: Record<string, unknown>): StackContext<TPrisma>;
|
|
111
|
+
export declare function getContext<TConfig extends OpenSaasConfig, TPrisma extends PrismaClientLike = PrismaClientLike>(config: TConfig, prisma: TPrisma, session: Session | null, storage?: StorageUtils, _isSudo?: boolean, _sharedPlugins?: Record<string, unknown>, _transactionOwner?: TransactionRegistry): StackContext<TPrisma>;
|
|
123
112
|
/**
|
|
124
113
|
* Populate `target` with the access-controlled CRUD operations for every list,
|
|
125
114
|
* each bound to `prisma` and `context`. Used both by {@link getContext} (at
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,oBAAoB,CAAA;AACpE,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/context/index.ts"],"names":[],"mappings":"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"}
|