@waypointjs/core 0.1.4 → 0.1.6

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/dist/index.cjs CHANGED
@@ -5,11 +5,15 @@ var middleware = require('zustand/middleware');
5
5
  var zod = require('zod');
6
6
 
7
7
  // src/conditions.ts
8
- function resolveFieldValue(path, data, externalVars) {
8
+ function resolveFieldValue(path, data, externalVars, skippedSteps) {
9
9
  if (path.startsWith("$ext.")) {
10
10
  const varId = path.slice(5);
11
11
  return externalVars[varId];
12
12
  }
13
+ if (path.startsWith("$step.") && path.endsWith(".skipped")) {
14
+ const stepId2 = path.slice(6, -8);
15
+ return skippedSteps?.includes(stepId2) ?? false;
16
+ }
13
17
  const dotIndex = path.indexOf(".");
14
18
  if (dotIndex === -1) return void 0;
15
19
  const stepId = path.slice(0, dotIndex);
@@ -65,24 +69,29 @@ function evaluateOperator(operator, actual, expected) {
65
69
  return false;
66
70
  }
67
71
  }
68
- function evaluateRule(rule, data, externalVars) {
69
- const actual = resolveFieldValue(rule.field, data, externalVars);
72
+ function evaluateRule(rule, data, externalVars, externalEnums, skippedSteps) {
73
+ const actual = resolveFieldValue(rule.field, data, externalVars, skippedSteps);
74
+ if ((rule.operator === "inEnum" || rule.operator === "notInEnum") && externalEnums) {
75
+ const enumDef = externalEnums.find((e) => e.id === String(rule.value));
76
+ const values = enumDef?.values.map((v) => String(v.value)) ?? [];
77
+ return rule.operator === "inEnum" ? values.includes(String(actual)) : !values.includes(String(actual));
78
+ }
70
79
  return evaluateOperator(rule.operator, actual, rule.value);
71
80
  }
72
- function evaluateConditionGroup(group, data, externalVars) {
81
+ function evaluateConditionGroup(group, data, externalVars, externalEnums, skippedSteps) {
73
82
  const ruleResults = group.rules.map(
74
- (rule) => evaluateRule(rule, data, externalVars)
83
+ (rule) => evaluateRule(rule, data, externalVars, externalEnums, skippedSteps)
75
84
  );
76
85
  const groupResults = (group.groups ?? []).map(
77
- (subGroup) => evaluateConditionGroup(subGroup, data, externalVars)
86
+ (subGroup) => evaluateConditionGroup(subGroup, data, externalVars, externalEnums, skippedSteps)
78
87
  );
79
88
  const allResults = [...ruleResults, ...groupResults];
80
89
  if (allResults.length === 0) return true;
81
90
  return group.combinator === "and" ? allResults.every(Boolean) : allResults.some(Boolean);
82
91
  }
83
- function isVisible(visibleWhen, data, externalVars) {
92
+ function isVisible(visibleWhen, data, externalVars, externalEnums, skippedSteps) {
84
93
  if (!visibleWhen) return true;
85
- return evaluateConditionGroup(visibleWhen, data, externalVars);
94
+ return evaluateConditionGroup(visibleWhen, data, externalVars, externalEnums, skippedSteps);
86
95
  }
87
96
 
88
97
  // src/tree-resolver.ts
@@ -106,16 +115,34 @@ function findMissingBlockingVars(externalVariables, externalVars, visibleSteps)
106
115
  return value === void 0 || value === null;
107
116
  }).map((extVar) => extVar.id);
108
117
  }
109
- function resolveTree(schema, data, externalVars) {
118
+ function resolveTree(schema, data, externalVars, externalEnums, skippedSteps) {
110
119
  const visibleSteps = [];
111
120
  const hiddenSteps = [];
112
121
  for (const stepDef of schema.steps) {
113
- const stepVisible = isVisible(stepDef.visibleWhen, data, externalVars);
114
- const resolvedFields = stepDef.fields.map((fieldDef) => ({
115
- definition: fieldDef,
116
- visible: isVisible(fieldDef.visibleWhen, data, externalVars),
117
- dependenciesMet: areDependenciesMet(fieldDef.dependsOn, data, externalVars)
118
- }));
122
+ const stepVisible = isVisible(stepDef.visibleWhen, data, externalVars, externalEnums, skippedSteps);
123
+ const resolvedFields = stepDef.fields.map((fieldDef) => {
124
+ let resolvedOptions;
125
+ if (fieldDef.externalEnumId && externalEnums) {
126
+ const enumDef = externalEnums.find((e) => e.id === fieldDef.externalEnumId);
127
+ resolvedOptions = enumDef?.values;
128
+ }
129
+ let resolvedDefaultValue;
130
+ if (fieldDef.dynamicDefault && fieldDef.dynamicDefault.length > 0) {
131
+ for (const rule of fieldDef.dynamicDefault) {
132
+ if (evaluateConditionGroup(rule.when, data, externalVars, externalEnums, skippedSteps)) {
133
+ resolvedDefaultValue = rule.value;
134
+ break;
135
+ }
136
+ }
137
+ }
138
+ return {
139
+ definition: fieldDef,
140
+ visible: isVisible(fieldDef.visibleWhen, data, externalVars, externalEnums, skippedSteps),
141
+ dependenciesMet: areDependenciesMet(fieldDef.dependsOn, data, externalVars),
142
+ resolvedOptions,
143
+ resolvedDefaultValue
144
+ };
145
+ });
119
146
  const resolvedStep = {
120
147
  definition: stepDef,
121
148
  visible: stepVisible,
@@ -180,7 +207,7 @@ function getResolvedTree(state) {
180
207
  if (!state.schema) {
181
208
  return { steps: [], hiddenSteps: [], missingExternalVars: [] };
182
209
  }
183
- return resolveTree(state.schema, state.data, state.externalVars);
210
+ return resolveTree(state.schema, state.data, state.externalVars, void 0, state.skippedSteps);
184
211
  }
185
212
  function getCurrentStep(state) {
186
213
  if (!state.currentStepId) return void 0;
@@ -212,6 +239,7 @@ var initialState = {
212
239
  externalVars: {},
213
240
  currentStepId: null,
214
241
  history: [],
242
+ skippedSteps: [],
215
243
  isSubmitting: false,
216
244
  completed: false
217
245
  };
@@ -227,6 +255,7 @@ function buildStateCreator() {
227
255
  externalVars,
228
256
  currentStepId: firstStepId,
229
257
  history: firstStepId ? [firstStepId] : [],
258
+ skippedSteps: [],
230
259
  isSubmitting: false,
231
260
  completed: false
232
261
  });
@@ -268,6 +297,16 @@ function buildStateCreator() {
268
297
  setCompleted(b) {
269
298
  set({ completed: b });
270
299
  },
300
+ skipStep(stepId) {
301
+ set((state) => ({
302
+ skippedSteps: state.skippedSteps.includes(stepId) ? state.skippedSteps : [...state.skippedSteps, stepId]
303
+ }));
304
+ },
305
+ unskipStep(stepId) {
306
+ set((state) => ({
307
+ skippedSteps: state.skippedSteps.filter((id) => id !== stepId)
308
+ }));
309
+ },
271
310
  truncateHistoryAt(stepId) {
272
311
  set((state) => {
273
312
  const idx = state.history.indexOf(stepId);
@@ -306,6 +345,7 @@ function createRuntimeStore(options = {}) {
306
345
  data: state.data,
307
346
  currentStepId: state.currentStepId,
308
347
  history: state.history,
348
+ skippedSteps: state.skippedSteps,
309
349
  completed: state.completed
310
350
  })
311
351
  })
@@ -322,7 +362,13 @@ var customValidatorRegistry = {};
322
362
  function registerCustomValidator(id, fn) {
323
363
  customValidatorRegistry[id] = fn;
324
364
  }
325
- function buildFieldSchema(field) {
365
+ function resolveRuleValue(rule, data) {
366
+ if (rule.refField && data) {
367
+ return resolveFieldValue(rule.refField, data, {});
368
+ }
369
+ return rule.value;
370
+ }
371
+ function buildFieldSchema(field, externalEnums, data) {
326
372
  const rules = field.validation ?? [];
327
373
  const isRequired = rules.some((r) => r.type === "required");
328
374
  const isNumeric = field.type === "number";
@@ -340,16 +386,47 @@ function buildFieldSchema(field) {
340
386
  let numSchema = zod.z.coerce.number({
341
387
  invalid_type_error: "Must be a number"
342
388
  });
389
+ const numRefineRules = [];
343
390
  for (const rule of rules) {
344
- if (rule.type === "min") {
345
- const n = Number(rule.value);
346
- if (!isNaN(n)) numSchema = numSchema.gte(n, rule.message);
347
- } else if (rule.type === "max") {
348
- const n = Number(rule.value);
349
- if (!isNaN(n)) numSchema = numSchema.lte(n, rule.message);
391
+ const rv = resolveRuleValue(rule, data);
392
+ const isRef = !!rule.refField;
393
+ if (rule.type === "min" || rule.type === "greaterThanOrEqual") {
394
+ const n = Number(rv);
395
+ if (!isNaN(n)) {
396
+ if (isRef) numRefineRules.push({ fn: (v) => v >= n, message: rule.message });
397
+ else numSchema = numSchema.gte(n, rule.message);
398
+ }
399
+ } else if (rule.type === "max" || rule.type === "lessThanOrEqual") {
400
+ const n = Number(rv);
401
+ if (!isNaN(n)) {
402
+ if (isRef) numRefineRules.push({ fn: (v) => v <= n, message: rule.message });
403
+ else numSchema = numSchema.lte(n, rule.message);
404
+ }
405
+ } else if (rule.type === "greaterThan") {
406
+ const n = Number(rv);
407
+ if (!isNaN(n)) {
408
+ if (isRef) numRefineRules.push({ fn: (v) => v > n, message: rule.message });
409
+ else numSchema = numSchema.gt(n, rule.message);
410
+ }
411
+ } else if (rule.type === "lessThan") {
412
+ const n = Number(rv);
413
+ if (!isNaN(n)) {
414
+ if (isRef) numRefineRules.push({ fn: (v) => v < n, message: rule.message });
415
+ else numSchema = numSchema.lt(n, rule.message);
416
+ }
417
+ } else if (rule.type === "equals") {
418
+ const n = Number(rv);
419
+ if (!isNaN(n)) numRefineRules.push({ fn: (v) => v === n, message: rule.message });
420
+ } else if (rule.type === "notEquals") {
421
+ const n = Number(rv);
422
+ if (!isNaN(n)) numRefineRules.push({ fn: (v) => v !== n, message: rule.message });
350
423
  }
351
424
  }
352
- return isRequired ? numSchema : numSchema.optional();
425
+ let numFinal = isRequired ? numSchema : numSchema.optional();
426
+ for (const { fn, message } of numRefineRules) {
427
+ numFinal = numFinal.refine((v) => v == null || fn(v), message);
428
+ }
429
+ return numFinal;
353
430
  }
354
431
  let strSchema = zod.z.string();
355
432
  const refineRules = [];
@@ -379,27 +456,112 @@ function buildFieldSchema(field) {
379
456
  strSchema = strSchema.regex(new RegExp(String(rule.value)), rule.message);
380
457
  }
381
458
  break;
459
+ case "equals": {
460
+ const rv = resolveRuleValue(rule, data);
461
+ if (rv !== void 0) {
462
+ const eq = String(rv);
463
+ refineRules.push({ fn: (v) => String(v) === eq, message: rule.message });
464
+ }
465
+ break;
466
+ }
467
+ case "notEquals": {
468
+ const rv = resolveRuleValue(rule, data);
469
+ if (rv !== void 0) {
470
+ const neq = String(rv);
471
+ refineRules.push({ fn: (v) => String(v) !== neq, message: rule.message });
472
+ }
473
+ break;
474
+ }
475
+ case "greaterThan": {
476
+ const rv = resolveRuleValue(rule, data);
477
+ if (rv !== void 0) {
478
+ const gt = Number(rv);
479
+ refineRules.push({ fn: (v) => Number(v) > gt, message: rule.message });
480
+ }
481
+ break;
482
+ }
483
+ case "greaterThanOrEqual": {
484
+ const rv = resolveRuleValue(rule, data);
485
+ if (rv !== void 0) {
486
+ const gte = Number(rv);
487
+ refineRules.push({ fn: (v) => Number(v) >= gte, message: rule.message });
488
+ }
489
+ break;
490
+ }
491
+ case "lessThan": {
492
+ const rv = resolveRuleValue(rule, data);
493
+ if (rv !== void 0) {
494
+ const lt = Number(rv);
495
+ refineRules.push({ fn: (v) => Number(v) < lt, message: rule.message });
496
+ }
497
+ break;
498
+ }
499
+ case "lessThanOrEqual": {
500
+ const rv = resolveRuleValue(rule, data);
501
+ if (rv !== void 0) {
502
+ const lte = Number(rv);
503
+ refineRules.push({ fn: (v) => Number(v) <= lte, message: rule.message });
504
+ }
505
+ break;
506
+ }
507
+ case "contains": {
508
+ const rv = resolveRuleValue(rule, data);
509
+ if (rv !== void 0) {
510
+ const sub = String(rv);
511
+ refineRules.push({ fn: (v) => String(v).includes(sub), message: rule.message });
512
+ }
513
+ break;
514
+ }
515
+ case "notContains": {
516
+ const rv = resolveRuleValue(rule, data);
517
+ if (rv !== void 0) {
518
+ const nsub = String(rv);
519
+ refineRules.push({ fn: (v) => !String(v).includes(nsub), message: rule.message });
520
+ }
521
+ break;
522
+ }
523
+ case "matches":
524
+ if (rule.value !== void 0 && rule.value !== null) {
525
+ const rx = new RegExp(String(rule.value));
526
+ refineRules.push({ fn: (v) => rx.test(String(v)), message: rule.message });
527
+ }
528
+ break;
529
+ case "inEnum":
530
+ if (rule.value && externalEnums) {
531
+ const enumDef = externalEnums.find((e) => e.id === String(rule.value));
532
+ if (enumDef) {
533
+ const values = enumDef.values.map((v) => String(v.value));
534
+ refineRules.push({ fn: (v) => values.includes(String(v)), message: rule.message });
535
+ }
536
+ }
537
+ break;
538
+ case "notInEnum":
539
+ if (rule.value && externalEnums) {
540
+ const enumDef = externalEnums.find((e) => e.id === String(rule.value));
541
+ if (enumDef) {
542
+ const values = enumDef.values.map((v) => String(v.value));
543
+ refineRules.push({ fn: (v) => !values.includes(String(v)), message: rule.message });
544
+ }
545
+ }
546
+ break;
382
547
  case "custom":
383
548
  if (rule.customValidatorId && customValidatorRegistry[rule.customValidatorId]) {
384
- refineRules.push({ id: rule.customValidatorId, message: rule.message });
549
+ refineRules.push({ fn: (v) => Boolean(customValidatorRegistry[rule.customValidatorId]?.(v)), message: rule.message });
385
550
  }
386
551
  break;
387
552
  }
388
553
  }
389
554
  let finalSchema = isRequired ? strSchema : strSchema.optional();
390
- for (const { id, message } of refineRules) {
391
- finalSchema = finalSchema.refine(
392
- (val) => Boolean(customValidatorRegistry[id]?.(val)),
393
- message
394
- );
555
+ for (const { fn, message } of refineRules) {
556
+ finalSchema = finalSchema.refine(fn, message);
395
557
  }
396
558
  return finalSchema;
397
559
  }
398
- function buildZodSchema(fields) {
560
+ function buildZodSchema(fields, externalEnums, data) {
399
561
  const shape = {};
400
562
  for (const resolvedField of fields) {
401
563
  if (!resolvedField.visible) continue;
402
- shape[resolvedField.definition.id] = buildFieldSchema(resolvedField.definition);
564
+ shape[resolvedField.definition.id] = buildFieldSchema(resolvedField.definition, externalEnums, data);
403
565
  }
404
566
  return zod.z.object(shape);
405
567
  }