@getodk/xforms-engine 0.1.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 (208) hide show
  1. package/README.md +44 -0
  2. package/dist/.vite/manifest.json +7 -0
  3. package/dist/XFormDOM.d.ts +31 -0
  4. package/dist/XFormDataType.d.ts +26 -0
  5. package/dist/XFormDefinition.d.ts +14 -0
  6. package/dist/body/BodyDefinition.d.ts +52 -0
  7. package/dist/body/BodyElementDefinition.d.ts +32 -0
  8. package/dist/body/RepeatDefinition.d.ts +15 -0
  9. package/dist/body/UnsupportedBodyElementDefinition.d.ts +10 -0
  10. package/dist/body/control/ControlDefinition.d.ts +16 -0
  11. package/dist/body/control/InputDefinition.d.ts +5 -0
  12. package/dist/body/control/select/ItemDefinition.d.ts +13 -0
  13. package/dist/body/control/select/ItemsetDefinition.d.ts +16 -0
  14. package/dist/body/control/select/ItemsetNodesetContext.d.ts +11 -0
  15. package/dist/body/control/select/ItemsetNodesetExpression.d.ts +5 -0
  16. package/dist/body/control/select/ItemsetValueExpression.d.ts +6 -0
  17. package/dist/body/control/select/SelectDefinition.d.ts +23 -0
  18. package/dist/body/group/BaseGroupDefinition.d.ts +46 -0
  19. package/dist/body/group/LogicalGroupDefinition.d.ts +6 -0
  20. package/dist/body/group/PresentationGroupDefinition.d.ts +11 -0
  21. package/dist/body/group/RepeatGroupDefinition.d.ts +12 -0
  22. package/dist/body/group/StructuralGroupDefinition.d.ts +6 -0
  23. package/dist/body/text/HintDefinition.d.ts +11 -0
  24. package/dist/body/text/LabelDefinition.d.ts +20 -0
  25. package/dist/body/text/TextElementDefinition.d.ts +32 -0
  26. package/dist/body/text/TextElementOutputPart.d.ts +12 -0
  27. package/dist/body/text/TextElementPart.d.ts +12 -0
  28. package/dist/body/text/TextElementReferencePart.d.ts +6 -0
  29. package/dist/body/text/TextElementStaticPart.d.ts +6 -0
  30. package/dist/client/BaseNode.d.ts +138 -0
  31. package/dist/client/EngineConfig.d.ts +78 -0
  32. package/dist/client/FormLanguage.d.ts +63 -0
  33. package/dist/client/GroupNode.d.ts +24 -0
  34. package/dist/client/OpaqueReactiveObjectFactory.d.ts +70 -0
  35. package/dist/client/RepeatInstanceNode.d.ts +28 -0
  36. package/dist/client/RepeatRangeNode.d.ts +94 -0
  37. package/dist/client/RootNode.d.ts +31 -0
  38. package/dist/client/SelectNode.d.ts +60 -0
  39. package/dist/client/StringNode.d.ts +41 -0
  40. package/dist/client/SubtreeNode.d.ts +52 -0
  41. package/dist/client/TextRange.d.ts +55 -0
  42. package/dist/client/hierarchy.d.ts +48 -0
  43. package/dist/client/index.d.ts +11 -0
  44. package/dist/client/node-types.d.ts +1 -0
  45. package/dist/expression/DependencyContext.d.ts +12 -0
  46. package/dist/expression/DependentExpression.d.ts +43 -0
  47. package/dist/index.d.ts +16 -0
  48. package/dist/index.js +37622 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/instance/Group.d.ts +31 -0
  51. package/dist/instance/RepeatInstance.d.ts +60 -0
  52. package/dist/instance/RepeatRange.d.ts +81 -0
  53. package/dist/instance/Root.d.ts +70 -0
  54. package/dist/instance/SelectField.d.ts +45 -0
  55. package/dist/instance/StringField.d.ts +39 -0
  56. package/dist/instance/Subtree.d.ts +30 -0
  57. package/dist/instance/abstract/DescendantNode.d.ts +76 -0
  58. package/dist/instance/abstract/InstanceNode.d.ts +107 -0
  59. package/dist/instance/children.d.ts +2 -0
  60. package/dist/instance/hierarchy.d.ts +12 -0
  61. package/dist/instance/identity.d.ts +7 -0
  62. package/dist/instance/index.d.ts +8 -0
  63. package/dist/instance/internal-api/EvaluationContext.d.ts +34 -0
  64. package/dist/instance/internal-api/InstanceConfig.d.ts +8 -0
  65. package/dist/instance/internal-api/SubscribableDependency.d.ts +59 -0
  66. package/dist/instance/internal-api/TranslationContext.d.ts +4 -0
  67. package/dist/instance/internal-api/ValueContext.d.ts +22 -0
  68. package/dist/instance/resource.d.ts +10 -0
  69. package/dist/instance/text/FormattedTextStub.d.ts +1 -0
  70. package/dist/instance/text/TextChunk.d.ts +11 -0
  71. package/dist/instance/text/TextRange.d.ts +10 -0
  72. package/dist/lib/dom/query.d.ts +20 -0
  73. package/dist/lib/reactivity/createChildrenState.d.ts +36 -0
  74. package/dist/lib/reactivity/createComputedExpression.d.ts +12 -0
  75. package/dist/lib/reactivity/createSelectItems.d.ts +16 -0
  76. package/dist/lib/reactivity/createValueState.d.ts +44 -0
  77. package/dist/lib/reactivity/materializeCurrentStateChildren.d.ts +18 -0
  78. package/dist/lib/reactivity/node-state/createClientState.d.ts +9 -0
  79. package/dist/lib/reactivity/node-state/createCurrentState.d.ts +6 -0
  80. package/dist/lib/reactivity/node-state/createEngineState.d.ts +5 -0
  81. package/dist/lib/reactivity/node-state/createSharedNodeState.d.ts +22 -0
  82. package/dist/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.d.ts +6 -0
  83. package/dist/lib/reactivity/node-state/createSpecifiedState.d.ts +139 -0
  84. package/dist/lib/reactivity/node-state/representations.d.ts +25 -0
  85. package/dist/lib/reactivity/scope.d.ts +23 -0
  86. package/dist/lib/reactivity/text/createFieldHint.d.ts +5 -0
  87. package/dist/lib/reactivity/text/createNodeLabel.d.ts +5 -0
  88. package/dist/lib/reactivity/text/createTextRange.d.ts +19 -0
  89. package/dist/lib/reactivity/types.d.ts +21 -0
  90. package/dist/lib/unique-id.d.ts +27 -0
  91. package/dist/lib/xpath/analysis.d.ts +22 -0
  92. package/dist/model/BindComputation.d.ts +30 -0
  93. package/dist/model/BindDefinition.d.ts +31 -0
  94. package/dist/model/BindElement.d.ts +6 -0
  95. package/dist/model/DescendentNodeDefinition.d.ts +25 -0
  96. package/dist/model/ModelBindMap.d.ts +15 -0
  97. package/dist/model/ModelDefinition.d.ts +10 -0
  98. package/dist/model/NodeDefinition.d.ts +74 -0
  99. package/dist/model/RepeatInstanceDefinition.d.ts +15 -0
  100. package/dist/model/RepeatSequenceDefinition.d.ts +19 -0
  101. package/dist/model/RepeatTemplateDefinition.d.ts +29 -0
  102. package/dist/model/RootDefinition.d.ts +24 -0
  103. package/dist/model/SubtreeDefinition.d.ts +14 -0
  104. package/dist/model/ValueNodeDefinition.d.ts +15 -0
  105. package/dist/solid.js +37273 -0
  106. package/dist/solid.js.map +1 -0
  107. package/package.json +87 -0
  108. package/src/XFormDOM.ts +224 -0
  109. package/src/XFormDataType.ts +64 -0
  110. package/src/XFormDefinition.ts +40 -0
  111. package/src/body/BodyDefinition.ts +202 -0
  112. package/src/body/BodyElementDefinition.ts +62 -0
  113. package/src/body/RepeatDefinition.ts +54 -0
  114. package/src/body/UnsupportedBodyElementDefinition.ts +17 -0
  115. package/src/body/control/ControlDefinition.ts +42 -0
  116. package/src/body/control/InputDefinition.ts +9 -0
  117. package/src/body/control/select/ItemDefinition.ts +31 -0
  118. package/src/body/control/select/ItemsetDefinition.ts +36 -0
  119. package/src/body/control/select/ItemsetNodesetContext.ts +26 -0
  120. package/src/body/control/select/ItemsetNodesetExpression.ts +8 -0
  121. package/src/body/control/select/ItemsetValueExpression.ts +11 -0
  122. package/src/body/control/select/SelectDefinition.ts +74 -0
  123. package/src/body/group/BaseGroupDefinition.ts +137 -0
  124. package/src/body/group/LogicalGroupDefinition.ts +11 -0
  125. package/src/body/group/PresentationGroupDefinition.ts +28 -0
  126. package/src/body/group/RepeatGroupDefinition.ts +91 -0
  127. package/src/body/group/StructuralGroupDefinition.ts +11 -0
  128. package/src/body/text/HintDefinition.ts +26 -0
  129. package/src/body/text/LabelDefinition.ts +54 -0
  130. package/src/body/text/TextElementDefinition.ts +97 -0
  131. package/src/body/text/TextElementOutputPart.ts +27 -0
  132. package/src/body/text/TextElementPart.ts +31 -0
  133. package/src/body/text/TextElementReferencePart.ts +21 -0
  134. package/src/body/text/TextElementStaticPart.ts +26 -0
  135. package/src/client/BaseNode.ts +180 -0
  136. package/src/client/EngineConfig.ts +83 -0
  137. package/src/client/FormLanguage.ts +77 -0
  138. package/src/client/GroupNode.ts +33 -0
  139. package/src/client/OpaqueReactiveObjectFactory.ts +100 -0
  140. package/src/client/README.md +39 -0
  141. package/src/client/RepeatInstanceNode.ts +41 -0
  142. package/src/client/RepeatRangeNode.ts +100 -0
  143. package/src/client/RootNode.ts +36 -0
  144. package/src/client/SelectNode.ts +69 -0
  145. package/src/client/StringNode.ts +46 -0
  146. package/src/client/SubtreeNode.ts +57 -0
  147. package/src/client/TextRange.ts +63 -0
  148. package/src/client/hierarchy.ts +63 -0
  149. package/src/client/index.ts +29 -0
  150. package/src/client/node-types.ts +10 -0
  151. package/src/expression/DependencyContext.ts +53 -0
  152. package/src/expression/DependentExpression.ts +102 -0
  153. package/src/index.ts +35 -0
  154. package/src/instance/Group.ts +82 -0
  155. package/src/instance/RepeatInstance.ts +164 -0
  156. package/src/instance/RepeatRange.ts +214 -0
  157. package/src/instance/Root.ts +264 -0
  158. package/src/instance/SelectField.ts +204 -0
  159. package/src/instance/StringField.ts +93 -0
  160. package/src/instance/Subtree.ts +79 -0
  161. package/src/instance/abstract/DescendantNode.ts +182 -0
  162. package/src/instance/abstract/InstanceNode.ts +257 -0
  163. package/src/instance/children.ts +52 -0
  164. package/src/instance/hierarchy.ts +54 -0
  165. package/src/instance/identity.ts +11 -0
  166. package/src/instance/index.ts +37 -0
  167. package/src/instance/internal-api/EvaluationContext.ts +41 -0
  168. package/src/instance/internal-api/InstanceConfig.ts +9 -0
  169. package/src/instance/internal-api/SubscribableDependency.ts +61 -0
  170. package/src/instance/internal-api/TranslationContext.ts +5 -0
  171. package/src/instance/internal-api/ValueContext.ts +27 -0
  172. package/src/instance/resource.ts +75 -0
  173. package/src/instance/text/FormattedTextStub.ts +8 -0
  174. package/src/instance/text/TextChunk.ts +20 -0
  175. package/src/instance/text/TextRange.ts +23 -0
  176. package/src/lib/dom/query.ts +49 -0
  177. package/src/lib/reactivity/createChildrenState.ts +60 -0
  178. package/src/lib/reactivity/createComputedExpression.ts +114 -0
  179. package/src/lib/reactivity/createSelectItems.ts +163 -0
  180. package/src/lib/reactivity/createValueState.ts +258 -0
  181. package/src/lib/reactivity/materializeCurrentStateChildren.ts +121 -0
  182. package/src/lib/reactivity/node-state/createClientState.ts +51 -0
  183. package/src/lib/reactivity/node-state/createCurrentState.ts +27 -0
  184. package/src/lib/reactivity/node-state/createEngineState.ts +18 -0
  185. package/src/lib/reactivity/node-state/createSharedNodeState.ts +79 -0
  186. package/src/lib/reactivity/node-state/createSpecifiedPropertyDescriptor.ts +85 -0
  187. package/src/lib/reactivity/node-state/createSpecifiedState.ts +229 -0
  188. package/src/lib/reactivity/node-state/representations.ts +64 -0
  189. package/src/lib/reactivity/scope.ts +106 -0
  190. package/src/lib/reactivity/text/createFieldHint.ts +16 -0
  191. package/src/lib/reactivity/text/createNodeLabel.ts +16 -0
  192. package/src/lib/reactivity/text/createTextRange.ts +155 -0
  193. package/src/lib/reactivity/types.ts +27 -0
  194. package/src/lib/unique-id.ts +34 -0
  195. package/src/lib/xpath/analysis.ts +241 -0
  196. package/src/model/BindComputation.ts +88 -0
  197. package/src/model/BindDefinition.ts +104 -0
  198. package/src/model/BindElement.ts +8 -0
  199. package/src/model/DescendentNodeDefinition.ts +56 -0
  200. package/src/model/ModelBindMap.ts +71 -0
  201. package/src/model/ModelDefinition.ts +19 -0
  202. package/src/model/NodeDefinition.ts +146 -0
  203. package/src/model/RepeatInstanceDefinition.ts +39 -0
  204. package/src/model/RepeatSequenceDefinition.ts +53 -0
  205. package/src/model/RepeatTemplateDefinition.ts +150 -0
  206. package/src/model/RootDefinition.ts +121 -0
  207. package/src/model/SubtreeDefinition.ts +50 -0
  208. package/src/model/ValueNodeDefinition.ts +39 -0
@@ -0,0 +1,182 @@
1
+ import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
+ import type { Accessor } from 'solid-js';
3
+ import { createMemo } from 'solid-js';
4
+ import type { BaseNode } from '../../client/BaseNode.ts';
5
+ import { createComputedExpression } from '../../lib/reactivity/createComputedExpression.ts';
6
+ import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
7
+ import type { AnyDescendantNodeDefinition } from '../../model/DescendentNodeDefinition.ts';
8
+ import type { AnyNodeDefinition } from '../../model/NodeDefinition.ts';
9
+ import type { RepeatInstanceDefinition } from '../../model/RepeatInstanceDefinition.ts';
10
+ import type { ValueNodeDefinition } from '../../model/ValueNodeDefinition.ts';
11
+ import type { RepeatInstance } from '../RepeatInstance.ts';
12
+ import type { RepeatRange } from '../RepeatRange.ts';
13
+ import type { Root } from '../Root.ts';
14
+ import type { AnyChildNode, GeneralParentNode } from '../hierarchy.ts';
15
+ import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
16
+ import type { SubscribableDependency } from '../internal-api/SubscribableDependency.ts';
17
+ import type { InstanceNodeStateSpec } from './InstanceNode.ts';
18
+ import { InstanceNode } from './InstanceNode.ts';
19
+
20
+ export interface DescendantNodeSharedStateSpec {
21
+ readonly reference: Accessor<string>;
22
+ readonly readonly: Accessor<boolean>;
23
+ readonly relevant: Accessor<boolean>;
24
+ readonly required: Accessor<boolean>;
25
+ }
26
+
27
+ // prettier-ignore
28
+ export type DescendantNodeStateSpec<Value = never> =
29
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
30
+ & InstanceNodeStateSpec<Value>
31
+ & DescendantNodeSharedStateSpec;
32
+
33
+ // prettier-ignore
34
+ export type DescendantNodeDefinition = Extract<
35
+ AnyNodeDefinition,
36
+ AnyDescendantNodeDefinition
37
+ >;
38
+
39
+ // prettier-ignore
40
+ export type DescendantNodeParent<Definition extends DescendantNodeDefinition> =
41
+ Definition extends ValueNodeDefinition
42
+ ? GeneralParentNode
43
+ : Definition extends RepeatInstanceDefinition
44
+ ? RepeatRange
45
+ : GeneralParentNode;
46
+
47
+ export type AnyDescendantNode = DescendantNode<
48
+ DescendantNodeDefinition,
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ DescendantNodeStateSpec<any>,
51
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
+ any
53
+ >;
54
+
55
+ export abstract class DescendantNode<
56
+ Definition extends DescendantNodeDefinition,
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ Spec extends DescendantNodeStateSpec<any>,
59
+ Child extends AnyChildNode | null = null,
60
+ >
61
+ extends InstanceNode<Definition, Spec, Child>
62
+ implements BaseNode, EvaluationContext, SubscribableDependency
63
+ {
64
+ readonly root: Root;
65
+ readonly evaluator: XFormsXPathEvaluator;
66
+ readonly contextNode: Element;
67
+
68
+ constructor(
69
+ override readonly parent: DescendantNodeParent<Definition>,
70
+ override readonly definition: Definition
71
+ ) {
72
+ super(parent.engineConfig, parent, definition);
73
+
74
+ const { evaluator, root } = parent;
75
+
76
+ this.root = root;
77
+ this.evaluator = evaluator;
78
+ this.contextNode = this.initializeContextNode(parent.contextNode, definition.nodeName);
79
+ }
80
+
81
+ protected computeChildStepReference(parent: DescendantNodeParent<Definition>): string {
82
+ return `${parent.contextReference}/${this.definition.nodeName}`;
83
+ }
84
+
85
+ protected abstract override computeReference(
86
+ parent: DescendantNodeParent<Definition>,
87
+ definition: Definition
88
+ ): string;
89
+
90
+ protected buildSharedStateSpec(
91
+ parent: DescendantNodeParent<Definition>,
92
+ definition: Definition
93
+ ): DescendantNodeSharedStateSpec {
94
+ return this.scope.runTask(() => {
95
+ const reference = createMemo(() => this.contextReference);
96
+ const { bind } = definition;
97
+
98
+ // TODO: we can likely short-circuit `readonly` computation when a node
99
+ // is non-relevant.
100
+ const selfReadonly = createComputedExpression(this, bind.readonly);
101
+ const readonly = createMemo(() => {
102
+ return parent.isReadonly || selfReadonly();
103
+ });
104
+
105
+ const selfRelevant = createComputedExpression(this, bind.relevant);
106
+ const relevant = createMemo(() => {
107
+ return parent.isRelevant && selfRelevant();
108
+ });
109
+
110
+ // TODO: we can likely short-circuit `required` computation when a node
111
+ // is non-relevant.
112
+ const required = createComputedExpression(this, bind.required);
113
+
114
+ return {
115
+ reference,
116
+ readonly,
117
+ relevant,
118
+ required,
119
+ };
120
+ });
121
+ }
122
+
123
+ protected createContextNode(parentContextNode: Element, nodeName: string): Element {
124
+ return parentContextNode.ownerDocument.createElement(nodeName);
125
+ }
126
+
127
+ /**
128
+ * Currently expected to be overridden by...
129
+ *
130
+ * - Repeat range: returns its parent's context node, because it doesn't have
131
+ * a node in the primary instance tree.
132
+ *
133
+ * - Repeat instance: returns its created context node, but overrides handles
134
+ * appending behavior separately (for inserting at the end of its parent
135
+ * range, or even at an arbitrary index within the range, after instance
136
+ * creation is has completed).
137
+ */
138
+ protected initializeContextNode(parentContextNode: Element, nodeName: string): Element {
139
+ const element = this.createContextNode(parentContextNode, nodeName);
140
+
141
+ parentContextNode.append(element);
142
+
143
+ return element;
144
+ }
145
+
146
+ /**
147
+ * @package
148
+ *
149
+ * Performs recursive removal, first of the node's descendants, then of the
150
+ * node itself. For all {@link DescendantNode}s, removal involves **at least**
151
+ * disposal of its {@link scope} ({@link ReactiveScope}).
152
+ *
153
+ * It is expected that the outermost node targeted for removal will always be
154
+ * a {@link RepeatInstance}. @see {@link RepeatInstance.remove} for additional
155
+ * details.
156
+ *
157
+ * It is also expected that upon that outermost node's removal, its parent
158
+ * {@link RepeatRange} will perform a reactive update to its children state so
159
+ * that:
160
+ *
161
+ * 1. Any downstream computations affected by the removal are updated.
162
+ * 2. The client invoking removal is also reactively updated (where
163
+ * applicable).
164
+ *
165
+ * @see {@link RepeatInstance.remove} and {@link RepeatRange.removeInstances}
166
+ * for additional details about their respective node-specific removal
167
+ * behaviors and ordering.
168
+ *
169
+ * @todo Possibly retain removed repeat instances in memory. This came up as a
170
+ * behavior of Collect/JavaRosa, and we should investigate the details and
171
+ * ramifications of that, and whether it's the desired behavior.
172
+ */
173
+ remove(this: AnyChildNode): void {
174
+ this.scope.runTask(() => {
175
+ this.getChildren().forEach((child) => {
176
+ child.remove();
177
+ });
178
+ });
179
+
180
+ this.scope.dispose();
181
+ }
182
+ }
@@ -0,0 +1,257 @@
1
+ import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
+ import type { Accessor, Signal } from 'solid-js';
3
+ import { createSignal } from 'solid-js';
4
+ import type { BaseNode } from '../../client/BaseNode.ts';
5
+ import type { InstanceNodeType } from '../../client/node-types.ts';
6
+ import type { TextRange } from '../../index.ts';
7
+ import type { MaterializedChildren } from '../../lib/reactivity/materializeCurrentStateChildren.ts';
8
+ import type { CurrentState } from '../../lib/reactivity/node-state/createCurrentState.ts';
9
+ import type { EngineState } from '../../lib/reactivity/node-state/createEngineState.ts';
10
+ import type { SharedNodeState } from '../../lib/reactivity/node-state/createSharedNodeState.ts';
11
+ import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
12
+ import { createReactiveScope } from '../../lib/reactivity/scope.ts';
13
+ import type { SimpleAtomicState } from '../../lib/reactivity/types.ts';
14
+ import type { AnyNodeDefinition } from '../../model/NodeDefinition.ts';
15
+ import type { Root } from '../Root.ts';
16
+ import type { AnyChildNode, AnyNode, AnyParentNode } from '../hierarchy.ts';
17
+ import type { NodeID } from '../identity.ts';
18
+ import { declareNodeID } from '../identity.ts';
19
+ import type { EvaluationContext } from '../internal-api/EvaluationContext.ts';
20
+ import type { InstanceConfig } from '../internal-api/InstanceConfig.ts';
21
+ import type { SubscribableDependency } from '../internal-api/SubscribableDependency.ts';
22
+
23
+ export interface InstanceNodeStateSpec<Value = never> {
24
+ readonly reference: Accessor<string> | string;
25
+ readonly readonly: Accessor<boolean> | boolean;
26
+ readonly relevant: Accessor<boolean> | boolean;
27
+ readonly required: Accessor<boolean> | boolean;
28
+ readonly label: Accessor<TextRange<'label'> | null> | null;
29
+ readonly hint: Accessor<TextRange<'hint'> | null> | null;
30
+ readonly children: Accessor<readonly NodeID[]> | null;
31
+ readonly valueOptions: Accessor<null> | Accessor<readonly unknown[]> | null;
32
+ readonly value: Signal<Value> | SimpleAtomicState<Value> | null;
33
+ }
34
+
35
+ type AnyInstanceNode = InstanceNode<
36
+ AnyNodeDefinition,
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ InstanceNodeStateSpec<any>,
39
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
+ any
41
+ >;
42
+
43
+ interface InitializedStateOptions<T, K extends keyof T> {
44
+ readonly uninitializedFallback: T[K];
45
+ }
46
+
47
+ /**
48
+ * This type has the same effect as {@link MaterializedChildren}, but abstractly
49
+ * handles leaf node types as well.
50
+ */
51
+ // prettier-ignore
52
+ export type InstanceNodeCurrentState<
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ Spec extends InstanceNodeStateSpec<any>,
55
+ Child
56
+ > =
57
+ & CurrentState<Omit<Spec, 'children'>>
58
+ & {
59
+ readonly children: [Child] extends [AnyChildNode]
60
+ ? readonly Child[]
61
+ : null;
62
+ };
63
+
64
+ export abstract class InstanceNode<
65
+ Definition extends AnyNodeDefinition,
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ Spec extends InstanceNodeStateSpec<any>,
68
+ Child extends AnyChildNode | null = null,
69
+ >
70
+ implements BaseNode, EvaluationContext, SubscribableDependency
71
+ {
72
+ protected readonly isStateInitialized: Accessor<boolean>;
73
+
74
+ protected abstract readonly state: SharedNodeState<Spec>;
75
+ protected abstract readonly engineState: EngineState<Spec>;
76
+
77
+ /**
78
+ * Provides a generalized mechanism for accessing a reactive state value
79
+ * during a node's construction, while {@link engineState} is still being
80
+ * defined and thus isn't assigned.
81
+ *
82
+ * The fallback value specified in {@link options} will be returned on access
83
+ * until {@link isStateInitialized} returns true. This ensures:
84
+ *
85
+ * - a value of the expected type will be available
86
+ * - any read access will become reactive to the actual state, once it has
87
+ * been initialized and {@link engineState} is assigned
88
+ *
89
+ * @todo This is one among several chicken/egg problems encountered trying to
90
+ * support state initialization in which some aspects of the state derive from
91
+ * other aspects of it. It would be nice to dispense with this entirely. But
92
+ * if it must persist, we should also consider replacing the method with a
93
+ * direct accessor once state initialization completes, so the initialized
94
+ * check is only called until it becomes impertinent.
95
+ */
96
+ protected getInitializedState<K extends keyof EngineState<Spec>>(
97
+ key: K,
98
+ options: InitializedStateOptions<EngineState<Spec>, K>
99
+ ): EngineState<Spec>[K] {
100
+ if (this.isStateInitialized()) {
101
+ return this.engineState[key];
102
+ }
103
+
104
+ return options.uninitializedFallback;
105
+ }
106
+
107
+ /**
108
+ * @package Exposed on every node type to facilitate inheritance, as well as
109
+ * conditional behavior for value nodes.
110
+ */
111
+ get isReadonly(): boolean {
112
+ return (this as AnyInstanceNode).getInitializedState('readonly', {
113
+ uninitializedFallback: false,
114
+ });
115
+ }
116
+
117
+ /**
118
+ * @package Exposed on every node type to facilitate inheritance, as well as
119
+ * conditional behavior for value nodes.
120
+ */
121
+ get isRelevant(): boolean {
122
+ return (this as AnyInstanceNode).getInitializedState('relevant', {
123
+ uninitializedFallback: true,
124
+ });
125
+ }
126
+
127
+ // BaseNode: identity
128
+ readonly nodeId: NodeID;
129
+
130
+ // BaseNode: node types and variants (e.g. for narrowing)
131
+ abstract readonly nodeType: InstanceNodeType;
132
+
133
+ abstract readonly currentState: InstanceNodeCurrentState<Spec, Child>;
134
+
135
+ // BaseNode: structural
136
+ abstract readonly root: Root;
137
+
138
+ // EvaluationContext: instance-global/shared
139
+ abstract readonly evaluator: XFormsXPathEvaluator;
140
+
141
+ // EvaluationContext *and* Subscribable: node-specific
142
+ readonly scope: ReactiveScope;
143
+
144
+ // EvaluationContext: node-specific
145
+ get contextReference(): string {
146
+ return this.computeReference(this.parent, this.definition);
147
+ }
148
+
149
+ abstract readonly contextNode: Element;
150
+
151
+ constructor(
152
+ readonly engineConfig: InstanceConfig,
153
+ readonly parent: AnyParentNode | null,
154
+ readonly definition: Definition
155
+ ) {
156
+ this.scope = createReactiveScope();
157
+ this.engineConfig = engineConfig;
158
+ this.nodeId = declareNodeID(engineConfig.createUniqueId());
159
+ this.definition = definition;
160
+
161
+ const checkStateInitialized = () => this.engineState != null;
162
+ const [isStateInitialized, setStateInitialized] = createSignal(checkStateInitialized());
163
+
164
+ this.isStateInitialized = isStateInitialized;
165
+
166
+ queueMicrotask(() => {
167
+ if (checkStateInitialized()) {
168
+ setStateInitialized(true);
169
+ } else {
170
+ throw new Error('Node state was never initialized');
171
+ }
172
+ });
173
+ }
174
+
175
+ /**
176
+ * @package This presently serves a few internal use cases, where certain
177
+ * behaviors depend on arbitrary traversal from any point in the instance
178
+ * tree, without particular regard for the visited node type. It isn't
179
+ * intended for external traversal or any other means of consuming children by
180
+ * a client. This return type intentionally deviates from one structural
181
+ * expectation, requiring even leaf nodes to return an array (though for those
182
+ * nodes it will always be empty). This affords consistency and efficiency of
183
+ * interface for those internal uses.
184
+ */
185
+ abstract getChildren(this: AnyInstanceNode): readonly AnyChildNode[];
186
+
187
+ protected abstract computeReference(
188
+ parent: AnyInstanceNode | null,
189
+ definition: Definition
190
+ ): string;
191
+
192
+ getNodeByReference(
193
+ this: AnyNode,
194
+ visited: WeakSet<AnyNode>,
195
+ dependencyReference: string
196
+ ): SubscribableDependency | null {
197
+ if (visited.has(this)) {
198
+ return null;
199
+ }
200
+
201
+ visited.add(this);
202
+
203
+ const { nodeset } = this.definition;
204
+
205
+ if (dependencyReference === nodeset) {
206
+ return this;
207
+ }
208
+
209
+ if (
210
+ dependencyReference.startsWith(`${nodeset}/`) ||
211
+ dependencyReference.startsWith(`${nodeset}[`)
212
+ ) {
213
+ const children = this.getChildren();
214
+
215
+ if (children == null) {
216
+ return null;
217
+ }
218
+
219
+ for (const child of children) {
220
+ const dependency = child.getNodeByReference(visited, dependencyReference);
221
+
222
+ if (dependency != null) {
223
+ return dependency;
224
+ }
225
+ }
226
+ }
227
+
228
+ return this.parent?.getNodeByReference(visited, dependencyReference) ?? null;
229
+ }
230
+
231
+ // EvaluationContext: node-relative
232
+ getSubscribableDependencyByReference(
233
+ this: AnyNode,
234
+ reference: string
235
+ ): SubscribableDependency | null {
236
+ const visited = new WeakSet<SubscribableDependency>();
237
+
238
+ return this.getNodeByReference(visited, reference);
239
+ }
240
+
241
+ // SubscribableDependency
242
+ /**
243
+ * This is a default implementation suitable for most node types. The rest
244
+ * (currently: `Root`, `RepeatRange`, `RepeatInstance`) should likely extend
245
+ * this behavior, rather than simply overriding it.
246
+ */
247
+ subscribe(): void {
248
+ const { engineState } = this;
249
+
250
+ if (engineState.relevant) {
251
+ engineState.reference;
252
+ engineState.relevant;
253
+ engineState.children;
254
+ engineState.value;
255
+ }
256
+ }
257
+ }
@@ -0,0 +1,52 @@
1
+ import { UnreachableError } from '@getodk/common/lib/error/UnreachableError.ts';
2
+ import { SelectDefinition } from '../body/control/select/SelectDefinition.ts';
3
+ import type { GroupDefinition } from '../client/GroupNode.ts';
4
+ import type { SubtreeDefinition } from '../client/SubtreeNode.ts';
5
+ import type { SubtreeDefinition as ModelSubtreeDefinition } from '../model/SubtreeDefinition.ts';
6
+ import { Group } from './Group.ts';
7
+ import { RepeatRange } from './RepeatRange.ts';
8
+ import { SelectField, type SelectFieldDefinition } from './SelectField.ts';
9
+ import { StringField } from './StringField.ts';
10
+ import { Subtree } from './Subtree.ts';
11
+ import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts';
12
+
13
+ const isSubtreeDefinition = (
14
+ definition: ModelSubtreeDefinition
15
+ ): definition is SubtreeDefinition => {
16
+ return definition.bodyElement == null;
17
+ };
18
+
19
+ export const buildChildren = (parent: GeneralParentNode): GeneralChildNode[] => {
20
+ const { children } = parent.definition;
21
+
22
+ return children.map((child): GeneralChildNode => {
23
+ switch (child.type) {
24
+ case 'subtree': {
25
+ if (isSubtreeDefinition(child)) {
26
+ return new Subtree(parent, child);
27
+ }
28
+
29
+ // TODO: it'd be good to be able to do without this type assertion. The
30
+ // only distinction between the types is whether `bodyElement` is
31
+ // `null`, but for some reason that's insufficient to narrow the union.
32
+ return new Group(parent, child as GroupDefinition);
33
+ }
34
+
35
+ case 'repeat-sequence': {
36
+ return new RepeatRange(parent, child);
37
+ }
38
+
39
+ case 'value-node': {
40
+ if (child.bodyElement instanceof SelectDefinition) {
41
+ return new SelectField(parent, child as SelectFieldDefinition);
42
+ }
43
+
44
+ return new StringField(parent, child);
45
+ }
46
+
47
+ default: {
48
+ throw new UnreachableError(child);
49
+ }
50
+ }
51
+ });
52
+ };
@@ -0,0 +1,54 @@
1
+ import type { Group } from './Group.ts';
2
+ import type { RepeatInstance } from './RepeatInstance.ts';
3
+ import type { RepeatRange } from './RepeatRange.ts';
4
+ import type { Root } from './Root.ts';
5
+ import type { SelectField } from './SelectField.ts';
6
+ import type { StringField } from './StringField.ts';
7
+ import type { Subtree } from './Subtree.ts';
8
+
9
+ // prettier-ignore
10
+ export type AnyNode =
11
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
12
+ | Root
13
+ | Group
14
+ | Subtree
15
+ | RepeatRange
16
+ | RepeatInstance
17
+ | StringField
18
+ | SelectField;
19
+
20
+ // prettier-ignore
21
+ export type AnyParentNode =
22
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
23
+ | Root
24
+ | Group
25
+ | Subtree
26
+ | RepeatRange
27
+ | RepeatInstance;
28
+
29
+ // prettier-ignore
30
+ export type GeneralParentNode =
31
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
32
+ | Root
33
+ | Group
34
+ | Subtree
35
+ | RepeatInstance;
36
+
37
+ // prettier-ignore
38
+ export type AnyChildNode =
39
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
40
+ | Group
41
+ | Subtree
42
+ | RepeatRange
43
+ | RepeatInstance
44
+ | StringField
45
+ | SelectField;
46
+
47
+ // prettier-ignore
48
+ export type GeneralChildNode =
49
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
50
+ | Group
51
+ | Subtree
52
+ | RepeatRange
53
+ | StringField
54
+ | SelectField;
@@ -0,0 +1,11 @@
1
+ declare const NODE_ID_BRAND: unique symbol;
2
+ type NODE_ID_BRAND = typeof NODE_ID_BRAND;
3
+
4
+ export type NodeID = string & { readonly [NODE_ID_BRAND]: true };
5
+
6
+ // Just another added safeguard to ensure we're not mistakenly handling
7
+ // rando `NodeID` strings which aren't explicitly attached to the node
8
+ // types we expect.
9
+ export const declareNodeID = (id: string): NodeID => {
10
+ return id as NodeID;
11
+ };
@@ -0,0 +1,37 @@
1
+ import { identity } from '@getodk/common/lib/identity.ts';
2
+ import { XFormDefinition } from '../XFormDefinition.ts';
3
+ import type { RootNode } from '../client/RootNode.ts';
4
+ import type {
5
+ InitializeFormOptions as BaseInitializeFormOptions,
6
+ FormResource,
7
+ InitializeForm,
8
+ } from '../client/index.ts';
9
+ import { retrieveSourceXMLResource } from '../instance/resource.ts';
10
+ import { createUniqueId } from '../lib/unique-id.ts';
11
+ import { Root } from './Root.ts';
12
+ import type { InstanceConfig } from './internal-api/InstanceConfig.ts';
13
+
14
+ interface InitializeFormOptions extends BaseInitializeFormOptions {
15
+ readonly config: Partial<InstanceConfig>;
16
+ }
17
+
18
+ const buildInstanceConfig = (options: Partial<InstanceConfig> = {}): InstanceConfig => {
19
+ return {
20
+ createUniqueId: options.createUniqueId ?? createUniqueId,
21
+ fetchResource: options.fetchResource ?? fetch,
22
+ stateFactory: options.stateFactory ?? identity,
23
+ };
24
+ };
25
+
26
+ export const initializeForm = async (
27
+ input: FormResource,
28
+ options: Partial<InitializeFormOptions> = {}
29
+ ): Promise<RootNode> => {
30
+ const engineConfig = buildInstanceConfig(options.config);
31
+ const sourceXML = await retrieveSourceXMLResource(input, engineConfig);
32
+ const form = new XFormDefinition(sourceXML);
33
+
34
+ return Root.initialize(form.xformDOM, form.model.root, engineConfig);
35
+ };
36
+
37
+ initializeForm satisfies InitializeForm;
@@ -0,0 +1,41 @@
1
+ import type { XFormsXPathEvaluator } from '@getodk/xpath';
2
+ import type { DependentExpression } from '../../expression/DependentExpression.ts';
3
+ import type { ReactiveScope } from '../../lib/reactivity/scope.ts';
4
+ import type { SubscribableDependency } from './SubscribableDependency.ts';
5
+ import type { TranslationContext } from './TranslationContext.ts';
6
+
7
+ export interface EvaluationContextRoot extends SubscribableDependency, TranslationContext {}
8
+
9
+ /**
10
+ * Provides a common interface to establish context for XPath-based
11
+ * computations, i.e. to evaluate {@link DependentExpression}s where:
12
+ *
13
+ * - the expression may have dynamic dependency **references** (e.g. relative
14
+ * references resolve to repeat instances or their descendants)
15
+ * - the expression may reference dynamic dependency **values** (e.g. an
16
+ * expression referencing the value of another node)
17
+ * - the expression may be dependent on the form's currently active language
18
+ * (e.g. `jr:itext`)
19
+ * - any dynamic case is expected to be internally reactive
20
+ */
21
+ export interface EvaluationContext {
22
+ readonly scope: ReactiveScope;
23
+ readonly evaluator: XFormsXPathEvaluator;
24
+ readonly root: EvaluationContextRoot;
25
+
26
+ /**
27
+ * Produces the current absolute reference to the {@link contextNode}, where
28
+ * the absolute `/` resolves to the active form state's primary instance root.
29
+ */
30
+ get contextReference(): string;
31
+
32
+ readonly contextNode: Node;
33
+
34
+ /**
35
+ * Resolves a nodeset reference, possibly relative to the
36
+ * {@link EvaluationContext.contextNode}.
37
+ */
38
+ readonly getSubscribableDependencyByReference: (
39
+ reference: string
40
+ ) => SubscribableDependency | null;
41
+ }
@@ -0,0 +1,9 @@
1
+ import type { EngineConfig } from '../../client/EngineConfig.ts';
2
+ import type { CreateUniqueId } from '../../lib/unique-id.ts';
3
+
4
+ export interface InstanceConfig extends Required<EngineConfig> {
5
+ /**
6
+ * Uniqueness per form instance session (so e.g. persistence isn't necessary).
7
+ */
8
+ readonly createUniqueId: CreateUniqueId;
9
+ }