effortless-aws 0.36.0 → 0.37.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.
@@ -1,889 +0,0 @@
1
- // src/runtime/table-client.ts
2
- import { DynamoDB } from "@aws-sdk/client-dynamodb";
3
- import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
4
- var GSI_TAG_PK = "tag-pk-index";
5
- var marshallKey = (key) => marshall(key, { removeUndefinedValues: true });
6
- var buildSkCondition = (sk) => {
7
- const names = { "#sk": "sk" };
8
- if (typeof sk === "string") {
9
- return { expression: "AND #sk = :sk", names, values: { ":sk": sk } };
10
- }
11
- if ("begins_with" in sk) {
12
- return { expression: "AND begins_with(#sk, :sk)", names, values: { ":sk": sk.begins_with } };
13
- }
14
- if ("gt" in sk) {
15
- return { expression: "AND #sk > :sk", names, values: { ":sk": sk.gt } };
16
- }
17
- if ("gte" in sk) {
18
- return { expression: "AND #sk >= :sk", names, values: { ":sk": sk.gte } };
19
- }
20
- if ("lt" in sk) {
21
- return { expression: "AND #sk < :sk", names, values: { ":sk": sk.lt } };
22
- }
23
- if ("lte" in sk) {
24
- return { expression: "AND #sk <= :sk", names, values: { ":sk": sk.lte } };
25
- }
26
- if ("between" in sk) {
27
- return { expression: "AND #sk BETWEEN :sk1 AND :sk2", names, values: { ":sk1": sk.between[0], ":sk2": sk.between[1] } };
28
- }
29
- return { expression: "", names: {}, values: {} };
30
- };
31
- var createTableClient = (tableName, options) => {
32
- let client2 = null;
33
- const getClient2 = () => client2 ??= new DynamoDB({});
34
- const tagField = options?.tagField ?? "tag";
35
- return {
36
- tableName,
37
- async put(item, putOptions) {
38
- const dataObj = item.data;
39
- const tag = dataObj?.[tagField] || "";
40
- if (!tag) throw new Error(`tag is required: data must include a "${tagField}" field`);
41
- const dynamoItem = {
42
- pk: item.pk,
43
- sk: item.sk,
44
- tag,
45
- data: item.data
46
- };
47
- if (item.ttl !== void 0) dynamoItem.ttl = item.ttl;
48
- await getClient2().putItem({
49
- TableName: tableName,
50
- Item: marshall(dynamoItem, { removeUndefinedValues: true }),
51
- ...putOptions?.ifNotExists ? { ConditionExpression: "attribute_not_exists(pk)" } : {}
52
- });
53
- },
54
- async get(key) {
55
- const result = await getClient2().getItem({
56
- TableName: tableName,
57
- Key: marshallKey(key)
58
- });
59
- return result.Item ? unmarshall(result.Item) : void 0;
60
- },
61
- async delete(key) {
62
- await getClient2().deleteItem({
63
- TableName: tableName,
64
- Key: marshallKey(key)
65
- });
66
- },
67
- async update(key, actions) {
68
- const names = {};
69
- const values = {};
70
- const setClauses = [];
71
- const removeClauses = [];
72
- let counter = 0;
73
- const DATA_ALIAS = "#data";
74
- let needsDataAlias = false;
75
- if (actions.set) {
76
- for (const [attr, val] of Object.entries(actions.set)) {
77
- needsDataAlias = true;
78
- const alias = `#a${counter}`;
79
- const valAlias = `:v${counter}`;
80
- names[alias] = attr;
81
- values[valAlias] = val;
82
- setClauses.push(`${DATA_ALIAS}.${alias} = ${valAlias}`);
83
- counter++;
84
- }
85
- }
86
- if (actions.increment) {
87
- for (const [attr, val] of Object.entries(actions.increment)) {
88
- needsDataAlias = true;
89
- const alias = `#a${counter}`;
90
- const valAlias = `:v${counter}`;
91
- const zeroAlias = `:zero${counter}`;
92
- names[alias] = attr;
93
- values[valAlias] = val;
94
- values[zeroAlias] = 0;
95
- setClauses.push(`${DATA_ALIAS}.${alias} = if_not_exists(${DATA_ALIAS}.${alias}, ${zeroAlias}) + ${valAlias}`);
96
- counter++;
97
- }
98
- }
99
- if (actions.append) {
100
- for (const [attr, val] of Object.entries(actions.append)) {
101
- needsDataAlias = true;
102
- const alias = `#a${counter}`;
103
- const valAlias = `:v${counter}`;
104
- const emptyAlias = `:empty${counter}`;
105
- names[alias] = attr;
106
- values[valAlias] = val;
107
- values[emptyAlias] = [];
108
- setClauses.push(`${DATA_ALIAS}.${alias} = list_append(if_not_exists(${DATA_ALIAS}.${alias}, ${emptyAlias}), ${valAlias})`);
109
- counter++;
110
- }
111
- }
112
- if (actions.remove) {
113
- for (const attr of actions.remove) {
114
- needsDataAlias = true;
115
- const alias = `#a${counter}`;
116
- names[alias] = attr;
117
- removeClauses.push(`${DATA_ALIAS}.${alias}`);
118
- counter++;
119
- }
120
- }
121
- if (needsDataAlias) {
122
- names[DATA_ALIAS] = "data";
123
- }
124
- if (actions.tag !== void 0) {
125
- names["#tag"] = "tag";
126
- values[":tagVal"] = actions.tag;
127
- setClauses.push("#tag = :tagVal");
128
- }
129
- if (actions.ttl !== void 0) {
130
- names["#ttl"] = "ttl";
131
- if (actions.ttl === null) {
132
- removeClauses.push("#ttl");
133
- } else {
134
- values[":ttlVal"] = actions.ttl;
135
- setClauses.push("#ttl = :ttlVal");
136
- }
137
- }
138
- const parts = [];
139
- if (setClauses.length) parts.push(`SET ${setClauses.join(", ")}`);
140
- if (removeClauses.length) parts.push(`REMOVE ${removeClauses.join(", ")}`);
141
- if (!parts.length) return;
142
- const request = {
143
- TableName: tableName,
144
- Key: marshallKey(key),
145
- UpdateExpression: parts.join(" "),
146
- ExpressionAttributeNames: names,
147
- ...Object.keys(values).length ? { ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }) } : {}
148
- };
149
- try {
150
- await getClient2().updateItem(request);
151
- } catch (err) {
152
- if (needsDataAlias && err.name === "ValidationException") {
153
- const dataMap = {};
154
- if (actions.set) Object.assign(dataMap, actions.set);
155
- if (actions.increment) Object.assign(dataMap, actions.increment);
156
- if (actions.append) Object.assign(dataMap, actions.append);
157
- const retryNames = { "#data": "data" };
158
- const retryValues = { ":fullData": dataMap };
159
- const retrySets = ["#data = :fullData"];
160
- if (actions.tag !== void 0) {
161
- retryNames["#tag"] = "tag";
162
- retryValues[":tagVal"] = actions.tag;
163
- retrySets.push("#tag = :tagVal");
164
- }
165
- if (actions.ttl !== void 0 && actions.ttl !== null) {
166
- retryNames["#ttl"] = "ttl";
167
- retryValues[":ttlVal"] = actions.ttl;
168
- retrySets.push("#ttl = :ttlVal");
169
- }
170
- const retryParts = [`SET ${retrySets.join(", ")}`];
171
- if (actions.ttl === null) {
172
- retryNames["#ttl"] = "ttl";
173
- retryParts.push("REMOVE #ttl");
174
- }
175
- await getClient2().updateItem({
176
- TableName: tableName,
177
- Key: marshallKey(key),
178
- UpdateExpression: retryParts.join(" "),
179
- ExpressionAttributeNames: retryNames,
180
- ExpressionAttributeValues: marshall(retryValues, { removeUndefinedValues: true })
181
- });
182
- } else {
183
- throw err;
184
- }
185
- }
186
- },
187
- async query(params) {
188
- const names = { "#pk": "pk" };
189
- const values = { ":pk": params.pk };
190
- let keyCondition = "#pk = :pk";
191
- if (params.sk !== void 0) {
192
- const skCond = buildSkCondition(params.sk);
193
- keyCondition += ` ${skCond.expression}`;
194
- Object.assign(names, skCond.names);
195
- Object.assign(values, skCond.values);
196
- }
197
- const result = await getClient2().query({
198
- TableName: tableName,
199
- KeyConditionExpression: keyCondition,
200
- ExpressionAttributeNames: names,
201
- ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),
202
- ...params.limit ? { Limit: params.limit } : {},
203
- ...params.scanIndexForward !== void 0 ? { ScanIndexForward: params.scanIndexForward } : {}
204
- });
205
- return (result.Items ?? []).map((item) => unmarshall(item));
206
- },
207
- async queryByTag(params) {
208
- const names = { "#tag": "tag" };
209
- const values = { ":tag": params.tag };
210
- let keyCondition = "#tag = :tag";
211
- if (params.pk !== void 0) {
212
- const pkCond = buildSkCondition(params.pk);
213
- const remapped = pkCond.expression.replace(/#sk/g, "#pk").replace(/:sk/g, ":pk");
214
- keyCondition += ` ${remapped}`;
215
- for (const [k, v] of Object.entries(pkCond.names)) {
216
- names[k === "#sk" ? "#pk" : k] = v === "sk" ? "pk" : v;
217
- }
218
- for (const [k, v] of Object.entries(pkCond.values)) {
219
- values[k.replace(":sk", ":pk")] = v;
220
- }
221
- }
222
- const result = await getClient2().query({
223
- TableName: tableName,
224
- IndexName: GSI_TAG_PK,
225
- KeyConditionExpression: keyCondition,
226
- ExpressionAttributeNames: names,
227
- ExpressionAttributeValues: marshall(values, { removeUndefinedValues: true }),
228
- ...params.limit ? { Limit: params.limit } : {},
229
- ...params.scanIndexForward !== void 0 ? { ScanIndexForward: params.scanIndexForward } : {}
230
- });
231
- return (result.Items ?? []).map((item) => unmarshall(item));
232
- }
233
- };
234
- };
235
-
236
- // src/handlers/handler-options.ts
237
- var toSeconds = (d) => {
238
- if (typeof d === "number") return d;
239
- const match = d.match(/^(\d+(?:\.\d+)?)(s|m|h|d)$/);
240
- if (!match) throw new Error(`Invalid duration: "${d}"`);
241
- const n = Number(match[1]);
242
- const unit = match[2];
243
- if (unit === "d") return n * 86400;
244
- if (unit === "h") return n * 3600;
245
- if (unit === "m") return n * 60;
246
- return n;
247
- };
248
-
249
- // src/handlers/auth.ts
250
- import * as crypto from "crypto";
251
- var cfBase64Encode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/=/g, "_").replace(/\//g, "~");
252
- var signCfCookies = (policy, config) => {
253
- const ttlSeconds = toSeconds(policy.ttl);
254
- const expireTime = Math.floor(Date.now() / 1e3) + ttlSeconds;
255
- const resource = config.domain === "*" ? `https://*${policy.path}` : `https://${config.domain}${policy.path}`;
256
- const policyJson = JSON.stringify({
257
- Statement: [{
258
- Resource: resource,
259
- Condition: {
260
- DateLessThan: { "AWS:EpochTime": expireTime }
261
- }
262
- }]
263
- });
264
- const policyBase64 = cfBase64Encode(Buffer.from(policyJson, "utf-8"));
265
- const signature = cfBase64Encode(
266
- crypto.sign("sha1", Buffer.from(policyJson, "utf-8"), config.privateKey)
267
- );
268
- const cookieAttrs = `; Secure; SameSite=Lax; Path=/; Max-Age=${ttlSeconds}`;
269
- return [
270
- `CloudFront-Policy=${policyBase64}${cookieAttrs}`,
271
- `CloudFront-Signature=${signature}${cookieAttrs}`,
272
- `CloudFront-Key-Pair-Id=${config.keyPairId}${cookieAttrs}`
273
- ];
274
- };
275
- var AUTH_COOKIE_NAME = "__eff_session";
276
- var createAuthRuntime = (secret, defaultExpiresIn, apiTokenVerify, apiTokenHeader, apiTokenCacheTtlSeconds, cfSigningConfig) => {
277
- const tokenCache = apiTokenCacheTtlSeconds ? /* @__PURE__ */ new Map() : void 0;
278
- const sign2 = (payload) => crypto.createHmac("sha256", secret).update(payload).digest("base64url");
279
- const cookieBase = `${AUTH_COOKIE_NAME}=`;
280
- const cookieAttrs = "; HttpOnly; Secure; SameSite=Lax; Path=/";
281
- const decodeSession = (cookieValue) => {
282
- if (!cookieValue) return void 0;
283
- const dot = cookieValue.indexOf(".");
284
- if (dot === -1) return void 0;
285
- const payload = cookieValue.slice(0, dot);
286
- const sig = cookieValue.slice(dot + 1);
287
- if (sign2(payload) !== sig) return void 0;
288
- try {
289
- const parsed = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
290
- if (parsed.exp <= Math.floor(Date.now() / 1e3)) return void 0;
291
- const { exp: _, ...data } = parsed;
292
- return Object.keys(data).length > 0 ? data : void 0;
293
- } catch {
294
- return void 0;
295
- }
296
- };
297
- const extractTokenValue = (headerValue) => {
298
- const isDefaultHeader = !apiTokenHeader || apiTokenHeader.toLowerCase() === "authorization";
299
- if (isDefaultHeader && headerValue.toLowerCase().startsWith("bearer ")) {
300
- return headerValue.slice(7);
301
- }
302
- return headerValue;
303
- };
304
- const buildHelpers = (sessionData) => ({
305
- createSession(data, options) {
306
- const seconds = options?.expiresIn ? toSeconds(options.expiresIn) : defaultExpiresIn;
307
- const exp = Math.floor(Date.now() / 1e3) + seconds;
308
- const payload = Buffer.from(JSON.stringify({ exp, ...data }), "utf-8").toString("base64url");
309
- const sig = sign2(payload);
310
- const sessionCookie = `${cookieBase}${payload}.${sig}${cookieAttrs}; Max-Age=${seconds}`;
311
- const cfCookies = options?.cdnPolicy && cfSigningConfig ? signCfCookies(options.cdnPolicy, cfSigningConfig) : void 0;
312
- return {
313
- status: 200,
314
- body: { ok: true },
315
- headers: {
316
- "set-cookie": sessionCookie
317
- },
318
- ...cfCookies ? { cookies: [sessionCookie, ...cfCookies] } : {}
319
- };
320
- },
321
- clearSession() {
322
- return {
323
- status: 200,
324
- body: { ok: true },
325
- headers: {
326
- "set-cookie": `${cookieBase}${cookieAttrs}; Max-Age=0`
327
- }
328
- };
329
- },
330
- session: sessionData
331
- });
332
- return {
333
- async forRequest(cookieValue, authHeader) {
334
- if (authHeader && apiTokenVerify) {
335
- const tokenValue = extractTokenValue(authHeader);
336
- if (tokenCache) {
337
- const cached = tokenCache.get(tokenValue);
338
- if (cached && cached.expiresAt > Date.now()) {
339
- return buildHelpers(cached.session);
340
- }
341
- }
342
- const session = await apiTokenVerify({ value: tokenValue });
343
- if (tokenCache && apiTokenCacheTtlSeconds) {
344
- tokenCache.set(tokenValue, { session, expiresAt: Date.now() + apiTokenCacheTtlSeconds * 1e3 });
345
- }
346
- return buildHelpers(session);
347
- }
348
- return buildHelpers(decodeSession(cookieValue));
349
- }
350
- };
351
- };
352
-
353
- // src/runtime/bucket-client.ts
354
- import { S3 } from "@aws-sdk/client-s3";
355
- var createBucketMethods = (getClient2, bucketName) => ({
356
- bucketName,
357
- async put(key, body, options) {
358
- await getClient2().putObject({
359
- Bucket: bucketName,
360
- Key: key,
361
- Body: typeof body === "string" ? Buffer.from(body) : body,
362
- ...options?.contentType ? { ContentType: options.contentType } : {}
363
- });
364
- },
365
- async get(key) {
366
- try {
367
- const result = await getClient2().getObject({
368
- Bucket: bucketName,
369
- Key: key
370
- });
371
- const chunks = [];
372
- const stream = result.Body;
373
- for await (const chunk of stream) {
374
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
375
- }
376
- return {
377
- body: Buffer.concat(chunks),
378
- contentType: result.ContentType
379
- };
380
- } catch (error) {
381
- if (error instanceof Error && (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404)) {
382
- return void 0;
383
- }
384
- throw error;
385
- }
386
- },
387
- async delete(key) {
388
- await getClient2().deleteObject({
389
- Bucket: bucketName,
390
- Key: key
391
- });
392
- },
393
- async list(prefix) {
394
- const items = [];
395
- let continuationToken;
396
- do {
397
- const result = await getClient2().listObjectsV2({
398
- Bucket: bucketName,
399
- ...prefix ? { Prefix: prefix } : {},
400
- ...continuationToken ? { ContinuationToken: continuationToken } : {}
401
- });
402
- for (const obj of result.Contents ?? []) {
403
- if (obj.Key) {
404
- items.push({
405
- key: obj.Key,
406
- size: obj.Size ?? 0,
407
- lastModified: obj.LastModified
408
- });
409
- }
410
- }
411
- continuationToken = result.IsTruncated ? result.NextContinuationToken : void 0;
412
- } while (continuationToken);
413
- return items;
414
- }
415
- });
416
- var createEntityClient = (getClient2, bucketName, entityName, cacheSeconds) => {
417
- const entityKey = (id) => `${entityName}/${id}.json`;
418
- return {
419
- async put(id, data) {
420
- await getClient2().putObject({
421
- Bucket: bucketName,
422
- Key: entityKey(id),
423
- Body: JSON.stringify(data),
424
- ContentType: "application/json",
425
- ...cacheSeconds != null ? { CacheControl: `public, max-age=${cacheSeconds}` } : {}
426
- });
427
- },
428
- async get(id) {
429
- try {
430
- const result = await getClient2().getObject({
431
- Bucket: bucketName,
432
- Key: entityKey(id)
433
- });
434
- const chunks = [];
435
- const stream = result.Body;
436
- for await (const chunk of stream) {
437
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
438
- }
439
- return JSON.parse(Buffer.concat(chunks).toString("utf-8"));
440
- } catch (error) {
441
- if (error instanceof Error && (error.name === "NoSuchKey" || error.$metadata?.httpStatusCode === 404)) {
442
- return void 0;
443
- }
444
- throw error;
445
- }
446
- },
447
- async delete(id) {
448
- await getClient2().deleteObject({
449
- Bucket: bucketName,
450
- Key: entityKey(id)
451
- });
452
- },
453
- async list() {
454
- const items = [];
455
- let continuationToken;
456
- const prefix = `${entityName}/`;
457
- do {
458
- const result = await getClient2().listObjectsV2({
459
- Bucket: bucketName,
460
- Prefix: prefix,
461
- ...continuationToken ? { ContinuationToken: continuationToken } : {}
462
- });
463
- for (const obj of result.Contents ?? []) {
464
- if (!obj.Key || !obj.Key.endsWith(".json")) continue;
465
- const id = obj.Key.slice(prefix.length, -".json".length);
466
- try {
467
- const getResult = await getClient2().getObject({
468
- Bucket: bucketName,
469
- Key: obj.Key
470
- });
471
- const chunks = [];
472
- const stream = getResult.Body;
473
- for await (const chunk of stream) {
474
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
475
- }
476
- items.push({ id, data: JSON.parse(Buffer.concat(chunks).toString("utf-8")) });
477
- } catch {
478
- }
479
- }
480
- continuationToken = result.IsTruncated ? result.NextContinuationToken : void 0;
481
- } while (continuationToken);
482
- return items;
483
- }
484
- };
485
- };
486
- var createBucketClient = (bucketName) => {
487
- let client2 = null;
488
- const getClient2 = () => client2 ??= new S3({});
489
- return createBucketMethods(getClient2, bucketName);
490
- };
491
- var createBucketClientWithEntities = (bucketName, entitiesConfig) => {
492
- let client2 = null;
493
- const getClient2 = () => client2 ??= new S3({});
494
- const base = createBucketMethods(getClient2, bucketName);
495
- const result = { ...base };
496
- for (const [entityName, config] of Object.entries(entitiesConfig)) {
497
- result[entityName] = createEntityClient(getClient2, bucketName, entityName, config.cacheSeconds);
498
- }
499
- return result;
500
- };
501
-
502
- // src/runtime/handler-utils.ts
503
- import { readFileSync } from "fs";
504
- import { join } from "path";
505
-
506
- // src/runtime/email-client.ts
507
- import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
508
- var createEmailClient = () => {
509
- let client2 = null;
510
- const getClient2 = () => client2 ??= new SESv2Client({});
511
- return {
512
- async send({ from, to, subject, html, text }) {
513
- const toAddresses = Array.isArray(to) ? to : [to];
514
- await getClient2().send(
515
- new SendEmailCommand({
516
- FromEmailAddress: from,
517
- Destination: { ToAddresses: toAddresses },
518
- Content: {
519
- Simple: {
520
- Subject: { Data: subject },
521
- Body: {
522
- ...html ? { Html: { Data: html } } : {},
523
- ...text ? { Text: { Data: text } } : {}
524
- }
525
- }
526
- }
527
- })
528
- );
529
- }
530
- };
531
- };
532
-
533
- // src/runtime/queue-client.ts
534
- import { SQS } from "@aws-sdk/client-sqs";
535
- var createQueueClient = (queueName) => {
536
- let client2 = null;
537
- const getClient2 = () => client2 ??= new SQS({});
538
- let resolvedUrl;
539
- const getQueueUrl = async () => {
540
- if (resolvedUrl) return resolvedUrl;
541
- const result = await getClient2().getQueueUrl({ QueueName: `${queueName}.fifo` });
542
- resolvedUrl = result.QueueUrl;
543
- return resolvedUrl;
544
- };
545
- return {
546
- queueName,
547
- async send(input) {
548
- const queueUrl = await getQueueUrl();
549
- await getClient2().sendMessage({
550
- QueueUrl: queueUrl,
551
- MessageBody: JSON.stringify(input.body),
552
- MessageGroupId: input.groupId,
553
- ...input.deduplicationId ? { MessageDeduplicationId: input.deduplicationId } : {},
554
- ...input.messageAttributes ? {
555
- MessageAttributes: Object.fromEntries(
556
- Object.entries(input.messageAttributes).map(([k, v]) => [k, {
557
- DataType: v.dataType,
558
- StringValue: v.stringValue
559
- }])
560
- )
561
- } : {}
562
- });
563
- },
564
- async sendBatch(messages) {
565
- const queueUrl = await getQueueUrl();
566
- const entries = messages.map((msg, i) => ({
567
- Id: String(i),
568
- MessageBody: JSON.stringify(msg.body),
569
- MessageGroupId: msg.groupId,
570
- ...msg.deduplicationId ? { MessageDeduplicationId: msg.deduplicationId } : {}
571
- }));
572
- const result = await getClient2().sendMessageBatch({
573
- QueueUrl: queueUrl,
574
- Entries: entries
575
- });
576
- if (result.Failed && result.Failed.length > 0) {
577
- throw new Error(`Failed to send ${result.Failed.length} message(s): ${result.Failed.map((f) => f.Message).join(", ")}`);
578
- }
579
- }
580
- };
581
- };
582
-
583
- // src/runtime/worker-client.ts
584
- import { SQS as SQS2 } from "@aws-sdk/client-sqs";
585
- import { ECSClient, DescribeServicesCommand, UpdateServiceCommand } from "@aws-sdk/client-ecs";
586
- var createWorkerClient = (depValue) => {
587
- const lastColon = depValue.lastIndexOf(":");
588
- const workerName = depValue.slice(0, lastColon);
589
- const idleTimeoutMs = Number(depValue.slice(lastColon + 1)) * 1e3;
590
- const queueName = `${workerName}-worker`;
591
- const cluster = workerName.replace(/-[^-]+$/, "");
592
- const service = workerName;
593
- let sqsClient = null;
594
- const getSqs = () => sqsClient ??= new SQS2({});
595
- let ecsClient = null;
596
- const getEcs = () => ecsClient ??= new ECSClient({});
597
- let resolvedQueueUrl;
598
- const getQueueUrl = async () => {
599
- if (resolvedQueueUrl) return resolvedQueueUrl;
600
- const result = await getSqs().getQueueUrl({ QueueName: queueName });
601
- resolvedQueueUrl = result.QueueUrl;
602
- return resolvedQueueUrl;
603
- };
604
- let awakeUntil = 0;
605
- const ensureRunning = async () => {
606
- if (Date.now() < awakeUntil) return;
607
- const resp = await getEcs().send(new DescribeServicesCommand({ cluster, services: [service] }));
608
- const svc = resp.services?.[0];
609
- if (svc && svc.desiredCount === 0) {
610
- await getEcs().send(new UpdateServiceCommand({ cluster, service, desiredCount: 1 }));
611
- }
612
- awakeUntil = Date.now() + idleTimeoutMs;
613
- };
614
- return {
615
- async send(msg, options) {
616
- const queueUrl = await getQueueUrl();
617
- await getSqs().sendMessage({
618
- QueueUrl: queueUrl,
619
- MessageBody: JSON.stringify(msg),
620
- ...options?.delay ? { DelaySeconds: toSeconds(options.delay) } : {}
621
- });
622
- if (options?.start !== false && !options?.delay) {
623
- await ensureRunning();
624
- }
625
- },
626
- async status() {
627
- const resp = await getEcs().send(new DescribeServicesCommand({ cluster, services: [service] }));
628
- const svc = resp.services?.[0];
629
- if (svc && svc.runningCount && svc.runningCount > 0) return "running";
630
- return "idle";
631
- },
632
- async stop() {
633
- await getEcs().send(new UpdateServiceCommand({ cluster, service, desiredCount: 0 }));
634
- awakeUntil = 0;
635
- }
636
- };
637
- };
638
-
639
- // src/runtime/ssm-client.ts
640
- import { SSM } from "@aws-sdk/client-ssm";
641
- var client = null;
642
- var getClient = () => client ??= new SSM({});
643
- var getParameters = async (names) => {
644
- const map = /* @__PURE__ */ new Map();
645
- for (let i = 0; i < names.length; i += 10) {
646
- const batch = names.slice(i, i + 10);
647
- const result = await getClient().getParameters({
648
- Names: batch,
649
- WithDecryption: true
650
- });
651
- for (const p of result.Parameters ?? []) {
652
- if (p.Name && p.Value !== void 0) {
653
- map.set(p.Name, p.Value);
654
- }
655
- }
656
- }
657
- return map;
658
- };
659
-
660
- // src/runtime/handler-utils.ts
661
- var ENV_DEP_PREFIX = "EFF_DEP_";
662
- var ENV_PARAM_PREFIX = "EFF_PARAM_";
663
- var LOG_RANK = { error: 0, info: 1, debug: 2 };
664
- var truncate = (value, maxLength = 4096) => {
665
- if (value === void 0 || value === null) return value;
666
- const str = typeof value === "string" ? value : JSON.stringify(value);
667
- if (str.length <= maxLength) return value;
668
- return str.slice(0, maxLength) + "...[truncated]";
669
- };
670
- var DEP_FACTORIES = {
671
- table: (name, depHandler) => {
672
- const tagField = depHandler?.__spec?.tagField;
673
- return createTableClient(name, tagField ? { tagField } : void 0);
674
- },
675
- bucket: (name, depHandler) => {
676
- const entities = depHandler?.__spec?.entities;
677
- if (entities && Object.keys(entities).length > 0) {
678
- const config = {};
679
- for (const [entityName, entityOpts] of Object.entries(entities)) {
680
- config[entityName] = entityOpts.cache ? { cacheSeconds: toSeconds(entityOpts.cache) } : {};
681
- }
682
- return createBucketClientWithEntities(name, config);
683
- }
684
- return createBucketClient(name);
685
- },
686
- mailer: () => createEmailClient(),
687
- queue: (name) => createQueueClient(name),
688
- worker: (name) => createWorkerClient(name)
689
- };
690
- var parseDepValue = (raw) => {
691
- const idx = raw.indexOf(":");
692
- return { type: raw.slice(0, idx), name: raw.slice(idx + 1) };
693
- };
694
- var resolveDepsInput = (deps) => typeof deps === "function" ? deps() : deps;
695
- var buildDeps = (rawDeps) => {
696
- const deps = resolveDepsInput(rawDeps);
697
- if (!deps) return void 0;
698
- const result = {};
699
- for (const key of Object.keys(deps)) {
700
- const raw = process.env[`${ENV_DEP_PREFIX}${key}`];
701
- if (!raw) throw new Error(`Missing environment variable ${ENV_DEP_PREFIX}${key} for dep "${key}"`);
702
- const { type, name } = parseDepValue(raw);
703
- const factory = DEP_FACTORIES[type];
704
- if (!factory) throw new Error(`Unknown dep type "${type}" for dep "${key}"`);
705
- result[key] = factory(name, deps[key]);
706
- }
707
- return result;
708
- };
709
- var buildParams = async (params) => {
710
- if (!params) return void 0;
711
- const entries = [];
712
- for (const propName of Object.keys(params)) {
713
- const ssmPath = process.env[`${ENV_PARAM_PREFIX}${propName}`];
714
- if (!ssmPath) {
715
- throw new Error(`Missing environment variable ${ENV_PARAM_PREFIX}${propName} for param "${propName}"`);
716
- }
717
- entries.push({ propName, ssmPath });
718
- }
719
- if (entries.length === 0) return void 0;
720
- const values = await getParameters(entries.map((e) => e.ssmPath));
721
- const result = {};
722
- for (const { propName, ssmPath } of entries) {
723
- const raw = values.get(ssmPath) ?? "";
724
- const ref = params[propName];
725
- const transform = typeof ref === "object" && ref !== null && "transform" in ref && typeof ref.transform === "function" ? ref.transform : void 0;
726
- result[propName] = transform ? transform(raw) : raw;
727
- }
728
- return result;
729
- };
730
- var resolvePath = (filePath) => join(process.cwd(), filePath);
731
- var staticFiles = {
732
- read: (filePath) => readFileSync(resolvePath(filePath), "utf-8"),
733
- readBuffer: (filePath) => readFileSync(resolvePath(filePath)),
734
- path: resolvePath
735
- };
736
- var createHandlerRuntime = (handler, handlerType, logLevel = "info", extraSetupArgs) => {
737
- const handlerName = process.env.EFF_HANDLER ?? "unknown";
738
- const rank = LOG_RANK[logLevel];
739
- let ctx = null;
740
- let resolvedDeps;
741
- let resolvedParams = null;
742
- let resolvedAuthRuntime = null;
743
- const getDeps = () => resolvedDeps ??= buildDeps(handler.deps);
744
- const getParams = async () => {
745
- if (resolvedParams !== null) return resolvedParams;
746
- resolvedParams = await buildParams(handler.config);
747
- return resolvedParams;
748
- };
749
- let resolvedCfSigningConfig = null;
750
- const getCfSigningConfig = async () => {
751
- if (resolvedCfSigningConfig !== null) return resolvedCfSigningConfig;
752
- const cfSigningKeySsmPath = process.env.EFF_CF_SIGNING_KEY;
753
- const cfKeyPairId = process.env.EFF_CF_KEY_PAIR_ID;
754
- const cfDomain = process.env.EFF_CF_DOMAIN;
755
- if (!cfSigningKeySsmPath || !cfKeyPairId || !cfDomain) {
756
- resolvedCfSigningConfig = void 0;
757
- return void 0;
758
- }
759
- const values = await getParameters([cfSigningKeySsmPath]);
760
- const privateKey = values.get(cfSigningKeySsmPath);
761
- if (!privateKey) {
762
- resolvedCfSigningConfig = void 0;
763
- return void 0;
764
- }
765
- resolvedCfSigningConfig = { privateKey, keyPairId: cfKeyPairId, domain: cfDomain };
766
- return resolvedCfSigningConfig;
767
- };
768
- const getAuthRuntime = async () => {
769
- if (resolvedAuthRuntime !== null) return resolvedAuthRuntime;
770
- if (!handler.authFn) {
771
- resolvedAuthRuntime = void 0;
772
- return void 0;
773
- }
774
- const params = await getParams();
775
- const deps = getDeps();
776
- const authArgs = {};
777
- if (params) authArgs.config = params;
778
- if (deps) authArgs.deps = deps;
779
- const authOpts = await handler.authFn(authArgs);
780
- if (!authOpts?.secret) {
781
- resolvedAuthRuntime = void 0;
782
- return void 0;
783
- }
784
- const secret = authOpts.secret;
785
- resolvedAuthHeaderName = authOpts.apiToken?.header;
786
- const defaultExpires = authOpts.expiresIn ? toSeconds(authOpts.expiresIn) : 604800;
787
- const apiToken = authOpts.apiToken;
788
- const cacheTtlSeconds = apiToken?.cacheTtl ? toSeconds(apiToken.cacheTtl) : void 0;
789
- const rawVerify = apiToken?.verify;
790
- const wrappedVerify = rawVerify ? (args) => rawVerify(args.value) : void 0;
791
- const cfSigningConfig = await getCfSigningConfig();
792
- resolvedAuthRuntime = createAuthRuntime(
793
- secret,
794
- defaultExpires,
795
- wrappedVerify,
796
- apiToken?.header,
797
- cacheTtlSeconds,
798
- cfSigningConfig
799
- );
800
- return resolvedAuthRuntime;
801
- };
802
- const getSetup = async () => {
803
- if (ctx !== null) return ctx;
804
- if (handler.setup) {
805
- const params = await getParams();
806
- const deps = getDeps();
807
- const args = {};
808
- if (params) args.config = params;
809
- if (deps) args.deps = deps;
810
- if (handler.static) args.files = staticFiles;
811
- if (extraSetupArgs) Object.assign(args, extraSetupArgs());
812
- ctx = await handler.setup(args);
813
- }
814
- return ctx;
815
- };
816
- let resolvedAuthHeaderName;
817
- const commonArgs = async (cookieValue, authHeader, headers) => {
818
- const args = {};
819
- if (handler.setup) args.ctx = await getSetup();
820
- const deps = getDeps();
821
- if (deps) args.deps = deps;
822
- const params = await getParams();
823
- if (params) args.config = params;
824
- if (handler.static) args.files = staticFiles;
825
- const authRuntime = await getAuthRuntime();
826
- if (authRuntime) {
827
- let finalAuthHeader = authHeader;
828
- if (finalAuthHeader === void 0 && headers && resolvedAuthHeaderName) {
829
- finalAuthHeader = headers[resolvedAuthHeaderName] ?? headers[resolvedAuthHeaderName.toLowerCase()] ?? void 0;
830
- }
831
- args.auth = await authRuntime.forRequest(cookieValue, finalAuthHeader);
832
- }
833
- return args;
834
- };
835
- const logExecution = (startTime, input, output) => {
836
- if (rank < LOG_RANK.info) return;
837
- const entry = {
838
- level: "info",
839
- handler: handlerName,
840
- type: handlerType,
841
- ms: Date.now() - startTime
842
- };
843
- if (rank >= LOG_RANK.debug) {
844
- entry.input = truncate(input);
845
- entry.output = truncate(output);
846
- }
847
- console.log(JSON.stringify(entry));
848
- };
849
- const logError = (startTime, input, error) => {
850
- const entry = {
851
- level: "error",
852
- handler: handlerName,
853
- type: handlerType,
854
- ms: Date.now() - startTime,
855
- error: error instanceof Error ? error.message : String(error)
856
- };
857
- if (rank >= LOG_RANK.debug) {
858
- entry.input = truncate(input);
859
- }
860
- console.error(JSON.stringify(entry));
861
- };
862
- const noop = () => {
863
- };
864
- const saved = { log: console.log, info: console.info, debug: console.debug };
865
- const patchConsole = () => {
866
- if (rank < LOG_RANK.debug) console.debug = noop;
867
- if (rank < LOG_RANK.info) {
868
- console.log = noop;
869
- console.info = noop;
870
- }
871
- };
872
- const restoreConsole = () => {
873
- console.log = saved.log;
874
- console.info = saved.info;
875
- console.debug = saved.debug;
876
- };
877
- return { commonArgs, logExecution, logError, patchConsole, restoreConsole, handlerName };
878
- };
879
-
880
- export {
881
- createTableClient,
882
- toSeconds,
883
- AUTH_COOKIE_NAME,
884
- createBucketClient,
885
- createBucketClientWithEntities,
886
- buildDeps,
887
- buildParams,
888
- createHandlerRuntime
889
- };