@saga-a2ui/core 0.5.6-rc.1 → 0.7.6
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/README.md +1 -1
- package/package.json +1 -1
- package/src/actions/binding.ts +23 -5
- package/src/actions/category.ts +13 -4
- package/src/actions/definition.ts +250 -77
- package/src/actions/index.ts +14 -2
- package/src/actions/instance-host.ts +73 -21
- package/src/actions/invocation.ts +295 -76
- package/src/actions/limits.ts +23 -6
- package/src/actions/outcome.ts +57 -12
- package/src/actions/prerequisite.ts +33 -10
- package/src/actions/scope.ts +50 -14
- package/src/actions/server.ts +45 -12
- package/src/actions/thenable.ts +3 -1
- package/src/delivered-result.ts +31 -0
- package/src/host-links.ts +49 -0
- package/src/protocols/a2ui/v0_9/decoder.ts +48 -19
- package/src/protocols/a2ui/v0_9/deep-freeze.ts +8 -0
- package/src/protocols/a2ui/v0_9/index.ts +14 -1
- package/src/protocols/a2ui/v0_9/session.ts +26 -9
- package/src/protocols/a2ui/v0_9/surface-layout.ts +256 -0
|
@@ -34,7 +34,8 @@ export interface InstanceEvent<Native = unknown> {
|
|
|
34
34
|
read(path: string): unknown;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
type NativeOf<Event> =
|
|
37
|
+
type NativeOf<Event> =
|
|
38
|
+
Event extends InstanceEvent<infer Native> ? Native : never;
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Optional, per binding. `subject` reads, through a supported application or renderer API and not
|
|
@@ -71,12 +72,19 @@ export interface InstanceHandle<Events> {
|
|
|
71
72
|
|
|
72
73
|
export interface InstanceView {
|
|
73
74
|
/** The one instance with this ID and kind. Throws when there is none or more than one. */
|
|
74
|
-
getById<Events>(
|
|
75
|
+
getById<Events>(
|
|
76
|
+
id: string,
|
|
77
|
+
kind: ComponentKind<Events>,
|
|
78
|
+
): InstanceHandle<Events>;
|
|
75
79
|
/**
|
|
76
80
|
* Setup-time discovery: calls `onHandle` once for each present and later instance with this ID
|
|
77
81
|
* and kind. Unsubscribing stops discovery only. It disposes no binding and removes no callable.
|
|
78
82
|
*/
|
|
79
|
-
watchById<Events>(
|
|
83
|
+
watchById<Events>(
|
|
84
|
+
id: string,
|
|
85
|
+
kind: ComponentKind<Events>,
|
|
86
|
+
onHandle: (handle: InstanceHandle<Events>) => void,
|
|
87
|
+
): () => void;
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
export interface OpenInfo<Events> {
|
|
@@ -104,14 +112,19 @@ export interface OpenInstance<Events> {
|
|
|
104
112
|
* `unbound` when nothing is installed, and `retired` when the instance was retired or is no
|
|
105
113
|
* longer live. `started` means the callable received the event; a binding that is busy drops it.
|
|
106
114
|
*/
|
|
107
|
-
activate<Name extends keyof Events & string>(
|
|
115
|
+
activate<Name extends keyof Events & string>(
|
|
116
|
+
event: Name,
|
|
117
|
+
native: NativeOf<Events[Name]>,
|
|
118
|
+
): 'started' | 'unbound' | 'retired';
|
|
108
119
|
/**
|
|
109
120
|
* The binding now attached to one event of this instance, by identity: `undefined` while
|
|
110
121
|
* nothing is attached, and never a disposed binding. An adapter that captures it with what it
|
|
111
122
|
* renders can later tell, by reference, that the callable `activate` would reach is no longer
|
|
112
123
|
* the one that render showed. A rebind on the same handle is a different reference.
|
|
113
124
|
*/
|
|
114
|
-
attached<Name extends keyof Events & string>(
|
|
125
|
+
attached<Name extends keyof Events & string>(
|
|
126
|
+
event: Name,
|
|
127
|
+
): ActionBinding | undefined;
|
|
115
128
|
/**
|
|
116
129
|
* Calls `listener` synchronously after this instance gains or loses an attached binding, and
|
|
117
130
|
* once when it retires, with the change already visible through `attached`. A listener that
|
|
@@ -235,11 +248,15 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
235
248
|
for (const binding of [...registration.bindings]) binding.dispose();
|
|
236
249
|
}
|
|
237
250
|
|
|
238
|
-
function handleFor<Events>(
|
|
251
|
+
function handleFor<Events>(
|
|
252
|
+
registration: Registration,
|
|
253
|
+
record: InstanceRecord,
|
|
254
|
+
): InstanceHandle<Events> {
|
|
239
255
|
return {
|
|
240
256
|
id: record.componentId,
|
|
241
257
|
dataPath: record.dataPath,
|
|
242
|
-
read: (path) =>
|
|
258
|
+
read: (path) =>
|
|
259
|
+
record.retired || !record.isLive() ? undefined : record.read(path),
|
|
243
260
|
bindAction<Name extends keyof Events & string>(
|
|
244
261
|
event: Name,
|
|
245
262
|
action: ActionDefinition<Events[Name]>,
|
|
@@ -247,7 +264,9 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
247
264
|
) {
|
|
248
265
|
const subject = options?.subject;
|
|
249
266
|
if (subject !== undefined && typeof subject !== 'function') {
|
|
250
|
-
throw new TypeError(
|
|
267
|
+
throw new TypeError(
|
|
268
|
+
`The subject option for '${record.componentId}' must be a function.`,
|
|
269
|
+
);
|
|
251
270
|
}
|
|
252
271
|
// `isLive` is read fresh: the renderer may have dropped this instance's model before it retires the element.
|
|
253
272
|
if (!registration.active || record.retired || !record.isLive()) {
|
|
@@ -257,7 +276,9 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
257
276
|
);
|
|
258
277
|
}
|
|
259
278
|
if (!record.events.has(event)) {
|
|
260
|
-
throw new Error(
|
|
279
|
+
throw new Error(
|
|
280
|
+
`The component '${record.componentId}' does not offer the event '${event}'.`,
|
|
281
|
+
);
|
|
261
282
|
}
|
|
262
283
|
if (record.slots.has(event)) {
|
|
263
284
|
throw new Error(
|
|
@@ -267,10 +288,12 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
267
288
|
}
|
|
268
289
|
const installation = {};
|
|
269
290
|
const detach = (): void => {
|
|
270
|
-
const detached =
|
|
291
|
+
const detached =
|
|
292
|
+
record.slots.get(event)?.installation === installation;
|
|
271
293
|
if (detached) record.slots.delete(event);
|
|
272
294
|
registration.bindings.delete(binding);
|
|
273
|
-
for (const scope of registration.callbackScopes)
|
|
295
|
+
for (const scope of registration.callbackScopes)
|
|
296
|
+
scope.delete(binding);
|
|
274
297
|
if (detached) announce(record);
|
|
275
298
|
};
|
|
276
299
|
// Throws for a definition `Action` did not build, before anything is installed.
|
|
@@ -291,7 +314,10 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
291
314
|
dataPath: record.dataPath,
|
|
292
315
|
native,
|
|
293
316
|
read(path) {
|
|
294
|
-
if (!delivering)
|
|
317
|
+
if (!delivering)
|
|
318
|
+
throw new Error(
|
|
319
|
+
'read() answers only while the event is being delivered.',
|
|
320
|
+
);
|
|
295
321
|
return record.read(path);
|
|
296
322
|
},
|
|
297
323
|
};
|
|
@@ -328,16 +354,31 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
328
354
|
}
|
|
329
355
|
}
|
|
330
356
|
|
|
331
|
-
function matches(
|
|
332
|
-
|
|
357
|
+
function matches(
|
|
358
|
+
id: string,
|
|
359
|
+
kind: ComponentKind<unknown>,
|
|
360
|
+
record: InstanceRecord,
|
|
361
|
+
): boolean {
|
|
362
|
+
return (
|
|
363
|
+
!record.retired &&
|
|
364
|
+
record.componentId === id &&
|
|
365
|
+
record.kind === kind &&
|
|
366
|
+
record.isLive()
|
|
367
|
+
);
|
|
333
368
|
}
|
|
334
369
|
|
|
335
370
|
function viewFor(registration: Registration): InstanceView {
|
|
336
371
|
return {
|
|
337
372
|
getById(id, kind) {
|
|
338
|
-
if (!registration.active)
|
|
339
|
-
|
|
340
|
-
|
|
373
|
+
if (!registration.active)
|
|
374
|
+
throw new Error(`The setup that asked for '${id}' has ended.`);
|
|
375
|
+
const found = [...instances].filter((record) =>
|
|
376
|
+
matches(id, kind, record),
|
|
377
|
+
);
|
|
378
|
+
if (found.length === 0)
|
|
379
|
+
throw new Error(
|
|
380
|
+
`No rendered instance has the component ID '${id}' and kind '${kind.label}'.`,
|
|
381
|
+
);
|
|
341
382
|
if (found.length > 1) {
|
|
342
383
|
throw new Error(
|
|
343
384
|
`${found.length} rendered instances have the component ID '${id}' and kind '${kind.label}'. ` +
|
|
@@ -347,12 +388,16 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
347
388
|
return handleFor(registration, found[0]!);
|
|
348
389
|
},
|
|
349
390
|
watchById(id, kind, onHandle) {
|
|
350
|
-
if (!registration.active)
|
|
391
|
+
if (!registration.active)
|
|
392
|
+
throw new Error(`The setup that watches '${id}' has ended.`);
|
|
351
393
|
const watch: Watch = { id, kind, onHandle };
|
|
352
394
|
registration.watches.add(watch);
|
|
353
395
|
for (const record of [...instances]) {
|
|
354
396
|
if (!registration.watches.has(watch)) break;
|
|
355
|
-
if (matches(id, kind, record))
|
|
397
|
+
if (matches(id, kind, record))
|
|
398
|
+
runCallback(registration, () =>
|
|
399
|
+
onHandle(handleFor(registration, record)),
|
|
400
|
+
);
|
|
356
401
|
}
|
|
357
402
|
return () => {
|
|
358
403
|
registration.watches.delete(watch);
|
|
@@ -397,8 +442,15 @@ export function createInstanceHost(options: InstanceHostOptions): InstanceHost {
|
|
|
397
442
|
instances.add(record);
|
|
398
443
|
for (const registration of [...registrations]) {
|
|
399
444
|
for (const watch of [...registration.watches]) {
|
|
400
|
-
if (
|
|
401
|
-
|
|
445
|
+
if (
|
|
446
|
+
record.retired ||
|
|
447
|
+
!registration.watches.has(watch) ||
|
|
448
|
+
!matches(watch.id, watch.kind, record)
|
|
449
|
+
)
|
|
450
|
+
continue;
|
|
451
|
+
runCallback(registration, () =>
|
|
452
|
+
watch.onHandle(handleFor(registration, record)),
|
|
453
|
+
);
|
|
402
454
|
}
|
|
403
455
|
}
|
|
404
456
|
}
|