alchemy 0.11.2 → 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.
|
@@ -20,8 +20,10 @@ export interface ViteSiteProps<B extends Bindings> {
|
|
|
20
20
|
main?: string;
|
|
21
21
|
/**
|
|
22
22
|
* The directory containing your static assets
|
|
23
|
+
*
|
|
24
|
+
* @default "./dist"
|
|
23
25
|
*/
|
|
24
|
-
assets
|
|
26
|
+
assets?: string;
|
|
25
27
|
/**
|
|
26
28
|
* The bindings to pass to the worker
|
|
27
29
|
*/
|
|
@@ -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:
|
|
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
|
|
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
|
};
|
package/lib/cloudflare/worker.js
CHANGED
|
@@ -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 =
|
|
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 {
|
|
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
|
@@ -26,8 +26,10 @@ export interface ViteSiteProps<B extends Bindings> {
|
|
|
26
26
|
main?: string;
|
|
27
27
|
/**
|
|
28
28
|
* The directory containing your static assets
|
|
29
|
+
*
|
|
30
|
+
* @default "./dist"
|
|
29
31
|
*/
|
|
30
|
-
assets
|
|
32
|
+
assets?: string;
|
|
31
33
|
/**
|
|
32
34
|
* The bindings to pass to the worker
|
|
33
35
|
*/
|
|
@@ -80,18 +82,31 @@ export async function ViteSite<B extends Bindings>(
|
|
|
80
82
|
});
|
|
81
83
|
|
|
82
84
|
const staticAssets = await Assets("assets", {
|
|
83
|
-
path:
|
|
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
|
+
})()),
|
|
84
95
|
});
|
|
85
|
-
|
|
86
96
|
const worker = await Worker("worker", {
|
|
87
97
|
name: props.name ?? id,
|
|
88
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
|
+
},
|
|
89
104
|
script: props.main
|
|
90
105
|
? undefined
|
|
91
106
|
: `
|
|
92
107
|
export default {
|
|
93
108
|
async fetch(request, env) {
|
|
94
|
-
return
|
|
109
|
+
return new Response("Not Found", { status: 404 });
|
|
95
110
|
},
|
|
96
111
|
};`,
|
|
97
112
|
url: true,
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -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
|
-
|
|
494
|
-
|
|
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 =
|
|
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 {
|
|
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
|
|