@suss/packs 0.29.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.
@@ -29,6 +29,43 @@ var RETURNS_A_RECORD = [
29
29
  "create!",
30
30
  "reload"
31
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
+ ];
32
69
  var RETURNS_A_RELATION = [
33
70
  "where",
34
71
  "rewhere",
@@ -60,6 +97,7 @@ var RETURNS_A_RELATION = [
60
97
  "only",
61
98
  "except"
62
99
  ];
100
+ var READS = [...RUNS_A_QUERY, ...RETURNS_A_RELATION, "not"];
63
101
  function activeRecordStorage(options) {
64
102
  return [
65
103
  {
@@ -81,9 +119,24 @@ function activeRecordStorage(options) {
81
119
  "destroy_all",
82
120
  "delete",
83
121
  "delete_all",
84
- "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!"
85
127
  ],
128
+ reads: READS,
86
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
+ },
87
140
  storageSystem: options.storageSystem
88
141
  }
89
142
  ];
@@ -108,7 +161,7 @@ var declares = {
108
161
  { ecosystem: "rubygems", name: "activerecord" },
109
162
  { ecosystem: "rubygems", name: "rails" }
110
163
  ],
111
- 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.",
112
165
  configuration: {
113
166
  file: "suss.activerecord.json",
114
167
  example: { storageSystem: "postgresql" },
@@ -96,7 +96,8 @@ function sqlalchemyModels() {
96
96
  baseNames: MODEL_BASE_NAMES,
97
97
  givesBack: [...NARROWS_A_QUERY, ...RUNS_A_QUERY],
98
98
  entryMethods: SESSION_ENTRY_METHODS,
99
- entryFunctions: [{ module: "sqlalchemy", name: "select", argument: 0 }]
99
+ entryFunctions: [{ module: "sqlalchemy", name: "select", argument: 0 }],
100
+ relationships: [{ module: "sqlalchemy.orm", name: "relationship" }]
100
101
  }
101
102
  ];
102
103
  }
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-CZY5QLMS.js";
5
+ } from "./chunk-NYWZHQBJ.js";
6
6
  export {
7
7
  declares,
8
8
  index_default as default,
package/dist/sqlmodel.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  sqlalchemyModels,
3
3
  sqlalchemyRawSql,
4
4
  sqlalchemyStorage
5
- } from "./chunk-CZY5QLMS.js";
5
+ } from "./chunk-NYWZHQBJ.js";
6
6
 
7
7
  // ../framework/sqlmodel/dist/index.js
8
8
  import { z } from "zod";
@@ -78,6 +78,10 @@ function sqlmodelModels() {
78
78
  entryFunctions: [
79
79
  ...model.entryFunctions,
80
80
  { module: "sqlmodel", name: "select", argument: 0 }
81
+ ],
82
+ relationships: [
83
+ ...model.relationships ?? [],
84
+ { module: "sqlmodel", name: "Relationship" }
81
85
  ]
82
86
  }));
83
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/packs",
3
- "version": "0.29.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.29.0",
222
- "@suss/adapter-ruby": "0.29.0",
223
- "@suss/adapter-typescript": "0.29.0",
224
- "@suss/behavioral-ir": "0.29.0",
225
- "@suss/contract-graphql": "0.29.0",
226
- "@suss/extractor": "0.29.0",
227
- "@suss/manifest-aws": "0.29.0",
228
- "@suss/recognize": "0.29.0",
229
- "@suss/sql": "0.29.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": {