@pithy-sh/core 0.1.2 → 0.1.4
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/dist/address/address.d.ts +57 -0
- package/dist/address/address.d.ts.map +1 -0
- package/dist/address/address.js +71 -0
- package/dist/audit/auditEvent.d.ts +89 -0
- package/dist/audit/auditEvent.d.ts.map +1 -0
- package/dist/audit/auditEvent.js +69 -0
- package/dist/audit/recorder.d.ts +18 -0
- package/dist/audit/recorder.d.ts.map +1 -0
- package/dist/audit/recorder.js +8 -0
- package/dist/capability/bindings.d.ts +62 -0
- package/dist/capability/bindings.d.ts.map +1 -0
- package/dist/capability/bindings.js +97 -0
- package/dist/capability/capability.d.ts +501 -0
- package/dist/capability/capability.d.ts.map +1 -0
- package/dist/capability/capability.js +16 -0
- package/dist/capability/client.d.ts +70 -0
- package/dist/capability/client.d.ts.map +1 -0
- package/dist/capability/client.js +86 -0
- package/dist/capability/compose.d.ts +25 -0
- package/dist/capability/compose.d.ts.map +1 -0
- package/dist/capability/compose.js +54 -0
- package/dist/capability/composition.d.ts +26 -0
- package/dist/capability/composition.d.ts.map +1 -0
- package/dist/capability/composition.js +84 -0
- package/dist/capability/config.d.ts +7 -0
- package/dist/capability/config.d.ts.map +1 -0
- package/dist/capability/config.js +14 -0
- package/dist/capability/devSecret.d.ts +32 -0
- package/dist/capability/devSecret.d.ts.map +1 -0
- package/dist/capability/devSecret.js +28 -0
- package/dist/capability/manifest.d.ts +298 -0
- package/dist/capability/manifest.d.ts.map +1 -0
- package/dist/capability/manifest.js +356 -0
- package/dist/capability/secretOrigin.d.ts +121 -0
- package/dist/capability/secretOrigin.d.ts.map +1 -0
- package/dist/capability/secretOrigin.js +134 -0
- package/dist/capability/settings.d.ts +119 -0
- package/dist/capability/settings.d.ts.map +1 -0
- package/dist/capability/settings.js +66 -0
- package/dist/capability/validateBindings.d.ts +4 -0
- package/dist/capability/validateBindings.d.ts.map +1 -0
- package/dist/capability/validateBindings.js +34 -0
- package/dist/capability/vanishingKey.d.ts +31 -0
- package/dist/capability/vanishingKey.d.ts.map +1 -0
- package/dist/capability/vanishingKey.js +79 -0
- package/dist/controlPlane/audit/actions.d.ts +70 -0
- package/dist/controlPlane/audit/actions.d.ts.map +1 -0
- package/dist/controlPlane/audit/actions.js +76 -0
- package/dist/controlPlane/capability.d.ts +47 -0
- package/dist/controlPlane/capability.d.ts.map +1 -0
- package/dist/controlPlane/capability.js +138 -0
- package/dist/controlPlane/config/config.d.ts +19 -0
- package/dist/controlPlane/config/config.d.ts.map +1 -0
- package/dist/controlPlane/config/config.js +85 -0
- package/dist/controlPlane/context.d.ts +27 -0
- package/dist/controlPlane/context.d.ts.map +1 -0
- package/dist/controlPlane/context.js +29 -0
- package/dist/controlPlane/data/connection.d.ts +79 -0
- package/dist/controlPlane/data/connection.d.ts.map +1 -0
- package/dist/controlPlane/data/connection.js +55 -0
- package/dist/controlPlane/data/keyLifecycle.d.ts +62 -0
- package/dist/controlPlane/data/keyLifecycle.d.ts.map +1 -0
- package/dist/controlPlane/data/keyLifecycle.js +126 -0
- package/dist/controlPlane/data/replay.d.ts +18 -0
- package/dist/controlPlane/data/replay.d.ts.map +1 -0
- package/dist/controlPlane/data/replay.js +20 -0
- package/dist/controlPlane/data/tables.d.ts +34 -0
- package/dist/controlPlane/data/tables.d.ts.map +1 -0
- package/dist/controlPlane/data/tables.js +37 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts +313 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -0
- package/dist/controlPlane/discovery/adminRoute.js +136 -0
- package/dist/controlPlane/discovery/configuration.d.ts +108 -0
- package/dist/controlPlane/discovery/configuration.d.ts.map +1 -0
- package/dist/controlPlane/discovery/configuration.js +187 -0
- package/dist/controlPlane/discovery/drift.d.ts +64 -0
- package/dist/controlPlane/discovery/drift.d.ts.map +1 -0
- package/dist/controlPlane/discovery/drift.js +58 -0
- package/dist/controlPlane/discovery/health.d.ts +99 -0
- package/dist/controlPlane/discovery/health.d.ts.map +1 -0
- package/dist/controlPlane/discovery/health.js +151 -0
- package/dist/controlPlane/discovery/healthSummary.d.ts +236 -0
- package/dist/controlPlane/discovery/healthSummary.d.ts.map +1 -0
- package/dist/controlPlane/discovery/healthSummary.js +278 -0
- package/dist/controlPlane/error/errors.d.ts +67 -0
- package/dist/controlPlane/error/errors.d.ts.map +1 -0
- package/dist/controlPlane/error/errors.js +79 -0
- package/dist/controlPlane/http/cors.d.ts +112 -0
- package/dist/controlPlane/http/cors.d.ts.map +1 -0
- package/dist/controlPlane/http/cors.js +192 -0
- package/dist/controlPlane/http/guard.d.ts +77 -0
- package/dist/controlPlane/http/guard.d.ts.map +1 -0
- package/dist/controlPlane/http/guard.js +142 -0
- package/dist/controlPlane/http/handlers.d.ts +179 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -0
- package/dist/controlPlane/http/handlers.js +245 -0
- package/dist/controlPlane/http/responses.d.ts +99 -0
- package/dist/controlPlane/http/responses.d.ts.map +1 -0
- package/dist/controlPlane/http/responses.js +63 -0
- package/dist/controlPlane/http/routes.d.ts +40 -0
- package/dist/controlPlane/http/routes.d.ts.map +1 -0
- package/dist/controlPlane/http/routes.js +83 -0
- package/dist/controlPlane/http/schemas.d.ts +39 -0
- package/dist/controlPlane/http/schemas.d.ts.map +1 -0
- package/dist/controlPlane/http/schemas.js +31 -0
- package/dist/controlPlane/http/verify.d.ts +84 -0
- package/dist/controlPlane/http/verify.d.ts.map +1 -0
- package/dist/controlPlane/http/verify.js +65 -0
- package/dist/controlPlane/migrations/0001_init.d.ts +51 -0
- package/dist/controlPlane/migrations/0001_init.d.ts.map +1 -0
- package/dist/controlPlane/migrations/0001_init.js +65 -0
- package/dist/controlPlane/replay/d1Guard.d.ts +29 -0
- package/dist/controlPlane/replay/d1Guard.d.ts.map +1 -0
- package/dist/controlPlane/replay/d1Guard.js +39 -0
- package/dist/controlPlane/replay/guard.d.ts +52 -0
- package/dist/controlPlane/replay/guard.d.ts.map +1 -0
- package/dist/controlPlane/replay/guard.js +0 -0
- package/dist/controlPlane/replay/kvGuard.d.ts +99 -0
- package/dist/controlPlane/replay/kvGuard.d.ts.map +1 -0
- package/dist/controlPlane/replay/kvGuard.js +101 -0
- package/dist/controlPlane/scope/scope.d.ts +68 -0
- package/dist/controlPlane/scope/scope.d.ts.map +1 -0
- package/dist/controlPlane/scope/scope.js +63 -0
- package/dist/controlPlane/token/base64url.d.ts +10 -0
- package/dist/controlPlane/token/base64url.d.ts.map +1 -0
- package/dist/controlPlane/token/base64url.js +48 -0
- package/dist/controlPlane/token/claims.d.ts +83 -0
- package/dist/controlPlane/token/claims.d.ts.map +1 -0
- package/dist/controlPlane/token/claims.js +86 -0
- package/dist/controlPlane/token/digest.d.ts +44 -0
- package/dist/controlPlane/token/digest.d.ts.map +1 -0
- package/dist/controlPlane/token/digest.js +56 -0
- package/dist/controlPlane/token/jws.d.ts +52 -0
- package/dist/controlPlane/token/jws.d.ts.map +1 -0
- package/dist/controlPlane/token/jws.js +65 -0
- package/dist/controlPlane/token/mint.d.ts +56 -0
- package/dist/controlPlane/token/mint.d.ts.map +1 -0
- package/dist/controlPlane/token/mint.js +55 -0
- package/dist/controlPlane/wire.d.ts +122 -0
- package/dist/controlPlane/wire.d.ts.map +1 -0
- package/dist/controlPlane/wire.js +123 -0
- package/dist/createBackend.d.ts +58 -0
- package/dist/createBackend.d.ts.map +1 -0
- package/dist/createBackend.js +170 -0
- package/dist/createEntrypoint.d.ts +53 -0
- package/dist/createEntrypoint.d.ts.map +1 -0
- package/dist/createEntrypoint.js +52 -0
- package/dist/data/boundParameters.d.ts +83 -0
- package/dist/data/boundParameters.d.ts.map +1 -0
- package/dist/data/boundParameters.js +162 -0
- package/dist/data/codecs.d.ts +34 -0
- package/dist/data/codecs.d.ts.map +1 -0
- package/dist/data/codecs.js +135 -0
- package/dist/data/cursor.d.ts +81 -0
- package/dist/data/cursor.d.ts.map +1 -0
- package/dist/data/cursor.js +104 -0
- package/dist/data/databases.d.ts +55 -0
- package/dist/data/databases.d.ts.map +1 -0
- package/dist/data/databases.js +32 -0
- package/dist/data/db.d.ts +42 -0
- package/dist/data/db.d.ts.map +1 -0
- package/dist/data/db.js +24 -0
- package/dist/data/withD1Retry.d.ts +91 -0
- package/dist/data/withD1Retry.d.ts.map +1 -0
- package/dist/data/withD1Retry.js +152 -0
- package/dist/entitlement/entitlement.d.ts +118 -0
- package/dist/entitlement/entitlement.d.ts.map +1 -0
- package/dist/entitlement/entitlement.js +59 -0
- package/dist/entitlement/gateScan.d.ts +26 -0
- package/dist/entitlement/gateScan.d.ts.map +1 -0
- package/dist/entitlement/gateScan.js +96 -0
- package/dist/entitlement/require.d.ts +12 -0
- package/dist/entitlement/require.d.ts.map +1 -0
- package/dist/entitlement/require.js +123 -0
- package/dist/env/ambient.d.ts +52 -0
- package/dist/env/ambient.d.ts.map +1 -0
- package/dist/env/ambient.js +35 -0
- package/dist/env/ci.d.ts +36 -0
- package/dist/env/ci.d.ts.map +1 -0
- package/dist/env/ci.js +40 -0
- package/dist/env/stem.d.ts +23 -0
- package/dist/env/stem.d.ts.map +1 -0
- package/dist/env/stem.js +29 -0
- package/dist/error/cause.d.ts +143 -0
- package/dist/error/cause.d.ts.map +1 -0
- package/dist/error/cause.js +192 -0
- package/dist/error/client.d.ts +35 -0
- package/dist/error/client.d.ts.map +1 -0
- package/dist/error/client.js +38 -0
- package/dist/error/extend.d.ts +101 -0
- package/dist/error/extend.d.ts.map +1 -0
- package/dist/error/extend.js +76 -0
- package/dist/error/http.d.ts +1516 -0
- package/dist/error/http.d.ts.map +1 -0
- package/dist/error/http.js +72 -0
- package/dist/error/payload.d.ts +3034 -0
- package/dist/error/payload.d.ts.map +1 -0
- package/dist/error/payload.js +1110 -0
- package/dist/error/pithyError.d.ts +154 -0
- package/dist/error/pithyError.d.ts.map +1 -0
- package/dist/error/pithyError.js +197 -0
- package/dist/error/terminal.d.ts +28 -0
- package/dist/error/terminal.d.ts.map +1 -0
- package/dist/error/terminal.js +33 -0
- package/dist/http/authContext.d.ts +21 -0
- package/dist/http/authContext.d.ts.map +1 -0
- package/dist/http/authContext.js +23 -0
- package/dist/http/routeContract.d.ts +39 -0
- package/dist/http/routeContract.d.ts.map +1 -0
- package/dist/http/routeContract.js +82 -0
- package/dist/http/sameOrigin.d.ts +47 -0
- package/dist/http/sameOrigin.d.ts.map +1 -0
- package/dist/http/sameOrigin.js +24 -0
- package/dist/http/signedWebhook.d.ts +225 -0
- package/dist/http/signedWebhook.d.ts.map +1 -0
- package/dist/http/signedWebhook.js +247 -0
- package/dist/http/validation.d.ts +37 -0
- package/dist/http/validation.d.ts.map +1 -0
- package/dist/http/validation.js +14 -0
- package/dist/http/verification.d.ts +10 -0
- package/dist/http/verification.d.ts.map +1 -0
- package/dist/http/verification.js +17 -0
- package/dist/i18n/acceptLanguage.d.ts +22 -0
- package/dist/i18n/acceptLanguage.d.ts.map +1 -0
- package/dist/i18n/acceptLanguage.js +55 -0
- package/dist/i18n/catalog.d.ts +39 -0
- package/dist/i18n/catalog.d.ts.map +1 -0
- package/dist/i18n/catalog.js +57 -0
- package/dist/i18n/locale.d.ts +74 -0
- package/dist/i18n/locale.d.ts.map +1 -0
- package/dist/i18n/locale.js +109 -0
- package/dist/i18n/localeMarker.d.ts +29 -0
- package/dist/i18n/localeMarker.d.ts.map +1 -0
- package/dist/i18n/localeMarker.js +112 -0
- package/dist/i18n/match.d.ts +26 -0
- package/dist/i18n/match.d.ts.map +1 -0
- package/dist/i18n/match.js +88 -0
- package/dist/i18n/registry.d.ts +25 -0
- package/dist/i18n/registry.d.ts.map +1 -0
- package/dist/i18n/registry.js +51 -0
- package/dist/i18n/translator.d.ts +107 -0
- package/dist/i18n/translator.d.ts.map +1 -0
- package/dist/i18n/translator.js +80 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/kv/kv.d.ts +166 -0
- package/dist/kv/kv.d.ts.map +1 -0
- package/dist/kv/kv.js +261 -0
- package/dist/kv/namespaces.d.ts +58 -0
- package/dist/kv/namespaces.d.ts.map +1 -0
- package/dist/kv/namespaces.js +40 -0
- package/dist/logger/local.d.ts +41 -0
- package/dist/logger/local.d.ts.map +1 -0
- package/dist/logger/local.js +45 -0
- package/dist/logger/logger.d.ts +56 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +83 -0
- package/dist/logger/record.d.ts +919 -0
- package/dist/logger/record.d.ts.map +1 -0
- package/dist/logger/record.js +79 -0
- package/dist/logger/worker.d.ts +74 -0
- package/dist/logger/worker.d.ts.map +1 -0
- package/dist/logger/worker.js +54 -0
- package/dist/migrations/batch.d.ts +10 -0
- package/dist/migrations/batch.d.ts.map +1 -0
- package/dist/migrations/batch.js +193 -0
- package/dist/migrations/bookkeeping.d.ts +24 -0
- package/dist/migrations/bookkeeping.d.ts.map +1 -0
- package/dist/migrations/bookkeeping.js +57 -0
- package/dist/migrations/owner.d.ts +41 -0
- package/dist/migrations/owner.d.ts.map +1 -0
- package/dist/migrations/owner.js +105 -0
- package/dist/migrations/registry.d.ts +36 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +70 -0
- package/dist/migrations/runner.d.ts +67 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +218 -0
- package/dist/naming/domains.d.ts +108 -0
- package/dist/naming/domains.d.ts.map +1 -0
- package/dist/naming/domains.js +156 -0
- package/dist/naming/environment.d.ts +118 -0
- package/dist/naming/environment.d.ts.map +1 -0
- package/dist/naming/environment.js +190 -0
- package/dist/naming/feature.d.ts +68 -0
- package/dist/naming/feature.d.ts.map +1 -0
- package/dist/naming/feature.js +106 -0
- package/dist/naming/limits.d.ts +229 -0
- package/dist/naming/limits.d.ts.map +1 -0
- package/dist/naming/limits.js +187 -0
- package/dist/naming/provisionScope.d.ts +91 -0
- package/dist/naming/provisionScope.d.ts.map +1 -0
- package/dist/naming/provisionScope.js +65 -0
- package/dist/naming/resource.d.ts +169 -0
- package/dist/naming/resource.d.ts.map +1 -0
- package/dist/naming/resource.js +220 -0
- package/dist/naming/resourceNames.d.ts +83 -0
- package/dist/naming/resourceNames.d.ts.map +1 -0
- package/dist/naming/resourceNames.js +102 -0
- package/dist/naming/segment.d.ts +29 -0
- package/dist/naming/segment.d.ts.map +1 -0
- package/dist/naming/segment.js +31 -0
- package/dist/projection/asRead.d.ts +72 -0
- package/dist/projection/asRead.d.ts.map +1 -0
- package/dist/projection/asRead.js +169 -0
- package/dist/projection/published.d.ts +95 -0
- package/dist/projection/published.d.ts.map +1 -0
- package/dist/projection/published.js +114 -0
- package/dist/schema/describedness.d.ts +55 -0
- package/dist/schema/describedness.d.ts.map +1 -0
- package/dist/schema/describedness.js +180 -0
- package/dist/seed/compose.d.ts +41 -0
- package/dist/seed/compose.d.ts.map +1 -0
- package/dist/seed/compose.js +51 -0
- package/dist/seed/devLogin.d.ts +50 -0
- package/dist/seed/devLogin.d.ts.map +1 -0
- package/dist/seed/devLogin.js +52 -0
- package/dist/seed/exampleIdentities.d.ts +58 -0
- package/dist/seed/exampleIdentities.d.ts.map +1 -0
- package/dist/seed/exampleIdentities.js +26 -0
- package/dist/seed/metadata.d.ts +20 -0
- package/dist/seed/metadata.d.ts.map +1 -0
- package/dist/seed/metadata.js +27 -0
- package/dist/seed/seed.d.ts +264 -0
- package/dist/seed/seed.d.ts.map +1 -0
- package/dist/seed/seed.js +37 -0
- package/dist/seed/seededRows.d.ts +26 -0
- package/dist/seed/seededRows.d.ts.map +1 -0
- package/dist/seed/seededRows.js +20 -0
- package/dist/seed/writeD1.d.ts +43 -0
- package/dist/seed/writeD1.d.ts.map +1 -0
- package/dist/seed/writeD1.js +73 -0
- package/dist/seed/writeKv.d.ts +29 -0
- package/dist/seed/writeKv.d.ts.map +1 -0
- package/dist/seed/writeKv.js +63 -0
- package/dist/semver/semver.d.ts +62 -0
- package/dist/semver/semver.d.ts.map +1 -0
- package/dist/semver/semver.js +137 -0
- package/dist/text/comments.d.ts +62 -0
- package/dist/text/comments.d.ts.map +1 -0
- package/dist/text/comments.js +161 -0
- package/dist/version.generated.d.ts +5 -0
- package/dist/version.generated.d.ts.map +1 -0
- package/dist/version.generated.js +7 -0
- package/dist/worker/health.d.ts +39 -0
- package/dist/worker/health.d.ts.map +1 -0
- package/dist/worker/health.js +41 -0
- package/dist/worker/identity.d.ts +191 -0
- package/dist/worker/identity.d.ts.map +1 -0
- package/dist/worker/identity.js +162 -0
- package/dist/workflow/bindings.d.ts +41 -0
- package/dist/workflow/bindings.d.ts.map +1 -0
- package/dist/workflow/bindings.js +53 -0
- package/dist/workflow/dispatch.d.ts +98 -0
- package/dist/workflow/dispatch.d.ts.map +1 -0
- package/dist/workflow/dispatch.js +148 -0
- package/dist/workflow/dispatchRoute.d.ts +80 -0
- package/dist/workflow/dispatchRoute.d.ts.map +1 -0
- package/dist/workflow/dispatchRoute.js +134 -0
- package/dist/workflow/faults.d.ts +97 -0
- package/dist/workflow/faults.d.ts.map +1 -0
- package/dist/workflow/faults.js +111 -0
- package/dist/workflow/host.d.ts +176 -0
- package/dist/workflow/host.d.ts.map +1 -0
- package/dist/workflow/host.js +116 -0
- package/dist/workflow/hostEntry.d.ts +50 -0
- package/dist/workflow/hostEntry.d.ts.map +1 -0
- package/dist/workflow/hostEntry.js +24 -0
- package/dist/workflow/hostEnv.d.ts +143 -0
- package/dist/workflow/hostEnv.d.ts.map +1 -0
- package/dist/workflow/hostEnv.js +170 -0
- package/dist/workflow/loopback.d.ts +52 -0
- package/dist/workflow/loopback.d.ts.map +1 -0
- package/dist/workflow/loopback.js +76 -0
- package/dist/workflow/naming.d.ts +79 -0
- package/dist/workflow/naming.d.ts.map +1 -0
- package/dist/workflow/naming.js +138 -0
- package/dist/workflow/register.d.ts +19 -0
- package/dist/workflow/register.d.ts.map +1 -0
- package/dist/workflow/register.js +40 -0
- package/dist/workflow/schemas.d.ts +44 -0
- package/dist/workflow/schemas.d.ts.map +1 -0
- package/dist/workflow/schemas.js +43 -0
- package/dist/workflow/spec.d.ts +81 -0
- package/dist/workflow/spec.d.ts.map +1 -0
- package/dist/workflow/spec.js +0 -0
- package/dist/workflow/stepMessage.d.ts +122 -0
- package/dist/workflow/stepMessage.d.ts.map +1 -0
- package/dist/workflow/stepMessage.js +152 -0
- package/package.json +20 -9
- package/src/capability/manifest.ts +21 -0
- package/src/version.generated.ts +1 -1
package/dist/kv/kv.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { InternalError, NotFoundError, messageOf } from "../error/pithyError.js";
|
|
2
|
+
import { noopLogger } from "../logger/logger.js";
|
|
3
|
+
//#region src/kv/kv.ts
|
|
4
|
+
/** Cloudflare's hard limit on a KV entry's metadata, in bytes. */
|
|
5
|
+
const KV_METADATA_MAX_BYTES = 1024;
|
|
6
|
+
const encoder = new TextEncoder();
|
|
7
|
+
function byteLength(value) {
|
|
8
|
+
return encoder.encode(value).length;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Wrap a metadata schema with KV's size limit.
|
|
12
|
+
*
|
|
13
|
+
* WARNING: metadata travels with **every** `list` entry and is capped at
|
|
14
|
+
* {@link KV_METADATA_MAX_BYTES} bytes serialized. It is for small, list-time
|
|
15
|
+
* fields you want without reading each value — a status flag, a type, a
|
|
16
|
+
* timestamp — never a payload. This refinement fails a value that would exceed
|
|
17
|
+
* the cap, turning a late KV rejection into an early, clear validation error.
|
|
18
|
+
*/
|
|
19
|
+
function kvMetadata(schema) {
|
|
20
|
+
return schema.refine((value) => byteLength(JSON.stringify(value)) <= KV_METADATA_MAX_BYTES, { message: `KV metadata exceeds ${KV_METADATA_MAX_BYTES} bytes serialized. Keep it tiny — it rides on every list entry.` });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Validate a {@link TypedKvConfig} at construction — config is a boundary, so it
|
|
24
|
+
* is checked, not trusted. Catches the misconfigurations the type system can't
|
|
25
|
+
* (or won't, for JS callers and casts): an empty namespace, a separator that
|
|
26
|
+
* collides with the prefix, a key with no segments, `deriveMetadata` without a
|
|
27
|
+
* `metadata` schema to validate against, or a non-positive default TTL.
|
|
28
|
+
*/
|
|
29
|
+
function assertValidConfig(config) {
|
|
30
|
+
if (config.prefix.length === 0) throw new InternalError({ message: "TypedKv config: prefix must be a non-empty capability namespace." });
|
|
31
|
+
const separator = config.separator ?? ":";
|
|
32
|
+
if (separator.length === 0) throw new InternalError({ message: "TypedKv config: separator must be a non-empty string." });
|
|
33
|
+
if (config.prefix.includes(separator)) throw new InternalError({ message: `TypedKv config: prefix "${config.prefix}" must not contain the separator "${separator}".` });
|
|
34
|
+
if (Object.keys(config.key.shape).length === 0) throw new InternalError({ message: "TypedKv config: key must declare at least one segment." });
|
|
35
|
+
if (config.deriveMetadata !== void 0 && config.metadata === void 0) throw new InternalError({ message: "TypedKv config: deriveMetadata requires a metadata schema to validate the derived value." });
|
|
36
|
+
if (config.ttlSeconds !== void 0 && config.ttlSeconds <= 0) throw new InternalError({ message: "TypedKv config: ttlSeconds must be positive." });
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Typed, validated access to one Workers KV namespace.
|
|
40
|
+
*
|
|
41
|
+
* Keys are structured: a fixed capability `prefix` followed by ordered,
|
|
42
|
+
* Zod-validated segments — so a single store models `pages:<uuid>` or
|
|
43
|
+
* `assets:<assetType>:<uuid>` and `list` can scope by any leading run of those
|
|
44
|
+
* segments. Values are validated on the way in and out; there is no untyped
|
|
45
|
+
* `JSON.parse`. Metadata is validated too and rides along with `list`; with
|
|
46
|
+
* `deriveMetadata` the value is the source of truth and `list` rebuilds metadata
|
|
47
|
+
* that external edits have dropped.
|
|
48
|
+
*/
|
|
49
|
+
var TypedKv = class {
|
|
50
|
+
#namespace;
|
|
51
|
+
#prefix;
|
|
52
|
+
#keySchema;
|
|
53
|
+
#valueSchema;
|
|
54
|
+
#metadataSchema;
|
|
55
|
+
#deriveMetadata;
|
|
56
|
+
#separator;
|
|
57
|
+
#defaultTtlSeconds;
|
|
58
|
+
#segmentNames;
|
|
59
|
+
#logger;
|
|
60
|
+
constructor(namespace, config, options) {
|
|
61
|
+
assertValidConfig(config);
|
|
62
|
+
this.#namespace = namespace;
|
|
63
|
+
this.#prefix = config.prefix;
|
|
64
|
+
this.#keySchema = config.key;
|
|
65
|
+
this.#valueSchema = config.value;
|
|
66
|
+
this.#metadataSchema = config.metadata;
|
|
67
|
+
this.#deriveMetadata = config.deriveMetadata;
|
|
68
|
+
this.#separator = config.separator ?? ":";
|
|
69
|
+
this.#defaultTtlSeconds = config.ttlSeconds;
|
|
70
|
+
this.#segmentNames = Object.keys(config.key.shape);
|
|
71
|
+
this.#logger = options?.logger ?? noopLogger;
|
|
72
|
+
}
|
|
73
|
+
/** Build the full physical KV key (name) from validated segments. */
|
|
74
|
+
name(key) {
|
|
75
|
+
const parsed = this.#keySchema.parse(key);
|
|
76
|
+
const segments = this.#segmentNames.map((segment) => this.#segment(segment, parsed[segment]));
|
|
77
|
+
return [this.#prefix, ...segments].join(this.#separator);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Read and validate the value at `key`. Returns `null` on a miss. The stored
|
|
81
|
+
* JSON is parsed and validated against the schema before it is returned.
|
|
82
|
+
*/
|
|
83
|
+
async get(key) {
|
|
84
|
+
const raw = await this.#namespace.get(this.name(key), "text");
|
|
85
|
+
return raw === null ? null : this.#parseValue(raw);
|
|
86
|
+
}
|
|
87
|
+
/** Read the value at `key` together with its validated metadata. `null` on a miss. */
|
|
88
|
+
async getWithMetadata(key) {
|
|
89
|
+
const result = await this.#namespace.getWithMetadata(this.name(key), "text");
|
|
90
|
+
if (result.value === null) return null;
|
|
91
|
+
return {
|
|
92
|
+
value: this.#parseValue(result.value),
|
|
93
|
+
metadata: this.#parseMetadata(result.metadata)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Validate `value` (and any `metadata`) against their schemas, then store the
|
|
98
|
+
* value as JSON under the namespaced key. With `deriveMetadata`, metadata is
|
|
99
|
+
* computed from the value unless overridden per call. Throws (before writing)
|
|
100
|
+
* on an invalid value, invalid metadata, or metadata over
|
|
101
|
+
* {@link KV_METADATA_MAX_BYTES}. A per-call `ttlSeconds` overrides the default.
|
|
102
|
+
*/
|
|
103
|
+
async put(key, value, options = {}) {
|
|
104
|
+
const validated = this.#valueSchema.parse(value);
|
|
105
|
+
const putOptions = {};
|
|
106
|
+
const ttlSeconds = options.ttlSeconds ?? this.#defaultTtlSeconds;
|
|
107
|
+
if (ttlSeconds !== void 0) putOptions.expirationTtl = ttlSeconds;
|
|
108
|
+
const metadata = this.#resolveMetadata(validated, options.metadata);
|
|
109
|
+
if (metadata !== void 0) putOptions.metadata = metadata;
|
|
110
|
+
await this.#namespace.put(this.name(key), JSON.stringify(validated), putOptions);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Partially update the value at `key`: read it, merge `changes` over the
|
|
114
|
+
* current fields, validate the merged result, and write it back. Returns the
|
|
115
|
+
* merged value. Throws if the key does not exist — there is nothing to merge.
|
|
116
|
+
*
|
|
117
|
+
* Metadata stays correct: a `deriveMetadata` store recomputes it from the
|
|
118
|
+
* merged value; an explicit-metadata store preserves the current metadata
|
|
119
|
+
* (pass `metadata` to override). TTL resets to the store default unless
|
|
120
|
+
* `ttlSeconds` is given.
|
|
121
|
+
*
|
|
122
|
+
* WARNING: KV has no compare-and-set, so this read-modify-write is
|
|
123
|
+
* last-write-wins. Concurrent patches to the same key can lose updates — do
|
|
124
|
+
* not use it where atomicity matters.
|
|
125
|
+
*/
|
|
126
|
+
async patch(key, changes, options = {}) {
|
|
127
|
+
const existing = await this.getWithMetadata(key);
|
|
128
|
+
if (existing === null) throw new NotFoundError({ message: `Cannot patch ${this.name(key)}: no value to update.` });
|
|
129
|
+
const merged = this.#valueSchema.parse({
|
|
130
|
+
...existing.value,
|
|
131
|
+
...changes
|
|
132
|
+
});
|
|
133
|
+
const putOptions = { ...options };
|
|
134
|
+
if (this.#deriveMetadata === void 0 && options.metadata === void 0 && existing.metadata !== null) putOptions.metadata = existing.metadata;
|
|
135
|
+
await this.put(key, merged, putOptions);
|
|
136
|
+
return merged;
|
|
137
|
+
}
|
|
138
|
+
/** Delete the value at `key`. A miss is a no-op. */
|
|
139
|
+
async delete(key) {
|
|
140
|
+
await this.#namespace.delete(this.name(key));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* List entries under a leading run of key segments. Each entry carries its
|
|
144
|
+
* parsed key segments and validated metadata — no value reads, unless
|
|
145
|
+
* `deriveMetadata` is configured and an entry's metadata is missing, in which
|
|
146
|
+
* case it is rebuilt from the value and written back. Pass `options.prefix` to
|
|
147
|
+
* scope (e.g. `{ assetType: "photo" }`); omit to list the whole namespace.
|
|
148
|
+
*/
|
|
149
|
+
async list(options = {}) {
|
|
150
|
+
const result = await this.#namespace.list({
|
|
151
|
+
prefix: this.#listPrefix(options.prefix),
|
|
152
|
+
limit: options.limit,
|
|
153
|
+
cursor: options.cursor
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
keys: await Promise.all(result.keys.map(async (entry) => ({
|
|
157
|
+
name: entry.name,
|
|
158
|
+
key: this.#splitKey(entry.name),
|
|
159
|
+
metadata: await this.#resolveListMetadata(entry),
|
|
160
|
+
expiration: entry.expiration
|
|
161
|
+
}))),
|
|
162
|
+
cursor: result.list_complete ? void 0 : result.cursor,
|
|
163
|
+
listComplete: result.list_complete
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve one list entry's metadata: parse what is stored, else self-heal it
|
|
168
|
+
* from the value when `deriveMetadata` is configured. A single entry with
|
|
169
|
+
* corrupt or schema-incompatible metadata/value must not abort the whole
|
|
170
|
+
* listing — it degrades to `null` metadata while the key is still returned.
|
|
171
|
+
*/
|
|
172
|
+
async #resolveListMetadata(entry) {
|
|
173
|
+
try {
|
|
174
|
+
const stored = this.#parseMetadata(entry.metadata);
|
|
175
|
+
if (stored !== null) return stored;
|
|
176
|
+
const derive = this.#deriveMetadata;
|
|
177
|
+
const metadataSchema = this.#metadataSchema;
|
|
178
|
+
if (derive === void 0 || metadataSchema === void 0) return null;
|
|
179
|
+
return await this.#rebuildMetadata(entry.name, entry.expiration, derive, metadataSchema);
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Resolve the metadata to store: explicit wins, else derive, else none. */
|
|
185
|
+
#resolveMetadata(value, explicit) {
|
|
186
|
+
if (this.#metadataSchema === void 0) return void 0;
|
|
187
|
+
let candidate;
|
|
188
|
+
if (explicit !== void 0) candidate = explicit;
|
|
189
|
+
else if (this.#deriveMetadata !== void 0) candidate = this.#deriveMetadata(value);
|
|
190
|
+
else return void 0;
|
|
191
|
+
return this.#validateMetadata(this.#metadataSchema, candidate);
|
|
192
|
+
}
|
|
193
|
+
/** Read the value, re-derive metadata, and write it back — preserving value bytes and expiration. */
|
|
194
|
+
async #rebuildMetadata(name, expiration, derive, metadataSchema) {
|
|
195
|
+
const raw = await this.#namespace.get(name, "text");
|
|
196
|
+
if (raw === null) return null;
|
|
197
|
+
const derived = derive(this.#parseValue(raw));
|
|
198
|
+
if (derived === null || derived === void 0) return null;
|
|
199
|
+
const metadata = this.#validateMetadata(metadataSchema, derived);
|
|
200
|
+
try {
|
|
201
|
+
const putOptions = { metadata };
|
|
202
|
+
if (expiration !== void 0) putOptions.expiration = expiration;
|
|
203
|
+
await this.#namespace.put(name, raw, putOptions);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
this.#logger.warn("kv metadata heal write-back failed", {
|
|
206
|
+
key: name,
|
|
207
|
+
detail: messageOf(error)
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return metadata;
|
|
211
|
+
}
|
|
212
|
+
/** Decode a stored value: JSON-parse, then validate against the value schema. */
|
|
213
|
+
#parseValue(raw) {
|
|
214
|
+
return this.#valueSchema.parse(JSON.parse(raw));
|
|
215
|
+
}
|
|
216
|
+
/** Validate a metadata candidate against the schema and the KV size cap. */
|
|
217
|
+
#validateMetadata(schema, candidate) {
|
|
218
|
+
const parsed = schema.parse(candidate);
|
|
219
|
+
const bytes = byteLength(JSON.stringify(parsed));
|
|
220
|
+
if (bytes > 1024) throw new InternalError({ message: `KV metadata is ${bytes} bytes; the limit is ${KV_METADATA_MAX_BYTES}. Keep metadata small.` });
|
|
221
|
+
return parsed;
|
|
222
|
+
}
|
|
223
|
+
/** Stringify and guard one key segment. */
|
|
224
|
+
#segment(name, value) {
|
|
225
|
+
const segment = String(value);
|
|
226
|
+
if (segment.includes(this.#separator)) throw new InternalError({
|
|
227
|
+
message: `Key segment "${name}" must not contain the separator "${this.#separator}".`,
|
|
228
|
+
detail: `Offending value for "${name}": ${segment}`
|
|
229
|
+
});
|
|
230
|
+
return segment;
|
|
231
|
+
}
|
|
232
|
+
/** Build the physical prefix string for a `list`, enforcing a proper, contiguous leading run. */
|
|
233
|
+
#listPrefix(parts) {
|
|
234
|
+
const trailing = `${this.#prefix}${this.#separator}`;
|
|
235
|
+
if (!parts) return trailing;
|
|
236
|
+
const provided = this.#keySchema.partial().parse(parts);
|
|
237
|
+
const present = this.#segmentNames.filter((name) => provided[name] !== void 0);
|
|
238
|
+
if (present.length === 0) return trailing;
|
|
239
|
+
if (present.length === this.#segmentNames.length) throw new InternalError({ message: "list prefix is a full key; use get() or getWithMetadata() for an exact key." });
|
|
240
|
+
const leading = this.#segmentNames.slice(0, present.length);
|
|
241
|
+
if (present.join(this.#separator) !== leading.join(this.#separator)) throw new InternalError({ message: `list prefix must be a leading run of key segments [${this.#segmentNames.join(", ")}]; got [${present.join(", ")}].` });
|
|
242
|
+
const segments = leading.map((name) => this.#segment(name, provided[name]));
|
|
243
|
+
return `${[this.#prefix, ...segments].join(this.#separator)}${this.#separator}`;
|
|
244
|
+
}
|
|
245
|
+
/** Split a physical key back into its named segments. Safe: segments never contain the separator. */
|
|
246
|
+
#splitKey(name) {
|
|
247
|
+
const segments = name.slice(this.#prefix.length + this.#separator.length).split(this.#separator);
|
|
248
|
+
const out = {};
|
|
249
|
+
this.#segmentNames.forEach((segmentName, index) => {
|
|
250
|
+
out[segmentName] = segments[index] ?? "";
|
|
251
|
+
});
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/** Validate stored metadata against the schema; `null` when absent or unconfigured. */
|
|
255
|
+
#parseMetadata(raw) {
|
|
256
|
+
if (this.#metadataSchema === void 0 || raw === null || raw === void 0) return null;
|
|
257
|
+
return this.#metadataSchema.parse(raw);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
//#endregion
|
|
261
|
+
export { KV_METADATA_MAX_BYTES, TypedKv, assertValidConfig, kvMetadata };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type { Capability } from "../capability/capability";
|
|
3
|
+
import { type BindingGroup } from "../capability/compose";
|
|
4
|
+
import type { Logger } from "../logger/logger";
|
|
5
|
+
import { TypedKv, type TypedKvConfig } from "./kv";
|
|
6
|
+
/**
|
|
7
|
+
* One typed store within a KV namespace — a {@link TypedKvConfig} (prefix, key, value, metadata).
|
|
8
|
+
* The peer of a D1 table: the binding lives on the enclosing namespace, not the store.
|
|
9
|
+
*/
|
|
10
|
+
export type KvStoreSpec<V extends z.ZodType = z.ZodType, K extends z.ZodObject = z.ZodObject, M extends z.ZodType = z.ZodType> = TypedKvConfig<V, K, M>;
|
|
11
|
+
/** A capability's stores for one namespace: store name → {@link KvStoreSpec}. */
|
|
12
|
+
export type KvStoreSpecMap = Record<string, KvStoreSpec>;
|
|
13
|
+
/**
|
|
14
|
+
* One named KV namespace a capability registers — the peer of a {@link DatabaseSpec}: the `binding`
|
|
15
|
+
* it lives in plus the named stores it holds. `createBackend` serves each store as a live
|
|
16
|
+
* {@link TypedKv} on `c.var.kv.<namespace>.<store>`. Capabilities targeting the same namespace name
|
|
17
|
+
* merge their stores.
|
|
18
|
+
*/
|
|
19
|
+
export interface KvNamespaceSpec<Stores extends KvStoreSpecMap = KvStoreSpecMap> {
|
|
20
|
+
/** The KV binding name in the Worker env this namespace lives in. */
|
|
21
|
+
binding: string;
|
|
22
|
+
/** The named typed stores in this namespace (store name → spec). */
|
|
23
|
+
stores: Stores;
|
|
24
|
+
}
|
|
25
|
+
/** A capability's KV namespaces: namespace name → {@link KvNamespaceSpec}. */
|
|
26
|
+
export type KvNamespaceSpecMap = Record<string, KvNamespaceSpec>;
|
|
27
|
+
/**
|
|
28
|
+
* The typed KV registry exposed on `c.var.kv`: namespace name → store name → live {@link TypedKv},
|
|
29
|
+
* preserving each store's value/key/metadata types — so `c.var.kv.cms.pages.get(...)` returns the
|
|
30
|
+
* page type, validated, and `c.var.kv.audit.pages` is a distinct store with no name conflict.
|
|
31
|
+
*/
|
|
32
|
+
export type KvRegistry<Namespaces extends KvNamespaceSpecMap> = {
|
|
33
|
+
[Ns in keyof Namespaces]: Namespaces[Ns] extends KvNamespaceSpec<infer Stores> ? {
|
|
34
|
+
[S in keyof Stores]: Stores[S] extends KvStoreSpec<infer V, infer K, infer M> ? TypedKv<V, K, M> : never;
|
|
35
|
+
} : never;
|
|
36
|
+
};
|
|
37
|
+
/** The merged namespaces: each namespace name → its binding and the union of every capability's stores. */
|
|
38
|
+
export type MergedKvNamespaces = Record<string, BindingGroup<KvStoreSpec>>;
|
|
39
|
+
/**
|
|
40
|
+
* Merge every capability's `kvNamespaces` into one map keyed by namespace name. Capabilities
|
|
41
|
+
* targeting the same name union their stores; a store claimed twice in one namespace, or a namespace
|
|
42
|
+
* name bound to two bindings, throws at assembly. Mirrors `composeDatabases`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function composeKv(capabilities: Capability[]): MergedKvNamespaces;
|
|
45
|
+
/** A registry of live stores keyed by namespace then store name; the per-request value of `c.var.kv`. */
|
|
46
|
+
type LiveStore = TypedKv<z.ZodType, z.ZodObject, z.ZodType>;
|
|
47
|
+
type LiveKvRegistry = Record<string, Record<string, LiveStore>>;
|
|
48
|
+
/**
|
|
49
|
+
* Build the per-request KV registry from the merged namespaces and the request's env. Each store is
|
|
50
|
+
* constructed lazily on first access — a request that never touches `c.var.kv.cms.fragments` never
|
|
51
|
+
* builds it (mirroring Leed's lazy `KVProvider` getters) — and the namespace's binding is read from
|
|
52
|
+
* the per-request env at access time. `logger` is the request logger (`c.var.log`) each store routes
|
|
53
|
+
* its observable best-effort failures through — notably the `list` self-heal write-back; it defaults to
|
|
54
|
+
* the no-op logger so a store built outside a request still constructs.
|
|
55
|
+
*/
|
|
56
|
+
export declare function buildKvRegistry(env: Record<string, unknown>, namespaces: MergedKvNamespaces, logger?: Logger): LiveKvRegistry;
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=namespaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namespaces.d.ts","sourceRoot":"","sources":["../../src/kv/namespaces.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAA2C,MAAM,uBAAuB,CAAC;AACnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,MAAM,CAAC;AAEnD;;;GAGG;AACH,MAAM,MAAM,WAAW,CACrB,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,EAC/B,CAAC,SAAS,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,EACnC,CAAC,SAAS,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,IAC7B,aAAa,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE3B,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,eAAe,CAAC,MAAM,SAAS,cAAc,GAAG,cAAc;IAC7E,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8EAA8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAEjE;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,UAAU,SAAS,kBAAkB,IAAI;KAC7D,EAAE,IAAI,MAAM,UAAU,GAAG,UAAU,CAAC,EAAE,CAAC,SAAS,eAAe,CAAC,MAAM,MAAM,CAAC,GAC1E;SAAG,CAAC,IAAI,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK;KAAE,GAC5G,KAAK;CACV,CAAC;AAEF,2GAA2G;AAC3G,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC;AAE3E;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,YAAY,EAAE,UAAU,EAAE,GAAG,kBAAkB,CAOxE;AAED,yGAAyG;AACzG,KAAK,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;AAC5D,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAEhE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5B,UAAU,EAAE,kBAAkB,EAC9B,MAAM,GAAE,MAAmB,GAC1B,cAAc,CAiBhB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { bindingGroupsFrom, composeBindingGroups } from "../capability/compose.js";
|
|
2
|
+
import { noopLogger } from "../logger/logger.js";
|
|
3
|
+
import { TypedKv } from "./kv.js";
|
|
4
|
+
//#region src/kv/namespaces.ts
|
|
5
|
+
/**
|
|
6
|
+
* Merge every capability's `kvNamespaces` into one map keyed by namespace name. Capabilities
|
|
7
|
+
* targeting the same name union their stores; a store claimed twice in one namespace, or a namespace
|
|
8
|
+
* name bound to two bindings, throws at assembly. Mirrors `composeDatabases`.
|
|
9
|
+
*/
|
|
10
|
+
function composeKv(capabilities) {
|
|
11
|
+
return composeBindingGroups(capabilities, (cap) => bindingGroupsFrom(cap.kvNamespaces, (ns) => ns.stores), "KV namespace", "store");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build the per-request KV registry from the merged namespaces and the request's env. Each store is
|
|
15
|
+
* constructed lazily on first access — a request that never touches `c.var.kv.cms.fragments` never
|
|
16
|
+
* builds it (mirroring Leed's lazy `KVProvider` getters) — and the namespace's binding is read from
|
|
17
|
+
* the per-request env at access time. `logger` is the request logger (`c.var.log`) each store routes
|
|
18
|
+
* its observable best-effort failures through — notably the `list` self-heal write-back; it defaults to
|
|
19
|
+
* the no-op logger so a store built outside a request still constructs.
|
|
20
|
+
*/
|
|
21
|
+
function buildKvRegistry(env, namespaces, logger = noopLogger) {
|
|
22
|
+
const registry = {};
|
|
23
|
+
for (const [namespace, group] of Object.entries(namespaces)) {
|
|
24
|
+
const stores = {};
|
|
25
|
+
for (const [name, spec] of Object.entries(group.items)) {
|
|
26
|
+
let instance;
|
|
27
|
+
Object.defineProperty(stores, name, {
|
|
28
|
+
enumerable: true,
|
|
29
|
+
get() {
|
|
30
|
+
if (!instance) instance = new TypedKv(env[group.binding], spec, { logger });
|
|
31
|
+
return instance;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
registry[namespace] = stores;
|
|
36
|
+
}
|
|
37
|
+
return registry;
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { buildKvRegistry, composeKv };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Logger } from "./logger";
|
|
2
|
+
import type { LogLevel } from "./record";
|
|
3
|
+
/**
|
|
4
|
+
* Mode 1 — local diagnostics. One unified diagnostic layer for the `pithy` CLI process and a Worker
|
|
5
|
+
* running under `pithy dev` / `wrangler dev`. Human-readable and (optionally) colorized for a person at
|
|
6
|
+
* a terminal, or a `--json` structured line stream for agents and CI. This is *diagnostic logging only*
|
|
7
|
+
* — interactive CLI UX (prompts, spinners, `PithyError` rendering) stays on the CLI's `style.ts`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Level-keyed color functions the local adapter paints with. Injected, not hard-coded: core stays free
|
|
11
|
+
* of ANSI, and the CLI passes its `style.ts` seam so all CLI color still flows through one place. Every
|
|
12
|
+
* entry is optional; a missing one renders plain.
|
|
13
|
+
*/
|
|
14
|
+
export interface LocalPalette {
|
|
15
|
+
debug?: (text: string) => string;
|
|
16
|
+
info?: (text: string) => string;
|
|
17
|
+
warn?: (text: string) => string;
|
|
18
|
+
error?: (text: string) => string;
|
|
19
|
+
/** Dimmed secondary text — the namespace, fields, and error detail. */
|
|
20
|
+
dim?: (text: string) => string;
|
|
21
|
+
}
|
|
22
|
+
/** Options for {@link createLocalLogger}. */
|
|
23
|
+
export interface LocalLoggerOptions {
|
|
24
|
+
/** The threshold. Defaults to `debug` — local diagnostics are verbose by default. */
|
|
25
|
+
level?: LogLevel;
|
|
26
|
+
/** Emit each record as a `JSON.stringify` line instead of the human format — the agent/CI stream. */
|
|
27
|
+
json?: boolean;
|
|
28
|
+
/** Where a formatted line goes. Defaults to `console.error` (stderr under Node), so diagnostics never pollute a command's stdout. */
|
|
29
|
+
write?: (line: string) => void;
|
|
30
|
+
/** Optional color functions (the CLI injects its `style.ts` seam). Omitted → plain text. */
|
|
31
|
+
palette?: LocalPalette;
|
|
32
|
+
/** The clock, injectable for tests. */
|
|
33
|
+
now?: () => number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a Mode 1 local logger. Same interface as every other Pithy logger; the sink either renders a
|
|
37
|
+
* human line (default, colorized when a palette is injected) or a `--json` structured line. Writes to
|
|
38
|
+
* `stderr` by default so a command's machine-readable stdout stays clean.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createLocalLogger(options?: LocalLoggerOptions): Logger;
|
|
41
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/logger/local.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,KAAK,EAAE,QAAQ,EAAa,MAAM,UAAU,CAAC;AAGpD;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACjC,uEAAuE;IACvE,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CAChC;AAED,6CAA6C;AAC7C,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,qGAAqG;IACrG,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,qIAAqI;IACrI,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,4FAA4F;IAC5F,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAkCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAW1E"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { serializeRecord } from "./record.js";
|
|
2
|
+
import { createLogger } from "./logger.js";
|
|
3
|
+
//#region src/logger/local.ts
|
|
4
|
+
/** Render one field value: strings verbatim, everything else as compact JSON — never throwing on a cycle/BigInt. */
|
|
5
|
+
function renderValue(value) {
|
|
6
|
+
if (typeof value === "string") return value;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.stringify(value) ?? String(value);
|
|
9
|
+
} catch {
|
|
10
|
+
return String(value);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** `key=value key=value` for the record's fields, in insertion order. */
|
|
14
|
+
function renderFields(fields) {
|
|
15
|
+
return Object.entries(fields).map(([key, value]) => `${key}=${renderValue(value)}`).join(" ");
|
|
16
|
+
}
|
|
17
|
+
/** Format one record for a terminal: `LEVEL (name) msg key=val … code detail`, colorized when a palette is set. */
|
|
18
|
+
function renderHuman(record, palette) {
|
|
19
|
+
const paint = palette?.[record.level] ?? ((text) => text);
|
|
20
|
+
const dim = palette?.dim ?? ((text) => text);
|
|
21
|
+
const parts = [paint(record.level.toUpperCase())];
|
|
22
|
+
if (record.name) parts.push(dim(`(${record.name})`));
|
|
23
|
+
parts.push(record.msg);
|
|
24
|
+
if (record.fields) parts.push(dim(renderFields(record.fields)));
|
|
25
|
+
if (record.error) {
|
|
26
|
+
const detail = record.error.detail ? ` ${record.error.detail}` : "";
|
|
27
|
+
parts.push(dim(`${record.error.code}${detail}`));
|
|
28
|
+
}
|
|
29
|
+
return parts.join(" ");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build a Mode 1 local logger. Same interface as every other Pithy logger; the sink either renders a
|
|
33
|
+
* human line (default, colorized when a palette is injected) or a `--json` structured line. Writes to
|
|
34
|
+
* `stderr` by default so a command's machine-readable stdout stays clean.
|
|
35
|
+
*/
|
|
36
|
+
function createLocalLogger(options = {}) {
|
|
37
|
+
const write = options.write ?? ((line) => console.error(line));
|
|
38
|
+
return createLogger({
|
|
39
|
+
level: options.level ?? "debug",
|
|
40
|
+
now: options.now,
|
|
41
|
+
sink: (record) => write(options.json ? serializeRecord(record) : renderHuman(record, options.palette))
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { createLocalLogger };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type LogFields, type LogLevel, type LogRecord } from "./record";
|
|
2
|
+
/**
|
|
3
|
+
* The `Logger` seam. Four levels, each taking a message plus **structured fields** (not a formatted
|
|
4
|
+
* string), and `child(name)` to derive a namespaced sub-logger. Shared across runtimes: the same
|
|
5
|
+
* interface backs the `pithy` CLI process, a Worker running locally, and a deployed Worker — only the
|
|
6
|
+
* adapter behind it differs. Resolve it from the request context (`c.var.log`) or the CLI process
|
|
7
|
+
* logger; never reach for `console`.
|
|
8
|
+
*
|
|
9
|
+
* The reserved `error` field on any call may carry a `PithyError` or `ErrorPayload` — the engine lifts
|
|
10
|
+
* it into the record's `error`, `detail` and all. A log is internal; this is the inverse of the HTTP
|
|
11
|
+
* codec, which strips `detail`. The logger must never be wired to a client-facing surface.
|
|
12
|
+
*/
|
|
13
|
+
export interface Logger {
|
|
14
|
+
/** Dev-time diagnostics. Dropped unless the logger's level is `debug`. */
|
|
15
|
+
debug(msg: string, fields?: LogFields): void;
|
|
16
|
+
/** Normal operational events. */
|
|
17
|
+
info(msg: string, fields?: LogFields): void;
|
|
18
|
+
/** A recoverable problem worth attention. */
|
|
19
|
+
warn(msg: string, fields?: LogFields): void;
|
|
20
|
+
/** A failure that was observed. Pass `{ error }` (a PithyError) to carry its full payload. */
|
|
21
|
+
error(msg: string, fields?: LogFields): void;
|
|
22
|
+
/**
|
|
23
|
+
* A namespaced sub-logger. `name` composes as `parent:child`; bound `fields` merge into every record
|
|
24
|
+
* the child emits. Omit `name` (pass `undefined`) to bind fields only — how request-correlation
|
|
25
|
+
* attaches `request`/`method`/`path` without renaming the logger.
|
|
26
|
+
*/
|
|
27
|
+
child(name?: string, fields?: LogFields): Logger;
|
|
28
|
+
}
|
|
29
|
+
/** A sink receives a finished {@link LogRecord}. Adapters supply one; the engine builds records and calls it. */
|
|
30
|
+
export type LogSink = (record: LogRecord) => void;
|
|
31
|
+
/** Inputs to {@link createLogger}: the level threshold, the sink, and optional bound name/fields/clock. */
|
|
32
|
+
export interface CreateLoggerOptions {
|
|
33
|
+
/** The threshold; records below it are dropped. */
|
|
34
|
+
level: LogLevel;
|
|
35
|
+
/** Where finished records go. */
|
|
36
|
+
sink: LogSink;
|
|
37
|
+
/** A starting namespace for this logger, if any. */
|
|
38
|
+
name?: string;
|
|
39
|
+
/** Fields bound onto every record this logger emits. */
|
|
40
|
+
fields?: LogFields;
|
|
41
|
+
/** The clock, injectable for tests. Defaults to `Date.now`. */
|
|
42
|
+
now?: () => number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The shared logger engine. Both adapters build on it: they supply a {@link LogSink} and a level, and
|
|
46
|
+
* this handles level filtering, `child` namespacing, field merging, and error lifting — one place, so
|
|
47
|
+
* every adapter behaves identically.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createLogger(options: CreateLoggerOptions): Logger;
|
|
50
|
+
/**
|
|
51
|
+
* The safe default: a logger that drops every record. Shipped so a capability that does nothing still
|
|
52
|
+
* has a `c.var.log` to call, and a KV store built without a logger still logs to *somewhere*. Both
|
|
53
|
+
* adapters replace it; nothing has to null-check `log`.
|
|
54
|
+
*/
|
|
55
|
+
export declare const noopLogger: Logger;
|
|
56
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger/logger.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1F;;;;;;;;;;GAUG;AACH,MAAM,WAAW,MAAM;IACrB,0EAA0E;IAC1E,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7C,iCAAiC;IACjC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5C,6CAA6C;IAC7C,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5C,8FAA8F;IAC9F,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7C;;;;OAIG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;CAClD;AAED,iHAAiH;AACjH,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;AAElD,2GAA2G;AAC3G,MAAM,WAAW,mBAAmB;IAClC,mDAAmD;IACnD,KAAK,EAAE,QAAQ,CAAC;IAChB,iCAAiC;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAoED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAQjE;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,MAMxB,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PithyError } from "../error/pithyError.js";
|
|
2
|
+
import { LOG_LEVEL_ORDER } from "./record.js";
|
|
3
|
+
//#region src/logger/logger.ts
|
|
4
|
+
/**
|
|
5
|
+
* Duck-type an `ErrorPayload`: a **namespaced** `code` (`domain/reason`) plus an HTTP-range `status`.
|
|
6
|
+
* Cheap — no per-call Zod parse. The `/` and status-range checks keep an ordinary field that happens to
|
|
7
|
+
* be named `error` (e.g. `{ code: "retry", status: 3 }`) from being mistaken for a payload and lifted.
|
|
8
|
+
*/
|
|
9
|
+
function isErrorPayloadLike(value) {
|
|
10
|
+
if (typeof value !== "object" || value === null) return false;
|
|
11
|
+
const { code, status } = value;
|
|
12
|
+
return typeof code === "string" && code.includes("/") && typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599;
|
|
13
|
+
}
|
|
14
|
+
/** Build a logger over `state`: level-filters, merges bound + call fields, lifts a carried error. */
|
|
15
|
+
function makeLogger(state) {
|
|
16
|
+
const emit = (level, msg, callFields) => {
|
|
17
|
+
if (LOG_LEVEL_ORDER[level] < LOG_LEVEL_ORDER[state.level]) return;
|
|
18
|
+
const merged = {
|
|
19
|
+
...state.fields,
|
|
20
|
+
...callFields
|
|
21
|
+
};
|
|
22
|
+
let error;
|
|
23
|
+
const carried = merged.error;
|
|
24
|
+
if (carried instanceof PithyError) {
|
|
25
|
+
error = carried.payload;
|
|
26
|
+
delete merged.error;
|
|
27
|
+
} else if (isErrorPayloadLike(carried)) {
|
|
28
|
+
error = carried;
|
|
29
|
+
delete merged.error;
|
|
30
|
+
}
|
|
31
|
+
const record = {
|
|
32
|
+
level,
|
|
33
|
+
msg,
|
|
34
|
+
time: state.now()
|
|
35
|
+
};
|
|
36
|
+
if (state.name) record.name = state.name;
|
|
37
|
+
if (Object.keys(merged).length > 0) record.fields = merged;
|
|
38
|
+
if (error) record.error = error;
|
|
39
|
+
state.sink(record);
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
debug: (msg, fields) => emit("debug", msg, fields),
|
|
43
|
+
info: (msg, fields) => emit("info", msg, fields),
|
|
44
|
+
warn: (msg, fields) => emit("warn", msg, fields),
|
|
45
|
+
error: (msg, fields) => emit("error", msg, fields),
|
|
46
|
+
child: (name, fields) => makeLogger({
|
|
47
|
+
...state,
|
|
48
|
+
name: name ? state.name ? `${state.name}:${name}` : name : state.name,
|
|
49
|
+
fields: {
|
|
50
|
+
...state.fields,
|
|
51
|
+
...fields
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The shared logger engine. Both adapters build on it: they supply a {@link LogSink} and a level, and
|
|
58
|
+
* this handles level filtering, `child` namespacing, field merging, and error lifting — one place, so
|
|
59
|
+
* every adapter behaves identically.
|
|
60
|
+
*/
|
|
61
|
+
function createLogger(options) {
|
|
62
|
+
return makeLogger({
|
|
63
|
+
level: options.level,
|
|
64
|
+
sink: options.sink,
|
|
65
|
+
name: options.name,
|
|
66
|
+
fields: options.fields,
|
|
67
|
+
now: options.now ?? (() => Date.now())
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The safe default: a logger that drops every record. Shipped so a capability that does nothing still
|
|
72
|
+
* has a `c.var.log` to call, and a KV store built without a logger still logs to *somewhere*. Both
|
|
73
|
+
* adapters replace it; nothing has to null-check `log`.
|
|
74
|
+
*/
|
|
75
|
+
const noopLogger = {
|
|
76
|
+
debug: () => {},
|
|
77
|
+
info: () => {},
|
|
78
|
+
warn: () => {},
|
|
79
|
+
error: () => {},
|
|
80
|
+
child: () => noopLogger
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { createLogger, noopLogger };
|