@tokamakdev/tok 0.1.1-beta.126 → 0.1.1-beta.128

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.
@@ -4,16 +4,6 @@ export interface ModuleExport {
4
4
  module: string;
5
5
  export: string;
6
6
  }
7
- /** Where an operation runs, and what it needs there. */
8
- export interface Operation {
9
- /** The native platforms that implement it, with any minimum OS version. */
10
- platforms: {
11
- name: NativePlatform;
12
- minimum?: string;
13
- }[];
14
- /** The native platforms where it needs the app in the foreground, to show UI. */
15
- foreground: NativePlatform[];
16
- }
17
7
  export interface PluginDefinition {
18
8
  id: string;
19
9
  package: string;
@@ -24,8 +14,6 @@ export interface PluginDefinition {
24
14
  client: string;
25
15
  /** The native platforms the plugin builds for. */
26
16
  platforms: NativePlatform[];
27
- /** Each operation the manifest describes; others run on every platform the plugin builds for. */
28
- operations: Record<string, Operation>;
29
17
  }
30
18
  /** Installed direct dependencies select the application's plugin services. */
31
19
  export declare function discoverPlugins(root: string): PluginDefinition[];
@@ -35,6 +35,9 @@ export function discoverPlugins(root) {
35
35
  const reserved = reservedIds.find(word => data.id === word || data.id.startsWith(`${word}-`));
36
36
  if (reserved)
37
37
  throw new Error(`${manifest}: plugin id must not start with '${reserved}', which Tokamak's options use.`);
38
+ if (data.operations !== undefined) {
39
+ throw new Error(`${manifest}: operations is no longer read; tag a method that only some platforms implement with @platforms in its doc comment.`);
40
+ }
38
41
  const api = moduleExport(directory, data.api, "api");
39
42
  if (plugins.has(data.id))
40
43
  throw new Error(`Duplicate Tokamak plugin id ${data.id}.`);
@@ -45,7 +48,6 @@ export function discoverPlugins(root) {
45
48
  binding: `TOKAMAK_${data.id.toUpperCase().replaceAll("-", "_")}`,
46
49
  client: `${name}/client`,
47
50
  platforms,
48
- operations: operations(manifest, data.operations ?? {}, platforms),
49
51
  });
50
52
  }
51
53
  return [...plugins.values()].sort((left, right) => left.id.localeCompare(right.id));
@@ -62,35 +64,3 @@ function moduleExport(root, reference, field) {
62
64
  }
63
65
  return { module: module.split(path.sep).join("/"), export: value.export };
64
66
  }
65
- /** The manifest's `operations`, each on platforms the plugin builds for. */
66
- function operations(manifest, value, built) {
67
- const fail = (message) => { throw new Error(`${manifest}: ${message}`); };
68
- const platform = (name, field) => built.includes(name) ? name : fail(`${field} names ${name}, which the plugin does not build for.`);
69
- if (!isRecord(value))
70
- return fail("operations must be an object.");
71
- const result = {};
72
- for (const [name, entry] of Object.entries(value)) {
73
- const field = `operations.${name}`;
74
- if (!isRecord(entry))
75
- return fail(`${field} must be an object.`);
76
- const { platforms, foreground = false, ...unknown } = entry;
77
- if (Object.keys(unknown).length)
78
- fail(`${field} has unknown keys: ${Object.keys(unknown).join(", ")}.`);
79
- if (platforms !== undefined && (!isRecord(platforms) ||
80
- !Object.values(platforms).every(version => typeof version === "string" || version === true))) {
81
- fail(`${field}.platforms must map platforms to a minimum OS version, or true for the app's minimum.`);
82
- }
83
- if (typeof foreground !== "boolean" && !(Array.isArray(foreground) && foreground.every(name => typeof name === "string"))) {
84
- fail(`${field}.foreground must be true or a list of platforms.`);
85
- }
86
- const implemented = platforms === undefined
87
- ? built.map(name => ({ name }))
88
- : Object.entries(platforms).map(([name, minimum]) => ({ name: platform(name, `${field}.platforms`), minimum: minimum === true ? undefined : minimum }));
89
- const needs = foreground === true ? implemented.map(({ name }) => name) : foreground === false ? [] : foreground;
90
- result[name] = { platforms: implemented, foreground: needs.map(name => platform(name, `${field}.foreground`)) };
91
- }
92
- return result;
93
- }
94
- function isRecord(value) {
95
- return typeof value === "object" && value !== null && !Array.isArray(value);
96
- }
@@ -1,4 +1,4 @@
1
- import type { PluginDefinition } from "./plugin-manifest.mjs";
1
+ import type { NativePlatform, PluginDefinition } from "./plugin-manifest.mjs";
2
2
  /** The callable prototype methods and source dependencies of a Worker entrypoint. */
3
3
  export interface WorkerContract {
4
4
  methods: string[];
@@ -8,8 +8,11 @@ export interface WorkerContract {
8
8
  export interface PluginContract extends WorkerContract {
9
9
  definition: PluginDefinition;
10
10
  webMethods: string[];
11
- /** Every operation of the native SDK, including those only the Tokamak worker can call. */
12
- operations: string[];
11
+ /**
12
+ * The native platforms that implement each operation of the native SDK, including those only the
13
+ * Tokamak worker can call.
14
+ */
15
+ operations: Record<string, NativePlatform[]>;
13
16
  }
14
17
  /** The contract of a Tokamak worker without a module. */
15
18
  export declare function emptyContract(): WorkerContract;
@@ -123,7 +123,7 @@ function pluginContract(module, definition, validate) {
123
123
  }
124
124
  const reference = nativeSdkExport(program, checker, module.entry, "NativeReference", "@tokamakdev/plugin");
125
125
  const methods = [];
126
- const operations = [];
126
+ const operations = {};
127
127
  for (const property of api.getProperties()) {
128
128
  if (privateProperty(property))
129
129
  continue;
@@ -137,7 +137,7 @@ function pluginContract(module, definition, validate) {
137
137
  if (!method.getCallSignatures().length || String(property.escapedName).startsWith("__@")) {
138
138
  throw new Error(`Plugin ${definition.id}.${property.name} must be a method.`);
139
139
  }
140
- operations.push(property.name);
140
+ operations[property.name] = implementedOn(property, definition);
141
141
  try {
142
142
  checkRpcMethod(checker, method, `${definition.id}.${property.name}`, reference);
143
143
  }
@@ -148,39 +148,45 @@ function pluginContract(module, definition, validate) {
148
148
  if (validate)
149
149
  throw error;
150
150
  }
151
- methods.push(methodDeclaration(checker, property, method));
151
+ // Its `@platforms` tag becomes the availability line the documentation ends with.
152
+ methods.push(methodDeclaration(checker, property, method, ["platforms"]));
152
153
  }
153
154
  methods.sort(byName);
154
- const unknown = Object.keys(definition.operations).find(name => !operations.includes(name));
155
- if (unknown)
156
- throw new Error(`${definition.manifest}: operations.${unknown} names no method of ${definition.api.export}.`);
157
155
  const webMethods = browserMethods(module, definition, methods, validate);
158
156
  for (const method of methods) {
159
- method.documentation = [method.documentation, availability(definition, method.name, webMethods.includes(method.name))]
157
+ method.documentation = [method.documentation, availability(operations[method.name], webMethods.includes(method.name))]
160
158
  .filter(Boolean).join("\n\n");
161
159
  }
162
160
  return {
163
161
  definition, methods: methods.map(method => method.name), files: [...module.files, definition.manifest],
164
- webMethods, operations: operations.sort(),
162
+ webMethods, operations: Object.fromEntries(Object.entries(operations).sort(([left], [right]) => left.localeCompare(right))),
165
163
  declarations: serviceTypes(`ReturnType<typeof import(${JSON.stringify(definition.api.module)}, { with: { "resolution-mode": "import" } })[${JSON.stringify(definition.api.export)}]>`, methods),
166
164
  };
167
165
  }
168
166
  const platformNames = {
169
167
  ios: "iOS", macos: "macOS", android: "Android", windows: "Windows", linux: "Linux",
170
168
  };
171
- /** Where the operation `name` is implemented, and what it needs there, for editor documentation. */
172
- function availability(definition, name, web) {
173
- const operation = definition.operations[name];
174
- const platforms = operation?.platforms ?? definition.platforms.map(platform => ({ name: platform, minimum: undefined }));
175
- const native = platforms.map(({ name, minimum }) => {
176
- const version = name === "android" ? `API ${minimum ?? ""}` : minimum;
177
- return minimum ? `${platformNames[name]} ${version}+` : platformNames[name];
178
- });
179
- const foreground = operation?.foreground.map(platform => platformNames[platform]) ?? [];
180
- return [
181
- `Native: ${native.join(", ") || "none"}. Web: ${web ? "browser implementation" : "not supported"}.`,
182
- foreground.length ? `May need the app in the foreground on ${foreground.join(", ")}, to show UI.` : "",
183
- ].filter(Boolean).join(" ");
169
+ /** Where an operation is implemented, for editor documentation. */
170
+ function availability(platforms, web) {
171
+ const native = platforms.map(platform => platformNames[platform]).join(", ") || "none";
172
+ return `Native: ${native}. Web: ${web ? "browser implementation" : "not supported"}.`;
173
+ }
174
+ /**
175
+ * The native platforms that implement the plugin method `property`: those the `@platforms` tag of
176
+ * its doc comment names, or every one the plugin builds for.
177
+ */
178
+ function implementedOn(property, definition) {
179
+ const tag = property.declarations?.flatMap(node => ts.getJSDocTags(node)).find(({ tagName }) => tagName.text === "platforms");
180
+ if (!tag)
181
+ return definition.platforms;
182
+ const method = `Plugin ${definition.id}.${property.name}`;
183
+ const names = (ts.getTextOfJSDocComment(tag.comment) ?? "").split(/\s+/).filter(Boolean);
184
+ if (!names.length)
185
+ throw new Error(`${method}: @platforms names no platform.`);
186
+ const unknown = names.find(name => !definition.platforms.includes(name));
187
+ if (unknown)
188
+ throw new Error(`${method}: @platforms names ${unknown}, which the plugin does not build for (${definition.platforms.join(", ")}).`);
189
+ return names;
184
190
  }
185
191
  function browserMethods(project, definition, methods, validate) {
186
192
  if (!definition.web)
@@ -258,13 +264,15 @@ function classContract(module, name, native, validate) {
258
264
  function byName(left, right) {
259
265
  return left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
260
266
  }
261
- function methodDeclaration(checker, property, type) {
267
+ /** `property`'s declaration in generated service types, with its documentation apart from `consumed` tags. */
268
+ function methodDeclaration(checker, property, type, consumed = []) {
262
269
  return {
263
270
  name: property.name,
264
271
  overloads: type.getCallSignatures().length,
265
272
  documentation: [
266
273
  ts.displayPartsToString(property.getDocumentationComment(checker)),
267
- ...property.getJsDocTags(checker).map(tag => `@${tag.name} ${ts.displayPartsToString(tag.text)}`.trim()),
274
+ ...property.getJsDocTags(checker).filter(tag => !consumed.includes(tag.name))
275
+ .map(tag => `@${tag.name} ${ts.displayPartsToString(tag.text)}`.trim()),
268
276
  ].filter(Boolean).join("\n"),
269
277
  };
270
278
  }
@@ -257,15 +257,9 @@ function nativeConnectionModule(relay) {
257
257
  return `import { connectDevice } from ${JSON.stringify(NATIVE_CALLS)};
258
258
  export const connect = ctx => connectDevice(${JSON.stringify(relay)}, ctx);`;
259
259
  }
260
- /** Each plugin operation's native platforms: those its manifest names, or every one the plugin builds for. */
260
+ /** The native platforms of each plugin operation. */
261
261
  function availability(plugins) {
262
- return Object.fromEntries(plugins.map(({ definition, operations }) => [
263
- definition.id,
264
- Object.fromEntries(operations.map(operation => [
265
- operation,
266
- definition.operations[operation]?.platforms.map(({ name }) => name) ?? definition.platforms,
267
- ])),
268
- ]));
262
+ return Object.fromEntries(plugins.map(({ definition, operations }) => [definition.id, operations]));
269
263
  }
270
264
  function serviceEntrypoint(entry, services) {
271
265
  const sdk = normalizePath(fileURLToPath(import.meta.resolve("@tokamakdev/plugin/worker")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/tok",
3
- "version": "0.1.1-beta.126",
3
+ "version": "0.1.1-beta.128",
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",
@@ -43,7 +43,7 @@
43
43
  "node": ">=18"
44
44
  },
45
45
  "dependencies": {
46
- "@tokamakdev/plugin": "0.1.0-beta.126",
46
+ "@tokamakdev/plugin": "0.1.0-beta.128",
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.126",
79
- "@tokamakdev/platform-ios-arm64": "0.1.1-beta.126",
80
- "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.126",
81
- "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.126",
82
- "@tokamakdev/platform-macos-arm64": "0.1.1-beta.126",
83
- "@tokamakdev/platform-macos-x64": "0.1.1-beta.126",
84
- "@tokamakdev/platform-windows-x64": "0.1.1-beta.126",
85
- "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.126",
86
- "@tokamakdev/tok-darwin-x64": "0.1.1-beta.126",
87
- "@tokamakdev/tok-linux-x64": "0.1.1-beta.126",
88
- "@tokamakdev/tok-win32-x64": "0.1.1-beta.126"
78
+ "@tokamakdev/platform-android-arm64": "0.1.1-beta.128",
79
+ "@tokamakdev/platform-ios-arm64": "0.1.1-beta.128",
80
+ "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.128",
81
+ "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.128",
82
+ "@tokamakdev/platform-macos-arm64": "0.1.1-beta.128",
83
+ "@tokamakdev/platform-macos-x64": "0.1.1-beta.128",
84
+ "@tokamakdev/platform-windows-x64": "0.1.1-beta.128",
85
+ "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.128",
86
+ "@tokamakdev/tok-darwin-x64": "0.1.1-beta.128",
87
+ "@tokamakdev/tok-linux-x64": "0.1.1-beta.128",
88
+ "@tokamakdev/tok-win32-x64": "0.1.1-beta.128"
89
89
  },
90
90
  "publishConfig": {
91
91
  "access": "public"