@starci/hfs 4.0.10 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -1
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +3 -2
- package/package.json +1 -1
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +187 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +221 -239
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +35 -31
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +98 -65
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -41
- package/runtime/modules/kernel/removed-vocabulary.yaml +168 -0
- package/runtime/modules/models/registry.yaml +25 -359
- package/runtime/modules/models/runtimes.yaml +92 -256
- package/runtime/modules/models/tiers.yaml +65 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +34 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
- package/runtime/scripts/api/process/run-program.mjs +2 -1
- package/runtime/scripts/api/sops/lib.mjs +198 -120
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +158 -102
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +100 -68
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/check.mjs +61 -70
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +5 -2
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +59 -48
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +72 -693
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/path-key.mjs +38 -5
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +95 -48
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
- package/runtime/engine/admission.mjs +0 -308
- package/runtime/engine/canonical-json.mjs +0 -10
- package/runtime/engine/db/blob.mjs +0 -315
- package/runtime/engine/db/ledger-paths.mjs +0 -83
- package/runtime/engine/db/ledger.mjs +0 -1118
- package/runtime/engine/db/machine-connection.mjs +0 -135
- package/runtime/engine/db/machine-schema.mjs +0 -84
- package/runtime/engine/db/machine.mjs +0 -1367
- package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
- package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
- package/runtime/engine/db/provider-reservations.mjs +0 -101
- package/runtime/engine/digest.mjs +0 -16
- package/runtime/engine/refuse.mjs +0 -11
- package/runtime/modules/ops/_labels.yaml +0 -53
- package/runtime/scripts/api/node/lib.mjs +0 -14
- package/runtime/scripts/api/node/spawn-node.mjs +0 -6
- package/runtime/scripts/api/process/lib.mjs +0 -111
- package/runtime/scripts/api/process/owned-process.mjs +0 -78
- package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
- package/runtime/scripts/connectors/lib.mjs +0 -488
- package/runtime/scripts/lib/clip.mjs +0 -19
- package/runtime/scripts/lib/display-names.mjs +0 -259
- package/runtime/scripts/lib/example-refs.mjs +0 -158
- package/runtime/scripts/lib/json-schema.mjs +0 -52
- package/runtime/scripts/lib/process-identity.mjs +0 -6
- package/runtime/scripts/lib/read-yaml.mjs +0 -18
- package/runtime/scripts/lib/redact.mjs +0 -161
- package/runtime/scripts/lib/sleep.mjs +0 -7
- package/runtime/scripts/lib/source-phrases.mjs +0 -34
- package/runtime/scripts/lib/sqlite.mjs +0 -21
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
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(...
|
|
242
|
+
problems.push(...inlineSelectionProblems(context, selection));
|
|
388
243
|
continue;
|
|
389
244
|
}
|
|
390
245
|
if (selection.name === '__typename') continue;
|
|
391
|
-
|
|
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
|
}
|