@opensaas/stack-core 0.37.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 +191 -0
- package/CLAUDE.md +42 -3
- package/dist/access/access-filter.d.ts +70 -113
- package/dist/access/access-filter.d.ts.map +1 -1
- package/dist/access/access-filter.js +183 -210
- package/dist/access/access-filter.js.map +1 -1
- package/dist/access/access-filter.test.js +148 -188
- package/dist/access/access-filter.test.js.map +1 -1
- package/dist/access/declared-dependencies.d.ts +66 -26
- package/dist/access/declared-dependencies.d.ts.map +1 -1
- package/dist/access/declared-dependencies.js +62 -17
- package/dist/access/declared-dependencies.js.map +1 -1
- package/dist/access/declared-dependencies.test.d.ts +2 -0
- package/dist/access/declared-dependencies.test.d.ts.map +1 -0
- package/dist/access/declared-dependencies.test.js +226 -0
- package/dist/access/declared-dependencies.test.js.map +1 -0
- package/dist/access/depth-limits.d.ts +8 -7
- package/dist/access/depth-limits.d.ts.map +1 -1
- package/dist/access/depth-limits.js +8 -7
- package/dist/access/depth-limits.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 +61 -8
- package/dist/access/errors.d.ts.map +1 -1
- package/dist/access/errors.js +91 -12
- 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 +2 -5
- package/dist/access/field-visibility.d.ts.map +1 -1
- package/dist/access/field-visibility.js +89 -41
- package/dist/access/field-visibility.js.map +1 -1
- package/dist/access/index.d.ts +6 -3
- 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 +1 -1
- 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 +223 -215
- 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 +55 -227
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/index.js +41 -117
- package/dist/query/index.js.map +1 -1
- package/dist/query/relationship-options.d.ts +1 -1
- package/dist/query/relationship-options.js +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.test.ts +205 -275
- package/src/access/access-filter.ts +236 -272
- package/src/access/declared-dependencies.test.ts +277 -0
- package/src/access/declared-dependencies.ts +117 -37
- package/src/access/depth-limits.ts +8 -7
- package/src/access/engine.ts +0 -25
- package/src/access/errors.ts +97 -12
- 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 +99 -45
- package/src/access/index.ts +17 -5
- package/src/access/query-validation.ts +359 -0
- package/src/access/relationship-count.ts +3 -11
- 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 +311 -276
- 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 +82 -235
- package/src/query/relationship-options.ts +1 -1
- package/src/utils/password.ts +7 -76
- package/src/validation/field-config.ts +0 -6
- package/src/validation/schema.ts +0 -10
- package/tests/access-relationships.test.ts +18 -16
- package/tests/computed-field-selective-evaluation.test.ts +418 -0
- package/tests/context.test.ts +727 -1
- package/tests/field-types.test.ts +222 -0
- package/tests/mcp-handler.test.ts +34 -0
- package/tests/needs-declared-dependencies.test.ts +7 -4
- package/tests/resolve-chain.test.ts +11 -11
- package/tests/transaction-boundary-outermost.test.ts +530 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,63 +1,34 @@
|
|
|
1
1
|
import type { FieldConfig } from '../config/types.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Fills a field's `defaultValue` into `resolvedData` for CREATE when the field
|
|
5
|
+
* was omitted, run after resolveInput hooks and before validation. Shared by
|
|
6
|
+
* the top-level create path (Hook Pipeline) and the nested-relation create path.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* fails `isRequired` validation on an omitted input even though a default exists.
|
|
12
|
-
*
|
|
13
|
-
* This helper closes that gap: in the resolve phase (after `resolveInput` hooks,
|
|
14
|
-
* before validation) it fills `resolvedData[field]` with the field's
|
|
15
|
-
* `defaultValue` ONLY when the field was OMITTED (value is `undefined`). It is a
|
|
16
|
-
* SINGLE shared mechanism used by both the top-level create path (Hook Pipeline)
|
|
17
|
-
* and the nested-relation create path.
|
|
18
|
-
*
|
|
19
|
-
* Guard rails (each acceptance-criteria-driven):
|
|
20
|
-
* - CREATE only. Update never injects defaults for omitted fields (the caller
|
|
21
|
-
* only invokes this for `operation === 'create'`).
|
|
22
|
-
* - Explicitly-provided values are preserved. A key present in `resolvedData`
|
|
23
|
-
* — INCLUDING an explicit `null` — is left untouched; only `undefined`
|
|
24
|
-
* (omitted) keys are filled.
|
|
25
|
-
* - Virtual, system (`id`/`createdAt`/`updatedAt`) and relationship fields are
|
|
26
|
-
* skipped — they have no scalar `defaultValue` to inject and relationships
|
|
27
|
-
* carry connect/create payloads rather than literal defaults.
|
|
28
|
-
* - The timestamp `{ kind: 'now' }` sentinel is NOT injected: it is not a
|
|
29
|
-
* literal value but a request for the DB-level `@default(now())`, which still
|
|
30
|
-
* applies at write time. Injecting the sentinel object would corrupt the
|
|
31
|
-
* payload. (A concrete `Date` default is a real literal and IS injected.)
|
|
32
|
-
*
|
|
33
|
-
* The function mutates and returns `resolvedData` (consistent with the other
|
|
34
|
-
* resolve-phase helpers that thread `resolvedData` through the pipeline).
|
|
8
|
+
* Prisma only realises `defaultValue` as `@default(...)` at the database write,
|
|
9
|
+
* which is after this pipeline's validation phase — so without this, a
|
|
10
|
+
* required field with a default (e.g. `select({ validation: { isRequired: true },
|
|
11
|
+
* defaultValue: 'X' })`) fails `isRequired` on an omitted input. See issue #615.
|
|
35
12
|
*/
|
|
36
13
|
export function applyCreateDefaults(
|
|
37
14
|
resolvedData: Record<string, unknown>,
|
|
38
15
|
fieldConfigs: Record<string, FieldConfig>,
|
|
39
16
|
): Record<string, unknown> {
|
|
40
17
|
for (const [fieldKey, fieldConfig] of Object.entries(fieldConfigs)) {
|
|
41
|
-
// Skip virtual fields — not stored in the database.
|
|
42
18
|
if (fieldConfig.virtual) continue
|
|
43
|
-
|
|
44
|
-
// Skip system fields — always managed by the framework/DB.
|
|
45
19
|
if (fieldKey === 'id' || fieldKey === 'createdAt' || fieldKey === 'updatedAt') continue
|
|
46
|
-
|
|
47
|
-
// Skip relationships — they carry connect/create payloads, not literal defaults.
|
|
20
|
+
// Relationships carry connect/create payloads, not literal defaults.
|
|
48
21
|
if (fieldConfig.type === 'relationship') continue
|
|
49
|
-
|
|
50
|
-
// No declared default → nothing to inject.
|
|
51
22
|
if (!('defaultValue' in fieldConfig) || fieldConfig.defaultValue === undefined) continue
|
|
52
23
|
|
|
53
|
-
// Only fill OMITTED keys
|
|
54
|
-
// `null
|
|
24
|
+
// Only fill OMITTED (`undefined`) keys — an explicitly-provided value,
|
|
25
|
+
// including explicit `null`, must survive untouched.
|
|
55
26
|
if (resolvedData[fieldKey] !== undefined) continue
|
|
56
27
|
|
|
57
28
|
const defaultValue = fieldConfig.defaultValue
|
|
58
29
|
|
|
59
|
-
//
|
|
60
|
-
//
|
|
30
|
+
// `{ kind: 'now' }` requests the DB-level `@default(now())`, not a literal
|
|
31
|
+
// value — injecting the sentinel object itself would corrupt the payload.
|
|
61
32
|
if (isNowSentinel(defaultValue)) continue
|
|
62
33
|
|
|
63
34
|
resolvedData[fieldKey] = defaultValue
|
|
@@ -66,9 +37,6 @@ export function applyCreateDefaults(
|
|
|
66
37
|
return resolvedData
|
|
67
38
|
}
|
|
68
39
|
|
|
69
|
-
/**
|
|
70
|
-
* Detect the timestamp `{ kind: 'now' }` default sentinel.
|
|
71
|
-
*/
|
|
72
40
|
function isNowSentinel(value: unknown): boolean {
|
|
73
41
|
return (
|
|
74
42
|
typeof value === 'object' &&
|
|
@@ -12,78 +12,33 @@ import {
|
|
|
12
12
|
import { applyCreateDefaults } from './apply-defaults.js'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* It is THE place where input is shaped and validated; it throws
|
|
21
|
-
* {@link ValidationError} on failure exactly as before (validate hooks via
|
|
22
|
-
* `addValidationError`, then `validateFieldRules`) — validation is never silent.
|
|
23
|
-
*
|
|
24
|
-
* Side-effect hooks (`beforeOperation`/`afterOperation`), operation-level access,
|
|
25
|
-
* writable-field filtering, nested operations, persistence and Field Visibility
|
|
26
|
-
* are deliberately OUT of this span — they stay in the Write Pipeline. See the
|
|
27
|
-
* "Hook Pipeline" and "Write Pipeline" glossary terms in CONTEXT.md.
|
|
15
|
+
* The transform+validate span of a write. See the "Hook Pipeline" and "Write
|
|
16
|
+
* Pipeline" glossary terms in CONTEXT.md for the full phase order and the
|
|
17
|
+
* boundary with the Write Pipeline (which owns side-effect hooks, access,
|
|
18
|
+
* writable-field filtering, nested operations, persistence, Field Visibility).
|
|
28
19
|
*/
|
|
29
20
|
|
|
30
|
-
/**
|
|
31
|
-
* Arguments for one transform+validate span. Only the create/update operations
|
|
32
|
-
* run this span (delete skips the input-shaping phases entirely).
|
|
33
|
-
*/
|
|
34
21
|
export interface HookPipelineArgs {
|
|
35
22
|
operation: 'create' | 'update'
|
|
36
23
|
listName: string
|
|
37
24
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- ListConfig must accept any TypeInfo
|
|
38
25
|
listConfig: ListConfig<any>
|
|
39
|
-
/** The original input data for the write. */
|
|
40
26
|
inputData: Record<string, unknown>
|
|
41
|
-
/** The existing row for update; `undefined` for create. */
|
|
42
27
|
item: Record<string, unknown> | undefined
|
|
43
28
|
context: AccessContext
|
|
44
29
|
}
|
|
45
30
|
|
|
46
|
-
/**
|
|
47
|
-
* Result of a transform+validate span: the fully-resolved write data after the
|
|
48
|
-
* resolveInput hooks have run and all validation has passed.
|
|
49
|
-
*/
|
|
50
31
|
export interface HookPipelineResult {
|
|
51
32
|
resolvedData: Record<string, unknown>
|
|
52
33
|
}
|
|
53
34
|
|
|
54
|
-
/**
|
|
55
|
-
* The transform+validate span, owning order + `resolvedData` threading.
|
|
56
|
-
*/
|
|
57
35
|
export interface HookPipeline {
|
|
58
36
|
run(args: HookPipelineArgs): Promise<HookPipelineResult>
|
|
59
37
|
}
|
|
60
38
|
|
|
61
|
-
/**
|
|
62
|
-
* Run the transform+validate span once.
|
|
63
|
-
*
|
|
64
|
-
* Phase order (owned here, in one place):
|
|
65
|
-
* list `resolveInput`
|
|
66
|
-
* → field `resolveInput`
|
|
67
|
-
* → list `validate`
|
|
68
|
-
* → field `validate`
|
|
69
|
-
* → built-in field rules (`validateFieldRules`)
|
|
70
|
-
* → split multi-column fields (`splitMultiColumnFields`)
|
|
71
|
-
*
|
|
72
|
-
* Contract preserved exactly:
|
|
73
|
-
* - `resolvedData` starts as `inputData` and is threaded through each phase;
|
|
74
|
-
* - validate hooks report failures via `addValidationError` → THROW
|
|
75
|
-
* `ValidationError` (never silent);
|
|
76
|
-
* - built-in field rule failures THROW `ValidationError`;
|
|
77
|
-
* - a multi-column field (e.g. storage image()/file() in Keystone-parity
|
|
78
|
-
* mode) is validated under its LOGICAL key BEFORE it is split into
|
|
79
|
-
* physical columns (#789) — an unrecognised value throws instead of being
|
|
80
|
-
* silently split into null/undefined columns;
|
|
81
|
-
* - on success returns the transformed `resolvedData`.
|
|
82
|
-
*/
|
|
83
39
|
async function runHookPipeline(args: HookPipelineArgs): Promise<HookPipelineResult> {
|
|
84
40
|
const { operation, listName, listConfig, inputData, item, context } = args
|
|
85
41
|
|
|
86
|
-
// ── Phase 1: list-level resolveInput ──────────────────────────────────────
|
|
87
42
|
let resolvedData = await executeResolveInput(
|
|
88
43
|
listConfig.hooks,
|
|
89
44
|
operation === 'create'
|
|
@@ -105,7 +60,6 @@ async function runHookPipeline(args: HookPipelineArgs): Promise<HookPipelineResu
|
|
|
105
60
|
},
|
|
106
61
|
)
|
|
107
62
|
|
|
108
|
-
// ── Phase 1.5: field-level resolveInput (e.g. hash passwords) ──────────────
|
|
109
63
|
resolvedData = await executeFieldResolveInputHooks(
|
|
110
64
|
inputData,
|
|
111
65
|
resolvedData,
|
|
@@ -116,17 +70,13 @@ async function runHookPipeline(args: HookPipelineArgs): Promise<HookPipelineResu
|
|
|
116
70
|
item,
|
|
117
71
|
)
|
|
118
72
|
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
// and BEFORE validation — but only when the field was OMITTED, so a
|
|
123
|
-
// required-with-default field passes `isRequired` instead of failing it.
|
|
124
|
-
// Update is untouched (no default injection on update).
|
|
73
|
+
// Must run after resolveInput hooks and before validation, so a
|
|
74
|
+
// required-with-default field passes `isRequired` on an omitted input
|
|
75
|
+
// instead of failing it (see applyCreateDefaults, issue #615).
|
|
125
76
|
if (operation === 'create') {
|
|
126
77
|
resolvedData = applyCreateDefaults(resolvedData, listConfig.fields)
|
|
127
78
|
}
|
|
128
79
|
|
|
129
|
-
// ── Phase 2: list-level validate ──────────────────────────────────────────
|
|
130
80
|
await executeValidate(
|
|
131
81
|
listConfig.hooks,
|
|
132
82
|
operation === 'create'
|
|
@@ -148,7 +98,6 @@ async function runHookPipeline(args: HookPipelineArgs): Promise<HookPipelineResu
|
|
|
148
98
|
},
|
|
149
99
|
)
|
|
150
100
|
|
|
151
|
-
// ── Phase 2.5: field-level validate ───────────────────────────────────────
|
|
152
101
|
await executeFieldValidateHooks(
|
|
153
102
|
inputData,
|
|
154
103
|
resolvedData,
|
|
@@ -159,17 +108,16 @@ async function runHookPipeline(args: HookPipelineArgs): Promise<HookPipelineResu
|
|
|
159
108
|
item,
|
|
160
109
|
)
|
|
161
110
|
|
|
162
|
-
//
|
|
163
|
-
//
|
|
111
|
+
// Unlike an access-denied `context.db` operation, a validation failure
|
|
112
|
+
// throws rather than returning null/[] — validation is never silent.
|
|
164
113
|
const validation = validateFieldRules(resolvedData, listConfig.fields, operation)
|
|
165
114
|
if (validation.errors.length > 0) {
|
|
166
115
|
throw new ValidationError(validation.errors, validation.fieldErrors)
|
|
167
116
|
}
|
|
168
117
|
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
// the field's own write access (see `splitMultiColumnFields`).
|
|
118
|
+
// Runs only once the logical value has passed validation above (#789) — a
|
|
119
|
+
// multi-column field validated after splitting could see an unrecognised
|
|
120
|
+
// value silently become null/undefined columns instead of throwing.
|
|
173
121
|
resolvedData = await splitMultiColumnFields(
|
|
174
122
|
inputData,
|
|
175
123
|
resolvedData,
|
|
@@ -182,9 +130,6 @@ async function runHookPipeline(args: HookPipelineArgs): Promise<HookPipelineResu
|
|
|
182
130
|
return { resolvedData }
|
|
183
131
|
}
|
|
184
132
|
|
|
185
|
-
/**
|
|
186
|
-
* The default Hook Pipeline instance used by the Write Pipeline.
|
|
187
|
-
*/
|
|
188
133
|
export const hookPipeline: HookPipeline = {
|
|
189
134
|
run: runHookPipeline,
|
|
190
135
|
}
|