@paged-media/plugin-sdk 0.2.16-canary.0 → 0.2.19-canary.0
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/index.d.ts +83 -2
- package/dist/index.js +281 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,17 @@ declare const BLOB_BUDGETS: {
|
|
|
98
98
|
/** Default per-plugin blob ceiling, in bytes (64 MiB). */
|
|
99
99
|
readonly defaultQuotaBytes: number;
|
|
100
100
|
};
|
|
101
|
+
/** Worker spawn + SAB budgets (K-3 / S-07). The host enforces the
|
|
102
|
+
* stricter of these and any manifest tightening. */
|
|
103
|
+
declare const WORKER_BUDGETS: {
|
|
104
|
+
/** Hard worker-count cap per bundle. The grant is `min(declared.max,
|
|
105
|
+
* hardwareConcurrency, maxWorkers)` — a runaway `max` can't exhaust
|
|
106
|
+
* the machine. */
|
|
107
|
+
readonly maxWorkers: 8;
|
|
108
|
+
/** Default per-bundle shared-memory ceiling, in bytes (256 MiB). A
|
|
109
|
+
* manifest's `maxSharedBytes` may only TIGHTEN this. */
|
|
110
|
+
readonly defaultSharedBytes: number;
|
|
111
|
+
};
|
|
101
112
|
/**
|
|
102
113
|
* The backend the editor injects to back `host.blob` (K-4 / S-08): a
|
|
103
114
|
* RAW per-plugin byte store (OPFS in-browser; an in-memory map in the
|
|
@@ -113,6 +124,60 @@ interface BlobStore {
|
|
|
113
124
|
/** Total bytes this plugin currently stores. */
|
|
114
125
|
used(pluginId: string): Promise<number>;
|
|
115
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* A raw spawned worker the `WorkerBackend` hands back (K-3 / S-07). The
|
|
129
|
+
* SDK adapter owns the capability gate, the count cap, the SAB budget,
|
|
130
|
+
* and teardown tracking; this backend only constructs the realm + does
|
|
131
|
+
* the raw message IO. `post` forwards to the underlying worker (optional
|
|
132
|
+
* transfer); `onMessage` subscribes (the backend fans out); `terminate`
|
|
133
|
+
* destroys the realm.
|
|
134
|
+
*/
|
|
135
|
+
interface SpawnedWorker {
|
|
136
|
+
post(message: unknown, transfer?: Transferable[]): void;
|
|
137
|
+
onMessage(handler: (message: unknown) => void): Disposable;
|
|
138
|
+
terminate(): void;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The backend the editor injects to back `host.workers` (K-3 / S-07 /
|
|
142
|
+
* I-02): it resolves a bundle's DECLARED, bundle-relative `module` path
|
|
143
|
+
* through the bundle's own asset base (the `/@fs/`-allowed sibling-plugin
|
|
144
|
+
* path the wasm artifacts use) and constructs an ES-module `Worker`. The
|
|
145
|
+
* SDK adapter passes the plugin id + the declared module path; the
|
|
146
|
+
* backend never invents a URL. Returns a `SpawnedWorker` (or rejects when
|
|
147
|
+
* the module fails to resolve/construct). The SAB itself is allocated by
|
|
148
|
+
* the SDK adapter (a plain `new SharedArrayBuffer` under the host budget)
|
|
149
|
+
* — the backend's job is purely the worker realm. A headless host injects
|
|
150
|
+
* no backend → `spawn` rejects + `supports("workers@1")` is false.
|
|
151
|
+
*/
|
|
152
|
+
interface WorkerBackend {
|
|
153
|
+
spawn(pluginId: string, module: string, name?: string): Promise<SpawnedWorker>;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The backend the editor injects to back `host.secrets` (D-11;
|
|
157
|
+
* rfc-credential-store): a REFERENCE-ONLY, host-owned credential store. The
|
|
158
|
+
* SDK adapter owns the capability gate + the namespacing (passes the plugin
|
|
159
|
+
* id); this backend owns the storage tier (WebCrypto-wrapped IndexedDB in
|
|
160
|
+
* the editor, an in-memory map in the headless harness) AND the user PROMPT
|
|
161
|
+
* — the RFC's "via host UI only": `set` resolves only after the host has
|
|
162
|
+
* the material (the editor backing prompts the user; the plugin's supplied
|
|
163
|
+
* `secret` is the value to STORE, never persisted silently by the adapter).
|
|
164
|
+
*
|
|
165
|
+
* The trust line is the ABSENCE of a read: there is `set`/`exists`/`forget`
|
|
166
|
+
* and NO `get` — secret bytes never leave the host realm. A headless host
|
|
167
|
+
* injects no backend → `set`/`forget` reject, `exists` is false,
|
|
168
|
+
* `supports("secrets@1")` is false. The plugin id + the (caller-supplied)
|
|
169
|
+
* `ref` together namespace the stored secret (`paged:<plugin-id>:<ref>`).
|
|
170
|
+
*/
|
|
171
|
+
interface SecretStoreBackend {
|
|
172
|
+
/** Store `secret` for `pluginId` under `ref` (the editor backing PROMPTS
|
|
173
|
+
* the user — "via host UI only"). Rejects when the user declines or the
|
|
174
|
+
* store is unavailable. */
|
|
175
|
+
set(pluginId: string, ref: string, secret: string): Promise<void>;
|
|
176
|
+
/** Whether a secret is stored for `pluginId`/`ref`. */
|
|
177
|
+
exists(pluginId: string, ref: string): Promise<boolean>;
|
|
178
|
+
/** Forget the secret for `pluginId`/`ref` (idempotent). */
|
|
179
|
+
forget(pluginId: string, ref: string): Promise<void>;
|
|
180
|
+
}
|
|
116
181
|
/**
|
|
117
182
|
* The backend the editor injects to back `host.clipboard` (K-6 / S-14): a
|
|
118
183
|
* thin read/write pair over the REAL system clipboard (`navigator.clipboard`
|
|
@@ -240,6 +305,22 @@ interface CreateBundleHostOptions {
|
|
|
240
305
|
* When absent, `read` answers `null` and `write` is a no-op (the honest
|
|
241
306
|
* no-clipboard door). */
|
|
242
307
|
clipboard?: ClipboardBackend;
|
|
308
|
+
/** Host-provided WORKER backend (K-3 / S-07 / I-02). When present,
|
|
309
|
+
* `host.workers.spawn` resolves a declared bundle-relative module +
|
|
310
|
+
* constructs a host-owned `Worker` through it (capability-gated,
|
|
311
|
+
* count-capped, SAB-budgeted, teardown-tracked) and
|
|
312
|
+
* `supports("workers@1")` answers true. When absent, `spawn` rejects
|
|
313
|
+
* honestly, `concurrency()` is 0, and the feature flag is false (the
|
|
314
|
+
* honest no-worker door). */
|
|
315
|
+
workers?: WorkerBackend;
|
|
316
|
+
/** Host-provided CREDENTIAL-STORE backend (D-11; rfc-credential-store).
|
|
317
|
+
* When present, `host.secrets.set/exists/forget` go through it
|
|
318
|
+
* (capability-gated on `capabilities.secrets`; `set` prompts the user —
|
|
319
|
+
* "via host UI only") and `supports("secrets@1")` answers true. When
|
|
320
|
+
* absent, `set`/`forget` reject and `exists` is false (the honest
|
|
321
|
+
* no-store door). REFERENCE-ONLY: there is no `get` anywhere — secret
|
|
322
|
+
* bytes never enter the plugin realm. */
|
|
323
|
+
secrets?: SecretStoreBackend;
|
|
243
324
|
/**
|
|
244
325
|
* How the host treats a declaration↔use mismatch — a bundle that
|
|
245
326
|
* USES a door (`contribute.tool`, `document.mutate`, …) it did not
|
|
@@ -334,7 +415,7 @@ interface RecordedContribution {
|
|
|
334
415
|
id: string;
|
|
335
416
|
value: ToolContribution | PanelContribution | SchemaPanelContribution | CommandContribution | KeybindingContribution | OverlayContribution | EditContextContribution | ObjectTypeContribution | ImporterContribution | ExporterContribution;
|
|
336
417
|
}
|
|
337
|
-
interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard"> {
|
|
418
|
+
interface HarnessOptions extends LoadHeadlessEngineOptions, Pick<CreateBundleHostOptions, "console" | "storage" | "capabilityMode" | "assetSource" | "blobStore" | "clipboard" | "secrets"> {
|
|
338
419
|
}
|
|
339
420
|
/** What `createHeadlessHost` resolves to: a real engine-backed host plus
|
|
340
421
|
* the conformance affordances (load an IDML, read the contribution log,
|
|
@@ -603,4 +684,4 @@ declare function contributeEditContext(host: BundleHost, contribution: EditConte
|
|
|
603
684
|
* descent. Capability-gated on `contributes.objectTypes`. */
|
|
604
685
|
declare function contributeObjectType(host: BundleHost, contribution: ObjectTypeContribution): Disposable;
|
|
605
686
|
|
|
606
|
-
export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type SeededFace, type StorageBacking, WASM_BUDGETS, beginPageDrag, commitAndSelect, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
|
|
687
|
+
export { API_VERSION, ASSET_BUDGETS, BLOB_BUDGETS, type BlobStore, type BundleAssetProvider, type BundleAssetSource, type BundleHostHandle, type BundleTrust, CANVAS_WASM_PKG, CLICK_DRAG_THRESHOLD_PX, type ClipboardBackend, type ConsentBackend, type CreateBundleHostOptions, type DataProviderBackend, type DiagnosticsSink, DisposableStore, FALLBACK_WIDGETS, HOST_FEATURES, type HarnessOptions, type HeadlessCanvasWorker, type HeadlessHost, type HeadlessHostHandle, type LoadBundleWasmOptions, type LoadHeadlessEngineOptions, type LoadedBundle, type LoadedBundleWasm, type LoadedEngine, type PageDrag, PluginApiNotImplemented, PluginCapabilityError, type RecordableAssetSource, type RecordedContribution, type RecordedFontFaceRequest, type SecretStoreBackend, type SeededFace, type SpawnedWorker, type StorageBacking, WASM_BUDGETS, WORKER_BUDGETS, type WorkerBackend, beginPageDrag, commitAndSelect, contributeEditContext, contributeObjectType, contributePanel, contributeSchemaPanel, contributeTool, createBundleHost, createDataProviderRegistry, createHeadlessHost, createRecordableAssetSource, defineBundle, endLocalFor, loadBundle, loadBundleWasm, loadHeadlessEngine, makeSchemaPanelComponent, protocolFromVersion, pxToPt, readVendoredWireVersion, resolveCanvasWasm, resolveGate, satisfiesApiVersion, toDisposable };
|
package/dist/index.js
CHANGED
|
@@ -201,6 +201,15 @@ var BLOB_BUDGETS = {
|
|
|
201
201
|
/** Default per-plugin blob ceiling, in bytes (64 MiB). */
|
|
202
202
|
defaultQuotaBytes: 64 * 1024 * 1024
|
|
203
203
|
};
|
|
204
|
+
var WORKER_BUDGETS = {
|
|
205
|
+
/** Hard worker-count cap per bundle. The grant is `min(declared.max,
|
|
206
|
+
* hardwareConcurrency, maxWorkers)` — a runaway `max` can't exhaust
|
|
207
|
+
* the machine. */
|
|
208
|
+
maxWorkers: 8,
|
|
209
|
+
/** Default per-bundle shared-memory ceiling, in bytes (256 MiB). A
|
|
210
|
+
* manifest's `maxSharedBytes` may only TIGHTEN this. */
|
|
211
|
+
defaultSharedBytes: 256 * 1024 * 1024
|
|
212
|
+
};
|
|
204
213
|
function createDataProviderRegistry() {
|
|
205
214
|
const providers = /* @__PURE__ */ new Map();
|
|
206
215
|
const listeners = /* @__PURE__ */ new Map();
|
|
@@ -273,6 +282,8 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
273
282
|
const hasAsset = (k) => caps?.assets?.includes(k) ?? false;
|
|
274
283
|
const hasBlobStore = () => caps?.storage?.blob === true;
|
|
275
284
|
const clipboardGrant = () => caps?.clipboard === "full" ? "full" : caps?.clipboard === "vector" ? "vector" : "none";
|
|
285
|
+
const hasWorkers = () => typeof caps?.workers?.max === "number";
|
|
286
|
+
const hasSecrets = () => caps?.secrets?.sources === true;
|
|
276
287
|
const lists = (arr, id) => arr?.includes(id) ?? false;
|
|
277
288
|
const declaresType = (arr, type) => arr?.some((e) => e.type === type) ?? false;
|
|
278
289
|
const requireDeclared = (ok, door, missing) => {
|
|
@@ -984,6 +995,87 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
984
995
|
}
|
|
985
996
|
}
|
|
986
997
|
};
|
|
998
|
+
const imageChannel = () => getEditor().images;
|
|
999
|
+
const images = {
|
|
1000
|
+
claimImageResource(elementId, opts) {
|
|
1001
|
+
requireDeclared(
|
|
1002
|
+
hasRendering("resourceProvider"),
|
|
1003
|
+
"images.claimImageResource",
|
|
1004
|
+
'capabilities.rendering must include "resourceProvider"'
|
|
1005
|
+
);
|
|
1006
|
+
const channel = imageChannel();
|
|
1007
|
+
if (!channel) {
|
|
1008
|
+
log.warn(
|
|
1009
|
+
`images.claimImageResource("${elementId}") ignored \u2014 the host wired no resource channel (probe supports("rendering.resourceProvider@1"))`
|
|
1010
|
+
);
|
|
1011
|
+
return store.add(toDisposable(() => {
|
|
1012
|
+
}));
|
|
1013
|
+
}
|
|
1014
|
+
let released = false;
|
|
1015
|
+
const off = channel.onResourceTilesNeeded((need) => {
|
|
1016
|
+
if (released || need.imageId !== elementId) return;
|
|
1017
|
+
void (async () => {
|
|
1018
|
+
const tiles = [];
|
|
1019
|
+
for (const [x, y] of need.tiles) {
|
|
1020
|
+
let tile;
|
|
1021
|
+
try {
|
|
1022
|
+
tile = await opts.source(need.level, x, y);
|
|
1023
|
+
} catch (err) {
|
|
1024
|
+
log.warn(
|
|
1025
|
+
`images.source(level ${need.level}, ${x}, ${y}) threw \u2014 tile skipped`,
|
|
1026
|
+
err
|
|
1027
|
+
);
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (!tile) continue;
|
|
1031
|
+
tiles.push({
|
|
1032
|
+
x: tile.x,
|
|
1033
|
+
y: tile.y,
|
|
1034
|
+
width: tile.width,
|
|
1035
|
+
height: tile.height,
|
|
1036
|
+
// The wire carries a plain number[]; Array.from copies the
|
|
1037
|
+
// RGBA8 view (isolate-proxy-safe, like the scene-layer path).
|
|
1038
|
+
rgba: Array.from(tile.rgba)
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
if (released || tiles.length === 0) return;
|
|
1042
|
+
try {
|
|
1043
|
+
await channel.submitTiles(
|
|
1044
|
+
elementId,
|
|
1045
|
+
need.level,
|
|
1046
|
+
tiles,
|
|
1047
|
+
need.generation
|
|
1048
|
+
);
|
|
1049
|
+
} catch (err) {
|
|
1050
|
+
log.warn(
|
|
1051
|
+
`images.submitTiles("${elementId}", level ${need.level}) failed`,
|
|
1052
|
+
err
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
})();
|
|
1056
|
+
});
|
|
1057
|
+
void channel.claim({
|
|
1058
|
+
imageId: elementId,
|
|
1059
|
+
levels: opts.levels,
|
|
1060
|
+
tileSize: opts.tileSize,
|
|
1061
|
+
baseWidth: opts.baseWidth,
|
|
1062
|
+
baseHeight: opts.baseHeight,
|
|
1063
|
+
revision: opts.revision()
|
|
1064
|
+
}).catch((err) => {
|
|
1065
|
+
log.warn(`images.claim("${elementId}") failed`, err);
|
|
1066
|
+
});
|
|
1067
|
+
return store.add(
|
|
1068
|
+
toDisposable(() => {
|
|
1069
|
+
if (released) return;
|
|
1070
|
+
released = true;
|
|
1071
|
+
off();
|
|
1072
|
+
void channel.release(elementId).catch((err) => {
|
|
1073
|
+
log.warn(`images.release("${elementId}") failed`, err);
|
|
1074
|
+
});
|
|
1075
|
+
})
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
987
1079
|
const blobBackend = options?.blobStore;
|
|
988
1080
|
const blobQuota = Math.min(
|
|
989
1081
|
BLOB_BUDGETS.defaultQuotaBytes,
|
|
@@ -1077,6 +1169,159 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1077
1169
|
}
|
|
1078
1170
|
}
|
|
1079
1171
|
};
|
|
1172
|
+
const workerBackend = options?.workers;
|
|
1173
|
+
const declaredWorkers = caps?.workers;
|
|
1174
|
+
const hardwareConcurrency = globalThis.navigator?.hardwareConcurrency ?? 1;
|
|
1175
|
+
const workerCap = hasWorkers() && workerBackend ? Math.max(
|
|
1176
|
+
0,
|
|
1177
|
+
Math.min(
|
|
1178
|
+
declaredWorkers?.max ?? 0,
|
|
1179
|
+
hardwareConcurrency,
|
|
1180
|
+
WORKER_BUDGETS.maxWorkers
|
|
1181
|
+
)
|
|
1182
|
+
) : 0;
|
|
1183
|
+
const sharedByteBudget = Math.min(
|
|
1184
|
+
WORKER_BUDGETS.defaultSharedBytes,
|
|
1185
|
+
declaredWorkers?.maxSharedBytes ?? WORKER_BUDGETS.defaultSharedBytes
|
|
1186
|
+
);
|
|
1187
|
+
const sharedMemoryDeclared = declaredWorkers?.sharedMemory === true;
|
|
1188
|
+
const crossOriginIsolated = globalThis.crossOriginIsolated === true;
|
|
1189
|
+
let liveWorkerCount = 0;
|
|
1190
|
+
let sharedBytesUsed = 0;
|
|
1191
|
+
const makeBundleWorker = (raw) => {
|
|
1192
|
+
let terminated = false;
|
|
1193
|
+
let mySharedBytes = 0;
|
|
1194
|
+
const subs = new DisposableStore();
|
|
1195
|
+
const worker = {
|
|
1196
|
+
post(message, transfer) {
|
|
1197
|
+
if (terminated) return;
|
|
1198
|
+
raw.post(message, transfer);
|
|
1199
|
+
},
|
|
1200
|
+
onMessage(handler) {
|
|
1201
|
+
if (terminated) return toDisposable(() => {
|
|
1202
|
+
});
|
|
1203
|
+
return subs.add(raw.onMessage(handler));
|
|
1204
|
+
},
|
|
1205
|
+
allocateShared(bytes) {
|
|
1206
|
+
if (terminated) return null;
|
|
1207
|
+
if (!sharedMemoryDeclared) {
|
|
1208
|
+
log.warn(
|
|
1209
|
+
"workers.allocateShared: capabilities.workers.sharedMemory is not declared \u2014 no SharedArrayBuffer (declare it to allocate)"
|
|
1210
|
+
);
|
|
1211
|
+
return null;
|
|
1212
|
+
}
|
|
1213
|
+
if (!crossOriginIsolated) {
|
|
1214
|
+
log.warn(
|
|
1215
|
+
"workers.allocateShared: the environment is not cross-origin isolated \u2014 SharedArrayBuffer is unavailable (the host needs COOP/COEP)"
|
|
1216
|
+
);
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
if (!Number.isInteger(bytes) || bytes <= 0) return null;
|
|
1220
|
+
if (sharedBytesUsed + bytes > sharedByteBudget) {
|
|
1221
|
+
log.warn(
|
|
1222
|
+
`workers.allocateShared(${bytes}) would exceed the ${sharedByteBudget}-byte per-bundle shared-memory budget (used ${sharedBytesUsed}) \u2014 refused`
|
|
1223
|
+
);
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
let sab;
|
|
1227
|
+
try {
|
|
1228
|
+
sab = new SharedArrayBuffer(bytes);
|
|
1229
|
+
} catch (err) {
|
|
1230
|
+
log.warn(`workers.allocateShared(${bytes}) failed`, err);
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
sharedBytesUsed += bytes;
|
|
1234
|
+
mySharedBytes += bytes;
|
|
1235
|
+
return sab;
|
|
1236
|
+
},
|
|
1237
|
+
terminate() {
|
|
1238
|
+
if (terminated) return;
|
|
1239
|
+
terminated = true;
|
|
1240
|
+
subs.dispose();
|
|
1241
|
+
sharedBytesUsed -= mySharedBytes;
|
|
1242
|
+
mySharedBytes = 0;
|
|
1243
|
+
liveWorkerCount = Math.max(0, liveWorkerCount - 1);
|
|
1244
|
+
try {
|
|
1245
|
+
raw.terminate();
|
|
1246
|
+
} catch (err) {
|
|
1247
|
+
log.warn("workers.terminate: backend terminate threw", err);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
};
|
|
1251
|
+
return worker;
|
|
1252
|
+
};
|
|
1253
|
+
const workers = {
|
|
1254
|
+
async spawn(opts) {
|
|
1255
|
+
requireDeclared(
|
|
1256
|
+
hasWorkers(),
|
|
1257
|
+
"workers.spawn",
|
|
1258
|
+
"capabilities.workers must be declared"
|
|
1259
|
+
);
|
|
1260
|
+
if (!workerBackend) {
|
|
1261
|
+
throw new Error(
|
|
1262
|
+
`host.workers.spawn("${opts.module}") \u2014 no worker backend wired (supports("workers@1") is false; the editor injects one)`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
if (liveWorkerCount >= workerCap) {
|
|
1266
|
+
throw new Error(
|
|
1267
|
+
`host.workers.spawn("${opts.module}") \u2014 the ${workerCap}-worker count cap is reached (declared max ${declaredWorkers?.max}, clamped to min(declared, hardwareConcurrency ${hardwareConcurrency}, ${WORKER_BUDGETS.maxWorkers})) \u2014 terminate a worker first`
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
liveWorkerCount++;
|
|
1271
|
+
let raw;
|
|
1272
|
+
try {
|
|
1273
|
+
raw = await workerBackend.spawn(manifest.id, opts.module, opts.name);
|
|
1274
|
+
} catch (err) {
|
|
1275
|
+
liveWorkerCount = Math.max(0, liveWorkerCount - 1);
|
|
1276
|
+
throw err instanceof Error ? err : new Error(
|
|
1277
|
+
`host.workers.spawn("${opts.module}") failed: ${String(err)}`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const worker = makeBundleWorker(raw);
|
|
1281
|
+
store.add(toDisposable(() => worker.terminate()));
|
|
1282
|
+
return worker;
|
|
1283
|
+
},
|
|
1284
|
+
concurrency: () => workerCap
|
|
1285
|
+
};
|
|
1286
|
+
const secretStore = options?.secrets;
|
|
1287
|
+
const secrets = {
|
|
1288
|
+
async set(ref, secret) {
|
|
1289
|
+
requireDeclared(
|
|
1290
|
+
hasSecrets(),
|
|
1291
|
+
"secrets.set",
|
|
1292
|
+
"capabilities.secrets must declare { sources: true }"
|
|
1293
|
+
);
|
|
1294
|
+
if (!secretStore) {
|
|
1295
|
+
throw new Error(
|
|
1296
|
+
`host.secrets.set("${ref}") \u2014 no secret-store backend wired (supports("secrets@1") is false; the editor injects one)`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
await secretStore.set(manifest.id, ref, secret);
|
|
1300
|
+
},
|
|
1301
|
+
async exists(ref) {
|
|
1302
|
+
requireDeclared(
|
|
1303
|
+
hasSecrets(),
|
|
1304
|
+
"secrets.exists",
|
|
1305
|
+
"capabilities.secrets must declare { sources: true }"
|
|
1306
|
+
);
|
|
1307
|
+
if (!secretStore) return false;
|
|
1308
|
+
return secretStore.exists(manifest.id, ref);
|
|
1309
|
+
},
|
|
1310
|
+
async forget(ref) {
|
|
1311
|
+
requireDeclared(
|
|
1312
|
+
hasSecrets(),
|
|
1313
|
+
"secrets.forget",
|
|
1314
|
+
"capabilities.secrets must declare { sources: true }"
|
|
1315
|
+
);
|
|
1316
|
+
if (!secretStore) {
|
|
1317
|
+
log.warn(
|
|
1318
|
+
`host.secrets.forget("${ref}") \u2014 no secret-store backend wired (supports("secrets@1") is false); nothing to forget`
|
|
1319
|
+
);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
await secretStore.forget(manifest.id, ref);
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1080
1325
|
const featureSet = new Set(HOST_FEATURES);
|
|
1081
1326
|
if (getEditor().text) {
|
|
1082
1327
|
featureSet.add("text.measure@1");
|
|
@@ -1084,6 +1329,9 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1084
1329
|
if (getEditor().sceneLayers) {
|
|
1085
1330
|
featureSet.add("rendering.sceneLayer@1");
|
|
1086
1331
|
}
|
|
1332
|
+
if (getEditor().images) {
|
|
1333
|
+
featureSet.add("rendering.resourceProvider@1");
|
|
1334
|
+
}
|
|
1087
1335
|
if (options?.shell) {
|
|
1088
1336
|
featureSet.add("shell.openPanel@1");
|
|
1089
1337
|
featureSet.add("shell.pickFile@1");
|
|
@@ -1112,6 +1360,12 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1112
1360
|
if (options?.clipboard) {
|
|
1113
1361
|
featureSet.add("clipboard@1");
|
|
1114
1362
|
}
|
|
1363
|
+
if (options?.workers) {
|
|
1364
|
+
featureSet.add("workers@1");
|
|
1365
|
+
}
|
|
1366
|
+
if (options?.secrets) {
|
|
1367
|
+
featureSet.add("secrets@1");
|
|
1368
|
+
}
|
|
1115
1369
|
const host = {
|
|
1116
1370
|
manifest,
|
|
1117
1371
|
log,
|
|
@@ -1130,6 +1384,9 @@ function createBundleHost(getEditor, manifest, options) {
|
|
|
1130
1384
|
bindings,
|
|
1131
1385
|
widgets,
|
|
1132
1386
|
assets,
|
|
1387
|
+
images,
|
|
1388
|
+
workers,
|
|
1389
|
+
secrets,
|
|
1133
1390
|
clipboard,
|
|
1134
1391
|
supports: (feature) => featureSet.has(feature),
|
|
1135
1392
|
get editor() {
|
|
@@ -1323,6 +1580,25 @@ function inMemoryClipboard() {
|
|
|
1323
1580
|
}
|
|
1324
1581
|
};
|
|
1325
1582
|
}
|
|
1583
|
+
function inMemorySecretStore() {
|
|
1584
|
+
const byPlugin = /* @__PURE__ */ new Map();
|
|
1585
|
+
const dir = (id) => {
|
|
1586
|
+
let d = byPlugin.get(id);
|
|
1587
|
+
if (!d) byPlugin.set(id, d = /* @__PURE__ */ new Set());
|
|
1588
|
+
return d;
|
|
1589
|
+
};
|
|
1590
|
+
return {
|
|
1591
|
+
async set(id, ref, _secret) {
|
|
1592
|
+
dir(id).add(ref);
|
|
1593
|
+
},
|
|
1594
|
+
async exists(id, ref) {
|
|
1595
|
+
return dir(id).has(ref);
|
|
1596
|
+
},
|
|
1597
|
+
async forget(id, ref) {
|
|
1598
|
+
dir(id).delete(ref);
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1326
1602
|
var seqCounter = 1;
|
|
1327
1603
|
function makeEngineEditor(worker, recorder, onToolPreview) {
|
|
1328
1604
|
const protocol = worker.protocolVersion;
|
|
@@ -1486,11 +1762,13 @@ async function createHeadlessHost(options = {}) {
|
|
|
1486
1762
|
let disposed = false;
|
|
1487
1763
|
const blobStore = options.blobStore ?? inMemoryBlobStore();
|
|
1488
1764
|
const clipboard = options.clipboard ?? inMemoryClipboard();
|
|
1765
|
+
const secrets = options.secrets ?? inMemorySecretStore();
|
|
1489
1766
|
const buildHost = (manifest, mode) => createBundleHost(() => editor, manifest, {
|
|
1490
1767
|
console: options.console,
|
|
1491
1768
|
storage: options.storage,
|
|
1492
1769
|
blobStore,
|
|
1493
1770
|
clipboard,
|
|
1771
|
+
secrets,
|
|
1494
1772
|
capabilityMode: mode,
|
|
1495
1773
|
// W-06 — a recordable fake asset source the conformance harness
|
|
1496
1774
|
// can pass so a bundle's `@font-face` byte path is exercisable
|
|
@@ -1557,7 +1835,8 @@ async function createHeadlessHost(options = {}) {
|
|
|
1557
1835
|
document: { read: "broad", write: "broad" },
|
|
1558
1836
|
rendering: ["overlay", "hitTest", "sceneLayer"],
|
|
1559
1837
|
keybindings: true,
|
|
1560
|
-
storage: { blob: true }
|
|
1838
|
+
storage: { blob: true },
|
|
1839
|
+
secrets: { sources: true }
|
|
1561
1840
|
},
|
|
1562
1841
|
// Broad contribution declarations so the neutral DRIVER host (which
|
|
1563
1842
|
// registers arbitrary contributions directly in 'warn' mode) never
|
|
@@ -1915,6 +2194,7 @@ export {
|
|
|
1915
2194
|
PluginApiNotImplemented,
|
|
1916
2195
|
PluginCapabilityError,
|
|
1917
2196
|
WASM_BUDGETS,
|
|
2197
|
+
WORKER_BUDGETS,
|
|
1918
2198
|
beginPageDrag,
|
|
1919
2199
|
commitAndSelect,
|
|
1920
2200
|
contributeEditContext,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paged-media/plugin-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19-canary.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@paged-media/plugin-api": "0.2.
|
|
14
|
+
"@paged-media/plugin-api": "0.2.19-canary.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
17
|
"react": "^18.3.0"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@paged-media/canvas-wasm": "0.44.
|
|
25
|
+
"@paged-media/canvas-wasm": "0.44.1",
|
|
26
26
|
"@types/node": "20.19.39",
|
|
27
27
|
"@types/react": "^18.3.12",
|
|
28
28
|
"react": "^18.3.0",
|