@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
@@ -10,237 +10,11 @@
10
10
  // and an object field selects something, and every declared variable is used. Validation stops at the first failure of
11
11
  // each field path, so a broken document reads as a short list of named problems.
12
12
 
13
- const PUNCTUATORS = new Set(['{', '}', '(', ')', '[', ']', ':', '!', '=', '$', '@', '|', '&']);
14
- const BUILT_IN_SCALARS = new Set(['String', 'Int', 'Float', 'Boolean', 'ID']);
15
-
16
- /** The tokens of a GraphQL source: names, punctuators, `...`, strings and numbers; comments and commas are insignificant. */
17
- export function tokenize(source) {
18
- const tokens = [];
19
- let index = 0;
20
- while (index < source.length) {
21
- const char = source[index];
22
- if (char === '#') {
23
- while (index < source.length && source[index] !== '\n') index += 1;
24
- } else if (/[\s,]/.test(char)) {
25
- index += 1;
26
- } else if (source.startsWith('...', index)) {
27
- tokens.push({ kind: 'spread', value: '...' });
28
- index += 3;
29
- } else if (source.startsWith('"""', index)) {
30
- const end = source.indexOf('"""', index + 3);
31
- if (end === -1) throw new SyntaxError('unterminated block string');
32
- tokens.push({ kind: 'string', value: source.slice(index + 3, end) });
33
- index = end + 3;
34
- } else if (char === '"') {
35
- let end = index + 1;
36
- while (end < source.length && source[end] !== '"') end += source[end] === '\\' ? 2 : 1;
37
- tokens.push({ kind: 'string', value: source.slice(index + 1, end) });
38
- index = end + 1;
39
- } else if (PUNCTUATORS.has(char)) {
40
- tokens.push({ kind: 'punct', value: char });
41
- index += 1;
42
- } else if (/[_A-Za-z]/.test(char)) {
43
- const match = /^[_A-Za-z][_0-9A-Za-z]*/.exec(source.slice(index));
44
- tokens.push({ kind: 'name', value: match[0] });
45
- index += match[0].length;
46
- } else if (/[-0-9]/.test(char)) {
47
- const match = /^-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(source.slice(index));
48
- if (!match) throw new SyntaxError(`unexpected character "${char}"`);
49
- tokens.push({ kind: 'number', value: match[0] });
50
- index += match[0].length;
51
- } else {
52
- throw new SyntaxError(`unexpected character "${char}"`);
53
- }
54
- }
55
- return tokens;
56
- }
57
-
58
- /** A cursor over tokens with the expectations both parsers share. */
59
- function cursorOf(tokens) {
60
- let index = 0;
61
- const cursor = {
62
- peek: (offset = 0) => tokens[index + offset] ?? null,
63
- next: () => {
64
- const token = tokens[index];
65
- if (token === undefined) throw new SyntaxError('unexpected end of document');
66
- index += 1;
67
- return token;
68
- },
69
- is: (value) => tokens[index]?.value === value && tokens[index]?.kind !== 'string',
70
- done: () => index >= tokens.length,
71
- take: (value) => {
72
- if (!cursor.is(value)) return false;
73
- index += 1;
74
- return true;
75
- },
76
- expect: (value) => {
77
- const token = cursor.next();
78
- if (token.value !== value || token.kind === 'string') throw new SyntaxError(`expected "${value}", found "${token.value}"`);
79
- return token;
80
- },
81
- name: () => {
82
- const token = cursor.next();
83
- if (token.kind !== 'name') throw new SyntaxError(`expected a name, found "${token.value}"`);
84
- return token.value;
85
- },
86
- };
87
- return cursor;
88
- }
89
-
90
- /** A type reference: `{ name, list, nonNull, item }`. */
91
- function parseTypeRef(cursor) {
92
- let type;
93
- if (cursor.take('[')) {
94
- const item = parseTypeRef(cursor);
95
- cursor.expect(']');
96
- type = { list: true, item, name: item.name, nonNull: false };
97
- } else {
98
- type = { list: false, name: cursor.name(), nonNull: false };
99
- }
100
- if (cursor.take('!')) type = { ...type, nonNull: true };
101
- return type;
102
- }
13
+ import { cursorOf, parseTypeRef, skipDirectives, skipValue, tokenize } from './graphql-sdl.mjs';
103
14
 
104
- /** Skips one value of any shape (a default value, a directive argument). */
105
- function skipValue(cursor) {
106
- const token = cursor.next();
107
- if (token.kind !== 'punct') return;
108
- if (token.value === '$') return void cursor.name();
109
- const close = token.value === '{' ? '}' : token.value === '[' ? ']' : null;
110
- if (close === null) return;
111
- while (!cursor.take(close)) {
112
- if (token.value === '{') {
113
- cursor.name();
114
- cursor.expect(':');
115
- }
116
- skipValue(cursor);
117
- }
118
- }
15
+ export { parseSchema } from './graphql-sdl.mjs';
119
16
 
120
- function skipDirectives(cursor) {
121
- while (cursor.take('@')) {
122
- cursor.name();
123
- if (cursor.take('(')) {
124
- while (!cursor.take(')')) {
125
- cursor.name();
126
- cursor.expect(':');
127
- skipValue(cursor);
128
- }
129
- }
130
- }
131
- }
132
-
133
- function skipDescription(cursor) {
134
- if (cursor.peek()?.kind === 'string') cursor.next();
135
- }
136
-
137
- /** Field or input-value definitions between braces. */
138
- function parseFieldDefinitions(cursor, withArguments) {
139
- const fields = new Map();
140
- cursor.expect('{');
141
- while (!cursor.take('}')) {
142
- skipDescription(cursor);
143
- const name = cursor.name();
144
- const args = new Map();
145
- if (withArguments && cursor.take('(')) {
146
- while (!cursor.take(')')) {
147
- skipDescription(cursor);
148
- const argName = cursor.name();
149
- cursor.expect(':');
150
- const type = parseTypeRef(cursor);
151
- const hasDefault = cursor.take('=');
152
- if (hasDefault) skipValue(cursor);
153
- skipDirectives(cursor);
154
- args.set(argName, { type, hasDefault });
155
- }
156
- }
157
- cursor.expect(':');
158
- const type = parseTypeRef(cursor);
159
- const hasDefault = cursor.take('=');
160
- if (hasDefault) skipValue(cursor);
161
- skipDirectives(cursor);
162
- fields.set(name, { type, args, hasDefault });
163
- }
164
- return fields;
165
- }
166
-
167
- /** The types of an SDL source: Map(name -> { kind, fields? , values? , members? }), plus the root operation type names. */
168
- export function parseSchema(source) {
169
- const cursor = cursorOf(tokenize(source));
170
- const types = new Map();
171
- const roots = { query: 'Query', mutation: 'Mutation', subscription: 'Subscription' };
172
- while (!cursor.done()) {
173
- skipDescription(cursor);
174
- const keyword = cursor.name();
175
- if (keyword === 'schema' || (keyword === 'extend' && cursor.is('schema'))) {
176
- if (keyword === 'extend') cursor.name();
177
- skipDirectives(cursor);
178
- cursor.expect('{');
179
- while (!cursor.take('}')) {
180
- const operation = cursor.name();
181
- cursor.expect(':');
182
- roots[operation] = cursor.name();
183
- }
184
- } else if (keyword === 'directive') {
185
- cursor.expect('@');
186
- cursor.name();
187
- if (cursor.take('(')) parseArgumentList(cursor);
188
- if (cursor.is('repeatable')) cursor.name();
189
- cursor.name();
190
- cursor.take('|');
191
- cursor.name();
192
- while (cursor.take('|')) cursor.name();
193
- } else if (keyword === 'scalar') {
194
- const name = cursor.name();
195
- skipDirectives(cursor);
196
- types.set(name, { kind: 'scalar' });
197
- } else if (keyword === 'enum') {
198
- const name = cursor.name();
199
- skipDirectives(cursor);
200
- const values = new Set();
201
- cursor.expect('{');
202
- while (!cursor.take('}')) {
203
- skipDescription(cursor);
204
- values.add(cursor.name());
205
- skipDirectives(cursor);
206
- }
207
- types.set(name, { kind: 'enum', values });
208
- } else if (keyword === 'union') {
209
- const name = cursor.name();
210
- skipDirectives(cursor);
211
- cursor.expect('=');
212
- cursor.take('|');
213
- const members = [cursor.name()];
214
- while (cursor.take('|')) members.push(cursor.name());
215
- types.set(name, { kind: 'union', members });
216
- } else if (keyword === 'type' || keyword === 'interface' || keyword === 'input') {
217
- const name = cursor.name();
218
- if (cursor.is('implements')) {
219
- cursor.name();
220
- cursor.take('&');
221
- cursor.name();
222
- while (cursor.take('&')) cursor.name();
223
- }
224
- skipDirectives(cursor);
225
- const kind = keyword === 'input' ? 'input' : keyword === 'interface' ? 'interface' : 'object';
226
- types.set(name, { kind, fields: parseFieldDefinitions(cursor, kind !== 'input') });
227
- } else {
228
- throw new SyntaxError(`unexpected "${keyword}" in a schema`);
229
- }
230
- }
231
- return { types, roots };
232
- }
233
-
234
- function parseArgumentList(cursor) {
235
- while (!cursor.take(')')) {
236
- skipDescription(cursor);
237
- cursor.name();
238
- cursor.expect(':');
239
- parseTypeRef(cursor);
240
- if (cursor.take('=')) skipValue(cursor);
241
- skipDirectives(cursor);
242
- }
243
- }
17
+ const BUILT_IN_SCALARS = new Set(['String', 'Int', 'Float', 'Boolean', 'ID']);
244
18
 
245
19
  /** A value of a document: `{ kind: 'variable', name }`, `{ kind: 'object', fields: Map }`, `{ kind: 'list', items }` or a scalar. */
246
20
  function parseValue(cursor) {
@@ -264,48 +38,85 @@ function parseValue(cursor) {
264
38
  }
265
39
 
266
40
  /** A selection set: [{ kind: 'field', name, alias, args: Map, selections } | { kind: 'inline', on, selections } | { kind: 'spread', name }]. */
41
+ function parseSpreadSelection(cursor) {
42
+ cursor.next();
43
+ if (cursor.is('on')) {
44
+ cursor.name();
45
+ const on = cursor.name();
46
+ skipDirectives(cursor);
47
+ return { kind: 'inline', on, selections: parseSelectionSet(cursor) };
48
+ }
49
+ if (cursor.is('{') || cursor.is('@')) {
50
+ skipDirectives(cursor);
51
+ return { kind: 'inline', on: null, selections: parseSelectionSet(cursor) };
52
+ }
53
+ const selection = { kind: 'spread', name: cursor.name() };
54
+ skipDirectives(cursor);
55
+ return selection;
56
+ }
57
+
58
+ function parseFieldSelection(cursor) {
59
+ let name = cursor.name();
60
+ let alias = null;
61
+ if (cursor.take(':')) {
62
+ alias = name;
63
+ name = cursor.name();
64
+ }
65
+ const args = new Map();
66
+ if (cursor.take('(')) {
67
+ while (!cursor.take(')')) {
68
+ const argName = cursor.name();
69
+ cursor.expect(':');
70
+ args.set(argName, parseValue(cursor));
71
+ }
72
+ }
73
+ skipDirectives(cursor);
74
+ const subSelections = cursor.is('{') ? parseSelectionSet(cursor) : null;
75
+ return { kind: 'field', name, alias, args, selections: subSelections };
76
+ }
77
+
267
78
  function parseSelectionSet(cursor) {
268
79
  const selections = [];
269
80
  cursor.expect('{');
270
81
  while (!cursor.take('}')) {
271
- if (cursor.peek()?.kind === 'spread') {
272
- cursor.next();
273
- if (cursor.is('on')) {
274
- cursor.name();
275
- const on = cursor.name();
276
- skipDirectives(cursor);
277
- selections.push({ kind: 'inline', on, selections: parseSelectionSet(cursor) });
278
- } else if (cursor.is('{') || cursor.is('@')) {
279
- skipDirectives(cursor);
280
- selections.push({ kind: 'inline', on: null, selections: parseSelectionSet(cursor) });
281
- } else {
282
- selections.push({ kind: 'spread', name: cursor.name() });
283
- skipDirectives(cursor);
284
- }
285
- continue;
286
- }
287
- let name = cursor.name();
288
- let alias = null;
289
- if (cursor.take(':')) {
290
- alias = name;
291
- name = cursor.name();
292
- }
293
- const args = new Map();
294
- if (cursor.take('(')) {
295
- while (!cursor.take(')')) {
296
- const argName = cursor.name();
297
- cursor.expect(':');
298
- args.set(argName, parseValue(cursor));
299
- }
300
- }
301
- skipDirectives(cursor);
302
- const subSelections = cursor.is('{') ? parseSelectionSet(cursor) : null;
303
- selections.push({ kind: 'field', name, alias, args, selections: subSelections });
82
+ if (cursor.peek()?.kind === 'spread') selections.push(parseSpreadSelection(cursor));
83
+ else selections.push(parseFieldSelection(cursor));
304
84
  }
305
85
  return selections;
306
86
  }
307
87
 
308
88
  /** The operations and fragments of an executable document. */
89
+ function parseDocumentVariables(cursor) {
90
+ const variables = new Map();
91
+ if (cursor.take('(')) {
92
+ while (!cursor.take(')')) {
93
+ cursor.expect('$');
94
+ const variable = cursor.name();
95
+ cursor.expect(':');
96
+ const type = parseTypeRef(cursor);
97
+ if (cursor.take('=')) skipValue(cursor);
98
+ skipDirectives(cursor);
99
+ variables.set(variable, type);
100
+ }
101
+ }
102
+ return variables;
103
+ }
104
+
105
+ function parseFragmentDefinition(cursor) {
106
+ const name = cursor.name();
107
+ if (!cursor.take('on')) throw new SyntaxError(`fragment ${name} names no type condition`);
108
+ const on = cursor.name();
109
+ skipDirectives(cursor);
110
+ return { name, fragment: { on, selections: parseSelectionSet(cursor) } };
111
+ }
112
+
113
+ function parseOperationDefinition(cursor, keyword) {
114
+ const name = cursor.peek()?.kind === 'name' ? cursor.name() : null;
115
+ const variables = parseDocumentVariables(cursor);
116
+ skipDirectives(cursor);
117
+ return { operation: keyword, name, variables, selections: parseSelectionSet(cursor) };
118
+ }
119
+
309
120
  export function parseDocument(source) {
310
121
  const cursor = cursorOf(tokenize(source));
311
122
  const operations = [];
@@ -317,97 +128,122 @@ export function parseDocument(source) {
317
128
  }
318
129
  const keyword = cursor.name();
319
130
  if (keyword === 'fragment') {
320
- const name = cursor.name();
321
- if (!cursor.take('on')) throw new SyntaxError(`fragment ${name} names no type condition`);
322
- const on = cursor.name();
323
- skipDirectives(cursor);
324
- fragments.set(name, { on, selections: parseSelectionSet(cursor) });
131
+ const definition = parseFragmentDefinition(cursor);
132
+ fragments.set(definition.name, definition.fragment);
325
133
  continue;
326
134
  }
327
135
  if (keyword !== 'query' && keyword !== 'mutation' && keyword !== 'subscription') throw new SyntaxError(`unexpected "${keyword}" in a document`);
328
- const name = cursor.peek()?.kind === 'name' ? cursor.name() : null;
329
- const variables = new Map();
330
- if (cursor.take('(')) {
331
- while (!cursor.take(')')) {
332
- cursor.expect('$');
333
- const variable = cursor.name();
334
- cursor.expect(':');
335
- const type = parseTypeRef(cursor);
336
- if (cursor.take('=')) skipValue(cursor);
337
- skipDirectives(cursor);
338
- variables.set(variable, type);
339
- }
340
- }
341
- skipDirectives(cursor);
342
- operations.push({ operation: keyword, name, variables, selections: parseSelectionSet(cursor) });
136
+ operations.push(parseOperationDefinition(cursor, keyword));
343
137
  }
344
138
  return { operations, fragments };
345
139
  }
346
140
 
347
141
  const typeText = (type) => (type.list ? `[${typeText(type.item)}]` : type.name) + (type.nonNull ? '!' : '');
348
142
 
349
- /** The problems of one value against the input type it is passed as. */
350
- function valueProblems(schema, value, type, where, operation, used) {
143
+ function variableProblems(value, type, where, operation, used) {
351
144
  if (value.kind === 'variable') {
352
145
  used.add(value.name);
353
146
  const declared = operation.variables.get(value.name);
354
147
  if (declared === undefined) return [`${where} uses $${value.name}, which the operation does not declare`];
355
148
  return declared.name === type.name ? [] : [`${where} takes ${typeText(type)}, but $${value.name} is ${typeText(declared)}`];
356
149
  }
150
+ return null;
151
+ }
152
+
153
+ function objectValueProblems(schema, value, type, where, operation, used, named) {
154
+ if (named?.kind !== 'input') return [`${where} is an object, but ${type.name} is not an input type`];
155
+ const problems = [];
156
+ for (const [field, inner] of value.fields) {
157
+ const declared = named.fields.get(field);
158
+ if (declared === undefined) problems.push(`${where} names ${field}, which the input ${type.name} does not declare (it declares ${[...named.fields.keys()].join(', ') || 'nothing'})`);
159
+ else problems.push(...valueProblems(schema, inner, declared.type, `${where}.${field}`, operation, used));
160
+ }
161
+ for (const [field, declared] of named.fields) {
162
+ if (declared.type.nonNull && !declared.hasDefault && !value.fields.has(field)) problems.push(`${where} omits ${field}, which the input ${type.name} requires`);
163
+ }
164
+ return problems;
165
+ }
166
+
167
+ /** The problems of one value against the input type it is passed as. */
168
+ function valueProblems(schema, value, type, where, operation, used) {
169
+ const variable = variableProblems(value, type, where, operation, used);
170
+ if (variable !== null) return variable;
357
171
  if (type.list && value.kind === 'list') return value.items.flatMap((item, i) => valueProblems(schema, item, type.item, `${where}[${i}]`, operation, used));
358
172
  const named = schema.types.get(type.name);
359
- if (value.kind === 'object') {
360
- if (named?.kind !== 'input') return [`${where} is an object, but ${type.name} is not an input type`];
361
- const problems = [];
362
- for (const [field, inner] of value.fields) {
363
- const declared = named.fields.get(field);
364
- if (declared === undefined) problems.push(`${where} names ${field}, which the input ${type.name} does not declare (it declares ${[...named.fields.keys()].join(', ') || 'nothing'})`);
365
- else problems.push(...valueProblems(schema, inner, declared.type, `${where}.${field}`, operation, used));
366
- }
367
- for (const [field, declared] of named.fields) {
368
- if (declared.type.nonNull && !declared.hasDefault && !value.fields.has(field)) problems.push(`${where} omits ${field}, which the input ${type.name} requires`);
369
- }
370
- return problems;
173
+ if (value.kind === 'object') return objectValueProblems(schema, value, type, where, operation, used, named);
174
+ return [];
175
+ }
176
+
177
+ function fragmentSelectionProblems(context, selection) {
178
+ const { schema, document, path, operation, used, visiting } = context;
179
+ const fragment = document.fragments.get(selection.name);
180
+ if (fragment === undefined) return [`${path} spreads ...${selection.name}, which the document does not define`];
181
+ if (visiting.has(selection.name)) return [];
182
+ return selectionProblems({ schema, document, parentName: fragment.on, selections: fragment.selections, path, operation, used, visiting: new Set([...visiting, selection.name]) });
183
+ }
184
+
185
+ function inlineSelectionProblems(context, selection) {
186
+ const { schema, document, parentName, path, operation, used, visiting } = context;
187
+ return selectionProblems({ schema, document, parentName: selection.on ?? parentName, selections: selection.selections, path, operation, used, visiting });
188
+ }
189
+
190
+ function fieldArgumentProblems({ schema, parentName, operation, used }, selection, where, field) {
191
+ const problems = [];
192
+ for (const [arg, value] of selection.args) {
193
+ const declared = field.args.get(arg);
194
+ if (declared === undefined) problems.push(`${where} passes ${arg}, which ${parentName}.${selection.name} does not take (it takes ${[...field.args.keys()].join(', ') || 'no argument'})`);
195
+ else problems.push(...valueProblems(schema, value, declared.type, `${where}(${arg})`, operation, used));
196
+ }
197
+ return problems;
198
+ }
199
+
200
+ function requiredFieldArgumentProblems(parentName, selection, where, field) {
201
+ const problems = [];
202
+ for (const [arg, declared] of field.args) {
203
+ if (declared.type.nonNull && !declared.hasDefault && !selection.args.has(arg)) problems.push(`${where} omits ${arg}, which ${parentName}.${selection.name} requires`);
371
204
  }
205
+ return problems;
206
+ }
207
+
208
+ function fieldSelectionShapeProblems(context, selection, where, field) {
209
+ const { schema, document, operation, used, visiting } = context;
210
+ const target = schema.types.get(field.type.name);
211
+ const leaf = BUILT_IN_SCALARS.has(field.type.name) || target?.kind === 'scalar' || target?.kind === 'enum';
212
+ if (leaf && selection.selections !== null) return [`${where} is a ${field.type.name}, which selects no fields`];
213
+ if (!leaf && selection.selections === null) return [`${where} is a ${field.type.name}, which needs a selection of its fields`];
214
+ if (!leaf) return selectionProblems({ schema, document, parentName: field.type.name, selections: selection.selections, path: where, operation, used, visiting });
372
215
  return [];
373
216
  }
374
217
 
218
+ function fieldSelectionProblems(context, parent, selection) {
219
+ const { parentName } = context;
220
+ const where = `${context.path}.${selection.name}`;
221
+ const field = parent?.fields?.get(selection.name);
222
+ if (field === undefined) {
223
+ const known = parent?.fields ? [...parent.fields.keys()].join(', ') : 'nothing';
224
+ return [`${where} is not a field of ${parentName} (it has ${known})`];
225
+ }
226
+ const problems = fieldArgumentProblems(context, selection, where, field);
227
+ problems.push(...requiredFieldArgumentProblems(parentName, selection, where, field), ...fieldSelectionShapeProblems(context, selection, where, field));
228
+ return problems;
229
+ }
230
+
375
231
  /** The problems of one selection set against its parent type. */
376
- function selectionProblems(schema, document, parentName, selections, path, operation, used, visiting = new Set()) {
232
+ function selectionProblems({ schema, document, parentName, selections, path, operation, used, visiting = new Set() }) {
233
+ const context = { schema, document, parentName, path, operation, used, visiting };
377
234
  const parent = schema.types.get(parentName);
378
235
  const problems = [];
379
236
  for (const selection of selections) {
380
237
  if (selection.kind === 'spread') {
381
- const fragment = document.fragments.get(selection.name);
382
- if (fragment === undefined) problems.push(`${path} spreads ...${selection.name}, which the document does not define`);
383
- else if (!visiting.has(selection.name)) problems.push(...selectionProblems(schema, document, fragment.on, fragment.selections, path, operation, used, new Set([...visiting, selection.name])));
238
+ problems.push(...fragmentSelectionProblems(context, selection));
384
239
  continue;
385
240
  }
386
241
  if (selection.kind === 'inline') {
387
- problems.push(...selectionProblems(schema, document, selection.on ?? parentName, selection.selections, path, operation, used, visiting));
242
+ problems.push(...inlineSelectionProblems(context, selection));
388
243
  continue;
389
244
  }
390
245
  if (selection.name === '__typename') continue;
391
- const where = `${path}.${selection.name}`;
392
- const field = parent?.fields?.get(selection.name);
393
- if (field === undefined) {
394
- const known = parent?.fields ? [...parent.fields.keys()].join(', ') : 'nothing';
395
- problems.push(`${where} is not a field of ${parentName} (it has ${known})`);
396
- continue;
397
- }
398
- for (const [arg, value] of selection.args) {
399
- const declared = field.args.get(arg);
400
- if (declared === undefined) problems.push(`${where} passes ${arg}, which ${parentName}.${selection.name} does not take (it takes ${[...field.args.keys()].join(', ') || 'no argument'})`);
401
- else problems.push(...valueProblems(schema, value, declared.type, `${where}(${arg})`, operation, used));
402
- }
403
- for (const [arg, declared] of field.args) {
404
- if (declared.type.nonNull && !declared.hasDefault && !selection.args.has(arg)) problems.push(`${where} omits ${arg}, which ${parentName}.${selection.name} requires`);
405
- }
406
- const target = schema.types.get(field.type.name);
407
- const leaf = BUILT_IN_SCALARS.has(field.type.name) || target?.kind === 'scalar' || target?.kind === 'enum';
408
- if (leaf && selection.selections !== null) problems.push(`${where} is a ${field.type.name}, which selects no fields`);
409
- else if (!leaf && selection.selections === null) problems.push(`${where} is a ${field.type.name}, which needs a selection of its fields`);
410
- else if (!leaf) problems.push(...selectionProblems(schema, document, field.type.name, selection.selections, where, operation, used, visiting));
246
+ problems.push(...fieldSelectionProblems(context, parent, selection));
411
247
  }
412
248
  return problems;
413
249
  }
@@ -421,7 +257,7 @@ export function operationProblems(schema, document, operation) {
421
257
  if (!schema.types.has(rootName)) return [`the contract has no ${operation.operation} type`];
422
258
  const used = new Set();
423
259
  const label = operation.name ?? `anonymous ${operation.operation}`;
424
- const problems = selectionProblems(schema, document, rootName, operation.selections, label, operation, used);
260
+ const problems = selectionProblems({ schema, document, parentName: rootName, selections: operation.selections, path: label, operation, used });
425
261
  for (const variable of operation.variables.keys()) {
426
262
  if (!used.has(variable)) problems.push(`${label} declares $${variable}, which no argument uses`);
427
263
  }