janela 0.6.0 → 0.7.0
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 +46 -15
- package/api/index.d.ts +26 -6
- package/package.json +1 -1
- package/runtime/types.ts +16 -9
- package/templates/react/files/src-host/main.ts +8 -10
- package/templates/solid/files/src-host/main.ts +8 -10
- package/templates/svelte/files/src-host/main.ts +8 -10
- package/templates/vue/files/src-host/main.ts +8 -10
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ framework templates' default.
|
|
|
138
138
|
|
|
139
139
|
```ts
|
|
140
140
|
// src-host/main.ts
|
|
141
|
-
import {
|
|
141
|
+
import type { JanelaApp } from "janela/host";
|
|
142
142
|
|
|
143
143
|
export type AppCommands = {
|
|
144
144
|
add: { args: { a: number; b: number }; result: number };
|
|
@@ -147,13 +147,11 @@ export type AppCommands = {
|
|
|
147
147
|
};
|
|
148
148
|
export type AppEvents = { added: number };
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
export
|
|
152
|
-
|
|
153
|
-
export type App = { commands: typeof commands; events: typeof events };
|
|
150
|
+
/** The app, carrying its contract. This is what the page imports. */
|
|
151
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
154
152
|
|
|
155
153
|
// Typing the app with the contract is what makes the methods below checked.
|
|
156
|
-
export function setup(app:
|
|
154
|
+
export function setup(app: App): void {
|
|
157
155
|
app.command("add", (args) => { // args inferred: { a: number; b: number }
|
|
158
156
|
app.emit("added", args.a + args.b); // event name and payload checked
|
|
159
157
|
return args.a + args.b; // return type checked against the contract
|
|
@@ -312,6 +310,37 @@ nested modal loop would otherwise re-enter the host loop underneath a live TS
|
|
|
312
310
|
frame; [docs/native-shell.md](../../docs/native-shell.md) has the details, the
|
|
313
311
|
per-platform table, and the Windows GUI-subsystem note.
|
|
314
312
|
|
|
313
|
+
## Migrating from 0.6.x
|
|
314
|
+
|
|
315
|
+
The contract lives entirely in the types now, so the runtime tokens are gone.
|
|
316
|
+
Name the app instead of wrapping its two tables:
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
// before (0.6.x)
|
|
320
|
+
import { defineCommands, defineEvents, type JanelaApp } from "janela/host";
|
|
321
|
+
|
|
322
|
+
export const commands = defineCommands<AppCommands>();
|
|
323
|
+
export const events = defineEvents<AppEvents>();
|
|
324
|
+
export type App = { commands: typeof commands; events: typeof events };
|
|
325
|
+
|
|
326
|
+
export function setup(app: JanelaApp<AppCommands, AppEvents>): void { … }
|
|
327
|
+
|
|
328
|
+
// after (0.7.x)
|
|
329
|
+
import type { JanelaApp } from "janela/host";
|
|
330
|
+
|
|
331
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
332
|
+
|
|
333
|
+
export function setup(app: App): void { … }
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`AppCommands` and `AppEvents` are unchanged, and so is every page: the
|
|
337
|
+
frontend still writes `createClient<App>()` and `client.invoke(...)`, because
|
|
338
|
+
`createClient` reads the contract off either shape.
|
|
339
|
+
|
|
340
|
+
`defineCommands` and `defineEvents` still exist and still work — they are
|
|
341
|
+
`@deprecated` no-ops that only ever carried types — so a 0.6.x project keeps
|
|
342
|
+
compiling and running untouched.
|
|
343
|
+
|
|
315
344
|
## Migrating from 0.5.x
|
|
316
345
|
|
|
317
346
|
The contract now rides on the app itself, so the standalone registrars are no
|
|
@@ -333,14 +362,13 @@ export function setup(app: JanelaApp<AppCommands, AppEvents>): void {
|
|
|
333
362
|
}
|
|
334
363
|
```
|
|
335
364
|
|
|
336
|
-
Declare each contract as a named type
|
|
337
|
-
|
|
365
|
+
Declare each contract as a named type and hand both to the app (0.7.x drops
|
|
366
|
+
the `defineCommands` / `defineEvents` tokens entirely — see above):
|
|
338
367
|
|
|
339
368
|
```ts
|
|
340
369
|
export type AppCommands = { add: { args: { a: number; b: number }; result: number } };
|
|
341
370
|
export type AppEvents = { added: number };
|
|
342
|
-
export
|
|
343
|
-
export const events = defineEvents<AppEvents>();
|
|
371
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
344
372
|
```
|
|
345
373
|
|
|
346
374
|
`on`, `onAsync` and `emit` still work — they are `@deprecated` one-line
|
|
@@ -358,9 +386,11 @@ Two things are new:
|
|
|
358
386
|
|
|
359
387
|
- `listen()` (and the injected `janela.listen`) now **return a disposer**.
|
|
360
388
|
Previously they returned nothing, so existing code is unaffected.
|
|
361
|
-
- The **typed contract** —
|
|
389
|
+
- The **typed contract** — a contract-typed app on the host,
|
|
362
390
|
`createClient<App>()` on the page. The framework templates now scaffold with
|
|
363
|
-
it. See [The typed contract](#the-typed-contract).
|
|
391
|
+
it. See [The typed contract](#the-typed-contract). (0.4.x shipped this with
|
|
392
|
+
`defineCommands` / `defineEvents` tokens; 0.7.x replaced them with the `App`
|
|
393
|
+
type alias below, and the tokens are deprecated but still work.)
|
|
364
394
|
|
|
365
395
|
To adopt it in an existing app, declare what the host already exposes and swap
|
|
366
396
|
the registrations:
|
|
@@ -373,10 +403,11 @@ app.command("add", (args) => {
|
|
|
373
403
|
});
|
|
374
404
|
|
|
375
405
|
// after
|
|
376
|
-
export
|
|
406
|
+
export type AppCommands = {
|
|
377
407
|
add: { args: { a: number; b: number }; result: number };
|
|
378
|
-
}
|
|
379
|
-
export type
|
|
408
|
+
};
|
|
409
|
+
export type AppEvents = { added: number };
|
|
410
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
380
411
|
|
|
381
412
|
app.command("add", (args) => args.a + args.b); // args inferred, no cast
|
|
382
413
|
```
|
package/api/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* parse.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import type { JanelaApp } from "../runtime/janela";
|
|
9
10
|
import type { CommandShapes, Commands, Events } from "../runtime/types";
|
|
10
11
|
|
|
11
12
|
/** Removes a subscription created by `listen` / `client.on`. */
|
|
@@ -50,17 +51,36 @@ export declare function listen<T = unknown>(
|
|
|
50
51
|
// Typed contract
|
|
51
52
|
// ---------------------------------------------------------------------------
|
|
52
53
|
|
|
53
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Anything shaped like a host contract module's exported `App` type.
|
|
56
|
+
*
|
|
57
|
+
* @deprecated The `{ commands, events }` wrapper of 0.5.x/0.6.x. Export
|
|
58
|
+
* `type App = JanelaApp<AppCommands, AppEvents>` instead.
|
|
59
|
+
*/
|
|
54
60
|
export interface Contract {
|
|
55
61
|
commands: Commands<CommandShapes>;
|
|
56
62
|
events: Events<unknown>;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
|
-
/**
|
|
60
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The command table declared by a contract.
|
|
67
|
+
*
|
|
68
|
+
* Reads the contract off a `JanelaApp<C, E>` — what a host's `App` type is
|
|
69
|
+
* from 0.7.0 — and falls back to the 0.5.x/0.6.x `{ commands, events }`
|
|
70
|
+
* wrapper, so a project written against either shape keeps checking.
|
|
71
|
+
*/
|
|
72
|
+
export type CommandsOf<A> = A extends JanelaApp<infer M, infer _E>
|
|
73
|
+
? M
|
|
74
|
+
: A extends { commands: Commands<infer M> }
|
|
75
|
+
? M
|
|
76
|
+
: never;
|
|
61
77
|
|
|
62
|
-
/** The event table declared by a contract. */
|
|
63
|
-
export type EventsOf<A> = A extends
|
|
78
|
+
/** The event table declared by a contract; see CommandsOf for the two shapes. */
|
|
79
|
+
export type EventsOf<A> = A extends JanelaApp<infer _M, infer E>
|
|
80
|
+
? E
|
|
81
|
+
: A extends { events: Events<infer E> }
|
|
82
|
+
? E
|
|
83
|
+
: never;
|
|
64
84
|
|
|
65
85
|
/**
|
|
66
86
|
* A client bound to a host contract: command names, argument shapes, result
|
|
@@ -89,7 +109,7 @@ export interface JanelaClient<A> {
|
|
|
89
109
|
* Build a client checked against a host's contract.
|
|
90
110
|
*
|
|
91
111
|
* ```ts
|
|
92
|
-
* import type { App } from "../src-host/main";
|
|
112
|
+
* import type { App } from "../src-host/main"; // App = JanelaApp<Cmds, Evts>
|
|
93
113
|
* const client = createClient<App>();
|
|
94
114
|
* const sum = await client.invoke("add", { a: 2, b: 40 }); // number
|
|
95
115
|
* const off = client.on("added", (v) => console.log(v)); // v: number
|
package/package.json
CHANGED
package/runtime/types.ts
CHANGED
|
@@ -85,11 +85,10 @@ export interface WindowConfig {
|
|
|
85
85
|
// Payloads still cross as JSON, so these types are compile-time only. Nothing
|
|
86
86
|
// validates a malformed payload at runtime.
|
|
87
87
|
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
// compile in a host build, while `on(app, commands, ...)` does.
|
|
88
|
+
// The contract is carried by the app's own type — `JanelaApp<Commands, Events>`
|
|
89
|
+
// — so the tables below are types and nothing more. The `Commands`/`Events`
|
|
90
|
+
// tokens and their `define*` constructors are the 0.5.x/0.6.x shape, kept so
|
|
91
|
+
// projects written against it still compile.
|
|
93
92
|
|
|
94
93
|
/** One command's argument and result types. */
|
|
95
94
|
export interface CommandShape {
|
|
@@ -116,17 +115,25 @@ export interface Events<E> {
|
|
|
116
115
|
/**
|
|
117
116
|
* Declare the commands a host exposes.
|
|
118
117
|
*
|
|
118
|
+
* @deprecated The contract needs no runtime token. Declare the tables as
|
|
119
|
+
* types and name the app itself:
|
|
120
|
+
*
|
|
119
121
|
* ```ts
|
|
120
|
-
* export
|
|
121
|
-
*
|
|
122
|
-
*
|
|
122
|
+
* export type AppCommands = { add: { args: { a: number; b: number }; result: number } };
|
|
123
|
+
* export type AppEvents = { added: number };
|
|
124
|
+
* export type App = JanelaApp<AppCommands, AppEvents>;
|
|
125
|
+
* export function setup(app: App): void { … }
|
|
123
126
|
* ```
|
|
124
127
|
*/
|
|
125
128
|
export function defineCommands<M extends CommandShapes>(): Commands<M> {
|
|
126
129
|
return {};
|
|
127
130
|
}
|
|
128
131
|
|
|
129
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Declare the events a host emits: `defineEvents<{ added: number }>()`.
|
|
134
|
+
*
|
|
135
|
+
* @deprecated Pass the event table to `JanelaApp` instead — see defineCommands.
|
|
136
|
+
*/
|
|
130
137
|
export function defineEvents<E>(): Events<E> {
|
|
131
138
|
return {};
|
|
132
139
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The two tables below are the single declaration of what this app exposes,
|
|
4
|
+
// and `App` names an app that carries them. The frontend imports `App` with
|
|
5
|
+
// `import type`, so the page is checked against these exact types — command
|
|
6
|
+
// names, argument shapes, results and event payloads — with no code
|
|
7
|
+
// generation and nothing to keep in sync.
|
|
7
8
|
//
|
|
8
9
|
// Two gotchas inherited from scriptc:
|
|
9
10
|
// - never use a bare FFI-backed call as a complete variable initializer;
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
14
|
// to compile.
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import type { JanelaApp } from "janela/host";
|
|
16
17
|
|
|
17
18
|
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
19
|
export type AppCommands = {
|
|
@@ -28,16 +29,13 @@ export type AppEvents = {
|
|
|
28
29
|
added: number;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
export const commands = defineCommands<AppCommands>();
|
|
32
|
-
export const events = defineEvents<AppEvents>();
|
|
33
|
-
|
|
34
32
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
35
|
-
export type App =
|
|
33
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
36
34
|
|
|
37
35
|
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
36
|
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
37
|
// return value has to match. Same for `app.emit`.
|
|
40
|
-
export function setup(app:
|
|
38
|
+
export function setup(app: App): void {
|
|
41
39
|
app.command("add", (args) => {
|
|
42
40
|
const sum = args.a + args.b;
|
|
43
41
|
app.emit("added", sum);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The two tables below are the single declaration of what this app exposes,
|
|
4
|
+
// and `App` names an app that carries them. The frontend imports `App` with
|
|
5
|
+
// `import type`, so the page is checked against these exact types — command
|
|
6
|
+
// names, argument shapes, results and event payloads — with no code
|
|
7
|
+
// generation and nothing to keep in sync.
|
|
7
8
|
//
|
|
8
9
|
// Two gotchas inherited from scriptc:
|
|
9
10
|
// - never use a bare FFI-backed call as a complete variable initializer;
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
14
|
// to compile.
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import type { JanelaApp } from "janela/host";
|
|
16
17
|
|
|
17
18
|
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
19
|
export type AppCommands = {
|
|
@@ -28,16 +29,13 @@ export type AppEvents = {
|
|
|
28
29
|
added: number;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
export const commands = defineCommands<AppCommands>();
|
|
32
|
-
export const events = defineEvents<AppEvents>();
|
|
33
|
-
|
|
34
32
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
35
|
-
export type App =
|
|
33
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
36
34
|
|
|
37
35
|
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
36
|
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
37
|
// return value has to match. Same for `app.emit`.
|
|
40
|
-
export function setup(app:
|
|
38
|
+
export function setup(app: App): void {
|
|
41
39
|
app.command("add", (args) => {
|
|
42
40
|
const sum = args.a + args.b;
|
|
43
41
|
app.emit("added", sum);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The two tables below are the single declaration of what this app exposes,
|
|
4
|
+
// and `App` names an app that carries them. The frontend imports `App` with
|
|
5
|
+
// `import type`, so the page is checked against these exact types — command
|
|
6
|
+
// names, argument shapes, results and event payloads — with no code
|
|
7
|
+
// generation and nothing to keep in sync.
|
|
7
8
|
//
|
|
8
9
|
// Two gotchas inherited from scriptc:
|
|
9
10
|
// - never use a bare FFI-backed call as a complete variable initializer;
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
14
|
// to compile.
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import type { JanelaApp } from "janela/host";
|
|
16
17
|
|
|
17
18
|
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
19
|
export type AppCommands = {
|
|
@@ -28,16 +29,13 @@ export type AppEvents = {
|
|
|
28
29
|
added: number;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
export const commands = defineCommands<AppCommands>();
|
|
32
|
-
export const events = defineEvents<AppEvents>();
|
|
33
|
-
|
|
34
32
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
35
|
-
export type App =
|
|
33
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
36
34
|
|
|
37
35
|
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
36
|
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
37
|
// return value has to match. Same for `app.emit`.
|
|
40
|
-
export function setup(app:
|
|
38
|
+
export function setup(app: App): void {
|
|
41
39
|
app.command("add", (args) => {
|
|
42
40
|
const sum = args.a + args.b;
|
|
43
41
|
app.emit("added", sum);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src-host/main.ts — your app's backend, compiled to native code by scriptc.
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
3
|
+
// The two tables below are the single declaration of what this app exposes,
|
|
4
|
+
// and `App` names an app that carries them. The frontend imports `App` with
|
|
5
|
+
// `import type`, so the page is checked against these exact types — command
|
|
6
|
+
// names, argument shapes, results and event payloads — with no code
|
|
7
|
+
// generation and nothing to keep in sync.
|
|
7
8
|
//
|
|
8
9
|
// Two gotchas inherited from scriptc:
|
|
9
10
|
// - never use a bare FFI-backed call as a complete variable initializer;
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
// an `undefined` argument lowers to a zero-parameter function and fails
|
|
13
14
|
// to compile.
|
|
14
15
|
|
|
15
|
-
import {
|
|
16
|
+
import type { JanelaApp } from "janela/host";
|
|
16
17
|
|
|
17
18
|
/** Every command this app answers. Declared once; the page checks against it. */
|
|
18
19
|
export type AppCommands = {
|
|
@@ -28,16 +29,13 @@ export type AppEvents = {
|
|
|
28
29
|
added: number;
|
|
29
30
|
};
|
|
30
31
|
|
|
31
|
-
export const commands = defineCommands<AppCommands>();
|
|
32
|
-
export const events = defineEvents<AppEvents>();
|
|
33
|
-
|
|
34
32
|
/** The contract the page imports with `import type { App } from "../src-host/main"`. */
|
|
35
|
-
export type App =
|
|
33
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
36
34
|
|
|
37
35
|
// Typing the app with the contract is what makes `app.command` checked: the
|
|
38
36
|
// name must be one of the declared ones, `args` is inferred from it, and the
|
|
39
37
|
// return value has to match. Same for `app.emit`.
|
|
40
|
-
export function setup(app:
|
|
38
|
+
export function setup(app: App): void {
|
|
41
39
|
app.command("add", (args) => {
|
|
42
40
|
const sum = args.a + args.b;
|
|
43
41
|
app.emit("added", sum);
|