@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,79 @@
1
+ /**
2
+ * `BundleStorage` — bundle + signature + manifest blob storage. The
3
+ * hosted registry backs this with S3 behind a CDN; the open-source
4
+ * server backs it with the filesystem under
5
+ * `<root>/bundles/<scope>/<name>/<version>/`. A memory impl is
6
+ * provided for tests.
7
+ *
8
+ * Each `put*` method returns the fully-qualified URL the consumer
9
+ * (iframe runtime, install CLI) can fetch. The URL prefix is
10
+ * determined by the impl's constructor — a CDN alias for the hosted
11
+ * registry, or `http://localhost:9001` (or whatever the server is
12
+ * bound to) for the open-source server.
13
+ *
14
+ * Bundles are immutable post-publish. Responses MUST emit
15
+ * `Cache-Control: public, max-age=31536000, immutable` — SRI integrity
16
+ * depends on it. The OSS server's bundle route sets this header
17
+ * explicitly; the cloud's CloudFront distribution sets it via its
18
+ * cache policy.
19
+ *
20
+ * ## Protocol & Contract Bar
21
+ *
22
+ * **Parties:**
23
+ * - Producer: {@link publishArtifact} writes bundle (gadgets),
24
+ * signature (gadgets), and manifest (always) on every publish.
25
+ * - Consumer: iframe-runtime (bundleUrl), install CLI (signatureUrl,
26
+ * manifestUrl), audit tooling (manifestUrl on yanked versions).
27
+ *
28
+ * **Obligations:**
29
+ * - `put*` methods MUST be idempotent — re-publishes of the same
30
+ * `(scope, name, version)` triple write identical bytes (the
31
+ * per-version row immutability invariant on {@link RegistryStorage}
32
+ * prevents true re-publishes; the bundle-storage layer needs no
33
+ * conflict semantics).
34
+ * - URL composition methods (`bundleUrl`, `signatureUrl`,
35
+ * `manifestUrl`) MUST be pure — no I/O, no async. Consumers cache
36
+ * URLs liberally.
37
+ *
38
+ * **Failure mode:**
39
+ * - Transport-level failures throw; the publish op wraps and returns
40
+ * 500.
41
+ * - Missing-blob reads return `null`; never throw.
42
+ *
43
+ * **Observable violation:**
44
+ * - Contract test {@link bundleStorageContract} covers: bundle
45
+ * round-trip preserves bytes; signature round-trip preserves the
46
+ * structured object; manifest round-trip preserves the full
47
+ * discriminated-union shape; URL methods compose without side effects;
48
+ * missing reads return null.
49
+ */
50
+ import type { ArtifactManifest } from '@ggui-ai/artifact-manifest';
51
+ import type { GadgetSignature } from '@ggui-ai/gadget-signing';
52
+ export interface BundleStorage {
53
+ /** Write gadget bundle bytes. Returns the public URL. */
54
+ putBundle(scope: string, name: string, version: string, bytes: Uint8Array): Promise<string>;
55
+ /** Read gadget bundle bytes. `null` on miss. */
56
+ getBundle(scope: string, name: string, version: string): Promise<Uint8Array | null>;
57
+ /**
58
+ * Write the signature envelope. Returns the public URL.
59
+ *
60
+ * The envelope is a {@link GadgetSignature} — a discriminated union
61
+ * over `algorithm` (`ed25519` for private gadgets, `sigstore-cosign`
62
+ * for public). Impls serialize via `JSON.stringify(signature)` and
63
+ * store the resulting bytes verbatim.
64
+ */
65
+ putSignature(scope: string, name: string, version: string, signature: GadgetSignature): Promise<string>;
66
+ /** Read the signature envelope. `null` on miss. */
67
+ getSignature(scope: string, name: string, version: string): Promise<GadgetSignature | null>;
68
+ /** Write the manifest verbatim. Returns the public URL. */
69
+ putManifest(scope: string, name: string, version: string, manifest: ArtifactManifest): Promise<string>;
70
+ /** Read the manifest. `null` on miss. */
71
+ getManifest(scope: string, name: string, version: string): Promise<ArtifactManifest | null>;
72
+ /** Compose the public bundle URL. No I/O. */
73
+ bundleUrl(scope: string, name: string, version: string): string;
74
+ /** Compose the public signature URL. No I/O. */
75
+ signatureUrl(scope: string, name: string, version: string): string;
76
+ /** Compose the public manifest URL. No I/O. */
77
+ manifestUrl(scope: string, name: string, version: string): string;
78
+ }
79
+ //# sourceMappingURL=bundle-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-storage.d.ts","sourceRoot":"","sources":["../../src/interfaces/bundle-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5F,gDAAgD;IAChD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAEpF;;;;;;;OAOG;IACH,YAAY,CACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,eAAe,GACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,mDAAmD;IACnD,YAAY,CACV,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAEnC,2DAA2D;IAC3D,WAAW,CACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,MAAM,CAAC,CAAC;IACnB,yCAAyC;IACzC,WAAW,CACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IAEpC,6CAA6C;IAC7C,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAChE,gDAAgD;IAChD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACnE,+CAA+C;IAC/C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnE"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,204 @@
1
+ /**
2
+ * `RegistryStorage` — the per-row persistence seam for the marketplace
3
+ * registry. The interface mirrors a three-table DynamoDB shape
4
+ * (artifacts + artifact-versions + author-keys) so a hosted DynamoDB
5
+ * adapter can be a structural pass-through. Memory + filesystem impls
6
+ * back the open-source server and unit tests.
7
+ *
8
+ * The shape follows a single-interface / multiple-impls / contract-test
9
+ * pattern, with a Protocol & Contract Bar docstring.
10
+ *
11
+ * The umbrella noun is `artifact`: the registry stores BOTH gadgets
12
+ * AND blueprints, and `kind` discriminates.
13
+ *
14
+ * ## Protocol & Contract Bar
15
+ *
16
+ * **Parties:**
17
+ * - Producer / writer: {@link publishArtifact} — writes
18
+ * {@link ArtifactsMetadataRow} on every publish (upserting
19
+ * `latestVersion`) and writes {@link ArtifactVersionRow} once per
20
+ * version via {@link putArtifactVersionIfAbsent}.
21
+ * - Reader: {@link readArtifact}, {@link searchArtifacts},
22
+ * {@link publishArtifact} (signature verification path).
23
+ *
24
+ * **Obligations:**
25
+ * - {@link putArtifactVersionIfAbsent} MUST atomically reject when a row
26
+ * exists for `(artifactId, version)`. Mirrors DDB's
27
+ * `ConditionExpression: attribute_not_exists(...)`. Implementations
28
+ * MUST NOT overwrite on conflict — per-version immutability is a
29
+ * load-bearing registry invariant.
30
+ * - {@link getArtifactMetadata} / {@link getArtifactVersion} MUST return
31
+ * exactly what was last written. `null` on miss; throw on transport
32
+ * failure (caller decides retry).
33
+ * - {@link scanArtifacts} returns rows in arbitrary order (cloud DDB
34
+ * Scan order; memory insertion order; filesystem directory order).
35
+ * Consumers MUST treat ordering as non-deterministic.
36
+ *
37
+ * **Failure mode:**
38
+ * - Transport-level failures (DDB throttle, disk full) throw.
39
+ * {@link publishArtifact} wraps and returns 500 with `internal`
40
+ * error code.
41
+ * - Missing rows return `null`; never throw.
42
+ *
43
+ * **Observable violation:**
44
+ * - Contract test {@link registryStorageContract} covers:
45
+ * round-trip preservation, idempotent {@link putArtifactMetadata},
46
+ * `putArtifactVersionIfAbsent` rejects on collision, missing returns
47
+ * null, `listAuthorKeys` returns only keys for the queried subject.
48
+ */
49
+ import type { ArtifactScanFilter, ArtifactVersionRow, ArtifactsMetadataRow, AuthorKeyRow, CompiledBlobRow } from '../types.js';
50
+ /**
51
+ * Optional flags for {@link RegistryStorage.putAuthorKey}.
52
+ *
53
+ * `ifNotExists` — when `true`, the write MUST be conditional on no
54
+ * existing row for `(subject, keyId)`. On conflict (a concurrent first
55
+ * write landed between the caller's check and this put), implementations
56
+ * MUST throw {@link AuthorKeyAlreadyExistsError}. Used by
57
+ * {@link registerAuthorKey} to close the TOCTOU window between its
58
+ * idempotency read and the put.
59
+ */
60
+ export interface PutAuthorKeyOptions {
61
+ readonly ifNotExists?: boolean;
62
+ }
63
+ /**
64
+ * Thrown by {@link RegistryStorage.putAuthorKey} when the caller passed
65
+ * `ifNotExists: true` AND a row already exists for `(subject, keyId)`.
66
+ * Cloud DDB adapter surfaces `ConditionalCheckFailedException` as this
67
+ * type; in-memory impl mirrors the contract synchronously.
68
+ *
69
+ * Callers re-read via {@link RegistryStorage.getAuthorKey} and dispatch
70
+ * same-publicKey → 200, different-publicKey → 409.
71
+ */
72
+ export declare class AuthorKeyAlreadyExistsError extends Error {
73
+ readonly subject: string;
74
+ readonly keyId: string;
75
+ constructor(subject: string, keyId: string);
76
+ }
77
+ export interface RegistryStorage {
78
+ getArtifactMetadata(artifactId: string): Promise<ArtifactsMetadataRow | null>;
79
+ putArtifactMetadata(row: ArtifactsMetadataRow): Promise<void>;
80
+ /**
81
+ * Scan the metadata-row family with paginated cursor + post-fetch
82
+ * filter. The filter is applied per-row; impls MAY push it down
83
+ * (cloud GSI when available) or run it in-memory after a wide scan.
84
+ * `limit` is treated as a per-page ceiling; consumers may iterate
85
+ * via `nextCursor` for multi-page reads.
86
+ */
87
+ scanArtifacts(filter: ArtifactScanFilter): Promise<{
88
+ readonly rows: readonly ArtifactsMetadataRow[];
89
+ readonly nextCursor?: string;
90
+ }>;
91
+ getArtifactVersion(artifactId: string, version: string): Promise<ArtifactVersionRow | null>;
92
+ /**
93
+ * List every version row for `artifactId`. Returns in arbitrary order
94
+ * (DDB Query order by SK = version, lexicographic; memory map
95
+ * insertion order; filesystem directory order). Callers MUST sort
96
+ * by semver themselves if they need ordering.
97
+ *
98
+ * Backs the `GET /pkg/:scope/:name` list-versions route.
99
+ *
100
+ * **Cost note.** A DynamoDB impl uses `Query` with `PK = artifactId`,
101
+ * which is the cheapest possible per-artifact lookup (no Scan, no
102
+ * GSI). Memory + filesystem impls do a full table walk filtered by
103
+ * artifactId — adequate for bounded row counts.
104
+ *
105
+ * **Returns:** empty array when no versions exist (NOT null) — the
106
+ * "metadata-row present but no version rows" state should never
107
+ * happen post-publish but is technically representable; the empty
108
+ * array keeps the type narrow.
109
+ */
110
+ listArtifactVersions(artifactId: string): Promise<readonly ArtifactVersionRow[]>;
111
+ /**
112
+ * Atomically conditional put — succeeds only when no row exists for
113
+ * `(artifactId, version)`. The single load-bearing concurrency primitive
114
+ * in the registry; consumers MUST NOT pre-check with
115
+ * {@link getArtifactVersion} + put (race-prone).
116
+ */
117
+ putArtifactVersionIfAbsent(row: ArtifactVersionRow): Promise<{
118
+ ok: true;
119
+ } | {
120
+ ok: false;
121
+ reason: 'version_exists';
122
+ }>;
123
+ yankArtifactVersion(artifactId: string, version: string): Promise<void>;
124
+ /**
125
+ * Fetch the compiled-bytes row for `compiledDigest`. Returns `null` on miss.
126
+ *
127
+ * Used by:
128
+ * - Read op — projecting `compiledBytes` into
129
+ * {@link ReadPkgResponse} alongside the version row.
130
+ * - Install path — two-layer resolution of
131
+ * `ArtifactVersionRow.compiledDigest` → `CompiledBlobRow`.
132
+ * A missing blob when the version row's pointer is set is a
133
+ * CRITICAL inconsistency the caller surfaces, not silently
134
+ * fallback.
135
+ * - Tests / fixtures that pre-seed blob rows without a paired
136
+ * publish.
137
+ */
138
+ getCompiledBlob(compiledDigest: string): Promise<CompiledBlobRow | null>;
139
+ /**
140
+ * Atomically commit BOTH the version row AND the compiled-blob row
141
+ * under a single logical transaction. A single transaction avoids
142
+ * the "dangling pointer" failure mode where a blob write fails
143
+ * after the version row is already durable.
144
+ *
145
+ * **Two paths**, dispatched by the storage impl:
146
+ *
147
+ * - **New-blob path** — `blobRow.compiledDigest` is not yet present.
148
+ * Both rows are PUT-INSERTed under one transaction with
149
+ * `attribute_not_exists` conditions on each. Returns
150
+ * `{ ok: true, mode: 'new-blob' }`.
151
+ * - **Dedup path** — `blobRow.compiledDigest` already has a row.
152
+ * The version row is PUT-INSERTed AND the existing blob row's
153
+ * `refCount` is incremented under one transaction. Returns
154
+ * `{ ok: true, mode: 'dedup' }`.
155
+ *
156
+ * **Failure mode:**
157
+ *
158
+ * - Version-row conflict — `(artifactId, version)` already exists.
159
+ * NEITHER row mutates. Returns `{ ok: false, reason: 'version_exists' }`.
160
+ * The publisher's idempotent-retry path.
161
+ * - Transport-level failures (DDB throttle, disk full, transaction
162
+ * conflict not attributable to either conditional) throw —
163
+ * {@link publishArtifact} wraps and returns 500.
164
+ *
165
+ * **Atomicity guarantee:**
166
+ *
167
+ * - Memory + filesystem impls: single-threaded JS event loop —
168
+ * the function awaits both writes before returning, no other
169
+ * awaitable interleaves.
170
+ * - Cloud DDB impl: `TransactWriteItems` is all-or-nothing at the
171
+ * service level. The new-blob path issues one transaction; the
172
+ * dedup path may retry once if the optimistic new-blob path
173
+ * fails because the digest landed between read-and-write.
174
+ *
175
+ * **Why a single transaction matters (refCount double-increment on
176
+ * retry)**: a sequenced write path could increment refCount, then
177
+ * crash before persisting the version row, then on retry increment
178
+ * again. With TransactWriteItems both mutations either land together
179
+ * or not at all — refCount can only grow when a version row also
180
+ * lands.
181
+ *
182
+ * **Invariant preserved:** once a version row is durable, the
183
+ * `(artifactId, version)` tuple cannot be re-used — semver
184
+ * immutability holds.
185
+ */
186
+ commitVersionAndBlob(versionRow: ArtifactVersionRow, blobRow: CompiledBlobRow): Promise<{
187
+ ok: true;
188
+ mode: 'new-blob' | 'dedup';
189
+ } | {
190
+ ok: false;
191
+ reason: 'version_exists';
192
+ }>;
193
+ getAuthorKey(subject: string, keyId: string): Promise<AuthorKeyRow | null>;
194
+ /**
195
+ * Write an AuthorKey row. With `options.ifNotExists === true`, the
196
+ * write is conditional on no existing row for `(subject, keyId)`;
197
+ * on conflict, implementations MUST throw
198
+ * {@link AuthorKeyAlreadyExistsError}. Default (no options) is an
199
+ * unconditional upsert.
200
+ */
201
+ putAuthorKey(row: AuthorKeyRow, options?: PutAuthorKeyOptions): Promise<void>;
202
+ listAuthorKeys(subject: string): Promise<readonly AuthorKeyRow[]>;
203
+ }
204
+ //# sourceMappingURL=registry-storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry-storage.d.ts","sourceRoot":"","sources":["../../src/interfaces/registry-storage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;GASG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;gBACX,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAQ3C;AAED,MAAM,WAAW,eAAe;IAE9B,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC9E,mBAAmB,CAAC,GAAG,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D;;;;;;OAMG;IACH,aAAa,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC;QACjD,QAAQ,CAAC,IAAI,EAAE,SAAS,oBAAoB,EAAE,CAAC;QAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC,CAAC;IAGH,kBAAkB,CAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IACtC;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAClB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,SAAS,kBAAkB,EAAE,CAAC,CAAC;IAC1C;;;;;OAKG;IACH,0BAA0B,CACxB,GAAG,EAAE,kBAAkB,GACtB,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAAC,CAAC;IACnE,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGxE;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,oBAAoB,CAClB,UAAU,EAAE,kBAAkB,EAC9B,OAAO,EAAE,eAAe,GACvB,OAAO,CACN;QAAE,EAAE,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAA;KAAE,GACxC;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,CAC1C,CAAC;IAGF,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC3E;;;;;;OAMG;IACH,YAAY,CACV,GAAG,EAAE,YAAY,EACjB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;CACnE"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Thrown by {@link RegistryStorage.putAuthorKey} when the caller passed
3
+ * `ifNotExists: true` AND a row already exists for `(subject, keyId)`.
4
+ * Cloud DDB adapter surfaces `ConditionalCheckFailedException` as this
5
+ * type; in-memory impl mirrors the contract synchronously.
6
+ *
7
+ * Callers re-read via {@link RegistryStorage.getAuthorKey} and dispatch
8
+ * same-publicKey → 200, different-publicKey → 409.
9
+ */
10
+ export class AuthorKeyAlreadyExistsError extends Error {
11
+ subject;
12
+ keyId;
13
+ constructor(subject, keyId) {
14
+ super(`AuthorKey row already exists for (subject=${subject}, keyId=${keyId})`);
15
+ this.name = 'AuthorKeyAlreadyExistsError';
16
+ this.subject = subject;
17
+ this.keyId = keyId;
18
+ }
19
+ }
@@ -0,0 +1,48 @@
1
+ /** Compile output — base64-encoded bytes, hex digest, decoded size. */
2
+ export interface CompileBlueprintOk {
3
+ readonly ok: true;
4
+ readonly compiledBytes: string;
5
+ readonly compiledDigest: string;
6
+ readonly compiledSize: number;
7
+ }
8
+ /** Compile failure — structured esbuild diagnostics for wire surfacing. */
9
+ export interface CompileBlueprintErr {
10
+ readonly ok: false;
11
+ readonly errors: ReadonlyArray<{
12
+ readonly message: string;
13
+ readonly location?: {
14
+ readonly line?: number;
15
+ readonly column?: number;
16
+ };
17
+ }>;
18
+ }
19
+ export type CompileBlueprintResult = CompileBlueprintOk | CompileBlueprintErr;
20
+ /**
21
+ * Always-allowed imports on the SOURCE side. Mirrors the conformance
22
+ * gate's `BLUEPRINT_ALLOWED_IMPORTS` to keep the wire-contract aligned;
23
+ * any addition here MUST land in the conformance gate at the same
24
+ * commit (and vice versa).
25
+ */
26
+ export declare const BLUEPRINT_EXTERNAL_MODULES: readonly string[];
27
+ /**
28
+ * Hex SHA-256 of `compiledBytes` (base64-decoded). Pure function; the
29
+ * deterministic-compile contract is `compile(source) → sha256(bytes)`.
30
+ *
31
+ * Exported for callers that want to recompute the digest without
32
+ * re-compiling (e.g. install-time defense-in-depth).
33
+ */
34
+ export declare function compiledDigestHex(compiledBytes: string): string;
35
+ /**
36
+ * Compile a blueprint's TSX `source` into canonical compiled JS bytes.
37
+ *
38
+ * Sync at call site — esbuild's `transformSync` is used to keep the
39
+ * publish op's transaction boundaries clean (one async hop into
40
+ * compile, one async hop into storage). The transform is itself fast
41
+ * (<10ms for typical blueprints).
42
+ *
43
+ * @returns Discriminated-union result. Caller projects errors onto
44
+ * the conformance-failure wire envelope; success is a
45
+ * base64 string + hex digest + decoded byte count.
46
+ */
47
+ export declare function compileBlueprint(source: string): CompileBlueprintResult;
48
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.d.ts","sourceRoot":"","sources":["../../src/ops/compile.ts"],"names":[],"mappings":"AAsDA,uEAAuE;AACvE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,2EAA2E;AAC3E,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;QAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1E,CAAC,CAAC;CACJ;AAED,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAKtD,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,CA6CvE"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * `compileBlueprint` — the TSX → JS compile boundary.
3
+ *
4
+ * Pure transform from a blueprint's `manifest.source` (TSX text) to
5
+ * compiled JS bytes + content digest. Called by the publish op before
6
+ * writing the {@link CompiledBlobRow} + {@link ArtifactVersionRow}.
7
+ * NO I/O — esbuild's synchronous transform API is used so this op
8
+ * stays at the seam where reads and writes are sequenced by the
9
+ * caller's transaction.
10
+ *
11
+ * **Determinism.** The compile config below is a fixed settings
12
+ * matrix. Changing any field is a `compiledDigest` mass-invalidation
13
+ * — every previously-published version produces a different digest.
14
+ * Any change to this matrix requires a migration plan, because the
15
+ * matcher's cross-app cache sharing and federation keys depend on
16
+ * stable digests across all federating registries.
17
+ *
18
+ * format: 'esm' — required (the iframe runtime + matcher
19
+ * consume ES modules; no CJS path).
20
+ * target: 'es2022' — pinned to the lowest engine the
21
+ * protocol supports; rebumping is a
22
+ * minor protocol version bump.
23
+ * bundle: true — single-file output; the matcher writes
24
+ * the bytes directly into componentCode.
25
+ * minify: false — readability matters more than size at
26
+ * this scale (~10s of KB per blueprint).
27
+ * external: ['react', — host-provided modules at iframe-render
28
+ * 'react-dom', time. The conformance gate enforces
29
+ * 'react/jsx-runtime', the same allow-list on the SOURCE
30
+ * '@ggui-ai/gadgets'] side so we never compile a blueprint
31
+ * whose imports don't survive bundling.
32
+ * loader.tsx: 'tsx' — input is JSX/TSX text.
33
+ * treeShaking: false — preserve all imports so the conformance
34
+ * gate's import-walk matches what's in
35
+ * the compiled output.
36
+ *
37
+ * **esbuild version pin.** `packages/registry-core/package.json` pins
38
+ * `esbuild` to a single minor — the digest is sensitive to esbuild's
39
+ * own version bumps. Bumping esbuild is a coordinated re-publish
40
+ * exercise; the policy lives in the migration doc.
41
+ *
42
+ * **Failure mode.** Compile errors surface as a typed result
43
+ * `{ ok: false }` with structured `errors` from esbuild. The publish
44
+ * op projects them onto the conformance-failure wire envelope
45
+ * (`blueprint_compile_error`). The static-gates conformance op
46
+ * already runs a more permissive `esbuild.transformSync` to surface
47
+ * the same code at conformance-check time; this compile op is the
48
+ * load-bearing one (its OUTPUT is what's stored), but the two are
49
+ * deliberately separate so a `POST /conformance/check` dry-run
50
+ * remains cheap (no bundle resolution).
51
+ */
52
+ import * as esbuild from 'esbuild';
53
+ import { createHash } from 'node:crypto';
54
+ /**
55
+ * Always-allowed imports on the SOURCE side. Mirrors the conformance
56
+ * gate's `BLUEPRINT_ALLOWED_IMPORTS` to keep the wire-contract aligned;
57
+ * any addition here MUST land in the conformance gate at the same
58
+ * commit (and vice versa).
59
+ */
60
+ export const BLUEPRINT_EXTERNAL_MODULES = Object.freeze([
61
+ 'react',
62
+ 'react-dom',
63
+ 'react/jsx-runtime',
64
+ '@ggui-ai/gadgets',
65
+ ]);
66
+ /**
67
+ * Hex SHA-256 of `compiledBytes` (base64-decoded). Pure function; the
68
+ * deterministic-compile contract is `compile(source) → sha256(bytes)`.
69
+ *
70
+ * Exported for callers that want to recompute the digest without
71
+ * re-compiling (e.g. install-time defense-in-depth).
72
+ */
73
+ export function compiledDigestHex(compiledBytes) {
74
+ return createHash('sha256').update(Buffer.from(compiledBytes, 'base64')).digest('hex');
75
+ }
76
+ /**
77
+ * Compile a blueprint's TSX `source` into canonical compiled JS bytes.
78
+ *
79
+ * Sync at call site — esbuild's `transformSync` is used to keep the
80
+ * publish op's transaction boundaries clean (one async hop into
81
+ * compile, one async hop into storage). The transform is itself fast
82
+ * (<10ms for typical blueprints).
83
+ *
84
+ * @returns Discriminated-union result. Caller projects errors onto
85
+ * the conformance-failure wire envelope; success is a
86
+ * base64 string + hex digest + decoded byte count.
87
+ */
88
+ export function compileBlueprint(source) {
89
+ try {
90
+ // esbuild.buildSync would require a virtual-fs layer to handle
91
+ // imports; we use transformSync which compiles a single text input
92
+ // and emits a single text output. Tree-shaking is OFF and externals
93
+ // are not stripped (preserved as ESM import statements). This
94
+ // matches what the runtime + matcher consume.
95
+ const result = esbuild.transformSync(source, {
96
+ loader: 'tsx',
97
+ format: 'esm',
98
+ target: 'es2022',
99
+ minify: false,
100
+ treeShaking: false,
101
+ // `keepNames` keeps function/class names for stack traces — the
102
+ // matcher's source-map-less debug surface depends on these.
103
+ keepNames: true,
104
+ // `sourcemap` off — sourcemaps would inject non-deterministic
105
+ // path strings into the output, breaking digest stability.
106
+ sourcemap: false,
107
+ });
108
+ // Note on `BLUEPRINT_EXTERNAL_MODULES`: with `transformSync` (not
109
+ // `buildSync`) imports are preserved verbatim in the output —
110
+ // tree-shaking off + no bundle resolution means the import
111
+ // statements pass through unmodified. The externals list is the
112
+ // CONTRACT consumers (iframe runtime, matcher) honor at module-
113
+ // load time. The conformance gate's import-walk enforces the
114
+ // allow-list on the source side before publish.
115
+ const bytes = Buffer.from(result.code, 'utf-8');
116
+ const compiledBytes = bytes.toString('base64');
117
+ const compiledDigest = createHash('sha256').update(bytes).digest('hex');
118
+ return {
119
+ ok: true,
120
+ compiledBytes,
121
+ compiledDigest,
122
+ compiledSize: bytes.byteLength,
123
+ };
124
+ }
125
+ catch (err) {
126
+ // esbuild's BuildFailure shape exposes `errors: Message[]` with
127
+ // text + location. Other throw shapes (TypeError, OOM) surface
128
+ // as a single generic error.
129
+ const errors = extractEsbuildErrors(err);
130
+ return { ok: false, errors };
131
+ }
132
+ }
133
+ function extractEsbuildErrors(err) {
134
+ if (err === null || typeof err !== 'object') {
135
+ return [{ message: err instanceof Error ? err.message : String(err) }];
136
+ }
137
+ const maybeErrors = err.errors;
138
+ if (!Array.isArray(maybeErrors) || maybeErrors.length === 0) {
139
+ const message = err instanceof Error ? err.message : String(err);
140
+ return [{ message }];
141
+ }
142
+ const out = [];
143
+ for (const m of maybeErrors) {
144
+ if (m === null || typeof m !== 'object')
145
+ continue;
146
+ const msg = m;
147
+ const message = typeof msg.text === 'string' && msg.text.length > 0 ? msg.text : 'esbuild error';
148
+ if (msg.location !== null &&
149
+ msg.location !== undefined &&
150
+ (typeof msg.location.line === 'number' || typeof msg.location.column === 'number')) {
151
+ const location = {};
152
+ if (typeof msg.location.line === 'number')
153
+ location.line = msg.location.line;
154
+ if (typeof msg.location.column === 'number')
155
+ location.column = msg.location.column;
156
+ out.push({ message, location });
157
+ }
158
+ else {
159
+ out.push({ message });
160
+ }
161
+ }
162
+ return out;
163
+ }