@xyo-network/dapp-kit-cloudflare-r2 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # `@xyo-network/dapp-kit-cloudflare-r2`
2
+
3
+ Cloudflare R2 specialization and qualification for dapp-kit deployments.
4
+
5
+ The package combines the portable `@xyo-network/dapp-kit-s3` object store with:
6
+
7
+ - explicit R2 account endpoint construction;
8
+ - mandatory injected clients or explicit bucket-scoped credentials;
9
+ - credential-free, hash-bound resource plans;
10
+ - public custom-domain and browser CORS policy;
11
+ - a destructive prefix-confined credentialed qualification gate; and
12
+ - a read-only public URL/CORS audit.
13
+
14
+ R2 resource plans intentionally admit only datalakes, projection stores, and
15
+ checkpoint stores. They do not classify R2 as a secret store, challenge store,
16
+ queue, rate limiter, or high-contention effect journal. The admitted per-key
17
+ write rate is part of the plan instead of being hidden behind “S3 compatible.”
18
+
19
+ ```ts
20
+ import { CloudflareR2ObjectStore } from '@xyo-network/dapp-kit-cloudflare-r2'
21
+
22
+ const store = new CloudflareR2ObjectStore({
23
+ accountId,
24
+ bucket,
25
+ credentials: { accessKeyId, secretAccessKey },
26
+ maximumObjectBytes: 4 * 1024 * 1024,
27
+ prefix: 'my-dapp/public-projection',
28
+ })
29
+
30
+ // ...publish immutable generations and conditionally advance head.json...
31
+ store.close()
32
+ ```
33
+
34
+ Constructing a store is not qualification. Run `qualifyCloudflareR2` against
35
+ the intended bucket using a `CloudflareR2ObjectStore` constructed with explicit
36
+ credentials, and retain its result as `credentialed-cloudflare-r2` evidence.
37
+ The API refuses to issue that evidence class for an injected client.
38
+ `probeCloudflareR2Capabilities` is the lower-level fake/local gate and returns
39
+ capabilities without claiming credentialed R2 evidence.
@@ -0,0 +1,33 @@
1
+ import { S3Client } from '@aws-sdk/client-s3';
2
+ import type { S3CommandClient } from '@xyo-network/dapp-kit-s3';
3
+ import { S3ObjectStore } from '@xyo-network/dapp-kit-s3';
4
+ export type CloudflareR2Jurisdiction = 'default' | 'eu';
5
+ export interface CloudflareR2Credentials {
6
+ readonly accessKeyId: string;
7
+ readonly secretAccessKey: string;
8
+ readonly sessionToken?: string;
9
+ }
10
+ export interface CloudflareR2ObjectStoreOptions {
11
+ readonly accountId: string;
12
+ readonly bucket: string;
13
+ readonly client?: S3Client | S3CommandClient;
14
+ readonly credentials?: CloudflareR2Credentials;
15
+ readonly jurisdiction?: CloudflareR2Jurisdiction;
16
+ readonly maximumObjectBytes: number;
17
+ readonly prefix: string;
18
+ }
19
+ export declare function cloudflareR2S3Endpoint(accountIdValue: string, jurisdiction?: CloudflareR2Jurisdiction): string;
20
+ export declare function cloudflareR2PublicObjectUrl(baseUrl: string, prefix: string, key: string): string;
21
+ /**
22
+ * Cloudflare R2 specialization of the portable S3 store. Production
23
+ * construction requires explicit credentials; ambient AWS credential lookup
24
+ * is intentionally unavailable.
25
+ */
26
+ export declare class CloudflareR2ObjectStore extends S3ObjectStore {
27
+ readonly credentialSource: 'explicit-credentials' | 'injected-client';
28
+ readonly endpoint: string;
29
+ private readonly ownedClient?;
30
+ constructor(options: CloudflareR2ObjectStoreOptions);
31
+ close(): void;
32
+ }
33
+ //# sourceMappingURL=cloudflareR2ObjectStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflareR2ObjectStore.d.ts","sourceRoot":"","sources":["../../src/cloudflareR2ObjectStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAA;AAC/D,OAAO,EAEL,aAAa,EACd,MAAM,0BAA0B,CAAA;AAEjC,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,IAAI,CAAA;AAEvD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAA;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,uBAAuB,CAAA;IAC9C,QAAQ,CAAC,YAAY,CAAC,EAAE,wBAAwB,CAAA;IAChD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAcD,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,EACtB,YAAY,GAAE,wBAAoC,GACjD,MAAM,CAKR;AAUD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAOhG;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,aAAa;IACxD,QAAQ,CAAC,gBAAgB,EAAE,sBAAsB,GAAG,iBAAiB,CAAA;IACrE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IAEzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAU;gBAE3B,OAAO,EAAE,8BAA8B;IA6BnD,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,133 @@
1
+ import { z } from 'zod';
2
+ export declare const CloudflareR2AdapterId: "network.xyo.dapp.adapter.cloudflare-r2-s3";
3
+ export declare const CloudflareR2AdapterVersion: "0.1.8";
4
+ export declare const CloudflareR2ResourcePlanSchema: "network.xyo.dapp.cloudflare-r2.resource.plan";
5
+ export declare const CloudflareR2ResourcePlanHashAlgorithm: "jcs-sha256-v1";
6
+ export declare const CloudflareR2ResourcePlanDomainSeparator: "xyo-dapp-kit-cloudflare-r2:resource-plan:v1\n";
7
+ export declare const CloudflareR2ResourceClassZod: z.ZodEnum<{
8
+ datalake: "datalake";
9
+ "projection-store": "projection-store";
10
+ "checkpoint-store": "checkpoint-store";
11
+ }>;
12
+ export type CloudflareR2ResourceClass = z.infer<typeof CloudflareR2ResourceClassZod>;
13
+ export declare const CloudflareR2CredentialModeZod: z.ZodEnum<{
14
+ "static-s3": "static-s3";
15
+ "temporary-s3": "temporary-s3";
16
+ }>;
17
+ export type CloudflareR2CredentialMode = z.infer<typeof CloudflareR2CredentialModeZod>;
18
+ export declare const CloudflareR2ResourceAccessZod: z.ZodDiscriminatedUnion<[z.ZodObject<{
19
+ mode: z.ZodLiteral<"private">;
20
+ }, z.core.$strict>, z.ZodObject<{
21
+ mode: z.ZodLiteral<"public-custom-domain">;
22
+ baseUrl: z.ZodString;
23
+ cors: z.ZodObject<{
24
+ allowedOrigins: z.ZodArray<z.ZodString>;
25
+ allowedMethods: z.ZodArray<z.ZodEnum<{
26
+ GET: "GET";
27
+ HEAD: "HEAD";
28
+ }>>;
29
+ exposeEtag: z.ZodLiteral<true>;
30
+ }, z.core.$strict>;
31
+ }, z.core.$strict>], "mode">;
32
+ export type CloudflareR2ResourceAccess = z.infer<typeof CloudflareR2ResourceAccessZod>;
33
+ export declare const CloudflareR2ResourcePlanPreimageZod: z.ZodObject<{
34
+ schema: z.ZodLiteral<"network.xyo.dapp.cloudflare-r2.resource.plan">;
35
+ schemaVersion: z.ZodLiteral<1>;
36
+ hashAlgorithm: z.ZodLiteral<"jcs-sha256-v1">;
37
+ adapter: z.ZodObject<{
38
+ adapterId: z.ZodLiteral<"network.xyo.dapp.adapter.cloudflare-r2-s3">;
39
+ adapterVersion: z.ZodLiteral<"0.1.8">;
40
+ family: z.ZodLiteral<"s3-compatible">;
41
+ provider: z.ZodLiteral<"cloudflare-r2">;
42
+ }, z.core.$strict>;
43
+ policy: z.ZodObject<{
44
+ policyId: z.ZodString;
45
+ policyVersion: z.ZodString;
46
+ reviewedOn: z.ZodString;
47
+ validUntil: z.ZodString;
48
+ }, z.core.$strict>;
49
+ resourceId: z.ZodString;
50
+ resourceClass: z.ZodEnum<{
51
+ datalake: "datalake";
52
+ "projection-store": "projection-store";
53
+ "checkpoint-store": "checkpoint-store";
54
+ }>;
55
+ access: z.ZodDiscriminatedUnion<[z.ZodObject<{
56
+ mode: z.ZodLiteral<"private">;
57
+ }, z.core.$strict>, z.ZodObject<{
58
+ mode: z.ZodLiteral<"public-custom-domain">;
59
+ baseUrl: z.ZodString;
60
+ cors: z.ZodObject<{
61
+ allowedOrigins: z.ZodArray<z.ZodString>;
62
+ allowedMethods: z.ZodArray<z.ZodEnum<{
63
+ GET: "GET";
64
+ HEAD: "HEAD";
65
+ }>>;
66
+ exposeEtag: z.ZodLiteral<true>;
67
+ }, z.core.$strict>;
68
+ }, z.core.$strict>], "mode">;
69
+ credentialMode: z.ZodEnum<{
70
+ "static-s3": "static-s3";
71
+ "temporary-s3": "temporary-s3";
72
+ }>;
73
+ maximumObjectBytes: z.ZodNumber;
74
+ guarantees: z.ZodObject<{
75
+ conditionalPut: z.ZodLiteral<true>;
76
+ globalReadAfterWrite: z.ZodLiteral<true>;
77
+ maximumWritesPerSecondPerKey: z.ZodLiteral<1>;
78
+ }, z.core.$strict>;
79
+ }, z.core.$strict>;
80
+ export type CloudflareR2ResourcePlanPreimage = z.infer<typeof CloudflareR2ResourcePlanPreimageZod>;
81
+ export declare const CloudflareR2ResourcePlanZod: z.ZodObject<{
82
+ schema: z.ZodLiteral<"network.xyo.dapp.cloudflare-r2.resource.plan">;
83
+ schemaVersion: z.ZodLiteral<1>;
84
+ hashAlgorithm: z.ZodLiteral<"jcs-sha256-v1">;
85
+ adapter: z.ZodObject<{
86
+ adapterId: z.ZodLiteral<"network.xyo.dapp.adapter.cloudflare-r2-s3">;
87
+ adapterVersion: z.ZodLiteral<"0.1.8">;
88
+ family: z.ZodLiteral<"s3-compatible">;
89
+ provider: z.ZodLiteral<"cloudflare-r2">;
90
+ }, z.core.$strict>;
91
+ policy: z.ZodObject<{
92
+ policyId: z.ZodString;
93
+ policyVersion: z.ZodString;
94
+ reviewedOn: z.ZodString;
95
+ validUntil: z.ZodString;
96
+ }, z.core.$strict>;
97
+ resourceId: z.ZodString;
98
+ resourceClass: z.ZodEnum<{
99
+ datalake: "datalake";
100
+ "projection-store": "projection-store";
101
+ "checkpoint-store": "checkpoint-store";
102
+ }>;
103
+ access: z.ZodDiscriminatedUnion<[z.ZodObject<{
104
+ mode: z.ZodLiteral<"private">;
105
+ }, z.core.$strict>, z.ZodObject<{
106
+ mode: z.ZodLiteral<"public-custom-domain">;
107
+ baseUrl: z.ZodString;
108
+ cors: z.ZodObject<{
109
+ allowedOrigins: z.ZodArray<z.ZodString>;
110
+ allowedMethods: z.ZodArray<z.ZodEnum<{
111
+ GET: "GET";
112
+ HEAD: "HEAD";
113
+ }>>;
114
+ exposeEtag: z.ZodLiteral<true>;
115
+ }, z.core.$strict>;
116
+ }, z.core.$strict>], "mode">;
117
+ credentialMode: z.ZodEnum<{
118
+ "static-s3": "static-s3";
119
+ "temporary-s3": "temporary-s3";
120
+ }>;
121
+ maximumObjectBytes: z.ZodNumber;
122
+ guarantees: z.ZodObject<{
123
+ conditionalPut: z.ZodLiteral<true>;
124
+ globalReadAfterWrite: z.ZodLiteral<true>;
125
+ maximumWritesPerSecondPerKey: z.ZodLiteral<1>;
126
+ }, z.core.$strict>;
127
+ resourcePlanId: z.ZodString;
128
+ }, z.core.$strict>;
129
+ export type CloudflareR2ResourcePlan = z.infer<typeof CloudflareR2ResourcePlanZod>;
130
+ export declare function hashCloudflareR2ResourcePlan(value: unknown): string;
131
+ export declare function createCloudflareR2ResourcePlan(value: unknown): CloudflareR2ResourcePlan;
132
+ export declare function verifyCloudflareR2ResourcePlan(value: unknown): CloudflareR2ResourcePlan;
133
+ //# sourceMappingURL=cloudflareR2ResourcePlan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflareR2ResourcePlan.d.ts","sourceRoot":"","sources":["../../src/cloudflareR2ResourcePlan.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,qBAAqB,EAAG,2CAAoD,CAAA;AACzF,eAAO,MAAM,0BAA0B,EAAG,OAAgB,CAAA;AAC1D,eAAO,MAAM,8BAA8B,EAAG,8CAAuD,CAAA;AACrG,eAAO,MAAM,qCAAqC,iBAA4B,CAAA;AAC9E,eAAO,MAAM,uCAAuC,EAAG,+CAAwD,CAAA;AAE/G,eAAO,MAAM,4BAA4B;;;;EAIvC,CAAA;AACF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAA;AAEpF,eAAO,MAAM,6BAA6B;;;EAAwC,CAAA;AAClF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AAqBtF,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;4BAWxC,CAAA;AACF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAA;AAItF,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAkE9C,CAAA;AAEF,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAA;AAElG,eAAO,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAA8E,CAAA;AACtH,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAkBlF,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKnE;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAMvF;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,OAAO,GAAG,wBAAwB,CAQvF"}
@@ -0,0 +1,4 @@
1
+ export * from './cloudflareR2ObjectStore.ts';
2
+ export * from './cloudflareR2ResourcePlan.ts';
3
+ export * from './qualifyCloudflareR2.ts';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAA;AAC5C,cAAc,+BAA+B,CAAA;AAC7C,cAAc,0BAA0B,CAAA"}
@@ -0,0 +1,373 @@
1
+ // src/cloudflareR2ObjectStore.ts
2
+ import { S3Client } from "@aws-sdk/client-s3";
3
+ import { normalizeObjectStoreKey } from "@xyo-network/dapp-kit";
4
+ import {
5
+ createS3SdkObjectStoreTransport,
6
+ S3ObjectStore
7
+ } from "@xyo-network/dapp-kit-s3";
8
+ var AccountIdPattern = /^[0-9a-f]{32}$/u;
9
+ function accountId(value) {
10
+ if (!AccountIdPattern.test(value)) throw new TypeError("Cloudflare account ID must be 32 lowercase hexadecimal characters");
11
+ return value;
12
+ }
13
+ function secret(value, name) {
14
+ if (value.trim() === "") throw new TypeError(`${name} must be non-empty`);
15
+ return value;
16
+ }
17
+ function cloudflareR2S3Endpoint(accountIdValue, jurisdiction = "default") {
18
+ const prefix = accountId(accountIdValue);
19
+ return jurisdiction === "eu" ? `https://${prefix}.eu.r2.cloudflarestorage.com` : `https://${prefix}.r2.cloudflarestorage.com`;
20
+ }
21
+ function publicBaseUrl(value) {
22
+ const url = new URL(value);
23
+ if (url.protocol !== "https:" || url.origin !== value) {
24
+ throw new TypeError("Cloudflare R2 public base URL must be an HTTPS origin without credentials, query, or fragment");
25
+ }
26
+ return url;
27
+ }
28
+ function cloudflareR2PublicObjectUrl(baseUrl, prefix, key) {
29
+ const base = publicBaseUrl(baseUrl);
30
+ const pathname = [
31
+ normalizeObjectStoreKey(prefix, true),
32
+ normalizeObjectStoreKey(key)
33
+ ].filter((value) => value !== "").flatMap((value) => value.split("/")).map((segment) => encodeURIComponent(segment)).join("/");
34
+ return new URL(pathname, `${base.toString().replace(/\/+$/u, "")}/`).toString();
35
+ }
36
+ var CloudflareR2ObjectStore = class extends S3ObjectStore {
37
+ credentialSource;
38
+ endpoint;
39
+ ownedClient;
40
+ constructor(options) {
41
+ if (options.client === void 0 === (options.credentials === void 0)) {
42
+ throw new TypeError("provide exactly one of Cloudflare R2 credentials or an injected S3 client");
43
+ }
44
+ const endpoint = cloudflareR2S3Endpoint(options.accountId, options.jurisdiction);
45
+ const credentials = options.credentials;
46
+ const ownedClient = credentials === void 0 ? void 0 : new S3Client({
47
+ credentials: {
48
+ accessKeyId: secret(credentials.accessKeyId, "Cloudflare R2 access key ID"),
49
+ secretAccessKey: secret(credentials.secretAccessKey, "Cloudflare R2 secret access key"),
50
+ ...credentials.sessionToken !== void 0 && { sessionToken: secret(credentials.sessionToken, "Cloudflare R2 session token") }
51
+ },
52
+ endpoint,
53
+ region: "auto"
54
+ });
55
+ const client = options.client ?? ownedClient;
56
+ if (client === void 0) throw new TypeError("Cloudflare R2 client construction failed");
57
+ super({
58
+ maximumObjectBytes: options.maximumObjectBytes,
59
+ prefix: options.prefix,
60
+ transport: createS3SdkObjectStoreTransport({ bucket: options.bucket, client })
61
+ });
62
+ this.endpoint = endpoint;
63
+ this.credentialSource = credentials === void 0 ? "injected-client" : "explicit-credentials";
64
+ this.ownedClient = ownedClient;
65
+ }
66
+ close() {
67
+ this.ownedClient?.destroy();
68
+ }
69
+ };
70
+
71
+ // src/cloudflareR2ResourcePlan.ts
72
+ import { createHash } from "node:crypto";
73
+ import {
74
+ canonicalizeIJson,
75
+ DappDocumentHashAlgorithm,
76
+ HashZod,
77
+ IdentifierZod,
78
+ SemanticVersionZod
79
+ } from "@xyo-network/dapp-kit";
80
+ import { z } from "zod";
81
+ var CloudflareR2AdapterId = "network.xyo.dapp.adapter.cloudflare-r2-s3";
82
+ var CloudflareR2AdapterVersion = "0.1.8";
83
+ var CloudflareR2ResourcePlanSchema = "network.xyo.dapp.cloudflare-r2.resource.plan";
84
+ var CloudflareR2ResourcePlanHashAlgorithm = DappDocumentHashAlgorithm;
85
+ var CloudflareR2ResourcePlanDomainSeparator = "xyo-dapp-kit-cloudflare-r2:resource-plan:v1\n";
86
+ var CloudflareR2ResourceClassZod = z.enum([
87
+ "datalake",
88
+ "projection-store",
89
+ "checkpoint-store"
90
+ ]);
91
+ var CloudflareR2CredentialModeZod = z.enum(["static-s3", "temporary-s3"]);
92
+ var OriginZod = z.string().refine((value) => {
93
+ if (value === "*") return true;
94
+ try {
95
+ const url = new URL(value);
96
+ return url.origin === value && ["http:", "https:"].includes(url.protocol);
97
+ } catch {
98
+ return false;
99
+ }
100
+ }, "CORS origin must be * or an HTTP(S) origin");
101
+ var HttpsOriginZod = z.string().refine((value) => {
102
+ try {
103
+ const url = new URL(value);
104
+ return url.protocol === "https:" && url.origin === value;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }, "public base URL must be an HTTPS origin");
109
+ var CloudflareR2ResourceAccessZod = z.discriminatedUnion("mode", [
110
+ z.strictObject({ mode: z.literal("private") }),
111
+ z.strictObject({
112
+ mode: z.literal("public-custom-domain"),
113
+ baseUrl: HttpsOriginZod,
114
+ cors: z.strictObject({
115
+ allowedOrigins: z.array(OriginZod).min(1),
116
+ allowedMethods: z.array(z.enum(["GET", "HEAD"])).min(1),
117
+ exposeEtag: z.literal(true)
118
+ })
119
+ })
120
+ ]);
121
+ var IsoDatePattern = /^\d{4}-\d{2}-\d{2}$/u;
122
+ var CloudflareR2ResourcePlanPreimageZod = z.strictObject({
123
+ schema: z.literal(CloudflareR2ResourcePlanSchema),
124
+ schemaVersion: z.literal(1),
125
+ hashAlgorithm: z.literal(CloudflareR2ResourcePlanHashAlgorithm),
126
+ adapter: z.strictObject({
127
+ adapterId: z.literal(CloudflareR2AdapterId),
128
+ adapterVersion: z.literal(CloudflareR2AdapterVersion),
129
+ family: z.literal("s3-compatible"),
130
+ provider: z.literal("cloudflare-r2")
131
+ }),
132
+ policy: z.strictObject({
133
+ policyId: IdentifierZod,
134
+ policyVersion: SemanticVersionZod,
135
+ reviewedOn: z.string().regex(IsoDatePattern),
136
+ validUntil: z.string().regex(IsoDatePattern)
137
+ }),
138
+ resourceId: IdentifierZod,
139
+ resourceClass: CloudflareR2ResourceClassZod,
140
+ access: CloudflareR2ResourceAccessZod,
141
+ credentialMode: CloudflareR2CredentialModeZod,
142
+ maximumObjectBytes: z.number().int().positive(),
143
+ guarantees: z.strictObject({
144
+ conditionalPut: z.literal(true),
145
+ globalReadAfterWrite: z.literal(true),
146
+ maximumWritesPerSecondPerKey: z.literal(1)
147
+ })
148
+ }).superRefine((plan, context) => {
149
+ if (plan.policy.reviewedOn > plan.policy.validUntil) {
150
+ context.addIssue({
151
+ code: "custom",
152
+ message: "R2 resource policy validity cannot end before its review date",
153
+ path: ["policy", "validUntil"]
154
+ });
155
+ }
156
+ if (plan.resourceClass === "checkpoint-store" && plan.access.mode !== "private") {
157
+ context.addIssue({
158
+ code: "custom",
159
+ message: "R2 checkpoint storage must remain private",
160
+ path: ["access"]
161
+ });
162
+ }
163
+ if (plan.access.mode === "public-custom-domain") {
164
+ if (new Set(plan.access.cors.allowedOrigins).size !== plan.access.cors.allowedOrigins.length) {
165
+ context.addIssue({
166
+ code: "custom",
167
+ message: "public R2 CORS origins must be unique",
168
+ path: ["access", "cors", "allowedOrigins"]
169
+ });
170
+ }
171
+ if (new Set(plan.access.cors.allowedMethods).size !== plan.access.cors.allowedMethods.length) {
172
+ context.addIssue({
173
+ code: "custom",
174
+ message: "public R2 CORS methods must be unique",
175
+ path: ["access", "cors", "allowedMethods"]
176
+ });
177
+ }
178
+ for (const method of ["GET", "HEAD"]) {
179
+ if (!plan.access.cors.allowedMethods.includes(method)) {
180
+ context.addIssue({
181
+ code: "custom",
182
+ message: `public R2 access requires CORS ${method}`,
183
+ path: ["access", "cors", "allowedMethods"]
184
+ });
185
+ }
186
+ }
187
+ }
188
+ });
189
+ var CloudflareR2ResourcePlanZod = CloudflareR2ResourcePlanPreimageZod.safeExtend({ resourcePlanId: HashZod });
190
+ function normalized(value) {
191
+ const preimage = CloudflareR2ResourcePlanPreimageZod.parse(value);
192
+ if (preimage.access.mode === "private") return preimage;
193
+ return CloudflareR2ResourcePlanPreimageZod.parse({
194
+ ...preimage,
195
+ access: {
196
+ ...preimage.access,
197
+ cors: {
198
+ ...preimage.access.cors,
199
+ allowedMethods: [...preimage.access.cors.allowedMethods].sort(),
200
+ allowedOrigins: [...preimage.access.cors.allowedOrigins].sort()
201
+ }
202
+ }
203
+ });
204
+ }
205
+ function hashCloudflareR2ResourcePlan(value) {
206
+ const preimage = normalized(value);
207
+ return createHash("sha256").update(`${CloudflareR2ResourcePlanDomainSeparator}${canonicalizeIJson(preimage)}`, "utf8").digest("hex");
208
+ }
209
+ function createCloudflareR2ResourcePlan(value) {
210
+ const preimage = normalized(value);
211
+ return CloudflareR2ResourcePlanZod.parse({
212
+ ...preimage,
213
+ resourcePlanId: hashCloudflareR2ResourcePlan(preimage)
214
+ });
215
+ }
216
+ function verifyCloudflareR2ResourcePlan(value) {
217
+ const plan = CloudflareR2ResourcePlanZod.parse(value);
218
+ const { resourcePlanId, ...unresolvedPreimage } = plan;
219
+ const preimage = normalized(unresolvedPreimage);
220
+ if (hashCloudflareR2ResourcePlan(preimage) !== resourcePlanId) {
221
+ throw new TypeError("Cloudflare R2 resource plan ID does not match its canonical preimage");
222
+ }
223
+ return CloudflareR2ResourcePlanZod.parse({ ...preimage, resourcePlanId });
224
+ }
225
+
226
+ // src/qualifyCloudflareR2.ts
227
+ import {
228
+ IdentifierZod as IdentifierZod2,
229
+ normalizeObjectStoreKey as normalizeObjectStoreKey2,
230
+ ObjectStorePreconditionCode,
231
+ ObjectStorePreconditionError
232
+ } from "@xyo-network/dapp-kit";
233
+ import { z as z2 } from "zod";
234
+ var CloudflareR2QualificationEvidenceSchema = "network.xyo.dapp.cloudflare-r2.qualification";
235
+ var IsoDateTimeZod = z2.iso.datetime({ offset: true });
236
+ var CloudflareR2QualificationEvidenceZod = z2.strictObject({
237
+ schema: z2.literal(CloudflareR2QualificationEvidenceSchema),
238
+ schemaVersion: z2.literal(1),
239
+ adapterId: z2.literal(CloudflareR2AdapterId),
240
+ adapterVersion: z2.literal(CloudflareR2AdapterVersion),
241
+ evidenceClass: z2.literal("credentialed-cloudflare-r2"),
242
+ observedAt: IsoDateTimeZod,
243
+ probeId: IdentifierZod2,
244
+ resourcePlanId: z2.string().regex(/^[0-9a-f]{64}$/u),
245
+ capabilities: z2.strictObject({
246
+ conditionalCreate: z2.literal(true),
247
+ conditionalReplace: z2.literal(true),
248
+ deleteVisibility: z2.literal(true),
249
+ listVisibility: z2.literal(true),
250
+ readAfterWrite: z2.literal(true)
251
+ })
252
+ });
253
+ function equalBytes(left, right) {
254
+ return left?.byteLength === right.byteLength && left.every((value, index) => value === right[index]);
255
+ }
256
+ function expectedPrecondition(error, code) {
257
+ return error instanceof ObjectStorePreconditionError && error.code === code;
258
+ }
259
+ async function probeCloudflareR2Capabilities(options) {
260
+ const probeId = IdentifierZod2.parse(options.probeId);
261
+ const key = normalizeObjectStoreKey2(`qualification/${probeId}/cas.json`);
262
+ const firstBody = new TextEncoder().encode('{"revision":1}');
263
+ const secondBody = new TextEncoder().encode('{"revision":2}');
264
+ try {
265
+ const first = await options.store.put(key, firstBody, {
266
+ contentType: "application/json",
267
+ ifNotExists: true
268
+ });
269
+ if (first.etag === void 0) throw new Error("Cloudflare R2 qualification requires an ETag");
270
+ if (!equalBytes(await options.store.get(key), firstBody)) throw new Error("Cloudflare R2 failed read-after-write");
271
+ try {
272
+ await options.store.put(key, firstBody, { ifNotExists: true });
273
+ throw new Error("Cloudflare R2 admitted a duplicate conditional create");
274
+ } catch (error) {
275
+ if (!expectedPrecondition(error, ObjectStorePreconditionCode.ifNotExistsFailed)) throw error;
276
+ }
277
+ const second = await options.store.put(key, secondBody, { ifMatch: first.etag });
278
+ if (second.etag === void 0) throw new Error("Cloudflare R2 conditional replace returned no ETag");
279
+ try {
280
+ await options.store.put(key, firstBody, { ifMatch: first.etag });
281
+ throw new Error("Cloudflare R2 admitted a stale conditional replace");
282
+ } catch (error) {
283
+ if (!expectedPrecondition(error, ObjectStorePreconditionCode.ifMatchFailed)) throw error;
284
+ }
285
+ if (!(await options.store.list("qualification")).includes(key)) throw new Error("Cloudflare R2 listing omitted the probe");
286
+ await options.store.delete(key);
287
+ if (await options.store.get(key) !== void 0) throw new Error("Cloudflare R2 delete was not immediately visible");
288
+ return {
289
+ conditionalCreate: true,
290
+ conditionalReplace: true,
291
+ deleteVisibility: true,
292
+ listVisibility: true,
293
+ readAfterWrite: true
294
+ };
295
+ } finally {
296
+ await options.store.delete(key);
297
+ }
298
+ }
299
+ async function qualifyCloudflareR2(options) {
300
+ if (!(options.store instanceof CloudflareR2ObjectStore) || options.store.credentialSource !== "explicit-credentials") {
301
+ throw new TypeError("credentialed Cloudflare R2 qualification requires an explicitly credentialed R2 object store");
302
+ }
303
+ const plan = verifyCloudflareR2ResourcePlan(options.plan);
304
+ const observedAt = IsoDateTimeZod.parse(options.observedAt);
305
+ const observedOn = observedAt.slice(0, 10);
306
+ if (observedOn < plan.policy.reviewedOn || observedOn > plan.policy.validUntil) {
307
+ throw new TypeError("Cloudflare R2 resource policy is not valid at the qualification time");
308
+ }
309
+ const capabilities = await probeCloudflareR2Capabilities({
310
+ probeId: options.probeId,
311
+ store: options.store
312
+ });
313
+ return CloudflareR2QualificationEvidenceZod.parse({
314
+ schema: CloudflareR2QualificationEvidenceSchema,
315
+ schemaVersion: 1,
316
+ adapterId: CloudflareR2AdapterId,
317
+ adapterVersion: CloudflareR2AdapterVersion,
318
+ evidenceClass: "credentialed-cloudflare-r2",
319
+ observedAt,
320
+ probeId: options.probeId,
321
+ resourcePlanId: plan.resourcePlanId,
322
+ capabilities
323
+ });
324
+ }
325
+ async function auditCloudflareR2PublicRead(options) {
326
+ const plan = verifyCloudflareR2ResourcePlan(options.plan);
327
+ if (plan.access.mode !== "public-custom-domain") throw new TypeError("R2 public-read audit requires a public resource plan");
328
+ const url = cloudflareR2PublicObjectUrl(plan.access.baseUrl, "", options.key);
329
+ const plannedOrigins = plan.access.cors.allowedOrigins;
330
+ const issues = [];
331
+ if (!plannedOrigins.includes("*") && !plannedOrigins.includes(options.origin)) {
332
+ issues.push("requesting origin is not admitted by the R2 resource plan");
333
+ }
334
+ const response = await (options.fetchImpl ?? fetch)(url, {
335
+ headers: { Origin: options.origin },
336
+ method: "HEAD"
337
+ });
338
+ if (!response.ok) issues.push(`public object returned HTTP ${response.status}`);
339
+ const allowedOrigin = response.headers.get("access-control-allow-origin");
340
+ if (allowedOrigin !== "*" && allowedOrigin !== options.origin) issues.push("public object did not admit the requesting origin");
341
+ const exposed = (response.headers.get("access-control-expose-headers") ?? "").split(",").map((value) => value.trim().toLowerCase());
342
+ if (!exposed.includes("etag")) issues.push("public object did not expose ETag through CORS");
343
+ if (response.headers.get("etag") === null) issues.push("public object returned no ETag");
344
+ return {
345
+ issues,
346
+ success: issues.length === 0,
347
+ url
348
+ };
349
+ }
350
+ export {
351
+ CloudflareR2AdapterId,
352
+ CloudflareR2AdapterVersion,
353
+ CloudflareR2CredentialModeZod,
354
+ CloudflareR2ObjectStore,
355
+ CloudflareR2QualificationEvidenceSchema,
356
+ CloudflareR2QualificationEvidenceZod,
357
+ CloudflareR2ResourceAccessZod,
358
+ CloudflareR2ResourceClassZod,
359
+ CloudflareR2ResourcePlanDomainSeparator,
360
+ CloudflareR2ResourcePlanHashAlgorithm,
361
+ CloudflareR2ResourcePlanPreimageZod,
362
+ CloudflareR2ResourcePlanSchema,
363
+ CloudflareR2ResourcePlanZod,
364
+ auditCloudflareR2PublicRead,
365
+ cloudflareR2PublicObjectUrl,
366
+ cloudflareR2S3Endpoint,
367
+ createCloudflareR2ResourcePlan,
368
+ hashCloudflareR2ResourcePlan,
369
+ probeCloudflareR2Capabilities,
370
+ qualifyCloudflareR2,
371
+ verifyCloudflareR2ResourcePlan
372
+ };
373
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/cloudflareR2ObjectStore.ts", "../../src/cloudflareR2ResourcePlan.ts", "../../src/qualifyCloudflareR2.ts"],
4
+ "sourcesContent": ["import { S3Client } from '@aws-sdk/client-s3'\nimport { normalizeObjectStoreKey } from '@xyo-network/dapp-kit'\nimport type { S3CommandClient } from '@xyo-network/dapp-kit-s3'\nimport {\n createS3SdkObjectStoreTransport,\n S3ObjectStore,\n} from '@xyo-network/dapp-kit-s3'\n\nexport type CloudflareR2Jurisdiction = 'default' | 'eu'\n\nexport interface CloudflareR2Credentials {\n readonly accessKeyId: string\n readonly secretAccessKey: string\n readonly sessionToken?: string\n}\n\nexport interface CloudflareR2ObjectStoreOptions {\n readonly accountId: string\n readonly bucket: string\n readonly client?: S3Client | S3CommandClient\n readonly credentials?: CloudflareR2Credentials\n readonly jurisdiction?: CloudflareR2Jurisdiction\n readonly maximumObjectBytes: number\n readonly prefix: string\n}\n\nconst AccountIdPattern = /^[0-9a-f]{32}$/u\n\nfunction accountId(value: string): string {\n if (!AccountIdPattern.test(value)) throw new TypeError('Cloudflare account ID must be 32 lowercase hexadecimal characters')\n return value\n}\n\nfunction secret(value: string, name: string): string {\n if (value.trim() === '') throw new TypeError(`${name} must be non-empty`)\n return value\n}\n\nexport function cloudflareR2S3Endpoint(\n accountIdValue: string,\n jurisdiction: CloudflareR2Jurisdiction = 'default',\n): string {\n const prefix = accountId(accountIdValue)\n return jurisdiction === 'eu'\n ? `https://${prefix}.eu.r2.cloudflarestorage.com`\n : `https://${prefix}.r2.cloudflarestorage.com`\n}\n\nfunction publicBaseUrl(value: string): URL {\n const url = new URL(value)\n if (url.protocol !== 'https:' || url.origin !== value) {\n throw new TypeError('Cloudflare R2 public base URL must be an HTTPS origin without credentials, query, or fragment')\n }\n return url\n}\n\nexport function cloudflareR2PublicObjectUrl(baseUrl: string, prefix: string, key: string): string {\n const base = publicBaseUrl(baseUrl)\n const pathname = [\n normalizeObjectStoreKey(prefix, true),\n normalizeObjectStoreKey(key),\n ].filter(value => value !== '').flatMap(value => value.split('/')).map(segment => encodeURIComponent(segment)).join('/')\n return new URL(pathname, `${base.toString().replace(/\\/+$/u, '')}/`).toString()\n}\n\n/**\n * Cloudflare R2 specialization of the portable S3 store. Production\n * construction requires explicit credentials; ambient AWS credential lookup\n * is intentionally unavailable.\n */\nexport class CloudflareR2ObjectStore extends S3ObjectStore {\n readonly credentialSource: 'explicit-credentials' | 'injected-client'\n readonly endpoint: string\n\n private readonly ownedClient?: S3Client\n\n constructor(options: CloudflareR2ObjectStoreOptions) {\n if ((options.client === undefined) === (options.credentials === undefined)) {\n throw new TypeError('provide exactly one of Cloudflare R2 credentials or an injected S3 client')\n }\n const endpoint = cloudflareR2S3Endpoint(options.accountId, options.jurisdiction)\n const credentials = options.credentials\n const ownedClient = credentials === undefined\n ? undefined\n : new S3Client({\n credentials: {\n accessKeyId: secret(credentials.accessKeyId, 'Cloudflare R2 access key ID'),\n secretAccessKey: secret(credentials.secretAccessKey, 'Cloudflare R2 secret access key'),\n ...(credentials.sessionToken !== undefined && { sessionToken: secret(credentials.sessionToken, 'Cloudflare R2 session token') }),\n },\n endpoint,\n region: 'auto',\n })\n const client = options.client ?? ownedClient\n if (client === undefined) throw new TypeError('Cloudflare R2 client construction failed')\n super({\n maximumObjectBytes: options.maximumObjectBytes,\n prefix: options.prefix,\n transport: createS3SdkObjectStoreTransport({ bucket: options.bucket, client }),\n })\n this.endpoint = endpoint\n this.credentialSource = credentials === undefined ? 'injected-client' : 'explicit-credentials'\n this.ownedClient = ownedClient\n }\n\n close(): void {\n this.ownedClient?.destroy()\n }\n}\n", "import { createHash } from 'node:crypto'\n\nimport {\n canonicalizeIJson,\n DappDocumentHashAlgorithm,\n HashZod,\n IdentifierZod,\n SemanticVersionZod,\n} from '@xyo-network/dapp-kit'\nimport { z } from 'zod'\n\nexport const CloudflareR2AdapterId = 'network.xyo.dapp.adapter.cloudflare-r2-s3' as const\nexport const CloudflareR2AdapterVersion = '0.1.8' as const\nexport const CloudflareR2ResourcePlanSchema = 'network.xyo.dapp.cloudflare-r2.resource.plan' as const\nexport const CloudflareR2ResourcePlanHashAlgorithm = DappDocumentHashAlgorithm\nexport const CloudflareR2ResourcePlanDomainSeparator = 'xyo-dapp-kit-cloudflare-r2:resource-plan:v1\\n' as const\n\nexport const CloudflareR2ResourceClassZod = z.enum([\n 'datalake',\n 'projection-store',\n 'checkpoint-store',\n])\nexport type CloudflareR2ResourceClass = z.infer<typeof CloudflareR2ResourceClassZod>\n\nexport const CloudflareR2CredentialModeZod = z.enum(['static-s3', 'temporary-s3'])\nexport type CloudflareR2CredentialMode = z.infer<typeof CloudflareR2CredentialModeZod>\n\nconst OriginZod = z.string().refine((value) => {\n if (value === '*') return true\n try {\n const url = new URL(value)\n return url.origin === value && ['http:', 'https:'].includes(url.protocol)\n } catch {\n return false\n }\n}, 'CORS origin must be * or an HTTP(S) origin')\n\nconst HttpsOriginZod = z.string().refine((value) => {\n try {\n const url = new URL(value)\n return url.protocol === 'https:' && url.origin === value\n } catch {\n return false\n }\n}, 'public base URL must be an HTTPS origin')\n\nexport const CloudflareR2ResourceAccessZod = z.discriminatedUnion('mode', [\n z.strictObject({ mode: z.literal('private') }),\n z.strictObject({\n mode: z.literal('public-custom-domain'),\n baseUrl: HttpsOriginZod,\n cors: z.strictObject({\n allowedOrigins: z.array(OriginZod).min(1),\n allowedMethods: z.array(z.enum(['GET', 'HEAD'])).min(1),\n exposeEtag: z.literal(true),\n }),\n }),\n])\nexport type CloudflareR2ResourceAccess = z.infer<typeof CloudflareR2ResourceAccessZod>\n\nconst IsoDatePattern = /^\\d{4}-\\d{2}-\\d{2}$/u\n\nexport const CloudflareR2ResourcePlanPreimageZod = z.strictObject({\n schema: z.literal(CloudflareR2ResourcePlanSchema),\n schemaVersion: z.literal(1),\n hashAlgorithm: z.literal(CloudflareR2ResourcePlanHashAlgorithm),\n adapter: z.strictObject({\n adapterId: z.literal(CloudflareR2AdapterId),\n adapterVersion: z.literal(CloudflareR2AdapterVersion),\n family: z.literal('s3-compatible'),\n provider: z.literal('cloudflare-r2'),\n }),\n policy: z.strictObject({\n policyId: IdentifierZod,\n policyVersion: SemanticVersionZod,\n reviewedOn: z.string().regex(IsoDatePattern),\n validUntil: z.string().regex(IsoDatePattern),\n }),\n resourceId: IdentifierZod,\n resourceClass: CloudflareR2ResourceClassZod,\n access: CloudflareR2ResourceAccessZod,\n credentialMode: CloudflareR2CredentialModeZod,\n maximumObjectBytes: z.number().int().positive(),\n guarantees: z.strictObject({\n conditionalPut: z.literal(true),\n globalReadAfterWrite: z.literal(true),\n maximumWritesPerSecondPerKey: z.literal(1),\n }),\n}).superRefine((plan, context) => {\n if (plan.policy.reviewedOn > plan.policy.validUntil) {\n context.addIssue({\n code: 'custom',\n message: 'R2 resource policy validity cannot end before its review date',\n path: ['policy', 'validUntil'],\n })\n }\n if (plan.resourceClass === 'checkpoint-store' && plan.access.mode !== 'private') {\n context.addIssue({\n code: 'custom',\n message: 'R2 checkpoint storage must remain private',\n path: ['access'],\n })\n }\n if (plan.access.mode === 'public-custom-domain') {\n if (new Set(plan.access.cors.allowedOrigins).size !== plan.access.cors.allowedOrigins.length) {\n context.addIssue({\n code: 'custom',\n message: 'public R2 CORS origins must be unique',\n path: ['access', 'cors', 'allowedOrigins'],\n })\n }\n if (new Set(plan.access.cors.allowedMethods).size !== plan.access.cors.allowedMethods.length) {\n context.addIssue({\n code: 'custom',\n message: 'public R2 CORS methods must be unique',\n path: ['access', 'cors', 'allowedMethods'],\n })\n }\n for (const method of ['GET', 'HEAD'] as const) {\n if (!plan.access.cors.allowedMethods.includes(method)) {\n context.addIssue({\n code: 'custom',\n message: `public R2 access requires CORS ${method}`,\n path: ['access', 'cors', 'allowedMethods'],\n })\n }\n }\n }\n})\n\nexport type CloudflareR2ResourcePlanPreimage = z.infer<typeof CloudflareR2ResourcePlanPreimageZod>\n\nexport const CloudflareR2ResourcePlanZod = CloudflareR2ResourcePlanPreimageZod.safeExtend({ resourcePlanId: HashZod })\nexport type CloudflareR2ResourcePlan = z.infer<typeof CloudflareR2ResourcePlanZod>\n\nfunction normalized(value: unknown): CloudflareR2ResourcePlanPreimage {\n const preimage = CloudflareR2ResourcePlanPreimageZod.parse(value)\n if (preimage.access.mode === 'private') return preimage\n return CloudflareR2ResourcePlanPreimageZod.parse({\n ...preimage,\n access: {\n ...preimage.access,\n cors: {\n ...preimage.access.cors,\n allowedMethods: [...preimage.access.cors.allowedMethods].sort(),\n allowedOrigins: [...preimage.access.cors.allowedOrigins].sort(),\n },\n },\n })\n}\n\nexport function hashCloudflareR2ResourcePlan(value: unknown): string {\n const preimage = normalized(value)\n return createHash('sha256')\n .update(`${CloudflareR2ResourcePlanDomainSeparator}${canonicalizeIJson(preimage)}`, 'utf8')\n .digest('hex')\n}\n\nexport function createCloudflareR2ResourcePlan(value: unknown): CloudflareR2ResourcePlan {\n const preimage = normalized(value)\n return CloudflareR2ResourcePlanZod.parse({\n ...preimage,\n resourcePlanId: hashCloudflareR2ResourcePlan(preimage),\n })\n}\n\nexport function verifyCloudflareR2ResourcePlan(value: unknown): CloudflareR2ResourcePlan {\n const plan = CloudflareR2ResourcePlanZod.parse(value)\n const { resourcePlanId, ...unresolvedPreimage } = plan\n const preimage = normalized(unresolvedPreimage)\n if (hashCloudflareR2ResourcePlan(preimage) !== resourcePlanId) {\n throw new TypeError('Cloudflare R2 resource plan ID does not match its canonical preimage')\n }\n return CloudflareR2ResourcePlanZod.parse({ ...preimage, resourcePlanId })\n}\n", "import type { ObjectStoreWriter } from '@xyo-network/dapp-kit'\nimport {\n IdentifierZod,\n normalizeObjectStoreKey,\n ObjectStorePreconditionCode,\n ObjectStorePreconditionError,\n} from '@xyo-network/dapp-kit'\nimport { z } from 'zod'\n\nimport {\n CloudflareR2ObjectStore,\n cloudflareR2PublicObjectUrl,\n} from './cloudflareR2ObjectStore.ts'\nimport type { CloudflareR2ResourcePlan } from './cloudflareR2ResourcePlan.ts'\nimport {\n CloudflareR2AdapterId,\n CloudflareR2AdapterVersion,\n verifyCloudflareR2ResourcePlan,\n} from './cloudflareR2ResourcePlan.ts'\n\nexport const CloudflareR2QualificationEvidenceSchema = 'network.xyo.dapp.cloudflare-r2.qualification' as const\n\nconst IsoDateTimeZod = z.iso.datetime({ offset: true })\n\nexport const CloudflareR2QualificationEvidenceZod = z.strictObject({\n schema: z.literal(CloudflareR2QualificationEvidenceSchema),\n schemaVersion: z.literal(1),\n adapterId: z.literal(CloudflareR2AdapterId),\n adapterVersion: z.literal(CloudflareR2AdapterVersion),\n evidenceClass: z.literal('credentialed-cloudflare-r2'),\n observedAt: IsoDateTimeZod,\n probeId: IdentifierZod,\n resourcePlanId: z.string().regex(/^[0-9a-f]{64}$/u),\n capabilities: z.strictObject({\n conditionalCreate: z.literal(true),\n conditionalReplace: z.literal(true),\n deleteVisibility: z.literal(true),\n listVisibility: z.literal(true),\n readAfterWrite: z.literal(true),\n }),\n})\n\nexport type CloudflareR2QualificationEvidence = z.infer<typeof CloudflareR2QualificationEvidenceZod>\n\nexport interface QualifyCloudflareR2Options {\n readonly observedAt: string\n readonly plan: CloudflareR2ResourcePlan\n readonly probeId: string\n readonly store: CloudflareR2ObjectStore\n}\n\nexport interface ProbeCloudflareR2Options {\n readonly probeId: string\n readonly store: ObjectStoreWriter\n}\n\nexport interface CloudflareR2CapabilityProbeResult {\n readonly conditionalCreate: true\n readonly conditionalReplace: true\n readonly deleteVisibility: true\n readonly listVisibility: true\n readonly readAfterWrite: true\n}\n\nfunction equalBytes(left: Uint8Array | undefined, right: Uint8Array): boolean {\n return left?.byteLength === right.byteLength\n && left.every((value, index) => value === right[index])\n}\n\nfunction expectedPrecondition(error: unknown, code: string): boolean {\n return error instanceof ObjectStorePreconditionError && error.code === code\n}\n\n/** Exercises the destructive, prefix-confined capability matrix without assigning an evidence class. */\nexport async function probeCloudflareR2Capabilities(\n options: ProbeCloudflareR2Options,\n): Promise<CloudflareR2CapabilityProbeResult> {\n const probeId = IdentifierZod.parse(options.probeId)\n const key = normalizeObjectStoreKey(`qualification/${probeId}/cas.json`)\n const firstBody = new TextEncoder().encode('{\"revision\":1}')\n const secondBody = new TextEncoder().encode('{\"revision\":2}')\n try {\n const first = await options.store.put(key, firstBody, {\n contentType: 'application/json',\n ifNotExists: true,\n })\n if (first.etag === undefined) throw new Error('Cloudflare R2 qualification requires an ETag')\n if (!equalBytes(await options.store.get(key), firstBody)) throw new Error('Cloudflare R2 failed read-after-write')\n try {\n await options.store.put(key, firstBody, { ifNotExists: true })\n throw new Error('Cloudflare R2 admitted a duplicate conditional create')\n } catch (error) {\n if (!expectedPrecondition(error, ObjectStorePreconditionCode.ifNotExistsFailed)) throw error\n }\n const second = await options.store.put(key, secondBody, { ifMatch: first.etag })\n if (second.etag === undefined) throw new Error('Cloudflare R2 conditional replace returned no ETag')\n try {\n await options.store.put(key, firstBody, { ifMatch: first.etag })\n throw new Error('Cloudflare R2 admitted a stale conditional replace')\n } catch (error) {\n if (!expectedPrecondition(error, ObjectStorePreconditionCode.ifMatchFailed)) throw error\n }\n if (!(await options.store.list('qualification')).includes(key)) throw new Error('Cloudflare R2 listing omitted the probe')\n await options.store.delete(key)\n if (await options.store.get(key) !== undefined) throw new Error('Cloudflare R2 delete was not immediately visible')\n return {\n conditionalCreate: true,\n conditionalReplace: true,\n deleteVisibility: true,\n listVisibility: true,\n readAfterWrite: true,\n }\n } finally {\n await options.store.delete(key)\n }\n}\n\n/** Runs the destructive credentialed gate against an explicitly credentialed R2 instance. */\nexport async function qualifyCloudflareR2(options: QualifyCloudflareR2Options): Promise<CloudflareR2QualificationEvidence> {\n if (!(options.store instanceof CloudflareR2ObjectStore)\n || options.store.credentialSource !== 'explicit-credentials') {\n throw new TypeError('credentialed Cloudflare R2 qualification requires an explicitly credentialed R2 object store')\n }\n const plan = verifyCloudflareR2ResourcePlan(options.plan)\n const observedAt = IsoDateTimeZod.parse(options.observedAt)\n const observedOn = observedAt.slice(0, 10)\n if (observedOn < plan.policy.reviewedOn || observedOn > plan.policy.validUntil) {\n throw new TypeError('Cloudflare R2 resource policy is not valid at the qualification time')\n }\n const capabilities = await probeCloudflareR2Capabilities({\n probeId: options.probeId,\n store: options.store,\n })\n return CloudflareR2QualificationEvidenceZod.parse({\n schema: CloudflareR2QualificationEvidenceSchema,\n schemaVersion: 1,\n adapterId: CloudflareR2AdapterId,\n adapterVersion: CloudflareR2AdapterVersion,\n evidenceClass: 'credentialed-cloudflare-r2',\n observedAt,\n probeId: options.probeId,\n resourcePlanId: plan.resourcePlanId,\n capabilities,\n })\n}\n\nexport interface AuditCloudflareR2PublicReadOptions {\n readonly fetchImpl?: typeof fetch\n readonly key: string\n readonly origin: string\n readonly plan: CloudflareR2ResourcePlan\n}\n\nexport interface CloudflareR2PublicReadAudit {\n readonly issues: readonly string[]\n readonly success: boolean\n readonly url: string\n}\n\n/** Read-only hosted audit for the public custom-domain and browser CORS boundary. */\nexport async function auditCloudflareR2PublicRead(\n options: AuditCloudflareR2PublicReadOptions,\n): Promise<CloudflareR2PublicReadAudit> {\n const plan = verifyCloudflareR2ResourcePlan(options.plan)\n if (plan.access.mode !== 'public-custom-domain') throw new TypeError('R2 public-read audit requires a public resource plan')\n const url = cloudflareR2PublicObjectUrl(plan.access.baseUrl, '', options.key)\n const plannedOrigins = plan.access.cors.allowedOrigins\n const issues: string[] = []\n if (!plannedOrigins.includes('*') && !plannedOrigins.includes(options.origin)) {\n issues.push('requesting origin is not admitted by the R2 resource plan')\n }\n const response = await (options.fetchImpl ?? fetch)(url, {\n headers: { Origin: options.origin },\n method: 'HEAD',\n })\n if (!response.ok) issues.push(`public object returned HTTP ${response.status}`)\n const allowedOrigin = response.headers.get('access-control-allow-origin')\n if (allowedOrigin !== '*' && allowedOrigin !== options.origin) issues.push('public object did not admit the requesting origin')\n const exposed = (response.headers.get('access-control-expose-headers') ?? '')\n .split(',').map(value => value.trim().toLowerCase())\n if (!exposed.includes('etag')) issues.push('public object did not expose ETag through CORS')\n if (response.headers.get('etag') === null) issues.push('public object returned no ETag')\n return {\n issues, success: issues.length === 0, url,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["IdentifierZod", "normalizeObjectStoreKey", "z", "z", "IdentifierZod", "normalizeObjectStoreKey"]
7
+ }
@@ -0,0 +1,58 @@
1
+ import type { ObjectStoreWriter } from '@xyo-network/dapp-kit';
2
+ import { z } from 'zod';
3
+ import { CloudflareR2ObjectStore } from './cloudflareR2ObjectStore.ts';
4
+ import type { CloudflareR2ResourcePlan } from './cloudflareR2ResourcePlan.ts';
5
+ export declare const CloudflareR2QualificationEvidenceSchema: "network.xyo.dapp.cloudflare-r2.qualification";
6
+ export declare const CloudflareR2QualificationEvidenceZod: z.ZodObject<{
7
+ schema: z.ZodLiteral<"network.xyo.dapp.cloudflare-r2.qualification">;
8
+ schemaVersion: z.ZodLiteral<1>;
9
+ adapterId: z.ZodLiteral<"network.xyo.dapp.adapter.cloudflare-r2-s3">;
10
+ adapterVersion: z.ZodLiteral<"0.1.8">;
11
+ evidenceClass: z.ZodLiteral<"credentialed-cloudflare-r2">;
12
+ observedAt: z.ZodISODateTime;
13
+ probeId: z.ZodString;
14
+ resourcePlanId: z.ZodString;
15
+ capabilities: z.ZodObject<{
16
+ conditionalCreate: z.ZodLiteral<true>;
17
+ conditionalReplace: z.ZodLiteral<true>;
18
+ deleteVisibility: z.ZodLiteral<true>;
19
+ listVisibility: z.ZodLiteral<true>;
20
+ readAfterWrite: z.ZodLiteral<true>;
21
+ }, z.core.$strict>;
22
+ }, z.core.$strict>;
23
+ export type CloudflareR2QualificationEvidence = z.infer<typeof CloudflareR2QualificationEvidenceZod>;
24
+ export interface QualifyCloudflareR2Options {
25
+ readonly observedAt: string;
26
+ readonly plan: CloudflareR2ResourcePlan;
27
+ readonly probeId: string;
28
+ readonly store: CloudflareR2ObjectStore;
29
+ }
30
+ export interface ProbeCloudflareR2Options {
31
+ readonly probeId: string;
32
+ readonly store: ObjectStoreWriter;
33
+ }
34
+ export interface CloudflareR2CapabilityProbeResult {
35
+ readonly conditionalCreate: true;
36
+ readonly conditionalReplace: true;
37
+ readonly deleteVisibility: true;
38
+ readonly listVisibility: true;
39
+ readonly readAfterWrite: true;
40
+ }
41
+ /** Exercises the destructive, prefix-confined capability matrix without assigning an evidence class. */
42
+ export declare function probeCloudflareR2Capabilities(options: ProbeCloudflareR2Options): Promise<CloudflareR2CapabilityProbeResult>;
43
+ /** Runs the destructive credentialed gate against an explicitly credentialed R2 instance. */
44
+ export declare function qualifyCloudflareR2(options: QualifyCloudflareR2Options): Promise<CloudflareR2QualificationEvidence>;
45
+ export interface AuditCloudflareR2PublicReadOptions {
46
+ readonly fetchImpl?: typeof fetch;
47
+ readonly key: string;
48
+ readonly origin: string;
49
+ readonly plan: CloudflareR2ResourcePlan;
50
+ }
51
+ export interface CloudflareR2PublicReadAudit {
52
+ readonly issues: readonly string[];
53
+ readonly success: boolean;
54
+ readonly url: string;
55
+ }
56
+ /** Read-only hosted audit for the public custom-domain and browser CORS boundary. */
57
+ export declare function auditCloudflareR2PublicRead(options: AuditCloudflareR2PublicReadOptions): Promise<CloudflareR2PublicReadAudit>;
58
+ //# sourceMappingURL=qualifyCloudflareR2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qualifyCloudflareR2.d.ts","sourceRoot":"","sources":["../../src/qualifyCloudflareR2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAO9D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EACL,uBAAuB,EAExB,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAO7E,eAAO,MAAM,uCAAuC,EAAG,8CAAuD,CAAA;AAI9G,eAAO,MAAM,oCAAoC;;;;;;;;;;;;;;;;kBAgB/C,CAAA;AAEF,MAAM,MAAM,iCAAiC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oCAAoC,CAAC,CAAA;AAEpG,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,uBAAuB,CAAA;CACxC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;CAClC;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,iBAAiB,EAAE,IAAI,CAAA;IAChC,QAAQ,CAAC,kBAAkB,EAAE,IAAI,CAAA;IACjC,QAAQ,CAAC,gBAAgB,EAAE,IAAI,CAAA;IAC/B,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAA;IAC7B,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAA;CAC9B;AAWD,wGAAwG;AACxG,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iCAAiC,CAAC,CAuC5C;AAED,6FAA6F;AAC7F,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,iCAAiC,CAAC,CA0BzH;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAA;CACxC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,qFAAqF;AACrF,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,2BAA2B,CAAC,CAuBtC"}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@xyo-network/dapp-kit-cloudflare-r2",
3
+ "version": "0.1.9",
4
+ "description": "Cloudflare R2 resource adapters and qualification for headless XL1 dApps",
5
+ "keywords": [
6
+ "xyo",
7
+ "xl1",
8
+ "dapp",
9
+ "cloudflare",
10
+ "r2",
11
+ "typescript"
12
+ ],
13
+ "bugs": {
14
+ "url": "https://github.com/XYOracleNetwork/dapp-kit/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/XYOracleNetwork/dapp-kit.git",
19
+ "directory": "packages/dapp-kit-cloudflare-r2"
20
+ },
21
+ "license": "UNLICENSED",
22
+ "sideEffects": false,
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/node/index.d.ts",
27
+ "default": "./dist/node/index.mjs"
28
+ },
29
+ "./package.json": "./package.json",
30
+ "./README.md": "./README.md"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md"
35
+ ],
36
+ "dependencies": {
37
+ "@xyo-network/dapp-kit": "~0.1.9",
38
+ "@xyo-network/dapp-kit-s3": "~0.1.9"
39
+ },
40
+ "devDependencies": {
41
+ "@ariestools/toolchain": "~9.0.2",
42
+ "@ariestools/tsconfig": "~9.0.2",
43
+ "@aws-sdk/client-s3": "~3.1110.0",
44
+ "@noble/hashes": "~2.3.0",
45
+ "@types/node": "~26.2.0",
46
+ "eslint": "~10.8.1",
47
+ "eslint-import-resolver-typescript": "~4.4.5",
48
+ "typescript": "~6.0.3",
49
+ "vite": "~8.2.1",
50
+ "vitest": "~4.1.10",
51
+ "zod": "~4.4.3"
52
+ },
53
+ "peerDependencies": {
54
+ "@aws-sdk/client-s3": "^3.1110",
55
+ "@noble/hashes": "^2.3",
56
+ "zod": "^4.4"
57
+ },
58
+ "engines": {
59
+ "node": ">=24"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ },
64
+ "scripts": {
65
+ "test": "vitest run",
66
+ "test:ci": "vitest run"
67
+ }
68
+ }