@hoardodile/host 0.0.0 → 0.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoardodile/host",
3
- "version": "0.0.0",
3
+ "version": "0.1.1",
4
4
  "license": "MIT",
5
5
  "description": "hoardodile plugin runtime host: sandbox, hook strategy, ResourceAPI, containers and the test runner.",
6
6
  "keywords": [
@@ -67,11 +67,11 @@
67
67
  "file-type": "^22.0.2",
68
68
  "yauzl": "^3.4.0",
69
69
  "yazl": "^3.3.1",
70
- "@hoardodile/sdk-types": "0.0.0"
70
+ "@hoardodile/sdk-types": "0.1.1"
71
71
  },
72
72
  "optionalDependencies": {
73
73
  "@derhuerst/ffprobe-static": "^5.3.0",
74
- "@hoardodile/7z-bin": "^1.1.0",
74
+ "@hoardodile/7z-bin": "^1.1.3",
75
75
  "ffmpeg-static": "^5.3.0"
76
76
  },
77
77
  "peerDependencies": {
@@ -1,6 +1,7 @@
1
1
  // Sandbox fixture: exercises the plugin asset API within detect — reads
2
- // the vault stat, then performs a consent-gated download. Used to verify
3
- // the manifest permission gate and the wired plugin asset handler.
2
+ // the vault stat, performs a consent-gated single download and a batch
3
+ // download. Used to verify the manifest permission gate and the wired
4
+ // plugin asset handler (single in → single out, array in → array out).
4
5
  export default {
5
6
  detect: async (api) => {
6
7
  const stat = await api.statAsset("runtime/a.mjs")
@@ -8,6 +9,10 @@ export default {
8
9
  url: "https://example.com/runtime/a.mjs",
9
10
  dest: "runtime/a.mjs",
10
11
  })
11
- return { ok: true, stat, downloaded }
12
+ const batched = await api.download([
13
+ { url: "https://example.com/runtime/b.mjs", dest: "runtime/b.mjs" },
14
+ { url: "https://example.com/runtime/c.mjs", dest: "runtime/c.mjs" },
15
+ ])
16
+ return { ok: true, stat, downloaded, batched }
12
17
  },
13
18
  }
@@ -548,12 +548,13 @@ describe("plugin sandbox", () => {
548
548
  pluginAssets: {
549
549
  download: async (pluginId, request) => {
550
550
  seen.push([pluginId, request])
551
- return {
552
- path: request.dest,
551
+ const requests = Array.isArray(request) ? request : [request]
552
+ return requests.map((req) => ({
553
+ path: req.dest,
553
554
  sizeBytes: 3,
554
555
  sha256: "a".repeat(64),
555
556
  cached: false,
556
- }
557
+ }))
557
558
  },
558
559
  statAsset: async (pluginId, path) => {
559
560
  seen.push([pluginId, path])
@@ -574,12 +575,27 @@ describe("plugin sandbox", () => {
574
575
  await expect(plugin.detect(createStubApi())).resolves.toEqual({
575
576
  ok: true,
576
577
  stat: undefined,
578
+ // Single in → single result out, batch in → array out.
577
579
  downloaded: {
578
580
  path: "runtime/a.mjs",
579
581
  sizeBytes: 3,
580
582
  sha256: "a".repeat(64),
581
583
  cached: false,
582
584
  },
585
+ batched: [
586
+ {
587
+ path: "runtime/b.mjs",
588
+ sizeBytes: 3,
589
+ sha256: "a".repeat(64),
590
+ cached: false,
591
+ },
592
+ {
593
+ path: "runtime/c.mjs",
594
+ sizeBytes: 3,
595
+ sha256: "a".repeat(64),
596
+ cached: false,
597
+ },
598
+ ],
583
599
  })
584
600
  expect(seen[0]).toEqual(["asset-allowed", "runtime/a.mjs"])
585
601
  expect(seen[1]).toEqual([
@@ -589,6 +605,13 @@ describe("plugin sandbox", () => {
589
605
  dest: "runtime/a.mjs",
590
606
  },
591
607
  ])
608
+ expect(seen[2]).toEqual([
609
+ "asset-allowed",
610
+ [
611
+ { url: "https://example.com/runtime/b.mjs", dest: "runtime/b.mjs" },
612
+ { url: "https://example.com/runtime/c.mjs", dest: "runtime/c.mjs" },
613
+ ],
614
+ ])
592
615
  })
593
616
 
594
617
  test("a log flood exceeds the per-hook budget and fails the hook", async () => {
@@ -33,10 +33,15 @@ import { createSandboxedPlugin } from "./sandboxed-plugin.ts"
33
33
  * workbench) omit it and the methods answer `UNAVAILABLE`.
34
34
  */
35
35
  export type PluginAssetHandler = {
36
+ /**
37
+ * Download one request or a batch of requests. Answers with the
38
+ * result array (batch shape) — this adapter unwraps the single
39
+ * result for a single request (see {@link dispatchApi}).
40
+ */
36
41
  readonly download: (
37
42
  pluginId: string,
38
- request: PluginDownloadRequest,
39
- ) => Promise<PluginDownloadResult>
43
+ request: PluginDownloadRequest | readonly PluginDownloadRequest[],
44
+ ) => Promise<readonly PluginDownloadResult[]>
40
45
  readonly statAsset: (
41
46
  pluginId: string,
42
47
  path: string,
@@ -804,13 +809,15 @@ export function createPluginSandbox(
804
809
  return
805
810
  }
806
811
  // RPC boundary: the child-side proxy forwards args verbatim,
807
- // so the contract order is guaranteed (request object for
808
- // `download`, string path for the rest).
812
+ // so the contract order is guaranteed (request object or
813
+ // request array for `download`, string path for the rest).
809
814
  if (method === "download") {
810
- respond(
811
- true,
812
- await handler.download(state.id, args[0] as PluginDownloadRequest),
813
- )
815
+ const request = args[0] as
816
+ | PluginDownloadRequest
817
+ | readonly PluginDownloadRequest[]
818
+ const results = await handler.download(state.id, request)
819
+ // Wire rule: array in → array out, single in → single out.
820
+ respond(true, Array.isArray(request) ? results : results[0])
814
821
  } else if (method === "statAsset") {
815
822
  respond(true, await handler.statAsset(state.id, args[0] as string))
816
823
  } else if (method === "readAsset") {
@@ -153,10 +153,10 @@ function toFileUrl(path) {
153
153
  return pathToFileURL(path).href
154
154
  }
155
155
 
156
- const pluginDirPrefix = normalizePath(toFileUrl(pluginDir) + "/")
156
+ const pluginDirPrefix = normalizePath(`${toFileUrl(pluginDir)}/`)
157
157
  const assetVaultPrefix =
158
158
  typeof assetVaultDir === "string" && assetVaultDir.length > 0
159
- ? normalizePath(toFileUrl(assetVaultDir) + "/")
159
+ ? normalizePath(`${toFileUrl(assetVaultDir)}/`)
160
160
  : undefined
161
161
  const entryUrl = normalizePath(import.meta.url)
162
162