@async/db 0.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.
- package/CHANGELOG.md +167 -0
- package/README.md +431 -0
- package/SPEC.md +1429 -0
- package/db.config.example.mjs +128 -0
- package/dist/cli/args.d.ts +8 -0
- package/dist/cli/args.js +16 -0
- package/dist/cli/commands/create.d.ts +3 -0
- package/dist/cli/commands/create.js +13 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +31 -0
- package/dist/cli/commands/generate.d.ts +6 -0
- package/dist/cli/commands/generate.js +24 -0
- package/dist/cli/commands/operations.d.ts +12 -0
- package/dist/cli/commands/operations.js +61 -0
- package/dist/cli/commands/schema.d.ts +11 -0
- package/dist/cli/commands/schema.js +1086 -0
- package/dist/cli/commands/serve.d.ts +9 -0
- package/dist/cli/commands/serve.js +18 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.js +11 -0
- package/dist/cli/commands/types.d.ts +7 -0
- package/dist/cli/commands/types.js +37 -0
- package/dist/cli/commands/viewer.d.ts +6 -0
- package/dist/cli/commands/viewer.js +29 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +108 -0
- package/dist/cli/output.d.ts +25 -0
- package/dist/cli/output.js +149 -0
- package/dist/cli/schema-prompt.d.ts +20 -0
- package/dist/cli/schema-prompt.js +66 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/client-cache.d.ts +105 -0
- package/dist/client-cache.js +916 -0
- package/dist/client.d.ts +64 -0
- package/dist/client.js +405 -0
- package/dist/config-public.d.ts +1 -0
- package/dist/config-public.js +1 -0
- package/dist/config.d.ts +54 -0
- package/dist/config.js +2 -0
- package/dist/csv.d.ts +1 -0
- package/dist/csv.js +1 -0
- package/dist/db.d.ts +3 -0
- package/dist/db.js +3 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +1 -0
- package/dist/errors.d.ts +1 -0
- package/dist/errors.js +1 -0
- package/dist/features/config/defaults.d.ts +98 -0
- package/dist/features/config/defaults.js +95 -0
- package/dist/features/config/load.d.ts +11 -0
- package/dist/features/config/load.js +265 -0
- package/dist/features/config/public.d.ts +17 -0
- package/dist/features/config/public.js +75 -0
- package/dist/features/doctor/duplicate-ids.d.ts +18 -0
- package/dist/features/doctor/duplicate-ids.js +79 -0
- package/dist/features/doctor/field-consistency.d.ts +17 -0
- package/dist/features/doctor/field-consistency.js +48 -0
- package/dist/features/doctor/index.d.ts +39 -0
- package/dist/features/doctor/index.js +177 -0
- package/dist/features/doctor/relations.d.ts +22 -0
- package/dist/features/doctor/relations.js +90 -0
- package/dist/features/doctor/schema-guidance.d.ts +35 -0
- package/dist/features/doctor/schema-guidance.js +184 -0
- package/dist/features/generate/registry.d.ts +14 -0
- package/dist/features/generate/registry.js +37 -0
- package/dist/features/http/registry.d.ts +46 -0
- package/dist/features/http/registry.js +86 -0
- package/dist/features/operations/index.d.ts +49 -0
- package/dist/features/operations/index.js +199 -0
- package/dist/features/operations/maps.d.ts +1 -0
- package/dist/features/operations/maps.js +10 -0
- package/dist/features/operations/readiness.d.ts +30 -0
- package/dist/features/operations/readiness.js +228 -0
- package/dist/features/operations/runtime.d.ts +57 -0
- package/dist/features/operations/runtime.js +288 -0
- package/dist/features/runtime/collection.d.ts +51 -0
- package/dist/features/runtime/collection.js +198 -0
- package/dist/features/runtime/db.d.ts +152 -0
- package/dist/features/runtime/db.js +824 -0
- package/dist/features/runtime/document.d.ts +43 -0
- package/dist/features/runtime/document.js +111 -0
- package/dist/features/runtime/fanout.d.ts +24 -0
- package/dist/features/runtime/fanout.js +77 -0
- package/dist/features/runtime/json-pointer.d.ts +5 -0
- package/dist/features/runtime/json-pointer.js +49 -0
- package/dist/features/runtime/scope-state.d.ts +44 -0
- package/dist/features/runtime/scope-state.js +185 -0
- package/dist/features/runtime/state.d.ts +1 -0
- package/dist/features/runtime/state.js +1 -0
- package/dist/features/schema/api.d.ts +107 -0
- package/dist/features/schema/api.js +460 -0
- package/dist/features/schema/builders.d.ts +86 -0
- package/dist/features/schema/builders.js +110 -0
- package/dist/features/schema/fields.d.ts +38 -0
- package/dist/features/schema/fields.js +296 -0
- package/dist/features/schema/generated.d.ts +29 -0
- package/dist/features/schema/generated.js +32 -0
- package/dist/features/schema/locator.d.ts +16 -0
- package/dist/features/schema/locator.js +135 -0
- package/dist/features/schema/manifest.d.ts +91 -0
- package/dist/features/schema/manifest.js +384 -0
- package/dist/features/schema/metadata.d.ts +30 -0
- package/dist/features/schema/metadata.js +75 -0
- package/dist/features/schema/project.d.ts +46 -0
- package/dist/features/schema/project.js +442 -0
- package/dist/features/schema/relations.d.ts +38 -0
- package/dist/features/schema/relations.js +109 -0
- package/dist/features/schema/resolvers.d.ts +36 -0
- package/dist/features/schema/resolvers.js +111 -0
- package/dist/features/schema/resource.d.ts +75 -0
- package/dist/features/schema/resource.js +253 -0
- package/dist/features/schema/source-definitions.d.ts +21 -0
- package/dist/features/schema/source-definitions.js +29 -0
- package/dist/features/schema/sources.d.ts +83 -0
- package/dist/features/schema/sources.js +689 -0
- package/dist/features/schema/standard-schema.d.ts +57 -0
- package/dist/features/schema/standard-schema.js +232 -0
- package/dist/features/schema/validation.d.ts +69 -0
- package/dist/features/schema/validation.js +434 -0
- package/dist/features/storage/events.d.ts +12 -0
- package/dist/features/storage/events.js +30 -0
- package/dist/features/storage/json.d.ts +112 -0
- package/dist/features/storage/json.js +239 -0
- package/dist/features/storage/memory.d.ts +30 -0
- package/dist/features/storage/memory.js +44 -0
- package/dist/features/storage/resource-json.d.ts +31 -0
- package/dist/features/storage/resource-json.js +76 -0
- package/dist/features/storage/runtime.d.ts +37 -0
- package/dist/features/storage/runtime.js +184 -0
- package/dist/features/storage/source-metadata.d.ts +20 -0
- package/dist/features/storage/source-metadata.js +25 -0
- package/dist/features/storage/source.d.ts +37 -0
- package/dist/features/storage/source.js +60 -0
- package/dist/features/storage/static.d.ts +29 -0
- package/dist/features/storage/static.js +42 -0
- package/dist/features/sync/defaults.d.ts +21 -0
- package/dist/features/sync/defaults.js +21 -0
- package/dist/features/sync/index.d.ts +35 -0
- package/dist/features/sync/index.js +85 -0
- package/dist/features/sync/mirror-state.d.ts +14 -0
- package/dist/features/sync/mirror-state.js +4 -0
- package/dist/features/sync/runtime-dirs.d.ts +5 -0
- package/dist/features/sync/runtime-dirs.js +9 -0
- package/dist/features/sync/source-writes.d.ts +15 -0
- package/dist/features/sync/source-writes.js +27 -0
- package/dist/features/sync/synthetic-seed.d.ts +26 -0
- package/dist/features/sync/synthetic-seed.js +83 -0
- package/dist/features/viewer/manifest.d.ts +148 -0
- package/dist/features/viewer/manifest.js +165 -0
- package/dist/fs-utils.d.ts +1 -0
- package/dist/fs-utils.js +1 -0
- package/dist/generate/hono/app.d.ts +6 -0
- package/dist/generate/hono/app.js +51 -0
- package/dist/generate/hono/graphql.d.ts +7 -0
- package/dist/generate/hono/graphql.js +53 -0
- package/dist/generate/hono/index.d.ts +55 -0
- package/dist/generate/hono/index.js +140 -0
- package/dist/generate/hono/package.d.ts +6 -0
- package/dist/generate/hono/package.js +44 -0
- package/dist/generate/hono/readme.d.ts +13 -0
- package/dist/generate/hono/readme.js +28 -0
- package/dist/generate/hono/repository.d.ts +1 -0
- package/dist/generate/hono/repository.js +27 -0
- package/dist/generate/hono/rest.d.ts +1 -0
- package/dist/generate/hono/rest.js +38 -0
- package/dist/generate/hono/schema.d.ts +13 -0
- package/dist/generate/hono/schema.js +18 -0
- package/dist/generate/hono/sqlite.d.ts +20 -0
- package/dist/generate/hono/sqlite.js +266 -0
- package/dist/generate/hono/validators.d.ts +1 -0
- package/dist/generate/hono/validators.js +141 -0
- package/dist/generate/hono.d.ts +1 -0
- package/dist/generate/hono.js +1 -0
- package/dist/graphql/execute.d.ts +14 -0
- package/dist/graphql/execute.js +719 -0
- package/dist/graphql/http.d.ts +15 -0
- package/dist/graphql/http.js +29 -0
- package/dist/graphql/index.d.ts +3 -0
- package/dist/graphql/index.js +3 -0
- package/dist/graphql/parser.d.ts +54 -0
- package/dist/graphql/parser.js +433 -0
- package/dist/hono.d.ts +77 -0
- package/dist/hono.js +1 -0
- package/dist/index.d.ts +1065 -0
- package/dist/index.js +14 -0
- package/dist/integrations/hono.d.ts +136 -0
- package/dist/integrations/hono.js +508 -0
- package/dist/integrations/kv.d.ts +69 -0
- package/dist/integrations/kv.js +69 -0
- package/dist/integrations/postgres.d.ts +52 -0
- package/dist/integrations/postgres.js +113 -0
- package/dist/integrations/sqlite.d.ts +112 -0
- package/dist/integrations/sqlite.js +489 -0
- package/dist/integrations/vite.d.ts +45 -0
- package/dist/integrations/vite.js +111 -0
- package/dist/json.d.ts +48 -0
- package/dist/json.js +1 -0
- package/dist/jsonc.d.ts +1 -0
- package/dist/jsonc.js +1 -0
- package/dist/kv.d.ts +24 -0
- package/dist/kv.js +1 -0
- package/dist/mock.d.ts +1 -0
- package/dist/mock.js +1 -0
- package/dist/names.d.ts +1 -0
- package/dist/names.js +1 -0
- package/dist/operations.d.ts +3 -0
- package/dist/operations.js +3 -0
- package/dist/postgres.d.ts +24 -0
- package/dist/postgres.js +1 -0
- package/dist/redis.d.ts +14 -0
- package/dist/redis.js +1 -0
- package/dist/rest/formats.d.ts +80 -0
- package/dist/rest/formats.js +318 -0
- package/dist/rest/handler.d.ts +111 -0
- package/dist/rest/handler.js +833 -0
- package/dist/rest/shape.d.ts +33 -0
- package/dist/rest/shape.js +218 -0
- package/dist/schema-builders.d.ts +1 -0
- package/dist/schema-builders.js +1 -0
- package/dist/schema-manifest.d.ts +1 -0
- package/dist/schema-manifest.js +1 -0
- package/dist/schema.d.ts +193 -0
- package/dist/schema.js +6 -0
- package/dist/server.d.ts +116 -0
- package/dist/server.js +601 -0
- package/dist/shared/csv.d.ts +8 -0
- package/dist/shared/csv.js +149 -0
- package/dist/shared/errors.d.ts +40 -0
- package/dist/shared/errors.js +55 -0
- package/dist/shared/fs-utils.d.ts +4 -0
- package/dist/shared/fs-utils.js +30 -0
- package/dist/shared/jsonc.d.ts +2 -0
- package/dist/shared/jsonc.js +99 -0
- package/dist/shared/mock.d.ts +40 -0
- package/dist/shared/mock.js +83 -0
- package/dist/shared/names.d.ts +28 -0
- package/dist/shared/names.js +127 -0
- package/dist/shared/operations.d.ts +32 -0
- package/dist/shared/operations.js +302 -0
- package/dist/sqlite.d.ts +24 -0
- package/dist/sqlite.js +1 -0
- package/dist/state.d.ts +1 -0
- package/dist/state.js +1 -0
- package/dist/sync.d.ts +1 -0
- package/dist/sync.js +1 -0
- package/dist/tracing.d.ts +95 -0
- package/dist/tracing.js +260 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +285 -0
- package/dist/viewer-manifest.d.ts +1 -0
- package/dist/viewer-manifest.js +1 -0
- package/dist/vite.d.ts +59 -0
- package/dist/vite.js +1 -0
- package/dist/web/json-viewer.d.ts +5 -0
- package/dist/web/json-viewer.js +176 -0
- package/dist/web/viewer.d.ts +12 -0
- package/dist/web/viewer.js +1015 -0
- package/docs/README.md +42 -0
- package/docs/architecture.md +112 -0
- package/docs/ci-and-release.md +177 -0
- package/docs/cms-storage-patterns.md +108 -0
- package/docs/concepts.md +141 -0
- package/docs/configuration.md +552 -0
- package/docs/fixtures-and-schemas.md +527 -0
- package/docs/fork-branch-workflows.md +108 -0
- package/docs/generated-files.md +174 -0
- package/docs/getting-started.md +165 -0
- package/docs/integrations.md +206 -0
- package/docs/json-production.md +120 -0
- package/docs/package-api.md +418 -0
- package/docs/prototype-to-production.md +378 -0
- package/docs/server-and-viewer.md +466 -0
- package/docs/store-graduation.md +120 -0
- package/docs/typescript-schema-sources.md +79 -0
- package/examples/advanced/README.md +55 -0
- package/examples/advanced/db/projects.schema.jsonc +44 -0
- package/examples/advanced/db/settings.jsonc +9 -0
- package/examples/advanced/db/users.json +23 -0
- package/examples/advanced/db/users.schema.mjs +31 -0
- package/examples/advanced/db.config.mjs +18 -0
- package/examples/advanced/example.json +5 -0
- package/examples/advanced/src/generated/db.types.d.ts +64 -0
- package/examples/basic/README.md +95 -0
- package/examples/basic/db/operations/get-user.jsonc +8 -0
- package/examples/basic/db/settings.json +7 -0
- package/examples/basic/db/users.schema.jsonc +36 -0
- package/examples/basic/db.config.mjs +68 -0
- package/examples/basic/example.json +5 -0
- package/examples/basic/src/generated/db.types.d.ts +39 -0
- package/examples/cms-json-publish/README.md +21 -0
- package/examples/cms-json-publish/db/navigation.json +7 -0
- package/examples/cms-json-publish/db/pages.json +18 -0
- package/examples/cms-json-publish/example.json +5 -0
- package/examples/cms-json-publish/src/cms.mjs +104 -0
- package/examples/computed-fields/README.md +93 -0
- package/examples/computed-fields/db/orders.schema.mjs +62 -0
- package/examples/computed-fields/db/posts.schema.mjs +59 -0
- package/examples/computed-fields/db/products.schema.mjs +39 -0
- package/examples/computed-fields/db/users.schema.mjs +43 -0
- package/examples/computed-fields/db.config.mjs +15 -0
- package/examples/computed-fields/example.json +5 -0
- package/examples/computed-fields/src/generated/db.types.d.ts +81 -0
- package/examples/content-collections/README.md +91 -0
- package/examples/content-collections/db/authors.json +12 -0
- package/examples/content-collections/db/authors.schema.mjs +20 -0
- package/examples/content-collections/db/blog/draft-roadmap.mdx +12 -0
- package/examples/content-collections/db/blog/index.schema.mjs +61 -0
- package/examples/content-collections/db/blog/launch-notes.mdx +15 -0
- package/examples/content-collections/db/docs/index.schema.mjs +32 -0
- package/examples/content-collections/db/docs/intro.mdx +11 -0
- package/examples/content-collections/db/docs/schema-workflow.mdx +10 -0
- package/examples/content-collections/db/site.schema.jsonc +21 -0
- package/examples/content-collections/db.config.mjs +26 -0
- package/examples/content-collections/example.json +5 -0
- package/examples/content-collections/src/content-preview.mjs +66 -0
- package/examples/content-collections/src/generated/db.types.d.ts +81 -0
- package/examples/csv/README.md +52 -0
- package/examples/csv/db/customers.csv +4 -0
- package/examples/csv/db.config.mjs +13 -0
- package/examples/csv/example.json +5 -0
- package/examples/data-first/README.md +54 -0
- package/examples/data-first/db/posts.json +16 -0
- package/examples/data-first/db/settings.json +8 -0
- package/examples/data-first/db/users.json +14 -0
- package/examples/data-first/db.config.mjs +13 -0
- package/examples/data-first/example.json +5 -0
- package/examples/diagnostics/README.md +55 -0
- package/examples/diagnostics/db/projects.schema.jsonc +27 -0
- package/examples/diagnostics/db/users.json +9 -0
- package/examples/diagnostics/db/users.schema.jsonc +23 -0
- package/examples/diagnostics/db.config.mjs +16 -0
- package/examples/diagnostics/example.json +5 -0
- package/examples/free-plan-upgrade/README.md +22 -0
- package/examples/free-plan-upgrade/db/appSettings.json +4 -0
- package/examples/free-plan-upgrade/db/projects.json +7 -0
- package/examples/free-plan-upgrade/example.json +5 -0
- package/examples/free-plan-upgrade/src/upgrade-tenant-to-paid.mjs +105 -0
- package/examples/hono-auth/README.md +74 -0
- package/examples/hono-auth/db/pages.schema.jsonc +44 -0
- package/examples/hono-auth/db/users.schema.jsonc +42 -0
- package/examples/hono-auth/db.config.mjs +17 -0
- package/examples/hono-auth/example.json +5 -0
- package/examples/hono-auth/package.json +14 -0
- package/examples/hono-auth/src/app.mjs +79 -0
- package/examples/hono-auth/src/server.mjs +13 -0
- package/examples/production-json/README.md +102 -0
- package/examples/production-json/db/appSettings.schema.jsonc +41 -0
- package/examples/production-json/db/featureFlags.schema.jsonc +84 -0
- package/examples/production-json/db/operations/get-control-plane.jsonc +6 -0
- package/examples/production-json/db/operations/get-feature-flag.jsonc +9 -0
- package/examples/production-json/db/operations/list-feature-flags.jsonc +8 -0
- package/examples/production-json/db/operations/read-public-settings.jsonc +8 -0
- package/examples/production-json/db.config.mjs +33 -0
- package/examples/production-json/example.json +5 -0
- package/examples/production-json/src/client-demo.mjs +28 -0
- package/examples/production-json/src/generated/db.types.d.ts +60 -0
- package/examples/relations/README.md +56 -0
- package/examples/relations/db/posts.schema.jsonc +46 -0
- package/examples/relations/db/users.schema.jsonc +34 -0
- package/examples/relations/db.config.mjs +13 -0
- package/examples/relations/example.json +5 -0
- package/examples/rest-client/README.md +54 -0
- package/examples/rest-client/db/settings.json +5 -0
- package/examples/rest-client/db/users.schema.jsonc +42 -0
- package/examples/rest-client/db.config.mjs +13 -0
- package/examples/rest-client/example.json +5 -0
- package/examples/rest-client/src/client-demo.mjs +24 -0
- package/examples/schema-first/README.md +55 -0
- package/examples/schema-first/db/auditEvents.schema.jsonc +24 -0
- package/examples/schema-first/db/settings.schema.jsonc +29 -0
- package/examples/schema-first/db/users.schema.jsonc +36 -0
- package/examples/schema-first/db.config.mjs +15 -0
- package/examples/schema-first/example.json +5 -0
- package/examples/schema-first/src/generated/db.types.d.ts +47 -0
- package/examples/schema-manifest/README.md +50 -0
- package/examples/schema-manifest/db/projects.schema.jsonc +48 -0
- package/examples/schema-manifest/db/users.schema.jsonc +35 -0
- package/examples/schema-manifest/db.config.mjs +41 -0
- package/examples/schema-manifest/example.json +5 -0
- package/examples/schema-manifest/src/generated/db.schema.json +130 -0
- package/examples/schema-manifest/src/generated/db.types.d.ts +50 -0
- package/examples/schema-ui/README.md +103 -0
- package/examples/schema-ui/db/pages.schema.jsonc +53 -0
- package/examples/schema-ui/db/users.schema.jsonc +30 -0
- package/examples/schema-ui/db.config.mjs +55 -0
- package/examples/schema-ui/example.json +5 -0
- package/examples/schema-ui/src/cms-ssr.mjs +276 -0
- package/examples/schema-ui/src/generated/db.schema.json +133 -0
- package/examples/schema-ui/src/generated/db.types.d.ts +46 -0
- package/examples/schema-ui/src/render-admin.mjs +175 -0
- package/examples/schema-ui/src/schema-ui-ssr-handler.mjs +149 -0
- package/examples/schema-ui/src/start-schema-ui-server.mjs +140 -0
- package/examples/standard-schema/README.md +55 -0
- package/examples/standard-schema/db/settings.schema.mjs +22 -0
- package/examples/standard-schema/db/users.schema.mjs +72 -0
- package/examples/standard-schema/example.json +5 -0
- package/package.json +108 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
import { camelCase, singularResourceName } from '../names.js';
|
|
2
|
+
import { resolveSelectedComputedFields } from '../features/runtime/fanout.js';
|
|
3
|
+
import { describeValue, graphqlError, dbError, listChoices } from '../errors.js';
|
|
4
|
+
import { parseGraphql, } from './parser.js';
|
|
5
|
+
export async function executeGraphql(db, request) {
|
|
6
|
+
if (Array.isArray(request)) {
|
|
7
|
+
return executeGraphqlBatch(db, request);
|
|
8
|
+
}
|
|
9
|
+
return executeGraphqlSingle(db, request);
|
|
10
|
+
}
|
|
11
|
+
export async function executeGraphqlBatch(db, requests) {
|
|
12
|
+
const results = [];
|
|
13
|
+
for (const request of requests) {
|
|
14
|
+
results.push(await executeGraphqlSingle(db, request));
|
|
15
|
+
}
|
|
16
|
+
return results;
|
|
17
|
+
}
|
|
18
|
+
async function executeGraphqlSingle(db, request) {
|
|
19
|
+
try {
|
|
20
|
+
const query = typeof request === 'string' ? request : request.query;
|
|
21
|
+
const variables = typeof request === 'string' ? {} : request.variables ?? {};
|
|
22
|
+
const operationName = typeof request === 'string' ? null : request.operationName ?? null;
|
|
23
|
+
if (!query || typeof query !== 'string') {
|
|
24
|
+
throw dbError('GRAPHQL_MISSING_QUERY', 'GraphQL request is missing a query string.', {
|
|
25
|
+
hint: 'Pass a raw query string or an object like { query: "{ users { id } }", variables: {} }.',
|
|
26
|
+
details: { receivedType: describeValue(request) },
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const document = parseGraphql(query);
|
|
30
|
+
const operation = selectOperation(document, operationName);
|
|
31
|
+
const data = await executeSelectionSet(db, operation.operation, operation.selectionSet, variables, {
|
|
32
|
+
fragments: document.fragments ?? {},
|
|
33
|
+
typeName: operation.operation === 'mutation' ? 'Mutation' : 'Query',
|
|
34
|
+
});
|
|
35
|
+
return { data };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return {
|
|
39
|
+
data: null,
|
|
40
|
+
errors: [
|
|
41
|
+
graphqlError(error),
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function selectOperation(document, operationName) {
|
|
47
|
+
const operations = document.operations?.length
|
|
48
|
+
? document.operations
|
|
49
|
+
: [{
|
|
50
|
+
kind: 'operation',
|
|
51
|
+
operation: document.operation,
|
|
52
|
+
name: document.name,
|
|
53
|
+
directives: [],
|
|
54
|
+
selectionSet: document.selectionSet,
|
|
55
|
+
}];
|
|
56
|
+
if (operationName) {
|
|
57
|
+
const operation = operations.find((candidate) => candidate.name === operationName);
|
|
58
|
+
if (!operation) {
|
|
59
|
+
throw dbError('GRAPHQL_UNKNOWN_OPERATION', `Unknown GraphQL operation "${operationName}".`, {
|
|
60
|
+
hint: `Use one of: ${listChoices(operations.map((operation) => operation.name).filter(Boolean))}.`,
|
|
61
|
+
details: {
|
|
62
|
+
operationName,
|
|
63
|
+
availableOperations: operations.map((operation) => operation.name).filter(Boolean),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return operation;
|
|
68
|
+
}
|
|
69
|
+
if (operations.length === 1) {
|
|
70
|
+
return operations[0];
|
|
71
|
+
}
|
|
72
|
+
throw dbError('GRAPHQL_OPERATION_NAME_REQUIRED', 'GraphQL operationName is required when a document contains multiple operations.', {
|
|
73
|
+
hint: `Pass operationName with one of: ${listChoices(operations.map((operation) => operation.name).filter(Boolean))}.`,
|
|
74
|
+
details: {
|
|
75
|
+
availableOperations: operations.map((operation) => operation.name).filter(Boolean),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async function executeSelectionSet(db, operation, selections, variables, context) {
|
|
80
|
+
const data = {};
|
|
81
|
+
for (const selection of selections) {
|
|
82
|
+
await executeRootSelection(db, operation, selection, variables, context, data);
|
|
83
|
+
}
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
async function executeRootSelection(db, operation, selection, variables, context, data) {
|
|
87
|
+
if (!shouldIncludeSelection(selection, variables)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (selection.kind === 'fragment_spread') {
|
|
91
|
+
const fragment = requireFragment(context.fragments, selection.name);
|
|
92
|
+
if (typeConditionApplies(fragment.typeCondition, context.typeName) && shouldIncludeSelection(fragment, variables)) {
|
|
93
|
+
for (const fragmentSelection of fragment.selectionSet) {
|
|
94
|
+
await executeRootSelection(db, operation, fragmentSelection, variables, context, data);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (selection.kind === 'inline_fragment') {
|
|
100
|
+
if (typeConditionApplies(selection.typeCondition, context.typeName)) {
|
|
101
|
+
for (const fragmentSelection of selection.selectionSet) {
|
|
102
|
+
await executeRootSelection(db, operation, fragmentSelection, variables, context, data);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const key = responseKey(selection);
|
|
108
|
+
if (selection.name === '__typename') {
|
|
109
|
+
data[key] = context.typeName;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const result = operation === 'mutation'
|
|
113
|
+
? await executeMutationField(db, selection, variables)
|
|
114
|
+
: await executeQueryField(db, selection, variables);
|
|
115
|
+
data[key] = await projectValue(result.value, selection.selectionSet, {
|
|
116
|
+
...context,
|
|
117
|
+
db,
|
|
118
|
+
resource: result.resource ?? null,
|
|
119
|
+
variables,
|
|
120
|
+
typeName: result.typeName,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async function executeQueryField(db, selection, variables) {
|
|
124
|
+
if (selection.name === '__schema') {
|
|
125
|
+
return {
|
|
126
|
+
value: introspectionSchema(db),
|
|
127
|
+
typeName: '__Schema',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (selection.name === '__type') {
|
|
131
|
+
return {
|
|
132
|
+
value: introspectionType(db, readArgument(selection, 'name', variables)),
|
|
133
|
+
typeName: '__Type',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const resource = findQueryResource(db, selection.name);
|
|
137
|
+
if (!resource) {
|
|
138
|
+
throw dbError('GRAPHQL_UNKNOWN_QUERY_FIELD', `Unknown GraphQL query field "${selection.name}".`, {
|
|
139
|
+
hint: `Use one of: ${listChoices(availableQueryFields(db))}.`,
|
|
140
|
+
details: {
|
|
141
|
+
field: selection.name,
|
|
142
|
+
availableFields: availableQueryFields(db),
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (resource.kind === 'document') {
|
|
147
|
+
return {
|
|
148
|
+
value: await db.document(resource.name).all(),
|
|
149
|
+
typeName: resource.typeName,
|
|
150
|
+
resource,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (selection.name === collectionRootName(resource)) {
|
|
154
|
+
return {
|
|
155
|
+
value: await db.collection(resource.name).all(),
|
|
156
|
+
typeName: resource.typeName,
|
|
157
|
+
resource,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const id = readRequiredIdArgument(selection, variables);
|
|
161
|
+
return {
|
|
162
|
+
value: await db.collection(resource.name).get(id),
|
|
163
|
+
typeName: resource.typeName,
|
|
164
|
+
resource,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function executeMutationField(db, selection, variables) {
|
|
168
|
+
const mutation = parseMutationName(db, selection.name);
|
|
169
|
+
if (!mutation) {
|
|
170
|
+
throw dbError('GRAPHQL_UNKNOWN_MUTATION_FIELD', `Unknown GraphQL mutation field "${selection.name}".`, {
|
|
171
|
+
hint: `Use one of: ${listChoices(availableMutationFields(db))}.`,
|
|
172
|
+
details: {
|
|
173
|
+
field: selection.name,
|
|
174
|
+
availableFields: availableMutationFields(db),
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (mutation.resource.kind === 'collection') {
|
|
179
|
+
return {
|
|
180
|
+
value: await executeCollectionMutation(db, mutation, selection, variables),
|
|
181
|
+
typeName: mutation.action === 'delete' ? 'Boolean' : mutation.resource.typeName,
|
|
182
|
+
resource: mutation.action === 'delete' ? null : mutation.resource,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
value: await executeDocumentMutation(db, mutation, selection, variables),
|
|
187
|
+
typeName: mutation.resource.typeName,
|
|
188
|
+
resource: mutation.resource,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async function executeCollectionMutation(db, mutation, selection, variables) {
|
|
192
|
+
const collection = db.collection(mutation.resource.name);
|
|
193
|
+
if (mutation.action === 'create') {
|
|
194
|
+
const input = readArgument(selection, 'input', variables);
|
|
195
|
+
if (!isObject(input)) {
|
|
196
|
+
throw argumentTypeError(selection.name, 'input', 'object', input);
|
|
197
|
+
}
|
|
198
|
+
return collection.create(input);
|
|
199
|
+
}
|
|
200
|
+
if (mutation.action === 'update') {
|
|
201
|
+
const id = readRequiredIdArgument(selection, variables);
|
|
202
|
+
const patch = readArgument(selection, 'patch', variables);
|
|
203
|
+
if (!isObject(patch)) {
|
|
204
|
+
throw argumentTypeError(selection.name, 'patch', 'object', patch);
|
|
205
|
+
}
|
|
206
|
+
return collection.patch(id, patch);
|
|
207
|
+
}
|
|
208
|
+
if (mutation.action === 'delete') {
|
|
209
|
+
const id = readRequiredIdArgument(selection, variables);
|
|
210
|
+
return collection.delete(id);
|
|
211
|
+
}
|
|
212
|
+
throw dbError('GRAPHQL_UNSUPPORTED_MUTATION', `Unsupported GraphQL collection mutation "${selection.name}".`);
|
|
213
|
+
}
|
|
214
|
+
async function executeDocumentMutation(db, mutation, selection, variables) {
|
|
215
|
+
const document = db.document(mutation.resource.name);
|
|
216
|
+
if (mutation.action === 'update') {
|
|
217
|
+
const patch = readArgument(selection, 'patch', variables);
|
|
218
|
+
if (!isObject(patch)) {
|
|
219
|
+
throw argumentTypeError(selection.name, 'patch', 'object', patch);
|
|
220
|
+
}
|
|
221
|
+
return document.update(patch);
|
|
222
|
+
}
|
|
223
|
+
if (mutation.action === 'set') {
|
|
224
|
+
const path = readArgument(selection, 'path', variables);
|
|
225
|
+
const value = readArgument(selection, 'value', variables);
|
|
226
|
+
await document.set(path, value);
|
|
227
|
+
return document.all();
|
|
228
|
+
}
|
|
229
|
+
throw dbError('GRAPHQL_UNSUPPORTED_MUTATION', `Unsupported GraphQL document mutation "${selection.name}".`);
|
|
230
|
+
}
|
|
231
|
+
function findQueryResource(db, fieldName) {
|
|
232
|
+
return [...db.resources.values()].find((resource) => {
|
|
233
|
+
if (resource.kind === 'document') {
|
|
234
|
+
return resource.name === fieldName;
|
|
235
|
+
}
|
|
236
|
+
return collectionRootName(resource) === fieldName || singleRootName(resource) === fieldName;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function parseMutationName(db, fieldName) {
|
|
240
|
+
for (const resource of db.resources.values()) {
|
|
241
|
+
const typeName = resource.typeName;
|
|
242
|
+
for (const action of mutationActions(resource)) {
|
|
243
|
+
if (fieldName === `${action}${typeName}`) {
|
|
244
|
+
return { action, resource };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
function mutationActions(resource) {
|
|
251
|
+
return resource.kind === 'collection'
|
|
252
|
+
? ['create', 'update', 'delete']
|
|
253
|
+
: ['update', 'set'];
|
|
254
|
+
}
|
|
255
|
+
async function projectValue(value, selectionSet, context = {}) {
|
|
256
|
+
if (!selectionSet || value === null || value === undefined) {
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(value)) {
|
|
260
|
+
const records = context.resource && !context.computedResolved
|
|
261
|
+
? await resolveComputedSelection(value, selectionSet, context)
|
|
262
|
+
: value;
|
|
263
|
+
return Promise.all(records.map((item) => projectValue(item, selectionSet, {
|
|
264
|
+
...context,
|
|
265
|
+
computedResolved: true,
|
|
266
|
+
})));
|
|
267
|
+
}
|
|
268
|
+
if (!isObject(value)) {
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
271
|
+
const objectValue = context.resource && !context.computedResolved
|
|
272
|
+
? (await resolveComputedSelection([value], selectionSet, context))[0]
|
|
273
|
+
: value;
|
|
274
|
+
const projected = {};
|
|
275
|
+
for (const selection of selectionSet) {
|
|
276
|
+
await projectObjectSelection(objectValue, selection, projected, context);
|
|
277
|
+
}
|
|
278
|
+
return projected;
|
|
279
|
+
}
|
|
280
|
+
async function projectObjectSelection(value, selection, projected, context) {
|
|
281
|
+
if (!shouldIncludeSelection(selection, context.variables ?? {})) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (selection.kind === 'fragment_spread') {
|
|
285
|
+
const fragment = requireFragment(context.fragments ?? {}, selection.name);
|
|
286
|
+
if (typeConditionApplies(fragment.typeCondition, context.typeName) && shouldIncludeSelection(fragment, context.variables ?? {})) {
|
|
287
|
+
for (const fragmentSelection of fragment.selectionSet) {
|
|
288
|
+
await projectObjectSelection(value, fragmentSelection, projected, context);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (selection.kind === 'inline_fragment') {
|
|
294
|
+
if (typeConditionApplies(selection.typeCondition, context.typeName)) {
|
|
295
|
+
for (const fragmentSelection of selection.selectionSet) {
|
|
296
|
+
await projectObjectSelection(value, fragmentSelection, projected, context);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const key = responseKey(selection);
|
|
302
|
+
if (selection.name === '__typename') {
|
|
303
|
+
projected[key] = context.typeName ?? 'JSON';
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
projected[key] = await projectValue(value[selection.name], selection.selectionSet, {
|
|
307
|
+
...context,
|
|
308
|
+
resource: null,
|
|
309
|
+
typeName: null,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async function resolveComputedSelection(records, selectionSet, context) {
|
|
313
|
+
const fieldNames = selectedComputedFieldNames(selectionSet, context);
|
|
314
|
+
return resolveSelectedComputedFields(context.db, context.resource, records, fieldNames, {
|
|
315
|
+
cache: context.cache ?? new Map(),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
function selectedComputedFieldNames(selections, context) {
|
|
319
|
+
const names = [];
|
|
320
|
+
for (const selection of selections ?? []) {
|
|
321
|
+
if (!shouldIncludeSelection(selection, context.variables ?? {})) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (selection.kind === 'fragment_spread') {
|
|
325
|
+
const fragment = requireFragment(context.fragments ?? {}, selection.name);
|
|
326
|
+
if (typeConditionApplies(fragment.typeCondition, context.typeName) && shouldIncludeSelection(fragment, context.variables ?? {})) {
|
|
327
|
+
names.push(...selectedComputedFieldNames(fragment.selectionSet, context));
|
|
328
|
+
}
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (selection.kind === 'inline_fragment') {
|
|
332
|
+
if (typeConditionApplies(selection.typeCondition, context.typeName)) {
|
|
333
|
+
names.push(...selectedComputedFieldNames(selection.selectionSet, context));
|
|
334
|
+
}
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
if (selection.name !== '__typename' && context.resource?.fields?.[selection.name]?.computed) {
|
|
338
|
+
names.push(selection.name);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return names;
|
|
342
|
+
}
|
|
343
|
+
function shouldIncludeSelection(selection, variables) {
|
|
344
|
+
for (const directive of selection.directives ?? []) {
|
|
345
|
+
const condition = directiveCondition(directive, variables);
|
|
346
|
+
if (directive.name === 'skip') {
|
|
347
|
+
if (condition === true) {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (directive.name === 'include') {
|
|
353
|
+
if (condition === false) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
throw dbError('GRAPHQL_UNSUPPORTED_DIRECTIVE', `Unsupported GraphQL directive "@${directive.name}".`, {
|
|
359
|
+
hint: 'db supports @include(if: Boolean) and @skip(if: Boolean) executable directives.',
|
|
360
|
+
details: {
|
|
361
|
+
directive: directive.name,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
function directiveCondition(directive, variables) {
|
|
368
|
+
if (!('if' in directive.arguments)) {
|
|
369
|
+
throw dbError('GRAPHQL_DIRECTIVE_MISSING_IF', `GraphQL directive "@${directive.name}" requires argument "if".`, {
|
|
370
|
+
hint: `Use @${directive.name}(if: true) or pass a Boolean variable.`,
|
|
371
|
+
details: {
|
|
372
|
+
directive: directive.name,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const value = evaluateValue(directive.arguments.if, variables);
|
|
377
|
+
if (typeof value !== 'boolean') {
|
|
378
|
+
throw dbError('GRAPHQL_DIRECTIVE_INVALID_IF', `GraphQL directive "@${directive.name}" requires Boolean argument "if", but received ${describeValue(value)}.`, {
|
|
379
|
+
hint: `Pass true, false, or a Boolean variable to @${directive.name}(if: ...).`,
|
|
380
|
+
details: {
|
|
381
|
+
directive: directive.name,
|
|
382
|
+
received: describeValue(value),
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
return value;
|
|
387
|
+
}
|
|
388
|
+
function requireFragment(fragments, name) {
|
|
389
|
+
const fragment = fragments[name];
|
|
390
|
+
if (!fragment) {
|
|
391
|
+
throw dbError('GRAPHQL_UNKNOWN_FRAGMENT', `Unknown GraphQL fragment "${name}".`, {
|
|
392
|
+
hint: `Define fragment ${name} on TypeName { ... } in the same GraphQL document.`,
|
|
393
|
+
details: {
|
|
394
|
+
fragment: name,
|
|
395
|
+
availableFragments: Object.keys(fragments),
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
return fragment;
|
|
400
|
+
}
|
|
401
|
+
function typeConditionApplies(typeCondition, typeName) {
|
|
402
|
+
return !typeCondition || !typeName || typeCondition === typeName;
|
|
403
|
+
}
|
|
404
|
+
function readArgument(selection, name, variables) {
|
|
405
|
+
if (!(name in selection.arguments)) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return evaluateValue(selection.arguments[name], variables);
|
|
409
|
+
}
|
|
410
|
+
function readRequiredIdArgument(selection, variables) {
|
|
411
|
+
const id = readArgument(selection, 'id', variables);
|
|
412
|
+
if (id === undefined || id === null || id === '') {
|
|
413
|
+
throw dbError('GRAPHQL_MISSING_ID_ARGUMENT', `GraphQL field "${selection.name}" requires argument "id".`, {
|
|
414
|
+
hint: `Use ${selection.name}(id: "example-id") { id } or pass a variable such as ${selection.name}(id: $id).`,
|
|
415
|
+
details: { field: selection.name, argument: 'id' },
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return id;
|
|
419
|
+
}
|
|
420
|
+
function evaluateValue(valueNode, variables) {
|
|
421
|
+
switch (valueNode.kind) {
|
|
422
|
+
case 'variable':
|
|
423
|
+
if (!(valueNode.name in variables)) {
|
|
424
|
+
throw dbError('GRAPHQL_MISSING_VARIABLE', `GraphQL variable "$${valueNode.name}" was referenced but not provided.`, {
|
|
425
|
+
hint: `Add "${valueNode.name}" to the variables object for this request.`,
|
|
426
|
+
details: {
|
|
427
|
+
variable: valueNode.name,
|
|
428
|
+
providedVariables: Object.keys(variables),
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return variables[valueNode.name];
|
|
433
|
+
case 'list':
|
|
434
|
+
return valueNode.values.map((value) => evaluateValue(value, variables));
|
|
435
|
+
case 'object':
|
|
436
|
+
return Object.fromEntries(Object.entries(valueNode.fields).map(([name, value]) => [name, evaluateValue(value, variables)]));
|
|
437
|
+
case 'literal':
|
|
438
|
+
default:
|
|
439
|
+
return valueNode.value;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function collectionRootName(resource) {
|
|
443
|
+
return resource.name;
|
|
444
|
+
}
|
|
445
|
+
function singleRootName(resource) {
|
|
446
|
+
return camelCase(singularResourceName(resource.name));
|
|
447
|
+
}
|
|
448
|
+
function responseKey(selection) {
|
|
449
|
+
return selection.alias ?? selection.name;
|
|
450
|
+
}
|
|
451
|
+
function isObject(value) {
|
|
452
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
453
|
+
}
|
|
454
|
+
function argumentTypeError(field, argument, expected, actual) {
|
|
455
|
+
return dbError('GRAPHQL_INVALID_ARGUMENT_TYPE', `GraphQL mutation "${field}" requires ${expected} argument "${argument}", but received ${describeValue(actual)}.`, {
|
|
456
|
+
hint: `Pass ${field}(${argument}: { ... }) or provide a variable whose value is an object.`,
|
|
457
|
+
details: {
|
|
458
|
+
field,
|
|
459
|
+
argument,
|
|
460
|
+
expected,
|
|
461
|
+
received: describeValue(actual),
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
function introspectionSchema(db) {
|
|
466
|
+
return {
|
|
467
|
+
queryType: namedIntrospectionType('Query'),
|
|
468
|
+
mutationType: namedIntrospectionType('Mutation'),
|
|
469
|
+
subscriptionType: null,
|
|
470
|
+
types: introspectionTypes(db),
|
|
471
|
+
directives: [
|
|
472
|
+
{
|
|
473
|
+
name: 'include',
|
|
474
|
+
description: 'Includes this field or fragment only when the if argument is true.',
|
|
475
|
+
locations: ['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT'],
|
|
476
|
+
args: [
|
|
477
|
+
{
|
|
478
|
+
name: 'if',
|
|
479
|
+
description: null,
|
|
480
|
+
type: nonNullType(scalarType('Boolean')),
|
|
481
|
+
defaultValue: null,
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: 'skip',
|
|
487
|
+
description: 'Skips this field or fragment when the if argument is true.',
|
|
488
|
+
locations: ['FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT'],
|
|
489
|
+
args: [
|
|
490
|
+
{
|
|
491
|
+
name: 'if',
|
|
492
|
+
description: null,
|
|
493
|
+
type: nonNullType(scalarType('Boolean')),
|
|
494
|
+
defaultValue: null,
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
},
|
|
498
|
+
],
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function introspectionType(db, name) {
|
|
502
|
+
if (!name) {
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
return introspectionTypes(db).find((type) => type.name === name) ?? null;
|
|
506
|
+
}
|
|
507
|
+
function introspectionTypes(db) {
|
|
508
|
+
const resources = [...db.resources.values()];
|
|
509
|
+
return [
|
|
510
|
+
rootOperationType('Query', queryIntrospectionFields(db)),
|
|
511
|
+
rootOperationType('Mutation', mutationIntrospectionFields(db)),
|
|
512
|
+
scalarIntrospectionType('ID'),
|
|
513
|
+
scalarIntrospectionType('String'),
|
|
514
|
+
scalarIntrospectionType('Float'),
|
|
515
|
+
scalarIntrospectionType('Boolean'),
|
|
516
|
+
scalarIntrospectionType('JSON'),
|
|
517
|
+
...resources.map(resourceIntrospectionType),
|
|
518
|
+
rootOperationType('__Schema', [
|
|
519
|
+
fieldIntrospection('queryType', namedIntrospectionType('__Type')),
|
|
520
|
+
fieldIntrospection('mutationType', namedIntrospectionType('__Type')),
|
|
521
|
+
fieldIntrospection('subscriptionType', namedIntrospectionType('__Type')),
|
|
522
|
+
fieldIntrospection('types', listType(namedIntrospectionType('__Type'))),
|
|
523
|
+
fieldIntrospection('directives', listType(namedIntrospectionType('__Directive'))),
|
|
524
|
+
]),
|
|
525
|
+
rootOperationType('__Type', [
|
|
526
|
+
fieldIntrospection('kind', scalarType('String')),
|
|
527
|
+
fieldIntrospection('name', scalarType('String')),
|
|
528
|
+
fieldIntrospection('description', scalarType('String')),
|
|
529
|
+
fieldIntrospection('fields', listType(namedIntrospectionType('__Field'))),
|
|
530
|
+
fieldIntrospection('inputFields', listType(namedIntrospectionType('__InputValue'))),
|
|
531
|
+
fieldIntrospection('interfaces', listType(namedIntrospectionType('__Type'))),
|
|
532
|
+
fieldIntrospection('enumValues', listType(namedIntrospectionType('__EnumValue'))),
|
|
533
|
+
fieldIntrospection('possibleTypes', listType(namedIntrospectionType('__Type'))),
|
|
534
|
+
fieldIntrospection('ofType', namedIntrospectionType('__Type')),
|
|
535
|
+
]),
|
|
536
|
+
rootOperationType('__Field', [
|
|
537
|
+
fieldIntrospection('name', scalarType('String')),
|
|
538
|
+
fieldIntrospection('description', scalarType('String')),
|
|
539
|
+
fieldIntrospection('args', listType(namedIntrospectionType('__InputValue'))),
|
|
540
|
+
fieldIntrospection('type', namedIntrospectionType('__Type')),
|
|
541
|
+
fieldIntrospection('isDeprecated', scalarType('Boolean')),
|
|
542
|
+
fieldIntrospection('deprecationReason', scalarType('String')),
|
|
543
|
+
]),
|
|
544
|
+
rootOperationType('__InputValue', [
|
|
545
|
+
fieldIntrospection('name', scalarType('String')),
|
|
546
|
+
fieldIntrospection('description', scalarType('String')),
|
|
547
|
+
fieldIntrospection('type', namedIntrospectionType('__Type')),
|
|
548
|
+
fieldIntrospection('defaultValue', scalarType('String')),
|
|
549
|
+
]),
|
|
550
|
+
rootOperationType('__EnumValue', [
|
|
551
|
+
fieldIntrospection('name', scalarType('String')),
|
|
552
|
+
fieldIntrospection('description', scalarType('String')),
|
|
553
|
+
fieldIntrospection('isDeprecated', scalarType('Boolean')),
|
|
554
|
+
fieldIntrospection('deprecationReason', scalarType('String')),
|
|
555
|
+
]),
|
|
556
|
+
rootOperationType('__Directive', [
|
|
557
|
+
fieldIntrospection('name', scalarType('String')),
|
|
558
|
+
fieldIntrospection('description', scalarType('String')),
|
|
559
|
+
fieldIntrospection('locations', listType(scalarType('String'))),
|
|
560
|
+
fieldIntrospection('args', listType(namedIntrospectionType('__InputValue'))),
|
|
561
|
+
]),
|
|
562
|
+
];
|
|
563
|
+
}
|
|
564
|
+
function resourceIntrospectionType(resource) {
|
|
565
|
+
return {
|
|
566
|
+
kind: 'OBJECT',
|
|
567
|
+
name: resource.typeName,
|
|
568
|
+
description: resource.description ?? null,
|
|
569
|
+
fields: Object.entries((resource.fields ?? {})).map(([fieldName, field]) => ({
|
|
570
|
+
name: fieldName,
|
|
571
|
+
description: field.description ?? null,
|
|
572
|
+
args: [],
|
|
573
|
+
type: introspectionFieldType(field, fieldName === resource.idField),
|
|
574
|
+
isDeprecated: false,
|
|
575
|
+
deprecationReason: null,
|
|
576
|
+
})),
|
|
577
|
+
inputFields: null,
|
|
578
|
+
interfaces: [],
|
|
579
|
+
enumValues: null,
|
|
580
|
+
possibleTypes: null,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function rootOperationType(name, fields) {
|
|
584
|
+
return {
|
|
585
|
+
kind: 'OBJECT',
|
|
586
|
+
name,
|
|
587
|
+
description: null,
|
|
588
|
+
fields,
|
|
589
|
+
inputFields: null,
|
|
590
|
+
interfaces: [],
|
|
591
|
+
enumValues: null,
|
|
592
|
+
possibleTypes: null,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
function scalarIntrospectionType(name) {
|
|
596
|
+
return {
|
|
597
|
+
kind: 'SCALAR',
|
|
598
|
+
name,
|
|
599
|
+
description: null,
|
|
600
|
+
fields: null,
|
|
601
|
+
inputFields: null,
|
|
602
|
+
interfaces: null,
|
|
603
|
+
enumValues: null,
|
|
604
|
+
possibleTypes: null,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function queryIntrospectionFields(db) {
|
|
608
|
+
return [
|
|
609
|
+
fieldIntrospection('__schema', namedIntrospectionType('__Schema')),
|
|
610
|
+
fieldIntrospection('__type', namedIntrospectionType('__Type'), [
|
|
611
|
+
{
|
|
612
|
+
name: 'name',
|
|
613
|
+
description: null,
|
|
614
|
+
type: nonNullType(scalarType('String')),
|
|
615
|
+
defaultValue: null,
|
|
616
|
+
},
|
|
617
|
+
]),
|
|
618
|
+
...[...db.resources.values()].flatMap((resource) => {
|
|
619
|
+
if (resource.kind === 'document') {
|
|
620
|
+
return [fieldIntrospection(resource.name, namedIntrospectionType(resource.typeName))];
|
|
621
|
+
}
|
|
622
|
+
return [
|
|
623
|
+
fieldIntrospection(collectionRootName(resource), listType(namedIntrospectionType(resource.typeName))),
|
|
624
|
+
fieldIntrospection(singleRootName(resource), namedIntrospectionType(resource.typeName), [
|
|
625
|
+
{
|
|
626
|
+
name: 'id',
|
|
627
|
+
description: null,
|
|
628
|
+
type: nonNullType(scalarType('ID')),
|
|
629
|
+
defaultValue: null,
|
|
630
|
+
},
|
|
631
|
+
]),
|
|
632
|
+
];
|
|
633
|
+
}),
|
|
634
|
+
];
|
|
635
|
+
}
|
|
636
|
+
function mutationIntrospectionFields(db) {
|
|
637
|
+
return [...db.resources.values()].flatMap((resource) => mutationActions(resource).map((action) => {
|
|
638
|
+
const type = action === 'delete' ? scalarType('Boolean') : namedIntrospectionType(resource.typeName);
|
|
639
|
+
return fieldIntrospection(`${action}${resource.typeName}`, type);
|
|
640
|
+
}));
|
|
641
|
+
}
|
|
642
|
+
function fieldIntrospection(name, type, args = []) {
|
|
643
|
+
return {
|
|
644
|
+
name,
|
|
645
|
+
description: null,
|
|
646
|
+
args,
|
|
647
|
+
type,
|
|
648
|
+
isDeprecated: false,
|
|
649
|
+
deprecationReason: null,
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function introspectionFieldType(field, isIdField = false) {
|
|
653
|
+
const base = isIdField ? scalarType('ID') : scalarType(scalarNameForField(field));
|
|
654
|
+
const type = field.type === 'array'
|
|
655
|
+
? listType(base)
|
|
656
|
+
: base;
|
|
657
|
+
return field.required ? nonNullType(type) : type;
|
|
658
|
+
}
|
|
659
|
+
function scalarNameForField(field) {
|
|
660
|
+
switch (field.type) {
|
|
661
|
+
case 'number':
|
|
662
|
+
return 'Float';
|
|
663
|
+
case 'boolean':
|
|
664
|
+
return 'Boolean';
|
|
665
|
+
case 'string':
|
|
666
|
+
case 'datetime':
|
|
667
|
+
case 'enum':
|
|
668
|
+
return 'String';
|
|
669
|
+
case 'array':
|
|
670
|
+
case 'object':
|
|
671
|
+
case 'unknown':
|
|
672
|
+
default:
|
|
673
|
+
return 'JSON';
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function scalarType(name) {
|
|
677
|
+
return {
|
|
678
|
+
kind: 'SCALAR',
|
|
679
|
+
name,
|
|
680
|
+
ofType: null,
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function namedIntrospectionType(name) {
|
|
684
|
+
return {
|
|
685
|
+
kind: 'OBJECT',
|
|
686
|
+
name,
|
|
687
|
+
ofType: null,
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function listType(ofType) {
|
|
691
|
+
return {
|
|
692
|
+
kind: 'LIST',
|
|
693
|
+
name: null,
|
|
694
|
+
ofType,
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
function nonNullType(ofType) {
|
|
698
|
+
return {
|
|
699
|
+
kind: 'NON_NULL',
|
|
700
|
+
name: null,
|
|
701
|
+
ofType,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function availableQueryFields(db) {
|
|
705
|
+
return [...db.resources.values()].flatMap((resource) => {
|
|
706
|
+
if (resource.kind === 'document') {
|
|
707
|
+
return [resource.name];
|
|
708
|
+
}
|
|
709
|
+
return [collectionRootName(resource), singleRootName(resource)];
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
function availableMutationFields(db) {
|
|
713
|
+
return [...db.resources.values()].flatMap((resource) => {
|
|
714
|
+
const actions = resource.kind === 'collection'
|
|
715
|
+
? ['create', 'update', 'delete']
|
|
716
|
+
: ['update', 'set'];
|
|
717
|
+
return actions.map((action) => `${action}${resource.typeName}`);
|
|
718
|
+
});
|
|
719
|
+
}
|