@starci/hfs 4.0.10 → 4.1.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 (271) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +3 -2
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +187 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +221 -239
  10. package/runtime/engine/invalid-config.mjs +5 -5
  11. package/runtime/engine/model-config.mjs +90 -0
  12. package/runtime/engine/orca-config.mjs +3 -1
  13. package/runtime/engine/removed-vocabulary.mjs +28 -0
  14. package/runtime/engine/resources-config.mjs +52 -0
  15. package/runtime/engine/runtime-root.mjs +17 -0
  16. package/runtime/engine/secrets.mjs +35 -31
  17. package/runtime/engine/sonar-config.mjs +21 -0
  18. package/runtime/engine/temp-root.mjs +32 -0
  19. package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
  20. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  21. package/runtime/knowledge/hfs/rules.yaml +98 -65
  22. package/runtime/knowledge/hfs/slots.yaml +11 -14
  23. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  24. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  25. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  26. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  27. package/runtime/modules/kernel/failure-codes.yaml +1 -41
  28. package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
  29. package/runtime/modules/models/registry.yaml +25 -359
  30. package/runtime/modules/models/runtimes.yaml +92 -256
  31. package/runtime/modules/models/tiers.yaml +65 -0
  32. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  33. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  34. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  35. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  36. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  37. package/runtime/scripts/api/git/lib.mjs +34 -1
  38. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  39. package/runtime/scripts/api/process/run-program.mjs +2 -1
  40. package/runtime/scripts/api/sops/lib.mjs +198 -120
  41. package/runtime/scripts/hfs/allows.mjs +3 -3
  42. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  43. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  44. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  45. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  46. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  47. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  48. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  49. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  50. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  51. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  52. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  53. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  54. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  55. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  56. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  57. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  58. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  59. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  60. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  61. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  62. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  63. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  64. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  65. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  66. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  67. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  68. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  69. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  70. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  71. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  72. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  74. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  75. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  76. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  77. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  78. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  79. package/runtime/scripts/hfs/architecture/index.mjs +158 -102
  80. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  81. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  82. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  83. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  84. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  85. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  86. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  87. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  88. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  89. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  90. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  91. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  92. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  93. package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
  94. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  95. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  96. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  97. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  98. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  99. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  100. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  101. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  102. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  103. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  104. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  105. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  106. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  107. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  108. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  109. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  110. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  111. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  112. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  113. package/runtime/scripts/hfs/architecture.mjs +3 -4
  114. package/runtime/scripts/hfs/check.mjs +61 -70
  115. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  116. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  117. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  118. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  119. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  120. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  121. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  122. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  123. package/runtime/scripts/hfs/repo-identity.mjs +5 -2
  124. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  125. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  126. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  127. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  128. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  129. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  130. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  131. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  132. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  133. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  134. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  135. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  136. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  137. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  138. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  139. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  140. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  141. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  142. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  143. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  144. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  145. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  146. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  147. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  148. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  149. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  150. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  151. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  152. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  153. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  154. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  155. package/runtime/scripts/hfs/secret.mjs +59 -48
  156. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  157. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  158. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  159. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  160. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  161. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  162. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  163. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  164. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  165. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  166. package/runtime/scripts/hfs/slots.mjs +72 -693
  167. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  168. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  169. package/runtime/scripts/hfs/tree.mjs +19 -14
  170. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  171. package/runtime/scripts/hfs/view.mjs +1 -1
  172. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  173. package/runtime/scripts/lib/event-contract.mjs +17 -13
  174. package/runtime/scripts/lib/git.mjs +2 -2
  175. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  176. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  177. package/runtime/scripts/lib/i18n.mjs +3 -2
  178. package/runtime/scripts/lib/in-order.mjs +71 -0
  179. package/runtime/scripts/lib/language.mjs +3 -3
  180. package/runtime/scripts/lib/list.mjs +8 -1
  181. package/runtime/scripts/lib/path-key.mjs +38 -5
  182. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  183. package/runtime/scripts/lib/regex.mjs +1 -1
  184. package/runtime/scripts/lib/same-text.mjs +1 -1
  185. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  186. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  187. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  188. package/runtime/scripts/lib/stack-services.mjs +1 -2
  189. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  190. package/runtime/scripts/lib/walk.mjs +6 -1
  191. package/scaffold/add-cli-lite.mjs +2 -1
  192. package/scaffold/add-table.mjs +2 -2
  193. package/scaffold/add.mjs +3 -2
  194. package/scaffold/app.mjs +4 -2
  195. package/scaffold/edition-gate.mjs +25 -25
  196. package/scaffold/lite-exports.mjs +2 -1
  197. package/scaffold/service.mjs +5 -4
  198. package/sync/hygiene.mjs +15 -9
  199. package/sync/index.mjs +7 -3
  200. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  201. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  202. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  203. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  204. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  205. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  206. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  207. package/templates/app/starciwork.gitignore +1 -1
  208. package/templates/be/image/api/Dockerfile +1 -1
  209. package/templates/be/image/cli/Dockerfile +1 -1
  210. package/templates/be/image/worker/Dockerfile +1 -1
  211. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  212. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  213. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  214. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  215. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  216. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  217. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  218. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  219. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  220. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  221. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  222. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  223. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  224. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  225. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  226. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  227. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  228. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  229. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  230. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  231. package/templates/fe/image/next/Dockerfile +2 -1
  232. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  233. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  234. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  235. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  236. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  238. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  239. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  241. package/upgrade/index.mjs +9 -2
  242. package/runtime/engine/admission.mjs +0 -308
  243. package/runtime/engine/canonical-json.mjs +0 -10
  244. package/runtime/engine/db/blob.mjs +0 -315
  245. package/runtime/engine/db/ledger-paths.mjs +0 -83
  246. package/runtime/engine/db/ledger.mjs +0 -1118
  247. package/runtime/engine/db/machine-connection.mjs +0 -135
  248. package/runtime/engine/db/machine-schema.mjs +0 -84
  249. package/runtime/engine/db/machine.mjs +0 -1367
  250. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  251. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  252. package/runtime/engine/db/provider-reservations.mjs +0 -101
  253. package/runtime/engine/digest.mjs +0 -16
  254. package/runtime/engine/refuse.mjs +0 -11
  255. package/runtime/modules/ops/_labels.yaml +0 -53
  256. package/runtime/scripts/api/node/lib.mjs +0 -14
  257. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  258. package/runtime/scripts/api/process/lib.mjs +0 -111
  259. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  260. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  261. package/runtime/scripts/connectors/lib.mjs +0 -488
  262. package/runtime/scripts/lib/clip.mjs +0 -19
  263. package/runtime/scripts/lib/display-names.mjs +0 -259
  264. package/runtime/scripts/lib/example-refs.mjs +0 -158
  265. package/runtime/scripts/lib/json-schema.mjs +0 -52
  266. package/runtime/scripts/lib/process-identity.mjs +0 -6
  267. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  268. package/runtime/scripts/lib/redact.mjs +0 -161
  269. package/runtime/scripts/lib/sleep.mjs +0 -7
  270. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  271. package/runtime/scripts/lib/sqlite.mjs +0 -21
@@ -0,0 +1,338 @@
1
+ function accessPath(deps, ts, checker, node) {
2
+ node = deps.unwrapExpression(ts, node);
3
+ if (ts.isIdentifier(node)) return { symbol: deps.normalizedSymbol(ts, checker, node), parts: [node.text] };
4
+ if (ts.isPropertyAccessExpression(node)) {
5
+ const base = accessPath(deps, ts, checker, node.expression);
6
+ return base ? { symbol: base.symbol, parts: [...base.parts, node.name.text] } : null;
7
+ }
8
+ if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) {
9
+ const base = accessPath(deps, ts, checker, node.expression);
10
+ return base ? { symbol: base.symbol, parts: [...base.parts, node.argumentExpression.text] } : null;
11
+ }
12
+ return null;
13
+ }
14
+
15
+ function expandedIdentifierPath(deps, ts, checker, node, seen, depth) {
16
+ const symbol = deps.normalizedSymbol(ts, checker, node);
17
+ if (!symbol || seen.has(symbol)) return symbol ? { symbol, parts: [node.text] } : null;
18
+ const initializer = deps.constInitializer(ts, symbol);
19
+ return initializer ? expandedAccessPath(deps, ts, checker, initializer, new Set(seen).add(symbol), depth + 1)
20
+ ?? { symbol, parts: [node.text] } : { symbol, parts: [node.text] };
21
+ }
22
+
23
+ function expandedMemberPath(deps, ts, checker, node, seen, depth) {
24
+ const base = expandedAccessPath(deps, ts, checker, node.expression, seen, depth + 1);
25
+ if (!base) return null;
26
+ const part = ts.isPropertyAccessExpression(node) ? node.name.text : node.argumentExpression.text;
27
+ return { symbol: base.symbol, parts: [...base.parts, part] };
28
+ }
29
+
30
+ function expandedAccessPath(deps, ts, checker, node, seen = new Set(), depth = 0) {
31
+ if (!node || depth > 10) return null;
32
+ node = deps.unwrapExpression(ts, node);
33
+ if (ts.isIdentifier(node)) return expandedIdentifierPath(deps, ts, checker, node, seen, depth);
34
+ if (ts.isPropertyAccessExpression(node)) return expandedMemberPath(deps, ts, checker, node, seen, depth);
35
+ if (ts.isElementAccessExpression(node) && ts.isStringLiteralLike(node.argumentExpression)) return expandedMemberPath(deps, ts, checker, node, seen, depth);
36
+ return null;
37
+ }
38
+
39
+ function matchesIdentityAccess(deps, ts, checker, node, identity) {
40
+ const matches = access => access?.symbol === identity.symbol && access.parts.length >= identity.parts.length
41
+ && identity.parts.every((part, index) => access.parts[index] === part);
42
+ return matches(accessPath(deps, ts, checker, node)) || matches(expandedAccessPath(deps, ts, checker, node));
43
+ }
44
+
45
+ const expressionReferences = (deps, ts, checker, expression, identity) =>
46
+ deps.anyDescendant(ts, expression, node => matchesIdentityAccess(deps, ts, checker, node, identity));
47
+
48
+ function propertyKey(ts, name) {
49
+ if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) return name.text;
50
+ return null;
51
+ }
52
+
53
+ function effectiveObjectValues(ts, expression) {
54
+ const selected = new Map();
55
+ for (let index = expression.properties.length - 1; index >= 0; index -= 1) {
56
+ const property = expression.properties[index];
57
+ if (ts.isSpreadAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property)
58
+ || ts.isSetAccessorDeclaration(property)) return null;
59
+ const key = propertyKey(ts, property.name);
60
+ if (key === null) return null;
61
+ if (!selected.has(key)) selected.set(key, ts.isShorthandPropertyAssignment(property) ? property.name : property.initializer);
62
+ }
63
+ return [...selected.entries()].sort(([left], [right]) => left.localeCompare(right));
64
+ }
65
+
66
+ function combineContributions(values) {
67
+ const states = new Set(values);
68
+ return states.has('yes') && 'yes' || states.has('unproven') && 'unproven' || 'no';
69
+ }
70
+
71
+ function identityArrayContribution(deps, ts, checker, expression, identity, seen, depth) {
72
+ if (expression.elements.some(element => ts.isSpreadElement(element))) return 'unproven';
73
+ return combineContributions(expression.elements.map(element => identityContribution(deps, ts, checker, element, identity, seen, depth + 1)));
74
+ }
75
+
76
+ function identityObjectContribution(deps, ts, checker, expression, identity, seen, depth) {
77
+ const values = effectiveObjectValues(ts, expression);
78
+ if (!values || values.length !== expression.properties.length) return 'unproven';
79
+ return combineContributions(values.map(([, value]) => identityContribution(deps, ts, checker, value, identity, seen, depth + 1)));
80
+ }
81
+
82
+ function identityBinaryContribution(deps, ts, checker, expression, identity, seen, depth) {
83
+ if (expression.operatorToken.kind === ts.SyntaxKind.CommaToken) return identityContribution(deps, ts, checker, expression.right, identity, seen, depth + 1);
84
+ if (expression.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionToken) return expressionReferences(deps, ts, checker, expression, identity) ? 'unproven' : 'no';
85
+ const left = identityContribution(deps, ts, checker, expression.left, identity, seen, depth + 1);
86
+ const right = identityContribution(deps, ts, checker, expression.right, identity, seen, depth + 1);
87
+ return (left === 'yes' || right === 'yes') && 'yes' || (left === 'unproven' || right === 'unproven') && 'unproven' || 'no';
88
+ }
89
+
90
+ function identityIdentifierContribution(deps, ts, checker, expression, identity, seen, depth) {
91
+ const symbol = deps.normalizedSymbol(ts, checker, expression);
92
+ if (!symbol || seen.has(symbol)) return 'no';
93
+ const initializer = deps.constInitializer(ts, symbol);
94
+ return initializer ? identityContribution(deps, ts, checker, initializer, identity, new Set(seen).add(symbol), depth + 1) : 'no';
95
+ }
96
+
97
+ function identityConditionalContribution(deps, ts, checker, expression, identity, seen, depth) {
98
+ const whenTrue = identityContribution(deps, ts, checker, expression.whenTrue, identity, seen, depth + 1);
99
+ const whenFalse = identityContribution(deps, ts, checker, expression.whenFalse, identity, seen, depth + 1);
100
+ if (whenTrue === 'yes' && whenFalse === 'yes') return 'yes';
101
+ if (whenTrue === 'unproven' || whenFalse === 'unproven') return 'unproven';
102
+ return 'no';
103
+ }
104
+
105
+ function identityContribution(deps, ts, checker, expression, identity, seen = new Set(), depth = 0) {
106
+ if (!expression || depth > 12) return 'unproven';
107
+ expression = deps.unwrapExpression(ts, expression);
108
+ if (matchesIdentityAccess(deps, ts, checker, expression, identity)) return 'yes';
109
+ if (ts.isIdentifier(expression)) return identityIdentifierContribution(deps, ts, checker, expression, identity, seen, depth);
110
+ if (ts.isConditionalExpression(expression)) return identityConditionalContribution(deps, ts, checker, expression, identity, seen, depth);
111
+ if (ts.isBinaryExpression(expression)) return identityBinaryContribution(deps, ts, checker, expression, identity, seen, depth);
112
+ if (ts.isPrefixUnaryExpression(expression)) return expressionReferences(deps, ts, checker, expression, identity) ? 'unproven' : 'no';
113
+ if (ts.isTemplateExpression(expression)) return combineContributions(expression.templateSpans
114
+ .map(span => identityContribution(deps, ts, checker, span.expression, identity, seen, depth + 1)));
115
+ if (ts.isArrayLiteralExpression(expression)) return identityArrayContribution(deps, ts, checker, expression, identity, seen, depth);
116
+ if (ts.isObjectLiteralExpression(expression)) return identityObjectContribution(deps, ts, checker, expression, identity, seen, depth);
117
+ return ts.isCallExpression(expression) || ts.isNewExpression(expression) || ts.isAwaitExpression(expression) ? 'unproven' : 'no';
118
+ }
119
+
120
+ function returnedExpression(ts, fn) {
121
+ if (ts.isArrowFunction(fn) && !ts.isBlock(fn.body)) return fn.body;
122
+ if (!fn.body || !ts.isBlock(fn.body)) return null;
123
+ const returns = [];
124
+ const visit = node => {
125
+ if (node !== fn.body && (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) || ts.isMethodDeclaration(node))) return;
126
+ if (ts.isReturnStatement(node) && node.expression) returns.push(node.expression);
127
+ else ts.forEachChild(node, visit);
128
+ };
129
+ visit(fn.body);
130
+ return returns.length === 1 ? returns[0] : null;
131
+ }
132
+
133
+ function rootKeyExpression(deps, ts, checker, expression, identities, seen = new Set(), depth = 0) {
134
+ if (!expression || depth > 10) return null;
135
+ expression = deps.unwrapExpression(ts, expression);
136
+ if (ts.isArrowFunction(expression) || ts.isFunctionExpression(expression)) {
137
+ const returned = returnedExpression(ts, expression);
138
+ return returned ? rootKeyExpression(deps, ts, checker, returned, identities, seen, depth + 1) : null;
139
+ }
140
+ if (ts.isIdentifier(expression) && !identities.some(identity => identity.symbol === deps.normalizedSymbol(ts, checker, expression))) {
141
+ const symbol = deps.normalizedSymbol(ts, checker, expression);
142
+ const initializer = symbol && !seen.has(symbol) ? deps.constInitializer(ts, symbol) : null;
143
+ if (initializer) return rootKeyExpression(deps, ts, checker, initializer, identities, new Set(seen).add(symbol), depth + 1);
144
+ }
145
+ return expression;
146
+ }
147
+
148
+ function staticObjectKey(deps, ts, checker, expression, identities, seen, depth) {
149
+ const values = effectiveObjectValues(ts, expression);
150
+ return values !== null && values.length === expression.properties.length
151
+ && values.every(([, value]) => staticKeyExpression(deps, ts, checker, value, identities, seen, depth + 1));
152
+ }
153
+
154
+ function staticCompositeKey(deps, ts, checker, expression, identities, seen, depth) {
155
+ if (ts.isTemplateExpression(expression)) return expression.templateSpans.every(span => staticKeyExpression(deps, ts, checker, span.expression, identities, seen, depth + 1));
156
+ if (ts.isArrayLiteralExpression(expression)) return expression.elements.every(element => !ts.isSpreadElement(element)
157
+ && staticKeyExpression(deps, ts, checker, element, identities, seen, depth + 1));
158
+ if (ts.isObjectLiteralExpression(expression)) return staticObjectKey(deps, ts, checker, expression, identities, seen, depth);
159
+ if (ts.isConditionalExpression(expression)) return staticKeyExpression(deps, ts, checker, expression.condition, identities, seen, depth + 1)
160
+ && staticKeyExpression(deps, ts, checker, expression.whenTrue, identities, seen, depth + 1)
161
+ && staticKeyExpression(deps, ts, checker, expression.whenFalse, identities, seen, depth + 1);
162
+ if (ts.isBinaryExpression(expression)) return staticKeyExpression(deps, ts, checker, expression.left, identities, seen, depth + 1)
163
+ && staticKeyExpression(deps, ts, checker, expression.right, identities, seen, depth + 1);
164
+ if (ts.isPrefixUnaryExpression(expression)) return staticKeyExpression(deps, ts, checker, expression.operand, identities, seen, depth + 1);
165
+ return null;
166
+ }
167
+
168
+ function staticIdentifierKey(deps, ts, checker, expression, identities, seen, depth) {
169
+ if (expression.text === 'undefined' || identities.some(identity => identity.symbol === deps.normalizedSymbol(ts, checker, expression))) return true;
170
+ const symbol = deps.normalizedSymbol(ts, checker, expression);
171
+ if (!symbol || seen.has(symbol)) return false;
172
+ const initializer = deps.constInitializer(ts, symbol);
173
+ if (!initializer) return false;
174
+ return staticKeyExpression(deps, ts, checker, initializer, identities, new Set(seen).add(symbol), depth + 1);
175
+ }
176
+
177
+ function staticKeyExpression(deps, ts, checker, expression, identities, seen = new Set(), depth = 0) {
178
+ if (!expression || depth > 12) return false;
179
+ expression = deps.unwrapExpression(ts, expression);
180
+ if (expression.kind === ts.SyntaxKind.NullKeyword || expression.kind === ts.SyntaxKind.TrueKeyword
181
+ || expression.kind === ts.SyntaxKind.FalseKeyword || ts.isStringLiteralLike(expression)
182
+ || ts.isNumericLiteral(expression) || ts.isBigIntLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression)) return true;
183
+ const composite = staticCompositeKey(deps, ts, checker, expression, identities, seen, depth);
184
+ if (composite !== null) return composite;
185
+ if (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) {
186
+ return identities.some(identity => expressionReferences(deps, ts, checker, expression, identity));
187
+ }
188
+ if (ts.isIdentifier(expression)) return staticIdentifierKey(deps, ts, checker, expression, identities, seen, depth);
189
+ return false;
190
+ }
191
+
192
+ function keyLeaves(deps, ts, checker, expression, identities, decisions = [], depth = 0) {
193
+ if (depth > 12) return null;
194
+ expression = rootKeyExpression(deps, ts, checker, expression, identities);
195
+ if (!expression) return null;
196
+ if (ts.isConditionalExpression(expression)) {
197
+ if (!staticKeyExpression(deps, ts, checker, expression.condition, identities)) return null;
198
+ const whenTrue = keyLeaves(deps, ts, checker, expression.whenTrue, identities, [...decisions, { condition: expression.condition, branch: true }], depth + 1);
199
+ const whenFalse = keyLeaves(deps, ts, checker, expression.whenFalse, identities, [...decisions, { condition: expression.condition, branch: false }], depth + 1);
200
+ return whenTrue && whenFalse ? [...whenTrue, ...whenFalse] : null;
201
+ }
202
+ if (expression.kind === ts.SyntaxKind.NullKeyword) return [{ kind: 'null', expression, decisions }];
203
+ return staticKeyExpression(deps, ts, checker, expression, identities) ? [{ kind: 'active', expression, decisions }] : null;
204
+ }
205
+
206
+ function nullish(deps, ts, checker, expression) {
207
+ expression = deps.unwrapExpression(ts, expression);
208
+ if (expression.kind === ts.SyntaxKind.NullKeyword) return true;
209
+ if (!ts.isIdentifier(expression) || expression.text !== 'undefined') return false;
210
+ const symbol = checker.getSymbolAtLocation(expression);
211
+ return !symbol || (symbol.getDeclarations?.() ?? []).every(declaration => declaration.getSourceFile().isDeclarationFile);
212
+ }
213
+
214
+ function logicalBranchMeansAvailable(deps, ts, checker, condition, identity, branch) {
215
+ const operator = condition.operatorToken.kind;
216
+ if (operator !== ts.SyntaxKind.AmpersandAmpersandToken && operator !== ts.SyntaxKind.BarBarToken) return null;
217
+ // `a && b` is true only when both sides are true; `a || b` is false only when both sides are false.
218
+ const decisive = operator === ts.SyntaxKind.AmpersandAmpersandToken ? branch : !branch;
219
+ return decisive && [condition.left, condition.right]
220
+ .some(side => branchMeansAvailable(deps, ts, checker, side, identity, branch));
221
+ }
222
+
223
+ function equalityBranchMeansAvailable(deps, ts, checker, condition, identity, branch) {
224
+ const operator = condition.operatorToken.kind;
225
+ const leftIdentity = expressionReferences(deps, ts, checker, condition.left, identity) && nullish(deps, ts, checker, condition.right);
226
+ const rightIdentity = expressionReferences(deps, ts, checker, condition.right, identity) && nullish(deps, ts, checker, condition.left);
227
+ if (!leftIdentity && !rightIdentity) return false;
228
+ if ([ts.SyntaxKind.ExclamationEqualsToken, ts.SyntaxKind.ExclamationEqualsEqualsToken].includes(operator)) return branch;
229
+ if ([ts.SyntaxKind.EqualsEqualsToken, ts.SyntaxKind.EqualsEqualsEqualsToken].includes(operator)) return !branch;
230
+ return false;
231
+ }
232
+
233
+ function branchMeansAvailable(deps, ts, checker, condition, identity, branch) {
234
+ condition = deps.unwrapExpression(ts, condition);
235
+ if (expressionReferences(deps, ts, checker, condition, identity) && (ts.isIdentifier(condition)
236
+ || ts.isPropertyAccessExpression(condition) || ts.isElementAccessExpression(condition))) return branch;
237
+ if (ts.isPrefixUnaryExpression(condition) && condition.operator === ts.SyntaxKind.ExclamationToken) {
238
+ return branchMeansAvailable(deps, ts, checker, condition.operand, identity, !branch);
239
+ }
240
+ if (!ts.isBinaryExpression(condition)) return false;
241
+ const logicalResult = logicalBranchMeansAvailable(deps, ts, checker, condition, identity, branch);
242
+ if (logicalResult !== null) return logicalResult;
243
+ return equalityBranchMeansAvailable(deps, ts, checker, condition, identity, branch);
244
+ }
245
+
246
+ function nonNullFalsy(deps, ts, expression) {
247
+ expression = deps.unwrapExpression(ts, expression);
248
+ return expression.kind === ts.SyntaxKind.FalseKeyword || ts.isStringLiteralLike(expression) && expression.text === ''
249
+ || ts.isNumericLiteral(expression) && Number(expression.text) === 0
250
+ || ts.isIdentifier(expression) && expression.text === 'undefined';
251
+ }
252
+
253
+ function containerSymbolsFromKey(deps, ts, checker, expression, found = new Set(), seen = new Set(), depth = 0) {
254
+ if (!expression || depth > 12) return found;
255
+ expression = deps.unwrapExpression(ts, expression);
256
+ if (ts.isIdentifier(expression)) {
257
+ const symbol = deps.normalizedSymbol(ts, checker, expression);
258
+ if (!symbol || seen.has(symbol)) return found;
259
+ const initializer = deps.constInitializer(ts, symbol);
260
+ if (!initializer) return found;
261
+ const value = deps.unwrapExpression(ts, initializer);
262
+ if (ts.isArrayLiteralExpression(value) || ts.isObjectLiteralExpression(value)) found.add(symbol);
263
+ containerSymbolsFromKey(deps, ts, checker, initializer, found, new Set(seen).add(symbol), depth + 1);
264
+ return found;
265
+ }
266
+ ts.forEachChild(expression, child => { containerSymbolsFromKey(deps, ts, checker, child, found, seen, depth + 1); });
267
+ return found;
268
+ }
269
+
270
+ function keyContainerRisks(deps, ts, checker, key, owner) {
271
+ const symbols = containerSymbolsFromKey(deps, ts, checker, key);
272
+ if (!symbols.size || !owner?.body) return [];
273
+ const declarationNames = new Set([...symbols].flatMap(symbol => (symbol.getDeclarations?.() ?? [])
274
+ .filter(ts.isVariableDeclaration).map(declaration => declaration.name)));
275
+ const risks = [];
276
+ const visit = node => {
277
+ if (ts.isIdentifier(node) && symbols.has(deps.normalizedSymbol(ts, checker, node)) && !declarationNames.has(node)
278
+ && !(node.pos >= key.pos && node.end <= key.end)) risks.push(node);
279
+ ts.forEachChild(node, visit);
280
+ };
281
+ visit(owner.body);
282
+ return risks;
283
+ }
284
+
285
+ function inspectIdentityKey(deps, scope, entry, call, identity, active) {
286
+ const { config, context, violations, reasons } = scope;
287
+ if (!active.length) {
288
+ violations.push(deps.violation(config, call, entry.kind === 'mutation' && identity.resource ? deps.SWR_MUTATION_RULE_ID : deps.SWR_KEY_RULE_ID,
289
+ `${entry.id} has no active key path carrying declared identity ${identity.id} (${identity.binding}).`,
290
+ { lifecycle: entry.id, identity: identity.id }));
291
+ return;
292
+ }
293
+ const contributions = new Set(active.map(leaf => identityContribution(deps, context.ts, call.checker, leaf.expression, identity)));
294
+ if (contributions.has('unproven')) {
295
+ reasons.push(`${entry.path}#${entry.export} key value contribution for identity ${identity.id} (${identity.binding}) cannot be proved on every active path`);
296
+ } else if (contributions.has('no')) {
297
+ const ruleId = entry.kind === 'mutation' && identity.resource ? deps.SWR_MUTATION_RULE_ID : deps.SWR_KEY_RULE_ID;
298
+ violations.push(deps.violation(config, call, ruleId,
299
+ `${entry.id} key must include declared ${identity.resource ? 'resource ' : ''}identity ${identity.id} (${identity.binding}) on every active key path.`,
300
+ { lifecycle: entry.id, identity: identity.id }));
301
+ }
302
+ if (identity.gatesRequest) {
303
+ const gated = active.every(leaf => leaf.decisions.some(decision => {
304
+ return branchMeansAvailable(deps, context.ts, call.checker, decision.condition, identity, decision.branch);
305
+ }));
306
+ if (!gated) violations.push(deps.violation(config, call, deps.SWR_KEY_RULE_ID,
307
+ `${entry.id} must produce an explicit null key when ${identity.id} (${identity.binding}) is unavailable.`,
308
+ { lifecycle: entry.id, identity: identity.id }));
309
+ }
310
+ }
311
+
312
+ function inspectKey(deps, scope, entry, call, identities) {
313
+ const { config, context, violations, reasons } = scope;
314
+ const key = call.node.arguments[0];
315
+ if (!key) {
316
+ reasons.push(`${entry.path}#${entry.export} has an SWR call without a key`);
317
+ return;
318
+ }
319
+ const containerRisks = keyContainerRisks(deps, context.ts, call.checker, key, call.owner);
320
+ if (containerRisks.length) {
321
+ const locations = containerRisks.map(node => deps.sourceLocation(call.source, node).line).filter((line, index, all) => all.indexOf(line) === index);
322
+ reasons.push(`${entry.path}#${entry.export} key container is referenced outside its declaration and selected SWR key` + (locations.length ? ` (line${'s'.repeat(Number(locations.length !== 1))} ${locations.join(', ')})` : ''));
323
+ return;
324
+ }
325
+ const leaves = keyLeaves(deps, context.ts, call.checker, key, identities);
326
+ if (!leaves) {
327
+ reasons.push(`${entry.path}#${entry.export} uses a dynamic SWR key whose identity cannot be proved`);
328
+ return;
329
+ }
330
+ const active = leaves.filter(leaf => leaf.kind === 'active');
331
+ if (active.some(leaf => nonNullFalsy(deps, context.ts, leaf.expression))) violations.push(deps.violation(config, call, deps.SWR_KEY_RULE_ID,
332
+ `${entry.id} uses explicit null, rather than another falsy value, for a disabled SWR key.`, { lifecycle: entry.id }));
333
+ for (const identity of identities) inspectIdentityKey(deps, scope, entry, call, identity, active);
334
+ }
335
+
336
+ export function createNextDataKeyInspector(deps) {
337
+ return (config, context, entry, call, identities, violations, reasons) => inspectKey(deps, { config, context, violations, reasons }, entry, call, identities);
338
+ }