@starci/eslint-canon-be 3.2.18 → 3.3.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 (50) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +46 -4
  3. package/app-composition.mjs +176 -0
  4. package/config-owner.mjs +34 -19
  5. package/feature-thin.mjs +59 -17
  6. package/index.mjs +2 -0
  7. package/lib/app-roles.mjs +48 -0
  8. package/lib/webhook-ack.mjs +87 -0
  9. package/lib/webhook-handoff.mjs +138 -0
  10. package/lib/webhook-verifiers.mjs +147 -0
  11. package/module-layering.mjs +2 -2
  12. package/module-shape.mjs +6 -5
  13. package/package.json +1 -1
  14. package/runtime/knowledge/hfs/canon-pins.yaml +4 -4
  15. package/runtime/knowledge/hfs/rules.yaml +41 -13
  16. package/runtime/knowledge/hfs/slots.yaml +99 -43
  17. package/runtime/knowledge/patterns/fe/folder.yaml +3 -0
  18. package/runtime/scripts/api/git/cat-file.mjs +5 -0
  19. package/runtime/scripts/api/git/diff.mjs +5 -0
  20. package/runtime/scripts/hfs/architecture/backend-app-files.mjs +110 -0
  21. package/runtime/scripts/hfs/architecture/backend.mjs +20 -3
  22. package/runtime/scripts/hfs/architecture/background-unowned.mjs +157 -41
  23. package/runtime/scripts/hfs/architecture/canon-clones.mjs +240 -0
  24. package/runtime/scripts/hfs/architecture/canon-jsx-clones.mjs +143 -0
  25. package/runtime/scripts/hfs/architecture/clones.mjs +17 -8
  26. package/runtime/scripts/hfs/architecture/connection-map.mjs +14 -12
  27. package/runtime/scripts/hfs/architecture/context-map.mjs +25 -14
  28. package/runtime/scripts/hfs/architecture/context-owner.mjs +1 -2
  29. package/runtime/scripts/hfs/architecture/dead-exports.mjs +14 -9
  30. package/runtime/scripts/hfs/architecture/frontend-block-render.mjs +104 -0
  31. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +30 -11
  32. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +16 -3
  33. package/runtime/scripts/hfs/architecture/machine-ast.mjs +17 -0
  34. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +21 -12
  35. package/runtime/scripts/hfs/architecture/register-once.mjs +13 -5
  36. package/runtime/scripts/hfs/architecture/required-files.mjs +2 -1
  37. package/runtime/scripts/hfs/architecture/staged-consumers.mjs +37 -0
  38. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +3 -6
  39. package/runtime/scripts/hfs/architecture/supabase-be.mjs +1 -6
  40. package/runtime/scripts/hfs/architecture/tiers.mjs +8 -1
  41. package/runtime/scripts/hfs/architecture/typescript.mjs +1 -8
  42. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +1 -11
  43. package/runtime/scripts/hfs/manifest-shape.mjs +27 -2
  44. package/runtime/scripts/hfs/path-findings.mjs +32 -2
  45. package/runtime/scripts/hfs/rules/secrets.mjs +13 -0
  46. package/runtime/scripts/hfs/slot-imports.mjs +3 -1
  47. package/runtime/scripts/hfs/slot-required.mjs +13 -0
  48. package/runtime/scripts/hfs/slot-semantic-problems.mjs +2 -1
  49. package/runtime/scripts/lib/ts-analysis.mjs +20 -0
  50. package/webhooks.mjs +141 -69
package/webhooks.mjs CHANGED
@@ -3,14 +3,15 @@
3
3
  *
4
4
  * A provider webhook (`src/features/webhooks/<provider>/transport/http/<provider>.webhook.ts`, slot `be.feature.webhooks.http`) is a signed intake door. It
5
5
  * proves the delivery came from the provider, then hands it to exactly ONE domain intake method, which records it and
6
- * publishes the event inside its own transaction (`eventBus.publish(event, tx)` is called only from `modules/domain`). The door
6
+ * records the inbox delivery inside its own transaction. A reactor or job consumes it and dispatches the command. The door
7
7
  * writes nothing and decides nothing:
8
8
  *
9
9
  * - `webhook-shape` (R182): the door is one `@Controller` with one `@Post` handler, `@Public({ reason: PublicReason.SignedWebhook })`;
10
10
  * it injects only the `WebhookSignatureService` of `platform/http-security` and services of `modules/domain`; its handler has no
11
- * branch, loop or `try`, makes exactly one call on a domain service, calls on the signature service only `verify`, and returns
12
- * nothing.
13
- * - `webhook-verify-first` (R183): the first statement of the handler is the unconditional `verify` call, so no path reaches the
11
+ * branch, loop or `try`, makes one domain intake call, optionally preceded by a declared domain verifier, and returns nothing or
12
+ * its own exported literal `PROVIDER_ACK`. An optional signed `@Get` verifies and echoes only the domain verifier result.
13
+ * A single declared domain method may verify and intake internally, using per-connection secrets.
14
+ * - `webhook-verify-first` (R183): the first statement is the unconditional shared or declared domain proof call, so no path reaches the
14
15
  * intake with an unproven delivery.
15
16
  *
16
17
  * What a receiver is comes from its TYPE (where the class is declared); what a decorator is comes from the import that binds it.
@@ -19,26 +20,21 @@
19
20
  import { walk } from "./lib/ast.mjs"
20
21
  import { callsOf, decoratorsFrom, hasClassDecorator, injectedMembers, methodsDecoratedBy } from "./lib/doors.mjs"
21
22
  import { hfsOf } from "./lib/hfs.mjs"
22
- import { decoratorArguments, decoratorCallee, importOf } from "./lib/import-source.mjs"
23
- import { baseName, enumMemberOf, isOwnedType } from "./lib/ports.mjs"
24
- import { typeOrigins } from "./lib/types.mjs"
23
+ import { decoratorArguments, decoratorCallee, importOf, isImportedFrom } from "./lib/import-source.mjs"
24
+ import { baseName, enumMemberOf } from "./lib/ports.mjs"
25
+ import { isProviderAcknowledgement } from "./lib/webhook-ack.mjs"
26
+ import { webhookForbiddenReferences, webhookIntakeDispatchesCommand } from "./lib/webhook-handoff.mjs"
27
+ import { firstWebhookChallenge, firstWebhookVerification, isWebhookChallengeReturn, isWebhookDomainServiceType as isDomainServiceType, isWebhookSignatureType as isSignatureType } from "./lib/webhook-verifiers.mjs"
25
28
 
26
29
  /** The slot of provider webhook doors. */
27
30
  const SLOT = "be.feature.webhooks.http"
28
31
 
29
- /** The HTTP verbs of `@nestjs/common`; only `Post` is a webhook method. */
32
+ /** One POST intake and, optionally, one GET subscription challenge are the only webhook routes. */
30
33
  const HTTP_VERBS = ["Get", "Post", "Put", "Patch", "Delete", "All", "Options", "Head"]
31
34
 
32
35
  /** Statements and expressions that are a decision or a repetition: none of them belongs in a door. */
33
36
  const DECISION_NODES = new Set(["IfStatement", "SwitchStatement", "ConditionalExpression", "LogicalExpression", "ForStatement", "ForInStatement", "ForOfStatement", "WhileStatement", "DoWhileStatement", "TryStatement", "ThrowStatement"])
34
37
 
35
- /** The `WebhookSignatureService` of `platform/http-security`. */
36
- const isSignatureType = (context, node) => isOwnedType(context, node, { name: "WebhookSignatureService", capability: "http-security", tier: "platform" })
37
-
38
- /** A service of `modules/domain`: a class declared in a `<name>.service.ts` of the domain tier. */
39
- const isDomainServiceType = (context, hfs, node) =>
40
- typeOrigins(context, node).some((origin) => origin.module === null && hfs.tierOf(origin.file) === "domain" && baseName(origin.file).endsWith(".service.ts"))
41
-
42
38
  /** The `@Controller` classes of a webhook file, with their handler methods. */
43
39
  const doorsOf = (context, program) => {
44
40
  const doors = []
@@ -55,6 +51,19 @@ const routeMethods = (context, controller) => methodsDecoratedBy(context, contro
55
51
  /** The handler of a webhook door: its `@Post` method, or null when it has none. */
56
52
  const handlerOf = (context, controller) => methodsDecoratedBy(context, controller, "@nestjs/common", "Post")[0] ?? null
57
53
 
54
+ /** Aliased decorators retain the HTTP verb exported by Nest, rather than their local spelling. */
55
+ const routeVerbs = (context, route) => decoratorsFrom(context, route, "@nestjs/common", HTTP_VERBS).map((decorator) => HTTP_VERBS.find((verb) => isImportedFrom(context, decoratorCallee(decorator), "@nestjs/common", verb)))
56
+
57
+ /** One unambiguous POST plus at most one unambiguous GET; stacked verb decorators grant no extra routes. */
58
+ const validRoutes = (context, routes) => {
59
+ const verbs = routes.flatMap((route) => routeVerbs(context, route))
60
+ return verbs.length === routes.length && verbs.filter((verb) => verb === "Post").length === 1
61
+ && verbs.filter((verb) => verb === "Get").length <= 1 && verbs.every((verb) => verb === "Post" || verb === "Get")
62
+ }
63
+
64
+ /** A route's name is only presentation, never its identity. */
65
+ const methodName = (method) => method.key.type === "Identifier" ? method.key.name : "the handler"
66
+
58
67
  /** True when the method carries `@Public({ reason: PublicReason.SignedWebhook })`: the reason is read from the enum member the argument is. */
59
68
  const isSignedWebhookDoor = (context, method) =>
60
69
  (method.decorators ?? []).some((decorator) => {
@@ -67,60 +76,130 @@ const isSignedWebhookDoor = (context, method) =>
67
76
  return member !== null && member.enumName === "PublicReason" && member.member === "SignedWebhook"
68
77
  })
69
78
 
79
+ /** Decisions are equally forbidden in the delivery and subscription doors. */
80
+ const reportDecisions = (context, method) => {
81
+ walk(method.value.body, (child) => {
82
+ if (DECISION_NODES.has(child.type)) context.report({ node: child, messageId: "branch", data: { what: child.type === "LogicalExpression" ? child.operator : child.type.replace(/(Statement|Expression)$/, "").toLowerCase() } })
83
+ })
84
+ }
85
+
86
+ /** POST returns are restricted to the immutable literal acknowledgement of this webhook feature. */
87
+ const reportPostReturns = (context, hfs, handler) => {
88
+ walk(handler.value.body, (child) => {
89
+ if (child.type === "ReturnStatement" && child.argument && !isProviderAcknowledgement(context, hfs, child.argument)) context.report({ node: child, messageId: "returns", data: { name: methodName(handler) } })
90
+ })
91
+ }
92
+
93
+ /** Delivery calls consist only of the completed proof and one domain inbox intake. */
94
+ const deliveryCallKind = (context, hfs, entry, separateProof, proof) => {
95
+ if (entry.receiver === null) return "call"
96
+ if (isSignatureType(context, entry.receiver) && entry.method === "verify") return "proof"
97
+ if (!isDomainServiceType(context, hfs, entry.receiver)) return "call"
98
+ if (separateProof && entry.call === proof) return "proof"
99
+ return "intake"
100
+ }
101
+
102
+ /** A declared proof is no escape hatch for dispatching commands behind the intake door. */
103
+ const reportDomainDispatch = (context, hfs, call, receiver) => {
104
+ if (receiver !== null && isDomainServiceType(context, hfs, receiver) && webhookIntakeDispatchesCommand(context, hfs, call)) context.report({ node: call, messageId: "handoff" })
105
+ }
106
+
107
+ /** Delivery calls consist only of the completed proof and one domain inbox intake. */
108
+ const reportIntakeCalls = (context, hfs, handler) => {
109
+ const proof = firstWebhookVerification(context, hfs, handler)
110
+ const calls = callsOf(handler)
111
+ const domainCalls = calls.filter(({ receiver }) => receiver !== null && isDomainServiceType(context, hfs, receiver))
112
+ const separateProof = domainCalls.length > 1 && domainCalls.some(({ call }) => call === proof)
113
+ let intake = 0
114
+ for (const entry of calls) {
115
+ const { call } = entry
116
+ reportDomainDispatch(context, hfs, call, entry.receiver)
117
+ const kind = deliveryCallKind(context, hfs, entry, separateProof, proof)
118
+ if (kind === "proof") continue
119
+ if (kind === "intake") {
120
+ intake += 1
121
+ continue
122
+ }
123
+ context.report({ node: call, messageId: "call", data: { what: context.sourceCode.getText(call.callee) } })
124
+ }
125
+ if (intake !== 1) context.report({ node: handler.key, messageId: "intakeCalls", data: { name: methodName(handler), count: String(intake) } })
126
+ }
127
+
128
+ /** A challenge calls its domain verifier once and echoes exactly the returned value. */
129
+ const reportChallenge = (context, hfs, handler) => {
130
+ const challenge = firstWebhookChallenge(context, hfs, handler)
131
+ let echoes = 0
132
+ walk(handler.value.body, (child) => {
133
+ if (child.type !== "ReturnStatement") return
134
+ echoes += 1
135
+ if (!isWebhookChallengeReturn(context, child, challenge)) context.report({ node: child, messageId: "returns", data: { name: methodName(handler) } })
136
+ })
137
+ if (echoes === 0) context.report({ node: handler.key, messageId: "returns", data: { name: methodName(handler) } })
138
+ for (const { call, receiver } of callsOf(handler)) {
139
+ reportDomainDispatch(context, hfs, call, receiver)
140
+ if (call !== challenge?.call) context.report({ node: call, messageId: "call", data: { what: context.sourceCode.getText(call.callee) } })
141
+ }
142
+ }
143
+
144
+ /** Public declaration and branch restrictions apply to both routes. */
145
+ const reportRoute = (context, hfs, handler, challenge = false) => {
146
+ if (!isSignedWebhookDoor(context, handler)) context.report({ node: handler.key, messageId: "notPublic", data: { name: methodName(handler) } })
147
+ reportDecisions(context, handler)
148
+ if (challenge) reportChallenge(context, hfs, handler)
149
+ else {
150
+ reportPostReturns(context, hfs, handler)
151
+ reportIntakeCalls(context, hfs, handler)
152
+ }
153
+ }
154
+
155
+ /** The provider file contains one controller; an exported PROVIDER_ACK declaration may accompany it. */
156
+ const reportDoor = (context, hfs, program) => {
157
+ const doors = doorsOf(context, program)
158
+ if (doors.length !== 1) {
159
+ context.report({ node: program, messageId: "noDoor" })
160
+ return
161
+ }
162
+ const door = doors[0]
163
+ for (const { node, annotation } of injectedMembers(context, door)) {
164
+ if (!annotation || !(isSignatureType(context, annotation) || isDomainServiceType(context, hfs, annotation))) context.report({ node, messageId: "injects", data: { what: context.sourceCode.getText(node) } })
165
+ }
166
+ const routes = routeMethods(context, door)
167
+ if (!validRoutes(context, routes)) {
168
+ const verbs = routes.length > 0 ? ` (${routes.map((route) => routeVerbs(context, route).join("+")).join(", ")})` : ""
169
+ context.report({ node: door, messageId: "route", data: { count: String(routes.length), verbs } })
170
+ return
171
+ }
172
+ reportRoute(context, hfs, handlerOf(context, door))
173
+ for (const challenge of methodsDecoratedBy(context, door, "@nestjs/common", "Get")) reportRoute(context, hfs, challenge, true)
174
+ }
175
+
70
176
  /** A provider webhook door: one signed `@Post`, the signature proof, one domain intake call, nothing else. */
71
177
  export const webhookShape = {
72
178
  meta: {
73
179
  type: "problem",
74
- docs: { description: "A webhook door is one `@Controller` with one `@Public({ reason: PublicReason.SignedWebhook })` `@Post` handler that injects only the `WebhookSignatureService` and domain services, verifies, makes exactly one domain call and returns nothing." },
180
+ docs: { description: "A webhook door has one signed POST that verifies first, hands off once to a domain inbox intake and returns empty or its own literal PROVIDER_ACK, plus an optional signed GET that echoes only its domain verifier result. No command bus reaches the door or intake." },
75
181
  schema: [],
76
182
  messages: {
77
- noDoor: "A webhook file declares one `@Controller` class: the door of the provider. Nothing else lives in `<provider>.webhook.ts`.",
78
- injects: "`{{what}}` is injected into a webhook door. A webhook injects only the `WebhookSignatureService` of `platform/http-security` and services of `modules/domain`; a bus, a repository, an `EntityManager` or a queue never reaches it. The intake is one domain service method that records the delivery and publishes the event inside its own transaction.",
79
- route: "A webhook door has exactly one `@Post` handler (this class has {{count}} route methods{{verbs}}). One provider endpoint is one door; another route belongs in its own provider folder or in an api feature.",
183
+ noDoor: "A webhook file declares one `@Controller` class: the provider door. Its own exported literal `PROVIDER_ACK` may accompany it in `<provider>.webhook.ts`.",
184
+ injects: "`{{what}}` is injected into a webhook door. A webhook injects only the `WebhookSignatureService` of `platform/http-security` and services of `modules/domain`; a bus, a repository, an `EntityManager` or a queue never reaches it. The intake records the inbox delivery inside its own transaction; a reactor or job dispatches the command afterward.",
185
+ route: "A webhook door has exactly one `@Post` delivery handler and at most one `@Get` subscription challenge (this class has {{count}} route methods{{verbs}}). The GET calls its declared domain verifier first and echoes only that result; other routes belong in their own provider folder or an api feature.",
80
186
  notPublic: "`{{name}}` is not `@Public({ reason: PublicReason.SignedWebhook })`. The signature is the authentication of a webhook: it is public by that declaration and no other, so the open doors stay readable and the strict rate tier applies.",
81
187
  branch: "`{{what}}` is a decision or a loop in a webhook door. The door proves the delivery and hands it over: a refusal is the thrown error of `verify`, and every other decision belongs in the domain intake method.",
82
- call: "`{{what}}` is a call a webhook door does not make. The handler calls `verify` of the `WebhookSignatureService` once and one method of a domain service once; parsing, mapping and publishing belong in the domain service.",
83
- intakeCalls: "`{{name}}` calls domain services {{count}} times. A webhook hands the verified delivery to exactly one domain intake method (it records the delivery and publishes the event in one transaction); a second call is orchestration that belongs in that method.",
84
- returns: "`{{name}}` returns a value. A webhook acknowledges with its status and an empty body; it returns nothing of the domain to the provider.",
188
+ call: "`{{what}}` is a call a webhook door does not make. The handler starts with the shared signature proof or a declared domain verifier and hands the delivery to one domain intake; a declared domain method may do both internally. Parsing, mapping and publishing belong in the domain service.",
189
+ intakeCalls: "`{{name}}` makes {{count}} domain intake calls. A webhook hands the verified delivery to exactly one domain method that records its inbox delivery in a transaction; a separate first domain proof is allowed, and a second intake is orchestration that belongs in that method.",
190
+ returns: "`{{name}}` returns an undeclared or computed body. POST returns empty or the exported `PROVIDER_ACK` const of this webhook feature, initialized only with literal object/string data and declared `as const` or with a readonly literal type. GET echoes exactly its first declared domain verifier call's result. Domain data, inline literals and every other computed body are refused.",
191
+ handoff: "A webhook door, its declaring HTTP module or its called domain method reaches the command/query bus or a feature command handler. A delivery or challenge verifier cannot conceal command dispatch. Verify, record the inbox delivery, and let a reactor or job dispatch the command afterward.",
85
192
  },
86
193
  },
87
194
  create(context) {
88
195
  const hfs = hfsOf(context)
89
- if (hfs.slotOf(context.filename) !== SLOT || !baseName(context.filename).endsWith(".webhook.ts")) return {}
196
+ const name = baseName(context.filename)
197
+ const isDoor = name.endsWith(".webhook.ts")
198
+ if (hfs.slotOf(context.filename) !== SLOT || !(isDoor || name.endsWith(".module.ts"))) return {}
90
199
  return {
91
200
  "Program:exit"(program) {
92
- const doors = doorsOf(context, program)
93
- if (doors.length !== 1) {
94
- context.report({ node: program, messageId: "noDoor" })
95
- return
96
- }
97
- const door = doors[0]
98
- for (const { node, annotation } of injectedMembers(context, door)) {
99
- if (!annotation || !(isSignatureType(context, annotation) || isDomainServiceType(context, hfs, annotation))) context.report({ node, messageId: "injects", data: { what: context.sourceCode.getText(node) } })
100
- }
101
- const routes = routeMethods(context, door)
102
- const handler = handlerOf(context, door)
103
- if (routes.length !== 1 || handler === null) {
104
- const verbs = routes.length > 0 ? ` (${routes.map((route) => decoratorsFrom(context, route, "@nestjs/common", HTTP_VERBS).map((decorator) => decoratorCallee(decorator).name ?? "route").join("+")).join(", ")})` : ""
105
- context.report({ node: door, messageId: "route", data: { count: String(routes.length), verbs } })
106
- return
107
- }
108
- const name = handler.key.type === "Identifier" ? handler.key.name : "the handler"
109
- if (!isSignedWebhookDoor(context, handler)) context.report({ node: handler.key, messageId: "notPublic", data: { name } })
110
- walk(handler.value.body, (child) => {
111
- if (DECISION_NODES.has(child.type)) context.report({ node: child, messageId: "branch", data: { what: child.type === "LogicalExpression" ? child.operator : child.type.replace(/(Statement|Expression)$/, "").toLowerCase() } })
112
- else if (child.type === "ReturnStatement" && child.argument) context.report({ node: child, messageId: "returns", data: { name } })
113
- })
114
- let intake = 0
115
- for (const { call, receiver, method } of callsOf(handler)) {
116
- if (receiver !== null && isSignatureType(context, receiver) && method === "verify") continue
117
- if (receiver !== null && isDomainServiceType(context, hfs, receiver)) {
118
- intake += 1
119
- continue
120
- }
121
- context.report({ node: call, messageId: "call", data: { what: context.sourceCode.getText(call.callee) } })
122
- }
123
- if (intake !== 1) context.report({ node: handler.key, messageId: "intakeCalls", data: { name, count: String(intake) } })
201
+ for (const node of webhookForbiddenReferences(context, hfs, program)) context.report({ node, messageId: "handoff" })
202
+ if (isDoor) reportDoor(context, hfs, program)
124
203
  },
125
204
  }
126
205
  },
@@ -130,10 +209,10 @@ export const webhookShape = {
130
209
  export const webhookVerifyFirst = {
131
210
  meta: {
132
211
  type: "problem",
133
- docs: { description: "The first statement of a webhook handler is the unconditional `verify` call of the `WebhookSignatureService` (signature and replay window), before anything else runs." },
212
+ docs: { description: "POST starts with an unconditional shared signature or declared domain proof. An optional GET starts with its domain challenge verifier, binding or returning only that result. Async proofs are awaited before any other work." },
134
213
  schema: [],
135
214
  messages: {
136
- first: "`{{name}}` does not start with `verify` of the `WebhookSignatureService`. The first statement of a webhook handler is `this.<signature>.verify({ rawBody, signature, timestamp })` (awaited when it is async): a delivery that fails the signature or the replay window throws before any other code runs, so no path reaches the intake unproven.",
215
+ first: "`{{name}}` does not start with an unconditional webhook proof. POST starts with `WebhookSignatureService.verify` or a declared domain verifier. GET first binds or returns its declared domain verifier's challenge result. Await an async proof. Domain verifier service names end in `Verifier` or `verify`, or their methods appear in the adjacent HTTP module's exported `webhookVerifiers` array. No data call or other work may run before verification, including inside its arguments.",
137
216
  },
138
217
  },
139
218
  create(context) {
@@ -142,18 +221,11 @@ export const webhookVerifyFirst = {
142
221
  return {
143
222
  "Program:exit"(program) {
144
223
  for (const door of doorsOf(context, program)) {
145
- const handler = handlerOf(context, door)
146
- if (handler === null) continue
147
- const first = handler.value.body?.body[0]
148
- let expression = first?.type === "ExpressionStatement" ? first.expression : null
149
- if (expression?.type === "AwaitExpression") expression = expression.argument
150
- const proven = expression?.type === "CallExpression"
151
- && expression.callee.type === "MemberExpression"
152
- && !expression.callee.computed
153
- && expression.callee.property.type === "Identifier"
154
- && expression.callee.property.name === "verify"
155
- && isSignatureType(context, expression.callee.object)
156
- if (!proven) context.report({ node: handler.key, messageId: "first", data: { name: handler.key.type === "Identifier" ? handler.key.name : "the handler" } })
224
+ for (const handler of methodsDecoratedBy(context, door, "@nestjs/common", ["Post", "Get"])) {
225
+ const challenge = decoratorsFrom(context, handler, "@nestjs/common", "Get").length > 0
226
+ const proven = challenge ? firstWebhookChallenge(context, hfs, handler) : firstWebhookVerification(context, hfs, handler)
227
+ if (!proven) context.report({ node: handler.key, messageId: "first", data: { name: methodName(handler) } })
228
+ }
157
229
  }
158
230
  },
159
231
  }