dsh-plugin-shop 0.6.0 → 0.7.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.
@@ -151,7 +151,7 @@ export const TYPERT_REMOTE = {
151
151
  typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
152
152
  schema: dsh_plugin_shop_shop_catalog_result$schema,
153
153
  },
154
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":543,"column":9},
154
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":568,"column":9},
155
155
  },
156
156
  {
157
157
  id: 'dsh-plugin-shop#shop/installed',
@@ -166,7 +166,7 @@ export const TYPERT_REMOTE = {
166
166
  typeSymbol: 'dsh-plugin-shop#shop/installed:result',
167
167
  schema: dsh_plugin_shop_shop_installed_result$schema,
168
168
  },
169
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":723,"column":9},
169
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":748,"column":9},
170
170
  },
171
171
  {
172
172
  id: 'dsh-plugin-shop#shop/installStart',
@@ -192,7 +192,7 @@ export const TYPERT_REMOTE = {
192
192
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
193
193
  schema: dsh_plugin_shop_shop_installStart_result$schema,
194
194
  },
195
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":593,"column":9},
195
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":618,"column":9},
196
196
  },
197
197
  {
198
198
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -217,7 +217,7 @@ export const TYPERT_REMOTE = {
217
217
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
218
218
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
219
219
  },
220
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":712,"column":3},
220
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":737,"column":3},
221
221
  },
222
222
  {
223
223
  id: 'dsh-plugin-shop#shop/restart',
@@ -232,7 +232,7 @@ export const TYPERT_REMOTE = {
232
232
  typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
233
233
  schema: dsh_plugin_shop_shop_restart_result$schema,
234
234
  },
235
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":873,"column":9},
235
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":898,"column":9},
236
236
  },
237
237
  {
238
238
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -257,7 +257,7 @@ export const TYPERT_REMOTE = {
257
257
  typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
258
258
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
259
259
  },
260
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":460,"column":9},
260
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":466,"column":9},
261
261
  },
262
262
  {
263
263
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -283,7 +283,7 @@ export const TYPERT_REMOTE = {
283
283
  typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
284
284
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
285
285
  },
286
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":815,"column":9},
286
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":840,"column":9},
287
287
  },
288
288
  {
289
289
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -308,7 +308,7 @@ export const TYPERT_REMOTE = {
308
308
  typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
309
309
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
310
310
  },
311
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":933,"column":9},
311
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":958,"column":9},
312
312
  },
313
313
  {
314
314
  id: 'dsh-plugin-shop#shop/version',
@@ -323,7 +323,7 @@ export const TYPERT_REMOTE = {
323
323
  typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
324
324
  schema: dsh_plugin_shop_shop_version_result$schema,
325
325
  },
326
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":917,"column":9},
326
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":942,"column":9},
327
327
  },
328
328
  ],
329
329
  }
@@ -1,4 +1,5 @@
1
1
  /** Catalog fetch, verification, and disk cache — the Host's only network path. */
2
+ import { type CatalogOrigin } from './origin.ts';
2
3
  import type { CatalogEntry, DeniedEntry } from './types.ts';
3
4
  /** Highest schemaVersion this build understands; a higher one is refused (§10).
4
5
  * 3 adds `source` and repo entries (github install channel); 4 adds `subdir`
@@ -27,7 +28,11 @@ export interface CatalogFs {
27
28
  write: (path: string, data: string) => void;
28
29
  }
29
30
  export interface LoadCatalogOptions {
30
- baseUrl: string;
31
+ /** A single HTTP origin — the explicit-override spelling. Mutually
32
+ * exclusive with `origins`; exactly one must be given. */
33
+ baseUrl?: string;
34
+ /** Origins to race (design §3). */
35
+ origins?: CatalogOrigin[];
31
36
  cacheDir: string;
32
37
  refresh?: boolean;
33
38
  fetchImpl?: typeof fetch;
@@ -46,3 +51,16 @@ export interface LoadCatalogOptions {
46
51
  * included, degrades to no stars (spec §5).
47
52
  */
48
53
  export declare function loadCatalog(options: LoadCatalogOptions): Promise<CatalogResult>;
54
+ /** The catalog base the shipped `cordis.patch.yml` names. A row carrying
55
+ * exactly this value expresses no preference, so the loader races its
56
+ * defaults; anything else is a deliberate override and is used alone. */
57
+ export declare const DEFAULT_CATALOG_URL = "https://LivXue.github.io/dsh-plugin-shop/v1/";
58
+ /** The npm package carrying the same `v1/` tree (design §2). */
59
+ export declare const CATALOG_PACKAGE = "dsh-plugin-shop-catalog";
60
+ /**
61
+ * The origins to race for this installation (design §3).
62
+ *
63
+ * @param catalogUrl - the row's configured base.
64
+ * @param npmRegistry - the user's own registry from `~/.npmrc`, or null.
65
+ */
66
+ export declare function catalogOrigins(catalogUrl: string, fetchImpl: typeof fetch, npmRegistry: string | null): CatalogOrigin[];
@@ -238,6 +238,9 @@ export declare class ShopGateway extends TypertRemoteService {
238
238
  /** The install gate runs against the last loaded snapshot, never a fresh
239
239
  * fetch per request (§7.2: the Host's cached snapshot is the truth). */
240
240
  private lastSnapshot;
241
+ /** The origin list built for the last-seen `catalogUrl`, memoised so the
242
+ * user's npmrc is read at most once per gateway (see `originsFor`). */
243
+ private originCache;
241
244
  /** The incompatibility map already computed for `lastSnapshot`, keyed by
242
245
  * that snapshot's own object identity. Design §3 asks for the verdict
243
246
  * once per loaded snapshot, not once per RPC call: `loadCatalog` serves
@@ -294,6 +297,10 @@ export declare class ShopGateway extends TypertRemoteService {
294
297
  enabled: boolean;
295
298
  }): Promise<ShopSetEnabledResult>;
296
299
  private rowConfig;
300
+ /** The origins to race for this row's catalog. Read once per gateway: the
301
+ * user's npmrc does not change under a running dsh, and re-reading it on
302
+ * every catalog call would put a filesystem read on the hot path. */
303
+ private originsFor;
297
304
  /** The explicit restart override. Only the row's `config:` sub-object is
298
305
  * passed to a plugin — a top-level `allowRestart:` beside `name:` would be
299
306
  * silently ignored by the loader (dsh-market README, #227). */
@@ -0,0 +1,24 @@
1
+ /** The npm transport (design §2, §3): the catalog as a package.
2
+ *
3
+ * Shell — this and `origin.ts`'s fetch half are the only places the catalog
4
+ * loader touches the network. The payoff is measured, not assumed: the same
5
+ * bytes reach a China-side machine at 12.53 MB/s from npmmirror against
6
+ * 0.03 MB/s from GitHub Pages. */
7
+ import { type CatalogOrigin } from './origin.ts';
8
+ /** Normalise to a trailing slash so relative `URL` resolution against a
9
+ * registry that carries a path — every corporate registry, e.g.
10
+ * `https://artifactory.corp/api/npm/npm-repo` — keeps that path instead of
11
+ * eating its last segment; a host-root registry's trailing slash is already
12
+ * a no-op either way. Exported so `catalog.ts`'s dedupe compares against the
13
+ * same normalised form `npmOrigin` races on. */
14
+ export declare function normalizeRegistryUrl(url: string): string;
15
+ /**
16
+ * An origin that reads the catalog out of `<registryUrl>`'s copy of
17
+ * `<packageName>`.
18
+ *
19
+ * The probe is the abbreviated `latest` manifest — 13.5 KB against the live
20
+ * registry — so the race is decided without downloading anything large. The
21
+ * tarball is fetched lazily on the first `pointer()` or `file()` and kept on
22
+ * the handle, so one origin download serves the whole load.
23
+ */
24
+ export declare function npmOrigin(rawRegistryUrl: string, packageName: string, fetchImpl: typeof fetch): CatalogOrigin;
@@ -0,0 +1,23 @@
1
+ /** The user's configured npm registry, if they have one (design §3).
2
+ *
3
+ * Pure: the caller injects the read. This is a deliberately partial reading
4
+ * of npm's config resolution — only the user-level `registry=` line — and
5
+ * that is safe precisely because the origin list is raced: a registry we
6
+ * guess wrong about loses a 400-byte request and nothing else.
7
+ *
8
+ * That property only holds for a value that is actually a URL, which is why
9
+ * the value is VALIDATED here rather than left to the caller. `npmOrigin`
10
+ * addresses its probe with `new URL(<pkg>/latest, registryUrl)`, which
11
+ * throws a raw `TypeError` — not a `TransportError` — for anything that is
12
+ * not an absolute URL, and `catalog.ts`'s race loop rethrows everything that
13
+ * is not a `TransportError`. An unvalidated `registry=` line would therefore
14
+ * fail the WHOLE load with npmmirror, npmjs and Pages all healthy and no
15
+ * cache fallback: the opposite of the stated property. Not a hypothetical
16
+ * shape either — `registry=${NPM_REGISTRY}/` is npm's own documented config
17
+ * expansion, it works perfectly for npm, and a reader that does not expand
18
+ * it captures the literal. */
19
+ /**
20
+ * @param readFile - returns the file's text, or null when it does not exist.
21
+ * @param home - the user's home directory.
22
+ */
23
+ export declare function npmrcRegistry(readFile: (path: string) => string | null, home: string): string | null;
@@ -0,0 +1,40 @@
1
+ /** The transport seam under `loadCatalog` (design §3).
2
+ *
3
+ * An origin answers a cheap probe, then serves the pointer and the files the
4
+ * pointer names. HTTP and npm are interchangeable behind it, so every line of
5
+ * cache and validation logic in `catalog.ts` stays transport-blind. */
6
+ /** A failure of the link, not of the content: the wire threw, or answered
7
+ * non-2xx. This is the ONLY class `loadCatalog` retries on another origin.
8
+ * A bad schema, a sha mismatch, or a refused url is an interpretation
9
+ * failure and throws — masking a corrupt origin behind a healthy one is
10
+ * exactly the silent-wrongness this project refuses. */
11
+ export declare class TransportError extends Error {
12
+ constructor(message: string, options?: {
13
+ cause?: unknown;
14
+ });
15
+ }
16
+ export interface OriginHandle {
17
+ readonly id: string;
18
+ /** The pointer JSON text. Cheap: the probe has already paid for it. */
19
+ pointer: () => Promise<string>;
20
+ /** One file named by the pointer, by the pointer's own raw url string.
21
+ * Callers pass that string verbatim — never a basename, which would strip
22
+ * a hostile absolute url into a fetchable relative one. */
23
+ file: (url: string) => Promise<string>;
24
+ }
25
+ export interface CatalogOrigin {
26
+ readonly id: string;
27
+ /** Cheap reachability + identity request. Resolving means this origin can
28
+ * serve; the expensive work happens on the returned handle. */
29
+ probe: (signal: AbortSignal) => Promise<OriginHandle>;
30
+ }
31
+ /** Resolve the pointer's data URL against the catalog base. An absolute URL —
32
+ * any scheme, or a protocol-relative `//host/...` — would hand the pointer a
33
+ * fetch primitive to arbitrary hosts, so it is refused loudly before any
34
+ * fetch (§9.2). The guard is the resolved origin, not the raw string: WHATWG
35
+ * normalization strips leading whitespace and accepts backslash spellings
36
+ * before the string could be inspected, so only comparing the resolved URL's
37
+ * origin to the base's closes every spelling class. */
38
+ export declare function resolveDataUrl(baseUrl: string, url: string): string;
39
+ /** The transport this project has always used: a static `v1/` tree. */
40
+ export declare function httpOrigin(baseUrl: string, fetchImpl: typeof fetch): CatalogOrigin;
@@ -0,0 +1,41 @@
1
+ /** Settle a set of promises in the order they finish (design §3).
2
+ *
3
+ * Pure and timer-free. `Promise.any` would give only the first success and
4
+ * discard the rest; the origin race needs the losers too, in order, so a
5
+ * probe that "won" but then fails to produce a pointer can fall through to
6
+ * the next-finishing probe instead of failing the whole load — this is how
7
+ * an npm origin's bulk tarball download, which happens inside `pointer()`,
8
+ * gets a fall-through on failure.
9
+ *
10
+ * That is deliberately narrower than "any bulk fetch falls through to
11
+ * another origin." This module only ever wraps `probe()` promises; once
12
+ * `catalog.ts` has committed to a handle and calls `file()` for the data
13
+ * file, that call happens outside this generator entirely, and a
14
+ * TransportError there falls back to the disk cache instead (see
15
+ * `catalog.ts`'s `cachedOrThrow`), never back into a race. Do not "fix"
16
+ * that path to match this comment — the split is intentional. */
17
+ export type Settled<T> = {
18
+ index: number;
19
+ value: T;
20
+ } | {
21
+ index: number;
22
+ reason: unknown;
23
+ };
24
+ /**
25
+ * Yield each promise's outcome as it settles, tagged with its argument index.
26
+ *
27
+ * Deliberately NOT an `async function*`. Two properties depend on that:
28
+ *
29
+ * 1. **Handlers attach synchronously, at call time.** An async generator's
30
+ * body does not run until its first `next()`, so wiring the handlers
31
+ * inside one would leave a rejection unhandled for as long as the caller
32
+ * waits before iterating — which crashes the process under Node's default
33
+ * unhandled-rejection policy.
34
+ * 2. **Order is recorded when each promise settles**, not when a consumer
35
+ * asks. Re-racing the survivors on every turn tie-breaks on argument
36
+ * order instead: `Promise.race` over promises that are ALREADY settled
37
+ * resolves with the first in iteration order, not the first to have
38
+ * settled — and a consumer doing any work between yields, which is
39
+ * exactly this module's use case, is what lets two settle inside one turn.
40
+ */
41
+ export declare function inCompletionOrder<T>(promises: readonly Promise<T>[]): AsyncGenerator<Settled<T>>;
@@ -0,0 +1,13 @@
1
+ /** A read-only ustar parser: the npm transport's only way into a tarball.
2
+ *
3
+ * Pure — bytes in, a path-to-bytes map out. It handles exactly what `npm
4
+ * pack` emits and refuses everything else loudly, because the alternative to
5
+ * a small strict reader is a fourth runtime dependency (design §4). */
6
+ /**
7
+ * Parse an uncompressed tar archive into path → bytes.
8
+ *
9
+ * Directory entries and every non-regular type (symlinks, pax and GNU
10
+ * extension headers) are skipped: npm packs regular files under `package/`,
11
+ * and a catalog tarball that needs anything else is not one we published.
12
+ */
13
+ export declare function readTar(buffer: Buffer): Map<string, Buffer>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "repository": {
6
6
  "type": "git",