wirejs-deploy-amplify-basic 0.0.121 → 0.0.122-async-api

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,3 +1,4 @@
1
+ import { randomUUID } from 'crypto';
1
2
  import {
2
3
  defineBackend,
3
4
  } from '@aws-amplify/backend';
@@ -6,6 +7,7 @@ import { FunctionUrlAuthType } from 'aws-cdk-lib/aws-lambda';
6
7
  import { Bucket, BlockPublicAccess } from 'aws-cdk-lib/aws-s3';
7
8
  import { Table, AttributeType, BillingMode } from 'aws-cdk-lib/aws-dynamodb';
8
9
  import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
10
+
9
11
  import { TableDefinition, Resource, indexName } from 'wirejs-resources';
10
12
  import { TableIndexes } from './constructs/table-indexes';
11
13
  import { RealtimeService } from './constructs/realtime-service';
@@ -14,12 +16,19 @@ import { auth } from './auth/resource';
14
16
 
15
17
  // @ts-ignore
16
18
  import generatedResources from './generated-resources';
19
+
17
20
  const generated: any[] = generatedResources;
18
21
 
19
22
  const APP_ID = process.env.AWS_APP_ID ?? process.env.PWD?.replace(/[^a-zA-Z0-9-_]/g, '_');
20
23
  const BRANCH_ID = process.env.AWS_BRANCH ?? process.env.USER ?? 'anonymous';
21
24
  const TABLE_NAME_PREFIX = `${APP_ID}-${BRANCH_ID}-`;
22
25
 
26
+ /**
27
+ * Simple secret ID used for backend jobs to authenticate themselves with
28
+ * each other. I.e., for API to trigger background jobs.
29
+ */
30
+ const SELF_INVOCATION_ID = randomUUID();
31
+
23
32
  /**
24
33
  * Amplify resources
25
34
  */
@@ -177,6 +186,9 @@ backend.api.addEnvironment(
177
186
  backend.api.addEnvironment(
178
187
  'TABLE_NAME_PREFIX', TABLE_NAME_PREFIX
179
188
  );
189
+ backend.api.addEnvironment(
190
+ 'SELF_INVOCATION_ID', SELF_INVOCATION_ID
191
+ );
180
192
 
181
193
 
182
194
  /**
@@ -1,6 +1,11 @@
1
1
  import { env } from 'process';
2
2
  import { LambdaFunctionURLHandler, APIGatewayProxyEventV2 } from 'aws-lambda';
3
- import { CookieJar, Context, requiresContext } from 'wirejs-resources';
3
+ import {
4
+ BackgroundJob,
5
+ CookieJar,
6
+ Context,
7
+ requiresContext,
8
+ } from 'wirejs-resources';
4
9
 
5
10
  // @ts-ignore
6
11
  import * as api from '../../../api/index';
@@ -44,33 +49,75 @@ async function callApiMethod(api: any, call: any, context: any) {
44
49
  }
45
50
  }
46
51
 
52
+ function isBackgroundJob(o: any): o is BackgroundJob<any> {
53
+ return typeof o === 'object' && typeof o.start === 'function';
54
+ }
55
+
47
56
  export const handler: LambdaFunctionURLHandler = async (event, context) => {
48
57
  const calls = JSON.parse(event.body!);
49
- const responses = [];
50
- const wjsContext = createContext(event);
51
58
 
52
- for (const call of calls) {
53
- console.log('handling API call', call);
54
- responses.push(await callApiMethod(api, call, wjsContext));
55
- }
59
+ if (Array.isArray(calls)) {
60
+ const responses = [];
61
+ const wjsContext = createContext(event);
62
+ for (const call of calls) {
63
+ console.log('handling API call', call);
64
+ responses.push(await callApiMethod(api, call, wjsContext));
65
+ }
66
+
67
+ console.log('setting cookies', wjsContext.cookies.getSetCookies());
68
+
69
+ const cookies: string[] = [];
70
+ for (const cookie of wjsContext.cookies.getSetCookies()) {
71
+ const cookieOptions = [];
72
+ if (cookie.maxAge) cookieOptions.push(`Max-Age=${cookie.maxAge}`);
73
+ if (cookie.httpOnly) cookieOptions.push('HttpOnly');
74
+ if (cookie.secure) cookieOptions.push('Secure');
75
+ cookies.push(`${cookie.name}=${cookie.value}; ${cookieOptions.join('; ')}`);
76
+ }
56
77
 
57
- console.log('setting cookies', wjsContext.cookies.getSetCookies());
78
+ return {
79
+ statusCode: 200,
80
+ cookies,
81
+ headers: {
82
+ 'Content-Type': 'application/json; charset=utf-8'
83
+ },
84
+ body: JSON.stringify(responses)
85
+ }
86
+ } else if (typeof calls === 'object' && calls.async) {
87
+ console.log('handling async execution', calls);
58
88
 
59
- const cookies: string[] = [];
60
- for (const cookie of wjsContext.cookies.getSetCookies()) {
61
- const cookieOptions = [];
62
- if (cookie.maxAge) cookieOptions.push(`Max-Age=${cookie.maxAge}`);
63
- if (cookie.httpOnly) cookieOptions.push('HttpOnly');
64
- if (cookie.secure) cookieOptions.push('Secure');
65
- cookies.push(`${cookie.name}=${cookie.value}; ${cookieOptions.join('; ')}`);
66
- }
89
+ if (!calls.absoluteId) {
90
+ return {
91
+ statusCode: 400,
92
+ body: JSON.stringify({ error: 'Missing absoluteId' })
93
+ };
94
+ }
95
+
96
+ if (calls.selfInvocationId !== env.SELF_INVOCATION_ID) {
97
+ return {
98
+ statusCode: 403,
99
+ body: JSON.stringify({ error: 'Forbidden' })
100
+ };
101
+ }
102
+
103
+ const job = (BackgroundJob as any).registeredJobs[calls.absoluteId];
104
+ if (!job || !isBackgroundJob(job)) {
105
+ return {
106
+ statusCode: 404,
107
+ body: JSON.stringify({ error: 'Background job not found' })
108
+ };
109
+ }
110
+
111
+ await job.start(...calls.args)
67
112
 
68
- return {
69
- statusCode: 200,
70
- cookies,
71
- headers: {
72
- 'Content-Type': 'application/json; charset=utf-8'
73
- },
74
- body: JSON.stringify(responses)
113
+ return {
114
+ statusCode: 200,
115
+ body: JSON.stringify({ message: 'Background job complete' })
116
+ };
117
+ } else {
118
+ return {
119
+ statusCode: 400,
120
+ body: JSON.stringify({ error: 'Invalid request format' })
121
+ };
75
122
  }
76
123
  }
@@ -3,6 +3,6 @@
3
3
  "dependencies": {
4
4
  "jsdom": "^25.0.1",
5
5
  "wirejs-dom": "^1.0.41",
6
- "wirejs-resources": "^0.1.89"
6
+ "wirejs-resources": "^0.1.90-async-api"
7
7
  }
8
8
  }
@@ -0,0 +1,36 @@
1
+ import { Resource, BackgroundJob as BaseBackgroundJob } from 'wirejs-resources';
2
+ export type BackgroundJobDefinition = {
3
+ absoluteId: string;
4
+ timeoutSeconds: number;
5
+ };
6
+ /**
7
+ * A function that will be executed in the background.
8
+ *
9
+ * Intended for longer-running work that will continue after an initial API response
10
+ * to the client.
11
+ *
12
+ * ## WARNINGS
13
+ *
14
+ * 1. It may execute in a completely different environment that the one that invoked it.
15
+ * DO NOT depend on global variables, closures, or other process state that is not lazily
16
+ * created or constructed in-place by the script.
17
+ * 2. Fire-and-forget behavior is undefined. As soon as the `Promise` returned by your
18
+ * function resolves, the execution environment is free to terminate the process.
19
+ * 3. In local development, **timeouts are not enforced.** (Yet.)
20
+ */
21
+ export declare class BackgroundJob<T extends (...args: any) => Promise<void>> extends Resource implements Omit<BaseBackgroundJob<T>, '#private'> {
22
+ #private;
23
+ static registeredJobs: Record<string, BackgroundJob<any>>;
24
+ constructor(scope: Resource | string, id: string, options: {
25
+ handler: T;
26
+ timeoutSeconds?: number;
27
+ });
28
+ /**
29
+ * Serializes parameter data and schedules background execution.
30
+ *
31
+ * **NOTE:** This resolves once the job is scheduled; *not when it completes!*
32
+ *
33
+ * @param data
34
+ */
35
+ start(...args: Parameters<T>): Promise<void>;
36
+ }
@@ -0,0 +1,59 @@
1
+ import { env } from 'process';
2
+ import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
3
+ import { Resource, } from 'wirejs-resources';
4
+ import { addResource } from '../resource-collector.js';
5
+ /**
6
+ * A function that will be executed in the background.
7
+ *
8
+ * Intended for longer-running work that will continue after an initial API response
9
+ * to the client.
10
+ *
11
+ * ## WARNINGS
12
+ *
13
+ * 1. It may execute in a completely different environment that the one that invoked it.
14
+ * DO NOT depend on global variables, closures, or other process state that is not lazily
15
+ * created or constructed in-place by the script.
16
+ * 2. Fire-and-forget behavior is undefined. As soon as the `Promise` returned by your
17
+ * function resolves, the execution environment is free to terminate the process.
18
+ * 3. In local development, **timeouts are not enforced.** (Yet.)
19
+ */
20
+ export class BackgroundJob extends Resource {
21
+ #handler;
22
+ #timeoutSeconds;
23
+ static registeredJobs = {};
24
+ constructor(scope, id, options) {
25
+ super(scope, id);
26
+ this.#handler = options.handler;
27
+ if (options.timeoutSeconds && (options.timeoutSeconds < 1 || options.timeoutSeconds > 900)) {
28
+ throw new Error('timeoutSeconds must be between 1 and 900 seconds.');
29
+ }
30
+ this.#timeoutSeconds = options.timeoutSeconds ?? 900;
31
+ addResource('BackgroundJob', {
32
+ absoluteId: this.absoluteId,
33
+ timeoutSeconds: this.#timeoutSeconds,
34
+ });
35
+ BackgroundJob.registeredJobs[this.absoluteId] = this;
36
+ }
37
+ /**
38
+ * Serializes parameter data and schedules background execution.
39
+ *
40
+ * **NOTE:** This resolves once the job is scheduled; *not when it completes!*
41
+ *
42
+ * @param data
43
+ */
44
+ async start(...args) {
45
+ const client = new LambdaClient();
46
+ const command = new InvokeCommand({
47
+ FunctionName: env.AWS_LAMBDA_FUNCTION_NAME,
48
+ InvocationType: 'Event',
49
+ Payload: JSON.stringify({
50
+ body: {
51
+ absoluteId: this.absoluteId,
52
+ selfInvocationId: env.SELF_INVOCATION_ID,
53
+ args,
54
+ },
55
+ }),
56
+ });
57
+ await client.send(command);
58
+ }
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wirejs-deploy-amplify-basic",
3
- "version": "0.0.121",
3
+ "version": "0.0.122-async-api",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -32,6 +32,7 @@
32
32
  "@aws-sdk/client-dynamodb": "^3.774.0",
33
33
  "@aws-sdk/client-s3": "^3.738.0",
34
34
  "@aws-sdk/credential-provider-node": "^3.806.0",
35
+ "@aws-sdk/client-lambda": "3.821.0",
35
36
  "@aws-sdk/lib-dynamodb": "^3.778.0",
36
37
  "@aws-sdk/protocol-http": "^3.370.0",
37
38
  "@aws-sdk/signature-v4": "^3.370.0",
@@ -41,7 +42,7 @@
41
42
  "recursive-copy": "^2.0.14",
42
43
  "rimraf": "^6.0.1",
43
44
  "wirejs-dom": "^1.0.41",
44
- "wirejs-resources": "^0.1.89"
45
+ "wirejs-resources": "^0.1.90-async-api"
45
46
  },
46
47
  "devDependencies": {
47
48
  "@aws-amplify/backend": "^1.14.0",