Skip to content

Commit 67a55ef

Browse files
web-forms (Vue UI): ~minimal changes to integrate engine entrypoint revs
This leaves aside opportunities to take advantage of engine support for creating multiple instances for the same form. This is the remaining aspect of #316.
1 parent 56f8c15 commit 67a55ef

File tree

3 files changed

+88
-43
lines changed

3 files changed

+88
-43
lines changed

packages/web-forms/src/components/OdkWebForm.vue

+26-13
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script setup lang="ts">
22
import type {
33
ChunkedInstancePayload,
4+
FetchFormAttachment,
45
MissingResourceBehavior,
56
MonolithicInstancePayload,
7+
RootNode,
68
} from '@getodk/xforms-engine';
7-
import { initializeForm, type FetchFormAttachment, type RootNode } from '@getodk/xforms-engine';
9+
import { loadForm } from '@getodk/xforms-engine';
810
import Button from 'primevue/button';
911
import Card from 'primevue/card';
1012
import PrimeMessage from 'primevue/message';
@@ -107,20 +109,31 @@ const odkForm = ref<RootNode>();
107109
const submitPressed = ref(false);
108110
const initializeFormError = ref<FormInitializationError | null>();
109111
110-
initializeForm(props.formXml, {
111-
config: {
112-
fetchFormAttachment: props.fetchFormAttachment,
113-
missingResourceBehavior: props.missingResourceBehavior,
114-
stateFactory: reactive,
115-
},
116-
})
117-
.then((f) => {
118-
odkForm.value = f;
119-
})
120-
.catch((cause) => {
121-
initializeFormError.value = new FormInitializationError(cause);
112+
const init = async () => {
113+
const { formXml, fetchFormAttachment, missingResourceBehavior } = props;
114+
115+
const formResult = await loadForm(formXml, {
116+
fetchFormAttachment,
117+
missingResourceBehavior,
122118
});
123119
120+
if (formResult.status === 'failure') {
121+
initializeFormError.value = FormInitializationError.fromError(formResult.error);
122+
123+
return;
124+
}
125+
126+
try {
127+
const { root } = formResult.createInstance({ stateFactory: reactive });
128+
129+
odkForm.value = root;
130+
} catch (error) {
131+
initializeFormError.value = FormInitializationError.from(error);
132+
}
133+
};
134+
135+
void init();
136+
124137
const handleSubmit = () => {
125138
const root = odkForm.value;
126139

packages/web-forms/src/lib/error/FormInitializationError.ts

+57-26
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import {
22
isUnknownObject,
33
type UnknownObject,
44
} from '@getodk/common/lib/runtime-types/shared-type-predicates.ts';
5-
import type { initializeForm } from '@getodk/xforms-engine';
5+
import type { createInstance } from '@getodk/xforms-engine';
66

7-
interface ErrorLikeCause extends UnknownObject {
7+
interface ErrorLike {
88
readonly message: string;
99
readonly stack?: string | null;
1010
}
1111

12+
interface ErrorLikeCause extends ErrorLike, UnknownObject {}
13+
1214
const isErrorLikeCause = (cause: unknown): cause is ErrorLikeCause => {
1315
if (!isUnknownObject(cause)) {
1416
return false;
@@ -24,6 +26,13 @@ const isErrorLikeCause = (cause: unknown): cause is ErrorLikeCause => {
2426
*/
2527
const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
2628

29+
interface FormInitializationErrorOptions {
30+
readonly message: string;
31+
readonly cause?: unknown;
32+
readonly unknownCauseDetail?: string | null;
33+
readonly stack?: string | null;
34+
}
35+
2736
/**
2837
* Provides a minimal, uniform representation of most general, known form load
2938
* failure conditions.
@@ -37,7 +46,7 @@ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
3746
* coming refinement.
3847
*
3948
* We handle these broad cases, each of which may be produced by a rejected
40-
* {@link Promise}, as returned by {@link initializeForm}:
49+
* {@link Promise}, as returned by {@link createInstance}:
4150
*
4251
* 1. Promise is rejected with any {@link Error}, or subclass/inheritor thereof,
4352
* as thrown by `@getodk/xforms-engine` or `@getodk/xpath`. These are
@@ -63,7 +72,7 @@ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
6372
* system. We will likely make some meaningful effort on this front as well,
6473
* but we accept it will never be exhaustive. Most importantly, we cannot
6574
* truly know what types may be thrown (and then passed through as a
66-
* {@link Promise} rejection by {@link initializeForm}).
75+
* {@link Promise} rejection by {@link createInstance}).
6776
*
6877
* As such where the type of {@link cause} is...
6978
*
@@ -86,42 +95,64 @@ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
8695
* to `@getodk/xforms-engine`.
8796
*/
8897
export class FormInitializationError extends Error {
89-
readonly unknownCauseDetail: string | null;
90-
readonly stack?: string | undefined;
91-
92-
constructor(readonly cause: unknown) {
93-
let message: string;
94-
let unknownCauseDetail: string | null = null;
95-
let stack: string | null = null;
98+
static fromError(cause: ErrorLike): FormInitializationError {
99+
return new this(cause);
100+
}
96101

97-
// If `initializeForm` rejected with an error, we can derive its message and stack
102+
static from(cause: unknown): FormInitializationError {
98103
if (cause instanceof Error || isErrorLikeCause(cause)) {
99-
message = cause.message;
100-
stack = cause.stack ?? null;
101-
} else if (isUnknownObject(cause) && typeof cause.message === 'string') {
102-
message = cause.message;
103-
} else if (typeof cause === 'string') {
104-
message = cause;
105-
} else {
106-
message = 'Unknown error';
104+
return this.fromError(cause);
105+
}
107106

108-
try {
109-
unknownCauseDetail = JSON.stringify(cause, null, 2);
110-
} catch {
111-
// Ignore JSON serialization error
112-
}
107+
if (isUnknownObject(cause) && typeof cause.message === 'string') {
108+
return new this({
109+
message: cause.message,
110+
cause,
111+
});
113112
}
114113

114+
if (typeof cause === 'string') {
115+
return new this({
116+
message: cause,
117+
cause,
118+
});
119+
}
120+
121+
let unknownCauseDetail: string | null = null;
122+
123+
try {
124+
unknownCauseDetail = JSON.stringify(cause, null, 2);
125+
} catch {
126+
// Ignore JSON serialization error
127+
}
128+
129+
return new this({
130+
message: 'Unknown error',
131+
cause,
132+
unknownCauseDetail,
133+
});
134+
}
135+
136+
readonly cause: unknown;
137+
readonly unknownCauseDetail: string | null;
138+
readonly stack?: string | undefined;
139+
140+
private constructor(options: FormInitializationErrorOptions) {
141+
let message = options.message;
142+
115143
// TODO: this occurs when Solid's production build detects a "potential
116144
// infinite loop" (i.e. either a form cycle, or potentially a serious bug in
117145
// the engine!).
118146
if (message === '') {
119147
message = 'Unknown error';
120148
}
121149

150+
const { cause, unknownCauseDetail, stack } = options;
151+
122152
super(message, { cause });
123153

124-
this.unknownCauseDetail = unknownCauseDetail;
154+
this.cause = cause;
155+
this.unknownCauseDetail = unknownCauseDetail ?? null;
125156

126157
if (stack != null) {
127158
this.stack = stack;

packages/web-forms/tests/helpers.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { xformFixturesByIdentifier } from '@getodk/common/fixtures/xforms.ts';
22
import type { AnyFunction } from '@getodk/common/types/helpers.d.ts';
33
import type { AnyControlNode, RootNode } from '@getodk/xforms-engine';
4-
import { initializeForm } from '@getodk/xforms-engine';
4+
import { createInstance } from '@getodk/xforms-engine';
55
import type { MountingOptions } from '@vue/test-utils';
66
import PrimeVue from 'primevue/config';
77
import type { MockInstance } from 'vitest';
@@ -45,12 +45,13 @@ export const getFormXml = (fileName: string): Promise<string> => {
4545

4646
export const getReactiveForm = async (formPath: string): Promise<RootNode> => {
4747
const formXml = await getFormXml(formPath);
48-
49-
return await initializeForm(formXml, {
50-
config: {
48+
const instance = await createInstance(formXml, {
49+
instance: {
5150
stateFactory: reactive,
5251
},
5352
});
53+
54+
return instance.root;
5455
};
5556

5657
type GlobalMountOptions = Required<MountingOptions<unknown>>['global'];

0 commit comments

Comments
 (0)