@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,149 @@
|
|
|
1
|
+
import { dbError } from './errors.js';
|
|
2
|
+
export function parseCsvRecords(text, filePath = 'CSV file') {
|
|
3
|
+
const rows = parseCsvRows(text);
|
|
4
|
+
const headerRow = rows.shift();
|
|
5
|
+
if (!headerRow || headerRow.every((cell) => cell.value.trim() === '')) {
|
|
6
|
+
throw dbError('CSV_MISSING_HEADER', `${filePath} must start with a header row.`, {
|
|
7
|
+
status: 400,
|
|
8
|
+
hint: 'Add a first row with column names, for example: id,name,email.',
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
const headers = headerRow.map((cell, index) => ({
|
|
12
|
+
rawName: cell.value.trim(),
|
|
13
|
+
fieldName: fieldNameFromHeader(cell.value, index),
|
|
14
|
+
}));
|
|
15
|
+
const fieldNames = uniqueFieldNames(headers.map((header) => header.fieldName));
|
|
16
|
+
return rows
|
|
17
|
+
.filter((row) => row.some((cell) => cell.value.trim() !== ''))
|
|
18
|
+
.map((row) => {
|
|
19
|
+
const record = {};
|
|
20
|
+
for (const [index, header] of headers.entries()) {
|
|
21
|
+
const value = coerceCsvValue(row[index] ?? { value: '', quoted: false }, header.rawName);
|
|
22
|
+
if (value !== undefined) {
|
|
23
|
+
record[fieldNames[index]] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return record;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function parseCsvRows(text) {
|
|
30
|
+
const rows = [];
|
|
31
|
+
let row = [];
|
|
32
|
+
let field = '';
|
|
33
|
+
let quoted = false;
|
|
34
|
+
let wasQuoted = false;
|
|
35
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
36
|
+
const char = text[index];
|
|
37
|
+
if (quoted) {
|
|
38
|
+
if (char === '"' && text[index + 1] === '"') {
|
|
39
|
+
field += '"';
|
|
40
|
+
index += 1;
|
|
41
|
+
}
|
|
42
|
+
else if (char === '"') {
|
|
43
|
+
quoted = false;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
field += char;
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (char === '"' && field === '') {
|
|
51
|
+
quoted = true;
|
|
52
|
+
wasQuoted = true;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (char === ',') {
|
|
56
|
+
pushField();
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (char === '\n') {
|
|
60
|
+
pushField();
|
|
61
|
+
pushRow();
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (char === '\r') {
|
|
65
|
+
if (text[index + 1] === '\n') {
|
|
66
|
+
index += 1;
|
|
67
|
+
}
|
|
68
|
+
pushField();
|
|
69
|
+
pushRow();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
field += char;
|
|
73
|
+
}
|
|
74
|
+
if (quoted) {
|
|
75
|
+
throw dbError('CSV_UNTERMINATED_QUOTE', 'CSV file has an unterminated quoted field.', {
|
|
76
|
+
status: 400,
|
|
77
|
+
hint: 'Close the quoted field with another double quote, or escape embedded quotes as "".',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
pushField();
|
|
81
|
+
pushRow();
|
|
82
|
+
return rows;
|
|
83
|
+
function pushField() {
|
|
84
|
+
row.push({
|
|
85
|
+
value: wasQuoted ? field : field.trim(),
|
|
86
|
+
quoted: wasQuoted,
|
|
87
|
+
});
|
|
88
|
+
field = '';
|
|
89
|
+
wasQuoted = false;
|
|
90
|
+
}
|
|
91
|
+
function pushRow() {
|
|
92
|
+
if (row.length > 1 || row[0]?.value !== '') {
|
|
93
|
+
rows.push(row);
|
|
94
|
+
}
|
|
95
|
+
row = [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function fieldNameFromHeader(header, index) {
|
|
99
|
+
const trimmed = String(header).trim();
|
|
100
|
+
if (/^[a-z_$][A-Za-z0-9_$]*$/.test(trimmed)) {
|
|
101
|
+
return trimmed;
|
|
102
|
+
}
|
|
103
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(trimmed)) {
|
|
104
|
+
return trimmed.charAt(0).toLowerCase() + trimmed.slice(1);
|
|
105
|
+
}
|
|
106
|
+
const words = trimmed.match(/[A-Za-z0-9]+/g) ?? [];
|
|
107
|
+
const name = words.map((word, wordIndex) => {
|
|
108
|
+
const lower = word.toLowerCase();
|
|
109
|
+
return wordIndex === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
110
|
+
}).join('');
|
|
111
|
+
if (!name) {
|
|
112
|
+
return `column${index + 1}`;
|
|
113
|
+
}
|
|
114
|
+
return /^\d/.test(name) ? `_${name}` : name;
|
|
115
|
+
}
|
|
116
|
+
function uniqueFieldNames(names) {
|
|
117
|
+
const counts = new Map();
|
|
118
|
+
return names.map((name) => {
|
|
119
|
+
const count = counts.get(name) ?? 0;
|
|
120
|
+
counts.set(name, count + 1);
|
|
121
|
+
return count === 0 ? name : `${name}${count + 1}`;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function coerceCsvValue(cell, headerName) {
|
|
125
|
+
const value = cell.quoted ? cell.value : cell.value.trim();
|
|
126
|
+
if (value === '') {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
if (cell.quoted || isStringLikeHeader(headerName)) {
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
const lower = value.toLowerCase();
|
|
133
|
+
if (lower === 'true') {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (lower === 'false') {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (lower === 'null') {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
if (/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?$/.test(value)) {
|
|
143
|
+
return Number(value);
|
|
144
|
+
}
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
function isStringLikeHeader(headerName) {
|
|
148
|
+
return /\b(id|uuid|guid|zip|postal|phone|tel|code)\b/i.test(String(headerName).replaceAll(/[^A-Za-z0-9]+/g, ' '));
|
|
149
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare class DbError extends Error {
|
|
2
|
+
code: string;
|
|
3
|
+
status: number;
|
|
4
|
+
hint?: string;
|
|
5
|
+
details?: unknown;
|
|
6
|
+
constructor(code: string, message: string, options?: DbErrorOptions);
|
|
7
|
+
}
|
|
8
|
+
export type DbErrorOptions = {
|
|
9
|
+
status?: number;
|
|
10
|
+
hint?: string;
|
|
11
|
+
details?: unknown;
|
|
12
|
+
};
|
|
13
|
+
type ErrorLike = {
|
|
14
|
+
code?: string;
|
|
15
|
+
message?: string;
|
|
16
|
+
hint?: string;
|
|
17
|
+
details?: unknown;
|
|
18
|
+
};
|
|
19
|
+
export declare function dbError(code: string, message: string, options?: DbErrorOptions): DbError;
|
|
20
|
+
export declare function serializeError(error: ErrorLike, fallbackCode?: string): {
|
|
21
|
+
error: {
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
hint: string;
|
|
25
|
+
details: unknown;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export declare function graphqlError(error: ErrorLike, fallbackCode?: string): {
|
|
29
|
+
message: string;
|
|
30
|
+
extensions: {
|
|
31
|
+
code: string;
|
|
32
|
+
hint: string;
|
|
33
|
+
details: unknown;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
export declare function describeValue(value: unknown): "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | "null" | "array";
|
|
37
|
+
export declare function listChoices(values: unknown[], options?: {
|
|
38
|
+
max?: number;
|
|
39
|
+
}): string;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class DbError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
status;
|
|
4
|
+
hint;
|
|
5
|
+
details;
|
|
6
|
+
constructor(code, message, options = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'DbError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = options.status ?? 400;
|
|
11
|
+
this.hint = options.hint;
|
|
12
|
+
this.details = options.details;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function dbError(code, message, options = {}) {
|
|
16
|
+
return new DbError(code, message, options);
|
|
17
|
+
}
|
|
18
|
+
export function serializeError(error, fallbackCode = 'DB_ERROR') {
|
|
19
|
+
return {
|
|
20
|
+
error: {
|
|
21
|
+
code: error.code ?? fallbackCode,
|
|
22
|
+
message: error.message ?? String(error),
|
|
23
|
+
hint: error.hint,
|
|
24
|
+
details: error.details,
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function graphqlError(error, fallbackCode = 'GRAPHQL_ERROR') {
|
|
29
|
+
return {
|
|
30
|
+
message: error.message ?? String(error),
|
|
31
|
+
extensions: {
|
|
32
|
+
code: error.code ?? fallbackCode,
|
|
33
|
+
hint: error.hint,
|
|
34
|
+
details: error.details,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function describeValue(value) {
|
|
39
|
+
if (value === null) {
|
|
40
|
+
return 'null';
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return 'array';
|
|
44
|
+
}
|
|
45
|
+
return typeof value;
|
|
46
|
+
}
|
|
47
|
+
export function listChoices(values, options = {}) {
|
|
48
|
+
const max = options.max ?? 8;
|
|
49
|
+
const list = [...values].filter(Boolean).slice(0, max);
|
|
50
|
+
if (list.length === 0) {
|
|
51
|
+
return '(none)';
|
|
52
|
+
}
|
|
53
|
+
const suffix = values.length > max ? `, and ${values.length - max} more` : '';
|
|
54
|
+
return list.map((value) => `"${value}"`).join(', ') + suffix;
|
|
55
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function readText(filePath: string): Promise<string>;
|
|
2
|
+
export declare function writeText(filePath: string, content: string): Promise<boolean>;
|
|
3
|
+
export declare function resolveFrom(baseDir: string, maybeRelative: string): string;
|
|
4
|
+
export declare function toPosixPath(value: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function readText(filePath) {
|
|
4
|
+
return readFile(filePath, 'utf8');
|
|
5
|
+
}
|
|
6
|
+
export async function writeText(filePath, content) {
|
|
7
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
8
|
+
try {
|
|
9
|
+
if ((await readFile(filePath, 'utf8')) === content) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const fsError = error;
|
|
15
|
+
if (fsError.code !== 'ENOENT') {
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
await writeFile(filePath, content, 'utf8');
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
export function resolveFrom(baseDir, maybeRelative) {
|
|
23
|
+
if (path.isAbsolute(maybeRelative)) {
|
|
24
|
+
return maybeRelative;
|
|
25
|
+
}
|
|
26
|
+
return path.resolve(baseDir, maybeRelative);
|
|
27
|
+
}
|
|
28
|
+
export function toPosixPath(value) {
|
|
29
|
+
return value.split(path.sep).join('/');
|
|
30
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function parseJsonc(text, filename = 'JSONC input') {
|
|
2
|
+
try {
|
|
3
|
+
return JSON.parse(stripJsonc(text));
|
|
4
|
+
}
|
|
5
|
+
catch (error) {
|
|
6
|
+
error.message = `${filename}: ${error.message}`;
|
|
7
|
+
throw error;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function stripJsonc(text) {
|
|
11
|
+
const withoutBom = text.replace(/^\uFEFF/, '');
|
|
12
|
+
return stripTrailingCommas(stripComments(withoutBom));
|
|
13
|
+
}
|
|
14
|
+
function stripComments(text) {
|
|
15
|
+
let output = '';
|
|
16
|
+
let inString = false;
|
|
17
|
+
let quote = '';
|
|
18
|
+
let escaping = false;
|
|
19
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
20
|
+
const char = text[index];
|
|
21
|
+
const next = text[index + 1];
|
|
22
|
+
if (inString) {
|
|
23
|
+
output += char;
|
|
24
|
+
if (escaping) {
|
|
25
|
+
escaping = false;
|
|
26
|
+
}
|
|
27
|
+
else if (char === '\\') {
|
|
28
|
+
escaping = true;
|
|
29
|
+
}
|
|
30
|
+
else if (char === quote) {
|
|
31
|
+
inString = false;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (char === '"' || char === "'") {
|
|
36
|
+
inString = true;
|
|
37
|
+
quote = char;
|
|
38
|
+
output += char;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === '/' && next === '/') {
|
|
42
|
+
while (index < text.length && text[index] !== '\n') {
|
|
43
|
+
index += 1;
|
|
44
|
+
}
|
|
45
|
+
output += '\n';
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (char === '/' && next === '*') {
|
|
49
|
+
index += 2;
|
|
50
|
+
while (index < text.length && !(text[index] === '*' && text[index + 1] === '/')) {
|
|
51
|
+
output += text[index] === '\n' ? '\n' : ' ';
|
|
52
|
+
index += 1;
|
|
53
|
+
}
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
output += char;
|
|
58
|
+
}
|
|
59
|
+
return output;
|
|
60
|
+
}
|
|
61
|
+
function stripTrailingCommas(text) {
|
|
62
|
+
let output = '';
|
|
63
|
+
let inString = false;
|
|
64
|
+
let quote = '';
|
|
65
|
+
let escaping = false;
|
|
66
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
67
|
+
const char = text[index];
|
|
68
|
+
if (inString) {
|
|
69
|
+
output += char;
|
|
70
|
+
if (escaping) {
|
|
71
|
+
escaping = false;
|
|
72
|
+
}
|
|
73
|
+
else if (char === '\\') {
|
|
74
|
+
escaping = true;
|
|
75
|
+
}
|
|
76
|
+
else if (char === quote) {
|
|
77
|
+
inString = false;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '"' || char === "'") {
|
|
82
|
+
inString = true;
|
|
83
|
+
quote = char;
|
|
84
|
+
output += char;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (char === ',') {
|
|
88
|
+
let cursor = index + 1;
|
|
89
|
+
while (cursor < text.length && /\s/.test(text[cursor])) {
|
|
90
|
+
cursor += 1;
|
|
91
|
+
}
|
|
92
|
+
if (text[cursor] === '}' || text[cursor] === ']') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
output += char;
|
|
97
|
+
}
|
|
98
|
+
return output;
|
|
99
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type MockDelay = {
|
|
2
|
+
minMs?: number;
|
|
3
|
+
min?: number;
|
|
4
|
+
maxMs?: number;
|
|
5
|
+
max?: number;
|
|
6
|
+
};
|
|
7
|
+
type NormalizedMockDelay = {
|
|
8
|
+
minMs: number;
|
|
9
|
+
maxMs: number;
|
|
10
|
+
};
|
|
11
|
+
type MockErrorConfig = {
|
|
12
|
+
rate?: number;
|
|
13
|
+
probability?: number;
|
|
14
|
+
status?: number;
|
|
15
|
+
message?: string;
|
|
16
|
+
};
|
|
17
|
+
type MockConfig = {
|
|
18
|
+
delay?: number | [number, number] | MockDelay | false;
|
|
19
|
+
delayMs?: number | [number, number] | MockDelay | false;
|
|
20
|
+
errors?: number | MockErrorConfig | false | null;
|
|
21
|
+
error?: number | MockErrorConfig | false | null;
|
|
22
|
+
};
|
|
23
|
+
type RuntimeConfigWithMock = {
|
|
24
|
+
mock?: MockConfig | false | null;
|
|
25
|
+
chaos?: MockConfig | false | null;
|
|
26
|
+
server?: {
|
|
27
|
+
apiBase?: string;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
type MockErrorResponse = {
|
|
31
|
+
status: number;
|
|
32
|
+
body: {
|
|
33
|
+
error: string;
|
|
34
|
+
mock: true;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export declare function runMockBehavior(config: RuntimeConfigWithMock, url?: URL | null): Promise<MockErrorResponse | null>;
|
|
38
|
+
export declare function normalizeMockDelay(value: MockConfig['delay']): NormalizedMockDelay;
|
|
39
|
+
export declare function pickDelayMs(delay: Partial<NormalizedMockDelay>, random?: () => number): number;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export async function runMockBehavior(config, url = null) {
|
|
2
|
+
const mock = config.mock ?? config.chaos;
|
|
3
|
+
if (!mock || shouldSkipMock(config, url)) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
const delay = normalizeMockDelay(mock.delay ?? mock.delayMs);
|
|
7
|
+
if (delay.maxMs > 0) {
|
|
8
|
+
await sleep(pickDelayMs(delay));
|
|
9
|
+
}
|
|
10
|
+
const error = normalizeMockError(mock.errors ?? mock.error);
|
|
11
|
+
if (error.rate > 0 && Math.random() < error.rate) {
|
|
12
|
+
return {
|
|
13
|
+
status: error.status,
|
|
14
|
+
body: {
|
|
15
|
+
error: error.message,
|
|
16
|
+
mock: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
export function normalizeMockDelay(value) {
|
|
23
|
+
if (!value) {
|
|
24
|
+
return {
|
|
25
|
+
minMs: 0,
|
|
26
|
+
maxMs: 0,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return normalizeDelayNumbers(value[0], value[1]);
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === 'number') {
|
|
33
|
+
return normalizeDelayNumbers(value, value);
|
|
34
|
+
}
|
|
35
|
+
return normalizeDelayNumbers(value.minMs ?? value.min ?? 0, value.maxMs ?? value.max ?? value.minMs ?? value.min ?? 0);
|
|
36
|
+
}
|
|
37
|
+
export function pickDelayMs(delay, random = Math.random) {
|
|
38
|
+
const minMs = Math.max(0, Number(delay.minMs ?? 0));
|
|
39
|
+
const maxMs = Math.max(minMs, Number(delay.maxMs ?? minMs));
|
|
40
|
+
return Math.round(minMs + (maxMs - minMs) * random());
|
|
41
|
+
}
|
|
42
|
+
function normalizeMockError(value) {
|
|
43
|
+
if (!value) {
|
|
44
|
+
return {
|
|
45
|
+
rate: 0,
|
|
46
|
+
status: 503,
|
|
47
|
+
message: 'Mock chaos error',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === 'number') {
|
|
51
|
+
return {
|
|
52
|
+
rate: clampRate(value),
|
|
53
|
+
status: 503,
|
|
54
|
+
message: 'Mock chaos error',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
rate: clampRate(value.rate ?? value.probability ?? 0),
|
|
59
|
+
status: Number(value.status ?? 503),
|
|
60
|
+
message: String(value.message ?? 'Mock chaos error'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function normalizeDelayNumbers(min, max) {
|
|
64
|
+
const minMs = Math.max(0, Number(min ?? 0));
|
|
65
|
+
const maxMs = Math.max(minMs, Number(max ?? minMs));
|
|
66
|
+
return {
|
|
67
|
+
minMs,
|
|
68
|
+
maxMs,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function clampRate(value) {
|
|
72
|
+
return Math.min(1, Math.max(0, Number(value)));
|
|
73
|
+
}
|
|
74
|
+
function shouldSkipMock(config, url) {
|
|
75
|
+
return url?.pathname === normalizeBasePath(config.server?.apiBase ?? '/__db');
|
|
76
|
+
}
|
|
77
|
+
function normalizeBasePath(value) {
|
|
78
|
+
const path = `/${String(value ?? '').replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
79
|
+
return path === '/' ? '' : path;
|
|
80
|
+
}
|
|
81
|
+
function sleep(ms) {
|
|
82
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
83
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
type NamedResource = {
|
|
2
|
+
name: string;
|
|
3
|
+
};
|
|
4
|
+
type ResolveResourceResult<TResource> = {
|
|
5
|
+
resource: TResource | null;
|
|
6
|
+
matchedName: string | null;
|
|
7
|
+
candidates: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function pascalCase(value: string): string;
|
|
10
|
+
export declare function camelCase(value: string): string;
|
|
11
|
+
export declare function kebabCase(value: string): string;
|
|
12
|
+
export declare function resourceNameCandidates(value: string): string[];
|
|
13
|
+
export declare function resolveResource<TResource>(resources: Map<string, TResource>, requestedName: string): ResolveResourceResult<TResource>;
|
|
14
|
+
export declare function resourceAliasCollisions(resources: Array<string | NamedResource> | Map<string, NamedResource>): {
|
|
15
|
+
alias: string;
|
|
16
|
+
resources: string[];
|
|
17
|
+
}[];
|
|
18
|
+
export declare function resourceAliasCollisionGroups(resources: Array<string | NamedResource> | Map<string, NamedResource>): {
|
|
19
|
+
alias: string;
|
|
20
|
+
aliases: string[];
|
|
21
|
+
resources: string[];
|
|
22
|
+
candidates: Record<string, string[]>;
|
|
23
|
+
}[];
|
|
24
|
+
export declare function resourceConfigValue<TValue>(values: Record<string, TValue> | null | undefined, resourceName: string): TValue | undefined;
|
|
25
|
+
export declare function singularResourceName(resourceName: string): string;
|
|
26
|
+
export declare function typeNameForResource(resourceName: string, kind?: string): string;
|
|
27
|
+
export declare function routePathForResource(resourceName: string): string;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const IRREGULAR_SINGULARS = new Map([
|
|
2
|
+
['people', 'person'],
|
|
3
|
+
['children', 'child'],
|
|
4
|
+
['settings', 'settings'],
|
|
5
|
+
]);
|
|
6
|
+
export function pascalCase(value) {
|
|
7
|
+
return words(value)
|
|
8
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
9
|
+
.join('');
|
|
10
|
+
}
|
|
11
|
+
export function camelCase(value) {
|
|
12
|
+
const parts = words(value);
|
|
13
|
+
return parts
|
|
14
|
+
.map((word, index) => {
|
|
15
|
+
if (index === 0) {
|
|
16
|
+
return word;
|
|
17
|
+
}
|
|
18
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
19
|
+
})
|
|
20
|
+
.join('');
|
|
21
|
+
}
|
|
22
|
+
export function kebabCase(value) {
|
|
23
|
+
return words(value).join('-');
|
|
24
|
+
}
|
|
25
|
+
export function resourceNameCandidates(value) {
|
|
26
|
+
const exact = String(value);
|
|
27
|
+
return unique([
|
|
28
|
+
exact,
|
|
29
|
+
camelCase(exact),
|
|
30
|
+
kebabCase(exact),
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
export function resolveResource(resources, requestedName) {
|
|
34
|
+
const candidates = resourceNameCandidates(requestedName);
|
|
35
|
+
for (const candidate of candidates) {
|
|
36
|
+
if (resources.has(candidate)) {
|
|
37
|
+
return {
|
|
38
|
+
resource: resources.get(candidate),
|
|
39
|
+
matchedName: candidate,
|
|
40
|
+
candidates,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
resource: null,
|
|
46
|
+
matchedName: null,
|
|
47
|
+
candidates,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function resourceAliasCollisions(resources) {
|
|
51
|
+
const aliasResources = new Map();
|
|
52
|
+
const resourceList = Array.isArray(resources) ? resources : [...resources.values()];
|
|
53
|
+
for (const resource of resourceList) {
|
|
54
|
+
const resourceName = typeof resource === 'string' ? resource : resource.name;
|
|
55
|
+
for (const alias of resourceNameCandidates(resourceName)) {
|
|
56
|
+
const names = aliasResources.get(alias) ?? new Set();
|
|
57
|
+
names.add(resourceName);
|
|
58
|
+
aliasResources.set(alias, names);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return [...aliasResources.entries()]
|
|
62
|
+
.map(([alias, names]) => ({
|
|
63
|
+
alias,
|
|
64
|
+
resources: [...names].sort(),
|
|
65
|
+
}))
|
|
66
|
+
.filter((collision) => collision.resources.length > 1)
|
|
67
|
+
.sort((left, right) => left.alias.localeCompare(right.alias));
|
|
68
|
+
}
|
|
69
|
+
export function resourceAliasCollisionGroups(resources) {
|
|
70
|
+
const groups = new Map();
|
|
71
|
+
for (const collision of resourceAliasCollisions(resources)) {
|
|
72
|
+
const key = collision.resources.join('\0');
|
|
73
|
+
const group = groups.get(key) ?? {
|
|
74
|
+
alias: collision.alias,
|
|
75
|
+
aliases: [],
|
|
76
|
+
resources: collision.resources,
|
|
77
|
+
candidates: Object.fromEntries(collision.resources.map((resource) => [resource, resourceNameCandidates(resource)])),
|
|
78
|
+
};
|
|
79
|
+
group.aliases.push(collision.alias);
|
|
80
|
+
groups.set(key, group);
|
|
81
|
+
}
|
|
82
|
+
return [...groups.values()];
|
|
83
|
+
}
|
|
84
|
+
export function resourceConfigValue(values, resourceName) {
|
|
85
|
+
if (!values || typeof values !== 'object') {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
for (const candidate of resourceNameCandidates(resourceName)) {
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(values, candidate)) {
|
|
90
|
+
return values[candidate];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
export function singularResourceName(resourceName) {
|
|
96
|
+
const normalized = resourceName.toLowerCase();
|
|
97
|
+
if (IRREGULAR_SINGULARS.has(normalized)) {
|
|
98
|
+
return IRREGULAR_SINGULARS.get(normalized);
|
|
99
|
+
}
|
|
100
|
+
if (normalized.endsWith('ies') && normalized.length > 3) {
|
|
101
|
+
return `${resourceName.slice(0, -3)}y`;
|
|
102
|
+
}
|
|
103
|
+
if (normalized.endsWith('ses') || normalized.endsWith('xes') || normalized.endsWith('ches') || normalized.endsWith('shes')) {
|
|
104
|
+
return resourceName.slice(0, -2);
|
|
105
|
+
}
|
|
106
|
+
if (normalized.endsWith('s') && !normalized.endsWith('ss')) {
|
|
107
|
+
return resourceName.slice(0, -1);
|
|
108
|
+
}
|
|
109
|
+
return resourceName;
|
|
110
|
+
}
|
|
111
|
+
export function typeNameForResource(resourceName, kind = 'collection') {
|
|
112
|
+
const base = kind === 'collection' ? singularResourceName(resourceName) : resourceName;
|
|
113
|
+
return pascalCase(base);
|
|
114
|
+
}
|
|
115
|
+
export function routePathForResource(resourceName) {
|
|
116
|
+
return `/${kebabCase(resourceName)}`;
|
|
117
|
+
}
|
|
118
|
+
function words(value) {
|
|
119
|
+
return String(value)
|
|
120
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
121
|
+
.split(/[^A-Za-z0-9]+/)
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.map((part) => part.toLowerCase());
|
|
124
|
+
}
|
|
125
|
+
function unique(values) {
|
|
126
|
+
return [...new Set(values)];
|
|
127
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type OperationVariables = Record<string, unknown>;
|
|
2
|
+
export type OperationTemplateRecord = Record<string, unknown>;
|
|
3
|
+
export type OperationTemplate = string | OperationTemplateRecord;
|
|
4
|
+
export type NormalizedOperation = {
|
|
5
|
+
kind?: 'graphql';
|
|
6
|
+
name?: string;
|
|
7
|
+
ref?: string;
|
|
8
|
+
method?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
query?: string | OperationVariables;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
variables?: OperationVariables;
|
|
13
|
+
operationName?: string | null;
|
|
14
|
+
};
|
|
15
|
+
export type RegisteredOperation = NormalizedOperation & {
|
|
16
|
+
name: string;
|
|
17
|
+
ref: string;
|
|
18
|
+
};
|
|
19
|
+
export type OperationRequestResult = {
|
|
20
|
+
kind?: 'graphql';
|
|
21
|
+
ref?: string;
|
|
22
|
+
query?: string;
|
|
23
|
+
method?: string;
|
|
24
|
+
path?: string;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
variables?: OperationVariables;
|
|
27
|
+
operationName?: string | null;
|
|
28
|
+
};
|
|
29
|
+
export declare function normalizeOperationTemplate(input: OperationTemplate): NormalizedOperation;
|
|
30
|
+
export declare function canonicalOperation(input: OperationTemplate): NormalizedOperation;
|
|
31
|
+
export declare function operationRequest(input: OperationTemplate, variables?: OperationVariables): OperationRequestResult;
|
|
32
|
+
export declare function stableStringify(value: unknown): string;
|