@suss/packs 0.33.1 → 0.34.1

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 (64) hide show
  1. package/README.md +10 -11
  2. package/dist/activerecord.d.ts +7 -0
  3. package/dist/activerecord.js +53 -5
  4. package/dist/aiohttp.js +20 -9
  5. package/dist/apollo-client.js +26 -39
  6. package/dist/apollo.js +12 -20
  7. package/dist/aws-dynamodb.js +4 -4
  8. package/dist/aws-eventbridge.js +3 -3
  9. package/dist/aws-lambda.js +31 -54
  10. package/dist/aws-s3.js +1 -1
  11. package/dist/aws-secrets-manager.js +1 -1
  12. package/dist/aws-sns.js +2 -2
  13. package/dist/aws-sqs.js +20 -11
  14. package/dist/aws-ssm.js +1 -1
  15. package/dist/axios.js +36 -37
  16. package/dist/bigquery-python.js +3 -3
  17. package/dist/bigquery.js +1 -1
  18. package/dist/{chunk-NYWZHQBJ.js → chunk-GK4BILHB.js} +8 -9
  19. package/dist/cloudflare-workers.js +26 -35
  20. package/dist/drizzle.js +15 -20
  21. package/dist/express.js +3 -6
  22. package/dist/fabrication.d.ts +1 -0
  23. package/dist/fabrication.js +36 -0
  24. package/dist/factory-bot.d.ts +1 -0
  25. package/dist/factory-bot.js +36 -0
  26. package/dist/faraday.js +8 -6
  27. package/dist/fastapi.js +123 -5
  28. package/dist/fastify.js +10 -14
  29. package/dist/fetch.js +12 -6
  30. package/dist/flask-restx.js +19 -12
  31. package/dist/gcs.js +1 -1
  32. package/dist/graphql-ruby.js +20 -16
  33. package/dist/hono.js +17 -27
  34. package/dist/httpx.js +8 -6
  35. package/dist/minitest.d.ts +1 -0
  36. package/dist/minitest.js +59 -0
  37. package/dist/mongoose.js +7 -7
  38. package/dist/nestjs-graphql.js +14 -25
  39. package/dist/nestjs-microservices.js +9 -10
  40. package/dist/nestjs-rest.js +193 -23
  41. package/dist/net-http.js +9 -7
  42. package/dist/nextjs.js +16 -17
  43. package/dist/node.js +139 -23
  44. package/dist/package-exports.js +1 -1
  45. package/dist/pg.js +3 -2
  46. package/dist/prisma.js +375 -56
  47. package/dist/pytest.d.ts +1 -0
  48. package/dist/pytest.js +81 -0
  49. package/dist/rails.js +1792 -132
  50. package/dist/react-query.js +1 -1
  51. package/dist/react-router.js +21 -48
  52. package/dist/react.js +14 -5
  53. package/dist/redis.js +1 -1
  54. package/dist/requests.js +27 -19
  55. package/dist/rspec.d.ts +1 -0
  56. package/dist/rspec.js +135 -0
  57. package/dist/sqlalchemy.js +1 -1
  58. package/dist/sqlmodel.js +7 -8
  59. package/dist/ts-rest.js +6 -8
  60. package/dist/typeorm.d.ts +1 -0
  61. package/dist/typeorm.js +291 -0
  62. package/dist/vitest.d.ts +1 -0
  63. package/dist/vitest.js +64 -0
  64. package/package.json +47 -11
package/dist/prisma.js CHANGED
@@ -1,11 +1,12 @@
1
1
  // ../framework/prisma/dist/index.js
2
- import fs from "fs";
3
- import path from "path";
2
+ import fs2 from "fs";
3
+ import path2 from "path";
4
4
  import {
5
5
  Node as N
6
6
  } from "ts-morph";
7
7
  import { z } from "zod";
8
- import { storageBinding } from "@suss/behavioral-ir";
8
+ import { receiverTypesOf, writtenNodeOf } from "@suss/adapter-typescript";
9
+ import { storageBinding as storageBinding2 } from "@suss/behavioral-ir";
9
10
  import { scopeOption, storageSystemOption } from "@suss/extractor";
10
11
  import {
11
12
  compile,
@@ -13,6 +14,252 @@ import {
13
14
  declaredBy,
14
15
  sqlStatements
15
16
  } from "@suss/recognize";
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import { getSchema as getSchema2 } from "@mrleebo/prisma-ast";
20
+ import { ts } from "ts-morph";
21
+
22
+ // ../contract/prisma/dist/index.js
23
+ import { getSchema } from "@mrleebo/prisma-ast";
24
+ import { storageBinding } from "@suss/behavioral-ir";
25
+ var PROVIDER_TO_SYSTEM = {
26
+ postgresql: "postgresql",
27
+ postgres: "postgresql",
28
+ mysql: "mysql",
29
+ sqlite: "sqlite"
30
+ };
31
+ function prismaStorageSystem(source) {
32
+ try {
33
+ return storageSystemIn(getSchema(source).list);
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ function storageSystemIn(list) {
39
+ for (const node of list) {
40
+ if (node.type !== "datasource") {
41
+ continue;
42
+ }
43
+ const provider = readProviderString(node);
44
+ if (provider !== null && provider in PROVIDER_TO_SYSTEM) {
45
+ return PROVIDER_TO_SYSTEM[provider];
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ function readProviderString(ds) {
51
+ for (const a of ds.assignments) {
52
+ if (a.key !== "provider") {
53
+ continue;
54
+ }
55
+ if (typeof a.value === "string") {
56
+ return a.value.replace(/^"|"$/g, "");
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+
62
+ // ../framework/prisma/dist/index.js
63
+ var CONFIG_FILES = [
64
+ "prisma.config.ts",
65
+ "prisma.config.mts",
66
+ "prisma.config.cts",
67
+ "prisma.config.js",
68
+ "prisma.config.mjs",
69
+ "prisma.config.cjs",
70
+ path.join(".config", "prisma.ts")
71
+ ];
72
+ var DEFAULT_SCHEMAS = [path.join("prisma", "schema.prisma"), "schema.prisma"];
73
+ var readProjects = /* @__PURE__ */ new Map();
74
+ function generatedClientDirs(fromDir) {
75
+ return readProjectAt(fromDir)?.dirs ?? [];
76
+ }
77
+ function schemaStorageSystem(fromDir) {
78
+ return readProjectAt(fromDir)?.storageSystem ?? null;
79
+ }
80
+ function readProjectAt(fromDir) {
81
+ const root = projectRootOf(fromDir);
82
+ if (root === null) {
83
+ return null;
84
+ }
85
+ const known = readProjects.get(root);
86
+ if (known !== void 0 && unchanged(known.readFrom)) {
87
+ return known;
88
+ }
89
+ const schemaFiles = schemaFilesOf(root);
90
+ const readFrom = /* @__PURE__ */ new Map();
91
+ for (const file of [...projectFilesOf(root), ...schemaFiles]) {
92
+ readFrom.set(file, changedAt(file));
93
+ }
94
+ const sources = schemaFiles.map((file) => ({ file, text: textOf(file) }));
95
+ const project = {
96
+ dirs: sources.flatMap(outputDirsIn),
97
+ storageSystem: sources.map(({ text }) => prismaStorageSystem(text)).find((system) => system !== null) ?? null,
98
+ readFrom
99
+ };
100
+ readProjects.set(root, project);
101
+ return project;
102
+ }
103
+ function textOf(file) {
104
+ try {
105
+ return fs.readFileSync(file, "utf8");
106
+ } catch {
107
+ return "";
108
+ }
109
+ }
110
+ function isInGeneratedClient(filePath) {
111
+ return generatedClientDirs(path.dirname(filePath)).some(
112
+ (dir) => filePath.startsWith(`${dir}${path.sep}`)
113
+ );
114
+ }
115
+ function projectFilesOf(root) {
116
+ return [
117
+ path.join(root, "package.json"),
118
+ ...CONFIG_FILES.map((name) => path.join(root, name)),
119
+ ...DEFAULT_SCHEMAS.map((name) => path.join(root, name))
120
+ ];
121
+ }
122
+ function changedAt(file) {
123
+ try {
124
+ return fs.statSync(file).mtimeMs;
125
+ } catch {
126
+ return -1;
127
+ }
128
+ }
129
+ function unchanged(readFrom) {
130
+ for (const [file, at] of readFrom) {
131
+ if (changedAt(file) !== at) {
132
+ return false;
133
+ }
134
+ }
135
+ return true;
136
+ }
137
+ var rootsByDir = /* @__PURE__ */ new Map();
138
+ function projectRootOf(fromDir) {
139
+ const known = rootsByDir.get(fromDir);
140
+ if (known !== void 0) {
141
+ return known;
142
+ }
143
+ const root = findProjectRoot(fromDir);
144
+ rootsByDir.set(fromDir, root);
145
+ return root;
146
+ }
147
+ function findProjectRoot(fromDir) {
148
+ let dir = path.resolve(fromDir);
149
+ while (true) {
150
+ if (fs.existsSync(path.join(dir, "package.json")) || configFileIn(dir) !== null) {
151
+ return dir;
152
+ }
153
+ const parent = path.dirname(dir);
154
+ if (parent === dir) {
155
+ return null;
156
+ }
157
+ dir = parent;
158
+ }
159
+ }
160
+ function configFileIn(dir) {
161
+ for (const name of CONFIG_FILES) {
162
+ const candidate = path.join(dir, name);
163
+ if (fs.existsSync(candidate)) {
164
+ return candidate;
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ function schemaFilesOf(root) {
170
+ const stated = [configuredSchema(root), packageJsonSchema(root)];
171
+ for (const schema of [
172
+ ...stated.filter((one) => one !== null),
173
+ ...DEFAULT_SCHEMAS.map((name) => path.join(root, name))
174
+ ]) {
175
+ const files = prismaFilesAt(schema);
176
+ if (files.length > 0) {
177
+ return files;
178
+ }
179
+ }
180
+ return [];
181
+ }
182
+ function prismaFilesAt(schemaPath) {
183
+ let stat;
184
+ try {
185
+ stat = fs.statSync(schemaPath);
186
+ } catch {
187
+ return [];
188
+ }
189
+ if (stat.isFile()) {
190
+ return [schemaPath];
191
+ }
192
+ if (!stat.isDirectory()) {
193
+ return [];
194
+ }
195
+ return fs.readdirSync(schemaPath, { recursive: true, encoding: "utf8" }).filter((name) => name.endsWith(".prisma")).map((name) => path.join(schemaPath, name)).sort();
196
+ }
197
+ function configuredSchema(root) {
198
+ const config = configFileIn(root);
199
+ if (config === null) {
200
+ return null;
201
+ }
202
+ const source = ts.createSourceFile(
203
+ config,
204
+ fs.readFileSync(config, "utf8"),
205
+ ts.ScriptTarget.Latest
206
+ );
207
+ let found = null;
208
+ const visit = (node) => {
209
+ if (found !== null) {
210
+ return;
211
+ }
212
+ if (ts.isPropertyAssignment(node) && node.name.getText(source) === "schema" && ts.isStringLiteralLike(node.initializer)) {
213
+ found = node.initializer.text;
214
+ return;
215
+ }
216
+ ts.forEachChild(node, visit);
217
+ };
218
+ visit(source);
219
+ return found === null ? null : path.resolve(root, found);
220
+ }
221
+ function packageJsonSchema(root) {
222
+ const file = path.join(root, "package.json");
223
+ if (!fs.existsSync(file)) {
224
+ return null;
225
+ }
226
+ try {
227
+ const manifest = JSON.parse(fs.readFileSync(file, "utf8"));
228
+ const schema = manifest.prisma?.schema;
229
+ return typeof schema === "string" ? path.resolve(root, schema) : null;
230
+ } catch {
231
+ return null;
232
+ }
233
+ }
234
+ function outputDirsIn(schema) {
235
+ const schemaFile = schema.file;
236
+ let list;
237
+ try {
238
+ list = getSchema2(schema.text).list;
239
+ } catch {
240
+ return [];
241
+ }
242
+ const dirs = [];
243
+ for (const block of list) {
244
+ const output = generatorOutput(block);
245
+ if (output !== null) {
246
+ dirs.push(path.resolve(path.dirname(schemaFile), output));
247
+ }
248
+ }
249
+ return dirs;
250
+ }
251
+ function generatorOutput(block) {
252
+ const generator = block;
253
+ if (generator.type !== "generator") {
254
+ return null;
255
+ }
256
+ for (const assignment of generator.assignments ?? []) {
257
+ if (assignment.type === "assignment" && assignment.key === "output" && typeof assignment.value === "string") {
258
+ return assignment.value.replace(/^"|"$/g, "");
259
+ }
260
+ }
261
+ return null;
262
+ }
16
263
  var PRISMA_READ_METHODS = /* @__PURE__ */ new Set([
17
264
  "findUnique",
18
265
  "findFirst",
@@ -33,23 +280,30 @@ var PRISMA_WRITE_METHODS = /* @__PURE__ */ new Set([
33
280
  ]);
34
281
  var optionsSchema = z.object({
35
282
  /**
36
- * Storage system the recognized calls target. Must match the
37
- * `storageSystem` on schema-reader provider summaries; otherwise
38
- * pairing keys won't match. Defaults to `"postgresql"` since that's
39
- * the dominant Prisma deployment.
283
+ * The storage system the calls target. When unset, the project
284
+ * schema's datasource decides, then `"postgresql"`. The README says
285
+ * why it has to match the schema reader's summaries.
40
286
  */
41
287
  storageSystem: storageSystemOption.optional(),
42
288
  /**
43
- * Scope label that must match the schema reader's scope. Defaults
44
- * to `"default"` to align with `prismaSchemaToSummaries`'s default.
289
+ * Has to match the schema reader's scope. It is `"default"` when
290
+ * unset, the same default `prismaSchemaToSummaries` uses.
45
291
  */
46
292
  scope: scopeOption.optional()
47
293
  }).strict();
48
294
  function makeRecognizer(opts) {
49
- const storageSystem = opts.storageSystem ?? "postgresql";
50
295
  const scope = opts.scope ?? "default";
51
- return (call, ctx) => recognizePrismaCall(call, ctx, storageSystem, scope);
296
+ return (call, ctx) => recognizePrismaCall(
297
+ call,
298
+ ctx,
299
+ storageSystemFor(opts, call.getSourceFile()),
300
+ scope
301
+ );
302
+ }
303
+ function storageSystemFor(opts, file) {
304
+ return opts.storageSystem ?? schemaStorageSystem(path2.dirname(file.getFilePath())) ?? DEFAULT_STORAGE_SYSTEM;
52
305
  }
306
+ var DEFAULT_STORAGE_SYSTEM = "postgresql";
53
307
  function recognizePrismaCall(call, ctx, storageSystem, scope) {
54
308
  const callNode = call;
55
309
  const recognizerCtx = ctx;
@@ -68,7 +322,7 @@ function recognizePrismaCall(call, ctx, storageSystem, scope) {
68
322
  return null;
69
323
  }
70
324
  const receiverExpr = delegateExpr.getExpression();
71
- if (!isPrismaClientReceiver(receiverExpr)) {
325
+ if (!isPrismaClientReceiver(receiverExpr, recognizerCtx.resolution)) {
72
326
  return null;
73
327
  }
74
328
  const delegateName = delegateExpr.getName();
@@ -81,26 +335,31 @@ function recognizePrismaCall(call, ctx, storageSystem, scope) {
81
335
  const optionsArg = readObjectArg(argsShape[0]);
82
336
  const fields = extractFields(optionsArg, kind);
83
337
  const selector = extractSelector(optionsArg);
84
- const binding = storageBinding({
338
+ const binding = storageBinding2({
85
339
  recognition: "@suss/framework-prisma",
86
340
  storageSystem,
87
341
  scope,
88
342
  container: tableName
89
343
  });
90
344
  const callee = callNode.getExpression().getText();
345
+ const access = {
346
+ type: "interaction",
347
+ binding,
348
+ callee,
349
+ interaction: {
350
+ class: "storage-access",
351
+ kind,
352
+ fields,
353
+ ...selector !== null ? { selector } : {},
354
+ operation: method
355
+ }
356
+ };
357
+ statePayloadSlots(recognizerCtx.ops, access, {
358
+ fields: kind === "write" ? fields : [],
359
+ selector: selector ?? []
360
+ });
91
361
  return [
92
- {
93
- type: "interaction",
94
- binding,
95
- callee,
96
- interaction: {
97
- class: "storage-access",
98
- kind,
99
- fields,
100
- ...selector !== null ? { selector } : {},
101
- operation: method
102
- }
103
- },
362
+ access,
104
363
  ...nestedReads(optionsArg).map(
105
364
  (nested) => ({
106
365
  type: "interaction",
@@ -126,15 +385,34 @@ function recognizePrismaCall(call, ctx, storageSystem, scope) {
126
385
  fields: nested.fields,
127
386
  relationPath: nested.relationPath,
128
387
  ...nested.relationKey === true ? { relationKey: true } : {},
129
- // What reaches the other model is the nested operation, so
130
- // that is what the effect records. The outer method belongs
131
- // to the model in the binding.
388
+ // The outer method acts on the model in the binding. The model
389
+ // across the relation gets the nested operation.
132
390
  operation: nested.operation
133
391
  }
134
392
  })
135
393
  )
136
394
  ];
137
395
  }
396
+ function statePayloadSlots(ops, effect, columns) {
397
+ const options = ops?.statesSlots === void 0 ? null : ops.valueAt(0);
398
+ if (ops === void 0 || options === null) {
399
+ return;
400
+ }
401
+ const slots = [
402
+ ...slotsUnder(options, ["where"], "selector", columns.selector),
403
+ ...slotsUnder(options, WRITE_PAYLOAD_KEYS, "field", columns.fields)
404
+ ];
405
+ if (slots.length > 0) {
406
+ ops.statesSlots?.(effect, slots);
407
+ }
408
+ }
409
+ function slotsUnder(options, keys, slot, columns) {
410
+ return keys.flatMap(
411
+ (key) => (options.property(key)?.entries("nothing") ?? []).flatMap(
412
+ (entry) => entry.key !== null && columns.includes(entry.key) ? [{ slot, name: entry.key, value: entry.value }] : []
413
+ )
414
+ );
415
+ }
138
416
  function nestedReads(optionsArg) {
139
417
  if (optionsArg === null) {
140
418
  return [];
@@ -143,7 +421,7 @@ function nestedReads(optionsArg) {
143
421
  collectRelations(optionsArg, [], found);
144
422
  return found;
145
423
  }
146
- function collectRelations(shape, path2, found) {
424
+ function collectRelations(shape, path3, found) {
147
425
  const select = readObjectArg(shape.fields.select);
148
426
  if (select !== null) {
149
427
  for (const [name, value] of Object.entries(select.fields)) {
@@ -151,7 +429,7 @@ function collectRelations(shape, path2, found) {
151
429
  if (nested === null) {
152
430
  continue;
153
431
  }
154
- recordRelation(name, nested, path2, found);
432
+ recordRelation(name, nested, path3, found);
155
433
  }
156
434
  }
157
435
  const include = readObjectArg(shape.fields.include);
@@ -161,14 +439,14 @@ function collectRelations(shape, path2, found) {
161
439
  for (const [name, value] of Object.entries(include.fields)) {
162
440
  const nested = readObjectArg(value);
163
441
  if (nested === null) {
164
- found.push({ relationPath: [...path2, name], fields: ["*"] });
442
+ found.push({ relationPath: [...path3, name], fields: ["*"] });
165
443
  continue;
166
444
  }
167
- recordRelation(name, nested, path2, found);
445
+ recordRelation(name, nested, path3, found);
168
446
  }
169
447
  }
170
- function recordRelation(name, nested, path2, found) {
171
- const relationPath = [...path2, name];
448
+ function recordRelation(name, nested, path3, found) {
449
+ const relationPath = [...path3, name];
172
450
  found.push({ relationPath, fields: extractFields(nested, "read") });
173
451
  collectRelations(nested, relationPath, found);
174
452
  }
@@ -222,13 +500,13 @@ function nestedWrites(optionsArg) {
222
500
  }
223
501
  return found;
224
502
  }
225
- function collectNestedWrites(row, path2, found) {
503
+ function collectNestedWrites(row, path3, found) {
226
504
  for (const [field, value] of Object.entries(row.fields)) {
227
505
  const nested = readObjectArg(value);
228
506
  if (nested === null) {
229
507
  continue;
230
508
  }
231
- const relationPath = [...path2, field];
509
+ const relationPath = [...path3, field];
232
510
  for (const [operation, payload] of Object.entries(nested.fields)) {
233
511
  const does = NESTED_OPERATIONS.get(operation);
234
512
  if (does === void 0) {
@@ -279,29 +557,51 @@ function fieldsOfRows(read) {
279
557
  }
280
558
  return out.size === 0 ? ["*"] : [...out];
281
559
  }
282
- function isPrismaClientReceiver(node) {
283
- return extendsPrismaClient(node.getType(), /* @__PURE__ */ new Set());
560
+ function isPrismaClientReceiver(node, resolution) {
561
+ if (receiverTypesOf(node).some(
562
+ (type) => declaredByPrisma(type) || extendsImportedClient(type, resolution)
563
+ )) {
564
+ return true;
565
+ }
566
+ return node.getType().isAny() && madeFromImportedClient(node, resolution);
284
567
  }
285
- function extendsPrismaClient(type, seen) {
286
- if (seen.has(type)) {
568
+ function madeFromImportedClient(node, resolution) {
569
+ if (resolution === void 0) {
287
570
  return false;
288
571
  }
289
- seen.add(type);
290
- const declarations = type.getSymbol()?.getDeclarations() ?? [];
291
- if (declarations.some(
292
- (declaration) => isPrismaClientPath(declaration.getSourceFile().getFilePath())
293
- )) {
572
+ if (importedAsClient(node, resolution)) {
294
573
  return true;
295
574
  }
296
- return type.getBaseTypes().some((base) => extendsPrismaClient(base, seen));
575
+ const written = writtenNodeOf(node, resolution);
576
+ return written !== null && written !== node && importedAsClient(written, resolution);
577
+ }
578
+ function importedAsClient(value, resolution) {
579
+ return resolution.importOriginsOf(value, clientModulesFor(value)).some((origin) => origin.path.join(".") === CLIENT_CLASS);
580
+ }
581
+ function clientModulesFor(value) {
582
+ return [
583
+ CLIENT_MODULE,
584
+ ...generatedClientDirs(path2.dirname(value.getSourceFile().getFilePath()))
585
+ ];
586
+ }
587
+ function declaredByPrisma(type) {
588
+ return type.declaredIn.some((filePath) => isPrismaClientPath(filePath));
589
+ }
590
+ var CLIENT_MODULE = "@prisma/client";
591
+ var CLIENT_CLASS = "PrismaClient";
592
+ function extendsImportedClient(type, resolution) {
593
+ if (resolution === void 0) {
594
+ return false;
595
+ }
596
+ return type.extendsWritten.some((base) => importedAsClient(base, resolution));
297
597
  }
298
598
  var GENERATED_CLIENT_MARKER = "schema.prisma";
299
599
  function isPrismaClientPath(filePath) {
300
600
  if (filePath.includes("/@prisma/client/") || filePath.includes("/.prisma/client/")) {
301
601
  return true;
302
602
  }
303
- const beside = path.join(path.dirname(filePath), GENERATED_CLIENT_MARKER);
304
- return fs.existsSync(beside);
603
+ const beside = path2.join(path2.dirname(filePath), GENERATED_CLIENT_MARKER);
604
+ return fs2.existsSync(beside) || isInGeneratedClient(filePath);
305
605
  }
306
606
  function capitalizeFirst(name) {
307
607
  if (name.length === 0) {
@@ -383,8 +683,7 @@ function extractSelector(optionsArg) {
383
683
  return keys.length > 0 ? keys : null;
384
684
  }
385
685
  var STATEMENT = { statement: { at: 0 } };
386
- function rawStatements(options) {
387
- const provider = options.storageSystem ?? "postgresql";
686
+ function rawStatements(options, provider) {
388
687
  return sqlStatements({
389
688
  system: provider,
390
689
  dialect: provider,
@@ -397,8 +696,26 @@ function rawStatements(options) {
397
696
  $executeRawUnsafe: STATEMENT
398
697
  }).example('prisma.$queryRawUnsafe("SELECT id, email FROM users")');
399
698
  }
699
+ function rawRecognizer(options) {
700
+ const bySystem = /* @__PURE__ */ new Map();
701
+ return (call, ctx) => {
702
+ const system = storageSystemFor(options, call.getSourceFile());
703
+ let recognizer = bySystem.get(system);
704
+ if (recognizer === void 0) {
705
+ recognizer = compile(
706
+ rawStatements(options, system).declared,
707
+ "@suss/framework-prisma"
708
+ );
709
+ bySystem.set(system, recognizer);
710
+ }
711
+ return recognizer(call, ctx);
712
+ };
713
+ }
400
714
  function prismaFramework(options = {}) {
401
- const raw = rawStatements(options);
715
+ const raw = rawStatements(
716
+ options,
717
+ options.storageSystem ?? DEFAULT_STORAGE_SYSTEM
718
+ );
402
719
  return {
403
720
  name: "prisma",
404
721
  protocol: "in-process",
@@ -406,16 +723,18 @@ function prismaFramework(options = {}) {
406
723
  discovery: [],
407
724
  terminals: [],
408
725
  inputMapping: { type: "positionalParams", params: [] },
409
- // Skip files that don't import from @prisma/client: the
410
- // recognizer's type-resolution check would reject them anyway.
726
+ // The type check would reject every call in a file that does not
727
+ // import `@prisma/client`, so those files are skipped.
411
728
  requiresImport: ["@prisma/client"],
412
729
  // A generator with its own `output` puts the client in the project,
413
- // where the only way to reach it is a relative path.
730
+ // where the only way to reach it is a relative path. Prisma 7's
731
+ // `prisma-client` generator writes no schema beside it.
414
732
  generatedModuleMarkers: [GENERATED_CLIENT_MARKER],
733
+ generatedModuleDirs: generatedClientDirs,
415
734
  invocationRecognizers: [makeRecognizer(options)],
416
735
  // A tagged template is not an invocation, so the raw chain runs on
417
736
  // the access walk, which visits calls as well.
418
- accessRecognizers: [compile(raw.declared, "@suss/framework-prisma")],
737
+ accessRecognizers: [rawRecognizer(options)],
419
738
  declarations: declarationsIn([raw])
420
739
  };
421
740
  }
@@ -423,7 +742,7 @@ var declares = {
423
742
  kind: "effects",
424
743
  package: "@suss/framework-prisma",
425
744
  dependencies: [{ ecosystem: "npm", name: "@prisma/client" }],
426
- reads: "Prisma client calls, emits storage-access interactions per read / write."
745
+ reads: "Prisma client calls. Each read and each write becomes a storage-access interaction."
427
746
  };
428
747
  var index_default = prismaFramework;
429
748
  export {
@@ -0,0 +1 @@
1
+ export { declares, default, optionsSchema } from '@suss/framework-pytest';
package/dist/pytest.js ADDED
@@ -0,0 +1,81 @@
1
+ // ../framework/pytest/dist/index.js
2
+ import path from "path";
3
+ import { z } from "zod";
4
+ var optionsSchema = z.object({
5
+ /**
6
+ * The test files to read, matched on whole path segments from the
7
+ * end. `suss extract --intent` fills it in from the tests the PRDs
8
+ * list, so a run reads those files and no others. Left out, every
9
+ * file pytest would collect is read.
10
+ */
11
+ files: z.array(z.string().min(1)).optional()
12
+ }).strict();
13
+ var SHARED_FIXTURE_FILE = "conftest.py";
14
+ function pytestFramework(options = {}) {
15
+ return {
16
+ name: "pytest",
17
+ protocol: "in-process",
18
+ discovery: [],
19
+ // A test's fixtures can come from a conftest.py in any directory
20
+ // above it, so an edit to one has to re-read the tests below it.
21
+ discoveryInputs: (files) => files.filter((file) => path.basename(file) === SHARED_FIXTURE_FILE),
22
+ tests: [
23
+ {
24
+ filePatterns: ["test_*.py", "*_test.py"],
25
+ functionPrefix: "test",
26
+ classPrefix: "Test",
27
+ caseBaseClasses: [
28
+ "unittest.TestCase",
29
+ "unittest.IsolatedAsyncioTestCase"
30
+ ],
31
+ setUp: {
32
+ module: ["setup_module"],
33
+ function: ["setup_function"],
34
+ testClass: ["setup_class", "setup_method"],
35
+ caseClass: ["setUpClass", "setUp", "asyncSetUp"]
36
+ },
37
+ usesFixtureMarkers: ["pytest.mark.usefixtures"],
38
+ fixtureDecorators: ["pytest.fixture"],
39
+ fixtureNameKeyword: "name",
40
+ autouseKeyword: "autouse",
41
+ sharedFixtureFiles: [SHARED_FIXTURE_FILE],
42
+ skipDecorators: [
43
+ "pytest.mark.skip",
44
+ "pytest.mark.skipif",
45
+ "pytest.mark.xfail",
46
+ "unittest.skip",
47
+ "unittest.skipIf",
48
+ "unittest.skipUnless",
49
+ "unittest.expectedFailure"
50
+ ],
51
+ markerVariable: "pytestmark",
52
+ reservedParameters: ["self", "cls", "request"],
53
+ mocks: {
54
+ patchers: [
55
+ "unittest.mock.patch",
56
+ "unittest.mock.patch.object",
57
+ "mock.patch",
58
+ "mock.patch.object"
59
+ ],
60
+ fixturePatchers: [
61
+ { fixture: "mocker", methods: ["patch", "patch.object"] },
62
+ { fixture: "monkeypatch", methods: ["setattr"] }
63
+ ]
64
+ },
65
+ ...options.files !== void 0 ? { files: options.files } : {}
66
+ }
67
+ ]
68
+ };
69
+ }
70
+ var declares = {
71
+ kind: "framework",
72
+ package: "@suss/framework-pytest",
73
+ dependencies: [],
74
+ reads: "pytest tests (Python), named by their node ids, so a PRD scenario can say which test covers it."
75
+ };
76
+ var index_default = pytestFramework;
77
+ export {
78
+ declares,
79
+ index_default as default,
80
+ optionsSchema
81
+ };