@tokamakdev/tok 0.1.1-beta.112 → 0.1.1-beta.116
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/dist/astro.mjs +23 -33
- package/dist/client.mjs +3 -5
- package/dist/dev-socket.mjs +6 -5
- package/dist/dev-worker.d.mts +1 -0
- package/dist/dev-worker.mjs +5 -0
- package/dist/native-worker.d.mts +4 -0
- package/dist/native-worker.mjs +7 -2
- package/dist/native.d.mts +10 -4
- package/dist/native.mjs +3 -3
- package/dist/plugin-manifest.d.mts +2 -2
- package/dist/rpc-contract.d.mts +2 -0
- package/dist/rpc-contract.mjs +5 -1
- package/dist/vite.mjs +6 -9
- package/dist/worker-contract.d.mts +2 -0
- package/dist/worker-contract.mjs +18 -11
- package/dist/worker-graph.d.mts +11 -8
- package/dist/worker-graph.mjs +30 -26
- package/dist/worker-types.mjs +2 -2
- package/package.json +14 -14
package/dist/astro.mjs
CHANGED
|
@@ -8,6 +8,9 @@ import { normalizePath } from "vite";
|
|
|
8
8
|
import { experimental_readRawConfig, unstable_readConfig } from "wrangler";
|
|
9
9
|
import { createNativeWorker } from "./native-worker.mjs";
|
|
10
10
|
import { createClientPlugin } from "./client.mjs";
|
|
11
|
+
import { writeChanged } from "./worker-graph.mjs";
|
|
12
|
+
/** The Worker entry Astro's Cloudflare adapter builds when the configuration names none. */
|
|
13
|
+
const ASTRO_SERVER = "@astrojs/cloudflare/entrypoints/server";
|
|
11
14
|
/** Astro's Cloudflare adapter, with Tokamak's native worker and development relay. */
|
|
12
15
|
export function tokamak(options = {}, cloudflareOptions = {}) {
|
|
13
16
|
const output = process.env.TOKAMAK_VITE_OUTPUT;
|
|
@@ -25,16 +28,11 @@ export function tokamak(options = {}, cloudflareOptions = {}) {
|
|
|
25
28
|
config(vite) {
|
|
26
29
|
const root = path.resolve(vite.root ?? process.cwd());
|
|
27
30
|
const source = configuration(root, cloudflareOptions.configPath, false);
|
|
28
|
-
const
|
|
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";
|
|
31
|
+
const selected = source ? readWorker(source).selected : { main: ASTRO_SERVER };
|
|
33
32
|
client.configure({
|
|
34
33
|
...selected,
|
|
35
34
|
// Type inspection needs the server source, not a client-optimized module.
|
|
36
|
-
main: main ===
|
|
37
|
-
? fileURLToPath(import.meta.resolve(main)) : main,
|
|
35
|
+
main: selected.main === ASTRO_SERVER ? fileURLToPath(import.meta.resolve(ASTRO_SERVER)) : selected.main,
|
|
38
36
|
kv_namespaces: [...selected.kv_namespaces ?? [], ...sessionBinding ? [{ binding: sessionBinding }] : []],
|
|
39
37
|
});
|
|
40
38
|
},
|
|
@@ -85,42 +83,26 @@ export function tokamak(options = {}, cloudflareOptions = {}) {
|
|
|
85
83
|
const native = createNativeWorker(options, output, (root, graph) => {
|
|
86
84
|
const source = configuration(root, cloudflareOptions.configPath);
|
|
87
85
|
generatedPath = path.resolve(root, generated);
|
|
88
|
-
const
|
|
89
|
-
const
|
|
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;
|
|
86
|
+
const { raw, selected } = readWorker(source);
|
|
87
|
+
const appMain = selected.main;
|
|
94
88
|
graph.configure(selected);
|
|
95
|
-
|
|
96
|
-
|
|
89
|
+
// In development the graph serves main through its own module, which every environment uses.
|
|
90
|
+
const servedMain = selected.main === appMain ? undefined : selected.main;
|
|
91
|
+
for (const [env, config] of [[undefined, raw], ...Object.entries(raw.env ?? {})]) {
|
|
97
92
|
const resolved = unstable_readConfig({ config: source, env }, { hideWarnings: true });
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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;
|
|
93
|
+
config.services = [...config.services ?? [], ...graph.serviceBindings(resolved)];
|
|
94
|
+
if (servedMain)
|
|
95
|
+
config.main = servedMain;
|
|
108
96
|
}
|
|
109
97
|
// Keeping the generated configuration beside its source preserves relative paths and .dev.vars.
|
|
110
|
-
|
|
111
|
-
if (!fs.existsSync(generatedPath) || fs.readFileSync(generatedPath, "utf8") !== content) {
|
|
112
|
-
fs.writeFileSync(generatedPath, content);
|
|
113
|
-
}
|
|
98
|
+
writeChanged(generatedPath, `${JSON.stringify(raw, null, 2)}\n`);
|
|
114
99
|
process.off("exit", cleanup);
|
|
115
100
|
process.once("exit", cleanup);
|
|
116
101
|
});
|
|
117
102
|
const adapter = cloudflare({
|
|
118
103
|
...cloudflareOptions,
|
|
119
104
|
configPath: generated,
|
|
120
|
-
auxiliaryWorkers: [
|
|
121
|
-
viteEnvironment: { name: "tokamak" },
|
|
122
|
-
config: (worker, context) => native.graph().native(worker, context),
|
|
123
|
-
}],
|
|
105
|
+
auxiliaryWorkers: [native.auxiliaryWorker],
|
|
124
106
|
});
|
|
125
107
|
return {
|
|
126
108
|
...adapter,
|
|
@@ -149,6 +131,14 @@ export function tokamak(options = {}, cloudflareOptions = {}) {
|
|
|
149
131
|
},
|
|
150
132
|
};
|
|
151
133
|
}
|
|
134
|
+
/** The Wrangler configuration at `source`: as written, and resolved for `CLOUDFLARE_ENV` with Astro's server as its default main. */
|
|
135
|
+
function readWorker(source) {
|
|
136
|
+
const raw = experimental_readRawConfig({ config: source }, { useRedirectIfAvailable: false }).rawConfig;
|
|
137
|
+
const env = process.env.CLOUDFLARE_ENV;
|
|
138
|
+
const selected = unstable_readConfig({ config: source, env }, { hideWarnings: true });
|
|
139
|
+
selected.main = (env ? raw.env?.[env]?.main : undefined) ?? raw.main ?? ASTRO_SERVER;
|
|
140
|
+
return { raw, selected };
|
|
141
|
+
}
|
|
152
142
|
function configuration(root, configured, required = true) {
|
|
153
143
|
if (configured)
|
|
154
144
|
return path.resolve(root, configured);
|
package/dist/client.mjs
CHANGED
|
@@ -88,13 +88,11 @@ export default Object.assign(client, { capabilities: Object.freeze(Object.fromEn
|
|
|
88
88
|
if (!files.includes(file))
|
|
89
89
|
return;
|
|
90
90
|
contracts.refresh({ file, timestamp });
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!client)
|
|
94
|
-
continue;
|
|
91
|
+
const loaded = [...clients].map(id => this.environment.moduleGraph.getModuleById(id)).filter(client => client !== undefined);
|
|
92
|
+
for (const client of loaded)
|
|
95
93
|
this.environment.moduleGraph.invalidateModule(client);
|
|
94
|
+
if (loaded.length)
|
|
96
95
|
this.environment.hot.send({ type: "full-reload" });
|
|
97
|
-
}
|
|
98
96
|
},
|
|
99
97
|
};
|
|
100
98
|
}
|
package/dist/dev-socket.mjs
CHANGED
|
@@ -17,8 +17,9 @@ const CONNECT_TIMEOUT = 10_000;
|
|
|
17
17
|
export async function openDevSocket(workerName, token) {
|
|
18
18
|
const worker = new DevWorker(workerName);
|
|
19
19
|
const device = new Device();
|
|
20
|
+
const authorized = (request) => token !== undefined && request.headers[SESSION_HEADER] === token;
|
|
20
21
|
const server = http.createServer((request, response) => {
|
|
21
|
-
if (!
|
|
22
|
+
if (!authorized(request)) {
|
|
22
23
|
response.writeHead(401).end();
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
@@ -34,7 +35,7 @@ export async function openDevSocket(workerName, token) {
|
|
|
34
35
|
});
|
|
35
36
|
const sockets = new WebSocketServer({ noServer: true });
|
|
36
37
|
server.on("upgrade", (request, socket, head) => {
|
|
37
|
-
if (!
|
|
38
|
+
if (!authorized(request)) {
|
|
38
39
|
socket.end("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
@@ -127,7 +128,7 @@ function serve(websocket, worker, device) {
|
|
|
127
128
|
}
|
|
128
129
|
const { header, payload } = message;
|
|
129
130
|
if (header.type === "result" && header.id !== undefined) {
|
|
130
|
-
device.receive(header.id,
|
|
131
|
+
device.receive(header.id, pluginResult(message));
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
if (header.type === "pull" && header.id !== undefined) {
|
|
@@ -177,14 +178,14 @@ async function forward(device, request, response) {
|
|
|
177
178
|
response.end(frame);
|
|
178
179
|
}, async () => {
|
|
179
180
|
const next = await frames.next().catch(() => ({ done: true, value: undefined }));
|
|
180
|
-
return next.done ? failure("DataCloneError", "The call's body ended early") :
|
|
181
|
+
return next.done ? failure("DataCloneError", "The call's body ended early") : pluginResult(next.value);
|
|
181
182
|
});
|
|
182
183
|
response.once("close", () => {
|
|
183
184
|
if (!response.writableEnded)
|
|
184
185
|
cancel();
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
|
-
function
|
|
188
|
+
function pluginResult({ header, payload }) {
|
|
188
189
|
return { payload, failed: header.failed === true, done: header.done === true };
|
|
189
190
|
}
|
|
190
191
|
/** The most a subscription's response holds while the Worker is not reading it. */
|
package/dist/dev-worker.d.mts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class DevWorker {
|
|
|
16
16
|
dispatch(name: string, event: Uint8Array): Promise<Uint8Array>;
|
|
17
17
|
fetch(request: IncomingMessage, signal: AbortSignal): Promise<WorkerResponse>;
|
|
18
18
|
couple(socket: WebSocket, pair: WorkerSocket): Promise<void>;
|
|
19
|
+
/** Disposes the runtime; later deliveries reject rather than start another. */
|
|
19
20
|
dispose(): Promise<void>;
|
|
20
21
|
}
|
|
21
22
|
export {};
|
package/dist/dev-worker.mjs
CHANGED
|
@@ -17,6 +17,7 @@ const CALLER = `export default {
|
|
|
17
17
|
export class DevWorker {
|
|
18
18
|
name;
|
|
19
19
|
#session;
|
|
20
|
+
#disposed = false;
|
|
20
21
|
constructor(name) {
|
|
21
22
|
this.name = name;
|
|
22
23
|
}
|
|
@@ -52,10 +53,14 @@ export class DevWorker {
|
|
|
52
53
|
const { module } = await this.#connect();
|
|
53
54
|
await module.coupleWebSocket(socket, pair);
|
|
54
55
|
}
|
|
56
|
+
/** Disposes the runtime; later deliveries reject rather than start another. */
|
|
55
57
|
async dispose() {
|
|
58
|
+
this.#disposed = true;
|
|
56
59
|
await (await this.#session?.catch(() => undefined))?.runtime.dispose();
|
|
57
60
|
}
|
|
58
61
|
#connect() {
|
|
62
|
+
if (this.#disposed)
|
|
63
|
+
return Promise.reject(new Error("the development server has closed"));
|
|
59
64
|
return this.#session ??= this.#start().catch((error) => {
|
|
60
65
|
this.#session = undefined;
|
|
61
66
|
throw error;
|
package/dist/native-worker.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { PluginConfig } from "@cloudflare/vite-plugin";
|
|
1
2
|
import { type Plugin } from "vite";
|
|
2
3
|
import type { Config } from "./index.mjs";
|
|
3
4
|
import { type WorkerGraph } from "./worker-graph.mjs";
|
|
@@ -5,8 +6,11 @@ export type TokamakOptions = Config & {
|
|
|
5
6
|
/** Native worker entrypoint, relative to the Vite root; defaults to src/tokamak.ts or .js. */
|
|
6
7
|
module?: string;
|
|
7
8
|
};
|
|
9
|
+
type AuxiliaryWorker = NonNullable<PluginConfig["auxiliaryWorkers"]>[number];
|
|
8
10
|
/** The native worker and its development relay. */
|
|
9
11
|
export declare function createNativeWorker(options: TokamakOptions, output: string, prepare?: (root: string, graph: WorkerGraph) => void | Promise<void>): {
|
|
10
12
|
plugin: Plugin;
|
|
11
13
|
graph(): WorkerGraph;
|
|
14
|
+
auxiliaryWorker: AuxiliaryWorker;
|
|
12
15
|
};
|
|
16
|
+
export {};
|
package/dist/native-worker.mjs
CHANGED
|
@@ -47,7 +47,7 @@ export function createNativeWorker(options, output, prepare) {
|
|
|
47
47
|
async handler(server) {
|
|
48
48
|
const devSocket = await serveNativeWorker(server, graph.names(), output, token);
|
|
49
49
|
if (devSocket && token)
|
|
50
|
-
graph.relay(devSocket.port, token, platform);
|
|
50
|
+
graph.relay({ port: devSocket.port, token, platform });
|
|
51
51
|
// A page loaded from this server, not the device, still gets the device's capabilities.
|
|
52
52
|
server.middlewares.use(CAPABILITIES, (_request, response) => {
|
|
53
53
|
const capabilities = capabilitiesFor(graph.availability(), platform);
|
|
@@ -57,7 +57,12 @@ export function createNativeWorker(options, output, prepare) {
|
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
59
|
};
|
|
60
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
plugin,
|
|
62
|
+
graph: () => graph,
|
|
63
|
+
// The graph exists once Vite's config is known, after Cloudflare's plugin is created.
|
|
64
|
+
auxiliaryWorker: { viteEnvironment: { name: "tokamak" }, config: (worker, context) => graph.native(worker, context) },
|
|
65
|
+
};
|
|
61
66
|
}
|
|
62
67
|
async function serveNativeWorker(server, names, output, token) {
|
|
63
68
|
const httpServer = server.httpServer;
|
package/dist/native.d.mts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { NativePlatform } from "@tokamakdev/plugin/capabilities";
|
|
2
|
+
/** The device relay a development Worker reaches native code through. */
|
|
3
|
+
export interface DeviceRelay {
|
|
4
|
+
port: number;
|
|
5
|
+
token: string;
|
|
6
|
+
platform?: NativePlatform;
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
|
-
* The development invocation `ctx`'s native connection, which reaches the byte endpoints of
|
|
4
|
-
*
|
|
9
|
+
* The development invocation `ctx`'s native connection, which reaches the byte endpoints of the
|
|
10
|
+
* `relay`'s device. Its subscriptions are `ctx`'s `waitUntil` work.
|
|
5
11
|
*/
|
|
6
|
-
export declare function connectDevice(port
|
|
12
|
+
export declare function connectDevice({ port, token, platform }: DeviceRelay, ctx: {
|
|
7
13
|
waitUntil(promise: Promise<unknown>): void;
|
|
8
|
-
}
|
|
14
|
+
}): {
|
|
9
15
|
platform: NativePlatform | undefined;
|
|
10
16
|
call(plugin: string, method: string, payload: Uint8Array, { body, signal }?: {
|
|
11
17
|
body?: ReadableStream<Uint8Array>;
|
package/dist/native.mjs
CHANGED
|
@@ -2,10 +2,10 @@ import { decodeMessage, encodeMessage, messages, nativeError } from "@tokamakdev
|
|
|
2
2
|
const encoder = new TextEncoder();
|
|
3
3
|
const decoder = new TextDecoder();
|
|
4
4
|
/**
|
|
5
|
-
* The development invocation `ctx`'s native connection, which reaches the byte endpoints of
|
|
6
|
-
*
|
|
5
|
+
* The development invocation `ctx`'s native connection, which reaches the byte endpoints of the
|
|
6
|
+
* `relay`'s device. Its subscriptions are `ctx`'s `waitUntil` work.
|
|
7
7
|
*/
|
|
8
|
-
export function connectDevice(port, token,
|
|
8
|
+
export function connectDevice({ port, token, platform }, ctx) {
|
|
9
9
|
const post = async (path, request, signal) => {
|
|
10
10
|
const response = await fetch(`http://127.0.0.1:${String(port)}${path}`, {
|
|
11
11
|
method: "POST",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { NativePlatform } from "@tokamakdev/plugin/capabilities";
|
|
2
|
+
export type { NativePlatform };
|
|
1
3
|
export interface ModuleExport {
|
|
2
4
|
module: string;
|
|
3
5
|
export: string;
|
|
4
6
|
}
|
|
5
|
-
/** A native platform a plugin builds for. */
|
|
6
|
-
export type NativePlatform = "ios" | "macos" | "android" | "windows" | "linux";
|
|
7
7
|
/** Where an operation runs, and what it needs there. */
|
|
8
8
|
export interface Operation {
|
|
9
9
|
/** The native platforms that implement it, with any minimum OS version. */
|
package/dist/rpc-contract.d.mts
CHANGED
|
@@ -7,3 +7,5 @@ export declare class WorkerOnlyMethod extends Error {
|
|
|
7
7
|
* `workerOnly` type throws `WorkerOnlyMethod`.
|
|
8
8
|
*/
|
|
9
9
|
export declare function checkRpcMethod(checker: ts.TypeChecker, method: ts.Type, name: string, workerOnly?: ts.Symbol): void;
|
|
10
|
+
/** The symbol that `symbol` names: itself, or the declaration an import or re-export refers to. */
|
|
11
|
+
export declare function aliased(checker: ts.TypeChecker, symbol: ts.Symbol): ts.Symbol;
|
package/dist/rpc-contract.mjs
CHANGED
|
@@ -56,7 +56,11 @@ function builtin(checker, type) {
|
|
|
56
56
|
function nodeReadableStream(checker, symbol) {
|
|
57
57
|
return checker.getAmbientModules().filter((module) => ["\"stream/web\"", "\"node:stream/web\""].includes(module.name))
|
|
58
58
|
.some((module) => checker.getExportsOfModule(module).some((member) => member.name === "ReadableStream" &&
|
|
59
|
-
(
|
|
59
|
+
aliased(checker, member) === symbol));
|
|
60
|
+
}
|
|
61
|
+
/** The symbol that `symbol` names: itself, or the declaration an import or re-export refers to. */
|
|
62
|
+
export function aliased(checker, symbol) {
|
|
63
|
+
return symbol.flags & ts.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
60
64
|
}
|
|
61
65
|
function argumentsOf(checker, type) {
|
|
62
66
|
return type.flags & ts.TypeFlags.Object && type.objectFlags & ts.ObjectFlags.Reference
|
package/dist/vite.mjs
CHANGED
|
@@ -19,17 +19,14 @@ export function tokamak(options = {}, cloudflareOptions = {}) {
|
|
|
19
19
|
...worker, ...custom,
|
|
20
20
|
services: [...custom?.services ?? [], ...worker.services ?? []],
|
|
21
21
|
};
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
if (!native) {
|
|
23
|
+
client.configure(configured);
|
|
24
24
|
return custom;
|
|
25
|
-
|
|
26
|
-
graph.configure(configured);
|
|
25
|
+
}
|
|
26
|
+
const generated = native.graph().configure(configured);
|
|
27
27
|
// Cloudflare merges returned service entries with those already in the config.
|
|
28
|
-
return { ...custom, main: configured.main, services: [...custom?.services ?? [], ...
|
|
28
|
+
return { ...custom, main: configured.main, services: [...custom?.services ?? [], ...generated] };
|
|
29
29
|
},
|
|
30
|
-
auxiliaryWorkers: native ? [
|
|
31
|
-
viteEnvironment: { name: "tokamak" },
|
|
32
|
-
config: (worker, context) => native.graph().native(worker, context),
|
|
33
|
-
}] : cloudflareOptions.auxiliaryWorkers,
|
|
30
|
+
auxiliaryWorkers: native ? [native.auxiliaryWorker] : cloudflareOptions.auxiliaryWorkers,
|
|
34
31
|
})];
|
|
35
32
|
}
|
|
@@ -11,6 +11,8 @@ export interface PluginContract extends WorkerContract {
|
|
|
11
11
|
/** Every operation of the native SDK, including those only the Tokamak worker can call. */
|
|
12
12
|
operations: string[];
|
|
13
13
|
}
|
|
14
|
+
/** The contract of a Tokamak worker without a module. */
|
|
15
|
+
export declare function emptyContract(): WorkerContract;
|
|
14
16
|
export declare function findModule(root: string, name: string | undefined): string | undefined;
|
|
15
17
|
/** Resolve the application's types before generating its service adapters. */
|
|
16
18
|
export declare function readWorkerContract(entry: string, root: string): WorkerContract;
|
package/dist/worker-contract.mjs
CHANGED
|
@@ -2,8 +2,14 @@ import path from "node:path";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import ts from "typescript";
|
|
5
|
-
import { checkRpcMethod, WorkerOnlyMethod } from "./rpc-contract.mjs";
|
|
5
|
+
import { aliased, checkRpcMethod, WorkerOnlyMethod } from "./rpc-contract.mjs";
|
|
6
6
|
import { serviceTypes } from "./service-types.mjs";
|
|
7
|
+
/** Worker handler names, which are not RPC methods. */
|
|
8
|
+
const HANDLERS = ["fetch", "event", "queue", "scheduled", "email", "tail", "trace", "tailStream", "test"];
|
|
9
|
+
/** The contract of a Tokamak worker without a module. */
|
|
10
|
+
export function emptyContract() {
|
|
11
|
+
return { methods: [], files: [], declarations: serviceTypes("{}", []) };
|
|
12
|
+
}
|
|
7
13
|
export function findModule(root, name) {
|
|
8
14
|
const candidates = (name ? [name] : ["src/tokamak.ts", "src/tokamak.js"])
|
|
9
15
|
.map(candidate => path.resolve(root, candidate).split(path.sep).join("/"));
|
|
@@ -27,8 +33,7 @@ export function readWorkerContracts(entry, main, root, validate, plugins = []) {
|
|
|
27
33
|
const project = entries.length ? workerProgram(entries, root) : undefined;
|
|
28
34
|
const native = entry && project ? workerModule(entry, project) : undefined;
|
|
29
35
|
return {
|
|
30
|
-
native: native ? classContract(native, "default", true, validate)
|
|
31
|
-
: { methods: [], files: [], declarations: serviceTypes("{}", []) },
|
|
36
|
+
native: native ? classContract(native, "default", true, validate) : emptyContract(),
|
|
32
37
|
named: native ? namedContracts(native, validate) : {},
|
|
33
38
|
main: main && project ? serviceContracts(workerModule(main, project), validate) : {},
|
|
34
39
|
plugins: project ? plugins.map(plugin => pluginContract(workerModule(plugin.api.module, project), plugin, validate)) : [],
|
|
@@ -95,8 +100,7 @@ function workerModule(entry, project) {
|
|
|
95
100
|
throw new Error(`Cannot read Tokamak entrypoint ${entry}.`);
|
|
96
101
|
const module = checker.getSymbolAtLocation(source);
|
|
97
102
|
const exports = new Map((module ? checker.getExportsOfModule(module) : []).map(exported => {
|
|
98
|
-
|
|
99
|
-
return [exported.name, checker.getTypeOfSymbolAtLocation(symbol, source)];
|
|
103
|
+
return [exported.name, checker.getTypeOfSymbolAtLocation(aliased(checker, exported), source)];
|
|
100
104
|
}));
|
|
101
105
|
return { ...project, entry, source, exports };
|
|
102
106
|
}
|
|
@@ -126,7 +130,7 @@ function pluginContract(module, definition, validate) {
|
|
|
126
130
|
if (property.name === "capabilities")
|
|
127
131
|
continue;
|
|
128
132
|
assertMethodName(property.name);
|
|
129
|
-
if (
|
|
133
|
+
if (HANDLERS.includes(property.name)) {
|
|
130
134
|
throw new Error(`Plugin method ${property.name} is reserved for a Worker handler.`);
|
|
131
135
|
}
|
|
132
136
|
const method = checker.getTypeOfSymbolAtLocation(property, source);
|
|
@@ -146,7 +150,7 @@ function pluginContract(module, definition, validate) {
|
|
|
146
150
|
}
|
|
147
151
|
methods.push(methodDeclaration(checker, property, method));
|
|
148
152
|
}
|
|
149
|
-
methods.sort(
|
|
153
|
+
methods.sort(byName);
|
|
150
154
|
const unknown = Object.keys(definition.operations).find(name => !operations.includes(name));
|
|
151
155
|
if (unknown)
|
|
152
156
|
throw new Error(`${definition.manifest}: operations.${unknown} names no method of ${definition.api.export}.`);
|
|
@@ -244,13 +248,16 @@ function classContract(module, name, native, validate) {
|
|
|
244
248
|
checkRpcMethod(checker, type, property.name);
|
|
245
249
|
methods.push(methodDeclaration(checker, property, type));
|
|
246
250
|
}
|
|
247
|
-
methods.sort(
|
|
251
|
+
methods.sort(byName);
|
|
248
252
|
return {
|
|
249
253
|
methods: methods.map((method) => method.name),
|
|
250
254
|
declarations: serviceTypes(`InstanceType<typeof import(${JSON.stringify(entry.split(path.sep).join("/"))}, { with: { "resolution-mode": "import" } })[${JSON.stringify(name)}]>`, methods, native ? "Tokamak" : "Main"),
|
|
251
255
|
files: module.files,
|
|
252
256
|
};
|
|
253
257
|
}
|
|
258
|
+
function byName(left, right) {
|
|
259
|
+
return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
260
|
+
}
|
|
254
261
|
function methodDeclaration(checker, property, type) {
|
|
255
262
|
return {
|
|
256
263
|
name: property.name,
|
|
@@ -264,7 +271,7 @@ function methodDeclaration(checker, property, type) {
|
|
|
264
271
|
function applicationPrototypes(program, checker, instance, entry, allowNative = false) {
|
|
265
272
|
const module = checker.getAmbientModules().find((symbol) => symbol.name === '"cloudflare:workers"');
|
|
266
273
|
const exported = module && checker.getExportsOfModule(module).find((symbol) => symbol.name === "WorkerEntrypoint");
|
|
267
|
-
const base = exported && (
|
|
274
|
+
const base = exported && aliased(checker, exported);
|
|
268
275
|
const native = allowNative ? nativeSdkExport(program, checker, entry, "TokamakEntrypoint") : undefined;
|
|
269
276
|
const prototypes = new Set();
|
|
270
277
|
let current = instance;
|
|
@@ -288,7 +295,7 @@ function nativeSdkExport(program, checker, entry, name, specifier = "@tokamakdev
|
|
|
288
295
|
const source = resolved && program.getSourceFile(resolved.resolvedFileName);
|
|
289
296
|
const module = source && checker.getSymbolAtLocation(source);
|
|
290
297
|
const exported = module && checker.getExportsOfModule(module).find(symbol => symbol.name === name);
|
|
291
|
-
return exported && (
|
|
298
|
+
return exported && aliased(checker, exported);
|
|
292
299
|
}
|
|
293
300
|
function exposedMethod(property) {
|
|
294
301
|
const declarations = property.declarations?.filter(ts.isMethodDeclaration) ?? [];
|
|
@@ -299,7 +306,7 @@ function exposedMethod(property) {
|
|
|
299
306
|
throw new Error(`Method ${property.name} is on the public JavaScript prototype; use a #private method for a local helper.`);
|
|
300
307
|
}
|
|
301
308
|
assertMethodName(property.name);
|
|
302
|
-
return !
|
|
309
|
+
return !HANDLERS.includes(property.name);
|
|
303
310
|
}
|
|
304
311
|
function assertMethodName(name) {
|
|
305
312
|
if (["then", "connect", "dup", "constructor", "alarm", "webSocketMessage", "webSocketClose", "webSocketError", "__tokamakRpc"].includes(name)) {
|
package/dist/worker-graph.d.mts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import type { PluginConfig } from "@cloudflare/vite-plugin";
|
|
2
2
|
import { type EnvironmentOptions, type Plugin } from "vite";
|
|
3
|
-
import type { Availability
|
|
4
|
-
import type {
|
|
3
|
+
import type { Availability } from "@tokamakdev/plugin/capabilities";
|
|
4
|
+
import type { DeviceRelay } from "./native.mjs";
|
|
5
5
|
import { WorkerTypes, type WorkerConfiguration } from "./worker-types.mjs";
|
|
6
6
|
type ServiceConfiguration = Pick<WorkerConfiguration, "name" | "topLevelName" | "services" | "main" | "configPath" | "vars" | "kv_namespaces" | "d1_databases" | "r2_buckets">;
|
|
7
|
+
type ServiceBindings = NonNullable<ServiceConfiguration["services"]>;
|
|
7
8
|
type Auxiliary = NonNullable<PluginConfig["auxiliaryWorkers"]>[number];
|
|
8
9
|
type AuxiliaryCustomizer = Extract<Extract<Auxiliary, {
|
|
9
10
|
config: unknown;
|
|
10
11
|
}>["config"], (...args: never[]) => unknown>;
|
|
11
12
|
export interface WorkerGraph {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
relay(port: number, token: string, platform: NativePlatform | undefined): void;
|
|
13
|
+
/** Connects development invocations to a device through `relay`. */
|
|
14
|
+
relay(relay: DeviceRelay): void;
|
|
15
15
|
/** Each plugin operation's native platforms, by plugin ID. */
|
|
16
16
|
availability(): Availability;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/** Configures the main Worker `config`, returning the generated service bindings it adds. */
|
|
18
|
+
configure(config: ServiceConfiguration): ServiceBindings;
|
|
19
|
+
/** The generated service bindings of the main Worker configuration `config`, once it binds none of their names. */
|
|
20
|
+
serviceBindings(config: ServiceConfiguration): ServiceBindings;
|
|
19
21
|
native: AuxiliaryCustomizer;
|
|
20
22
|
names(): {
|
|
21
23
|
main: string;
|
|
22
24
|
native: string;
|
|
23
25
|
app: string;
|
|
24
26
|
};
|
|
25
|
-
contract(): WorkerContract;
|
|
26
27
|
types: WorkerTypes;
|
|
27
28
|
cloudflare: PluginConfig;
|
|
28
29
|
plugin: Plugin & {
|
|
@@ -40,4 +41,6 @@ export interface WorkerGraph {
|
|
|
40
41
|
/** Configure the two bundles through Cloudflare's public auxiliary-worker API. */
|
|
41
42
|
export declare function createWorkerGraph(root: string, entry: string | undefined, output: string): WorkerGraph;
|
|
42
43
|
export declare function workerName(main: string): string;
|
|
44
|
+
/** Writes `source` to `file` unless it already holds it, so watchers see only changes. */
|
|
45
|
+
export declare function writeChanged(file: string, source: string): void;
|
|
43
46
|
export {};
|
package/dist/worker-graph.mjs
CHANGED
|
@@ -6,6 +6,8 @@ import { normalizePath } from "vite";
|
|
|
6
6
|
import { WorkerTypes } from "./worker-types.mjs";
|
|
7
7
|
const NATIVE_CONNECTION = "virtual:tokamak/native-connection";
|
|
8
8
|
const NATIVE_CALLS = normalizePath(fileURLToPath(new URL("./native.mjs", import.meta.url)));
|
|
9
|
+
/** The two Workers' roles: the app's main Worker and the Tokamak worker. */
|
|
10
|
+
const ROLES = ["main", "native"];
|
|
9
11
|
/** Configure the two bundles through Cloudflare's public auxiliary-worker API. */
|
|
10
12
|
export function createWorkerGraph(root, entry, output) {
|
|
11
13
|
const generated = path.join(output, "worker.mjs");
|
|
@@ -56,7 +58,8 @@ export function createWorkerGraph(root, entry, output) {
|
|
|
56
58
|
export const TokamakEvents = eventEntrypoint(Worker, nativeContext);\n`);
|
|
57
59
|
writeChanged(path.join(output, "methods.json"), `${JSON.stringify(contracts.native.methods)}\n`);
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
/** The name of the main Worker configuration `config`, which binds none of the generated names. */
|
|
62
|
+
const checkedName = (config) => {
|
|
60
63
|
if (!config.name)
|
|
61
64
|
throw new Error("Tokamak requires a name in the main Worker's configuration.");
|
|
62
65
|
const reserved = new Set(["MAIN", "TOKAMAK_RUNTIME", ...services(config.name).map(service => service.binding)]);
|
|
@@ -64,15 +67,20 @@ export const TokamakEvents = eventEntrypoint(Worker, nativeContext);\n`);
|
|
|
64
67
|
if (collision) {
|
|
65
68
|
throw new Error(`${collision} is reserved for a generated native worker binding.`);
|
|
66
69
|
}
|
|
67
|
-
|
|
70
|
+
return config.name;
|
|
71
|
+
};
|
|
72
|
+
const configure = (config) => {
|
|
73
|
+
mainName = checkedName(config);
|
|
68
74
|
nativeName = workerName(mainName);
|
|
69
75
|
appName = config.topLevelName ?? mainName;
|
|
70
76
|
contracts.configure(config);
|
|
71
77
|
if (dev)
|
|
72
78
|
config.main = serviceModule("main");
|
|
73
|
-
|
|
79
|
+
const generated = services(mainName);
|
|
80
|
+
config.services = [...config.services ?? [], ...generated];
|
|
74
81
|
mainServices = config.services;
|
|
75
82
|
configuredEntrypoints = entrypoints();
|
|
83
|
+
return generated;
|
|
76
84
|
};
|
|
77
85
|
const native = (_config, { entryWorkerConfig }) => {
|
|
78
86
|
if (!mainName)
|
|
@@ -95,17 +103,15 @@ export const TokamakEvents = eventEntrypoint(Worker, nativeContext);\n`);
|
|
|
95
103
|
};
|
|
96
104
|
refresh();
|
|
97
105
|
return {
|
|
98
|
-
|
|
99
|
-
relay(port, token, platform) { relay = { port, token, platform }; },
|
|
106
|
+
relay(configured) { relay = configured; },
|
|
100
107
|
availability: () => availability(contracts.plugins),
|
|
101
108
|
configure,
|
|
102
|
-
services,
|
|
109
|
+
serviceBindings: config => services(checkedName(config)),
|
|
103
110
|
native,
|
|
104
111
|
names: () => ({ main: mainName, native: nativeName, app: appName }),
|
|
105
|
-
contract: () => contracts.native,
|
|
106
112
|
types: contracts,
|
|
107
113
|
cloudflare: {
|
|
108
|
-
config: configure,
|
|
114
|
+
config: config => { configure(config); },
|
|
109
115
|
auxiliaryWorkers: [{
|
|
110
116
|
viteEnvironment: { name: "tokamak" },
|
|
111
117
|
config: native,
|
|
@@ -125,16 +131,17 @@ export const TokamakEvents = eventEntrypoint(Worker, nativeContext);\n`);
|
|
|
125
131
|
configEnvironment: {
|
|
126
132
|
order: "post",
|
|
127
133
|
handler(name, config) {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
scanned.
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
const served = new Map([[serviceModule("main"), contracts.mainEntry], [serviceModule("native"), entry]]);
|
|
135
|
+
const entries = [config.optimizeDeps?.entries ?? []].flat()
|
|
136
|
+
.map(scanned => served.get(scanned))
|
|
137
|
+
.filter(module => module !== undefined)
|
|
138
|
+
.map(normalizePath);
|
|
133
139
|
// The main Worker's entry is the app, not a dependency, and the
|
|
134
140
|
// plugin SDK keeps state that a dependency bundle would copy.
|
|
135
|
-
const exclude =
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
const exclude = contracts.mainEntry ? [contracts.mainEntry] : [];
|
|
142
|
+
if (name === "tokamak")
|
|
143
|
+
exclude.push("@tokamakdev/plugin");
|
|
144
|
+
return { optimizeDeps: { entries, exclude } };
|
|
138
145
|
},
|
|
139
146
|
},
|
|
140
147
|
async buildStart() {
|
|
@@ -170,20 +177,16 @@ export const TokamakEvents = eventEntrypoint(Worker, nativeContext);\n`);
|
|
|
170
177
|
return [];
|
|
171
178
|
},
|
|
172
179
|
resolveId(id) {
|
|
173
|
-
if (
|
|
174
|
-
return `\0${
|
|
180
|
+
if ([NATIVE_CONNECTION, serviceModule("main"), serviceModule("native")].includes(id))
|
|
181
|
+
return `\0${id}`;
|
|
175
182
|
if (id.startsWith("\0tokamak:") || id.startsWith("\0virtual:tokamak/"))
|
|
176
183
|
return id;
|
|
177
|
-
const role = id === serviceModule("native") ? "native" : id === serviceModule("main") ? "main" : undefined;
|
|
178
|
-
if (!role)
|
|
179
|
-
return;
|
|
180
|
-
return `\0${serviceModule(role)}`;
|
|
181
184
|
},
|
|
182
185
|
async load(id) {
|
|
183
186
|
if (id === `\0${NATIVE_CONNECTION}`)
|
|
184
187
|
return nativeConnectionModule(dev ? relay : undefined);
|
|
185
|
-
const role =
|
|
186
|
-
if (
|
|
188
|
+
const role = ROLES.find(role => id === bindingsModule(role) || id === `\0${serviceModule(role)}`);
|
|
189
|
+
if (!role)
|
|
187
190
|
return;
|
|
188
191
|
await contracts.resolve(source => this.resolve(source, undefined, { skipSelf: true }));
|
|
189
192
|
refresh();
|
|
@@ -252,7 +255,7 @@ function nativeConnectionModule(relay) {
|
|
|
252
255
|
if (!relay)
|
|
253
256
|
return "export const connect = () => undefined;";
|
|
254
257
|
return `import { connectDevice } from ${JSON.stringify(NATIVE_CALLS)};
|
|
255
|
-
export const connect = ctx => connectDevice(${
|
|
258
|
+
export const connect = ctx => connectDevice(${JSON.stringify(relay)}, ctx);`;
|
|
256
259
|
}
|
|
257
260
|
/** Each plugin operation's native platforms: those its manifest names, or every one the plugin builds for. */
|
|
258
261
|
function availability(plugins) {
|
|
@@ -324,7 +327,8 @@ export default class extends WorkerEntrypoint {
|
|
|
324
327
|
${named.join("")}
|
|
325
328
|
`;
|
|
326
329
|
}
|
|
327
|
-
|
|
330
|
+
/** Writes `source` to `file` unless it already holds it, so watchers see only changes. */
|
|
331
|
+
export function writeChanged(file, source) {
|
|
328
332
|
if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === source)
|
|
329
333
|
return;
|
|
330
334
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
package/dist/worker-types.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { readWorkerContracts } from "./worker-contract.mjs";
|
|
3
|
+
import { emptyContract, readWorkerContracts } from "./worker-contract.mjs";
|
|
4
4
|
import { serviceTypes, writeEnvironmentTypes } from "./service-types.mjs";
|
|
5
5
|
import { discoverPlugins } from "./plugin-manifest.mjs";
|
|
6
6
|
/** Shared contracts for the two entrypoints and their generated callers. */
|
|
7
7
|
export class WorkerTypes {
|
|
8
8
|
root;
|
|
9
9
|
entry;
|
|
10
|
-
native =
|
|
10
|
+
native = emptyContract();
|
|
11
11
|
/** The Tokamak worker's named entrypoints. */
|
|
12
12
|
named = {};
|
|
13
13
|
main = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokamakdev/tok",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.116",
|
|
4
4
|
"description": "The tokamak CLI, Vite plugin and app API: build native apps from a Cloudflare Workers codebase",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsc",
|
|
39
39
|
"prepack": "npm run build",
|
|
40
|
-
"test": "node --test --test-concurrency=1"
|
|
40
|
+
"test": "node --import ./test-leaks.mjs --test --test-concurrency=1"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=18"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@tokamakdev/plugin": "0.1.0-beta.
|
|
46
|
+
"@tokamakdev/plugin": "0.1.0-beta.116",
|
|
47
47
|
"typescript": "5.9.3",
|
|
48
48
|
"ws": "^8.22.0"
|
|
49
49
|
},
|
|
@@ -75,17 +75,17 @@
|
|
|
75
75
|
"wrangler": "4.147.0"
|
|
76
76
|
},
|
|
77
77
|
"optionalDependencies": {
|
|
78
|
-
"@tokamakdev/platform-android-arm64": "0.1.1-beta.
|
|
79
|
-
"@tokamakdev/platform-ios-arm64": "0.1.1-beta.
|
|
80
|
-
"@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.
|
|
81
|
-
"@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.
|
|
82
|
-
"@tokamakdev/platform-macos-arm64": "0.1.1-beta.
|
|
83
|
-
"@tokamakdev/platform-macos-x64": "0.1.1-beta.
|
|
84
|
-
"@tokamakdev/platform-windows-x64": "0.1.1-beta.
|
|
85
|
-
"@tokamakdev/tok-darwin-arm64": "0.1.1-beta.
|
|
86
|
-
"@tokamakdev/tok-darwin-x64": "0.1.1-beta.
|
|
87
|
-
"@tokamakdev/tok-linux-x64": "0.1.1-beta.
|
|
88
|
-
"@tokamakdev/tok-win32-x64": "0.1.1-beta.
|
|
78
|
+
"@tokamakdev/platform-android-arm64": "0.1.1-beta.116",
|
|
79
|
+
"@tokamakdev/platform-ios-arm64": "0.1.1-beta.116",
|
|
80
|
+
"@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.116",
|
|
81
|
+
"@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.116",
|
|
82
|
+
"@tokamakdev/platform-macos-arm64": "0.1.1-beta.116",
|
|
83
|
+
"@tokamakdev/platform-macos-x64": "0.1.1-beta.116",
|
|
84
|
+
"@tokamakdev/platform-windows-x64": "0.1.1-beta.116",
|
|
85
|
+
"@tokamakdev/tok-darwin-arm64": "0.1.1-beta.116",
|
|
86
|
+
"@tokamakdev/tok-darwin-x64": "0.1.1-beta.116",
|
|
87
|
+
"@tokamakdev/tok-linux-x64": "0.1.1-beta.116",
|
|
88
|
+
"@tokamakdev/tok-win32-x64": "0.1.1-beta.116"
|
|
89
89
|
},
|
|
90
90
|
"publishConfig": {
|
|
91
91
|
"access": "public"
|