@tokamakdev/tok 0.1.1-beta.107 → 0.1.1-beta.112
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 +100 -10
- package/dist/astro.d.mts +5 -0
- package/dist/astro.mjs +160 -0
- package/dist/client.d.mts +6 -0
- package/dist/client.mjs +100 -0
- package/dist/dev-socket.d.mts +3 -6
- package/dist/dev-socket.mjs +167 -96
- package/dist/dev-worker.d.mts +21 -0
- package/dist/dev-worker.mjs +88 -0
- package/dist/events.d.mts +3 -3
- package/dist/events.mjs +3 -13
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -1
- package/dist/native-worker.d.mts +12 -0
- package/dist/native-worker.mjs +101 -0
- package/dist/native.d.mts +17 -4
- package/dist/native.mjs +99 -52
- package/dist/plugin-manifest.d.mts +31 -0
- package/dist/plugin-manifest.mjs +96 -0
- package/dist/rpc-contract.d.mts +9 -0
- package/dist/rpc-contract.mjs +168 -0
- package/dist/service-types.d.mts +9 -0
- package/dist/service-types.mjs +64 -0
- package/dist/vite.d.mts +6 -17
- package/dist/vite.mjs +30 -208
- package/dist/worker-contract.d.mts +25 -0
- package/dist/worker-contract.mjs +343 -0
- package/dist/worker-graph.d.mts +43 -0
- package/dist/worker-graph.mjs +332 -0
- package/dist/worker-types.d.mts +46 -0
- package/dist/worker-types.mjs +112 -0
- package/package.json +28 -17
package/README.md
CHANGED
|
@@ -11,29 +11,119 @@ npx tok targets
|
|
|
11
11
|
|
|
12
12
|
| Entry | Contents |
|
|
13
13
|
| --- | --- |
|
|
14
|
-
| `@tokamakdev/tok` |
|
|
14
|
+
| `@tokamakdev/tok` | `TokamakEntrypoint`, `TokamakContext`, `TokamakEvent`, and the Vite plugin's `Config` type |
|
|
15
15
|
| `@tokamakdev/tok/events` | The app's events, `onStart`, `onResume` and `onSuspend`, and `getLifecycleStage()` |
|
|
16
16
|
| `@tokamakdev/tok/vite` | The Vite plugin, `tokamak()` |
|
|
17
|
+
| `@tokamakdev/tok/astro` | The Astro adapter, `tokamak()` |
|
|
17
18
|
| `tok` | The CLI |
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
Create a plugin with `tok plugin my-plugin`. It starts as a TypeScript package
|
|
21
|
+
composing native context APIs. Add `--native apple` or `--native android` for a
|
|
22
|
+
small Swift or Kotlin implementation. Install it in an app as a workspace or
|
|
23
|
+
local npm dependency; Tokamak generates its clients and service bindings.
|
|
24
|
+
The generated README covers checking, local development, packing and publishing.
|
|
25
|
+
|
|
26
|
+
The Vite integration includes Cloudflare's plugin. Its options are the app's
|
|
27
|
+
tokamak configuration; the second argument accepts Cloudflare plugin options:
|
|
21
28
|
|
|
22
29
|
```ts
|
|
23
|
-
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
24
30
|
import { tokamak } from "@tokamakdev/tok/vite";
|
|
25
31
|
import { defineConfig } from "vite";
|
|
26
32
|
|
|
27
33
|
export default defineConfig({
|
|
28
|
-
plugins: [
|
|
34
|
+
plugins: [tokamak({ name: "My App", version: "1.0.0" })],
|
|
29
35
|
});
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
In Astro, use `tokamak()` from `@tokamakdev/tok/astro` as the `adapter`, passing
|
|
39
|
+
Cloudflare adapter options as the second argument.
|
|
40
|
+
|
|
41
|
+
Web builds use the ordinary Cloudflare integration. When `tok` runs the
|
|
42
|
+
command, `src/tokamak.ts` becomes a separate native worker. Its default export
|
|
43
|
+
extends `TokamakEntrypoint`; public methods are checked against Tokamak's
|
|
44
|
+
RPC contract. The main worker calls it through `env.TOKAMAK`; the native worker
|
|
45
|
+
calls the main worker through `env.MAIN`. Development delivers native events
|
|
46
|
+
through Cloudflare's dev registry and gives only the native worker the device
|
|
47
|
+
relay. Vite reloads its modules and listeners when their source changes.
|
|
48
|
+
Run `wrangler types` to generate the project's Workers declarations before
|
|
49
|
+
building or starting development.
|
|
50
|
+
|
|
51
|
+
Tokamak generates `tokamak-env.d.ts` in the project root, refreshing it during
|
|
52
|
+
development. Include it in the project's TypeScript inputs alongside
|
|
53
|
+
`worker-configuration.d.ts`, and ignore the generated file in version control.
|
|
54
|
+
It adds the optional
|
|
55
|
+
`Cloudflare.Env.TOKAMAK` binding and exports `TokamakService` and `TokamakClient`
|
|
56
|
+
types from `virtual:tokamak/client`. Service methods return ordinary promises,
|
|
57
|
+
preserve the entrypoint's overloads, and expose only its public RPC operations.
|
|
58
|
+
`Tokamak.Env` describes the native worker's required `MAIN` service plus its
|
|
59
|
+
shared variables, KV, D1, and R2 bindings. `this.env` in a `TokamakEntrypoint`
|
|
60
|
+
and the `env` argument of native listeners use this type.
|
|
61
|
+
|
|
62
|
+
Workspace implementation and API-reference edits reload through Vite.
|
|
63
|
+
Installing, removing, or renaming plugins requires restarting `tok dev` so the
|
|
64
|
+
native shell and worker bindings use the same installed capabilities.
|
|
65
|
+
|
|
66
|
+
Plugin packages declare their native API factory in `tokamak-plugin.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"schemaVersion": 2,
|
|
71
|
+
"id": "widgets",
|
|
72
|
+
"api": { "module": "./src/index.ts", "export": "widgets" }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The factory accepts a `TokamakContext` from `@tokamakdev/plugin` and returns
|
|
77
|
+
an object with typed public methods. Its module path is relative to the package.
|
|
78
|
+
Published JavaScript can supply adjacent TypeScript declarations. Existing
|
|
79
|
+
native platform sections and build settings remain in the same manifest.
|
|
80
|
+
|
|
81
|
+
Installing the package generates `env.TOKAMAK_WIDGETS` and its named native
|
|
82
|
+
entrypoint. Page code imports the generated default client from
|
|
83
|
+
`<package-name>/client`; native code calls `widgets(ctx)` directly. Both callers
|
|
84
|
+
use the same declared methods and RPC value contract. Workspace dependencies
|
|
85
|
+
work without publishing, and API source edits reload through Vite.
|
|
86
|
+
|
|
87
|
+
A plugin can add `"web": { "module": "./web.ts", "export": "web" }` to the
|
|
88
|
+
manifest. That export is an object implementing any subset of its generated
|
|
89
|
+
asynchronous client methods. The generator checks the signatures. Web clients
|
|
90
|
+
use those implementations and reject omitted methods with `NotSupportedError`;
|
|
91
|
+
native clients always call the native worker.
|
|
92
|
+
|
|
93
|
+
Page code imports the generated client as the module's default export:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// src/tokamak.ts
|
|
97
|
+
import { TokamakEntrypoint } from "@tokamakdev/tok";
|
|
98
|
+
|
|
99
|
+
export default class Native extends TokamakEntrypoint {
|
|
100
|
+
greeting(name: string): string {
|
|
101
|
+
return `Hello, ${name}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// Page code
|
|
108
|
+
import native from "virtual:tokamak/client";
|
|
109
|
+
|
|
110
|
+
const message = await native.greeting("Ada");
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Native clients call the Tokamak worker through the reserved `/_tokamak/app`
|
|
114
|
+
route. The native worker also receives other requests under `/_tokamak/` in
|
|
115
|
+
its `fetch` handler. Arguments and results support the RPC value and readable
|
|
116
|
+
byte-stream contract. Stream cancellation reaches its producer, and leaving
|
|
117
|
+
the page closes its calls. Web builds expose the same app-method names, rejecting calls with
|
|
118
|
+
`NotSupportedError`. The client bundle contains the callable interface and
|
|
119
|
+
transport.
|
|
120
|
+
|
|
121
|
+
The entrypoint receives its own bindings in `this.env` and native invocation
|
|
122
|
+
context in `this.ctx`. Its default `event()` runs registered listeners; an
|
|
123
|
+
override receives `{ type, data }` and can return a reply or promise. Native
|
|
124
|
+
events use this handler independently of page requests. `event()` is excluded
|
|
125
|
+
from the service binding's public methods. Listener callbacks receive the same
|
|
126
|
+
invocation context and register follow-up work with `ctx.waitUntil()`.
|
|
37
127
|
|
|
38
128
|
The package installs the `tok` binary for the current machine through an
|
|
39
129
|
optional dependency (`@tokamakdev/tok-darwin-arm64`, `-darwin-x64`,
|
package/dist/astro.d.mts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Options as CloudflareOptions } from "@astrojs/cloudflare";
|
|
2
|
+
import type { AstroIntegration } from "astro";
|
|
3
|
+
import { type TokamakOptions } from "./native-worker.mjs";
|
|
4
|
+
/** Astro's Cloudflare adapter, with Tokamak's native worker and development relay. */
|
|
5
|
+
export declare function tokamak(options?: TokamakOptions, cloudflareOptions?: CloudflareOptions): AstroIntegration;
|
package/dist/astro.mjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import cloudflare, {} from "@astrojs/cloudflare";
|
|
6
|
+
import { sessionDrivers } from "astro/config";
|
|
7
|
+
import { normalizePath } from "vite";
|
|
8
|
+
import { experimental_readRawConfig, unstable_readConfig } from "wrangler";
|
|
9
|
+
import { createNativeWorker } from "./native-worker.mjs";
|
|
10
|
+
import { createClientPlugin } from "./client.mjs";
|
|
11
|
+
/** Astro's Cloudflare adapter, with Tokamak's native worker and development relay. */
|
|
12
|
+
export function tokamak(options = {}, cloudflareOptions = {}) {
|
|
13
|
+
const output = process.env.TOKAMAK_VITE_OUTPUT;
|
|
14
|
+
const client = createClientPlugin(options.module, !!output, output ? () => native.graph().types : undefined);
|
|
15
|
+
if (!output) {
|
|
16
|
+
const adapter = cloudflare(cloudflareOptions);
|
|
17
|
+
let sessionBinding;
|
|
18
|
+
return {
|
|
19
|
+
...adapter,
|
|
20
|
+
hooks: {
|
|
21
|
+
...adapter.hooks,
|
|
22
|
+
async "astro:config:setup"(context) {
|
|
23
|
+
const types = {
|
|
24
|
+
name: "tokamak:astro-types",
|
|
25
|
+
config(vite) {
|
|
26
|
+
const root = path.resolve(vite.root ?? process.cwd());
|
|
27
|
+
const source = configuration(root, cloudflareOptions.configPath, false);
|
|
28
|
+
const raw = source ? experimental_readRawConfig({ config: source }, { useRedirectIfAvailable: false }).rawConfig : {};
|
|
29
|
+
const environment = process.env.CLOUDFLARE_ENV;
|
|
30
|
+
const selected = source ? unstable_readConfig({ config: source, env: environment }, { hideWarnings: true }) : {};
|
|
31
|
+
const main = (environment ? raw.env?.[environment]?.main : undefined)
|
|
32
|
+
?? raw.main ?? "@astrojs/cloudflare/entrypoints/server";
|
|
33
|
+
client.configure({
|
|
34
|
+
...selected,
|
|
35
|
+
// Type inspection needs the server source, not a client-optimized module.
|
|
36
|
+
main: main === "@astrojs/cloudflare/entrypoints/server"
|
|
37
|
+
? fileURLToPath(import.meta.resolve(main)) : main,
|
|
38
|
+
kv_namespaces: [...selected.kv_namespaces ?? [], ...sessionBinding ? [{ binding: sessionBinding }] : []],
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
context.updateConfig({ vite: { plugins: [client, types] } });
|
|
43
|
+
await adapter.hooks["astro:config:setup"]?.(context);
|
|
44
|
+
},
|
|
45
|
+
async "astro:config:done"(context) {
|
|
46
|
+
await adapter.hooks["astro:config:done"]?.(context);
|
|
47
|
+
const driver = context.config.session?.driver;
|
|
48
|
+
const entry = typeof driver === "string" ? driver : driver?.entrypoint.toString();
|
|
49
|
+
if (entry === "cloudflareKVBinding" || entry?.endsWith("cloudflare-kv-binding") ||
|
|
50
|
+
entry === sessionDrivers.cloudflareKVBinding().entrypoint) {
|
|
51
|
+
sessionBinding = cloudflareOptions.sessionKVBindingName ?? "SESSION";
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (cloudflareOptions.auxiliaryWorkers?.length) {
|
|
58
|
+
throw new Error("Tokamak hosts the main and native workers; additional auxiliary workers are not supported.");
|
|
59
|
+
}
|
|
60
|
+
const generated = path.join(path.dirname(cloudflareOptions.configPath ?? "."), `.tokamak.${randomUUID()}.json`);
|
|
61
|
+
let generatedPath;
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
if (generatedPath)
|
|
64
|
+
fs.rmSync(generatedPath, { force: true });
|
|
65
|
+
process.off("exit", cleanup);
|
|
66
|
+
};
|
|
67
|
+
const servers = new Set();
|
|
68
|
+
const lifecycle = {
|
|
69
|
+
name: "tokamak:astro-config",
|
|
70
|
+
configureServer({ httpServer }) {
|
|
71
|
+
if (!httpServer)
|
|
72
|
+
return;
|
|
73
|
+
servers.add(httpServer);
|
|
74
|
+
httpServer.once("close", () => {
|
|
75
|
+
servers.delete(httpServer);
|
|
76
|
+
if (!servers.size)
|
|
77
|
+
cleanup();
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
buildEnd(error) {
|
|
81
|
+
if (error && this.environment.mode === "build")
|
|
82
|
+
cleanup();
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const native = createNativeWorker(options, output, (root, graph) => {
|
|
86
|
+
const source = configuration(root, cloudflareOptions.configPath);
|
|
87
|
+
generatedPath = path.resolve(root, generated);
|
|
88
|
+
const rawConfig = experimental_readRawConfig({ config: source }, { useRedirectIfAvailable: false }).rawConfig;
|
|
89
|
+
const environment = process.env.CLOUDFLARE_ENV;
|
|
90
|
+
const selected = unstable_readConfig({ config: source, env: environment }, { hideWarnings: true });
|
|
91
|
+
selected.main = (environment ? rawConfig.env?.[environment]?.main : undefined)
|
|
92
|
+
?? rawConfig.main ?? "@astrojs/cloudflare/entrypoints/server";
|
|
93
|
+
const main = selected.main;
|
|
94
|
+
graph.configure(selected);
|
|
95
|
+
const environments = [[undefined, rawConfig], ...Object.entries(rawConfig.env ?? {})];
|
|
96
|
+
for (const [env, config] of environments) {
|
|
97
|
+
const resolved = unstable_readConfig({ config: source, env }, { hideWarnings: true });
|
|
98
|
+
if (!resolved.name)
|
|
99
|
+
throw new Error("Tokamak requires a name in the main Worker's configuration.");
|
|
100
|
+
const services = graph.services(resolved.name);
|
|
101
|
+
const reserved = config.services?.find(binding => services.some(service => service.binding === binding.binding));
|
|
102
|
+
if (reserved) {
|
|
103
|
+
throw new Error(`${reserved.binding} is reserved for a generated native worker binding.`);
|
|
104
|
+
}
|
|
105
|
+
config.services = [...config.services ?? [], ...services];
|
|
106
|
+
if (selected.main !== main)
|
|
107
|
+
config.main = selected.main;
|
|
108
|
+
}
|
|
109
|
+
// Keeping the generated configuration beside its source preserves relative paths and .dev.vars.
|
|
110
|
+
const content = `${JSON.stringify(rawConfig, null, 2)}\n`;
|
|
111
|
+
if (!fs.existsSync(generatedPath) || fs.readFileSync(generatedPath, "utf8") !== content) {
|
|
112
|
+
fs.writeFileSync(generatedPath, content);
|
|
113
|
+
}
|
|
114
|
+
process.off("exit", cleanup);
|
|
115
|
+
process.once("exit", cleanup);
|
|
116
|
+
});
|
|
117
|
+
const adapter = cloudflare({
|
|
118
|
+
...cloudflareOptions,
|
|
119
|
+
configPath: generated,
|
|
120
|
+
auxiliaryWorkers: [{
|
|
121
|
+
viteEnvironment: { name: "tokamak" },
|
|
122
|
+
config: (worker, context) => native.graph().native(worker, context),
|
|
123
|
+
}],
|
|
124
|
+
});
|
|
125
|
+
return {
|
|
126
|
+
...adapter,
|
|
127
|
+
name: "@tokamakdev/tok/astro",
|
|
128
|
+
hooks: {
|
|
129
|
+
...adapter.hooks,
|
|
130
|
+
async "astro:config:setup"(context) {
|
|
131
|
+
const source = configuration(fileURLToPath(context.config.root), cloudflareOptions.configPath);
|
|
132
|
+
context.addWatchFile(pathToFileURL(source));
|
|
133
|
+
context.updateConfig({
|
|
134
|
+
vite: {
|
|
135
|
+
server: { fs: { deny: [normalizePath(source)] } },
|
|
136
|
+
plugins: [native.plugin, client, lifecycle],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
await adapter.hooks["astro:config:setup"]?.(context);
|
|
140
|
+
},
|
|
141
|
+
async "astro:build:done"(context) {
|
|
142
|
+
try {
|
|
143
|
+
await adapter.hooks["astro:build:done"]?.(context);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
cleanup();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function configuration(root, configured, required = true) {
|
|
153
|
+
if (configured)
|
|
154
|
+
return path.resolve(root, configured);
|
|
155
|
+
const file = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"]
|
|
156
|
+
.map(name => path.join(root, name)).find(file => fs.existsSync(file));
|
|
157
|
+
if (!file && required)
|
|
158
|
+
throw new Error("Tokamak requires a Wrangler configuration in the Astro project root.");
|
|
159
|
+
return file;
|
|
160
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Plugin } from "vite";
|
|
2
|
+
import { WorkerTypes, type BindingConfiguration } from "./worker-types.mjs";
|
|
3
|
+
/** The app's public methods, without its native implementation. */
|
|
4
|
+
export declare function createClientPlugin(module: string | undefined, native: boolean, shared?: () => WorkerTypes): Plugin & {
|
|
5
|
+
configure(config: BindingConfiguration): void;
|
|
6
|
+
};
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { normalizePath } from "vite";
|
|
4
|
+
import { findModule } from "./worker-contract.mjs";
|
|
5
|
+
import { WorkerTypes } from "./worker-types.mjs";
|
|
6
|
+
const MODULE = "virtual:tokamak/client";
|
|
7
|
+
/** The Tokamak worker's page module of each plugin operation's support on the app's platform. */
|
|
8
|
+
const CAPABILITIES = "/_tokamak/capabilities.js";
|
|
9
|
+
const TRANSPORT = normalizePath(fileURLToPath(new URL("./rpc/client.mjs", import.meta.resolve("@tokamakdev/plugin/events"))));
|
|
10
|
+
/** The app's public methods, without its native implementation. */
|
|
11
|
+
export function createClientPlugin(module, native, shared) {
|
|
12
|
+
let root;
|
|
13
|
+
/** Whether `id` is a generated client: the app's, a named entrypoint's or a plugin's. */
|
|
14
|
+
const isClient = (id) => id === MODULE || id.startsWith(`${MODULE}/`) ||
|
|
15
|
+
contracts.definitions.some(plugin => plugin.client === id);
|
|
16
|
+
let contracts;
|
|
17
|
+
let files = [];
|
|
18
|
+
const clients = new Set();
|
|
19
|
+
return {
|
|
20
|
+
name: "tokamak:client",
|
|
21
|
+
enforce: "pre",
|
|
22
|
+
config(config) {
|
|
23
|
+
clients.clear();
|
|
24
|
+
files = [];
|
|
25
|
+
root = path.resolve(config.root ?? process.cwd());
|
|
26
|
+
contracts = shared?.() ?? new WorkerTypes(root, findModule(root, module));
|
|
27
|
+
},
|
|
28
|
+
configure(config) { contracts.configure(config); },
|
|
29
|
+
buildStart() {
|
|
30
|
+
for (const file of contracts.metadata)
|
|
31
|
+
this.addWatchFile(file);
|
|
32
|
+
},
|
|
33
|
+
resolveId(id) {
|
|
34
|
+
if (id === CAPABILITIES)
|
|
35
|
+
return { id, external: true };
|
|
36
|
+
if (isClient(id))
|
|
37
|
+
return `\0${id}`;
|
|
38
|
+
},
|
|
39
|
+
async load(id) {
|
|
40
|
+
const name = id.slice(1);
|
|
41
|
+
if (!id.startsWith("\0") || !isClient(name))
|
|
42
|
+
return;
|
|
43
|
+
await contracts.resolve(source => this.resolve(source, undefined, { skipSelf: true }));
|
|
44
|
+
const service = contracts.services().find(service => service.client === name);
|
|
45
|
+
if (!service)
|
|
46
|
+
throw new Error(`${name} names no Tokamak worker entrypoint.`);
|
|
47
|
+
clients.add(id);
|
|
48
|
+
files = [...contracts.native.files, ...contracts.plugins.flatMap(plugin => plugin.files)]
|
|
49
|
+
.filter(file => file !== normalizePath(path.join(root, "tokamak-env.d.ts")));
|
|
50
|
+
for (const file of files)
|
|
51
|
+
this.addWatchFile(file);
|
|
52
|
+
const methods = JSON.stringify(service.contract.methods);
|
|
53
|
+
const route = JSON.stringify(service.route);
|
|
54
|
+
const plugin = service.plugin;
|
|
55
|
+
if (native && plugin)
|
|
56
|
+
return `import { createClient } from ${JSON.stringify(TRANSPORT)};
|
|
57
|
+
import capabilities from ${JSON.stringify(CAPABILITIES)};
|
|
58
|
+
export default createClient(${methods}, ${route}, capabilities[${JSON.stringify(plugin.definition.id)}]);`;
|
|
59
|
+
// The app's own operations run wherever its Tokamak worker does.
|
|
60
|
+
if (native)
|
|
61
|
+
return `import { createClient } from ${JSON.stringify(TRANSPORT)};
|
|
62
|
+
export default createClient(${methods}, ${route}, Object.fromEntries(${methods}.map(method => [method, true])));`;
|
|
63
|
+
const browser = plugin?.definition.web;
|
|
64
|
+
const fallback = browser
|
|
65
|
+
? `import { ${JSON.stringify(browser.export)} as browser } from ${JSON.stringify(browser.module)};`
|
|
66
|
+
: "const browser = {};";
|
|
67
|
+
const webMethods = JSON.stringify(plugin?.webMethods ?? []);
|
|
68
|
+
return `${fallback}
|
|
69
|
+
async function unavailable() { throw new DOMException("This operation requires a native app", "NotSupportedError"); }
|
|
70
|
+
const supported = new Set(${webMethods});
|
|
71
|
+
const client = Object.fromEntries(${methods}.map(method => [method, supported.has(method) ? (...args) => Promise.resolve().then(() => browser[method](...args)) : unavailable]));
|
|
72
|
+
export default Object.assign(client, { capabilities: Object.freeze(Object.fromEntries(${methods}.map(method => [method, supported.has(method)]))) });`;
|
|
73
|
+
},
|
|
74
|
+
hotUpdate({ file, timestamp, server }) {
|
|
75
|
+
if (contracts.metadata.includes(file)) {
|
|
76
|
+
if (this.environment.name === "client") {
|
|
77
|
+
try {
|
|
78
|
+
contracts.discover();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
server.config.logger.error(String(error));
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
void server.restart().catch(error => server.config.logger.error(String(error)));
|
|
85
|
+
}
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
if (!files.includes(file))
|
|
89
|
+
return;
|
|
90
|
+
contracts.refresh({ file, timestamp });
|
|
91
|
+
for (const id of clients) {
|
|
92
|
+
const client = this.environment.moduleGraph.getModuleById(id);
|
|
93
|
+
if (!client)
|
|
94
|
+
continue;
|
|
95
|
+
this.environment.moduleGraph.invalidateModule(client);
|
|
96
|
+
this.environment.hot.send({ type: "full-reload" });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
package/dist/dev-socket.d.mts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
/** The header that carries the device's session token, which `tok dev`'s relay forwards. */
|
|
2
2
|
export declare const SESSION_HEADER = "x-tokamak-session";
|
|
3
|
-
/**
|
|
3
|
+
/** The native endpoint's opaque result and completion status. */
|
|
4
4
|
export interface PluginResult {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
name: string;
|
|
8
|
-
message: string;
|
|
9
|
-
};
|
|
5
|
+
payload: Uint8Array;
|
|
6
|
+
failed: boolean;
|
|
10
7
|
done: boolean;
|
|
11
8
|
}
|
|
12
9
|
/** The loopback server the device's dev WebSocket reaches through `tok dev`'s relay. */
|