alchemy 0.1.11 → 0.1.12
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 +324 -0
- package/lib/apply.d.ts +2 -2
- package/lib/apply.d.ts.map +1 -1
- package/lib/apply.js +2 -2
- package/lib/apply.js.map +1 -1
- package/lib/aws/index.d.ts +1 -0
- package/lib/aws/index.d.ts.map +1 -1
- package/lib/aws/index.js +1 -0
- package/lib/aws/index.js.map +1 -1
- package/lib/aws/ses.d.ts +75 -0
- package/lib/aws/ses.d.ts.map +1 -0
- package/lib/aws/ses.js +197 -0
- package/lib/aws/ses.js.map +1 -0
- package/lib/cloudflare/api.d.ts +84 -0
- package/lib/cloudflare/api.d.ts.map +1 -0
- package/lib/cloudflare/api.js +178 -0
- package/lib/cloudflare/api.js.map +1 -0
- package/lib/cloudflare/asset-manifest.d.ts +15 -0
- package/lib/cloudflare/asset-manifest.d.ts.map +1 -0
- package/lib/cloudflare/asset-manifest.js +2 -0
- package/lib/cloudflare/asset-manifest.js.map +1 -0
- package/lib/cloudflare/auth.d.ts +52 -0
- package/lib/cloudflare/auth.d.ts.map +1 -0
- package/lib/cloudflare/auth.js +114 -0
- package/lib/cloudflare/auth.js.map +1 -0
- package/lib/cloudflare/bindings.d.ts +244 -0
- package/lib/cloudflare/bindings.d.ts.map +1 -0
- package/lib/cloudflare/bindings.js +7 -0
- package/lib/cloudflare/bindings.js.map +1 -0
- package/lib/cloudflare/generate-asset-manifest.d.ts +3 -0
- package/lib/cloudflare/generate-asset-manifest.d.ts.map +1 -0
- package/lib/cloudflare/generate-asset-manifest.js +69 -0
- package/lib/cloudflare/generate-asset-manifest.js.map +1 -0
- package/lib/cloudflare/index.d.ts +6 -0
- package/lib/cloudflare/index.d.ts.map +1 -0
- package/lib/cloudflare/index.js +6 -0
- package/lib/cloudflare/index.js.map +1 -0
- package/lib/cloudflare/kv-namespace.d.ts +61 -0
- package/lib/cloudflare/kv-namespace.d.ts.map +1 -0
- package/lib/cloudflare/kv-namespace.js +93 -0
- package/lib/cloudflare/kv-namespace.js.map +1 -0
- package/lib/cloudflare/state.d.ts +83 -0
- package/lib/cloudflare/state.d.ts.map +1 -0
- package/lib/cloudflare/state.js +109 -0
- package/lib/cloudflare/state.js.map +1 -0
- package/lib/cloudflare/static-site-router.d.ts +10 -0
- package/lib/cloudflare/static-site-router.d.ts.map +1 -0
- package/lib/cloudflare/static-site-router.js +88 -0
- package/lib/cloudflare/static-site-router.js.map +1 -0
- package/lib/cloudflare/static-site.d.ts +166 -0
- package/lib/cloudflare/static-site.d.ts.map +1 -0
- package/lib/cloudflare/static-site.js +152 -0
- package/lib/cloudflare/static-site.js.map +1 -0
- package/lib/cloudflare/types.d.ts +52 -0
- package/lib/cloudflare/types.d.ts.map +1 -0
- package/lib/cloudflare/types.js +21 -0
- package/lib/cloudflare/types.js.map +1 -0
- package/lib/cloudflare/upload-asset-manifest.d.ts +12 -0
- package/lib/cloudflare/upload-asset-manifest.d.ts.map +1 -0
- package/lib/cloudflare/upload-asset-manifest.js +55 -0
- package/lib/cloudflare/upload-asset-manifest.js.map +1 -0
- package/lib/cloudflare/worker.d.ts +87 -0
- package/lib/cloudflare/worker.d.ts.map +1 -0
- package/lib/cloudflare/worker.js +364 -0
- package/lib/cloudflare/worker.js.map +1 -0
- package/lib/esbuild.d.ts +82 -0
- package/lib/esbuild.d.ts.map +1 -1
- package/lib/esbuild.js +24 -17
- package/lib/esbuild.js.map +1 -1
- package/lib/fs.d.ts +1 -1
- package/lib/global.d.ts.map +1 -1
- package/lib/global.js +3 -1
- package/lib/global.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/main.js +1 -1
- package/lib/main.js.map +1 -1
- package/lib/output.d.ts +17 -13
- package/lib/output.d.ts.map +1 -1
- package/lib/output.js +2 -2
- package/lib/output.js.map +1 -1
- package/lib/resource.d.ts +3 -2
- package/lib/resource.d.ts.map +1 -1
- package/lib/resource.js +2 -2
- package/lib/resource.js.map +1 -1
- package/lib/utils/content-type.d.ts +8 -0
- package/lib/utils/content-type.d.ts.map +1 -0
- package/lib/utils/content-type.js +41 -0
- package/lib/utils/content-type.js.map +1 -0
- package/package.json +26 -8
- package/src/apply.ts +5 -5
- package/src/aws/index.ts +1 -0
- package/src/aws/ses.ts +346 -0
- package/src/cloudflare/api.ts +276 -0
- package/src/cloudflare/asset-manifest.ts +16 -0
- package/src/cloudflare/auth.ts +187 -0
- package/src/cloudflare/bindings.ts +286 -0
- package/src/cloudflare/generate-asset-manifest.ts +93 -0
- package/src/cloudflare/index.ts +5 -0
- package/src/cloudflare/kv-namespace.ts +195 -0
- package/src/cloudflare/state.ts +138 -0
- package/src/cloudflare/static-site-router.ts +108 -0
- package/src/cloudflare/static-site.ts +379 -0
- package/src/cloudflare/types.ts +71 -0
- package/src/cloudflare/upload-asset-manifest.ts +83 -0
- package/src/cloudflare/worker.ts +568 -0
- package/src/esbuild.ts +31 -22
- package/src/global.ts +1 -0
- package/src/index.ts +0 -1
- package/src/main.ts +1 -1
- package/src/output.ts +32 -32
- package/src/resource.ts +11 -5
- package/src/utils/content-type.ts +42 -0
- package/lib/$.d.ts +0 -7
- package/lib/$.d.ts.map +0 -1
- package/lib/$.js +0 -31
- package/lib/$.js.map +0 -1
- package/lib/agent/ai.d.ts +0 -40
- package/lib/agent/ai.d.ts.map +0 -1
- package/lib/agent/ai.js +0 -132
- package/lib/agent/ai.js.map +0 -1
- package/lib/agent/anthropic.d.ts +0 -9
- package/lib/agent/anthropic.d.ts.map +0 -1
- package/lib/agent/anthropic.js +0 -25
- package/lib/agent/anthropic.js.map +0 -1
- package/lib/agent/dependencies.d.ts +0 -4
- package/lib/agent/dependencies.d.ts.map +0 -1
- package/lib/agent/dependencies.js +0 -48
- package/lib/agent/dependencies.js.map +0 -1
- package/lib/agent/file-context.d.ts +0 -13
- package/lib/agent/file-context.d.ts.map +0 -1
- package/lib/agent/file-context.js +0 -6
- package/lib/agent/file-context.js.map +0 -1
- package/lib/agent/index.d.ts +0 -10
- package/lib/agent/index.d.ts.map +0 -1
- package/lib/agent/index.js +0 -10
- package/lib/agent/index.js.map +0 -1
- package/lib/agent/model.d.ts +0 -8
- package/lib/agent/model.d.ts.map +0 -1
- package/lib/agent/model.js +0 -29
- package/lib/agent/model.js.map +0 -1
- package/lib/agent/module.d.ts +0 -81
- package/lib/agent/module.d.ts.map +0 -1
- package/lib/agent/module.js +0 -309
- package/lib/agent/module.js.map +0 -1
- package/lib/agent/openai.d.ts +0 -6
- package/lib/agent/openai.d.ts.map +0 -1
- package/lib/agent/openai.js +0 -41
- package/lib/agent/openai.js.map +0 -1
- package/lib/agent/program.d.ts +0 -12
- package/lib/agent/program.d.ts.map +0 -1
- package/lib/agent/program.js +0 -21
- package/lib/agent/program.js.map +0 -1
- package/lib/agent/prompts.d.ts +0 -4
- package/lib/agent/prompts.d.ts.map +0 -1
- package/lib/agent/prompts.js +0 -11
- package/lib/agent/prompts.js.map +0 -1
- package/lib/agent/scrape.d.ts +0 -71
- package/lib/agent/scrape.d.ts.map +0 -1
- package/lib/agent/scrape.js +0 -45
- package/lib/agent/scrape.js.map +0 -1
- package/lib/aws/auto/docs.d.ts +0 -11
- package/lib/aws/auto/docs.d.ts.map +0 -1
- package/lib/aws/auto/docs.js +0 -56
- package/lib/aws/auto/docs.js.map +0 -1
- package/lib/aws/auto/index.d.ts +0 -5
- package/lib/aws/auto/index.d.ts.map +0 -1
- package/lib/aws/auto/index.js +0 -5
- package/lib/aws/auto/index.js.map +0 -1
- package/lib/aws/auto/resource.d.ts +0 -24
- package/lib/aws/auto/resource.d.ts.map +0 -1
- package/lib/aws/auto/resource.js +0 -194
- package/lib/aws/auto/resource.js.map +0 -1
- package/lib/aws/auto/service.d.ts +0 -10
- package/lib/aws/auto/service.d.ts.map +0 -1
- package/lib/aws/auto/service.js +0 -34
- package/lib/aws/auto/service.js.map +0 -1
- package/lib/aws/auto/spec.d.ts +0 -83
- package/lib/aws/auto/spec.d.ts.map +0 -1
- package/lib/aws/auto/spec.js +0 -58
- package/lib/aws/auto/spec.js.map +0 -1
- package/lib/markdown/design.d.ts +0 -17
- package/lib/markdown/design.d.ts.map +0 -1
- package/lib/markdown/design.js +0 -92
- package/lib/markdown/design.js.map +0 -1
- package/lib/markdown/extract.d.ts +0 -7
- package/lib/markdown/extract.d.ts.map +0 -1
- package/lib/markdown/extract.js +0 -10
- package/lib/markdown/extract.js.map +0 -1
- package/lib/markdown/index.d.ts +0 -4
- package/lib/markdown/index.d.ts.map +0 -1
- package/lib/markdown/index.js +0 -4
- package/lib/markdown/index.js.map +0 -1
- package/lib/markdown/requirements.d.ts +0 -79
- package/lib/markdown/requirements.d.ts.map +0 -1
- package/lib/markdown/requirements.js +0 -105
- package/lib/markdown/requirements.js.map +0 -1
- package/lib/typescript/check-omission.d.ts +0 -2
- package/lib/typescript/check-omission.d.ts.map +0 -1
- package/lib/typescript/check-omission.js +0 -43
- package/lib/typescript/check-omission.js.map +0 -1
- package/lib/typescript/debug-type-errors.d.ts +0 -3
- package/lib/typescript/debug-type-errors.d.ts.map +0 -1
- package/lib/typescript/debug-type-errors.js +0 -35
- package/lib/typescript/debug-type-errors.js.map +0 -1
- package/lib/typescript/extract.d.ts +0 -7
- package/lib/typescript/extract.d.ts.map +0 -1
- package/lib/typescript/extract.js +0 -10
- package/lib/typescript/extract.js.map +0 -1
- package/lib/typescript/index.d.ts +0 -7
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/index.js +0 -7
- package/lib/typescript/index.js.map +0 -1
- package/lib/typescript/install-dependencies.d.ts +0 -29
- package/lib/typescript/install-dependencies.d.ts.map +0 -1
- package/lib/typescript/install-dependencies.js +0 -52
- package/lib/typescript/install-dependencies.js.map +0 -1
- package/lib/typescript/package.d.ts +0 -157
- package/lib/typescript/package.d.ts.map +0 -1
- package/lib/typescript/package.js +0 -115
- package/lib/typescript/package.js.map +0 -1
- package/lib/typescript/repair-omissions.d.ts +0 -3
- package/lib/typescript/repair-omissions.d.ts.map +0 -1
- package/lib/typescript/repair-omissions.js +0 -23
- package/lib/typescript/repair-omissions.js.map +0 -1
- package/lib/typescript/repair.d.ts +0 -3
- package/lib/typescript/repair.d.ts.map +0 -1
- package/lib/typescript/repair.js +0 -34
- package/lib/typescript/repair.js.map +0 -1
- package/lib/typescript/tsconfig.d.ts +0 -469
- package/lib/typescript/tsconfig.d.ts.map +0 -1
- package/lib/typescript/tsconfig.js +0 -143
- package/lib/typescript/tsconfig.js.map +0 -1
- package/lib/typescript/typescript.d.ts +0 -107
- package/lib/typescript/typescript.d.ts.map +0 -1
- package/lib/typescript/typescript.js +0 -180
- package/lib/typescript/typescript.js.map +0 -1
- package/lib/typescript/validate.d.ts +0 -10
- package/lib/typescript/validate.d.ts.map +0 -1
- package/lib/typescript/validate.js +0 -49
- package/lib/typescript/validate.js.map +0 -1
- package/src/$.ts +0 -36
- package/src/agent/ai.ts +0 -215
- package/src/agent/anthropic.ts +0 -31
- package/src/agent/dependencies.ts +0 -62
- package/src/agent/file-context.ts +0 -8
- package/src/agent/index.ts +0 -9
- package/src/agent/model.ts +0 -39
- package/src/agent/module.ts +0 -418
- package/src/agent/openai.ts +0 -50
- package/src/agent/program.md +0 -38
- package/src/agent/program.ts +0 -37
- package/src/agent/prompts.ts +0 -20
- package/src/agent/scrape.ts +0 -47
- package/src/aws/auto/cfn.json +0 -225310
- package/src/aws/auto/docs.ts +0 -73
- package/src/aws/auto/index.ts +0 -4
- package/src/aws/auto/resource.ts +0 -269
- package/src/aws/auto/service.ts +0 -51
- package/src/aws/auto/spec.ts +0 -160
- package/src/markdown/design.ts +0 -125
- package/src/markdown/extract.ts +0 -9
- package/src/markdown/index.ts +0 -3
- package/src/markdown/requirements.ts +0 -135
- package/src/typescript/check-omission.ts +0 -47
- package/src/typescript/debug-type-errors.ts +0 -42
- package/src/typescript/extract.ts +0 -9
- package/src/typescript/index.ts +0 -6
- package/src/typescript/install-dependencies.ts +0 -61
- package/src/typescript/package.ts +0 -143
- package/src/typescript/repair-omissions.ts +0 -31
- package/src/typescript/repair.ts +0 -45
- package/src/typescript/tsconfig.ts +0 -170
- package/src/typescript/typescript.ts +0 -217
- package/src/typescript/validate.ts +0 -73
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { type Context, Resource } from "../resource";
|
|
2
|
+
import { createCloudflareApi } from "./api";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Properties for creating or updating a KV Namespace
|
|
6
|
+
*/
|
|
7
|
+
export interface KVNamespaceProps {
|
|
8
|
+
/**
|
|
9
|
+
* Title of the namespace
|
|
10
|
+
*/
|
|
11
|
+
title: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* KV pairs to store in the namespace
|
|
15
|
+
* Only used for initial setup or updates
|
|
16
|
+
*/
|
|
17
|
+
values?: KVPair[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Key-value pair to store in a KV Namespace
|
|
22
|
+
*/
|
|
23
|
+
export interface KVPair {
|
|
24
|
+
/**
|
|
25
|
+
* Key name
|
|
26
|
+
*/
|
|
27
|
+
key: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Value to store (string or JSON object)
|
|
31
|
+
*/
|
|
32
|
+
value: string | object;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Optional expiration in seconds from now
|
|
36
|
+
*/
|
|
37
|
+
expiration?: number;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Optional expiration timestamp in seconds since epoch
|
|
41
|
+
*/
|
|
42
|
+
expirationTtl?: number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Optional metadata for the key
|
|
46
|
+
*/
|
|
47
|
+
metadata?: any;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Output returned after KV Namespace creation/update
|
|
52
|
+
*/
|
|
53
|
+
export interface KVNamespaceOutput extends KVNamespaceProps {
|
|
54
|
+
/**
|
|
55
|
+
* The ID of the namespace
|
|
56
|
+
*/
|
|
57
|
+
id: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Time at which the namespace was created
|
|
61
|
+
*/
|
|
62
|
+
createdAt: number;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Time at which the namespace was last modified
|
|
66
|
+
*/
|
|
67
|
+
modifiedAt: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class KVNamespace extends Resource(
|
|
71
|
+
"cloudflare::KVNamespace",
|
|
72
|
+
async (ctx: Context<KVNamespaceOutput>, props: KVNamespaceProps) => {
|
|
73
|
+
// Create Cloudflare API client with automatic account discovery
|
|
74
|
+
const api = await createCloudflareApi();
|
|
75
|
+
|
|
76
|
+
if (ctx.event === "delete") {
|
|
77
|
+
// For delete operations, we need to check if the namespace ID exists in the output
|
|
78
|
+
const namespaceId = ctx.output?.id;
|
|
79
|
+
|
|
80
|
+
if (namespaceId) {
|
|
81
|
+
// Delete KV namespace
|
|
82
|
+
const deleteResponse = await api.delete(
|
|
83
|
+
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}`,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
87
|
+
const errorData: any = await deleteResponse.json().catch(() => ({
|
|
88
|
+
errors: [{ message: deleteResponse.statusText }],
|
|
89
|
+
}));
|
|
90
|
+
console.error(
|
|
91
|
+
"Error deleting KV namespace:",
|
|
92
|
+
errorData.errors?.[0]?.message || deleteResponse.statusText,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Return minimal output for deleted state
|
|
98
|
+
return;
|
|
99
|
+
} else {
|
|
100
|
+
// For create or update operations
|
|
101
|
+
// If ctx.event is "update", we expect ctx.output to exist
|
|
102
|
+
let namespaceId = ctx.event === "update" ? ctx.output?.id || "" : "";
|
|
103
|
+
let createdAt =
|
|
104
|
+
ctx.event === "update"
|
|
105
|
+
? ctx.output?.createdAt || Date.now()
|
|
106
|
+
: Date.now();
|
|
107
|
+
|
|
108
|
+
if (ctx.event === "update" && namespaceId) {
|
|
109
|
+
// Can't update a KV namespace title directly, just work with existing ID
|
|
110
|
+
} else {
|
|
111
|
+
// Create new KV namespace
|
|
112
|
+
const createResponse = await api.post(
|
|
113
|
+
`/accounts/${api.accountId}/storage/kv/namespaces`,
|
|
114
|
+
{
|
|
115
|
+
title: props.title,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
if (!createResponse.ok) {
|
|
120
|
+
const errorData: any = await createResponse.json().catch(() => ({
|
|
121
|
+
errors: [{ message: createResponse.statusText }],
|
|
122
|
+
}));
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Error creating KV namespace: ${errorData.errors?.[0]?.message || createResponse.statusText}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const createResult: any = await createResponse.json();
|
|
129
|
+
namespaceId = createResult.result.id;
|
|
130
|
+
createdAt = Date.now();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handle KV pairs if provided
|
|
134
|
+
if (props.values && props.values.length > 0 && namespaceId) {
|
|
135
|
+
// Process KV pairs in batches of 10000 (API limit)
|
|
136
|
+
const BATCH_SIZE = 10000;
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < props.values.length; i += BATCH_SIZE) {
|
|
139
|
+
const batch = props.values.slice(i, i + BATCH_SIZE);
|
|
140
|
+
|
|
141
|
+
const bulkPayload = batch.map((entry) => {
|
|
142
|
+
const item: any = {
|
|
143
|
+
key: entry.key,
|
|
144
|
+
value:
|
|
145
|
+
typeof entry.value === "string"
|
|
146
|
+
? entry.value
|
|
147
|
+
: JSON.stringify(entry.value),
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (entry.expiration) {
|
|
151
|
+
item.expiration = entry.expiration;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (entry.expirationTtl) {
|
|
155
|
+
item.expiration_ttl = entry.expirationTtl;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (entry.metadata) {
|
|
159
|
+
item.metadata = entry.metadata;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return item;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const bulkResponse = await api.put(
|
|
166
|
+
`/accounts/${api.accountId}/storage/kv/namespaces/${namespaceId}/bulk`,
|
|
167
|
+
bulkPayload,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (!bulkResponse.ok) {
|
|
171
|
+
const errorData: any = await bulkResponse.json().catch(() => ({
|
|
172
|
+
errors: [{ message: bulkResponse.statusText }],
|
|
173
|
+
}));
|
|
174
|
+
console.warn(
|
|
175
|
+
`Error writing KV batch: ${errorData.errors?.[0]?.message || bulkResponse.statusText}`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const now = Date.now();
|
|
182
|
+
|
|
183
|
+
// Construct the output
|
|
184
|
+
const output: KVNamespaceOutput = {
|
|
185
|
+
id: namespaceId,
|
|
186
|
+
title: props.title,
|
|
187
|
+
values: props.values,
|
|
188
|
+
createdAt: createdAt,
|
|
189
|
+
modifiedAt: now,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
return output;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
) {}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { KVNamespace } from "@cloudflare/workers-types";
|
|
2
|
+
import type { State, StateStore } from "../state";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for CloudflareStateStore
|
|
6
|
+
*/
|
|
7
|
+
export interface CloudflareStateStoreOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The prefix to use for keys in the KV namespace
|
|
10
|
+
* This allows multiple state stores to use the same KV namespace
|
|
11
|
+
*/
|
|
12
|
+
prefix?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* State store implementation using Cloudflare KV Namespace
|
|
17
|
+
*/
|
|
18
|
+
export class CloudflareStateStore implements StateStore {
|
|
19
|
+
private namespace: KVNamespace;
|
|
20
|
+
private prefix: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a new CloudflareStateStore
|
|
24
|
+
*
|
|
25
|
+
* @param namespace The KV namespace to use for storing state
|
|
26
|
+
* @param options Options for the state store
|
|
27
|
+
*/
|
|
28
|
+
constructor(
|
|
29
|
+
namespace: KVNamespace,
|
|
30
|
+
options: CloudflareStateStoreOptions = {},
|
|
31
|
+
) {
|
|
32
|
+
this.namespace = namespace;
|
|
33
|
+
this.prefix = options.prefix || "alchemy-state:";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* No initialization needed for KV namespaces as they are pre-provisioned
|
|
38
|
+
*/
|
|
39
|
+
async init(): Promise<void> {
|
|
40
|
+
// KV namespaces are pre-provisioned in Cloudflare, so no initialization is needed
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* KV namespaces cannot be deleted programmatically
|
|
45
|
+
*/
|
|
46
|
+
async deinit(): Promise<void> {
|
|
47
|
+
// KV namespaces can only be deleted through the Cloudflare dashboard or API
|
|
48
|
+
// We cannot delete the namespace itself here
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* List all resources in the state store
|
|
53
|
+
*/
|
|
54
|
+
async list(): Promise<string[]> {
|
|
55
|
+
const list = await this.namespace.list({ prefix: this.prefix });
|
|
56
|
+
return list.keys.map((key) => key.name.slice(this.prefix.length));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Count the number of items in the state store
|
|
61
|
+
*/
|
|
62
|
+
async count(): Promise<number> {
|
|
63
|
+
const list = await this.namespace.list({ prefix: this.prefix });
|
|
64
|
+
return list.keys.length;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get a state by key
|
|
69
|
+
*
|
|
70
|
+
* @param key The key to look up
|
|
71
|
+
* @returns The state or undefined if not found
|
|
72
|
+
*/
|
|
73
|
+
async get(key: string): Promise<State | undefined> {
|
|
74
|
+
const data = await this.namespace.get(this.getNamespacedKey(key), "json");
|
|
75
|
+
return data as State | undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get multiple states by their keys
|
|
80
|
+
*
|
|
81
|
+
* @param ids Array of keys to fetch
|
|
82
|
+
* @returns Record mapping keys to their states
|
|
83
|
+
*/
|
|
84
|
+
async getBatch(ids: string[]): Promise<Record<string, State>> {
|
|
85
|
+
const result: Record<string, State> = {};
|
|
86
|
+
|
|
87
|
+
// KV doesn't have a native batch get operation, so we need to make multiple requests
|
|
88
|
+
// We could optimize this with Promise.all for parallel fetching
|
|
89
|
+
const promises = ids.map(async (id) => {
|
|
90
|
+
const state = await this.get(id);
|
|
91
|
+
if (state) {
|
|
92
|
+
result[id] = state;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await Promise.all(promises);
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get all states in the store
|
|
102
|
+
*
|
|
103
|
+
* @returns Record mapping all keys to their states
|
|
104
|
+
*/
|
|
105
|
+
async all(): Promise<Record<string, State>> {
|
|
106
|
+
const keys = await this.list();
|
|
107
|
+
return this.getBatch(keys);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Set a state for a key
|
|
112
|
+
*
|
|
113
|
+
* @param key The key to set
|
|
114
|
+
* @param value The state to store
|
|
115
|
+
*/
|
|
116
|
+
async set(key: string, value: State): Promise<void> {
|
|
117
|
+
await this.namespace.put(this.getNamespacedKey(key), JSON.stringify(value));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Delete a state by key
|
|
122
|
+
*
|
|
123
|
+
* @param key The key to delete
|
|
124
|
+
*/
|
|
125
|
+
async delete(key: string): Promise<void> {
|
|
126
|
+
await this.namespace.delete(this.getNamespacedKey(key));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get the namespaced key for storage
|
|
131
|
+
*
|
|
132
|
+
* @param key The original key
|
|
133
|
+
* @returns The key with prefix for use in the KV namespace
|
|
134
|
+
*/
|
|
135
|
+
private getNamespacedKey(key: string): string {
|
|
136
|
+
return `${this.prefix}${key}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// see: https://developers.cloudflare.com/workers/runtime-apis/cache/
|
|
2
|
+
declare var caches: any;
|
|
3
|
+
|
|
4
|
+
// injected by src/cloudflare/static-site.ts
|
|
5
|
+
declare var __ASSET_MANIFEST__: Record<string, string>;
|
|
6
|
+
|
|
7
|
+
export interface Env {
|
|
8
|
+
ASSETS: KVNamespace;
|
|
9
|
+
INDEX_PAGE: string;
|
|
10
|
+
ERROR_PAGE?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
15
|
+
const url = new URL(request.url);
|
|
16
|
+
console.log("url", request.url);
|
|
17
|
+
const pathname = url.pathname.replace(/^\//, "");
|
|
18
|
+
const filePath = pathname === "" ? env.INDEX_PAGE : pathname;
|
|
19
|
+
|
|
20
|
+
// Return from cache if available
|
|
21
|
+
const cachedResponse = await lookupCache();
|
|
22
|
+
if (cachedResponse) return cachedResponse;
|
|
23
|
+
|
|
24
|
+
// Fetch from KV
|
|
25
|
+
{
|
|
26
|
+
const object = await env.ASSETS.getWithMetadata(filePath);
|
|
27
|
+
if (object.value) return await respond(200, filePath, object);
|
|
28
|
+
}
|
|
29
|
+
{
|
|
30
|
+
const guess =
|
|
31
|
+
filePath + (filePath.endsWith("/") ? "" : "/") + "index.html";
|
|
32
|
+
const object = await env.ASSETS.getWithMetadata(guess);
|
|
33
|
+
if (object.value) return await respond(200, guess, object);
|
|
34
|
+
}
|
|
35
|
+
{
|
|
36
|
+
const guess = filePath + ".html";
|
|
37
|
+
const object = await env.ASSETS.getWithMetadata(guess);
|
|
38
|
+
if (object.value) return await respond(200, guess, object);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle error page
|
|
42
|
+
if (env.ERROR_PAGE) {
|
|
43
|
+
const object = await env.ASSETS.getWithMetadata(env.ERROR_PAGE);
|
|
44
|
+
if (object.value) return await respond(404, env.ERROR_PAGE, object);
|
|
45
|
+
} else {
|
|
46
|
+
const object = await env.ASSETS.getWithMetadata(env.INDEX_PAGE);
|
|
47
|
+
if (object.value) return await respond(200, env.INDEX_PAGE, object);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Handle failed to render error page
|
|
51
|
+
return new Response("Page Not Found", { status: 404 });
|
|
52
|
+
|
|
53
|
+
async function lookupCache() {
|
|
54
|
+
const cache = caches.default;
|
|
55
|
+
const r = await cache.match(request);
|
|
56
|
+
|
|
57
|
+
// cache does not exist
|
|
58
|
+
if (!r) return;
|
|
59
|
+
|
|
60
|
+
// cache exists but etag does not match
|
|
61
|
+
if (r.headers.get("etag") !== __ASSET_MANIFEST__[filePath]) return;
|
|
62
|
+
|
|
63
|
+
// cache exists
|
|
64
|
+
return r;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function saveCache(response: Response) {
|
|
68
|
+
const cache = caches.default;
|
|
69
|
+
await cache.put(request, response.clone());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function respond(
|
|
73
|
+
status: number,
|
|
74
|
+
filePath: string,
|
|
75
|
+
object: KVNamespaceGetWithMetadataResult<any, any>,
|
|
76
|
+
) {
|
|
77
|
+
// build response
|
|
78
|
+
const headers = new Headers();
|
|
79
|
+
if (__ASSET_MANIFEST__[filePath]) {
|
|
80
|
+
headers.set("etag", __ASSET_MANIFEST__[filePath]);
|
|
81
|
+
headers.set("content-type", object.metadata.contentType);
|
|
82
|
+
headers.set("cache-control", object.metadata.cacheControl);
|
|
83
|
+
}
|
|
84
|
+
// TODO: do we need base64 encoded here?
|
|
85
|
+
// const response = new Response(base64ToArrayBuffer(object.value), {
|
|
86
|
+
const response = new Response(object.value, {
|
|
87
|
+
status,
|
|
88
|
+
headers,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (request.method === "GET") {
|
|
92
|
+
await saveCache(response);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return response;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
function base64ToArrayBuffer(base64: any) {
|
|
101
|
+
const binaryString = atob(base64);
|
|
102
|
+
const len = binaryString.length;
|
|
103
|
+
const bytes = new Uint8Array(len);
|
|
104
|
+
for (let i = 0; i < len; i++) {
|
|
105
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
106
|
+
}
|
|
107
|
+
return bytes.buffer;
|
|
108
|
+
}
|