@starci/hfs 4.0.10 → 4.2.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 (275) hide show
  1. package/CHANGELOG.md +5 -1
  2. package/emit/operations.mjs +1 -1
  3. package/emit/type-schema.mjs +2 -1
  4. package/lint/run.mjs +65 -24
  5. package/package.json +1 -1
  6. package/report/order.mjs +2 -0
  7. package/runtime/config.example.yaml +194 -0
  8. package/runtime/engine/by-code-unit.mjs +12 -0
  9. package/runtime/engine/config.mjs +222 -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/release-config.mjs +28 -0
  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 +35 -31
  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 +14 -14
  21. package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
  22. package/runtime/knowledge/hfs/rules.yaml +161 -65
  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 -41
  29. package/runtime/modules/kernel/removed-vocabulary.yaml +268 -0
  30. package/runtime/modules/models/registry.yaml +25 -359
  31. package/runtime/modules/models/runtimes.yaml +126 -265
  32. package/runtime/modules/models/tiers.yaml +66 -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/make-temp-dir.mjs +11 -0
  37. package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
  38. package/runtime/scripts/api/git/lib.mjs +34 -1
  39. package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
  40. package/runtime/scripts/api/process/run-program.mjs +2 -1
  41. package/runtime/scripts/api/sops/lib.mjs +198 -120
  42. package/runtime/scripts/hfs/allows.mjs +3 -3
  43. package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
  44. package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
  45. package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
  46. package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
  47. package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
  48. package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
  49. package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
  50. package/runtime/scripts/hfs/architecture/config.mjs +95 -80
  51. package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
  52. package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
  53. package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
  54. package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
  55. package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
  56. package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
  57. package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
  58. package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
  59. package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
  60. package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
  61. package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
  62. package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
  63. package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
  64. package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
  65. package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
  66. package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
  67. package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
  68. package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
  69. package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
  70. package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
  71. package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
  72. package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
  73. package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
  74. package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
  75. package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
  76. package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
  77. package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
  78. package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
  79. package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
  80. package/runtime/scripts/hfs/architecture/index.mjs +164 -104
  81. package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
  82. package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
  83. package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
  84. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
  85. package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
  86. package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
  87. package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
  88. package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
  89. package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
  90. package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
  91. package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
  92. package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
  93. package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
  94. package/runtime/scripts/hfs/architecture/required-files.mjs +121 -70
  95. package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
  96. package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
  97. package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
  98. package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
  99. package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
  100. package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
  101. package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
  102. package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
  103. package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
  104. package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
  105. package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
  106. package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
  107. package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
  108. package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
  109. package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
  110. package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
  111. package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
  112. package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
  113. package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
  114. package/runtime/scripts/hfs/architecture.mjs +3 -4
  115. package/runtime/scripts/hfs/catalog-once.mjs +15 -0
  116. package/runtime/scripts/hfs/check.mjs +63 -71
  117. package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
  118. package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
  119. package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
  120. package/runtime/scripts/hfs/edition-slots.mjs +5 -3
  121. package/runtime/scripts/hfs/linear-text.mjs +30 -0
  122. package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
  123. package/runtime/scripts/hfs/path-findings.mjs +58 -38
  124. package/runtime/scripts/hfs/pin-findings.mjs +31 -0
  125. package/runtime/scripts/hfs/repo-identity.mjs +21 -4
  126. package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
  127. package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
  128. package/runtime/scripts/hfs/rules/cli.mjs +5 -1
  129. package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
  130. package/runtime/scripts/hfs/rules/contract.mjs +35 -27
  131. package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
  132. package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
  133. package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
  134. package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
  135. package/runtime/scripts/hfs/rules/database.mjs +36 -23
  136. package/runtime/scripts/hfs/rules/deps.mjs +64 -32
  137. package/runtime/scripts/hfs/rules/docker.mjs +113 -65
  138. package/runtime/scripts/hfs/rules/edition.mjs +118 -111
  139. package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
  140. package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
  141. package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
  142. package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
  143. package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
  144. package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
  145. package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
  146. package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
  147. package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
  148. package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
  149. package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
  150. package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
  151. package/runtime/scripts/hfs/rules/saga.mjs +128 -61
  152. package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
  153. package/runtime/scripts/hfs/rules/services.mjs +34 -16
  154. package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
  155. package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
  156. package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
  157. package/runtime/scripts/hfs/secret.mjs +59 -48
  158. package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
  159. package/runtime/scripts/hfs/slot-classify.mjs +65 -0
  160. package/runtime/scripts/hfs/slot-errors.mjs +11 -0
  161. package/runtime/scripts/hfs/slot-imports.mjs +76 -0
  162. package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
  163. package/runtime/scripts/hfs/slot-match.mjs +153 -0
  164. package/runtime/scripts/hfs/slot-path.mjs +8 -0
  165. package/runtime/scripts/hfs/slot-required.mjs +76 -0
  166. package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
  167. package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
  168. package/runtime/scripts/hfs/slots.mjs +74 -695
  169. package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
  170. package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
  171. package/runtime/scripts/hfs/tree.mjs +19 -14
  172. package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
  173. package/runtime/scripts/hfs/view.mjs +1 -1
  174. package/runtime/scripts/lib/dockerfile.mjs +56 -33
  175. package/runtime/scripts/lib/env.mjs +10 -0
  176. package/runtime/scripts/lib/event-contract.mjs +17 -13
  177. package/runtime/scripts/lib/git.mjs +2 -2
  178. package/runtime/scripts/lib/graphql-contract.mjs +158 -322
  179. package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
  180. package/runtime/scripts/lib/i18n.mjs +3 -2
  181. package/runtime/scripts/lib/in-order.mjs +71 -0
  182. package/runtime/scripts/lib/language.mjs +3 -3
  183. package/runtime/scripts/lib/list.mjs +8 -1
  184. package/runtime/scripts/lib/path-key.mjs +38 -5
  185. package/runtime/scripts/lib/pid-alive.mjs +7 -0
  186. package/runtime/scripts/lib/regex.mjs +1 -1
  187. package/runtime/scripts/lib/same-text.mjs +1 -1
  188. package/runtime/scripts/lib/secret-patterns.mjs +6 -2
  189. package/runtime/scripts/lib/sleep-sync.mjs +2 -2
  190. package/runtime/scripts/lib/sops-envelope.mjs +95 -48
  191. package/runtime/scripts/lib/stack-services.mjs +1 -2
  192. package/runtime/scripts/lib/ts-ast.mjs +9 -0
  193. package/runtime/scripts/lib/walk.mjs +6 -1
  194. package/runtime/scripts/lib/yaml-cached.mjs +14 -0
  195. package/scaffold/add-cli-lite.mjs +2 -1
  196. package/scaffold/add-table.mjs +2 -2
  197. package/scaffold/add.mjs +3 -2
  198. package/scaffold/app.mjs +4 -2
  199. package/scaffold/edition-gate.mjs +25 -25
  200. package/scaffold/lite-exports.mjs +2 -1
  201. package/scaffold/service.mjs +5 -4
  202. package/sync/hygiene.mjs +15 -9
  203. package/sync/index.mjs +7 -3
  204. package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
  205. package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
  206. package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
  207. package/templates/app/ci-workflows/sonar-steps.yml +2 -2
  208. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
  209. package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
  210. package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
  211. package/templates/app/starciwork.gitignore +1 -1
  212. package/templates/be/image/api/Dockerfile +1 -1
  213. package/templates/be/image/cli/Dockerfile +1 -1
  214. package/templates/be/image/worker/Dockerfile +1 -1
  215. package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
  216. package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
  217. package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
  218. package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
  219. package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
  220. package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
  221. package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
  222. package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
  223. package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
  224. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
  225. package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
  226. package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
  227. package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
  228. package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
  229. package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
  230. package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
  231. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
  232. package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
  233. package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
  234. package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
  235. package/templates/fe/image/next/Dockerfile +2 -1
  236. package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
  237. package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
  238. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
  239. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
  240. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
  241. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
  242. package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
  243. package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
  244. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
  245. package/upgrade/index.mjs +9 -2
  246. package/runtime/engine/admission.mjs +0 -308
  247. package/runtime/engine/canonical-json.mjs +0 -10
  248. package/runtime/engine/db/blob.mjs +0 -315
  249. package/runtime/engine/db/ledger-paths.mjs +0 -83
  250. package/runtime/engine/db/ledger.mjs +0 -1118
  251. package/runtime/engine/db/machine-connection.mjs +0 -135
  252. package/runtime/engine/db/machine-schema.mjs +0 -84
  253. package/runtime/engine/db/machine.mjs +0 -1367
  254. package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
  255. package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
  256. package/runtime/engine/db/provider-reservations.mjs +0 -101
  257. package/runtime/engine/digest.mjs +0 -16
  258. package/runtime/engine/refuse.mjs +0 -11
  259. package/runtime/modules/ops/_labels.yaml +0 -53
  260. package/runtime/scripts/api/node/lib.mjs +0 -14
  261. package/runtime/scripts/api/node/spawn-node.mjs +0 -6
  262. package/runtime/scripts/api/process/lib.mjs +0 -111
  263. package/runtime/scripts/api/process/owned-process.mjs +0 -78
  264. package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
  265. package/runtime/scripts/connectors/lib.mjs +0 -488
  266. package/runtime/scripts/lib/clip.mjs +0 -19
  267. package/runtime/scripts/lib/display-names.mjs +0 -259
  268. package/runtime/scripts/lib/example-refs.mjs +0 -158
  269. package/runtime/scripts/lib/json-schema.mjs +0 -52
  270. package/runtime/scripts/lib/process-identity.mjs +0 -6
  271. package/runtime/scripts/lib/read-yaml.mjs +0 -18
  272. package/runtime/scripts/lib/redact.mjs +0 -161
  273. package/runtime/scripts/lib/sleep.mjs +0 -7
  274. package/runtime/scripts/lib/source-phrases.mjs +0 -34
  275. package/runtime/scripts/lib/sqlite.mjs +0 -21
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { machineKit } from './machine-ast.mjs';
3
3
  import { treeOf } from './required-files.mjs';
4
+ import { byCodeUnit } from '../../lib/list.mjs';
5
+ import { trimTrailingSlashes } from '../trailing-slashes.mjs';
4
6
 
5
7
  /**
6
8
  * R45 `module-per-transport` (BE_MODULE_SHAPE, BE-CONVENTION 1.2 and 1.6). A feature has one Nest module for its
@@ -22,48 +24,38 @@ export const MODULE_PER_TRANSPORT_RULE_IDS = ['BE_MODULE_SHAPE'];
22
24
  const RULE = 'BE_MODULE_SHAPE';
23
25
  const APP_ROOT_FILE = 'app.module.ts';
24
26
 
25
- export function checkModulePerTransport(input) {
26
- const { config, graph } = input;
27
- const kit = machineKit(input);
28
- const { ts, resolver } = kit;
29
- const violations = [];
30
- const report = (file, node, message, extra = {}) => violations.push({ ruleId: RULE, path: file.rel, ...kit.at(file.rel, file.sourceFile, node), message, ...extra });
31
- const plain = (rel, message, extra = {}) => violations.push({ ruleId: RULE, path: rel, line: 1, column: 1, message, ...extra });
27
+ function featureOwnerOf(resolver, rel) {
28
+ const owner = resolver.ownerOf(rel);
29
+ return owner && resolver.slot(owner.slot)?.tier === 'feature' ? owner : null;
30
+ }
32
31
 
33
- const featureOf = rel => {
34
- const owner = resolver.ownerOf(rel);
35
- return owner && resolver.slot(owner.slot)?.tier === 'feature' ? owner : null;
36
- };
37
- /**
38
- * {feature, root, protocol, composed} of a file inside a feature: protocol is the transport folder, null for the application
39
- * side. composed is set for a composed root, an owner slot with `composedBy` (be.cli): an app of those kinds lists its root
40
- * module, and every folder of the root holds one static module named after the folder.
41
- */
42
- const placeOf = rel => {
43
- const owner = featureOf(rel);
44
- if (!owner) return null;
45
- const slot = resolver.slot(owner.slot);
46
- const below = rel.slice(owner.root.length + 1).split('/');
47
- const feature = path.posix.basename(owner.root);
48
- if (slot.composedBy) return { root: owner.root, feature, protocol: null, below, composed: slot };
49
- return { root: owner.root, feature, protocol: below[0] === 'transport' && below.length > 2 ? below[1] : null, below, composed: null };
50
- };
51
- /** The one module file a folder of a composed root may hold: <folder>/<folder>.module.ts. */
52
- const composedModuleOf = rel => {
53
- const dir = path.posix.dirname(rel);
54
- return `${dir}/${path.posix.basename(dir)}.module.ts`;
55
- };
32
+ function modulePlaceOf(resolver, rel) {
33
+ const owner = featureOwnerOf(resolver, rel);
34
+ if (!owner) return null;
35
+ const slot = resolver.slot(owner.slot);
36
+ const below = rel.slice(owner.root.length + 1).split('/');
37
+ const feature = path.posix.basename(owner.root);
38
+ if (slot.composedBy) return { root: owner.root, feature, protocol: null, below, composed: slot };
39
+ return { root: owner.root, feature, protocol: below[0] === 'transport' && below.length > 2 ? below[1] : null, below, composed: null };
40
+ }
56
41
 
57
- // The transport slots and the app kinds that compose them, by protocol folder name.
42
+ function composedModulePath(rel) {
43
+ const dir = path.posix.dirname(rel);
44
+ return `${dir}/${path.posix.basename(dir)}.module.ts`;
45
+ }
46
+
47
+ function composedByProtocol(resolver) {
58
48
  const composedBy = new Map();
59
49
  for (const slot of resolver.slots()) {
60
50
  if (!slot.composedBy || slot.owner) continue;
61
- composedBy.set(path.posix.basename(slot.path.replace(/\/+$/u, '')), slot.composedBy);
51
+ composedBy.set(path.posix.basename(trimTrailingSlashes(slot.path)), slot.composedBy);
62
52
  }
53
+ return composedBy;
54
+ }
63
55
 
56
+ function reportTransportFileShapes(tree, placeOf, plain) {
64
57
  let transportModules = 0;
65
- const tree = treeOf(config.root);
66
- for (const rel of [...tree.files].sort()) {
58
+ for (const rel of [...tree.files].sort(byCodeUnit)) {
67
59
  const place = placeOf(rel);
68
60
  if (!place) continue;
69
61
  const base = path.posix.basename(rel);
@@ -74,81 +66,138 @@ export function checkModulePerTransport(input) {
74
66
  else transportModules += 1;
75
67
  }
76
68
  }
69
+ return transportModules;
70
+ }
71
+
72
+ function isNestModuleClass(ts, kit, checker, statement) {
73
+ return kit.decorators(statement).some(decorator => {
74
+ const call = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
75
+ return kit.isImportOf(checker, call, 'Module', '@nestjs/common');
76
+ });
77
+ }
78
+
79
+ function reportModuleClassLocation(file, statement, place, composedModuleOf, report) {
80
+ if (place.composed) {
81
+ const own = composedModuleOf(file.rel);
82
+ if (file.rel !== own) report(file, statement.name, `${statement.name.text} is a Nest module declared in ${file.rel}; every folder of ${place.root} holds exactly one static module named after it (${own}), never a module per command.`, { feature: place.feature });
83
+ return;
84
+ }
85
+ const own = place.protocol === null
86
+ ? `${place.root}/${place.feature}.module.ts`
87
+ : `${place.root}/transport/${place.protocol}/${place.feature}-${place.protocol}.module.ts`;
88
+ if (file.rel !== own) report(file, statement.name, `${statement.name.text} is a Nest module declared in ${file.rel}; a feature declares exactly its application module (${place.feature}.module.ts) and one module per transport folder (${place.feature}-<protocol>.module.ts), never a module per operation.`, { feature: place.feature });
89
+ }
77
90
 
78
- // @Module classes of the program: where they live, and the feature classes among them.
91
+ function reportConfigurableModuleUses(file, checker, kit, ts, place, report) {
92
+ kit.walk(file.sourceFile, node => {
93
+ if (ts.isNewExpression(node) && kit.isImportOf(checker, node.expression, 'ConfigurableModuleBuilder', '@nestjs/common')) {
94
+ report(file, node, `ConfigurableModuleBuilder builds a capability's module definition; feature ${place.feature} has static modules only, so declare the providers in its @Module.`, { feature: place.feature });
95
+ } else if (ts.isHeritageClause(node)) {
96
+ for (const type of node.types) if (kit.isImportOf(checker, type.expression, 'ConfigurableModuleClass', '@nestjs/common')) {
97
+ report(file, type, `A feature module never extends ConfigurableModuleClass; feature ${place.feature} has static modules only.`, { feature: place.feature });
98
+ }
99
+ }
100
+ return true;
101
+ });
102
+ }
103
+
104
+ function moduleClassesInFile(file, checker, place, modules, env) {
105
+ const { kit, ts, composedModuleOf, report } = env;
106
+ for (const statement of file.sourceFile.statements) {
107
+ if (!ts.isClassDeclaration(statement) || !statement.name) continue;
108
+ if (!isNestModuleClass(ts, kit, checker, statement)) continue;
109
+ modules.set(statement, { name: statement.name.text, file });
110
+ if (place) reportModuleClassLocation(file, statement, place, composedModuleOf, report);
111
+ }
112
+ }
113
+
114
+ function collectFeatureModules(graph, kit, ts, placeOf, composedModuleOf, report) {
79
115
  const modules = new Map(); // class declaration -> {name, file}
116
+ const env = { kit, ts, composedModuleOf, report };
80
117
  for (const file of graph.files.values()) {
81
118
  const checker = kit.checkerOf(file.sourceFile);
82
119
  const place = placeOf(file.rel);
83
- for (const statement of file.sourceFile.statements) {
84
- if (!ts.isClassDeclaration(statement) || !statement.name) continue;
85
- const isModule = kit.decorators(statement).some(decorator => {
86
- const call = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
87
- return kit.isImportOf(checker, call, 'Module', '@nestjs/common');
88
- });
89
- if (!isModule) continue;
90
- modules.set(statement, { name: statement.name.text, file });
91
- if (!place) continue;
92
- if (place.composed) {
93
- const own = composedModuleOf(file.rel);
94
- if (file.rel !== own) report(file, statement.name, `${statement.name.text} is a Nest module declared in ${file.rel}; every folder of ${place.root} holds exactly one static module named after it (${own}), never a module per command.`, { feature: place.feature });
95
- continue;
96
- }
97
- const own = place.protocol === null
98
- ? `${place.root}/${place.feature}.module.ts`
99
- : `${place.root}/transport/${place.protocol}/${place.feature}-${place.protocol}.module.ts`;
100
- if (file.rel !== own) report(file, statement.name, `${statement.name.text} is a Nest module declared in ${file.rel}; a feature declares exactly its application module (${place.feature}.module.ts) and one module per transport folder (${place.feature}-<protocol>.module.ts), never a module per operation.`, { feature: place.feature });
101
- }
102
- if (place) {
103
- kit.walk(file.sourceFile, node => {
104
- if (ts.isNewExpression(node) && kit.isImportOf(checker, node.expression, 'ConfigurableModuleBuilder', '@nestjs/common')) {
105
- report(file, node, `ConfigurableModuleBuilder builds a capability's module definition; feature ${place.feature} has static modules only, so declare the providers in its @Module.`, { feature: place.feature });
106
- } else if (ts.isHeritageClause(node)) {
107
- for (const type of node.types) if (kit.isImportOf(checker, type.expression, 'ConfigurableModuleClass', '@nestjs/common')) {
108
- report(file, type, `A feature module never extends ConfigurableModuleClass; feature ${place.feature} has static modules only.`, { feature: place.feature });
109
- }
110
- }
111
- return true;
112
- });
113
- }
120
+ moduleClassesInFile(file, checker, place, modules, env);
121
+ if (place) reportConfigurableModuleUses(file, checker, kit, ts, place, report);
122
+ }
123
+ return modules;
124
+ }
125
+
126
+ function reportModuleReference(root, node, app, module, place, composedBy, report) {
127
+ if (place.composed) {
128
+ const rootModule = `${place.root}/${place.feature}.module.ts`;
129
+ if (!place.composed.composedBy.includes(app.kind)) report(root, node, `App ${app.name} is of kind ${app.kind} and lists ${module.name} from ${place.root}; only an app of kind ${place.composed.composedBy.join(', ')} composes that root.`, { app: app.name, module: module.name });
130
+ else if (module.file.rel !== rootModule) report(root, node, `App ${app.name} lists ${module.name}, a group module inside ${place.root}; the app lists the root module (${rootModule}), which imports every group module.`, { app: app.name, module: module.name });
131
+ } else if (place.protocol === null) {
132
+ report(root, node, `App ${app.name} lists ${module.name}, the application module of feature ${place.feature}; apps import only transport modules (${place.feature}-<protocol>.module.ts), which import the application module themselves.`, { app: app.name, module: module.name });
133
+ } else if (!(composedBy.get(place.protocol) ?? []).includes(app.kind)) {
134
+ const allowed = [...composedBy].filter(([, kinds]) => kinds.includes(app.kind)).map(([protocol]) => protocol).sort(byCodeUnit);
135
+ const transports = allowed.length ? `${allowed.join(', ')} transports only` : 'no transport module';
136
+ report(root, node, `App ${app.name} is of kind ${app.kind} and lists ${module.name}, a ${place.protocol} transport module; an app of kind ${app.kind} composes ${transports}.`, { app: app.name, module: module.name, protocol: place.protocol });
114
137
  }
138
+ }
115
139
 
116
- // What each app root lists: a feature module must be a transport module of a kind the app kind composes.
140
+ function inspectModuleDeclarations(checker, target, node, root, app, scan, seen) {
141
+ const { moduleMap, kit, placeOf, composedBy, report } = scan;
117
142
  let references = 0;
118
- for (const app of resolver.repo.apps) {
119
- const root = graph.files.get(`apps/${app.name}/src/${APP_ROOT_FILE}`);
120
- if (!root) continue;
121
- const checker = kit.checkerOf(root.sourceFile);
122
- const seen = new Set();
123
- const judge = node => {
124
- const call = ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'register';
125
- if (ts.isCallExpression(node) && !call) return;
126
- const target = call ? node.expression.expression : node;
127
- for (const declaration of kit.declarationsOf(checker, target)) {
128
- const module = modules.get(declaration);
129
- if (!module) continue;
130
- const place = placeOf(module.file.rel);
131
- if (!place || seen.has(node)) continue;
132
- seen.add(node);
133
- references += 1;
134
- if (place.composed) {
135
- const rootModule = `${place.root}/${place.feature}.module.ts`;
136
- if (!place.composed.composedBy.includes(app.kind)) report(root, node, `App ${app.name} is of kind ${app.kind} and lists ${module.name} from ${place.root}; only an app of kind ${place.composed.composedBy.join(', ')} composes that root.`, { app: app.name, module: module.name });
137
- else if (module.file.rel !== rootModule) report(root, node, `App ${app.name} lists ${module.name}, a group module inside ${place.root}; the app lists the root module (${rootModule}), which imports every group module.`, { app: app.name, module: module.name });
138
- } else if (place.protocol === null) {
139
- report(root, node, `App ${app.name} lists ${module.name}, the application module of feature ${place.feature}; apps import only transport modules (${place.feature}-<protocol>.module.ts), which import the application module themselves.`, { app: app.name, module: module.name });
140
- } else if (!(composedBy.get(place.protocol) ?? []).includes(app.kind)) {
141
- const allowed = [...composedBy].filter(([, kinds]) => kinds.includes(app.kind)).map(([protocol]) => protocol).sort();
142
- report(root, node, `App ${app.name} is of kind ${app.kind} and lists ${module.name}, a ${place.protocol} transport module; an app of kind ${app.kind} composes ${allowed.length ? `${allowed.join(', ')} transports only` : 'no transport module'}.`, { app: app.name, module: module.name, protocol: place.protocol });
143
- }
144
- }
145
- };
146
- kit.walk(root.sourceFile, node => {
147
- if (ts.isPropertyAssignment(node) && kit.propertyNameText(node.name) === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {
148
- for (const element of node.initializer.elements) judge(element);
149
- } else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'register') judge(node);
150
- return true;
151
- });
143
+ for (const declaration of kit.declarationsOf(checker, target)) {
144
+ const module = moduleMap.get(declaration);
145
+ if (!module) continue;
146
+ const place = placeOf(module.file.rel);
147
+ if (!place || seen.has(node)) continue;
148
+ seen.add(node);
149
+ references += 1;
150
+ reportModuleReference(root, node, app, module, place, composedBy, report);
152
151
  }
152
+ return references;
153
+ }
154
+
155
+ function inspectAppRoot(app, scan) {
156
+ const { graph, kit, ts } = scan;
157
+ const root = graph.files.get(`apps/${app.name}/src/${APP_ROOT_FILE}`);
158
+ if (!root) return 0;
159
+ const checker = kit.checkerOf(root.sourceFile);
160
+ const seen = new Set();
161
+ let references = 0;
162
+ const judge = node => {
163
+ const call = ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'register';
164
+ if (ts.isCallExpression(node) && !call) return;
165
+ const target = call ? node.expression.expression : node;
166
+ references += inspectModuleDeclarations(checker, target, node, root, app, scan, seen);
167
+ };
168
+ kit.walk(root.sourceFile, node => {
169
+ if (ts.isPropertyAssignment(node) && kit.propertyNameText(node.name) === 'imports' && ts.isArrayLiteralExpression(node.initializer)) {
170
+ for (const element of node.initializer.elements) judge(element);
171
+ } else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === 'register') judge(node);
172
+ return true;
173
+ });
174
+ return references;
175
+ }
176
+
177
+ function appModuleReferences(apps, scan) {
178
+ let references = 0;
179
+ for (const app of apps) references += inspectAppRoot(app, scan);
180
+ return references;
181
+ }
182
+
183
+ export function checkModulePerTransport(input) {
184
+ const { config, graph } = input;
185
+ const kit = machineKit(input);
186
+ const { ts, resolver } = kit;
187
+ const violations = [];
188
+ const report = (file, node, message, extra = {}) => violations.push({ ruleId: RULE, path: file.rel, ...kit.at(file.rel, file.sourceFile, node), message, ...extra });
189
+ const plain = (rel, message, extra = {}) => violations.push({ ruleId: RULE, path: rel, line: 1, column: 1, message, ...extra });
190
+
191
+ const placeOf = rel => modulePlaceOf(resolver, rel);
192
+ const composedModuleOf = composedModulePath;
193
+
194
+ const composedBy = composedByProtocol(resolver);
195
+
196
+ const tree = treeOf(config.root);
197
+ const transportModules = reportTransportFileShapes(tree, placeOf, plain);
198
+
199
+ const modules = collectFeatureModules(graph, kit, ts, placeOf, composedModuleOf, report);
200
+
201
+ const references = appModuleReferences(resolver.repo.apps, { graph, kit, ts, moduleMap: modules, placeOf, composedBy, report });
153
202
  return { violations, coverage: { status: 'checked', modules: modules.size, transportModules, appReferences: references } };
154
203
  }
@@ -13,64 +13,72 @@ const HOOK_KEYS = new Set(['id', 'path', 'export', 'kind', 'resultBinding', 'ide
13
13
  const IDENTITY_KEYS = new Set(['id', 'binding', 'gatesRequest', 'resource']);
14
14
  function relativeSource(repository, value, label) {
15
15
  repository = path.resolve(repository);
16
- if (typeof value !== 'string' || !value.trim() || path.isAbsolute(value)) throw Error(`${label} must be a non-empty repository-relative source path.`);
16
+ if (typeof value !== 'string' || !value.trim() || path.isAbsolute(value)) throw new Error(`${label} must be a non-empty repository-relative source path.`);
17
17
  const relative = slash(value.trim()).replace(/^\.\//, '');
18
18
  if (relative.split('/').includes('..') || !SOURCE.test(relative) || /\.d\.[cm]?[jt]s$/i.test(relative)) {
19
- throw Error(`${label} must be a production TypeScript or JavaScript source path.`);
19
+ throw new Error(`${label} must be a production TypeScript or JavaScript source path.`);
20
20
  }
21
21
  const absolute = path.resolve(repository, ...relative.split('/'));
22
- if (!isInside(repository, absolute)) throw Error(`${label} must stay inside the repository.`);
22
+ if (!isInside(repository, absolute)) throw new Error(`${label} must stay inside the repository.`);
23
23
  for (let current = absolute; current !== repository; current = path.dirname(current)) {
24
24
  const stat = fs.lstatSync(current);
25
- if (stat.isSymbolicLink()) throw Error(`${label} must not cross a symbolic-link or junction ancestor.`);
25
+ if (stat.isSymbolicLink()) throw new Error(`${label} must not cross a symbolic-link or junction ancestor.`);
26
26
  }
27
27
  const stat = fs.lstatSync(absolute);
28
- if (!stat.isFile() || !isInside(canonical(repository), canonical(absolute))) throw Error(`${label} must be a regular source file inside the repository.`);
28
+ if (!stat.isFile() || !isInside(canonical(repository), canonical(absolute))) throw new Error(`${label} must be a regular source file inside the repository.`);
29
29
  return relative;
30
30
  }
31
31
 
32
+ const CONTRACT_LABEL = 'package.json#starci.codePatterns.next.dataLifecycle';
33
+
34
+ function parseSwr(swr) {
35
+ exactKeys(swr, SWR_KEYS, 'dataLifecycle.swr');
36
+ if (swr.package !== 'swr' || !Number.isInteger(swr.major) || swr.major < 1) {
37
+ throw new Error('dataLifecycle.swr must bind package swr and one positive integer major version.');
38
+ }
39
+ }
40
+
41
+ function parseIdentity(identity, identityLabel, seen) {
42
+ exactKeys(identity, IDENTITY_KEYS, identityLabel);
43
+ if (typeof identity.id !== 'string' || !identity.id.trim() || seen.ids.has(identity.id.trim())) throw new Error(`${identityLabel}.id must be unique and non-empty.`);
44
+ if (typeof identity.binding !== 'string' || !BINDING.test(identity.binding) || seen.bindings.has(identity.binding)) throw new Error(`${identityLabel}.binding must be a unique identifier or property path.`);
45
+ if (typeof identity.gatesRequest !== 'boolean' || typeof identity.resource !== 'boolean') throw new Error(`${identityLabel} must explicitly declare gatesRequest and resource booleans.`);
46
+ seen.ids.add(identity.id.trim());
47
+ seen.bindings.add(identity.binding);
48
+ return { id: identity.id.trim(), binding: identity.binding, gatesRequest: identity.gatesRequest, resource: identity.resource };
49
+ }
50
+
51
+ function parseHook(config, hook, label, ids) {
52
+ exactKeys(hook, HOOK_KEYS, label);
53
+ if (typeof hook.id !== 'string' || !hook.id.trim() || ids.has(hook.id.trim())) throw new Error(`${label}.id must be unique and non-empty.`);
54
+ ids.add(hook.id.trim());
55
+ const source = relativeSource(config.root, hook.path, `${label}.path`);
56
+ if (typeof hook.export !== 'string' || !/^use[A-Z0-9_$][\w$]*$/.test(hook.export)) throw new Error(`${label}.export must name one exported use* hook.`);
57
+ if (!['query', 'mutation'].includes(hook.kind)) throw new Error(`${label}.kind must be query or mutation.`);
58
+ if (hook.resultBinding !== undefined && (typeof hook.resultBinding !== 'string' || !IDENTIFIER.test(hook.resultBinding))) {
59
+ throw new Error(`${label}.resultBinding must be one local identifier.`);
60
+ }
61
+ if (!Array.isArray(hook.identities)) throw new Error(`${label}.identities must be an array.`);
62
+ const seen = { ids: new Set(), bindings: new Set() };
63
+ const identities = hook.identities.map((identity, identityIndex) => parseIdentity(identity, `${label}.identities[${identityIndex}]`, seen));
64
+ if (hook.kind === 'mutation' && !identities.some(identity => identity.resource)) throw new Error(`${label} mutation must declare at least one resource identity.`);
65
+ return { id: hook.id.trim(), path: source, export: hook.export, kind: hook.kind,
66
+ ...(hook.resultBinding === undefined ? {} : { resultBinding: hook.resultBinding }), identities };
67
+ }
68
+
32
69
  export function parseContract(config) {
33
70
  // The contract is declared in the app's one package.json (the app root; config.root is the fe side folder).
34
71
  const manifest = JSON.parse(fs.readFileSync(path.join(config.packageRoot ?? config.root, 'package.json'), 'utf8'));
35
72
  const next = manifest?.starci?.codePatterns?.next;
36
73
  const value = next?.dataLifecycle;
37
74
  if (value === undefined) return null;
38
- if (next.schema !== 'starci/next-code-pattern-contract@1') throw Error('package.json#starci.codePatterns.next must use starci/next-code-pattern-contract@1.');
39
- exactKeys(value, TOP_KEYS, 'package.json#starci.codePatterns.next.dataLifecycle');
40
- if (value.schema !== CONTRACT_SCHEMA) throw Error(`dataLifecycle.schema must be ${CONTRACT_SCHEMA}.`);
41
- exactKeys(value.swr, SWR_KEYS, 'dataLifecycle.swr');
42
- if (value.swr.package !== 'swr' || !Number.isInteger(value.swr.major) || value.swr.major < 1) {
43
- throw Error('dataLifecycle.swr must bind package swr and one positive integer major version.');
44
- }
45
- if (!Array.isArray(value.hooks) || value.hooks.length === 0) throw Error('dataLifecycle.hooks must contain at least one declared lifecycle call.');
75
+ if (next.schema !== 'starci/next-code-pattern-contract@1') throw new Error('package.json#starci.codePatterns.next must use starci/next-code-pattern-contract@1.');
76
+ exactKeys(value, TOP_KEYS, CONTRACT_LABEL);
77
+ if (value.schema !== CONTRACT_SCHEMA) throw new Error(`dataLifecycle.schema must be ${CONTRACT_SCHEMA}.`);
78
+ parseSwr(value.swr);
79
+ if (!Array.isArray(value.hooks) || value.hooks.length === 0) throw new Error('dataLifecycle.hooks must contain at least one declared lifecycle call.');
46
80
  const ids = new Set();
47
- const hooks = value.hooks.map((hook, index) => {
48
- const label = `dataLifecycle.hooks[${index}]`;
49
- exactKeys(hook, HOOK_KEYS, label);
50
- if (typeof hook.id !== 'string' || !hook.id.trim() || ids.has(hook.id.trim())) throw Error(`${label}.id must be unique and non-empty.`);
51
- ids.add(hook.id.trim());
52
- const source = relativeSource(config.root, hook.path, `${label}.path`);
53
- if (typeof hook.export !== 'string' || !/^use[A-Z0-9_$][\w$]*$/.test(hook.export)) throw Error(`${label}.export must name one exported use* hook.`);
54
- if (!['query', 'mutation'].includes(hook.kind)) throw Error(`${label}.kind must be query or mutation.`);
55
- if (hook.resultBinding !== undefined && (typeof hook.resultBinding !== 'string' || !IDENTIFIER.test(hook.resultBinding))) {
56
- throw Error(`${label}.resultBinding must be one local identifier.`);
57
- }
58
- if (!Array.isArray(hook.identities)) throw Error(`${label}.identities must be an array.`);
59
- const identityIds = new Set(), bindings = new Set();
60
- const identities = hook.identities.map((identity, identityIndex) => {
61
- const identityLabel = `${label}.identities[${identityIndex}]`;
62
- exactKeys(identity, IDENTITY_KEYS, identityLabel);
63
- if (typeof identity.id !== 'string' || !identity.id.trim() || identityIds.has(identity.id.trim())) throw Error(`${identityLabel}.id must be unique and non-empty.`);
64
- if (typeof identity.binding !== 'string' || !BINDING.test(identity.binding) || bindings.has(identity.binding)) throw Error(`${identityLabel}.binding must be a unique identifier or property path.`);
65
- if (typeof identity.gatesRequest !== 'boolean' || typeof identity.resource !== 'boolean') throw Error(`${identityLabel} must explicitly declare gatesRequest and resource booleans.`);
66
- identityIds.add(identity.id.trim());
67
- bindings.add(identity.binding);
68
- return { id: identity.id.trim(), binding: identity.binding, gatesRequest: identity.gatesRequest, resource: identity.resource };
69
- });
70
- if (hook.kind === 'mutation' && !identities.some(identity => identity.resource)) throw Error(`${label} mutation must declare at least one resource identity.`);
71
- return { id: hook.id.trim(), path: source, export: hook.export, kind: hook.kind,
72
- ...(hook.resultBinding === undefined ? {} : { resultBinding: hook.resultBinding }), identities };
73
- });
81
+ const hooks = value.hooks.map((hook, index) => parseHook(config, hook, `dataLifecycle.hooks[${index}]`, ids));
74
82
  return { schema: value.schema, swr: { package: 'swr', major: value.swr.major }, hooks };
75
83
  }
76
84
 
@@ -79,7 +87,7 @@ export function installedSWR(repository) {
79
87
  let manifest;
80
88
  try { manifest = require.resolve('swr/package.json'); } catch {
81
89
  let entry;
82
- try { entry = require.resolve('swr'); } catch { throw Error('Installed swr package is unavailable.'); }
90
+ try { entry = require.resolve('swr'); } catch { throw new Error('Installed swr package is unavailable.'); }
83
91
  let current = path.dirname(entry);
84
92
  while (isInside(repository, current) && current !== repository) {
85
93
  const candidate = path.join(current, 'package.json');
@@ -87,10 +95,10 @@ export function installedSWR(repository) {
87
95
  current = path.dirname(current);
88
96
  }
89
97
  }
90
- if (!manifest) throw Error('Installed swr package.json is unavailable.');
98
+ if (!manifest) throw new Error('Installed swr package.json is unavailable.');
91
99
  const actualManifest = canonical(manifest);
92
100
  const parsed = JSON.parse(fs.readFileSync(actualManifest, 'utf8'));
93
101
  const major = Number.parseInt(String(parsed.version ?? '').split('.')[0], 10);
94
- if (parsed.name !== 'swr' || !Number.isInteger(major)) throw Error('Installed swr package identity or version is invalid.');
102
+ if (parsed.name !== 'swr' || !Number.isInteger(major)) throw new Error('Installed swr package identity or version is invalid.');
95
103
  return { name: parsed.name, version: parsed.version, major, root: canonical(path.dirname(actualManifest)) };
96
104
  }