@vormio/plugin-sdk 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @vormio/plugin-sdk
2
2
 
3
- The manifest schema, runtime helpers and packaging command for writing Vormio plugins, plus the commands that publish a signed plugin catalog. The package is versioned with Vormio-Core (currently 0.0.5) and published to npm under the MIT license: `npm install @vormio/plugin-sdk@0.0.5`.
3
+ The manifest schema, runtime helpers and packaging command for writing Vormio plugins, plus the commands that publish a signed plugin catalog. The package is versioned with Vormio-Core (currently 0.0.6) and published to npm under the MIT license: `npm install @vormio/plugin-sdk@0.0.6`.
4
4
 
5
- Plugin contract version: `apiVersion: 1`. During development (0.0.x) there is no cross-version compatibility promise: the host loads only plugins with the same `apiVersion` as its own.
5
+ Plugin contract version: `apiVersion: 2`. During development (0.0.x) there is no cross-version compatibility promise: the host loads only plugins with the same `apiVersion` as its own.
6
6
 
7
7
  ## What a plugin can do
8
8
 
@@ -28,7 +28,7 @@ A plugin is a folder: a `manifest.json` at the root plus the built static files.
28
28
 
29
29
  ```json
30
30
  {
31
- "apiVersion": 1,
31
+ "apiVersion": 2,
32
32
  "id": "acme.model-viewer",
33
33
  "version": "0.1.0",
34
34
  "name": "Model Viewer",
@@ -78,7 +78,8 @@ Reading more formats or companion formats, or adding an extension point, require
78
78
  ```ts
79
79
  import { analyzer, readCompanion, readInput, thumbnail, viewer } from '@vormio/plugin-sdk'
80
80
 
81
- // Viewer: called first and on every file change. Returning means ready; throwing reports a failure.
81
+ // Viewer: called first and on every file change. Returning means ready; throwing reports a failure;
82
+ // returning notApplicable() hands the file to the next viewer (see below).
82
83
  viewer(async (input, signal) => {
83
84
  const bytes = await readInput(input)
84
85
  const texture = await readCompanion(input, 'textures/base.png')
@@ -114,12 +115,36 @@ analyzer(async (input) => {
114
115
  | `changed` | The file changed on disk and no longer matches the library's record | 409 |
115
116
  | `unavailable` | Any other reason, such as an offline linked folder or an expired binding | Other |
116
117
 
117
- - Pages that do not use these helpers can call `window.vormio` directly; see the `PluginBridge` type in `@vormio/plugin-sdk/bridge`. A thumbnail handler returns the PNG as an `ArrayBuffer`, and an analyzer handler (`onAnalyzeRequest`) returns the composition report.
118
+ - Pages that do not use these helpers can call `window.vormio` directly; see the `PluginBridge` type in `@vormio/plugin-sdk/bridge`. A thumbnail handler returns the PNG as an `ArrayBuffer`, and an analyzer handler (`onAnalyzeRequest`) returns the composition report; either can return `{ notApplicable: true }` instead, and a viewer calls `notApplicable(input)`.
118
119
  - While a plugin view has focus, unmodified Esc, ← and → are handled by the host (back to the grid, previous, next); the plugin does not receive these keys.
119
120
 
121
+ ## Files that are not your format
122
+
123
+ The host chooses plugins by extension only. When a format you declare is shared with other tools, such as `json`, every file with that extension comes to you. Return `notApplicable()` for the files that are not yours:
124
+
125
+ ```ts
126
+ import { analyzer, notApplicable, readInput, thumbnail, viewer } from '@vormio/plugin-sdk'
127
+
128
+ analyzer(async (input) => {
129
+ const data = JSON.parse(new TextDecoder().decode(await readInput(input)))
130
+ if (!isSkeleton(data)) return notApplicable()
131
+ return analyzeSkeleton(data)
132
+ })
133
+ ```
134
+
135
+ `thumbnail()` and `viewer()` accept the same answer.
136
+
137
+ - It is not a failure. The host records no reason and shows nothing for it:
138
+ - an analyzer's file gets no composition section;
139
+ - thumbnails and viewers move on to the next enabled plugin for the format, then to the built-in preview or the type icon.
140
+ - The host asks each extension point about each file version once. It asks again when the file changes or your plugin's code changes, so a plugin loaded from a development folder is asked again after every reload.
141
+ - Decide from the file alone; companion files read before answering are not tracked. Decide from the start of the file when you can (the input supports Range requests): a viewer's user waits for the answer before seeing the built-in preview.
142
+ - A file that is your format but has a problem should throw, or report diagnostics, so the user sees why.
143
+ - When several enabled plugins declare a format, the host asks them in order of plugin ID. Thumbnails and viewers ask the next one after a `notApplicable()`; analyzers ask only the first.
144
+
120
145
  ## Analyzers
121
146
 
122
- Once a plugin is enabled in a library, the host analyzes its declared formats in the background. Matching files join the queue when linking and scanning find new files or versions, when the plugin is enabled, and when the plugin's code changes. The file whose details the user is viewing goes first, and the user can also re-analyze manually. At most 2 analyses run at once, each with a 30-second timeout. A thrown error, a timeout or an invalid report counts as a failed analysis and shows the reason; the same plugin code is not retried on the same file version.
147
+ Once a plugin is enabled in a library, the host analyzes its declared formats in the background. Matching files join the queue when linking and scanning find new files or versions, when the plugin is enabled, and when the plugin's code changes. The file whose details the user is viewing goes first, and the user can also re-analyze manually. At most 2 analyses run at once, each with a 30-second timeout. A thrown error, a timeout or an invalid report counts as a failed analysis and shows the reason; the same plugin code is not retried on the same file version. For files that are not your format, return `notApplicable()` (see [Files that are not your format](#files-that-are-not-your-format)).
123
148
 
124
149
  A report describes one version of the analyzed file:
125
150
 
package/dist/bridge.d.ts CHANGED
@@ -54,6 +54,16 @@ export interface CompositionReport {
54
54
  /** The analyzer could only finish part of the file. */
55
55
  partial?: boolean;
56
56
  }
57
+ /**
58
+ * A handler's answer for a file that is not its format, such as a JSON file that is no skeleton.
59
+ * It is not a failure: the host shows nothing for it and does not ask again about this version of
60
+ * the file until the plugin's code changes.
61
+ */
62
+ export interface NotApplicable {
63
+ notApplicable: true;
64
+ }
65
+ /** Whether a handler's answer is exactly `{ notApplicable: true }`. */
66
+ export declare function isNotApplicable(value: unknown): value is NotApplicable;
57
67
  /** The frozen object the host's plugin preload exposes as `window.vormio`. */
58
68
  export interface PluginBridge {
59
69
  /** The viewer's current input. */
@@ -64,13 +74,22 @@ export interface PluginBridge {
64
74
  ready(input: PluginInput): void;
65
75
  /** The viewer cannot show `input`; the host shows `message` as plain text. */
66
76
  fail(input: PluginInput, message: string): void;
77
+ /**
78
+ * `input` is not the viewer's format; the host moves on to the next viewer or the built-in
79
+ * preview.
80
+ */
81
+ notApplicable(input: PluginInput): void;
67
82
  /**
68
83
  * Thumbnail pages: handles each request from the host and returns PNG bytes no larger than
69
- * `maxEdge` on either side. (Blobs cannot cross the bridge; `thumbnail()` converts them.)
84
+ * `maxEdge` on either side, or `{ notApplicable: true }`. (Blobs cannot cross the bridge;
85
+ * `thumbnail()` converts them.)
70
86
  */
71
87
  onThumbnailRequest(handler: (input: PluginInput, size: {
72
88
  maxEdge: number;
73
- }) => Promise<ArrayBuffer>): void;
74
- /** Analyzer pages: handles each request from the host and returns the composition report. */
75
- onAnalyzeRequest(handler: (input: PluginInput) => Promise<CompositionReport>): void;
89
+ }) => Promise<ArrayBuffer | NotApplicable>): void;
90
+ /**
91
+ * Analyzer pages: handles each request from the host and returns the composition report, or
92
+ * `{ notApplicable: true }`.
93
+ */
94
+ onAnalyzeRequest(handler: (input: PluginInput) => Promise<CompositionReport | NotApplicable>): void;
76
95
  }
package/dist/bridge.js CHANGED
@@ -1,2 +1,11 @@
1
- // Types shared by plugin pages and the host's plugin preload. No DOM types, so Node code can use them.
2
- export {};
1
+ // Types shared by plugin pages and the host's plugin preload, and one check free of schemas that the
2
+ // preload uses. No DOM types, so Node code can use them.
3
+ /** Whether a handler's answer is exactly `{ notApplicable: true }`. */
4
+ export function isNotApplicable(value) {
5
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
6
+ return false;
7
+ const keys = Object.keys(value);
8
+ return (keys.length === 1 &&
9
+ keys[0] === 'notApplicable' &&
10
+ value.notApplicable === true);
11
+ }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export { contributionsOf, manifestSchema, missingEntries, parseManifest, permiss
2
2
  export { checkPackageFiles, checkPackagePath, packageExtensions, packageLimits, type PackageFile, } from './package-rules.ts';
3
3
  export { compositionReportSchema, parseReport, reportLimits, type ReportResult } from './report.ts';
4
4
  export type { ReportDiagnostic, ReportReference, ReportSubresource } from './bridge.ts';
5
- export { analyzer, bridge, readCompanion, readInput, thumbnail, viewer, type CompanionResult, type CompanionStatus, type CompositionReport, type PluginBridge, type PluginInput, } from './runtime.ts';
5
+ export { analyzer, bridge, isNotApplicable, notApplicable, readCompanion, readInput, thumbnail, viewer, type CompanionResult, type CompanionStatus, type CompositionReport, type NotApplicable, type PluginBridge, type PluginInput, } from './runtime.ts';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { contributionsOf, manifestSchema, missingEntries, parseManifest, permissionSummary, pluginApiVersion, } from "./manifest.js";
2
2
  export { checkPackageFiles, checkPackagePath, packageExtensions, packageLimits, } from "./package-rules.js";
3
3
  export { compositionReportSchema, parseReport, reportLimits } from "./report.js";
4
- export { analyzer, bridge, readCompanion, readInput, thumbnail, viewer, } from "./runtime.js";
4
+ export { analyzer, bridge, isNotApplicable, notApplicable, readCompanion, readInput, thumbnail, viewer, } from "./runtime.js";
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  /** The plugin contract version this SDK describes. Hosts load only plugins with this version. */
3
- export declare const pluginApiVersion = 1;
3
+ export declare const pluginApiVersion = 2;
4
4
  declare const contributionSchema: z.ZodObject<{
5
5
  id: z.ZodString;
6
6
  entry: z.ZodString;
@@ -13,7 +13,7 @@ export declare const pluginIdSchema: z.ZodString;
13
13
  /** A semantic version without build metadata. */
14
14
  export declare const versionSchema: z.ZodString;
15
15
  export declare const manifestSchema: z.ZodObject<{
16
- apiVersion: z.ZodLiteral<1>;
16
+ apiVersion: z.ZodLiteral<2>;
17
17
  id: z.ZodString;
18
18
  version: z.ZodString;
19
19
  name: z.ZodString;
package/dist/manifest.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { checkPackagePath } from "./package-rules.js";
3
3
  /** The plugin contract version this SDK describes. Hosts load only plugins with this version. */
4
- export const pluginApiVersion = 1;
4
+ export const pluginApiVersion = 2;
5
5
  const unique = (values) => new Set(values).size === values.length;
6
6
  const extension = z.string().regex(/^[a-z0-9]{1,16}$/, 'Use a lower-case extension without a dot');
7
7
  const extensions = (min) => z.array(extension).min(min).max(64).refine(unique, 'Extensions must not repeat');
package/dist/runtime.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- import type { CompanionResult, CompanionStatus, CompositionReport, PluginBridge, PluginInput } from './bridge.ts';
2
- export type { CompanionResult, CompanionStatus, CompositionReport, PluginBridge, PluginInput };
1
+ import { isNotApplicable, type CompanionResult, type CompanionStatus, type CompositionReport, type NotApplicable, type PluginBridge, type PluginInput } from './bridge.ts';
2
+ export type { CompanionResult, CompanionStatus, CompositionReport, NotApplicable, PluginBridge, PluginInput, };
3
+ export { isNotApplicable };
4
+ /**
5
+ * The answer for a file that is not this handler's format. Return it from a viewer, thumbnail or
6
+ * analyzer handler; a file of your format with a problem should throw instead, so the user sees why.
7
+ */
8
+ export declare function notApplicable(): NotApplicable;
3
9
  /** The host bridge; throws outside a Vormio plugin page. */
4
10
  export declare function bridge(): PluginBridge;
5
11
  /**
@@ -11,16 +17,17 @@ export declare function readCompanion(input: PluginInput, relativePath: string):
11
17
  export declare function readInput(input: PluginInput): Promise<ArrayBuffer>;
12
18
  /**
13
19
  * Runs a viewer: `show` is called for the first input and again whenever the host moves to another
14
- * file, with the previous call's signal aborted. Resolving reports ready; throwing reports the
15
- * error message.
20
+ * file, with the previous call's signal aborted. Resolving reports ready, resolving with
21
+ * `notApplicable()` reports the file is not this viewer's format, and throwing reports the error
22
+ * message.
16
23
  */
17
- export declare function viewer(show: (input: PluginInput, signal: AbortSignal) => Promise<void>): void;
18
- /** Runs a thumbnail provider; a canvas is converted to PNG. */
24
+ export declare function viewer(show: (input: PluginInput, signal: AbortSignal) => Promise<void | NotApplicable>): void;
25
+ /** Runs a thumbnail provider; a canvas is converted to PNG. `render` may return `notApplicable()`. */
19
26
  export declare function thumbnail(render: (input: PluginInput, size: {
20
27
  maxEdge: number;
21
- }) => Promise<Blob | HTMLCanvasElement | OffscreenCanvas>): void;
28
+ }) => Promise<Blob | HTMLCanvasElement | OffscreenCanvas | NotApplicable>): void;
22
29
  /**
23
30
  * Runs an analyzer: `analyze` is called once per request and returns the composition report of
24
- * the input. Throwing reports the analysis as failed with the error message.
31
+ * the input, or `notApplicable()`. Throwing reports the analysis as failed with the error message.
25
32
  */
26
- export declare function analyzer(analyze: (input: PluginInput) => Promise<CompositionReport>): void;
33
+ export declare function analyzer(analyze: (input: PluginInput) => Promise<CompositionReport | NotApplicable>): void;
package/dist/runtime.js CHANGED
@@ -1,3 +1,12 @@
1
+ import { isNotApplicable, } from "./bridge.js";
2
+ export { isNotApplicable };
3
+ /**
4
+ * The answer for a file that is not this handler's format. Return it from a viewer, thumbnail or
5
+ * analyzer handler; a file of your format with a problem should throw instead, so the user sees why.
6
+ */
7
+ export function notApplicable() {
8
+ return { notApplicable: true };
9
+ }
1
10
  /** The host bridge; throws outside a Vormio plugin page. */
2
11
  export function bridge() {
3
12
  const host = globalThis.vormio;
@@ -38,8 +47,9 @@ export async function readInput(input) {
38
47
  }
39
48
  /**
40
49
  * Runs a viewer: `show` is called for the first input and again whenever the host moves to another
41
- * file, with the previous call's signal aborted. Resolving reports ready; throwing reports the
42
- * error message.
50
+ * file, with the previous call's signal aborted. Resolving reports ready, resolving with
51
+ * `notApplicable()` reports the file is not this viewer's format, and throwing reports the error
52
+ * message.
43
53
  */
44
54
  export function viewer(show) {
45
55
  const host = bridge();
@@ -49,8 +59,12 @@ export function viewer(show) {
49
59
  const controller = new AbortController();
50
60
  current = controller;
51
61
  try {
52
- await show(input, controller.signal);
53
- if (!controller.signal.aborted)
62
+ const result = await show(input, controller.signal);
63
+ if (controller.signal.aborted)
64
+ return;
65
+ if (isNotApplicable(result))
66
+ host.notApplicable(input);
67
+ else
54
68
  host.ready(input);
55
69
  }
56
70
  catch (error) {
@@ -61,10 +75,12 @@ export function viewer(show) {
61
75
  host.onInput((input) => void run(input));
62
76
  void host.getInput().then(run);
63
77
  }
64
- /** Runs a thumbnail provider; a canvas is converted to PNG. */
78
+ /** Runs a thumbnail provider; a canvas is converted to PNG. `render` may return `notApplicable()`. */
65
79
  export function thumbnail(render) {
66
80
  bridge().onThumbnailRequest(async (input, size) => {
67
81
  const result = await render(input, size);
82
+ if (isNotApplicable(result))
83
+ return result;
68
84
  let blob;
69
85
  if (result instanceof Blob)
70
86
  blob = result;
@@ -77,7 +93,7 @@ export function thumbnail(render) {
77
93
  }
78
94
  /**
79
95
  * Runs an analyzer: `analyze` is called once per request and returns the composition report of
80
- * the input. Throwing reports the analysis as failed with the error message.
96
+ * the input, or `notApplicable()`. Throwing reports the analysis as failed with the error message.
81
97
  */
82
98
  export function analyzer(analyze) {
83
99
  bridge().onAnalyzeRequest(analyze);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vormio/plugin-sdk",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Manifest schema, runtime helpers and packaging for Vormio plugins.",
5
5
  "license": "MIT",
6
6
  "keywords": [