alchemy 0.20.0 → 0.21.0
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/bound.d.ts +4 -1
- package/lib/cloudflare/bound.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.d.ts +2 -0
- package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
- package/lib/cloudflare/durable-object-namespace.js +3 -0
- package/lib/cloudflare/durable-object-namespace.js.map +1 -1
- package/lib/cloudflare/worker-metadata.d.ts +1 -1
- package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
- package/lib/cloudflare/worker-metadata.js +115 -24
- package/lib/cloudflare/worker-metadata.js.map +1 -1
- package/lib/cloudflare/worker.d.ts +41 -10
- package/lib/cloudflare/worker.d.ts.map +1 -1
- package/lib/cloudflare/worker.js +34 -16
- package/lib/cloudflare/worker.js.map +1 -1
- package/lib/cloudflare/workflow.d.ts +2 -0
- package/lib/cloudflare/workflow.d.ts.map +1 -1
- package/lib/cloudflare/workflow.js +3 -0
- package/lib/cloudflare/workflow.js.map +1 -1
- package/lib/cloudflare/wrangler.json.d.ts +1 -1
- package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
- package/lib/cloudflare/wrangler.json.js +4 -0
- package/lib/cloudflare/wrangler.json.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/type.d.ts +6 -0
- package/lib/type.d.ts.map +1 -0
- package/lib/type.js +7 -0
- package/lib/type.js.map +1 -0
- package/package.json +1 -1
- package/src/cloudflare/bound.ts +45 -35
- package/src/cloudflare/durable-object-namespace.ts +10 -0
- package/src/cloudflare/worker-metadata.ts +136 -36
- package/src/cloudflare/worker.ts +183 -93
- package/src/cloudflare/workflow.ts +5 -0
- package/src/cloudflare/wrangler.json.ts +5 -1
- package/src/index.ts +1 -0
- package/src/type.ts +9 -0
package/src/cloudflare/worker.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { bootstrapPlugin } from "../runtime/plugin.js";
|
|
|
8
8
|
import { Scope } from "../scope.js";
|
|
9
9
|
import { Secret, secret } from "../secret.js";
|
|
10
10
|
import { serializeScope } from "../serde.js";
|
|
11
|
+
import type { type } from "../type.js";
|
|
11
12
|
import { withExponentialBackoff } from "../util/retry.js";
|
|
12
13
|
import { slugify } from "../util/slugify.js";
|
|
13
14
|
import { CloudflareApiError, handleApiError } from "./api-error.js";
|
|
@@ -17,11 +18,20 @@ import {
|
|
|
17
18
|
createCloudflareApi,
|
|
18
19
|
} from "./api.js";
|
|
19
20
|
import type { Assets } from "./assets.js";
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type Binding,
|
|
23
|
+
type Bindings,
|
|
24
|
+
Json,
|
|
25
|
+
type WorkerBindingSpec,
|
|
26
|
+
} from "./bindings.js";
|
|
21
27
|
import type { Bound } from "./bound.js";
|
|
22
28
|
import { isBucket } from "./bucket.js";
|
|
23
29
|
import { bundleWorkerScript } from "./bundle/bundle-worker.js";
|
|
24
30
|
import { isD1Database } from "./d1-database.js";
|
|
31
|
+
import {
|
|
32
|
+
DurableObjectNamespace,
|
|
33
|
+
isDurableObjectNamespace,
|
|
34
|
+
} from "./durable-object-namespace.js";
|
|
25
35
|
import {
|
|
26
36
|
type EventSource,
|
|
27
37
|
type QueueEventSource,
|
|
@@ -90,8 +100,10 @@ export interface AssetsConfig {
|
|
|
90
100
|
serve_directly?: boolean;
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
export interface BaseWorkerProps<
|
|
94
|
-
extends
|
|
103
|
+
export interface BaseWorkerProps<
|
|
104
|
+
B extends Bindings | undefined = undefined,
|
|
105
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
106
|
+
> extends CloudflareApiOptions {
|
|
95
107
|
/**
|
|
96
108
|
* Bundle options when using entryPoint
|
|
97
109
|
*
|
|
@@ -198,16 +210,27 @@ export interface BaseWorkerProps<B extends Bindings = Bindings>
|
|
|
198
210
|
* Can include queues, streams, or other event sources.
|
|
199
211
|
*/
|
|
200
212
|
eventSources?: EventSource[];
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The RPC class to use for the worker.
|
|
216
|
+
*
|
|
217
|
+
* This is only used when using the rpc property.
|
|
218
|
+
*/
|
|
219
|
+
rpc?: (new (...args: any[]) => RPC) | type<RPC>;
|
|
201
220
|
}
|
|
202
221
|
|
|
203
|
-
export interface InlineWorkerProps<
|
|
204
|
-
extends
|
|
222
|
+
export interface InlineWorkerProps<
|
|
223
|
+
B extends Bindings | undefined = Bindings,
|
|
224
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
225
|
+
> extends BaseWorkerProps<B, RPC> {
|
|
205
226
|
script: string;
|
|
206
227
|
entrypoint?: undefined;
|
|
207
228
|
}
|
|
208
229
|
|
|
209
|
-
export interface EntrypointWorkerProps<
|
|
210
|
-
extends
|
|
230
|
+
export interface EntrypointWorkerProps<
|
|
231
|
+
B extends Bindings | undefined = Bindings,
|
|
232
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
233
|
+
> extends BaseWorkerProps<B, RPC> {
|
|
211
234
|
entrypoint: string;
|
|
212
235
|
script?: undefined;
|
|
213
236
|
}
|
|
@@ -215,9 +238,10 @@ export interface EntrypointWorkerProps<B extends Bindings = Bindings>
|
|
|
215
238
|
/**
|
|
216
239
|
* Properties for creating or updating a Worker
|
|
217
240
|
*/
|
|
218
|
-
export type WorkerProps<
|
|
219
|
-
|
|
|
220
|
-
|
|
241
|
+
export type WorkerProps<
|
|
242
|
+
B extends Bindings | undefined = Bindings,
|
|
243
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
244
|
+
> = InlineWorkerProps<B, RPC> | EntrypointWorkerProps<B, RPC>;
|
|
221
245
|
|
|
222
246
|
export type FetchWorkerProps<
|
|
223
247
|
B extends Bindings = Bindings,
|
|
@@ -265,63 +289,70 @@ export function isWorker(resource: Resource): resource is Worker<any> {
|
|
|
265
289
|
/**
|
|
266
290
|
* Output returned after Worker creation/update
|
|
267
291
|
*/
|
|
268
|
-
export type Worker<
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*/
|
|
277
|
-
id: string;
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* The name of the worker
|
|
281
|
-
*/
|
|
282
|
-
name: string;
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Time at which the worker was created
|
|
286
|
-
*/
|
|
287
|
-
createdAt: number;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Time at which the worker was last updated
|
|
291
|
-
*/
|
|
292
|
-
updatedAt: number;
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* The worker's URL if enabled
|
|
296
|
-
* Format: {name}.{subdomain}.workers.dev
|
|
297
|
-
*/
|
|
298
|
-
url?: string;
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* The bindings that were created
|
|
302
|
-
*/
|
|
303
|
-
bindings: B | undefined;
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Configuration for static assets
|
|
307
|
-
*/
|
|
308
|
-
assets?: AssetsConfig;
|
|
309
|
-
|
|
310
|
-
// phantom property (for typeof myWorker.Env)
|
|
311
|
-
Env: {
|
|
312
|
-
[bindingName in keyof B]: Bound<B[bindingName]>;
|
|
313
|
-
};
|
|
292
|
+
export type Worker<
|
|
293
|
+
B extends Bindings | undefined = Bindings | undefined,
|
|
294
|
+
RPC extends Rpc.WorkerEntrypointBranded = Rpc.WorkerEntrypointBranded,
|
|
295
|
+
> = Resource<"cloudflare::Worker"> &
|
|
296
|
+
Omit<WorkerProps<B>, "url" | "script"> &
|
|
297
|
+
globalThis.Service & {
|
|
298
|
+
/** @internal phantom property */
|
|
299
|
+
__rpc__?: RPC;
|
|
314
300
|
|
|
315
|
-
|
|
316
|
-
* The compatibility date for the worker
|
|
317
|
-
*/
|
|
318
|
-
compatibilityDate: string;
|
|
301
|
+
type: "service";
|
|
319
302
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
303
|
+
/**
|
|
304
|
+
* The ID of the worker
|
|
305
|
+
*/
|
|
306
|
+
id: string;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* The name of the worker
|
|
310
|
+
*/
|
|
311
|
+
name: string;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Time at which the worker was created
|
|
315
|
+
*/
|
|
316
|
+
createdAt: number;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Time at which the worker was last updated
|
|
320
|
+
*/
|
|
321
|
+
updatedAt: number;
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The worker's URL if enabled
|
|
325
|
+
* Format: {name}.{subdomain}.workers.dev
|
|
326
|
+
*/
|
|
327
|
+
url?: string;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The bindings that were created
|
|
331
|
+
*/
|
|
332
|
+
bindings: B;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Configuration for static assets
|
|
336
|
+
*/
|
|
337
|
+
assets?: AssetsConfig;
|
|
338
|
+
|
|
339
|
+
// phantom property (for typeof myWorker.Env)
|
|
340
|
+
Env: B extends Bindings
|
|
341
|
+
? {
|
|
342
|
+
[bindingName in keyof B]: Bound<B[bindingName]>;
|
|
343
|
+
}
|
|
344
|
+
: undefined;
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The compatibility date for the worker
|
|
348
|
+
*/
|
|
349
|
+
compatibilityDate: string;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* The compatibility flags for the worker
|
|
353
|
+
*/
|
|
354
|
+
compatibilityFlags: string[];
|
|
355
|
+
};
|
|
325
356
|
|
|
326
357
|
/**
|
|
327
358
|
* A Cloudflare Worker is a serverless function that can be deployed to the Cloudflare network.
|
|
@@ -412,13 +443,36 @@ export type Worker<B extends Bindings = Bindings> =
|
|
|
412
443
|
* crons: ['* 15 * * *', '0 0 * * *', '0 12 * * MON']
|
|
413
444
|
* })
|
|
414
445
|
*
|
|
446
|
+
* @example
|
|
447
|
+
* // Create cross-script durable object binding where one worker
|
|
448
|
+
* // defines the durable object and another worker accesses it:
|
|
449
|
+
* const dataWorker = await Worker("data-worker", {
|
|
450
|
+
* name: "data-worker",
|
|
451
|
+
* entrypoint: "./src/data.ts",
|
|
452
|
+
* bindings: {
|
|
453
|
+
* // Bind to its own durable object
|
|
454
|
+
* STORAGE: new DurableObjectNamespace("storage", {
|
|
455
|
+
* className: "DataStorage"
|
|
456
|
+
* })
|
|
457
|
+
* }
|
|
458
|
+
* });
|
|
459
|
+
*
|
|
460
|
+
* const apiWorker = await Worker("api-worker", {
|
|
461
|
+
* name: "api-worker",
|
|
462
|
+
* entrypoint: "./src/api.ts",
|
|
463
|
+
* bindings: {
|
|
464
|
+
* // Cross-script binding to the data worker's durable object
|
|
465
|
+
* SHARED_STORAGE: dataWorker.bindings.STORAGE
|
|
466
|
+
* }
|
|
467
|
+
* });
|
|
468
|
+
*
|
|
415
469
|
* @see
|
|
416
470
|
* https://developers.cloudflare.com/workers/
|
|
417
471
|
*/
|
|
418
|
-
export function Worker<
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
): Promise<Worker<B>>;
|
|
472
|
+
export function Worker<
|
|
473
|
+
const B extends Bindings,
|
|
474
|
+
RPC extends Rpc.WorkerEntrypointBranded,
|
|
475
|
+
>(id: string, props: WorkerProps<B, RPC>): Promise<Worker<B, RPC>>;
|
|
422
476
|
export function Worker<const B extends Bindings, const E extends EventSource[]>(
|
|
423
477
|
id: string,
|
|
424
478
|
meta: ImportMeta,
|
|
@@ -431,6 +485,7 @@ export function Worker<const B extends Bindings>(
|
|
|
431
485
|
): Promise<Worker<B>> {
|
|
432
486
|
const [id, meta, props] =
|
|
433
487
|
args.length === 2 ? [args[0], undefined, args[1]] : args;
|
|
488
|
+
|
|
434
489
|
if (("fetch" in props && props.fetch) || ("queue" in props && props.queue)) {
|
|
435
490
|
const scope = Scope.current;
|
|
436
491
|
|
|
@@ -648,7 +703,11 @@ export const _Worker = Resource(
|
|
|
648
703
|
const compatibilityFlags = props.compatibilityFlags ?? [];
|
|
649
704
|
|
|
650
705
|
const uploadWorkerScript = async (props: WorkerProps<B>) => {
|
|
651
|
-
const oldBindings = await
|
|
706
|
+
const [oldBindings, oldMetadata] = await Promise.all([
|
|
707
|
+
getWorkerBindings(api, workerName),
|
|
708
|
+
getWorkerScriptMetadata(api, workerName),
|
|
709
|
+
]);
|
|
710
|
+
const oldTags = oldMetadata?.default_environment?.script?.tags;
|
|
652
711
|
|
|
653
712
|
// Get the script content - either from props.script, or by bundling
|
|
654
713
|
const scriptContent =
|
|
@@ -695,6 +754,7 @@ export const _Worker = Resource(
|
|
|
695
754
|
const scriptMetadata = await prepareWorkerMetadata(
|
|
696
755
|
this,
|
|
697
756
|
oldBindings,
|
|
757
|
+
oldTags,
|
|
698
758
|
{
|
|
699
759
|
...props,
|
|
700
760
|
compatibilityDate,
|
|
@@ -706,9 +766,6 @@ export const _Worker = Resource(
|
|
|
706
766
|
|
|
707
767
|
await putWorker(api, workerName, scriptContent, scriptMetadata);
|
|
708
768
|
|
|
709
|
-
// TODO: it is less than ideal that this can fail, resulting in state problem
|
|
710
|
-
await this.set("bindings", props.bindings);
|
|
711
|
-
|
|
712
769
|
for (const workflow of workflowsBindings) {
|
|
713
770
|
await upsertWorkflow(api, {
|
|
714
771
|
workflowName: workflow.workflowName,
|
|
@@ -816,6 +873,24 @@ export const _Worker = Resource(
|
|
|
816
873
|
|
|
817
874
|
const { scriptMetadata, workerUrl, now } = await uploadWorkerScript(props);
|
|
818
875
|
|
|
876
|
+
function exportBindings() {
|
|
877
|
+
return Object.fromEntries(
|
|
878
|
+
Object.entries(props.bindings ?? ({} as B)).map(
|
|
879
|
+
([bindingName, binding]) => [
|
|
880
|
+
bindingName,
|
|
881
|
+
isDurableObjectNamespace(binding) &&
|
|
882
|
+
binding.scriptName === undefined
|
|
883
|
+
? new DurableObjectNamespace(binding.id, {
|
|
884
|
+
...binding,
|
|
885
|
+
// re-export this binding mapping to the host worker (this worker)
|
|
886
|
+
scriptName: workerName,
|
|
887
|
+
})
|
|
888
|
+
: binding,
|
|
889
|
+
],
|
|
890
|
+
),
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
819
894
|
// Construct the output
|
|
820
895
|
return this({
|
|
821
896
|
...props,
|
|
@@ -826,7 +901,7 @@ export const _Worker = Resource(
|
|
|
826
901
|
compatibilityDate,
|
|
827
902
|
compatibilityFlags,
|
|
828
903
|
format: props.format || "esm", // Include format in the output
|
|
829
|
-
bindings:
|
|
904
|
+
bindings: exportBindings(),
|
|
830
905
|
env: props.env,
|
|
831
906
|
observability: scriptMetadata.observability,
|
|
832
907
|
createdAt: now,
|
|
@@ -1058,34 +1133,49 @@ export async function getWorkerScriptMetadata(
|
|
|
1058
1133
|
return ((await response.json()) as any).result as WorkerScriptMetadata;
|
|
1059
1134
|
}
|
|
1060
1135
|
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
) {
|
|
1136
|
+
async function getWorkerBindings(api: CloudflareApi, workerName: string) {
|
|
1137
|
+
// Fetch the bindings for a worker by calling the Cloudflare API endpoint:
|
|
1138
|
+
// GET /accounts/:account_id/workers/scripts/:script_name/bindings
|
|
1139
|
+
// See: https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/script_and_version_settings/methods/get/
|
|
1066
1140
|
const response = await api.get(
|
|
1067
|
-
`/accounts/${api.accountId}/workers/
|
|
1068
|
-
{
|
|
1069
|
-
headers: {
|
|
1070
|
-
Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`,
|
|
1071
|
-
"Content-Type": "application/json",
|
|
1072
|
-
},
|
|
1073
|
-
},
|
|
1141
|
+
`/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
|
|
1074
1142
|
);
|
|
1075
|
-
|
|
1076
1143
|
if (response.status === 404) {
|
|
1077
1144
|
return undefined;
|
|
1078
1145
|
}
|
|
1079
|
-
|
|
1080
1146
|
if (!response.ok) {
|
|
1081
1147
|
throw new Error(
|
|
1082
|
-
`
|
|
1148
|
+
`Error getting worker bindings: ${response.status} ${response.statusText}`,
|
|
1083
1149
|
);
|
|
1084
1150
|
}
|
|
1085
|
-
|
|
1086
|
-
const
|
|
1087
|
-
|
|
1088
|
-
|
|
1151
|
+
// The result is an object with a "result" property containing the bindings array
|
|
1152
|
+
const { result, success, errors } = (await response.json()) as {
|
|
1153
|
+
result: {
|
|
1154
|
+
bindings: WorkerBindingSpec[];
|
|
1155
|
+
compatibility_date: string;
|
|
1156
|
+
compatibility_flags: string[];
|
|
1157
|
+
[key: string]: any;
|
|
1158
|
+
};
|
|
1159
|
+
success: boolean;
|
|
1160
|
+
errors: Array<{
|
|
1161
|
+
code: number;
|
|
1162
|
+
message: string;
|
|
1163
|
+
documentation_url: string;
|
|
1164
|
+
[key: string]: any;
|
|
1165
|
+
}>;
|
|
1166
|
+
messages: Array<{
|
|
1167
|
+
code: number;
|
|
1168
|
+
message: string;
|
|
1169
|
+
documentation_url: string;
|
|
1170
|
+
[key: string]: any;
|
|
1171
|
+
}>;
|
|
1172
|
+
};
|
|
1173
|
+
if (!success) {
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
`Error getting worker bindings: ${response.status} ${response.statusText}\nErrors:\n${errors.map((e) => `- [${e.code}] ${e.message} (${e.documentation_url})`).join("\n")}`,
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
return result.bindings;
|
|
1089
1179
|
}
|
|
1090
1180
|
|
|
1091
1181
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { handleApiError } from "./api-error.js";
|
|
2
2
|
import type { CloudflareApi } from "./api.js";
|
|
3
|
+
import type { Binding } from "./bindings.js";
|
|
3
4
|
|
|
4
5
|
export interface WorkflowProps {
|
|
5
6
|
/**
|
|
@@ -20,6 +21,10 @@ export interface WorkflowProps {
|
|
|
20
21
|
className?: string;
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
export function isWorkflow(binding: Binding): binding is Workflow {
|
|
25
|
+
return typeof binding === "object" && binding.type === "workflow";
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
export class Workflow<PARAMS = unknown> {
|
|
24
29
|
public readonly type = "workflow";
|
|
25
30
|
/**
|
|
@@ -17,7 +17,7 @@ export interface WranglerJsonProps {
|
|
|
17
17
|
/**
|
|
18
18
|
* The worker to generate the wrangler.json file for
|
|
19
19
|
*/
|
|
20
|
-
worker: Worker
|
|
20
|
+
worker: Worker<any>;
|
|
21
21
|
/**
|
|
22
22
|
* Path to write the wrangler.json file to
|
|
23
23
|
*
|
|
@@ -393,6 +393,10 @@ function processBindings(
|
|
|
393
393
|
}
|
|
394
394
|
// Process each binding
|
|
395
395
|
for (const [bindingName, binding] of Object.entries(bindings)) {
|
|
396
|
+
if (typeof binding === "function") {
|
|
397
|
+
// this is only reachable in the
|
|
398
|
+
throw new Error(`Invalid binding ${bindingName} is a function`);
|
|
399
|
+
}
|
|
396
400
|
if (typeof binding === "string") {
|
|
397
401
|
// Plain text binding - add to vars
|
|
398
402
|
if (!spec.vars) {
|
package/src/index.ts
CHANGED
package/src/type.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type type<T> = typeof type<T>;
|
|
2
|
+
/**
|
|
3
|
+
* Used to construct type-level alias information.
|
|
4
|
+
*/
|
|
5
|
+
export const type = ((): any => {
|
|
6
|
+
throw new Error("Should never be called, purely for type-level aliasing");
|
|
7
|
+
}) as (<T>() => T) &
|
|
8
|
+
// we also want to make this a "class type" so that syntax highlighting is always as a type
|
|
9
|
+
(new <T>() => T);
|