@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
package/SPEC.md
ADDED
|
@@ -0,0 +1,1429 @@
|
|
|
1
|
+
# JSON Fixture DB Spec
|
|
2
|
+
|
|
3
|
+
## Schema And Type Generation
|
|
4
|
+
|
|
5
|
+
The `db/` folder can contain fixture data, schema/type definitions, or both.
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
db/
|
|
9
|
+
users.json optional seed data
|
|
10
|
+
posts.json optional seed data
|
|
11
|
+
settings.json optional singleton data
|
|
12
|
+
|
|
13
|
+
users.schema.json optional schema/type source (strict JSON)
|
|
14
|
+
users.schema.jsonc optional schema/type source (JSON with comments)
|
|
15
|
+
users.schema.mjs optional executable schema source
|
|
16
|
+
users.schema.js optional executable schema source when package type is module
|
|
17
|
+
posts.schema.jsonc
|
|
18
|
+
settings.schema.jsonc
|
|
19
|
+
|
|
20
|
+
.db/
|
|
21
|
+
state/
|
|
22
|
+
wal/
|
|
23
|
+
migrations/
|
|
24
|
+
schema.generated.json
|
|
25
|
+
types/
|
|
26
|
+
index.d.ts generated TypeScript declarations
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Projects can also opt into committed generated types:
|
|
30
|
+
|
|
31
|
+
```txt
|
|
32
|
+
src/generated/
|
|
33
|
+
db.types.d.ts committed generated types
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Developer Workflows
|
|
37
|
+
|
|
38
|
+
Developers can choose among data-first fixtures, schema/type-first fixtures, or mixed mode.
|
|
39
|
+
|
|
40
|
+
### Data-First Fixtures
|
|
41
|
+
|
|
42
|
+
The simplest path is a JSON fixture:
|
|
43
|
+
|
|
44
|
+
```txt
|
|
45
|
+
db/users.json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
[
|
|
50
|
+
{
|
|
51
|
+
"id": "u_1",
|
|
52
|
+
"name": "Ada Lovelace",
|
|
53
|
+
"email": "ada@example.com",
|
|
54
|
+
"role": "admin"
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The tool infers:
|
|
60
|
+
|
|
61
|
+
```txt
|
|
62
|
+
users collection
|
|
63
|
+
id field: id
|
|
64
|
+
fields: id, name, email, role
|
|
65
|
+
TypeScript type: User
|
|
66
|
+
REST routes
|
|
67
|
+
GraphQL fields
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Schema/Type-First Fixtures
|
|
71
|
+
|
|
72
|
+
Developers can define types without real data:
|
|
73
|
+
|
|
74
|
+
```txt
|
|
75
|
+
db/users.schema.jsonc
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
// Users who can sign into the local test app.
|
|
81
|
+
"kind": "collection",
|
|
82
|
+
"idField": "id",
|
|
83
|
+
|
|
84
|
+
"fields": {
|
|
85
|
+
"id": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"required": true,
|
|
88
|
+
"description": "Stable user id."
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
"name": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"required": true,
|
|
94
|
+
"description": "Display name shown in the UI."
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
"email": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"required": true,
|
|
100
|
+
"unique": true,
|
|
101
|
+
"pattern": "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$",
|
|
102
|
+
"description": "Unique email address."
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
"role": {
|
|
106
|
+
"type": "enum",
|
|
107
|
+
"values": ["admin", "user"],
|
|
108
|
+
"required": false,
|
|
109
|
+
"default": "user",
|
|
110
|
+
"description": "Local authorization role."
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
"seed": [
|
|
115
|
+
{
|
|
116
|
+
"id": "u_1",
|
|
117
|
+
"name": "Ada Lovelace",
|
|
118
|
+
"email": "ada@example.com",
|
|
119
|
+
"role": "admin"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This file acts as:
|
|
126
|
+
|
|
127
|
+
```txt
|
|
128
|
+
schema source
|
|
129
|
+
TypeScript source
|
|
130
|
+
REST/GraphQL source
|
|
131
|
+
optional default seed data
|
|
132
|
+
documentation source
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Field schemas may declare practical local constraints:
|
|
136
|
+
|
|
137
|
+
```txt
|
|
138
|
+
unique: true collection values must not repeat
|
|
139
|
+
min/max numeric lower and upper bounds
|
|
140
|
+
minLength/maxLength string or array length bounds
|
|
141
|
+
pattern JavaScript RegExp source for string values
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Mixed Mode
|
|
145
|
+
|
|
146
|
+
Developers can provide both a data fixture and a schema fixture:
|
|
147
|
+
|
|
148
|
+
```txt
|
|
149
|
+
db/users.json
|
|
150
|
+
db/users.schema.jsonc
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
In mixed mode:
|
|
154
|
+
|
|
155
|
+
```txt
|
|
156
|
+
users.schema.jsonc controls the type/schema
|
|
157
|
+
users.json controls the seed records
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
If the schema file also contains `seed`, that embedded seed is ignored in favor of
|
|
161
|
+
the data fixture. The CLI should warn and suggest unbundling the seed from the
|
|
162
|
+
schema source so mixed mode keeps contracts and seed records in separate files.
|
|
163
|
+
`async-db schema unbundle users` removes embedded seed from the schema source and
|
|
164
|
+
writes non-empty seed data to `db/users.json`. Empty schema-only seed is removed
|
|
165
|
+
without creating an empty fixture unless `--empty-seed` is passed. In-place JSONC
|
|
166
|
+
rewrites may lose comments, so the CLI should warn when it rewrites `.schema.jsonc`
|
|
167
|
+
without `--schema-out`.
|
|
168
|
+
|
|
169
|
+
`async-db schema bundle users` creates a portable schema-plus-seed artifact. Bundled
|
|
170
|
+
outputs should live outside the active fixture directory by default, such as
|
|
171
|
+
`artifacts/users.bundle.schema.json`, so they are not rediscovered as live schema
|
|
172
|
+
sources. Writing a bundle inside `db/` requires `--force`. Overwriting an existing
|
|
173
|
+
different seed or bundle output also requires `--force`.
|
|
174
|
+
|
|
175
|
+
`db.schema.mjs` at the project root is the canonical aggregate schema registry.
|
|
176
|
+
When present, it is authoritative for explicit schema definitions and
|
|
177
|
+
`db/**/*.schema.*` files are not auto-discovered as live schemas unless imported
|
|
178
|
+
by the root module. `async-db schema bundle --all --out db.schema.mjs` creates a
|
|
179
|
+
schema-only root registry without embedding seed/data fixtures. If a schema source
|
|
180
|
+
has embedded seed and no separate data fixture is loaded, aggregate bundle first
|
|
181
|
+
writes that seed to `db/<resource>.json` and leaves the root schema seed-free.
|
|
182
|
+
`async-db schema unbundle --all --schema-dir db` spreads a root registry back to
|
|
183
|
+
per-resource schema files and leaves seed/data fixtures untouched.
|
|
184
|
+
When bundling folder collection markers into a root registry, source globs are
|
|
185
|
+
rebased from the marker folder to the project root; for example,
|
|
186
|
+
`db/blog/index.schema.mjs` with `source: files('./**/*.mdx', { read: 'frontmatter' })`
|
|
187
|
+
becomes `source: files('./db/blog/**/*.mdx', { read: 'frontmatter' })` in
|
|
188
|
+
`db.schema.mjs`.
|
|
189
|
+
|
|
190
|
+
If the two disagree, the CLI reports the mismatch:
|
|
191
|
+
|
|
192
|
+
```txt
|
|
193
|
+
users.json has field "twitterHandle"
|
|
194
|
+
users.schema.jsonc does not define "twitterHandle"
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Default behavior should be permissive in local development:
|
|
198
|
+
|
|
199
|
+
```txt
|
|
200
|
+
warn and allow
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Configuration can enable stricter behavior:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
export default {
|
|
207
|
+
schema: {
|
|
208
|
+
unknownFields: 'warn', // "allow" | "warn" | "error"
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Type Generation
|
|
214
|
+
|
|
215
|
+
By default, generated TypeScript types are written to:
|
|
216
|
+
|
|
217
|
+
```txt
|
|
218
|
+
.db/types/index.d.ts
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Projects can customize the output location:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
export default {
|
|
225
|
+
dbDir: './db',
|
|
226
|
+
stateDir: './.db',
|
|
227
|
+
schemaOutFile: './src/generated/db.schema.json',
|
|
228
|
+
|
|
229
|
+
schemaManifest: {
|
|
230
|
+
customizeField({ fieldName, defaultManifest }) {
|
|
231
|
+
if (fieldName.endsWith('Markdown')) {
|
|
232
|
+
return {
|
|
233
|
+
...defaultManifest,
|
|
234
|
+
ui: {
|
|
235
|
+
...defaultManifest.ui,
|
|
236
|
+
component: 'markdown',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return defaultManifest;
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
types: {
|
|
246
|
+
enabled: true,
|
|
247
|
+
|
|
248
|
+
// Default gitignored output.
|
|
249
|
+
outFile: './.db/types/index.d.ts',
|
|
250
|
+
|
|
251
|
+
// Optional committed output.
|
|
252
|
+
// If set, generate the same types here too.
|
|
253
|
+
commitOutFile: './src/generated/db.types.d.ts',
|
|
254
|
+
|
|
255
|
+
// Optional.
|
|
256
|
+
useReadonly: false,
|
|
257
|
+
exportRuntimeHelpers: true,
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
This supports two common workflows.
|
|
263
|
+
|
|
264
|
+
### Gitignored Generated Types
|
|
265
|
+
|
|
266
|
+
Good for quick local development:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import type { DbTypes } from '../.db/types/index';
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Committed Generated Types
|
|
273
|
+
|
|
274
|
+
Better for apps and CI:
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import type { DbTypes } from './generated/db.types';
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
If the app relies on generated types, committing them is usually better because CI and other developers do not need to run `async-db sync` before TypeScript can resolve imports.
|
|
281
|
+
|
|
282
|
+
## Example Generated TypeScript
|
|
283
|
+
|
|
284
|
+
From `users.schema.jsonc`, generate something like this:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
export type UserRole = 'admin' | 'user';
|
|
288
|
+
|
|
289
|
+
export type User = {
|
|
290
|
+
/** Stable user id. */
|
|
291
|
+
id: string;
|
|
292
|
+
|
|
293
|
+
/** Display name shown in the UI. */
|
|
294
|
+
name: string;
|
|
295
|
+
|
|
296
|
+
/** Unique email address. */
|
|
297
|
+
email: string;
|
|
298
|
+
|
|
299
|
+
/** Local authorization role. */
|
|
300
|
+
role?: UserRole;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export type Settings = {
|
|
304
|
+
theme?: string;
|
|
305
|
+
locale?: string;
|
|
306
|
+
features?: {
|
|
307
|
+
billing?: boolean;
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
export type DbCollections = {
|
|
312
|
+
users: User;
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
export type DbDocuments = {
|
|
316
|
+
settings: Settings;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export type DbTypes = {
|
|
320
|
+
collections: DbCollections;
|
|
321
|
+
documents: DbDocuments;
|
|
322
|
+
};
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Package usage:
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
import { openDb } from '@async/db';
|
|
329
|
+
import type { DbTypes } from './generated/db.types';
|
|
330
|
+
|
|
331
|
+
const db = await openDb<DbTypes>({
|
|
332
|
+
dbDir: './db',
|
|
333
|
+
stateDir: './.db',
|
|
334
|
+
stores: {
|
|
335
|
+
default: 'json',
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const users = db.collection('users');
|
|
340
|
+
|
|
341
|
+
await users.create({
|
|
342
|
+
id: 'u_2',
|
|
343
|
+
name: 'Grace Hopper',
|
|
344
|
+
email: 'grace@example.com',
|
|
345
|
+
role: 'user',
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const user = users.get('u_2');
|
|
349
|
+
|
|
350
|
+
if (user) {
|
|
351
|
+
console.log(user.email);
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
Singleton document usage:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
const settings = db.document('settings');
|
|
359
|
+
|
|
360
|
+
await settings.set('/theme', 'dark');
|
|
361
|
+
|
|
362
|
+
const value = settings.get('/theme');
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## JavaScript Schema Sources
|
|
366
|
+
|
|
367
|
+
JSONC is useful, but a JavaScript schema file can be more expressive while staying simple.
|
|
368
|
+
|
|
369
|
+
```txt
|
|
370
|
+
db/users.schema.mjs
|
|
371
|
+
db/users.schema.js
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
```js
|
|
375
|
+
import { collection, field } from '@async/db/schema';
|
|
376
|
+
|
|
377
|
+
export default collection({
|
|
378
|
+
description: 'Users who can sign into the local test app.',
|
|
379
|
+
idField: 'id',
|
|
380
|
+
|
|
381
|
+
fields: {
|
|
382
|
+
id: field.string({
|
|
383
|
+
required: true,
|
|
384
|
+
description: 'Stable user id.',
|
|
385
|
+
}),
|
|
386
|
+
|
|
387
|
+
name: field.string({
|
|
388
|
+
required: true,
|
|
389
|
+
description: 'Display name shown in the UI.',
|
|
390
|
+
}),
|
|
391
|
+
|
|
392
|
+
email: field.string({
|
|
393
|
+
required: true,
|
|
394
|
+
description: 'Unique email address.',
|
|
395
|
+
}),
|
|
396
|
+
|
|
397
|
+
role: field.enum(['admin', 'user'], {
|
|
398
|
+
default: 'user',
|
|
399
|
+
description: 'Local authorization role.',
|
|
400
|
+
}),
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
seed: [
|
|
404
|
+
{
|
|
405
|
+
id: 'u_1',
|
|
406
|
+
name: 'Ada Lovelace',
|
|
407
|
+
email: 'ada@example.com',
|
|
408
|
+
role: 'admin',
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
This provides normal comments and a clean authoring API without requiring Node.js to load TypeScript files directly.
|
|
415
|
+
|
|
416
|
+
For v1, support:
|
|
417
|
+
|
|
418
|
+
```txt
|
|
419
|
+
.json
|
|
420
|
+
.jsonc
|
|
421
|
+
.csv
|
|
422
|
+
.schema.json
|
|
423
|
+
.schema.jsonc
|
|
424
|
+
.schema.mjs
|
|
425
|
+
.schema.js
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Use `.schema.js` only with normal Node ESM rules: the nearest `package.json` must declare `"type": "module"`. Avoid direct `.ts` schema sources in v1; projects that author schemas in TypeScript should compile to `.schema.js` or `.schema.mjs`.
|
|
429
|
+
|
|
430
|
+
## Source Readers
|
|
431
|
+
|
|
432
|
+
All built-in source loading should use the same reader pipeline:
|
|
433
|
+
|
|
434
|
+
```txt
|
|
435
|
+
.json data
|
|
436
|
+
.jsonc data
|
|
437
|
+
.csv data
|
|
438
|
+
.schema.json
|
|
439
|
+
.schema.jsonc
|
|
440
|
+
.schema.mjs
|
|
441
|
+
.schema.js
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
Projects may add `sources.readers` in `db.config.mjs` to parse custom files into raw db inputs:
|
|
445
|
+
|
|
446
|
+
```ts
|
|
447
|
+
type DbSourceReader = {
|
|
448
|
+
name: string;
|
|
449
|
+
match(context): boolean | Promise<boolean>;
|
|
450
|
+
read(context): DbSourceReaderResult | Promise<DbSourceReaderResult>;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
type DbSourceReaderResult =
|
|
454
|
+
| { kind: 'data'; data: unknown; format?: string; resourceName?: string }
|
|
455
|
+
| { kind: 'schema'; schema: unknown; format?: string; resourceName?: string }
|
|
456
|
+
| Array<DbSourceReaderResult>
|
|
457
|
+
| null;
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Custom readers run before built-in readers. Returning `null` lets later readers try; the first non-null result owns the file. Reader context includes repo-relative file path, absolute source path, parsed fixture path metadata, config, source hash, `readText()`, and `readBuffer()`.
|
|
461
|
+
|
|
462
|
+
Readers must return raw data or raw schema only. Resource normalization, diagnostics, type generation, schema manifest output, REST/GraphQL metadata, generated ids, and runtime sync stay centralized in db. A reader may return multiple sources from one file, but each result must include `resourceName`; otherwise db reports a structured diagnostic.
|
|
463
|
+
|
|
464
|
+
## Type-Only Fixtures
|
|
465
|
+
|
|
466
|
+
A schema file can define a resource without seed data.
|
|
467
|
+
|
|
468
|
+
```jsonc
|
|
469
|
+
{
|
|
470
|
+
// Audit events generated during local development.
|
|
471
|
+
"kind": "collection",
|
|
472
|
+
"idField": "id",
|
|
473
|
+
|
|
474
|
+
"fields": {
|
|
475
|
+
"id": {
|
|
476
|
+
"type": "string",
|
|
477
|
+
"required": true
|
|
478
|
+
},
|
|
479
|
+
"type": {
|
|
480
|
+
"type": "string",
|
|
481
|
+
"required": true
|
|
482
|
+
},
|
|
483
|
+
"createdAt": {
|
|
484
|
+
"type": "string",
|
|
485
|
+
"required": true
|
|
486
|
+
},
|
|
487
|
+
"payload": {
|
|
488
|
+
"type": "object",
|
|
489
|
+
"required": false,
|
|
490
|
+
"default": {}
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
|
|
494
|
+
"seed": []
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Generated runtime state:
|
|
499
|
+
|
|
500
|
+
```txt
|
|
501
|
+
.db/state/auditEvents.json
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```json
|
|
505
|
+
[]
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Generated TypeScript:
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
export type AuditEvent = {
|
|
512
|
+
id: string;
|
|
513
|
+
type: string;
|
|
514
|
+
createdAt: string;
|
|
515
|
+
payload?: Record<string, unknown>;
|
|
516
|
+
};
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Generated REST:
|
|
520
|
+
|
|
521
|
+
```txt
|
|
522
|
+
GET /audit-events
|
|
523
|
+
GET /audit-events/:id
|
|
524
|
+
POST /audit-events
|
|
525
|
+
PATCH /audit-events/:id
|
|
526
|
+
DELETE /audit-events/:id
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Generated GraphQL:
|
|
530
|
+
|
|
531
|
+
```graphql
|
|
532
|
+
type AuditEvent {
|
|
533
|
+
id: ID!
|
|
534
|
+
type: String
|
|
535
|
+
createdAt: String
|
|
536
|
+
payload: JSON
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Defaults
|
|
541
|
+
|
|
542
|
+
Defaults should be used in three places:
|
|
543
|
+
|
|
544
|
+
```txt
|
|
545
|
+
1. When creating new records through REST/GraphQL/package API.
|
|
546
|
+
2. When backfilling safe additive schema changes.
|
|
547
|
+
3. When initializing an empty runtime store.
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Example schema:
|
|
551
|
+
|
|
552
|
+
```jsonc
|
|
553
|
+
{
|
|
554
|
+
"kind": "collection",
|
|
555
|
+
"idField": "id",
|
|
556
|
+
"fields": {
|
|
557
|
+
"id": {
|
|
558
|
+
"type": "string",
|
|
559
|
+
"required": true
|
|
560
|
+
},
|
|
561
|
+
"name": {
|
|
562
|
+
"type": "string",
|
|
563
|
+
"required": true
|
|
564
|
+
},
|
|
565
|
+
"role": {
|
|
566
|
+
"type": "enum",
|
|
567
|
+
"values": ["admin", "user"],
|
|
568
|
+
"default": "user"
|
|
569
|
+
},
|
|
570
|
+
"active": {
|
|
571
|
+
"type": "boolean",
|
|
572
|
+
"default": true
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Creating a user:
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
async-db create users '{"id":"u_3","name":"Linus"}'
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
Stored result:
|
|
585
|
+
|
|
586
|
+
```json
|
|
587
|
+
{
|
|
588
|
+
"id": "u_3",
|
|
589
|
+
"name": "Linus",
|
|
590
|
+
"role": "user",
|
|
591
|
+
"active": true
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## Comments And Descriptions
|
|
596
|
+
|
|
597
|
+
JSON itself does not support comments, so support comments through one or both of these:
|
|
598
|
+
|
|
599
|
+
```txt
|
|
600
|
+
.schema.jsonc
|
|
601
|
+
.schema.mjs
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Comments are primarily for humans. For generated TypeScript and GraphQL docs, use machine-readable descriptions:
|
|
605
|
+
|
|
606
|
+
```jsonc
|
|
607
|
+
{
|
|
608
|
+
"email": {
|
|
609
|
+
"type": "string",
|
|
610
|
+
"description": "Unique email address used for login."
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Generate:
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
export type User = {
|
|
619
|
+
/** Unique email address used for login. */
|
|
620
|
+
email: string;
|
|
621
|
+
};
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
And GraphQL:
|
|
625
|
+
|
|
626
|
+
```graphql
|
|
627
|
+
type User {
|
|
628
|
+
"Unique email address used for login."
|
|
629
|
+
email: String
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## Config
|
|
634
|
+
|
|
635
|
+
Add this to `db.config.mjs`:
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
export default {
|
|
639
|
+
dbDir: './db',
|
|
640
|
+
stateDir: './.db',
|
|
641
|
+
|
|
642
|
+
sources: {
|
|
643
|
+
writePolicy: 'preserve',
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
stores: {
|
|
647
|
+
default: 'json',
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
types: {
|
|
651
|
+
enabled: true,
|
|
652
|
+
outFile: './.db/types/index.d.ts',
|
|
653
|
+
commitOutFile: './src/generated/db.types.d.ts',
|
|
654
|
+
useReadonly: false,
|
|
655
|
+
emitComments: true,
|
|
656
|
+
},
|
|
657
|
+
|
|
658
|
+
schema: {
|
|
659
|
+
source: 'auto', // "auto" | "data" | "schema"
|
|
660
|
+
allowJsonc: true,
|
|
661
|
+
unknownFields: 'warn', // "allow" | "warn" | "error"
|
|
662
|
+
additiveChanges: 'auto',
|
|
663
|
+
destructiveChanges: 'manual',
|
|
664
|
+
typeChanges: 'manual',
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
defaults: {
|
|
668
|
+
applyOnCreate: true,
|
|
669
|
+
applyOnSafeMigration: true,
|
|
670
|
+
},
|
|
671
|
+
|
|
672
|
+
collections: {
|
|
673
|
+
users: {
|
|
674
|
+
idField: 'id',
|
|
675
|
+
},
|
|
676
|
+
},
|
|
677
|
+
|
|
678
|
+
server: {
|
|
679
|
+
apiBase: '/__db',
|
|
680
|
+
dataPath: '/db',
|
|
681
|
+
host: '127.0.0.1',
|
|
682
|
+
port: 7331,
|
|
683
|
+
maxBodyBytes: 1048576,
|
|
684
|
+
},
|
|
685
|
+
|
|
686
|
+
rest: {
|
|
687
|
+
enabled: true,
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
graphql: {
|
|
691
|
+
enabled: true,
|
|
692
|
+
path: '/graphql',
|
|
693
|
+
},
|
|
694
|
+
};
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
Set `dbDir: './db'` to use `db/` instead of the default `db/` fixture folder. Existing `sourceDir` configs remain supported; if both are provided, `sourceDir` wins for backwards compatibility.
|
|
698
|
+
|
|
699
|
+
## CLI
|
|
700
|
+
|
|
701
|
+
Add type-specific commands:
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
async-db types
|
|
705
|
+
async-db types --watch
|
|
706
|
+
async-db types --out ./src/generated/db.types.d.ts
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
Add schema commands:
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
async-db schema
|
|
713
|
+
async-db schema users
|
|
714
|
+
async-db schema manifest --out ./src/generated/db.schema.json
|
|
715
|
+
async-db schema validate
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
`async-db sync` should also regenerate types and should write the committed schema manifest when `schemaOutFile` is configured.
|
|
719
|
+
|
|
720
|
+
Expected output:
|
|
721
|
+
|
|
722
|
+
```txt
|
|
723
|
+
Loaded db/users.schema.jsonc
|
|
724
|
+
Loaded db/posts.json
|
|
725
|
+
Generated .db/schema.generated.json
|
|
726
|
+
Generated .db/types/index.d.ts
|
|
727
|
+
Generated src/generated/db.types.d.ts
|
|
728
|
+
Generated src/generated/db.schema.json
|
|
729
|
+
Synced runtime store
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
## REST And GraphQL Runtime
|
|
733
|
+
|
|
734
|
+
The package should keep protocol-specific implementation in dedicated modules:
|
|
735
|
+
|
|
736
|
+
```txt
|
|
737
|
+
src/rest/
|
|
738
|
+
src/graphql/
|
|
739
|
+
src/web/
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
REST should expose generated collection and singleton document routes.
|
|
743
|
+
|
|
744
|
+
Collection and single-record reads should support selective response shapes without changing the REST-first model:
|
|
745
|
+
|
|
746
|
+
```txt
|
|
747
|
+
GET /posts?select=id,title
|
|
748
|
+
GET /posts?offset=0&limit=20
|
|
749
|
+
GET /posts/p1?select=id,title
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
`offset` must be a non-negative integer, `limit` must be a positive integer, and collection responses should remain arrays. Pagination is applied before projection.
|
|
753
|
+
|
|
754
|
+
Schema-backed scalar fields can declare explicit to-one relation metadata:
|
|
755
|
+
|
|
756
|
+
```jsonc
|
|
757
|
+
{
|
|
758
|
+
"authorId": {
|
|
759
|
+
"type": "string",
|
|
760
|
+
"required": true,
|
|
761
|
+
"relation": {
|
|
762
|
+
"name": "author",
|
|
763
|
+
"to": "authors",
|
|
764
|
+
"toField": "id",
|
|
765
|
+
"cardinality": "one"
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
Generated schema metadata should include normalized `relations` both on the resource and at the top level. REST should support depth-1 explicit to-one expansion:
|
|
772
|
+
|
|
773
|
+
```txt
|
|
774
|
+
GET /posts/p1?expand=author
|
|
775
|
+
GET /posts/p1?expand=author&select=id,title,author.name
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
`select=author.name` without `expand=author` should fail with a structured hint. Missing required relation targets should produce schema diagnostics; optional missing targets should expand as `null`.
|
|
779
|
+
|
|
780
|
+
REST should support sequential batch requests:
|
|
781
|
+
|
|
782
|
+
```txt
|
|
783
|
+
POST /__db/batch
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
```json
|
|
787
|
+
[
|
|
788
|
+
{
|
|
789
|
+
"method": "GET",
|
|
790
|
+
"path": "/users"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"method": "PATCH",
|
|
794
|
+
"path": "/settings",
|
|
795
|
+
"body": {
|
|
796
|
+
"theme": "dark"
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
]
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
REST batches are non-transactional by design. Items execute in order, and earlier successful writes remain committed if a later item fails.
|
|
803
|
+
|
|
804
|
+
Schema-backed writes should validate declared field types before mutating runtime state. Required fields, primitive types, enum values, arrays, nullable fields, datetime strings, flexible objects with intentional additional properties, nested objects, and field constraints (`unique`, `min`, `max`, `minLength`, `maxLength`, `pattern`) should be checked for package API writes, REST writes, GraphQL mutations, `async-db sync`, and `async-db schema validate`.
|
|
805
|
+
|
|
806
|
+
The root route should work as a discovery endpoint. API-style requests to `GET /` should return JSON with resource names plus links for the data viewer, schema endpoint, GraphQL endpoint, resource routes, and registered response formats. Browser-style requests that prefer `text/html` should return a small HTML index with those same links.
|
|
807
|
+
|
|
808
|
+
The local server should also expose a built-in dependency-free viewer:
|
|
809
|
+
|
|
810
|
+
```txt
|
|
811
|
+
GET /__db
|
|
812
|
+
GET /__db/manifest
|
|
813
|
+
GET /__db/manifest.json
|
|
814
|
+
GET /__db/manifest.html
|
|
815
|
+
GET /__db/manifest.md
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
`server.apiBase` should default to `/__db` and should configure the
|
|
819
|
+
standalone viewer, viewer manifest, schema, batch, import, events, log, and fork route base
|
|
820
|
+
without changing root REST resource routes or the standalone GraphQL path.
|
|
821
|
+
|
|
822
|
+
`server.dataPath` should default to `/db` and should mount an app-facing REST
|
|
823
|
+
resource alias. For a fixture at `db/users.json`, `GET /db/users.json` should
|
|
824
|
+
return the same synced runtime JSON resource as the REST resource route, not raw
|
|
825
|
+
static file contents. `GET /db/users.json?id=u_1` should return the same single
|
|
826
|
+
record shape as `GET /db/users/u_1.json`. Setting `server.dataPath: false`
|
|
827
|
+
should disable the alias while keeping scoped REST under `/__db/rest` and
|
|
828
|
+
standalone root REST routes.
|
|
829
|
+
|
|
830
|
+
The viewer manifest should be the shared JSON contract used by the built-in viewer and custom data viewers. `/manifest.json` should return JSON by default. `/manifest.html` should render a formatted JSON viewer with dark mode as the default, dark/light/system controls, copy, and pretty/raw formatting controls. `/manifest.md` should render Markdown with the manifest JSON in a fenced code block for AI clients. `/manifest` should choose among registered media types from `Accept`, and explicit `/manifest.<extension>` routes should use the matching registered response format. The manifest should include API links, capabilities, diagnostics, configured viewer links, response format metadata, collections, documents, field metadata, UI hints, and relation hints. It must not include seed records, source paths, source hashes, runtime state paths, or GraphQL SDL. Custom viewers should use the manifest for UI metadata and fetch actual records from REST or GraphQL.
|
|
831
|
+
|
|
832
|
+
The viewer should support:
|
|
833
|
+
|
|
834
|
+
```txt
|
|
835
|
+
resource list
|
|
836
|
+
collection table viewer
|
|
837
|
+
singleton document JSON viewer
|
|
838
|
+
selected JSON copy
|
|
839
|
+
CSV drag-and-drop import into the configured fixture folder
|
|
840
|
+
REST route specs with copy/paste examples
|
|
841
|
+
REST request runner
|
|
842
|
+
GraphQL SDL viewer
|
|
843
|
+
GraphQL query and mutation examples
|
|
844
|
+
GraphQL runner with variables
|
|
845
|
+
schema and field inspection
|
|
846
|
+
diagnostics summary
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
The CLI should include a fixture health check:
|
|
850
|
+
|
|
851
|
+
```txt
|
|
852
|
+
async-db doctor
|
|
853
|
+
async-db doctor --json
|
|
854
|
+
async-db doctor --strict
|
|
855
|
+
async-db check --strict
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
`doctor` should include existing source/schema diagnostics and advisory fixture findings. It should detect duplicate collection ids, mixed id value types, inconsistent field value types, likely relation fields such as `todos.userId -> users.id`, and likely relation values missing from a target collection. Relation inference must be suggestive only; it must not mutate fixtures, write schema files, or silently change REST/GraphQL shape behavior. `doctor` should exit nonzero for error diagnostics, while `--strict` should also exit nonzero for warnings. Informational relation suggestions should not fail strict mode.
|
|
859
|
+
|
|
860
|
+
CSV data-first fixtures should be treated as collections. The first row is the header row, headers become JSON field names, values are parsed into records, and the default JSON store is written as `.db/state/<resource>.json`. When a CSV data file is paired with a schema file, schema-declared array fields should coerce semicolon-delimited cells and JSON array string cells into arrays before validation and store hydration.
|
|
861
|
+
|
|
862
|
+
Collection fixtures should always have an id field. If a JSON/JSONC/CSV collection source omits `id`, generate counter ids in the selected runtime store, starting at `"1"` and avoiding existing ids. Source files stay unchanged by default. For resources bound to the `sourceFile` store, write generated ids back to plain `.json` fixtures.
|
|
863
|
+
|
|
864
|
+
Runtime stores should track source hashes for JSON, JSONC, and CSV files. If a source hash changes during sync, regenerate runtime state for that resource from the source fixture. If the hash is unchanged, preserve runtime edits.
|
|
865
|
+
|
|
866
|
+
The viewer should support uploading a CSV through:
|
|
867
|
+
|
|
868
|
+
```txt
|
|
869
|
+
POST /__db/import
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
The upload should copy the CSV into the configured fixture folder, run sync, reload the in-memory resources, update the URL query parameter to the imported resource, and reload the dashboard view.
|
|
873
|
+
|
|
874
|
+
While serving, db should watch the configured fixture folder for fixture and schema changes, ignoring `.db/`. On change, reload resources and notify the single-file viewer through the configured events route, defaulting to `/__db/events`, so the dashboard refreshes automatically. If one source file fails to parse or load, report a file-specific diagnostic in the viewer and keep the remaining valid resources available. If the runtime cannot create a file watcher because of environment resource limits such as `EMFILE` or `ENOSPC`, keep the HTTP server running, publish a warning diagnostic, and serve without live reload.
|
|
875
|
+
|
|
876
|
+
Vite development should be supported through a dependency-light plugin export:
|
|
877
|
+
|
|
878
|
+
```js
|
|
879
|
+
import { dbPlugin } from '@async/db/vite';
|
|
880
|
+
|
|
881
|
+
export default {
|
|
882
|
+
plugins: [dbPlugin()],
|
|
883
|
+
};
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
The plugin should return a plain Vite-compatible plugin object with `apply: 'serve'`, mount @async/db into the existing Vite dev middleware stack, and avoid bundling Node-only fixture runtime code into production builds. By default, Vite dev routes should be scoped under `/__db` and should not answer root app routes. A plugin-level `apiBase` should win over `server.apiBase`:
|
|
887
|
+
|
|
888
|
+
```txt
|
|
889
|
+
GET /db/users.json
|
|
890
|
+
GET /__db
|
|
891
|
+
GET /__db/schema
|
|
892
|
+
POST /__db/batch
|
|
893
|
+
POST /__db/graphql
|
|
894
|
+
GET /__db/rest/users
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
Standalone `async-db serve` should keep root REST routes such as `/users` and `/graphql`. The Vite plugin may opt into root REST routes with `rootRoutes: true`.
|
|
898
|
+
|
|
899
|
+
GraphQL should support a dependency-free subset suitable for local app development:
|
|
900
|
+
|
|
901
|
+
```graphql
|
|
902
|
+
query GetUser($id: ID!) {
|
|
903
|
+
allUsers: users {
|
|
904
|
+
id
|
|
905
|
+
displayName: name
|
|
906
|
+
}
|
|
907
|
+
ada: user(id: $id) {
|
|
908
|
+
email
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
Supported GraphQL behavior:
|
|
914
|
+
|
|
915
|
+
```txt
|
|
916
|
+
queries
|
|
917
|
+
mutations
|
|
918
|
+
root and nested aliases
|
|
919
|
+
variables
|
|
920
|
+
operationName selection for multi-operation documents
|
|
921
|
+
__typename meta fields
|
|
922
|
+
named fragments and inline fragments
|
|
923
|
+
@include and @skip executable directives
|
|
924
|
+
object/list/scalar input values
|
|
925
|
+
collection list queries
|
|
926
|
+
collection single-record queries by id
|
|
927
|
+
collection create/update/delete mutations
|
|
928
|
+
singleton document queries
|
|
929
|
+
singleton document update/set mutations
|
|
930
|
+
selection-set projection
|
|
931
|
+
minimal __schema and __type introspection
|
|
932
|
+
HTTP batching by posting an array to /graphql
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
Unsupported in the dependency-free v1 subset:
|
|
936
|
+
|
|
937
|
+
```txt
|
|
938
|
+
subscriptions
|
|
939
|
+
full spec introspection coverage
|
|
940
|
+
general-purpose GraphQL validation
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
## Repo Example Launcher
|
|
944
|
+
|
|
945
|
+
The repo should include an npm task that starts every example database and serves an index page of viewer links:
|
|
946
|
+
|
|
947
|
+
```bash
|
|
948
|
+
npm run examples
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
The index page should list each example and link to:
|
|
952
|
+
|
|
953
|
+
```txt
|
|
954
|
+
/__db
|
|
955
|
+
/__db/schema
|
|
956
|
+
/graphql
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
Examples should range from basic to advanced:
|
|
960
|
+
|
|
961
|
+
```txt
|
|
962
|
+
examples/basic
|
|
963
|
+
examples/data-first
|
|
964
|
+
examples/schema-first
|
|
965
|
+
examples/advanced
|
|
966
|
+
```
|
|
967
|
+
|
|
968
|
+
## Client API
|
|
969
|
+
|
|
970
|
+
Provide a small HTTP client for consuming db from apps and tests:
|
|
971
|
+
|
|
972
|
+
```ts
|
|
973
|
+
import { createDbClient } from '@async/db/client';
|
|
974
|
+
|
|
975
|
+
const client = createDbClient({
|
|
976
|
+
baseUrl: 'http://127.0.0.1:7331',
|
|
977
|
+
restBasePath: '',
|
|
978
|
+
batching: {
|
|
979
|
+
enabled: true,
|
|
980
|
+
delayMs: 0,
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
The client should support:
|
|
986
|
+
|
|
987
|
+
```txt
|
|
988
|
+
client.graphql(query, variables)
|
|
989
|
+
client.graphql.batch(requests)
|
|
990
|
+
client.rest(method, path, body)
|
|
991
|
+
client.rest.batch(requests)
|
|
992
|
+
optional scoped REST base paths for embedded dev servers
|
|
993
|
+
optional automatic batching for individual GraphQL and REST calls
|
|
994
|
+
10ms default automatic batching window
|
|
995
|
+
read-safe dedupe for identical REST GET and GraphQL query requests
|
|
996
|
+
explicit dedupe: 'all' opt-in for deduping writes and mutations
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
Local mock behavior should support latency and chaos errors:
|
|
1000
|
+
|
|
1001
|
+
```js
|
|
1002
|
+
export default {
|
|
1003
|
+
mock: {
|
|
1004
|
+
delay: [50, 300],
|
|
1005
|
+
errors: {
|
|
1006
|
+
rate: 0.05,
|
|
1007
|
+
status: 503,
|
|
1008
|
+
message: 'Random local mock failure',
|
|
1009
|
+
},
|
|
1010
|
+
},
|
|
1011
|
+
};
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
## Error Messages
|
|
1015
|
+
|
|
1016
|
+
Errors should be readable by humans and useful to AI agents. They should explain:
|
|
1017
|
+
|
|
1018
|
+
```txt
|
|
1019
|
+
what failed
|
|
1020
|
+
where it failed
|
|
1021
|
+
what value was received when useful
|
|
1022
|
+
what values or commands are valid
|
|
1023
|
+
what to try next
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
REST and server errors should use this shape:
|
|
1027
|
+
|
|
1028
|
+
```json
|
|
1029
|
+
{
|
|
1030
|
+
"error": {
|
|
1031
|
+
"code": "REST_BATCH_INVALID_PATH",
|
|
1032
|
+
"message": "REST batch path must start with \"/\": users",
|
|
1033
|
+
"hint": "Use absolute local paths such as \"/users\", \"/settings\", or \"/__db/schema\".",
|
|
1034
|
+
"details": {
|
|
1035
|
+
"path": "users"
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
GraphQL errors should use standard GraphQL `errors[]` entries with `extensions`:
|
|
1042
|
+
|
|
1043
|
+
```json
|
|
1044
|
+
{
|
|
1045
|
+
"data": null,
|
|
1046
|
+
"errors": [
|
|
1047
|
+
{
|
|
1048
|
+
"message": "Unknown GraphQL query field \"nope\".",
|
|
1049
|
+
"extensions": {
|
|
1050
|
+
"code": "GRAPHQL_UNKNOWN_QUERY_FIELD",
|
|
1051
|
+
"hint": "Use one of: \"users\", \"user\".",
|
|
1052
|
+
"details": {
|
|
1053
|
+
"field": "nope",
|
|
1054
|
+
"availableFields": ["users", "user"]
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
]
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
## Codex Prompt Add-On
|
|
1063
|
+
|
|
1064
|
+
Append this to the Codex prompt:
|
|
1065
|
+
|
|
1066
|
+
````md
|
|
1067
|
+
## Type generation and schema-only fixtures
|
|
1068
|
+
|
|
1069
|
+
Add automatic TypeScript type generation.
|
|
1070
|
+
|
|
1071
|
+
By default, generated types should be written to:
|
|
1072
|
+
|
|
1073
|
+
```txt
|
|
1074
|
+
.db/types/index.d.ts
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
Also support a configurable committed output file:
|
|
1078
|
+
|
|
1079
|
+
```js
|
|
1080
|
+
export default {
|
|
1081
|
+
types: {
|
|
1082
|
+
enabled: true,
|
|
1083
|
+
outFile: './.db/types/index.d.ts',
|
|
1084
|
+
commitOutFile: './src/generated/db.types.d.ts',
|
|
1085
|
+
emitComments: true,
|
|
1086
|
+
useReadonly: false
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
If `commitOutFile` is set, generate the same TypeScript types there so users can import and commit them.
|
|
1092
|
+
|
|
1093
|
+
The generated file should export:
|
|
1094
|
+
|
|
1095
|
+
```ts
|
|
1096
|
+
export type DbCollections = {};
|
|
1097
|
+
export type DbDocuments = {};
|
|
1098
|
+
export type DbTypes = {
|
|
1099
|
+
collections: DbCollections;
|
|
1100
|
+
documents: DbDocuments;
|
|
1101
|
+
};
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
For each collection, generate a record type:
|
|
1105
|
+
|
|
1106
|
+
```ts
|
|
1107
|
+
export type User = {
|
|
1108
|
+
id: string;
|
|
1109
|
+
name: string;
|
|
1110
|
+
email: string;
|
|
1111
|
+
role?: 'admin' | 'user';
|
|
1112
|
+
};
|
|
1113
|
+
```
|
|
1114
|
+
|
|
1115
|
+
Use schema field descriptions to emit JSDoc comments.
|
|
1116
|
+
|
|
1117
|
+
## Schema manifest output and model-driven admin UIs
|
|
1118
|
+
|
|
1119
|
+
Add optional JSON schema manifest generation for local-first admin/CMS UIs that render forms from db models instead of duplicating per-resource form configuration.
|
|
1120
|
+
|
|
1121
|
+
This is separate from `.db/schema.generated.json`. The existing generated schema file remains runtime/server metadata and may include diagnostics, source paths, seeds, REST route lists, and GraphQL SDL. The committed manifest is a small importable artifact for applications.
|
|
1122
|
+
|
|
1123
|
+
Configure it with:
|
|
1124
|
+
|
|
1125
|
+
```js
|
|
1126
|
+
export default {
|
|
1127
|
+
schemaOutFile: './src/generated/db.schema.json',
|
|
1128
|
+
};
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
When `schemaOutFile` is set, `async-db sync` writes the manifest. The CLI can also write one directly:
|
|
1132
|
+
|
|
1133
|
+
```bash
|
|
1134
|
+
async-db schema manifest --out ./src/generated/db.schema.json
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
Custom viewer UIs can use the live `GET /__db/manifest.json` route or a committed viewer manifest. Browser users can open `GET /__db/manifest.html`, AI clients can open `GET /__db/manifest.md`, and `GET /__db/manifest` negotiates from registered `Accept` media types:
|
|
1138
|
+
|
|
1139
|
+
```js
|
|
1140
|
+
export default {
|
|
1141
|
+
viewerManifestOutFile: './src/generated/db.viewer.json',
|
|
1142
|
+
server: {
|
|
1143
|
+
viewerLinks: [
|
|
1144
|
+
{ label: 'App Data Viewer', href: 'http://127.0.0.1:5173/db' },
|
|
1145
|
+
],
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
```bash
|
|
1151
|
+
async-db viewer manifest --out ./src/generated/db.viewer.json
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
The manifest should have this top-level shape:
|
|
1155
|
+
|
|
1156
|
+
```json
|
|
1157
|
+
{
|
|
1158
|
+
"version": 1,
|
|
1159
|
+
"collections": {},
|
|
1160
|
+
"documents": {}
|
|
1161
|
+
}
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
Each resource entry should include `kind`, `name`, `idField` for collections, optional `description`, and `fields`. Each field should include normalized field metadata such as `type`, `required`, `nullable`, `default`, `values`, nested object `fields`, array `items`, `relation`, constraints, and inferred `ui` defaults.
|
|
1165
|
+
|
|
1166
|
+
The manifest must not include seed records, source hashes, source paths, runtime state, diagnostics, REST route lists, or GraphQL SDL.
|
|
1167
|
+
|
|
1168
|
+
Default UI inference should be deterministic and safe:
|
|
1169
|
+
|
|
1170
|
+
```txt
|
|
1171
|
+
boolean -> toggle
|
|
1172
|
+
small enum -> radio
|
|
1173
|
+
larger enum -> select
|
|
1174
|
+
email-like field name -> email
|
|
1175
|
+
url-like field name -> url
|
|
1176
|
+
image/avatar/photo-like field name -> image
|
|
1177
|
+
description/body/content/notes/bio/markdown-like field name -> textarea
|
|
1178
|
+
array<string> -> tags
|
|
1179
|
+
array<enum> -> multiSelect
|
|
1180
|
+
object with declared fields -> fieldset
|
|
1181
|
+
open object or unknown field -> json
|
|
1182
|
+
relation field -> relationSelect with optionsFrom
|
|
1183
|
+
collection id field -> readonly
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
Manifest defaults are metadata only. They must not change fixtures, seed data, runtime state, validation, REST, or GraphQL behavior.
|
|
1187
|
+
|
|
1188
|
+
Apps can customize or omit field entries with a visitor hook:
|
|
1189
|
+
|
|
1190
|
+
```js
|
|
1191
|
+
export default {
|
|
1192
|
+
schemaManifest: {
|
|
1193
|
+
customizeField({ field, fieldName, resource, resourceName, path, file, sourceFile, defaultManifest }) {
|
|
1194
|
+
if (resourceName === 'users' && fieldName === 'passwordHash') {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
if (fieldName.endsWith('Markdown')) {
|
|
1199
|
+
return {
|
|
1200
|
+
...defaultManifest,
|
|
1201
|
+
ui: {
|
|
1202
|
+
...defaultManifest.ui,
|
|
1203
|
+
component: 'markdown',
|
|
1204
|
+
},
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return defaultManifest;
|
|
1209
|
+
},
|
|
1210
|
+
},
|
|
1211
|
+
};
|
|
1212
|
+
```
|
|
1213
|
+
|
|
1214
|
+
The visitor return value must be JSON-serializable. Functions, classes, symbols, bigint values, non-finite numbers, and non-plain objects should fail generation with a diagnostic that includes resource and field path. Returning `null` omits the field from the manifest.
|
|
1215
|
+
|
|
1216
|
+
The intended first use is permissioned admin CRUD for resources such as dashboards, users, and permission policies. Admin screens can map manifest field metadata to reusable create/edit/view components while policy checks decide whether fields are hidden, readonly, or editable for a given session.
|
|
1217
|
+
|
|
1218
|
+
Support schema-only fixtures.
|
|
1219
|
+
|
|
1220
|
+
The package should accept these source formats:
|
|
1221
|
+
|
|
1222
|
+
```txt
|
|
1223
|
+
db/users.json data-first fixture
|
|
1224
|
+
db/users.jsonc data-first fixture with comments
|
|
1225
|
+
db/users.csv data-first collection fixture
|
|
1226
|
+
db/users.schema.jsonc schema/type-first fixture
|
|
1227
|
+
db/users.schema.mjs schema/type-first fixture using JS helpers
|
|
1228
|
+
db/users.schema.js schema/type-first fixture using JS helpers in type: module projects
|
|
1229
|
+
```
|
|
1230
|
+
|
|
1231
|
+
The main source JSON/JSONC/CSV fixture can be used to infer schema and generate types.
|
|
1232
|
+
|
|
1233
|
+
A `.schema.jsonc` file can define a resource without seed data:
|
|
1234
|
+
|
|
1235
|
+
```jsonc
|
|
1236
|
+
{
|
|
1237
|
+
// Users who can sign into the local test app.
|
|
1238
|
+
"kind": "collection",
|
|
1239
|
+
"idField": "id",
|
|
1240
|
+
"fields": {
|
|
1241
|
+
"id": {
|
|
1242
|
+
"type": "string",
|
|
1243
|
+
"required": true,
|
|
1244
|
+
"description": "Stable user id."
|
|
1245
|
+
},
|
|
1246
|
+
"role": {
|
|
1247
|
+
"type": "enum",
|
|
1248
|
+
"values": ["admin", "user"],
|
|
1249
|
+
"default": "user",
|
|
1250
|
+
"description": "Local authorization role."
|
|
1251
|
+
}
|
|
1252
|
+
},
|
|
1253
|
+
"seed": []
|
|
1254
|
+
}
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
Support `.schema.mjs` and `.schema.js` files for richer authoring:
|
|
1258
|
+
|
|
1259
|
+
```js
|
|
1260
|
+
import { collection, field } from '@async/db/schema';
|
|
1261
|
+
|
|
1262
|
+
export default collection({
|
|
1263
|
+
description: 'Users who can sign into the local test app.',
|
|
1264
|
+
idField: 'id',
|
|
1265
|
+
fields: {
|
|
1266
|
+
id: field.string({
|
|
1267
|
+
required: true,
|
|
1268
|
+
description: 'Stable user id.'
|
|
1269
|
+
}),
|
|
1270
|
+
role: field.enum(['admin', 'user'], {
|
|
1271
|
+
default: 'user',
|
|
1272
|
+
description: 'Local authorization role.'
|
|
1273
|
+
})
|
|
1274
|
+
},
|
|
1275
|
+
seed: []
|
|
1276
|
+
});
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
Support a root `db.schema.mjs` or `db.schema.js` registry for one-file schema authoring:
|
|
1280
|
+
|
|
1281
|
+
```js
|
|
1282
|
+
import { collection, field } from '@async/db/schema';
|
|
1283
|
+
|
|
1284
|
+
export default {
|
|
1285
|
+
users: collection({
|
|
1286
|
+
idField: 'id',
|
|
1287
|
+
fields: {
|
|
1288
|
+
id: field.string({ required: true }),
|
|
1289
|
+
firstName: field.string(),
|
|
1290
|
+
lastName: field.string(),
|
|
1291
|
+
fullName: field.computed(field.string(), function users_fullName_resolver({ record }) {
|
|
1292
|
+
return `${record.firstName} ${record.lastName}`;
|
|
1293
|
+
})
|
|
1294
|
+
}
|
|
1295
|
+
})
|
|
1296
|
+
};
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
`field.computed(type, fn)` is shorthand for `{ resolve: fn }`. Normal function
|
|
1300
|
+
resolvers are invoked with `this` bound to a delegated runtime resolver context.
|
|
1301
|
+
The context exposes `this.get(name)`, `this.has(name)`, direct property aliases,
|
|
1302
|
+
and `this._internal` for the unoverridden internal view. Internal values include
|
|
1303
|
+
`db`, `resource`, `field`, `fieldName`, `config`, `services`, `cache`, `value`,
|
|
1304
|
+
`record`, `records`, and `args`. App-provided context values win over internal
|
|
1305
|
+
values with the same key. Schema/type/manifest/doctor/bundle/unbundle/generate
|
|
1306
|
+
commands may import trusted schema modules for metadata, but must not call
|
|
1307
|
+
computed resolvers.
|
|
1308
|
+
|
|
1309
|
+
The package API should expose `loadDbSchema({ from })` for metadata-only schema
|
|
1310
|
+
loading from a project root, `db/` folder, `db.schema.mjs`, or individual schema
|
|
1311
|
+
file. `db.schema.js` follows the same locator rules when the project uses `"type": "module"`. Loaded schemas expose `schema.validator(resource, options)` for endpoint
|
|
1312
|
+
input validation and `schema.resolver(resourceOrField, options)` for direct
|
|
1313
|
+
computed field execution. Validators reject computed/read-only fields, default
|
|
1314
|
+
unknown fields to `error`, and support `strip`, `allow`, `warn`, and patch/replace
|
|
1315
|
+
validation modes. `openDb({ schema })` accepts a loaded schema object and opens
|
|
1316
|
+
runtime stores from the same locator.
|
|
1317
|
+
|
|
1318
|
+
Folder-backed content collections use `index.schema.mjs` or `index.schema.js` as an explicit marker:
|
|
1319
|
+
|
|
1320
|
+
```txt
|
|
1321
|
+
db/docs/index.schema.mjs
|
|
1322
|
+
db/docs/index.schema.js
|
|
1323
|
+
db/docs/intro.mdx
|
|
1324
|
+
```
|
|
1325
|
+
|
|
1326
|
+
The resource name comes from the containing folder. Folder collections require an
|
|
1327
|
+
explicit `source: files(pattern, { read })` declaration. Runtime store behavior
|
|
1328
|
+
belongs in `db.config.mjs` through `resources.<name>.store`; use `store: 'static'`
|
|
1329
|
+
there when file-backed content should be read-only. Core only parses frontmatter
|
|
1330
|
+
plus raw `.md` / `.mdx` body text. MDX compilation remains app-owned.
|
|
1331
|
+
|
|
1332
|
+
Do not require TypeScript execution for schema files in v1. Use `.mjs` for package-type-independent executable schema definitions, or compile TypeScript-authored schema files to `.schema.js` / `.schema.mjs`.
|
|
1333
|
+
|
|
1334
|
+
Rules:
|
|
1335
|
+
|
|
1336
|
+
1. If only `users.json` exists, infer schema from data.
|
|
1337
|
+
2. If only `users.schema.json`, `users.schema.jsonc`, `users.schema.mjs`, or `users.schema.js` exists, create the collection from schema and optional seed/default data.
|
|
1338
|
+
3. If both `users.json` and `users.schema.*` exist, the schema file is authoritative for types and validation, while the JSON file provides seed data.
|
|
1339
|
+
4. Additive fields are safe and automatic.
|
|
1340
|
+
5. Removed fields and type changes require explicit approval.
|
|
1341
|
+
6. Defaults should apply when creating records and when safely backfilling additive fields.
|
|
1342
|
+
7. Generated TypeScript types should update during `async-db sync`, `async-db types`, and service startup when needed.
|
|
1343
|
+
|
|
1344
|
+
Add CLI commands:
|
|
1345
|
+
|
|
1346
|
+
```bash
|
|
1347
|
+
async-db types
|
|
1348
|
+
async-db types --watch
|
|
1349
|
+
async-db types --out ./src/generated/db.types.d.ts
|
|
1350
|
+
async-db schema
|
|
1351
|
+
async-db schema validate
|
|
1352
|
+
async-db schema unbundle users
|
|
1353
|
+
async-db schema unbundle --all --schema-dir db
|
|
1354
|
+
async-db schema bundle users --out artifacts/users.bundle.schema.json
|
|
1355
|
+
async-db schema bundle --all --out db.schema.mjs
|
|
1356
|
+
async-db generate hono
|
|
1357
|
+
async-db generate hono --api rest,graphql --out ./server
|
|
1358
|
+
async-db generate hono --api none --app module
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
## Hono And SQLite Starter Generation
|
|
1362
|
+
|
|
1363
|
+
Add `async-db generate hono` for graduating a fixture-backed app into a starter API backed by SQLite.
|
|
1364
|
+
|
|
1365
|
+
Default behavior:
|
|
1366
|
+
|
|
1367
|
+
```txt
|
|
1368
|
+
outDir: ./db-api
|
|
1369
|
+
api: rest
|
|
1370
|
+
db: sqlite
|
|
1371
|
+
app: standalone
|
|
1372
|
+
runtime: node-sqlite
|
|
1373
|
+
seed: false
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
Generated output should be TypeScript-first and include a portable repository interface, SQLite adapter using `node:sqlite`, validators, initial SQL migration, and optional Hono REST/GraphQL route modules. Standalone output should include `package.json`, `tsconfig.json`, `src/app.ts`, and `src/server.ts`.
|
|
1377
|
+
|
|
1378
|
+
API selection:
|
|
1379
|
+
|
|
1380
|
+
```bash
|
|
1381
|
+
async-db generate hono --api rest
|
|
1382
|
+
async-db generate hono --api graphql
|
|
1383
|
+
async-db generate hono --api rest,graphql
|
|
1384
|
+
async-db generate hono --api none
|
|
1385
|
+
```
|
|
1386
|
+
|
|
1387
|
+
SQLite generation rules:
|
|
1388
|
+
|
|
1389
|
+
```txt
|
|
1390
|
+
collections -> SQLite tables with id TEXT PRIMARY KEY
|
|
1391
|
+
documents -> _db_documents(name TEXT PRIMARY KEY, value TEXT)
|
|
1392
|
+
string/enum -> TEXT
|
|
1393
|
+
number -> REAL
|
|
1394
|
+
boolean -> INTEGER
|
|
1395
|
+
object/array/unknown -> JSON text in TEXT columns
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
Generation should fail on schema errors. For production SQLite output, warning diagnostics should also block generation unless `--allow-warnings` is provided. Seed insertion is disabled by default; `--seed fixtures` can emit fixture seed support for local SQLite mimicry.
|
|
1399
|
+
|
|
1400
|
+
Keep Hono and SQLite runtime support isolated under optional exports:
|
|
1401
|
+
|
|
1402
|
+
```txt
|
|
1403
|
+
db/hono
|
|
1404
|
+
db/sqlite
|
|
1405
|
+
```
|
|
1406
|
+
|
|
1407
|
+
The core package must not add mandatory Hono or SQLite npm dependencies.
|
|
1408
|
+
|
|
1409
|
+
Acceptance criteria:
|
|
1410
|
+
|
|
1411
|
+
* Data-first fixtures generate TypeScript types.
|
|
1412
|
+
* Schema-only fixtures generate TypeScript types.
|
|
1413
|
+
* JSONC schema comments are allowed.
|
|
1414
|
+
* Field descriptions become JSDoc in generated TypeScript.
|
|
1415
|
+
* `types.outFile` writes to `.db/types/index.d.ts` by default.
|
|
1416
|
+
* `types.commitOutFile` writes to a custom importable location.
|
|
1417
|
+
* Package API can be typed with the generated `DbTypes`.
|
|
1418
|
+
````
|
|
1419
|
+
|
|
1420
|
+
The intended developer loop is:
|
|
1421
|
+
|
|
1422
|
+
```txt
|
|
1423
|
+
create/edit JSON or schema fixtures
|
|
1424
|
+
run async-db sync
|
|
1425
|
+
types are generated
|
|
1426
|
+
REST and GraphQL are generated
|
|
1427
|
+
runtime store is updated
|
|
1428
|
+
source files stay clean unless writeback is requested
|
|
1429
|
+
```
|