alchemy 0.2.0 → 0.2.2
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 +506 -0
- package/lib/alchemy.js +10 -2
- package/lib/apply.js +86 -77
- package/lib/cloudflare/index.d.ts +2 -0
- package/lib/cloudflare/index.js +2 -0
- package/lib/cloudflare/static-site.js +9 -9
- package/lib/cloudflare/worker.js +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +243 -0
- package/lib/cloudflare/wrangler.json.js +35 -0
- package/lib/cloudflare/zone-settings.d.ts +301 -0
- package/lib/cloudflare/zone-settings.js +1 -0
- package/lib/cloudflare/zone.d.ts +196 -0
- package/lib/cloudflare/zone.js +175 -0
- package/lib/destroy.js +1 -1
- package/lib/project/vite.d.ts +90 -0
- package/lib/project/vite.js +228 -0
- package/lib/scope.d.ts +2 -0
- package/lib/scope.js +20 -1
- package/lib/shadcn/component.d.ts +29 -0
- package/lib/shadcn/component.js +31 -0
- package/lib/shadcn/index.d.ts +1 -0
- package/lib/shadcn/index.js +1 -0
- package/lib/state.js +6 -1
- package/lib/util/rm.d.ts +1 -0
- package/lib/util/rm.js +11 -0
- package/lib/vite/index.d.ts +1 -0
- package/lib/vite/index.js +1 -0
- package/lib/vite/vite.d.ts +90 -0
- package/lib/vite/vite.js +228 -0
- package/package.json +5 -3
- package/src/alchemy.ts +9 -2
- package/src/apply.ts +100 -94
- package/src/cloudflare/index.ts +2 -0
- package/src/cloudflare/static-site.ts +11 -11
- package/src/cloudflare/worker.ts +1 -1
- package/src/cloudflare/wrangler.json.ts +309 -0
- package/src/cloudflare/zone-settings.ts +368 -0
- package/src/cloudflare/zone.ts +491 -0
- package/src/destroy.ts +1 -1
- package/src/project/vite.ts +406 -0
- package/src/scope.ts +25 -1
- package/src/shadcn/component.ts +80 -0
- package/src/shadcn/index.ts +1 -0
- package/src/state.ts +9 -1
- package/src/util/rm.ts +11 -0
- package/src/vite/index.ts +1 -0
- package/src/vite/vite.ts +406 -0
package/src/apply.ts
CHANGED
|
@@ -21,110 +21,116 @@ export async function apply<Out extends Resource>(
|
|
|
21
21
|
options?: ApplyOptions,
|
|
22
22
|
): Promise<Awaited<Out>> {
|
|
23
23
|
const scope = resource.Scope;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
state
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
24
|
+
try {
|
|
25
|
+
const quiet = props.quiet ?? scope.quiet;
|
|
26
|
+
await scope.init();
|
|
27
|
+
let state: State | undefined = (await scope.state.get(resource.ID))!;
|
|
28
|
+
const provider: Provider = PROVIDERS.get(resource.Kind);
|
|
29
|
+
if (provider === undefined) {
|
|
30
|
+
throw new Error(`Provider "${resource.Kind}" not found`);
|
|
31
|
+
}
|
|
32
|
+
if (state === undefined) {
|
|
33
|
+
state = {
|
|
34
|
+
kind: resource.Kind,
|
|
35
|
+
id: resource.ID,
|
|
36
|
+
fqn: resource.FQN,
|
|
37
|
+
seq: resource.Seq,
|
|
38
|
+
status: "creating",
|
|
39
|
+
data: {},
|
|
40
|
+
output: {
|
|
41
|
+
ID: resource.ID,
|
|
42
|
+
FQN: resource.FQN,
|
|
43
|
+
Kind: resource.Kind,
|
|
44
|
+
Scope: scope,
|
|
45
|
+
Seq: resource.Seq,
|
|
46
|
+
},
|
|
47
|
+
// deps: [...deps],
|
|
48
|
+
props,
|
|
49
|
+
};
|
|
50
|
+
await scope.state.set(resource.ID, state);
|
|
51
|
+
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
const alwaysUpdate =
|
|
54
|
+
options?.alwaysUpdate ?? provider.options?.alwaysUpdate ?? false;
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
// Skip update if inputs haven't changed and resource is in a stable state
|
|
57
|
+
if (state.status === "created" || state.status === "updated") {
|
|
58
|
+
if (
|
|
59
|
+
JSON.stringify(state.props) ===
|
|
60
|
+
JSON.stringify(await serialize(scope, props)) &&
|
|
61
|
+
alwaysUpdate !== true
|
|
62
|
+
) {
|
|
63
|
+
if (!quiet) {
|
|
64
|
+
console.log(`Skip: "${resource.FQN}" (no changes)`);
|
|
65
|
+
}
|
|
66
|
+
// if (resourceState.output !== undefined) {
|
|
67
|
+
// resource[Provide](resourceState.output);
|
|
68
|
+
// }
|
|
69
|
+
return state.output as Awaited<Out>;
|
|
63
70
|
}
|
|
64
|
-
// if (resourceState.output !== undefined) {
|
|
65
|
-
// resource[Provide](resourceState.output);
|
|
66
|
-
// }
|
|
67
|
-
return state.output as Awaited<Out>;
|
|
68
71
|
}
|
|
69
|
-
}
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const phase = state.status === "creating" ? "create" : "update";
|
|
74
|
+
state.status = phase === "create" ? "creating" : "updating";
|
|
75
|
+
state.oldProps = state.props;
|
|
76
|
+
state.props = props;
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (!quiet) {
|
|
79
|
+
console.log(
|
|
80
|
+
`${phase === "create" ? "Create" : "Update"}: "${resource.FQN}"`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
81
83
|
|
|
82
|
-
|
|
84
|
+
await scope.state.set(resource.ID, state);
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
let isReplaced = false;
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
88
|
+
const ctx = context({
|
|
89
|
+
scope,
|
|
90
|
+
phase,
|
|
91
|
+
kind: resource.Kind,
|
|
92
|
+
id: resource.ID,
|
|
93
|
+
fqn: resource.FQN,
|
|
94
|
+
seq: resource.Seq,
|
|
95
|
+
state,
|
|
96
|
+
replace: () => {
|
|
97
|
+
if (isReplaced) {
|
|
98
|
+
console.warn(
|
|
99
|
+
`Resource ${resource.Kind} ${resource.FQN} is already marked as REPLACE`,
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
isReplaced = true;
|
|
104
|
+
},
|
|
105
|
+
});
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
const output = await alchemy.run(resource.ID, async () => {
|
|
108
|
+
return provider.handler.bind(ctx)(resource.ID, props);
|
|
109
|
+
});
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
if (!quiet) {
|
|
112
|
+
console.log(
|
|
113
|
+
`${phase === "create" ? "Created" : "Updated"}: "${resource.FQN}"`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
117
|
+
await scope.state.set(resource.ID, {
|
|
118
|
+
kind: resource.Kind,
|
|
119
|
+
id: resource.ID,
|
|
120
|
+
fqn: resource.FQN,
|
|
121
|
+
seq: resource.Seq,
|
|
122
|
+
data: state.data,
|
|
123
|
+
status: phase === "create" ? "created" : "updated",
|
|
124
|
+
output,
|
|
125
|
+
props,
|
|
126
|
+
// deps: [...deps],
|
|
127
|
+
});
|
|
128
|
+
// if (output !== undefined) {
|
|
129
|
+
// resource[Provide](output as Out);
|
|
130
|
+
// }
|
|
131
|
+
return output as any;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
scope.fail();
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
130
136
|
}
|
package/src/cloudflare/index.ts
CHANGED
|
@@ -230,17 +230,6 @@ export const StaticSite = Resource(
|
|
|
230
230
|
throw new Error("Directory is required for StaticSite");
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
try {
|
|
234
|
-
const dirStat = await fs.stat(props.dir);
|
|
235
|
-
if (!dirStat.isDirectory()) {
|
|
236
|
-
throw new Error(`"${props.dir}" is not a directory`);
|
|
237
|
-
}
|
|
238
|
-
} catch (error: any) {
|
|
239
|
-
throw new Error(
|
|
240
|
-
`Directory "${props.dir}" does not exist: ${error.message}`,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
233
|
// Run build command if provided
|
|
245
234
|
if (props.build?.command) {
|
|
246
235
|
try {
|
|
@@ -266,6 +255,17 @@ export const StaticSite = Resource(
|
|
|
266
255
|
}
|
|
267
256
|
}
|
|
268
257
|
|
|
258
|
+
try {
|
|
259
|
+
const dirStat = await fs.stat(props.dir);
|
|
260
|
+
if (!dirStat.isDirectory()) {
|
|
261
|
+
throw new Error(`"${props.dir}" is not a directory`);
|
|
262
|
+
}
|
|
263
|
+
} catch (error: any) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Directory "${props.dir}" does not exist: ${error.message}`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
269
|
// Use the provided name
|
|
270
270
|
const siteName = props.name;
|
|
271
271
|
const indexPage = props.indexPage || "index.html";
|
package/src/cloudflare/worker.ts
CHANGED
|
@@ -528,7 +528,7 @@ async function assertWorkerDoesNotExist<B extends Bindings>(
|
|
|
528
528
|
);
|
|
529
529
|
} else {
|
|
530
530
|
throw new Error(
|
|
531
|
-
`Error checking if worker exists: ${response.status} ${response.statusText}`,
|
|
531
|
+
`Error checking if worker exists: ${response.status} ${response.statusText} ${await response.text()}`,
|
|
532
532
|
);
|
|
533
533
|
}
|
|
534
534
|
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import * as fs from "fs/promises";
|
|
2
|
+
import type { Context } from "../context";
|
|
3
|
+
import { Resource } from "../resource";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Properties for wrangler.json configuration file
|
|
7
|
+
*/
|
|
8
|
+
export interface WranglerJsonProps {
|
|
9
|
+
/**
|
|
10
|
+
* The name of your worker
|
|
11
|
+
*/
|
|
12
|
+
name: string;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The directory containing your worker entry point
|
|
16
|
+
* @default "src"
|
|
17
|
+
*/
|
|
18
|
+
main?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The entry point for your worker
|
|
22
|
+
* @default "index.ts" or "index.js"
|
|
23
|
+
*/
|
|
24
|
+
entrypoint?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The directory to store build artifacts
|
|
28
|
+
* @default "dist"
|
|
29
|
+
*/
|
|
30
|
+
outdir?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The directory to serve static assets from
|
|
34
|
+
*/
|
|
35
|
+
assets?: string;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Minify the worker script
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
minify?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Node.js compatibility mode
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
node_compat?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* First-party worker service bindings
|
|
51
|
+
*/
|
|
52
|
+
services?: Array<{
|
|
53
|
+
/**
|
|
54
|
+
* Binding name
|
|
55
|
+
*/
|
|
56
|
+
name: string;
|
|
57
|
+
/**
|
|
58
|
+
* Service environment
|
|
59
|
+
*/
|
|
60
|
+
environment?: string;
|
|
61
|
+
}>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Worker environment variables
|
|
65
|
+
*/
|
|
66
|
+
vars?: Record<string, string>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* KV Namespace bindings
|
|
70
|
+
*/
|
|
71
|
+
kv_namespaces?: Array<{
|
|
72
|
+
/**
|
|
73
|
+
* Binding name
|
|
74
|
+
*/
|
|
75
|
+
binding: string;
|
|
76
|
+
/**
|
|
77
|
+
* KV namespace ID
|
|
78
|
+
*/
|
|
79
|
+
id: string;
|
|
80
|
+
/**
|
|
81
|
+
* Preview KV namespace ID
|
|
82
|
+
*/
|
|
83
|
+
preview_id?: string;
|
|
84
|
+
}>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* R2 bucket bindings
|
|
88
|
+
*/
|
|
89
|
+
r2_buckets?: Array<{
|
|
90
|
+
/**
|
|
91
|
+
* Binding name
|
|
92
|
+
*/
|
|
93
|
+
binding: string;
|
|
94
|
+
/**
|
|
95
|
+
* Bucket name
|
|
96
|
+
*/
|
|
97
|
+
bucket_name: string;
|
|
98
|
+
/**
|
|
99
|
+
* Preview bucket name
|
|
100
|
+
*/
|
|
101
|
+
preview_bucket_name?: string;
|
|
102
|
+
}>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* D1 database bindings
|
|
106
|
+
*/
|
|
107
|
+
d1_databases?: Array<{
|
|
108
|
+
/**
|
|
109
|
+
* Binding name
|
|
110
|
+
*/
|
|
111
|
+
binding: string;
|
|
112
|
+
/**
|
|
113
|
+
* Database name
|
|
114
|
+
*/
|
|
115
|
+
database_name: string;
|
|
116
|
+
/**
|
|
117
|
+
* Database ID
|
|
118
|
+
*/
|
|
119
|
+
database_id: string;
|
|
120
|
+
/**
|
|
121
|
+
* Preview database ID
|
|
122
|
+
*/
|
|
123
|
+
preview_database_id?: string;
|
|
124
|
+
}>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Durable Object bindings
|
|
128
|
+
*/
|
|
129
|
+
durable_objects?: {
|
|
130
|
+
/**
|
|
131
|
+
* Durable Object bindings
|
|
132
|
+
*/
|
|
133
|
+
bindings: Array<{
|
|
134
|
+
/**
|
|
135
|
+
* Binding name
|
|
136
|
+
*/
|
|
137
|
+
name: string;
|
|
138
|
+
/**
|
|
139
|
+
* Class name
|
|
140
|
+
*/
|
|
141
|
+
class_name: string;
|
|
142
|
+
/**
|
|
143
|
+
* Script name
|
|
144
|
+
*/
|
|
145
|
+
script_name?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Environment name
|
|
148
|
+
*/
|
|
149
|
+
environment?: string;
|
|
150
|
+
}>;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Queue bindings
|
|
155
|
+
*/
|
|
156
|
+
queues?: {
|
|
157
|
+
/**
|
|
158
|
+
* Producer bindings
|
|
159
|
+
*/
|
|
160
|
+
producers?: Array<{
|
|
161
|
+
/**
|
|
162
|
+
* Binding name
|
|
163
|
+
*/
|
|
164
|
+
binding: string;
|
|
165
|
+
/**
|
|
166
|
+
* Queue name
|
|
167
|
+
*/
|
|
168
|
+
queue: string;
|
|
169
|
+
}>;
|
|
170
|
+
/**
|
|
171
|
+
* Consumer configuration
|
|
172
|
+
*/
|
|
173
|
+
consumers?: Array<{
|
|
174
|
+
/**
|
|
175
|
+
* Queue name
|
|
176
|
+
*/
|
|
177
|
+
queue: string;
|
|
178
|
+
/**
|
|
179
|
+
* Maximum batch size
|
|
180
|
+
*/
|
|
181
|
+
max_batch_size?: number;
|
|
182
|
+
/**
|
|
183
|
+
* Maximum batch timeout
|
|
184
|
+
*/
|
|
185
|
+
max_batch_timeout?: number;
|
|
186
|
+
/**
|
|
187
|
+
* Maximum retries
|
|
188
|
+
*/
|
|
189
|
+
max_retries?: number;
|
|
190
|
+
/**
|
|
191
|
+
* Dead letter queue
|
|
192
|
+
*/
|
|
193
|
+
dead_letter_queue?: string;
|
|
194
|
+
}>;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Analytics Engine bindings
|
|
199
|
+
*/
|
|
200
|
+
analytics_engine_datasets?: Array<{
|
|
201
|
+
/**
|
|
202
|
+
* Binding name
|
|
203
|
+
*/
|
|
204
|
+
binding: string;
|
|
205
|
+
/**
|
|
206
|
+
* Dataset name
|
|
207
|
+
*/
|
|
208
|
+
dataset?: string;
|
|
209
|
+
}>;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Route configuration
|
|
213
|
+
*/
|
|
214
|
+
routes?: Array<string>;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Triggers configuration
|
|
218
|
+
*/
|
|
219
|
+
triggers?: {
|
|
220
|
+
/**
|
|
221
|
+
* Cron triggers
|
|
222
|
+
*/
|
|
223
|
+
crons: Array<string>;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Worker compatibility date
|
|
228
|
+
*/
|
|
229
|
+
compatibility_date?: string;
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Worker compatibility flags
|
|
233
|
+
*/
|
|
234
|
+
compatibility_flags?: Array<string>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Worker usage model
|
|
238
|
+
* "bundled" - Includes resources like CPU and memory. Better for consistent workloads.
|
|
239
|
+
* "unbound" - Pay only for what you use. Better for sporadic workloads.
|
|
240
|
+
*/
|
|
241
|
+
usage_model?: "bundled" | "unbound";
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Path to the wrangler.json file
|
|
245
|
+
* @internal
|
|
246
|
+
*/
|
|
247
|
+
path?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Output returned after WranglerJson creation/update
|
|
252
|
+
*/
|
|
253
|
+
export interface WranglerJson
|
|
254
|
+
extends Resource<"cloudflare::WranglerJson">,
|
|
255
|
+
WranglerJsonProps {
|
|
256
|
+
/**
|
|
257
|
+
* Time at which the file was created
|
|
258
|
+
*/
|
|
259
|
+
createdAt: number;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Time at which the file was last updated
|
|
263
|
+
*/
|
|
264
|
+
updatedAt: number;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Resource for managing wrangler.json configuration files
|
|
269
|
+
*/
|
|
270
|
+
export const WranglerJson = Resource(
|
|
271
|
+
"cloudflare::WranglerJson",
|
|
272
|
+
async function (
|
|
273
|
+
this: Context<WranglerJson>,
|
|
274
|
+
id: string,
|
|
275
|
+
props: WranglerJsonProps,
|
|
276
|
+
): Promise<WranglerJson> {
|
|
277
|
+
// Default path is wrangler.json in current directory
|
|
278
|
+
const filePath = props.path || "wrangler.json";
|
|
279
|
+
|
|
280
|
+
if (this.phase === "delete") {
|
|
281
|
+
try {
|
|
282
|
+
await fs.unlink(filePath);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// Ignore errors if file doesn't exist
|
|
285
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return this.destroy();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Create or update the file
|
|
293
|
+
const config = {
|
|
294
|
+
...props,
|
|
295
|
+
// Remove internal path property
|
|
296
|
+
path: undefined,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Write the file
|
|
300
|
+
await fs.writeFile(filePath, JSON.stringify(config, null, 2));
|
|
301
|
+
|
|
302
|
+
// Return the resource
|
|
303
|
+
return this({
|
|
304
|
+
...props,
|
|
305
|
+
createdAt: Date.now(),
|
|
306
|
+
updatedAt: Date.now(),
|
|
307
|
+
});
|
|
308
|
+
},
|
|
309
|
+
);
|