@larc-iu/plaid-client 0.2.0-alpha.23 → 0.2.0-alpha.29

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/src/roles.js CHANGED
@@ -65,9 +65,10 @@ export function readRole(config) {
65
65
  * The first layer in `layers` carrying the given role, or null. The single
66
66
  * "find a layer by its role" primitive — apps build their named finders
67
67
  * (findWordTokenLayer, etc.) on top of this.
68
- * @param {Array<{config?: object}>} [layers]
68
+ * @template {{config?: object}} T
69
+ * @param {T[] | undefined} layers
69
70
  * @param {string} role
70
- * @returns {object|null}
71
+ * @returns {T | null}
71
72
  */
72
73
  export function findByRole(layers, role) {
73
74
  return (layers || []).find(l => readRole(l?.config) === role) || null;
@@ -0,0 +1,331 @@
1
+ /**
2
+ * One choice for an `enum` or `multiselect` service parameter.
3
+ * @typedef {{value: string, label: string}} ServiceParamOption
4
+ */
5
+ /**
6
+ * A single user-controllable argument a service advertises.
7
+ * @typedef {object} ServiceParam
8
+ * @property {string} key Key the value is sent under in the request payload.
9
+ * @property {string} label
10
+ * @property {"string" | "number" | "boolean" | "enum" | "multiselect" | "field"} type
11
+ * @property {string} [description]
12
+ * @property {any} [default]
13
+ * @property {boolean} [required]
14
+ * @property {ServiceParamOption[]} [options] Required for `enum` and `multiselect`.
15
+ * @property {number} [min] `number` only.
16
+ * @property {number} [max] `number` only.
17
+ * @property {number} [step] `number` only.
18
+ * @property {boolean} [slider] `number` only: render as a slider to drag
19
+ * rather than a box to type in.
20
+ * @property {string} [placeholder] `string` only.
21
+ * @property {boolean} [multiline] `string` only.
22
+ * @property {"Sentence" | "Word" | "Morpheme"} [scope] `field` only: the
23
+ * scope of the project's annotation fields the value names one of. A
24
+ * `field` value is a string everywhere else.
25
+ */
26
+ /**
27
+ * A service's standardized self-description (lives in `extras`).
28
+ * @typedef {object} ServiceExtras
29
+ * @property {number} [schemaVersion]
30
+ * @property {string[]} [tasks] Tasks this service serves, from the TASKS vocabulary.
31
+ * @property {string} [summary] Rich human description (Markdown), beyond the
32
+ * short `description`.
33
+ * @property {ServiceParam[]} [parameters] Ordered parameter schema, rendered
34
+ * into a form by the UI.
35
+ */
36
+ /**
37
+ * What a service says about itself when it registers.
38
+ * @typedef {object} ServiceInfo
39
+ * @property {string} serviceId
40
+ * @property {string} serviceName
41
+ * @property {string} description
42
+ * @property {ServiceExtras & Record<string, any>} [extras]
43
+ */
44
+ /**
45
+ * A service as `messages.discoverServices` lists it.
46
+ * @typedef {object} DiscoveredService
47
+ * @property {string} serviceId
48
+ * @property {string} serviceName
49
+ * @property {string} description
50
+ * @property {ServiceExtras & Record<string, any>} extras
51
+ * @property {boolean} online True while the service holds an open request
52
+ * channel, false for a previously seen offline service.
53
+ * @property {string | null} [lastSeenAt] ISO-8601 stamp of when the service
54
+ * was last seen alive, or null when never stored.
55
+ * @property {string} [runnerName] Online services only: the display name of
56
+ * the account that runs the service.
57
+ * @property {boolean} [runByYou] Online services only: the caller runs it.
58
+ * @property {boolean} [servesYou] Online services only: it would take the
59
+ * caller's requests on this project. A delegating service serves members
60
+ * other than its runner only where the runner is a maintainer or an admin.
61
+ */
62
+ /**
63
+ * Does `service` serve `task`, as its declared `extras.tasks` says?
64
+ * @param {{serviceId?: string, extras?: {tasks?: string[]}}} service
65
+ * @param {string} task one of TASKS
66
+ * @returns {boolean}
67
+ */
68
+ export function servesTask(service: {
69
+ serviceId?: string;
70
+ extras?: {
71
+ tasks?: string[];
72
+ };
73
+ }, task: string): boolean;
74
+ /**
75
+ * The discovered services that serve `task`.
76
+ * @param {DiscoveredService[] | undefined} services result of client.messages.discoverServices()
77
+ * @param {string} task one of TASKS
78
+ * @returns {DiscoveredService[]}
79
+ */
80
+ export function filterServicesByTask(services: DiscoveredService[] | undefined, task: string): DiscoveredService[];
81
+ /**
82
+ * The parameter schema a service declares (ordered array), or [].
83
+ * @param {{extras?: {parameters?: ServiceParam[]}}} service
84
+ * @returns {ServiceParam[]}
85
+ */
86
+ export function getParamSchema(service: {
87
+ extras?: {
88
+ parameters?: ServiceParam[];
89
+ };
90
+ }): ServiceParam[];
91
+ /**
92
+ * A service's human summary: the rich `extras.summary`, else the short
93
+ * `description`, else ''.
94
+ * @param {{description?: string, extras?: {summary?: string}}} service
95
+ * @returns {string}
96
+ */
97
+ export function getServiceSummary(service: {
98
+ description?: string;
99
+ extras?: {
100
+ summary?: string;
101
+ };
102
+ }): string;
103
+ /**
104
+ * Default values keyed by param key — the initial form state.
105
+ * @param {ServiceParam[]} schema getParamSchema(service)
106
+ * @returns {Record<string, any>} { [key]: defaultValue }
107
+ */
108
+ export function buildDefaultValues(schema: ServiceParam[]): Record<string, any>;
109
+ /**
110
+ * Coerce/validate raw form values against the schema. Returns the cleaned
111
+ * values (keyed by param key, ready to merge into the request payload) plus any
112
+ * validation errors keyed by param key. Unknown keys in `raw` are dropped.
113
+ * @param {ServiceParam[]} schema getParamSchema(service)
114
+ * @param {Record<string, any>} raw current form values
115
+ * @returns {{values: Record<string, any>, errors: Record<string, string>}}
116
+ */
117
+ export function coerceParamValues(schema: ServiceParam[], raw: Record<string, any>): {
118
+ values: Record<string, any>;
119
+ errors: Record<string, string>;
120
+ };
121
+ /**
122
+ * Service self-description: tasks, summary, and a parameter schema.
123
+ *
124
+ * The service framework's transport carries an opaque `extras` JSON map on every
125
+ * registered service (see services.js). This module standardizes what a service
126
+ * advertises in that map so apps can, at a fixed integration point (a "task"
127
+ * like tokenize / parse / transcribe):
128
+ *
129
+ * 1. SELECT one of several services that serve the task,
130
+ * 2. let the user SPECIFY arguments the service declares, and
131
+ * 3. show the user a service-provided SUMMARY.
132
+ *
133
+ * Shape of a service's `extras` (camelCase here; Python services author the
134
+ * snake_case equivalent and the client transform converts it — see the Plaid
135
+ * manual, "Describing a service"):
136
+ *
137
+ * {
138
+ * schemaVersion: 1,
139
+ * tasks: ["tokenize"], // controlled vocab (TASKS)
140
+ * summary: "## markdown …", // rich human description
141
+ * parameters: [ // ordered; rendered into a form
142
+ * { key, label, type, description?, default?, required?,
143
+ * options?: [{value, label}], // enum / multiselect
144
+ * min?, max?, step?, slider?, // number (slider: drag, don't type)
145
+ * placeholder?, multiline?, // string
146
+ * scope? } // field: 'Sentence' | 'Word' | 'Morpheme'
147
+ * ]
148
+ * }
149
+ *
150
+ * A `field` parameter names one of the project's annotation fields at its
151
+ * `scope`. A UI that knows the project offers those fields to choose from; to
152
+ * everything here, and to a UI that does not, it is a string.
153
+ *
154
+ * A parameter's `key` is a string VALUE, so it passes over the wire verbatim;
155
+ * the UI sends `{ [param.key]: value }` in the request data. Declare each `key`
156
+ * in your service's own convention (Python: snake_case; JS: camelCase) and read
157
+ * it back under that same key — request data is recased symmetrically, so a
158
+ * snake_case key round-trips unchanged to a JS UI and back.
159
+ */
160
+ /**
161
+ * The controlled task vocabulary — the fixed integration-point goals.
162
+ *
163
+ * Whatever the task, a service should stamp everything it creates with the
164
+ * provenance convention (see ./provenance.js: stampInferred(serviceSource(id)))
165
+ * so UIs can distinguish machine output until a human verifies it, and should
166
+ * honor the write contract: never destroy human-made or verified material
167
+ * without an explicit `overwrite` parameter. See the manual, "Provenance".
168
+ */
169
+ export const TASKS: Readonly<{
170
+ TOKENIZE: "tokenize";
171
+ PARSE: "parse";
172
+ TRANSCRIBE: "transcribe";
173
+ /** Create vocab links for unlinked tokens. */
174
+ LINK_VOCAB: "link-vocab";
175
+ /** Propose an interlinear analysis (morpheme segmentation + glosses) for words. */
176
+ ANALYZE: "analyze";
177
+ /**
178
+ * Draft a meaning representation graph over the sentences of a document,
179
+ * for correction by hand. The request names a `scope` ('document' or
180
+ * 'sentence'), a `sentence` number when the scope is one sentence, and
181
+ * `overwrite`. See plaid-umr's drafting services.
182
+ *
183
+ * Apart from `analyze` on purpose. A task is the CONTRACT a spot offers a
184
+ * service against, and these two share almost none of it: an analyze
185
+ * request names the three token layers plus the language and metalanguage,
186
+ * and writes morphemes and glosses. Both claimed `analyze` for
187
+ * a while, so every app offering an analyze spot offered the other app's
188
+ * services, which could not answer the request they would have been given.
189
+ */
190
+ DRAFT_GRAPH: "draft-graph";
191
+ /** Propose a free translation for each sentence. */
192
+ TRANSLATE: "translate";
193
+ /**
194
+ * Propose time-aligned speech regions for a recording.
195
+ *
196
+ * The odd one out: a detect-speech service RETURNS its regions and writes
197
+ * nothing, because a time-aligned segment is a stretch of the baseline and
198
+ * cannot exist without text. The response carries
199
+ * `{segments: [{timeBegin, timeEnd, speaker?}]}` (seconds), and the app
200
+ * holds them as proposals until a person types into one.
201
+ */
202
+ DETECT_SPEECH: "detect-speech";
203
+ /** A conversational assistant over a project (chat turns; see plaid-agent). */
204
+ ASSIST: "assist";
205
+ /**
206
+ * Score one document's annotation against another document's of the same
207
+ * text (two annotators' copies), writing a report on the scored document's
208
+ * metadata and never an annotation. The request names the other document
209
+ * as `against`. See plaid-umr's AnCast service.
210
+ */
211
+ COMPARE: "compare";
212
+ }>;
213
+ /**
214
+ * One choice for an `enum` or `multiselect` service parameter.
215
+ */
216
+ export type ServiceParamOption = {
217
+ value: string;
218
+ label: string;
219
+ };
220
+ /**
221
+ * A single user-controllable argument a service advertises.
222
+ */
223
+ export type ServiceParam = {
224
+ /**
225
+ * Key the value is sent under in the request payload.
226
+ */
227
+ key: string;
228
+ label: string;
229
+ type: "string" | "number" | "boolean" | "enum" | "multiselect" | "field";
230
+ description?: string;
231
+ default?: any;
232
+ required?: boolean;
233
+ /**
234
+ * Required for `enum` and `multiselect`.
235
+ */
236
+ options?: ServiceParamOption[];
237
+ /**
238
+ * `number` only.
239
+ */
240
+ min?: number;
241
+ /**
242
+ * `number` only.
243
+ */
244
+ max?: number;
245
+ /**
246
+ * `number` only.
247
+ */
248
+ step?: number;
249
+ /**
250
+ * `number` only: render as a slider to drag
251
+ * rather than a box to type in.
252
+ */
253
+ slider?: boolean;
254
+ /**
255
+ * `string` only.
256
+ */
257
+ placeholder?: string;
258
+ /**
259
+ * `string` only.
260
+ */
261
+ multiline?: boolean;
262
+ /**
263
+ * `field` only: the
264
+ * scope of the project's annotation fields the value names one of. A
265
+ * `field` value is a string everywhere else.
266
+ */
267
+ scope?: "Sentence" | "Word" | "Morpheme";
268
+ };
269
+ /**
270
+ * A service's standardized self-description (lives in `extras`).
271
+ */
272
+ export type ServiceExtras = {
273
+ schemaVersion?: number;
274
+ /**
275
+ * Tasks this service serves, from the TASKS vocabulary.
276
+ */
277
+ tasks?: string[];
278
+ /**
279
+ * Rich human description (Markdown), beyond the
280
+ * short `description`.
281
+ */
282
+ summary?: string;
283
+ /**
284
+ * Ordered parameter schema, rendered
285
+ * into a form by the UI.
286
+ */
287
+ parameters?: ServiceParam[];
288
+ };
289
+ /**
290
+ * What a service says about itself when it registers.
291
+ */
292
+ export type ServiceInfo = {
293
+ serviceId: string;
294
+ serviceName: string;
295
+ description: string;
296
+ extras?: ServiceExtras & Record<string, any>;
297
+ };
298
+ /**
299
+ * A service as `messages.discoverServices` lists it.
300
+ */
301
+ export type DiscoveredService = {
302
+ serviceId: string;
303
+ serviceName: string;
304
+ description: string;
305
+ extras: ServiceExtras & Record<string, any>;
306
+ /**
307
+ * True while the service holds an open request
308
+ * channel, false for a previously seen offline service.
309
+ */
310
+ online: boolean;
311
+ /**
312
+ * ISO-8601 stamp of when the service
313
+ * was last seen alive, or null when never stored.
314
+ */
315
+ lastSeenAt?: string | null;
316
+ /**
317
+ * Online services only: the display name of
318
+ * the account that runs the service.
319
+ */
320
+ runnerName?: string;
321
+ /**
322
+ * Online services only: the caller runs it.
323
+ */
324
+ runByYou?: boolean;
325
+ /**
326
+ * Online services only: it would take the
327
+ * caller's requests on this project. A delegating service serves members
328
+ * other than its runner only where the runner is a maintainer or an admin.
329
+ */
330
+ servesYou?: boolean;
331
+ };
@@ -92,6 +92,72 @@ export const TASKS = Object.freeze({
92
92
  COMPARE: "compare",
93
93
  });
94
94
 
95
+ /**
96
+ * One choice for an `enum` or `multiselect` service parameter.
97
+ * @typedef {{value: string, label: string}} ServiceParamOption
98
+ */
99
+
100
+ /**
101
+ * A single user-controllable argument a service advertises.
102
+ * @typedef {object} ServiceParam
103
+ * @property {string} key Key the value is sent under in the request payload.
104
+ * @property {string} label
105
+ * @property {"string" | "number" | "boolean" | "enum" | "multiselect" | "field"} type
106
+ * @property {string} [description]
107
+ * @property {any} [default]
108
+ * @property {boolean} [required]
109
+ * @property {ServiceParamOption[]} [options] Required for `enum` and `multiselect`.
110
+ * @property {number} [min] `number` only.
111
+ * @property {number} [max] `number` only.
112
+ * @property {number} [step] `number` only.
113
+ * @property {boolean} [slider] `number` only: render as a slider to drag
114
+ * rather than a box to type in.
115
+ * @property {string} [placeholder] `string` only.
116
+ * @property {boolean} [multiline] `string` only.
117
+ * @property {"Sentence" | "Word" | "Morpheme"} [scope] `field` only: the
118
+ * scope of the project's annotation fields the value names one of. A
119
+ * `field` value is a string everywhere else.
120
+ */
121
+
122
+ /**
123
+ * A service's standardized self-description (lives in `extras`).
124
+ * @typedef {object} ServiceExtras
125
+ * @property {number} [schemaVersion]
126
+ * @property {string[]} [tasks] Tasks this service serves, from the TASKS vocabulary.
127
+ * @property {string} [summary] Rich human description (Markdown), beyond the
128
+ * short `description`.
129
+ * @property {ServiceParam[]} [parameters] Ordered parameter schema, rendered
130
+ * into a form by the UI.
131
+ */
132
+
133
+ /**
134
+ * What a service says about itself when it registers.
135
+ * @typedef {object} ServiceInfo
136
+ * @property {string} serviceId
137
+ * @property {string} serviceName
138
+ * @property {string} description
139
+ * @property {ServiceExtras & Record<string, any>} [extras]
140
+ */
141
+
142
+ /**
143
+ * A service as `messages.discoverServices` lists it.
144
+ * @typedef {object} DiscoveredService
145
+ * @property {string} serviceId
146
+ * @property {string} serviceName
147
+ * @property {string} description
148
+ * @property {ServiceExtras & Record<string, any>} extras
149
+ * @property {boolean} online True while the service holds an open request
150
+ * channel, false for a previously seen offline service.
151
+ * @property {string | null} [lastSeenAt] ISO-8601 stamp of when the service
152
+ * was last seen alive, or null when never stored.
153
+ * @property {string} [runnerName] Online services only: the display name of
154
+ * the account that runs the service.
155
+ * @property {boolean} [runByYou] Online services only: the caller runs it.
156
+ * @property {boolean} [servesYou] Online services only: it would take the
157
+ * caller's requests on this project. A delegating service serves members
158
+ * other than its runner only where the runner is a maintainer or an admin.
159
+ */
160
+
95
161
  /**
96
162
  * Does `service` serve `task`, as its declared `extras.tasks` says?
97
163
  * @param {{serviceId?: string, extras?: {tasks?: string[]}}} service
@@ -105,9 +171,9 @@ export function servesTask(service, task) {
105
171
 
106
172
  /**
107
173
  * The discovered services that serve `task`.
108
- * @param {Array} services result of client.messages.discoverServices()
174
+ * @param {DiscoveredService[] | undefined} services result of client.messages.discoverServices()
109
175
  * @param {string} task one of TASKS
110
- * @returns {Array}
176
+ * @returns {DiscoveredService[]}
111
177
  */
112
178
  export function filterServicesByTask(services, task) {
113
179
  return (services || []).filter((s) => servesTask(s, task));
@@ -115,8 +181,8 @@ export function filterServicesByTask(services, task) {
115
181
 
116
182
  /**
117
183
  * The parameter schema a service declares (ordered array), or [].
118
- * @param {{extras?: {parameters?: Array}}} service
119
- * @returns {Array}
184
+ * @param {{extras?: {parameters?: ServiceParam[]}}} service
185
+ * @returns {ServiceParam[]}
120
186
  */
121
187
  export function getParamSchema(service) {
122
188
  const params = service?.extras?.parameters;
@@ -169,8 +235,8 @@ function defaultForParam(param) {
169
235
 
170
236
  /**
171
237
  * Default values keyed by param key — the initial form state.
172
- * @param {Array} schema getParamSchema(service)
173
- * @returns {Object} { [key]: defaultValue }
238
+ * @param {ServiceParam[]} schema getParamSchema(service)
239
+ * @returns {Record<string, any>} { [key]: defaultValue }
174
240
  */
175
241
  export function buildDefaultValues(schema) {
176
242
  const out = {};
@@ -185,9 +251,9 @@ export function buildDefaultValues(schema) {
185
251
  * Coerce/validate raw form values against the schema. Returns the cleaned
186
252
  * values (keyed by param key, ready to merge into the request payload) plus any
187
253
  * validation errors keyed by param key. Unknown keys in `raw` are dropped.
188
- * @param {Array} schema getParamSchema(service)
189
- * @param {Object} raw current form values
190
- * @returns {{values: Object, errors: Object}}
254
+ * @param {ServiceParam[]} schema getParamSchema(service)
255
+ * @param {Record<string, any>} raw current form values
256
+ * @returns {{values: Record<string, any>, errors: Record<string, string>}}
191
257
  */
192
258
  export function coerceParamValues(schema, raw) {
193
259
  const values = {};