@suss/packs 0.22.2 → 0.22.3

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.
package/dist/fastapi.js CHANGED
@@ -37,6 +37,20 @@ function fastapiFramework(options = {}) {
37
37
  defaultStatusCode: 200,
38
38
  responseModelKeyword: "response_model",
39
39
  statusCodeKeyword: "status_code",
40
+ // FastAPI re-exports Starlette's class, and a project may import
41
+ // it from either module.
42
+ responseStatusCalls: [
43
+ {
44
+ callee: "fastapi.HTTPException",
45
+ statusKeyword: "status_code",
46
+ statusArgument: 0
47
+ },
48
+ {
49
+ callee: "starlette.exceptions.HTTPException",
50
+ statusKeyword: "status_code",
51
+ statusArgument: 0
52
+ }
53
+ ],
40
54
  routerComposition: {
41
55
  routerConstructorName: "APIRouter",
42
56
  includeMethodName: "include_router",
@@ -57,8 +57,15 @@ function flaskRestxFramework(options = {}) {
57
57
  // and sets no status of its own.
58
58
  defaultStatusCode: 200,
59
59
  // Flask reads a status out of `return body, 201`, so the 200
60
- // above only stands when the body writes no status of its own.
61
- statusFromReturnedTuple: true
60
+ // above only applies where the body writes no status of its own.
61
+ statusFromReturnedTuple: true,
62
+ // flask-restx re-exports Flask's function, which re-exports
63
+ // Werkzeug's, and a project may import it from any of the three.
64
+ responseStatusCalls: [
65
+ { callee: "flask_restx.abort", statusArgument: 0 },
66
+ { callee: "flask.abort", statusArgument: 0 },
67
+ { callee: "werkzeug.exceptions.abort", statusArgument: 0 }
68
+ ]
62
69
  }
63
70
  ]
64
71
  };
@@ -11,8 +11,10 @@ var optionsSchema = z.object({
11
11
  */
12
12
  root: z.string(),
13
13
  /**
14
- * The directory of the config file these options came from. Whatever read
15
- * that file supplies this; it is not written in the file itself.
14
+ * The directory a relative `root` is read against: the config file's
15
+ * own directory, or the directory the run reads when the options came
16
+ * without one. The CLI supplies this; it is not written in the file
17
+ * itself.
16
18
  */
17
19
  configDirectory: z.string().optional(),
18
20
  /** Base classes beyond `Types::BaseObject` that also mark a class as declaring GraphQL fields. */
@@ -65,10 +65,9 @@ function nestjsRestFramework(options = {}) {
65
65
  extraction: {}
66
66
  },
67
67
  {
68
- // Many controllers fall through (return undefined) when the
69
- // operation is fire-and-forget (`@HttpCode(204)` for
70
- // delete-style). Keep a default transition so
71
- // `transitions: []` isn't the surface shape.
68
+ // A method that runs off the end returns undefined, and Nest
69
+ // sends the same 200 with an empty body it sends for a bare
70
+ // `return;`. Only `@HttpCode(N)` changes that.
72
71
  kind: "response",
73
72
  match: { type: "functionFallthrough" },
74
73
  extraction: {
package/dist/rails.js CHANGED
@@ -136,7 +136,9 @@ function joinKey(prefix, segment) {
136
136
  return prefix === "" ? segment : `${prefix}/${segment}`;
137
137
  }
138
138
  function joinPath(prefix, segment) {
139
- return `${prefix}/${segment}`;
139
+ const trimmed = segment.replace(/^\/+/, "").replace(/\/+$/, "");
140
+ const base = prefix.replace(/\/+$/, "");
141
+ return trimmed === "" ? base || "/" : `${base}/${trimmed}`;
140
142
  }
141
143
  function handleResourceCall(call, ctx, out, plural) {
142
144
  const args = readSimpleArgs(call);
@@ -144,10 +146,10 @@ function handleResourceCall(call, ctx, out, plural) {
144
146
  if (symbol === null) {
145
147
  return;
146
148
  }
147
- const controllerSegment = plural ? symbol : pluralize(symbol);
149
+ const controllerSegment = wordValue(args.keyword.controller) ?? (plural ? symbol : pluralize(symbol));
148
150
  const controllerKey = joinKey(ctx.modulePrefix, controllerSegment);
149
151
  const prefixBase = ctx.resource !== void 0 ? ctx.resource.nestedBase : ctx.pathPrefix;
150
- const base = joinPath(prefixBase, symbol);
152
+ const base = joinPath(prefixBase, wordValue(args.keyword.path) ?? symbol);
151
153
  const only = readSymbolList(args.keyword.only);
152
154
  const except = readSymbolList(args.keyword.except);
153
155
  const table = plural ? RESTFUL_ROUTES_PLURAL : RESTFUL_ROUTES_SINGULAR;
@@ -165,12 +167,13 @@ function handleResourceCall(call, ctx, out, plural) {
165
167
  if (body === null) {
166
168
  return;
167
169
  }
170
+ const nestedBase = plural ? `${base}/:${singularize(symbol)}_id` : base;
168
171
  const nested = {
169
172
  controllerKey,
170
173
  collectionBase: base,
171
174
  memberBase: plural ? `${base}/:id` : base,
172
- nestedBase: `${base}/:${singularize(symbol)}_id`,
173
- ambientBase: null
175
+ nestedBase,
176
+ ambientBase: nestedBase
174
177
  };
175
178
  walkBody(body, { ...ctx, resource: nested }, out);
176
179
  }
@@ -186,6 +189,15 @@ function handleOnBlock(call, ctx, out, isMember) {
186
189
  const ambientBase = isMember ? ctx.resource.memberBase : ctx.resource.collectionBase;
187
190
  walkBody(body, { ...ctx, resource: { ...ctx.resource, ambientBase } }, out);
188
191
  }
192
+ function baseForOn(resource, on) {
193
+ if (on === "collection") {
194
+ return resource.collectionBase;
195
+ }
196
+ if (on === "member") {
197
+ return resource.memberBase;
198
+ }
199
+ return resource.ambientBase;
200
+ }
189
201
  function handleVerb(call, ctx, out, method) {
190
202
  const args = readSimpleArgs(call);
191
203
  const target = readRouteTarget(args);
@@ -193,9 +205,9 @@ function handleVerb(call, ctx, out, method) {
193
205
  const literalPath = args.positional[0] ? stringValueOf(args.positional[0]) : null;
194
206
  const path2 = target.path ?? literalPath;
195
207
  if (path2 !== null) {
196
- out.add(target.controllerKey, target.action, {
208
+ out.add(joinKey(ctx.modulePrefix, target.controllerKey), target.action, {
197
209
  method,
198
- path: `${ctx.pathPrefix}${path2}`
210
+ path: joinPath(ctx.pathPrefix, path2)
199
211
  });
200
212
  }
201
213
  return;
@@ -207,11 +219,7 @@ function handleVerb(call, ctx, out, method) {
207
219
  if (symbol === null) {
208
220
  return;
209
221
  }
210
- const on = wordValue(args.keyword.on);
211
- const base = on === "collection" ? ctx.resource.collectionBase : on === "member" ? ctx.resource.memberBase : ctx.resource.ambientBase;
212
- if (base === null) {
213
- return;
214
- }
222
+ const base = baseForOn(ctx.resource, wordValue(args.keyword.on));
215
223
  out.add(ctx.resource.controllerKey, symbol, {
216
224
  method,
217
225
  path: `${base}/${symbol}`
@@ -221,7 +229,7 @@ function handleRoot(call, ctx, out) {
221
229
  const args = readSimpleArgs(call);
222
230
  const target = readRouteTarget(args) ?? (args.positional[0] ? splitControllerAction(stringValueOf(args.positional[0]) ?? "") : null);
223
231
  if (target !== null) {
224
- out.add(target.controllerKey, target.action, {
232
+ out.add(joinKey(ctx.modulePrefix, target.controllerKey), target.action, {
225
233
  method: "GET",
226
234
  path: ctx.pathPrefix === "" ? "/" : ctx.pathPrefix
227
235
  });
@@ -247,7 +255,7 @@ function handleNamespace(call, ctx, out) {
247
255
  function handleScope(call, ctx, out) {
248
256
  const args = readSimpleArgs(call);
249
257
  const moduleName = wordValue(args.keyword.module);
250
- const pathSegment = wordValue(args.keyword.path);
258
+ const pathSegment = wordValue(args.keyword.path) ?? (args.positional[0] ? wordValue(args.positional[0]) : null);
251
259
  const block = field(call, "block");
252
260
  const body = block !== null ? field(block, "body") : null;
253
261
  if (body === null) {
@@ -329,16 +337,92 @@ function readRoutesFile(absRoutesPath, displayPath) {
329
337
  gaps: out.gapsAgainst(displayPath)
330
338
  };
331
339
  }
340
+ var RACK_STATUS_CODE_NAMES = {
341
+ continue: 100,
342
+ switching_protocols: 101,
343
+ processing: 102,
344
+ early_hints: 103,
345
+ ok: 200,
346
+ created: 201,
347
+ accepted: 202,
348
+ non_authoritative_information: 203,
349
+ no_content: 204,
350
+ reset_content: 205,
351
+ partial_content: 206,
352
+ multi_status: 207,
353
+ already_reported: 208,
354
+ im_used: 226,
355
+ multiple_choices: 300,
356
+ moved_permanently: 301,
357
+ found: 302,
358
+ see_other: 303,
359
+ not_modified: 304,
360
+ use_proxy: 305,
361
+ temporary_redirect: 307,
362
+ permanent_redirect: 308,
363
+ bad_request: 400,
364
+ unauthorized: 401,
365
+ payment_required: 402,
366
+ forbidden: 403,
367
+ not_found: 404,
368
+ method_not_allowed: 405,
369
+ not_acceptable: 406,
370
+ proxy_authentication_required: 407,
371
+ request_timeout: 408,
372
+ conflict: 409,
373
+ gone: 410,
374
+ length_required: 411,
375
+ precondition_failed: 412,
376
+ request_entity_too_large: 413,
377
+ payload_too_large: 413,
378
+ content_too_large: 413,
379
+ request_uri_too_long: 414,
380
+ uri_too_long: 414,
381
+ unsupported_media_type: 415,
382
+ requested_range_not_satisfiable: 416,
383
+ range_not_satisfiable: 416,
384
+ expectation_failed: 417,
385
+ misdirected_request: 421,
386
+ unprocessable_entity: 422,
387
+ unprocessable_content: 422,
388
+ locked: 423,
389
+ failed_dependency: 424,
390
+ too_early: 425,
391
+ upgrade_required: 426,
392
+ precondition_required: 428,
393
+ too_many_requests: 429,
394
+ request_header_fields_too_large: 431,
395
+ unavailable_for_legal_reasons: 451,
396
+ internal_server_error: 500,
397
+ not_implemented: 501,
398
+ bad_gateway: 502,
399
+ service_unavailable: 503,
400
+ gateway_timeout: 504,
401
+ http_version_not_supported: 505,
402
+ variant_also_negotiates: 506,
403
+ insufficient_storage: 507,
404
+ loop_detected: 508,
405
+ not_extended: 510,
406
+ network_authentication_required: 511
407
+ };
332
408
  var optionsSchema = z.object({
333
409
  /** The app directory a bare controller name is looked up under. Every Rails app scaffolds this at `app`. */
334
410
  root: z.string().optional(),
335
411
  /**
336
- * The directory of the config file these options came from. Whatever
337
- * read that file supplies this; it is not written in the file itself.
412
+ * The directory a relative `root` or `routesFile` is read against: the
413
+ * config file's own directory, or the directory the run reads when the
414
+ * options came without one. The CLI supplies this; it is not written in
415
+ * the file itself.
338
416
  */
339
417
  configDirectory: z.string().optional(),
340
418
  /** Base classes beyond `ApplicationController` that also mark a class as a controller. */
341
419
  baseClassNames: z.array(z.string()).optional(),
420
+ /**
421
+ * Methods beyond Rails' own that every controller in this project
422
+ * gets without defining them, from a gem such as Devise. They are
423
+ * left off an action's effects the same way Rails' own are.
424
+ */
425
+ inheritedMethodNames: z.array(z.string()).optional(),
342
426
  /** Where this project's routes live, relative to `configDirectory` when there is one. Every Rails app scaffolds this at `config/routes.rb`. */
343
427
  routesFile: z.string().optional()
344
428
  }).strict();
@@ -355,6 +439,33 @@ var RESTFUL_ACTIONS = {
355
439
  update: { method: "PATCH", pathTemplate: "/:resource/:id" },
356
440
  destroy: { method: "DELETE", pathTemplate: "/:resource/:id" }
357
441
  };
442
+ var RESPONSE_STATUS_CALLS = [
443
+ { name: "render", statusKeyword: "status" },
444
+ { name: "head", statusArgument: 0, statusKeyword: "status" },
445
+ { name: "redirect_to", statusKeyword: "status", defaultStatusCode: 302 },
446
+ { name: "redirect_back", statusKeyword: "status", defaultStatusCode: 302 }
447
+ ];
448
+ var RAILS_CONTROLLER_METHODS = [
449
+ "params",
450
+ "request",
451
+ "response",
452
+ "session",
453
+ "cookies",
454
+ "flash",
455
+ "headers",
456
+ "render",
457
+ "head",
458
+ "redirect_to",
459
+ "redirect_back",
460
+ "respond_to",
461
+ "render_to_string",
462
+ "url_for",
463
+ "helpers",
464
+ "logger",
465
+ "action_name",
466
+ "controller_name",
467
+ "controller_path"
468
+ ];
358
469
  function controllerKeyFromQualified(qualifiedName) {
359
470
  const withoutSuffix = qualifiedName.endsWith("Controller") ? qualifiedName.slice(0, -"Controller".length) : qualifiedName;
360
471
  return underscoreConstantPath(withoutSuffix);
@@ -400,6 +511,12 @@ function railsFramework(options = {}) {
400
511
  pathConvention: "railsUnderscore",
401
512
  ancestryRootClassNames: [...RAILS_ROOT_CLASS_NAMES],
402
513
  defaultStatusCode: 200,
514
+ responseStatusCalls: RESPONSE_STATUS_CALLS,
515
+ statusCodeNames: RACK_STATUS_CODE_NAMES,
516
+ inheritedMethodNames: [
517
+ ...RAILS_CONTROLLER_METHODS,
518
+ ...options.inheritedMethodNames ?? []
519
+ ],
403
520
  routesFile,
404
521
  routeFor: (controllerQualifiedName, actionName) => {
405
522
  const key = controllerKeyFromQualified(controllerQualifiedName);
@@ -14,7 +14,32 @@ function sqlalchemyStorage(options) {
14
14
  {
15
15
  module: "sqlalchemy.orm",
16
16
  queryTypes: ["Query", "Session"],
17
- writes: ["update", "delete", "add", "add_all", "merge", "commit"],
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
+ ],
18
43
  storageSystem: options.storageSystem
19
44
  },
20
45
  {
@@ -25,6 +50,7 @@ function sqlalchemyStorage(options) {
25
50
  // constructor rather than reaching it through a mapped class, so there
26
51
  // is no project method in between whose return says what it is.
27
52
  queryFunctions: ["select", "insert", "update", "delete"],
53
+ valueMethods: ["values"],
28
54
  storageSystem: options.storageSystem
29
55
  }
30
56
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/packs",
3
- "version": "0.22.2",
3
+ "version": "0.22.3",
4
4
  "type": "module",
5
5
  "description": "Every pack suss ships, as one package with a subpath per pack.",
6
6
  "keywords": [
@@ -189,15 +189,15 @@
189
189
  "ts-morph": "^23.0.0"
190
190
  },
191
191
  "dependencies": {
192
- "@suss/adapter-python": "0.22.2",
193
- "@suss/adapter-ruby": "0.22.2",
194
- "@suss/adapter-typescript": "0.22.2",
195
- "@suss/behavioral-ir": "0.22.2",
196
- "@suss/contract-graphql": "0.22.2",
197
- "@suss/extractor": "0.22.2",
198
- "@suss/manifest-aws": "0.22.2",
199
- "@suss/recognize": "0.22.2",
200
- "@suss/sql": "0.22.2",
192
+ "@suss/adapter-python": "0.22.3",
193
+ "@suss/adapter-ruby": "0.22.3",
194
+ "@suss/adapter-typescript": "0.22.3",
195
+ "@suss/behavioral-ir": "0.22.3",
196
+ "@suss/contract-graphql": "0.22.3",
197
+ "@suss/extractor": "0.22.3",
198
+ "@suss/manifest-aws": "0.22.3",
199
+ "@suss/recognize": "0.22.3",
200
+ "@suss/sql": "0.22.3",
201
201
  "zod": "^4.0.0"
202
202
  },
203
203
  "devDependencies": {