@aletheia-dev/collection-flow 0.0.0-stage → 0.1.1
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/CHANGELOG.md +22 -0
- package/LICENSE +202 -0
- package/README.md +15 -2
- package/dist/index.cjs +500 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +79 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +466 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Field, FlowStep, CollectionFlowBody, ValidationReport } from '@aletheia-dev/core';
|
|
2
|
+
export { Condition, evaluateCondition } from '@aletheia-dev/core';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
/** A field without `visibleWhen` is always visible; otherwise its condition is evaluated against `data`. */
|
|
6
|
+
declare function isFieldVisible(field: Field, data: Record<string, unknown>): boolean;
|
|
7
|
+
/** Fields of `step` that are currently visible given `data`, in declaration order. */
|
|
8
|
+
declare function visibleFields(step: FlowStep, data: Record<string, unknown>): Field[];
|
|
9
|
+
|
|
10
|
+
/** Looks up a step by id. Throws a ValidationError when the flow has no such step. */
|
|
11
|
+
declare function getStep(flow: CollectionFlowBody, stepId: string): FlowStep;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves the step that follows `stepId` for the given submission data.
|
|
14
|
+
* The first branch whose condition holds wins, then `step.next`; undefined means terminal.
|
|
15
|
+
*/
|
|
16
|
+
declare function nextStepId(flow: CollectionFlowBody, stepId: string, data: Record<string, unknown>): string | undefined;
|
|
17
|
+
/** True when no step follows `stepId` for the given data. */
|
|
18
|
+
declare function isTerminal(flow: CollectionFlowBody, stepId: string, data: Record<string, unknown>): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The path a submission with `data` takes through the flow, starting at the entry step.
|
|
21
|
+
* Throws a ValidationError if the path revisits a step.
|
|
22
|
+
*/
|
|
23
|
+
declare function orderedSteps(flow: CollectionFlowBody, data: Record<string, unknown>): FlowStep[];
|
|
24
|
+
|
|
25
|
+
/** Zod schema for a single field's value, derived from its type, validation and required flag. */
|
|
26
|
+
declare function fieldSchema(field: Field): z.ZodType;
|
|
27
|
+
/** Object schema for the fields of `step` that are visible given `data`. Unknown keys are stripped. */
|
|
28
|
+
declare function buildZodForStep(step: FlowStep, data: Record<string, unknown>): z.ZodObject;
|
|
29
|
+
/** Object schema covering every step on the path `data` takes through `flow`. */
|
|
30
|
+
declare function buildZodForFlow(flow: CollectionFlowBody, data: Record<string, unknown>): z.ZodObject;
|
|
31
|
+
type SubmissionIssue = {
|
|
32
|
+
path: string;
|
|
33
|
+
message: string;
|
|
34
|
+
};
|
|
35
|
+
type ValidateSubmissionResult = {
|
|
36
|
+
success: true;
|
|
37
|
+
data: Record<string, unknown>;
|
|
38
|
+
} | {
|
|
39
|
+
success: false;
|
|
40
|
+
issues: SubmissionIssue[];
|
|
41
|
+
};
|
|
42
|
+
/** Validates a full submission against the steps it would traverse. */
|
|
43
|
+
declare function validateSubmission(flow: CollectionFlowBody, data: Record<string, unknown>): ValidateSubmissionResult;
|
|
44
|
+
|
|
45
|
+
interface FileFieldRef {
|
|
46
|
+
fieldKey: string;
|
|
47
|
+
fileId: string;
|
|
48
|
+
}
|
|
49
|
+
/** True when `flow` has a `file` field with `key` on any step (visibility and path ignored). */
|
|
50
|
+
declare function isFileField(flow: CollectionFlowBody, key: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* The `{ fileId }` values held by the visible `file` fields on the path `data` takes through
|
|
53
|
+
* `flow`, in step and field order. Fields without a well-formed value are skipped: shape
|
|
54
|
+
* validation is `validateSubmission`'s job, this only finds the documents to verify.
|
|
55
|
+
*/
|
|
56
|
+
declare function fileFieldValues(flow: CollectionFlowBody, data: Record<string, unknown>): FileFieldRef[];
|
|
57
|
+
|
|
58
|
+
/** A minimal KYB flow: business details, then UBO details unless the entity is a sole trader, then review. */
|
|
59
|
+
declare const kybBasicFlow: CollectionFlowBody;
|
|
60
|
+
declare const exampleFlows: Record<string, CollectionFlowBody>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Full validation of a collection-flow draft without saving it (`POST
|
|
64
|
+
* /collection-flows/validate`). Pure. Every problem becomes a `DefinitionIssue` with the step id
|
|
65
|
+
* and, where it has one, the field key, so the builder can mark the step and the field.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validates a flow body (`{ entryStepId, steps, linkTtlSeconds? }`). `prefix` is prepended to
|
|
70
|
+
* every issue path (e.g. `definition` when the body sits inside an upsert input).
|
|
71
|
+
*/
|
|
72
|
+
declare function validateFlowBody(body: unknown, prefix?: string): ValidationReport;
|
|
73
|
+
/**
|
|
74
|
+
* Validates an `UpsertCollectionFlowInput`-shaped body: the envelope (`name`, `description`) and
|
|
75
|
+
* the definition, with definition issue paths under `definition.`.
|
|
76
|
+
*/
|
|
77
|
+
declare function validateFlowInput(input: unknown): ValidationReport;
|
|
78
|
+
|
|
79
|
+
export { type FileFieldRef, type SubmissionIssue, type ValidateSubmissionResult, buildZodForFlow, buildZodForStep, exampleFlows, fieldSchema, fileFieldValues, getStep, isFieldVisible, isFileField, isTerminal, kybBasicFlow, nextStepId, orderedSteps, validateFlowBody, validateFlowInput, validateSubmission, visibleFields };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
// src/visibility.ts
|
|
2
|
+
import { evaluateCondition } from "@aletheia-dev/core";
|
|
3
|
+
function isFieldVisible(field, data) {
|
|
4
|
+
if (!field.visibleWhen) return true;
|
|
5
|
+
return evaluateCondition(field.visibleWhen, data);
|
|
6
|
+
}
|
|
7
|
+
function visibleFields(step, data) {
|
|
8
|
+
return step.fields.filter((field) => isFieldVisible(field, data));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// src/navigation.ts
|
|
12
|
+
import {
|
|
13
|
+
evaluateCondition as evaluateCondition2,
|
|
14
|
+
ValidationError
|
|
15
|
+
} from "@aletheia-dev/core";
|
|
16
|
+
function getStep(flow, stepId) {
|
|
17
|
+
const step = flow.steps.find((s) => s.id === stepId);
|
|
18
|
+
if (!step) throw new ValidationError(`unknown step "${stepId}"`);
|
|
19
|
+
return step;
|
|
20
|
+
}
|
|
21
|
+
function nextStepId(flow, stepId, data) {
|
|
22
|
+
const step = getStep(flow, stepId);
|
|
23
|
+
for (const branch of step.branches ?? []) {
|
|
24
|
+
if (evaluateCondition2(branch.when, data)) return branch.next;
|
|
25
|
+
}
|
|
26
|
+
return step.next;
|
|
27
|
+
}
|
|
28
|
+
function isTerminal(flow, stepId, data) {
|
|
29
|
+
return nextStepId(flow, stepId, data) === void 0;
|
|
30
|
+
}
|
|
31
|
+
function orderedSteps(flow, data) {
|
|
32
|
+
const visited = /* @__PURE__ */ new Set();
|
|
33
|
+
const steps = [];
|
|
34
|
+
let current = flow.entryStepId;
|
|
35
|
+
while (current !== void 0) {
|
|
36
|
+
if (visited.has(current)) {
|
|
37
|
+
throw new ValidationError(`cycle detected at step "${current}"`, { stepId: current });
|
|
38
|
+
}
|
|
39
|
+
visited.add(current);
|
|
40
|
+
steps.push(getStep(flow, current));
|
|
41
|
+
current = nextStepId(flow, current, data);
|
|
42
|
+
}
|
|
43
|
+
return steps;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/validation.ts
|
|
47
|
+
import { z } from "zod";
|
|
48
|
+
import {
|
|
49
|
+
ValidationError as ValidationError2
|
|
50
|
+
} from "@aletheia-dev/core";
|
|
51
|
+
var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
52
|
+
function dateSchema() {
|
|
53
|
+
const iso = z.iso;
|
|
54
|
+
return iso?.date ? iso.date() : z.string().regex(ISO_DATE, "expected YYYY-MM-DD");
|
|
55
|
+
}
|
|
56
|
+
function textSchema(field) {
|
|
57
|
+
let schema = z.string();
|
|
58
|
+
const { min, max, pattern } = field.validation ?? {};
|
|
59
|
+
if (field.required) schema = schema.min(1, `${field.label} is required`);
|
|
60
|
+
if (min !== void 0) schema = schema.min(min);
|
|
61
|
+
if (max !== void 0) schema = schema.max(max);
|
|
62
|
+
if (pattern !== void 0) schema = schema.regex(new RegExp(pattern));
|
|
63
|
+
return schema;
|
|
64
|
+
}
|
|
65
|
+
function numberSchema(field) {
|
|
66
|
+
let schema = z.coerce.number();
|
|
67
|
+
const { min, max } = field.validation ?? {};
|
|
68
|
+
if (min !== void 0) schema = schema.min(min);
|
|
69
|
+
if (max !== void 0) schema = schema.max(max);
|
|
70
|
+
return schema;
|
|
71
|
+
}
|
|
72
|
+
function selectSchema(field) {
|
|
73
|
+
const values = (field.options ?? []).map((o) => o.value);
|
|
74
|
+
if (values.length === 0) {
|
|
75
|
+
throw new ValidationError2(`select field "${field.key}" has no options`, { key: field.key });
|
|
76
|
+
}
|
|
77
|
+
return z.enum(values);
|
|
78
|
+
}
|
|
79
|
+
function baseSchema(field) {
|
|
80
|
+
switch (field.type) {
|
|
81
|
+
case "text":
|
|
82
|
+
return textSchema(field);
|
|
83
|
+
case "email":
|
|
84
|
+
return z.email();
|
|
85
|
+
case "number":
|
|
86
|
+
return numberSchema(field);
|
|
87
|
+
case "select":
|
|
88
|
+
return selectSchema(field);
|
|
89
|
+
case "date":
|
|
90
|
+
return dateSchema();
|
|
91
|
+
case "boolean":
|
|
92
|
+
return z.boolean();
|
|
93
|
+
case "file":
|
|
94
|
+
return z.object({ fileId: z.string(), name: z.string().optional() });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function fieldSchema(field) {
|
|
98
|
+
const schema = baseSchema(field);
|
|
99
|
+
return field.required ? schema : schema.optional();
|
|
100
|
+
}
|
|
101
|
+
function buildZodForStep(step, data) {
|
|
102
|
+
const shape = {};
|
|
103
|
+
for (const field of visibleFields(step, data)) shape[field.key] = fieldSchema(field);
|
|
104
|
+
return z.object(shape);
|
|
105
|
+
}
|
|
106
|
+
function buildZodForFlow(flow, data) {
|
|
107
|
+
let schema = z.object({});
|
|
108
|
+
for (const step of orderedSteps(flow, data))
|
|
109
|
+
schema = schema.extend(buildZodForStep(step, data).shape);
|
|
110
|
+
return schema;
|
|
111
|
+
}
|
|
112
|
+
function validateSubmission(flow, data) {
|
|
113
|
+
const result = buildZodForFlow(flow, data).safeParse(data);
|
|
114
|
+
if (result.success) return { success: true, data: result.data };
|
|
115
|
+
return {
|
|
116
|
+
success: false,
|
|
117
|
+
issues: result.error.issues.map((issue) => ({
|
|
118
|
+
path: issue.path.map(String).join("."),
|
|
119
|
+
message: issue.message
|
|
120
|
+
}))
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/files.ts
|
|
125
|
+
function isFileField(flow, key) {
|
|
126
|
+
return flow.steps.some(
|
|
127
|
+
(step) => step.fields.some((field) => field.key === key && field.type === "file")
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function fileIdOf(value) {
|
|
131
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
132
|
+
const fileId = value.fileId;
|
|
133
|
+
return typeof fileId === "string" && fileId.length > 0 ? fileId : void 0;
|
|
134
|
+
}
|
|
135
|
+
function fileFieldValues(flow, data) {
|
|
136
|
+
const refs = [];
|
|
137
|
+
for (const step of orderedSteps(flow, data)) {
|
|
138
|
+
for (const field of visibleFields(step, data)) {
|
|
139
|
+
if (field.type !== "file") continue;
|
|
140
|
+
const fileId = fileIdOf(data[field.key]);
|
|
141
|
+
if (fileId !== void 0) refs.push({ fieldKey: field.key, fileId });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return refs;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/examples/kyb-basic.ts
|
|
148
|
+
import { CollectionFlowBody } from "@aletheia-dev/core";
|
|
149
|
+
var countryOptions = [
|
|
150
|
+
{ value: "US", label: "United States" },
|
|
151
|
+
{ value: "GB", label: "United Kingdom" },
|
|
152
|
+
{ value: "DE", label: "Germany" },
|
|
153
|
+
{ value: "KP", label: "North Korea" }
|
|
154
|
+
];
|
|
155
|
+
var kybBasicFlow = CollectionFlowBody.parse({
|
|
156
|
+
entryStepId: "business",
|
|
157
|
+
steps: [
|
|
158
|
+
{
|
|
159
|
+
id: "business",
|
|
160
|
+
title: "Business details",
|
|
161
|
+
fields: [
|
|
162
|
+
{ key: "legalName", label: "Legal name", type: "text", required: true },
|
|
163
|
+
{
|
|
164
|
+
key: "country",
|
|
165
|
+
label: "Country of incorporation",
|
|
166
|
+
type: "select",
|
|
167
|
+
required: true,
|
|
168
|
+
options: countryOptions
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
key: "entityType",
|
|
172
|
+
label: "Entity type",
|
|
173
|
+
type: "select",
|
|
174
|
+
required: true,
|
|
175
|
+
options: [
|
|
176
|
+
{ value: "company", label: "Company" },
|
|
177
|
+
{ value: "sole_trader", label: "Sole trader" }
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: "website",
|
|
182
|
+
label: "Website",
|
|
183
|
+
type: "text",
|
|
184
|
+
validation: { pattern: "^(https?://)?([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(/\\S*)?$" }
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
branches: [{ when: { path: "entityType", op: "eq", value: "sole_trader" }, next: "review" }],
|
|
188
|
+
next: "ubo"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
id: "ubo",
|
|
192
|
+
title: "Ultimate beneficial owner",
|
|
193
|
+
fields: [
|
|
194
|
+
{ key: "uboName", label: "Full name", type: "text", required: true },
|
|
195
|
+
{
|
|
196
|
+
key: "uboOwnershipPct",
|
|
197
|
+
label: "Ownership percentage",
|
|
198
|
+
type: "number",
|
|
199
|
+
required: true,
|
|
200
|
+
validation: { min: 0, max: 100 }
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
key: "uboCountry",
|
|
204
|
+
label: "Country of residence",
|
|
205
|
+
type: "select",
|
|
206
|
+
required: true,
|
|
207
|
+
options: countryOptions
|
|
208
|
+
}
|
|
209
|
+
],
|
|
210
|
+
next: "review"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "review",
|
|
214
|
+
title: "Review",
|
|
215
|
+
fields: [
|
|
216
|
+
{
|
|
217
|
+
key: "confirm",
|
|
218
|
+
label: "I confirm the information is accurate",
|
|
219
|
+
type: "boolean",
|
|
220
|
+
required: true
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
});
|
|
226
|
+
var exampleFlows = { "kyb-basic": kybBasicFlow };
|
|
227
|
+
|
|
228
|
+
// src/index.ts
|
|
229
|
+
import { evaluateCondition as evaluateCondition3 } from "@aletheia-dev/core";
|
|
230
|
+
|
|
231
|
+
// src/validate-flow.ts
|
|
232
|
+
import {
|
|
233
|
+
FlowStep,
|
|
234
|
+
MAX_LINK_TTL_SECONDS,
|
|
235
|
+
UpsertCollectionFlowInput
|
|
236
|
+
} from "@aletheia-dev/core";
|
|
237
|
+
import { z as z2 } from "zod";
|
|
238
|
+
var isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
239
|
+
var joinPath = (...parts) => parts.filter((p) => p !== void 0 && p !== "").join(".");
|
|
240
|
+
var FlowShape = z2.object({
|
|
241
|
+
entryStepId: z2.string().min(1),
|
|
242
|
+
steps: z2.array(z2.unknown()).min(1),
|
|
243
|
+
linkTtlSeconds: z2.number().int().min(60).max(MAX_LINK_TTL_SECONDS).optional()
|
|
244
|
+
});
|
|
245
|
+
function schemaIssues(error, prefix, stepId, stepRaw) {
|
|
246
|
+
return error.issues.map((issue) => {
|
|
247
|
+
let fieldKey;
|
|
248
|
+
if (issue.path[0] === "fields" && typeof issue.path[1] === "number" && isRecord(stepRaw)) {
|
|
249
|
+
const field = Array.isArray(stepRaw.fields) ? stepRaw.fields[issue.path[1]] : void 0;
|
|
250
|
+
if (isRecord(field) && typeof field.key === "string") fieldKey = field.key;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
severity: "error",
|
|
254
|
+
path: joinPath(prefix, ...issue.path.map(String)),
|
|
255
|
+
...stepId !== void 0 && { stepId },
|
|
256
|
+
...fieldKey !== void 0 && { fieldKey },
|
|
257
|
+
code: "schema",
|
|
258
|
+
message: issue.message
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
function targetsOf(step) {
|
|
263
|
+
return [
|
|
264
|
+
...step.next !== void 0 ? [{ to: step.next, path: "next" }] : [],
|
|
265
|
+
...(step.branches ?? []).map((b, j) => ({ to: b.next, path: `branches.${j}.next` }))
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
function looseNode(raw) {
|
|
269
|
+
const strings = (items, prop) => (Array.isArray(items) ? items : []).map((item) => isRecord(item) ? item[prop] : void 0).filter((v) => typeof v === "string");
|
|
270
|
+
return {
|
|
271
|
+
keys: strings(raw.fields, "key"),
|
|
272
|
+
targets: [
|
|
273
|
+
...typeof raw.next === "string" ? [raw.next] : [],
|
|
274
|
+
...strings(raw.branches, "next")
|
|
275
|
+
]
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function answeredBefore(entry, nodes) {
|
|
279
|
+
const avail = /* @__PURE__ */ new Map([[entry, /* @__PURE__ */ new Set()]]);
|
|
280
|
+
let changed = true;
|
|
281
|
+
while (changed) {
|
|
282
|
+
changed = false;
|
|
283
|
+
for (const [id, keys] of [...avail]) {
|
|
284
|
+
const node = nodes.get(id);
|
|
285
|
+
const out = /* @__PURE__ */ new Set([...keys, ...node.keys]);
|
|
286
|
+
for (const to of node.targets) {
|
|
287
|
+
if (!nodes.has(to)) continue;
|
|
288
|
+
const target = avail.get(to) ?? /* @__PURE__ */ new Set();
|
|
289
|
+
const before = target.size;
|
|
290
|
+
for (const key of out) target.add(key);
|
|
291
|
+
if (!avail.has(to) || target.size !== before) {
|
|
292
|
+
avail.set(to, target);
|
|
293
|
+
changed = true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return avail;
|
|
299
|
+
}
|
|
300
|
+
function validateFlowBody(body, prefix = "") {
|
|
301
|
+
const issues = [];
|
|
302
|
+
const push = (issue) => issues.push(issue);
|
|
303
|
+
const shape = FlowShape.safeParse(body);
|
|
304
|
+
if (!shape.success) issues.push(...schemaIssues(shape.error, prefix));
|
|
305
|
+
const raw = isRecord(body) ? body : {};
|
|
306
|
+
const rawSteps = Array.isArray(raw.steps) ? raw.steps : [];
|
|
307
|
+
const steps = /* @__PURE__ */ new Map();
|
|
308
|
+
const indexOf = /* @__PURE__ */ new Map();
|
|
309
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
310
|
+
rawSteps.forEach((rawStep, i) => {
|
|
311
|
+
const base = joinPath(prefix, "steps", i);
|
|
312
|
+
const id = isRecord(rawStep) && typeof rawStep.id === "string" ? rawStep.id : void 0;
|
|
313
|
+
if (id !== void 0 && isRecord(rawStep) && !nodes.has(id)) nodes.set(id, looseNode(rawStep));
|
|
314
|
+
const parsed = FlowStep.safeParse(rawStep);
|
|
315
|
+
if (!parsed.success) {
|
|
316
|
+
issues.push(...schemaIssues(parsed.error, base, id, rawStep));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (steps.has(parsed.data.id)) {
|
|
320
|
+
push({
|
|
321
|
+
severity: "error",
|
|
322
|
+
path: joinPath(base, "id"),
|
|
323
|
+
stepId: parsed.data.id,
|
|
324
|
+
code: "duplicate_step_id",
|
|
325
|
+
message: `step id "${parsed.data.id}" is used more than once`
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
steps.set(parsed.data.id, parsed.data);
|
|
330
|
+
indexOf.set(parsed.data.id, i);
|
|
331
|
+
});
|
|
332
|
+
const entry = typeof raw.entryStepId === "string" ? raw.entryStepId : void 0;
|
|
333
|
+
if (entry && rawSteps.length > 0 && !nodes.has(entry)) {
|
|
334
|
+
push({
|
|
335
|
+
severity: "error",
|
|
336
|
+
path: joinPath(prefix, "entryStepId"),
|
|
337
|
+
code: "entry_missing",
|
|
338
|
+
message: `entry step "${entry}" does not exist`
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const fieldOwner = /* @__PURE__ */ new Map();
|
|
342
|
+
for (const [id, step] of steps) {
|
|
343
|
+
const base = joinPath(prefix, "steps", indexOf.get(id));
|
|
344
|
+
for (const target of targetsOf(step)) {
|
|
345
|
+
if (!nodes.has(target.to)) {
|
|
346
|
+
push({
|
|
347
|
+
severity: "error",
|
|
348
|
+
path: joinPath(base, target.path),
|
|
349
|
+
stepId: id,
|
|
350
|
+
code: "unknown_branch_target",
|
|
351
|
+
message: `step "${id}" points to unknown step "${target.to}"`
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
step.fields.forEach((field, j) => {
|
|
356
|
+
const fieldBase = joinPath(base, "fields", j);
|
|
357
|
+
const owner = fieldOwner.get(field.key);
|
|
358
|
+
if (owner !== void 0) {
|
|
359
|
+
push({
|
|
360
|
+
severity: "error",
|
|
361
|
+
path: joinPath(fieldBase, "key"),
|
|
362
|
+
stepId: id,
|
|
363
|
+
fieldKey: field.key,
|
|
364
|
+
code: "duplicate_field_key",
|
|
365
|
+
message: `field key "${field.key}" is already used in step "${owner}"`
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
fieldOwner.set(field.key, id);
|
|
369
|
+
}
|
|
370
|
+
if (field.type === "select" && (field.options ?? []).length === 0) {
|
|
371
|
+
push({
|
|
372
|
+
severity: "error",
|
|
373
|
+
path: joinPath(fieldBase, "options"),
|
|
374
|
+
stepId: id,
|
|
375
|
+
fieldKey: field.key,
|
|
376
|
+
code: "select_without_options",
|
|
377
|
+
message: `select field "${field.key}" needs at least one option`
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (field.type === "file" && field.required && id === entry) {
|
|
381
|
+
push({
|
|
382
|
+
severity: "warning",
|
|
383
|
+
path: joinPath(fieldBase, "required"),
|
|
384
|
+
stepId: id,
|
|
385
|
+
fieldKey: field.key,
|
|
386
|
+
code: "file_field_in_first_step_required",
|
|
387
|
+
message: `required upload "${field.key}" on the first step; applicants abandon flows that open with one`
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (entry && nodes.has(entry)) {
|
|
393
|
+
const answered = answeredBefore(entry, nodes);
|
|
394
|
+
for (const [id, step] of steps) {
|
|
395
|
+
const base = joinPath(prefix, "steps", indexOf.get(id));
|
|
396
|
+
const before = answered.get(id);
|
|
397
|
+
if (!before) {
|
|
398
|
+
push({
|
|
399
|
+
severity: "warning",
|
|
400
|
+
path: base,
|
|
401
|
+
stepId: id,
|
|
402
|
+
code: "unreachable_step",
|
|
403
|
+
message: `step "${id}" cannot be reached from the entry step`
|
|
404
|
+
});
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
step.fields.forEach((field, j) => {
|
|
408
|
+
if (!field.visibleWhen) return;
|
|
409
|
+
const root = field.visibleWhen.path.split(".")[0] ?? "";
|
|
410
|
+
const sameStep = step.fields.some((f) => f.key === root && f.key !== field.key);
|
|
411
|
+
if (before.has(root) || sameStep) return;
|
|
412
|
+
push({
|
|
413
|
+
severity: "warning",
|
|
414
|
+
path: joinPath(base, "fields", j, "visibleWhen", "path"),
|
|
415
|
+
stepId: id,
|
|
416
|
+
fieldKey: field.key,
|
|
417
|
+
code: "visible_when_unknown_path",
|
|
418
|
+
message: `"${field.visibleWhen.path}" is not a field answered before "${field.key}"`
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return { ok: !issues.some((i) => i.severity === "error"), issues };
|
|
424
|
+
}
|
|
425
|
+
var Envelope = UpsertCollectionFlowInput.omit({ definition: true });
|
|
426
|
+
function validateFlowInput(input) {
|
|
427
|
+
if (!isRecord(input)) {
|
|
428
|
+
return {
|
|
429
|
+
ok: false,
|
|
430
|
+
issues: [{ severity: "error", path: "", code: "schema", message: "expected an object" }]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const envelope = Envelope.safeParse(input);
|
|
434
|
+
const issues = envelope.success ? [] : schemaIssues(envelope.error, "");
|
|
435
|
+
if (input.definition === void 0) {
|
|
436
|
+
issues.push({
|
|
437
|
+
severity: "error",
|
|
438
|
+
path: "definition",
|
|
439
|
+
code: "schema",
|
|
440
|
+
message: "definition is required"
|
|
441
|
+
});
|
|
442
|
+
} else {
|
|
443
|
+
issues.push(...validateFlowBody(input.definition, "definition").issues);
|
|
444
|
+
}
|
|
445
|
+
return { ok: !issues.some((i) => i.severity === "error"), issues };
|
|
446
|
+
}
|
|
447
|
+
export {
|
|
448
|
+
buildZodForFlow,
|
|
449
|
+
buildZodForStep,
|
|
450
|
+
evaluateCondition3 as evaluateCondition,
|
|
451
|
+
exampleFlows,
|
|
452
|
+
fieldSchema,
|
|
453
|
+
fileFieldValues,
|
|
454
|
+
getStep,
|
|
455
|
+
isFieldVisible,
|
|
456
|
+
isFileField,
|
|
457
|
+
isTerminal,
|
|
458
|
+
kybBasicFlow,
|
|
459
|
+
nextStepId,
|
|
460
|
+
orderedSteps,
|
|
461
|
+
validateFlowBody,
|
|
462
|
+
validateFlowInput,
|
|
463
|
+
validateSubmission,
|
|
464
|
+
visibleFields
|
|
465
|
+
};
|
|
466
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/visibility.ts","../src/navigation.ts","../src/validation.ts","../src/files.ts","../src/examples/kyb-basic.ts","../src/index.ts","../src/validate-flow.ts"],"sourcesContent":["import { evaluateCondition, type Field, type FlowStep } from '@aletheia-dev/core';\n\n/** A field without `visibleWhen` is always visible; otherwise its condition is evaluated against `data`. */\nexport function isFieldVisible(field: Field, data: Record<string, unknown>): boolean {\n if (!field.visibleWhen) return true;\n return evaluateCondition(field.visibleWhen, data);\n}\n\n/** Fields of `step` that are currently visible given `data`, in declaration order. */\nexport function visibleFields(step: FlowStep, data: Record<string, unknown>): Field[] {\n return step.fields.filter((field) => isFieldVisible(field, data));\n}\n","import {\n evaluateCondition,\n ValidationError,\n type CollectionFlowBody,\n type FlowStep,\n} from '@aletheia-dev/core';\n\n/** Looks up a step by id. Throws a ValidationError when the flow has no such step. */\nexport function getStep(flow: CollectionFlowBody, stepId: string): FlowStep {\n const step = flow.steps.find((s) => s.id === stepId);\n if (!step) throw new ValidationError(`unknown step \"${stepId}\"`);\n return step;\n}\n\n/**\n * Resolves the step that follows `stepId` for the given submission data.\n * The first branch whose condition holds wins, then `step.next`; undefined means terminal.\n */\nexport function nextStepId(\n flow: CollectionFlowBody,\n stepId: string,\n data: Record<string, unknown>,\n): string | undefined {\n const step = getStep(flow, stepId);\n for (const branch of step.branches ?? []) {\n if (evaluateCondition(branch.when, data)) return branch.next;\n }\n return step.next;\n}\n\n/** True when no step follows `stepId` for the given data. */\nexport function isTerminal(\n flow: CollectionFlowBody,\n stepId: string,\n data: Record<string, unknown>,\n): boolean {\n return nextStepId(flow, stepId, data) === undefined;\n}\n\n/**\n * The path a submission with `data` takes through the flow, starting at the entry step.\n * Throws a ValidationError if the path revisits a step.\n */\nexport function orderedSteps(flow: CollectionFlowBody, data: Record<string, unknown>): FlowStep[] {\n const visited = new Set<string>();\n const steps: FlowStep[] = [];\n let current: string | undefined = flow.entryStepId;\n while (current !== undefined) {\n if (visited.has(current)) {\n throw new ValidationError(`cycle detected at step \"${current}\"`, { stepId: current });\n }\n visited.add(current);\n steps.push(getStep(flow, current));\n current = nextStepId(flow, current, data);\n }\n return steps;\n}\n","import { z } from 'zod';\nimport {\n ValidationError,\n type CollectionFlowBody,\n type Field,\n type FlowStep,\n} from '@aletheia-dev/core';\nimport { orderedSteps } from './navigation.js';\nimport { visibleFields } from './visibility.js';\n\nconst ISO_DATE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\nfunction dateSchema(): z.ZodType<string> {\n const iso = (z as { iso?: { date?: () => z.ZodType<string> } }).iso;\n return iso?.date ? iso.date() : z.string().regex(ISO_DATE, 'expected YYYY-MM-DD');\n}\n\nfunction textSchema(field: Field): z.ZodType {\n let schema = z.string();\n const { min, max, pattern } = field.validation ?? {};\n if (field.required) schema = schema.min(1, `${field.label} is required`);\n if (min !== undefined) schema = schema.min(min);\n if (max !== undefined) schema = schema.max(max);\n if (pattern !== undefined) schema = schema.regex(new RegExp(pattern));\n return schema;\n}\n\nfunction numberSchema(field: Field): z.ZodType {\n let schema = z.coerce.number();\n const { min, max } = field.validation ?? {};\n if (min !== undefined) schema = schema.min(min);\n if (max !== undefined) schema = schema.max(max);\n return schema;\n}\n\nfunction selectSchema(field: Field): z.ZodType {\n const values = (field.options ?? []).map((o) => o.value);\n if (values.length === 0) {\n throw new ValidationError(`select field \"${field.key}\" has no options`, { key: field.key });\n }\n return z.enum(values as [string, ...string[]]);\n}\n\nfunction baseSchema(field: Field): z.ZodType {\n switch (field.type) {\n case 'text':\n return textSchema(field);\n case 'email':\n return z.email();\n case 'number':\n return numberSchema(field);\n case 'select':\n return selectSchema(field);\n case 'date':\n return dateSchema();\n case 'boolean':\n return z.boolean();\n case 'file':\n return z.object({ fileId: z.string(), name: z.string().optional() });\n }\n}\n\n/** Zod schema for a single field's value, derived from its type, validation and required flag. */\nexport function fieldSchema(field: Field): z.ZodType {\n const schema = baseSchema(field);\n return field.required ? schema : schema.optional();\n}\n\n/** Object schema for the fields of `step` that are visible given `data`. Unknown keys are stripped. */\nexport function buildZodForStep(step: FlowStep, data: Record<string, unknown>): z.ZodObject {\n const shape: Record<string, z.ZodType> = {};\n for (const field of visibleFields(step, data)) shape[field.key] = fieldSchema(field);\n return z.object(shape);\n}\n\n/** Object schema covering every step on the path `data` takes through `flow`. */\nexport function buildZodForFlow(\n flow: CollectionFlowBody,\n data: Record<string, unknown>,\n): z.ZodObject {\n let schema = z.object({});\n for (const step of orderedSteps(flow, data))\n schema = schema.extend(buildZodForStep(step, data).shape);\n return schema;\n}\n\nexport type SubmissionIssue = { path: string; message: string };\nexport type ValidateSubmissionResult =\n { success: true; data: Record<string, unknown> } | { success: false; issues: SubmissionIssue[] };\n\n/** Validates a full submission against the steps it would traverse. */\nexport function validateSubmission(\n flow: CollectionFlowBody,\n data: Record<string, unknown>,\n): ValidateSubmissionResult {\n const result = buildZodForFlow(flow, data).safeParse(data);\n if (result.success) return { success: true, data: result.data };\n return {\n success: false,\n issues: result.error.issues.map((issue) => ({\n path: issue.path.map(String).join('.'),\n message: issue.message,\n })),\n };\n}\n","import type { CollectionFlowBody } from '@aletheia-dev/core';\nimport { orderedSteps } from './navigation.js';\nimport { visibleFields } from './visibility.js';\n\nexport interface FileFieldRef {\n fieldKey: string;\n fileId: string;\n}\n\n/** True when `flow` has a `file` field with `key` on any step (visibility and path ignored). */\nexport function isFileField(flow: CollectionFlowBody, key: string): boolean {\n return flow.steps.some((step) =>\n step.fields.some((field) => field.key === key && field.type === 'file'),\n );\n}\n\nfunction fileIdOf(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const fileId = (value as { fileId?: unknown }).fileId;\n return typeof fileId === 'string' && fileId.length > 0 ? fileId : undefined;\n}\n\n/**\n * The `{ fileId }` values held by the visible `file` fields on the path `data` takes through\n * `flow`, in step and field order. Fields without a well-formed value are skipped: shape\n * validation is `validateSubmission`'s job, this only finds the documents to verify.\n */\nexport function fileFieldValues(\n flow: CollectionFlowBody,\n data: Record<string, unknown>,\n): FileFieldRef[] {\n const refs: FileFieldRef[] = [];\n for (const step of orderedSteps(flow, data)) {\n for (const field of visibleFields(step, data)) {\n if (field.type !== 'file') continue;\n const fileId = fileIdOf(data[field.key]);\n if (fileId !== undefined) refs.push({ fieldKey: field.key, fileId });\n }\n }\n return refs;\n}\n","import { CollectionFlowBody } from '@aletheia-dev/core';\n\nconst countryOptions = [\n { value: 'US', label: 'United States' },\n { value: 'GB', label: 'United Kingdom' },\n { value: 'DE', label: 'Germany' },\n { value: 'KP', label: 'North Korea' },\n];\n\n/** A minimal KYB flow: business details, then UBO details unless the entity is a sole trader, then review. */\nexport const kybBasicFlow: CollectionFlowBody = CollectionFlowBody.parse({\n entryStepId: 'business',\n steps: [\n {\n id: 'business',\n title: 'Business details',\n fields: [\n { key: 'legalName', label: 'Legal name', type: 'text', required: true },\n {\n key: 'country',\n label: 'Country of incorporation',\n type: 'select',\n required: true,\n options: countryOptions,\n },\n {\n key: 'entityType',\n label: 'Entity type',\n type: 'select',\n required: true,\n options: [\n { value: 'company', label: 'Company' },\n { value: 'sole_trader', label: 'Sole trader' },\n ],\n },\n {\n key: 'website',\n label: 'Website',\n type: 'text',\n validation: { pattern: '^(https?://)?([a-zA-Z0-9-]+\\\\.)+[a-zA-Z]{2,}(/\\\\S*)?$' },\n },\n ],\n branches: [{ when: { path: 'entityType', op: 'eq', value: 'sole_trader' }, next: 'review' }],\n next: 'ubo',\n },\n {\n id: 'ubo',\n title: 'Ultimate beneficial owner',\n fields: [\n { key: 'uboName', label: 'Full name', type: 'text', required: true },\n {\n key: 'uboOwnershipPct',\n label: 'Ownership percentage',\n type: 'number',\n required: true,\n validation: { min: 0, max: 100 },\n },\n {\n key: 'uboCountry',\n label: 'Country of residence',\n type: 'select',\n required: true,\n options: countryOptions,\n },\n ],\n next: 'review',\n },\n {\n id: 'review',\n title: 'Review',\n fields: [\n {\n key: 'confirm',\n label: 'I confirm the information is accurate',\n type: 'boolean',\n required: true,\n },\n ],\n },\n ],\n});\n\nexport const exampleFlows: Record<string, CollectionFlowBody> = { 'kyb-basic': kybBasicFlow };\n","export * from './visibility.js';\nexport * from './navigation.js';\nexport * from './validation.js';\nexport * from './files.js';\nexport * from './examples/kyb-basic.js';\nexport { evaluateCondition, type Condition } from '@aletheia-dev/core';\nexport * from './validate-flow.js';\n","/**\n * Full validation of a collection-flow draft without saving it (`POST\n * /collection-flows/validate`). Pure. Every problem becomes a `DefinitionIssue` with the step id\n * and, where it has one, the field key, so the builder can mark the step and the field.\n */\nimport {\n FlowStep,\n MAX_LINK_TTL_SECONDS,\n UpsertCollectionFlowInput,\n type DefinitionIssue,\n type ValidationReport,\n} from '@aletheia-dev/core';\nimport { z } from 'zod';\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value);\n\nconst joinPath = (...parts: Array<string | number | undefined>): string =>\n parts.filter((p) => p !== undefined && p !== '').join('.');\n\n/** The top level without the core schema's graph refinements, which become coded issues here. */\nconst FlowShape = z.object({\n entryStepId: z.string().min(1),\n steps: z.array(z.unknown()).min(1),\n linkTtlSeconds: z.number().int().min(60).max(MAX_LINK_TTL_SECONDS).optional(),\n});\n\nfunction schemaIssues(\n error: z.ZodError,\n prefix: string,\n stepId?: string,\n stepRaw?: unknown,\n): DefinitionIssue[] {\n return error.issues.map((issue) => {\n // `fields.<j>.…` issues name the field when its key is readable.\n let fieldKey: string | undefined;\n if (issue.path[0] === 'fields' && typeof issue.path[1] === 'number' && isRecord(stepRaw)) {\n const field = Array.isArray(stepRaw.fields) ? stepRaw.fields[issue.path[1]] : undefined;\n if (isRecord(field) && typeof field.key === 'string') fieldKey = field.key;\n }\n return {\n severity: 'error' as const,\n path: joinPath(prefix, ...issue.path.map(String)),\n ...(stepId !== undefined && { stepId }),\n ...(fieldKey !== undefined && { fieldKey }),\n code: 'schema',\n message: issue.message,\n };\n });\n}\n\nfunction targetsOf(step: FlowStep): Array<{ to: string; path: string }> {\n return [\n ...(step.next !== undefined ? [{ to: step.next, path: 'next' }] : []),\n ...(step.branches ?? []).map((b, j) => ({ to: b.next, path: `branches.${j}.next` })),\n ];\n}\n\n/** A step's graph view, read from the raw body so steps that fail the schema still count. */\ninterface Node {\n keys: string[];\n targets: string[];\n}\n\nfunction looseNode(raw: Record<string, unknown>): Node {\n const strings = (items: unknown, prop: string): string[] =>\n (Array.isArray(items) ? items : [])\n .map((item) => (isRecord(item) ? item[prop] : undefined))\n .filter((v): v is string => typeof v === 'string');\n return {\n keys: strings(raw.fields, 'key'),\n targets: [\n ...(typeof raw.next === 'string' ? [raw.next] : []),\n ...strings(raw.branches, 'next'),\n ],\n };\n}\n\n/**\n * Field keys that may already be answered when each step is shown: the union, over every path\n * from the entry, of the fields of the steps before it. Forward data-flow to a fixed point.\n */\nfunction answeredBefore(entry: string, nodes: Map<string, Node>): Map<string, Set<string>> {\n const avail = new Map<string, Set<string>>([[entry, new Set()]]);\n let changed = true;\n while (changed) {\n changed = false;\n for (const [id, keys] of [...avail]) {\n const node = nodes.get(id)!;\n const out = new Set([...keys, ...node.keys]);\n for (const to of node.targets) {\n if (!nodes.has(to)) continue;\n const target = avail.get(to) ?? new Set<string>();\n const before = target.size;\n for (const key of out) target.add(key);\n if (!avail.has(to) || target.size !== before) {\n avail.set(to, target);\n changed = true;\n }\n }\n }\n }\n return avail;\n}\n\n/**\n * Validates a flow body (`{ entryStepId, steps, linkTtlSeconds? }`). `prefix` is prepended to\n * every issue path (e.g. `definition` when the body sits inside an upsert input).\n */\nexport function validateFlowBody(body: unknown, prefix = ''): ValidationReport {\n const issues: DefinitionIssue[] = [];\n const push = (issue: DefinitionIssue) => issues.push(issue);\n const shape = FlowShape.safeParse(body);\n if (!shape.success) issues.push(...schemaIssues(shape.error, prefix));\n const raw = isRecord(body) ? body : {};\n const rawSteps = Array.isArray(raw.steps) ? raw.steps : [];\n\n // Steps that pass the schema take part in the graph and field checks.\n const steps = new Map<string, FlowStep>();\n const indexOf = new Map<string, number>();\n // Every first step per id, valid or not, for the graph checks.\n const nodes = new Map<string, Node>();\n rawSteps.forEach((rawStep, i) => {\n const base = joinPath(prefix, 'steps', i);\n const id = isRecord(rawStep) && typeof rawStep.id === 'string' ? rawStep.id : undefined;\n if (id !== undefined && isRecord(rawStep) && !nodes.has(id)) nodes.set(id, looseNode(rawStep));\n const parsed = FlowStep.safeParse(rawStep);\n if (!parsed.success) {\n issues.push(...schemaIssues(parsed.error, base, id, rawStep));\n return;\n }\n if (steps.has(parsed.data.id)) {\n push({\n severity: 'error',\n path: joinPath(base, 'id'),\n stepId: parsed.data.id,\n code: 'duplicate_step_id',\n message: `step id \"${parsed.data.id}\" is used more than once`,\n });\n return;\n }\n steps.set(parsed.data.id, parsed.data);\n indexOf.set(parsed.data.id, i);\n });\n\n const entry = typeof raw.entryStepId === 'string' ? raw.entryStepId : undefined;\n if (entry && rawSteps.length > 0 && !nodes.has(entry)) {\n push({\n severity: 'error',\n path: joinPath(prefix, 'entryStepId'),\n code: 'entry_missing',\n message: `entry step \"${entry}\" does not exist`,\n });\n }\n\n const fieldOwner = new Map<string, string>();\n for (const [id, step] of steps) {\n const base = joinPath(prefix, 'steps', indexOf.get(id)!);\n for (const target of targetsOf(step)) {\n if (!nodes.has(target.to)) {\n push({\n severity: 'error',\n path: joinPath(base, target.path),\n stepId: id,\n code: 'unknown_branch_target',\n message: `step \"${id}\" points to unknown step \"${target.to}\"`,\n });\n }\n }\n step.fields.forEach((field, j) => {\n const fieldBase = joinPath(base, 'fields', j);\n const owner = fieldOwner.get(field.key);\n if (owner !== undefined) {\n push({\n severity: 'error',\n path: joinPath(fieldBase, 'key'),\n stepId: id,\n fieldKey: field.key,\n code: 'duplicate_field_key',\n message: `field key \"${field.key}\" is already used in step \"${owner}\"`,\n });\n } else {\n fieldOwner.set(field.key, id);\n }\n if (field.type === 'select' && (field.options ?? []).length === 0) {\n push({\n severity: 'error',\n path: joinPath(fieldBase, 'options'),\n stepId: id,\n fieldKey: field.key,\n code: 'select_without_options',\n message: `select field \"${field.key}\" needs at least one option`,\n });\n }\n if (field.type === 'file' && field.required && id === entry) {\n push({\n severity: 'warning',\n path: joinPath(fieldBase, 'required'),\n stepId: id,\n fieldKey: field.key,\n code: 'file_field_in_first_step_required',\n message: `required upload \"${field.key}\" on the first step; applicants abandon flows that open with one`,\n });\n }\n });\n }\n\n if (entry && nodes.has(entry)) {\n const answered = answeredBefore(entry, nodes);\n for (const [id, step] of steps) {\n const base = joinPath(prefix, 'steps', indexOf.get(id)!);\n const before = answered.get(id);\n if (!before) {\n push({\n severity: 'warning',\n path: base,\n stepId: id,\n code: 'unreachable_step',\n message: `step \"${id}\" cannot be reached from the entry step`,\n });\n continue;\n }\n step.fields.forEach((field, j) => {\n if (!field.visibleWhen) return;\n const root = field.visibleWhen.path.split('.')[0] ?? '';\n const sameStep = step.fields.some((f) => f.key === root && f.key !== field.key);\n if (before.has(root) || sameStep) return;\n push({\n severity: 'warning',\n path: joinPath(base, 'fields', j, 'visibleWhen', 'path'),\n stepId: id,\n fieldKey: field.key,\n code: 'visible_when_unknown_path',\n message: `\"${field.visibleWhen.path}\" is not a field answered before \"${field.key}\"`,\n });\n });\n }\n }\n\n return { ok: !issues.some((i) => i.severity === 'error'), issues };\n}\n\nconst Envelope = UpsertCollectionFlowInput.omit({ definition: true });\n\n/**\n * Validates an `UpsertCollectionFlowInput`-shaped body: the envelope (`name`, `description`) and\n * the definition, with definition issue paths under `definition.`.\n */\nexport function validateFlowInput(input: unknown): ValidationReport {\n if (!isRecord(input)) {\n return {\n ok: false,\n issues: [{ severity: 'error', path: '', code: 'schema', message: 'expected an object' }],\n };\n }\n const envelope = Envelope.safeParse(input);\n const issues = envelope.success ? [] : schemaIssues(envelope.error, '');\n if (input.definition === undefined) {\n issues.push({\n severity: 'error',\n path: 'definition',\n code: 'schema',\n message: 'definition is required',\n });\n } else {\n issues.push(...validateFlowBody(input.definition, 'definition').issues);\n }\n return { ok: !issues.some((i) => i.severity === 'error'), issues };\n}\n"],"mappings":"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","names":["evaluateCondition","ValidationError","ValidationError","evaluateCondition","z"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aletheia-dev/collection-flow",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Collection flow logic for Aletheia: field visibility, step navigation and dynamic validation, shared by the server and the UIs.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/akhiljames/aletheia.git",
|
|
10
|
+
"directory": "packages/collection-flow"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/akhiljames/aletheia/tree/main/packages/collection-flow#readme",
|
|
13
|
+
"bugs": "https://github.com/akhiljames/aletheia/issues",
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"README.md",
|
|
28
|
+
"CHANGELOG.md"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"zod": "^4.6.5",
|
|
35
|
+
"@aletheia-dev/core": "0.2.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.20.4",
|
|
39
|
+
"eslint": "^10.11.0",
|
|
40
|
+
"tsup": "^8.5.1",
|
|
41
|
+
"typescript": "5.9.3",
|
|
42
|
+
"vitest": "^5.0.3"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"lint": "eslint .",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"clean": "rm -rf dist .turbo"
|
|
51
|
+
}
|
|
6
52
|
}
|