@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,239 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { dbError } from '../../errors.js';
|
|
4
|
+
import { applyDefaultsToSeed } from '../sync/defaults.js';
|
|
5
|
+
import { seedForRuntimeState } from '../sync/synthetic-seed.js';
|
|
6
|
+
import { updateSourceMetadataResource } from './source-metadata.js';
|
|
7
|
+
const writeQueues = new Map();
|
|
8
|
+
export const jsonRuntimeCapabilities = {
|
|
9
|
+
writable: true,
|
|
10
|
+
persistence: 'local-file',
|
|
11
|
+
atomicity: 'resource',
|
|
12
|
+
liveEvents: true,
|
|
13
|
+
staticExport: false,
|
|
14
|
+
production: 'small-local',
|
|
15
|
+
};
|
|
16
|
+
export function fileStorage(root) {
|
|
17
|
+
return {
|
|
18
|
+
kind: 'file',
|
|
19
|
+
root,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function s3Storage(options) {
|
|
23
|
+
return {
|
|
24
|
+
kind: 's3',
|
|
25
|
+
...options,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function jsonStore(options = {}) {
|
|
29
|
+
return ({ config, storeName }) => {
|
|
30
|
+
const storage = options.storage ?? fileStorage(config.__asyncDbScope?.rootStateDir ?? config.stateDir);
|
|
31
|
+
if (storage.kind !== 'file') {
|
|
32
|
+
return unsupportedObjectStorageAdapter(storeName, storage, options);
|
|
33
|
+
}
|
|
34
|
+
const fileBackend = storage;
|
|
35
|
+
const rootDirectory = options.storage ? 'resources' : 'state';
|
|
36
|
+
function statePath(resource) {
|
|
37
|
+
return jsonStoreStatePath(config, fileBackend, resource.name, rootDirectory);
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
name: storeName,
|
|
41
|
+
capabilities: {
|
|
42
|
+
...jsonRuntimeCapabilities,
|
|
43
|
+
durability: options.durability ?? 'current',
|
|
44
|
+
encryption: options.encryption ?? null,
|
|
45
|
+
layout: 'resource-files',
|
|
46
|
+
},
|
|
47
|
+
statePath,
|
|
48
|
+
readResource(resource, fallback) {
|
|
49
|
+
return readJsonState(statePath(resource), fallback);
|
|
50
|
+
},
|
|
51
|
+
writeResource(resource, value) {
|
|
52
|
+
return writeJsonState(statePath(resource), value);
|
|
53
|
+
},
|
|
54
|
+
withResourceWrite(resource, operation) {
|
|
55
|
+
return withJsonStateWrite(statePath(resource), operation);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function createJsonRuntimeAdapter(config) {
|
|
61
|
+
return {
|
|
62
|
+
name: 'json',
|
|
63
|
+
capabilities: jsonRuntimeCapabilities,
|
|
64
|
+
statePath(resource) {
|
|
65
|
+
return statePathForResource(config, resource.name);
|
|
66
|
+
},
|
|
67
|
+
async hydrate(resources) {
|
|
68
|
+
await mkdir(jsonResourceStateDir(config), { recursive: true });
|
|
69
|
+
const sourceMetadataPath = path.join(jsonResourceStateDir(config), '.sources.json');
|
|
70
|
+
const sourceMetadata = await readJsonState(sourceMetadataPath, { resources: {} });
|
|
71
|
+
sourceMetadata.resources ??= {};
|
|
72
|
+
for (const resource of resources) {
|
|
73
|
+
await syncJsonResourceState(config, resource, sourceMetadata);
|
|
74
|
+
}
|
|
75
|
+
await writeJsonState(sourceMetadataPath, sourceMetadata);
|
|
76
|
+
},
|
|
77
|
+
readResource(resource, fallback) {
|
|
78
|
+
return readJsonState(statePathForResource(config, resource.name), fallback);
|
|
79
|
+
},
|
|
80
|
+
writeResource(resource, value) {
|
|
81
|
+
return writeJsonState(statePathForResource(config, resource.name), value);
|
|
82
|
+
},
|
|
83
|
+
withResourceWrite(resource, operation) {
|
|
84
|
+
return withJsonStateWrite(statePathForResource(config, resource.name), operation);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function statePathForResource(config, resourceName) {
|
|
89
|
+
const name = typeof resourceName === 'string'
|
|
90
|
+
? resourceName
|
|
91
|
+
: resourceName.name;
|
|
92
|
+
return jsonResourcePath({
|
|
93
|
+
root: config.__asyncDbScope?.rootStateDir ?? config.stateDir,
|
|
94
|
+
rootDirectory: 'state',
|
|
95
|
+
scope: config.__asyncDbScope,
|
|
96
|
+
resourceName: name,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function jsonResourceStateDir(config) {
|
|
100
|
+
return jsonResourceDir({
|
|
101
|
+
root: config.__asyncDbScope?.rootStateDir ?? config.stateDir,
|
|
102
|
+
rootDirectory: 'state',
|
|
103
|
+
scope: config.__asyncDbScope,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function jsonStoreStatePath(config, storage, resourceName, rootDirectory) {
|
|
107
|
+
const root = path.isAbsolute(storage.root)
|
|
108
|
+
? storage.root
|
|
109
|
+
: path.resolve(config.cwd, storage.root);
|
|
110
|
+
return jsonResourcePath({
|
|
111
|
+
root,
|
|
112
|
+
rootDirectory,
|
|
113
|
+
scope: config.__asyncDbScope,
|
|
114
|
+
resourceName,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function jsonResourcePath(options) {
|
|
118
|
+
return path.join(jsonResourceDir(options), `${options.resourceName}.json`);
|
|
119
|
+
}
|
|
120
|
+
function jsonResourceDir(options) {
|
|
121
|
+
const { root, rootDirectory, scope } = options;
|
|
122
|
+
if (scope?.fork) {
|
|
123
|
+
return path.join(root, 'forks', scope.fork, 'branches', scope.branch ?? 'main', 'resources');
|
|
124
|
+
}
|
|
125
|
+
return path.join(root, rootDirectory);
|
|
126
|
+
}
|
|
127
|
+
function unsupportedObjectStorageAdapter(storeName, storage, options) {
|
|
128
|
+
const error = () => dbError('JSON_STORAGE_BACKEND_UNAVAILABLE', `JSON store "${storeName}" cannot use "${storage.kind}" storage in this runtime yet.`, {
|
|
129
|
+
status: 500,
|
|
130
|
+
hint: 'Use fileStorage() for the built-in runtime today, or provide a custom object-storage adapter that implements read/write semantics.',
|
|
131
|
+
details: {
|
|
132
|
+
store: storeName,
|
|
133
|
+
storage: storage.kind,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
name: storeName,
|
|
138
|
+
capabilities: {
|
|
139
|
+
...jsonRuntimeCapabilities,
|
|
140
|
+
persistence: 'object-storage',
|
|
141
|
+
durability: options.durability ?? 'versioned',
|
|
142
|
+
encryption: storage.encryption ?? options.encryption ?? null,
|
|
143
|
+
layout: 'resource-files',
|
|
144
|
+
},
|
|
145
|
+
readResource() {
|
|
146
|
+
throw error();
|
|
147
|
+
},
|
|
148
|
+
writeResource() {
|
|
149
|
+
throw error();
|
|
150
|
+
},
|
|
151
|
+
withResourceWrite(_resource, operation) {
|
|
152
|
+
throw error();
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export async function readJsonState(filePath, fallback) {
|
|
157
|
+
try {
|
|
158
|
+
return JSON.parse(await readFile(filePath, 'utf8'));
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error.code === 'ENOENT') {
|
|
162
|
+
return fallback;
|
|
163
|
+
}
|
|
164
|
+
if (error instanceof SyntaxError) {
|
|
165
|
+
throw dbError('JSON_STATE_INVALID', `JSON state file is not valid JSON: ${filePath}`, {
|
|
166
|
+
status: 500,
|
|
167
|
+
hint: 'Restore this file from a known-good snapshot, delete it to rehydrate from seed data when safe, or fix the JSON syntax before restarting.',
|
|
168
|
+
details: {
|
|
169
|
+
filePath,
|
|
170
|
+
parserMessage: error.message,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export async function writeJsonState(filePath, value) {
|
|
178
|
+
return atomicWriteJson(filePath, value);
|
|
179
|
+
}
|
|
180
|
+
export async function atomicWriteJson(filePath, value) {
|
|
181
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
182
|
+
const text = `${JSON.stringify(value, null, 2)}\n`;
|
|
183
|
+
try {
|
|
184
|
+
if ((await readFile(filePath, 'utf8')) === text) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (error.code !== 'ENOENT') {
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
194
|
+
try {
|
|
195
|
+
await writeFile(tempPath, text, 'utf8');
|
|
196
|
+
await rename(tempPath, filePath);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
try {
|
|
201
|
+
await rm(tempPath, { force: true });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Best-effort cleanup only.
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export function withJsonStateWrite(filePath, operation) {
|
|
210
|
+
const previous = writeQueues.get(filePath) ?? Promise.resolve();
|
|
211
|
+
const current = previous.then(operation, operation);
|
|
212
|
+
const stored = current.catch(() => { });
|
|
213
|
+
writeQueues.set(filePath, stored);
|
|
214
|
+
stored.finally(() => {
|
|
215
|
+
if (writeQueues.get(filePath) === stored) {
|
|
216
|
+
writeQueues.delete(filePath);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
return current;
|
|
220
|
+
}
|
|
221
|
+
export async function syncJsonResourceState(config, resource, sourceMetadata) {
|
|
222
|
+
const statePath = statePathForResource(config, resource.name);
|
|
223
|
+
const existing = await readJsonState(statePath, undefined);
|
|
224
|
+
const metadata = sourceMetadata.resources[resource.name];
|
|
225
|
+
const sourceChanged = resource.dataHash
|
|
226
|
+
&& metadata?.hash !== resource.dataHash;
|
|
227
|
+
if (existing === undefined || sourceChanged) {
|
|
228
|
+
await writeJsonState(statePath, applyDefaultsToSeed(seedForRuntimeState(resource, config), resource, config));
|
|
229
|
+
updateSourceMetadata(sourceMetadata, config, resource);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (config.defaults?.applyOnSafeMigration !== false) {
|
|
233
|
+
await writeJsonState(statePath, applyDefaultsToSeed(existing, resource, config));
|
|
234
|
+
}
|
|
235
|
+
updateSourceMetadata(sourceMetadata, config, resource);
|
|
236
|
+
}
|
|
237
|
+
function updateSourceMetadata(sourceMetadata, config, resource) {
|
|
238
|
+
updateSourceMetadataResource(sourceMetadata, config, resource);
|
|
239
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type RuntimeConfig = Record<string, unknown>;
|
|
2
|
+
type RuntimeResource = {
|
|
3
|
+
name: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
type ResourceWriteOperation<T> = () => T | Promise<T>;
|
|
7
|
+
export declare const memoryRuntimeCapabilities: {
|
|
8
|
+
writable: boolean;
|
|
9
|
+
persistence: string;
|
|
10
|
+
atomicity: string;
|
|
11
|
+
liveEvents: boolean;
|
|
12
|
+
staticExport: boolean;
|
|
13
|
+
production: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function createMemoryRuntimeAdapter(config: RuntimeConfig): {
|
|
16
|
+
name: string;
|
|
17
|
+
capabilities: {
|
|
18
|
+
writable: boolean;
|
|
19
|
+
persistence: string;
|
|
20
|
+
atomicity: string;
|
|
21
|
+
liveEvents: boolean;
|
|
22
|
+
staticExport: boolean;
|
|
23
|
+
production: boolean;
|
|
24
|
+
};
|
|
25
|
+
hydrate(resources: RuntimeResource[]): Promise<void>;
|
|
26
|
+
readResource(resource: RuntimeResource, fallback: unknown): Promise<unknown>;
|
|
27
|
+
writeResource(resource: RuntimeResource, value: unknown): Promise<void>;
|
|
28
|
+
withResourceWrite<T>(resource: RuntimeResource, operation: ResourceWriteOperation<T>): Promise<T>;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { applyDefaultsToSeed } from '../sync/defaults.js';
|
|
2
|
+
import { seedForRuntimeState } from '../sync/synthetic-seed.js';
|
|
3
|
+
export const memoryRuntimeCapabilities = {
|
|
4
|
+
writable: true,
|
|
5
|
+
persistence: 'memory',
|
|
6
|
+
atomicity: 'process',
|
|
7
|
+
liveEvents: true,
|
|
8
|
+
staticExport: false,
|
|
9
|
+
production: false,
|
|
10
|
+
};
|
|
11
|
+
export function createMemoryRuntimeAdapter(config) {
|
|
12
|
+
const values = new Map();
|
|
13
|
+
const queues = new Map();
|
|
14
|
+
return {
|
|
15
|
+
name: 'memory',
|
|
16
|
+
capabilities: memoryRuntimeCapabilities,
|
|
17
|
+
async hydrate(resources) {
|
|
18
|
+
for (const resource of resources) {
|
|
19
|
+
values.set(resource.name, clone(applyDefaultsToSeed(seedForRuntimeState(resource, config), resource, config)));
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
async readResource(resource, fallback) {
|
|
23
|
+
return values.has(resource.name) ? clone(values.get(resource.name)) : clone(fallback);
|
|
24
|
+
},
|
|
25
|
+
async writeResource(resource, value) {
|
|
26
|
+
values.set(resource.name, clone(value));
|
|
27
|
+
},
|
|
28
|
+
withResourceWrite(resource, operation) {
|
|
29
|
+
const previous = queues.get(resource.name) ?? Promise.resolve();
|
|
30
|
+
const current = previous.then(operation, operation);
|
|
31
|
+
const stored = current.catch(() => { });
|
|
32
|
+
queues.set(resource.name, stored);
|
|
33
|
+
stored.finally(() => {
|
|
34
|
+
if (queues.get(resource.name) === stored) {
|
|
35
|
+
queues.delete(resource.name);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return current;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function clone(value) {
|
|
43
|
+
return value === undefined ? value : structuredClone(value);
|
|
44
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type RuntimeConfig = {
|
|
2
|
+
defaults?: {
|
|
3
|
+
applyOnSafeMigration?: boolean;
|
|
4
|
+
};
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
type RuntimeResource = {
|
|
8
|
+
kind?: string;
|
|
9
|
+
name: string;
|
|
10
|
+
dataHash?: string | null;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
export type ResourceEnvelope<T = unknown> = {
|
|
14
|
+
kind?: string;
|
|
15
|
+
sourceHash?: string | null;
|
|
16
|
+
value: T;
|
|
17
|
+
};
|
|
18
|
+
type EnvelopeReader<T> = (resource: RuntimeResource) => Promise<ResourceEnvelope<T> | null | undefined>;
|
|
19
|
+
type EnvelopeWriter<T> = (resource: RuntimeResource, envelope: ResourceEnvelope<T>) => Promise<unknown>;
|
|
20
|
+
type ResourceWriteOperation<T> = () => T | Promise<T>;
|
|
21
|
+
export declare function createResourceWriteQueue(): <T>(queueKey: string, operation: ResourceWriteOperation<T>) => Promise<T>;
|
|
22
|
+
export declare function hydrateJsonResourceStore<T = unknown>({ config, resource, readEnvelope, writeEnvelope, }: {
|
|
23
|
+
config: RuntimeConfig;
|
|
24
|
+
resource: RuntimeResource;
|
|
25
|
+
readEnvelope: EnvelopeReader<T>;
|
|
26
|
+
writeEnvelope: EnvelopeWriter<T>;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
export declare function envelopeForResource<T>(resource: RuntimeResource, value: T): ResourceEnvelope<T>;
|
|
29
|
+
export declare function parseJsonEnvelope<T = unknown>(raw: string | ResourceEnvelope<T> | null | undefined, storeName: string): ResourceEnvelope<T> | null;
|
|
30
|
+
export declare function closeInjectedClient(client: Record<string, unknown> | null | undefined, closeOption: boolean | ((client: Record<string, unknown> | null | undefined) => unknown | Promise<unknown>)): Promise<void>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { dbError } from '../../errors.js';
|
|
2
|
+
import { applyDefaultsToSeed } from '../sync/defaults.js';
|
|
3
|
+
import { seedForRuntimeState } from '../sync/synthetic-seed.js';
|
|
4
|
+
export function createResourceWriteQueue() {
|
|
5
|
+
const queues = new Map();
|
|
6
|
+
return function withQueuedResourceWrite(queueKey, operation) {
|
|
7
|
+
const previous = queues.get(queueKey) ?? Promise.resolve();
|
|
8
|
+
const current = previous.then(operation, operation);
|
|
9
|
+
const stored = current.catch(() => { });
|
|
10
|
+
queues.set(queueKey, stored);
|
|
11
|
+
stored.finally(() => {
|
|
12
|
+
if (queues.get(queueKey) === stored) {
|
|
13
|
+
queues.delete(queueKey);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return current;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function hydrateJsonResourceStore({ config, resource, readEnvelope, writeEnvelope, }) {
|
|
20
|
+
const envelope = await readEnvelope(resource);
|
|
21
|
+
const sourceChanged = resource.dataHash && envelope?.sourceHash !== resource.dataHash;
|
|
22
|
+
if (!envelope || sourceChanged) {
|
|
23
|
+
await writeEnvelope(resource, {
|
|
24
|
+
kind: resource.kind,
|
|
25
|
+
sourceHash: resource.dataHash ?? null,
|
|
26
|
+
value: applyDefaultsToSeed(seedForRuntimeState(resource, config), resource, config),
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (config.defaults?.applyOnSafeMigration !== false) {
|
|
31
|
+
await writeEnvelope(resource, {
|
|
32
|
+
kind: resource.kind,
|
|
33
|
+
sourceHash: resource.dataHash ?? envelope.sourceHash ?? null,
|
|
34
|
+
value: applyDefaultsToSeed(envelope.value, resource, config),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function envelopeForResource(resource, value) {
|
|
39
|
+
return {
|
|
40
|
+
kind: resource.kind,
|
|
41
|
+
sourceHash: resource.dataHash ?? null,
|
|
42
|
+
value,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function parseJsonEnvelope(raw, storeName) {
|
|
46
|
+
if (raw === undefined || raw === null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const envelope = (typeof raw === 'string' ? JSON.parse(raw) : raw);
|
|
50
|
+
if (!envelope || typeof envelope !== 'object' || !('value' in envelope)) {
|
|
51
|
+
throw dbError('STORE_INVALID_RESOURCE_ENVELOPE', `Store "${storeName}" returned an invalid resource envelope.`, {
|
|
52
|
+
status: 500,
|
|
53
|
+
hint: 'Resource JSON stores must persist { kind, sourceHash, value } envelopes.',
|
|
54
|
+
details: {
|
|
55
|
+
store: storeName,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return envelope;
|
|
60
|
+
}
|
|
61
|
+
export async function closeInjectedClient(client, closeOption) {
|
|
62
|
+
if (!closeOption) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (typeof closeOption === 'function') {
|
|
66
|
+
await closeOption(client);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const method of ['close', 'end', 'quit', 'disconnect']) {
|
|
70
|
+
const closeMethod = client?.[method];
|
|
71
|
+
if (typeof closeMethod === 'function') {
|
|
72
|
+
await closeMethod.call(client);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
type RuntimeResource = {
|
|
2
|
+
name: string;
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
};
|
|
5
|
+
type RuntimeConfig = {
|
|
6
|
+
cwd: string;
|
|
7
|
+
stateDir: string;
|
|
8
|
+
resources?: Record<string, unknown>;
|
|
9
|
+
stores?: Record<string, unknown>;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
type RuntimeAdapter = {
|
|
13
|
+
name: string;
|
|
14
|
+
capabilities?: unknown;
|
|
15
|
+
statePath?: (resource: RuntimeResource) => unknown;
|
|
16
|
+
hydrate?: (resources: RuntimeResource[]) => unknown | Promise<unknown>;
|
|
17
|
+
readResource?: (resource: RuntimeResource, fallback: unknown) => unknown | Promise<unknown>;
|
|
18
|
+
writeResource?: (resource: RuntimeResource, value: unknown) => unknown | Promise<unknown>;
|
|
19
|
+
withResourceWrite?: <T>(resource: RuntimeResource, operation: () => T | Promise<T>) => T | Promise<T>;
|
|
20
|
+
close?: () => unknown | Promise<unknown>;
|
|
21
|
+
};
|
|
22
|
+
export declare function createRuntime(config: RuntimeConfig, resources: RuntimeResource[]): {
|
|
23
|
+
events: {
|
|
24
|
+
readonly version: number;
|
|
25
|
+
emit(change: import("./events.js").RuntimeEventChange): import("./events.js").RuntimeEvent;
|
|
26
|
+
subscribe(subscriber: import("./events.js").RuntimeEventSubscriber): () => void;
|
|
27
|
+
close(): void;
|
|
28
|
+
};
|
|
29
|
+
adapterNames(): string[];
|
|
30
|
+
strategyFor(resource: RuntimeResource): string;
|
|
31
|
+
adapterForStore(resource: RuntimeResource, storeName: string): RuntimeAdapter;
|
|
32
|
+
adapterFor(resource: RuntimeResource): RuntimeAdapter;
|
|
33
|
+
hydrate(): Promise<void>;
|
|
34
|
+
emit(change: Record<string, unknown>): import("./events.js").RuntimeEvent;
|
|
35
|
+
close(): Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { dbError, listChoices } from '../../errors.js';
|
|
2
|
+
import { resourceConfigValue } from '../../names.js';
|
|
3
|
+
import { createJsonRuntimeAdapter } from './json.js';
|
|
4
|
+
import { createMemoryRuntimeAdapter } from './memory.js';
|
|
5
|
+
import { createSourceRuntimeAdapter } from './source.js';
|
|
6
|
+
import { createStaticRuntimeAdapter } from './static.js';
|
|
7
|
+
import { createRuntimeEventHub } from './events.js';
|
|
8
|
+
export function createRuntime(config, resources) {
|
|
9
|
+
const events = createRuntimeEventHub();
|
|
10
|
+
const adapters = new Map();
|
|
11
|
+
const storeDefinitions = new Map();
|
|
12
|
+
const customStoreQueues = new Map();
|
|
13
|
+
let closed = false;
|
|
14
|
+
for (const adapter of builtinAdapters(config)) {
|
|
15
|
+
adapters.set(adapter.name, adapter);
|
|
16
|
+
}
|
|
17
|
+
for (const [storeName, storeDefinition] of Object.entries(config.stores ?? {})) {
|
|
18
|
+
if (storeName === 'default' || typeof storeDefinition === 'string' || storeRecord(storeDefinition)?.driver) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const store = typeof storeDefinition === 'function'
|
|
22
|
+
? storeDefinition({ config, resources, storeName })
|
|
23
|
+
: storeDefinition;
|
|
24
|
+
if (store) {
|
|
25
|
+
storeDefinitions.set(storeName, store);
|
|
26
|
+
adapters.set(storeName, customStoreAdapter(storeName, store, customStoreQueues));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
events,
|
|
31
|
+
adapterNames() {
|
|
32
|
+
return [...adapters.keys()];
|
|
33
|
+
},
|
|
34
|
+
strategyFor(resource) {
|
|
35
|
+
const resourceConfig = storeRecord(resourceConfigValue(config.resources, resource.name));
|
|
36
|
+
const storeName = String(resourceConfig?.store ?? config.stores?.default ?? 'json');
|
|
37
|
+
return strategyForStoreName(resource, storeName, config, adapters, storeDefinitions);
|
|
38
|
+
},
|
|
39
|
+
adapterForStore(resource, storeName) {
|
|
40
|
+
const strategy = strategyForStoreName(resource, storeName, config, adapters, storeDefinitions);
|
|
41
|
+
const adapter = adapters.get(strategy);
|
|
42
|
+
if (!adapter) {
|
|
43
|
+
throw dbError('STORE_DRIVER_NOT_FOUND', `Store driver "${strategy}" is not registered for resource "${resource.name}".`, {
|
|
44
|
+
status: 500,
|
|
45
|
+
hint: `Register one of: ${listChoices([...adapters.keys()])}.`,
|
|
46
|
+
details: {
|
|
47
|
+
resource: resource.name,
|
|
48
|
+
store: strategy,
|
|
49
|
+
availableStores: [...adapters.keys()],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return adapter;
|
|
54
|
+
},
|
|
55
|
+
adapterFor(resource) {
|
|
56
|
+
const strategy = this.strategyFor(resource);
|
|
57
|
+
const adapter = adapters.get(strategy);
|
|
58
|
+
if (!adapter) {
|
|
59
|
+
throw dbError('STORE_DRIVER_NOT_FOUND', `Store driver "${strategy}" is not registered for resource "${resource.name}".`, {
|
|
60
|
+
status: 500,
|
|
61
|
+
hint: `Register one of: ${listChoices([...adapters.keys()])}.`,
|
|
62
|
+
details: {
|
|
63
|
+
resource: resource.name,
|
|
64
|
+
store: strategy,
|
|
65
|
+
availableStores: [...adapters.keys()],
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return adapter;
|
|
70
|
+
},
|
|
71
|
+
async hydrate() {
|
|
72
|
+
const byAdapter = new Map();
|
|
73
|
+
for (const resource of resources) {
|
|
74
|
+
const adapter = this.adapterFor(resource);
|
|
75
|
+
const group = byAdapter.get(adapter) ?? [];
|
|
76
|
+
group.push(resource);
|
|
77
|
+
byAdapter.set(adapter, group);
|
|
78
|
+
}
|
|
79
|
+
for (const [adapter, adapterResources] of byAdapter) {
|
|
80
|
+
await adapter.hydrate?.(adapterResources);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
emit(change) {
|
|
84
|
+
return events.emit(change);
|
|
85
|
+
},
|
|
86
|
+
async close() {
|
|
87
|
+
if (closed) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
closed = true;
|
|
91
|
+
for (const adapter of new Set(adapters.values())) {
|
|
92
|
+
await adapter.close?.();
|
|
93
|
+
}
|
|
94
|
+
events.close();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function strategyForStoreName(resource, storeName, config, adapters, storeDefinitions) {
|
|
99
|
+
const configured = storeDefinitions.has(storeName)
|
|
100
|
+
? storeName
|
|
101
|
+
: config.stores?.[storeName] ?? storeName;
|
|
102
|
+
if (!adapters.has(storeName) && config.stores?.[storeName] === undefined) {
|
|
103
|
+
throw missingStoreError(resource, storeName, config, adapters);
|
|
104
|
+
}
|
|
105
|
+
const configuredRecord = storeRecord(configured);
|
|
106
|
+
return typeof configured === 'string' ? configured : configuredRecord?.driver ?? storeName;
|
|
107
|
+
}
|
|
108
|
+
function customStoreAdapter(storeName, store, queues) {
|
|
109
|
+
return {
|
|
110
|
+
name: store.name ?? storeName,
|
|
111
|
+
capabilities: store.capabilities,
|
|
112
|
+
statePath(resource) {
|
|
113
|
+
return store.statePath?.(resource);
|
|
114
|
+
},
|
|
115
|
+
hydrate(resources) {
|
|
116
|
+
return store.hydrate?.(resources);
|
|
117
|
+
},
|
|
118
|
+
readResource(resource, fallback) {
|
|
119
|
+
if (store.readResource) {
|
|
120
|
+
return store.readResource(resource, fallback);
|
|
121
|
+
}
|
|
122
|
+
if (store.get) {
|
|
123
|
+
return store.get(resource, fallback);
|
|
124
|
+
}
|
|
125
|
+
return store.read(resource, fallback);
|
|
126
|
+
},
|
|
127
|
+
writeResource(resource, value) {
|
|
128
|
+
if (store.writeResource) {
|
|
129
|
+
return store.writeResource(resource, value);
|
|
130
|
+
}
|
|
131
|
+
if (store.set) {
|
|
132
|
+
return store.set(resource, value);
|
|
133
|
+
}
|
|
134
|
+
return store.write(resource, value);
|
|
135
|
+
},
|
|
136
|
+
withResourceWrite(resource, operation) {
|
|
137
|
+
if (store.withResourceWrite) {
|
|
138
|
+
return store.withResourceWrite(resource, operation);
|
|
139
|
+
}
|
|
140
|
+
const queueKey = `${storeName}:${resource.name}`;
|
|
141
|
+
const previous = queues.get(queueKey) ?? Promise.resolve();
|
|
142
|
+
const current = previous.then(operation, operation);
|
|
143
|
+
const stored = current.catch(() => { });
|
|
144
|
+
queues.set(queueKey, stored);
|
|
145
|
+
stored.finally(() => {
|
|
146
|
+
if (queues.get(queueKey) === stored) {
|
|
147
|
+
queues.delete(queueKey);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return current;
|
|
151
|
+
},
|
|
152
|
+
close() {
|
|
153
|
+
return store.close?.();
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function missingStoreError(resource, storeName, config, adapters) {
|
|
158
|
+
const availableStores = [
|
|
159
|
+
...new Set([
|
|
160
|
+
...adapters.keys(),
|
|
161
|
+
...Object.keys(config.stores ?? {}).filter((name) => name !== 'default'),
|
|
162
|
+
]),
|
|
163
|
+
];
|
|
164
|
+
return dbError('STORE_NOT_FOUND', `Store "${storeName}" is not configured for resource "${resource.name}".`, {
|
|
165
|
+
status: 500,
|
|
166
|
+
hint: `Configure stores.${storeName}, choose stores.default, or use one of: ${listChoices(availableStores)}.`,
|
|
167
|
+
details: {
|
|
168
|
+
resource: resource.name,
|
|
169
|
+
store: storeName,
|
|
170
|
+
availableStores,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
function builtinAdapters(config) {
|
|
175
|
+
return [
|
|
176
|
+
createJsonRuntimeAdapter(config),
|
|
177
|
+
createMemoryRuntimeAdapter(config),
|
|
178
|
+
createSourceRuntimeAdapter(config),
|
|
179
|
+
createStaticRuntimeAdapter(config),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
function storeRecord(value) {
|
|
183
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
|
|
184
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type SourceMetadataEntry = {
|
|
2
|
+
path?: string | null;
|
|
3
|
+
format?: unknown;
|
|
4
|
+
hash?: unknown;
|
|
5
|
+
updatedAt?: string;
|
|
6
|
+
};
|
|
7
|
+
export type SourceMetadata = {
|
|
8
|
+
resources: Record<string, SourceMetadataEntry>;
|
|
9
|
+
};
|
|
10
|
+
type SourceMetadataConfig = {
|
|
11
|
+
cwd: string;
|
|
12
|
+
};
|
|
13
|
+
type SourceMetadataResource = {
|
|
14
|
+
name: string;
|
|
15
|
+
dataHash?: unknown;
|
|
16
|
+
dataPath?: string | null;
|
|
17
|
+
dataFormat?: unknown;
|
|
18
|
+
};
|
|
19
|
+
export declare function updateSourceMetadataResource(sourceMetadata: SourceMetadata, config: SourceMetadataConfig, resource: SourceMetadataResource): void;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function updateSourceMetadataResource(sourceMetadata, config, resource) {
|
|
2
|
+
if (!resource.dataHash) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
const previous = sourceMetadata.resources[resource.name];
|
|
6
|
+
const next = {
|
|
7
|
+
path: resource.dataPath ? relativePath(config, resource.dataPath) : null,
|
|
8
|
+
format: resource.dataFormat,
|
|
9
|
+
hash: resource.dataHash,
|
|
10
|
+
};
|
|
11
|
+
sourceMetadata.resources[resource.name] = {
|
|
12
|
+
...next,
|
|
13
|
+
updatedAt: sameSource(previous, next) && previous.updatedAt
|
|
14
|
+
? previous.updatedAt
|
|
15
|
+
: new Date().toISOString(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function sameSource(previous, next) {
|
|
19
|
+
return previous?.path === next.path
|
|
20
|
+
&& previous?.format === next.format
|
|
21
|
+
&& previous?.hash === next.hash;
|
|
22
|
+
}
|
|
23
|
+
function relativePath(config, filePath) {
|
|
24
|
+
return filePath.startsWith(config.cwd) ? filePath.slice(config.cwd.length + 1).split('\\').join('/') : filePath;
|
|
25
|
+
}
|