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 CHANGED
@@ -138,7 +138,7 @@ framework templates' default.
138
138
 
139
139
  ```ts
140
140
  // src-host/main.ts
141
- import { defineCommands, defineEvents, type JanelaApp } from "janela/host";
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
- export const commands = defineCommands<AppCommands>();
151
- export const events = defineEvents<AppEvents>();
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: JanelaApp<AppCommands, AppEvents>): void {
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 so the same one feeds `defineCommands`
337
- and the `setup` signature:
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 const commands = defineCommands<AppCommands>();
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** — `defineCommands` / `defineEvents` on the host,
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 const commands = defineCommands<{
406
+ export type AppCommands = {
377
407
  add: { args: { a: number; b: number }; result: number };
378
- }>();
379
- export type App = { commands: typeof commands; events: typeof events };
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
- /** 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "janela",
3
- "version": "0.6.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": {
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
- // SHAPE NOTE: the registrars below are standalone generic FUNCTIONS taking the
89
- // contract as a value, rather than methods on a returned registrar object.
90
- // That is not a style choice scriptc cannot dispatch a generic method
91
- // through an interface-typed receiver (SC1090), so `commands.on(...)` does not
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 const commands = defineCommands<{
121
- * add: { args: { a: number; b: number }; result: number };
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
- /** Declare the events a host emits: `defineEvents<{ added: number }>()`. */
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 contract below is the single declaration of what this app exposes. The
4
- // frontend imports `App` with `import type`, so the page is checked against
5
- // these exact types command names, argument shapes, results and event
6
- // payloads with no code generation and nothing to keep in sync.
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 { defineCommands, defineEvents, type JanelaApp } from "janela/host";
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 = { commands: typeof commands; events: typeof events };
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: JanelaApp<AppCommands, AppEvents>): void {
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 contract below is the single declaration of what this app exposes. The
4
- // frontend imports `App` with `import type`, so the page is checked against
5
- // these exact types command names, argument shapes, results and event
6
- // payloads with no code generation and nothing to keep in sync.
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 { defineCommands, defineEvents, type JanelaApp } from "janela/host";
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 = { commands: typeof commands; events: typeof events };
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: JanelaApp<AppCommands, AppEvents>): void {
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 contract below is the single declaration of what this app exposes. The
4
- // frontend imports `App` with `import type`, so the page is checked against
5
- // these exact types command names, argument shapes, results and event
6
- // payloads with no code generation and nothing to keep in sync.
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 { defineCommands, defineEvents, type JanelaApp } from "janela/host";
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 = { commands: typeof commands; events: typeof events };
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: JanelaApp<AppCommands, AppEvents>): void {
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 contract below is the single declaration of what this app exposes. The
4
- // frontend imports `App` with `import type`, so the page is checked against
5
- // these exact types command names, argument shapes, results and event
6
- // payloads with no code generation and nothing to keep in sync.
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 { defineCommands, defineEvents, type JanelaApp } from "janela/host";
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 = { commands: typeof commands; events: typeof events };
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: JanelaApp<AppCommands, AppEvents>): void {
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);