@starci/hfs 4.0.9 → 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 (247) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/LICENSE +21 -0
  3. package/emit/operations.mjs +1 -1
  4. package/emit/type-schema.mjs +2 -1
  5. package/lint/run.mjs +82 -8
  6. package/package.json +3 -2
  7. package/report/order.mjs +2 -0
  8. package/runtime/config.example.yaml +187 -0
  9. package/runtime/engine/by-code-unit.mjs +12 -0
  10. package/runtime/engine/config.mjs +247 -273
  11. package/runtime/engine/invalid-config.mjs +5 -5
  12. package/runtime/engine/model-config.mjs +90 -0
  13. package/runtime/engine/orca-config.mjs +3 -1
  14. package/runtime/engine/removed-vocabulary.mjs +28 -0
  15. package/runtime/engine/resources-config.mjs +52 -0
  16. package/runtime/engine/runtime-root.mjs +17 -0
  17. package/runtime/engine/secrets.mjs +134 -0
  18. package/runtime/engine/sonar-config.mjs +21 -0
  19. package/runtime/engine/temp-root.mjs +32 -0
  20. package/runtime/knowledge/hfs/canon-pins.yaml +18 -18
  21. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  22. package/runtime/knowledge/hfs/rules.yaml +102 -69
  23. package/runtime/knowledge/hfs/slots.yaml +11 -14
  24. package/runtime/knowledge/patterns/be/api.yaml +7 -24
  25. package/runtime/knowledge/patterns/be/cli.yaml +3 -12
  26. package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
  27. package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
  28. package/runtime/modules/kernel/failure-codes.yaml +1 -1
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
  30. package/runtime/modules/models/registry.yaml +25 -397
  31. package/runtime/modules/models/runtimes.yaml +92 -262
  32. package/runtime/modules/models/tiers.yaml +65 -0
  33. package/runtime/scripts/api/fs/claim-file.mjs +30 -0
  34. package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
  35. package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
  36. package/runtime/scripts/api/fs/lib.mjs +6 -0
  37. package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
  38. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  39. package/runtime/scripts/api/git/lib.mjs +36 -1
  40. package/runtime/scripts/api/process/resolve-real-tool.mjs +52 -0
  41. package/runtime/scripts/api/process/run-program.mjs +8 -0
  42. package/runtime/scripts/api/sops/decrypt.mjs +9 -16
  43. package/runtime/scripts/api/sops/lib.mjs +309 -11
  44. package/runtime/scripts/api/sops/seal.mjs +8 -4
  45. package/runtime/scripts/hfs/allows.mjs +3 -3
  46. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  47. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  48. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  49. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  50. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  51. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  52. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  53. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  54. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  55. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  56. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  57. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  58. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  59. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  60. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  61. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  62. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  63. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  64. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  65. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  66. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  67. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  68. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  69. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  70. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  71. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +3 -2
  72. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  73. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  74. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  75. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  76. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  77. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  78. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  79. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  80. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  81. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  82. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  83. package/runtime/scripts/hfs/architecture/index.mjs +158 -102
  84. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  85. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  86. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  87. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  88. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  89. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  90. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  91. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  92. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  93. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  94. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  95. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  96. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  97. package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
  98. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  99. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  100. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  101. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  102. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  103. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  104. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  105. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  106. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  107. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  108. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  109. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  110. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  111. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  112. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  113. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  114. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  115. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  116. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  117. package/runtime/scripts/hfs/architecture.mjs +3 -4
  118. package/runtime/scripts/hfs/check.mjs +61 -70
  119. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  120. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  121. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  122. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  123. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  124. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  125. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  126. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  127. package/runtime/scripts/hfs/repo-identity.mjs +5 -2
  128. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  129. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  130. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  131. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  132. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  133. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  134. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  135. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  136. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  137. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  138. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  139. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  140. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  141. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  142. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  143. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  144. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  145. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  146. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  147. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  148. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  149. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  150. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  151. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  152. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  153. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  154. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  155. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  156. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  157. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  158. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  159. package/runtime/scripts/hfs/secret.mjs +90 -45
  160. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  161. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  162. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  163. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  164. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  165. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  166. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  167. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  168. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  169. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  170. package/runtime/scripts/hfs/slots.mjs +72 -693
  171. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  172. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  173. package/runtime/scripts/hfs/tree.mjs +19 -14
  174. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  175. package/runtime/scripts/hfs/view.mjs +1 -1
  176. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  177. package/runtime/scripts/lib/event-contract.mjs +17 -13
  178. package/runtime/scripts/lib/fs-kind.mjs +14 -4
  179. package/runtime/scripts/lib/git.mjs +2 -2
  180. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  181. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  182. package/runtime/scripts/lib/i18n.mjs +3 -2
  183. package/runtime/scripts/lib/in-order.mjs +71 -0
  184. package/runtime/scripts/lib/language.mjs +3 -3
  185. package/runtime/scripts/lib/list.mjs +8 -1
  186. package/runtime/scripts/lib/mutation-fence.mjs +15 -0
  187. package/runtime/scripts/lib/path-key.mjs +42 -9
  188. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  189. package/runtime/scripts/lib/regex.mjs +1 -1
  190. package/runtime/scripts/lib/same-text.mjs +1 -1
  191. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  192. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  193. package/runtime/scripts/lib/sops-envelope.mjs +103 -2
  194. package/runtime/scripts/lib/stack-services.mjs +1 -2
  195. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  196. package/runtime/scripts/lib/walk.mjs +6 -1
  197. package/scaffold/add-cli-lite.mjs +2 -1
  198. package/scaffold/add-table.mjs +2 -2
  199. package/scaffold/add.mjs +3 -2
  200. package/scaffold/app.mjs +4 -2
  201. package/scaffold/edition-gate.mjs +25 -25
  202. package/scaffold/lite-exports.mjs +2 -1
  203. package/scaffold/service.mjs +5 -4
  204. package/sync/hygiene.mjs +15 -9
  205. package/sync/index.mjs +7 -3
  206. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  207. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  208. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  209. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  210. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  211. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  212. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  213. package/templates/app/starciwork.gitignore +1 -1
  214. package/templates/be/image/api/Dockerfile +1 -1
  215. package/templates/be/image/cli/Dockerfile +1 -1
  216. package/templates/be/image/worker/Dockerfile +1 -1
  217. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  218. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  219. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  220. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  221. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  222. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  223. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  224. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  225. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  226. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  227. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  228. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  229. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  230. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  231. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  232. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  233. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  234. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  235. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  236. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  237. package/templates/fe/image/next/Dockerfile +2 -1
  238. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  239. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  240. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  241. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  242. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  243. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  244. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  245. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  246. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  247. package/upgrade/index.mjs +9 -2
@@ -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
+ }