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 CHANGED
@@ -138,24 +138,25 @@ framework templates' default.
138
138
 
139
139
  ```ts
140
140
  // src-host/main.ts
141
- import { defineCommands, defineEvents, emit, on, onAsync, type JanelaApp } from "janela/host";
141
+ import type { JanelaApp } from "janela/host";
142
142
 
143
- export const commands = defineCommands<{
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
- 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>;
152
152
 
153
- export function setup(app: JanelaApp): void {
154
- on(app, commands, "add", (args) => { // args inferred: { a: number; b: number }
155
- emit(app, events, "added", args.a + args.b);
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
- onAsync(app, commands, "wait", (args, resolve) => {
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** — `defineCommands` / `defineEvents` on the host,
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 const commands = defineCommands<{
406
+ export type AppCommands = {
337
407
  add: { args: { a: number; b: number }; result: number };
338
- }>();
339
- export type App = { commands: typeof commands; events: typeof events };
408
+ };
409
+ export type AppEvents = { added: number };
410
+ export type App = JanelaApp<AppCommands, AppEvents>;
340
411
 
341
- on(app, commands, "add", (args) => args.a + args.b); // args inferred, no cast
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
- /** Anything shaped like a host contract module's exported `App` type. */
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
- /** The command table declared by a contract. */
60
- export type CommandsOf<A> = A extends { commands: Commands<infer M> } ? M : never;
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 { events: Events<infer E> } ? E : never;
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
- `const app = createApp(WINDOW);\n` +
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.5.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/types.ts"
16
+ "types": "./runtime/janela.ts"
17
17
  },
18
18
  "./global": {
19
19
  "types": "./api/global.d.ts"