@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,229 @@
1
+ import { getPropertyKeys } from '@getodk/common/lib/objects/structure.ts';
2
+ import type { Accessor, Signal } from 'solid-js';
3
+ import type { SimpleAtomicState } from '../types.ts';
4
+ import type { SpecifiedPropertyDescriptor } from './createSpecifiedPropertyDescriptor.ts';
5
+ import { createSpecifiedPropertyDescriptor } from './createSpecifiedPropertyDescriptor.ts';
6
+
7
+ /**
8
+ * Specifies a state object's property as mutable. Basic usage:
9
+ *
10
+ * ```ts
11
+ * import { createSignal } from 'solid-js';
12
+ *
13
+ * const count = createSignal(1);
14
+ *
15
+ * const state = createSpecifiedState({ count });
16
+ * // ^? { count: number }
17
+ * ```
18
+ *
19
+ * While basic usage involves passing a Solid {@link Signal}, a
20
+ * {@link SimpleAtomicState} type is also supported, allowing a slightly more
21
+ * permissive setter type.
22
+ *
23
+ * @see {@link SimpleAtomicState} and its related types for more detail.
24
+ *
25
+ * A property specified with this type will be used to define a reactively
26
+ * mutable property on the resulting state object where:
27
+ *
28
+ * - Reading the property will read its value from the provided spec's getter
29
+ * function, establishing an internally reactive subscription.
30
+ *
31
+ * - Mutating the property will pass the assigned value to the provided spec's
32
+ * setter function, triggering any downstream computations—whether that
33
+ * computation is associated with the provided spec itself, or reads from the
34
+ * derived property.
35
+ *
36
+ * - All such mutations will be propagated to any derived (client) state object.
37
+ */
38
+ // prettier-ignore
39
+ export type MutablePropertySpec<T> =
40
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
41
+ | SimpleAtomicState<T>
42
+ | Signal<T>;
43
+
44
+ /**
45
+ * Specifies a state object's property as reactively computed, and read-only.
46
+ * Basic usage:
47
+ *
48
+ * ```ts
49
+ * import { createMemo, createSignal } from 'solid-js';
50
+ *
51
+ * const [count, setCount] = createSignal(1);
52
+ * const doubleCount = createMemo(() => count() * 2);
53
+ *
54
+ * const state = createSpecifiedState({ doubleCount });
55
+ * // ^? { readonly doubleCount: number }
56
+ * ```
57
+ *
58
+ * Note: ideally, the produced type would better reflect the computed nature of
59
+ * the resulting property. Unfortunately this isn't currently supported by the
60
+ * TypeScript type system (by inferring the derived state type). This tradeoff
61
+ * is reasonable for internal engine use, but should not be used to derive types
62
+ * which will be directly consumed by clients.
63
+ *
64
+ * Any property specified by a thunk (zero-argument function) is supported. A
65
+ * property specified with this type will be used to define a reactively
66
+ * readable property on the resulting state object where:
67
+ *
68
+ * - Reading the property will read its value on each access, establishing an
69
+ * internally reactive subscription (presuming the input property spec is
70
+ * itself reactive).
71
+ *
72
+ * - Mutating the property will throw a {@link TypeError}, just as it would when
73
+ * attempting to mutate a normal `get` accessor.
74
+ *
75
+ * - Any change which would result in a reactive update in the property spec
76
+ * itself will be propagated to any derived (client) state object.
77
+ */
78
+ export type ComputedPropertySpec<T> = Accessor<T>;
79
+
80
+ /**
81
+ * @see {@link StaticPropertySpec}
82
+ */
83
+ // eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any
84
+ type NonStaticValue = Function | Signal<any> | SimpleAtomicState<any>;
85
+
86
+ /**
87
+ * Specifies a state object's property with a static value, immutable for the
88
+ * lifetime of that state object. Basic usage:
89
+ *
90
+ * ```ts
91
+ * const state = createSpecifiedState({ num: 10 });
92
+ * // ^? { readonly num: number }
93
+ * ```
94
+ *
95
+ * Properties will be specified as static when they are none of:
96
+ *
97
+ * - {@link MutablePropertySpec}
98
+ * - {@link ComputedPropertySpec}
99
+ * - Any function with a non-zero arity (which are not supported for specifying
100
+ * a state object's properties at all)
101
+ *
102
+ * Any property so specified will produce a property where:
103
+ *
104
+ * - Reading the property will produce the provided static value.
105
+ *
106
+ * - Mutating the property will throw a {@link TypeError}, just as it would when
107
+ * attempting to mutate a normal `get` accessor (or a
108
+ * {@link PropertyDescriptor} with `writable: false`).
109
+ *
110
+ * - The property will reflect the same static value on any derived (client)
111
+ * state object.
112
+ */
113
+ export type StaticPropertySpec<T> = Exclude<T, NonStaticValue>;
114
+
115
+ /**
116
+ * Every state property may be specified with any of these types:
117
+ *
118
+ * - {@link MutablePropertySpec}
119
+ * - {@link ComputedPropertySpec}
120
+ * - {@link StaticPropertySpec}
121
+ */
122
+ // prettier-ignore
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ export type StatePropertySpec<T = any> =
125
+ // eslint-disable-next-line @typescript-eslint/sort-type-constituents
126
+ | MutablePropertySpec<T>
127
+ | ComputedPropertySpec<T>
128
+ | StaticPropertySpec<T>;
129
+
130
+ type ParametersOfArity<Arity extends number> = unknown[] & {
131
+ length: Arity;
132
+ };
133
+
134
+ type FunctionOfArity<Arity extends number> = (...args: ParametersOfArity<Arity>) => unknown;
135
+
136
+ const isFunctionOfArity = <Arity extends number>(
137
+ arity: Arity,
138
+ value: unknown
139
+ ): value is FunctionOfArity<Arity> => {
140
+ return typeof value === 'function' && value.length === arity;
141
+ };
142
+
143
+ export const isMutablePropertySpec = <T>(
144
+ propertySpec: StatePropertySpec<T>
145
+ ): propertySpec is MutablePropertySpec<T> => {
146
+ if (!Array.isArray(propertySpec) || propertySpec.length !== 2) {
147
+ return false;
148
+ }
149
+
150
+ const [read, write] = propertySpec;
151
+
152
+ return isFunctionOfArity(0, read) && isFunctionOfArity(1, write);
153
+ };
154
+
155
+ export const isComputedPropertySpec = <T>(
156
+ propertySpec: StatePropertySpec<T>
157
+ ): propertySpec is ComputedPropertySpec<T> => {
158
+ return isFunctionOfArity(0, propertySpec);
159
+ };
160
+
161
+ export const isStaticPropertySpec = <T>(
162
+ propertySpec: StatePropertySpec<T>
163
+ ): propertySpec is StaticPropertySpec<T> => {
164
+ return !isMutablePropertySpec(propertySpec) && !isComputedPropertySpec(propertySpec);
165
+ };
166
+
167
+ export type StateSpec = Record<string, StatePropertySpec>;
168
+
169
+ // prettier-ignore
170
+ type SpecifiedStatePropertyValue<PropertySpec extends StatePropertySpec> =
171
+ PropertySpec extends StatePropertySpec<infer T>
172
+ ? T
173
+ : never;
174
+
175
+ // prettier-ignore
176
+ type DerivedMutableKeys<Spec extends StateSpec> = {
177
+ [K in keyof Spec]:
178
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
179
+ Spec[K] extends MutablePropertySpec<any>
180
+ ? K
181
+ : never;
182
+ }[keyof Spec];
183
+
184
+ type DerivedMutableState<Spec extends StateSpec> = {
185
+ -readonly [K in DerivedMutableKeys<Spec>]: SpecifiedStatePropertyValue<Spec[K]>;
186
+ };
187
+
188
+ type DerivedReadableKeys<Spec extends StateSpec> = Exclude<keyof Spec, DerivedMutableKeys<Spec>>;
189
+
190
+ type DerivedReadableState<Spec extends StateSpec> = {
191
+ readonly [K in DerivedReadableKeys<Spec>]: SpecifiedStatePropertyValue<Spec[K]>;
192
+ };
193
+
194
+ // prettier-ignore
195
+ export type SpecifiedState<Spec extends StateSpec> =(
196
+ & DerivedMutableState<Spec>
197
+ & DerivedReadableState<Spec>
198
+ ) extends infer DerivedState
199
+ ? {
200
+ [K in keyof Spec]:
201
+ K extends keyof DerivedState
202
+ ? DerivedState[K]
203
+ : never;
204
+ }
205
+ : never;
206
+
207
+ /**
208
+ * Produces an (internally) reactive object whose properties may be a mix of:
209
+ *
210
+ * - {@link MutablePropertySpec | mutable} (atomically reactive when mutated by
211
+ * engine internal logic; reactively propagated as read-only to clients)
212
+ * - {@link ComputedPropertySpec | computed} (read-only, reactive to changes in
213
+ * derived state; reactively propagated as read-only to clients)
214
+ * - {@link StaticPropertySpec | static} (read-only, immutable after creation;
215
+ * statically propagated as read-only to clients)
216
+ */
217
+ export const createSpecifiedState = <Spec extends StateSpec>(spec: Spec): SpecifiedState<Spec> => {
218
+ const keys = getPropertyKeys(spec);
219
+ const descriptors = Object.fromEntries(
220
+ keys.map((key) => {
221
+ const propertySpec = spec[key];
222
+ const descriptor = createSpecifiedPropertyDescriptor(propertySpec);
223
+
224
+ return [key, descriptor];
225
+ })
226
+ ) satisfies Record<string, SpecifiedPropertyDescriptor>;
227
+
228
+ return Object.create(null, descriptors) as SpecifiedState<Spec>;
229
+ };
@@ -0,0 +1,64 @@
1
+ import type { ShallowMutable } from '@getodk/common/types/helpers.js';
2
+
3
+ const ENGINE_REPRESENTATION = Symbol('ENGINE_REPRESENTATION');
4
+ type ENGINE_REPRESENTATION = typeof ENGINE_REPRESENTATION;
5
+
6
+ const INTERNAL_CLIENT_REPRESENTATION = Symbol('INTERNAL_CLIENT_REPRESENTATION');
7
+ type INTERNAL_CLIENT_REPRESENTATION = typeof INTERNAL_CLIENT_REPRESENTATION;
8
+
9
+ const READONLY_CLIENT_REPRESENTATION = Symbol('READONLY_CLIENT_REPRESENTATION');
10
+ type READONLY_CLIENT_REPRESENTATION = typeof READONLY_CLIENT_REPRESENTATION;
11
+
12
+ // prettier-ignore
13
+ type RepresentationType =
14
+ | ENGINE_REPRESENTATION
15
+ | INTERNAL_CLIENT_REPRESENTATION
16
+ | READONLY_CLIENT_REPRESENTATION;
17
+
18
+ // prettier-ignore
19
+ type TypedRepresentation<Type extends RepresentationType, T> =
20
+ & T
21
+ & { readonly [K in RepresentationType]?: K extends Type ? K : never };
22
+
23
+ // prettier-ignore
24
+ export type EngineRepresentation<T extends object> = TypedRepresentation<
25
+ ENGINE_REPRESENTATION,
26
+ ShallowMutable<T>
27
+ >;
28
+
29
+ export const declareEngineRepresentation = <T extends object>(
30
+ stateObject: T
31
+ ): EngineRepresentation<T> => {
32
+ return stateObject as EngineRepresentation<T>;
33
+ };
34
+
35
+ // prettier-ignore
36
+ export type InternalClientRepresentation<T extends object> = TypedRepresentation<
37
+ INTERNAL_CLIENT_REPRESENTATION,
38
+ ShallowMutable<T>
39
+ >;
40
+
41
+ export const declareInternalClientRepresentation = <T extends object>(
42
+ stateObject: T
43
+ ): InternalClientRepresentation<T> => {
44
+ return stateObject as InternalClientRepresentation<T>;
45
+ };
46
+
47
+ // prettier-ignore
48
+ export type ReadonlyClientRepresentation<T> = TypedRepresentation<
49
+ READONLY_CLIENT_REPRESENTATION,
50
+ T
51
+ >;
52
+
53
+ /**
54
+ * Provides a static type mechanism to reduce the chance of mistakenly assigning
55
+ * one state representation to another (e.g. `engineState = clientState` or
56
+ * `doSomethingWithCurrentState(engineState)`). Each representation is either
57
+ * fully or partially assignable to the other, but this bit of indirection should
58
+ * prevent that (unless one of the types is widened to {@link T}).
59
+ */
60
+ export const declareReadonlyClientRepresentation = <T extends object>(
61
+ stateObject: Readonly<T>
62
+ ): ReadonlyClientRepresentation<T> => {
63
+ return stateObject as ReadonlyClientRepresentation<T>;
64
+ };
@@ -0,0 +1,106 @@
1
+ import type { Owner } from 'solid-js';
2
+ import { runWithOwner as baseRunWithOwner, createRoot, getOwner } from 'solid-js';
3
+
4
+ type ReactiveScopeTask<T> = (scope: ReactiveScope) => T;
5
+
6
+ type RunReactiveScopeTask = <T>(task: ReactiveScopeTask<T>) => T;
7
+
8
+ export interface ReactiveScope {
9
+ readonly owner: Owner;
10
+ readonly dispose: VoidFunction;
11
+ readonly runTask: RunReactiveScopeTask;
12
+ }
13
+
14
+ interface CreateReactiveScopeOptions {
15
+ readonly owner?: Owner | null;
16
+ }
17
+
18
+ /**
19
+ * Default types for these pertinent reactive scope APIs as produced by Solid.
20
+ * These types are correct, but only so restrictive to account for their
21
+ * behavior outside of a reactive root.
22
+ */
23
+ interface UnknownReactiveScopeAPI {
24
+ /**
25
+ * The type returned by Solid's {@link getOwner}. It will always produce
26
+ * `Owner` when called in a reactive scope (created by {@link createRoot}).
27
+ */
28
+ readonly owner: Owner | null;
29
+
30
+ /**
31
+ * The unmodified type of Solid's {@link baseRunWithOwner | runWithOwner}.
32
+ * It will return {@link T} when {@link owner} is not `null`.
33
+ */
34
+ readonly runWithOwner: <T>(owner: Owner | null, fn: () => T) => T | undefined;
35
+ }
36
+
37
+ /**
38
+ * Refined types for the same reactive APIs as {@link UnknownReactiveScopeAPI},
39
+ * where their less restricted behavior is known when run within a reactive
40
+ * scope (via {@link createRoot}).
41
+ */
42
+ interface ValidatedReactiveScopeAPI {
43
+ readonly owner: Owner;
44
+ readonly runWithOwner: <T>(owner: Owner, fn: () => T) => T;
45
+ }
46
+
47
+ /**
48
+ * @see {@link UnknownReactiveScopeAPI} and {@link ValidatedReactiveScopeAPI}
49
+ */
50
+ const validateReactiveScopeAPI = (api: UnknownReactiveScopeAPI): ValidatedReactiveScopeAPI => {
51
+ if (api.owner == null) {
52
+ throw new Error('Must be run in a reactive scope');
53
+ }
54
+
55
+ return api as ValidatedReactiveScopeAPI;
56
+ };
57
+
58
+ /**
59
+ * Creates a reactive scope for internal engine use. This currently uses Solid's
60
+ * implementation of reactivity, and makes no attempt to obscure that. As such,
61
+ * all of the terms and types exposed are intentionally direct references to
62
+ * their concepts in Solid.
63
+ *
64
+ * This reactive scope is suitable for isolating reactivity between tests. It is
65
+ * also suitable for scoping reactivity for nodes in engine/client state, as
66
+ * well as creating nested scopes for their descendants.
67
+ */
68
+ export const createReactiveScope = (options: CreateReactiveScopeOptions = {}): ReactiveScope => {
69
+ return createRoot((baseDispose) => {
70
+ let isDisposed = false;
71
+
72
+ const dispose: VoidFunction = () => {
73
+ if (isDisposed) {
74
+ throw new Error('Cannot dispose reactive scope multiple times');
75
+ }
76
+
77
+ baseDispose();
78
+ isDisposed = true;
79
+ };
80
+
81
+ const { owner, runWithOwner } = validateReactiveScopeAPI({
82
+ owner: getOwner(),
83
+ runWithOwner: baseRunWithOwner,
84
+ });
85
+
86
+ const runTask = <T>(task: ReactiveScopeTask<T>): T => {
87
+ if (isDisposed) {
88
+ throw new Error('Cannot run reactive task in reactive scope after it has been disposed');
89
+ }
90
+
91
+ return runWithOwner(owner, () => {
92
+ return task({
93
+ dispose,
94
+ owner,
95
+ runTask,
96
+ });
97
+ });
98
+ };
99
+
100
+ return {
101
+ dispose,
102
+ owner,
103
+ runTask,
104
+ };
105
+ }, options.owner ?? getOwner());
106
+ };
@@ -0,0 +1,16 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import type { EvaluationContext } from '../../../instance/internal-api/EvaluationContext.ts';
3
+ import { TextRange } from '../../../instance/text/TextRange.ts';
4
+ import type { ValueNodeDefinition } from '../../../model/ValueNodeDefinition.ts';
5
+ import { createTextRange } from './createTextRange.ts';
6
+
7
+ export const createFieldHint = (
8
+ context: EvaluationContext,
9
+ definition: ValueNodeDefinition
10
+ ): Accessor<TextRange<'hint'> | null> => {
11
+ const hintDefinition = definition.bodyElement?.hint ?? null;
12
+
13
+ return createTextRange(context, 'hint', hintDefinition, {
14
+ fallbackValue: null,
15
+ });
16
+ };
@@ -0,0 +1,16 @@
1
+ import { type Accessor } from 'solid-js';
2
+ import type { EvaluationContext } from '../../../instance/internal-api/EvaluationContext.ts';
3
+ import { TextRange } from '../../../instance/text/TextRange.ts';
4
+ import type { AnyNodeDefinition } from '../../../model/NodeDefinition.ts';
5
+ import { createTextRange } from './createTextRange.ts';
6
+
7
+ export const createNodeLabel = (
8
+ context: EvaluationContext,
9
+ definition: AnyNodeDefinition
10
+ ): Accessor<TextRange<'label'> | null> => {
11
+ const labelDefinition = definition.bodyElement?.label ?? null;
12
+
13
+ return createTextRange(context, 'label', labelDefinition, {
14
+ fallbackValue: null,
15
+ });
16
+ };
@@ -0,0 +1,155 @@
1
+ import type { CollectionValues } from '@getodk/common/types/collections/CollectionValues.ts';
2
+ import { createMemo, type Accessor } from 'solid-js';
3
+ import type {
4
+ TextElementChild,
5
+ TextElementDefinition,
6
+ } from '../../../body/text/TextElementDefinition.ts';
7
+ import type { TextElementReferencePart } from '../../../body/text/TextElementReferencePart.ts';
8
+ import type { TextChunkSource } from '../../../client/TextRange.ts';
9
+ import type { EvaluationContext } from '../../../instance/internal-api/EvaluationContext.ts';
10
+ import { TextChunk } from '../../../instance/text/TextChunk.ts';
11
+ import { TextRange, type TextRole } from '../../../instance/text/TextRange.ts';
12
+ import { createComputedExpression } from '../createComputedExpression.ts';
13
+
14
+ // prettier-ignore
15
+ type TextSources =
16
+ | readonly [TextElementReferencePart]
17
+ | readonly TextElementChild[];
18
+
19
+ type TextSource = CollectionValues<TextSources>;
20
+
21
+ interface TextChunkComputation {
22
+ readonly source: TextChunkSource;
23
+ readonly getText: Accessor<string>;
24
+ }
25
+
26
+ const createComputedTextChunk = (
27
+ context: EvaluationContext,
28
+ textSource: TextSource
29
+ ): TextChunkComputation => {
30
+ const { type } = textSource;
31
+
32
+ if (type === 'static') {
33
+ const { stringValue } = textSource;
34
+
35
+ return {
36
+ source: type,
37
+ getText: () => stringValue,
38
+ };
39
+ }
40
+
41
+ return context.scope.runTask(() => {
42
+ const source: TextChunkSource = type === 'reference' ? 'itext' : type;
43
+ const getText = createComputedExpression(context, textSource);
44
+
45
+ return {
46
+ source,
47
+ getText,
48
+ };
49
+ });
50
+ };
51
+
52
+ const createTextChunks = (
53
+ context: EvaluationContext,
54
+ textSources: TextSources
55
+ ): Accessor<readonly TextChunk[]> => {
56
+ return context.scope.runTask(() => {
57
+ const { root } = context;
58
+ const chunkComputations = textSources.map((textSource) => {
59
+ return createComputedTextChunk(context, textSource);
60
+ });
61
+
62
+ return createMemo(() => {
63
+ return chunkComputations.map(({ source, getText }) => {
64
+ return new TextChunk(root, source, getText());
65
+ });
66
+ });
67
+ });
68
+ };
69
+
70
+ interface CreateTextRangeOptions<FallbackValue extends string | null> {
71
+ readonly fallbackValue?: FallbackValue;
72
+ }
73
+
74
+ // prettier-ignore
75
+ type ComputedTextRange<
76
+ Role extends TextRole,
77
+ Definition extends TextElementDefinition<Role> | null,
78
+ FallbackValue extends string | null
79
+ > = Accessor<
80
+ Definition extends null
81
+ ? FallbackValue extends null
82
+ ? TextRange<Role> | null
83
+ : TextRange<Role>
84
+ : TextRange<Role>
85
+ >;
86
+
87
+ // prettier-ignore
88
+ type FallbackTextRange<
89
+ Role extends TextRole,
90
+ FallbackValue extends string | null
91
+ > =
92
+ FallbackValue extends null
93
+ ? TextRange<Role> | null
94
+ : TextRange<Role>;
95
+
96
+ const createFallbackTextRange = <Role extends TextRole, FallbackValue extends string | null>(
97
+ context: EvaluationContext,
98
+ role: Role,
99
+ fallbackValue: FallbackValue
100
+ ): FallbackTextRange<Role, FallbackValue> => {
101
+ if (fallbackValue == null) {
102
+ return null as FallbackTextRange<Role, FallbackValue>;
103
+ }
104
+
105
+ const staticChunk = new TextChunk(context.root, 'static', fallbackValue);
106
+
107
+ return new TextRange(role, [staticChunk]);
108
+ };
109
+
110
+ /**
111
+ * Creates a text range (e.g. label or hint) from the provided definition,
112
+ * reactive to:
113
+ *
114
+ * - The form's current language (e.g. `<label ref="jr:itext('text-id')" />`)
115
+ * - Direct `<output>` references within the label's children
116
+ *
117
+ * @todo This does not yet handle itext translations **with** outputs!
118
+ */
119
+ export const createTextRange = <
120
+ Role extends TextRole,
121
+ Definition extends TextElementDefinition<Role> | null,
122
+ FallbackValue extends string | null = null,
123
+ >(
124
+ context: EvaluationContext,
125
+ role: Role,
126
+ definition: Definition,
127
+ options?: CreateTextRangeOptions<FallbackValue>
128
+ ): ComputedTextRange<Role, Definition, FallbackValue> => {
129
+ return context.scope.runTask(() => {
130
+ if (definition == null) {
131
+ const textRange = createFallbackTextRange(
132
+ context,
133
+ role,
134
+ options?.fallbackValue ?? (null as FallbackValue)
135
+ );
136
+ const getTextRange = () => textRange;
137
+
138
+ return getTextRange as ComputedTextRange<Role, Definition, FallbackValue>;
139
+ }
140
+
141
+ const { children, referenceExpression } = definition;
142
+
143
+ let getTextChunks: Accessor<readonly TextChunk[]>;
144
+
145
+ if (referenceExpression == null) {
146
+ getTextChunks = createTextChunks(context, children);
147
+ } else {
148
+ getTextChunks = createTextChunks(context, [referenceExpression]);
149
+ }
150
+
151
+ return createMemo(() => {
152
+ return new TextRange(role, getTextChunks());
153
+ });
154
+ });
155
+ };
@@ -0,0 +1,27 @@
1
+ import type { Accessor, Setter, Signal } from 'solid-js';
2
+
3
+ /**
4
+ * A write interface to reactive atomic state. This type is intended to be used
5
+ * as a relaxed version of {@link Setter}, where its callback form isn't
6
+ * required for conforming implementations.
7
+ */
8
+ export type SimpleAtomicStateSetter<T> = (newValue: T) => T;
9
+
10
+ /**
11
+ * A read/write interface to reactive atomic state. This type is intended to be
12
+ * used as a relaxed version of {@link Signal}, with a
13
+ * {@link SimpleAtomicStateSetter | simpler setter type}.
14
+ */
15
+ // prettier-ignore
16
+ export type SimpleAtomicState<T> = readonly [
17
+ get: Accessor<T>,
18
+ set: SimpleAtomicStateSetter<T>,
19
+ ];
20
+
21
+ export type AtomicStateSetter<T> = Setter<T> | SimpleAtomicStateSetter<T>;
22
+
23
+ // prettier-ignore
24
+ export type AtomicState<T> = readonly [
25
+ get: Accessor<T>,
26
+ set: AtomicStateSetter<T>,
27
+ ];
@@ -0,0 +1,34 @@
1
+ import { createUniqueId as baseCreateUniqueId } from 'solid-js';
2
+
3
+ /**
4
+ * **WARNING:** Uniqueness is not guaranteed across multiple sessions. Do not
5
+ * use IDs produced by this function in persistence scenarios, or where external
6
+ * interfaces expect stronger uniqueness guarantees.
7
+ */
8
+ export type CreateUniqueId = () => string;
9
+
10
+ /**
11
+ * Where available, uses the
12
+ * {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API | Web Crypto API}'s
13
+ * {@link crypto.randomUUID | `randomUUID`}.
14
+ *
15
+ * Notes on supported environments:
16
+ *
17
+ * - Browsers: only available in a
18
+ * {@link https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts | secure context}
19
+ * - Node: >= v19.0.0
20
+ * - Deno: >= 1.11
21
+ * - Bun: fully supported, unknown minimum version
22
+ *
23
+ * Where `randomUUID` is not supported, falls back to an internal implementation
24
+ * with weaker uniqueness constraints.
25
+ *
26
+ * **WARNING:** Uniqueness is not guaranteed across multiple sessions. Do not
27
+ * use IDs produced by this function in persistence scenarios, or where external
28
+ * interfaces expect stronger uniqueness guarantees.
29
+ */
30
+ export const createUniqueId: CreateUniqueId = (() => {
31
+ const { crypto } = globalThis;
32
+
33
+ return crypto?.randomUUID?.bind(crypto) ?? baseCreateUniqueId;
34
+ })();