-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(WIP!) simplify client interface node types
Pushing up now for visibility. Many comments to re-integrate, at least some types not fully reconciled. The general idea is to have a much simpler, single base node type, and make the extensions to it more clear and direct. This results in some duplication that might prove burdensome, but it’s probably a lot easier to reason about otherwise.
- Loading branch information
1 parent
7a08386
commit ab1d2ab
Showing
13 changed files
with
380 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
packages/odk-web-forms/src/lib/engine/client-interface/simplified/BaseNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import type { AnyNodeDefinition } from '../../../xform/model/NodeDefinition.ts'; | ||
import type { EngineConfig } from './EngineConfig.ts'; | ||
import type { ActiveLanguage } from './FormLanguage.ts'; | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- referenced in JSDoc | ||
import type { OpaqueReactiveObjectFactory } from '../state/OpaqueReactiveObjectFactory.ts'; | ||
import type { TextRange } from '../text/TextRange.ts'; | ||
|
||
export interface BaseNodeState { | ||
get reference(): string; | ||
|
||
get activeLanguage(): ActiveLanguage; | ||
|
||
get readonly(): boolean; | ||
get relevant(): boolean; | ||
get required(): boolean; | ||
|
||
/** | ||
* Each node's children (if it is a parent node) will be accessed on that | ||
* node's state. While some node types will technically have static children, | ||
* other nodes' children will be stateful (i.e. repeats). For a client, both | ||
* cases are accessed the same way for consistency. | ||
* | ||
* @todo Interfaces for specific (non-base) node types should override this to | ||
* specify the actual type of their children. | ||
* @todo Interfaces for non-parent node types should override this to `null`. | ||
*/ | ||
get children(): readonly BaseNode[] | null; | ||
|
||
get label(): TextRange<'label'> | null; | ||
|
||
get hint(): TextRange<'hint'> | null; | ||
|
||
/** | ||
* @todo Interfaces for specific (non-base) node types should override this | ||
* to specify the actual type of their value. | ||
* @todo Parent nodes should specify their value as `null`, to make clear | ||
* that parent nodes do not bear a value at all. | ||
*/ | ||
get value(): unknown; | ||
} | ||
|
||
type FormNodeID = string; | ||
|
||
export interface BaseNode { | ||
/** | ||
* Each node retains access to the client-provided engine configuration. | ||
* | ||
* @todo this is more of a hint to implementation than a contract with clients. | ||
* But it's likely harmless to expose as long as it remains (deeply) readonly. | ||
*/ | ||
readonly engineConfig: EngineConfig; | ||
|
||
/** | ||
* Each node has a unique identifier. That identifier is stable throughout | ||
* the lifetime of an active session filling a form. | ||
*/ | ||
readonly nodeId: FormNodeID; | ||
|
||
/** | ||
* Each node has a definition which specifies aspects of the node defined in | ||
* the form. These aspects include (but are not limited to) the node's data | ||
* type, its body presentation constraints (if any), its bound nodeset, and | ||
* so on... | ||
* | ||
* @todo Interfaces for specific (non-base) node types should override this | ||
* to specify the actual (concrete or union) type of their `definition`. | ||
*/ | ||
readonly definition: AnyNodeDefinition; | ||
|
||
/** | ||
* Each node links back to the node representing the root of the form. | ||
* | ||
* @todo Interfaces for all concrete node types should override this to | ||
* specify the actual root node type. | ||
*/ | ||
readonly root: BaseNode; | ||
|
||
/** | ||
* Each node links back to its parent, if any. All nodes have a parent except | ||
* the form's {@link root}. | ||
*/ | ||
readonly parent: BaseNode | null; | ||
|
||
/** | ||
* Each node provides a discrete object representing the stateful aspects of | ||
* that node which will change over time. When a client provides a {@link OpaqueReactiveObjectFactory} | ||
*/ | ||
readonly currentState: BaseNodeState; | ||
} |
62 changes: 62 additions & 0 deletions
62
packages/odk-web-forms/src/lib/engine/client-interface/simplified/EngineConfig.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import type { OpaqueReactiveObjectFactory } from '../state/OpaqueReactiveObjectFactory.ts'; | ||
|
||
interface FetchResourceResponse { | ||
readonly body?: ReadableStream<Uint8Array> | null; | ||
readonly bodyUsed?: boolean; | ||
|
||
blob(): Promise<Blob>; | ||
text(): Promise<string>; | ||
} | ||
|
||
type FetchResource = (resource: URL) => Promise<FetchResourceResponse>; | ||
|
||
/** | ||
* Options provided by a client to specify certain aspects of engine runtime | ||
* behavior. These options will generally be intended to facilitate cooperation | ||
* where there is mixed responsibility between a client and the engine, or where | ||
* the engine may provide sensible defaults which a client could be expected to | ||
* override or augment. | ||
*/ | ||
export interface EngineConfig { | ||
/** | ||
* A client may specify a generic function for constructing stateful objects. | ||
* The only hard requirement of this function is that it accepts an **object** | ||
* and returns an object of the same shape. The engine will use this function | ||
* to initialize client-facing state, and will mutate properties of the object | ||
* when their corresponding state is changed. | ||
* | ||
* A client may use this function to provide its own implementation of | ||
* reactivity with semantics like those described above. The mechanism of | ||
* reactivity, if any, is at the discretion of the client. It is expected | ||
* that clients providing this function will use a reactive subscribe-on-read | ||
* mechanism to handle state updates conveyed by the engine. | ||
*/ | ||
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.) | ||
* | ||
* 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: | ||
* | ||
* - 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) | ||
* | ||
* 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). | ||
*/ | ||
readonly fetchResource?: FetchResource; | ||
} |
24 changes: 24 additions & 0 deletions
24
packages/odk-web-forms/src/lib/engine/client-interface/simplified/FormLanguage.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export interface BaseFormLanguage { | ||
readonly isSyntheticDefault?: true; | ||
readonly language: string; | ||
} | ||
|
||
export interface SyntheticDefaultLanguage extends BaseFormLanguage { | ||
readonly isSyntheticDefault: true; | ||
readonly language: ''; | ||
} | ||
|
||
export interface FormLanguage extends BaseFormLanguage { | ||
readonly isSyntheticDefault?: never; | ||
readonly language: string; | ||
} | ||
|
||
// prettier-ignore | ||
export type ActiveLanguage = | ||
| FormLanguage | ||
| SyntheticDefaultLanguage; | ||
|
||
// prettier-ignore | ||
export type FormLanguages = | ||
| readonly [FormLanguage, ...FormLanguage[]] | ||
| readonly [SyntheticDefaultLanguage]; |
11 changes: 11 additions & 0 deletions
11
packages/odk-web-forms/src/lib/engine/client-interface/simplified/GroupNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import type { NonRepeatGroupElementDefinition } from '../../../xform/body/BodyDefinition.ts'; | ||
import type { SubtreeDefinition } from '../../../xform/model/SubtreeDefinition.ts'; | ||
import type { SubtreeNode } from './SubtreeNode.ts'; | ||
|
||
interface GroupDefinition extends SubtreeDefinition { | ||
readonly bodyElement: NonRepeatGroupElementDefinition; | ||
} | ||
|
||
export interface GroupNode extends SubtreeNode { | ||
readonly definition: GroupDefinition; | ||
} |
18 changes: 18 additions & 0 deletions
18
packages/odk-web-forms/src/lib/engine/client-interface/simplified/RepeatInstanceNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import type { RepeatInstanceDefinition } from '../../../xform/model/RepeatInstanceDefinition.ts'; | ||
import type { BaseNode, BaseNodeState } from './BaseNode.ts'; | ||
import type { RepeatRangeNode } from './RepeatRangeNode.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
import type { GeneralChildNode } from './hierarchy.ts'; | ||
|
||
export interface RepeatInstanceNodeState extends BaseNodeState { | ||
get hint(): null; | ||
get children(): readonly GeneralChildNode[]; | ||
get value(): null; | ||
} | ||
|
||
export interface RepeatInstanceNode extends BaseNode { | ||
readonly definition: RepeatInstanceDefinition; | ||
readonly root: RootNode; | ||
readonly parent: RepeatRangeNode; | ||
readonly currentState: RepeatInstanceNodeState; | ||
} |
23 changes: 23 additions & 0 deletions
23
packages/odk-web-forms/src/lib/engine/client-interface/simplified/RepeatRangeNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import type { RepeatSequenceDefinition } from '../../../xform/model/RepeatSequenceDefinition.ts'; | ||
import type { BaseNode, BaseNodeState } from './BaseNode.ts'; | ||
import type { RepeatInstanceNode } from './RepeatInstanceNode.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
import type { GeneralParentNode } from './hierarchy.ts'; | ||
|
||
export interface RepeatRangeNodeState extends BaseNodeState { | ||
get hint(): null; | ||
get label(): null; | ||
get children(): readonly RepeatInstanceNode[]; | ||
get value(): null; | ||
} | ||
|
||
export interface RepeatRangeNode extends BaseNode { | ||
readonly definition: RepeatSequenceDefinition; | ||
readonly root: RootNode; | ||
readonly parent: GeneralParentNode; | ||
readonly currentState: RepeatRangeNodeState; | ||
|
||
addInstances(afterIndex?: number, count?: number): RootNode; | ||
|
||
removeInstances(startIndex: number, count?: number): RootNode; | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/odk-web-forms/src/lib/engine/client-interface/simplified/RootNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import type { RootDefinition } from '../../../xform/model/RootDefinition.ts'; | ||
import type { BaseNode, BaseNodeState } from './BaseNode.ts'; | ||
import type { FormLanguage } from './FormLanguage.ts'; | ||
import type { GeneralChildNode } from './hierarchy.ts'; | ||
|
||
export interface RootNodeState extends BaseNodeState { | ||
get label(): null; | ||
get hint(): null; | ||
get children(): readonly GeneralChildNode[]; | ||
get value(): null; | ||
} | ||
|
||
export interface RootNode extends BaseNode { | ||
readonly definition: RootDefinition; | ||
readonly root: RootNode; | ||
readonly parent: null; | ||
readonly currentState: RootNodeState; | ||
|
||
setLanguage(language: FormLanguage): RootNode; | ||
} |
24 changes: 24 additions & 0 deletions
24
packages/odk-web-forms/src/lib/engine/client-interface/simplified/SelectNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import type { ValueNodeDefinition } from '../../../xform/model/ValueNodeDefinition.ts'; | ||
import type { TextRange } from '../text/TextRange.ts'; | ||
import type { BaseNode, BaseNodeState } from './BaseNode.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
import type { GeneralParentNode } from './hierarchy.ts'; | ||
|
||
interface SelectItem { | ||
get value(): string; | ||
get label(): TextRange<'label'> | null; | ||
} | ||
|
||
export interface SelectNodeState extends BaseNodeState { | ||
get children(): null; | ||
get value(): readonly SelectItem[]; | ||
} | ||
|
||
export interface SelectNode extends BaseNode { | ||
readonly definition: ValueNodeDefinition; | ||
readonly root: RootNode; | ||
readonly parent: GeneralParentNode; | ||
|
||
select(item: SelectItem): RootNode; | ||
deselect(item: SelectItem): RootNode; | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/odk-web-forms/src/lib/engine/client-interface/simplified/StringNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { ValueNodeDefinition } from '../../../xform/model/ValueNodeDefinition.ts'; | ||
import type { BaseNode, BaseNodeState } from './BaseNode.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
import type { GeneralParentNode } from './hierarchy.ts'; | ||
|
||
export interface StringNodeState extends BaseNodeState { | ||
get children(): null; | ||
get value(): string; | ||
} | ||
|
||
export interface StringNode extends BaseNode { | ||
readonly definition: ValueNodeDefinition; | ||
readonly root: RootNode; | ||
readonly parent: GeneralParentNode; | ||
|
||
setValue(value: string): RootNode; | ||
} |
17 changes: 17 additions & 0 deletions
17
packages/odk-web-forms/src/lib/engine/client-interface/simplified/SubtreeNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type { SubtreeDefinition } from '../../../xform/model/SubtreeDefinition.ts'; | ||
import type { BaseNode, BaseNodeState } from './BaseNode.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
import type { GeneralChildNode, GeneralParentNode } from './hierarchy.ts'; | ||
|
||
export interface SubtreeNodeState extends BaseNodeState { | ||
get hint(): null; | ||
get children(): readonly GeneralChildNode[]; | ||
get value(): null; | ||
} | ||
|
||
export interface SubtreeNode extends BaseNode { | ||
readonly definition: SubtreeDefinition; | ||
readonly root: RootNode; | ||
readonly parent: GeneralParentNode; | ||
readonly currentState: SubtreeNodeState; | ||
} |
65 changes: 65 additions & 0 deletions
65
packages/odk-web-forms/src/lib/engine/client-interface/simplified/hierarchy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import type { ExpandUnion } from '@odk/common/types/helpers.js'; | ||
import type { GroupNode } from './GroupNode.ts'; | ||
import type { RepeatInstanceNode } from './RepeatInstanceNode.ts'; | ||
import type { RepeatRangeNode } from './RepeatRangeNode.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
import type { SelectNode } from './SelectNode.ts'; | ||
import type { StringNode } from './StringNode.ts'; | ||
import type { SubtreeNode } from './SubtreeNode.ts'; | ||
|
||
// prettier-ignore | ||
type AnyLeafNode = | ||
| SelectNode | ||
| StringNode; | ||
|
||
/** | ||
* Any of the concrete node types which may be a parent of any other node. | ||
* | ||
* This is an intermediate type, which shouldn't be referenced by other concrete | ||
* node types directly. Instead: | ||
* | ||
* - Repeat instances should (continue to) specify {@link RepeatRangeNode}. | ||
* - All other child nodes should specify {@link GeneralParentNode}. | ||
*/ | ||
type AnyParentNode = | ||
| RootNode // eslint-disable-line @typescript-eslint/sort-type-constituents | ||
| SubtreeNode | ||
| GroupNode | ||
| RepeatRangeNode | ||
| RepeatInstanceNode; | ||
|
||
/** | ||
* Any of the concrete node types a client may get from the engine's form | ||
* representation. This union should be updated when any new concrete node type | ||
* is added (or when any of the current members is changed/removed). | ||
* | ||
* @see {@link GeneralParentNode}, which is derived from this type | ||
* @see {@link GeneralChildNode}, which is derived from this type | ||
*/ | ||
export type AnyNode = ExpandUnion<AnyLeafNode | AnyParentNode>; | ||
|
||
/** | ||
* Any of the concrete node types which may be a parent of non-repeat instance | ||
* child nodes. | ||
*/ | ||
export type GeneralParentNode = Exclude<AnyParentNode, RepeatRangeNode>; | ||
|
||
export type RepeatRangeChildNode = RepeatInstanceNode; | ||
|
||
/** | ||
* Any of the concrete node types which may be a child of any other node. | ||
* | ||
* This is an intermediate type, which shouldn't be referenced by other concrete | ||
* node types directly. Instead: | ||
* | ||
* - Repeat ranges should (continue to) specify {@link RepeatInstanceNode}. | ||
* - All other parent nodes should specify {@link GeneralChildNode}. | ||
*/ | ||
// prettier-ignore | ||
type AnyChildNode = Exclude<AnyNode, RootNode>; | ||
|
||
/** | ||
* Any of the concrete node types which may be a child of non-repeat range | ||
* parent nodes. | ||
*/ | ||
export type GeneralChildNode = Exclude<AnyChildNode, RepeatInstanceNode>; |
8 changes: 8 additions & 0 deletions
8
packages/odk-web-forms/src/lib/engine/client-interface/simplified/init.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { EngineConfig } from './EngineConfig.ts'; | ||
import type { RootNode } from './RootNode.ts'; | ||
|
||
type InitFormInput = Blob | URL | string; | ||
|
||
export type InitForm = (input: InitFormInput, config?: EngineConfig) => Promise<RootNode>; | ||
|
||
export declare const init: InitForm; |