alchemy 0.11.3 → 0.11.4

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.
@@ -29,17 +29,31 @@ export async function ViteSite(id, props) {
29
29
  command: props.command,
30
30
  });
31
31
  const staticAssets = await Assets("assets", {
32
- path: props.assets ?? "./dist",
32
+ path: props.assets ??
33
+ (await (async () => {
34
+ try {
35
+ await fs.access(path.join("dist", "client", "index.html"));
36
+ return path.join(".", "dist", "client");
37
+ }
38
+ catch {
39
+ return path.join(".", "dist");
40
+ }
41
+ })()),
33
42
  });
34
43
  const worker = await Worker("worker", {
35
44
  name: props.name ?? id,
36
45
  entrypoint: props.main,
46
+ assets: {
47
+ html_handling: "auto-trailing-slash",
48
+ not_found_handling: "single-page-application",
49
+ run_worker_first: false,
50
+ },
37
51
  script: props.main
38
52
  ? undefined
39
53
  : `
40
54
  export default {
41
55
  async fetch(request, env) {
42
- return env.ASSETS.fetch(request);
56
+ return new Response("Not Found", { status: 404 });
43
57
  },
44
58
  };`,
45
59
  url: true,
@@ -5,6 +5,40 @@ import { type CloudflareApiOptions } from "./api";
5
5
  import { type Bindings } from "./bindings";
6
6
  import type { Bound } from "./bound";
7
7
  import type { SingleStepMigration } from "./worker-migration";
8
+ /**
9
+ * Configuration options for static assets
10
+ */
11
+ export interface AssetsConfig {
12
+ /**
13
+ * The contents of a _headers file (used to attach custom headers on asset responses)
14
+ */
15
+ _headers?: string;
16
+ /**
17
+ * The contents of a _redirects file (used to apply redirects or proxy paths ahead of asset serving)
18
+ */
19
+ _redirects?: string;
20
+ /**
21
+ * Determines the redirects and rewrites of requests for HTML content
22
+ * @default "auto-trailing-slash"
23
+ */
24
+ html_handling?: "auto-trailing-slash" | "force-trailing-slash" | "drop-trailing-slash" | "none";
25
+ /**
26
+ * Determines the response when a request does not match a static asset, and there is no Worker script
27
+ */
28
+ not_found_handling?: "none" | "404-page" | "single-page-application";
29
+ /**
30
+ * When true, requests will always invoke the Worker script.
31
+ * Otherwise, attempt to serve an asset matching the request, falling back to the Worker script.
32
+ */
33
+ run_worker_first?: boolean;
34
+ /**
35
+ * When true and the incoming request matches an asset, that will be served instead of invoking the Worker script.
36
+ * When false, requests will always invoke the Worker script.
37
+ * @default true
38
+ * @deprecated
39
+ */
40
+ serve_directly?: boolean;
41
+ }
8
42
  /**
9
43
  * Properties for creating or updating a Worker
10
44
  */
@@ -83,6 +117,10 @@ export interface WorkerProps<B extends Bindings = Bindings> extends CloudflareAp
83
117
  * The compatibility flags for the worker
84
118
  */
85
119
  compatibilityFlags?: string[];
120
+ /**
121
+ * Configuration for static assets
122
+ */
123
+ assets?: AssetsConfig;
86
124
  }
87
125
  /**
88
126
  * Output returned after Worker creation/update
@@ -110,6 +148,10 @@ export interface Worker<B extends Bindings = Bindings> extends Resource<"cloudfl
110
148
  * The bindings that were created
111
149
  */
112
150
  bindings: B | undefined;
151
+ /**
152
+ * Configuration for static assets
153
+ */
154
+ assets?: AssetsConfig;
113
155
  Env: {
114
156
  [bindingName in keyof B]: Bound<B[bindingName]>;
115
157
  };
@@ -132,7 +132,7 @@ export const Worker = Resource("cloudflare::Worker", {
132
132
  // In the future, we might want to support multiple asset bindings
133
133
  const assetBinding = assetsBindings[0];
134
134
  // Upload the assets and get the completion token
135
- assetUploadResult = await uploadAssets(api, workerName, assetBinding.assets);
135
+ assetUploadResult = await uploadAssets(api, workerName, assetBinding.assets, props.assets);
136
136
  }
137
137
  // Prepare metadata with bindings
138
138
  const scriptMetadata = await prepareWorkerMetadata(this, oldBindings, props, assetUploadResult);
@@ -165,6 +165,8 @@ export const Worker = Resource("cloudflare::Worker", {
165
165
  createdAt: now,
166
166
  updatedAt: now,
167
167
  url: workerUrl,
168
+ // Include assets configuration in the output
169
+ assets: props.assets,
168
170
  // phantom property
169
171
  Env: undefined,
170
172
  });
@@ -236,6 +238,35 @@ class NotFoundError extends Error {
236
238
  this.name = "NotFoundError";
237
239
  }
238
240
  }
241
+ /**
242
+ * Creates asset configuration object from provided config or defaults
243
+ */
244
+ function createAssetConfig(config) {
245
+ const assetConfig = {
246
+ html_handling: "auto-trailing-slash",
247
+ };
248
+ if (config) {
249
+ if (config._headers !== undefined) {
250
+ assetConfig._headers = config._headers;
251
+ }
252
+ if (config._redirects !== undefined) {
253
+ assetConfig._redirects = config._redirects;
254
+ }
255
+ if (config.html_handling !== undefined) {
256
+ assetConfig.html_handling = config.html_handling;
257
+ }
258
+ if (config.not_found_handling !== undefined) {
259
+ assetConfig.not_found_handling = config.not_found_handling;
260
+ }
261
+ if (config.run_worker_first !== undefined) {
262
+ assetConfig.run_worker_first = config.run_worker_first;
263
+ }
264
+ if (config.serve_directly !== undefined) {
265
+ assetConfig.serve_directly = config.serve_directly;
266
+ }
267
+ }
268
+ return assetConfig;
269
+ }
239
270
  async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult) {
240
271
  // Prepare metadata with bindings
241
272
  const meta = {
@@ -260,8 +291,16 @@ async function prepareWorkerMetadata(ctx, oldBindings, props, assetUploadResult)
260
291
  meta.assets = {
261
292
  jwt: assetUploadResult.completionToken,
262
293
  };
294
+ // Initialize config from assetUploadResult if it exists
263
295
  if (assetUploadResult.assetConfig) {
264
- meta.assets.config = assetUploadResult.assetConfig;
296
+ meta.assets.config = {
297
+ ...assetUploadResult.assetConfig,
298
+ };
299
+ }
300
+ // If there's no config from assetUploadResult but we have props.assets,
301
+ // we need to create the config ourselves (this handles the case when no assets were uploaded)
302
+ if (!meta.assets.config && props.assets) {
303
+ meta.assets.config = createAssetConfig(props.assets);
265
304
  }
266
305
  }
267
306
  const bindings = (props.bindings ?? {});
@@ -538,9 +577,12 @@ async function getWorkerBindings(api, workerName, environment = "production") {
538
577
  * @param api CloudflareApi instance
539
578
  * @param workerName Name of the worker
540
579
  * @param assets Assets resource containing files to upload
580
+ * @param assetConfig Configuration for the assets
541
581
  * @returns Completion token for the assets upload
542
582
  */
543
- async function uploadAssets(api, workerName, assets) {
583
+ async function uploadAssets(api, workerName, assets, assetConfig) {
584
+ // Process the assets configuration once at the beginning
585
+ const processedConfig = createAssetConfig(assetConfig);
544
586
  // Generate the file manifest
545
587
  const fileMetadata = {};
546
588
  // Process each file in the assets
@@ -561,7 +603,10 @@ async function uploadAssets(api, workerName, assets) {
561
603
  const sessionData = (await uploadSessionResponse.json());
562
604
  // If there are no buckets, assets are already uploaded or empty
563
605
  if (!sessionData.result.buckets || sessionData.result.buckets.length === 0) {
564
- return { completionToken: sessionData.result.jwt };
606
+ return {
607
+ completionToken: sessionData.result.jwt,
608
+ assetConfig: processedConfig,
609
+ };
565
610
  }
566
611
  // Upload the files in batches as specified by the API
567
612
  let completionToken = sessionData.result.jwt;
@@ -607,12 +652,10 @@ async function uploadAssets(api, workerName, assets) {
607
652
  completionToken = uploadData.result.jwt;
608
653
  }
609
654
  }
610
- // Return the final completion token
655
+ // Return the final completion token with asset configuration
611
656
  return {
612
657
  completionToken,
613
- assetConfig: {
614
- html_handling: "auto-trailing-slash",
615
- },
658
+ assetConfig: processedConfig,
616
659
  };
617
660
  }
618
661
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemy",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
4
4
  "type": "module",
5
5
  "module": "./lib/index.js",
6
6
  "scripts": {
@@ -82,18 +82,31 @@ export async function ViteSite<B extends Bindings>(
82
82
  });
83
83
 
84
84
  const staticAssets = await Assets("assets", {
85
- path: props.assets ?? "./dist",
85
+ path:
86
+ props.assets ??
87
+ (await (async () => {
88
+ try {
89
+ await fs.access(path.join("dist", "client", "index.html"));
90
+ return path.join(".", "dist", "client");
91
+ } catch {
92
+ return path.join(".", "dist");
93
+ }
94
+ })()),
86
95
  });
87
-
88
96
  const worker = await Worker("worker", {
89
97
  name: props.name ?? id,
90
98
  entrypoint: props.main,
99
+ assets: {
100
+ html_handling: "auto-trailing-slash",
101
+ not_found_handling: "single-page-application",
102
+ run_worker_first: false,
103
+ },
91
104
  script: props.main
92
105
  ? undefined
93
106
  : `
94
107
  export default {
95
108
  async fetch(request, env) {
96
- return env.ASSETS.fetch(request);
109
+ return new Response("Not Found", { status: 404 });
97
110
  },
98
111
  };`,
99
112
  url: true,
@@ -19,6 +19,50 @@ import type { WorkerScriptMetadata } from "./worker-metadata";
19
19
  import type { SingleStepMigration } from "./worker-migration";
20
20
  import { upsertWorkflow, type Workflow } from "./workflow";
21
21
 
22
+ /**
23
+ * Configuration options for static assets
24
+ */
25
+ export interface AssetsConfig {
26
+ /**
27
+ * The contents of a _headers file (used to attach custom headers on asset responses)
28
+ */
29
+ _headers?: string;
30
+
31
+ /**
32
+ * The contents of a _redirects file (used to apply redirects or proxy paths ahead of asset serving)
33
+ */
34
+ _redirects?: string;
35
+
36
+ /**
37
+ * Determines the redirects and rewrites of requests for HTML content
38
+ * @default "auto-trailing-slash"
39
+ */
40
+ html_handling?:
41
+ | "auto-trailing-slash"
42
+ | "force-trailing-slash"
43
+ | "drop-trailing-slash"
44
+ | "none";
45
+
46
+ /**
47
+ * Determines the response when a request does not match a static asset, and there is no Worker script
48
+ */
49
+ not_found_handling?: "none" | "404-page" | "single-page-application";
50
+
51
+ /**
52
+ * When true, requests will always invoke the Worker script.
53
+ * Otherwise, attempt to serve an asset matching the request, falling back to the Worker script.
54
+ */
55
+ run_worker_first?: boolean;
56
+
57
+ /**
58
+ * When true and the incoming request matches an asset, that will be served instead of invoking the Worker script.
59
+ * When false, requests will always invoke the Worker script.
60
+ * @default true
61
+ * @deprecated
62
+ */
63
+ serve_directly?: boolean;
64
+ }
65
+
22
66
  /**
23
67
  * Properties for creating or updating a Worker
24
68
  */
@@ -110,6 +154,11 @@ export interface WorkerProps<B extends Bindings = Bindings>
110
154
  * The compatibility flags for the worker
111
155
  */
112
156
  compatibilityFlags?: string[];
157
+
158
+ /**
159
+ * Configuration for static assets
160
+ */
161
+ assets?: AssetsConfig;
113
162
  }
114
163
 
115
164
  /**
@@ -146,6 +195,11 @@ export interface Worker<B extends Bindings = Bindings>
146
195
  */
147
196
  bindings: B | undefined;
148
197
 
198
+ /**
199
+ * Configuration for static assets
200
+ */
201
+ assets?: AssetsConfig;
202
+
149
203
  // phantom property (for typeof myWorker.Env)
150
204
  Env: {
151
205
  [bindingName in keyof B]: Bound<B[bindingName]>;
@@ -291,7 +345,8 @@ export const Worker = Resource(
291
345
  assetUploadResult = await uploadAssets(
292
346
  api,
293
347
  workerName,
294
- assetBinding.assets
348
+ assetBinding.assets,
349
+ props.assets
295
350
  );
296
351
  }
297
352
 
@@ -342,6 +397,8 @@ export const Worker = Resource(
342
397
  createdAt: now,
343
398
  updatedAt: now,
344
399
  url: workerUrl,
400
+ // Include assets configuration in the output
401
+ assets: props.assets,
345
402
  // phantom property
346
403
  Env: undefined!,
347
404
  });
@@ -480,19 +537,50 @@ interface WorkerMetadata {
480
537
  assets?: {
481
538
  jwt?: string;
482
539
  keep_assets?: boolean;
483
- config?: {
484
- html_handling?: "auto-trailing-slash" | "none";
485
- not_found_handling?: "none" | "fall-through";
486
- };
540
+ config?: AssetsConfig;
487
541
  };
488
542
  }
489
543
 
490
544
  interface AssetUploadResult {
491
545
  completionToken: string;
492
- assetConfig?: {
493
- html_handling?: "auto-trailing-slash" | "none";
494
- not_found_handling?: "none" | "fall-through";
546
+ assetConfig?: AssetsConfig;
547
+ }
548
+
549
+ /**
550
+ * Creates asset configuration object from provided config or defaults
551
+ */
552
+ function createAssetConfig(config?: AssetsConfig): AssetsConfig {
553
+ const assetConfig: AssetsConfig = {
554
+ html_handling: "auto-trailing-slash",
495
555
  };
556
+
557
+ if (config) {
558
+ if (config._headers !== undefined) {
559
+ assetConfig._headers = config._headers;
560
+ }
561
+
562
+ if (config._redirects !== undefined) {
563
+ assetConfig._redirects = config._redirects;
564
+ }
565
+
566
+ if (config.html_handling !== undefined) {
567
+ assetConfig.html_handling = config.html_handling;
568
+ }
569
+
570
+ if (config.not_found_handling !== undefined) {
571
+ assetConfig.not_found_handling = config.not_found_handling;
572
+ }
573
+
574
+ if (config.run_worker_first !== undefined) {
575
+ assetConfig.run_worker_first = config.run_worker_first;
576
+ }
577
+
578
+ if (config.serve_directly !== undefined) {
579
+ assetConfig.serve_directly = config.serve_directly;
580
+ }
581
+ }
582
+
583
+ return assetConfig;
496
584
  }
497
585
 
498
586
  async function prepareWorkerMetadata<B extends Bindings>(
@@ -526,8 +614,17 @@ async function prepareWorkerMetadata<B extends Bindings>(
526
614
  jwt: assetUploadResult.completionToken,
527
615
  };
528
616
 
617
+ // Initialize config from assetUploadResult if it exists
529
618
  if (assetUploadResult.assetConfig) {
530
- meta.assets.config = assetUploadResult.assetConfig;
619
+ meta.assets.config = {
620
+ ...assetUploadResult.assetConfig,
621
+ };
622
+ }
623
+
624
+ // If there's no config from assetUploadResult but we have props.assets,
625
+ // we need to create the config ourselves (this handles the case when no assets were uploaded)
626
+ if (!meta.assets.config && props.assets) {
627
+ meta.assets.config = createAssetConfig(props.assets);
531
628
  }
532
629
  }
533
630
 
@@ -910,13 +1007,18 @@ interface UploadResponse {
910
1007
  * @param api CloudflareApi instance
911
1008
  * @param workerName Name of the worker
912
1009
  * @param assets Assets resource containing files to upload
1010
+ * @param assetConfig Configuration for the assets
913
1011
  * @returns Completion token for the assets upload
914
1012
  */
915
1013
  async function uploadAssets(
916
1014
  api: CloudflareApi,
917
1015
  workerName: string,
918
- assets: Assets
1016
+ assets: Assets,
1017
+ assetConfig?: WorkerProps["assets"]
919
1018
  ): Promise<AssetUploadResult> {
1019
+ // Process the assets configuration once at the beginning
1020
+ const processedConfig = createAssetConfig(assetConfig);
1021
+
920
1022
  // Generate the file manifest
921
1023
  const fileMetadata: Record<string, FileMetadata> = {};
922
1024
 
@@ -949,7 +1051,10 @@ async function uploadAssets(
949
1051
 
950
1052
  // If there are no buckets, assets are already uploaded or empty
951
1053
  if (!sessionData.result.buckets || sessionData.result.buckets.length === 0) {
952
- return { completionToken: sessionData.result.jwt };
1054
+ return {
1055
+ completionToken: sessionData.result.jwt,
1056
+ assetConfig: processedConfig,
1057
+ };
953
1058
  }
954
1059
 
955
1060
  // Upload the files in batches as specified by the API
@@ -1013,12 +1118,10 @@ async function uploadAssets(
1013
1118
  }
1014
1119
  }
1015
1120
 
1016
- // Return the final completion token
1121
+ // Return the final completion token with asset configuration
1017
1122
  return {
1018
1123
  completionToken,
1019
- assetConfig: {
1020
- html_handling: "auto-trailing-slash",
1021
- },
1124
+ assetConfig: processedConfig,
1022
1125
  };
1023
1126
  }
1024
1127