@getodk/xforms-engine 0.7.0 → 0.9.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 (81) hide show
  1. package/dist/client/InputNode.d.ts +4 -2
  2. package/dist/client/NoteNode.d.ts +4 -2
  3. package/dist/client/RootNode.d.ts +4 -4
  4. package/dist/client/UploadNode.d.ts +53 -0
  5. package/dist/client/attachments/InstanceAttachmentMeta.d.ts +8 -0
  6. package/dist/client/attachments/InstanceAttachmentsConfig.d.ts +8 -0
  7. package/dist/client/form/FormInstanceConfig.d.ts +2 -0
  8. package/dist/client/hierarchy.d.ts +4 -5
  9. package/dist/client/index.d.ts +5 -2
  10. package/dist/client/node-types.d.ts +2 -3
  11. package/dist/client/serialization/InstanceData.d.ts +7 -5
  12. package/dist/client/serialization/InstancePayloadOptions.d.ts +3 -2
  13. package/dist/error/UploadValueTypeError.d.ts +8 -0
  14. package/dist/index.js +16938 -29914
  15. package/dist/index.js.map +1 -1
  16. package/dist/instance/PrimaryInstance.d.ts +2 -3
  17. package/dist/instance/UploadControl.d.ts +58 -0
  18. package/dist/instance/attachments/InstanceAttachment.d.ts +42 -0
  19. package/dist/instance/attachments/InstanceAttachmentsState.d.ts +9 -0
  20. package/dist/instance/hierarchy.d.ts +6 -7
  21. package/dist/instance/internal-api/InstanceAttachmentContext.d.ts +19 -0
  22. package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
  23. package/dist/instance/internal-api/serialization/ClientReactiveSerializableInstance.d.ts +2 -0
  24. package/dist/integration/xpath/adapter/traversal.d.ts +2 -2
  25. package/dist/lib/codecs/DateValueCodec.d.ts +7 -0
  26. package/dist/lib/codecs/getSharedValueCodec.d.ts +3 -2
  27. package/dist/lib/reactivity/createInstanceAttachment.d.ts +8 -0
  28. package/dist/lib/resource-helpers.d.ts +2 -0
  29. package/dist/parse/body/BodyDefinition.d.ts +2 -1
  30. package/dist/parse/body/control/UploadControlDefinition.d.ts +2 -0
  31. package/dist/parse/model/BindTypeDefinition.d.ts +2 -7
  32. package/dist/parse/text/abstract/TextElementDefinition.d.ts +1 -1
  33. package/dist/solid.js +16935 -29912
  34. package/dist/solid.js.map +1 -1
  35. package/package.json +16 -14
  36. package/src/client/InputNode.ts +4 -0
  37. package/src/client/NoteNode.ts +4 -0
  38. package/src/client/RootNode.ts +8 -3
  39. package/src/client/UploadNode.ts +78 -0
  40. package/src/client/attachments/InstanceAttachmentMeta.ts +10 -0
  41. package/src/client/attachments/InstanceAttachmentsConfig.ts +13 -0
  42. package/src/client/form/FormInstanceConfig.ts +3 -0
  43. package/src/client/hierarchy.ts +5 -8
  44. package/src/client/index.ts +4 -2
  45. package/src/client/node-types.ts +2 -5
  46. package/src/client/serialization/InstanceData.ts +14 -6
  47. package/src/client/serialization/InstancePayloadOptions.ts +3 -2
  48. package/src/entrypoints/FormInstance.ts +3 -2
  49. package/src/error/UploadValueTypeError.ts +13 -0
  50. package/src/instance/PrimaryInstance.ts +4 -5
  51. package/src/instance/UploadControl.ts +184 -0
  52. package/src/instance/attachments/InstanceAttachment.ts +69 -0
  53. package/src/instance/attachments/InstanceAttachmentsState.ts +18 -0
  54. package/src/instance/children/buildChildren.ts +5 -8
  55. package/src/instance/hierarchy.ts +6 -9
  56. package/src/instance/input/InstanceAttachmentMap.ts +33 -21
  57. package/src/instance/internal-api/InstanceAttachmentContext.ts +20 -0
  58. package/src/instance/internal-api/InstanceConfig.ts +3 -0
  59. package/src/instance/internal-api/serialization/ClientReactiveSerializableInstance.ts +2 -0
  60. package/src/integration/xpath/adapter/traversal.ts +4 -2
  61. package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +72 -8
  62. package/src/lib/codecs/DateValueCodec.ts +94 -0
  63. package/src/lib/codecs/getSharedValueCodec.ts +5 -3
  64. package/src/lib/reactivity/createInstanceAttachment.ts +212 -0
  65. package/src/lib/resource-helpers.ts +33 -0
  66. package/src/parse/XFormDOM.ts +1 -3
  67. package/src/parse/body/BodyDefinition.ts +4 -0
  68. package/src/parse/body/control/UploadControlDefinition.ts +42 -0
  69. package/src/parse/model/BindDefinition.ts +1 -1
  70. package/src/parse/model/BindTypeDefinition.ts +68 -26
  71. package/src/parse/model/ModelBindMap.ts +0 -5
  72. package/src/parse/model/SecondaryInstance/sources/ExternalSecondaryInstanceResource.ts +1 -30
  73. package/src/parse/text/abstract/TextElementDefinition.ts +2 -2
  74. package/dist/client/unsupported/UnsupportedControlNode.d.ts +0 -30
  75. package/dist/client/unsupported/UploadNode.d.ts +0 -9
  76. package/dist/instance/unsupported/UploadControl.d.ts +0 -34
  77. package/dist/lib/codecs/TempUnsupportedControlCodec.d.ts +0 -7
  78. package/src/client/unsupported/UnsupportedControlNode.ts +0 -36
  79. package/src/client/unsupported/UploadNode.ts +0 -14
  80. package/src/instance/unsupported/UploadControl.ts +0 -120
  81. package/src/lib/codecs/TempUnsupportedControlCodec.ts +0 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getodk/xforms-engine",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "XForms engine for ODK Web Forms",
6
6
  "type": "module",
@@ -54,27 +54,29 @@
54
54
  "test:types": "tsc --project ./tsconfig.json --emitDeclarationOnly false --noEmit"
55
55
  },
56
56
  "dependencies": {
57
- "papaparse": "^5.4.1",
58
- "solid-js": "^1.9.3"
57
+ "bin-packer": "1.7.0",
58
+ "papaparse": "^5.5.2",
59
+ "solid-js": "^1.9.5",
60
+ "temporal-polyfill": "^0.3.0"
59
61
  },
60
62
  "devDependencies": {
61
- "@babel/core": "^7.26.0",
62
- "@getodk/tree-sitter-xpath": "0.1.2",
63
- "@getodk/xpath": "0.4.0",
63
+ "@babel/core": "^7.26.10",
64
+ "@getodk/tree-sitter-xpath": "0.1.3",
65
+ "@getodk/xpath": "0.5.0",
64
66
  "@playwright/test": "^1.49.1",
65
67
  "@types/papaparse": "^5.3.15",
66
- "@vitest/browser": "^2.1.8",
67
- "babel-plugin-transform-jsbi-to-bigint": "^1.4.0",
68
+ "@vitest/browser": "^3.0.9",
69
+ "babel-plugin-transform-jsbi-to-bigint": "^1.4.2",
68
70
  "http-server": "^14.1.1",
69
- "jsdom": "^25.0.1",
70
- "typedoc": "^0.27.5",
71
- "vite": "^5.4.11",
72
- "vite-plugin-dts": "^4.3.0",
71
+ "jsdom": "^26.0.0",
72
+ "typedoc": "^0.28.2",
73
+ "vite": "^6.2.3",
74
+ "vite-plugin-dts": "^4.5.3",
73
75
  "vite-plugin-no-bundle": "^4.0.0",
74
- "vitest": "^2.1.8"
76
+ "vitest": "^3.0.9"
75
77
  },
76
78
  "peerDependencies": {
77
- "solid-js": "^1.9.3"
79
+ "solid-js": "^1.9.5"
78
80
  },
79
81
  "peerDependenciesMeta": {
80
82
  "solid-js": {
@@ -84,11 +84,13 @@ export interface InputNode<V extends ValueType = ValueType>
84
84
  export type StringInputValue = InputValue<'string'>;
85
85
  export type IntInputValue = InputValue<'int'>;
86
86
  export type DecimalInputValue = InputValue<'decimal'>;
87
+ export type DateInputValue = InputValue<'date'>;
87
88
  export type GeopointInputValue = InputValue<'geopoint'>;
88
89
 
89
90
  export type StringInputNode = InputNode<'string'>;
90
91
  export type IntInputNode = InputNode<'int'>;
91
92
  export type DecimalInputNode = InputNode<'decimal'>;
93
+ export type DateInputNode = InputNode<'date'>;
92
94
  export type GeopointInputNode = InputNode<'geopoint'>;
93
95
 
94
96
  // prettier-ignore
@@ -97,6 +99,7 @@ type SupportedInputValueType =
97
99
  | 'string'
98
100
  | 'int'
99
101
  | 'decimal'
102
+ | 'date'
100
103
  | 'geopoint';
101
104
 
102
105
  type TemporaryStringValueType = Exclude<ValueType, SupportedInputValueType>;
@@ -109,5 +112,6 @@ export type AnyInputNode =
109
112
  | StringInputNode
110
113
  | IntInputNode
111
114
  | DecimalInputNode
115
+ | DateInputNode
112
116
  | GeopointInputNode
113
117
  | TemporaryStringValueInputNode;
@@ -81,11 +81,13 @@ export interface NoteNode<V extends ValueType = ValueType> extends BaseValueNode
81
81
  export type StringNoteValue = NoteValue<'string'>;
82
82
  export type IntNoteValue = NoteValue<'int'>;
83
83
  export type DecimalNoteValue = NoteValue<'decimal'>;
84
+ export type DateNoteValue = NoteValue<'date'>;
84
85
  export type GeopointNoteValue = NoteValue<'geopoint'>;
85
86
 
86
87
  export type StringNoteNode = NoteNode<'string'>;
87
88
  export type IntNoteNode = NoteNode<'int'>;
88
89
  export type DecimalNoteNode = NoteNode<'decimal'>;
90
+ export type DateNoteNode = NoteNode<'date'>;
89
91
  export type GeopointNoteNode = NoteNode<'geopoint'>;
90
92
 
91
93
  // prettier-ignore
@@ -94,6 +96,7 @@ type SupportedNoteValueType =
94
96
  | 'string'
95
97
  | 'int'
96
98
  | 'decimal'
99
+ | 'date'
97
100
  | 'geopoint';
98
101
 
99
102
  type TemporaryStringValueType = Exclude<ValueType, SupportedNoteValueType>;
@@ -106,5 +109,6 @@ export type AnyNoteNode =
106
109
  | StringNoteNode
107
110
  | IntNoteNode
108
111
  | DecimalNoteNode
112
+ | DateNoteNode
109
113
  | GeopointNoteNode
110
114
  | TemporaryStringValueNoteNode;
@@ -8,7 +8,10 @@ import type {
8
8
  InstancePayload,
9
9
  MonolithicInstancePayload,
10
10
  } from './serialization/InstancePayload.ts';
11
- import type { InstancePayloadOptions } from './serialization/InstancePayloadOptions.ts';
11
+ import type {
12
+ InstancePayloadOptions,
13
+ InstancePayloadType,
14
+ } from './serialization/InstancePayloadOptions.ts';
12
15
  import type { AncestorNodeValidationState } from './validation.ts';
13
16
 
14
17
  export interface RootNodeState extends BaseNodeState {
@@ -94,11 +97,13 @@ export interface RootNode extends BaseNode {
94
97
  * case a {@link ChunkedInstancePayload} will be produced, with form
95
98
  * attachments
96
99
  */
97
- prepareInstancePayload(): Promise<MonolithicInstancePayload>;
98
100
  prepareInstancePayload(
99
- options: InstancePayloadOptions<'monolithic'>
101
+ options?: InstancePayloadOptions<'monolithic'>
100
102
  ): Promise<MonolithicInstancePayload>;
101
103
  prepareInstancePayload(
102
104
  options: InstancePayloadOptions<'chunked'>
103
105
  ): Promise<ChunkedInstancePayload>;
106
+ prepareInstancePayload<PayloadType extends InstancePayloadType>(
107
+ options: InstancePayloadOptions<PayloadType>
108
+ ): Promise<InstancePayload<PayloadType>>;
104
109
  }
@@ -0,0 +1,78 @@
1
+ import type { PartiallyKnownString } from '@getodk/common/types/string/PartiallyKnownString.ts';
2
+ import type { UnknownAppearanceDefinition } from '../parse/body/appearance/unknownAppearanceParser.ts';
3
+ import type { UploadControlDefinition } from '../parse/body/control/UploadControlDefinition.ts';
4
+ import type { LeafNodeDefinition } from '../parse/model/LeafNodeDefinition.ts';
5
+ import type { BaseValueNode, BaseValueNodeState } from './BaseValueNode.ts';
6
+ import type { GeneralParentNode } from './hierarchy.ts';
7
+ import type { RootNode } from './RootNode.ts';
8
+ import type { InstanceAttachmentFileName } from './serialization/InstanceData.ts';
9
+ import type { LeafNodeValidationState } from './validation.ts';
10
+ import type { ValueType } from './ValueType.ts';
11
+
12
+ export type UploadValue = File | null;
13
+
14
+ export interface UploadNodeState extends BaseValueNodeState<UploadValue> {
15
+ get valueOptions(): null;
16
+ get value(): UploadValue;
17
+ get instanceValue(): InstanceAttachmentFileName;
18
+ }
19
+
20
+ export interface UploadDefinition<V extends ValueType = ValueType> extends LeafNodeDefinition<V> {
21
+ readonly bodyElement: UploadControlDefinition;
22
+ }
23
+
24
+ // prettier-ignore
25
+ export type UploadMediaType = PartiallyKnownString<
26
+ | '*'
27
+ | 'audio'
28
+ | 'image'
29
+ | 'video'
30
+ >;
31
+
32
+ export type UploadMediaSubtype = PartiallyKnownString<'*'>;
33
+
34
+ // prettier-ignore
35
+ export type UploadMediaAccept = PartiallyKnownString<
36
+ | '*'
37
+ | `${UploadMediaType}/${UploadMediaSubtype}`
38
+ >;
39
+
40
+ interface BaseUploadMediaOptions {
41
+ readonly accept: UploadMediaAccept;
42
+ readonly type: UploadMediaType | null;
43
+ readonly subtype: UploadMediaSubtype | null;
44
+ }
45
+
46
+ export interface ExplicitUploadMediaOptions extends BaseUploadMediaOptions {
47
+ readonly type: UploadMediaType;
48
+ readonly subtype: UploadMediaSubtype;
49
+ }
50
+
51
+ export interface UnspecifiedUploadMediaOptions extends BaseUploadMediaOptions {
52
+ readonly type: null;
53
+ readonly subtype: null;
54
+ }
55
+
56
+ // prettier-ignore
57
+ export type UploadMediaOptions =
58
+ | ExplicitUploadMediaOptions
59
+ | UnspecifiedUploadMediaOptions;
60
+
61
+ export interface UploadNodeOptions {
62
+ readonly media: UploadMediaOptions;
63
+ }
64
+
65
+ export interface UploadNode extends BaseValueNode<'binary', UploadValue> {
66
+ readonly nodeType: 'upload';
67
+ /** @todo */
68
+ readonly appearances: UnknownAppearanceDefinition;
69
+ readonly nodeOptions: UploadNodeOptions;
70
+ readonly valueType: 'binary';
71
+ readonly definition: UploadDefinition<'binary'>;
72
+ readonly root: RootNode;
73
+ readonly parent: GeneralParentNode;
74
+ readonly currentState: UploadNodeState;
75
+ readonly validationState: LeafNodeValidationState;
76
+
77
+ setValue(value: UploadValue): RootNode;
78
+ }
@@ -0,0 +1,10 @@
1
+ import type { FormNodeID } from '../identity.ts';
2
+
3
+ export type InstanceAttachmentExtension = `.${string}`;
4
+
5
+ export interface InstanceAttachmentMeta {
6
+ readonly nodeId: FormNodeID;
7
+ readonly writtenAt: Date | null;
8
+ readonly basename: string;
9
+ readonly extension: InstanceAttachmentExtension | null;
10
+ }
@@ -0,0 +1,13 @@
1
+ import type { InstanceAttachmentMeta } from './InstanceAttachmentMeta.ts';
2
+
3
+ export interface UpdatedInstanceAttachmentMeta extends InstanceAttachmentMeta {
4
+ readonly writtenAt: Date;
5
+ }
6
+
7
+ export type InstanceAttachmentFileNameFactory = (
8
+ meta: UpdatedInstanceAttachmentMeta
9
+ ) => string | null;
10
+
11
+ export interface InstanceAttachmentsConfig {
12
+ readonly fileNameFactory?: InstanceAttachmentFileNameFactory;
13
+ }
@@ -1,3 +1,4 @@
1
+ import type { InstanceAttachmentsConfig } from '../attachments/InstanceAttachmentsConfig.ts';
1
2
  import type { OpaqueReactiveObjectFactory } from '../OpaqueReactiveObjectFactory.ts';
2
3
 
3
4
  export interface FormInstanceConfig {
@@ -15,4 +16,6 @@ export interface FormInstanceConfig {
15
16
  * mechanism to handle state updates conveyed by the engine.
16
17
  */
17
18
  readonly stateFactory?: OpaqueReactiveObjectFactory;
19
+
20
+ readonly instanceAttachments?: InstanceAttachmentsConfig;
18
21
  }
@@ -4,6 +4,7 @@ import type { AnyInputNode } from './InputNode.ts';
4
4
  import type { AnyModelValueNode } from './ModelValueNode.ts';
5
5
  import type { AnyNoteNode } from './NoteNode.ts';
6
6
  import type { AnyRangeNode } from './RangeNode.ts';
7
+ import type { RankNode } from './RankNode.ts';
7
8
  import type { RepeatInstanceNode } from './repeat/RepeatInstanceNode.ts';
8
9
  import type { RepeatRangeControlledNode } from './repeat/RepeatRangeControlledNode.ts';
9
10
  import type { RepeatRangeUncontrolledNode } from './repeat/RepeatRangeUncontrolledNode.ts';
@@ -11,11 +12,7 @@ import type { RootNode } from './RootNode.ts';
11
12
  import type { SelectNode } from './SelectNode.ts';
12
13
  import type { SubtreeNode } from './SubtreeNode.ts';
13
14
  import type { TriggerNode } from './TriggerNode.ts';
14
- import type { RankNode } from './RankNode.ts';
15
- import type { UploadNode } from './unsupported/UploadNode.ts';
16
-
17
- // prettier-ignore
18
- export type AnyUnsupportedControlNode = UploadNode;
15
+ import type { UploadNode } from './UploadNode.ts';
19
16
 
20
17
  // prettier-ignore
21
18
  export type AnyControlNode =
@@ -24,13 +21,13 @@ export type AnyControlNode =
24
21
  | AnyRangeNode
25
22
  | RankNode
26
23
  | SelectNode
27
- | TriggerNode;
24
+ | TriggerNode
25
+ | UploadNode;
28
26
 
29
27
  // prettier-ignore
30
28
  export type AnyLeafNode =
31
29
  | AnyControlNode
32
- | AnyModelValueNode
33
- | AnyUnsupportedControlNode;
30
+ | AnyModelValueNode;
34
31
 
35
32
  // prettier-ignore
36
33
  export type RepeatRangeNode =
@@ -1,3 +1,5 @@
1
+ export type * from './attachments/InstanceAttachmentMeta.ts';
2
+ export type * from './attachments/InstanceAttachmentsConfig.ts';
1
3
  export type * from './constants.ts';
2
4
  export * as constants from './constants.ts';
3
5
  export type * from './form/CreateFormInstance.ts';
@@ -16,11 +18,11 @@ export type {
16
18
  AnyLeafNode,
17
19
  AnyNode,
18
20
  AnyParentNode,
19
- AnyUnsupportedControlNode,
20
21
  GeneralChildNode,
21
22
  GeneralParentNode,
22
23
  RepeatRangeNode,
23
24
  } from './hierarchy.ts';
25
+ export type * from './identity.ts';
24
26
  export type * from './InputNode.ts';
25
27
  export type * from './ModelValueNode.ts';
26
28
  export type * from './NoteNode.ts';
@@ -42,6 +44,6 @@ export type * from './submission/SubmissionMeta.ts';
42
44
  export type * from './SubtreeNode.ts';
43
45
  export type * from './TextRange.ts';
44
46
  export type * from './TriggerNode.ts';
45
- export type * from './unsupported/UploadNode.ts';
47
+ export type * from './UploadNode.ts';
46
48
  export type * from './validation.ts';
47
49
  export type * from './ValueType.ts';
@@ -1,5 +1,3 @@
1
- export type UnsupportedControlNodeType = 'upload';
2
-
3
1
  // prettier-ignore
4
2
  export type RepeatRangeNodeType =
5
3
  | 'repeat-range:controlled'
@@ -15,7 +13,7 @@ export type LeafNodeType =
15
13
  | 'trigger'
16
14
  | 'range'
17
15
  | 'rank'
18
- | UnsupportedControlNodeType;
16
+ | 'upload';
19
17
 
20
18
  // prettier-ignore
21
19
  export type InstanceNodeType =
@@ -25,5 +23,4 @@ export type InstanceNodeType =
25
23
  | 'repeat-instance'
26
24
  | 'group'
27
25
  | 'subtree'
28
- | LeafNodeType
29
- | UnsupportedControlNodeType;
26
+ | LeafNodeType;
@@ -2,14 +2,22 @@ import type { INSTANCE_FILE_NAME, InstanceFile } from './InstanceFile.ts';
2
2
 
3
3
  export type InstanceAttachmentFileName = string;
4
4
 
5
+ export type InstanceDataEntryValue = File;
6
+
5
7
  export interface InstanceData extends FormData {
6
- get(name: INSTANCE_FILE_NAME): InstanceFile;
8
+ [Symbol.iterator](): FormDataIterator<[string, InstanceDataEntryValue]>;
9
+ entries(): FormDataIterator<[string, InstanceDataEntryValue]>;
10
+ values(): FormDataIterator<InstanceDataEntryValue>;
7
11
 
8
- /**
9
- * @todo Can we guarantee (both in static types and at runtime) that
10
- * {@link InstanceData} only contains files?
11
- */
12
- get(name: InstanceAttachmentFileName): FormDataEntryValue | null;
12
+ forEach(
13
+ callbackfn: (value: InstanceDataEntryValue, key: string, parent: InstanceData) => void,
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ thisArg?: any
16
+ ): void;
17
+
18
+ get(name: INSTANCE_FILE_NAME): InstanceFile;
19
+ get(name: InstanceAttachmentFileName): InstanceDataEntryValue | null;
20
+ getAll(name: string): InstanceDataEntryValue[];
13
21
 
14
22
  has(name: INSTANCE_FILE_NAME): true;
15
23
  has(name: InstanceAttachmentFileName): boolean;
@@ -1,7 +1,7 @@
1
1
  export type InstancePayloadType = 'chunked' | 'monolithic';
2
2
 
3
3
  interface BaseInstancePayloadOptions<PayloadType extends InstancePayloadType> {
4
- readonly payloadType?: PayloadType | undefined;
4
+ readonly payloadType: PayloadType;
5
5
 
6
6
  /**
7
7
  * As described in the
@@ -13,11 +13,12 @@ interface BaseInstancePayloadOptions<PayloadType extends InstancePayloadType> {
13
13
  }
14
14
 
15
15
  interface ChunkedInstancePayloadOptions extends BaseInstancePayloadOptions<'chunked'> {
16
+ readonly payloadType: 'chunked';
16
17
  readonly maxSize: number;
17
18
  }
18
19
 
19
20
  interface MonolithicInstancePayloadOptions extends BaseInstancePayloadOptions<'monolithic'> {
20
- readonly payloadType?: 'monolithic' | undefined;
21
+ readonly payloadType: 'monolithic';
21
22
  readonly maxSize?: never;
22
23
  }
23
24
 
@@ -37,9 +37,10 @@ export class FormInstance<Mode extends FormInstanceInitializationMode>
37
37
  readonly formResult: InstantiableFormResult,
38
38
  options: FormInstanceOptions<Mode>
39
39
  ) {
40
- const { mode, initialState } = options;
40
+ const { mode, initialState, instanceConfig } = options;
41
41
  const config: InstanceConfig = {
42
- clientStateFactory: options.instanceConfig.stateFactory ?? identity,
42
+ clientStateFactory: instanceConfig.stateFactory ?? identity,
43
+ computeAttachmentName: instanceConfig.instanceAttachments?.fileNameFactory ?? (() => null),
43
44
  };
44
45
  const primaryInstanceOptions: PrimaryInstanceOptions<Mode> = {
45
46
  ...options.instanceOptions,
@@ -0,0 +1,13 @@
1
+ import type { UploadDefinition } from '../client/UploadNode.ts';
2
+ import type { ValueType } from '../client/ValueType.ts';
3
+ import { XFormsSpecViolationError } from './XFormsSpecViolationError.ts';
4
+
5
+ type UnsupportedUploadValueType = Exclude<ValueType, 'binary'>;
6
+
7
+ export class UploadValueTypeError extends XFormsSpecViolationError {
8
+ constructor(definition: UploadDefinition<UnsupportedUploadValueType>) {
9
+ const { valueType } = definition;
10
+
11
+ super(`Expected upload control to have 'binary' value type, got: ${valueType}`);
12
+ }
13
+ }
@@ -32,6 +32,7 @@ import type { ModelDefinition } from '../parse/model/ModelDefinition.ts';
32
32
  import type { RootDefinition } from '../parse/model/RootDefinition.ts';
33
33
  import type { SecondaryInstancesDefinition } from '../parse/model/SecondaryInstance/SecondaryInstancesDefinition.ts';
34
34
  import { InstanceNode } from './abstract/InstanceNode.ts';
35
+ import { InstanceAttachmentsState } from './attachments/InstanceAttachmentsState.ts';
35
36
  import type { InitialInstanceState } from './input/InitialInstanceState.ts';
36
37
  import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
37
38
  import type { InstanceConfig } from './internal-api/InstanceConfig.ts';
@@ -113,12 +114,9 @@ export class PrimaryInstance<
113
114
  EvaluationContext,
114
115
  ClientReactiveSerializableInstance
115
116
  {
116
- /**
117
- * @todo this will be populated as we introduce other initialization modes!
118
- */
119
- readonly initializationMode: FormInstanceInitializationMode = 'create';
120
-
117
+ readonly initializationMode: FormInstanceInitializationMode;
121
118
  readonly model: ModelDefinition;
119
+ readonly attachments: InstanceAttachmentsState;
122
120
 
123
121
  // InstanceNode
124
122
  protected readonly state: SharedNodeState<PrimaryInstanceStateSpec>;
@@ -169,6 +167,7 @@ export class PrimaryInstance<
169
167
 
170
168
  this.initializationMode = mode;
171
169
  this.model = model;
170
+ this.attachments = new InstanceAttachmentsState(initialState?.attachments);
172
171
  this.instanceNode = activeInstance;
173
172
 
174
173
  const [isAttached, setIsAttached] = createSignal(false);
@@ -0,0 +1,184 @@
1
+ import { XPathNodeKindKey } from '@getodk/xpath';
2
+ import type { Accessor } from 'solid-js';
3
+ import type { TextRange } from '../client/TextRange.ts';
4
+ import type { UploadDefinition, UploadNode, UploadNodeOptions } from '../client/UploadNode.ts';
5
+ import type { ValueType } from '../client/ValueType.ts';
6
+ import type { InstanceAttachmentFileName, InstanceState } from '../client/index.ts';
7
+ import type { AnyViolation, LeafNodeValidationState } from '../client/validation.ts';
8
+ import { UploadValueTypeError } from '../error/UploadValueTypeError.ts';
9
+ import type { XFormsXPathElement } from '../integration/xpath/adapter/XFormsXPathNode.ts';
10
+ import type { StaticLeafElement } from '../integration/xpath/static-dom/StaticElement.ts';
11
+ import { createValueNodeInstanceState } from '../lib/client-reactivity/instance-state/createValueNodeInstanceState.ts';
12
+ import { createInstanceAttachment } from '../lib/reactivity/createInstanceAttachment.ts';
13
+ import type { CurrentState } from '../lib/reactivity/node-state/createCurrentState.ts';
14
+ import type { EngineState } from '../lib/reactivity/node-state/createEngineState.ts';
15
+ import type { SharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
16
+ import { createSharedNodeState } from '../lib/reactivity/node-state/createSharedNodeState.ts';
17
+ import { createFieldHint } from '../lib/reactivity/text/createFieldHint.ts';
18
+ import { createNodeLabel } from '../lib/reactivity/text/createNodeLabel.ts';
19
+ import type { SimpleAtomicState } from '../lib/reactivity/types.ts';
20
+ import type { SharedValidationState } from '../lib/reactivity/validation/createValidation.ts';
21
+ import { createValidationState } from '../lib/reactivity/validation/createValidation.ts';
22
+ import type { UnknownAppearanceDefinition } from '../parse/body/appearance/unknownAppearanceParser.ts';
23
+ import type { Root } from './Root.ts';
24
+ import type { DescendantNodeStateSpec } from './abstract/DescendantNode.ts';
25
+ import { DescendantNode } from './abstract/DescendantNode.ts';
26
+ import type {
27
+ InstanceAttachment,
28
+ InstanceAttachmentRuntimeValue,
29
+ } from './attachments/InstanceAttachment.ts';
30
+ import type { GeneralParentNode } from './hierarchy.ts';
31
+ import type { EvaluationContext } from './internal-api/EvaluationContext.ts';
32
+ import type { InstanceAttachmentContext } from './internal-api/InstanceAttachmentContext.ts';
33
+ import type {
34
+ DecodeInstanceValue,
35
+ InstanceValueContext,
36
+ } from './internal-api/InstanceValueContext.ts';
37
+ import type { ValidationContext } from './internal-api/ValidationContext.ts';
38
+ import type { ClientReactiveSerializableValueNode } from './internal-api/serialization/ClientReactiveSerializableValueNode.ts';
39
+
40
+ export type AnyUploadDefinition = {
41
+ [V in ValueType]: UploadDefinition<V>;
42
+ }[ValueType];
43
+
44
+ type AssertUploadDefinition = (
45
+ definition: AnyUploadDefinition
46
+ ) => asserts definition is UploadDefinition<'binary'>;
47
+
48
+ const assertUploadDefinition: AssertUploadDefinition = (definition) => {
49
+ const { valueType } = definition;
50
+
51
+ if (valueType !== 'binary') {
52
+ throw new UploadValueTypeError(definition);
53
+ }
54
+ };
55
+
56
+ interface UploadControlStateSpec extends DescendantNodeStateSpec<InstanceAttachmentRuntimeValue> {
57
+ readonly label: Accessor<TextRange<'label'> | null>;
58
+ readonly hint: Accessor<TextRange<'hint'> | null>;
59
+ readonly children: null;
60
+ readonly valueOptions: null;
61
+ readonly value: SimpleAtomicState<InstanceAttachmentRuntimeValue>;
62
+ readonly instanceValue: Accessor<InstanceAttachmentFileName>;
63
+ }
64
+
65
+ export class UploadControl
66
+ extends DescendantNode<
67
+ UploadDefinition<'binary'>,
68
+ UploadControlStateSpec,
69
+ GeneralParentNode,
70
+ null
71
+ >
72
+ implements
73
+ UploadNode,
74
+ XFormsXPathElement,
75
+ EvaluationContext,
76
+ InstanceAttachmentContext,
77
+ InstanceValueContext,
78
+ ValidationContext,
79
+ ClientReactiveSerializableValueNode
80
+ {
81
+ static from(
82
+ parent: GeneralParentNode,
83
+ instanceNode: StaticLeafElement | null,
84
+ definition: UploadDefinition
85
+ ): UploadControl;
86
+ static from(
87
+ parent: GeneralParentNode,
88
+ instanceNode: StaticLeafElement | null,
89
+ definition: AnyUploadDefinition
90
+ ): UploadControl {
91
+ assertUploadDefinition(definition);
92
+
93
+ return new this(parent, instanceNode, definition);
94
+ }
95
+
96
+ private readonly validation: SharedValidationState;
97
+ private readonly instanceAttachment: InstanceAttachment;
98
+
99
+ // XFormsXPathElement
100
+ override readonly [XPathNodeKindKey] = 'element';
101
+ override readonly getXPathValue: () => InstanceAttachmentFileName;
102
+
103
+ // InstanceNode
104
+ protected readonly state: SharedNodeState<UploadControlStateSpec>;
105
+ protected readonly engineState: EngineState<UploadControlStateSpec>;
106
+
107
+ // InstanceValueContext
108
+ readonly decodeInstanceValue: DecodeInstanceValue;
109
+
110
+ // UploadNode
111
+ readonly nodeType = 'upload';
112
+ readonly valueType = 'binary';
113
+ readonly appearances: UnknownAppearanceDefinition;
114
+ readonly nodeOptions: UploadNodeOptions;
115
+ readonly currentState: CurrentState<UploadControlStateSpec>;
116
+
117
+ get validationState(): LeafNodeValidationState {
118
+ return this.validation.currentState;
119
+ }
120
+
121
+ readonly instanceState: InstanceState;
122
+
123
+ private constructor(
124
+ parent: GeneralParentNode,
125
+ override readonly instanceNode: StaticLeafElement | null,
126
+ definition: UploadDefinition<'binary'>
127
+ ) {
128
+ super(parent, instanceNode, definition);
129
+
130
+ this.appearances = definition.bodyElement.appearances;
131
+ this.nodeOptions = definition.bodyElement.options;
132
+
133
+ const instanceAttachment = createInstanceAttachment(this);
134
+
135
+ this.instanceAttachment = instanceAttachment;
136
+ this.decodeInstanceValue = instanceAttachment.decodeInstanceValue;
137
+ this.getXPathValue = instanceAttachment.getInstanceValue;
138
+
139
+ const state = createSharedNodeState(
140
+ this.scope,
141
+ {
142
+ reference: this.contextReference,
143
+ readonly: this.isReadonly,
144
+ relevant: this.isRelevant,
145
+ required: this.isRequired,
146
+
147
+ label: createNodeLabel(this, definition),
148
+ hint: createFieldHint(this, definition),
149
+ children: null,
150
+ valueOptions: null,
151
+ value: instanceAttachment.valueState,
152
+ instanceValue: instanceAttachment.getInstanceValue,
153
+ },
154
+ this.instanceConfig
155
+ );
156
+
157
+ this.state = state;
158
+ this.engineState = state.engineState;
159
+ this.currentState = state.currentState;
160
+ this.validation = createValidationState(this, this.instanceConfig);
161
+ this.instanceState = createValueNodeInstanceState(this);
162
+ }
163
+
164
+ // ValidationContext
165
+ getViolation(): AnyViolation | null {
166
+ return this.validation.engineState.violation;
167
+ }
168
+
169
+ isBlank(): boolean {
170
+ return this.getXPathValue() === '';
171
+ }
172
+
173
+ // InstanceNode
174
+ getChildren(): readonly [] {
175
+ return [];
176
+ }
177
+
178
+ // UploadNode
179
+ setValue(value: InstanceAttachmentRuntimeValue): Root {
180
+ this.instanceAttachment.setValue(value);
181
+
182
+ return this.root;
183
+ }
184
+ }