@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,916 @@
|
|
|
1
|
+
import { dbError } from './errors.js';
|
|
2
|
+
const DEFAULT_READ_POLICY = 'cache-first';
|
|
3
|
+
const DEFAULT_WRITE_POLICY = 'merge-and-invalidate';
|
|
4
|
+
const DEFAULT_EVENT_POLICY = 'invalidate';
|
|
5
|
+
const CACHE_READ_POLICIES = new Set(['cache-first', 'cache-and-network', 'network-first', 'network-only', 'cache-only']);
|
|
6
|
+
export function createClientCache(options = {}) {
|
|
7
|
+
const config = normalizeCacheConfig(options.cache);
|
|
8
|
+
if (!config.enabled) {
|
|
9
|
+
return disabledClientCache();
|
|
10
|
+
}
|
|
11
|
+
const state = {
|
|
12
|
+
manifest: config.manifest ?? null,
|
|
13
|
+
resources: new Map(),
|
|
14
|
+
resourcesByTypeName: new Map(),
|
|
15
|
+
collectionsBySingularName: new Map(),
|
|
16
|
+
queries: new Map(),
|
|
17
|
+
entities: new Map(),
|
|
18
|
+
inflight: new Map(),
|
|
19
|
+
subscribers: new Map(),
|
|
20
|
+
eventSources: [],
|
|
21
|
+
storage: normalizeStorage(config.storage),
|
|
22
|
+
storageNamespace: normalizeCacheNamespace(options.cacheNamespace),
|
|
23
|
+
storageLoaded: false,
|
|
24
|
+
storagePromise: null,
|
|
25
|
+
manifestPromise: null,
|
|
26
|
+
};
|
|
27
|
+
if (state.manifest) {
|
|
28
|
+
indexManifest(state, state.manifest);
|
|
29
|
+
}
|
|
30
|
+
async function ensureManifest() {
|
|
31
|
+
await ensureStorageLoaded(state);
|
|
32
|
+
if (state.manifest) {
|
|
33
|
+
connectEventSources(state, config, options);
|
|
34
|
+
return state.manifest;
|
|
35
|
+
}
|
|
36
|
+
state.manifestPromise ??= Promise.resolve()
|
|
37
|
+
.then(() => options.fetchManifest())
|
|
38
|
+
.then((manifest) => {
|
|
39
|
+
const nextManifest = manifest;
|
|
40
|
+
state.manifest = nextManifest;
|
|
41
|
+
indexManifest(state, nextManifest);
|
|
42
|
+
connectEventSources(state, config, options);
|
|
43
|
+
return nextManifest;
|
|
44
|
+
})
|
|
45
|
+
.finally(() => {
|
|
46
|
+
state.manifestPromise = null;
|
|
47
|
+
});
|
|
48
|
+
return state.manifestPromise;
|
|
49
|
+
}
|
|
50
|
+
async function executeRestRead(request, network, requestOptions = {}) {
|
|
51
|
+
const policy = readPolicyFor(requestOptions, config.readPolicy);
|
|
52
|
+
if (!policy) {
|
|
53
|
+
return network();
|
|
54
|
+
}
|
|
55
|
+
await ensureManifest();
|
|
56
|
+
const key = queryKey('rest', request);
|
|
57
|
+
const target = restTargetForRequest(state, request, options);
|
|
58
|
+
function writeNetworkResult(result) {
|
|
59
|
+
if (!shouldCacheRestResult(result)) {
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
const meta = createQueryMeta();
|
|
63
|
+
const normalizedBody = target
|
|
64
|
+
? normalizeResourceValue(state, target.resource, result.body, meta)
|
|
65
|
+
: normalizeAnyValue(state, result.body, meta);
|
|
66
|
+
writeQuery(state, key, {
|
|
67
|
+
...result,
|
|
68
|
+
body: normalizedBody,
|
|
69
|
+
}, meta, () => fetchOnce(key, network).then(writeNetworkResult));
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
return executeRead({
|
|
73
|
+
key,
|
|
74
|
+
policy,
|
|
75
|
+
network,
|
|
76
|
+
readCached: () => readQuery(state, key),
|
|
77
|
+
writeResult: writeNetworkResult,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function executeGraphqlRead(request, network, requestOptions = {}) {
|
|
81
|
+
const policy = readPolicyFor(requestOptions, config.readPolicy);
|
|
82
|
+
if (!policy) {
|
|
83
|
+
return network();
|
|
84
|
+
}
|
|
85
|
+
await ensureManifest();
|
|
86
|
+
const key = queryKey('graphql', request);
|
|
87
|
+
function writeNetworkResult(result) {
|
|
88
|
+
if (!shouldCacheGraphqlResult(result)) {
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
const meta = createQueryMeta();
|
|
92
|
+
const normalizedData = normalizeGraphqlData(state, result.data, meta);
|
|
93
|
+
writeQuery(state, key, {
|
|
94
|
+
...result,
|
|
95
|
+
data: normalizedData,
|
|
96
|
+
}, meta, () => fetchOnce(key, network).then(writeNetworkResult));
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
return executeRead({
|
|
100
|
+
key,
|
|
101
|
+
policy,
|
|
102
|
+
network,
|
|
103
|
+
readCached: () => readQuery(state, key),
|
|
104
|
+
writeResult: writeNetworkResult,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function executeRead({ key, policy, network, readCached, writeResult }) {
|
|
108
|
+
if (policy === 'network-first') {
|
|
109
|
+
try {
|
|
110
|
+
const result = await fetchOnce(key, network);
|
|
111
|
+
return writeResult(result);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const cached = readCached();
|
|
115
|
+
if (cached) {
|
|
116
|
+
return cached.value;
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (policy !== 'network-only') {
|
|
122
|
+
const cached = readCached();
|
|
123
|
+
if (cached && !cached.stale) {
|
|
124
|
+
if (policy === 'cache-and-network') {
|
|
125
|
+
void fetchOnce(key, network).then(writeResult, () => { });
|
|
126
|
+
}
|
|
127
|
+
return cached.value;
|
|
128
|
+
}
|
|
129
|
+
if (policy === 'cache-only') {
|
|
130
|
+
return cached?.value ?? null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const result = await fetchOnce(key, network);
|
|
134
|
+
return writeResult(result);
|
|
135
|
+
}
|
|
136
|
+
function fetchOnce(key, network) {
|
|
137
|
+
if (state.inflight.has(key)) {
|
|
138
|
+
return state.inflight.get(key);
|
|
139
|
+
}
|
|
140
|
+
const promise = Promise.resolve()
|
|
141
|
+
.then(network)
|
|
142
|
+
.finally(() => {
|
|
143
|
+
state.inflight.delete(key);
|
|
144
|
+
});
|
|
145
|
+
state.inflight.set(key, promise);
|
|
146
|
+
return promise;
|
|
147
|
+
}
|
|
148
|
+
function recordRestWrite(request, result) {
|
|
149
|
+
if (!isSuccessfulRestWrite(request, result)) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const target = restTargetForRequest(state, request, options);
|
|
153
|
+
if (!target) {
|
|
154
|
+
invalidateQueries(state, null, { staleRecords: true, refetch: config.writePolicy === 'refetch' });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (config.writePolicy === 'invalidate') {
|
|
158
|
+
invalidateQueries(state, target.resource.name, { staleRecords: true });
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const meta = createQueryMeta();
|
|
162
|
+
if (request.method === 'DELETE') {
|
|
163
|
+
if (target.id !== undefined) {
|
|
164
|
+
deleteEntity(state, target.resource.name, target.id);
|
|
165
|
+
}
|
|
166
|
+
invalidateQueries(state, target.resource.name, {
|
|
167
|
+
staleRecords: true,
|
|
168
|
+
refetch: config.writePolicy === 'refetch',
|
|
169
|
+
});
|
|
170
|
+
scheduleStorageSave(state);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
normalizeResourceValue(state, target.resource, result.body, meta);
|
|
174
|
+
invalidateQueries(state, target.resource.name, config.writePolicy === 'refetch'
|
|
175
|
+
? { staleRecords: true, refetch: true }
|
|
176
|
+
: { staleLists: true });
|
|
177
|
+
scheduleStorageSave(state);
|
|
178
|
+
}
|
|
179
|
+
function recordGraphqlWrite(result) {
|
|
180
|
+
if (!shouldCacheGraphqlResult(result)) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (config.writePolicy === 'invalidate') {
|
|
184
|
+
const meta = createQueryMeta();
|
|
185
|
+
collectGraphqlResources(state, result.data, meta);
|
|
186
|
+
invalidateMetaResources(state, meta, { staleRecords: true });
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const meta = createQueryMeta();
|
|
190
|
+
normalizeGraphqlData(state, result.data, meta);
|
|
191
|
+
invalidateMetaResources(state, meta, config.writePolicy === 'refetch'
|
|
192
|
+
? { staleRecords: true, refetch: true }
|
|
193
|
+
: { staleLists: true });
|
|
194
|
+
scheduleStorageSave(state);
|
|
195
|
+
}
|
|
196
|
+
const publicApi = {
|
|
197
|
+
enabled: true,
|
|
198
|
+
clear() {
|
|
199
|
+
state.queries.clear();
|
|
200
|
+
state.entities.clear();
|
|
201
|
+
void state.storage?.clear?.(storageContext(state));
|
|
202
|
+
notifySubscribers(state);
|
|
203
|
+
},
|
|
204
|
+
invalidate(resourceName) {
|
|
205
|
+
invalidateQueries(state, resourceName, { staleRecords: true });
|
|
206
|
+
},
|
|
207
|
+
snapshot() {
|
|
208
|
+
return snapshotForStorage(state);
|
|
209
|
+
},
|
|
210
|
+
watch(request, callback) {
|
|
211
|
+
const key = watchKey(request);
|
|
212
|
+
const subscribers = state.subscribers.get(key) ?? new Set();
|
|
213
|
+
subscribers.add(callback);
|
|
214
|
+
state.subscribers.set(key, subscribers);
|
|
215
|
+
const cached = readQuery(state, key);
|
|
216
|
+
if (cached) {
|
|
217
|
+
callback({
|
|
218
|
+
data: cached.value,
|
|
219
|
+
stale: cached.stale,
|
|
220
|
+
source: 'cache',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return () => {
|
|
224
|
+
subscribers.delete(callback);
|
|
225
|
+
if (subscribers.size === 0) {
|
|
226
|
+
state.subscribers.delete(key);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
return {
|
|
232
|
+
enabled: true,
|
|
233
|
+
publicApi,
|
|
234
|
+
executeRestRead,
|
|
235
|
+
executeGraphqlRead,
|
|
236
|
+
recordRestWrite,
|
|
237
|
+
recordGraphqlWrite,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function connectEventSources(state, config, options) {
|
|
241
|
+
if (config.eventPolicy === false || state.eventSources.length > 0 || typeof options.createEventSource !== 'function') {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const logPath = state.manifest?.api?.log;
|
|
245
|
+
const eventsPath = state.manifest?.api?.events;
|
|
246
|
+
if (logPath) {
|
|
247
|
+
const logEvents = options.createEventSource(logPath);
|
|
248
|
+
if (logEvents) {
|
|
249
|
+
addEventListener(logEvents, 'db-log', (event) => {
|
|
250
|
+
const payload = parseEventPayload(event);
|
|
251
|
+
if (payload?.resource) {
|
|
252
|
+
invalidateQueries(state, payload.resource, { staleRecords: true, refetch: config.eventPolicy === 'refetch' });
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
state.eventSources.push(logEvents);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (eventsPath) {
|
|
259
|
+
const sourceEvents = options.createEventSource(eventsPath);
|
|
260
|
+
if (sourceEvents) {
|
|
261
|
+
addEventListener(sourceEvents, 'db', (event) => {
|
|
262
|
+
const payload = parseEventPayload(event);
|
|
263
|
+
if (!payload || payload.type === 'connected') {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
state.manifest = null;
|
|
267
|
+
state.resources.clear();
|
|
268
|
+
state.resourcesByTypeName.clear();
|
|
269
|
+
state.collectionsBySingularName.clear();
|
|
270
|
+
invalidateQueries(state, null, { staleRecords: true, refetch: config.eventPolicy === 'refetch' });
|
|
271
|
+
});
|
|
272
|
+
state.eventSources.push(sourceEvents);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function addEventListener(source, type, listener) {
|
|
277
|
+
if (typeof source.addEventListener === 'function') {
|
|
278
|
+
source.addEventListener(type, listener);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (typeof source.on === 'function') {
|
|
282
|
+
source.on(type, listener);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function parseEventPayload(event) {
|
|
286
|
+
try {
|
|
287
|
+
return JSON.parse(event?.data ?? 'null');
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function disabledClientCache() {
|
|
294
|
+
return {
|
|
295
|
+
enabled: false,
|
|
296
|
+
publicApi: {
|
|
297
|
+
enabled: false,
|
|
298
|
+
clear() { },
|
|
299
|
+
invalidate() { },
|
|
300
|
+
snapshot() {
|
|
301
|
+
return {
|
|
302
|
+
manifest: null,
|
|
303
|
+
queries: [],
|
|
304
|
+
resources: {},
|
|
305
|
+
};
|
|
306
|
+
},
|
|
307
|
+
watch() {
|
|
308
|
+
return () => { };
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
async executeRestRead(_request, network) {
|
|
312
|
+
return network();
|
|
313
|
+
},
|
|
314
|
+
async executeGraphqlRead(_request, network) {
|
|
315
|
+
return network();
|
|
316
|
+
},
|
|
317
|
+
recordRestWrite() { },
|
|
318
|
+
recordGraphqlWrite() { },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function normalizeCacheConfig(cache) {
|
|
322
|
+
if (cache === true) {
|
|
323
|
+
return {
|
|
324
|
+
enabled: true,
|
|
325
|
+
readPolicy: DEFAULT_READ_POLICY,
|
|
326
|
+
writePolicy: DEFAULT_WRITE_POLICY,
|
|
327
|
+
eventPolicy: DEFAULT_EVENT_POLICY,
|
|
328
|
+
manifest: null,
|
|
329
|
+
storage: 'memory',
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (!cache || cache.enabled === false) {
|
|
333
|
+
return {
|
|
334
|
+
enabled: false,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
enabled: true,
|
|
339
|
+
readPolicy: normalizeReadPolicy(cache.readPolicy ?? DEFAULT_READ_POLICY),
|
|
340
|
+
writePolicy: cache.writePolicy ?? DEFAULT_WRITE_POLICY,
|
|
341
|
+
eventPolicy: cache.eventPolicy ?? DEFAULT_EVENT_POLICY,
|
|
342
|
+
manifest: cache.manifest ?? null,
|
|
343
|
+
storage: cache.storage ?? 'memory',
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
function normalizeStorage(storage) {
|
|
347
|
+
if (!storage || storage === 'memory') {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
if (typeof storage === 'object') {
|
|
351
|
+
return storage;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
function normalizeCacheNamespace(value) {
|
|
356
|
+
if (value === undefined || value === null || value === '') {
|
|
357
|
+
return 'default';
|
|
358
|
+
}
|
|
359
|
+
if (typeof value === 'string') {
|
|
360
|
+
return value;
|
|
361
|
+
}
|
|
362
|
+
return stableStringify(value);
|
|
363
|
+
}
|
|
364
|
+
async function ensureStorageLoaded(state) {
|
|
365
|
+
if (state.storageLoaded || !state.storage?.load) {
|
|
366
|
+
state.storageLoaded = true;
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
state.storagePromise ??= Promise.resolve()
|
|
370
|
+
.then(() => state.storage.load(storageContext(state)))
|
|
371
|
+
.then((snapshot) => {
|
|
372
|
+
hydrateStorageSnapshot(state, snapshot);
|
|
373
|
+
state.storageLoaded = true;
|
|
374
|
+
})
|
|
375
|
+
.catch(() => {
|
|
376
|
+
state.storageLoaded = true;
|
|
377
|
+
})
|
|
378
|
+
.finally(() => {
|
|
379
|
+
state.storagePromise = null;
|
|
380
|
+
});
|
|
381
|
+
await state.storagePromise;
|
|
382
|
+
}
|
|
383
|
+
function hydrateStorageSnapshot(state, snapshot) {
|
|
384
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (snapshot.manifest && !state.manifest) {
|
|
388
|
+
state.manifest = cloneValue(snapshot.manifest);
|
|
389
|
+
indexManifest(state, state.manifest);
|
|
390
|
+
}
|
|
391
|
+
if (snapshot.entities && typeof snapshot.entities === 'object') {
|
|
392
|
+
for (const [resourceName, records] of Object.entries(snapshot.entities)) {
|
|
393
|
+
state.entities.set(resourceName, new Map(Object.entries(records ?? {}).map(([id, value]) => [id, cloneValue(value)])));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
if (Array.isArray(snapshot.queries)) {
|
|
397
|
+
for (const entry of snapshot.queries) {
|
|
398
|
+
if (!entry?.key) {
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
state.queries.set(entry.key, {
|
|
402
|
+
value: cloneValue(entry.value),
|
|
403
|
+
stale: Boolean(entry.stale),
|
|
404
|
+
resources: new Set(entry.resources ?? []),
|
|
405
|
+
lists: new Set(entry.lists ?? []),
|
|
406
|
+
refetch: null,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function scheduleStorageSave(state) {
|
|
412
|
+
if (!state.storage?.save || !state.storageLoaded) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
void Promise.resolve(state.storage.save(snapshotForStorage(state), storageContext(state))).catch(() => { });
|
|
416
|
+
}
|
|
417
|
+
function snapshotForStorage(state) {
|
|
418
|
+
const context = storageContext(state);
|
|
419
|
+
return {
|
|
420
|
+
namespace: context.namespace,
|
|
421
|
+
manifestFingerprint: context.manifestFingerprint,
|
|
422
|
+
manifest: cloneValue(state.manifest),
|
|
423
|
+
queries: [...state.queries.entries()].map(([key, entry]) => ({
|
|
424
|
+
key,
|
|
425
|
+
value: cloneValue(entry.value),
|
|
426
|
+
stale: Boolean(entry.stale),
|
|
427
|
+
resources: [...entry.resources],
|
|
428
|
+
lists: [...entry.lists],
|
|
429
|
+
})),
|
|
430
|
+
resources: Object.fromEntries([...state.entities.entries()].map(([resourceName, records]) => [
|
|
431
|
+
resourceName,
|
|
432
|
+
Object.fromEntries([...records.entries()].map(([id, value]) => [id, denormalizeValue(state, value)])),
|
|
433
|
+
])),
|
|
434
|
+
entities: Object.fromEntries([...state.entities.entries()].map(([resourceName, records]) => [
|
|
435
|
+
resourceName,
|
|
436
|
+
Object.fromEntries([...records.entries()].map(([id, value]) => [id, cloneValue(value)])),
|
|
437
|
+
])),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
function storageContext(state) {
|
|
441
|
+
const baseNamespace = `async-db:${state.storageNamespace}`;
|
|
442
|
+
const manifestFingerprint = state.manifest ? stableManifestFingerprint(state.manifest) : null;
|
|
443
|
+
return {
|
|
444
|
+
baseNamespace,
|
|
445
|
+
manifestFingerprint,
|
|
446
|
+
namespace: manifestFingerprint ? `${baseNamespace}:${manifestFingerprint}` : baseNamespace,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function normalizeReadPolicy(value) {
|
|
450
|
+
const policy = String(value ?? DEFAULT_READ_POLICY);
|
|
451
|
+
if (CACHE_READ_POLICIES.has(policy)) {
|
|
452
|
+
return policy;
|
|
453
|
+
}
|
|
454
|
+
return DEFAULT_READ_POLICY;
|
|
455
|
+
}
|
|
456
|
+
function readPolicyFor(requestOptions, defaultPolicy) {
|
|
457
|
+
const option = requestOptions?.cache;
|
|
458
|
+
if (option === false) {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
if (typeof option === 'string') {
|
|
462
|
+
return normalizeReadPolicy(option);
|
|
463
|
+
}
|
|
464
|
+
if (option && typeof option === 'object' && option.readPolicy) {
|
|
465
|
+
return normalizeReadPolicy(option.readPolicy);
|
|
466
|
+
}
|
|
467
|
+
return defaultPolicy;
|
|
468
|
+
}
|
|
469
|
+
function indexManifest(state, manifest) {
|
|
470
|
+
state.resources.clear();
|
|
471
|
+
state.resourcesByTypeName.clear();
|
|
472
|
+
state.collectionsBySingularName.clear();
|
|
473
|
+
for (const [name, resource] of Object.entries(manifest?.collections ?? {})) {
|
|
474
|
+
const normalized = normalizeManifestResource(name, 'collection', resource);
|
|
475
|
+
state.resources.set(name, normalized);
|
|
476
|
+
if (normalized.typeName) {
|
|
477
|
+
state.resourcesByTypeName.set(normalized.typeName, normalized);
|
|
478
|
+
state.collectionsBySingularName.set(lowerFirst(normalized.typeName), normalized);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
for (const [name, resource] of Object.entries(manifest?.documents ?? {})) {
|
|
482
|
+
const normalized = normalizeManifestResource(name, 'document', resource);
|
|
483
|
+
state.resources.set(name, normalized);
|
|
484
|
+
if (normalized.typeName) {
|
|
485
|
+
state.resourcesByTypeName.set(normalized.typeName, normalized);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function normalizeManifestResource(name, kind, resource) {
|
|
490
|
+
return {
|
|
491
|
+
...resource,
|
|
492
|
+
name,
|
|
493
|
+
kind,
|
|
494
|
+
idField: resource.idField ?? 'id',
|
|
495
|
+
routePath: resource.routePath ?? `/${name}`,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function restTargetForRequest(state, request, options = {}) {
|
|
499
|
+
const method = String(request.method ?? 'GET').toUpperCase();
|
|
500
|
+
const url = new URL(request.path ?? '/', 'http://db.local');
|
|
501
|
+
const pathname = stripBasePath(url.pathname, options.restBasePath);
|
|
502
|
+
for (const resource of state.resources.values()) {
|
|
503
|
+
const route = resource.routePath ?? `/${resource.name}`;
|
|
504
|
+
const routeJson = `${route}.json`;
|
|
505
|
+
if (pathname === route || pathname === routeJson) {
|
|
506
|
+
return {
|
|
507
|
+
resource,
|
|
508
|
+
kind: resource.kind === 'collection' && method === 'GET' && url.searchParams.has('id') ? 'record' : resource.kind,
|
|
509
|
+
id: resource.kind === 'collection' && url.searchParams.has('id') ? url.searchParams.get('id') : undefined,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (resource.kind !== 'collection') {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
const prefix = `${route}/`;
|
|
516
|
+
if (pathname.startsWith(prefix)) {
|
|
517
|
+
const rawId = pathname.slice(prefix.length).split('/')[0];
|
|
518
|
+
return {
|
|
519
|
+
resource,
|
|
520
|
+
kind: 'record',
|
|
521
|
+
id: decodeURIComponent(stripFormat(rawId)),
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
function stripBasePath(pathname, basePath) {
|
|
528
|
+
if (!basePath) {
|
|
529
|
+
return pathname;
|
|
530
|
+
}
|
|
531
|
+
const base = `/${String(basePath).replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
532
|
+
if (pathname === base) {
|
|
533
|
+
return '/';
|
|
534
|
+
}
|
|
535
|
+
if (pathname.startsWith(`${base}/`)) {
|
|
536
|
+
return pathname.slice(base.length);
|
|
537
|
+
}
|
|
538
|
+
return pathname;
|
|
539
|
+
}
|
|
540
|
+
function stripFormat(value) {
|
|
541
|
+
return String(value ?? '').replace(/\.[A-Za-z][A-Za-z0-9_-]*$/, '');
|
|
542
|
+
}
|
|
543
|
+
function shouldCacheRestResult(result) {
|
|
544
|
+
return result && typeof result.status === 'number' && result.status >= 200 && result.status < 300;
|
|
545
|
+
}
|
|
546
|
+
function shouldCacheGraphqlResult(result) {
|
|
547
|
+
return result && (!Array.isArray(result.errors) || result.errors.length === 0);
|
|
548
|
+
}
|
|
549
|
+
function isSuccessfulRestWrite(request, result) {
|
|
550
|
+
const method = String(request.method ?? 'GET').toUpperCase();
|
|
551
|
+
return method !== 'GET' && shouldCacheRestResult(result);
|
|
552
|
+
}
|
|
553
|
+
function normalizeGraphqlData(state, data, meta) {
|
|
554
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
555
|
+
return cloneValue(data);
|
|
556
|
+
}
|
|
557
|
+
const normalized = {};
|
|
558
|
+
for (const [key, value] of Object.entries(data)) {
|
|
559
|
+
const resource = graphqlResourceForRoot(state, key, value);
|
|
560
|
+
normalized[key] = resource
|
|
561
|
+
? normalizeResourceValue(state, resource, value, meta)
|
|
562
|
+
: normalizeAnyValue(state, value, meta);
|
|
563
|
+
}
|
|
564
|
+
return normalized;
|
|
565
|
+
}
|
|
566
|
+
function collectGraphqlResources(state, value, meta) {
|
|
567
|
+
if (Array.isArray(value)) {
|
|
568
|
+
for (const item of value) {
|
|
569
|
+
collectGraphqlResources(state, item, meta);
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (!isObject(value)) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const resource = value.__typename ? state.resourcesByTypeName.get(value.__typename) : null;
|
|
577
|
+
if (resource) {
|
|
578
|
+
meta.resources.add(resource.name);
|
|
579
|
+
}
|
|
580
|
+
for (const child of Object.values(value)) {
|
|
581
|
+
collectGraphqlResources(state, child, meta);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function invalidateMetaResources(state, meta, options) {
|
|
585
|
+
if (meta.resources.size === 0) {
|
|
586
|
+
invalidateQueries(state, null, options);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
for (const resourceName of meta.resources) {
|
|
590
|
+
invalidateQueries(state, resourceName, options);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
function graphqlResourceForRoot(state, key, value) {
|
|
594
|
+
if (state.resources.has(key)) {
|
|
595
|
+
return state.resources.get(key);
|
|
596
|
+
}
|
|
597
|
+
if (state.collectionsBySingularName.has(key)) {
|
|
598
|
+
return state.collectionsBySingularName.get(key);
|
|
599
|
+
}
|
|
600
|
+
const sample = Array.isArray(value) ? value.find(isObject) : value;
|
|
601
|
+
if (sample?.__typename && state.resourcesByTypeName.has(sample.__typename)) {
|
|
602
|
+
return state.resourcesByTypeName.get(sample.__typename);
|
|
603
|
+
}
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
function normalizeResourceValue(state, resource, value, meta) {
|
|
607
|
+
meta.resources.add(resource.name);
|
|
608
|
+
if (resource.kind === 'document') {
|
|
609
|
+
const normalized = normalizeAnyValue(state, value, meta);
|
|
610
|
+
setEntity(state, resource.name, '__document', normalized);
|
|
611
|
+
return entityRef(resource.name, '__document');
|
|
612
|
+
}
|
|
613
|
+
if (Array.isArray(value)) {
|
|
614
|
+
meta.lists.add(resource.name);
|
|
615
|
+
return value.map((item) => normalizeRecordValue(state, resource, item, meta));
|
|
616
|
+
}
|
|
617
|
+
return normalizeRecordValue(state, resource, value, meta);
|
|
618
|
+
}
|
|
619
|
+
function normalizeRecordValue(state, resource, value, meta) {
|
|
620
|
+
if (!isObject(value)) {
|
|
621
|
+
return cloneValue(value);
|
|
622
|
+
}
|
|
623
|
+
const normalized = normalizeObjectFields(state, value, meta);
|
|
624
|
+
const id = normalized?.[resource.idField];
|
|
625
|
+
if (id === undefined || id === null || id === '') {
|
|
626
|
+
return normalized;
|
|
627
|
+
}
|
|
628
|
+
setEntity(state, resource.name, id, normalized);
|
|
629
|
+
return entityRef(resource.name, id);
|
|
630
|
+
}
|
|
631
|
+
function normalizeAnyValue(state, value, meta) {
|
|
632
|
+
if (Array.isArray(value)) {
|
|
633
|
+
return value.map((item) => normalizeAnyValue(state, item, meta));
|
|
634
|
+
}
|
|
635
|
+
if (!isObject(value)) {
|
|
636
|
+
return cloneValue(value);
|
|
637
|
+
}
|
|
638
|
+
const resource = value.__typename ? state.resourcesByTypeName.get(value.__typename) : null;
|
|
639
|
+
if (resource?.kind === 'collection' && value[resource.idField] !== undefined) {
|
|
640
|
+
meta.resources.add(resource.name);
|
|
641
|
+
return normalizeRecordValue(state, resource, value, meta);
|
|
642
|
+
}
|
|
643
|
+
if (resource?.kind === 'document') {
|
|
644
|
+
meta.resources.add(resource.name);
|
|
645
|
+
const normalized = normalizeObjectFields(state, value, meta);
|
|
646
|
+
setEntity(state, resource.name, '__document', normalized);
|
|
647
|
+
return entityRef(resource.name, '__document');
|
|
648
|
+
}
|
|
649
|
+
return normalizeObjectFields(state, value, meta);
|
|
650
|
+
}
|
|
651
|
+
function normalizeObjectFields(state, value, meta) {
|
|
652
|
+
const normalized = {};
|
|
653
|
+
for (const [key, child] of Object.entries(value)) {
|
|
654
|
+
normalized[key] = normalizeAnyValue(state, child, meta);
|
|
655
|
+
}
|
|
656
|
+
return normalized;
|
|
657
|
+
}
|
|
658
|
+
function setEntity(state, resourceName, id, value) {
|
|
659
|
+
const idKey = String(id);
|
|
660
|
+
const records = state.entities.get(resourceName) ?? new Map();
|
|
661
|
+
const existing = records.get(idKey);
|
|
662
|
+
records.set(idKey, mergeEntity(existing, value));
|
|
663
|
+
state.entities.set(resourceName, records);
|
|
664
|
+
}
|
|
665
|
+
function deleteEntity(state, resourceName, id) {
|
|
666
|
+
const records = state.entities.get(resourceName);
|
|
667
|
+
records?.delete(String(id));
|
|
668
|
+
}
|
|
669
|
+
function mergeEntity(existing, value) {
|
|
670
|
+
if (!isObject(existing) || !isObject(value)) {
|
|
671
|
+
return cloneValue(value);
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
...existing,
|
|
675
|
+
...cloneValue(value),
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function entityRef(resource, id) {
|
|
679
|
+
return {
|
|
680
|
+
__dbRef: {
|
|
681
|
+
resource,
|
|
682
|
+
id: String(id),
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
function createQueryMeta() {
|
|
687
|
+
return {
|
|
688
|
+
resources: new Set(),
|
|
689
|
+
lists: new Set(),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function writeQuery(state, key, value, meta, refetch = null) {
|
|
693
|
+
state.queries.set(key, {
|
|
694
|
+
value,
|
|
695
|
+
stale: false,
|
|
696
|
+
resources: new Set(meta.resources),
|
|
697
|
+
lists: new Set(meta.lists),
|
|
698
|
+
refetch,
|
|
699
|
+
});
|
|
700
|
+
scheduleStorageSave(state);
|
|
701
|
+
notifySubscribers(state, key);
|
|
702
|
+
}
|
|
703
|
+
function readQuery(state, key) {
|
|
704
|
+
const entry = state.queries.get(key);
|
|
705
|
+
if (!entry) {
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
return {
|
|
709
|
+
stale: entry.stale,
|
|
710
|
+
value: denormalizeValue(state, entry.value),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function invalidateQueries(state, resourceName, options = {}) {
|
|
714
|
+
for (const [key, entry] of state.queries.entries()) {
|
|
715
|
+
const touchesResource = !resourceName || entry.resources.has(resourceName);
|
|
716
|
+
if (!touchesResource) {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
const stale = !resourceName
|
|
720
|
+
|| options.staleRecords
|
|
721
|
+
|| (options.staleLists && entry.lists.has(resourceName));
|
|
722
|
+
if (stale) {
|
|
723
|
+
entry.stale = true;
|
|
724
|
+
}
|
|
725
|
+
notifySubscribers(state, key);
|
|
726
|
+
if (stale && options.refetch && state.subscribers.has(key) && typeof entry.refetch === 'function') {
|
|
727
|
+
void entry.refetch();
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
scheduleStorageSave(state);
|
|
731
|
+
}
|
|
732
|
+
function denormalizeValue(state, value) {
|
|
733
|
+
if (Array.isArray(value)) {
|
|
734
|
+
return value.map((item) => denormalizeValue(state, item));
|
|
735
|
+
}
|
|
736
|
+
if (!isObject(value)) {
|
|
737
|
+
return cloneValue(value);
|
|
738
|
+
}
|
|
739
|
+
if (value.__dbRef) {
|
|
740
|
+
const record = state.entities.get(value.__dbRef.resource)?.get(String(value.__dbRef.id));
|
|
741
|
+
return record ? denormalizeValue(state, record) : null;
|
|
742
|
+
}
|
|
743
|
+
const denormalized = {};
|
|
744
|
+
for (const [key, child] of Object.entries(value)) {
|
|
745
|
+
denormalized[key] = denormalizeValue(state, child);
|
|
746
|
+
}
|
|
747
|
+
return denormalized;
|
|
748
|
+
}
|
|
749
|
+
function notifySubscribers(state, key = null) {
|
|
750
|
+
const keys = key ? [key] : [...state.subscribers.keys()];
|
|
751
|
+
for (const queryKeyValue of keys) {
|
|
752
|
+
const subscribers = state.subscribers.get(queryKeyValue);
|
|
753
|
+
if (!subscribers?.size) {
|
|
754
|
+
continue;
|
|
755
|
+
}
|
|
756
|
+
const cached = readQuery(state, queryKeyValue);
|
|
757
|
+
for (const subscriber of subscribers) {
|
|
758
|
+
subscriber({
|
|
759
|
+
data: cached?.value ?? null,
|
|
760
|
+
stale: cached?.stale ?? true,
|
|
761
|
+
source: 'cache',
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
function watchKey(request) {
|
|
767
|
+
if (request?.kind === 'graphql') {
|
|
768
|
+
return queryKey('graphql', {
|
|
769
|
+
query: request.query,
|
|
770
|
+
variables: request.variables,
|
|
771
|
+
operationName: request.operationName,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
return queryKey('rest', {
|
|
775
|
+
method: request?.method ?? 'GET',
|
|
776
|
+
path: request?.path ?? '/',
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
function queryKey(kind, request) {
|
|
780
|
+
return `${kind}:${stableStringify(omitUndefined(request))}`;
|
|
781
|
+
}
|
|
782
|
+
function omitUndefined(value) {
|
|
783
|
+
if (Array.isArray(value)) {
|
|
784
|
+
return value.map(omitUndefined);
|
|
785
|
+
}
|
|
786
|
+
if (!isObject(value)) {
|
|
787
|
+
return value;
|
|
788
|
+
}
|
|
789
|
+
return Object.fromEntries(Object.entries(value)
|
|
790
|
+
.filter(([, entryValue]) => entryValue !== undefined)
|
|
791
|
+
.map(([key, entryValue]) => [key, omitUndefined(entryValue)]));
|
|
792
|
+
}
|
|
793
|
+
function stableStringify(value) {
|
|
794
|
+
if (Array.isArray(value)) {
|
|
795
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
796
|
+
}
|
|
797
|
+
if (value && typeof value === 'object') {
|
|
798
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
|
|
799
|
+
}
|
|
800
|
+
return JSON.stringify(value);
|
|
801
|
+
}
|
|
802
|
+
function stableManifestFingerprint(manifest) {
|
|
803
|
+
const string = stableStringify(stripGeneratedAt(manifest));
|
|
804
|
+
let hash = 2166136261;
|
|
805
|
+
for (let index = 0; index < string.length; index += 1) {
|
|
806
|
+
hash ^= string.charCodeAt(index);
|
|
807
|
+
hash = Math.imul(hash, 16777619);
|
|
808
|
+
}
|
|
809
|
+
return (hash >>> 0).toString(36);
|
|
810
|
+
}
|
|
811
|
+
function stripGeneratedAt(value) {
|
|
812
|
+
if (Array.isArray(value)) {
|
|
813
|
+
return value.map(stripGeneratedAt);
|
|
814
|
+
}
|
|
815
|
+
if (!isObject(value)) {
|
|
816
|
+
return value;
|
|
817
|
+
}
|
|
818
|
+
return Object.fromEntries(Object.entries(value)
|
|
819
|
+
.filter(([key]) => key !== 'generatedAt')
|
|
820
|
+
.map(([key, entryValue]) => [key, stripGeneratedAt(entryValue)]));
|
|
821
|
+
}
|
|
822
|
+
function cloneValue(value) {
|
|
823
|
+
if (value === undefined) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
if (typeof structuredClone === 'function') {
|
|
827
|
+
return structuredClone(value);
|
|
828
|
+
}
|
|
829
|
+
return JSON.parse(JSON.stringify(value));
|
|
830
|
+
}
|
|
831
|
+
function isObject(value) {
|
|
832
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
833
|
+
}
|
|
834
|
+
function lowerFirst(value) {
|
|
835
|
+
const string = String(value ?? '');
|
|
836
|
+
return string ? `${string[0].toLowerCase()}${string.slice(1)}` : string;
|
|
837
|
+
}
|
|
838
|
+
export function createIndexedDbCacheStorage(options = {}) {
|
|
839
|
+
const databaseName = options.name ?? 'async-db-cache';
|
|
840
|
+
const storeName = options.storeName ?? 'snapshots';
|
|
841
|
+
const hasExplicitKey = Object.prototype.hasOwnProperty.call(options, 'key');
|
|
842
|
+
const key = options.key ?? 'default';
|
|
843
|
+
const indexedDb = options.indexedDB ?? globalThis.indexedDB;
|
|
844
|
+
if (!indexedDb) {
|
|
845
|
+
throw dbError('CLIENT_INDEXEDDB_UNAVAILABLE', 'IndexedDB cache storage is not available in this environment.', {
|
|
846
|
+
hint: 'Use cache: { enabled: true, storage: "memory" } or call createIndexedDbCacheStorage() in a browser with IndexedDB.',
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
function storageKey(context) {
|
|
850
|
+
if (hasExplicitKey) {
|
|
851
|
+
return key;
|
|
852
|
+
}
|
|
853
|
+
return context?.namespace ?? context?.baseNamespace ?? key;
|
|
854
|
+
}
|
|
855
|
+
function latestKey(context) {
|
|
856
|
+
if (hasExplicitKey || !context?.baseNamespace) {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
return `${context.baseNamespace}:latest`;
|
|
860
|
+
}
|
|
861
|
+
async function withStore(mode, callback) {
|
|
862
|
+
const db = await openIndexedDb(indexedDb, databaseName, storeName);
|
|
863
|
+
return new Promise((resolve, reject) => {
|
|
864
|
+
const transaction = db.transaction(storeName, mode);
|
|
865
|
+
const store = transaction.objectStore(storeName);
|
|
866
|
+
const request = callback(store);
|
|
867
|
+
request.onsuccess = () => resolve(request.result ?? null);
|
|
868
|
+
request.onerror = () => reject(request.error ?? transaction.error);
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
return {
|
|
872
|
+
async load(context) {
|
|
873
|
+
const indexKey = latestKey(context);
|
|
874
|
+
if (indexKey && !context?.manifestFingerprint) {
|
|
875
|
+
const indexedKey = await withStore('readonly', (store) => store.get(indexKey));
|
|
876
|
+
if (indexedKey) {
|
|
877
|
+
return withStore('readonly', (store) => store.get(indexedKey));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return withStore('readonly', (store) => store.get(storageKey(context)));
|
|
881
|
+
},
|
|
882
|
+
async save(snapshot, context) {
|
|
883
|
+
const selectedKey = storageKey(context);
|
|
884
|
+
await withStore('readwrite', (store) => store.put(snapshot, selectedKey));
|
|
885
|
+
const indexKey = latestKey(context);
|
|
886
|
+
if (indexKey) {
|
|
887
|
+
await withStore('readwrite', (store) => store.put(selectedKey, indexKey));
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
async clear(context) {
|
|
891
|
+
const selectedKey = storageKey(context);
|
|
892
|
+
const indexKey = latestKey(context);
|
|
893
|
+
if (indexKey) {
|
|
894
|
+
const indexedKey = await withStore('readonly', (store) => store.get(indexKey));
|
|
895
|
+
if (indexedKey) {
|
|
896
|
+
await withStore('readwrite', (store) => store.delete(indexedKey));
|
|
897
|
+
}
|
|
898
|
+
await withStore('readwrite', (store) => store.delete(indexKey));
|
|
899
|
+
}
|
|
900
|
+
await withStore('readwrite', (store) => store.delete(selectedKey));
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function openIndexedDb(indexedDb, databaseName, storeName) {
|
|
905
|
+
return new Promise((resolve, reject) => {
|
|
906
|
+
const request = indexedDb.open(databaseName, 1);
|
|
907
|
+
request.onupgradeneeded = () => {
|
|
908
|
+
const db = request.result;
|
|
909
|
+
if (!db.objectStoreNames?.contains?.(storeName)) {
|
|
910
|
+
db.createObjectStore(storeName);
|
|
911
|
+
}
|
|
912
|
+
};
|
|
913
|
+
request.onsuccess = () => resolve(request.result);
|
|
914
|
+
request.onerror = () => reject(request.error);
|
|
915
|
+
});
|
|
916
|
+
}
|