janela 0.5.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 +87 -16
- package/api/index.d.ts +26 -6
- package/bin/janela.mjs +9 -1
- package/package.json +2 -2
- package/runtime/janela.ts +387 -266
- package/runtime/types.ts +15 -127
- package/templates/react/files/src-host/main.ts +23 -26
- package/templates/solid/files/src-host/main.ts +23 -26
- package/templates/svelte/files/src-host/main.ts +23 -26
- package/templates/vue/files/src-host/main.ts +23 -26
package/README.md
CHANGED
|
@@ -138,24 +138,25 @@ 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
|
-
export
|
|
143
|
+
export type AppCommands = {
|
|
144
144
|
add: { args: { a: number; b: number }; result: number };
|
|
145
145
|
greet: { args: { name: string }; result: string };
|
|
146
146
|
wait: { args: { ms: number }; result: string };
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const events = defineEvents<{ added: number }>();
|
|
147
|
+
};
|
|
148
|
+
export type AppEvents = { added: number };
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
/** The app, carrying its contract. This is what the page imports. */
|
|
151
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
// Typing the app with the contract is what makes the methods below checked.
|
|
154
|
+
export function setup(app: App): void {
|
|
155
|
+
app.command("add", (args) => { // args inferred: { a: number; b: number }
|
|
156
|
+
app.emit("added", args.a + args.b); // event name and payload checked
|
|
156
157
|
return args.a + args.b; // return type checked against the contract
|
|
157
158
|
});
|
|
158
|
-
|
|
159
|
+
app.commandAsync("wait", (args, resolve) => {
|
|
159
160
|
app.sleep(args.ms, () => resolve("waited " + args.ms + "ms"));
|
|
160
161
|
});
|
|
161
162
|
}
|
|
@@ -309,6 +310,73 @@ nested modal loop would otherwise re-enter the host loop underneath a live TS
|
|
|
309
310
|
frame; [docs/native-shell.md](../../docs/native-shell.md) has the details, the
|
|
310
311
|
per-platform table, and the Windows GUI-subsystem note.
|
|
311
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
|
+
|
|
344
|
+
## Migrating from 0.5.x
|
|
345
|
+
|
|
346
|
+
The contract now rides on the app itself, so the standalone registrars are no
|
|
347
|
+
longer needed. Type the app with your contract and call its methods:
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
// before (0.5.x)
|
|
351
|
+
export function setup(app: JanelaApp): void {
|
|
352
|
+
on(app, commands, "add", (args) => args.a + args.b);
|
|
353
|
+
onAsync(app, commands, "wait", (args, resolve) => { … });
|
|
354
|
+
emit(app, events, "added", 42);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// after (0.6.x)
|
|
358
|
+
export function setup(app: JanelaApp<AppCommands, AppEvents>): void {
|
|
359
|
+
app.command("add", (args) => args.a + args.b);
|
|
360
|
+
app.commandAsync("wait", (args, resolve) => { … });
|
|
361
|
+
app.emit("added", 42);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
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):
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
export type AppCommands = { add: { args: { a: number; b: number }; result: number } };
|
|
370
|
+
export type AppEvents = { added: number };
|
|
371
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`on`, `onAsync` and `emit` still work — they are `@deprecated` one-line
|
|
375
|
+
wrappers now — so 0.5.x code keeps compiling. The page side is unchanged:
|
|
376
|
+
`createClient<App>()` and `client.invoke(...)` are exactly as before. An app
|
|
377
|
+
with no contract needs no change at all: `setup(app: JanelaApp)` still gets an
|
|
378
|
+
untyped `app.command(name, handler)`.
|
|
379
|
+
|
|
312
380
|
## Migrating from 0.4.x
|
|
313
381
|
|
|
314
382
|
Nothing breaks: `app.command`, `app.emit`, and the untyped `invoke` / `listen`
|
|
@@ -318,9 +386,11 @@ Two things are new:
|
|
|
318
386
|
|
|
319
387
|
- `listen()` (and the injected `janela.listen`) now **return a disposer**.
|
|
320
388
|
Previously they returned nothing, so existing code is unaffected.
|
|
321
|
-
- The **typed contract** —
|
|
389
|
+
- The **typed contract** — a contract-typed app on the host,
|
|
322
390
|
`createClient<App>()` on the page. The framework templates now scaffold with
|
|
323
|
-
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.)
|
|
324
394
|
|
|
325
395
|
To adopt it in an existing app, declare what the host already exposes and swap
|
|
326
396
|
the registrations:
|
|
@@ -333,12 +403,13 @@ app.command("add", (args) => {
|
|
|
333
403
|
});
|
|
334
404
|
|
|
335
405
|
// after
|
|
336
|
-
export
|
|
406
|
+
export type AppCommands = {
|
|
337
407
|
add: { args: { a: number; b: number }; result: number };
|
|
338
|
-
}
|
|
339
|
-
export type
|
|
408
|
+
};
|
|
409
|
+
export type AppEvents = { added: number };
|
|
410
|
+
export type App = JanelaApp<AppCommands, AppEvents>;
|
|
340
411
|
|
|
341
|
-
|
|
412
|
+
app.command("add", (args) => args.a + args.b); // args inferred, no cast
|
|
342
413
|
```
|
|
343
414
|
|
|
344
415
|
then on the page, replace `invoke<number>("add", …)` with
|
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/bin/janela.mjs
CHANGED
|
@@ -516,10 +516,18 @@ function build(root, { devUrl = null, gui = true } = {}) {
|
|
|
516
516
|
join(buildDir, "entry.ts"),
|
|
517
517
|
`// Generated by janela — do not edit.\n` +
|
|
518
518
|
`import { createApp } from "./janela";\n` +
|
|
519
|
+
`import type { CommandShapes, JanelaApp } from "./janela";\n` +
|
|
519
520
|
`import { WINDOW } from "./config";\n` +
|
|
520
521
|
`import { INDEX_HTML } from "./frontend";\n` +
|
|
521
522
|
`import { setup } from "./main";\n\n` +
|
|
522
|
-
|
|
523
|
+
`// The app's type parameters are read back off setup()'s own signature,\n` +
|
|
524
|
+
`// so a contract-typed setup(app: JanelaApp<App>) and a plain\n` +
|
|
525
|
+
`// setup(app: JanelaApp) each get an app instantiated to match. scriptc\n` +
|
|
526
|
+
`// monomorphises generic classes, so the right instantiation must be\n` +
|
|
527
|
+
`// CONSTRUCTED here - no cast can bridge JanelaApp<A> and JanelaApp<B>.\n` +
|
|
528
|
+
`type CmdsOf<F> = F extends (app: JanelaApp<infer C, infer _E>) => void ? C : CommandShapes;\n` +
|
|
529
|
+
`type EvtsOf<F> = F extends (app: JanelaApp<infer _C, infer E>) => void ? E : Record<string, unknown>;\n\n` +
|
|
530
|
+
`const app = createApp<CmdsOf<typeof setup>, EvtsOf<typeof setup>>(WINDOW);\n` +
|
|
523
531
|
`setup(app);\n` +
|
|
524
532
|
`const rc = app.run(INDEX_HTML) + 0;\n` +
|
|
525
533
|
`console.log("[janela] run returned", rc);\n`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "janela",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Desktop apps in pure TypeScript, compiled to native. No Rust, no Node, no Electron.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"default": "./api/index.js"
|
|
14
14
|
},
|
|
15
15
|
"./host": {
|
|
16
|
-
"types": "./runtime/
|
|
16
|
+
"types": "./runtime/janela.ts"
|
|
17
17
|
},
|
|
18
18
|
"./global": {
|
|
19
19
|
"types": "./api/global.d.ts"
|