@suss/packs 0.25.0 → 0.27.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.
@@ -39,5 +39,6 @@ import '@suss/framework-react';
39
39
  import '@suss/framework-redis';
40
40
  import '@suss/client-requests';
41
41
  import '@suss/framework-sqlalchemy';
42
+ import '@suss/framework-sqlmodel';
42
43
  import '@suss/framework-ts-rest';
43
44
  import '@suss/framework-zustand';
package/dist/axios.js CHANGED
@@ -82,15 +82,56 @@ function discoveryForVerb(verb, factories) {
82
82
  function isPathShapedSpecifier(specifier) {
83
83
  return specifier.startsWith(".") || specifier.startsWith("/");
84
84
  }
85
+ function configCallDiscovery(factories) {
86
+ const bindingExtraction = {
87
+ method: {
88
+ type: "fromArgumentProperty",
89
+ position: 0,
90
+ property: "method",
91
+ default: "GET"
92
+ },
93
+ path: { type: "fromArgumentProperty", position: 0, property: "url" }
94
+ };
95
+ const patterns = [
96
+ {
97
+ kind: "client",
98
+ match: {
99
+ type: "clientCall",
100
+ importModule: "axios",
101
+ importName: "axios",
102
+ methodFilter: ["request"],
103
+ factoryMethods: ["create"],
104
+ callable: true
105
+ },
106
+ bindingExtraction,
107
+ requiresImport: ["axios"]
108
+ }
109
+ ];
110
+ for (const factory of factories) {
111
+ patterns.push({
112
+ kind: "client",
113
+ match: {
114
+ type: "clientCall",
115
+ importModule: factory.module,
116
+ importName: factory.export,
117
+ methodFilter: ["request"]
118
+ },
119
+ bindingExtraction,
120
+ requiresImport: isPathShapedSpecifier(factory.module) ? [] : [factory.module]
121
+ });
122
+ }
123
+ return patterns;
124
+ }
85
125
  function axiosPack(options = {}) {
86
126
  const factories = options.factories ?? [];
87
127
  return {
88
128
  name: "axios",
89
129
  protocol: "http",
90
130
  languages: ["typescript", "javascript"],
91
- discovery: HTTP_METHODS.flatMap(
92
- (verb) => discoveryForVerb(verb, factories)
93
- ),
131
+ discovery: [
132
+ ...HTTP_METHODS.flatMap((verb) => discoveryForVerb(verb, factories)),
133
+ ...configCallDiscovery(factories)
134
+ ],
94
135
  terminals: [
95
136
  {
96
137
  kind: "return",
@@ -0,0 +1,101 @@
1
+ // ../framework/sqlalchemy/dist/index.js
2
+ import { z } from "zod";
3
+ import { storageSystemOption } from "@suss/extractor";
4
+ var optionsSchema = z.object({
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.
9
+ */
10
+ storageSystem: storageSystemOption
11
+ }).strict();
12
+ function sqlalchemyStorage(options) {
13
+ return [
14
+ {
15
+ module: "sqlalchemy.orm",
16
+ queryTypes: ["Query", "Session"],
17
+ writes: [
18
+ "update",
19
+ "delete",
20
+ "add",
21
+ "add_all",
22
+ "merge",
23
+ "flush",
24
+ "commit",
25
+ "bulk_save_objects",
26
+ "bulk_insert_mappings",
27
+ "bulk_update_mappings"
28
+ ],
29
+ // A Session runs a statement built by `select`/`update`/... whose
30
+ // own chain is read, and the rest of these manage the session.
31
+ recordsNothing: [
32
+ "execute",
33
+ "scalars",
34
+ "scalar",
35
+ "begin",
36
+ "rollback",
37
+ "close",
38
+ "expire",
39
+ "expire_all",
40
+ "expunge",
41
+ "expunge_all"
42
+ ],
43
+ storageSystem: options.storageSystem
44
+ },
45
+ {
46
+ module: "sqlalchemy",
47
+ queryTypes: ["Select", "Update", "Delete", "Insert"],
48
+ 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.
52
+ queryFunctions: ["select", "insert", "update", "delete"],
53
+ valueMethods: ["values"],
54
+ storageSystem: options.storageSystem
55
+ }
56
+ ];
57
+ }
58
+ function sqlalchemyRawSql(options) {
59
+ return [
60
+ {
61
+ module: "sqlalchemy",
62
+ functions: ["text"],
63
+ storageSystem: options.storageSystem
64
+ }
65
+ ];
66
+ }
67
+ function sqlalchemyFramework(options) {
68
+ if (typeof options?.storageSystem !== "string") {
69
+ throw new Error(
70
+ "it needs `storageSystem`, which database is behind the engine: postgresql, mysql, or sqlite. SQLAlchemy talks to all of them and the connection URL settles which, so the pack cannot."
71
+ );
72
+ }
73
+ return {
74
+ name: "sqlalchemy",
75
+ protocol: options.storageSystem,
76
+ discovery: [],
77
+ storage: sqlalchemyStorage(options),
78
+ rawSql: sqlalchemyRawSql(options)
79
+ };
80
+ }
81
+ var declares = {
82
+ kind: "effects",
83
+ package: "@suss/framework-sqlalchemy",
84
+ dependencies: [{ ecosystem: "pypi", name: "sqlalchemy" }],
85
+ 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.`,
86
+ configuration: {
87
+ file: "suss.sqlalchemy.json",
88
+ example: { storageSystem: "postgresql" },
89
+ required: true,
90
+ why: "which database is behind the engine: postgresql, mysql, or sqlite. SQLAlchemy talks to all of them and the connection URL settles which, so the pack cannot."
91
+ }
92
+ };
93
+ var index_default = sqlalchemyFramework;
94
+
95
+ export {
96
+ optionsSchema,
97
+ sqlalchemyStorage,
98
+ sqlalchemyRawSql,
99
+ declares,
100
+ index_default
101
+ };
package/dist/express.js CHANGED
@@ -5,7 +5,17 @@ import {
5
5
  routeHelperIndex,
6
6
  wrapperDiscovery
7
7
  } from "@suss/extractor";
8
- var METHODS = [".get", ".post", ".put", ".delete", ".patch", ".all"];
8
+ var METHODS = [
9
+ ".get",
10
+ ".post",
11
+ ".put",
12
+ ".delete",
13
+ ".patch",
14
+ ".head",
15
+ ".options",
16
+ ".search",
17
+ ".all"
18
+ ];
9
19
  var HEADER_SETTERS = [
10
20
  "set",
11
21
  "header",
package/dist/fastify.js CHANGED
@@ -19,7 +19,6 @@ function fastifyFramework(_options = {}) {
19
19
  languages: ["typescript", "javascript"],
20
20
  // Fastify exposes the routable via either default `Fastify` or
21
21
  // named `fastify()`. Both drive handler registration the same way.
22
- // Unlike Express, Fastify's router supports `.head` and `.options`.
23
22
  discovery: [
24
23
  ...httpRouteDiscovery({
25
24
  importModule: "fastify",
@@ -95,14 +95,30 @@ function graphqlRubyFramework(options) {
95
95
  return {
96
96
  name: "graphql-ruby",
97
97
  protocol: "http-graphql",
98
- discovery: [pattern]
98
+ discovery: [pattern],
99
+ // A resolver that reads a model through the dataloader passes the model
100
+ // as an argument, so the read is recorded against that model when a
101
+ // storage pack in the same run says it is one.
102
+ loaders: [
103
+ {
104
+ loader: "dataloader",
105
+ pick: "with",
106
+ reads: ["load", "load_all", "request", "request_all"],
107
+ shortcuts: [
108
+ "dataload",
109
+ "dataload_all",
110
+ "dataload_record",
111
+ "dataload_all_records"
112
+ ]
113
+ }
114
+ ]
99
115
  };
100
116
  }
101
117
  var declares = {
102
118
  kind: "framework",
103
119
  package: "@suss/framework-graphql-ruby",
104
120
  dependencies: [{ ecosystem: "rubygems", name: "graphql" }],
105
- reads: "graphql-ruby class-based field DSL (Ruby), including `mutation:` / `resolver:` wiring one hop out to what the referenced class itself declares.",
121
+ reads: "graphql-ruby class-based field DSL (Ruby), including `mutation:` / `resolver:` wiring one hop out to what the referenced class itself declares, and the model a resolver reads through `dataloader` when a storage pack in the run recognizes it.",
106
122
  configuration: {
107
123
  file: "suss.graphql-ruby.json",
108
124
  example: { root: "app/graphql" },
package/dist/prisma.js CHANGED
@@ -278,23 +278,23 @@ function fieldsOfRows(read) {
278
278
  return out.size === 0 ? ["*"] : [...out];
279
279
  }
280
280
  function isPrismaClientReceiver(node) {
281
- const type = node.getType();
282
- if (type === null || typeof type !== "object") {
281
+ return extendsPrismaClient(node.getType(), /* @__PURE__ */ new Set());
282
+ }
283
+ function extendsPrismaClient(type, seen) {
284
+ if (seen.has(type)) {
283
285
  return false;
284
286
  }
285
- const symbol = type.getSymbol?.();
286
- if (symbol === null || symbol === void 0) {
287
- return false;
287
+ seen.add(type);
288
+ const declarations = type.getSymbol()?.getDeclarations() ?? [];
289
+ if (declarations.some(
290
+ (declaration) => isPrismaClientPath(declaration.getSourceFile().getFilePath())
291
+ )) {
292
+ return true;
288
293
  }
289
- const decls = symbol.getDeclarations?.() ?? [];
290
- for (const decl of decls) {
291
- const declSourceFile = decl.getSourceFile();
292
- const filePath = declSourceFile.getFilePath();
293
- if (filePath.includes("/@prisma/client/") || filePath.includes("/.prisma/client/")) {
294
- return true;
295
- }
296
- }
297
- return false;
294
+ return type.getBaseTypes().some((base) => extendsPrismaClient(base, seen));
295
+ }
296
+ function isPrismaClientPath(filePath) {
297
+ return filePath.includes("/@prisma/client/") || filePath.includes("/.prisma/client/");
298
298
  }
299
299
  function capitalizeFirst(name) {
300
300
  if (name.length === 0) {