@suss/packs 0.11.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.
Files changed (63) hide show
  1. package/LICENSE +205 -0
  2. package/README.md +29 -0
  3. package/dist/activerecord.d.ts +30 -0
  4. package/dist/activerecord.js +45 -0
  5. package/dist/apollo-client.d.ts +1 -0
  6. package/dist/apollo-client.js +137 -0
  7. package/dist/apollo.d.ts +1 -0
  8. package/dist/apollo.js +90 -0
  9. package/dist/aws-dynamodb.d.ts +1 -0
  10. package/dist/aws-dynamodb.js +386 -0
  11. package/dist/aws-eventbridge.d.ts +1 -0
  12. package/dist/aws-eventbridge.js +240 -0
  13. package/dist/aws-lambda.d.ts +1 -0
  14. package/dist/aws-lambda.js +382 -0
  15. package/dist/aws-s3.d.ts +1 -0
  16. package/dist/aws-s3.js +120 -0
  17. package/dist/aws-sqs.d.ts +1 -0
  18. package/dist/aws-sqs.js +296 -0
  19. package/dist/axios.d.ts +1 -0
  20. package/dist/axios.js +107 -0
  21. package/dist/cloudflare-workers.d.ts +1 -0
  22. package/dist/cloudflare-workers.js +562 -0
  23. package/dist/drizzle.d.ts +1 -0
  24. package/dist/drizzle.js +383 -0
  25. package/dist/express.d.ts +1 -0
  26. package/dist/express.js +124 -0
  27. package/dist/fastapi.d.ts +1 -0
  28. package/dist/fastapi.js +42 -0
  29. package/dist/fastify.d.ts +1 -0
  30. package/dist/fastify.js +116 -0
  31. package/dist/fetch.d.ts +1 -0
  32. package/dist/fetch.js +64 -0
  33. package/dist/flask-restx.d.ts +1 -0
  34. package/dist/flask-restx.js +59 -0
  35. package/dist/gcs.d.ts +1 -0
  36. package/dist/gcs.js +134 -0
  37. package/dist/graphql-ruby.d.ts +1 -0
  38. package/dist/graphql-ruby.js +84 -0
  39. package/dist/hono.d.ts +1 -0
  40. package/dist/hono.js +184 -0
  41. package/dist/mongoose.d.ts +1 -0
  42. package/dist/mongoose.js +379 -0
  43. package/dist/nestjs-graphql.d.ts +1 -0
  44. package/dist/nestjs-graphql.js +77 -0
  45. package/dist/nestjs-rest.d.ts +1 -0
  46. package/dist/nestjs-rest.js +89 -0
  47. package/dist/nextjs.d.ts +1 -0
  48. package/dist/nextjs.js +154 -0
  49. package/dist/node.d.ts +1 -0
  50. package/dist/node.js +667 -0
  51. package/dist/prisma.d.ts +1 -0
  52. package/dist/prisma.js +448 -0
  53. package/dist/react-router.d.ts +1 -0
  54. package/dist/react-router.js +197 -0
  55. package/dist/react.d.ts +1 -0
  56. package/dist/react.js +221 -0
  57. package/dist/redis.d.ts +1 -0
  58. package/dist/redis.js +93 -0
  59. package/dist/sqlalchemy.d.ts +1 -0
  60. package/dist/sqlalchemy.js +48 -0
  61. package/dist/ts-rest.d.ts +1 -0
  62. package/dist/ts-rest.js +89 -0
  63. package/package.json +208 -0
@@ -0,0 +1,382 @@
1
+ // ../framework/aws-lambda/dist/index.js
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import {
5
+ loadTemplateTree,
6
+ qualifiedLogicalId,
7
+ readAppSyncResolvers,
8
+ readServerlessFunctions,
9
+ unfollowedStackMessage
10
+ } from "@suss/manifest-aws";
11
+ var TEMPLATE_NAMES = ["template.yaml", "template.yml", "template.json"];
12
+ var dirToTemplate = /* @__PURE__ */ new Map();
13
+ var templateToIndex = /* @__PURE__ */ new Map();
14
+ function findTemplate(startDir) {
15
+ const chain = [];
16
+ let dir = startDir;
17
+ while (true) {
18
+ const cached = dirToTemplate.get(dir);
19
+ if (cached !== void 0) {
20
+ for (const d of chain) {
21
+ dirToTemplate.set(d, cached);
22
+ }
23
+ return cached;
24
+ }
25
+ chain.push(dir);
26
+ const found = TEMPLATE_NAMES.map((name) => path.join(dir, name)).find(
27
+ (candidate) => fs.existsSync(candidate)
28
+ ) ?? null;
29
+ if (found !== null) {
30
+ for (const d of chain) {
31
+ dirToTemplate.set(d, found);
32
+ }
33
+ return found;
34
+ }
35
+ const parent = path.dirname(dir);
36
+ if (parent === dir) {
37
+ for (const d of chain) {
38
+ dirToTemplate.set(d, null);
39
+ }
40
+ return null;
41
+ }
42
+ dir = parent;
43
+ }
44
+ }
45
+ function indexForTemplate(templatePath) {
46
+ const cached = templateToIndex.get(templatePath);
47
+ if (cached !== void 0) {
48
+ return cached;
49
+ }
50
+ const index = /* @__PURE__ */ new Map();
51
+ try {
52
+ const tree = loadTemplateTree(templatePath);
53
+ for (const stack of tree.unfollowed) {
54
+ process.stderr.write(
55
+ `[suss] aws-lambda: ${unfollowedStackMessage(stack)}
56
+ `
57
+ );
58
+ }
59
+ for (const document of tree.documents) {
60
+ const templateDir = path.dirname(document.path);
61
+ const fieldsByFunction = groupFieldsByFunction(
62
+ readAppSyncResolvers(document.template)
63
+ );
64
+ for (const fn of readServerlessFunctions(document.template)) {
65
+ const resolvedModule = path.resolve(
66
+ templateDir,
67
+ fn.codeUri,
68
+ fn.modulePath
69
+ );
70
+ const list = index.get(resolvedModule) ?? [];
71
+ list.push({
72
+ functionLogicalId: qualifiedLogicalId(
73
+ document.stackPath,
74
+ fn.logicalId
75
+ ),
76
+ graphqlFields: fieldsByFunction.get(fn.logicalId) ?? [],
77
+ handler: fn.handler,
78
+ exportName: fn.exportName,
79
+ httpRoutes: fn.httpRoutes,
80
+ nonHttpEvents: fn.nonHttpEvents
81
+ });
82
+ index.set(resolvedModule, list);
83
+ }
84
+ }
85
+ } catch (err) {
86
+ process.stderr.write(
87
+ `[suss] aws-lambda: failed to read ${templatePath}: ${err instanceof Error ? err.message : String(err)}
88
+ `
89
+ );
90
+ }
91
+ templateToIndex.set(templatePath, index);
92
+ return index;
93
+ }
94
+ function toModulePath(filePath) {
95
+ const resolved = path.resolve(filePath);
96
+ const ext = path.extname(resolved);
97
+ return ext === "" ? resolved : resolved.slice(0, -ext.length);
98
+ }
99
+ function groupFieldsByFunction(bindings) {
100
+ const out = /* @__PURE__ */ new Map();
101
+ for (const binding of bindings) {
102
+ for (const logicalId of binding.lambdaFunctionLogicalIds) {
103
+ const fields = out.get(logicalId) ?? [];
104
+ fields.push({ typeName: binding.typeName, fieldName: binding.fieldName });
105
+ out.set(logicalId, fields);
106
+ }
107
+ }
108
+ return out;
109
+ }
110
+ function handlersForFile(filePath) {
111
+ const templatePath = findTemplate(path.dirname(path.resolve(filePath)));
112
+ if (templatePath === null) {
113
+ return [];
114
+ }
115
+ const index = indexForTemplate(templatePath);
116
+ return index.get(toModulePath(filePath)) ?? [];
117
+ }
118
+ var HTTP_TERMINALS = [
119
+ {
120
+ // `return { statusCode, body, headers? }`, written at the return site or
121
+ // built by a helper the adapter follows into. `body` contains the
122
+ // serialized payload, so unwrap `JSON.stringify(x)` to the shape of `x`.
123
+ kind: "response",
124
+ match: { type: "returnShape", requiredProperties: ["statusCode"] },
125
+ extraction: {
126
+ statusCode: { from: "property", name: "statusCode" },
127
+ body: { from: "property", name: "body", unwrapJsonStringify: true }
128
+ }
129
+ },
130
+ {
131
+ // `return { batchItemFailures }`, which is how a consumer tells Lambda
132
+ // which records to retry and which to drop. Lambda defines this shape, so
133
+ // the pack can match on it, the same way it matches the envelope above.
134
+ kind: "return",
135
+ match: {
136
+ type: "returnShape",
137
+ requiredProperties: ["batchItemFailures"]
138
+ },
139
+ extraction: {}
140
+ },
141
+ {
142
+ // `throw new SomeError(...)`. An uncaught throw becomes a
143
+ // throw-output transition; API Gateway maps it to a 5xx, but the
144
+ // specific status is the platform's, not the handler's.
145
+ kind: "throw",
146
+ match: { type: "throwExpression" },
147
+ extraction: {}
148
+ }
149
+ ];
150
+ var NON_HTTP_TERMINALS = [
151
+ ...HTTP_TERMINALS,
152
+ {
153
+ kind: "return",
154
+ match: { type: "returnShape" },
155
+ extraction: {}
156
+ },
157
+ {
158
+ // A queue consumer acknowledges a batch by not throwing: it processes the
159
+ // records, falls off the end, and Lambda reads the absence of an error as
160
+ // the ack. Without this the handler has no terminal, so it gets no
161
+ // transition, and everything it does goes with it.
162
+ kind: "return",
163
+ match: { type: "functionFallthrough" },
164
+ extraction: {}
165
+ }
166
+ ];
167
+ var METADATA_NAMESPACE = "awsLambda";
168
+ function deployableUnit(entry) {
169
+ return { deploymentTarget: "lambda", instanceName: entry.functionLogicalId };
170
+ }
171
+ function httpRouteUnits(entry, func) {
172
+ const units = [];
173
+ for (const route of entry.httpRoutes) {
174
+ if (route.method === "ANY") {
175
+ continue;
176
+ }
177
+ units.push({
178
+ func,
179
+ kind: "handler",
180
+ name: `${entry.functionLogicalId}.${entry.exportName}`,
181
+ routeInfo: { method: route.method, path: route.path },
182
+ deployableUnit: deployableUnit(entry),
183
+ metadata: {
184
+ [METADATA_NAMESPACE]: {
185
+ handler: entry.handler,
186
+ eventId: route.eventId,
187
+ apiEventType: route.eventType
188
+ }
189
+ }
190
+ });
191
+ }
192
+ return units;
193
+ }
194
+ var OPERATION_ROOT_TYPES = ["Query", "Mutation", "Subscription"];
195
+ function operationFields(entry) {
196
+ return entry.graphqlFields.filter(
197
+ (f) => OPERATION_ROOT_TYPES.includes(f.typeName)
198
+ );
199
+ }
200
+ function typeFields(entry) {
201
+ return entry.graphqlFields.filter(
202
+ (f) => !OPERATION_ROOT_TYPES.includes(f.typeName)
203
+ );
204
+ }
205
+ function graphqlResolverUnits(entry, func) {
206
+ return operationFields(entry).map((field) => ({
207
+ func,
208
+ kind: "handler",
209
+ name: `${entry.functionLogicalId}.${entry.exportName}`,
210
+ resolverInfo: { typeName: field.typeName, fieldName: field.fieldName },
211
+ deployableUnit: deployableUnit(entry),
212
+ metadata: {
213
+ [METADATA_NAMESPACE]: {
214
+ handler: entry.handler,
215
+ recognition: "appsync-resolver"
216
+ }
217
+ }
218
+ }));
219
+ }
220
+ function accountingUnit(entry, func, channel) {
221
+ const eventTypes = accountedEventTypes(entry);
222
+ const backs = typeFields(entry);
223
+ const wire = channel !== null ? "aws_sqs" : messageBusWire(eventTypes);
224
+ return {
225
+ func,
226
+ kind: "handler",
227
+ name: `${entry.functionLogicalId}.${entry.exportName}`,
228
+ terminals: NON_HTTP_TERMINALS,
229
+ ...wire !== null ? { channelInfo: { messageBus: wire, channel } } : {},
230
+ deployableUnit: deployableUnit(entry),
231
+ metadata: {
232
+ [METADATA_NAMESPACE]: {
233
+ handler: entry.handler,
234
+ recognition: "recognized-not-http",
235
+ eventTypes,
236
+ ...backs.length > 0 ? { graphqlTypeFields: backs } : {}
237
+ }
238
+ }
239
+ };
240
+ }
241
+ function subjectChannel(entry, sf, tsCtx, factories) {
242
+ const consumesSqs = entry.nonHttpEvents.some((e) => e.eventType === "SQS");
243
+ if (!consumesSqs) {
244
+ return null;
245
+ }
246
+ for (const factory of factories) {
247
+ const subject = tsCtx.exportedCallConfigString(
248
+ sf,
249
+ entry.exportName,
250
+ factory
251
+ );
252
+ if (subject !== null) {
253
+ return subject;
254
+ }
255
+ }
256
+ return null;
257
+ }
258
+ var EVENT_WIRES = {
259
+ SQS: "aws_sqs",
260
+ SNS: "aws.sns",
261
+ S3: "s3",
262
+ Schedule: "eventbridge",
263
+ ScheduleV2: "eventbridge",
264
+ EventBridgeRule: "eventbridge",
265
+ CloudWatchEvent: "eventbridge"
266
+ };
267
+ function messageBusWire(eventTypes) {
268
+ const wires = new Set(
269
+ eventTypes.map((type) => EVENT_WIRES[type] ?? null)
270
+ );
271
+ if (wires.size !== 1) {
272
+ return null;
273
+ }
274
+ const [wire] = wires;
275
+ return wire ?? null;
276
+ }
277
+ function accountedEventTypes(entry) {
278
+ return [
279
+ ...entry.nonHttpEvents.map((e) => e.eventType),
280
+ ...entry.httpRoutes.filter((r) => r.method === "ANY").map((r) => r.eventType)
281
+ ];
282
+ }
283
+ function hasBindableRoute(entry) {
284
+ return entry.httpRoutes.some((r) => r.method !== "ANY");
285
+ }
286
+ function awsLambdaDiscovery(subjectFactories = []) {
287
+ return (sourceFile, ctx) => {
288
+ const sf = sourceFile;
289
+ const tsCtx = ctx;
290
+ const filePath = tsCtx.getFilePath(sf);
291
+ const entries = handlersForFile(filePath);
292
+ if (entries.length === 0) {
293
+ return [];
294
+ }
295
+ const exported = /* @__PURE__ */ new Map();
296
+ for (const { name, func } of tsCtx.exportedFunctions(sf)) {
297
+ exported.set(name, func);
298
+ }
299
+ const units = [];
300
+ for (const entry of entries) {
301
+ const func = exported.get(entry.exportName);
302
+ if (func === void 0) {
303
+ continue;
304
+ }
305
+ units.push(...httpRouteUnits(entry, func));
306
+ units.push(...graphqlResolverUnits(entry, func));
307
+ const unaccountedEvents = accountedEventTypes(entry);
308
+ const boundToNothing = !hasBindableRoute(entry) && operationFields(entry).length === 0;
309
+ if (unaccountedEvents.length > 0 || boundToNothing) {
310
+ units.push(
311
+ accountingUnit(
312
+ entry,
313
+ func,
314
+ subjectChannel(entry, sf, tsCtx, subjectFactories)
315
+ )
316
+ );
317
+ }
318
+ }
319
+ return units;
320
+ };
321
+ }
322
+ function awsLambdaFramework(options = {}) {
323
+ return {
324
+ name: "aws-lambda",
325
+ protocol: "http",
326
+ // Sentry's wrapper is a library call whose body the adapter cannot
327
+ // read, so the pack states the judgment: the handler is argument 0.
328
+ // Project-local wrappers need no declaration; the adapter derives
329
+ // those by reading the factory body.
330
+ transparentWrappers: [
331
+ {
332
+ callee: "Sentry.wrapHandler",
333
+ argument: 0,
334
+ module: "@sentry/aws-serverless"
335
+ }
336
+ ],
337
+ languages: ["typescript", "javascript"],
338
+ // No data-driven discovery: routing lives in the SAM/CFN template,
339
+ // not in code. The callback resolves handlers against the template.
340
+ discovery: [],
341
+ discoverUnits: awsLambdaDiscovery(options.subjectFactories ?? []),
342
+ // No import gate, on purpose.
343
+ //
344
+ // A TypeScript handler writes `import type { APIGatewayProxyHandlerV2 }
345
+ // from "aws-lambda"`, which TypeScript resolves to
346
+ // `@types/aws-lambda`, to annotate its export. A JavaScript handler
347
+ // has nothing to annotate and writes no such import. Gating
348
+ // discovery on it therefore meant "TypeScript handlers that bothered
349
+ // to annotate", and every JavaScript Lambda service extracted
350
+ // nothing.
351
+ //
352
+ // The SAM template is the gate instead, and a better one, because it
353
+ // says which handlers outright. `discoverUnits` looks each file up in
354
+ // the template reachable from it, and a directory with no template
355
+ // resolves to null once and stays memoized.
356
+ // Route units extract against these. Non-HTTP accounting units get their
357
+ // own wider list, attached per unit by the discovery callback.
358
+ terminals: HTTP_TERMINALS,
359
+ // Lambda's handler signature is `(event, context, callback?)`. The
360
+ // HTTP request data (path params, query, body) lives on `event`;
361
+ // decomposing it into typed inputs is a follow-up.
362
+ inputMapping: {
363
+ type: "positionalParams",
364
+ params: [
365
+ { position: 0, role: "event" },
366
+ { position: 1, role: "context" }
367
+ ]
368
+ },
369
+ // The powertools read their configuration from inside node_modules,
370
+ // so a template that declares these has a reader no walk ever sees.
371
+ libraryEnvVars: [
372
+ {
373
+ module: "@aws-lambda-powertools/",
374
+ prefixes: ["POWERTOOLS_"]
375
+ }
376
+ ]
377
+ };
378
+ }
379
+ var index_default = awsLambdaFramework;
380
+ export {
381
+ index_default as default
382
+ };
@@ -0,0 +1 @@
1
+ export { default } from '@suss/framework-aws-s3';
package/dist/aws-s3.js ADDED
@@ -0,0 +1,120 @@
1
+ // ../framework/aws-s3/dist/index.js
2
+ import { Node as N } from "ts-morph";
3
+ import { readName, rootIdentifier } from "@suss/adapter-typescript";
4
+ import { storageBinding } from "@suss/behavioral-ir";
5
+ var RECOGNITION = "@suss/framework-aws-s3";
6
+ var COMMAND_MODULE = "@aws-sdk/client-s3";
7
+ var COMMANDS = {
8
+ GetObjectCommand: "read",
9
+ HeadObjectCommand: "read",
10
+ ListObjectsV2Command: "read",
11
+ ListObjectsCommand: "read",
12
+ PutObjectCommand: "write",
13
+ DeleteObjectCommand: "write",
14
+ DeleteObjectsCommand: "write",
15
+ CopyObjectCommand: "write",
16
+ // A large object goes up in parts, and each command in that sequence
17
+ // writes the same object.
18
+ CreateMultipartUploadCommand: "write",
19
+ UploadPartCommand: "write",
20
+ CompleteMultipartUploadCommand: "write",
21
+ AbortMultipartUploadCommand: "write"
22
+ };
23
+ function s3Recognizer(call, ctx) {
24
+ const callNode = call;
25
+ const recognizerCtx = ctx;
26
+ const resolve = recognizerCtx.resolveWrittenValue ?? (() => null);
27
+ const built = commandArgument(callNode, resolve);
28
+ if (built === null) {
29
+ return null;
30
+ }
31
+ const ctorExpr = built.getExpression();
32
+ const commandName = N.isPropertyAccessExpression(ctorExpr) ? ctorExpr.getName() : ctorExpr.getText();
33
+ const kind = COMMANDS[commandName];
34
+ if (kind === void 0 || !fromS3Module(ctorExpr, recognizerCtx)) {
35
+ return null;
36
+ }
37
+ const input = built.getArguments()[0];
38
+ if (input === void 0 || !N.isObjectLiteralExpression(input)) {
39
+ return null;
40
+ }
41
+ const key = nameOfProperty(input, "Key", resolve);
42
+ const prefix = nameOfProperty(input, "Prefix", resolve);
43
+ const addressed = key ?? prefix;
44
+ return [
45
+ {
46
+ type: "interaction",
47
+ binding: storageBinding({
48
+ recognition: RECOGNITION,
49
+ storageSystem: "s3",
50
+ transport: "aws-sdk",
51
+ scope: "default",
52
+ container: nameOfProperty(input, "Bucket", resolve),
53
+ accessPath: null
54
+ }),
55
+ callee: callNode.getExpression().getText(),
56
+ interaction: {
57
+ class: "storage-access",
58
+ kind,
59
+ // An object has no fields, so a call says nothing about any.
60
+ fields: [],
61
+ operation: commandName,
62
+ ...addressed !== null ? { selector: [addressed] } : {}
63
+ }
64
+ }
65
+ ];
66
+ }
67
+ function commandArgument(call, resolve) {
68
+ for (const arg of call.getArguments()) {
69
+ if (N.isNewExpression(arg)) {
70
+ return arg;
71
+ }
72
+ if (!N.isIdentifier(arg) || !typedAsCommand(arg)) {
73
+ continue;
74
+ }
75
+ const written = resolve(arg);
76
+ if (written !== null && N.isNewExpression(written)) {
77
+ return written;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ function typedAsCommand(value) {
83
+ const declared = value.getType().getSymbol();
84
+ return declared !== void 0 && COMMANDS[declared.getName()] !== void 0;
85
+ }
86
+ function fromS3Module(ctorExpr, ctx) {
87
+ const target = N.isPropertyAccessExpression(ctorExpr) ? rootIdentifier(ctorExpr) : ctorExpr;
88
+ return target !== null && ctx.isImportedFrom(target, COMMAND_MODULE);
89
+ }
90
+ function nameOfProperty(input, name, resolve) {
91
+ const written = property(input, name);
92
+ return written === null ? null : readName(written, { resolve, unsettled: "reference" });
93
+ }
94
+ function property(input, name) {
95
+ if (!N.isObjectLiteralExpression(input)) {
96
+ return null;
97
+ }
98
+ for (const prop of input.getProperties()) {
99
+ if (N.isPropertyAssignment(prop) && prop.getName() === name) {
100
+ return prop.getInitializer() ?? null;
101
+ }
102
+ }
103
+ return null;
104
+ }
105
+ function s3Framework() {
106
+ return {
107
+ name: "aws-s3",
108
+ protocol: "s3",
109
+ languages: ["typescript", "javascript"],
110
+ discovery: [],
111
+ terminals: [],
112
+ inputMapping: { type: "positionalParams", params: [] },
113
+ requiresImport: [COMMAND_MODULE],
114
+ invocationRecognizers: [s3Recognizer]
115
+ };
116
+ }
117
+ var index_default = s3Framework;
118
+ export {
119
+ index_default as default
120
+ };
@@ -0,0 +1 @@
1
+ export { default } from '@suss/framework-aws-sqs';