@alzulejos/laranja-runtime 0.2.4
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/dist/codegen.d.ts +39 -0
- package/dist/codegen.js +174 -0
- package/dist/codegen.js.map +1 -0
- package/dist/dev-build.d.ts +1 -0
- package/dist/dev-build.js +54 -0
- package/dist/dev-build.js.map +1 -0
- package/dist/http.d.ts +13 -0
- package/dist/http.js +10 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/nest-http.d.ts +34 -0
- package/dist/nest-http.js +23 -0
- package/dist/nest-http.js.map +1 -0
- package/dist/nest-worker.d.ts +43 -0
- package/dist/nest-worker.js +73 -0
- package/dist/nest-worker.js.map +1 -0
- package/dist/queue.d.ts +23 -0
- package/dist/queue.js +41 -0
- package/dist/queue.js.map +1 -0
- package/dist/scheduled.d.ts +16 -0
- package/dist/scheduled.js +17 -0
- package/dist/scheduled.js.map +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { InfraIR } from "@alzulejos/laranja-core";
|
|
2
|
+
/**
|
|
3
|
+
* A generated Lambda entry file. These tiny shims are what the bundler points at:
|
|
4
|
+
* each imports the user's code + the matching runtime factory and re-exports a
|
|
5
|
+
* `handler`. Generating them (rather than asking users to write them) is what
|
|
6
|
+
* makes the decorator-driven model work.
|
|
7
|
+
*/
|
|
8
|
+
export interface GeneratedEntry {
|
|
9
|
+
/** Logical id of the resulting Lambda (matches the IR id). */
|
|
10
|
+
id: string;
|
|
11
|
+
/** "worker" = a consolidated Nest module Lambda hosting several crons/queues. */
|
|
12
|
+
kind: "http" | "cron" | "queue" | "worker";
|
|
13
|
+
/** File name to write under the entry dir. */
|
|
14
|
+
fileName: string;
|
|
15
|
+
/** Exported handler symbol (always "handler" for now). */
|
|
16
|
+
handlerExport: string;
|
|
17
|
+
contents: string;
|
|
18
|
+
}
|
|
19
|
+
export interface GenerateEntriesOptions {
|
|
20
|
+
/** Absolute path to the user's project root. */
|
|
21
|
+
projectDir: string;
|
|
22
|
+
/** Absolute path to the dir the entry files will be written to. */
|
|
23
|
+
entryDir: string;
|
|
24
|
+
/**
|
|
25
|
+
* Absolute path the HTTP shim should import instead of `<projectDir>/<http.handlerEntry>`.
|
|
26
|
+
* Used for Nest: the shim imports the user's COMPILED bootstrap (e.g.
|
|
27
|
+
* `dist/main.js`, which carries the DI metadata their build emitted) rather than
|
|
28
|
+
* the `.ts` source. Ignored for the worker shims.
|
|
29
|
+
*/
|
|
30
|
+
httpEntry?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Map a source file to its COMPILED path. Nest class-based workers must import the
|
|
33
|
+
* compiled provider AND their compiled `workers(...)` module (DI metadata intact),
|
|
34
|
+
* not the `.ts` source. Absent for Express, where shims bundle straight from source.
|
|
35
|
+
*/
|
|
36
|
+
resolveCompiled?: (file: string) => string;
|
|
37
|
+
}
|
|
38
|
+
/** Generate all Lambda entry shims for an Infra IR. */
|
|
39
|
+
export declare function generateEntries(ir: InfraIR, opts: GenerateEntriesOptions): GeneratedEntry[];
|
package/dist/codegen.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
/** Build an import specifier from `fromDir` to a source file, posix-style, no extension. */
|
|
3
|
+
function importSpecifier(fromDir, toFile) {
|
|
4
|
+
let rel = path.relative(fromDir, toFile.replace(/\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/, ""));
|
|
5
|
+
rel = rel.split(path.sep).join("/");
|
|
6
|
+
if (!rel.startsWith("."))
|
|
7
|
+
rel = `./${rel}`;
|
|
8
|
+
return rel;
|
|
9
|
+
}
|
|
10
|
+
/** Make an id safe to use as a file name. */
|
|
11
|
+
function safe(id) {
|
|
12
|
+
return id.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
13
|
+
}
|
|
14
|
+
/** An `import` line binding a (possibly-default, possibly-aliased) export to a local name. */
|
|
15
|
+
function importBinding(local, exportName, spec) {
|
|
16
|
+
if (exportName === "default")
|
|
17
|
+
return `import ${local} from "${spec}";`;
|
|
18
|
+
if (exportName === local)
|
|
19
|
+
return `import { ${local} } from "${spec}";`;
|
|
20
|
+
return `import { ${exportName} as ${local} } from "${spec}";`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The consolidated Nest worker shim: ONE Lambda for a whole `workers()` module.
|
|
24
|
+
* Imports the compiled module once + each hosted provider once, then wires a
|
|
25
|
+
* `createNestWorkerDispatcher` with two routing tables — crons keyed by id (the
|
|
26
|
+
* EventBridge input), queues keyed by name (the SQS source). `NestFactory` is
|
|
27
|
+
* imported from the user's own `@nestjs/core`, keeping the runtime package
|
|
28
|
+
* framework-agnostic.
|
|
29
|
+
*/
|
|
30
|
+
function workerDispatcherShim(worker, crons, queues, opts) {
|
|
31
|
+
if (!opts.resolveCompiled) {
|
|
32
|
+
throw new Error(`Cannot generate the worker shim for "${worker.id}": missing the compiled module. ` +
|
|
33
|
+
`Add \`export default workers(${worker.id})\` and build first.`);
|
|
34
|
+
}
|
|
35
|
+
const resolve = opts.resolveCompiled;
|
|
36
|
+
const workersSpec = importSpecifier(opts.entryDir, resolve(worker.handlerEntry));
|
|
37
|
+
const workersImport = importBinding("workersModule", worker.appExport, workersSpec);
|
|
38
|
+
// Import each provider class once, even if it hosts several methods.
|
|
39
|
+
const providerImports = new Map();
|
|
40
|
+
const cronRows = [];
|
|
41
|
+
const queueRows = [];
|
|
42
|
+
for (const c of crons) {
|
|
43
|
+
if (c.style !== "method")
|
|
44
|
+
continue;
|
|
45
|
+
providerImports.set(c.className, importSpecifier(opts.entryDir, resolve(c.file)));
|
|
46
|
+
cronRows.push(` "${c.id}": [${c.className}, "${c.method}"],`);
|
|
47
|
+
}
|
|
48
|
+
for (const q of queues) {
|
|
49
|
+
if (q.style !== "method")
|
|
50
|
+
continue;
|
|
51
|
+
providerImports.set(q.className, importSpecifier(opts.entryDir, resolve(q.file)));
|
|
52
|
+
queueRows.push(` "${q.name}": [${q.className}, "${q.method}"],`);
|
|
53
|
+
}
|
|
54
|
+
const imports = [...providerImports].map(([cls, spec]) => `import { ${cls} } from "${spec}";`).join("\n");
|
|
55
|
+
return `import { NestFactory } from "@nestjs/core";
|
|
56
|
+
${workersImport}
|
|
57
|
+
${imports}
|
|
58
|
+
import { createNestWorkerDispatcher } from "@alzulejos/laranja-runtime";
|
|
59
|
+
|
|
60
|
+
export const handler = createNestWorkerDispatcher(
|
|
61
|
+
() => NestFactory.createApplicationContext(workersModule),
|
|
62
|
+
{
|
|
63
|
+
crons: {
|
|
64
|
+
${cronRows.join("\n")}
|
|
65
|
+
},
|
|
66
|
+
queues: {
|
|
67
|
+
${queueRows.join("\n")}
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The import line + runtime-factory arguments for a handler, branching on whether
|
|
75
|
+
* it's a class method (`Ctor, "method"`) or a standalone function (`fn`).
|
|
76
|
+
*/
|
|
77
|
+
function handlerWiring(ref, spec) {
|
|
78
|
+
if (ref.style === "function") {
|
|
79
|
+
return {
|
|
80
|
+
importLine: `import { ${ref.exportName} } from "${spec}";`,
|
|
81
|
+
factoryArgs: ref.exportName,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
importLine: `import { ${ref.className} } from "${spec}";`,
|
|
86
|
+
factoryArgs: `${ref.className}, "${ref.method}"`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Generate all Lambda entry shims for an Infra IR. */
|
|
90
|
+
export function generateEntries(ir, opts) {
|
|
91
|
+
const entries = [];
|
|
92
|
+
const isNest = ir.app.framework === "nest";
|
|
93
|
+
const workers = ir.workers ?? [];
|
|
94
|
+
// A method-style Nest handler is GROUPED into its module's one worker Lambda.
|
|
95
|
+
const isGrouped = (h) => isNest && h.style === "method" && h.workersId !== undefined;
|
|
96
|
+
// HTTP proxy: one Lambda wrapping the whole app. Absent for workers-only apps.
|
|
97
|
+
// Express exports a ready app instance (createHttpHandler(app)); Nest exports an
|
|
98
|
+
// async bootstrap factory that returns the app (createNestHttpHandler(bootstrap)),
|
|
99
|
+
// and its shim imports the COMPILED bootstrap via `opts.httpEntry`.
|
|
100
|
+
if (ir.http) {
|
|
101
|
+
const local = isNest ? "bootstrap" : "app";
|
|
102
|
+
const factory = isNest ? "createNestHttpHandler" : "createHttpHandler";
|
|
103
|
+
const httpTarget = opts.httpEntry ?? path.join(opts.projectDir, ir.http.handlerEntry);
|
|
104
|
+
const httpSpec = importSpecifier(opts.entryDir, httpTarget);
|
|
105
|
+
const appImport = importBinding(local, ir.http.appExport, httpSpec);
|
|
106
|
+
entries.push({
|
|
107
|
+
id: "http",
|
|
108
|
+
kind: "http",
|
|
109
|
+
fileName: "http.ts",
|
|
110
|
+
handlerExport: "handler",
|
|
111
|
+
contents: `${appImport}
|
|
112
|
+
import { ${factory} } from "@alzulejos/laranja-runtime";
|
|
113
|
+
|
|
114
|
+
export const handler = ${factory}(${local});
|
|
115
|
+
`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
// Worker Lambdas: one per `workers()` module, hosting all its grouped (method-
|
|
119
|
+
// style) crons + queues behind a single dispatcher. This is where bundle
|
|
120
|
+
// duplication disappears — the module's DI graph is bundled once, not per handler.
|
|
121
|
+
for (const w of workers) {
|
|
122
|
+
const crons = ir.crons.filter((c) => c.workersId === w.id && c.style === "method");
|
|
123
|
+
const queues = ir.queues.filter((q) => q.workersId === w.id && q.style === "method");
|
|
124
|
+
if (crons.length === 0 && queues.length === 0)
|
|
125
|
+
continue;
|
|
126
|
+
entries.push({
|
|
127
|
+
id: w.id,
|
|
128
|
+
kind: "worker",
|
|
129
|
+
fileName: `worker-${safe(w.id)}.ts`,
|
|
130
|
+
handlerExport: "handler",
|
|
131
|
+
contents: workerDispatcherShim(w, crons, queues, opts),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// Cron: one Lambda per STANDALONE @Cron / cron() (Express classes, or function-
|
|
135
|
+
// style). Grouped Nest crons are hosted by their worker Lambda above.
|
|
136
|
+
for (const cron of ir.crons) {
|
|
137
|
+
if (isGrouped(cron))
|
|
138
|
+
continue;
|
|
139
|
+
const spec = importSpecifier(opts.entryDir, path.join(opts.projectDir, cron.file));
|
|
140
|
+
const { importLine, factoryArgs } = handlerWiring(cron, spec);
|
|
141
|
+
entries.push({
|
|
142
|
+
id: cron.id,
|
|
143
|
+
kind: "cron",
|
|
144
|
+
fileName: `cron-${safe(cron.id)}.ts`,
|
|
145
|
+
handlerExport: "handler",
|
|
146
|
+
contents: `${importLine}
|
|
147
|
+
import { createScheduledHandler } from "@alzulejos/laranja-runtime";
|
|
148
|
+
|
|
149
|
+
export const handler = createScheduledHandler(${factoryArgs});
|
|
150
|
+
`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// Queue: one consumer Lambda per STANDALONE @Queue / queue(). Grouped Nest
|
|
154
|
+
// queues are hosted by their worker Lambda above.
|
|
155
|
+
for (const queue of ir.queues) {
|
|
156
|
+
if (isGrouped(queue))
|
|
157
|
+
continue;
|
|
158
|
+
const spec = importSpecifier(opts.entryDir, path.join(opts.projectDir, queue.file));
|
|
159
|
+
const { importLine, factoryArgs } = handlerWiring(queue, spec);
|
|
160
|
+
entries.push({
|
|
161
|
+
id: queue.id,
|
|
162
|
+
kind: "queue",
|
|
163
|
+
fileName: `queue-${safe(queue.id)}.ts`,
|
|
164
|
+
handlerExport: "handler",
|
|
165
|
+
contents: `${importLine}
|
|
166
|
+
import { createQueueHandler } from "@alzulejos/laranja-runtime";
|
|
167
|
+
|
|
168
|
+
export const handler = createQueueHandler(${factoryArgs});
|
|
169
|
+
`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return entries;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=codegen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.js","sourceRoot":"","sources":["../src/codegen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev helper: scan a project, generate Lambda entry shims, and smoke-bundle them
|
|
3
|
+
* with esbuild to prove the decorator -> shim -> bundle path resolves end to end.
|
|
4
|
+
*
|
|
5
|
+
* npm run build:express
|
|
6
|
+
* tsx packages/runtime/src/dev-build.ts <project-dir>
|
|
7
|
+
*/
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { build } from "esbuild";
|
|
11
|
+
import { loadConfig } from "@alzulejos/laranja-core";
|
|
12
|
+
import { scan } from "@alzulejos/laranja-scanner";
|
|
13
|
+
import { generateEntries } from "./codegen.js";
|
|
14
|
+
async function main() {
|
|
15
|
+
const projectDir = path.resolve(process.argv[2] ?? ".");
|
|
16
|
+
const outRoot = path.join(projectDir, ".laranja");
|
|
17
|
+
const entryDir = path.join(outRoot, "entries");
|
|
18
|
+
const buildDir = path.join(outRoot, "build");
|
|
19
|
+
rmSync(outRoot, { recursive: true, force: true });
|
|
20
|
+
mkdirSync(entryDir, { recursive: true });
|
|
21
|
+
const config = await loadConfig(projectDir);
|
|
22
|
+
const ir = scan({ projectDir, config });
|
|
23
|
+
const entries = generateEntries(ir, { projectDir, entryDir });
|
|
24
|
+
for (const e of entries) {
|
|
25
|
+
writeFileSync(path.join(entryDir, e.fileName), e.contents);
|
|
26
|
+
}
|
|
27
|
+
console.log(`Generated ${entries.length} entry shims:`);
|
|
28
|
+
for (const e of entries) {
|
|
29
|
+
console.log(` [${e.kind.padEnd(5)}] ${e.fileName} -> ${e.id}`);
|
|
30
|
+
}
|
|
31
|
+
const result = await build({
|
|
32
|
+
entryPoints: entries.map((e) => path.join(entryDir, e.fileName)),
|
|
33
|
+
outdir: buildDir,
|
|
34
|
+
bundle: true,
|
|
35
|
+
platform: "node",
|
|
36
|
+
target: "node20",
|
|
37
|
+
format: "cjs",
|
|
38
|
+
outExtension: { ".js": ".cjs" },
|
|
39
|
+
external: ["@aws-sdk/*", "aws-sdk"],
|
|
40
|
+
logLevel: "warning",
|
|
41
|
+
metafile: true,
|
|
42
|
+
});
|
|
43
|
+
console.log("\nBundled (handler = <file>.handler):");
|
|
44
|
+
for (const [file, out] of Object.entries(result.metafile.outputs)) {
|
|
45
|
+
if (file.endsWith(".cjs")) {
|
|
46
|
+
console.log(` ${path.relative(projectDir, file)} (${(out.bytes / 1024).toFixed(1)} KB)`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
main().catch((err) => {
|
|
51
|
+
console.error(err);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=dev-build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-build.js","sourceRoot":"","sources":["../src/dev-build.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { configure } from "@codegenie/serverless-express";
|
|
2
|
+
import type { Handler } from "aws-lambda";
|
|
3
|
+
/**
|
|
4
|
+
* The user's framework app (Express in v1). Typed via the adapter's own param so
|
|
5
|
+
* we don't take a hard dependency on express's types here.
|
|
6
|
+
*/
|
|
7
|
+
export type FrameworkApp = Parameters<typeof configure>[0]["app"];
|
|
8
|
+
/**
|
|
9
|
+
* Wraps the user's exported app in an API Gateway proxy handler. This is the
|
|
10
|
+
* single Lambda that serves ALL HTTP routes (the proxy model). The adapter caches
|
|
11
|
+
* its server across warm invocations internally.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createHttpHandler(app: FrameworkApp): Handler;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { configure } from "@codegenie/serverless-express";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps the user's exported app in an API Gateway proxy handler. This is the
|
|
4
|
+
* single Lambda that serves ALL HTTP routes (the proxy model). The adapter caches
|
|
5
|
+
* its server across warm invocations internally.
|
|
6
|
+
*/
|
|
7
|
+
export function createHttpHandler(app) {
|
|
8
|
+
return configure({ app });
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAS1D;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO,SAAS,CAAC,EAAE,GAAG,EAAE,CAAY,CAAC;AACvC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createHttpHandler } from "./http.js";
|
|
2
|
+
export type { FrameworkApp } from "./http.js";
|
|
3
|
+
export { createNestHttpHandler } from "./nest-http.js";
|
|
4
|
+
export type { NestAppLike, NestBootstrap } from "./nest-http.js";
|
|
5
|
+
export { createScheduledHandler } from "./scheduled.js";
|
|
6
|
+
export { createQueueHandler } from "./queue.js";
|
|
7
|
+
export type { QueueConsumer } from "./queue.js";
|
|
8
|
+
export { createNestScheduledHandler, createNestQueueHandler, createNestWorkerDispatcher } from "./nest-worker.js";
|
|
9
|
+
export type { NestContextLike, NestContextFactory, DispatchEntry } from "./nest-worker.js";
|
|
10
|
+
export { generateEntries } from "./codegen.js";
|
|
11
|
+
export type { GeneratedEntry, GenerateEntriesOptions } from "./codegen.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createHttpHandler } from "./http.js";
|
|
2
|
+
export { createNestHttpHandler } from "./nest-http.js";
|
|
3
|
+
export { createScheduledHandler } from "./scheduled.js";
|
|
4
|
+
export { createQueueHandler } from "./queue.js";
|
|
5
|
+
export { createNestScheduledHandler, createNestQueueHandler, createNestWorkerDispatcher } from "./nest-worker.js";
|
|
6
|
+
// Build-time codegen (not used inside the Lambda, but co-located because it
|
|
7
|
+
// generates code that imports the factories above).
|
|
8
|
+
export { generateEntries } from "./codegen.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,0BAA0B,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AAGlH,4EAA4E;AAC5E,oDAAoD;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Handler } from "aws-lambda";
|
|
2
|
+
import { type FrameworkApp } from "./http.js";
|
|
3
|
+
/**
|
|
4
|
+
* The slice of a NestJS `INestApplication` we actually touch. Kept structural —
|
|
5
|
+
* no `@nestjs` import — so the runtime package stays framework-agnostic, exactly
|
|
6
|
+
* like `http.ts` avoids a hard express dependency. The user's app supplies the
|
|
7
|
+
* real instance at runtime.
|
|
8
|
+
*/
|
|
9
|
+
export interface NestAppLike {
|
|
10
|
+
/** Idempotent in Nest: safe to call even if `bootstrap()` already listened. */
|
|
11
|
+
init(): Promise<unknown>;
|
|
12
|
+
/** Yields the underlying Express app (requires `@nestjs/platform-express`). */
|
|
13
|
+
getHttpAdapter(): {
|
|
14
|
+
getInstance(): FrameworkApp;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The user's exported bootstrap: creates + configures a Nest app and RETURNS it
|
|
19
|
+
* (`export default http(bootstrap)`). Unlike Express — a ready synchronous app —
|
|
20
|
+
* a Nest app only exists after an async `NestFactory.create`, so laranja wraps the
|
|
21
|
+
* factory rather than an instance.
|
|
22
|
+
*/
|
|
23
|
+
export type NestBootstrap = () => Promise<NestAppLike>;
|
|
24
|
+
/**
|
|
25
|
+
* The Nest counterpart to `createHttpHandler`: wraps the user's bootstrap factory
|
|
26
|
+
* in an API Gateway proxy handler (the single Lambda serving ALL routes).
|
|
27
|
+
*
|
|
28
|
+
* We run the user's `bootstrap()` verbatim — so every pipe/guard/middleware they
|
|
29
|
+
* configured is preserved — then `init()` it (idempotent even if their bootstrap
|
|
30
|
+
* also called `listen`), extract the underlying Express instance, and hand it to
|
|
31
|
+
* the shared serverless-express adapter. The app + adapter are built once and
|
|
32
|
+
* cached across warm invocations.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createNestHttpHandler(bootstrap: NestBootstrap): Handler;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createHttpHandler } from "./http.js";
|
|
2
|
+
/**
|
|
3
|
+
* The Nest counterpart to `createHttpHandler`: wraps the user's bootstrap factory
|
|
4
|
+
* in an API Gateway proxy handler (the single Lambda serving ALL routes).
|
|
5
|
+
*
|
|
6
|
+
* We run the user's `bootstrap()` verbatim — so every pipe/guard/middleware they
|
|
7
|
+
* configured is preserved — then `init()` it (idempotent even if their bootstrap
|
|
8
|
+
* also called `listen`), extract the underlying Express instance, and hand it to
|
|
9
|
+
* the shared serverless-express adapter. The app + adapter are built once and
|
|
10
|
+
* cached across warm invocations.
|
|
11
|
+
*/
|
|
12
|
+
export function createNestHttpHandler(bootstrap) {
|
|
13
|
+
let cached;
|
|
14
|
+
return (async (event, context, callback) => {
|
|
15
|
+
if (!cached) {
|
|
16
|
+
const app = await bootstrap();
|
|
17
|
+
await app.init();
|
|
18
|
+
cached = createHttpHandler(app.getHttpAdapter().getInstance());
|
|
19
|
+
}
|
|
20
|
+
return cached(event, context, callback);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=nest-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest-http.js","sourceRoot":"","sources":["../src/nest-http.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAqB,MAAM,WAAW,CAAC;AAuBjE;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAwB;IAC5D,IAAI,MAA2B,CAAC;IAChC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;YAC9B,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC1C,CAAC,CAAY,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Context, Handler, SQSBatchResponse, SQSEvent } from "aws-lambda";
|
|
2
|
+
/** A DI‑resolvable provider + the method to invoke on it. */
|
|
3
|
+
export type DispatchEntry = [Ctor<object>, string];
|
|
4
|
+
/**
|
|
5
|
+
* Nest-backed worker handlers. Unlike the Express path — where a `@Cron`/`@Queue`
|
|
6
|
+
* class is `new`'d directly — a Nest provider's method depends on injected
|
|
7
|
+
* services, so we must resolve the instance through the DI container. The
|
|
8
|
+
* generated shim hands us a factory that builds a standalone Nest context
|
|
9
|
+
* (`NestFactory.createApplicationContext(WorkersModule)`); we build it once, cache
|
|
10
|
+
* it across warm invocations, and pull the provider out with `context.get()`.
|
|
11
|
+
*
|
|
12
|
+
* Kept structural (no `@nestjs/*` import) so the runtime package stays
|
|
13
|
+
* framework-agnostic — the shim, generated inside the user's Nest project, is what
|
|
14
|
+
* imports `NestFactory`.
|
|
15
|
+
*/
|
|
16
|
+
/** The slice of a Nest `INestApplicationContext` we use: DI resolution by class. */
|
|
17
|
+
export interface NestContextLike {
|
|
18
|
+
get<T>(type: new (...args: any[]) => T): T;
|
|
19
|
+
}
|
|
20
|
+
/** Builds the standalone Nest context. Async in Nest (`createApplicationContext`). */
|
|
21
|
+
export type NestContextFactory = () => NestContextLike | Promise<NestContextLike>;
|
|
22
|
+
type Ctor<T> = new (...args: any[]) => T;
|
|
23
|
+
/** The Nest counterpart to `createScheduledHandler` — resolves the provider via DI. */
|
|
24
|
+
export declare function createNestScheduledHandler<T extends object>(contextFactory: NestContextFactory, Ctor: Ctor<T>, method: keyof T & string): Handler;
|
|
25
|
+
/** The Nest counterpart to `createQueueHandler` — resolves the consumer via DI. */
|
|
26
|
+
export declare function createNestQueueHandler<T extends object>(contextFactory: NestContextFactory, Ctor: Ctor<T>, method: keyof T & string): (event: SQSEvent, context: Context) => Promise<SQSBatchResponse>;
|
|
27
|
+
/**
|
|
28
|
+
* The consolidated worker handler: ONE Lambda for a whole `workers()` module,
|
|
29
|
+
* hosting all its `@Cron` and `@Queue` methods. It builds the module's DI context
|
|
30
|
+
* once (cached across warm invocations) and routes by event shape —
|
|
31
|
+
*
|
|
32
|
+
* - **SQS** (`Records` present): each record → the consumer for its source queue,
|
|
33
|
+
* looked up by `eventSourceARN`, through the partial‑batch‑failure contract.
|
|
34
|
+
* - **EventBridge**: our schedules pass `{ handler: "<cronId>" }` → the cron method.
|
|
35
|
+
*
|
|
36
|
+
* The routing tables map an id to `[Provider, method]`; the provider resolves
|
|
37
|
+
* through DI so injected dependencies work exactly as in the app.
|
|
38
|
+
*/
|
|
39
|
+
export declare function createNestWorkerDispatcher(contextFactory: NestContextFactory, tables: {
|
|
40
|
+
crons: Record<string, DispatchEntry>;
|
|
41
|
+
queues: Record<string, DispatchEntry>;
|
|
42
|
+
}): Handler;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { runSqsBatch } from "./queue.js";
|
|
2
|
+
/** Build (once) the context, resolve the provider, and pull the target method off it.
|
|
3
|
+
* `method` is a plain string (the dispatch tables carry `[Provider, "name"]`), so
|
|
4
|
+
* there's no compile-time `keyof` guard here — the runtime check below catches a
|
|
5
|
+
* bad name. */
|
|
6
|
+
function resolveMethod(ctx, Ctor, method, kind) {
|
|
7
|
+
const instance = ctx.get(Ctor);
|
|
8
|
+
const fn = instance[method];
|
|
9
|
+
if (typeof fn !== "function") {
|
|
10
|
+
throw new Error(`${kind} target ${Ctor.name}.${method} is not a method`);
|
|
11
|
+
}
|
|
12
|
+
return fn.bind(instance);
|
|
13
|
+
}
|
|
14
|
+
/** The Nest counterpart to `createScheduledHandler` — resolves the provider via DI. */
|
|
15
|
+
export function createNestScheduledHandler(contextFactory, Ctor, method) {
|
|
16
|
+
let call;
|
|
17
|
+
return (async (event, context) => {
|
|
18
|
+
call ??= resolveMethod(await contextFactory(), Ctor, method, "@Cron");
|
|
19
|
+
return await call(event, context);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** The Nest counterpart to `createQueueHandler` — resolves the consumer via DI. */
|
|
23
|
+
export function createNestQueueHandler(contextFactory, Ctor, method) {
|
|
24
|
+
let consumer;
|
|
25
|
+
return async (event, context) => {
|
|
26
|
+
consumer ??= resolveMethod(await contextFactory(), Ctor, method, "@Queue");
|
|
27
|
+
return runSqsBatch(consumer, event, context);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The declared queue name is the last `:`‑segment of an SQS `eventSourceARN`
|
|
31
|
+
* (`arn:aws:sqs:<region>:<acct>:<name>`); laranja names the queue = its declared
|
|
32
|
+
* name, so this maps a record straight back to the routing table key. */
|
|
33
|
+
function queueNameFromArn(arn) {
|
|
34
|
+
return arn.slice(arn.lastIndexOf(":") + 1);
|
|
35
|
+
}
|
|
36
|
+
function isSqsEvent(event) {
|
|
37
|
+
return !!event && typeof event === "object" && Array.isArray(event.Records);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The consolidated worker handler: ONE Lambda for a whole `workers()` module,
|
|
41
|
+
* hosting all its `@Cron` and `@Queue` methods. It builds the module's DI context
|
|
42
|
+
* once (cached across warm invocations) and routes by event shape —
|
|
43
|
+
*
|
|
44
|
+
* - **SQS** (`Records` present): each record → the consumer for its source queue,
|
|
45
|
+
* looked up by `eventSourceARN`, through the partial‑batch‑failure contract.
|
|
46
|
+
* - **EventBridge**: our schedules pass `{ handler: "<cronId>" }` → the cron method.
|
|
47
|
+
*
|
|
48
|
+
* The routing tables map an id to `[Provider, method]`; the provider resolves
|
|
49
|
+
* through DI so injected dependencies work exactly as in the app.
|
|
50
|
+
*/
|
|
51
|
+
export function createNestWorkerDispatcher(contextFactory, tables) {
|
|
52
|
+
let ctx;
|
|
53
|
+
const getCtx = async () => (ctx ??= await contextFactory());
|
|
54
|
+
return (async (event, context) => {
|
|
55
|
+
if (isSqsEvent(event)) {
|
|
56
|
+
const c = await getCtx();
|
|
57
|
+
const consumer = (body, record, ctx2) => {
|
|
58
|
+
const name = queueNameFromArn(record.eventSourceARN);
|
|
59
|
+
const entry = tables.queues[name];
|
|
60
|
+
if (!entry)
|
|
61
|
+
throw new Error(`worker: no @Queue consumer for "${name}"`);
|
|
62
|
+
return resolveMethod(c, entry[0], entry[1], "@Queue")(body, record, ctx2);
|
|
63
|
+
};
|
|
64
|
+
return runSqsBatch(consumer, event, context);
|
|
65
|
+
}
|
|
66
|
+
const handlerId = event?.handler;
|
|
67
|
+
const entry = handlerId ? tables.crons[handlerId] : undefined;
|
|
68
|
+
if (!entry)
|
|
69
|
+
throw new Error(`worker: no @Cron handler for "${String(handlerId)}"`);
|
|
70
|
+
return await resolveMethod(await getCtx(), entry[0], entry[1], "@Cron")(event, context);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=nest-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest-worker.js","sourceRoot":"","sources":["../src/nest-worker.ts"],"names":[],"mappings":"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"}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context, SQSBatchResponse, SQSEvent, SQSRecord } from "aws-lambda";
|
|
2
|
+
type Ctor<T> = new () => T;
|
|
3
|
+
/** The signature an `@Queue` consumer method receives: parsed body + raw record. */
|
|
4
|
+
export type QueueConsumer = (body: unknown, record: SQSRecord, context: Context) => unknown | Promise<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Drive an SQS batch through a consumer, one message at a time, collecting the
|
|
7
|
+
* IDs that threw into the partial-batch-failure response. Shared by the plain and
|
|
8
|
+
* the Nest/DI-backed consumer handlers so the retry contract lives in one place.
|
|
9
|
+
*/
|
|
10
|
+
export declare function runSqsBatch(consumer: QueueConsumer, event: SQSEvent, context: Context): Promise<SQSBatchResponse>;
|
|
11
|
+
/**
|
|
12
|
+
* Builds the Lambda handler for a `@Queue` method or `queue()` function. Calls the
|
|
13
|
+
* consumer once per message with the JSON-parsed body. Failed messages are
|
|
14
|
+
* reported back via the partial-batch-failure contract, so the CDK event source
|
|
15
|
+
* must enable `reportBatchItemFailures`.
|
|
16
|
+
*
|
|
17
|
+
* - Function form (`createQueueHandler(fn)`): calls the function per message.
|
|
18
|
+
* - Method form (`createQueueHandler(Ctor, "method")`): instantiates the class
|
|
19
|
+
* once (cached) and calls the decorated method.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createQueueHandler(consumer: QueueConsumer): (event: SQSEvent, context: Context) => Promise<SQSBatchResponse>;
|
|
22
|
+
export declare function createQueueHandler<T extends object>(Ctor: Ctor<T>, method: keyof T & string): (event: SQSEvent, context: Context) => Promise<SQSBatchResponse>;
|
|
23
|
+
export {};
|
package/dist/queue.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function parseBody(body) {
|
|
2
|
+
try {
|
|
3
|
+
return JSON.parse(body);
|
|
4
|
+
}
|
|
5
|
+
catch {
|
|
6
|
+
return body;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Drive an SQS batch through a consumer, one message at a time, collecting the
|
|
11
|
+
* IDs that threw into the partial-batch-failure response. Shared by the plain and
|
|
12
|
+
* the Nest/DI-backed consumer handlers so the retry contract lives in one place.
|
|
13
|
+
*/
|
|
14
|
+
export async function runSqsBatch(consumer, event, context) {
|
|
15
|
+
const batchItemFailures = [];
|
|
16
|
+
for (const record of event.Records) {
|
|
17
|
+
try {
|
|
18
|
+
await consumer(parseBody(record.body), record, context);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { batchItemFailures };
|
|
25
|
+
}
|
|
26
|
+
export function createQueueHandler(target, method) {
|
|
27
|
+
let instance;
|
|
28
|
+
const resolveConsumer = () => {
|
|
29
|
+
if (method === undefined)
|
|
30
|
+
return target;
|
|
31
|
+
const Ctor = target;
|
|
32
|
+
instance ??= new Ctor();
|
|
33
|
+
const fn = instance[method];
|
|
34
|
+
if (typeof fn !== "function") {
|
|
35
|
+
throw new Error(`@Queue target ${Ctor.name}.${String(method)} is not a method`);
|
|
36
|
+
}
|
|
37
|
+
return fn.bind(instance);
|
|
38
|
+
};
|
|
39
|
+
return async (event, context) => runSqsBatch(resolveConsumer(), event, context);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=queue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAOA,SAAS,SAAS,CAAC,IAAY;IAC7B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAuB,EACvB,KAAe,EACf,OAAgB;IAEhB,MAAM,iBAAiB,GAAiC,EAAE,CAAC;IAC3D,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,iBAAiB,CAAC,IAAI,CAAC,EAAE,cAAc,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC/B,CAAC;AAmBD,MAAM,UAAU,kBAAkB,CAChC,MAA+B,EAC/B,MAAyB;IAEzB,IAAI,QAAuB,CAAC;IAC5B,MAAM,eAAe,GAAG,GAAkB,EAAE;QAC1C,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAuB,CAAC;QACzD,MAAM,IAAI,GAAG,MAAiB,CAAC;QAC/B,QAAQ,KAAK,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAY,CAAC;QACvC,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QAClF,CAAC;QACD,OAAQ,EAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,CAAC,CAAC;IAEF,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,eAAe,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;AAClF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Context, Handler } from "aws-lambda";
|
|
2
|
+
type Ctor<T> = new () => T;
|
|
3
|
+
/** A standalone scheduled function registered via `cron(...)`. */
|
|
4
|
+
export type ScheduledFn = (event: unknown, context: Context) => unknown | Promise<unknown>;
|
|
5
|
+
/**
|
|
6
|
+
* Builds the Lambda handler for a `@Cron` method or `cron()` function. EventBridge
|
|
7
|
+
* invokes this on a schedule.
|
|
8
|
+
*
|
|
9
|
+
* - Function form (`createScheduledHandler(fn)`): just calls the function.
|
|
10
|
+
* - Method form (`createScheduledHandler(Ctor, "method")`): instantiates the class
|
|
11
|
+
* once (cached across warm invocations — v1 assumes a no-arg constructor) and
|
|
12
|
+
* calls the decorated method.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createScheduledHandler(handler: ScheduledFn): Handler;
|
|
15
|
+
export declare function createScheduledHandler<T extends object>(Ctor: Ctor<T>, method: keyof T & string): Handler;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createScheduledHandler(target, method) {
|
|
2
|
+
if (method === undefined) {
|
|
3
|
+
const fn = target;
|
|
4
|
+
return async (event, context) => await fn(event, context);
|
|
5
|
+
}
|
|
6
|
+
const Ctor = target;
|
|
7
|
+
let instance;
|
|
8
|
+
return async (event, context) => {
|
|
9
|
+
instance ??= new Ctor();
|
|
10
|
+
const fn = instance[method];
|
|
11
|
+
if (typeof fn !== "function") {
|
|
12
|
+
throw new Error(`@Cron target ${Ctor.name}.${String(method)} is not a method`);
|
|
13
|
+
}
|
|
14
|
+
return await fn.call(instance, event, context);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=scheduled.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduled.js","sourceRoot":"","sources":["../src/scheduled.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,sBAAsB,CACpC,MAA6B,EAC7B,MAAyB;IAEzB,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,MAAqB,CAAC;QACjC,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,IAAI,GAAG,MAAiB,CAAC;IAC/B,IAAI,QAAuB,CAAC;IAC5B,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,QAAQ,KAAK,IAAI,IAAI,EAAE,CAAC;QACxB,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAY,CAAC;QACvC,IAAI,OAAO,EAAE,KAAK,UAAU,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACjF,CAAC;QACD,OAAO,MAAO,EAAsC,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACtF,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alzulejos/laranja-runtime",
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@codegenie/serverless-express": "^4.1.6",
|
|
23
|
+
"@alzulejos/laranja-core": "0.2.4"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@alzulejos/laranja-scanner": "0.2.4",
|
|
27
|
+
"@types/aws-lambda": "^8.10.145",
|
|
28
|
+
"esbuild": "^0.28.1"
|
|
29
|
+
}
|
|
30
|
+
}
|