@ggui-ai/registry-core 0.1.0-rc.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.
Files changed (60) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +76 -0
  3. package/dist/impls/memory-bundle-storage.d.ts +12 -0
  4. package/dist/impls/memory-bundle-storage.d.ts.map +1 -0
  5. package/dist/impls/memory-bundle-storage.js +40 -0
  6. package/dist/impls/memory-registry-storage.d.ts +3 -0
  7. package/dist/impls/memory-registry-storage.d.ts.map +1 -0
  8. package/dist/impls/memory-registry-storage.js +154 -0
  9. package/dist/index.d.ts +26 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +26 -0
  12. package/dist/interfaces/authn.d.ts +51 -0
  13. package/dist/interfaces/authn.d.ts.map +1 -0
  14. package/dist/interfaces/authn.js +1 -0
  15. package/dist/interfaces/bundle-storage.d.ts +79 -0
  16. package/dist/interfaces/bundle-storage.d.ts.map +1 -0
  17. package/dist/interfaces/bundle-storage.js +1 -0
  18. package/dist/interfaces/registry-storage.d.ts +204 -0
  19. package/dist/interfaces/registry-storage.d.ts.map +1 -0
  20. package/dist/interfaces/registry-storage.js +19 -0
  21. package/dist/ops/compile.d.ts +48 -0
  22. package/dist/ops/compile.d.ts.map +1 -0
  23. package/dist/ops/compile.js +163 -0
  24. package/dist/ops/conformance.d.ts +154 -0
  25. package/dist/ops/conformance.d.ts.map +1 -0
  26. package/dist/ops/conformance.js +487 -0
  27. package/dist/ops/list-versions.d.ts +58 -0
  28. package/dist/ops/list-versions.d.ts.map +1 -0
  29. package/dist/ops/list-versions.js +60 -0
  30. package/dist/ops/publish.d.ts +70 -0
  31. package/dist/ops/publish.d.ts.map +1 -0
  32. package/dist/ops/publish.js +417 -0
  33. package/dist/ops/read.d.ts +52 -0
  34. package/dist/ops/read.d.ts.map +1 -0
  35. package/dist/ops/read.js +60 -0
  36. package/dist/ops/register-author-key.d.ts +22 -0
  37. package/dist/ops/register-author-key.d.ts.map +1 -0
  38. package/dist/ops/register-author-key.js +171 -0
  39. package/dist/ops/search.d.ts +50 -0
  40. package/dist/ops/search.d.ts.map +1 -0
  41. package/dist/ops/search.js +106 -0
  42. package/dist/testing/bundle-storage-contract.d.ts +3 -0
  43. package/dist/testing/bundle-storage-contract.d.ts.map +1 -0
  44. package/dist/testing/bundle-storage-contract.js +176 -0
  45. package/dist/testing/index.d.ts +10 -0
  46. package/dist/testing/index.d.ts.map +1 -0
  47. package/dist/testing/index.js +9 -0
  48. package/dist/testing/registry-storage-contract.d.ts +3 -0
  49. package/dist/testing/registry-storage-contract.d.ts.map +1 -0
  50. package/dist/testing/registry-storage-contract.js +392 -0
  51. package/dist/types.d.ts +482 -0
  52. package/dist/types.d.ts.map +1 -0
  53. package/dist/types.js +110 -0
  54. package/dist/utils/base64.d.ts +15 -0
  55. package/dist/utils/base64.d.ts.map +1 -0
  56. package/dist/utils/base64.js +35 -0
  57. package/dist/utils/semver.d.ts +13 -0
  58. package/dist/utils/semver.d.ts.map +1 -0
  59. package/dist/utils/semver.js +69 -0
  60. package/package.json +85 -0
@@ -0,0 +1,482 @@
1
+ /**
2
+ * Row + wire shapes for the ggui marketplace registry. The same
3
+ * shapes back both this open-source server and the hosted registry,
4
+ * so they project against one definition.
5
+ *
6
+ * Rows are the storage-layer projection — what {@link RegistryStorage}
7
+ * implementations read/write. Wire shapes are the public HTTP response
8
+ * surface — what the registry exposes to CLI clients. The two are
9
+ * deliberately separate: the row carries fields the wire never emits
10
+ * (`yanked`, `sk`) and the wire flattens fields the row stores nested.
11
+ *
12
+ * The umbrella noun is `artifact` — the registry stores BOTH gadgets
13
+ * AND blueprints under one row family, with `kind: 'gadget' |
14
+ * 'blueprint'` as the discriminator. The field name `artifactId`
15
+ * matches what the field actually contains.
16
+ */
17
+ import type { ArtifactManifest } from '@ggui-ai/artifact-manifest';
18
+ import type { GadgetSignature } from '@ggui-ai/gadget-signing';
19
+ import type { ConformanceFailureCode } from './ops/conformance.js';
20
+ /**
21
+ * SK literal for the per-artifactId metadata row on the cloud's
22
+ * Artifacts table. Pinned as a constant so cloud + OSS impls agree
23
+ * on the SK.
24
+ *
25
+ * OSS impls (filesystem / memory) MAY ignore this — they have one row
26
+ * per artifactId so no SK is needed. Cloud DDB impls MUST honor it.
27
+ */
28
+ export declare const ARTIFACTS_METADATA_SK: "metadata#";
29
+ /**
30
+ * Visibility — public artifacts are sigstore-signed + listable; private
31
+ * artifacts are Ed25519-signed + scoped to the publisher org. Matches
32
+ * `ArtifactVisibilitySchema` in `@ggui-ai/artifact-manifest`.
33
+ */
34
+ export type Visibility = 'public' | 'private';
35
+ /**
36
+ * Artifact discriminator. Matches `kind` on every artifact manifest.
37
+ */
38
+ export type ArtifactKind = 'gadget' | 'blueprint';
39
+ /**
40
+ * Per-artifactId metadata row. One row per `<scope>/<name>`, regardless
41
+ * of how many versions exist. Updated on every publish to point at the
42
+ * new latest. `/search` scans only this row family — versions-table
43
+ * scans would explode the search domain.
44
+ *
45
+ * `artifactId` is the umbrella noun: the registry stores gadgets +
46
+ * blueprints under one row family, and `kind` distinguishes them.
47
+ */
48
+ export interface ArtifactsMetadataRow {
49
+ readonly artifactId: string;
50
+ readonly sk: typeof ARTIFACTS_METADATA_SK;
51
+ readonly kind: ArtifactKind;
52
+ readonly latestVersion: string;
53
+ readonly description?: string;
54
+ readonly tags?: readonly string[];
55
+ readonly visibility: Visibility;
56
+ readonly hook?: string;
57
+ readonly authorName?: string;
58
+ readonly publishedAt: string;
59
+ readonly publishedBy: string;
60
+ }
61
+ /**
62
+ * Per-version row. One row per published version. The manifest is
63
+ * stored verbatim (post-parse) plus the fetchable URLs + signing
64
+ * metadata. Yanks set `yanked: true` — the row stays so the URL keeps
65
+ * resolving with a 410 for audit. Hard-delete is intentionally NOT
66
+ * supported at MVP.
67
+ *
68
+ * Blueprint versions point at a {@link CompiledBlobRow} via
69
+ * `compiledDigest`. The blob row carries the compiled JS bytes
70
+ * (esbuild output) keyed by `sha256(compiledBytes)`. Raw TSX is
71
+ * preserved on `manifest.source` for audit / future-recompile;
72
+ * compiled JS lives only in the blob layer. Two-layer storage gives
73
+ * dedup (same TSX → same digest → single blob row) and federation-
74
+ * ready content-addressable identity.
75
+ */
76
+ export interface ArtifactVersionRow {
77
+ readonly artifactId: string;
78
+ readonly version: string;
79
+ readonly manifest: ArtifactManifest;
80
+ readonly kind: ArtifactKind;
81
+ readonly visibility: Visibility;
82
+ readonly bundleUrl?: string;
83
+ readonly bundleSri?: string;
84
+ readonly signatureUrl?: string;
85
+ /**
86
+ * Pointer into the {@link CompiledBlobRow} table. Set on blueprint
87
+ * publish; absent on gadget rows (gadgets ship via `bundleUrl`).
88
+ * Hex-encoded SHA-256 of the compiled JS bytes.
89
+ *
90
+ * Resolution invariant: a non-null `compiledDigest` on a published
91
+ * blueprint row MUST resolve to a {@link CompiledBlobRow} via
92
+ * {@link RegistryStorage.getCompiledBlob}. A missing blob row when
93
+ * the pointer is set is a CRITICAL storage-layer inconsistency —
94
+ * `fetchAndVerifyBlueprint` (cloud) and `runArtifactInstall` (OSS)
95
+ * raise immediately rather than fall back.
96
+ */
97
+ readonly compiledDigest?: string;
98
+ /**
99
+ * Base64-encoded raw Ed25519 public key bytes (32 bytes). Pinned at
100
+ * publish time so a subsequent key rotation in {@link AuthorKeyRow}
101
+ * does NOT invalidate historical versions.
102
+ */
103
+ readonly authorPublicKey?: string;
104
+ readonly publishedAt: string;
105
+ readonly publishedBy: string;
106
+ readonly yanked?: boolean;
107
+ }
108
+ /**
109
+ * Content-addressed compiled-bytes row — the storage layer for a
110
+ * blueprint's compiled JS (esbuild output of its TSX source).
111
+ *
112
+ * Stored in a separate table keyed by `compiledDigest = sha256(bytes)`.
113
+ * Multiple {@link ArtifactVersionRow} entries can point at the SAME
114
+ * `compiledDigest` — two publishes of byte-identical compiled output
115
+ * dedup to one blob row with `refCount` tracking pointer count. The
116
+ * deterministic-compile contract (`compile(source) → sha256(bytes)`
117
+ * is a pure function pinned to a frozen esbuild config) makes dedup
118
+ * load-bearing: federation-ready, cross-app cache sharing, and
119
+ * sigstore signing all key off `compiledDigest`.
120
+ *
121
+ * **Storage choice — inline base64 vs S3 pointer.** The compiled
122
+ * bytes are inlined as a base64-encoded string column. Rationale:
123
+ * blueprints today ship raw TSX inline on `manifest.source` (capped
124
+ * at 5 MiB by `MAX_BLUEPRINT_SOURCE_BYTES`); compiled JS is typically
125
+ * 1.5–3× the TSX size. DynamoDB's 400-KiB item ceiling caps practical
126
+ * inline storage around ~300 KiB compiled (~200 KiB TSX), which
127
+ * accommodates every realistic blueprint observed so far. Larger
128
+ * blueprints (and gadgets, which always exceed this) will need an
129
+ * S3-pointer variant as a future enhancement. Inline storage keeps
130
+ * the two-layer write/read contract simple, without the bucket-
131
+ * lifecycle and IAM-grant overhead of S3.
132
+ *
133
+ * **GC.** `refCount` is maintained on publish (+1) and yank (no
134
+ * decrement — yanked versions retain their pointer for audit;
135
+ * permanent delete would decrement). A dedicated reaper is a future
136
+ * enhancement; the column is reserved so it can land later without a
137
+ * row migration. Today no row ever drops to `refCount=0`, so the
138
+ * absence of a reaper is observable only via storage growth.
139
+ *
140
+ * **Signatures.** `manifestSig` is the publisher's signature
141
+ * (Ed25519 for private artifacts, sigstore-cosign for public) over
142
+ * the manifest's canonical-JSON projection — read by the hosted
143
+ * install path for defense-in-depth re-verification. Stored as a
144
+ * JSON-encoded `GadgetSignature` discriminated union. `compiledSig`
145
+ * (a registry-rooted sigstore signature over `compiledBytes`) is
146
+ * reserved for a future enhancement.
147
+ */
148
+ export interface CompiledBlobRow {
149
+ /** PK. Lowercase hex SHA-256 of `compiledBytes` (64 chars). */
150
+ readonly compiledDigest: string;
151
+ /** Base64-encoded compiled JS bytes (esbuild output). */
152
+ readonly compiledBytes: string;
153
+ /** Decoded byte length — convenience for size checks without re-decoding. */
154
+ readonly compiledSize: number;
155
+ /**
156
+ * Pointer count from {@link ArtifactVersionRow.compiledDigest}.
157
+ * Maintained on publish; reserved for a future GC reaper.
158
+ */
159
+ readonly refCount: number;
160
+ /**
161
+ * Publisher's signature envelope, JSON-encoded `GadgetSignature`
162
+ * discriminated union (Ed25519 for private, sigstore-cosign for
163
+ * public). Read by the hosted install path for defense-in-depth
164
+ * re-verification before persisting an installed blueprint. May be
165
+ * absent on rows published before signature persistence existed;
166
+ * the install path hard-fails such rows with a clear error pointing
167
+ * at this column.
168
+ */
169
+ readonly manifestSig?: string;
170
+ /**
171
+ * Reserved — a registry-rooted sigstore signature over
172
+ * `compiledBytes`, planned for a future enhancement. When shipped,
173
+ * the install-time verifier's full anchor is:
174
+ * `verify(manifestSig, manifest) AND verify(compiledSig, bytes) AND
175
+ * sha256(bytes) === compiledDigest`.
176
+ */
177
+ readonly compiledSig?: string;
178
+ /** ISO timestamp set on first-write of the blob row. */
179
+ readonly createdAt: string;
180
+ }
181
+ /**
182
+ * Author signing-key row. One per `(subject, keyId)` pair. The key is
183
+ * stored base64 — verification re-decodes + invokes
184
+ * {@link verifyBundleEd25519}.
185
+ */
186
+ export interface AuthorKeyRow {
187
+ readonly subject: string;
188
+ readonly keyId: string;
189
+ readonly publicKeyBase64: string;
190
+ }
191
+ /**
192
+ * `GET /pkg/{scope}/{name}/{version}` body.
193
+ *
194
+ * `manifest` is always present (even on 410 Gone — yanked versions
195
+ * still return the manifest for audit). `bundleUrl` / `bundleSri` /
196
+ * `signatureUrl` are gadget-only.
197
+ *
198
+ * Blueprints expose their canonical compiled JS bytes via
199
+ * `compiledBytes` (base64) + `compiledDigest` (hex SHA-256). The raw
200
+ * TSX is on `manifest.source` for audit / recompile; install paths
201
+ * MUST use `compiledBytes` — the registry is the trust boundary for
202
+ * the compile step. `compiledDigest` doubles as the cache key for
203
+ * cross-app sharing and federation.
204
+ */
205
+ export interface ReadPkgResponse {
206
+ readonly manifest: ArtifactManifest;
207
+ readonly bundleUrl?: string;
208
+ readonly bundleSri?: string;
209
+ readonly signatureUrl?: string;
210
+ /** Hex SHA-256 of the compiled bytes — present on blueprint reads. */
211
+ readonly compiledDigest?: string;
212
+ /** Base64 compiled JS bytes — present on blueprint reads. */
213
+ readonly compiledBytes?: string;
214
+ readonly authorPublicKey?: string;
215
+ readonly publishedAt: string;
216
+ readonly publishedBy: string;
217
+ }
218
+ /**
219
+ * One row of `GET /search?…` results — a lightweight per-artifactId
220
+ * summary intentionally narrower than the full read response so the
221
+ * CLI list view stays cheap.
222
+ */
223
+ export interface SearchResultEntry {
224
+ readonly artifactId: string;
225
+ readonly latestVersion: string;
226
+ readonly kind: ArtifactKind;
227
+ readonly description?: string;
228
+ readonly tags?: readonly string[];
229
+ readonly publishedAt: string;
230
+ }
231
+ /**
232
+ * `GET /search?…` body. `nextCursor` is opaque (impl-defined: cloud
233
+ * uses base64-encoded DDB LastEvaluatedKey; OSS uses an offset). Clients
234
+ * roundtrip it verbatim on the next call.
235
+ */
236
+ export interface SearchResponse {
237
+ readonly results: readonly SearchResultEntry[];
238
+ readonly nextCursor?: string;
239
+ }
240
+ /**
241
+ * `GET /search` sort options. Supports a registry-web "Recent" view
242
+ * that needs `publishedAt`-DESC ordering.
243
+ *
244
+ * - `recent` — order by {@link ArtifactsMetadataRow.publishedAt} DESC.
245
+ *
246
+ * Default (sort omitted) is impl-defined ordering: memory uses insertion
247
+ * order, filesystem uses directory order, DDB uses Scan order. Callers
248
+ * MUST opt in to a deterministic order via this field.
249
+ *
250
+ * **Scale ceiling.** The `recent` sort is implemented as an in-memory
251
+ * pass on the page returned by {@link RegistryStorage.scanArtifacts}.
252
+ * For pre-launch row counts (< ~1k artifacts, single-page scans with
253
+ * `limit=200`) this gives a globally-correct order. Once the artifact
254
+ * table grows past one Scan page (~1 MiB of items), the order is only
255
+ * page-local — clients paginating with `cursor` see per-page recency,
256
+ * not global. A DDB GSI on `publishedAt` is the proper fix and is
257
+ * planned as a follow-up.
258
+ */
259
+ export declare const SEARCH_SORT_OPTIONS: readonly ["recent"];
260
+ export type SearchSort = (typeof SEARCH_SORT_OPTIONS)[number];
261
+ /**
262
+ * One row of `GET /pkg/{scope}/{name}` list-versions results — backs
263
+ * a package detail page's version timeline. Lightweight by design:
264
+ * drops manifest, compiledDigest, manifestSig, etc. — clients hit
265
+ * `/pkg/.../:version` for the full row.
266
+ *
267
+ * - `version` — the semver string. UNIQUE per (artifactId, version).
268
+ * - `publishedAt` — ISO timestamp of the original publish.
269
+ * - `yanked` — true ⇔ the version row carries `yanked: true`. UI
270
+ * should render yanked rows with a strikethrough +
271
+ * warning rather than hide them.
272
+ * - `kind` — gadget | blueprint (matches the metadata row's
273
+ * kind; carried per-version so cross-version kind
274
+ * drift would surface in the UI).
275
+ * - `visibility` — public | private. Private rows are filtered out
276
+ * for unauthenticated callers at the op layer.
277
+ */
278
+ export interface VersionListEntry {
279
+ readonly version: string;
280
+ readonly publishedAt: string;
281
+ readonly yanked: boolean;
282
+ readonly kind: ArtifactKind;
283
+ readonly visibility: Visibility;
284
+ }
285
+ /**
286
+ * `GET /pkg/{scope}/{name}` body.
287
+ *
288
+ * - `artifactId` — echoed so a single-row consumer doesn't have to
289
+ * reconstruct from URL params.
290
+ * - `versions` — semver-DESC list of {@link VersionListEntry}.
291
+ * Latest first.
292
+ *
293
+ * No pagination cursor — registry artifact version counts are
294
+ * inherently bounded (typical: < 50 versions per artifact, hard ceiling
295
+ * around ~1k for the lifetime of an artifact). If a single artifact ever
296
+ * crosses the 1 MiB DDB Query page, we'll add a cursor — but for now the
297
+ * round-trip cost of paginating versions is greater than the cost of
298
+ * returning them all.
299
+ */
300
+ export interface ListVersionsResponse {
301
+ readonly artifactId: string;
302
+ readonly versions: readonly VersionListEntry[];
303
+ }
304
+ /**
305
+ * `POST /publish` request body.
306
+ */
307
+ export interface PublishRequestBody {
308
+ readonly manifest: unknown;
309
+ readonly bundle?: string;
310
+ readonly bundleSha384?: string;
311
+ readonly signature: GadgetSignature;
312
+ }
313
+ /**
314
+ * 201 response on a successful publish. `installCommand` is the exact
315
+ * shell command to install the artifact — the registry hostname is
316
+ * threaded through {@link PublishArtifactDeps.registryHostname}
317
+ * so each deployment issues an environment-appropriate command.
318
+ */
319
+ export interface PublishResponseBody {
320
+ readonly artifactId: string;
321
+ readonly version: string;
322
+ readonly manifestUrl: string;
323
+ readonly bundleUrl?: string;
324
+ readonly signatureUrl?: string;
325
+ readonly installCommand: string;
326
+ }
327
+ /**
328
+ * Locked publish error codes. Strings are the wire contract — the
329
+ * publish CLI matches on these for human-readable rendering. The
330
+ * `as const` tuple is the value-level source of truth; the
331
+ * {@link PublishErrorCode} type is derived from it so a single edit
332
+ * propagates to both the wire and the runtime membership check used
333
+ * by downstream guards.
334
+ */
335
+ export declare const PUBLISH_ERROR_CODES: readonly ["unauthorized", "manifest_invalid", "bundle_required", "bundle_too_large", "conformance_failed", "bundle_hash_mismatch", "unknown_key", "signature_invalid", "version_exists", "internal"];
336
+ export type PublishErrorCode = (typeof PUBLISH_ERROR_CODES)[number];
337
+ /**
338
+ * Closed enum for `GET /pkg/:scope/:name[/version]` read responses.
339
+ *
340
+ * `yanked` is the 410-Gone path — the version exists in metadata but
341
+ * the publisher revoked it. Clients SHOULD treat this as a hard
342
+ * failure (don't fall back to a different version automatically).
343
+ */
344
+ export declare const READ_ERROR_CODES: readonly ["not_found", "forbidden", "invalid_request", "yanked", "server_error"];
345
+ export type ReadErrorCode = (typeof READ_ERROR_CODES)[number];
346
+ /**
347
+ * Closed enum for `GET /search` responses. Same posture as
348
+ * {@link ReadErrorCode} minus the `yanked` path (search filters
349
+ * yanked rows out of the result set; it never surfaces as a top-level
350
+ * error).
351
+ */
352
+ export declare const SEARCH_ERROR_CODES: readonly ["forbidden", "invalid_request", "server_error"];
353
+ export type SearchErrorCode = (typeof SEARCH_ERROR_CODES)[number];
354
+ /**
355
+ * Closed enum for `POST /author-keys` register responses. An operator
356
+ * registers a per-scope Ed25519 public key with the registry so
357
+ * future `POST /publish` calls signed by the matching private key
358
+ * validate against an `AuthorKeyRow`.
359
+ *
360
+ * - `unauthorized` — missing or invalid caller credentials.
361
+ * - `invalid_request` — body missing `publicKeyBase64`, malformed
362
+ * base64, or wrong byte length.
363
+ * - `key_conflict` — a row already exists for `(subject, keyId)`
364
+ * with a different `publicKeyBase64`. Same
365
+ * publicKey is treated as idempotent → 200.
366
+ * - `server_error` — unexpected adapter failure.
367
+ */
368
+ export declare const REGISTER_AUTHOR_KEY_ERROR_CODES: readonly ["unauthorized", "invalid_request", "key_conflict", "server_error"];
369
+ export type RegisterAuthorKeyErrorCode = (typeof REGISTER_AUTHOR_KEY_ERROR_CODES)[number];
370
+ /**
371
+ * `POST /author-keys` request body. The subject is derived from the
372
+ * verified caller credentials (not the body) — the operator can only
373
+ * register keys under their own identity. `keyId` is derived
374
+ * deterministically from
375
+ * the public key bytes via `derivePublicKeyId` (gadget-signing) — not
376
+ * caller-supplied — so two clients registering the same public key
377
+ * produce the same row.
378
+ */
379
+ export interface RegisterAuthorKeyRequestBody {
380
+ readonly publicKeyBase64: string;
381
+ }
382
+ /**
383
+ * `POST /author-keys` 200/201 response. Echoes the stored row so the
384
+ * CLI can confirm the (subject, keyId) tuple the registry now knows.
385
+ * 201 on first-write; 200 on idempotent re-register of the same row.
386
+ */
387
+ export interface RegisterAuthorKeyResponseBody {
388
+ readonly subject: string;
389
+ readonly keyId: string;
390
+ readonly publicKeyBase64: string;
391
+ }
392
+ /**
393
+ * `POST /author-keys` error body — same shape as the read/search
394
+ * error bodies; `error` is narrowed to the closed enum.
395
+ */
396
+ export interface RegisterAuthorKeyErrorBody {
397
+ readonly error: RegisterAuthorKeyErrorCode;
398
+ readonly message: string;
399
+ readonly detail?: unknown;
400
+ }
401
+ /**
402
+ * Publish-endpoint error body. `error` is narrowed to
403
+ * {@link PublishErrorCode} so consumers get autocomplete +
404
+ * exhaustiveness; `conformanceFailureCode` is hoisted to the TOP of
405
+ * the body (alongside `error: 'conformance_failed'`) so callers can
406
+ * branch without parsing nested JSON. The full per-error list stays
407
+ * in `detail.errors` for verbose rendering.
408
+ */
409
+ export interface PublishErrorBody {
410
+ readonly error: PublishErrorCode;
411
+ readonly message: string;
412
+ /**
413
+ * Sub-discriminator for `error: 'conformance_failed'` — the code of
414
+ * the FIRST conformance failure (the list is in `detail.errors`).
415
+ * Omitted for every other `error` value.
416
+ */
417
+ readonly conformanceFailureCode?: ConformanceFailureCode;
418
+ readonly detail?: unknown;
419
+ }
420
+ /**
421
+ * Read-endpoint error body. `error` narrowed to {@link ReadErrorCode}.
422
+ */
423
+ export interface ReadErrorBody {
424
+ readonly error: ReadErrorCode;
425
+ readonly message: string;
426
+ readonly detail?: unknown;
427
+ }
428
+ /**
429
+ * Search-endpoint error body. `error` narrowed to {@link SearchErrorCode}.
430
+ */
431
+ export interface SearchErrorBody {
432
+ readonly error: SearchErrorCode;
433
+ readonly message: string;
434
+ readonly detail?: unknown;
435
+ }
436
+ /**
437
+ * Discriminated union of every registry error body. Use the
438
+ * per-endpoint shapes ({@link PublishErrorBody} / {@link ReadErrorBody}
439
+ * / {@link SearchErrorBody}) at op + handler seams; this union exists
440
+ * for callers that touch more than one endpoint (e.g. a transport-layer
441
+ * logger).
442
+ */
443
+ export type ErrorBody = PublishErrorBody | ReadErrorBody | SearchErrorBody | RegisterAuthorKeyErrorBody | ListVersionsErrorBody;
444
+ /**
445
+ * Search filter shape. Passed to {@link RegistryStorage.scanArtifacts}.
446
+ * `limit` is clamped to [1, 200] by {@link searchArtifacts}; impls
447
+ * should respect it as a hard ceiling. `cursor` is opaque to consumers
448
+ * — impls choose their own encoding (DDB LastEvaluatedKey base64;
449
+ * memory uses an integer offset; filesystem uses a last-seen artifactId).
450
+ */
451
+ export interface ArtifactScanFilter {
452
+ readonly q?: string;
453
+ readonly kind?: ArtifactKind;
454
+ readonly hook?: string;
455
+ readonly tag?: string;
456
+ readonly author?: string;
457
+ readonly limit?: number;
458
+ readonly cursor?: string;
459
+ }
460
+ /**
461
+ * Closed enum for `GET /pkg/{scope}/{name}` list-versions responses.
462
+ *
463
+ * - `invalid_request` — missing or malformed `artifactId`.
464
+ * - `not_found` — no metadata row for `artifactId`. Distinct
465
+ * from "metadata present but every version is
466
+ * private + caller unauthed" (that path returns
467
+ * 200 with `versions: []` — exposing 404 vs
468
+ * empty would leak private-row existence).
469
+ * - `server_error` — unexpected adapter failure.
470
+ */
471
+ export declare const LIST_VERSIONS_ERROR_CODES: readonly ["invalid_request", "not_found", "server_error"];
472
+ export type ListVersionsErrorCode = (typeof LIST_VERSIONS_ERROR_CODES)[number];
473
+ /**
474
+ * List-versions error body. `error` narrowed to {@link ListVersionsErrorCode}.
475
+ * Mirrors {@link ReadErrorBody}'s shape so callers can share a renderer.
476
+ */
477
+ export interface ListVersionsErrorBody {
478
+ readonly error: ListVersionsErrorCode;
479
+ readonly message: string;
480
+ readonly detail?: unknown;
481
+ }
482
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * SK literal for the per-artifactId metadata row on the cloud's
3
+ * Artifacts table. Pinned as a constant so cloud + OSS impls agree
4
+ * on the SK.
5
+ *
6
+ * OSS impls (filesystem / memory) MAY ignore this — they have one row
7
+ * per artifactId so no SK is needed. Cloud DDB impls MUST honor it.
8
+ */
9
+ export const ARTIFACTS_METADATA_SK = 'metadata#';
10
+ /**
11
+ * `GET /search` sort options. Supports a registry-web "Recent" view
12
+ * that needs `publishedAt`-DESC ordering.
13
+ *
14
+ * - `recent` — order by {@link ArtifactsMetadataRow.publishedAt} DESC.
15
+ *
16
+ * Default (sort omitted) is impl-defined ordering: memory uses insertion
17
+ * order, filesystem uses directory order, DDB uses Scan order. Callers
18
+ * MUST opt in to a deterministic order via this field.
19
+ *
20
+ * **Scale ceiling.** The `recent` sort is implemented as an in-memory
21
+ * pass on the page returned by {@link RegistryStorage.scanArtifacts}.
22
+ * For pre-launch row counts (< ~1k artifacts, single-page scans with
23
+ * `limit=200`) this gives a globally-correct order. Once the artifact
24
+ * table grows past one Scan page (~1 MiB of items), the order is only
25
+ * page-local — clients paginating with `cursor` see per-page recency,
26
+ * not global. A DDB GSI on `publishedAt` is the proper fix and is
27
+ * planned as a follow-up.
28
+ */
29
+ export const SEARCH_SORT_OPTIONS = ['recent'];
30
+ /**
31
+ * Locked publish error codes. Strings are the wire contract — the
32
+ * publish CLI matches on these for human-readable rendering. The
33
+ * `as const` tuple is the value-level source of truth; the
34
+ * {@link PublishErrorCode} type is derived from it so a single edit
35
+ * propagates to both the wire and the runtime membership check used
36
+ * by downstream guards.
37
+ */
38
+ export const PUBLISH_ERROR_CODES = [
39
+ 'unauthorized',
40
+ 'manifest_invalid',
41
+ 'bundle_required',
42
+ 'bundle_too_large',
43
+ 'conformance_failed',
44
+ 'bundle_hash_mismatch',
45
+ 'unknown_key',
46
+ 'signature_invalid',
47
+ 'version_exists',
48
+ 'internal',
49
+ ];
50
+ /**
51
+ * Closed enum for `GET /pkg/:scope/:name[/version]` read responses.
52
+ *
53
+ * `yanked` is the 410-Gone path — the version exists in metadata but
54
+ * the publisher revoked it. Clients SHOULD treat this as a hard
55
+ * failure (don't fall back to a different version automatically).
56
+ */
57
+ export const READ_ERROR_CODES = [
58
+ 'not_found',
59
+ 'forbidden',
60
+ 'invalid_request',
61
+ 'yanked',
62
+ 'server_error',
63
+ ];
64
+ /**
65
+ * Closed enum for `GET /search` responses. Same posture as
66
+ * {@link ReadErrorCode} minus the `yanked` path (search filters
67
+ * yanked rows out of the result set; it never surfaces as a top-level
68
+ * error).
69
+ */
70
+ export const SEARCH_ERROR_CODES = [
71
+ 'forbidden',
72
+ 'invalid_request',
73
+ 'server_error',
74
+ ];
75
+ /**
76
+ * Closed enum for `POST /author-keys` register responses. An operator
77
+ * registers a per-scope Ed25519 public key with the registry so
78
+ * future `POST /publish` calls signed by the matching private key
79
+ * validate against an `AuthorKeyRow`.
80
+ *
81
+ * - `unauthorized` — missing or invalid caller credentials.
82
+ * - `invalid_request` — body missing `publicKeyBase64`, malformed
83
+ * base64, or wrong byte length.
84
+ * - `key_conflict` — a row already exists for `(subject, keyId)`
85
+ * with a different `publicKeyBase64`. Same
86
+ * publicKey is treated as idempotent → 200.
87
+ * - `server_error` — unexpected adapter failure.
88
+ */
89
+ export const REGISTER_AUTHOR_KEY_ERROR_CODES = [
90
+ 'unauthorized',
91
+ 'invalid_request',
92
+ 'key_conflict',
93
+ 'server_error',
94
+ ];
95
+ /**
96
+ * Closed enum for `GET /pkg/{scope}/{name}` list-versions responses.
97
+ *
98
+ * - `invalid_request` — missing or malformed `artifactId`.
99
+ * - `not_found` — no metadata row for `artifactId`. Distinct
100
+ * from "metadata present but every version is
101
+ * private + caller unauthed" (that path returns
102
+ * 200 with `versions: []` — exposing 404 vs
103
+ * empty would leak private-row existence).
104
+ * - `server_error` — unexpected adapter failure.
105
+ */
106
+ export const LIST_VERSIONS_ERROR_CODES = [
107
+ 'invalid_request',
108
+ 'not_found',
109
+ 'server_error',
110
+ ];
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Decode a base64 string into bytes. Returns `undefined` on malformed
3
+ * input so the caller can surface a domain-specific error rather than
4
+ * a thrown exception.
5
+ *
6
+ * `Buffer.from(b64, 'base64')` is permissive — it ignores invalid
7
+ * chars rather than throwing. To detect malformed input, re-encode and
8
+ * compare against a whitespace-stripped original.
9
+ */
10
+ export declare function safeBase64Decode(b64: string): Uint8Array | undefined;
11
+ /** Base64-encode bytes. */
12
+ export declare function base64Encode(bytes: Uint8Array): string;
13
+ /** SHA-384 of bytes, base64-encoded. The wire form for `bundleSha384`. */
14
+ export declare function sha384Base64(bytes: Uint8Array): string;
15
+ //# sourceMappingURL=base64.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base64.d.ts","sourceRoot":"","sources":["../../src/utils/base64.ts"],"names":[],"mappings":"AAUA;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAQpE;AAED,2BAA2B;AAC3B,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEtD;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEtD"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Base64 + SHA helpers used by the publish op. Pulled out so the op
3
+ * file stays focused on policy + flow control.
4
+ *
5
+ * Uses `node:crypto` for SHA-384. The wider browser-portable goal of
6
+ * registry-core means we MAY swap to `@noble/hashes` later — the
7
+ * interface is `Uint8Array → string` so the swap is structural.
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ /**
11
+ * Decode a base64 string into bytes. Returns `undefined` on malformed
12
+ * input so the caller can surface a domain-specific error rather than
13
+ * a thrown exception.
14
+ *
15
+ * `Buffer.from(b64, 'base64')` is permissive — it ignores invalid
16
+ * chars rather than throwing. To detect malformed input, re-encode and
17
+ * compare against a whitespace-stripped original.
18
+ */
19
+ export function safeBase64Decode(b64) {
20
+ const buf = Buffer.from(b64, 'base64');
21
+ const reencoded = buf.toString('base64');
22
+ const normalize = (s) => s.replace(/=+$/, '').replace(/\s/g, '');
23
+ if (normalize(reencoded) !== normalize(b64)) {
24
+ return undefined;
25
+ }
26
+ return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
27
+ }
28
+ /** Base64-encode bytes. */
29
+ export function base64Encode(bytes) {
30
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString('base64');
31
+ }
32
+ /** SHA-384 of bytes, base64-encoded. The wire form for `bundleSha384`. */
33
+ export function sha384Base64(bytes) {
34
+ return createHash('sha384').update(bytes).digest('base64');
35
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Local semver comparison — supports `MAJOR.MINOR.PATCH` + optional
3
+ * `-pre.release` + (ignored) `+build.metadata`. Implemented locally to
4
+ * avoid a `semver` runtime dep for the very narrow comparison
5
+ * registry-core needs (decide whether a newly-published version is
6
+ * the latest).
7
+ *
8
+ * Returns `-1` / `0` / `1` matching `Array.prototype.sort`. Pre-release
9
+ * versions sort lower than the corresponding non-pre version
10
+ * (`1.0.0-alpha < 1.0.0`) per semver.org. Build metadata is ignored.
11
+ */
12
+ export declare function compareSemver(a: string, b: string): -1 | 0 | 1;
13
+ //# sourceMappingURL=semver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semver.d.ts","sourceRoot":"","sources":["../../src/utils/semver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAgD9D"}