alchemy 0.15.2 → 0.15.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.
- package/lib/cloudflare/api-error.js +1 -1
- package/lib/cloudflare/api-error.js.map +1 -1
- package/lib/cloudflare/api.d.ts.map +1 -1
- package/lib/cloudflare/api.js +4 -3
- package/lib/cloudflare/api.js.map +1 -1
- package/lib/cloudflare/bindings.d.ts +3 -1
- package/lib/cloudflare/bindings.d.ts.map +1 -1
- package/lib/cloudflare/bindings.js +1 -0
- package/lib/cloudflare/bindings.js.map +1 -1
- package/lib/cloudflare/bound.d.ts +2 -2
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/r2-rest-state-store.d.ts.map +1 -1
- package/lib/cloudflare/r2-rest-state-store.js +12 -1
- package/lib/cloudflare/r2-rest-state-store.js.map +1 -1
- package/lib/cloudflare/user.d.ts +1 -1
- package/lib/cloudflare/user.d.ts.map +1 -1
- package/lib/cloudflare/user.js +10 -19
- package/lib/cloudflare/user.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +2 -2
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +108 -83
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/wrangler.json.js +15 -7
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/cloudflare/zone.d.ts.map +1 -1
- package/lib/cloudflare/zone.js +2 -5
- package/lib/cloudflare/zone.js.map +1 -1
- package/lib/serde.d.ts.map +1 -1
- package/lib/serde.js +49 -13
- package/lib/serde.js.map +1 -1
- package/package.json +1 -1
- package/src/cloudflare/api-error.ts +1 -1
- package/src/cloudflare/api.ts +4 -3
- package/src/cloudflare/bindings.ts +5 -1
- package/src/cloudflare/bound.ts +4 -2
- package/src/cloudflare/r2-rest-state-store.ts +15 -6
- package/src/cloudflare/user.ts +12 -22
- package/src/cloudflare/worker.ts +158 -118
- package/src/cloudflare/wrangler.json.ts +13 -7
- package/src/cloudflare/zone.ts +6 -6
- package/src/serde.ts +57 -22
package/src/cloudflare/worker.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
createCloudflareApi,
|
|
14
14
|
} from "./api.js";
|
|
15
15
|
import type { Assets } from "./assets.js";
|
|
16
|
-
import type
|
|
16
|
+
import { type Bindings, Self, type WorkerBindingSpec } from "./bindings.js";
|
|
17
17
|
import type { Bound } from "./bound.js";
|
|
18
18
|
import { bundleWorkerScript } from "./bundle/bundle-worker.js";
|
|
19
19
|
import type { DurableObjectNamespace } from "./durable-object-namespace.js";
|
|
@@ -207,7 +207,7 @@ export interface WorkerProps<B extends Bindings = Bindings>
|
|
|
207
207
|
*/
|
|
208
208
|
export interface Worker<B extends Bindings = Bindings>
|
|
209
209
|
extends Resource<"cloudflare::Worker">,
|
|
210
|
-
Omit<WorkerProps<B>, "url"> {
|
|
210
|
+
Omit<WorkerProps<B>, "url" | "script"> {
|
|
211
211
|
type: "service";
|
|
212
212
|
|
|
213
213
|
/**
|
|
@@ -358,143 +358,182 @@ export const Worker = Resource(
|
|
|
358
358
|
id: string,
|
|
359
359
|
props: WorkerProps<B>,
|
|
360
360
|
): Promise<Worker<B>> {
|
|
361
|
-
// Create Cloudflare API client with automatic account discovery
|
|
362
|
-
const api = await createCloudflareApi(props);
|
|
363
|
-
|
|
364
|
-
// Use the provided name
|
|
365
|
-
const workerName = props.name ?? id;
|
|
366
|
-
|
|
367
361
|
// Validate input - we need either script, entryPoint, or bundle
|
|
368
362
|
if (!props.script && !props.entrypoint) {
|
|
369
363
|
throw new Error("One of script or entryPoint must be provided");
|
|
370
364
|
}
|
|
371
365
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!props.adopt) {
|
|
378
|
-
await assertWorkerDoesNotExist(this, api, workerName);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
366
|
+
// Create Cloudflare API client with automatic account discovery
|
|
367
|
+
const api = await createCloudflareApi(props);
|
|
368
|
+
|
|
369
|
+
// Use the provided name
|
|
370
|
+
const workerName = props.name ?? id;
|
|
381
371
|
|
|
382
372
|
const oldBindings = await this.get<Bindings>("bindings");
|
|
383
373
|
|
|
384
374
|
const compatibilityDate = props.compatibilityDate ?? "2025-04-20";
|
|
385
375
|
const compatibilityFlags = props.compatibilityFlags ?? [];
|
|
386
376
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (binding
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
377
|
+
const uploadWorkerScript = async (props: WorkerProps<B>) => {
|
|
378
|
+
// Get the script content - either from props.script, or by bundling
|
|
379
|
+
const scriptContent =
|
|
380
|
+
props.script ??
|
|
381
|
+
(await bundleWorkerScript({
|
|
382
|
+
...props,
|
|
383
|
+
compatibilityDate,
|
|
384
|
+
compatibilityFlags,
|
|
385
|
+
}));
|
|
386
|
+
|
|
387
|
+
// Find any assets bindings
|
|
388
|
+
const assetsBindings: { name: string; assets: Assets }[] = [];
|
|
389
|
+
const workflowsBindings: Workflow[] = [];
|
|
390
|
+
|
|
391
|
+
if (props.bindings) {
|
|
392
|
+
for (const [bindingName, binding] of Object.entries(props.bindings)) {
|
|
393
|
+
if (typeof binding === "object") {
|
|
394
|
+
if (binding.type === "assets") {
|
|
395
|
+
assetsBindings.push({ name: bindingName, assets: binding });
|
|
396
|
+
} else if (binding.type === "workflow") {
|
|
397
|
+
workflowsBindings.push(binding);
|
|
398
|
+
}
|
|
407
399
|
}
|
|
408
400
|
}
|
|
409
401
|
}
|
|
410
|
-
}
|
|
411
402
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
403
|
+
// Upload any assets and get completion tokens
|
|
404
|
+
let assetUploadResult: AssetUploadResult | undefined;
|
|
405
|
+
if (assetsBindings.length > 0) {
|
|
406
|
+
// We'll use the first asset binding for now
|
|
407
|
+
// In the future, we might want to support multiple asset bindings
|
|
408
|
+
const assetBinding = assetsBindings[0];
|
|
409
|
+
|
|
410
|
+
// Upload the assets and get the completion token
|
|
411
|
+
assetUploadResult = await uploadAssets(
|
|
412
|
+
api,
|
|
413
|
+
workerName,
|
|
414
|
+
assetBinding.assets,
|
|
415
|
+
props.assets,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Prepare metadata with bindings
|
|
420
|
+
const scriptMetadata = await prepareWorkerMetadata(
|
|
421
|
+
this,
|
|
422
|
+
oldBindings,
|
|
423
|
+
{
|
|
424
|
+
...props,
|
|
425
|
+
compatibilityDate,
|
|
426
|
+
compatibilityFlags,
|
|
427
|
+
workerName,
|
|
428
|
+
},
|
|
429
|
+
assetUploadResult,
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
await putWorker(api, workerName, scriptContent, scriptMetadata);
|
|
433
|
+
|
|
434
|
+
for (const workflow of workflowsBindings) {
|
|
435
|
+
await upsertWorkflow(api, {
|
|
436
|
+
workflowName: workflow.workflowName,
|
|
437
|
+
className: workflow.className,
|
|
438
|
+
scriptName: workerName,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
await Promise.all(
|
|
443
|
+
props.eventSources?.map((eventSource) => {
|
|
444
|
+
if (isQueue(eventSource) || isQueueEventSource(eventSource)) {
|
|
445
|
+
const queue = isQueue(eventSource)
|
|
446
|
+
? eventSource
|
|
447
|
+
: eventSource.queue;
|
|
448
|
+
return QueueConsumer(`${queue.id}-consumer`, {
|
|
449
|
+
queue,
|
|
450
|
+
scriptName: workerName,
|
|
451
|
+
accountId: api.accountId,
|
|
452
|
+
settings: isQueueEventSource(eventSource)
|
|
453
|
+
? eventSource.settings
|
|
454
|
+
: undefined,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
throw new Error(`Unsupported event source type: ${eventSource}`);
|
|
458
|
+
}) ?? [],
|
|
459
|
+
);
|
|
418
460
|
|
|
419
|
-
//
|
|
420
|
-
|
|
461
|
+
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
462
|
+
await this.set("bindings", props.bindings);
|
|
463
|
+
|
|
464
|
+
// Handle worker URL if requested
|
|
465
|
+
const workerUrl = await configureURL(
|
|
466
|
+
this,
|
|
421
467
|
api,
|
|
422
468
|
workerName,
|
|
423
|
-
|
|
424
|
-
props.assets,
|
|
469
|
+
props.url ?? false,
|
|
425
470
|
);
|
|
426
|
-
}
|
|
427
471
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
this,
|
|
431
|
-
oldBindings,
|
|
432
|
-
{
|
|
433
|
-
...props,
|
|
434
|
-
compatibilityDate,
|
|
435
|
-
compatibilityFlags,
|
|
436
|
-
},
|
|
437
|
-
assetUploadResult,
|
|
438
|
-
);
|
|
472
|
+
// Get current timestamp
|
|
473
|
+
const now = Date.now();
|
|
439
474
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
scriptName: workerName,
|
|
447
|
-
});
|
|
448
|
-
}
|
|
475
|
+
// Update cron triggers
|
|
476
|
+
if (props.crons) {
|
|
477
|
+
const res = await api.put(
|
|
478
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`,
|
|
479
|
+
props.crons.map((cron) => ({ cron })),
|
|
480
|
+
);
|
|
449
481
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
accountId: api.accountId,
|
|
458
|
-
settings: isQueueEventSource(eventSource)
|
|
459
|
-
? eventSource.settings
|
|
460
|
-
: undefined,
|
|
461
|
-
});
|
|
482
|
+
if (!res.ok) {
|
|
483
|
+
await handleApiError(
|
|
484
|
+
res,
|
|
485
|
+
"updating cron triggers",
|
|
486
|
+
"worker",
|
|
487
|
+
workerName,
|
|
488
|
+
);
|
|
462
489
|
}
|
|
463
|
-
|
|
464
|
-
}) ?? [],
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
468
|
-
await this.set("bindings", props.bindings);
|
|
490
|
+
}
|
|
469
491
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
this,
|
|
473
|
-
api,
|
|
474
|
-
workerName,
|
|
475
|
-
props.url ?? false,
|
|
476
|
-
);
|
|
492
|
+
return { scriptMetadata, workerUrl, now };
|
|
493
|
+
};
|
|
477
494
|
|
|
478
|
-
|
|
479
|
-
|
|
495
|
+
if (this.phase === "delete") {
|
|
496
|
+
if (
|
|
497
|
+
Object.values(props.bindings ?? {}).some((binding) => binding === Self)
|
|
498
|
+
) {
|
|
499
|
+
// remove the Self bindings or else we can't remove (LOL)
|
|
500
|
+
await uploadWorkerScript({
|
|
501
|
+
...props,
|
|
502
|
+
bindings: Object.fromEntries(
|
|
503
|
+
Object.entries(props.bindings ?? {}).filter(
|
|
504
|
+
([_, binding]) => binding !== Self,
|
|
505
|
+
),
|
|
506
|
+
) as B,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
480
509
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
510
|
+
await withExponentialBackoff(
|
|
511
|
+
() =>
|
|
512
|
+
deleteWorker(this, api, {
|
|
513
|
+
...props,
|
|
514
|
+
workerName,
|
|
515
|
+
}),
|
|
516
|
+
(err) =>
|
|
517
|
+
(err.status === 400 &&
|
|
518
|
+
err.message.includes(
|
|
519
|
+
"is still referenced by service bindings in Workers",
|
|
520
|
+
)) ||
|
|
521
|
+
err.status === 500 ||
|
|
522
|
+
err.status === 503,
|
|
523
|
+
10,
|
|
524
|
+
100,
|
|
486
525
|
);
|
|
487
526
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
workerName,
|
|
494
|
-
);
|
|
527
|
+
return this.destroy();
|
|
528
|
+
}
|
|
529
|
+
if (this.phase === "create") {
|
|
530
|
+
if (!props.adopt) {
|
|
531
|
+
await assertWorkerDoesNotExist(this, api, workerName);
|
|
495
532
|
}
|
|
496
533
|
}
|
|
497
534
|
|
|
535
|
+
const { scriptMetadata, workerUrl, now } = await uploadWorkerScript(props);
|
|
536
|
+
|
|
498
537
|
// Construct the output
|
|
499
538
|
return this({
|
|
500
539
|
...props,
|
|
@@ -504,7 +543,6 @@ export const Worker = Resource(
|
|
|
504
543
|
name: workerName,
|
|
505
544
|
compatibilityDate,
|
|
506
545
|
compatibilityFlags,
|
|
507
|
-
script: scriptContent,
|
|
508
546
|
format: props.format || "esm", // Include format in the output
|
|
509
547
|
bindings: props.bindings ?? ({} as B),
|
|
510
548
|
env: props.env,
|
|
@@ -526,8 +564,9 @@ export const Worker = Resource(
|
|
|
526
564
|
async function deleteWorker<B extends Bindings>(
|
|
527
565
|
ctx: Context<Worker<B>>,
|
|
528
566
|
api: CloudflareApi,
|
|
529
|
-
workerName: string,
|
|
567
|
+
props: WorkerProps<B> & { workerName: string },
|
|
530
568
|
) {
|
|
569
|
+
const workerName = props.workerName;
|
|
531
570
|
// Delete any queue consumers attached to this worker first
|
|
532
571
|
await deleteQueueConsumers(ctx, api, workerName);
|
|
533
572
|
|
|
@@ -538,13 +577,7 @@ async function deleteWorker<B extends Bindings>(
|
|
|
538
577
|
|
|
539
578
|
// Check for success (2xx status code)
|
|
540
579
|
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
541
|
-
|
|
542
|
-
.json()
|
|
543
|
-
.catch(() => ({ errors: [{ message: deleteResponse.statusText }] }));
|
|
544
|
-
console.error(
|
|
545
|
-
"Error deleting worker:",
|
|
546
|
-
errorData.errors?.[0]?.message || deleteResponse.statusText,
|
|
547
|
-
);
|
|
580
|
+
await handleApiError(deleteResponse, "delete", "worker", workerName);
|
|
548
581
|
}
|
|
549
582
|
|
|
550
583
|
// Disable the URL if it was enabled
|
|
@@ -698,6 +731,7 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
698
731
|
props: WorkerProps & {
|
|
699
732
|
compatibilityDate: string;
|
|
700
733
|
compatibilityFlags: string[];
|
|
734
|
+
workerName: string;
|
|
701
735
|
},
|
|
702
736
|
assetUploadResult?: AssetUploadResult,
|
|
703
737
|
): Promise<WorkerMetadata> {
|
|
@@ -752,6 +786,12 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
752
786
|
name: bindingName,
|
|
753
787
|
text: binding,
|
|
754
788
|
});
|
|
789
|
+
} else if (binding === Self) {
|
|
790
|
+
meta.bindings.push({
|
|
791
|
+
type: "service",
|
|
792
|
+
name: bindingName,
|
|
793
|
+
service: props.workerName,
|
|
794
|
+
});
|
|
755
795
|
} else if (binding.type === "d1") {
|
|
756
796
|
meta.bindings.push({
|
|
757
797
|
type: "d1",
|
|
@@ -807,7 +847,7 @@ async function prepareWorkerMetadata<B extends Bindings>(
|
|
|
807
847
|
// script_name: ??,
|
|
808
848
|
});
|
|
809
849
|
// it's unclear whether this is needed, but it works both ways
|
|
810
|
-
configureClassMigration(binding, binding.id, binding.className);
|
|
850
|
+
// configureClassMigration(binding, binding.id, binding.className);
|
|
811
851
|
} else if (binding.type === "queue") {
|
|
812
852
|
meta.bindings.push({
|
|
813
853
|
type: "queue",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Context } from "../context.js";
|
|
2
2
|
import { StaticJsonFile } from "../fs/static-json-file.js";
|
|
3
3
|
import { Resource } from "../resource.js";
|
|
4
|
-
import type
|
|
4
|
+
import { Self, type Bindings } from "./bindings.js";
|
|
5
5
|
import type { DurableObjectNamespace } from "./durable-object-namespace.js";
|
|
6
6
|
import type { EventSource } from "./event-source.js";
|
|
7
7
|
import { isQueueEventSource } from "./event-source.js";
|
|
@@ -340,6 +340,18 @@ function processBindings(
|
|
|
340
340
|
spec.vars = {};
|
|
341
341
|
}
|
|
342
342
|
spec.vars[bindingName] = binding;
|
|
343
|
+
} else if (binding === Self) {
|
|
344
|
+
// Self(service) binding
|
|
345
|
+
services.push({
|
|
346
|
+
binding: bindingName,
|
|
347
|
+
service: bindingName,
|
|
348
|
+
});
|
|
349
|
+
} else if (binding.type === "service") {
|
|
350
|
+
// Service binding
|
|
351
|
+
services.push({
|
|
352
|
+
binding: bindingName,
|
|
353
|
+
service: binding.id,
|
|
354
|
+
});
|
|
343
355
|
} else if (binding.type === "kv_namespace") {
|
|
344
356
|
// KV Namespace binding
|
|
345
357
|
kvNamespaces.push({
|
|
@@ -363,12 +375,6 @@ function processBindings(
|
|
|
363
375
|
binding: bindingName,
|
|
364
376
|
bucket_name: binding.name,
|
|
365
377
|
});
|
|
366
|
-
} else if (binding.type === "service") {
|
|
367
|
-
// Service binding
|
|
368
|
-
services.push({
|
|
369
|
-
binding: bindingName,
|
|
370
|
-
service: binding.id,
|
|
371
|
-
});
|
|
372
378
|
} else if (binding.type === "secret") {
|
|
373
379
|
// Secret binding
|
|
374
380
|
secrets.push(bindingName);
|
package/src/cloudflare/zone.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Context } from "../context.js";
|
|
2
2
|
import { Resource } from "../resource.js";
|
|
3
|
+
import { handleApiError } from "./api-error.js";
|
|
3
4
|
import { createCloudflareApi, type CloudflareApiOptions } from "./api.js";
|
|
4
5
|
import type {
|
|
5
6
|
AlwaysUseHTTPSValue,
|
|
@@ -312,15 +313,14 @@ export const Zone = Resource(
|
|
|
312
313
|
|
|
313
314
|
if (this.phase === "delete") {
|
|
314
315
|
if (this.output?.id && props.delete !== false) {
|
|
315
|
-
// Delete zone
|
|
316
316
|
const deleteResponse = await api.delete(`/zones/${this.output.id}`);
|
|
317
317
|
|
|
318
318
|
if (!deleteResponse.ok && deleteResponse.status !== 404) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
319
|
+
await handleApiError(
|
|
320
|
+
deleteResponse,
|
|
321
|
+
"delete",
|
|
322
|
+
"zone",
|
|
323
|
+
this.output.id,
|
|
324
324
|
);
|
|
325
325
|
}
|
|
326
326
|
} else {
|
package/src/serde.ts
CHANGED
|
@@ -22,8 +22,7 @@ export async function serialize(
|
|
|
22
22
|
): Promise<any> {
|
|
23
23
|
if (Array.isArray(value)) {
|
|
24
24
|
return Promise.all(value.map((value) => serialize(scope, value, options)));
|
|
25
|
-
}
|
|
26
|
-
if (value instanceof Secret) {
|
|
25
|
+
} else if (value instanceof Secret) {
|
|
27
26
|
if (!scope.password) {
|
|
28
27
|
throw new Error("Cannot serialize secret without password");
|
|
29
28
|
}
|
|
@@ -33,31 +32,45 @@ export async function serialize(
|
|
|
33
32
|
? await encrypt(value.unencrypted, scope.password)
|
|
34
33
|
: value.unencrypted,
|
|
35
34
|
};
|
|
36
|
-
}
|
|
37
|
-
if (isType(value)) {
|
|
35
|
+
} else if (isType(value)) {
|
|
38
36
|
return {
|
|
39
37
|
"@schema": value.toJSON(),
|
|
40
38
|
};
|
|
41
|
-
}
|
|
42
|
-
if (value instanceof Date) {
|
|
39
|
+
} else if (value instanceof Date) {
|
|
43
40
|
return {
|
|
44
41
|
"@date": value.toISOString(),
|
|
45
42
|
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
} else if (typeof value === "symbol") {
|
|
44
|
+
assertNotUniqueSymbol(value);
|
|
45
|
+
return {
|
|
46
|
+
"@symbol": value.toString(),
|
|
47
|
+
};
|
|
48
|
+
} else if (value instanceof Scope) {
|
|
49
|
+
return {
|
|
50
|
+
"@scope": null,
|
|
51
|
+
};
|
|
52
|
+
} else if (value && typeof value === "object") {
|
|
53
|
+
for (const symbol of Object.getOwnPropertySymbols(value)) {
|
|
54
|
+
assertNotUniqueSymbol(symbol);
|
|
55
|
+
}
|
|
56
|
+
for (const key of Object.keys(value)) {
|
|
57
|
+
if (parseSymbol(key)) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Cannot serialize property '${key}' because it looks like a stringified symbol.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
51
63
|
return Object.fromEntries(
|
|
52
64
|
await Promise.all(
|
|
53
|
-
Object.
|
|
54
|
-
key
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
[...Object.getOwnPropertySymbols(value), ...Object.keys(value)].map(
|
|
66
|
+
async (key) => [
|
|
67
|
+
key.toString(),
|
|
68
|
+
await serialize(scope, value[key], options),
|
|
69
|
+
],
|
|
70
|
+
),
|
|
57
71
|
),
|
|
58
72
|
);
|
|
59
|
-
}
|
|
60
|
-
if (typeof value === "function") {
|
|
73
|
+
} else if (typeof value === "function") {
|
|
61
74
|
// can't serialize functions
|
|
62
75
|
return undefined;
|
|
63
76
|
}
|
|
@@ -76,17 +89,20 @@ export async function deserialize(scope: Scope, value: any): Promise<any> {
|
|
|
76
89
|
throw new Error("Cannot deserialize secret without password");
|
|
77
90
|
}
|
|
78
91
|
return new Secret(await decryptWithKey(value["@secret"], scope.password));
|
|
79
|
-
}
|
|
80
|
-
if ("@schema" in value) {
|
|
92
|
+
} else if ("@schema" in value) {
|
|
81
93
|
return value["@schema"];
|
|
82
|
-
}
|
|
83
|
-
if ("@date" in value) {
|
|
94
|
+
} else if ("@date" in value) {
|
|
84
95
|
return new Date(value["@date"]);
|
|
96
|
+
} else if ("@symbol" in value) {
|
|
97
|
+
return parseSymbol(value["@symbol"]);
|
|
98
|
+
} else if ("@scope" in value) {
|
|
99
|
+
return scope;
|
|
85
100
|
}
|
|
101
|
+
|
|
86
102
|
return Object.fromEntries(
|
|
87
103
|
await Promise.all(
|
|
88
104
|
Object.entries(value).map(async ([key, value]) => [
|
|
89
|
-
key,
|
|
105
|
+
parseSymbol(key) ?? key,
|
|
90
106
|
await deserialize(scope, value),
|
|
91
107
|
]),
|
|
92
108
|
),
|
|
@@ -94,3 +110,22 @@ export async function deserialize(scope: Scope, value: any): Promise<any> {
|
|
|
94
110
|
}
|
|
95
111
|
return value;
|
|
96
112
|
}
|
|
113
|
+
|
|
114
|
+
const symbolPattern = /^Symbol\((.*)\)$/;
|
|
115
|
+
|
|
116
|
+
function parseSymbol(value: string) {
|
|
117
|
+
const match = value.match(symbolPattern);
|
|
118
|
+
if (!match) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return Symbol.for(match[1]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function assertNotUniqueSymbol(symbol: Symbol) {
|
|
125
|
+
if (
|
|
126
|
+
symbol.description === undefined ||
|
|
127
|
+
symbol !== Symbol.for(symbol.description)
|
|
128
|
+
) {
|
|
129
|
+
throw new Error(`Cannot serialize unique symbol: ${symbol.description}`);
|
|
130
|
+
}
|
|
131
|
+
}
|