@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,93 @@
|
|
|
1
|
+
# Computed Fields Example
|
|
2
|
+
|
|
3
|
+
## What This Teaches
|
|
4
|
+
|
|
5
|
+
Use this when you want to see several ways to model computed fields with `.schema.mjs`. Computed fields are read-only projections: stored fixture records stay small, and REST or GraphQL resolves the computed value only when a client selects it.
|
|
6
|
+
|
|
7
|
+
## Files To Inspect
|
|
8
|
+
|
|
9
|
+
- [db/users.schema.mjs](./db/users.schema.mjs): `field.computed(type, function)` shorthand using `this.value`.
|
|
10
|
+
- [db/posts.schema.mjs](./db/posts.schema.mjs): object form with `resolveMany` for reading time.
|
|
11
|
+
- [db/products.schema.mjs](./db/products.schema.mjs): arrow resolver for simple price formatting.
|
|
12
|
+
- [db/orders.schema.mjs](./db/orders.schema.mjs): normal function resolver that uses `this.get('db')` to read product prices.
|
|
13
|
+
- [src/generated/db.types.d.ts](./src/generated/db.types.d.ts): committed generated types where computed fields are optional read-only projections.
|
|
14
|
+
|
|
15
|
+
## Run It
|
|
16
|
+
|
|
17
|
+
From the repository root:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm run db -- sync --cwd ./examples/computed-fields
|
|
21
|
+
npm run db -- serve --cwd ./examples/computed-fields
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Open the viewer:
|
|
25
|
+
|
|
26
|
+
```txt
|
|
27
|
+
http://127.0.0.1:7331/__db
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## REST Requests To Try
|
|
31
|
+
|
|
32
|
+
Default reads return stored fields only:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl http://127.0.0.1:7331/db/orders.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Select computed fields when you need them:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl 'http://127.0.0.1:7331/db/users.json?select=id,fullName'
|
|
42
|
+
curl 'http://127.0.0.1:7331/db/posts.json?select=id,title,readingTimeMinutes'
|
|
43
|
+
curl 'http://127.0.0.1:7331/db/products.json?select=id,name,priceLabel'
|
|
44
|
+
curl 'http://127.0.0.1:7331/db/orders.json?select=id,itemCount,totalCents,receiptLine'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## GraphQL Query To Try
|
|
48
|
+
|
|
49
|
+
```graphql
|
|
50
|
+
{
|
|
51
|
+
users {
|
|
52
|
+
id
|
|
53
|
+
fullName
|
|
54
|
+
}
|
|
55
|
+
products {
|
|
56
|
+
id
|
|
57
|
+
priceLabel
|
|
58
|
+
}
|
|
59
|
+
orders {
|
|
60
|
+
id
|
|
61
|
+
itemCount
|
|
62
|
+
totalCents
|
|
63
|
+
receiptLine
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Why This Shape?
|
|
69
|
+
|
|
70
|
+
Each model shows a different computed-field use case:
|
|
71
|
+
|
|
72
|
+
- `users.fullName` combines stored fields from `this.value` for display.
|
|
73
|
+
- `posts.readingTimeMinutes` uses `resolveMany` so a collection page can be computed in one batch.
|
|
74
|
+
- `products.priceLabel` formats raw `priceCents` without changing the stored number.
|
|
75
|
+
- `orders.totalCents` and `orders.receiptLine` use normal resolver functions so the delegated resolver context can read related product prices with `this.get('db')`.
|
|
76
|
+
|
|
77
|
+
Normal function resolvers can read internal runtime values such as `db`, `value`,
|
|
78
|
+
`record`, `fieldName`, and `services` with `this.get(name)`. App code can pass a
|
|
79
|
+
context object to `schema.resolver(...)` to override or add values; `this._internal`
|
|
80
|
+
keeps the original internal view available when a resolver needs it.
|
|
81
|
+
|
|
82
|
+
Computed fields are useful for values that are cheap to derive, read-only, and easier to keep out of source fixtures. They should not replace stored data that users need to edit directly.
|
|
83
|
+
|
|
84
|
+
## Features To Notice
|
|
85
|
+
|
|
86
|
+
- [Computed fields](../../docs/fixtures-and-schemas.md#computed-fields)
|
|
87
|
+
- [JavaScript schema sources](../../docs/fixtures-and-schemas.md#javascript-schema-sources)
|
|
88
|
+
- [Fixture-like `.json` REST routes](../../docs/server-and-viewer.md#fixture-like-json-routes)
|
|
89
|
+
- [Generated types](../../docs/generated-files.md#generated-types)
|
|
90
|
+
|
|
91
|
+
## Cleanup
|
|
92
|
+
|
|
93
|
+
Generated `.db/` output is ignored by git and can be removed whenever you want fresh runtime state.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { collection, field } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'Orders that derive totals from nested items and related products.',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
fields: {
|
|
7
|
+
id: field.string({
|
|
8
|
+
required: true,
|
|
9
|
+
description: 'Stable order id.',
|
|
10
|
+
}),
|
|
11
|
+
customerName: field.string({
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Stored customer display name.',
|
|
14
|
+
}),
|
|
15
|
+
items: field.array(field.object({
|
|
16
|
+
productId: field.string({ required: true }),
|
|
17
|
+
quantity: field.number({ required: true }),
|
|
18
|
+
}), {
|
|
19
|
+
description: 'Stored line items.',
|
|
20
|
+
}),
|
|
21
|
+
itemCount: field.computed(field.number({
|
|
22
|
+
description: 'Total quantity across line items.',
|
|
23
|
+
}), ({ record }) => itemCount(record)),
|
|
24
|
+
totalCents: field.computed(field.number({
|
|
25
|
+
description: 'Order total calculated from current product prices.',
|
|
26
|
+
}), async function orders_totalCents_resolver({ record }) {
|
|
27
|
+
return orderTotalCents(this.get('db'), record);
|
|
28
|
+
}),
|
|
29
|
+
receiptLine: field.computed(field.string({
|
|
30
|
+
description: 'Human-readable receipt summary.',
|
|
31
|
+
}), async function orders_receiptLine_resolver({ record }) {
|
|
32
|
+
const total = await orderTotalCents(this.get('db'), record);
|
|
33
|
+
return `${record.customerName} - ${itemCount(record)} items - ${formatMoney(total)}`;
|
|
34
|
+
}),
|
|
35
|
+
},
|
|
36
|
+
seed: [
|
|
37
|
+
{
|
|
38
|
+
id: 'ord_1',
|
|
39
|
+
customerName: 'Ada Lovelace',
|
|
40
|
+
items: [
|
|
41
|
+
{ productId: 'prod_sticker', quantity: 3 },
|
|
42
|
+
{ productId: 'prod_mug', quantity: 2 },
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
function itemCount(order) {
|
|
49
|
+
return (order.items ?? []).reduce((total, item) => total + Number(item.quantity ?? 0), 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function orderTotalCents(db, order) {
|
|
53
|
+
const products = await db.collection('products').all();
|
|
54
|
+
const prices = new Map(products.map((product) => [product.id, product.priceCents]));
|
|
55
|
+
return (order.items ?? []).reduce((total, item) => (
|
|
56
|
+
total + Number(prices.get(item.productId) ?? 0) * Number(item.quantity ?? 0)
|
|
57
|
+
), 0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function formatMoney(cents) {
|
|
61
|
+
return `$${(Number(cents ?? 0) / 100).toFixed(2)}`;
|
|
62
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { collection, field } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'Posts with derived reading metadata.',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
fields: {
|
|
7
|
+
id: field.string({
|
|
8
|
+
required: true,
|
|
9
|
+
description: 'Stable post id.',
|
|
10
|
+
}),
|
|
11
|
+
title: field.string({
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Post title.',
|
|
14
|
+
}),
|
|
15
|
+
authorId: field.string({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Author user id.',
|
|
18
|
+
relation: {
|
|
19
|
+
name: 'author',
|
|
20
|
+
to: 'users',
|
|
21
|
+
toField: 'id',
|
|
22
|
+
cardinality: 'one',
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
body: field.string({
|
|
26
|
+
required: true,
|
|
27
|
+
description: 'Stored markdown body.',
|
|
28
|
+
}),
|
|
29
|
+
readingTimeMinutes: field.computed(field.number({
|
|
30
|
+
description: 'One-minute minimum reading estimate derived from the body.',
|
|
31
|
+
}), {
|
|
32
|
+
resolveMany({ records }) {
|
|
33
|
+
return new Map(records.map((record) => [
|
|
34
|
+
record.id,
|
|
35
|
+
readingTimeMinutes(record.body),
|
|
36
|
+
]));
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
seed: [
|
|
41
|
+
{
|
|
42
|
+
id: 'post_intro',
|
|
43
|
+
title: 'Computed fields are projections',
|
|
44
|
+
authorId: 'u_1',
|
|
45
|
+
body: 'Computed fields keep stored fixture data small while adding useful values when a client selects them.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'post_release',
|
|
49
|
+
title: 'Batch resolver example',
|
|
50
|
+
authorId: 'u_2',
|
|
51
|
+
body: 'resolveMany can derive values for an entire selected page at once.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function readingTimeMinutes(body) {
|
|
57
|
+
const wordCount = String(body ?? '').trim().split(/\s+/u).filter(Boolean).length;
|
|
58
|
+
return Math.max(1, Math.ceil(wordCount / 200));
|
|
59
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { collection, field } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'Products that store cents and expose formatted display values.',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
fields: {
|
|
7
|
+
id: field.string({
|
|
8
|
+
required: true,
|
|
9
|
+
description: 'Stable product id.',
|
|
10
|
+
}),
|
|
11
|
+
name: field.string({
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Product name.',
|
|
14
|
+
}),
|
|
15
|
+
priceCents: field.number({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Stored integer price in cents.',
|
|
18
|
+
}),
|
|
19
|
+
priceLabel: field.computed(field.string({
|
|
20
|
+
description: 'Formatted price for UI display.',
|
|
21
|
+
}), ({ record }) => formatMoney(record.priceCents)),
|
|
22
|
+
},
|
|
23
|
+
seed: [
|
|
24
|
+
{
|
|
25
|
+
id: 'prod_sticker',
|
|
26
|
+
name: 'Async DB Sticker',
|
|
27
|
+
priceCents: 500,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'prod_mug',
|
|
31
|
+
name: 'Local Data Mug',
|
|
32
|
+
priceCents: 2000,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function formatMoney(cents) {
|
|
38
|
+
return `$${(Number(cents ?? 0) / 100).toFixed(2)}`;
|
|
39
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { collection, field } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'People used by posts and orders.',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
fields: {
|
|
7
|
+
id: field.string({
|
|
8
|
+
required: true,
|
|
9
|
+
description: 'Stable user id.',
|
|
10
|
+
}),
|
|
11
|
+
firstName: field.string({
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Given name stored in the fixture.',
|
|
14
|
+
}),
|
|
15
|
+
lastName: field.string({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'Family name stored in the fixture.',
|
|
18
|
+
}),
|
|
19
|
+
role: field.enum(['admin', 'editor', 'customer'], {
|
|
20
|
+
default: 'customer',
|
|
21
|
+
description: 'Local role for examples.',
|
|
22
|
+
}),
|
|
23
|
+
fullName: field.computed(field.string({
|
|
24
|
+
description: 'Display name assembled from first and last name.',
|
|
25
|
+
}), function users_fullName_resolver() {
|
|
26
|
+
return `${this.value.firstName} ${this.value.lastName}`;
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
seed: [
|
|
30
|
+
{
|
|
31
|
+
id: 'u_1',
|
|
32
|
+
firstName: 'Ada',
|
|
33
|
+
lastName: 'Lovelace',
|
|
34
|
+
role: 'admin',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: 'u_2',
|
|
38
|
+
firstName: 'Grace',
|
|
39
|
+
lastName: 'Hopper',
|
|
40
|
+
role: 'editor',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { defineConfig } from '@async/db/config';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
dbDir: './db',
|
|
6
|
+
outputs: {
|
|
7
|
+
stateDir: './.db',
|
|
8
|
+
types: './.db/types/index.d.ts',
|
|
9
|
+
committedTypes: './src/generated/db.types.d.ts',
|
|
10
|
+
},
|
|
11
|
+
types: {
|
|
12
|
+
enabled: true,
|
|
13
|
+
emitComments: true,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// This file is generated by db. Do not edit by hand.
|
|
3
|
+
|
|
4
|
+
export type UserRole = "admin" | "editor" | "customer";
|
|
5
|
+
|
|
6
|
+
/** Orders that derive totals from nested items and related products. */
|
|
7
|
+
export type Order = {
|
|
8
|
+
/** Stable order id. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Stored customer display name. */
|
|
11
|
+
customerName: string;
|
|
12
|
+
/** Stored line items. */
|
|
13
|
+
items?: Array<{
|
|
14
|
+
productId: string;
|
|
15
|
+
quantity: number;
|
|
16
|
+
}>;
|
|
17
|
+
/** Total quantity across line items. */
|
|
18
|
+
itemCount?: number;
|
|
19
|
+
/** Order total calculated from current product prices. */
|
|
20
|
+
totalCents?: number;
|
|
21
|
+
/** Human-readable receipt summary. */
|
|
22
|
+
receiptLine?: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Posts with derived reading metadata. */
|
|
26
|
+
export type Post = {
|
|
27
|
+
/** Stable post id. */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Post title. */
|
|
30
|
+
title: string;
|
|
31
|
+
/** Author user id. */
|
|
32
|
+
authorId: string;
|
|
33
|
+
/** Stored markdown body. */
|
|
34
|
+
body: string;
|
|
35
|
+
/** One-minute minimum reading estimate derived from the body. */
|
|
36
|
+
readingTimeMinutes?: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Products that store cents and expose formatted display values. */
|
|
40
|
+
export type Product = {
|
|
41
|
+
/** Stable product id. */
|
|
42
|
+
id: string;
|
|
43
|
+
/** Product name. */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Stored integer price in cents. */
|
|
46
|
+
priceCents: number;
|
|
47
|
+
/** Formatted price for UI display. */
|
|
48
|
+
priceLabel?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** People used by posts and orders. */
|
|
52
|
+
export type User = {
|
|
53
|
+
/** Stable user id. */
|
|
54
|
+
id: string;
|
|
55
|
+
/** Given name stored in the fixture. */
|
|
56
|
+
firstName: string;
|
|
57
|
+
/** Family name stored in the fixture. */
|
|
58
|
+
lastName: string;
|
|
59
|
+
/** Local role for examples. */
|
|
60
|
+
role?: UserRole;
|
|
61
|
+
/** Display name assembled from first and last name. */
|
|
62
|
+
fullName?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type DbCollections = {
|
|
66
|
+
orders: Order;
|
|
67
|
+
posts: Post;
|
|
68
|
+
products: Product;
|
|
69
|
+
users: User;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type DbDocuments = {
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type DbTypes = {
|
|
76
|
+
collections: DbCollections;
|
|
77
|
+
documents: DbDocuments;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type DbCollectionName = keyof DbCollections;
|
|
81
|
+
export type DbDocumentName = keyof DbDocuments;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Content Collections Example
|
|
2
|
+
|
|
3
|
+
## What This Teaches
|
|
4
|
+
|
|
5
|
+
Use this when you want a dependency-free version of the content collection pattern: docs and blog posts live as MDX files, `index.schema.mjs` files describe the record shape, and async-db exposes the content through generated types, REST, GraphQL, and the local viewer.
|
|
6
|
+
|
|
7
|
+
This example does not install `content-collections`, a frontmatter package, or an MDX compiler. Core reads simple scalar frontmatter plus the raw MDX body. The app-owned preview script shows where rendering or richer parsing belongs.
|
|
8
|
+
|
|
9
|
+
## Files To Inspect
|
|
10
|
+
|
|
11
|
+
- [db/docs/index.schema.mjs](./db/docs/index.schema.mjs): docs collection marker using `source: files(..., { read })`.
|
|
12
|
+
- [db/blog/index.schema.mjs](./db/blog/index.schema.mjs): blog collection with file sources, relations, and computed fields.
|
|
13
|
+
- [db.config.mjs](./db.config.mjs): config-owned `static` store selection for docs and blog.
|
|
14
|
+
- [db/blog/launch-notes.mdx](./db/blog/launch-notes.mdx): frontmatter plus raw MDX body.
|
|
15
|
+
- [db/authors.json](./db/authors.json): normal writable fixture records used by blog relations.
|
|
16
|
+
- [db/site.schema.jsonc](./db/site.schema.jsonc): embedded document seed for aggregate bundle seed splitting.
|
|
17
|
+
- [src/content-preview.mjs](./src/content-preview.mjs): dependency-free app-owned preview renderer.
|
|
18
|
+
|
|
19
|
+
## Run It
|
|
20
|
+
|
|
21
|
+
From the repository root, use the repo-internal CLI path:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run db -- sync --cwd ./examples/content-collections
|
|
25
|
+
npm run db -- serve --cwd ./examples/content-collections
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Open the viewer:
|
|
29
|
+
|
|
30
|
+
```txt
|
|
31
|
+
http://127.0.0.1:7331/__db
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Render the local preview:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
node ./examples/content-collections/src/content-preview.mjs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Expected Result
|
|
41
|
+
|
|
42
|
+
`sync` loads `authors`, `blog`, `docs`, and `site`. The docs and blog collections are static because `db.config.mjs` assigns those resources to the static store. The authors fixture stays writable in the runtime store.
|
|
43
|
+
|
|
44
|
+
## REST And GraphQL Requests To Try
|
|
45
|
+
|
|
46
|
+
Leave `serve` running and run these from another terminal:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl http://127.0.0.1:7331/db/docs.json
|
|
50
|
+
curl 'http://127.0.0.1:7331/db/blog.json?select=id,title,permalink,readingTimeMinutes'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
GraphQL selections use the same computed fields:
|
|
54
|
+
|
|
55
|
+
```graphql
|
|
56
|
+
{
|
|
57
|
+
blog {
|
|
58
|
+
id
|
|
59
|
+
title
|
|
60
|
+
permalink
|
|
61
|
+
readingTimeMinutes
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Bundle The Schemas
|
|
67
|
+
|
|
68
|
+
Aggregate bundle writes a root schema registry and keeps seed data out of that root file:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm run db -- schema bundle --all --cwd ./examples/content-collections --out db.schema.mjs
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Because `db/site.schema.jsonc` has embedded seed and no `db/site.json`, the command first writes `db/site.json`, then writes `db.schema.mjs`. Folder collection source globs are rebased so the root file points back to `files('./db/docs/**/*.mdx', { read: 'frontmatter' })` and `files('./db/blog/**/*.mdx', { read: 'frontmatter' })`.
|
|
75
|
+
|
|
76
|
+
## Why This Shape?
|
|
77
|
+
|
|
78
|
+
Docs and blog posts are static content, so their source of truth is the MDX file. Authors are normal fixture data because an app may create or edit them during local development. The relation from `blog.authorId` to `authors.id` keeps the content file small while still letting REST and GraphQL expand author data.
|
|
79
|
+
|
|
80
|
+
`tags` is a comma-separated scalar string on purpose. The built-in frontmatter parser is lightweight and dependency-free; if your app needs arrays, nested frontmatter, or full MDX compilation, keep that parser or compiler in app code like [src/content-preview.mjs](./src/content-preview.mjs).
|
|
81
|
+
|
|
82
|
+
## Features To Notice
|
|
83
|
+
|
|
84
|
+
- [Folder content collections](../../docs/fixtures-and-schemas.md#folder-content-collections)
|
|
85
|
+
- [Computed fields](../../docs/fixtures-and-schemas.md#computed-fields)
|
|
86
|
+
- [Bundle and unbundle](../../docs/fixtures-and-schemas.md#bundle-and-unbundle)
|
|
87
|
+
- [Fixture-like `.json` REST routes](../../docs/server-and-viewer.md#fixture-like-json-routes)
|
|
88
|
+
|
|
89
|
+
## Cleanup
|
|
90
|
+
|
|
91
|
+
Generated `.db/` output is ignored by git and can be removed whenever you want fresh runtime state. If you run the aggregate bundle command in this example folder, it intentionally creates `db.schema.mjs` and `db/site.json`; remove those files when you want to return to the per-resource authoring shape.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { collection, field } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'People who write or edit content records.',
|
|
5
|
+
idField: 'id',
|
|
6
|
+
fields: {
|
|
7
|
+
id: field.string({
|
|
8
|
+
required: true,
|
|
9
|
+
description: 'Stable author id used by content frontmatter.',
|
|
10
|
+
}),
|
|
11
|
+
name: field.string({
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Display name shown on bylines.',
|
|
14
|
+
}),
|
|
15
|
+
role: field.enum(['Editor', 'Author'], {
|
|
16
|
+
default: 'Author',
|
|
17
|
+
description: 'Editorial role in the local content workflow.',
|
|
18
|
+
}),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Draft Roadmap
|
|
3
|
+
status: draft
|
|
4
|
+
publishedAt: 2026-05-22T12:00:00.000Z
|
|
5
|
+
authorId: author_grace
|
|
6
|
+
tags: roadmap,draft
|
|
7
|
+
summary: A draft post that still validates against the same schema.
|
|
8
|
+
---
|
|
9
|
+
# Draft Roadmap
|
|
10
|
+
|
|
11
|
+
Draft content can live beside published content. The `status` field lets the app
|
|
12
|
+
decide what to show publicly.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { collection, field, files } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'Blog posts loaded from frontmatter and raw MDX body files.',
|
|
5
|
+
source: files('./**/*.mdx', { read: 'frontmatter' }),
|
|
6
|
+
idField: 'id',
|
|
7
|
+
fields: {
|
|
8
|
+
id: field.string({
|
|
9
|
+
required: true,
|
|
10
|
+
description: 'Stable post id. Defaults to the filename when frontmatter omits id.',
|
|
11
|
+
}),
|
|
12
|
+
title: field.string({
|
|
13
|
+
required: true,
|
|
14
|
+
description: 'Post title from frontmatter.',
|
|
15
|
+
}),
|
|
16
|
+
status: field.enum(['draft', 'published'], {
|
|
17
|
+
default: 'draft',
|
|
18
|
+
description: 'Publication state.',
|
|
19
|
+
}),
|
|
20
|
+
publishedAt: field.datetime({
|
|
21
|
+
description: 'Publication timestamp.',
|
|
22
|
+
}),
|
|
23
|
+
authorId: field.string({
|
|
24
|
+
required: true,
|
|
25
|
+
description: 'Author id from the authors fixture.',
|
|
26
|
+
relation: {
|
|
27
|
+
name: 'author',
|
|
28
|
+
to: 'authors',
|
|
29
|
+
toField: 'id',
|
|
30
|
+
cardinality: 'one',
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
tags: field.string({
|
|
34
|
+
description: 'Comma-separated tags kept scalar for the dependency-free frontmatter parser.',
|
|
35
|
+
}),
|
|
36
|
+
summary: field.string({
|
|
37
|
+
description: 'Short post summary.',
|
|
38
|
+
}),
|
|
39
|
+
body: field.string({
|
|
40
|
+
required: true,
|
|
41
|
+
description: 'Raw MDX body. Rendering and compilation are app-owned.',
|
|
42
|
+
}),
|
|
43
|
+
permalink: field.computed(field.string({
|
|
44
|
+
description: 'Read-only URL path derived from the post id.',
|
|
45
|
+
}), function blog_permalink_resolver({ record }) {
|
|
46
|
+
return `/blog/${record.id}`;
|
|
47
|
+
}),
|
|
48
|
+
readingTimeMinutes: field.computed(field.number({
|
|
49
|
+
description: 'Read-only one-minute minimum estimate derived from the raw body.',
|
|
50
|
+
}), {
|
|
51
|
+
resolveMany({ records }) {
|
|
52
|
+
return records.map((record) => readingTimeMinutes(record.body));
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
function readingTimeMinutes(body) {
|
|
59
|
+
const wordCount = String(body ?? '').trim().split(/\s+/u).filter(Boolean).length;
|
|
60
|
+
return Math.max(1, Math.ceil(wordCount / 200));
|
|
61
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Launch Notes
|
|
3
|
+
status: published
|
|
4
|
+
publishedAt: 2026-05-21T09:00:00.000Z
|
|
5
|
+
authorId: author_ada
|
|
6
|
+
tags: local,content,mdx
|
|
7
|
+
summary: Why folder content works well for local apps.
|
|
8
|
+
---
|
|
9
|
+
# Launch Notes
|
|
10
|
+
|
|
11
|
+
## Why local content
|
|
12
|
+
|
|
13
|
+
Docs and posts often start as files, but apps still need typed records, route
|
|
14
|
+
metadata, and API access. This example keeps the files readable while making the
|
|
15
|
+
records available through async-db.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { collection, field, files } from '@async/db/schema';
|
|
2
|
+
|
|
3
|
+
export default collection({
|
|
4
|
+
description: 'Documentation pages loaded from frontmatter and raw MDX body files.',
|
|
5
|
+
source: files('./**/*.mdx', { read: 'frontmatter' }),
|
|
6
|
+
idField: 'id',
|
|
7
|
+
fields: {
|
|
8
|
+
id: field.string({
|
|
9
|
+
required: true,
|
|
10
|
+
description: 'Stable document id. Defaults to the filename when frontmatter omits id.',
|
|
11
|
+
}),
|
|
12
|
+
title: field.string({
|
|
13
|
+
required: true,
|
|
14
|
+
description: 'Page title from frontmatter.',
|
|
15
|
+
}),
|
|
16
|
+
section: field.string({
|
|
17
|
+
required: true,
|
|
18
|
+
description: 'Navigation group for this page.',
|
|
19
|
+
}),
|
|
20
|
+
order: field.number({
|
|
21
|
+
default: 100,
|
|
22
|
+
description: 'Sort order inside the section.',
|
|
23
|
+
}),
|
|
24
|
+
summary: field.string({
|
|
25
|
+
description: 'Short page summary.',
|
|
26
|
+
}),
|
|
27
|
+
body: field.string({
|
|
28
|
+
required: true,
|
|
29
|
+
description: 'Raw MDX body. Rendering and compilation are app-owned.',
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Intro To Local Content
|
|
3
|
+
section: Guide
|
|
4
|
+
order: 1
|
|
5
|
+
summary: A first document loaded from a folder collection.
|
|
6
|
+
---
|
|
7
|
+
# Intro To Local Content
|
|
8
|
+
|
|
9
|
+
This page is stored as a normal MDX file under `db/docs`.
|
|
10
|
+
|
|
11
|
+
<Callout tone="info">Apps can compile or render this body with their own MDX pipeline.</Callout>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Schema Workflow
|
|
3
|
+
section: Guide
|
|
4
|
+
order: 2
|
|
5
|
+
summary: How folder content maps into typed local records.
|
|
6
|
+
---
|
|
7
|
+
# Schema Workflow
|
|
8
|
+
|
|
9
|
+
The `index.schema.mjs` marker describes the content contract. The matching MDX
|
|
10
|
+
files provide frontmatter fields plus the raw body string.
|