@aotter/mantle-cloudflare 0.0.11-alpha.11

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 (99) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +10 -0
  3. package/dist/assets/aotterFavicon.d.ts +2 -0
  4. package/dist/assets/aotterFavicon.d.ts.map +1 -0
  5. package/dist/assets/aotterFavicon.js +5 -0
  6. package/dist/assets/aotterFavicon.js.map +1 -0
  7. package/dist/auth/ConsoleEmailSender.d.ts +15 -0
  8. package/dist/auth/ConsoleEmailSender.d.ts.map +1 -0
  9. package/dist/auth/ConsoleEmailSender.js +19 -0
  10. package/dist/auth/ConsoleEmailSender.js.map +1 -0
  11. package/dist/auth/appleClientSecret.d.ts +70 -0
  12. package/dist/auth/appleClientSecret.d.ts.map +1 -0
  13. package/dist/auth/appleClientSecret.js +111 -0
  14. package/dist/auth/appleClientSecret.js.map +1 -0
  15. package/dist/auth/createAuth.d.ts +176 -0
  16. package/dist/auth/createAuth.d.ts.map +1 -0
  17. package/dist/auth/createAuth.js +360 -0
  18. package/dist/auth/createAuth.js.map +1 -0
  19. package/dist/bindings/AssetsAssetServer.d.ts +14 -0
  20. package/dist/bindings/AssetsAssetServer.d.ts.map +1 -0
  21. package/dist/bindings/AssetsAssetServer.js +20 -0
  22. package/dist/bindings/AssetsAssetServer.js.map +1 -0
  23. package/dist/bindings/D1DatabaseDriver.d.ts +21 -0
  24. package/dist/bindings/D1DatabaseDriver.d.ts.map +1 -0
  25. package/dist/bindings/D1DatabaseDriver.js +126 -0
  26. package/dist/bindings/D1DatabaseDriver.js.map +1 -0
  27. package/dist/bindings/KvCacheBinding.d.ts +14 -0
  28. package/dist/bindings/KvCacheBinding.d.ts.map +1 -0
  29. package/dist/bindings/KvCacheBinding.js +27 -0
  30. package/dist/bindings/KvCacheBinding.js.map +1 -0
  31. package/dist/bindings/R2MediaStorage.d.ts +80 -0
  32. package/dist/bindings/R2MediaStorage.d.ts.map +1 -0
  33. package/dist/bindings/R2MediaStorage.js +189 -0
  34. package/dist/bindings/R2MediaStorage.js.map +1 -0
  35. package/dist/bindings/WorkersQueueHookDispatcher.d.ts +38 -0
  36. package/dist/bindings/WorkersQueueHookDispatcher.d.ts.map +1 -0
  37. package/dist/bindings/WorkersQueueHookDispatcher.js +68 -0
  38. package/dist/bindings/WorkersQueueHookDispatcher.js.map +1 -0
  39. package/dist/bindings/index.d.ts +6 -0
  40. package/dist/bindings/index.d.ts.map +1 -0
  41. package/dist/bindings/index.js +6 -0
  42. package/dist/bindings/index.js.map +1 -0
  43. package/dist/handlers/index.d.ts +2 -0
  44. package/dist/handlers/index.d.ts.map +1 -0
  45. package/dist/handlers/index.js +2 -0
  46. package/dist/handlers/index.js.map +1 -0
  47. package/dist/handlers/turnstile.d.ts +43 -0
  48. package/dist/handlers/turnstile.d.ts.map +1 -0
  49. package/dist/handlers/turnstile.js +71 -0
  50. package/dist/handlers/turnstile.js.map +1 -0
  51. package/dist/index.d.ts +13 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +13 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/mount/bootRuntimeOnce.d.ts +17 -0
  56. package/dist/mount/bootRuntimeOnce.d.ts.map +1 -0
  57. package/dist/mount/bootRuntimeOnce.js +35 -0
  58. package/dist/mount/bootRuntimeOnce.js.map +1 -0
  59. package/dist/mount/cmsConfig.d.ts +33 -0
  60. package/dist/mount/cmsConfig.d.ts.map +1 -0
  61. package/dist/mount/cmsConfig.js +2 -0
  62. package/dist/mount/cmsConfig.js.map +1 -0
  63. package/dist/mount/index.d.ts +6 -0
  64. package/dist/mount/index.d.ts.map +1 -0
  65. package/dist/mount/index.js +5 -0
  66. package/dist/mount/index.js.map +1 -0
  67. package/dist/mount/mountMcp.d.ts +32 -0
  68. package/dist/mount/mountMcp.d.ts.map +1 -0
  69. package/dist/mount/mountMcp.js +107 -0
  70. package/dist/mount/mountMcp.js.map +1 -0
  71. package/dist/mount/mountPublicRoutes.d.ts +82 -0
  72. package/dist/mount/mountPublicRoutes.d.ts.map +1 -0
  73. package/dist/mount/mountPublicRoutes.js +243 -0
  74. package/dist/mount/mountPublicRoutes.js.map +1 -0
  75. package/dist/mount/mountServerEndpoints.d.ts +10 -0
  76. package/dist/mount/mountServerEndpoints.d.ts.map +1 -0
  77. package/dist/mount/mountServerEndpoints.js +503 -0
  78. package/dist/mount/mountServerEndpoints.js.map +1 -0
  79. package/dist/oauth/consentHtml.d.ts +15 -0
  80. package/dist/oauth/consentHtml.d.ts.map +1 -0
  81. package/dist/oauth/consentHtml.js +89 -0
  82. package/dist/oauth/consentHtml.js.map +1 -0
  83. package/dist/oauth/index.d.ts +4 -0
  84. package/dist/oauth/index.d.ts.map +1 -0
  85. package/dist/oauth/index.js +4 -0
  86. package/dist/oauth/index.js.map +1 -0
  87. package/dist/oauth/mountOAuth.d.ts +29 -0
  88. package/dist/oauth/mountOAuth.d.ts.map +1 -0
  89. package/dist/oauth/mountOAuth.js +105 -0
  90. package/dist/oauth/mountOAuth.js.map +1 -0
  91. package/dist/oauth/oauthConstants.d.ts +15 -0
  92. package/dist/oauth/oauthConstants.d.ts.map +1 -0
  93. package/dist/oauth/oauthConstants.js +15 -0
  94. package/dist/oauth/oauthConstants.js.map +1 -0
  95. package/dist/oauth/oauthSingleton.d.ts +34 -0
  96. package/dist/oauth/oauthSingleton.d.ts.map +1 -0
  97. package/dist/oauth/oauthSingleton.js +22 -0
  98. package/dist/oauth/oauthSingleton.js.map +1 -0
  99. package/package.json +61 -0
@@ -0,0 +1,80 @@
1
+ import { AwsClient } from "aws4fetch";
2
+ import { type CommitUploadArgs, type CreateUploadArgs, type CreateUploadResult, type DeleteAssetArgs, type GetPublicUrlArgs, type IdGenerator, type MediaAsset, type MediaStorage } from "@aotter/mantle-runtime";
3
+ /**
4
+ * `R2MediaStorage` — `MediaStorage` adapter backed by Cloudflare R2,
5
+ * **public bucket only**.
6
+ *
7
+ * The bound R2 bucket is expected to have public access enabled and
8
+ * (typically) a custom domain or `pub-<hash>.r2.dev` URL pointing at
9
+ * it. Reads bypass the Worker entirely. CORS on the bucket should be
10
+ * scoped to the admin SPA origin so browser direct PUTs work without
11
+ * exposing other origins.
12
+ *
13
+ * Two access modes — both required:
14
+ * - `bucket` (`R2Bucket` binding) — server-side `get` / `put` /
15
+ * `delete`. Used at commit-time to read uploaded-object metadata
16
+ * and rewrite it with `committedAt`, and to clean up failed
17
+ * uploads.
18
+ * - `s3` (`AwsClient` from `aws4fetch`) — SigV4 presigned PUT URL
19
+ * generation. The R2 binding cannot issue presigned URLs.
20
+ *
21
+ * Public URL resolution is fully consumer-supplied via `publicBase`.
22
+ * The hash in `pub-<hash>.r2.dev` is opaque (assigned only after the
23
+ * user enables public access on the bucket); custom domains are
24
+ * configured separately. Adapters never derive the public URL from
25
+ * account / bucket — the consumer must wire it through the `cmsConfig`
26
+ * env (typically `MEDIA_PUBLIC_URL_BASE`).
27
+ *
28
+ * # Future: private bucket adapter
29
+ *
30
+ * A v0.2 `R2PrivateMediaStorage` will live alongside this class and
31
+ * implement a separate `PrivateMediaStorage` port. It binds a
32
+ * different R2 bucket — public access disabled — and routes every
33
+ * read through a Worker policy gate (staff / subscription check)
34
+ * before streaming via `bucket.get()` or 302-ing to a short-lived
35
+ * signed GET. **Two buckets, two ports**, by design — see ADR-0011
36
+ * § "Public vs private media — two buckets, two ports". Don't bolt
37
+ * a `visibility` flag onto this class to handle private content.
38
+ */
39
+ export declare class R2MediaStorage implements MediaStorage {
40
+ private readonly bucket;
41
+ private readonly s3;
42
+ /** S3 endpoint for THIS bucket — must include the bucket as the
43
+ * subdomain, e.g. `https://<bucket>.<account>.r2.cloudflarestorage.com`.
44
+ * Used as the host for presigned PUT URLs. */
45
+ private readonly s3Endpoint;
46
+ /** ID source for `uploadId` and the random portion of `storageKey`.
47
+ * Defaults to `RandomUuidGenerator`; tests inject a deterministic
48
+ * fake to assert exact key strings. **Production must use a
49
+ * CSPRNG-backed generator** — `uploadId` is bearer-token-equivalent
50
+ * and a predictable storageKey leaks pre-commit object locations.
51
+ * See `IdGenerator`'s "Security invariant". */
52
+ private readonly idgen;
53
+ constructor(bucket: R2Bucket, s3: AwsClient,
54
+ /** S3 endpoint for THIS bucket — must include the bucket as the
55
+ * subdomain, e.g. `https://<bucket>.<account>.r2.cloudflarestorage.com`.
56
+ * Used as the host for presigned PUT URLs. */
57
+ s3Endpoint: string,
58
+ /** Public read-base URL — `https://media.example.com` (custom
59
+ * domain) or `https://pub-<hash>.r2.dev` (R2 public dev domain).
60
+ * Trailing slash is normalised away. */
61
+ publicBase: string,
62
+ /** ID source for `uploadId` and the random portion of `storageKey`.
63
+ * Defaults to `RandomUuidGenerator`; tests inject a deterministic
64
+ * fake to assert exact key strings. **Production must use a
65
+ * CSPRNG-backed generator** — `uploadId` is bearer-token-equivalent
66
+ * and a predictable storageKey leaks pre-commit object locations.
67
+ * See `IdGenerator`'s "Security invariant". */
68
+ idgen?: IdGenerator);
69
+ private readonly publicBase;
70
+ createUpload(args: CreateUploadArgs): Promise<CreateUploadResult>;
71
+ commitUpload(args: CommitUploadArgs): Promise<MediaAsset>;
72
+ getPublicUrl(args: GetPublicUrlArgs): Promise<string>;
73
+ deleteAsset(args: DeleteAssetArgs): Promise<void>;
74
+ /** Object keys are server-generated. The mime-derived extension is
75
+ * purely cosmetic — server-side serving never infers content-type
76
+ * from the key. Purpose, when supplied, becomes a dir-prefix so an
77
+ * operator can eyeball "post-cover/abc123.jpg" in R2 dashboards. */
78
+ private buildStorageKey;
79
+ }
80
+ //# sourceMappingURL=R2MediaStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"R2MediaStorage.d.ts","sourceRoot":"","sources":["../../src/bindings/R2MediaStorage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,YAAY,EAClB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,qBAAa,cAAe,YAAW,YAAY;IAE/C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB;;mDAE+C;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAK3B;;;;;oDAKgD;IAChD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAhBL,MAAM,EAAE,QAAQ,EAChB,EAAE,EAAE,SAAS;IAC9B;;mDAE+C;IAC9B,UAAU,EAAE,MAAM;IACnC;;6CAEyC;IACzC,UAAU,EAAE,MAAM;IAClB;;;;;oDAKgD;IAC/B,KAAK,GAAE,WAAiC;IAgB3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAE9B,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAiCjE,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC;IA2DzD,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAIrD,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD;;;yEAGqE;IACrE,OAAO,CAAC,eAAe;CAMxB"}
@@ -0,0 +1,189 @@
1
+ import { DiagnosticError, makeDiagnostic } from "@aotter/mantle-spec";
2
+ import { RandomUuidGenerator, extensionForMime, } from "@aotter/mantle-runtime";
3
+ /**
4
+ * `R2MediaStorage` — `MediaStorage` adapter backed by Cloudflare R2,
5
+ * **public bucket only**.
6
+ *
7
+ * The bound R2 bucket is expected to have public access enabled and
8
+ * (typically) a custom domain or `pub-<hash>.r2.dev` URL pointing at
9
+ * it. Reads bypass the Worker entirely. CORS on the bucket should be
10
+ * scoped to the admin SPA origin so browser direct PUTs work without
11
+ * exposing other origins.
12
+ *
13
+ * Two access modes — both required:
14
+ * - `bucket` (`R2Bucket` binding) — server-side `get` / `put` /
15
+ * `delete`. Used at commit-time to read uploaded-object metadata
16
+ * and rewrite it with `committedAt`, and to clean up failed
17
+ * uploads.
18
+ * - `s3` (`AwsClient` from `aws4fetch`) — SigV4 presigned PUT URL
19
+ * generation. The R2 binding cannot issue presigned URLs.
20
+ *
21
+ * Public URL resolution is fully consumer-supplied via `publicBase`.
22
+ * The hash in `pub-<hash>.r2.dev` is opaque (assigned only after the
23
+ * user enables public access on the bucket); custom domains are
24
+ * configured separately. Adapters never derive the public URL from
25
+ * account / bucket — the consumer must wire it through the `cmsConfig`
26
+ * env (typically `MEDIA_PUBLIC_URL_BASE`).
27
+ *
28
+ * # Future: private bucket adapter
29
+ *
30
+ * A v0.2 `R2PrivateMediaStorage` will live alongside this class and
31
+ * implement a separate `PrivateMediaStorage` port. It binds a
32
+ * different R2 bucket — public access disabled — and routes every
33
+ * read through a Worker policy gate (staff / subscription check)
34
+ * before streaming via `bucket.get()` or 302-ing to a short-lived
35
+ * signed GET. **Two buckets, two ports**, by design — see ADR-0011
36
+ * § "Public vs private media — two buckets, two ports". Don't bolt
37
+ * a `visibility` flag onto this class to handle private content.
38
+ */
39
+ export class R2MediaStorage {
40
+ bucket;
41
+ s3;
42
+ s3Endpoint;
43
+ idgen;
44
+ constructor(bucket, s3,
45
+ /** S3 endpoint for THIS bucket — must include the bucket as the
46
+ * subdomain, e.g. `https://<bucket>.<account>.r2.cloudflarestorage.com`.
47
+ * Used as the host for presigned PUT URLs. */
48
+ s3Endpoint,
49
+ /** Public read-base URL — `https://media.example.com` (custom
50
+ * domain) or `https://pub-<hash>.r2.dev` (R2 public dev domain).
51
+ * Trailing slash is normalised away. */
52
+ publicBase,
53
+ /** ID source for `uploadId` and the random portion of `storageKey`.
54
+ * Defaults to `RandomUuidGenerator`; tests inject a deterministic
55
+ * fake to assert exact key strings. **Production must use a
56
+ * CSPRNG-backed generator** — `uploadId` is bearer-token-equivalent
57
+ * and a predictable storageKey leaks pre-commit object locations.
58
+ * See `IdGenerator`'s "Security invariant". */
59
+ idgen = RandomUuidGenerator) {
60
+ this.bucket = bucket;
61
+ this.s3 = s3;
62
+ this.s3Endpoint = s3Endpoint;
63
+ this.idgen = idgen;
64
+ if (!publicBase) {
65
+ throw new DiagnosticError(makeDiagnostic({
66
+ code: "MEDIA_NOT_CONFIGURED",
67
+ phase: "runtime",
68
+ severity: "error",
69
+ path: "adapter/R2MediaStorage",
70
+ expected: "publicBase URL (set MEDIA_PUBLIC_URL_BASE)",
71
+ }));
72
+ }
73
+ this.publicBase = publicBase.replace(/\/+$/, "");
74
+ }
75
+ publicBase;
76
+ async createUpload(args) {
77
+ const uploadId = this.idgen.next();
78
+ const storageKey = this.buildStorageKey(args.mimeType, args.purpose);
79
+ const ttlSeconds = Math.max(60, Math.floor((args.expiresAt - args.now) / 1000));
80
+ // Sign-as-query: PUT URL with the SigV4 signature in the query
81
+ // string. Browsers / agents PUT to this URL and we don't ask
82
+ // them to mint signing headers.
83
+ //
84
+ // Why sign nothing else: pinning Content-Type into the signature
85
+ // forces clients to send EXACTLY that header — browsers will
86
+ // strip mismatches and produce 403s. Pinning Content-Length is
87
+ // pointless because it's a forbidden header. The signature
88
+ // already constrains key + method + expiry; mime + size + etag
89
+ // are re-verified at commit-time via `bucket.get`.
90
+ const target = new URL(`${this.s3Endpoint.replace(/\/+$/, "")}/${storageKey}`);
91
+ target.searchParams.set("X-Amz-Expires", String(ttlSeconds));
92
+ const signed = await this.s3.sign(target.toString(), {
93
+ method: "PUT",
94
+ aws: { signQuery: true, service: "s3" },
95
+ });
96
+ return {
97
+ uploadId,
98
+ method: "PUT",
99
+ uploadUrl: signed.url,
100
+ storageKey,
101
+ expiresAt: args.expiresAt,
102
+ requiredHeaders: { "Content-Type": args.mimeType },
103
+ publicUrl: `${this.publicBase}/${storageKey}`,
104
+ };
105
+ }
106
+ async commitUpload(args) {
107
+ // Single `get` covers existence + metadata + body stream for the
108
+ // metadata-rewrite PUT. R2 has no metadata-only patch; the PUT
109
+ // streams `existing.body` (a ReadableStream) back without
110
+ // materialising bytes in Worker memory.
111
+ const existing = await this.bucket.get(args.storageKey);
112
+ if (!existing)
113
+ throw mediaDiagnostic("MEDIA_OBJECT_NOT_FOUND", { value: args.uploadId });
114
+ const actualMime = existing.httpMetadata?.contentType ?? "application/octet-stream";
115
+ if (args.checksum) {
116
+ // R2 always returns an etag for stored objects, but fail closed
117
+ // if it's missing for any reason — a supplied checksum that
118
+ // can't be verified must not silently no-op.
119
+ const etag = existing.etag?.replace(/^"|"$/g, "") ?? "";
120
+ if (etag !== args.checksum) {
121
+ throw mediaDiagnostic("MEDIA_CHECKSUM_MISMATCH", {
122
+ value: existing.etag ?? "(no etag returned)",
123
+ expected: args.checksum,
124
+ });
125
+ }
126
+ }
127
+ if (actualMime !== args.expectedMimeType) {
128
+ throw mediaDiagnostic("MEDIA_MIME_REJECTED", {
129
+ value: actualMime,
130
+ expected: args.expectedMimeType,
131
+ });
132
+ }
133
+ if (existing.size > args.maxBytes) {
134
+ throw mediaDiagnostic("MEDIA_SIZE_EXCEEDED", {
135
+ value: existing.size,
136
+ expected: `<= ${args.maxBytes}`,
137
+ });
138
+ }
139
+ const customMetadata = {
140
+ ...existing.customMetadata,
141
+ committedAt: String(args.now),
142
+ };
143
+ if (args.alt)
144
+ customMetadata["alt"] = args.alt;
145
+ if (args.caption)
146
+ customMetadata["caption"] = args.caption;
147
+ await this.bucket.put(args.storageKey, existing.body, {
148
+ httpMetadata: { contentType: actualMime },
149
+ customMetadata,
150
+ });
151
+ return {
152
+ id: args.uploadId,
153
+ storageKey: args.storageKey,
154
+ publicUrl: `${this.publicBase}/${args.storageKey}`,
155
+ mimeType: actualMime,
156
+ byteSize: existing.size,
157
+ alt: args.alt,
158
+ caption: args.caption,
159
+ createdAt: args.now,
160
+ metadata: customMetadata,
161
+ };
162
+ }
163
+ async getPublicUrl(args) {
164
+ return `${this.publicBase}/${args.storageKey}`;
165
+ }
166
+ async deleteAsset(args) {
167
+ await this.bucket.delete(args.storageKey);
168
+ }
169
+ /** Object keys are server-generated. The mime-derived extension is
170
+ * purely cosmetic — server-side serving never infers content-type
171
+ * from the key. Purpose, when supplied, becomes a dir-prefix so an
172
+ * operator can eyeball "post-cover/abc123.jpg" in R2 dashboards. */
173
+ buildStorageKey(mimeType, purpose) {
174
+ const id = this.idgen.next();
175
+ const ext = extensionForMime(mimeType);
176
+ const prefix = purpose && /^[a-z0-9-]+$/.test(purpose) ? `${purpose}/` : "";
177
+ return `${prefix}${id}.${ext}`;
178
+ }
179
+ }
180
+ function mediaDiagnostic(code, fields) {
181
+ return new DiagnosticError(makeDiagnostic({
182
+ code,
183
+ phase: "runtime",
184
+ severity: "error",
185
+ path: "adapter/R2MediaStorage/commitUpload",
186
+ ...fields,
187
+ }));
188
+ }
189
+ //# sourceMappingURL=R2MediaStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"R2MediaStorage.js","sourceRoot":"","sources":["../../src/bindings/R2MediaStorage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { CmsRuntime, DeferredHookDispatcher, DeferredHookEnvelope } from "@aotter/mantle-runtime";
2
+ /**
3
+ * `DeferredHookDispatcher` impl backed by a Cloudflare Workers Queue
4
+ * binding. Producer side: serialises each `DeferredHookEnvelope` and
5
+ * sends it via `queue.send`. The runtime decorator handles fallback
6
+ * on rejection (its three-rung ladder downgrades to `ctx.waitUntil`
7
+ * → inline-await), so this dispatcher does not retry — it lets the
8
+ * caller decide.
9
+ *
10
+ * Per ADR-0011 only this adapter package may import the CF binding
11
+ * type. The runtime sees only the `DeferredHookDispatcher` port.
12
+ */
13
+ export declare class WorkersQueueHookDispatcher implements DeferredHookDispatcher {
14
+ private readonly queue;
15
+ constructor(queue: Queue<DeferredHookEnvelope>);
16
+ enqueue(envelope: DeferredHookEnvelope): Promise<void>;
17
+ }
18
+ /**
19
+ * Build the `queue(batch, env, ctx)` Workers handler that consumes
20
+ * `clam_internal` messages and re-fires each `after_*` hook through
21
+ * the runtime. Consumers wire this alongside `fetch` in their default
22
+ * Worker export:
23
+ *
24
+ * ```ts
25
+ * export default {
26
+ * fetch: app.fetch,
27
+ * queue: createQueueHandler(cms),
28
+ * } satisfies ExportedHandler<Env>;
29
+ * ```
30
+ *
31
+ * Per-message ack / retry: a thrown handler error retries the
32
+ * message; success acks. CF Queues then handles backoff and the
33
+ * dead-letter queue per the binding's wrangler config.
34
+ */
35
+ export declare function createQueueHandler<Env>(cmsRef: {
36
+ get(): Promise<CmsRuntime>;
37
+ }): (batch: MessageBatch<DeferredHookEnvelope>, env: Env) => Promise<void>;
38
+ //# sourceMappingURL=WorkersQueueHookDispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkersQueueHookDispatcher.d.ts","sourceRoot":"","sources":["../../src/bindings/WorkersQueueHookDispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;GAUG;AACH,qBAAa,0BAA2B,YAAW,sBAAsB;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC;IAEzD,OAAO,CAAC,QAAQ,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;CAG7D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EACpC,MAAM,EAAE;IAAE,GAAG,IAAI,OAAO,CAAC,UAAU,CAAC,CAAA;CAAE,GACrC,CAAC,KAAK,EAAE,YAAY,CAAC,oBAAoB,CAAC,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAgCxE"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `DeferredHookDispatcher` impl backed by a Cloudflare Workers Queue
3
+ * binding. Producer side: serialises each `DeferredHookEnvelope` and
4
+ * sends it via `queue.send`. The runtime decorator handles fallback
5
+ * on rejection (its three-rung ladder downgrades to `ctx.waitUntil`
6
+ * → inline-await), so this dispatcher does not retry — it lets the
7
+ * caller decide.
8
+ *
9
+ * Per ADR-0011 only this adapter package may import the CF binding
10
+ * type. The runtime sees only the `DeferredHookDispatcher` port.
11
+ */
12
+ export class WorkersQueueHookDispatcher {
13
+ queue;
14
+ constructor(queue) {
15
+ this.queue = queue;
16
+ }
17
+ async enqueue(envelope) {
18
+ await this.queue.send(envelope);
19
+ }
20
+ }
21
+ /**
22
+ * Build the `queue(batch, env, ctx)` Workers handler that consumes
23
+ * `clam_internal` messages and re-fires each `after_*` hook through
24
+ * the runtime. Consumers wire this alongside `fetch` in their default
25
+ * Worker export:
26
+ *
27
+ * ```ts
28
+ * export default {
29
+ * fetch: app.fetch,
30
+ * queue: createQueueHandler(cms),
31
+ * } satisfies ExportedHandler<Env>;
32
+ * ```
33
+ *
34
+ * Per-message ack / retry: a thrown handler error retries the
35
+ * message; success acks. CF Queues then handles backoff and the
36
+ * dead-letter queue per the binding's wrangler config.
37
+ */
38
+ export function createQueueHandler(cmsRef) {
39
+ return async (batch, env) => {
40
+ let cms;
41
+ try {
42
+ cms = await cmsRef.get();
43
+ }
44
+ catch (err) {
45
+ // Boot failed (D1 unreachable, manifest invalid, …). Use
46
+ // batch.retryAll so each message's per-message attempt counter
47
+ // increments — letting wrangler's max_retries / DLQ rules
48
+ // engage instead of looping forever at the batch level.
49
+ console.error("[clam-internal] runtime boot failed; retrying batch", err);
50
+ batch.retryAll();
51
+ return;
52
+ }
53
+ // Drain in parallel — after-hooks are independent and CF Queues
54
+ // ack/retry is per-message, so serial processing only burns the
55
+ // 30s consumer wall clock on larger batches.
56
+ await Promise.allSettled(batch.messages.map(async (message) => {
57
+ try {
58
+ await cms.runDeferredHook.execute({ envelope: message.body, env });
59
+ message.ack();
60
+ }
61
+ catch (err) {
62
+ console.error(`[clam-internal] consume failed for ${message.body.hook} on ${message.body.schema}/${message.body.entry.id}`, err);
63
+ message.retry();
64
+ }
65
+ }));
66
+ };
67
+ }
68
+ //# sourceMappingURL=WorkersQueueHookDispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkersQueueHookDispatcher.js","sourceRoot":"","sources":["../../src/bindings/WorkersQueueHookDispatcher.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,MAAM,OAAO,0BAA0B;IACR;IAA7B,YAA6B,KAAkC;QAAlC,UAAK,GAAL,KAAK,CAA6B;IAAG,CAAC;IAEnE,KAAK,CAAC,OAAO,CAAC,QAA8B;QAC1C,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClC,CAAC;CACF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAsC;IAEtC,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC1B,IAAI,GAAe,CAAC;QACpB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,yDAAyD;YACzD,+DAA+D;YAC/D,0DAA0D;YAC1D,wDAAwD;YACxD,OAAO,CAAC,KAAK,CAAC,qDAAqD,EAAE,GAAG,CAAC,CAAC;YAC1E,KAAK,CAAC,QAAQ,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,gEAAgE;QAChE,gEAAgE;QAChE,6CAA6C;QAC7C,MAAM,OAAO,CAAC,UAAU,CACtB,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YACnC,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;gBACnE,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CACX,sCAAsC,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAC5G,GAAG,CACJ,CAAC;gBACF,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,CAAC;QACH,CAAC,CAAC,CACH,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { D1DatabaseDriver } from "./D1DatabaseDriver.js";
2
+ export { KvCacheBinding } from "./KvCacheBinding.js";
3
+ export { AssetsAssetServer } from "./AssetsAssetServer.js";
4
+ export { R2MediaStorage } from "./R2MediaStorage.js";
5
+ export { WorkersQueueHookDispatcher, createQueueHandler, } from "./WorkersQueueHookDispatcher.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { D1DatabaseDriver } from "./D1DatabaseDriver.js";
2
+ export { KvCacheBinding } from "./KvCacheBinding.js";
3
+ export { AssetsAssetServer } from "./AssetsAssetServer.js";
4
+ export { R2MediaStorage } from "./R2MediaStorage.js";
5
+ export { WorkersQueueHookDispatcher, createQueueHandler, } from "./WorkersQueueHookDispatcher.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,iCAAiC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { cloudflareTurnstileCheck, type CloudflareTurnstileCheckOptions, } from "./turnstile.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,EACxB,KAAK,+BAA+B,GACrC,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { cloudflareTurnstileCheck, } from "./turnstile.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/handlers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,wBAAwB,GAEzB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { type HandlerContext } from "@aotter/mantle-runtime";
2
+ export interface CloudflareTurnstileCheckOptions {
3
+ /** Server-side secret. Pulled from `env.TURNSTILE_SECRET_KEY` by the
4
+ * factory. The literal string `"dev-stub"` short-circuits to a
5
+ * local check (`token === "fail"` is rejected, anything else
6
+ * passes) — useful for integration smokes that run with no
7
+ * network. Any other value triggers real `siteverify`. */
8
+ readonly secret: string;
9
+ /** Field name on the procedure input carrying the client-side
10
+ * widget token. Defaults to `"turnstileToken"` (matches the
11
+ * starter's contact-messages Schema). */
12
+ readonly tokenField?: string;
13
+ }
14
+ /**
15
+ * `before_create` (or any pre-mutation) hook that verifies a
16
+ * Cloudflare Turnstile token. Builtin handler factory — register
17
+ * with:
18
+ *
19
+ * register("captchaCheck", cloudflareTurnstileCheck({
20
+ * secret: env.TURNSTILE_SECRET_KEY ?? "dev-stub",
21
+ * }));
22
+ *
23
+ * Behavior:
24
+ *
25
+ * - Authenticated callers (`ctx.user` non-null) bypass — the gate
26
+ * guards anonymous public-form abuse, not signed-in writes
27
+ * (admin UI, MCP agents).
28
+ * - `secret === "dev-stub"`: short-circuit. `token === "fail"`
29
+ * rejects; anything else passes. Tests assert on this token
30
+ * shape; integration runs need no network.
31
+ * - Otherwise: POSTs to `siteverify`, fails on `{ success: false }`
32
+ * or any HTTP error.
33
+ *
34
+ * Throws `InvokeFailure(AUTH_DENIED)` on rejection so the procedure
35
+ * mount maps it to HTTP 403, the right code for "your form
36
+ * submission was rejected by abuse-prevention." A plain `Error`
37
+ * surfaces as INTERNAL_ERROR (500) and falsely implies a server
38
+ * bug.
39
+ */
40
+ export declare function cloudflareTurnstileCheck(options: CloudflareTurnstileCheckOptions): (input: Record<string, unknown>, ctx: HandlerContext) => Promise<{
41
+ ok: true;
42
+ }>;
43
+ //# sourceMappingURL=turnstile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turnstile.d.ts","sourceRoot":"","sources":["../../src/handlers/turnstile.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,wBAAwB,CAAC;AAUhC,MAAM,WAAW,+BAA+B;IAC9C;;;;+DAI2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;;8CAE0C;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,IAG7E,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,cAAc,KAClB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,CAAC,CAiBzB"}
@@ -0,0 +1,71 @@
1
+ import { InvokeFailure, } from "@aotter/mantle-runtime";
2
+ import { runtimeDiagnostic } from "@aotter/mantle-spec";
3
+ const TURNSTILE_VERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
4
+ /**
5
+ * `before_create` (or any pre-mutation) hook that verifies a
6
+ * Cloudflare Turnstile token. Builtin handler factory — register
7
+ * with:
8
+ *
9
+ * register("captchaCheck", cloudflareTurnstileCheck({
10
+ * secret: env.TURNSTILE_SECRET_KEY ?? "dev-stub",
11
+ * }));
12
+ *
13
+ * Behavior:
14
+ *
15
+ * - Authenticated callers (`ctx.user` non-null) bypass — the gate
16
+ * guards anonymous public-form abuse, not signed-in writes
17
+ * (admin UI, MCP agents).
18
+ * - `secret === "dev-stub"`: short-circuit. `token === "fail"`
19
+ * rejects; anything else passes. Tests assert on this token
20
+ * shape; integration runs need no network.
21
+ * - Otherwise: POSTs to `siteverify`, fails on `{ success: false }`
22
+ * or any HTTP error.
23
+ *
24
+ * Throws `InvokeFailure(AUTH_DENIED)` on rejection so the procedure
25
+ * mount maps it to HTTP 403, the right code for "your form
26
+ * submission was rejected by abuse-prevention." A plain `Error`
27
+ * surfaces as INTERNAL_ERROR (500) and falsely implies a server
28
+ * bug.
29
+ */
30
+ export function cloudflareTurnstileCheck(options) {
31
+ const { secret, tokenField = "turnstileToken" } = options;
32
+ return async function turnstileCheck(input, ctx) {
33
+ if (ctx.user)
34
+ return { ok: true };
35
+ const tokenRaw = input[tokenField];
36
+ const token = typeof tokenRaw === "string" ? tokenRaw : "";
37
+ if (!token)
38
+ reject("missing turnstile token");
39
+ if (secret === "dev-stub") {
40
+ if (token === "fail")
41
+ reject("dev-stub rejected literal 'fail' token");
42
+ return { ok: true };
43
+ }
44
+ const verified = await verifyWithTurnstile(secret, token);
45
+ if (!verified.success) {
46
+ reject(`Turnstile siteverify rejected: ${(verified["error-codes"] ?? ["unknown"]).join(", ")}`);
47
+ }
48
+ return { ok: true };
49
+ };
50
+ }
51
+ async function verifyWithTurnstile(secret, token) {
52
+ const body = new URLSearchParams({ secret, response: token });
53
+ const res = await fetch(TURNSTILE_VERIFY_URL, {
54
+ method: "POST",
55
+ body,
56
+ headers: { "content-type": "application/x-www-form-urlencoded" },
57
+ });
58
+ if (!res.ok)
59
+ reject(`Turnstile siteverify HTTP ${res.status}`);
60
+ return (await res.json());
61
+ }
62
+ function reject(detail) {
63
+ throw new InvokeFailure(runtimeDiagnostic({
64
+ code: "AUTH_DENIED",
65
+ severity: "error",
66
+ path: "captcha-check",
67
+ expected: "valid Turnstile token verified by siteverify",
68
+ message: `CAPTCHA verification failed: ${detail}.`,
69
+ }));
70
+ }
71
+ //# sourceMappingURL=turnstile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turnstile.js","sourceRoot":"","sources":["../../src/handlers/turnstile.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAEd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,oBAAoB,GAAG,2DAA2D,CAAC;AAoBzF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAwC;IAC/E,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,gBAAgB,EAAE,GAAG,OAAO,CAAC;IAC1D,OAAO,KAAK,UAAU,cAAc,CAClC,KAA8B,EAC9B,GAAmB;QAEnB,IAAI,GAAG,CAAC,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QAClC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;QACnC,MAAM,KAAK,GAAG,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,KAAK;YAAE,MAAM,CAAC,yBAAyB,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,UAAU,EAAE,CAAC;YAC1B,IAAI,KAAK,KAAK,MAAM;gBAAE,MAAM,CAAC,wCAAwC,CAAC,CAAC;YACvE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,MAAM,CACJ,kCAAkC,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACxF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,MAAc,EAAE,KAAa;IAC9D,MAAM,IAAI,GAAG,IAAI,eAAe,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oBAAoB,EAAE;QAC5C,MAAM,EAAE,MAAM;QACd,IAAI;QACJ,OAAO,EAAE,EAAE,cAAc,EAAE,mCAAmC,EAAE;KACjE,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,CAAC,6BAA6B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;AACvD,CAAC;AAED,SAAS,MAAM,CAAC,MAAc;IAC5B,MAAM,IAAI,aAAa,CACrB,iBAAiB,CAAC;QAChB,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,OAAO;QACjB,IAAI,EAAE,eAAe;QACrB,QAAQ,EAAE,8CAA8C;QACxD,OAAO,EAAE,gCAAgC,MAAM,GAAG;KACnD,CAAC,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@aotter/mantle-cloudflare` — Cloudflare Workers adapter for
3
+ * mantle. The only place in the codebase that may import D1Database
4
+ * / KVNamespace / Fetcher; runtime stays portable per ADR-0011.
5
+ */
6
+ export * from "./bindings/index.js";
7
+ export * from "./mount/index.js";
8
+ export * from "./handlers/index.js";
9
+ export * from "./oauth/index.js";
10
+ export * from "./auth/createAuth.js";
11
+ export { ConsoleEmailSender } from "./auth/ConsoleEmailSender.js";
12
+ export { appleClientSecret, type AppleClientSecretArgs, } from "./auth/appleClientSecret.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,GAC3B,MAAM,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@aotter/mantle-cloudflare` — Cloudflare Workers adapter for
3
+ * mantle. The only place in the codebase that may import D1Database
4
+ * / KVNamespace / Fetcher; runtime stays portable per ADR-0011.
5
+ */
6
+ export * from "./bindings/index.js";
7
+ export * from "./mount/index.js";
8
+ export * from "./handlers/index.js";
9
+ export * from "./oauth/index.js";
10
+ export * from "./auth/createAuth.js";
11
+ export { ConsoleEmailSender } from "./auth/ConsoleEmailSender.js";
12
+ export { appleClientSecret, } from "./auth/appleClientSecret.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EACL,iBAAiB,GAElB,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type CmsRuntime } from "@aotter/mantle-runtime";
2
+ import type { Manifest } from "@aotter/mantle-spec";
3
+ import type { Auth } from "../auth/createAuth.js";
4
+ import type { CmsConfig } from "./cmsConfig.js";
5
+ /**
6
+ * Per-isolate runtime singleton. The cached promise MUST reset on
7
+ * rejection (PR #29 carry-forward) — otherwise a transient D1 error
8
+ * during boot poisons the isolate permanently and every subsequent
9
+ * request re-throws the same rejected promise.
10
+ */
11
+ export interface CmsRuntimeRef {
12
+ get(): Promise<CmsRuntime>;
13
+ readonly manifests: readonly Manifest[];
14
+ readonly auth: Auth;
15
+ }
16
+ export declare function createCmsRef(config: CmsConfig): CmsRuntimeRef;
17
+ //# sourceMappingURL=bootRuntimeOnce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootRuntimeOnce.d.ts","sourceRoot":"","sources":["../../src/mount/bootRuntimeOnce.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,GAAG,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,aAAa,CAgC7D"}