Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for external secondary instances #259

Merged
merged 27 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7ae25f3
scenario: correct porting mistake in repeat-based secondary instance …
eyelidlessness Nov 19, 2024
c57cb10
Set up shared access to form attachment fixtures
eyelidlessness Nov 21, 2024
ee8cb7f
Reuse simpler glob import generalization for XML XForms fixtures
eyelidlessness Nov 25, 2024
3d58132
Shared abstractions for jr resources
eyelidlessness Nov 21, 2024
4fa77ba
Former JR port ReferenceManagerTestUtils now references SharedJRResou…
eyelidlessness Nov 21, 2024
9b5c195
engine: separate configuration for retrieval of form definition, atta…
eyelidlessness Nov 21, 2024
b3f7b9c
scenario: support for JRResourceService to handle `fetchFormAttachments`
eyelidlessness Nov 25, 2024
7b59a0b
scenario: add failing tests for basic external secondary instance sup…
eyelidlessness Nov 25, 2024
75bc335
engine/client: expand `FetchResourceResponse` to include optional `he…
eyelidlessness Nov 25, 2024
0fbc3c5
engine: support for XML external secondary instances
eyelidlessness Nov 25, 2024
edf14f6
web-forms (Vue UI): auto setup JRResourceService to serve/load form a…
eyelidlessness Nov 25, 2024
e14ea93
engine: special case 404 response as a “blank” secondary instance
eyelidlessness Nov 26, 2024
2b65998
engine: support CSV external secondary instances
eyelidlessness Nov 26, 2024
d898240
scenario: remove now-impertinent notes on CSV not found test
eyelidlessness Nov 26, 2024
706aa50
scenario: remove now-impertinent notes about header-only CSV test
eyelidlessness Nov 26, 2024
4e68c48
engine: support for GeoJSON external secondary instances
eyelidlessness Nov 26, 2024
3e39af6
scenario: test exercising GeoJSON external secondary instances now pa…
eyelidlessness Nov 26, 2024
3b91a2c
scenario: test basic CSV external secondary instance support
eyelidlessness Nov 26, 2024
c0dc32c
fix: parsing CSV with trailing new lines
eyelidlessness Nov 26, 2024
2fddcec
changeset
eyelidlessness Nov 26, 2024
6b1f145
scenario: test details of CSV parsing…
eyelidlessness Nov 27, 2024
7222c30
engine: missing resource behavior error by default, blank by config
eyelidlessness Nov 27, 2024
4bcff2b
engine: client constant **types** are exported without the `constants…
eyelidlessness Nov 27, 2024
6f107bc
web-forms: partial support for previewing forms w/ external secondary…
eyelidlessness Nov 27, 2024
bd4a064
Use `JR_RESOURCE_URL_PROTOCOL` constant rather than same value inline
eyelidlessness Dec 11, 2024
d5ec9bd
Remove `fetchResource` config, update remaining references to more sp…
eyelidlessness Dec 11, 2024
128e15f
scenario: remove redundant tests with inline external secondary insta…
eyelidlessness Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 58 additions & 20 deletions packages/xforms-engine/src/client/EngineConfig.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { JRResourceURL } from '@getodk/common/jr-resources/JRResourceURL.ts';
import type { initializeForm } from '../instance/index.ts';
import type { OpaqueReactiveObjectFactory } from './OpaqueReactiveObjectFactory.ts';

/**
Expand Down Expand Up @@ -29,7 +31,13 @@ export interface FetchResourceResponse {
* actual resources (likely, but not necessarily, accessed at a corresponding
* HTTP URL).
*/
export type FetchResource = (resource: URL) => Promise<FetchResourceResponse>;
export type FetchResource<Resource extends URL = URL> = (
resource: Resource
) => Promise<FetchResourceResponse>;

export type FormAttachmentURL = JRResourceURL;

export type FetchFormAttachment = FetchResource<FormAttachmentURL>;

/**
* Options provided by a client to specify certain aspects of engine runtime
Expand All @@ -55,29 +63,59 @@ export interface EngineConfig {
readonly stateFactory?: OpaqueReactiveObjectFactory;

/**
* A client may specify a generic function for retrieving resources referenced
* by a form, such as:
*
* - Form definitions themselves (if not provided directly to the engine by
* the client)
* - External secondary instances
* - Media (images, audio, video, etc.)
* A client may specify an arbitrary {@link fetch}-like function for retrieving an XML XForm form
* definition.
*
* The function is expected to be a subset of the
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API | Fetch API},
* performing `GET` requests for:
* Notes:
*
* - Text resources (e.g. XML, CSV, JSON/GeoJSON)
* - Binary resources (e.g. media)
* - Optionally streamed binary data of either (e.g. for optimized
* presentation of audio/video)
* - This configuration will only be consuled for calls to
* {@link initializeForm} with a URL referencing an XML XForm definition. It
* will be ignored for calls passing an XML XForm form definition directly.
*
* If provided by a client, this function will be used by the engine to
* retrieve any such resource, as required for engine functionality. If
* absent, the engine will use the native `fetch` function (if available, a
* polyfill otherwise). Clients may use this function to provide resources
* from sources other than the network, (or even in a test client to provide
* e.g. resources from test fixtures).
* - For calls to {@link initializeForm} with a URL, if this configuration is
* not specified it will default to the global {@link fetch} function (if
* one is defined).
*/
readonly fetchFormDefinition?: FetchResource;

/**
* @deprecated
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to mark things as deprecated and keep them around yet. I think it's simpler to immediately get rid of them.

* @alias fetchFormDefinition
*/
readonly fetchResource?: FetchResource;

/**
* A client may specify an arbitrary {@link fetch}-like function to retrieve a
* form's attachments, i.e. any `jr:` URL referenced by the form (as specified
* by {@link https://getodk.github.io/xforms-spec/ | ODK XForms}).
*
* Notes:
*
* - This configuration will be consulted for all supported form attachments,
* as a part of {@link initializeForm | form initialization}.
*
* - If this configuration is not specified it will default to the global
* {@link fetch} function (if one is defined).
*
* This default behavior will typically result in failure to load form
* attachments—and in most cases this will also cause
* {@link initializeForm | form initialization} to fail overall—with the
* following exceptions:
*
* - **CLIENT-SPECIFIC:** Usage in coordination with a client-implemented
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API | Serivce Worker},
* which can intercept network requests **generally**. Clients already using
* a Service Worker may opt for the convenience of handling network requests
* for `jr:` URLs along with any other network interception logic. Client
* implementors should be warned, however, that such `jr:` URLs are not
* namespaced or otherwise scoped to a particular form; such a client would
* therefore inherently need to coordinate state between the Service Worker
* and the main thread (or whatever other realm calls
* {@link initializeForm}).
*
* - **PENDING:** Any usage where the engine does not require access to a
* form's attachments.
*/
readonly fetchFormAttachment?: FetchFormAttachment;
}
12 changes: 8 additions & 4 deletions packages/xforms-engine/src/instance/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { identity } from '@getodk/common/lib/identity.ts';
import { getOwner } from 'solid-js';
import type { EngineConfig } from '../client/EngineConfig.ts';
import type { RootNode } from '../client/RootNode.ts';
import type {
InitializeFormOptions as BaseInitializeFormOptions,
Expand All @@ -17,10 +18,11 @@ interface InitializeFormOptions extends BaseInitializeFormOptions {
readonly config: Partial<InstanceConfig>;
}

const buildInstanceConfig = (options: Partial<InstanceConfig> = {}): InstanceConfig => {
const buildInstanceConfig = (options: EngineConfig = {}): InstanceConfig => {
return {
createUniqueId: options.createUniqueId ?? createUniqueId,
fetchResource: options.fetchResource ?? fetch,
createUniqueId,
fetchFormDefinition: options.fetchFormDefinition ?? options.fetchResource ?? fetch,
fetchFormAttachment: options.fetchFormAttachment ?? fetch,
stateFactory: options.stateFactory ?? identity,
};
};
Expand All @@ -32,7 +34,9 @@ export const initializeForm = async (
const owner = getOwner();
const scope = createReactiveScope({ owner });
const engineConfig = buildInstanceConfig(options.config);
const sourceXML = await retrieveSourceXMLResource(input, engineConfig);
const sourceXML = await retrieveSourceXMLResource(input, {
fetchResource: engineConfig.fetchFormDefinition,
});
const form = new XFormDefinition(sourceXML);
const primaryInstance = new PrimaryInstance(scope, form.model, engineConfig);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import type { EngineConfig } from '../../client/EngineConfig.ts';
import type { FetchFormAttachment, FetchResource } from '../../client/EngineConfig.ts';
import type { OpaqueReactiveObjectFactory } from '../../client/OpaqueReactiveObjectFactory.ts';
import type { CreateUniqueId } from '../../lib/unique-id.ts';

export interface InstanceConfig extends Required<EngineConfig> {
export interface InstanceConfig {
readonly stateFactory: OpaqueReactiveObjectFactory;
readonly fetchFormDefinition: FetchResource;
readonly fetchFormAttachment: FetchFormAttachment;

/**
* Uniqueness per form instance session (so e.g. persistence isn't necessary).
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/xforms-engine/test/instance/PrimaryInstance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ describe('PrimaryInstance engine representation of instance state', () => {
return scope.runTask(() => {
return new PrimaryInstance(scope, xformDefinition.model, {
createUniqueId,
fetchResource,
fetchFormAttachment: fetchResource,
fetchFormDefinition: fetchResource,
stateFactory,
});
});
Expand Down