@suss/packs 0.33.0 → 0.34.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/README.md +10 -11
  2. package/dist/activerecord.d.ts +7 -0
  3. package/dist/activerecord.js +34 -4
  4. package/dist/apollo-client.js +26 -39
  5. package/dist/apollo.js +12 -20
  6. package/dist/aws-dynamodb.js +4 -4
  7. package/dist/aws-eventbridge.js +3 -3
  8. package/dist/aws-lambda.js +31 -54
  9. package/dist/aws-s3.js +1 -1
  10. package/dist/aws-secrets-manager.js +1 -1
  11. package/dist/aws-sns.js +2 -2
  12. package/dist/aws-sqs.js +20 -11
  13. package/dist/aws-ssm.js +1 -1
  14. package/dist/axios.js +13 -27
  15. package/dist/bigquery-python.js +3 -3
  16. package/dist/bigquery.js +1 -1
  17. package/dist/{chunk-NYWZHQBJ.js → chunk-GK4BILHB.js} +8 -9
  18. package/dist/cloudflare-workers.js +26 -35
  19. package/dist/drizzle.js +15 -20
  20. package/dist/express.js +3 -6
  21. package/dist/fabrication.d.ts +1 -0
  22. package/dist/fabrication.js +36 -0
  23. package/dist/factory-bot.d.ts +1 -0
  24. package/dist/factory-bot.js +36 -0
  25. package/dist/faraday.js +4 -5
  26. package/dist/fastapi.js +21 -5
  27. package/dist/fastify.js +10 -14
  28. package/dist/fetch.js +3 -4
  29. package/dist/flask-restx.js +19 -12
  30. package/dist/gcs.js +1 -1
  31. package/dist/graphql-ruby.js +20 -16
  32. package/dist/hono.js +17 -27
  33. package/dist/httpx.js +3 -5
  34. package/dist/minitest.d.ts +1 -0
  35. package/dist/minitest.js +59 -0
  36. package/dist/mongoose.js +7 -7
  37. package/dist/nestjs-graphql.js +14 -25
  38. package/dist/nestjs-microservices.js +9 -10
  39. package/dist/nestjs-rest.js +42 -22
  40. package/dist/net-http.js +5 -6
  41. package/dist/nextjs.js +16 -17
  42. package/dist/node.js +144 -28
  43. package/dist/package-exports.js +1 -1
  44. package/dist/pg.js +3 -2
  45. package/dist/prisma.js +281 -48
  46. package/dist/pytest.d.ts +1 -0
  47. package/dist/pytest.js +81 -0
  48. package/dist/rails.js +46 -27
  49. package/dist/react-query.js +1 -1
  50. package/dist/react-router.js +21 -48
  51. package/dist/react.js +14 -5
  52. package/dist/redis.js +1 -1
  53. package/dist/requests.js +4 -5
  54. package/dist/rspec.d.ts +1 -0
  55. package/dist/rspec.js +135 -0
  56. package/dist/sqlalchemy.js +1 -1
  57. package/dist/sqlmodel.js +7 -8
  58. package/dist/ts-rest.js +6 -8
  59. package/dist/typeorm.d.ts +1 -0
  60. package/dist/typeorm.js +291 -0
  61. package/dist/vitest.d.ts +1 -0
  62. package/dist/vitest.js +64 -0
  63. package/package.json +47 -11
package/dist/aws-sqs.js CHANGED
@@ -1,7 +1,11 @@
1
1
  // ../framework/aws-sqs/dist/index.js
2
2
  import { Node as N } from "ts-morph";
3
3
  import { z } from "zod";
4
- import { climbSyntax, readConfiguredCall } from "@suss/adapter-typescript";
4
+ import {
5
+ climbSyntax,
6
+ readConfiguredCall,
7
+ receiverTypesOf
8
+ } from "@suss/adapter-typescript";
5
9
  import { messageBusBinding } from "@suss/behavioral-ir";
6
10
  import { configuredCallOption } from "@suss/extractor";
7
11
  import { constructedFrom, messageSends, pack } from "@suss/recognize";
@@ -35,7 +39,7 @@ function sendDeclarations() {
35
39
  wire: "aws_sqs",
36
40
  client: constructedFrom(SQS),
37
41
  messages: { each: "in", property: "Entries" },
38
- // A batch states the queue once beside the list of messages.
42
+ // The queue is on the command input, once, beside the list of messages.
39
43
  channel: [{ property: ["QueueUrl"], on: "theInput" }],
40
44
  body: "MessageBody"
41
45
  }).methods(INSIDE_THE_COMMAND(["SendMessageBatchCommand"])).example(
@@ -72,16 +76,15 @@ function messageReceiveRecognizer(call, ctx) {
72
76
  if (!N.isIdentifier(recordExpr)) {
73
77
  return null;
74
78
  }
75
- if (!isSqsRecordIdentifier(recordExpr)) {
79
+ if (!isSqsRecordIdentifier(recordExpr) && !isTypedSqsRecord(recordExpr)) {
76
80
  return null;
77
81
  }
78
82
  const fields = extractDestructuredFields(callNode);
79
83
  return [
80
84
  {
81
85
  type: "interaction",
82
- // Channel intentionally null: the queue a handler drains is
83
- // stated by the CFN event-source mapping, so this side does not
84
- // name it and the pairing pass joins by codeScope instead.
86
+ // The CloudFormation event source mapping sets the queue, so the
87
+ // pairing pass joins this effect to its consumer by code scope.
85
88
  binding: messageBusBinding({
86
89
  recognition: "@suss/framework-aws-sqs",
87
90
  messageBus: "aws_sqs",
@@ -122,6 +125,12 @@ function isSqsRecordIdentifier(recordExpr) {
122
125
  }
123
126
  return false;
124
127
  }
128
+ function isTypedSqsRecord(recordExpr) {
129
+ return receiverTypesOf(recordExpr).some((type) => isLambdaSqsRecord(type));
130
+ }
131
+ function isLambdaSqsRecord(type) {
132
+ return type.name === "SQSRecord" && type.declaredIn.some((filePath) => filePath.includes("/aws-lambda/"));
133
+ }
125
134
  function extractDestructuredFields(call) {
126
135
  const parent = climbSyntax(call).getParent();
127
136
  if (parent === void 0 || !N.isVariableDeclaration(parent)) {
@@ -153,8 +162,8 @@ function extractDestructuredFields(call) {
153
162
  }
154
163
  var optionsSchema = z.object({
155
164
  /**
156
- * Dispatchers this project sends through. Each one adds a
157
- * recognizer and widens the import gate to the module it points at.
165
+ * Each dispatcher adds a recognizer, and the pack also reads files
166
+ * that import the dispatcher's module.
158
167
  */
159
168
  producers: z.array(configuredCallOption).optional()
160
169
  }).strict();
@@ -191,8 +200,8 @@ function sqsFramework(options = {}) {
191
200
  languages: ["typescript", "javascript"],
192
201
  recognizedAs: "@suss/framework-aws-sqs",
193
202
  protocol: "sqs",
194
- // The consumer side reads SQSEvent handlers, whose files import
195
- // aws-lambda rather than the SQS client.
203
+ // A handler file imports `SQSEvent` from aws-lambda and often never
204
+ // imports the SQS client.
196
205
  requiresImport: ["aws-lambda", ...producers.map((p) => p.module)],
197
206
  recognizers: [
198
207
  messageReceiveRecognizer,
@@ -204,7 +213,7 @@ var declares = {
204
213
  kind: "effects",
205
214
  package: "@suss/framework-aws-sqs",
206
215
  dependencies: [{ ecosystem: "npm", name: "@aws-sdk/client-sqs" }],
207
- reads: "AWS SDK v3 SQS producer calls, emits message-send interactions."
216
+ reads: "AWS SDK v3 SQS producer calls. Each one becomes a message-send interaction."
208
217
  };
209
218
  var index_default = sqsFramework;
210
219
  export {
package/dist/aws-ssm.js CHANGED
@@ -29,7 +29,7 @@ var declares = {
29
29
  kind: "effects",
30
30
  package: "@suss/framework-aws-ssm",
31
31
  dependencies: [{ ecosystem: "npm", name: "@aws-sdk/client-ssm" }],
32
- reads: "AWS SSM Parameter Store calls, emits storage-access interactions against the parameter."
32
+ reads: "AWS SSM Parameter Store calls. Each one becomes a storage-access interaction on the parameter."
33
33
  };
34
34
  var index_default = ssmFramework;
35
35
  export {
package/dist/axios.js CHANGED
@@ -17,23 +17,18 @@ var clientFactory = z.object({
17
17
  }).strict();
18
18
  var optionsSchema = z.object({
19
19
  /**
20
- * Project functions this project builds axios instances through,
21
- * beyond the built-in axios.create. Each site importing one of
22
- * these and calling it is a client instance the same way an
23
- * axios.create() result is, wherever the call it delegates to
24
- * lives.
20
+ * Project functions that build axios instances, in addition to
21
+ * axios.create. Calling one of these gives a client instance, the
22
+ * same as an axios.create() result.
25
23
  */
26
24
  factories: z.array(clientFactory).optional()
27
25
  }).strict();
28
26
  function discoveryForVerb(verb, factories) {
29
27
  const patterns = [
30
28
  {
31
- // Matches both shapes:
32
- // axios.<verb>("/path", ...)
33
- // const api = axios.create({ ... }); api.<verb>("/path", ...)
34
- // The factoryMethods entry tells the adapter that variables initialized
35
- // from axios.create(...) are also clients, wherever that call and
36
- // this one turn out to live.
29
+ // Matches `axios.<verb>(path)` and `api.<verb>(path)`. factoryMethods
30
+ // makes a variable set from axios.create(...) a client too, even
31
+ // when it is created in another file.
37
32
  kind: "client",
38
33
  match: {
39
34
  type: "clientCall",
@@ -64,18 +59,9 @@ function discoveryForVerb(verb, factories) {
64
59
  method: { type: "literal", value: verb.toUpperCase() },
65
60
  path: { type: "fromArgument", position: 0 }
66
61
  },
67
- // A bare specifier gates the way "axios" above does: cheap and
68
- // exact, since two files spelling a package name the same way
69
- // mean the same package. A path-shaped module ("./apiClient")
70
- // points at a location relative to wherever it's written, and the
71
- // pre-filter only ever reads a file's own import text before
72
- // anything is parsed, so it has no way to tell "./apiClient"
73
- // and a consumer's "../apiClient" apart from string text alone.
74
- // Narrowing on that string would exclude the consumer roughly
75
- // as often as include it, so a path-shaped factory gives no
76
- // gate at all and every file is walked; the discovery layer
77
- // resolves the module correctly once it's reading a file's
78
- // imports against a parsed project.
62
+ // Each importing file spells a relative path differently, so the
63
+ // import-text prefilter cannot match one and a path-shaped factory
64
+ // walks every file. The README has the details.
79
65
  requiresImport: isPathShapedSpecifier(factory.module) ? [] : [factory.module]
80
66
  });
81
67
  }
@@ -153,13 +139,13 @@ function axiosPack(options = {}) {
153
139
  params: []
154
140
  },
155
141
  responseSemantics: [
156
- // axios returns AxiosResponse: body lives on .data, not .body or .json()
142
+ // axios puts the parsed response body on `.data`.
157
143
  { name: "data", access: "property", semantics: { type: "body" } },
158
144
  { name: "status", access: "property", semantics: { type: "statusCode" } },
159
145
  { name: "headers", access: "property", semantics: { type: "headers" } }
160
146
  ],
161
- // A non-2xx rejects, so the caller never gets a response to read a
162
- // status off, and its catch is where every failure arrives.
147
+ // axios rejects on a non-2xx status, so every failure reaches the
148
+ // caller's catch block instead of coming back as a response.
163
149
  failureDelivery: "exception"
164
150
  };
165
151
  }
@@ -167,7 +153,7 @@ var declares = {
167
153
  kind: "client",
168
154
  package: "@suss/client-axios",
169
155
  dependencies: [{ ecosystem: "npm", name: "axios" }],
170
- reads: "axios call sites + `axios.create` factories."
156
+ reads: "axios call sites, including calls on a client that `axios.create` made."
171
157
  };
172
158
  var index_default = axiosPack;
173
159
  export {
@@ -88,8 +88,8 @@ function bigqueryClients() {
88
88
  clientTypes: ["BigQueryHook"],
89
89
  statements: HOOK_STATEMENTS,
90
90
  tables: HOOK_TABLES,
91
- // The hook documents `get_client` as handing back the client
92
- // library's own `Client`, so a chain off it reads as one.
91
+ // The hook documents `get_client` as returning the client library's
92
+ // own `Client`, so a chain off it is read the same way.
93
93
  handsBack: [
94
94
  { method: "get_client", module: CLIENT_MODULE, name: "Client" }
95
95
  ],
@@ -113,7 +113,7 @@ var declares = {
113
113
  { ecosystem: "pypi", name: "google-cloud-bigquery" },
114
114
  { ecosystem: "pypi", name: "apache-airflow-providers-google" }
115
115
  ],
116
- reads: "BigQuery calls (Python): the statement a client or an Airflow hook is handed, read for the tables it touches, and the calls that say which table without writing SQL."
116
+ reads: "BigQuery calls (Python). The statement a client or an Airflow hook is handed is read for the tables it touches. Calls such as `get_table` and `insert_rows` take a table name instead of SQL, and that name is read too."
117
117
  };
118
118
  var index_default = bigqueryFramework;
119
119
  export {
package/dist/bigquery.js CHANGED
@@ -45,7 +45,7 @@ var declares = {
45
45
  kind: "effects",
46
46
  package: "@suss/framework-bigquery",
47
47
  dependencies: [{ ecosystem: "npm", name: "@google-cloud/bigquery" }],
48
- reads: "BigQuery queries and table calls, emits storage-access interactions with the dataset as the scope."
48
+ reads: "BigQuery queries and table calls. Each one becomes a storage-access interaction scoped to its dataset."
49
49
  };
50
50
  var index_default = bigqueryFramework;
51
51
  export {
@@ -3,9 +3,8 @@ import { z } from "zod";
3
3
  import { storageSystemOption } from "@suss/extractor";
4
4
  var optionsSchema = z.object({
5
5
  /**
6
- * Which database is behind the connection. SQLAlchemy talks to all of
7
- * them and the URL says which, so this is the project's own choice
8
- * rather than something the library settles.
6
+ * Which database is behind the engine. SQLAlchemy works with several,
7
+ * and the connection URL picks one, so the project has to set this.
9
8
  */
10
9
  storageSystem: storageSystemOption
11
10
  }).strict();
@@ -26,8 +25,8 @@ function sqlalchemyStorage(options) {
26
25
  "bulk_insert_mappings",
27
26
  "bulk_update_mappings"
28
27
  ],
29
- // A Session runs a statement built by `select`/`update`/... whose
30
- // own chain is read, and the rest of these manage the session.
28
+ // `execute` and the scalar calls run a statement whose own chain is
29
+ // read already, and the rest only manage the session.
31
30
  recordsNothing: [
32
31
  "execute",
33
32
  "scalars",
@@ -46,9 +45,9 @@ function sqlalchemyStorage(options) {
46
45
  module: "sqlalchemy",
47
46
  queryTypes: ["Select", "Update", "Delete", "Insert"],
48
47
  writes: ["update", "delete", "insert", "commit"],
49
- // 2.0 style writes `select(User.id).where(...)`, importing the
50
- // constructor rather than reaching it through a mapped class, so there
51
- // is no project method in between whose return says what it is.
48
+ // A 2.0 statement such as `select(User.id).where(...)` calls an
49
+ // imported constructor directly, so there is no project method in
50
+ // the chain whose return type the adapter could read.
52
51
  queryFunctions: ["select", "insert", "update", "delete"],
53
52
  valueMethods: ["values"],
54
53
  storageSystem: options.storageSystem
@@ -129,7 +128,7 @@ var declares = {
129
128
  kind: "effects",
130
129
  package: "@suss/framework-sqlalchemy",
131
130
  dependencies: [{ ecosystem: "pypi", name: "sqlalchemy" }],
132
- reads: `SQLAlchemy calls (Python): says which types a query comes back as and which methods write, and the adapter matches a call chain by resolving through a project's own base class to what the method behind it says it returns.`,
131
+ reads: `SQLAlchemy calls (Python). The pack declares which types a query returns and which methods write. The adapter matches a call chain by following a project's own base class to the method behind the call, and reads that method's declared return type.`,
133
132
  configuration: {
134
133
  file: "suss.sqlalchemy.json",
135
134
  example: { storageSystem: "postgresql" },
@@ -15,7 +15,11 @@ import {
15
15
  } from "@suss/adapter-typescript";
16
16
  import { runtimeConfigBinding } from "@suss/behavioral-ir";
17
17
  import { Node as N3 } from "ts-morph";
18
- import { readName as readName2, stringValueOf } from "@suss/adapter-typescript";
18
+ import {
19
+ readName as readName2,
20
+ receiverTypesOf,
21
+ stringValueOf
22
+ } from "@suss/adapter-typescript";
19
23
  import { storageBinding } from "@suss/behavioral-ir";
20
24
  import { readSqlAccess } from "@suss/sql";
21
25
  var TRIGGERS = {
@@ -163,11 +167,10 @@ function listenerAt(node) {
163
167
  var ENV_PARAMETER_POSITION = 1;
164
168
  var readName = (name) => `env.${name}`;
165
169
  function envBindingRecognizer(options = {}) {
166
- const instanceName = options.scriptName ?? "<unknown>";
167
170
  return (access, ctx) => {
168
171
  const { resolution } = ctx;
169
172
  const read = envReadAt(access, resolution);
170
- return read === null ? null : [configReadEffect(read, instanceName)];
173
+ return read === null ? null : [configReadEffect(read, options.scriptName)];
171
174
  };
172
175
  }
173
176
  function envReadAt(node, resolution) {
@@ -222,7 +225,7 @@ function configReadEffect(read, instanceName) {
222
225
  binding: runtimeConfigBinding({
223
226
  recognition: "@suss/framework-cloudflare-workers",
224
227
  deploymentTarget: "worker",
225
- instanceName
228
+ ...instanceName === void 0 ? {} : { instanceName }
226
229
  }),
227
230
  callee: readName(read.name),
228
231
  interaction: {
@@ -297,10 +300,7 @@ function storeBindingRecognizer(call, ctx) {
297
300
  if (binding === null) {
298
301
  return null;
299
302
  }
300
- const store = STORES[binding.typeName];
301
- if (store === void 0) {
302
- return null;
303
- }
303
+ const { store } = binding;
304
304
  const method = callee.getName();
305
305
  const access = store.read(method, callNode, reading);
306
306
  if (access === null) {
@@ -320,8 +320,8 @@ function storeBindingRecognizer(call, ctx) {
320
320
  interaction: {
321
321
  class: "storage-access",
322
322
  kind: access.kind,
323
- // KV and R2 keep opaque values and a D1 statement is judged at
324
- // database level, so no call states a field.
323
+ // KV and R2 values are opaque, and a D1 statement is read at the
324
+ // database level, so a call never records a field.
325
325
  fields: [],
326
326
  operation: method,
327
327
  ...access.selector !== void 0 ? { selector: [access.selector] } : {}
@@ -344,11 +344,16 @@ function boundReceiver(subject, reading) {
344
344
  if (!N3.isIdentifier(env) || !isTriggerEnvArgument(env, reading.resolution)) {
345
345
  return null;
346
346
  }
347
- const typeName = declaredTypeName(receiver);
348
- if (typeName === null) {
349
- return null;
347
+ const store = storeTypedOn(receiver);
348
+ return store === null ? null : { name: receiver.getName(), store };
349
+ }
350
+ function storeTypedOn(receiver) {
351
+ for (const type of receiverTypesOf(receiver)) {
352
+ if (Object.hasOwn(STORES, type.name)) {
353
+ return STORES[type.name];
354
+ }
350
355
  }
351
- return { name: receiver.getName(), typeName };
356
+ return null;
352
357
  }
353
358
  function declaredInitializer(identifier) {
354
359
  for (const declaration of identifier.getSymbol()?.getDeclarations() ?? []) {
@@ -358,25 +363,11 @@ function declaredInitializer(identifier) {
358
363
  }
359
364
  return null;
360
365
  }
361
- function declaredTypeName(receiver) {
362
- const declarations = receiver.getNameNode().getSymbol()?.getDeclarations() ?? [];
363
- for (const declaration of declarations) {
364
- if (!N3.isPropertySignature(declaration)) {
365
- continue;
366
- }
367
- const typeNode = declaration.getTypeNode();
368
- if (typeNode !== void 0) {
369
- return typeNode.getText();
370
- }
371
- }
372
- return null;
373
- }
374
366
  var optionsSchema = z.object({
375
367
  /**
376
- * The name the deployment gives this Worker, which is `name` in
377
- * `wrangler.toml`. Supplying it stamps every unit with the deployable
378
- * it runs as, so a runtime-config provider pairs by unit instead of by
379
- * directory. Left out, the directory decides.
368
+ * The Worker's `name` from `wrangler.toml`. When it is set, every unit
369
+ * records it as its deployable, so a runtime-config provider pairs
370
+ * with the Worker by name. Without it, units pair by directory.
380
371
  */
381
372
  scriptName: z.string().optional()
382
373
  }).strict();
@@ -385,14 +376,14 @@ function cloudflareWorkersFramework(options = {}) {
385
376
  name: "cloudflare-workers",
386
377
  protocol: "http",
387
378
  languages: ["typescript", "javascript"],
388
- // Nothing is registered in code, so there is no data-driven
389
- // discovery to declare.
379
+ // A Worker registers nothing in code, so the pattern list stays empty
380
+ // and the callback reads the exported object.
390
381
  discovery: [],
391
382
  discoverUnits: cloudflareWorkersDiscovery(options),
392
383
  terminals: [
393
384
  {
394
- // new Response(body, { status }), which is what a Worker returns
395
- // for everything: JSON, a redirect, a stream, plain text.
385
+ // new Response(body, { status }). A Worker returns a Response for
386
+ // JSON, a redirect, a stream and plain text alike.
396
387
  kind: "response",
397
388
  match: { type: "functionCall", functionName: "Response" },
398
389
  extraction: {
package/dist/drizzle.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  propertiesOf,
8
8
  propertyNameOf,
9
9
  propertyOf,
10
+ receiverTypesOf,
10
11
  stringValueOf,
11
12
  writtenNodeOf
12
13
  } from "@suss/adapter-typescript";
@@ -25,12 +26,12 @@ var DRIZZLE_PACKAGE = "drizzle-orm";
25
26
  var CHAIN_WALK_LIMIT = 12;
26
27
  var optionsSchema = z.object({
27
28
  /**
28
- * Storage system the recognized calls target. Must match the
29
- * `storageSystem` on provider summaries for pairing keys to line
30
- * up. Defaults to `"postgresql"`, the dominant Drizzle deployment.
29
+ * The storage system the calls target, `"postgresql"` when unset. It
30
+ * has to match the `storageSystem` on provider summaries, or the
31
+ * calls do not pair.
31
32
  */
32
33
  storageSystem: storageSystemOption.optional(),
33
- /** Scope label for the storage binding. Defaults to `"default"`. */
34
+ /** Scope for the storage binding, `"default"` when unset. */
34
35
  scope: scopeOption.optional()
35
36
  }).strict();
36
37
  function rawStatements(opts) {
@@ -159,7 +160,7 @@ function recognizeQueryApi(call, receiver, operation, resolution) {
159
160
  if (!isDrizzleReceiver(queryAccess.getExpression())) {
160
161
  return null;
161
162
  }
162
- const table = resolveTableName(receiver, resolution) ?? receiver.getName();
163
+ const table = resolveTableName(receiver, resolution);
163
164
  const optionsArg = call.getArguments()[0];
164
165
  const options = optionsArg === void 0 ? null : objectLiteralOf(optionsArg, resolution);
165
166
  const fields = [];
@@ -200,17 +201,12 @@ function collectChainCalls(anchor) {
200
201
  return chain;
201
202
  }
202
203
  function isDrizzleReceiver(node) {
203
- const type = node.getType();
204
- const symbol = type.getSymbol() ?? type.getAliasSymbol();
205
- if (symbol === void 0) {
206
- return false;
207
- }
208
- for (const decl of symbol.getDeclarations()) {
209
- if (decl.getSourceFile().getFilePath().includes("/drizzle-orm/")) {
210
- return true;
211
- }
212
- }
213
- return false;
204
+ return receiverTypesOf(node).some((type) => declaredByDrizzle(type));
205
+ }
206
+ function declaredByDrizzle(type) {
207
+ return type.declaredIn.some(
208
+ (filePath) => filePath.includes(`/${DRIZZLE_PACKAGE}/`)
209
+ );
214
210
  }
215
211
  function resolveTableName(tableExpr, resolution) {
216
212
  const written = writtenNodeOf(tableExpr, resolution);
@@ -293,9 +289,8 @@ function drizzleFramework(options = {}) {
293
289
  discovery: [],
294
290
  terminals: [],
295
291
  inputMapping: { type: "positionalParams", params: [] },
296
- // Gate on drizzle-orm imports (matches subpaths like
297
- // drizzle-orm/pg-core and driver entry points): files without
298
- // them can't type-check as Drizzle receivers anyway.
292
+ // A file with no `drizzle-orm` import, subpaths included, has no
293
+ // value whose type Drizzle declares.
299
294
  requiresImport: ["drizzle-orm"],
300
295
  invocationRecognizers: [makeRecognizer(options)],
301
296
  // A statement can be written as a tagged template, which the
@@ -308,7 +303,7 @@ var declares = {
308
303
  kind: "effects",
309
304
  package: "@suss/framework-drizzle",
310
305
  dependencies: [{ ecosystem: "npm", name: "drizzle-orm" }],
311
- reads: "Drizzle ORM query-builder and relational-query calls, emits storage-access interactions with SQL table names."
306
+ reads: "Drizzle ORM query-builder and relational-query calls. Each one becomes a storage-access interaction on the SQL table it touches."
312
307
  };
313
308
  var index_default = drizzleFramework;
314
309
  export {
package/dist/express.js CHANGED
@@ -43,12 +43,9 @@ function expressFramework(_options = {}) {
43
43
  name: "express",
44
44
  protocol: "http",
45
45
  languages: ["typescript", "javascript"],
46
- // Express exposes the routable via either `Router()` (named) or
47
- // `express()` (default). Both drive handler registration the same
48
- // way; `httpRouteDiscovery` emits one DiscoveryPattern per name.
49
- // Either can also be mounted onto another with `app.use(prefix,
50
- // router)`, so a route declared on the mounted router summarizes
51
- // with the mount's prefix composed in.
46
+ // Routes go on `Router()` or on `express()`, and either can be mounted
47
+ // with `app.use(prefix, router)`. A route on a mounted router is
48
+ // summarized with the mount's prefix in front of its path.
52
49
  discovery: [
53
50
  ...httpRouteDiscovery({
54
51
  importModule: "express",
@@ -0,0 +1 @@
1
+ export { declares, default, optionsSchema } from '@suss/framework-fabrication';
@@ -0,0 +1,36 @@
1
+ // ../framework/fabrication/dist/index.js
2
+ import { z } from "zod";
3
+ var optionsSchema = z.object({}).strict();
4
+ var LIBRARY = "Fabricate";
5
+ function fabricationFramework(_options = {}) {
6
+ return {
7
+ name: "fabrication",
8
+ protocol: "in-process",
9
+ discovery: [],
10
+ factories: [
11
+ {
12
+ definitionMethods: ["Fabricator"],
13
+ classKeywords: ["class_name"],
14
+ parentKeywords: ["from"],
15
+ nestedDefinitionsInherit: false,
16
+ builders: [
17
+ { method: "Fabricate" },
18
+ { method: "build", receiver: LIBRARY },
19
+ { method: "create", receiver: LIBRARY }
20
+ ]
21
+ }
22
+ ]
23
+ };
24
+ }
25
+ var declares = {
26
+ kind: "framework",
27
+ package: "@suss/framework-fabrication",
28
+ dependencies: [],
29
+ reads: "Fabrication builds in tests (Ruby), typed by the class each fabricator builds, so a test's calls on them are followed."
30
+ };
31
+ var index_default = fabricationFramework;
32
+ export {
33
+ declares,
34
+ index_default as default,
35
+ optionsSchema
36
+ };
@@ -0,0 +1 @@
1
+ export { declares, default, optionsSchema } from '@suss/framework-factory-bot';
@@ -0,0 +1,36 @@
1
+ // ../framework/factory-bot/dist/index.js
2
+ import { z } from "zod";
3
+ var optionsSchema = z.object({}).strict();
4
+ var BUILDS = ["create", "build", "build_stubbed"];
5
+ var LIBRARY = "FactoryBot";
6
+ function factoryBotFramework(_options = {}) {
7
+ return {
8
+ name: "factory-bot",
9
+ protocol: "in-process",
10
+ discovery: [],
11
+ factories: [
12
+ {
13
+ definitionMethods: ["factory"],
14
+ classKeywords: ["class"],
15
+ parentKeywords: ["parent"],
16
+ nestedDefinitionsInherit: true,
17
+ builders: [
18
+ ...BUILDS.map((method) => ({ method })),
19
+ ...BUILDS.map((method) => ({ method, receiver: LIBRARY }))
20
+ ]
21
+ }
22
+ ]
23
+ };
24
+ }
25
+ var declares = {
26
+ kind: "framework",
27
+ package: "@suss/framework-factory-bot",
28
+ dependencies: [],
29
+ reads: "factory_bot builds in tests (Ruby), typed by the class each factory builds, so a test's calls on them are followed."
30
+ };
31
+ var index_default = factoryBotFramework;
32
+ export {
33
+ declares,
34
+ index_default as default,
35
+ optionsSchema
36
+ };
package/dist/faraday.js CHANGED
@@ -17,14 +17,13 @@ function faradayClient() {
17
17
  {
18
18
  constantName: "Faraday",
19
19
  verbMethodNames: VERB_METHODS,
20
- // Every one of them takes the URL first.
21
20
  url: { position: 0 },
22
21
  receiverBuilders: ["new"],
23
- // `Faraday.new(url: "https://api.example.com/v1")` serves every
24
- // call on that connection under the path of its own URL.
22
+ // Calls on a connection from `Faraday.new(url: ".../v1")` get the
23
+ // path of that URL in front of their own.
25
24
  builderUrlKeyword: "url",
26
- // What a caller reads off the response it got back. A test on
27
- // one of these says which statuses the caller handles.
25
+ // A caller's condition on one of these members shows which
26
+ // statuses it handles.
28
27
  response: {
29
28
  statusCode: ["status"],
30
29
  success: ["success?"],
package/dist/fastapi.js CHANGED
@@ -2,10 +2,9 @@
2
2
  import { z } from "zod";
3
3
  var optionsSchema = z.object({
4
4
  /**
5
- * Modules a project's own wrapper re-exports FastAPI's constructors
6
- * from. FastAPI's own module is always accepted. The wrapper's name
7
- * is the project's own choice, so it is supplied by whoever
8
- * configures the pack rather than hardcoded here.
5
+ * Modules that re-export FastAPI's constructors, on top of `fastapi`
6
+ * itself. A dependency stub fills this in; a project's config file
7
+ * may not set it.
9
8
  */
10
9
  wrapperModules: z.array(z.string()).optional()
11
10
  }).strict();
@@ -18,10 +17,25 @@ var VERB_ATTRIBUTE_NAMES = {
18
17
  head: "HEAD",
19
18
  options: "OPTIONS"
20
19
  };
20
+ var PARAMETER_SOURCES = {
21
+ Header: { role: "headers", underscoresAs: "-" },
22
+ Query: { role: "queryParams" },
23
+ Path: { role: "pathParams" },
24
+ Body: { role: "requestBody" },
25
+ Form: { role: "requestBody" },
26
+ Cookie: { role: "cookies" }
27
+ };
28
+ var REQUEST_SPELLING = {
29
+ headers: { path: ["headers"], saysWhichField: true },
30
+ query: { path: ["queryParams"], saysWhichField: true },
31
+ params: { path: ["pathParams"], saysWhichField: true },
32
+ body: { path: ["requestBody"], saysWhichField: true }
33
+ };
21
34
  function fastapiFramework(options = {}) {
22
35
  return {
23
36
  name: "fastapi",
24
37
  protocol: "http",
38
+ requestSpelling: REQUEST_SPELLING,
25
39
  ...options.wrapperModules !== void 0 ? { projectModules: options.wrapperModules } : {},
26
40
  discovery: [
27
41
  {
@@ -33,6 +47,8 @@ function fastapiFramework(options = {}) {
33
47
  // FastAPI resolves both of these itself and calls the handler with
34
48
  // the result, so a parameter defaulted to one is never sent.
35
49
  injectedParameterCallees: ["Depends", "Security"],
50
+ parameterSources: PARAMETER_SOURCES,
51
+ parameterAliasKeyword: "alias",
36
52
  // FastAPI returns 200 for a route that declares no status.
37
53
  defaultStatusCode: 200,
38
54
  responseModelKeyword: "response_model",
@@ -106,7 +122,7 @@ var declares = {
106
122
  kind: "framework",
107
123
  package: "@suss/framework-fastapi",
108
124
  dependencies: [{ ecosystem: "pypi", name: "fastapi" }],
109
- reads: `FastAPI routes (Python): the verb comes from the decorator's own attribute name, \`APIRouter\` prefixes are composed one \`include_router\` hop deep, and \`response_model\` / \`status_code\` are taken as the declared contract.`
125
+ reads: `FastAPI routes (Python). The verb comes from the decorator's attribute name, \`APIRouter\` prefixes are composed one \`include_router\` hop deep, and \`response_model\` and \`status_code\` are taken as the declared contract.`
110
126
  };
111
127
  var index_default = fastapiFramework;
112
128
  export {