@entifix/core 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/command-palette/command-matching.d.ts +1 -1
  2. package/dist/command-palette/command-matching.js +1 -1
  3. package/dist/command-palette/command-matching.js.map +1 -1
  4. package/dist/command-palette/command-source.d.ts +3 -3
  5. package/dist/command-palette/command-source.js.map +1 -1
  6. package/dist/entity-definition/decorators/use-case/use-case.d.ts +1 -1
  7. package/dist/entity-definition/decorators/use-case/use-case.js +1 -1
  8. package/dist/entity-definition/decorators/use-case/use-case.js.map +1 -1
  9. package/dist/entity-definition/describe/describe-child-columns.d.ts +1 -1
  10. package/dist/entity-definition/describe/describe-child-columns.js +1 -1
  11. package/dist/entity-definition/describe/describe-child-columns.js.map +1 -1
  12. package/dist/entity-definition/describe/describe-entity-use-cases.d.ts +2 -1
  13. package/dist/entity-definition/describe/describe-entity-use-cases.d.ts.map +1 -1
  14. package/dist/entity-definition/describe/describe-entity-use-cases.js +2 -1
  15. package/dist/entity-definition/describe/describe-entity-use-cases.js.map +1 -1
  16. package/dist/entity-definition/helpers/entity-definition-helpers.d.ts +2 -2
  17. package/dist/entity-definition/helpers/entity-definition-helpers.js +2 -2
  18. package/dist/entity-definition/helpers/entity-definition-helpers.js.map +1 -1
  19. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.d.ts +4 -4
  20. package/dist/entity-definition/meta-entities/meta-accessor/meta-accessor.js.map +1 -1
  21. package/dist/entity-definition/meta-entities/meta-use-case/meta-use-case.d.ts +1 -1
  22. package/dist/entity-definition/meta-entities/meta-use-case/meta-use-case.js.map +1 -1
  23. package/dist/entity-definition/reconstruct/clone-entity-draft.d.ts +1 -1
  24. package/dist/entity-definition/reconstruct/clone-entity-draft.js +1 -1
  25. package/dist/entity-definition/reconstruct/clone-entity-draft.js.map +1 -1
  26. package/dist/entity-definition/reconstruct/reconstruct-entity.js +1 -1
  27. package/dist/entity-definition/reconstruct/reconstruct-entity.js.map +1 -1
  28. package/dist/envelope/types.d.ts +5 -7
  29. package/dist/envelope/types.d.ts.map +1 -1
  30. package/dist/envelope/types.js.map +1 -1
  31. package/dist/http/session-cookies.d.ts +2 -3
  32. package/dist/http/session-cookies.d.ts.map +1 -1
  33. package/dist/http/session-cookies.js +2 -3
  34. package/dist/http/session-cookies.js.map +1 -1
  35. package/dist/i18n/locales.js +2 -2
  36. package/dist/i18n/locales.js.map +1 -1
  37. package/dist/types/BulkOutcome.d.ts +1 -2
  38. package/dist/types/BulkOutcome.d.ts.map +1 -1
  39. package/dist/types/BulkOutcome.js.map +1 -1
  40. package/dist/types/EntityAction.d.ts +1 -2
  41. package/dist/types/EntityAction.d.ts.map +1 -1
  42. package/dist/types/EntityAction.js +1 -2
  43. package/dist/types/EntityAction.js.map +1 -1
  44. package/dist/types/EntityDraft.d.ts +2 -3
  45. package/dist/types/EntityDraft.d.ts.map +1 -1
  46. package/dist/types/EntityDraft.js.map +1 -1
  47. package/dist/types/EntityRowDraft.d.ts +2 -3
  48. package/dist/types/EntityRowDraft.d.ts.map +1 -1
  49. package/dist/types/EntityRowDraft.js +1 -1
  50. package/dist/types/EntityRowDraft.js.map +1 -1
  51. package/dist/types/EntitySelection.d.ts +2 -3
  52. package/dist/types/EntitySelection.d.ts.map +1 -1
  53. package/dist/types/EntitySelection.js.map +1 -1
  54. package/dist/wizard/wizard-definition.d.ts +5 -8
  55. package/dist/wizard/wizard-definition.d.ts.map +1 -1
  56. package/dist/wizard/wizard-definition.js +3 -5
  57. package/dist/wizard/wizard-definition.js.map +1 -1
  58. package/dist/wizard/wizard-state.d.ts +4 -7
  59. package/dist/wizard/wizard-state.d.ts.map +1 -1
  60. package/dist/wizard/wizard-state.js.map +1 -1
  61. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/wizard/wizard-definition.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\n\n/**\n * What a step holds, which decides how its value is stored and edited.\n *\n * `form` is one `useEntityForm` instance over part of an entity; `selection` is\n * a table step whose value is a set of ids; `choice` is a branch point offering\n * a fixed list of options; `summary` is the read-only recap; `custom` is\n * anything else the host renders and reads itself.\n */\nexport const WizardStepKinds = [\n 'form',\n 'selection',\n 'choice',\n 'summary',\n 'custom',\n] as const;\nexport type WizardStepKind = (typeof WizardStepKinds)[number];\n\n/**\n * One step, and the edges out of it.\n *\n * **The graph is data; `next` only chooses along it.** `to` lists the steps this\n * one may lead to, so reachability, unknown targets, cycles and terminality are\n * decidable by reading the definition — no probing, no enumeration of the state\n * space, and no rule that \"only appears to hold\" because a `next` reading a form\n * value could never be enumerated. It is the move\n * [ADR 0039](../../../../../../docs/adr/0039-multi-step-sagas-are-orchestrated.md)\n * made for saga steps, for the same reason: a walkable definition is checkable\n * at load, and a function is not.\n *\n * A step with an empty `to` is **terminal**, and\n * {@link assertWizardDefinition} requires that to be the summary.\n */\nexport interface WizardStep {\n readonly id: string;\n readonly kind: WizardStepKind;\n /** Catalog key for the stepper's label. Resolved by the renderer, never here. */\n readonly labelKey: string;\n /**\n * The options a `choice` step offers, as stable identifiers.\n *\n * Required on `choice` and forbidden elsewhere: the value of a choice step is\n * one of these strings, so a choice with no options can never be answered and\n * options on a form step describe nothing.\n */\n readonly options?: readonly string[];\n /** Every step this one may lead to. Empty means the flow ends here. */\n readonly to: readonly string[];\n /**\n * Which of {@link WizardStep.to} comes next, given everything answered so far.\n *\n * Optional, and omitting it is the ordinary case: a step with exactly one edge\n * needs no function to pick it. A step with several **must** declare one, or\n * the definition cannot say where it goes.\n *\n * It returns a step id and never `undefined`: **the end of the flow is an\n * empty `to`, not a `next` that declines**. Two ways of saying the same thing\n * would let a step be terminal in one of them and not the other, and it is\n * `to` that {@link assertWizardDefinition} can read.\n */\n next?(state: WizardStateLike): string;\n}\n\n/**\n * The part of the state a `next` may read.\n *\n * Declared structurally rather than importing `WizardState`, so the definition\n * module stays below the state module and neither has to import the other.\n */\nexport interface WizardStateLike {\n readonly activeStep: string;\n readonly steps: { readonly [stepId: string]: unknown };\n}\n\n/**\n * A whole wizard: its key — which is also its screen key in a workspace address\n * — and its steps, the first of which is where the flow begins.\n */\nexport interface WizardDefinition {\n readonly key: string;\n readonly steps: readonly WizardStep[];\n}\n\n/** The step a flow starts at: the first declared. */\nexport const entryStep = (definition: WizardDefinition): WizardStep => {\n const [first] = definition.steps;\n if (first === undefined) {\n throw new EntifixBuildError(`wizard \"${definition.key}\" declares no steps`);\n }\n return first;\n};\n\n/** The step with this id, or `undefined` when the definition has none. */\nexport const findStep = (\n definition: WizardDefinition,\n stepId: string,\n): WizardStep | undefined => definition.steps.find(step => step.id === stepId);\n\n/**\n * Checks a definition the moment it is declared, and throws rather than\n * degrading.\n *\n * **Load time, not step time.** A wizard whose last step is missing, or whose\n * branch names a step that does not exist, must fail on the first render of any\n * surface rather than on the render of the step nobody reached — otherwise the\n * failure of the longest flow in the product arrives at the end of it. That is\n * the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take\n * ([ADR 0035](../../../../../../docs/adr/0035-entity-actions-selection-and-bulk.md)).\n *\n * Six faults, each of which is silent otherwise:\n *\n * - **no steps**, which renders an empty frame with a disabled button;\n * - **a duplicate id**, which makes one step's stored value overwrite another's;\n * - **an unknown `to` target**, a typo that strands the flow mid-way;\n * - **several edges and no `next`**, which cannot say where the step goes;\n * - **a step nothing reaches**, which is either a dead branch or a missing edge;\n * - **the wrong terminal step** — the flow must end in the summary, and there\n * must be exactly one of those. A wizard ending anywhere else commits without\n * the operator ever seeing what they answered, which\n * [#111](https://github.com/r10c-technologies/r10c/issues/111) records as not\n * optional.\n *\n * A cycle needs no separate check: every step is reachable from the entry and\n * exactly one is terminal, so a cycle would leave the steps on it unable to\n * reach that terminal, which the walk below reports as unreachable-from-here.\n */\nexport function assertWizardDefinition(definition: WizardDefinition): void {\n const first = entryStep(definition);\n const ids = new Set<string>();\n\n for (const step of definition.steps) {\n if (ids.has(step.id)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares step \"${step.id}\" twice`,\n );\n }\n ids.add(step.id);\n }\n\n for (const step of definition.steps) {\n assertStepOptions(definition, step);\n\n for (const target of step.to) {\n if (!ids.has(target)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" leads to unknown step \"${target}\"`,\n );\n }\n }\n\n if (step.to.length > 1 && step.next === undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" has ${step.to.length} edges and no next()`,\n );\n }\n }\n\n assertOneSummaryAtTheEnd(definition);\n assertEveryStepIsReachable(definition, first);\n}\n\n/** `options` belongs to `choice` and to nothing else. */\nfunction assertStepOptions(\n definition: WizardDefinition,\n step: WizardStep,\n): void {\n if (step.kind === 'choice') {\n if (step.options === undefined || step.options.length === 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" choice step \"${step.id}\" offers no options`,\n );\n }\n return;\n }\n if (step.options !== undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" is a ${step.kind} step and cannot declare options`,\n );\n }\n}\n\n/** Exactly one summary, and it is the only step the flow may end on. */\nfunction assertOneSummaryAtTheEnd(definition: WizardDefinition): void {\n const summaries = definition.steps.filter(step => step.kind === 'summary');\n if (summaries.length !== 1) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares ${summaries.length} summary steps, and must declare exactly one`,\n );\n }\n\n for (const step of definition.steps) {\n const terminal = step.to.length === 0;\n if (terminal && step.kind !== 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" ends on \"${step.id}\", which is not its summary`,\n );\n }\n if (!terminal && step.kind === 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" summary step \"${step.id}\" leads onward, and must end the flow`,\n );\n }\n }\n}\n\n/** Every declared step is on some path out of the entry step. */\nfunction assertEveryStepIsReachable(\n definition: WizardDefinition,\n first: WizardStep,\n): void {\n const seen = new Set<string>([first.id]);\n\n // A fixpoint over `seen` rather than a queue of resolved steps: every target\n // has already been checked to name a declared step, so a walk that looked one\n // up would carry an `undefined` arm nothing can reach — and an unreachable\n // arm is indistinguishable from an untested one.\n for (let grew = true; grew;) {\n grew = false;\n for (const step of definition.steps) {\n if (!seen.has(step.id)) continue;\n for (const target of step.to) {\n if (seen.has(target)) continue;\n seen.add(target);\n grew = true;\n }\n }\n }\n\n const stranded = definition.steps\n .filter(step => !seen.has(step.id))\n .map(step => step.id);\n\n if (stranded.length > 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" cannot reach ${stranded.map(id => `\"${id}\"`).join(', ')} from \"${first.id}\"`,\n );\n }\n}\n"],"names":["EntifixBuildError","WizardStepKinds","entryStep","definition","first","steps","undefined","key","findStep","stepId","find","step","id","assertWizardDefinition","ids","Set","has","add","assertStepOptions","target","to","length","next","assertOneSummaryAtTheEnd","assertEveryStepIsReachable","kind","options","summaries","filter","terminal","seen","grew","stranded","map","join"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/wizard/wizard-definition.ts"],"sourcesContent":["import { EntifixBuildError } from '../base-entities/entifix-error/index.js';\n\n/**\n * What a step holds, which decides how its value is stored and edited.\n *\n * `form` is one `useEntityForm` instance over part of an entity; `selection` is\n * a table step whose value is a set of ids; `choice` is a branch point offering\n * a fixed list of options; `summary` is the read-only recap; `custom` is\n * anything else the host renders and reads itself.\n */\nexport const WizardStepKinds = [\n 'form',\n 'selection',\n 'choice',\n 'summary',\n 'custom',\n] as const;\nexport type WizardStepKind = (typeof WizardStepKinds)[number];\n\n/**\n * One step, and the edges out of it.\n *\n * **The graph is data; `next` only chooses along it.** `to` lists the steps this\n * one may lead to, so reachability, unknown targets, cycles and terminality are\n * decidable by reading the definition — no probing, no enumeration of the state\n * space, and no rule that \"only appears to hold\" because a `next` reading a form\n * value could never be enumerated. It is the move a saga definition makes for\n * its steps, for the same reason: a walkable definition is checkable\n * at load, and a function is not.\n *\n * A step with an empty `to` is **terminal**, and\n * {@link assertWizardDefinition} requires that to be the summary.\n */\nexport interface WizardStep {\n readonly id: string;\n readonly kind: WizardStepKind;\n /** Catalog key for the stepper's label. Resolved by the renderer, never here. */\n readonly labelKey: string;\n /**\n * The options a `choice` step offers, as stable identifiers.\n *\n * Required on `choice` and forbidden elsewhere: the value of a choice step is\n * one of these strings, so a choice with no options can never be answered and\n * options on a form step describe nothing.\n */\n readonly options?: readonly string[];\n /** Every step this one may lead to. Empty means the flow ends here. */\n readonly to: readonly string[];\n /**\n * Which of {@link WizardStep.to} comes next, given everything answered so far.\n *\n * Optional, and omitting it is the ordinary case: a step with exactly one edge\n * needs no function to pick it. A step with several **must** declare one, or\n * the definition cannot say where it goes.\n *\n * It returns a step id and never `undefined`: **the end of the flow is an\n * empty `to`, not a `next` that declines**. Two ways of saying the same thing\n * would let a step be terminal in one of them and not the other, and it is\n * `to` that {@link assertWizardDefinition} can read.\n */\n next?(state: WizardStateLike): string;\n}\n\n/**\n * The part of the state a `next` may read.\n *\n * Declared structurally rather than importing `WizardState`, so the definition\n * module stays below the state module and neither has to import the other.\n */\nexport interface WizardStateLike {\n readonly activeStep: string;\n readonly steps: { readonly [stepId: string]: unknown };\n}\n\n/**\n * A whole wizard: its key — which is also its screen key in a workspace address\n * — and its steps, the first of which is where the flow begins.\n */\nexport interface WizardDefinition {\n readonly key: string;\n readonly steps: readonly WizardStep[];\n}\n\n/** The step a flow starts at: the first declared. */\nexport const entryStep = (definition: WizardDefinition): WizardStep => {\n const [first] = definition.steps;\n if (first === undefined) {\n throw new EntifixBuildError(`wizard \"${definition.key}\" declares no steps`);\n }\n return first;\n};\n\n/** The step with this id, or `undefined` when the definition has none. */\nexport const findStep = (\n definition: WizardDefinition,\n stepId: string,\n): WizardStep | undefined => definition.steps.find(step => step.id === stepId);\n\n/**\n * Checks a definition the moment it is declared, and throws rather than\n * degrading.\n *\n * **Load time, not step time.** A wizard whose last step is missing, or whose\n * branch names a step that does not exist, must fail on the first render of any\n * surface rather than on the render of the step nobody reached — otherwise the\n * failure of the longest flow in the product arrives at the end of it. That is\n * the posture `assertLinkSourcesAreEditable` and `surfaceFor` already take.\n *\n * Six faults, each of which is silent otherwise:\n *\n * - **no steps**, which renders an empty frame with a disabled button;\n * - **a duplicate id**, which makes one step's stored value overwrite another's;\n * - **an unknown `to` target**, a typo that strands the flow mid-way;\n * - **several edges and no `next`**, which cannot say where the step goes;\n * - **a step nothing reaches**, which is either a dead branch or a missing edge;\n * - **the wrong terminal step** — the flow must end in the summary, and there\n * must be exactly one of those. A wizard ending anywhere else commits without\n * the operator ever seeing what they answered, which is not optional in a\n * long flow.\n *\n * A cycle needs no separate check: every step is reachable from the entry and\n * exactly one is terminal, so a cycle would leave the steps on it unable to\n * reach that terminal, which the walk below reports as unreachable-from-here.\n */\nexport function assertWizardDefinition(definition: WizardDefinition): void {\n const first = entryStep(definition);\n const ids = new Set<string>();\n\n for (const step of definition.steps) {\n if (ids.has(step.id)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares step \"${step.id}\" twice`,\n );\n }\n ids.add(step.id);\n }\n\n for (const step of definition.steps) {\n assertStepOptions(definition, step);\n\n for (const target of step.to) {\n if (!ids.has(target)) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" leads to unknown step \"${target}\"`,\n );\n }\n }\n\n if (step.to.length > 1 && step.next === undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" has ${step.to.length} edges and no next()`,\n );\n }\n }\n\n assertOneSummaryAtTheEnd(definition);\n assertEveryStepIsReachable(definition, first);\n}\n\n/** `options` belongs to `choice` and to nothing else. */\nfunction assertStepOptions(\n definition: WizardDefinition,\n step: WizardStep,\n): void {\n if (step.kind === 'choice') {\n if (step.options === undefined || step.options.length === 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" choice step \"${step.id}\" offers no options`,\n );\n }\n return;\n }\n if (step.options !== undefined) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" step \"${step.id}\" is a ${step.kind} step and cannot declare options`,\n );\n }\n}\n\n/** Exactly one summary, and it is the only step the flow may end on. */\nfunction assertOneSummaryAtTheEnd(definition: WizardDefinition): void {\n const summaries = definition.steps.filter(step => step.kind === 'summary');\n if (summaries.length !== 1) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" declares ${summaries.length} summary steps, and must declare exactly one`,\n );\n }\n\n for (const step of definition.steps) {\n const terminal = step.to.length === 0;\n if (terminal && step.kind !== 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" ends on \"${step.id}\", which is not its summary`,\n );\n }\n if (!terminal && step.kind === 'summary') {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" summary step \"${step.id}\" leads onward, and must end the flow`,\n );\n }\n }\n}\n\n/** Every declared step is on some path out of the entry step. */\nfunction assertEveryStepIsReachable(\n definition: WizardDefinition,\n first: WizardStep,\n): void {\n const seen = new Set<string>([first.id]);\n\n // A fixpoint over `seen` rather than a queue of resolved steps: every target\n // has already been checked to name a declared step, so a walk that looked one\n // up would carry an `undefined` arm nothing can reach — and an unreachable\n // arm is indistinguishable from an untested one.\n for (let grew = true; grew;) {\n grew = false;\n for (const step of definition.steps) {\n if (!seen.has(step.id)) continue;\n for (const target of step.to) {\n if (seen.has(target)) continue;\n seen.add(target);\n grew = true;\n }\n }\n }\n\n const stranded = definition.steps\n .filter(step => !seen.has(step.id))\n .map(step => step.id);\n\n if (stranded.length > 0) {\n throw new EntifixBuildError(\n `wizard \"${definition.key}\" cannot reach ${stranded.map(id => `\"${id}\"`).join(', ')} from \"${first.id}\"`,\n );\n }\n}\n"],"names":["EntifixBuildError","WizardStepKinds","entryStep","definition","first","steps","undefined","key","findStep","stepId","find","step","id","assertWizardDefinition","ids","Set","has","add","assertStepOptions","target","to","length","next","assertOneSummaryAtTheEnd","assertEveryStepIsReachable","kind","options","summaries","filter","terminal","seen","grew","stranded","map","join"],"mappings":"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"}
@@ -8,9 +8,8 @@ import type { EntityDraft } from '../types/EntityDraft.js';
8
8
  * members, which would let a form step carry ids nothing would ever read.
9
9
  *
10
10
  * ⚠️ A selection holds `readonly string[]`, **never a `ReadonlySet`**. A `Set`
11
- * serializes to `{}` and does it silently, which is the fault
12
- * [ADR 0035](../../../../../../docs/adr/0035-entity-actions-selection-and-bulk.md)
13
- * named for the wire — and a wizard's state goes through `createJSONStorage` on
11
+ * serializes to `{}` and does it silently, which is the fault a bulk
12
+ * selection meets on the wire — and a wizard's state goes through `createJSONStorage` on
14
13
  * every keystroke, so it would be met far more often here than there.
15
14
  */
16
15
  export type WizardStepValue = {
@@ -32,13 +31,11 @@ export type WizardStepValue = {
32
31
  * **A `type`, never an `interface`.** This is persisted through
33
32
  * `createJSONStorage` and therefore has to satisfy `JsonValue`, and TypeScript
34
33
  * gives an interface no implicit index signature — the resulting "Index
35
- * signature for type 'string' is missing" explains nothing about the real cause
36
- * ([ADR 0032](../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)).
34
+ * signature for type 'string' is missing" explains nothing about the real cause.
37
35
  *
38
36
  * `history` is **the path actually taken**, and it is what Back pops. A branch's
39
37
  * inverse is ambiguous once an earlier answer changes, so a computed `previous`
40
- * would walk the operator back through a path they were never on
41
- * ([ADR 0045](../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).
38
+ * would walk the operator back through a path they were never on.
42
39
  *
43
40
  * `steps` is deliberately **not** pruned when a branch is abandoned. It is the
44
41
  * record of what was typed, and {@link wizardPath} decides what counts — so
@@ -1 +1 @@
1
- {"version":3,"file":"wizard-state.d.ts","sourceRoot":"","sources":["../../src/wizard/wizard-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAAA;KAAE,CAAC;CAChE,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,KAAG,WAIrD,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,eAAe,GAAG,SAAgC,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,WAGF,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,WAAW,GACtB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,SAAS,MAAM,EAGjB,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,cAAc,GACzB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,MAAM,GAAG,SAGX,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,EAClB,QAAQ,MAAM,EACd,OAAO,eAAe,KACrB,WAGD,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAiBvE"}
1
+ {"version":3,"file":"wizard-state.d.ts","sourceRoot":"","sources":["../../src/wizard/wizard-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC/D;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,CAAA;KAAE,CAAC;CAChE,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,GAAI,aAAa,MAAM,KAAG,WAIrD,CAAC;AAEH,oEAAoE;AACpE,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,eAAe,GAAG,SAAgC,CAAC;AAEtD;;;;;;GAMG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,WAGF,CAAC;AAEF,+CAA+C;AAC/C,eAAO,MAAM,WAAW,GACtB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,SAAS,MAAM,EAGjB,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,cAAc,GACzB,OAAO,WAAW,EAClB,QAAQ,MAAM,KACb,MAAM,GAAG,SAGX,CAAC;AAEF,gDAAgD;AAChD,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,EAClB,QAAQ,MAAM,EACd,OAAO,eAAe,KACrB,WAGD,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAiBvE"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/wizard/wizard-state.ts"],"sourcesContent":["import type { EntityDraft } from '../types/EntityDraft.js';\n\n/**\n * What one step holds.\n *\n * A wizard's state is **heterogeneous by construction** — a form step drafts\n * strings, a table step holds a set of ids, a branch point holds the option\n * taken — so this is a discriminated union rather than one shape with optional\n * members, which would let a form step carry ids nothing would ever read.\n *\n * ⚠️ A selection holds `readonly string[]`, **never a `ReadonlySet`**. A `Set`\n * serializes to `{}` and does it silently, which is the fault\n * [ADR 0035](../../../../../../docs/adr/0035-entity-actions-selection-and-bulk.md)\n * named for the wire — and a wizard's state goes through `createJSONStorage` on\n * every keystroke, so it would be met far more often here than there.\n */\nexport type WizardStepValue =\n | { readonly kind: 'form'; readonly values: EntityDraft }\n | { readonly kind: 'selection'; readonly ids: readonly string[] }\n | { readonly kind: 'choice'; readonly option: string }\n | { readonly kind: 'none' };\n\n/**\n * A wizard in progress: where the operator is, how they got there, and what\n * they have answered.\n *\n * **A `type`, never an `interface`.** This is persisted through\n * `createJSONStorage` and therefore has to satisfy `JsonValue`, and TypeScript\n * gives an interface no implicit index signature — the resulting \"Index\n * signature for type 'string' is missing\" explains nothing about the real cause\n * ([ADR 0032](../../../../../../docs/adr/0032-what-may-live-in-an-autosaved-draft.md)).\n *\n * `history` is **the path actually taken**, and it is what Back pops. A branch's\n * inverse is ambiguous once an earlier answer changes, so a computed `previous`\n * would walk the operator back through a path they were never on\n * ([ADR 0045](../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).\n *\n * `steps` is deliberately **not** pruned when a branch is abandoned. It is the\n * record of what was typed, and {@link wizardPath} decides what counts — so\n * flipping back to an earlier answer restores the work instead of discarding it,\n * while the summary and the submit still read only the steps on the live path.\n */\nexport type WizardState = {\n readonly activeStep: string;\n readonly history: readonly string[];\n readonly steps: { readonly [stepId: string]: WizardStepValue };\n};\n\n/** A wizard that has been opened and not yet answered. */\nexport const emptyWizardState = (entryStepId: string): WizardState => ({\n activeStep: entryStepId,\n history: [],\n steps: {},\n});\n\n/** The value stored for a step, or `undefined` when it has none. */\nexport const readStepValue = (\n state: WizardState,\n stepId: string,\n): WizardStepValue | undefined => state.steps[stepId];\n\n/**\n * The draft a form step holds, or an empty one.\n *\n * The fallback is `{}` rather than `undefined` for the reason every draft read\n * in this repo has one: a form handed `undefined` flips its inputs from\n * controlled to uncontrolled mid-render.\n */\nexport const readStepDraft = (\n state: WizardState,\n stepId: string,\n): EntityDraft => {\n const value = state.steps[stepId];\n return value?.kind === 'form' ? value.values : {};\n};\n\n/** The ids a selection step holds, or none. */\nexport const readStepIds = (\n state: WizardState,\n stepId: string,\n): readonly string[] => {\n const value = state.steps[stepId];\n return value?.kind === 'selection' ? value.ids : [];\n};\n\n/** The option a choice step holds, or `undefined` while it is unanswered. */\nexport const readStepChoice = (\n state: WizardState,\n stepId: string,\n): string | undefined => {\n const value = state.steps[stepId];\n return value?.kind === 'choice' ? value.option : undefined;\n};\n\n/** The state with one step's value replaced. */\nexport const withStepValue = (\n state: WizardState,\n stepId: string,\n value: WizardStepValue,\n): WizardState => ({\n ...state,\n steps: { ...state.steps, [stepId]: value },\n});\n\n/**\n * Narrows a restored value to a usable state.\n *\n * A persisted wizard comes back as `JsonValue`, so it can be anything — written\n * by a build before a step existed, `null`, or the right shape with the wrong\n * contents. Every caller must ask before reading, and the answer for a value\n * that fails is a **fresh** wizard rather than a crash: an unreadable draft is\n * an unfinished edit, and `restoreEntityDraft` already discards those per entry\n * rather than losing the whole form.\n *\n * A step value of an unknown `kind` fails the whole state rather than being\n * dropped, deliberately. Dropping it would resume the operator on a step whose\n * answer had silently vanished, which reads as the wizard having forgotten one\n * page of a flow it otherwise restored perfectly.\n */\nexport function readWizardState(value: unknown): WizardState | undefined {\n if (!isRecord(value)) return undefined;\n\n const { activeStep, history, steps } = value;\n if (typeof activeStep !== 'string' || activeStep === '') return undefined;\n if (!Array.isArray(history)) return undefined;\n if (!history.every(entry => typeof entry === 'string')) return undefined;\n if (!isRecord(steps)) return undefined;\n\n const readSteps: Record<string, WizardStepValue> = {};\n for (const [stepId, stepValue] of Object.entries(steps)) {\n const read = readStepValueOf(stepValue);\n if (read === undefined) return undefined;\n readSteps[stepId] = read;\n }\n\n return { activeStep, history, steps: readSteps };\n}\n\nfunction readStepValueOf(value: unknown): WizardStepValue | undefined {\n if (!isRecord(value)) return undefined;\n\n switch (value.kind) {\n case 'none':\n return { kind: 'none' };\n case 'choice':\n return typeof value.option === 'string'\n ? { kind: 'choice', option: value.option }\n : undefined;\n case 'selection':\n return Array.isArray(value.ids) &&\n value.ids.every(id => typeof id === 'string')\n ? { kind: 'selection', ids: value.ids }\n : undefined;\n case 'form':\n return isDraft(value.values)\n ? { kind: 'form', values: value.values }\n : undefined;\n default:\n return undefined;\n }\n}\n\n/**\n * Whether a restored value is a usable {@link EntityDraft}.\n *\n * The same two shapes `EntityDraft` allows, checked the way `isRowDraftArray`\n * checks its half — a scalar is a string and a composition is a list of row\n * drafts, and anything else means the entry was written by something that does\n * not know the contract.\n */\nfunction isDraft(value: unknown): value is EntityDraft {\n return (\n isRecord(value) &&\n Object.values(value).every(\n member =>\n typeof member === 'string' ||\n (Array.isArray(member) && member.every(isRowOfStrings)),\n )\n );\n}\n\nconst isRowOfStrings = (row: unknown): boolean =>\n isRecord(row) && Object.values(row).every(cell => typeof cell === 'string');\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n"],"names":["emptyWizardState","entryStepId","activeStep","history","steps","readStepValue","state","stepId","readStepDraft","value","kind","values","readStepIds","ids","readStepChoice","option","undefined","withStepValue","readWizardState","isRecord","Array","isArray","every","entry","readSteps","stepValue","Object","entries","read","readStepValueOf","id","isDraft","member","isRowOfStrings","row","cell"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/wizard/wizard-state.ts"],"sourcesContent":["import type { EntityDraft } from '../types/EntityDraft.js';\n\n/**\n * What one step holds.\n *\n * A wizard's state is **heterogeneous by construction** — a form step drafts\n * strings, a table step holds a set of ids, a branch point holds the option\n * taken — so this is a discriminated union rather than one shape with optional\n * members, which would let a form step carry ids nothing would ever read.\n *\n * ⚠️ A selection holds `readonly string[]`, **never a `ReadonlySet`**. A `Set`\n * serializes to `{}` and does it silently, which is the fault a bulk\n * selection meets on the wire — and a wizard's state goes through `createJSONStorage` on\n * every keystroke, so it would be met far more often here than there.\n */\nexport type WizardStepValue =\n | { readonly kind: 'form'; readonly values: EntityDraft }\n | { readonly kind: 'selection'; readonly ids: readonly string[] }\n | { readonly kind: 'choice'; readonly option: string }\n | { readonly kind: 'none' };\n\n/**\n * A wizard in progress: where the operator is, how they got there, and what\n * they have answered.\n *\n * **A `type`, never an `interface`.** This is persisted through\n * `createJSONStorage` and therefore has to satisfy `JsonValue`, and TypeScript\n * gives an interface no implicit index signature — the resulting \"Index\n * signature for type 'string' is missing\" explains nothing about the real cause.\n *\n * `history` is **the path actually taken**, and it is what Back pops. A branch's\n * inverse is ambiguous once an earlier answer changes, so a computed `previous`\n * would walk the operator back through a path they were never on.\n *\n * `steps` is deliberately **not** pruned when a branch is abandoned. It is the\n * record of what was typed, and {@link wizardPath} decides what counts — so\n * flipping back to an earlier answer restores the work instead of discarding it,\n * while the summary and the submit still read only the steps on the live path.\n */\nexport type WizardState = {\n readonly activeStep: string;\n readonly history: readonly string[];\n readonly steps: { readonly [stepId: string]: WizardStepValue };\n};\n\n/** A wizard that has been opened and not yet answered. */\nexport const emptyWizardState = (entryStepId: string): WizardState => ({\n activeStep: entryStepId,\n history: [],\n steps: {},\n});\n\n/** The value stored for a step, or `undefined` when it has none. */\nexport const readStepValue = (\n state: WizardState,\n stepId: string,\n): WizardStepValue | undefined => state.steps[stepId];\n\n/**\n * The draft a form step holds, or an empty one.\n *\n * The fallback is `{}` rather than `undefined` for the reason every draft read\n * in this repo has one: a form handed `undefined` flips its inputs from\n * controlled to uncontrolled mid-render.\n */\nexport const readStepDraft = (\n state: WizardState,\n stepId: string,\n): EntityDraft => {\n const value = state.steps[stepId];\n return value?.kind === 'form' ? value.values : {};\n};\n\n/** The ids a selection step holds, or none. */\nexport const readStepIds = (\n state: WizardState,\n stepId: string,\n): readonly string[] => {\n const value = state.steps[stepId];\n return value?.kind === 'selection' ? value.ids : [];\n};\n\n/** The option a choice step holds, or `undefined` while it is unanswered. */\nexport const readStepChoice = (\n state: WizardState,\n stepId: string,\n): string | undefined => {\n const value = state.steps[stepId];\n return value?.kind === 'choice' ? value.option : undefined;\n};\n\n/** The state with one step's value replaced. */\nexport const withStepValue = (\n state: WizardState,\n stepId: string,\n value: WizardStepValue,\n): WizardState => ({\n ...state,\n steps: { ...state.steps, [stepId]: value },\n});\n\n/**\n * Narrows a restored value to a usable state.\n *\n * A persisted wizard comes back as `JsonValue`, so it can be anything — written\n * by a build before a step existed, `null`, or the right shape with the wrong\n * contents. Every caller must ask before reading, and the answer for a value\n * that fails is a **fresh** wizard rather than a crash: an unreadable draft is\n * an unfinished edit, and `restoreEntityDraft` already discards those per entry\n * rather than losing the whole form.\n *\n * A step value of an unknown `kind` fails the whole state rather than being\n * dropped, deliberately. Dropping it would resume the operator on a step whose\n * answer had silently vanished, which reads as the wizard having forgotten one\n * page of a flow it otherwise restored perfectly.\n */\nexport function readWizardState(value: unknown): WizardState | undefined {\n if (!isRecord(value)) return undefined;\n\n const { activeStep, history, steps } = value;\n if (typeof activeStep !== 'string' || activeStep === '') return undefined;\n if (!Array.isArray(history)) return undefined;\n if (!history.every(entry => typeof entry === 'string')) return undefined;\n if (!isRecord(steps)) return undefined;\n\n const readSteps: Record<string, WizardStepValue> = {};\n for (const [stepId, stepValue] of Object.entries(steps)) {\n const read = readStepValueOf(stepValue);\n if (read === undefined) return undefined;\n readSteps[stepId] = read;\n }\n\n return { activeStep, history, steps: readSteps };\n}\n\nfunction readStepValueOf(value: unknown): WizardStepValue | undefined {\n if (!isRecord(value)) return undefined;\n\n switch (value.kind) {\n case 'none':\n return { kind: 'none' };\n case 'choice':\n return typeof value.option === 'string'\n ? { kind: 'choice', option: value.option }\n : undefined;\n case 'selection':\n return Array.isArray(value.ids) &&\n value.ids.every(id => typeof id === 'string')\n ? { kind: 'selection', ids: value.ids }\n : undefined;\n case 'form':\n return isDraft(value.values)\n ? { kind: 'form', values: value.values }\n : undefined;\n default:\n return undefined;\n }\n}\n\n/**\n * Whether a restored value is a usable {@link EntityDraft}.\n *\n * The same two shapes `EntityDraft` allows, checked the way `isRowDraftArray`\n * checks its half — a scalar is a string and a composition is a list of row\n * drafts, and anything else means the entry was written by something that does\n * not know the contract.\n */\nfunction isDraft(value: unknown): value is EntityDraft {\n return (\n isRecord(value) &&\n Object.values(value).every(\n member =>\n typeof member === 'string' ||\n (Array.isArray(member) && member.every(isRowOfStrings)),\n )\n );\n}\n\nconst isRowOfStrings = (row: unknown): boolean =>\n isRecord(row) && Object.values(row).every(cell => typeof cell === 'string');\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n"],"names":["emptyWizardState","entryStepId","activeStep","history","steps","readStepValue","state","stepId","readStepDraft","value","kind","values","readStepIds","ids","readStepChoice","option","undefined","withStepValue","readWizardState","isRecord","Array","isArray","every","entry","readSteps","stepValue","Object","entries","read","readStepValueOf","id","isDraft","member","isRowOfStrings","row","cell"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "The entity system: decorators that make a class describe itself, entity serialization, the RSQL query protocol and the wire envelopes.",
5
5
  "license": "MIT",
6
6
  "repository": {