@tokamakdev/tok 0.1.1-beta.106 → 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.
@@ -0,0 +1,332 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { normalizePath } from "vite";
6
+ import { WorkerTypes } from "./worker-types.mjs";
7
+ const NATIVE_CONNECTION = "virtual:tokamak/native-connection";
8
+ const NATIVE_CALLS = normalizePath(fileURLToPath(new URL("./native.mjs", import.meta.url)));
9
+ /** Configure the two bundles through Cloudflare's public auxiliary-worker API. */
10
+ export function createWorkerGraph(root, entry, output) {
11
+ const generated = path.join(output, "worker.mjs");
12
+ const types = path.join(root, "tokamak-env.d.ts");
13
+ const contracts = new WorkerTypes(root, entry);
14
+ contracts.discover();
15
+ const events = normalizePath(fileURLToPath(new URL("./entrypoint.js", import.meta.resolve("@tokamakdev/plugin/events"))));
16
+ let mainName;
17
+ let nativeName;
18
+ let appName;
19
+ let mainEnvironment;
20
+ let dev = false;
21
+ let mainServices;
22
+ /** The named entrypoints the main Worker's bindings were configured with. */
23
+ let configuredEntrypoints;
24
+ const entrypoints = () => Object.keys(contracts.named).join();
25
+ const signatures = new Map();
26
+ const development = normalizePath(fileURLToPath(new URL("./rpc/binding.mjs", import.meta.resolve("@tokamakdev/plugin/events"))));
27
+ let relay;
28
+ const serviceModule = (role) => `virtual:tokamak/service/${role}`;
29
+ const bindingsModule = (role) => `\0tokamak:bindings:${role}`;
30
+ const nativeContracts = () => Object.fromEntries(contracts.services().map(service => [service.entrypoint, service.contract]));
31
+ const sources = () => [...contracts.native.files, ...Object.values(contracts.main).flatMap(value => value.files),
32
+ ...contracts.plugins.flatMap(plugin => plugin.files)];
33
+ const services = (name) => contracts.services().map(({ binding, entrypoint }) => entrypoint === "default" ? { binding, service: workerName(name) } : { binding, service: workerName(name), entrypoint });
34
+ function bindings(role) {
35
+ const services = role === "main" ? mainServices : [{ binding: "MAIN", service: mainName }];
36
+ const result = {};
37
+ for (const service of services ?? []) {
38
+ const targets = service.service === mainName ? contracts.main
39
+ : service.service === nativeName ? nativeContracts() : undefined;
40
+ if (!targets)
41
+ continue;
42
+ const target = targets[service.entrypoint ?? "default"];
43
+ if (!target)
44
+ throw new Error(`Binding ${service.binding} refers to an unknown Worker entrypoint ${service.entrypoint}.`);
45
+ result[service.binding] = target.methods;
46
+ }
47
+ return result;
48
+ }
49
+ function signature(role) {
50
+ const targets = role === "main" ? contracts.main : nativeContracts();
51
+ return JSON.stringify({ bindings: bindings(role), methods: Object.entries(targets).map(([name, value]) => [name, value.methods]) });
52
+ }
53
+ function refresh() {
54
+ const source = serviceEntrypoint(entry, contracts.services());
55
+ writeChanged(generated, `${source}import { eventEntrypoint } from ${JSON.stringify(events)};
56
+ export const TokamakEvents = eventEntrypoint(Worker, nativeContext);\n`);
57
+ writeChanged(path.join(output, "methods.json"), `${JSON.stringify(contracts.native.methods)}\n`);
58
+ }
59
+ const configure = (config) => {
60
+ if (!config.name)
61
+ throw new Error("Tokamak requires a name in the main Worker's configuration.");
62
+ const reserved = new Set(["MAIN", "TOKAMAK_RUNTIME", ...services(config.name).map(service => service.binding)]);
63
+ const collision = bindingNames(config).find(name => reserved.has(name));
64
+ if (collision) {
65
+ throw new Error(`${collision} is reserved for a generated native worker binding.`);
66
+ }
67
+ mainName = config.name;
68
+ nativeName = workerName(mainName);
69
+ appName = config.topLevelName ?? mainName;
70
+ contracts.configure(config);
71
+ if (dev)
72
+ config.main = serviceModule("main");
73
+ config.services = [...config.services ?? [], ...services(mainName)];
74
+ mainServices = config.services;
75
+ configuredEntrypoints = entrypoints();
76
+ };
77
+ const native = (_config, { entryWorkerConfig }) => {
78
+ if (!mainName)
79
+ throw new Error("The main Worker must be configured before the Tokamak worker.");
80
+ contracts.bindings(entryWorkerConfig);
81
+ const { configPath, userConfigPath, compatibility_date, compatibility_flags, vars, kv_namespaces, d1_databases, r2_buckets } = entryWorkerConfig;
82
+ return {
83
+ configPath,
84
+ userConfigPath,
85
+ name: nativeName,
86
+ main: dev ? serviceModule("native") : generated,
87
+ compatibility_date,
88
+ compatibility_flags,
89
+ vars,
90
+ kv_namespaces,
91
+ d1_databases,
92
+ r2_buckets,
93
+ services: [{ binding: "MAIN", service: mainName }],
94
+ };
95
+ };
96
+ refresh();
97
+ return {
98
+ generated: normalizePath(generated),
99
+ relay(port, token, platform) { relay = { port, token, platform }; },
100
+ availability: () => availability(contracts.plugins),
101
+ configure,
102
+ services,
103
+ native,
104
+ names: () => ({ main: mainName, native: nativeName, app: appName }),
105
+ contract: () => contracts.native,
106
+ types: contracts,
107
+ cloudflare: {
108
+ config: configure,
109
+ auxiliaryWorkers: [{
110
+ viteEnvironment: { name: "tokamak" },
111
+ config: native,
112
+ }],
113
+ },
114
+ plugin: {
115
+ name: "tokamak:worker-graph",
116
+ config(_config, environment) {
117
+ dev = environment.command === "serve";
118
+ contracts.discover();
119
+ },
120
+ // After Cloudflare's plugin, which points each Worker's dependency scan
121
+ // at its main. The scan cannot read a development service module, so it
122
+ // reads the app's module behind it, and finds that module's dependencies
123
+ // at startup rather than reloading the Worker when they are first
124
+ // imported.
125
+ configEnvironment: {
126
+ order: "post",
127
+ handler(name, config) {
128
+ const scanned = [config.optimizeDeps?.entries ?? []].flat();
129
+ const served = [
130
+ scanned.includes(serviceModule("main")) && contracts.mainEntry,
131
+ scanned.includes(serviceModule("native")) && entry,
132
+ ].filter(module => typeof module === "string").map(normalizePath);
133
+ // The main Worker's entry is the app, not a dependency, and the
134
+ // plugin SDK keeps state that a dependency bundle would copy.
135
+ const exclude = [contracts.mainEntry, name === "tokamak" && "@tokamakdev/plugin"]
136
+ .filter(module => typeof module === "string");
137
+ return { optimizeDeps: { entries: served, exclude } };
138
+ },
139
+ },
140
+ async buildStart() {
141
+ if (this.environment.name !== "tokamak")
142
+ return;
143
+ for (const file of contracts.metadata)
144
+ this.addWatchFile(file);
145
+ await contracts.resolve(id => this.resolve(id, undefined, { skipSelf: true }));
146
+ refresh();
147
+ for (const file of sources())
148
+ this.addWatchFile(file);
149
+ },
150
+ hotUpdate({ file, modules, timestamp }) {
151
+ if (file === normalizePath(types))
152
+ return;
153
+ if (contracts.metadata.includes(file))
154
+ return [];
155
+ const role = this.environment.name === "tokamak" ? "native"
156
+ : this.environment.name === mainEnvironment ? "main" : undefined;
157
+ if (!role)
158
+ return;
159
+ if (!sources().includes(file) && (this.environment.name !== "tokamak" || !modules.length))
160
+ return;
161
+ contracts.refresh({ file, timestamp });
162
+ refresh();
163
+ if (entrypoints() !== configuredEntrypoints) {
164
+ this.environment.logger.warnOnce("Restart tok dev after adding, removing, or renaming a Tokamak entrypoint.");
165
+ }
166
+ if (!modules.length && signatures.get(role) === signature(role))
167
+ return;
168
+ this.environment.moduleGraph.invalidateAll();
169
+ this.environment.hot.send({ type: "full-reload" });
170
+ return [];
171
+ },
172
+ resolveId(id) {
173
+ if (id === NATIVE_CONNECTION)
174
+ return `\0${NATIVE_CONNECTION}`;
175
+ if (id.startsWith("\0tokamak:") || id.startsWith("\0virtual:tokamak/"))
176
+ 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
+ },
182
+ async load(id) {
183
+ if (id === `\0${NATIVE_CONNECTION}`)
184
+ return nativeConnectionModule(dev ? relay : undefined);
185
+ const role = id.endsWith(":main") || id.endsWith("/main") ? "main" : "native";
186
+ if (id !== bindingsModule(role) && id !== `\0${serviceModule(role)}`)
187
+ return;
188
+ await contracts.resolve(source => this.resolve(source, undefined, { skipSelf: true }));
189
+ refresh();
190
+ if (role === "main")
191
+ mainEnvironment = this.environment.name;
192
+ if (id === bindingsModule(role)) {
193
+ return `import { env } from "cloudflare:workers";
194
+ import { installBindings } from ${JSON.stringify(development)};
195
+ installBindings(env, ${JSON.stringify(bindings(role))});`;
196
+ }
197
+ signatures.set(role, signature(role));
198
+ const source = JSON.stringify(role === "main" ? contracts.mainModule : normalizePath(generated));
199
+ const targets = role === "main" ? contracts.main : nativeContracts();
200
+ const exports = Object.entries(targets).map(([name, value], index) => `const Service${index} = developmentEntrypoint(module[${JSON.stringify(name)}], ${JSON.stringify(value.methods)}, ${JSON.stringify(bindings(role))});
201
+ export { Service${index} as ${JSON.stringify(name)} };`);
202
+ const hostEvents = role === "native"
203
+ ? `export const TokamakEvents = developmentEvents(module.TokamakEvents, ${JSON.stringify(bindings(role))});`
204
+ : "";
205
+ return `import ${JSON.stringify(bindingsModule(role))};
206
+ import * as module from ${source};
207
+ export * from ${source};
208
+ import { developmentEntrypoint, developmentEvents } from ${JSON.stringify(development)};
209
+ ${exports.join("\n")}
210
+ ${hostEvents}`;
211
+ },
212
+ },
213
+ };
214
+ }
215
+ /** The binding kinds of a Worker configuration besides variables, whose entries name bindings. */
216
+ const BINDING_KINDS = [
217
+ "ai", "analytics_engine_datasets", "assets", "browser", "d1_databases", "dispatch_namespaces", "durable_objects",
218
+ "hyperdrive", "images", "kv_namespaces", "mtls_certificates", "pipelines", "queues", "r2_buckets", "rate_limiting",
219
+ "secrets_store_secrets", "send_email", "services", "vectorize", "version_metadata", "workflows",
220
+ ];
221
+ /** Every binding name `config` declares, as the native packager reads them. */
222
+ function bindingNames(config) {
223
+ const names = Object.keys(config.vars ?? {});
224
+ const collect = (value) => {
225
+ if (Array.isArray(value))
226
+ return value.forEach(collect);
227
+ if (!value || typeof value !== "object")
228
+ return;
229
+ const entry = value;
230
+ const nested = ["bindings", "producers", "consumers"].filter(key => key in entry);
231
+ if (nested.length)
232
+ return nested.forEach(key => { collect(entry[key]); });
233
+ const name = ["binding", "name", "queue", "dataset"].map(key => entry[key]).find(name => typeof name === "string");
234
+ if (typeof name === "string")
235
+ names.push(name);
236
+ };
237
+ for (const kind of BINDING_KINDS)
238
+ collect(config[kind]);
239
+ return names;
240
+ }
241
+ export function workerName(main) {
242
+ const name = `${main}-tokamak`;
243
+ if (name.length <= 63)
244
+ return name;
245
+ return `${main.slice(0, 46)}-${createHash("sha256").update(main).digest("hex").slice(0, 8)}-tokamak`;
246
+ }
247
+ /**
248
+ * The module whose `connect(ctx)` gives an invocation its native connection: the device relay
249
+ * during development, otherwise none, so the context uses the connection the host attached.
250
+ */
251
+ function nativeConnectionModule(relay) {
252
+ if (!relay)
253
+ return "export const connect = () => undefined;";
254
+ return `import { connectDevice } from ${JSON.stringify(NATIVE_CALLS)};
255
+ export const connect = ctx => connectDevice(${String(relay.port)}, ${JSON.stringify(relay.token)}, ctx, ${JSON.stringify(relay.platform ?? null)} ?? undefined);`;
256
+ }
257
+ /** Each plugin operation's native platforms: those its manifest names, or every one the plugin builds for. */
258
+ function availability(plugins) {
259
+ return Object.fromEntries(plugins.map(({ definition, operations }) => [
260
+ definition.id,
261
+ Object.fromEntries(operations.map(operation => [
262
+ operation,
263
+ definition.operations[operation]?.platforms.map(({ name }) => name) ?? definition.platforms,
264
+ ])),
265
+ ]));
266
+ }
267
+ function serviceEntrypoint(entry, services) {
268
+ const sdk = normalizePath(fileURLToPath(import.meta.resolve("@tokamakdev/plugin/worker")));
269
+ const rpc = normalizePath(fileURLToPath(new URL("./rpc/endpoint.mjs", import.meta.resolve("@tokamakdev/plugin/events"))));
270
+ const plugins = services.flatMap(service => service.plugin ? [service.plugin] : []);
271
+ const imports = services.map((service, index) => {
272
+ if (service.plugin) {
273
+ const { api } = service.plugin.definition;
274
+ return `import { ${JSON.stringify(api.export)} as Service${index} } from ${JSON.stringify(api.module)};`;
275
+ }
276
+ if (!entry)
277
+ return `import { TokamakEntrypoint as Service${index} } from ${JSON.stringify(sdk)};`;
278
+ const name = service.entrypoint === "default" ? "default" : JSON.stringify(service.entrypoint);
279
+ return `import { ${name} as Service${index} } from ${JSON.stringify(normalizePath(entry))};`;
280
+ });
281
+ const table = services.map((service, index) => ` { route: ${JSON.stringify(service.route)}, methods: ${JSON.stringify(service.contract.methods)}, create: ${service.plugin
282
+ ? `ctx => Service${index}(nativeContext(ctx))`
283
+ : `(ctx, env) => new Service${index}(nativeContext(ctx), env)`} },`);
284
+ const methods = (methods, instance) => methods.map(method => `async [${JSON.stringify(method)}](...args) { return this.${instance}[${JSON.stringify(method)}](...args); }`).join("\n ");
285
+ const named = services.slice(1).map((service, index) => `
286
+ export class ${service.entrypoint} extends WorkerEntrypoint {
287
+ #api;
288
+ get #instance() { return this.#api ??= services[${index + 1}].create(this.ctx, this.env); }
289
+ fetch(request) {
290
+ ${service.plugin
291
+ ? `return rpcResponse(request, ctx => services[${index + 1}].create(ctx, this.env), services[${index + 1}].methods, this.ctx);`
292
+ : `if (typeof this.#instance.fetch !== "function") throw new TypeError("The ${service.entrypoint} entrypoint has no fetch handler");
293
+ return this.#instance.fetch(request);`}
294
+ }
295
+ ${methods(service.contract.methods, "#instance")}
296
+ }
297
+ `);
298
+ return `import { WorkerEntrypoint } from "cloudflare:workers";
299
+ import { rpcResponse } from ${JSON.stringify(rpc)};
300
+ import { capabilitiesModule, createNativeContext } from ${JSON.stringify(sdk)};
301
+ import { connect } from ${JSON.stringify(NATIVE_CONNECTION)};
302
+ ${imports.join("\n")}
303
+
304
+ const availability = ${JSON.stringify(availability(plugins))};
305
+ const nativeContext = ctx => createNativeContext(ctx, connect(ctx), availability);
306
+ const services = [
307
+ ${table.join("\n")}
308
+ ];
309
+ const Worker = Service0;
310
+
311
+ export default class extends WorkerEntrypoint {
312
+ #worker;
313
+ get #instance() { return this.#worker ??= services[0].create(this.ctx, this.env); }
314
+ fetch(request) {
315
+ const { pathname } = new URL(request.url);
316
+ if (pathname === "/_tokamak/capabilities.js") return capabilitiesModule(nativeContext(this.ctx));
317
+ const service = services.find(service => service.route === pathname);
318
+ if (service) return rpcResponse(request, ctx => service.create(ctx, this.env), service.methods, this.ctx);
319
+ if (typeof this.#instance.fetch !== "function") throw new TypeError("The Tokamak worker has no fetch handler");
320
+ return this.#instance.fetch(request);
321
+ }
322
+ ${methods(services[0]?.contract.methods ?? [], "#instance")}
323
+ }
324
+ ${named.join("")}
325
+ `;
326
+ }
327
+ function writeChanged(file, source) {
328
+ if (fs.existsSync(file) && fs.readFileSync(file, "utf8") === source)
329
+ return;
330
+ fs.mkdirSync(path.dirname(file), { recursive: true });
331
+ fs.writeFileSync(file, source);
332
+ }
@@ -0,0 +1,46 @@
1
+ import type { PluginConfig } from "@cloudflare/vite-plugin";
2
+ import { type PluginContract, type WorkerContract } from "./worker-contract.mjs";
3
+ import { type PluginDefinition } from "./plugin-manifest.mjs";
4
+ type MainCustomizer = Extract<NonNullable<PluginConfig["config"]>, (...args: never[]) => unknown>;
5
+ export type WorkerConfiguration = Parameters<MainCustomizer>[0];
6
+ export type BindingConfiguration = Partial<Pick<WorkerConfiguration, "main" | "configPath" | "vars" | "kv_namespaces" | "d1_databases" | "r2_buckets">>;
7
+ /** A generated binding to an entrypoint of the Tokamak worker, and the page client of its operations. */
8
+ export interface NativeService {
9
+ binding: string;
10
+ /** The entrypoint's export name in the generated Tokamak worker. */
11
+ entrypoint: string;
12
+ client: string;
13
+ /** The frontend RPC route. */
14
+ route: string;
15
+ contract: WorkerContract;
16
+ plugin?: PluginContract;
17
+ }
18
+ /** Shared contracts for the two entrypoints and their generated callers. */
19
+ export declare class WorkerTypes {
20
+ #private;
21
+ readonly root: string;
22
+ readonly entry: string | undefined;
23
+ native: WorkerContract;
24
+ /** The Tokamak worker's named entrypoints. */
25
+ named: Record<string, WorkerContract>;
26
+ main: Record<string, WorkerContract>;
27
+ plugins: PluginContract[];
28
+ definitions: PluginDefinition[];
29
+ mainEntry?: string;
30
+ mainModule?: string;
31
+ constructor(root: string, entry: string | undefined);
32
+ configure(config: BindingConfiguration): void;
33
+ bindings(config: BindingConfiguration): void;
34
+ resolve(resolve: (id: string) => Promise<{
35
+ id: string;
36
+ } | null>): Promise<void>;
37
+ discover(): void;
38
+ /** Every generated binding to the Tokamak worker: the app's entrypoints, then the plugins'. */
39
+ services(): NativeService[];
40
+ get metadata(): string[];
41
+ refresh(change?: {
42
+ file: string;
43
+ timestamp: number;
44
+ }): void;
45
+ }
46
+ export {};
@@ -0,0 +1,112 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { readWorkerContracts } from "./worker-contract.mjs";
4
+ import { serviceTypes, writeEnvironmentTypes } from "./service-types.mjs";
5
+ import { discoverPlugins } from "./plugin-manifest.mjs";
6
+ /** Shared contracts for the two entrypoints and their generated callers. */
7
+ export class WorkerTypes {
8
+ root;
9
+ entry;
10
+ native = { methods: [], files: [], declarations: serviceTypes("{}", []) };
11
+ /** The Tokamak worker's named entrypoints. */
12
+ named = {};
13
+ main = {};
14
+ plugins = [];
15
+ definitions;
16
+ mainEntry;
17
+ mainModule;
18
+ #bindings = [];
19
+ #ready = false;
20
+ #change;
21
+ constructor(root, entry) {
22
+ this.root = root;
23
+ this.entry = entry;
24
+ this.definitions = discoverPlugins(root);
25
+ }
26
+ configure(config) {
27
+ if (!config.main)
28
+ throw new Error("Tokamak requires a main Worker entrypoint.");
29
+ const source = path.resolve(config.configPath ? path.dirname(config.configPath) : this.root, config.main);
30
+ this.mainEntry = fs.existsSync(source) ? source : config.main;
31
+ this.mainModule = undefined;
32
+ this.#ready = false;
33
+ this.bindings(config);
34
+ }
35
+ bindings(config) {
36
+ this.#ready = false;
37
+ this.#bindings = [...new Set([
38
+ ...Object.keys(config.vars ?? {}),
39
+ ...[...config.kv_namespaces ?? [], ...config.d1_databases ?? [], ...config.r2_buckets ?? []].map(value => value.binding),
40
+ ])].sort();
41
+ }
42
+ async resolve(resolve) {
43
+ if (this.#ready)
44
+ return;
45
+ if (this.mainEntry && !this.mainModule) {
46
+ const resolved = await resolve(this.mainEntry);
47
+ const source = resolved?.id.split(/[?#]/, 1)[0];
48
+ if (!source || !fs.existsSync(source)) {
49
+ throw new Error(`Cannot resolve the main Worker's service contract: ${this.mainEntry} (${resolved?.id ?? "unresolved"}).`);
50
+ }
51
+ this.mainModule = source.split(path.sep).join("/");
52
+ }
53
+ this.refresh();
54
+ }
55
+ discover() {
56
+ const definitions = discoverPlugins(this.root);
57
+ if (definitions.length !== this.definitions.length || definitions.some((plugin, index) => plugin.id !== this.definitions[index].id || plugin.package !== this.definitions[index].package)) {
58
+ throw new Error("Restart tok dev after installing, removing, or renaming a Tokamak plugin.");
59
+ }
60
+ this.definitions = definitions;
61
+ this.#read(false);
62
+ }
63
+ /** Every generated binding to the Tokamak worker: the app's entrypoints, then the plugins'. */
64
+ services() {
65
+ const services = [
66
+ { binding: "TOKAMAK", entrypoint: "default", client: "virtual:tokamak/client", route: "/_tokamak/app", contract: this.native },
67
+ ...Object.entries(this.named).map(([name, contract]) => ({
68
+ binding: `TOKAMAK_${constantCase(name)}`, entrypoint: name, client: `virtual:tokamak/client/${name}`,
69
+ route: `/_tokamak/app/${name}`, contract,
70
+ })),
71
+ ...this.plugins.map(plugin => ({
72
+ binding: plugin.definition.binding, entrypoint: plugin.definition.binding, client: plugin.definition.client,
73
+ route: `/_tokamak/plugin/${plugin.definition.id}`, contract: plugin, plugin,
74
+ })),
75
+ ];
76
+ const bindings = new Set();
77
+ for (const { binding, entrypoint } of services) {
78
+ if (bindings.has(binding))
79
+ throw new Error(`The generated binding ${binding} of entrypoint ${entrypoint} has the name of another.`);
80
+ bindings.add(binding);
81
+ }
82
+ return services;
83
+ }
84
+ get metadata() {
85
+ return [path.join(this.root, "package.json"), ...this.definitions.map(plugin => plugin.manifest)]
86
+ .map(file => file.split(path.sep).join("/"));
87
+ }
88
+ refresh(change) {
89
+ if (this.mainEntry && !this.mainModule)
90
+ return;
91
+ if (this.#ready && change && change.file === this.#change?.file && change.timestamp === this.#change.timestamp)
92
+ return;
93
+ this.#ready = false;
94
+ this.discover();
95
+ // Declare both APIs before checking signatures inferred from cross-worker calls.
96
+ writeEnvironmentTypes(this.root, this.services(), this.main.default?.declarations ?? serviceTypes("{}", [], "Main"), this.#bindings);
97
+ this.#read(true);
98
+ this.#ready = true;
99
+ this.#change = change;
100
+ }
101
+ #read(validate) {
102
+ const contracts = readWorkerContracts(this.entry, this.mainModule, this.root, validate, this.definitions);
103
+ this.native = contracts.native;
104
+ this.named = contracts.named;
105
+ this.main = contracts.main;
106
+ this.plugins = contracts.plugins;
107
+ }
108
+ }
109
+ /** `name` in upper case with words joined by underscores: `DataSync` becomes `DATA_SYNC`. */
110
+ function constantCase(name) {
111
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").toUpperCase();
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/tok",
3
- "version": "0.1.1-beta.106",
3
+ "version": "0.1.1-beta.112",
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",
@@ -19,6 +19,10 @@
19
19
  "./vite": {
20
20
  "types": "./dist/vite.d.mts",
21
21
  "default": "./dist/vite.mjs"
22
+ },
23
+ "./astro": {
24
+ "types": "./dist/astro.d.mts",
25
+ "default": "./dist/astro.mjs"
22
26
  }
23
27
  },
24
28
  "bin": {
@@ -33,22 +37,28 @@
33
37
  "scripts": {
34
38
  "build": "tsc",
35
39
  "prepack": "npm run build",
36
- "test": "node --test"
40
+ "test": "node --test --test-concurrency=1"
37
41
  },
38
42
  "engines": {
39
43
  "node": ">=18"
40
44
  },
41
45
  "dependencies": {
42
- "@tokamakdev/plugin": "0.1.0-beta.106",
46
+ "@tokamakdev/plugin": "0.1.0-beta.112",
47
+ "typescript": "5.9.3",
43
48
  "ws": "^8.22.0"
44
49
  },
45
50
  "peerDependencies": {
51
+ "@astrojs/cloudflare": "^13.1.8",
46
52
  "@cloudflare/vite-plugin": "^1.54.0",
53
+ "astro": "^6.1.5",
47
54
  "vite": "^7.0.0 || ^8.0.0",
48
55
  "wrangler": "^4.126.0"
49
56
  },
50
57
  "peerDependenciesMeta": {
51
- "@cloudflare/vite-plugin": {
58
+ "@astrojs/cloudflare": {
59
+ "optional": true
60
+ },
61
+ "astro": {
52
62
  "optional": true
53
63
  },
54
64
  "wrangler": {
@@ -56,25 +66,26 @@
56
66
  }
57
67
  },
58
68
  "devDependencies": {
69
+ "@astrojs/cloudflare": "13.1.8",
59
70
  "@cloudflare/vite-plugin": "1.62.5",
60
71
  "@types/node": "22.19.15",
61
72
  "@types/ws": "8.18.2",
62
- "typescript": "5.9.3",
63
- "vite": "8.3.2",
73
+ "astro": "6.1.5",
74
+ "vite": "7.3.7",
64
75
  "wrangler": "4.147.0"
65
76
  },
66
77
  "optionalDependencies": {
67
- "@tokamakdev/platform-android-arm64": "0.1.1-beta.106",
68
- "@tokamakdev/platform-ios-arm64": "0.1.1-beta.106",
69
- "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.106",
70
- "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.106",
71
- "@tokamakdev/platform-macos-arm64": "0.1.1-beta.106",
72
- "@tokamakdev/platform-macos-x64": "0.1.1-beta.106",
73
- "@tokamakdev/platform-windows-x64": "0.1.1-beta.106",
74
- "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.106",
75
- "@tokamakdev/tok-darwin-x64": "0.1.1-beta.106",
76
- "@tokamakdev/tok-linux-x64": "0.1.1-beta.106",
77
- "@tokamakdev/tok-win32-x64": "0.1.1-beta.106"
78
+ "@tokamakdev/platform-android-arm64": "0.1.1-beta.112",
79
+ "@tokamakdev/platform-ios-arm64": "0.1.1-beta.112",
80
+ "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.112",
81
+ "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.112",
82
+ "@tokamakdev/platform-macos-arm64": "0.1.1-beta.112",
83
+ "@tokamakdev/platform-macos-x64": "0.1.1-beta.112",
84
+ "@tokamakdev/platform-windows-x64": "0.1.1-beta.112",
85
+ "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.112",
86
+ "@tokamakdev/tok-darwin-x64": "0.1.1-beta.112",
87
+ "@tokamakdev/tok-linux-x64": "0.1.1-beta.112",
88
+ "@tokamakdev/tok-win32-x64": "0.1.1-beta.112"
78
89
  },
79
90
  "publishConfig": {
80
91
  "access": "public"