You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/en/4-concepts/4.1-instruments.mdx
+47-46Lines changed: 47 additions & 46 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,7 +5,7 @@ sidebar:
5
5
order: 1
6
6
---
7
7
8
-
## Overview
8
+
###Overview
9
9
10
10
In Open Data Capture (ODC), an instrument is the unit of data collection: it defines _what the user sees_, _what data is produced_, and _how that data is validated_.
11
11
@@ -17,19 +17,19 @@ This page is organized in two halves:
17
17
-**Instrument Model**: What an instrument is, how it narrows by `kind`, how scalar vs series instruments differ, and how schema-driven typing works.
18
18
-**Instrument Sources and Bundling**: How multi-file instrument sources become a single executable bundle that can be stored and executed at runtime.
19
19
20
-
## Instruments Are JavaScript Objects
20
+
###Instruments Are JavaScript Objects
21
21
22
22
At runtime, an instrument is a plain [JavaScript object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object). It is _not_ a class instance, and it does not require any runtime type metadata; what makes an object a valid instrument is that it matches the expected shape used by the ODC runtime and UI.
Although instruments are plain objects at runtime, ODC uses [TypeScript](https://www.typescriptlang.org/) to provide static type checking and compile-time data-shape enforcement.
27
27
28
28
The type system is designed to enforce and enable things like:
29
29
30
-
-**Discriminated narrowing**: based on `kind`, the type of `content` (and other fields) narrows to the correct variant.
31
-
-**Schema-first data typing**: the instrument output `data` type is inferred from the Zod validation schema.
32
-
-**Localization shaping**: based on `language`, UI-facing values become either a plain value or a per-language mapping.
30
+
-**Discriminated Narrowing**: Based on `kind`, the type of `content` (and other fields) narrows to the correct variant.
31
+
-**Schema-Inferred Data Typing**: The instrument output `data` type is inferred from the Zod validation schema.
32
+
-**Localization Shaping**: Based on `language`, UI-facing values become either a single value or a per-language mapping.
33
33
34
34
These rules are implemented using TypeScript features such as:
35
35
@@ -38,26 +38,26 @@ These rules are implemented using TypeScript features such as:
38
38
39
39
It is important to understand that **TypeScript types do not exist at runtime**. Their role is to help authors and maintainers catch mistakes early.
40
40
41
-
## Public Runtime API
41
+
###Public Runtime API
42
42
43
43
In the runtime environment, users typically define instruments by importing helper functions from the runtime v1 entrypoint:
Instruments can also import approved third-party libraries from the runtime. For example, all instruments must define a validation schema using Zod:
49
+
Instruments can also import approved third-party libraries from the runtime. For example, scalar instruments (`FORM` and `INTERACTIVE`) define a validation schema using Zod:
50
50
51
51
```ts
52
52
import { z } from'/runtime/v1/zod@3.x';
53
53
```
54
54
55
-
Although an instrument can be technically be defined without these helpers, `defineInstrument` and `defineSeriesInstrument` are intentionally designed to make the type system usable in practice:
55
+
Although an instrument can technically be defined without these helpers, `defineInstrument` and `defineSeriesInstrument` are intentionally designed to make the type system usable in practice:
56
56
57
-
-they set internal, runtime-controlled fields (like the runtime version marker)
58
-
-they make TypeScript inference flow in the right direction to provide a better developer experience
57
+
-They set internal, runtime-controlled fields (like the runtime version marker)
58
+
-They make TypeScript inference flow in the right direction to provide a better developer experience
59
59
60
-
## Mental Model: "Instrument" Is a Discriminated Family
60
+
###Mental Model: "Instrument" Is a Discriminated Family
61
61
62
62
At the highest level, an `Instrument` is not one shape: it is a _family_ of shapes that share a common envelope and then diverge based on a discriminator.
63
63
@@ -71,7 +71,7 @@ There is also a higher-level split:
71
71
-**Scalar instruments**: "completable" instruments that produce a single output payload, validated by a schema.
72
72
-**Series instruments**: "compositional" instruments that reference multiple scalar instruments by identity.
73
73
74
-
### The Base Instrument
74
+
####The Base Instrument
75
75
76
76
Every instrument, regardless of kind, carries:
77
77
@@ -111,7 +111,7 @@ type BaseInstrument = {
111
111
112
112
The important point is not the exact properties; it is that the system is deliberately set up so that _once `kind` is known_, everything downstream becomes more specific.
113
113
114
-
### How Narrowing Works: `kind` Drives Shape
114
+
####How Narrowing Works: `kind` Drives Shape
115
115
116
116
The type system is built around the idea:
117
117
@@ -122,7 +122,7 @@ The type system is built around the idea:
122
122
Therefore, in the codebase, consumers can narrow the type of `Instrument` based on `kind`:
When this package is compiled inside the ODC repo, a global type (`OpenDataCaptureContext`) marks `isRepo: true`, and the definition type is intersected with an extra requirement:
421
425
@@ -424,7 +428,7 @@ When this package is compiled inside the ODC repo, a global type (`OpenDataCaptu
424
428
425
429
This is a type-level policy hook: it changes authoring constraints without changing runtime behavior. If you are authoring instruments in the instrument playground for your own instance, this will not affect you.
426
430
427
-
## Instrument Sources: Why Instruments Are Bundled
431
+
###Instrument Sources: Why Instruments Are Bundled
428
432
429
433
A single-file instrument source (like the examples above) is the simplest case, but many instruments are multi-file:
430
434
@@ -438,7 +442,7 @@ If instruments were defined directly in the Open Data Capture codebase, adding o
438
442
439
443
To avoid that, ODC treats "instrument sources" as the authoring units (a set of source files), and a bundling pipeline turns those sources into a single executable artifact.
440
444
441
-
## Instrument Bundler
445
+
###Instrument Bundler
442
446
443
447
Although we often talk about files, the instrument bundler is platform-agnostic and does not require a filesystem.
444
448
@@ -462,7 +466,7 @@ Once found, the bundler injects a tiny entry module into esbuild. For example, i
462
466
The effect is that the instrument's default export becomes available under a known name (`__exports`) in the bundle.
463
467
464
468
Our custom plugin assumes responsibility for resolving all imports found. In this case, we resolve the static
465
-
relative import `'./index.js` which exists in the inputs. We then return the content of the index input to
469
+
relative import `'./index.js'` which exists in the inputs. We then return the content of the index input to
466
470
esbuild for further processing. In this case, esbuild will look for imports in the content of the index,
467
471
and pass any results to our resolver. The resolver marks all HTTP imports as external (e.g., `import React from '/runtime/v1/react@18.x'`).
468
472
It looks for any static imports inside the inputs and throws an exception if it is not found. If it is found,
@@ -487,24 +491,21 @@ before the `render` method is called.
487
491
488
492
This new content is then passed into the esbuild transpiler as a
489
493
single asynchronous [Immediately Invoked Function Expression](https://developer.mozilla.org/en-US/docs/Glossary/IIFE)
490
-
that resolves to a [Promise](https://developer.mozilla.org/en-US/docs/Web/J) of an `Instrument`. This output
494
+
that resolves to a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) of an `Instrument`. This output
491
495
conforms to the [ECMAScript 2022 Language Specification](https://262.ecma-international.org/13.0/) and can be executed in any modern browser.
492
-
At this point, the code can be minified and treeshaken (i.e., dead code removed).
496
+
At this point, the code can be minified and tree-shaken (i.e., dead code removed).
493
497
494
498
For example:
495
499
496
500
```js
497
-
`(async () => {
501
+
(async () => {
498
502
// code with styles injected (if applicable)
499
503
return __exports;
500
-
})()`;
504
+
})();
501
505
```
502
506
503
-
## Storage and Execution
507
+
###Storage and Execution
504
508
505
509
The final bundle output is stored in the database.
506
510
507
511
At runtime, it can be evaluated in the global scope (using indirect `eval` or the `Function` constructor) to produce a Promise of an Instrument object, which the runtime can then render/execute.
508
-
509
-
This bundle is then stored in the database. It can be evaluated in the global scope
510
-
(using indirect eval or the `Function` constructor) at runtime.
0 commit comments