@suss/packs 0.33.0 → 0.34.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.
Files changed (63) hide show
  1. package/README.md +10 -11
  2. package/dist/activerecord.d.ts +7 -0
  3. package/dist/activerecord.js +34 -4
  4. package/dist/apollo-client.js +26 -39
  5. package/dist/apollo.js +12 -20
  6. package/dist/aws-dynamodb.js +4 -4
  7. package/dist/aws-eventbridge.js +3 -3
  8. package/dist/aws-lambda.js +31 -54
  9. package/dist/aws-s3.js +1 -1
  10. package/dist/aws-secrets-manager.js +1 -1
  11. package/dist/aws-sns.js +2 -2
  12. package/dist/aws-sqs.js +20 -11
  13. package/dist/aws-ssm.js +1 -1
  14. package/dist/axios.js +13 -27
  15. package/dist/bigquery-python.js +3 -3
  16. package/dist/bigquery.js +1 -1
  17. package/dist/{chunk-NYWZHQBJ.js → chunk-GK4BILHB.js} +8 -9
  18. package/dist/cloudflare-workers.js +26 -35
  19. package/dist/drizzle.js +15 -20
  20. package/dist/express.js +3 -6
  21. package/dist/fabrication.d.ts +1 -0
  22. package/dist/fabrication.js +36 -0
  23. package/dist/factory-bot.d.ts +1 -0
  24. package/dist/factory-bot.js +36 -0
  25. package/dist/faraday.js +4 -5
  26. package/dist/fastapi.js +21 -5
  27. package/dist/fastify.js +10 -14
  28. package/dist/fetch.js +3 -4
  29. package/dist/flask-restx.js +19 -12
  30. package/dist/gcs.js +1 -1
  31. package/dist/graphql-ruby.js +20 -16
  32. package/dist/hono.js +17 -27
  33. package/dist/httpx.js +3 -5
  34. package/dist/minitest.d.ts +1 -0
  35. package/dist/minitest.js +59 -0
  36. package/dist/mongoose.js +7 -7
  37. package/dist/nestjs-graphql.js +14 -25
  38. package/dist/nestjs-microservices.js +9 -10
  39. package/dist/nestjs-rest.js +42 -22
  40. package/dist/net-http.js +5 -6
  41. package/dist/nextjs.js +16 -17
  42. package/dist/node.js +144 -28
  43. package/dist/package-exports.js +1 -1
  44. package/dist/pg.js +3 -2
  45. package/dist/prisma.js +281 -48
  46. package/dist/pytest.d.ts +1 -0
  47. package/dist/pytest.js +81 -0
  48. package/dist/rails.js +46 -27
  49. package/dist/react-query.js +1 -1
  50. package/dist/react-router.js +21 -48
  51. package/dist/react.js +14 -5
  52. package/dist/redis.js +1 -1
  53. package/dist/requests.js +4 -5
  54. package/dist/rspec.d.ts +1 -0
  55. package/dist/rspec.js +135 -0
  56. package/dist/sqlalchemy.js +1 -1
  57. package/dist/sqlmodel.js +7 -8
  58. package/dist/ts-rest.js +6 -8
  59. package/dist/typeorm.d.ts +1 -0
  60. package/dist/typeorm.js +291 -0
  61. package/dist/vitest.d.ts +1 -0
  62. package/dist/vitest.js +64 -0
  63. package/package.json +47 -11
package/dist/fastify.js CHANGED
@@ -17,8 +17,8 @@ function fastifyFramework(_options = {}) {
17
17
  name: "fastify",
18
18
  protocol: "http",
19
19
  languages: ["typescript", "javascript"],
20
- // Fastify exposes the routable via either default `Fastify` or
21
- // named `fastify()`. Both drive handler registration the same way.
20
+ // Projects import the app factory as the default `Fastify` or the
21
+ // named `fastify`, and both register routes the same way.
22
22
  discovery: [
23
23
  ...httpRouteDiscovery({
24
24
  importModule: "fastify",
@@ -48,7 +48,7 @@ function fastifyFramework(_options = {}) {
48
48
  }
49
49
  },
50
50
  {
51
- // reply.status(N).send(body), `.status` is the Express-style alias
51
+ // reply.status(N).send(body), the alias for `code`
52
52
  kind: "response",
53
53
  match: {
54
54
  type: "parameterMethodCall",
@@ -74,9 +74,8 @@ function fastifyFramework(_options = {}) {
74
74
  }
75
75
  },
76
76
  {
77
- // reply.redirect(url) or reply.redirect(N, url)
78
- // Arg 0 is a status code only in the 2-arg form; minArgs prevents
79
- // extracting the URL string as a status code in the 1-arg form.
77
+ // reply.redirect(url) or reply.redirect(N, url). `minArgs` keeps
78
+ // the URL in the one-argument form from being read as a status.
80
79
  kind: "response",
81
80
  match: {
82
81
  type: "parameterMethodCall",
@@ -89,19 +88,16 @@ function fastifyFramework(_options = {}) {
89
88
  }
90
89
  },
91
90
  {
92
- // throw new Error(...) / throw httpErrors.notFound() / etc.
93
- // Status code is left for the consumer to infer from exception type;
94
- // Fastify error libraries vary too widely to map here.
91
+ // throw httpErrors.notFound(). Fastify error libraries differ too
92
+ // much to map a status here, so the throw does not record one.
95
93
  kind: "throw",
96
94
  match: { type: "throwExpression" },
97
95
  extraction: {}
98
96
  },
99
97
  {
100
- // `return user`, `return { id, name }`, `return await db.find(id)`,
101
- // Fastify serialises the returned value as a 200 response body.
102
- // `excludeCallReturns: true` keeps `return reply.send(...)` out
103
- // of this branch: that call already lands as a parameterMethodCall
104
- // terminal above, and matching it here would double-fire.
98
+ // `return user`: Fastify sends the value as a 200 body. Without
99
+ // `excludeCallReturns`, `return reply.send(...)` would match here
100
+ // and again as the `send` terminal above.
105
101
  kind: "response",
106
102
  match: {
107
103
  type: "returnStatement",
package/dist/fetch.js CHANGED
@@ -21,10 +21,9 @@ function webFetchPack() {
21
21
  },
22
22
  path: { type: "fromArgument", position: 0 }
23
23
  },
24
- // No import gate: `fetch` is a global. Walk every file,
25
- // the discovery dispatch is itself cheap (just looks for
26
- // call expressions named `fetch`), so the lack of pre-
27
- // filter doesn't blow up perf.
24
+ // `fetch` is a global, so there is no import to gate on. Walking
25
+ // every file stays cheap because discovery only looks for calls
26
+ // named `fetch`.
28
27
  requiresImport: []
29
28
  }
30
29
  ],
@@ -2,10 +2,9 @@
2
2
  import { z } from "zod";
3
3
  var optionsSchema = z.object({
4
4
  /**
5
- * Modules a project's own wrapper re-exports flask-restx's route
6
- * decorator from. flask-restx's own module is always accepted. The
7
- * wrapper's name is the project's own choice, so it is supplied by
8
- * whoever configures the pack rather than hardcoded here.
5
+ * Modules that re-export flask-restx's route decorator, on top of
6
+ * `flask_restx` itself. A dependency stub fills this in; a project's
7
+ * config file may not set it.
9
8
  */
10
9
  wrapperModules: z.array(z.string()).optional()
11
10
  }).strict();
@@ -30,8 +29,8 @@ function flaskRestxFramework(options = {}) {
30
29
  decoratorName: "route",
31
30
  verbMethodNames: VERB_METHOD_NAMES,
32
31
  pathParamSyntax: "flaskConverters",
33
- // What each of these settings means for a written prefix is the
34
- // grid in the Python adapter's README.
32
+ // The Python adapter's README has a table of what each of these
33
+ // settings does to a written prefix.
35
34
  pathRepeatedSlashes: "merged",
36
35
  routerComposition: {
37
36
  routerConstructorName: "Namespace",
@@ -53,11 +52,9 @@ function flaskRestxFramework(options = {}) {
53
52
  }
54
53
  }
55
54
  },
56
- // Flask returns 200 from a resource method that returns a value
57
- // and sets no status of its own.
58
55
  defaultStatusCode: 200,
59
56
  // Flask reads a status out of `return body, 201`, so the 200
60
- // above only applies where the body writes no status of its own.
57
+ // above applies only when the return writes no status.
61
58
  statusFromReturnedTuple: true,
62
59
  // flask-restx re-exports Flask's function, which re-exports
63
60
  // Werkzeug's, and a project may import it from any of the three.
@@ -70,8 +67,8 @@ function flaskRestxFramework(options = {}) {
70
67
  {
71
68
  type: "decoratedWrapper",
72
69
  attribute: "before_request",
73
- // A blueprint's own hook is not read: a route is decorated on
74
- // a namespace, and the blueprint it is served under is a mount.
70
+ // A blueprint's own hook is skipped, because a route is decorated
71
+ // on a namespace and the blueprint only mounts it.
75
72
  registrars: [
76
73
  {
77
74
  constructorName: "Flask",
@@ -100,7 +97,17 @@ function flaskRestxFramework(options = {}) {
100
97
  }
101
98
  ]
102
99
  }
103
- ]
100
+ ],
101
+ // A resource method reads the request off Flask's module-level object.
102
+ requestObjects: [{ module: "flask", name: "request" }],
103
+ // A path parameter is handed to the method, and the rest is read off
104
+ // Flask's `request`: `request.args` for the query string.
105
+ requestSpelling: {
106
+ headers: { path: ["request", "headers"], saysWhichField: true },
107
+ query: { path: ["request", "args"], saysWhichField: true },
108
+ params: { path: ["pathParams"], saysWhichField: true },
109
+ body: { path: ["request", "json"], saysWhichField: true }
110
+ }
104
111
  };
105
112
  }
106
113
  var declares = {
package/dist/gcs.js CHANGED
@@ -53,7 +53,7 @@ var declares = {
53
53
  kind: "effects",
54
54
  package: "@suss/framework-gcs",
55
55
  dependencies: [{ ecosystem: "npm", name: "@google-cloud/storage" }],
56
- reads: "Google Cloud Storage calls, emits storage-access interactions."
56
+ reads: "Google Cloud Storage calls. Each one becomes a storage-access interaction."
57
57
  };
58
58
  var index_default = gcsFramework;
59
59
  export {
@@ -4,20 +4,24 @@ import { z } from "zod";
4
4
  import { SCALAR_SHAPES } from "@suss/contract-graphql";
5
5
  var optionsSchema = z.object({
6
6
  /**
7
- * Directory a `mutation:` / `resolver:` field's referenced class is
8
- * looked up under, e.g. `path.join(repoRoot, "app/graphql")`.
9
- * Written relative, it is read relative to `configDirectory`, and
10
- * relative to the working directory when there is none.
7
+ * The directory the pack looks under for the class a `mutation:` or
8
+ * `resolver:` field references, for example
9
+ * `path.join(repoRoot, "app/graphql")`. A relative path resolves
10
+ * against `configDirectory`, or the working directory when that is
11
+ * unset.
11
12
  */
12
13
  root: z.string(),
13
14
  /**
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.
15
+ * The CLI fills this in with the config file's directory, or with the
16
+ * directory the run reads when there is no file. A config file does
17
+ * not set it.
18
18
  */
19
19
  configDirectory: z.string().optional(),
20
- /** Base classes beyond `Types::BaseObject` that also mark a class as declaring GraphQL fields. */
20
+ /**
21
+ * Base classes beyond the generated `Types::Base*` ones whose
22
+ * subclasses declare GraphQL fields. A config file may not set this; a
23
+ * dependency stub fills it.
24
+ */
21
25
  baseClassNames: z.array(z.string()).optional(),
22
26
  /** The schema-wide camelCase default. A `field` or `argument` call's own `camelize:` keyword overrides it for that one name. */
23
27
  camelize: z.boolean().optional()
@@ -72,10 +76,9 @@ function graphqlRubyFramework(options) {
72
76
  requiredDefault: true,
73
77
  camelizeKeyword: "camelize",
74
78
  camelizeDefault: options.camelize ?? true,
75
- // The standard scalars come from the shared SDL table, so a
76
- // contract read from Ruby compares against one read from SDL
77
- // without a vocabulary mismatch. graphql-ruby accepts Ruby's own
78
- // `Integer` class as a synonym for its `Int` scalar.
79
+ // The standard scalars come from the shared SDL table, so a contract
80
+ // read from Ruby uses the same shapes as one read from SDL.
81
+ // graphql-ruby also accepts Ruby's `Integer` class for `Int`.
79
82
  scalars: {
80
83
  ...SCALAR_SHAPES,
81
84
  Integer: { type: "number" }
@@ -98,7 +101,7 @@ function graphqlRubyFramework(options) {
98
101
  discovery: [pattern],
99
102
  // A resolver that reads a model through the dataloader passes the model
100
103
  // as an argument, so the read is recorded against that model when a
101
- // storage pack in the same run says it is one.
104
+ // storage pack in the same run recognizes it as one.
102
105
  loaders: [
103
106
  {
104
107
  loader: "dataloader",
@@ -121,12 +124,13 @@ var declares = {
121
124
  kind: "framework",
122
125
  package: "@suss/framework-graphql-ruby",
123
126
  dependencies: [{ ecosystem: "rubygems", name: "graphql" }],
124
- 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.",
127
+ reads: "graphql-ruby class-based field DSL (Ruby). The pack follows `mutation:` and `resolver:` wiring one hop out to what the referenced class declares. It also records the model a resolver reads through `dataloader`, when a storage pack in the run recognizes that model.",
125
128
  configuration: {
126
129
  file: "suss.graphql-ruby.json",
127
130
  example: { root: "app/graphql" },
128
131
  required: true,
129
- why: "the directory a field's wired class is looked up under, read relative to this file. A Rails app generated by the library keeps its types and mutations under app/graphql."
132
+ why: "the directory a field's wired class is looked up under, read relative to this file. A Rails app generated by the library keeps its types and mutations under app/graphql.",
133
+ defaults: { root: "app/graphql" }
130
134
  }
131
135
  };
132
136
  var index_default = graphqlRubyFramework;
package/dist/hono.js CHANGED
@@ -37,21 +37,15 @@ function honoFramework(_options = {}) {
37
37
  name: "hono",
38
38
  protocol: "http",
39
39
  languages: ["typescript", "javascript"],
40
- // createRoute wraps its config without changing it; the call IS the
41
- // route object. Declared here because the wrapper's body lives in
42
- // the library, where nobody can read it.
40
+ // `createRoute` returns its config unchanged, so the call is the route
41
+ // object. The pack has to declare this because the function's body is
42
+ // in the library, where suss cannot read it.
43
43
  transparentWrappers: [
44
- { callee: "createRoute", argument: 0, module: "@hono/zod-openapi" }
44
+ { module: "@hono/zod-openapi", name: "createRoute", argument: 0 }
45
45
  ],
46
- // `new Hono()` and `new OpenAPIHono()` both register the same way.
47
- // Sub-apps mounted with `app.route(prefix, sub)` compose the
48
- // prefix into a route declared on the sub-app, following the sub-app
49
- // through an import when it's declared in another file. A mount
50
- // nested more than one level deep composes too, since the same
51
- // index a mount resolves to is asked again for its own mount; a
52
- // mount the resolution store can't follow to a concrete sub-app,
53
- // or whose prefix isn't a string literal, leaves the route's path
54
- // as written.
46
+ // A route on a sub-app mounted with `app.route(prefix, sub)` gets the
47
+ // prefix in front of its path, at any depth of nesting. The README
48
+ // covers the mounts that leave a path as written.
55
49
  discovery: [
56
50
  ...httpRouteDiscovery({
57
51
  importModule: "hono",
@@ -66,11 +60,9 @@ function honoFramework(_options = {}) {
66
60
  mount: { method: "route", prefixPosition: 0, targetPosition: 1 }
67
61
  }),
68
62
  {
69
- // app.openapi(route, handler), where the route is a
70
- // createRoute({ method, path, ... }) object that usually lives
71
- // on a shared contract in another file. The fact layer follows
72
- // the reference, and the route object has its own method and path on
73
- // it.
63
+ // `app.openapi(route, handler)`, where the route is often a
64
+ // `createRoute({ method, path })` object in another file. The
65
+ // method and path come off that object.
74
66
  kind: "handler",
75
67
  match: {
76
68
  type: "registrationCall",
@@ -109,9 +101,9 @@ function honoFramework(_options = {}) {
109
101
  importNames: ["Hono", "OpenAPIHono"],
110
102
  methods: METHODS
111
103
  }),
112
- // The createRoute object registered alongside the handler declares
113
- // the endpoint's responses, so a handler returning a status the
114
- // route never declares is a contract finding, not a style choice.
104
+ // The `createRoute` object lists the endpoint's responses, so a
105
+ // handler that returns a status missing from that list gets a
106
+ // contract finding.
115
107
  contractReading: {
116
108
  discovery: {
117
109
  importModule: "@hono/zod-openapi",
@@ -190,8 +182,8 @@ function honoFramework(_options = {}) {
190
182
  extraction: { defaultStatusCode: 404 }
191
183
  },
192
184
  {
193
- // throw new HTTPException(status, { message }); hono sends the
194
- // exception's status as the wire response.
185
+ // throw new HTTPException(status, { message }): Hono sends the
186
+ // exception's status as the response.
195
187
  kind: "throw",
196
188
  match: { type: "throwExpression" },
197
189
  extraction: {
@@ -203,14 +195,12 @@ function honoFramework(_options = {}) {
203
195
  producesResponse: true
204
196
  }
205
197
  ],
206
- // One parameter, the context, carrying the request and the response
207
- // methods together.
208
198
  inputMapping: {
209
199
  type: "positionalParams",
210
200
  params: [{ position: 0, role: "context" }]
211
201
  },
212
- // `c.req.header("x-tenant-id")` puts the field in the argument, and
213
- // a read records the method without it, so no read says which field.
202
+ // In `c.req.header("x-tenant-id")` the field is an argument, and the
203
+ // recorded read keeps only the method, so it loses which field it was.
214
204
  requestSpelling: {
215
205
  headers: { path: ["context", "req", "header"], saysWhichField: false },
216
206
  query: { path: ["context", "req", "query"], saysWhichField: false },
package/dist/httpx.js CHANGED
@@ -25,11 +25,9 @@ function httpxClient() {
25
25
  methodKeyword: "method",
26
26
  urlPosition: 1
27
27
  },
28
- // Both are written as a context manager as often as they are
29
- // assigned, and the adapter reads a `with ... as` the same way.
30
28
  receiverConstructors: ["Client", "AsyncClient"],
31
- // httpx spells the response the way requests does, which is
32
- // deliberate on its part.
29
+ // httpx copies the requests response API, with `is_success` in
30
+ // place of `ok`.
33
31
  response: {
34
32
  statusCode: ["status_code"],
35
33
  success: ["is_success"],
@@ -38,7 +36,7 @@ function httpxClient() {
38
36
  }
39
37
  }
40
38
  ],
41
- // Both classes document `__enter__` as giving back the client, so
39
+ // Both classes document `__enter__` as returning the client, so
42
40
  // `with httpx.Client() as c` puts the constructed client in c.
43
41
  contextManagers: [
44
42
  { module: "httpx", returnsSelf: ["Client", "AsyncClient"] }
@@ -0,0 +1 @@
1
+ export { declares, default, optionsSchema } from '@suss/framework-minitest';
@@ -0,0 +1,59 @@
1
+ // ../framework/minitest/dist/index.js
2
+ import { z } from "zod";
3
+ var optionsSchema = z.object({
4
+ /**
5
+ * The test files to read, matched on whole path segments from the
6
+ * end. `suss extract --intent` fills it in from the tests the PRDs
7
+ * list, so a run reads those files and no others. Left out, every
8
+ * `*_test.rb` file is read.
9
+ */
10
+ files: z.array(z.string().min(1)).optional()
11
+ }).strict();
12
+ var TEST_CASE_CLASSES = [
13
+ "Minitest::Test",
14
+ "ActiveSupport::TestCase",
15
+ "ActionDispatch::IntegrationTest",
16
+ "ActionDispatch::SystemTestCase",
17
+ "ActionController::TestCase",
18
+ "ActionMailer::TestCase",
19
+ "ActionView::TestCase",
20
+ "ActiveJob::TestCase",
21
+ "ActionCable::TestCase",
22
+ "ActionCable::Channel::TestCase",
23
+ "ActionCable::Connection::TestCase",
24
+ "Rails::Generators::TestCase"
25
+ ];
26
+ function minitestFramework(options = {}) {
27
+ return {
28
+ name: "minitest",
29
+ protocol: "in-process",
30
+ discovery: [],
31
+ testClasses: [
32
+ {
33
+ filePatterns: ["*_test.rb"],
34
+ baseClassNames: TEST_CASE_CLASSES,
35
+ testMethodPrefix: "test_",
36
+ testBlockMethods: ["test"],
37
+ blockTestName: { prefix: "test_", spacesAs: "_" },
38
+ setupBlockMethods: ["setup"],
39
+ setupMethodNames: ["setup", "before_setup"],
40
+ skipStatements: ["skip"],
41
+ assertionPrefixes: ["assert", "refute", "must_", "wont_", "flunk"],
42
+ stubMethods: ["stub", "stubs", "expects"],
43
+ ...options.files !== void 0 ? { files: options.files } : {}
44
+ }
45
+ ]
46
+ };
47
+ }
48
+ var declares = {
49
+ kind: "framework",
50
+ package: "@suss/framework-minitest",
51
+ dependencies: [],
52
+ reads: "Minitest and Rails test cases (Ruby), named by class and test method, so a PRD scenario can say which test covers it."
53
+ };
54
+ var index_default = minitestFramework;
55
+ export {
56
+ declares,
57
+ index_default as default,
58
+ optionsSchema
59
+ };
package/dist/mongoose.js CHANGED
@@ -97,9 +97,9 @@ var METHODS = {
97
97
  },
98
98
  findByIdAndDelete: { kind: "write", selector: BY_ID, fields: WHOLE_DOCUMENT },
99
99
  findOneAndReplace: { kind: "write", selector: filter(0), fields: payload(1) },
100
- // A document can be changed after it was built, which this does not
101
- // track, so a save is not reported as touching the fields the
102
- // constructor happened to state.
100
+ // The pack does not track changes to a document after it is built, so
101
+ // the constructor's fields may be stale by the save. A save touches
102
+ // every field.
103
103
  save: { kind: "write", fields: WHOLE_DOCUMENT }
104
104
  };
105
105
  var MODEL_FACTORY = "model";
@@ -131,9 +131,9 @@ function pluralized(modelName) {
131
131
  }
132
132
  var optionsSchema = z.object({
133
133
  /**
134
- * Scope label for the storage binding. Defaults to `"default"`. Set
135
- * this when a project keeps more than one MongoDB connection and
136
- * wants their accesses paired separately.
134
+ * Scope for the storage binding, `"default"` when unset. Set it when a
135
+ * project has more than one MongoDB connection and their accesses
136
+ * should pair separately.
137
137
  */
138
138
  scope: scopeOption.optional()
139
139
  }).strict();
@@ -156,7 +156,7 @@ var declares = {
156
156
  kind: "effects",
157
157
  package: "@suss/framework-mongoose",
158
158
  dependencies: [{ ecosystem: "npm", name: "mongoose" }],
159
- reads: `Mongoose model calls, emits storage-access interactions against the collection a model's \`.model(...)\` call declares.`
159
+ reads: `Mongoose model calls. Each one becomes a storage-access interaction on the collection that the model's \`.model(...)\` call declares.`
160
160
  };
161
161
  var index_default = mongooseFramework;
162
162
  export {
@@ -2,14 +2,10 @@
2
2
  import { z } from "zod";
3
3
  var optionsSchema = z.object({
4
4
  /**
5
- * Class decorators this project composes `@Resolver()` into, for the
6
- * cases the adapter cannot follow on its own.
7
- *
8
- * A wrapper written in the project needs no entry here: the adapter
9
- * resolves a class decorator to the function behind it and accepts it
10
- * when calling that function calls `Resolver` from
11
- * `@nestjs/graphql`. What is left for this option is a wrapper whose
12
- * body is not in the project, so there is nothing to read.
5
+ * Class decorators that wrap `@Resolver()` in a package outside the
6
+ * project. A dependency stub fills this. A wrapper written in the
7
+ * project needs no entry, because the adapter reads its body and
8
+ * sees it call `Resolver` from `@nestjs/graphql`.
13
9
  */
14
10
  classDecorators: z.array(z.string()).optional()
15
11
  }).strict();
@@ -17,10 +13,8 @@ function nestjsGraphqlFramework(options = {}) {
17
13
  return {
18
14
  name: "nestjs-graphql",
19
15
  languages: ["typescript"],
20
- // Apollo Server runs underneath via `GraphQLModule.forRoot({...
21
- // ApolloDriver })`; the wire transport stays HTTP regardless of
22
- // whether the resolver was discovered via decorator or via an
23
- // object-literal resolver map.
16
+ // Apollo Server runs underneath `GraphQLModule.forRoot`, so the
17
+ // transport is HTTP here as it is for the apollo pack.
24
18
  protocol: "http",
25
19
  discovery: [
26
20
  {
@@ -28,8 +22,8 @@ function nestjsGraphqlFramework(options = {}) {
28
22
  match: {
29
23
  type: "decoratedMethod",
30
24
  importModule: "@nestjs/graphql",
31
- // First match wins, so the framework's own decorator is
32
- // tried before any wrapper a project names.
25
+ // The first match wins, so the framework's own decorator is
26
+ // tried before any wrapper.
33
27
  classDecorators: ["Resolver", ...options.classDecorators ?? []],
34
28
  methodDecorators: [
35
29
  "Query",
@@ -37,9 +31,8 @@ function nestjsGraphqlFramework(options = {}) {
37
31
  "ResolveField",
38
32
  "Subscription"
39
33
  ],
40
- // The three that settle their own type, each named after the
41
- // type it puts its field on. `ResolveField` is deliberately
42
- // absent: it is the one that needs the class decorator.
34
+ // `ResolveField` is left out because its type comes from the
35
+ // class decorator.
43
36
  methodDecoratorTypeMap: {
44
37
  Query: "Query",
45
38
  Mutation: "Mutation",
@@ -50,10 +43,8 @@ function nestjsGraphqlFramework(options = {}) {
50
43
  }
51
44
  ],
52
45
  terminals: [
53
- // Resolvers return a value; errors propagate as thrown
54
- // exceptions (NestJS / Apollo map them to `errors[]` on the
55
- // outgoing response). No framework-specific response-call
56
- // shape: return + throw cover the observable behaviour.
46
+ // A resolver has no response call. It returns a value or throws,
47
+ // and NestJS turns a thrown exception into `errors[]`.
57
48
  {
58
49
  kind: "return",
59
50
  match: { type: "returnStatement" },
@@ -65,10 +56,8 @@ function nestjsGraphqlFramework(options = {}) {
65
56
  extraction: {}
66
57
  },
67
58
  {
68
- // Resolver methods can fall through and return `undefined` when the
69
- // field is optional and the parent object already has the value. Keep a
70
- // default transition so the unit does not come out with no transitions
71
- // at all.
59
+ // A resolver for an optional field can fall off the end. Without
60
+ // this terminal that unit would come out with no transitions.
72
61
  kind: "return",
73
62
  match: { type: "functionFallthrough" },
74
63
  extraction: {}
@@ -7,14 +7,13 @@ var TRANSPORTS = [
7
7
  ];
8
8
  var optionsSchema = z.object({
9
9
  /**
10
- * The broker bootstrap connects, which the handler's own file never
11
- * states. Defaults to NATS.
10
+ * The broker the project connects at bootstrap. The handler's file
11
+ * never mentions it. Defaults to NATS.
12
12
  */
13
13
  transport: z.enum(TRANSPORTS).optional(),
14
14
  /**
15
- * Class decorators this project composes `@Controller()` into, the
16
- * same escape the REST pack takes for wrappers whose body is not in
17
- * the project.
15
+ * Class decorators that wrap `@Controller()` in a package outside the
16
+ * project, as in the REST pack. A dependency stub fills this.
18
17
  */
19
18
  classDecorators: z.array(z.string()).optional()
20
19
  }).strict();
@@ -35,8 +34,8 @@ function nestjsMicroservicesFramework(options = {}) {
35
34
  type: "decoratedRoute",
36
35
  importModule: ["@nestjs/microservices", "@nestjs/common"],
37
36
  classDecorators: ["Controller", ...options.classDecorators ?? []],
38
- // The values label the handler for a reader; the boundary
39
- // comes from the declared binding below.
37
+ // These values only label the handler. The boundary comes from
38
+ // the declared binding.
40
39
  methodDecoratorRouteMap: {
41
40
  EventPattern: "event",
42
41
  MessagePattern: "message"
@@ -47,9 +46,9 @@ function nestjsMicroservicesFramework(options = {}) {
47
46
  }
48
47
  ],
49
48
  terminals: [
50
- // An @MessagePattern handler's return is the reply; an
51
- // @EventPattern handler's return is dropped by the framework.
52
- // Both are recorded as returns, and reply pairing is deferred.
49
+ // An `@MessagePattern` handler's return value is the reply, and
50
+ // NestJS drops an `@EventPattern` handler's. Both are recorded as
51
+ // returns, and nothing pairs the reply yet.
53
52
  {
54
53
  kind: "return",
55
54
  match: { type: "returnStatement", excludeCallReturns: false },