@flow-like/widget-sdk 0.1.1
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 +258 -0
- package/package.json +42 -0
- package/src/contract.ts +121 -0
- package/src/define.ts +91 -0
- package/src/geometry.ts +277 -0
- package/src/global.d.ts +10 -0
- package/src/index.ts +44 -0
- package/src/llm-schemas.ts +1385 -0
- package/src/llm.ts +283 -0
- package/src/media.ts +107 -0
- package/src/microphone.ts +133 -0
- package/src/mount.ts +427 -0
- package/src/protocol.ts +178 -0
- package/src/query.ts +58 -0
- package/src/react.ts +34 -0
- package/src/standalone.ts +54 -0
- package/src/theme.ts +74 -0
- package/src/validate.ts +336 -0
- package/src/vanilla.ts +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# @flow-like/widget-sdk
|
|
2
|
+
|
|
3
|
+
SDK for Flow-Like micro widgets: typed contracts, the `flw/1` host bridge, and
|
|
4
|
+
framework adapters. Widgets run inside an opaque-origin sandboxed iframe and
|
|
5
|
+
talk to the host exclusively via `postMessage`; this SDK implements the widget
|
|
6
|
+
side of that protocol, plus a zero-config standalone mode for plain `vite dev`.
|
|
7
|
+
|
|
8
|
+
## Define a widget
|
|
9
|
+
|
|
10
|
+
Authors write ordinary TypeScript interfaces — `@flow-like/widget-bundler`
|
|
11
|
+
derives `contract.json` from the type arguments and injects it into the built
|
|
12
|
+
HTML as `globalThis.__FLW_CONTRACT__`.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// widget.config.ts
|
|
16
|
+
import { defineWidget } from "@flow-like/widget-sdk";
|
|
17
|
+
|
|
18
|
+
interface SalesRow {
|
|
19
|
+
x: string;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Inputs {
|
|
24
|
+
/** Chart headline @default "Sales" */
|
|
25
|
+
title: string;
|
|
26
|
+
/** @default "bar" */
|
|
27
|
+
variant: "bar" | "line";
|
|
28
|
+
/** @minimum 1 @maximum 500 @default 50 */
|
|
29
|
+
limit: number;
|
|
30
|
+
/** @default [] */
|
|
31
|
+
rows: SalesRow[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface Events {
|
|
35
|
+
pointSelected: SalesRow;
|
|
36
|
+
refreshRequested: void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Queries {
|
|
40
|
+
getSelection: { args: void; returns: { rows: SalesRow[] } };
|
|
41
|
+
getValue: { args: void; returns: string };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default defineWidget<Inputs, Events, Queries>({
|
|
45
|
+
id: "sales-chart",
|
|
46
|
+
name: "Sales Chart",
|
|
47
|
+
description: "Interactive bar/line chart",
|
|
48
|
+
sizing: { defaultHeight: 320, resizable: true },
|
|
49
|
+
dev: {
|
|
50
|
+
fixtures: {
|
|
51
|
+
empty: { rows: [] },
|
|
52
|
+
loaded: { title: "Q3 Sales", rows: [{ x: "Q1", y: 12 }] },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Geometry contracts
|
|
59
|
+
|
|
60
|
+
Use the geometry types exported by `@flow-like/widget-sdk` when a widget
|
|
61
|
+
exchanges geometry with a workflow. They describe two-dimensional WGS 84
|
|
62
|
+
GeoJSON geometry objects. Every position uses `[longitude, latitude]` order.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import {
|
|
66
|
+
defineWidget,
|
|
67
|
+
type GeoGeometry,
|
|
68
|
+
type GeoLineString,
|
|
69
|
+
type GeoPoint,
|
|
70
|
+
type GeoPolygon,
|
|
71
|
+
} from "@flow-like/widget-sdk";
|
|
72
|
+
|
|
73
|
+
interface Inputs {
|
|
74
|
+
focus?: GeoPoint;
|
|
75
|
+
/** @default [] */
|
|
76
|
+
routes: GeoLineString[];
|
|
77
|
+
/** @uniqueItems true @default [] */
|
|
78
|
+
waypoints: GeoPoint[];
|
|
79
|
+
regionsById?: Record<string, GeoPolygon>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
interface Events {
|
|
83
|
+
pointSelected: GeoPoint;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface Queries {
|
|
87
|
+
contains: {
|
|
88
|
+
args: { point: GeoPoint; area: GeoPolygon };
|
|
89
|
+
returns: boolean;
|
|
90
|
+
};
|
|
91
|
+
getGeometry: { args: void; returns: GeoGeometry };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default defineWidget<Inputs, Events, Queries>({
|
|
95
|
+
id: "route-map",
|
|
96
|
+
name: "Route Map",
|
|
97
|
+
description: "Routes and selectable map points",
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`GeoPoint`, `GeoLineString`, `GeoPolygon`, `GeoMultiPoint`,
|
|
102
|
+
`GeoMultiLineString`, `GeoMultiPolygon`, `GeoGeometryCollection`, and
|
|
103
|
+
`GeoGeometry` carry the schema metadata that creates native Geometry pins.
|
|
104
|
+
`GeoPosition` is the `[longitude, latitude]` tuple used by their coordinates.
|
|
105
|
+
Arrays retain array shape, `@uniqueItems true` makes a set-shaped pin, and
|
|
106
|
+
`Record<string, GeoPoint>` makes a map-shaped pin.
|
|
107
|
+
|
|
108
|
+
For a compatible type declared in the widget, annotate the scalar type or
|
|
109
|
+
scalar property with `@geometry Point`, `LineString`, `Polygon`, `MultiPoint`,
|
|
110
|
+
`MultiLineString`, `MultiPolygon`, `GeometryCollection`, or `Any`. Container
|
|
111
|
+
properties get their geometry metadata from the annotated element type.
|
|
112
|
+
|
|
113
|
+
`validateSchema` and `validateInputValue` enforce the geometry profile. The
|
|
114
|
+
hosted bridge uses those checks for incoming `props:update` values and emitted
|
|
115
|
+
event payloads. Use GeoJSON geometry objects directly. GeoJSON Feature and
|
|
116
|
+
FeatureCollection wrappers are outside this contract. Geometry values cannot
|
|
117
|
+
be `null`; an explicit nullable union remains JSON-shaped rather than
|
|
118
|
+
producing a native Geometry pin.
|
|
119
|
+
|
|
120
|
+
## LLM contracts
|
|
121
|
+
|
|
122
|
+
Use `LlmHistory`, `LlmResponse` and `LlmResponseChunk` when a widget exchanges
|
|
123
|
+
chat history or model output with model and agent nodes. They follow the
|
|
124
|
+
serialization of Flow-Like's `History`, `Response` and `ResponseChunk`.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import {
|
|
128
|
+
defineWidget,
|
|
129
|
+
type LlmHistory,
|
|
130
|
+
type LlmResponse,
|
|
131
|
+
type LlmResponseChunk,
|
|
132
|
+
} from "@flow-like/widget-sdk";
|
|
133
|
+
|
|
134
|
+
interface Events {
|
|
135
|
+
asked: { requestId: string; history: LlmHistory };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface Queries {
|
|
139
|
+
/** @mutation */
|
|
140
|
+
pushChunk: { args: { requestId: string; chunk: LlmResponseChunk }; returns: void };
|
|
141
|
+
/** @mutation */
|
|
142
|
+
pushResponse: { args: { requestId: string; response: LlmResponse }; returns: void };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export default defineWidget<{}, Events, Queries>({ id: "chat", name: "Chat" });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The bundler writes these types into the contract as
|
|
149
|
+
`{"type":"object","x-flow-like-type":"llm","x-llm":"Response"}`. A Query
|
|
150
|
+
Widget or Update Widget Inputs pin for that marker carries exactly the schema
|
|
151
|
+
of the native Rust type, so outputs of model and agent nodes connect directly.
|
|
152
|
+
Query Widget enforces that schema on its argument and result pins. A widget
|
|
153
|
+
action payload is a single struct: read a `history` field with **Get Field**
|
|
154
|
+
and connect it to a History input.
|
|
155
|
+
|
|
156
|
+
`validateSchema` checks marked values against the native schemas in
|
|
157
|
+
`src/llm-schemas.ts`, which are copies of `packages/schema/llm`. After
|
|
158
|
+
`cargo run -p schema-gen` changes those files, copy them again; a test fails
|
|
159
|
+
until they match. Annotate your own object type with `@llm History`,
|
|
160
|
+
`Response` or `ResponseChunk` only if it serializes exactly like that type.
|
|
161
|
+
Like geometry, a nullable union stays JSON-shaped instead of producing a
|
|
162
|
+
native pin.
|
|
163
|
+
|
|
164
|
+
## Mount (hosted)
|
|
165
|
+
|
|
166
|
+
`mountFlowWidget` registers the message listener, performs the `flw/1`
|
|
167
|
+
handshake (`hello` → `init` → `ready`), applies the host theme as CSS custom
|
|
168
|
+
properties (and a `dark` class) on `document.documentElement`, and keeps the
|
|
169
|
+
nanostores in sync with `props:update` / `theme:change`. Auto-height is
|
|
170
|
+
reported via a coalesced `resize` message unless the contract sets
|
|
171
|
+
`sizing.resizable: false`.
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { mountFlowWidget } from "@flow-like/widget-sdk";
|
|
175
|
+
import widget from "./widget.config";
|
|
176
|
+
|
|
177
|
+
const bridge = mountFlowWidget(widget);
|
|
178
|
+
|
|
179
|
+
bridge.$props.subscribe((props) => render(props));
|
|
180
|
+
bridge.emit("pointSelected", { x: "Q1", y: 12 });
|
|
181
|
+
bridge.onQuery("getSelection", () => ({ rows: currentSelection() }));
|
|
182
|
+
bridge.setValues({ value: currentSelection() });
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Standalone mode
|
|
186
|
+
|
|
187
|
+
When no host answers within 300 ms (or the widget is opened top-level, e.g.
|
|
188
|
+
plain `vite dev`), the bridge boots standalone:
|
|
189
|
+
|
|
190
|
+
- `$props` is filled from the contract's input defaults,
|
|
191
|
+
- the bundled Flow-Like theme tokens are applied, following
|
|
192
|
+
`prefers-color-scheme` live,
|
|
193
|
+
- `emit`/`setValues` log structured events to the console,
|
|
194
|
+
- queries are invokable from devtools via `window.__flw.query(name, args)`,
|
|
195
|
+
- a small "standalone" badge marks the mode.
|
|
196
|
+
|
|
197
|
+
## React
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { useWidgetProps, useWidgetTheme } from "@flow-like/widget-sdk/react";
|
|
201
|
+
import { bridge } from "./main";
|
|
202
|
+
|
|
203
|
+
export function App() {
|
|
204
|
+
const props = useWidgetProps(bridge);
|
|
205
|
+
const theme = useWidgetTheme(bridge);
|
|
206
|
+
return <h1 data-mode={theme.mode}>{props.title}</h1>;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Other frameworks use the official `@nanostores/*` bindings on `bridge.$props`
|
|
211
|
+
/ `bridge.$theme` / `bridge.$mode` directly (Svelte needs no adapter);
|
|
212
|
+
`@flow-like/widget-sdk/vanilla` re-exports the core for subscription-based
|
|
213
|
+
usage.
|
|
214
|
+
|
|
215
|
+
## Validation
|
|
216
|
+
|
|
217
|
+
`validateSchema` is a dependency-free JSON Schema subset validator (the repo
|
|
218
|
+
pins `ajv` too old to use here). Supported keywords: `type` (incl. `integer`
|
|
219
|
+
and type arrays), `enum`, `const`, `properties` / `required` /
|
|
220
|
+
`additionalProperties`, `items`, numeric bounds, string and array length,
|
|
221
|
+
`pattern`, `anyOf` / `oneOf` / `allOf`, and the Flow-Like geometry and LLM
|
|
222
|
+
markers.
|
|
223
|
+
Schemas must be pre-inlined by the bundler. `$ref` cannot be resolved at
|
|
224
|
+
runtime and is treated as valid.
|
|
225
|
+
|
|
226
|
+
## Host media and microphone
|
|
227
|
+
|
|
228
|
+
Widgets can declare `capabilities` in `defineWidget`: `workers`, `wasm`,
|
|
229
|
+
`media`, `microphone`, and `downloads`. Each is opt-in. Workers, media, and
|
|
230
|
+
WebAssembly access stay restricted to bundled assets and local Blob URLs. The
|
|
231
|
+
iframe retains its opaque origin. Downloads add only `allow-downloads` to its
|
|
232
|
+
sandbox policy.
|
|
233
|
+
|
|
234
|
+
Widgets cannot start workflows directly. To request bytes owned by a workflow,
|
|
235
|
+
emit a declared contract event and let a page-defined action handle it. That
|
|
236
|
+
workflow returns the bytes with Query Widget on a declared contract query.
|
|
237
|
+
Declare that query `@mutation` so each call needs a live acknowledgement.
|
|
238
|
+
|
|
239
|
+
`bridge.captureAudio({ maxDurationMs, signal })` records through the host and
|
|
240
|
+
returns audio bytes and a MIME type. `bridge.stopAudioCapture()` finishes early.
|
|
241
|
+
A host-owned stop button stays visible during capture. The recording lasts at
|
|
242
|
+
most 60 seconds and ends on teardown. Send its result to an appropriate workflow
|
|
243
|
+
audio node; this API does not create a WebRTC session.
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
For public radio streams, expose `publicMediaGrants: [{ id, url }]` alongside the
|
|
247
|
+
`media` capability. A source node must resolve and vet these credential-free
|
|
248
|
+
HTTPS broadcaster URLs. The host strips the grants before delivering props and
|
|
249
|
+
only announces their IDs in `$capabilities.mediaIds`. `playMedia(id)` returns a
|
|
250
|
+
promise; `pauseMedia()` and `stopMedia()` control the host player. `$media` reports
|
|
251
|
+
its current ID and state. The host always shows Play/Pause/Stop controls, including
|
|
252
|
+
a Play button when browser autoplay policy requires a fresh user gesture.
|
|
253
|
+
|
|
254
|
+
The host rejects literal private destinations, localhost, URL userinfo, and
|
|
255
|
+
credential query fields. Broadcaster DNS and redirects must also be vetted by
|
|
256
|
+
the source workflow. Native HTML audio uses anonymous CORS; MP3/AAC broadcasts
|
|
257
|
+
are supported when the broadcaster permits it. Widget teardown and grant
|
|
258
|
+
revocation stop playback.
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flow-like/widget-sdk",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Flow-Like micro widget SDK: typed contracts, flw/1 host bridge, and framework adapters.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": ["src"],
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.ts",
|
|
13
|
+
"./react": "./src/react.ts",
|
|
14
|
+
"./vanilla": "./src/vanilla.ts",
|
|
15
|
+
"./protocol": "./src/protocol.ts",
|
|
16
|
+
"./validate": "./src/validate.ts",
|
|
17
|
+
"./llm": "./src/llm.ts"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"test": "bun test"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"nanostores": "^1.0.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@nanostores/react": "^1.0.0",
|
|
28
|
+
"typescript": "^5.7.3"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@nanostores/react": ">=0.8.0",
|
|
32
|
+
"react": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"@nanostores/react": {
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
"react": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/contract.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
export const CONTRACT_VERSION = 1;
|
|
2
|
+
|
|
3
|
+
export type JsonSchema = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export type ContractInputType =
|
|
6
|
+
| "string"
|
|
7
|
+
| "number"
|
|
8
|
+
| "integer"
|
|
9
|
+
| "boolean"
|
|
10
|
+
| "enum"
|
|
11
|
+
| "json";
|
|
12
|
+
|
|
13
|
+
export interface ContractInput {
|
|
14
|
+
type: ContractInputType;
|
|
15
|
+
description?: string;
|
|
16
|
+
default?: unknown;
|
|
17
|
+
choices?: string[];
|
|
18
|
+
min?: number;
|
|
19
|
+
max?: number;
|
|
20
|
+
schema?: JsonSchema;
|
|
21
|
+
optional?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ContractEvent {
|
|
25
|
+
payloadSchema?: JsonSchema | null;
|
|
26
|
+
description?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ContractQuery {
|
|
30
|
+
argsSchema?: JsonSchema | null;
|
|
31
|
+
resultSchema?: JsonSchema | null;
|
|
32
|
+
description?: string;
|
|
33
|
+
/** Mutations change widget state and require a live acknowledgement. */
|
|
34
|
+
mutation?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface WidgetSizing {
|
|
38
|
+
defaultHeight?: number;
|
|
39
|
+
resizable?: boolean;
|
|
40
|
+
maxHeight?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface WidgetCapabilities {
|
|
44
|
+
workers?: boolean;
|
|
45
|
+
media?: boolean;
|
|
46
|
+
microphone?: boolean;
|
|
47
|
+
wasm?: boolean;
|
|
48
|
+
downloads?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** CSP fetch directives a widget may extend, in canonical order. */
|
|
52
|
+
export type WidgetCspDirective =
|
|
53
|
+
| "connectSrc"
|
|
54
|
+
| "imgSrc"
|
|
55
|
+
| "fontSrc"
|
|
56
|
+
| "mediaSrc"
|
|
57
|
+
| "styleSrc";
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Network sources per CSP fetch directive. Each entry is `scheme://host` or
|
|
61
|
+
* `scheme://*.host` (`https`, plus `wss` for `connectSrc`) without port or
|
|
62
|
+
* path.
|
|
63
|
+
*/
|
|
64
|
+
export interface WidgetCsp {
|
|
65
|
+
connectSrc?: string[];
|
|
66
|
+
imgSrc?: string[];
|
|
67
|
+
fontSrc?: string[];
|
|
68
|
+
mediaSrc?: string[];
|
|
69
|
+
styleSrc?: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Explicit expansion of `{s}` placeholders in runtime URL hosts. */
|
|
73
|
+
export interface WidgetUrlTemplate {
|
|
74
|
+
/** Lowercase DNS labels substituted for `{s}` */
|
|
75
|
+
subdomains?: string[];
|
|
76
|
+
/** A string or json input whose value lists the labels for `{s}` */
|
|
77
|
+
subdomainsInput?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A widget input whose string values carry URLs. The host takes only
|
|
82
|
+
* `scheme://host` from each value and asks the viewer to approve it.
|
|
83
|
+
*/
|
|
84
|
+
export interface WidgetNetworkInput {
|
|
85
|
+
/** `root *( "." key / "[]" / ".*" )`, e.g. `tileUrl` or `layers[].url` */
|
|
86
|
+
path: string;
|
|
87
|
+
directives: WidgetCspDirective[];
|
|
88
|
+
template?: WidgetUrlTemplate;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* One purpose group: a reason the viewer reads next to the sources, plus
|
|
93
|
+
* static sources and/or network inputs.
|
|
94
|
+
*/
|
|
95
|
+
export interface WidgetCspPurpose extends WidgetCsp {
|
|
96
|
+
reason: string;
|
|
97
|
+
inputs?: WidgetNetworkInput[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface WidgetContract {
|
|
101
|
+
contractVersion: number;
|
|
102
|
+
id: string;
|
|
103
|
+
inputs?: Record<string, ContractInput>;
|
|
104
|
+
events?: Record<string, ContractEvent>;
|
|
105
|
+
queries?: Record<string, ContractQuery>;
|
|
106
|
+
sizing?: WidgetSizing;
|
|
107
|
+
capabilities?: WidgetCapabilities;
|
|
108
|
+
/** Present only with `contractVersion` 2 */
|
|
109
|
+
csp?: WidgetCspPurpose[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function contractDefaults(
|
|
113
|
+
contract: WidgetContract | null | undefined,
|
|
114
|
+
): Record<string, unknown> {
|
|
115
|
+
const defaults: Record<string, unknown> = {};
|
|
116
|
+
if (!contract?.inputs) return defaults;
|
|
117
|
+
for (const [key, input] of Object.entries(contract.inputs)) {
|
|
118
|
+
if (input.default !== undefined) defaults[key] = input.default;
|
|
119
|
+
}
|
|
120
|
+
return defaults;
|
|
121
|
+
}
|
package/src/define.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
WidgetCapabilities,
|
|
3
|
+
WidgetCspPurpose,
|
|
4
|
+
WidgetSizing,
|
|
5
|
+
} from "./contract";
|
|
6
|
+
|
|
7
|
+
export type WidgetInputsShape = object;
|
|
8
|
+
export type WidgetEventsShape = object;
|
|
9
|
+
export type WidgetQueriesShape = object;
|
|
10
|
+
|
|
11
|
+
export type QueryArgs<Q, Name extends keyof Q> = Q[Name] extends {
|
|
12
|
+
args: infer A;
|
|
13
|
+
}
|
|
14
|
+
? A
|
|
15
|
+
: unknown;
|
|
16
|
+
|
|
17
|
+
export type QueryReturns<Q, Name extends keyof Q> = Q[Name] extends {
|
|
18
|
+
returns: infer R;
|
|
19
|
+
}
|
|
20
|
+
? R
|
|
21
|
+
: unknown;
|
|
22
|
+
|
|
23
|
+
export interface WidgetDevConfig<Inputs extends WidgetInputsShape> {
|
|
24
|
+
fixtures?: Record<string, Partial<Inputs>>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WidgetConfig<Inputs extends WidgetInputsShape> {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
sizing?: WidgetSizing;
|
|
32
|
+
capabilities?: WidgetCapabilities;
|
|
33
|
+
/**
|
|
34
|
+
* Purpose groups of network sources the viewer must approve before the
|
|
35
|
+
* widget can reach them. Extracted statically: use string literals only.
|
|
36
|
+
*/
|
|
37
|
+
csp?: WidgetCspPurpose[];
|
|
38
|
+
dev?: WidgetDevConfig<Inputs>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface WidgetDefinition<
|
|
42
|
+
Inputs extends WidgetInputsShape = Record<string, unknown>,
|
|
43
|
+
Events extends WidgetEventsShape = Record<string, unknown>,
|
|
44
|
+
Queries extends WidgetQueriesShape = Record<
|
|
45
|
+
string,
|
|
46
|
+
{ args: unknown; returns: unknown }
|
|
47
|
+
>,
|
|
48
|
+
> extends WidgetConfig<Inputs> {
|
|
49
|
+
// Phantom marker so the Events/Queries type arguments survive structural
|
|
50
|
+
// typing; never present at runtime.
|
|
51
|
+
readonly __types?: {
|
|
52
|
+
inputs: Inputs;
|
|
53
|
+
events: Events;
|
|
54
|
+
queries: Queries;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type WidgetInputsOf<D> = D extends WidgetDefinition<
|
|
59
|
+
infer Inputs,
|
|
60
|
+
WidgetEventsShape,
|
|
61
|
+
WidgetQueriesShape
|
|
62
|
+
>
|
|
63
|
+
? Inputs
|
|
64
|
+
: never;
|
|
65
|
+
|
|
66
|
+
export type WidgetEventsOf<D> = D extends WidgetDefinition<
|
|
67
|
+
WidgetInputsShape,
|
|
68
|
+
infer Events,
|
|
69
|
+
WidgetQueriesShape
|
|
70
|
+
>
|
|
71
|
+
? Events
|
|
72
|
+
: never;
|
|
73
|
+
|
|
74
|
+
export type WidgetQueriesOf<D> = D extends WidgetDefinition<
|
|
75
|
+
WidgetInputsShape,
|
|
76
|
+
WidgetEventsShape,
|
|
77
|
+
infer Queries
|
|
78
|
+
>
|
|
79
|
+
? Queries
|
|
80
|
+
: never;
|
|
81
|
+
|
|
82
|
+
export function defineWidget<
|
|
83
|
+
Inputs extends WidgetInputsShape = Record<string, unknown>,
|
|
84
|
+
Events extends WidgetEventsShape = Record<string, unknown>,
|
|
85
|
+
Queries extends WidgetQueriesShape = Record<
|
|
86
|
+
string,
|
|
87
|
+
{ args: unknown; returns: unknown }
|
|
88
|
+
>,
|
|
89
|
+
>(config: WidgetConfig<Inputs>): WidgetDefinition<Inputs, Events, Queries> {
|
|
90
|
+
return config;
|
|
91
|
+
}
|