@getodk/xforms-engine 0.7.0 → 0.8.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.
- package/dist/client/RootNode.d.ts +4 -4
- package/dist/client/UploadNode.d.ts +53 -0
- package/dist/client/attachments/InstanceAttachmentMeta.d.ts +8 -0
- package/dist/client/attachments/InstanceAttachmentsConfig.d.ts +8 -0
- package/dist/client/form/FormInstanceConfig.d.ts +2 -0
- package/dist/client/hierarchy.d.ts +4 -5
- package/dist/client/index.d.ts +5 -2
- package/dist/client/node-types.d.ts +2 -3
- package/dist/client/serialization/InstanceData.d.ts +7 -5
- package/dist/client/serialization/InstancePayloadOptions.d.ts +3 -2
- package/dist/error/UploadValueTypeError.d.ts +8 -0
- package/dist/index.js +6603 -5598
- package/dist/index.js.map +1 -1
- package/dist/instance/PrimaryInstance.d.ts +2 -3
- package/dist/instance/UploadControl.d.ts +58 -0
- package/dist/instance/attachments/InstanceAttachment.d.ts +42 -0
- package/dist/instance/attachments/InstanceAttachmentsState.d.ts +9 -0
- package/dist/instance/hierarchy.d.ts +6 -7
- package/dist/instance/internal-api/InstanceAttachmentContext.d.ts +19 -0
- package/dist/instance/internal-api/InstanceConfig.d.ts +2 -0
- package/dist/instance/internal-api/serialization/ClientReactiveSerializableInstance.d.ts +2 -0
- package/dist/lib/reactivity/createInstanceAttachment.d.ts +8 -0
- package/dist/lib/resource-helpers.d.ts +2 -0
- package/dist/parse/body/BodyDefinition.d.ts +2 -1
- package/dist/parse/body/control/UploadControlDefinition.d.ts +2 -0
- package/dist/parse/model/BindTypeDefinition.d.ts +2 -7
- package/dist/solid.js +6600 -5596
- package/dist/solid.js.map +1 -1
- package/package.json +15 -14
- package/src/client/RootNode.ts +8 -3
- package/src/client/UploadNode.ts +78 -0
- package/src/client/attachments/InstanceAttachmentMeta.ts +10 -0
- package/src/client/attachments/InstanceAttachmentsConfig.ts +13 -0
- package/src/client/form/FormInstanceConfig.ts +3 -0
- package/src/client/hierarchy.ts +5 -8
- package/src/client/index.ts +4 -2
- package/src/client/node-types.ts +2 -5
- package/src/client/serialization/InstanceData.ts +14 -6
- package/src/client/serialization/InstancePayloadOptions.ts +3 -2
- package/src/entrypoints/FormInstance.ts +3 -2
- package/src/error/UploadValueTypeError.ts +13 -0
- package/src/instance/PrimaryInstance.ts +4 -5
- package/src/instance/UploadControl.ts +184 -0
- package/src/instance/attachments/InstanceAttachment.ts +69 -0
- package/src/instance/attachments/InstanceAttachmentsState.ts +18 -0
- package/src/instance/children/buildChildren.ts +5 -8
- package/src/instance/hierarchy.ts +6 -9
- package/src/instance/input/InstanceAttachmentMap.ts +33 -21
- package/src/instance/internal-api/InstanceAttachmentContext.ts +20 -0
- package/src/instance/internal-api/InstanceConfig.ts +3 -0
- package/src/instance/internal-api/serialization/ClientReactiveSerializableInstance.ts +2 -0
- package/src/lib/client-reactivity/instance-state/prepareInstancePayload.ts +72 -8
- package/src/lib/reactivity/createInstanceAttachment.ts +212 -0
- package/src/lib/resource-helpers.ts +33 -0
- package/src/parse/XFormDOM.ts +1 -3
- package/src/parse/body/BodyDefinition.ts +4 -0
- package/src/parse/body/control/UploadControlDefinition.ts +42 -0
- package/src/parse/model/BindDefinition.ts +1 -1
- package/src/parse/model/BindTypeDefinition.ts +68 -26
- package/src/parse/model/ModelBindMap.ts +0 -5
- package/src/parse/model/SecondaryInstance/sources/ExternalSecondaryInstanceResource.ts +1 -30
- package/dist/client/unsupported/UnsupportedControlNode.d.ts +0 -30
- package/dist/client/unsupported/UploadNode.d.ts +0 -9
- package/dist/instance/unsupported/UploadControl.d.ts +0 -34
- package/dist/lib/codecs/TempUnsupportedControlCodec.d.ts +0 -7
- package/src/client/unsupported/UnsupportedControlNode.ts +0 -36
- package/src/client/unsupported/UploadNode.ts +0 -14
- package/src/instance/unsupported/UploadControl.ts +0 -120
- 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.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "XForms engine for ODK Web Forms",
|
|
6
6
|
"type": "module",
|
|
@@ -54,27 +54,28 @@
|
|
|
54
54
|
"test:types": "tsc --project ./tsconfig.json --emitDeclarationOnly false --noEmit"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"
|
|
58
|
-
"
|
|
57
|
+
"bin-packer": "1.7.0",
|
|
58
|
+
"papaparse": "^5.5.2",
|
|
59
|
+
"solid-js": "^1.9.5"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
|
-
"@babel/core": "^7.26.
|
|
62
|
-
"@getodk/tree-sitter-xpath": "0.1.
|
|
63
|
-
"@getodk/xpath": "0.4.
|
|
62
|
+
"@babel/core": "^7.26.10",
|
|
63
|
+
"@getodk/tree-sitter-xpath": "0.1.3",
|
|
64
|
+
"@getodk/xpath": "0.4.1",
|
|
64
65
|
"@playwright/test": "^1.49.1",
|
|
65
66
|
"@types/papaparse": "^5.3.15",
|
|
66
|
-
"@vitest/browser": "^
|
|
67
|
-
"babel-plugin-transform-jsbi-to-bigint": "^1.4.
|
|
67
|
+
"@vitest/browser": "^3.0.9",
|
|
68
|
+
"babel-plugin-transform-jsbi-to-bigint": "^1.4.2",
|
|
68
69
|
"http-server": "^14.1.1",
|
|
69
|
-
"jsdom": "^
|
|
70
|
-
"typedoc": "^0.
|
|
71
|
-
"vite": "^
|
|
72
|
-
"vite-plugin-dts": "^4.3
|
|
70
|
+
"jsdom": "^26.0.0",
|
|
71
|
+
"typedoc": "^0.28.2",
|
|
72
|
+
"vite": "^6.2.3",
|
|
73
|
+
"vite-plugin-dts": "^4.5.3",
|
|
73
74
|
"vite-plugin-no-bundle": "^4.0.0",
|
|
74
|
-
"vitest": "^
|
|
75
|
+
"vitest": "^3.0.9"
|
|
75
76
|
},
|
|
76
77
|
"peerDependencies": {
|
|
77
|
-
"solid-js": "^1.9.
|
|
78
|
+
"solid-js": "^1.9.5"
|
|
78
79
|
},
|
|
79
80
|
"peerDependenciesMeta": {
|
|
80
81
|
"solid-js": {
|
package/src/client/RootNode.ts
CHANGED
|
@@ -8,7 +8,10 @@ import type {
|
|
|
8
8
|
InstancePayload,
|
|
9
9
|
MonolithicInstancePayload,
|
|
10
10
|
} from './serialization/InstancePayload.ts';
|
|
11
|
-
import type {
|
|
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
|
|
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
|
}
|
package/src/client/hierarchy.ts
CHANGED
|
@@ -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 {
|
|
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 =
|
package/src/client/index.ts
CHANGED
|
@@ -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 './
|
|
47
|
+
export type * from './UploadNode.ts';
|
|
46
48
|
export type * from './validation.ts';
|
|
47
49
|
export type * from './ValueType.ts';
|
package/src/client/node-types.ts
CHANGED
|
@@ -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
|
-
|
|
|
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
|
-
|
|
8
|
+
[Symbol.iterator](): FormDataIterator<[string, InstanceDataEntryValue]>;
|
|
9
|
+
entries(): FormDataIterator<[string, InstanceDataEntryValue]>;
|
|
10
|
+
values(): FormDataIterator<InstanceDataEntryValue>;
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { Accessor } from 'solid-js';
|
|
2
|
+
import type { InstancePayload } from '../../client/index.ts';
|
|
3
|
+
import type { SimpleAtomicState, SimpleAtomicStateSetter } from '../../lib/reactivity/types.ts';
|
|
4
|
+
import type { InstanceAttachmentContext } from '../internal-api/InstanceAttachmentContext.ts';
|
|
5
|
+
import type { DecodeInstanceValue } from '../internal-api/InstanceValueContext.ts';
|
|
6
|
+
import type { InstanceAttachmentsState } from './InstanceAttachmentsState.ts';
|
|
7
|
+
|
|
8
|
+
export type InstanceAttachmentFileName = string;
|
|
9
|
+
export type InstanceAttachmentRuntimeValue = File | null;
|
|
10
|
+
|
|
11
|
+
export interface InstanceAttachmentOptions {
|
|
12
|
+
readonly getFileName: Accessor<InstanceAttachmentFileName | null>;
|
|
13
|
+
readonly getInstanceValue: Accessor<InstanceAttachmentFileName>;
|
|
14
|
+
readonly decodeInstanceValue: DecodeInstanceValue;
|
|
15
|
+
|
|
16
|
+
readonly getValue: Accessor<InstanceAttachmentRuntimeValue>;
|
|
17
|
+
readonly setValue: SimpleAtomicStateSetter<InstanceAttachmentRuntimeValue>;
|
|
18
|
+
readonly valueState: SimpleAtomicState<InstanceAttachmentRuntimeValue>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class InstanceAttachment {
|
|
22
|
+
/**
|
|
23
|
+
* 1. Creates {@link InstanceAttachment | attachment state} from
|
|
24
|
+
* {@link InstanceAttachmentOptions}
|
|
25
|
+
* 2. Registers that attachment state in an instance-global
|
|
26
|
+
* {@link InstanceAttachmentsState} entry
|
|
27
|
+
*
|
|
28
|
+
* This allows an instance to:
|
|
29
|
+
*
|
|
30
|
+
* - Produce distinct file names for each attachment
|
|
31
|
+
* - Track all attachments so they can be serialized in an
|
|
32
|
+
* {@link InstancePayload}
|
|
33
|
+
*/
|
|
34
|
+
static init(
|
|
35
|
+
context: InstanceAttachmentContext,
|
|
36
|
+
options: InstanceAttachmentOptions
|
|
37
|
+
): InstanceAttachment {
|
|
38
|
+
const attachment = new this(options);
|
|
39
|
+
const { attachments } = context.rootDocument;
|
|
40
|
+
|
|
41
|
+
attachments.set(context, attachment);
|
|
42
|
+
|
|
43
|
+
return attachment;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* This property isn't used at runtime. It causes TypeScript to treat
|
|
48
|
+
* {@link InstanceAttachment} as a nominal type, ensuring
|
|
49
|
+
* {@link InstanceAttachment.init} is called to instantiate it.
|
|
50
|
+
*/
|
|
51
|
+
protected readonly _ = null;
|
|
52
|
+
|
|
53
|
+
readonly getFileName: Accessor<InstanceAttachmentFileName | null>;
|
|
54
|
+
readonly getInstanceValue: Accessor<InstanceAttachmentFileName>;
|
|
55
|
+
readonly decodeInstanceValue: DecodeInstanceValue;
|
|
56
|
+
|
|
57
|
+
readonly getValue: Accessor<InstanceAttachmentRuntimeValue>;
|
|
58
|
+
readonly setValue: SimpleAtomicStateSetter<InstanceAttachmentRuntimeValue>;
|
|
59
|
+
readonly valueState: SimpleAtomicState<InstanceAttachmentRuntimeValue>;
|
|
60
|
+
|
|
61
|
+
private constructor(options: InstanceAttachmentOptions) {
|
|
62
|
+
this.getFileName = options.getFileName;
|
|
63
|
+
this.getInstanceValue = options.getInstanceValue;
|
|
64
|
+
this.decodeInstanceValue = options.decodeInstanceValue;
|
|
65
|
+
this.getValue = options.getValue;
|
|
66
|
+
this.setValue = options.setValue;
|
|
67
|
+
this.valueState = options.valueState;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { StaticLeafElement } from '../../integration/xpath/static-dom/StaticElement.ts';
|
|
2
|
+
import type { InstanceAttachmentMap } from '../input/InstanceAttachmentMap.ts';
|
|
3
|
+
import type { InstanceAttachmentContext } from '../internal-api/InstanceAttachmentContext.ts';
|
|
4
|
+
import type { InstanceAttachment, InstanceAttachmentRuntimeValue } from './InstanceAttachment.ts';
|
|
5
|
+
|
|
6
|
+
export class InstanceAttachmentsState extends Map<InstanceAttachmentContext, InstanceAttachment> {
|
|
7
|
+
constructor(private readonly sourceAttachments: InstanceAttachmentMap | null = null) {
|
|
8
|
+
super();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getInitialFileValue(instanceNode: StaticLeafElement | null): InstanceAttachmentRuntimeValue {
|
|
12
|
+
if (instanceNode == null) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return this.sourceAttachments?.get(instanceNode.value) ?? null;
|
|
17
|
+
}
|
|
18
|
+
}
|