@suss/packs 0.28.0 → 0.30.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.
@@ -8,6 +8,96 @@ var optionsSchema = z.object({
8
8
  */
9
9
  storageSystem: storageSystemOption
10
10
  }).strict();
11
+ var RETURNS_A_RECORD = [
12
+ "find",
13
+ "find_by",
14
+ "find_by!",
15
+ "find_or_create_by",
16
+ "find_or_create_by!",
17
+ "find_or_initialize_by",
18
+ "first",
19
+ "first!",
20
+ "last",
21
+ "last!",
22
+ "take",
23
+ "take!",
24
+ "sole",
25
+ "find_sole_by",
26
+ "new",
27
+ "build",
28
+ "create",
29
+ "create!",
30
+ "reload"
31
+ ];
32
+ var RUNS_A_QUERY = [
33
+ "find",
34
+ "find_by",
35
+ "find_by!",
36
+ "find_sole_by",
37
+ "sole",
38
+ "first",
39
+ "first!",
40
+ "last",
41
+ "last!",
42
+ "take",
43
+ "take!",
44
+ "exists?",
45
+ "count",
46
+ "size",
47
+ "any?",
48
+ "none?",
49
+ "many?",
50
+ "empty?",
51
+ "pluck",
52
+ "pick",
53
+ "ids",
54
+ "sum",
55
+ "average",
56
+ "minimum",
57
+ "maximum",
58
+ "calculate",
59
+ "to_a",
60
+ "each",
61
+ "map",
62
+ "find_each",
63
+ "find_in_batches",
64
+ "in_batches",
65
+ "load",
66
+ "reload",
67
+ "find_or_initialize_by"
68
+ ];
69
+ var RETURNS_A_RELATION = [
70
+ "where",
71
+ "rewhere",
72
+ "or",
73
+ "order",
74
+ "reorder",
75
+ "limit",
76
+ "offset",
77
+ "includes",
78
+ "preload",
79
+ "eager_load",
80
+ "joins",
81
+ "left_joins",
82
+ "left_outer_joins",
83
+ "distinct",
84
+ "select",
85
+ "group",
86
+ "having",
87
+ "merge",
88
+ "readonly",
89
+ "lock",
90
+ "none",
91
+ "all",
92
+ "unscoped",
93
+ "unscope",
94
+ "extending",
95
+ "from",
96
+ "references",
97
+ "only",
98
+ "except"
99
+ ];
100
+ var READS = [...RUNS_A_QUERY, ...RETURNS_A_RELATION, "not"];
11
101
  function activeRecordStorage(options) {
12
102
  return [
13
103
  {
@@ -29,8 +119,24 @@ function activeRecordStorage(options) {
29
119
  "destroy_all",
30
120
  "delete",
31
121
  "delete_all",
32
- "touch"
122
+ "touch",
123
+ // These two read first and store only when the read found
124
+ // nothing, and a write is the stronger of the two claims.
125
+ "find_or_create_by",
126
+ "find_or_create_by!"
33
127
  ],
128
+ reads: READS,
129
+ givesBack: [...RETURNS_A_RECORD, ...RETURNS_A_RELATION],
130
+ byPrimaryKey: {
131
+ methods: ["find", "exists?", "update", "destroy", "delete"],
132
+ column: "id"
133
+ },
134
+ columnArguments: ["select", "pluck", "pick"],
135
+ associations: {
136
+ singular: ["has_one", "belongs_to"],
137
+ plural: ["has_many", "has_and_belongs_to_many"],
138
+ classNameKeyword: "class_name"
139
+ },
34
140
  storageSystem: options.storageSystem
35
141
  }
36
142
  ];
@@ -55,7 +161,7 @@ var declares = {
55
161
  { ecosystem: "rubygems", name: "activerecord" },
56
162
  { ecosystem: "rubygems", name: "rails" }
57
163
  ],
58
- reads: "ActiveRecord calls (Ruby): a call matches when the constant its receivers start at reaches `ActiveRecord::Base`, following what each class extends through the project.",
164
+ reads: "ActiveRecord calls (Ruby): a call matches when its method is one ActiveRecord defines as a read or a write and the class behind its receiver reaches `ActiveRecord::Base`, following what each class extends through the project.",
59
165
  configuration: {
60
166
  file: "suss.activerecord.json",
61
167
  example: { storageSystem: "postgresql" },
@@ -55,6 +55,52 @@ function sqlalchemyStorage(options) {
55
55
  }
56
56
  ];
57
57
  }
58
+ var MODEL_BASE_NAMES = [
59
+ "DeclarativeBase",
60
+ "declarative_base",
61
+ "DeclarativeBaseNoMeta"
62
+ ];
63
+ var NARROWS_A_QUERY = [
64
+ "filter",
65
+ "filter_by",
66
+ "where",
67
+ "order_by",
68
+ "limit",
69
+ "offset",
70
+ "options",
71
+ "join",
72
+ "outerjoin",
73
+ "distinct",
74
+ "group_by",
75
+ "having"
76
+ ];
77
+ var RUNS_A_QUERY = [
78
+ "first",
79
+ "one",
80
+ "one_or_none",
81
+ "scalar",
82
+ "scalar_one",
83
+ "scalar_one_or_none",
84
+ "scalars",
85
+ "get",
86
+ "all"
87
+ ];
88
+ var SESSION_ENTRY_METHODS = [
89
+ { method: "get", argument: 0 },
90
+ { method: "query", argument: 0 },
91
+ { method: "execute", argument: 0 }
92
+ ];
93
+ function sqlalchemyModels() {
94
+ return [
95
+ {
96
+ baseNames: MODEL_BASE_NAMES,
97
+ givesBack: [...NARROWS_A_QUERY, ...RUNS_A_QUERY],
98
+ entryMethods: SESSION_ENTRY_METHODS,
99
+ entryFunctions: [{ module: "sqlalchemy", name: "select", argument: 0 }],
100
+ relationships: [{ module: "sqlalchemy.orm", name: "relationship" }]
101
+ }
102
+ ];
103
+ }
58
104
  function sqlalchemyRawSql(options) {
59
105
  return [
60
106
  {
@@ -75,6 +121,7 @@ function sqlalchemyFramework(options) {
75
121
  protocol: options.storageSystem,
76
122
  discovery: [],
77
123
  storage: sqlalchemyStorage(options),
124
+ models: sqlalchemyModels(),
78
125
  rawSql: sqlalchemyRawSql(options)
79
126
  };
80
127
  }
@@ -95,6 +142,7 @@ var index_default = sqlalchemyFramework;
95
142
  export {
96
143
  optionsSchema,
97
144
  sqlalchemyStorage,
145
+ sqlalchemyModels,
98
146
  sqlalchemyRawSql,
99
147
  declares,
100
148
  index_default
package/dist/rails.js CHANGED
@@ -133,25 +133,66 @@ function isolatedNamespaceOf(classNode, acronyms) {
133
133
  return "";
134
134
  }
135
135
  var ACRONYM_CALL = /\.acronym\s*\(?\s*(["'])([A-Za-z\d]+)\1/g;
136
- function readAcronyms(configDirectory) {
137
- const found = /* @__PURE__ */ new Set();
136
+ var IRREGULAR_CALL = /\.irregular\s*\(?\s*(["'])([^"']*)\1\s*,\s*(["'])([^"']*)\3/g;
137
+ var UNCOUNTABLE_CALL = /\.uncountable\s*\(?\s*([^\n]*)/g;
138
+ var SINGULAR_CALL = /\.singular\s*\(?\s*(?:(["'])([^"']*)\1|\/([^/]*)\/([a-z]*))\s*,\s*(["'])([^"']*)\5/g;
139
+ var WORD_LIST = /%[wi][([{]([^)\]}]*)[)\]}]/;
140
+ var EVERY_LITERAL = /(["'])([^"']*)\1/g;
141
+ var ANY_CALL = /\.(acronym|irregular|uncountable|singular)[\s("'/]/;
142
+ function group(match, at) {
143
+ return match[at] ?? "";
144
+ }
145
+ function readInflections(configDirectory) {
146
+ const acronyms = /* @__PURE__ */ new Set();
147
+ const irregular = [];
148
+ const uncountable = /* @__PURE__ */ new Set();
149
+ const singular = [];
138
150
  for (const file of inflectionFiles(configDirectory)) {
139
- const source = fs2.readFileSync(file, "utf8");
151
+ const source = uncommented(fs2.readFileSync(file, "utf8"));
140
152
  for (const match of source.matchAll(ACRONYM_CALL)) {
141
- found.add(match[2] ?? "");
153
+ acronyms.add(group(match, 2));
154
+ }
155
+ for (const match of source.matchAll(IRREGULAR_CALL)) {
156
+ irregular.push([group(match, 4), group(match, 2)]);
157
+ }
158
+ for (const match of source.matchAll(UNCOUNTABLE_CALL)) {
159
+ for (const word of wordsIn(group(match, 1))) {
160
+ uncountable.add(word);
161
+ }
162
+ }
163
+ for (const match of source.matchAll(SINGULAR_CALL)) {
164
+ singular.push([ruleOf(match), group(match, 6)]);
142
165
  }
143
166
  }
144
- found.delete("");
145
- return [...found].sort();
167
+ acronyms.delete("");
168
+ uncountable.delete("");
169
+ return {
170
+ acronyms: [...acronyms].sort(),
171
+ irregular,
172
+ uncountable: [...uncountable].sort(),
173
+ singular
174
+ };
175
+ }
176
+ function ruleOf(match) {
177
+ const literal = match[2];
178
+ return literal === void 0 ? `/${group(match, 3)}/${group(match, 4)}` : literal;
179
+ }
180
+ function wordsIn(argument) {
181
+ const list = WORD_LIST.exec(argument);
182
+ if (list !== null) {
183
+ return group(list, 1).split(/\s+/).filter((word) => word.length > 0);
184
+ }
185
+ return [...argument.matchAll(EVERY_LITERAL)].map((match) => group(match, 2));
146
186
  }
147
187
  function inflectionFiles(configDirectory) {
148
188
  const directory = path2.join(configDirectory, "config", "initializers");
149
189
  if (!fs2.existsSync(directory)) {
150
190
  return [];
151
191
  }
152
- return fs2.readdirSync(directory, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rb")).map((entry) => path2.join(entry.parentPath, entry.name)).filter(
153
- (file) => fs2.readFileSync(file, "utf8").match(ACRONYM_CALL) !== null
154
- ).sort();
192
+ return fs2.readdirSync(directory, { withFileTypes: true, recursive: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".rb")).map((entry) => path2.join(entry.parentPath, entry.name)).filter((file) => ANY_CALL.test(uncommented(fs2.readFileSync(file, "utf8")))).sort();
193
+ }
194
+ function uncommented(source) {
195
+ return source.split("\n").filter((line) => !line.trimStart().startsWith("#")).join("\n");
155
196
  }
156
197
  function readSimpleArgs(call, defaults = {}) {
157
198
  const argumentList = field2(call, "arguments");
@@ -926,6 +967,19 @@ var RAILS_CONTROLLER_METHODS = [
926
967
  "controller_name",
927
968
  "controller_path"
928
969
  ];
970
+ var BODY_BLOCKS = [
971
+ { name: "included", moduleOnly: true },
972
+ { name: "prepended", moduleOnly: true },
973
+ { name: "class_methods", moduleOnly: true, definesClassMethods: true },
974
+ { name: "concerning" },
975
+ { name: "with_options" }
976
+ ];
977
+ var EMPTY_INFLECTIONS = {
978
+ acronyms: [],
979
+ irregular: [],
980
+ uncountable: [],
981
+ singular: []
982
+ };
929
983
  function controllerKeyFromQualified(qualifiedName, acronyms) {
930
984
  const withoutSuffix = qualifiedName.endsWith("Controller") ? qualifiedName.slice(0, -"Controller".length) : qualifiedName;
931
985
  return underscoreConstantPath2(withoutSuffix, acronyms);
@@ -956,7 +1010,8 @@ function railsFramework(options = {}) {
956
1010
  const displayPathOf = (file) => options.configDirectory === void 0 ? file : path4.relative(options.configDirectory, file);
957
1011
  const engineRoots = () => expandPatterns(options.configDirectory, options.engineRoots);
958
1012
  const extraRoutesFiles = () => expandPatterns(options.configDirectory, options.routesFiles);
959
- const acronyms = options.configDirectory === void 0 ? [] : readAcronyms(options.configDirectory);
1013
+ const inflections = options.configDirectory === void 0 ? EMPTY_INFLECTIONS : readInflections(options.configDirectory);
1014
+ const acronyms = inflections.acronyms;
960
1015
  const routesInput = () => ({
961
1016
  routesFile: {
962
1017
  file: routesFile,
@@ -1006,6 +1061,8 @@ function railsFramework(options = {}) {
1006
1061
  name: "rails",
1007
1062
  protocol: "http",
1008
1063
  discovery: [pattern],
1064
+ bodyBlocks: BODY_BLOCKS,
1065
+ inflections,
1009
1066
  // Every file routing or naming is read from decides an action's
1010
1067
  // binding without being walked, so the cache key has to read them
1011
1068
  // here. This runs before the grammar loads, so nothing parses Ruby.
@@ -2,7 +2,7 @@ import {
2
2
  declares,
3
3
  index_default,
4
4
  optionsSchema
5
- } from "./chunk-SKGM3QL5.js";
5
+ } from "./chunk-NYWZHQBJ.js";
6
6
  export {
7
7
  declares,
8
8
  index_default as default,
package/dist/sqlmodel.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import {
2
+ sqlalchemyModels,
2
3
  sqlalchemyRawSql,
3
4
  sqlalchemyStorage
4
- } from "./chunk-SKGM3QL5.js";
5
+ } from "./chunk-NYWZHQBJ.js";
5
6
 
6
7
  // ../framework/sqlmodel/dist/index.js
7
8
  import { z } from "zod";
@@ -69,6 +70,21 @@ function sqlmodelStorage(options) {
69
70
  ...sqlalchemyStorage(options)
70
71
  ];
71
72
  }
73
+ function sqlmodelModels() {
74
+ return sqlalchemyModels().map((model) => ({
75
+ baseNames: [...model.baseNames, "SQLModel"],
76
+ givesBack: model.givesBack,
77
+ entryMethods: [...model.entryMethods, { method: "exec", argument: 0 }],
78
+ entryFunctions: [
79
+ ...model.entryFunctions,
80
+ { module: "sqlmodel", name: "select", argument: 0 }
81
+ ],
82
+ relationships: [
83
+ ...model.relationships ?? [],
84
+ { module: "sqlmodel", name: "Relationship" }
85
+ ]
86
+ }));
87
+ }
72
88
  function sqlmodelRawSql(options) {
73
89
  return [
74
90
  {
@@ -90,6 +106,7 @@ function sqlmodelFramework(options) {
90
106
  protocol: options.storageSystem,
91
107
  discovery: [],
92
108
  storage: sqlmodelStorage(options),
109
+ models: sqlmodelModels(),
93
110
  rawSql: sqlmodelRawSql(options)
94
111
  };
95
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/packs",
3
- "version": "0.28.0",
3
+ "version": "0.30.0",
4
4
  "type": "module",
5
5
  "description": "Every pack suss ships in one install, so it can read Express, Hono, NestJS, FastAPI, Rails, Prisma, OpenAPI and the rest.",
6
6
  "keywords": [
@@ -218,15 +218,15 @@
218
218
  "ts-morph": "^23.0.0"
219
219
  },
220
220
  "dependencies": {
221
- "@suss/adapter-python": "0.28.0",
222
- "@suss/adapter-ruby": "0.28.0",
223
- "@suss/adapter-typescript": "0.28.0",
224
- "@suss/behavioral-ir": "0.28.0",
225
- "@suss/contract-graphql": "0.28.0",
226
- "@suss/extractor": "0.28.0",
227
- "@suss/manifest-aws": "0.28.0",
228
- "@suss/recognize": "0.28.0",
229
- "@suss/sql": "0.28.0",
221
+ "@suss/adapter-python": "0.30.0",
222
+ "@suss/adapter-ruby": "0.30.0",
223
+ "@suss/adapter-typescript": "0.30.0",
224
+ "@suss/behavioral-ir": "0.30.0",
225
+ "@suss/contract-graphql": "0.30.0",
226
+ "@suss/extractor": "0.30.0",
227
+ "@suss/manifest-aws": "0.30.0",
228
+ "@suss/recognize": "0.30.0",
229
+ "@suss/sql": "0.30.0",
230
230
  "zod": "^4.0.0"
231
231
  },
232
232
  "devDependencies": {