@learncard/learn-cloud-service 2.5.35 → 2.5.36

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.
Files changed (43) hide show
  1. package/.env.example +24 -25
  2. package/CHANGELOG.md +24 -0
  3. package/bootstrapLambda.test.ts +68 -0
  4. package/config/config.dev.json +8 -0
  5. package/config/config.example.json +8 -0
  6. package/config/config.json +1 -0
  7. package/config/config.production.json +8 -0
  8. package/config/config.scouts.dev.json +3 -0
  9. package/config/config.scouts.production.json +4 -0
  10. package/didWebLambda.test.ts +41 -0
  11. package/didWebLambda.ts +13 -19
  12. package/didWebLambdaApp.ts +23 -0
  13. package/dist/config/applyDockerStageConfig.d.ts +2 -0
  14. package/dist/config/applyDockerStageConfig.d.ts.map +1 -0
  15. package/dist/config/serverlessFunctionEnv.test.d.ts +2 -0
  16. package/dist/config/serverlessFunctionEnv.test.d.ts.map +1 -0
  17. package/dist/config/stageConfig.d.ts +10 -0
  18. package/dist/config/stageConfig.d.ts.map +1 -0
  19. package/dist/config/stageConfig.test.d.ts +2 -0
  20. package/dist/config/stageConfig.test.d.ts.map +1 -0
  21. package/dist/didkit_wasm_bg.wasm +0 -0
  22. package/dist/docker-entry.d.ts +1 -1
  23. package/dist/docker-entry.d.ts.map +1 -1
  24. package/dist/index.js +2 -2
  25. package/esbuildPlugins.cjs +1 -0
  26. package/lambda.ts +22 -153
  27. package/lambdaApp.ts +163 -0
  28. package/oidcLambda.test.ts +41 -0
  29. package/oidcLambda.ts +13 -19
  30. package/oidcLambdaApp.ts +23 -0
  31. package/package.json +18 -16
  32. package/serverless-local.yml +3 -1
  33. package/serverless.function-env.cjs +31 -0
  34. package/serverless.yml +13 -29
  35. package/src/config/applyDockerStageConfig.ts +6 -0
  36. package/src/config/serverlessFunctionEnv.test.ts +81 -0
  37. package/src/config/stageConfig.test.ts +26 -0
  38. package/src/config/stageConfig.ts +24 -0
  39. package/src/docker-entry.ts +2 -0
  40. package/vitest.config.ts +3 -0
  41. package/xApiLambda.test.ts +41 -0
  42. package/xApiLambda.ts +13 -19
  43. package/xApiLambdaApp.ts +23 -0
package/.env.example CHANGED
@@ -1,36 +1,35 @@
1
- # LearnCloud service environment
2
- # Copy to .env. Validation runs before database/client initialization.
1
+ # LearnCloud: copy to .env. Values below are disposable local examples, not deploy secrets.
2
+ # Public configuration lives in config/config.json and config/config.<stage>.json.
3
+ # Precedence: schema defaults < base < stage < Lambda runtime bundle < non-empty real env.
4
+ # CONFIG_STAGE=dev or production opts local/Docker/offline into checked-in stage settings.
5
+ # Without CONFIG_STAGE, keep using local env and schema defaults as before.
6
+ # Lambda selects LAMBDA_STAGE; the bundle is learn-cloud-service/<stage>/runtime-secrets.
7
+ # Leave RUNTIME_SECRETS_ID unset locally. Keep GitHub fallbacks for rollback.
8
+ CONFIG_STAGE=
9
+ RUNTIME_SECRETS_ID=
3
10
 
4
- NODE_ENV=development
5
- PORT=3000
11
+ # Required credentials: replace every example before connecting to shared services.
6
12
  LEARN_CLOUD_SEED=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
7
13
  LEARN_CLOUD_MONGO_URI=mongodb://localhost:27017
8
14
  LEARN_CLOUD_MONGO_DB_NAME=learncloud
9
- DOMAIN_NAME=
10
- SERVER_URL=http://localhost:4100
11
- XAPI_ENDPOINT=
12
15
 
16
+ # Optional runtime credentials, including the private xAPI endpoint.
17
+ XAPI_ENDPOINT=
18
+ XAPI_USERNAME=
19
+ XAPI_PASSWORD=
20
+ JWT_SIGNING_KEY=
21
+ SENTRY_DSN=
13
22
  # Configure both RSA values or leave both empty to generate an ephemeral pair.
14
23
  RSA_PRIVATE_KEY=
15
24
  RSA_PUBLIC_KEY=
16
25
 
26
+ # Local infrastructure. Explicit env wins over a selected stage.
27
+ NODE_ENV=development
28
+ IS_OFFLINE=true
29
+ PORT=3000
17
30
  REDIS_HOST=
18
31
  REDIS_PORT=
19
- SENTRY_DSN=
20
- SENTRY_ENV=development
21
- IS_OFFLINE=false
22
- SKIP_DIDKIT_NAPI=false
23
- CI=false
24
-
25
- # LC-2187 service-only share-content routes start with valid service configuration.
26
- # All four values are required and REDIS_HOST/REDIS_PORT must point at the real
27
- # replay store. Real values are provisioned per environment, never committed.
28
- # LearnCloud's own service DID that service tokens must address.
29
- SHARE_CONTENT_AUDIENCE=
30
- # Comma-separated allowlisted Brain service DIDs.
31
- SHARE_CONTENT_SERVICE_DIDS=
32
- # Comma-separated exact JWS `kid` verification methods (DID URL with fragment).
33
- SHARE_CONTENT_VERIFICATION_METHODS=
34
- # JSON object mapping each service DID to the namespaces it may use, e.g.
35
- # {"did:web:brain.example":["learncard"]}
36
- SHARE_CONTENT_NAMESPACE_BINDINGS=
32
+ # Local URL override, if needed: SERVER_URL=http://localhost:4100.
33
+ # SENTRY_ENV and SHARE_CONTENT_* trust settings come from the selected stage.
34
+ # Self-hosters can register config/config.example.json as their own stage or use explicit env.
35
+ # SENTRY_AUTH_TOKEN / SENTRY_ORG / SENTRY_PROJECT belong only in the build environment.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # @learncard/network-brain-service
2
2
 
3
+ ## 2.5.36
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1670](https://github.com/learningeconomy/LearnCard/pull/1670) [`7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2`](https://github.com/learningeconomy/LearnCard/commit/7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2) Thanks [@Custard7](https://github.com/Custard7)! - Adopt the shared service-config model: load checked-in non-secret per-stage configuration before environment validation, bootstrap every Lambda entrypoint through the shared lazy loader, and deliver runtime secrets through an optional AWS Secrets Manager bundle with a per-value fallback for stages without one.
8
+
9
+ - [#1670](https://github.com/learningeconomy/LearnCard/pull/1670) [`7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2`](https://github.com/learningeconomy/LearnCard/commit/7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2) Thanks [@Custard7](https://github.com/Custard7)! - Select backend stage files by tenant as well as stage. `CONFIG_TENANT=scouts` loads `config.scouts.<stage>.json` (generated from the live ScoutPass Lambdas), so ScoutPass deployments no longer inherit LearnCard domains, trust settings or seed-encryption flags; a named tenant without a stage file fails closed. `ESCROW_ENCLAVE_MODE` is now forwarded in runtime-secrets bundle mode too.
10
+
11
+ - Updated dependencies [[`6209da80c52ac056a7644dab14669192740c9dad`](https://github.com/learningeconomy/LearnCard/commit/6209da80c52ac056a7644dab14669192740c9dad), [`5890451789b18afee45dc5ecbab3fa30aa90f085`](https://github.com/learningeconomy/LearnCard/commit/5890451789b18afee45dc5ecbab3fa30aa90f085), [`1387cd0a08fa6b0a706c86a5a0376b6c7d706856`](https://github.com/learningeconomy/LearnCard/commit/1387cd0a08fa6b0a706c86a5a0376b6c7d706856), [`d7ca528898d988c65f0dc7084cfdcd41767648c5`](https://github.com/learningeconomy/LearnCard/commit/d7ca528898d988c65f0dc7084cfdcd41767648c5), [`8f3bc66ab3d3e5e1edb3fbf5f362025875a914ba`](https://github.com/learningeconomy/LearnCard/commit/8f3bc66ab3d3e5e1edb3fbf5f362025875a914ba), [`1387cd0a08fa6b0a706c86a5a0376b6c7d706856`](https://github.com/learningeconomy/LearnCard/commit/1387cd0a08fa6b0a706c86a5a0376b6c7d706856), [`267cf40291b3a63caf178857be0e57feaf5e0948`](https://github.com/learningeconomy/LearnCard/commit/267cf40291b3a63caf178857be0e57feaf5e0948), [`7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2`](https://github.com/learningeconomy/LearnCard/commit/7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2), [`c78c982f9f4911f1ca2973380f19d80db775e012`](https://github.com/learningeconomy/LearnCard/commit/c78c982f9f4911f1ca2973380f19d80db775e012), [`590adf48867d6c6fd504e66a11c9827d5102122d`](https://github.com/learningeconomy/LearnCard/commit/590adf48867d6c6fd504e66a11c9827d5102122d)]:
12
+ - @learncard/types@5.23.0
13
+ - @learncard/service-config@0.1.1
14
+ - @learncard/didkit-plugin@1.10.3
15
+ - @learncard/core@9.4.38
16
+ - @learncard/helpers@1.6.2
17
+ - @learncard/did-web-plugin@1.1.38
18
+ - @learncard/didkey-plugin@1.1.38
19
+ - @learncard/didkit-plugin-node@0.3.3
20
+ - @learncard/encryption-plugin@1.1.38
21
+ - @learncard/learn-card-plugin@1.2.38
22
+ - @learncard/vc-plugin@1.6.4
23
+ - @learncard/vc-templates-plugin@1.1.38
24
+ - @learncard/crypto-plugin@1.1.38
25
+ - @learncard/expiration-plugin@1.2.38
26
+
3
27
  ## 2.5.35
4
28
 
5
29
  ### Patch Changes
@@ -0,0 +1,68 @@
1
+ import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';
2
+ import { beforeEach, expect, it, vi } from 'vitest';
3
+
4
+ const mocks = vi.hoisted(() => ({ bootstrap: vi.fn(), getApp: vi.fn(), handler: vi.fn() }));
5
+
6
+ const HANDLERS = ['trpcHandler', 'openApiHandler', 'shareContentHandler', 'swaggerUiHandler'];
7
+
8
+ vi.mock('@learncard/service-config', () => ({ bootstrapLambda: mocks.bootstrap }));
9
+ vi.mock('./src/config/stageConfig', () => ({ base: {}, stages: { dev: {}, production: {} } }));
10
+ vi.mock('./lambdaApp', () => Object.fromEntries(HANDLERS.map(name => [name, mocks.handler])));
11
+
12
+ beforeEach(() => {
13
+ vi.resetModules();
14
+ vi.clearAllMocks();
15
+ mocks.bootstrap.mockReturnValue(mocks.getApp);
16
+ mocks.handler.mockResolvedValue({ statusCode: 200 });
17
+ });
18
+
19
+ it('bootstraps the API once with checked-in stage config', async () => {
20
+ vi.stubEnv('AWS_LAMBDA_FUNCTION_NAME', 'learn-cloud-service-dev-trpc');
21
+ vi.stubEnv('LAMBDA_STAGE', 'dev');
22
+ await import('./lambda');
23
+ expect(mocks.bootstrap).toHaveBeenCalledTimes(1);
24
+ expect(mocks.bootstrap).toHaveBeenCalledWith({
25
+ base: {},
26
+ stages: { dev: {}, production: {} },
27
+ stage: 'dev',
28
+ importApp: expect.any(Function),
29
+ });
30
+ vi.unstubAllEnvs();
31
+ });
32
+
33
+ it('uses CONFIG_STAGE outside Lambda rather than LAMBDA_STAGE', async () => {
34
+ vi.stubEnv('AWS_LAMBDA_FUNCTION_NAME', '');
35
+ vi.stubEnv('LAMBDA_STAGE', 'production');
36
+ vi.stubEnv('CONFIG_STAGE', 'dev');
37
+ await import('./lambda');
38
+ expect(mocks.bootstrap).toHaveBeenCalledWith(expect.objectContaining({ stage: 'dev' }));
39
+ vi.unstubAllEnvs();
40
+ });
41
+
42
+ it('forces no deployed stage when serverless-offline sets a function name with an empty LAMBDA_STAGE', async () => {
43
+ vi.stubEnv('AWS_LAMBDA_FUNCTION_NAME', 'learn-cloud-service-dev-trpc');
44
+ vi.stubEnv('LAMBDA_STAGE', '');
45
+ vi.stubEnv('CONFIG_STAGE', '');
46
+ await import('./lambda');
47
+ expect(mocks.bootstrap).toHaveBeenCalledWith(expect.objectContaining({ stage: '' }));
48
+ vi.unstubAllEnvs();
49
+ });
50
+
51
+ it('forwards every handler through its bootstrap accessor', async () => {
52
+ mocks.getApp.mockResolvedValue(Object.fromEntries(HANDLERS.map(name => [name, mocks.handler])));
53
+ const lambda = await import('./lambda');
54
+ const event = {} as APIGatewayProxyEventV2;
55
+ const context = {} as Context;
56
+ const callback = vi.fn();
57
+ const handlers = [
58
+ lambda.trpcHandler,
59
+ lambda.openApiHandler,
60
+ lambda.shareContentHandler,
61
+ lambda.swaggerUiHandler,
62
+ ];
63
+ for (const handler of handlers) await handler(event, context, callback);
64
+ expect(mocks.bootstrap).toHaveBeenCalledTimes(1);
65
+ expect(mocks.getApp).toHaveBeenCalledTimes(handlers.length);
66
+ expect(mocks.handler).toHaveBeenCalledTimes(handlers.length);
67
+ expect(mocks.handler).toHaveBeenCalledWith(event, context, callback);
68
+ });
@@ -0,0 +1,8 @@
1
+ {
2
+ "SENTRY_ENV": "learn-cloud-staging",
3
+ "SERVER_URL": "https://staging.cloud.learncard.com",
4
+ "SHARE_CONTENT_AUDIENCE": "did:web:staging.cloud.learncard.com",
5
+ "SHARE_CONTENT_NAMESPACE_BINDINGS": "{\"did:web:staging.network.learncard.com\":[\"learncard\"]}",
6
+ "SHARE_CONTENT_SERVICE_DIDS": "did:web:staging.network.learncard.com",
7
+ "SHARE_CONTENT_VERIFICATION_METHODS": "did:web:staging.network.learncard.com#owner"
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "SENTRY_ENV": "learn-cloud-example",
3
+ "SERVER_URL": "https://cloud.example.com",
4
+ "SHARE_CONTENT_AUDIENCE": "did:web:cloud.example.com",
5
+ "SHARE_CONTENT_NAMESPACE_BINDINGS": "{\"did:web:network.example.com\":[\"learncard\"]}",
6
+ "SHARE_CONTENT_SERVICE_DIDS": "did:web:network.example.com",
7
+ "SHARE_CONTENT_VERIFICATION_METHODS": "did:web:network.example.com#owner"
8
+ }
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,8 @@
1
+ {
2
+ "SENTRY_ENV": "learn-cloud-production",
3
+ "SERVER_URL": "https://cloud.learncard.com",
4
+ "SHARE_CONTENT_AUDIENCE": "did:web:cloud.learncard.com",
5
+ "SHARE_CONTENT_NAMESPACE_BINDINGS": "{\"did:web:network.learncard.com\":[\"learncard\"]}",
6
+ "SHARE_CONTENT_SERVICE_DIDS": "did:web:network.learncard.com",
7
+ "SHARE_CONTENT_VERIFICATION_METHODS": "did:web:network.learncard.com#owner"
8
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "SENTRY_ENV": "scouts-learn-cloud-staging"
3
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "SENTRY_ENV": "scouts-learn-cloud-production",
3
+ "SERVER_URL": "https://cloud.learncard.com"
4
+ }
@@ -0,0 +1,41 @@
1
+ import type { APIGatewayProxyEventV2, Context } from 'aws-lambda';
2
+ import { beforeEach, expect, it, vi } from 'vitest';
3
+
4
+ const mocks = vi.hoisted(() => ({ bootstrap: vi.fn(), getApp: vi.fn(), handler: vi.fn() }));
5
+
6
+ vi.mock('@learncard/service-config', () => ({ bootstrapLambda: mocks.bootstrap }));
7
+ vi.mock('./src/config/stageConfig', () => ({ base: {}, stages: { dev: {}, production: {} } }));
8
+ vi.mock('./didWebLambdaApp', () => ({ didWebHandler: mocks.handler }));
9
+
10
+ beforeEach(() => {
11
+ vi.resetModules();
12
+ vi.clearAllMocks();
13
+ mocks.bootstrap.mockReturnValue(mocks.getApp);
14
+ mocks.getApp.mockResolvedValue({ didWebHandler: mocks.handler });
15
+ mocks.handler.mockResolvedValue({ statusCode: 200 });
16
+ });
17
+
18
+ it('bootstraps the didWeb app with checked-in stage config and the deploy stage', async () => {
19
+ vi.stubEnv('AWS_LAMBDA_FUNCTION_NAME', 'learn-cloud-service-production-didWeb');
20
+ vi.stubEnv('LAMBDA_STAGE', 'production');
21
+ await import('./didWebLambda');
22
+ expect(mocks.bootstrap).toHaveBeenCalledTimes(1);
23
+ expect(mocks.bootstrap).toHaveBeenCalledWith({
24
+ base: {},
25
+ stages: { dev: {}, production: {} },
26
+ stage: 'production',
27
+ importApp: expect.any(Function),
28
+ });
29
+ vi.unstubAllEnvs();
30
+ });
31
+
32
+ it('resolves the app once per cold start and forwards every argument', async () => {
33
+ const { didWebHandler } = await import('./didWebLambda');
34
+ const event = {} as APIGatewayProxyEventV2;
35
+ const context = {} as Context;
36
+ const callback = vi.fn();
37
+ await didWebHandler(event, context, callback);
38
+ await didWebHandler(event, context, callback);
39
+ expect(mocks.getApp).toHaveBeenCalledTimes(2);
40
+ expect(mocks.handler).toHaveBeenLastCalledWith(event, context, callback);
41
+ });
package/didWebLambda.ts CHANGED
@@ -1,23 +1,17 @@
1
- import serverlessHttp from 'serverless-http';
2
- import * as Sentry from '@sentry/serverless';
1
+ import { bootstrapLambda } from '@learncard/service-config';
3
2
 
4
- import didWebApp from './src/dids';
5
- import { environment } from './src/config/environment';
6
- import { toServerlessApplication } from './src/helpers/serverlessApplication';
3
+ import { base, stages } from './src/config/stageConfig';
7
4
 
8
- Sentry.AWSLambda.init({
9
- dsn: environment.SENTRY_DSN,
10
- environment: environment.SENTRY_ENV,
11
- enabled: Boolean(environment.SENTRY_DSN),
12
- tracesSampleRate: 1.0,
13
- integrations: [
14
- new Sentry.Integrations.Console(),
15
- new Sentry.Integrations.Http(),
16
- new Sentry.Integrations.ContextLines(),
17
- new Sentry.Integrations.Mongo(),
18
- ],
5
+ type DidWebApp = typeof import('./didWebLambdaApp');
6
+
7
+ const getApplication = bootstrapLambda<DidWebApp>({
8
+ base,
9
+ stages,
10
+ stage: process.env.AWS_LAMBDA_FUNCTION_NAME
11
+ ? process.env.LAMBDA_STAGE
12
+ : process.env.CONFIG_STAGE,
13
+ importApp: () => import('./didWebLambdaApp'),
19
14
  });
20
15
 
21
- export const didWebHandler = Sentry.AWSLambda.wrapHandler(
22
- serverlessHttp(toServerlessApplication(didWebApp))
23
- );
16
+ export const didWebHandler = async (...args: Parameters<DidWebApp['didWebHandler']>) =>
17
+ (await getApplication()).didWebHandler(...args);
@@ -0,0 +1,23 @@
1
+ import serverlessHttp from 'serverless-http';
2
+ import * as Sentry from '@sentry/serverless';
3
+
4
+ import didWebApp from './src/dids';
5
+ import { environment } from './src/config/environment';
6
+ import { toServerlessApplication } from './src/helpers/serverlessApplication';
7
+
8
+ Sentry.AWSLambda.init({
9
+ dsn: environment.SENTRY_DSN,
10
+ environment: environment.SENTRY_ENV,
11
+ enabled: Boolean(environment.SENTRY_DSN),
12
+ tracesSampleRate: 1.0,
13
+ integrations: [
14
+ new Sentry.Integrations.Console(),
15
+ new Sentry.Integrations.Http(),
16
+ new Sentry.Integrations.ContextLines(),
17
+ new Sentry.Integrations.Mongo(),
18
+ ],
19
+ });
20
+
21
+ export const didWebHandler = Sentry.AWSLambda.wrapHandler(
22
+ serverlessHttp(toServerlessApplication(didWebApp))
23
+ );
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=applyDockerStageConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"applyDockerStageConfig.d.ts","sourceRoot":"","sources":["../../src/config/applyDockerStageConfig.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=serverlessFunctionEnv.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serverlessFunctionEnv.test.d.ts","sourceRoot":"","sources":["../../src/config/serverlessFunctionEnv.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import 'dotenv/config';
2
+ export declare const base: Record<string, string>;
3
+ export declare const stages: {
4
+ dev: any;
5
+ production: any;
6
+ 'scouts.dev': any;
7
+ 'scouts.production': any;
8
+ };
9
+ export declare const applyLearnCloudStageConfig: (stage: string | undefined) => void;
10
+ //# sourceMappingURL=stageConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stageConfig.d.ts","sourceRoot":"","sources":["../../src/config/stageConfig.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AASvB,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAc,CAAC;AAEvD,eAAO,MAAM,MAAM;;;;;CAM+B,CAAC;AAInD,eAAO,MAAM,0BAA0B,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,IAEtE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stageConfig.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stageConfig.test.d.ts","sourceRoot":"","sources":["../../src/config/stageConfig.test.ts"],"names":[],"mappings":""}
Binary file
@@ -1,2 +1,2 @@
1
- export {};
1
+ import './config/applyDockerStageConfig';
2
2
  //# sourceMappingURL=docker-entry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"docker-entry.d.ts","sourceRoot":"","sources":["../src/docker-entry.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"docker-entry.d.ts","sourceRoot":"","sources":["../src/docker-entry.ts"],"names":[],"mappings":"AAAA,OAAO,iCAAiC,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/dist/index.js
3
- size: 53395239 bytes
4
- sha256: 8ebe2243c876780364a730330d6e9b013db922136ba371b2bbc02af8d79d579d
3
+ size: 53503108 bytes
4
+ sha256: 03a059be37d9873c94b453813b19e4720e22d5204f11248e83c67578612a4679
@@ -41,6 +41,7 @@ module.exports = [
41
41
  forceCjsSodiumWrapperPlugin,
42
42
  copyDidkitWasmPlugin,
43
43
  sentryEsbuildPlugin({
44
+ telemetry: process.env.SENTRY_BUILD_TELEMETRY !== 'false',
44
45
  authToken: process.env.SENTRY_AUTH_TOKEN,
45
46
  org: process.env.SENTRY_ORG,
46
47
  project: process.env.SENTRY_PROJECT,
package/lambda.ts CHANGED
@@ -1,163 +1,32 @@
1
- import serverlessHttp from 'serverless-http';
2
- import type { Context, APIGatewayProxyResultV2, APIGatewayProxyEventV2 } from 'aws-lambda';
3
- import { awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda';
4
- import * as Sentry from '@sentry/serverless';
5
- import Fastify, { type FastifyInstance } from 'fastify';
1
+ import { bootstrapLambda } from '@learncard/service-config';
6
2
 
7
- import app from './src/openapi';
8
- import { appRouter, createContext } from './src/app';
9
- import { createOpenApiAwsLambdaHandler } from './src/helpers/shim';
10
- import {
11
- handleTrpcError,
12
- sentryBeforeSend,
13
- getTracesSampleRate,
14
- } from './src/helpers/sentry.helpers';
15
- import { environment } from './src/config/environment';
16
- import { toServerlessApplication } from './src/helpers/serverlessApplication';
17
- import {
18
- assertShareContentEnvironmentConfiguration,
19
- createShareContentRuntimeFromEnvironment,
20
- registerShareContentRoutes,
21
- } from './src/share-content';
3
+ import { base, stages } from './src/config/stageConfig';
22
4
 
23
- Sentry.AWSLambda.init({
24
- dsn: environment.SENTRY_DSN,
25
- environment: environment.SENTRY_ENV,
26
- enabled: Boolean(environment.SENTRY_DSN),
27
- tracesSampleRate: getTracesSampleRate(),
28
- beforeSend: sentryBeforeSend,
29
- integrations: [
30
- new Sentry.Integrations.Console(),
31
- new Sentry.Integrations.Http({ tracing: true }),
32
- new Sentry.Integrations.ContextLines(),
33
- new Sentry.Integrations.Mongo(),
34
- ],
35
- });
36
-
37
- // Fail the cold start on an enabled-but-invalid LC-2187 share-content
38
- // configuration; a disabled configuration is a no-op.
39
- assertShareContentEnvironmentConfiguration();
40
-
41
- let shareContentProxyPromise: Promise<ReturnType<typeof serverlessHttp> | null> | null = null;
42
-
43
- /**
44
- * Lazy per-container boot for the LC-2187 service routes. Resolving the runtime
45
- * awaits repository index initialization and opens Redis only when enabled, so a
46
- * disabled deployment installs nothing and returns 404 for every internal path.
47
- */
48
- const getShareContentProxy = async (): Promise<ReturnType<typeof serverlessHttp> | null> => {
49
- if (!shareContentProxyPromise) {
50
- shareContentProxyPromise = (async () => {
51
- const runtime = await createShareContentRuntimeFromEnvironment();
52
-
53
- if (!runtime.enabled) return null;
54
-
55
- const shareContentApp: FastifyInstance = Fastify({ maxParamLength: 5000 });
56
- await registerShareContentRoutes(shareContentApp, runtime);
57
- await shareContentApp.ready();
58
-
59
- return serverlessHttp(toServerlessApplication(shareContentApp.server));
60
- })();
61
- }
62
-
63
- return shareContentProxyPromise;
64
- };
65
-
66
- export const shareContentHandler = async (
67
- event: APIGatewayProxyEventV2,
68
- context: Context
69
- ): Promise<APIGatewayProxyResultV2> => {
70
- let proxy: ReturnType<typeof serverlessHttp> | null;
71
-
72
- try {
73
- proxy = await getShareContentProxy();
74
- } catch {
75
- // Never return a raw boot error. Allow a later invocation to retry.
76
- shareContentProxyPromise = null;
77
-
78
- return {
79
- statusCode: 503,
80
- headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' },
81
- body: JSON.stringify({ ok: false, error: 'UNAVAILABLE' }),
82
- };
83
- }
84
-
85
- if (!proxy) {
86
- return { statusCode: 404, headers: { 'Cache-Control': 'no-store' }, body: '' };
87
- }
88
-
89
- return proxy(event, context);
90
- };
91
-
92
- export const swaggerUiHandler = serverlessHttp(toServerlessApplication(app), {
93
- basePath: '/docs',
94
- });
95
-
96
- export const _openApiHandler = createOpenApiAwsLambdaHandler({
97
- router: appRouter,
98
- responseMeta: () => {
99
- return {
100
- headers: {
101
- 'Access-Control-Allow-Origin': '*',
102
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
103
- 'Access-Control-Allow-Headers': 'Authorization, Content-Type',
104
- },
105
- };
106
- },
107
- createContext,
108
- onError: handleTrpcError,
109
- });
5
+ type LambdaApp = typeof import('./lambdaApp');
110
6
 
111
- export const _trpcHandler = awsLambdaRequestHandler({
112
- allowMethodOverride: true,
113
- router: appRouter,
114
- createContext,
115
- onError: handleTrpcError,
116
- responseMeta: () => {
117
- return {
118
- headers: {
119
- 'Access-Control-Allow-Origin': '*',
120
- 'Access-Control-Allow-Methods': '*',
121
- 'Access-Control-Allow-Headers': 'authorization',
122
- },
123
- };
124
- },
7
+ const getApplication = bootstrapLambda<LambdaApp>({
8
+ base,
9
+ stages,
10
+ stage: process.env.AWS_LAMBDA_FUNCTION_NAME
11
+ ? process.env.LAMBDA_STAGE
12
+ : process.env.CONFIG_STAGE,
13
+ importApp: () => import('./lambdaApp'),
125
14
  });
126
15
 
127
- export const openApiHandler = Sentry.AWSLambda.wrapHandler(
128
- async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
129
- if (event.requestContext.http.method === 'OPTIONS') {
130
- return {
131
- statusCode: 200,
132
- headers: {
133
- 'Access-Control-Allow-Origin': '*',
134
- 'Access-Control-Allow-Headers': '*',
135
- 'Access-Control-Allow-Methods': '*',
136
- },
137
- };
138
- }
16
+ export const trpcHandler = async (...args: Parameters<LambdaApp['trpcHandler']>) =>
17
+ (await getApplication()).trpcHandler(...args);
139
18
 
140
- context.callbackWaitsForEmptyEventLoop = false;
19
+ export const openApiHandler = async (...args: Parameters<LambdaApp['openApiHandler']>) =>
20
+ (await getApplication()).openApiHandler(...args);
141
21
 
142
- return _openApiHandler(event, context);
143
- }
144
- );
22
+ export const _trpcHandler = async (...args: Parameters<LambdaApp['_trpcHandler']>) =>
23
+ (await getApplication())._trpcHandler(...args);
145
24
 
146
- export const trpcHandler = Sentry.AWSLambda.wrapHandler(
147
- async (event: APIGatewayProxyEventV2, context: Context): Promise<APIGatewayProxyResultV2> => {
148
- if (event.requestContext.http.method === 'OPTIONS') {
149
- return {
150
- statusCode: 200,
151
- headers: {
152
- 'Access-Control-Allow-Origin': '*',
153
- 'Access-Control-Allow-Headers': '*',
154
- 'Access-Control-Allow-Methods': '*',
155
- },
156
- };
157
- }
25
+ export const _openApiHandler = async (...args: Parameters<LambdaApp['_openApiHandler']>) =>
26
+ (await getApplication())._openApiHandler(...args);
158
27
 
159
- context.callbackWaitsForEmptyEventLoop = false;
28
+ export const shareContentHandler = async (...args: Parameters<LambdaApp['shareContentHandler']>) =>
29
+ (await getApplication()).shareContentHandler(...args);
160
30
 
161
- return _trpcHandler(event, context);
162
- }
163
- );
31
+ export const swaggerUiHandler = async (...args: Parameters<LambdaApp['swaggerUiHandler']>) =>
32
+ (await getApplication()).swaggerUiHandler(...args);