@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,689 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, readdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { parseFixturePath, resourceNameFromPath } from '../../config-public.js';
|
|
6
|
+
import { parseCsvRecords } from '../../csv.js';
|
|
7
|
+
import { parseJsonc } from '../../jsonc.js';
|
|
8
|
+
import { routePathForResource, typeNameForResource } from '../../names.js';
|
|
9
|
+
export async function listSourceFiles(sourceDirOrConfig) {
|
|
10
|
+
const { sourceDir, ignoredDirs } = sourceFileListOptions(sourceDirOrConfig);
|
|
11
|
+
try {
|
|
12
|
+
return await listSourceFilesInDirectory(sourceDir, '', ignoredDirs);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.code === 'ENOENT') {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
throw error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function readRootSchemaFile(config) {
|
|
22
|
+
if (config.schema?.source === 'data') {
|
|
23
|
+
return {
|
|
24
|
+
sources: [],
|
|
25
|
+
diagnostics: [],
|
|
26
|
+
found: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const locator = config.schemaLocator;
|
|
30
|
+
if (locator && locator.mode !== 'project' && locator.mode !== 'root-schema') {
|
|
31
|
+
return {
|
|
32
|
+
sources: [],
|
|
33
|
+
diagnostics: [],
|
|
34
|
+
found: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const { sourceFile, diagnostics: rootDiagnostics } = await rootSchemaFile(config, locator);
|
|
38
|
+
if (!sourceFile) {
|
|
39
|
+
return {
|
|
40
|
+
sources: [],
|
|
41
|
+
diagnostics: rootDiagnostics,
|
|
42
|
+
found: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
let buffer;
|
|
46
|
+
try {
|
|
47
|
+
buffer = await readFile(sourceFile);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error.code === 'ENOENT') {
|
|
51
|
+
return {
|
|
52
|
+
sources: [],
|
|
53
|
+
diagnostics: rootDiagnostics,
|
|
54
|
+
found: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
sources: [],
|
|
59
|
+
diagnostics: [
|
|
60
|
+
...rootDiagnostics,
|
|
61
|
+
sourceLoadDiagnostic(error, sourceFile, 'dbSchema', config, {
|
|
62
|
+
readerName: rootSchemaReaderName(sourceFile),
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
found: true,
|
|
66
|
+
file: sourceFile,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await ensureSchemaJsModuleContext(config, sourceFile, { autoPackageJson: false });
|
|
71
|
+
const url = pathToFileURL(sourceFile);
|
|
72
|
+
url.searchParams.set('dbRootSchemaLoad', String(Date.now()));
|
|
73
|
+
const module = await import(url.href);
|
|
74
|
+
const exported = module.default ?? module.schema ?? {};
|
|
75
|
+
const hash = createHash('sha256').update(buffer).digest('hex');
|
|
76
|
+
return {
|
|
77
|
+
sources: rootSchemaSources(config, exported, sourceFile, hash),
|
|
78
|
+
diagnostics: rootDiagnostics,
|
|
79
|
+
found: true,
|
|
80
|
+
file: sourceFile,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
sources: [],
|
|
86
|
+
diagnostics: [
|
|
87
|
+
...rootDiagnostics,
|
|
88
|
+
sourceLoadDiagnostic(error, sourceFile, 'dbSchema', config, {
|
|
89
|
+
readerName: rootSchemaReaderName(sourceFile),
|
|
90
|
+
}),
|
|
91
|
+
],
|
|
92
|
+
found: true,
|
|
93
|
+
file: sourceFile,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function rootSchemaFile(config, locator) {
|
|
98
|
+
if (locator?.mode === 'root-schema') {
|
|
99
|
+
return {
|
|
100
|
+
sourceFile: locator.file,
|
|
101
|
+
diagnostics: [],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
const mjsFile = path.join(config.cwd, 'db.schema.mjs');
|
|
105
|
+
const jsFile = path.join(config.cwd, 'db.schema.js');
|
|
106
|
+
const mjsExists = await fileExists(mjsFile);
|
|
107
|
+
const jsExists = await fileExists(jsFile);
|
|
108
|
+
if (mjsExists) {
|
|
109
|
+
return {
|
|
110
|
+
sourceFile: mjsFile,
|
|
111
|
+
diagnostics: jsExists ? [duplicateRootSchemaDiagnostic(config, mjsFile, jsFile)] : [],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (jsExists) {
|
|
115
|
+
return {
|
|
116
|
+
sourceFile: jsFile,
|
|
117
|
+
diagnostics: [],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
sourceFile: null,
|
|
122
|
+
diagnostics: [],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function fileExists(file) {
|
|
126
|
+
try {
|
|
127
|
+
await readFile(file);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error.code === 'ENOENT') {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function duplicateRootSchemaDiagnostic(config, preferredFile, ignoredFile) {
|
|
138
|
+
const preferred = path.relative(config.cwd, preferredFile).split(path.sep).join('/');
|
|
139
|
+
const ignored = path.relative(config.cwd, ignoredFile).split(path.sep).join('/');
|
|
140
|
+
return {
|
|
141
|
+
code: 'ROOT_SCHEMA_DUPLICATE_IGNORED',
|
|
142
|
+
severity: 'warn',
|
|
143
|
+
resource: 'dbSchema',
|
|
144
|
+
file: ignored,
|
|
145
|
+
message: `Both ${preferred} and ${ignored} exist; ${preferred} is authoritative and ${ignored} was ignored.`,
|
|
146
|
+
hint: 'Remove the ignored root schema file or pass from: "./db.schema.js" to load it explicitly.',
|
|
147
|
+
details: {
|
|
148
|
+
preferred,
|
|
149
|
+
ignored,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function rootSchemaReaderName(sourceFile) {
|
|
154
|
+
return sourceFile.endsWith('.js') ? 'db:root-schema-js' : 'db:root-schema-mjs';
|
|
155
|
+
}
|
|
156
|
+
async function ensureSchemaJsModuleContext(config, sourceFile, options = {}) {
|
|
157
|
+
if (!sourceFile.endsWith('.js')) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const nearestPackage = await nearestPackageInfo(path.dirname(sourceFile));
|
|
161
|
+
if (nearestPackage?.type === 'module') {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (options.autoPackageJson !== false && await shouldCreateFixtureModulePackageJson(config, sourceFile, nearestPackage)) {
|
|
165
|
+
await writeFixtureModulePackageJson(config);
|
|
166
|
+
if ((await nearestPackageInfo(path.dirname(sourceFile)))?.type === 'module') {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const error = new Error('JavaScript schema files require ESM module context.');
|
|
171
|
+
error.code = 'DB_SCHEMA_JS_REQUIRES_MODULE';
|
|
172
|
+
error.hint = 'Use "type": "module" in the nearest package.json for .schema.js files, or allow @async/db to create db/package.json by keeping schema.autoModulePackageJson enabled.';
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
async function shouldCreateFixtureModulePackageJson(config, sourceFile, nearestPackage) {
|
|
176
|
+
if (config.schema?.autoModulePackageJson === false || !config.cwd || !config.sourceDir) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (!isInsideDirectory(config.sourceDir, sourceFile)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if ((await packageInfo(path.join(config.cwd, 'package.json')))?.type === 'module') {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const fixturePackageFile = path.join(config.sourceDir, 'package.json');
|
|
186
|
+
if (nearestPackage && (path.resolve(nearestPackage.file) === path.resolve(fixturePackageFile)
|
|
187
|
+
|| isInsideDirectory(config.sourceDir, nearestPackage.file))) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return !await fileExists(fixturePackageFile);
|
|
191
|
+
}
|
|
192
|
+
async function writeFixtureModulePackageJson(config) {
|
|
193
|
+
await writeFile(path.join(config.sourceDir, 'package.json'), `${JSON.stringify({ type: 'module' }, null, 2)}\n`, 'utf8');
|
|
194
|
+
}
|
|
195
|
+
async function nearestPackageInfo(directory) {
|
|
196
|
+
let current = path.resolve(directory);
|
|
197
|
+
while (true) {
|
|
198
|
+
const packageFile = path.join(current, 'package.json');
|
|
199
|
+
const info = await packageInfo(packageFile);
|
|
200
|
+
if (info) {
|
|
201
|
+
return info;
|
|
202
|
+
}
|
|
203
|
+
const parent = path.dirname(current);
|
|
204
|
+
if (parent === current) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
current = parent;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function packageInfo(packageFile) {
|
|
211
|
+
try {
|
|
212
|
+
const json = JSON.parse(await readFile(packageFile, 'utf8'));
|
|
213
|
+
return {
|
|
214
|
+
file: packageFile,
|
|
215
|
+
type: typeof json?.type === 'string' ? json.type : null,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error.code === 'ENOENT') {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
file: packageFile,
|
|
224
|
+
type: null,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function rootSchemaSources(config, exported, sourceFile, hash) {
|
|
229
|
+
if (!exported || typeof exported !== 'object' || Array.isArray(exported)) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
const file = path.relative(config.cwd, sourceFile).split(path.sep).join('/') || path.basename(sourceFile);
|
|
233
|
+
return Object.entries(exported)
|
|
234
|
+
.filter(([, schema]) => schema && typeof schema === 'object' && !Array.isArray(schema))
|
|
235
|
+
.map(([name, schema]) => ({
|
|
236
|
+
kind: 'schema',
|
|
237
|
+
name,
|
|
238
|
+
file,
|
|
239
|
+
sourceFile,
|
|
240
|
+
format: 'root-mjs',
|
|
241
|
+
hash,
|
|
242
|
+
schema,
|
|
243
|
+
baseDir: path.dirname(sourceFile),
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
async function listSourceFilesInDirectory(directory, prefix = '', ignoredDirs = []) {
|
|
247
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
248
|
+
const files = [];
|
|
249
|
+
for (const entry of entries) {
|
|
250
|
+
const relativePath = prefix ? path.join(prefix, entry.name) : entry.name;
|
|
251
|
+
if (entry.isDirectory()) {
|
|
252
|
+
if (entry.name.startsWith('.')) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const childDirectory = path.join(directory, entry.name);
|
|
256
|
+
if (isIgnoredSourceDirectory(childDirectory, ignoredDirs)) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
files.push(...await listSourceFilesInDirectory(childDirectory, relativePath, ignoredDirs));
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (entry.isFile()) {
|
|
263
|
+
files.push(relativePath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return files.sort();
|
|
267
|
+
}
|
|
268
|
+
function sourceFileListOptions(sourceDirOrConfig) {
|
|
269
|
+
if (typeof sourceDirOrConfig === 'string') {
|
|
270
|
+
return {
|
|
271
|
+
sourceDir: sourceDirOrConfig,
|
|
272
|
+
ignoredDirs: [],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const sourceDir = sourceDirOrConfig.sourceDir;
|
|
276
|
+
const operationSourceDir = sourceDirOrConfig.operations?.sourceDir;
|
|
277
|
+
return {
|
|
278
|
+
sourceDir,
|
|
279
|
+
ignoredDirs: operationSourceDir && isInsideDirectory(sourceDir, operationSourceDir)
|
|
280
|
+
? [path.resolve(operationSourceDir)]
|
|
281
|
+
: [],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function isIgnoredSourceDirectory(directory, ignoredDirs) {
|
|
285
|
+
const resolved = path.resolve(directory);
|
|
286
|
+
return ignoredDirs.some((ignoredDir) => resolved === ignoredDir || resolved.startsWith(`${ignoredDir}${path.sep}`));
|
|
287
|
+
}
|
|
288
|
+
function isInsideDirectory(parent, child) {
|
|
289
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
290
|
+
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
291
|
+
}
|
|
292
|
+
export async function readSourceFile(config, filename) {
|
|
293
|
+
let context;
|
|
294
|
+
try {
|
|
295
|
+
context = await createSourceReaderContext(config, filename);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
const sourceFile = path.join(config.sourceDir, filename);
|
|
299
|
+
return {
|
|
300
|
+
sources: [],
|
|
301
|
+
diagnostics: [sourceLoadDiagnostic(error, sourceFile, resolveSourceResourceName(config, filename), config, {
|
|
302
|
+
readerName: 'db:source-context',
|
|
303
|
+
})],
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const readers = sourceReaders(config);
|
|
307
|
+
const diagnostics = [];
|
|
308
|
+
for (const reader of readers) {
|
|
309
|
+
let matches = false;
|
|
310
|
+
try {
|
|
311
|
+
matches = await reader.match(context);
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
return {
|
|
315
|
+
sources: [],
|
|
316
|
+
diagnostics: [sourceReaderDiagnostic(error, context, reader)],
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (!matches) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
let result;
|
|
323
|
+
try {
|
|
324
|
+
result = await reader.read(context);
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
return {
|
|
328
|
+
sources: [],
|
|
329
|
+
diagnostics: [sourceLoadDiagnostic(error, context.sourceFile, resolveSourceResourceName(config, filename), config, {
|
|
330
|
+
readerName: reader.name,
|
|
331
|
+
})],
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
if (result === null || result === undefined) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const normalized = normalizeSourceReaderResult(result, context, reader);
|
|
338
|
+
diagnostics.push(...normalized.diagnostics);
|
|
339
|
+
return {
|
|
340
|
+
sources: normalized.sources,
|
|
341
|
+
diagnostics,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
sources: [],
|
|
346
|
+
diagnostics,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
async function createSourceReaderContext(config, filename) {
|
|
350
|
+
const sourceFile = path.join(config.sourceDir, filename);
|
|
351
|
+
const file = sourceFileLabel(config, filename);
|
|
352
|
+
const parsed = parseFixturePath(file);
|
|
353
|
+
let buffer;
|
|
354
|
+
let text;
|
|
355
|
+
const readBuffer = async () => {
|
|
356
|
+
buffer ??= await readFile(sourceFile);
|
|
357
|
+
return buffer;
|
|
358
|
+
};
|
|
359
|
+
const hash = createHash('sha256').update(await readBuffer()).digest('hex');
|
|
360
|
+
return {
|
|
361
|
+
...parsed,
|
|
362
|
+
config,
|
|
363
|
+
file,
|
|
364
|
+
sourceFile,
|
|
365
|
+
hash,
|
|
366
|
+
async readBuffer() {
|
|
367
|
+
return readBuffer();
|
|
368
|
+
},
|
|
369
|
+
async readText() {
|
|
370
|
+
text ??= (await readBuffer()).toString('utf8');
|
|
371
|
+
return text;
|
|
372
|
+
},
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function sourceReaders(config) {
|
|
376
|
+
return [
|
|
377
|
+
...normalizeUserSourceReaders(config.sources?.readers),
|
|
378
|
+
...builtInSourceReaders(),
|
|
379
|
+
];
|
|
380
|
+
}
|
|
381
|
+
function normalizeUserSourceReaders(readers) {
|
|
382
|
+
if (!Array.isArray(readers)) {
|
|
383
|
+
return [];
|
|
384
|
+
}
|
|
385
|
+
return readers.filter(isSourceReader);
|
|
386
|
+
}
|
|
387
|
+
function isSourceReader(reader) {
|
|
388
|
+
return Boolean(reader)
|
|
389
|
+
&& typeof reader === 'object'
|
|
390
|
+
&& typeof reader.name === 'string'
|
|
391
|
+
&& typeof reader.match === 'function'
|
|
392
|
+
&& typeof reader.read === 'function';
|
|
393
|
+
}
|
|
394
|
+
function builtInSourceReaders() {
|
|
395
|
+
return [
|
|
396
|
+
{
|
|
397
|
+
name: 'db:schema-mjs',
|
|
398
|
+
match: ({ file, config }) => config.schema?.source !== 'data' && file.endsWith('.schema.mjs'),
|
|
399
|
+
async read({ sourceFile }) {
|
|
400
|
+
const url = pathToFileURL(sourceFile);
|
|
401
|
+
url.searchParams.set('dbSchemaLoad', String(Date.now()));
|
|
402
|
+
const module = await import(url.href);
|
|
403
|
+
return {
|
|
404
|
+
kind: 'schema',
|
|
405
|
+
format: 'mjs',
|
|
406
|
+
schema: module.default,
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
name: 'db:schema-js',
|
|
412
|
+
match: ({ file, config }) => config.schema?.source !== 'data' && file.endsWith('.schema.js'),
|
|
413
|
+
async read({ config, sourceFile }) {
|
|
414
|
+
await ensureSchemaJsModuleContext(config, sourceFile);
|
|
415
|
+
const url = pathToFileURL(sourceFile);
|
|
416
|
+
url.searchParams.set('dbSchemaLoad', String(Date.now()));
|
|
417
|
+
const module = await import(url.href);
|
|
418
|
+
return {
|
|
419
|
+
kind: 'schema',
|
|
420
|
+
format: 'js',
|
|
421
|
+
schema: module.default,
|
|
422
|
+
};
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
name: 'db:schema-json',
|
|
427
|
+
match: ({ file, config }) => config.schema?.source !== 'data' && file.endsWith('.schema.json'),
|
|
428
|
+
async read({ readText }) {
|
|
429
|
+
return {
|
|
430
|
+
kind: 'schema',
|
|
431
|
+
format: 'json',
|
|
432
|
+
schema: JSON.parse(await readText()),
|
|
433
|
+
};
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'db:schema-jsonc',
|
|
438
|
+
match: ({ file, config }) => (config.schema?.source !== 'data'
|
|
439
|
+
&& config.schema?.allowJsonc !== false
|
|
440
|
+
&& file.endsWith('.schema.jsonc')),
|
|
441
|
+
async read({ readText, sourceFile }) {
|
|
442
|
+
return {
|
|
443
|
+
kind: 'schema',
|
|
444
|
+
format: 'jsonc',
|
|
445
|
+
schema: parseJsonc(await readText(), sourceFile),
|
|
446
|
+
};
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
name: 'db:data-csv',
|
|
451
|
+
match: ({ file, config }) => config.schema?.source !== 'schema' && file.endsWith('.csv'),
|
|
452
|
+
async read({ readText, sourceFile }) {
|
|
453
|
+
return {
|
|
454
|
+
kind: 'data',
|
|
455
|
+
format: 'csv',
|
|
456
|
+
data: parseCsvRecords(await readText(), sourceFile),
|
|
457
|
+
};
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'db:data-jsonc',
|
|
462
|
+
match: ({ file, config }) => (config.schema?.source !== 'schema'
|
|
463
|
+
&& config.schema?.allowJsonc !== false
|
|
464
|
+
&& !isSchemaSourceFile(file)
|
|
465
|
+
&& file.endsWith('.jsonc')),
|
|
466
|
+
async read({ readText, sourceFile }) {
|
|
467
|
+
return {
|
|
468
|
+
kind: 'data',
|
|
469
|
+
format: 'jsonc',
|
|
470
|
+
data: parseJsonc(await readText(), sourceFile),
|
|
471
|
+
};
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: 'db:data-json',
|
|
476
|
+
match: ({ file, config }) => config.schema?.source !== 'schema' && !isSchemaSourceFile(file) && file.endsWith('.json'),
|
|
477
|
+
async read({ readText }) {
|
|
478
|
+
return {
|
|
479
|
+
kind: 'data',
|
|
480
|
+
format: 'json',
|
|
481
|
+
data: JSON.parse(await readText()),
|
|
482
|
+
};
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
];
|
|
486
|
+
}
|
|
487
|
+
function isSchemaSourceFile(file) {
|
|
488
|
+
return file.endsWith('.schema.json') || file.endsWith('.schema.jsonc') || file.endsWith('.schema.mjs') || file.endsWith('.schema.js');
|
|
489
|
+
}
|
|
490
|
+
function normalizeSourceReaderResult(result, context, reader) {
|
|
491
|
+
const rawSources = flattenSourceReaderResult(result);
|
|
492
|
+
const diagnostics = [];
|
|
493
|
+
const sources = [];
|
|
494
|
+
const multipleSources = rawSources.length > 1;
|
|
495
|
+
for (const [index, rawSource] of rawSources.entries()) {
|
|
496
|
+
if (!rawSource || typeof rawSource !== 'object' || Array.isArray(rawSource)) {
|
|
497
|
+
diagnostics.push(invalidSourceReaderResultDiagnostic(context, reader, `Result ${index + 1} must be an object.`));
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
const source = rawSource;
|
|
501
|
+
if (source.kind !== 'data' && source.kind !== 'schema') {
|
|
502
|
+
diagnostics.push(invalidSourceReaderResultDiagnostic(context, reader, `Result ${index + 1} must set kind to "data" or "schema".`));
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const kind = source.kind;
|
|
506
|
+
if (multipleSources && !source.resourceName) {
|
|
507
|
+
diagnostics.push({
|
|
508
|
+
code: 'SOURCE_READER_RESOURCE_NAME_REQUIRED',
|
|
509
|
+
severity: 'error',
|
|
510
|
+
file: context.file,
|
|
511
|
+
message: `Source reader "${reader.name}" returned multiple sources from ${context.file}, but result ${index + 1} does not include resourceName.`,
|
|
512
|
+
hint: 'Add resourceName to every source returned from a multi-source reader.',
|
|
513
|
+
details: {
|
|
514
|
+
reader: reader.name,
|
|
515
|
+
sourceIndex: index,
|
|
516
|
+
file: context.file,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (!sourceKindAllowed(context.config, kind)) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (kind === 'data' && !Object.prototype.hasOwnProperty.call(source, 'data')) {
|
|
525
|
+
diagnostics.push(invalidSourceReaderResultDiagnostic(context, reader, `Data result ${index + 1} must include data.`));
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (kind === 'schema' && !Object.prototype.hasOwnProperty.call(source, 'schema')) {
|
|
529
|
+
diagnostics.push(invalidSourceReaderResultDiagnostic(context, reader, `Schema result ${index + 1} must include schema.`));
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const name = source.resourceName
|
|
533
|
+
? String(source.resourceName)
|
|
534
|
+
: resolveSourceResourceName(context.config, path.relative(context.config.sourceDir, context.sourceFile));
|
|
535
|
+
const format = source.format ? String(source.format) : reader.name;
|
|
536
|
+
sources.push({
|
|
537
|
+
kind,
|
|
538
|
+
name,
|
|
539
|
+
file: context.file,
|
|
540
|
+
sourceFile: context.sourceFile,
|
|
541
|
+
format,
|
|
542
|
+
hash: context.hash,
|
|
543
|
+
data: kind === 'data' ? source.data : undefined,
|
|
544
|
+
schema: kind === 'schema' ? source.schema : undefined,
|
|
545
|
+
baseDir: path.dirname(context.sourceFile),
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
sources,
|
|
550
|
+
diagnostics,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function flattenSourceReaderResult(result) {
|
|
554
|
+
if (result === null || result === undefined) {
|
|
555
|
+
return [];
|
|
556
|
+
}
|
|
557
|
+
if (Array.isArray(result)) {
|
|
558
|
+
return result.flatMap((item) => flattenSourceReaderResult(item));
|
|
559
|
+
}
|
|
560
|
+
return [result];
|
|
561
|
+
}
|
|
562
|
+
function sourceKindAllowed(config, kind) {
|
|
563
|
+
if (kind === 'data') {
|
|
564
|
+
return config.schema?.source !== 'schema';
|
|
565
|
+
}
|
|
566
|
+
return config.schema?.source !== 'data';
|
|
567
|
+
}
|
|
568
|
+
function resolveSourceResourceName(config, filename) {
|
|
569
|
+
const file = sourceFileLabel(config, filename);
|
|
570
|
+
const strategy = config.resources?.naming ?? 'basename';
|
|
571
|
+
const parsed = parseFixturePath(file);
|
|
572
|
+
const isIndexSchema = parsed.basename === 'index' && parsed.extension.startsWith('.schema.');
|
|
573
|
+
const defaultName = isIndexSchema && parsed.folder
|
|
574
|
+
? resourceNameFromPath(`db/${parsed.folder}.json`, { strategy: 'basename' })
|
|
575
|
+
: resourceNameFromPath(file, { strategy: strategy });
|
|
576
|
+
const defaultResource = { name: defaultName };
|
|
577
|
+
const customizeResource = config.resources?.customizeResource;
|
|
578
|
+
if (typeof customizeResource !== 'function') {
|
|
579
|
+
return defaultName;
|
|
580
|
+
}
|
|
581
|
+
const customized = customizeResource({
|
|
582
|
+
file,
|
|
583
|
+
sourceFile: path.join(config.sourceDir, filename),
|
|
584
|
+
basename: parsed.basename,
|
|
585
|
+
folder: parsed.folder,
|
|
586
|
+
folders: parsed.folders,
|
|
587
|
+
extension: parsed.extension,
|
|
588
|
+
defaultName,
|
|
589
|
+
defaultResource,
|
|
590
|
+
});
|
|
591
|
+
return String(customized?.name ?? defaultName);
|
|
592
|
+
}
|
|
593
|
+
function sourceFileLabel(config, filename) {
|
|
594
|
+
return path.relative(config.cwd, path.join(config.sourceDir, filename)).split(path.sep).join('/');
|
|
595
|
+
}
|
|
596
|
+
export function trackResourceSource(resourceSources, name, filename, kind) {
|
|
597
|
+
const sources = resourceSources.get(name) ?? [];
|
|
598
|
+
sources.push({ filename, kind });
|
|
599
|
+
resourceSources.set(name, sources);
|
|
600
|
+
}
|
|
601
|
+
export function duplicateResourceDiagnostics(resourceSources) {
|
|
602
|
+
const diagnostics = [];
|
|
603
|
+
for (const [name, rawSources] of resourceSources.entries()) {
|
|
604
|
+
const sources = rawSources;
|
|
605
|
+
const dataSources = sources.filter((source) => source.kind === 'data');
|
|
606
|
+
const schemaSources = sources.filter((source) => source.kind === 'schema');
|
|
607
|
+
const duplicates = dataSources.length > 1 ? dataSources : schemaSources.length > 1 ? schemaSources : [];
|
|
608
|
+
if (duplicates.length === 0) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const files = duplicates.map((source) => source.filename.split(path.sep).join('/'));
|
|
612
|
+
diagnostics.push({
|
|
613
|
+
code: 'DUPLICATE_RESOURCE_NAME',
|
|
614
|
+
severity: 'error',
|
|
615
|
+
resource: name,
|
|
616
|
+
file: files[0],
|
|
617
|
+
message: `Duplicate resource name "${name}" from nested fixtures:\n${files.map((file) => `- ${file}`).join('\n')}`,
|
|
618
|
+
hint: `Rename one fixture, set resources.naming to "folder-prefixed" or "path", or use resources.customizeResource to assign explicit names.`,
|
|
619
|
+
details: {
|
|
620
|
+
resource: name,
|
|
621
|
+
files,
|
|
622
|
+
apiEffects: [
|
|
623
|
+
`.db/state/${name}.json`,
|
|
624
|
+
`REST ${routePathForResource(name)}`,
|
|
625
|
+
`GraphQL ${name}/${typeNameForResource(name)}`,
|
|
626
|
+
`DbCollections["${name}"]`,
|
|
627
|
+
],
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return diagnostics;
|
|
632
|
+
}
|
|
633
|
+
function sourceLoadDiagnostic(error, filePath, resource, config, options = {}) {
|
|
634
|
+
const loadError = error;
|
|
635
|
+
const relativePath = path.relative(config.cwd, filePath);
|
|
636
|
+
return {
|
|
637
|
+
code: 'SOURCE_LOAD_FAILED',
|
|
638
|
+
severity: 'error',
|
|
639
|
+
resource,
|
|
640
|
+
file: relativePath,
|
|
641
|
+
message: `Could not load ${relativePath}: ${loadError.message}`,
|
|
642
|
+
hint: loadError.hint ?? schemaModuleLoadHint(relativePath, loadError) ?? 'Fix this source file and db will reload the rest of the project.',
|
|
643
|
+
details: {
|
|
644
|
+
path: relativePath,
|
|
645
|
+
reader: options.readerName,
|
|
646
|
+
parserMessage: loadError.message,
|
|
647
|
+
code: loadError.code,
|
|
648
|
+
},
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function schemaModuleLoadHint(relativePath, error) {
|
|
652
|
+
if (!relativePath.endsWith('.schema.js') && path.basename(relativePath) !== 'db.schema.js') {
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
const message = String(error?.message ?? '');
|
|
656
|
+
if (message.includes('Cannot use import statement outside a module') || message.includes('Unexpected token \'export\'')) {
|
|
657
|
+
return 'Use "type": "module" in the nearest package.json for .schema.js files, or allow @async/db to create db/package.json by keeping schema.autoModulePackageJson enabled.';
|
|
658
|
+
}
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
function sourceReaderDiagnostic(error, context, reader) {
|
|
662
|
+
const readerError = error;
|
|
663
|
+
return {
|
|
664
|
+
code: 'SOURCE_READER_FAILED',
|
|
665
|
+
severity: 'error',
|
|
666
|
+
file: context.file,
|
|
667
|
+
message: `Source reader "${reader.name}" could not inspect ${context.file}: ${readerError.message}`,
|
|
668
|
+
hint: 'Update the source reader or return null so another reader can handle this file.',
|
|
669
|
+
details: {
|
|
670
|
+
reader: reader.name,
|
|
671
|
+
path: context.file,
|
|
672
|
+
parserMessage: readerError.message,
|
|
673
|
+
code: readerError.code,
|
|
674
|
+
},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function invalidSourceReaderResultDiagnostic(context, reader, message) {
|
|
678
|
+
return {
|
|
679
|
+
code: 'SOURCE_READER_INVALID_RESULT',
|
|
680
|
+
severity: 'error',
|
|
681
|
+
file: context.file,
|
|
682
|
+
message: `Source reader "${reader.name}" returned an invalid result for ${context.file}: ${message}`,
|
|
683
|
+
hint: 'Return { kind: "data", data } or { kind: "schema", schema }, optionally with format and resourceName.',
|
|
684
|
+
details: {
|
|
685
|
+
reader: reader.name,
|
|
686
|
+
path: context.file,
|
|
687
|
+
},
|
|
688
|
+
};
|
|
689
|
+
}
|