@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
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The reader's contract: a capability's own response object, restated for a client that does not
|
|
4
|
+
* control the Worker that wrote the response.
|
|
5
|
+
*
|
|
6
|
+
* ## One schema, two consumers, two obligations
|
|
7
|
+
*
|
|
8
|
+
* A capability states each projection once, in its `http/responses.ts`, and both consumers validate
|
|
9
|
+
* against it — the Worker's own tests and every management client. That is right for the fields and
|
|
10
|
+
* wrong for the enums, because the two consumers are answering different questions.
|
|
11
|
+
*
|
|
12
|
+
* A **Worker validating its own projection is checking itself.** It must be strict: an enum there is
|
|
13
|
+
* what the ingest path branches on and what D1 holds, and a tolerated-unknown member would license the
|
|
14
|
+
* capability to *store* a value it cannot handle. Nothing here loosens that, and nothing here should:
|
|
15
|
+
* the producer's enums are untouched, and this module never sees them at parse time.
|
|
16
|
+
*
|
|
17
|
+
* A **management client reading that response is crossing a trust boundary.** The system that wrote it
|
|
18
|
+
* is a fork, a bug, a half-finished deploy, or hostile — none of which is downstream of how anybody's
|
|
19
|
+
* releases are cut — and one member it has never heard of currently costs it the whole response rather
|
|
20
|
+
* than the one row that carried it. A support console rendered zero of twenty-five conversations that
|
|
21
|
+
* way (`pithy-sh/dashboard#15`), and the client that fixed it had to widen the kit's shape locally,
|
|
22
|
+
* which is the mirror-of-a-projection that `#113` exists to forbid.
|
|
23
|
+
*
|
|
24
|
+
* So the tolerance is published **beside** the producer's shape rather than inside it. `asRead(X)` is
|
|
25
|
+
* the whole pattern: the same object, with every enum reachable through it read as a string.
|
|
26
|
+
*
|
|
27
|
+
* ## What it is not
|
|
28
|
+
*
|
|
29
|
+
* **It is not a loosening.** A missing field, a number where a string belongs, a value outside a
|
|
30
|
+
* numeric bound, a body that is not an object — all still refuse the whole response, exactly as before.
|
|
31
|
+
* The only thing that changes is which *members* an enum-shaped field admits.
|
|
32
|
+
*
|
|
33
|
+
* **It is not a mapping.** The token is handed back verbatim. The enum stays the authority on what a
|
|
34
|
+
* value means — `SupportChannel.safeParse(value).success` is the question, and the answer is the
|
|
35
|
+
* client's license to *mark* the row. Reading an unrecognized value as the nearest one you know is a
|
|
36
|
+
* lie about a fact somebody is about to act on, and this pattern exists to make marking cheap enough
|
|
37
|
+
* that nobody reaches for mapping.
|
|
38
|
+
*
|
|
39
|
+
* **It is not selective.** A reader's obligation does not vary field by field: it does not control the
|
|
40
|
+
* writer, so *any* unknown member costs it the response. Widening two enums and leaving a third is how
|
|
41
|
+
* the same blank pane returns one field over.
|
|
42
|
+
*
|
|
43
|
+
* ## What it refuses to do
|
|
44
|
+
*
|
|
45
|
+
* It rewrites what it can see through — objects, arrays, `nullable`, `optional` — and returns anything
|
|
46
|
+
* with no enum under it as the **identical schema instance**, so a reader's view and its producer share
|
|
47
|
+
* every untouched field rather than copying it. Everything else throws at construction:
|
|
48
|
+
* {@link asRead} either produces a view with no enum left in it or refuses to produce one, because a
|
|
49
|
+
* shape that silently passed a union through would publish a "reader's contract" that still refuses an
|
|
50
|
+
* unknown member — the original defect, wearing a name that says it was handled.
|
|
51
|
+
*/
|
|
52
|
+
/** What a widened field says about itself, appended to the description the enum already carried. */
|
|
53
|
+
export declare const TOLERATED_MEMBER = "Read as a string rather than as the enum it comes from: a client does not control the Worker that wrote this, so a member the enum does not declare is handed back verbatim instead of refusing the whole response. Ask the enum what a value means, mark what it does not declare, and never map it to the nearest value you know.";
|
|
54
|
+
/**
|
|
55
|
+
* The producer's schema with every enum under it read as a string.
|
|
56
|
+
*
|
|
57
|
+
* Enum in, string out; a wrapper keeps its own description and the widened member carries the enum's,
|
|
58
|
+
* with {@link TOLERATED_MEMBER} appended so the schema still documents itself. Everything else is the
|
|
59
|
+
* schema the capability declared, by identity.
|
|
60
|
+
*/
|
|
61
|
+
export type AsRead<T> = T extends z.ZodEnum ? z.ZodString : T extends z.ZodNullable<infer Inner> ? z.ZodNullable<Extract<AsRead<Inner>, z.ZodType>> : T extends z.ZodOptional<infer Inner> ? z.ZodOptional<Extract<AsRead<Inner>, z.ZodType>> : T extends z.ZodArray<infer Element> ? z.ZodArray<Extract<AsRead<Element>, z.ZodType>> : T extends z.ZodObject<infer Shape> ? z.ZodObject<{
|
|
62
|
+
[K in keyof Shape]: Extract<AsRead<Shape[K]>, z.ZodType>;
|
|
63
|
+
}> : T;
|
|
64
|
+
/**
|
|
65
|
+
* A capability's response object, as a client that does not control the writer must read it.
|
|
66
|
+
*
|
|
67
|
+
* Export the result beside the producer's schema — `XAsRead` beside `X` — so a client imports tolerance
|
|
68
|
+
* rather than building it. Throws if the projection holds a shape this cannot see through; see the
|
|
69
|
+
* module docstring for why that is a refusal rather than a pass-through.
|
|
70
|
+
*/
|
|
71
|
+
export declare function asRead<T extends z.ZodType>(schema: T): AsRead<T>;
|
|
72
|
+
//# sourceMappingURL=asRead.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asRead.d.ts","sourceRoot":"","sources":["../../src/projection/asRead.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,oGAAoG;AACpG,eAAO,MAAM,gBAAgB,wUAC0S,CAAC;AA2HxU;;;;;;GAMG;AACH,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,GACvC,CAAC,CAAC,SAAS,GACX,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,GAClC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,GAChD,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,GAClC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,GAChD,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,MAAM,OAAO,CAAC,GACjC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,GAC/C,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,GAChC,CAAC,CAAC,SAAS,CAAC;KAAG,CAAC,IAAI,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;CAAE,CAAC,GACzE,CAAC,CAAC;AAEd;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAEhE"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { InternalError } from "../error/pithyError.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/projection/asRead.ts
|
|
4
|
+
/**
|
|
5
|
+
* The reader's contract: a capability's own response object, restated for a client that does not
|
|
6
|
+
* control the Worker that wrote the response.
|
|
7
|
+
*
|
|
8
|
+
* ## One schema, two consumers, two obligations
|
|
9
|
+
*
|
|
10
|
+
* A capability states each projection once, in its `http/responses.ts`, and both consumers validate
|
|
11
|
+
* against it — the Worker's own tests and every management client. That is right for the fields and
|
|
12
|
+
* wrong for the enums, because the two consumers are answering different questions.
|
|
13
|
+
*
|
|
14
|
+
* A **Worker validating its own projection is checking itself.** It must be strict: an enum there is
|
|
15
|
+
* what the ingest path branches on and what D1 holds, and a tolerated-unknown member would license the
|
|
16
|
+
* capability to *store* a value it cannot handle. Nothing here loosens that, and nothing here should:
|
|
17
|
+
* the producer's enums are untouched, and this module never sees them at parse time.
|
|
18
|
+
*
|
|
19
|
+
* A **management client reading that response is crossing a trust boundary.** The system that wrote it
|
|
20
|
+
* is a fork, a bug, a half-finished deploy, or hostile — none of which is downstream of how anybody's
|
|
21
|
+
* releases are cut — and one member it has never heard of currently costs it the whole response rather
|
|
22
|
+
* than the one row that carried it. A support console rendered zero of twenty-five conversations that
|
|
23
|
+
* way (`pithy-sh/dashboard#15`), and the client that fixed it had to widen the kit's shape locally,
|
|
24
|
+
* which is the mirror-of-a-projection that `#113` exists to forbid.
|
|
25
|
+
*
|
|
26
|
+
* So the tolerance is published **beside** the producer's shape rather than inside it. `asRead(X)` is
|
|
27
|
+
* the whole pattern: the same object, with every enum reachable through it read as a string.
|
|
28
|
+
*
|
|
29
|
+
* ## What it is not
|
|
30
|
+
*
|
|
31
|
+
* **It is not a loosening.** A missing field, a number where a string belongs, a value outside a
|
|
32
|
+
* numeric bound, a body that is not an object — all still refuse the whole response, exactly as before.
|
|
33
|
+
* The only thing that changes is which *members* an enum-shaped field admits.
|
|
34
|
+
*
|
|
35
|
+
* **It is not a mapping.** The token is handed back verbatim. The enum stays the authority on what a
|
|
36
|
+
* value means — `SupportChannel.safeParse(value).success` is the question, and the answer is the
|
|
37
|
+
* client's license to *mark* the row. Reading an unrecognized value as the nearest one you know is a
|
|
38
|
+
* lie about a fact somebody is about to act on, and this pattern exists to make marking cheap enough
|
|
39
|
+
* that nobody reaches for mapping.
|
|
40
|
+
*
|
|
41
|
+
* **It is not selective.** A reader's obligation does not vary field by field: it does not control the
|
|
42
|
+
* writer, so *any* unknown member costs it the response. Widening two enums and leaving a third is how
|
|
43
|
+
* the same blank pane returns one field over.
|
|
44
|
+
*
|
|
45
|
+
* ## What it refuses to do
|
|
46
|
+
*
|
|
47
|
+
* It rewrites what it can see through — objects, arrays, `nullable`, `optional` — and returns anything
|
|
48
|
+
* with no enum under it as the **identical schema instance**, so a reader's view and its producer share
|
|
49
|
+
* every untouched field rather than copying it. Everything else throws at construction:
|
|
50
|
+
* {@link asRead} either produces a view with no enum left in it or refuses to produce one, because a
|
|
51
|
+
* shape that silently passed a union through would publish a "reader's contract" that still refuses an
|
|
52
|
+
* unknown member — the original defect, wearing a name that says it was handled.
|
|
53
|
+
*/
|
|
54
|
+
/** What a widened field says about itself, appended to the description the enum already carried. */
|
|
55
|
+
const TOLERATED_MEMBER = "Read as a string rather than as the enum it comes from: a client does not control the Worker that wrote this, so a member the enum does not declare is handed back verbatim instead of refusing the whole response. Ask the enum what a value means, mark what it does not declare, and never map it to the nearest value you know.";
|
|
56
|
+
/** Read a schema's internal def, tolerating both the `_zod.def` and `.def` shapes across Zod builds. */
|
|
57
|
+
function defOf(schema) {
|
|
58
|
+
const internal = schema;
|
|
59
|
+
return internal._zod?.def ?? internal.def ?? {};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The type tags that hold no other schema, so nothing inside one can be an enum.
|
|
63
|
+
*
|
|
64
|
+
* **Written out, never derived.** A permitted set computed from what the walk happens to handle would
|
|
65
|
+
* grant an exemption to every kind somebody adds later, silently, which is the failure this roster is
|
|
66
|
+
* here to prevent. A tag not on this list and not rewritten below is refused.
|
|
67
|
+
*
|
|
68
|
+
* `literal` is on it deliberately. A literal is not a vocabulary a capability might extend — it is how
|
|
69
|
+
* a discriminated union tells its arms apart, and widening one would destroy the union rather than
|
|
70
|
+
* tolerate anything.
|
|
71
|
+
*/
|
|
72
|
+
const LEAF_TYPES = /* @__PURE__ */ new Set([
|
|
73
|
+
"string",
|
|
74
|
+
"number",
|
|
75
|
+
"int",
|
|
76
|
+
"bigint",
|
|
77
|
+
"boolean",
|
|
78
|
+
"date",
|
|
79
|
+
"literal",
|
|
80
|
+
"template_literal",
|
|
81
|
+
"file",
|
|
82
|
+
"symbol",
|
|
83
|
+
"null",
|
|
84
|
+
"undefined",
|
|
85
|
+
"void",
|
|
86
|
+
"any",
|
|
87
|
+
"unknown",
|
|
88
|
+
"never",
|
|
89
|
+
"nan"
|
|
90
|
+
]);
|
|
91
|
+
/**
|
|
92
|
+
* The refusal, with the offending schema's *path* in it and never a value.
|
|
93
|
+
*
|
|
94
|
+
* Thrown while a module is being evaluated, which is exactly where it belongs: a reader's contract is
|
|
95
|
+
* built once at import, so a capability that cannot state one finds out in its own test run rather
|
|
96
|
+
* than in front of a client.
|
|
97
|
+
*/
|
|
98
|
+
function unreadable(path, kind) {
|
|
99
|
+
return new InternalError({
|
|
100
|
+
message: "This projection has no reader's contract.",
|
|
101
|
+
action: `State the reader's view of ${path} by hand, or reshape it so \`asRead\` can see through it.`,
|
|
102
|
+
detail: `asRead met a \`${kind}\` at ${path}. It rewrites objects, arrays, nullable and optional wrappers, and refuses anything else rather than return a view that may still hold an enum a reader cannot survive.`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** The refusal for an object whose unknown-key rule a rebuild would change. */
|
|
106
|
+
function unrebuildable(path) {
|
|
107
|
+
return new InternalError({
|
|
108
|
+
message: "This projection has no reader's contract.",
|
|
109
|
+
action: `Drop the unknown-key rule on ${path}, or state its reader's view by hand.`,
|
|
110
|
+
detail: `asRead met an object at ${path} that both holds an enum and declares its own unknown-key rule. Rebuilding it would silently change what it refuses, so it refuses to guess.`
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** A rebuilt schema wearing the description the original carried. Nothing is described twice. */
|
|
114
|
+
function describedLike(rebuilt, original) {
|
|
115
|
+
return original.description === void 0 ? rebuilt : rebuilt.describe(original.description);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* One node of the rewrite. Returns the identical instance when nothing under it is an enum.
|
|
119
|
+
*
|
|
120
|
+
* The identity is not an optimization. It is what keeps a reader's view from becoming a second opinion
|
|
121
|
+
* about the fields it did not widen: a field it shares with the producer is the *same object*, so a
|
|
122
|
+
* change upstream lands in both, and a client can assert that field by field.
|
|
123
|
+
*/
|
|
124
|
+
function readerOf(schema, path) {
|
|
125
|
+
const { type, catchall } = defOf(schema);
|
|
126
|
+
if (type === "enum") {
|
|
127
|
+
const said = schema.description;
|
|
128
|
+
return z.string().describe(said === void 0 ? TOLERATED_MEMBER : `${said} ${TOLERATED_MEMBER}`);
|
|
129
|
+
}
|
|
130
|
+
if (schema instanceof z.ZodNullable || schema instanceof z.ZodOptional) {
|
|
131
|
+
const inner = schema.unwrap();
|
|
132
|
+
const read = readerOf(inner, path);
|
|
133
|
+
if (read === inner) return schema;
|
|
134
|
+
return describedLike(schema instanceof z.ZodNullable ? z.nullable(read) : z.optional(read), schema);
|
|
135
|
+
}
|
|
136
|
+
if (schema instanceof z.ZodArray) {
|
|
137
|
+
const element = schema.element;
|
|
138
|
+
const read = readerOf(element, `${path}[]`);
|
|
139
|
+
if (read === element) return schema;
|
|
140
|
+
return describedLike(z.array(read), schema);
|
|
141
|
+
}
|
|
142
|
+
if (schema instanceof z.ZodObject) {
|
|
143
|
+
const shape = {};
|
|
144
|
+
let widened = false;
|
|
145
|
+
for (const [key, field] of Object.entries(schema.shape)) {
|
|
146
|
+
const original = field;
|
|
147
|
+
const read = readerOf(original, `${path}.${key}`);
|
|
148
|
+
shape[key] = read;
|
|
149
|
+
if (read !== original) widened = true;
|
|
150
|
+
}
|
|
151
|
+
if (!widened) return schema;
|
|
152
|
+
if (catchall !== void 0) throw unrebuildable(path);
|
|
153
|
+
return describedLike(z.object(shape), schema);
|
|
154
|
+
}
|
|
155
|
+
if (type !== void 0 && LEAF_TYPES.has(type)) return schema;
|
|
156
|
+
throw unreadable(path, type ?? "schema of no stated type");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* A capability's response object, as a client that does not control the writer must read it.
|
|
160
|
+
*
|
|
161
|
+
* Export the result beside the producer's schema — `XAsRead` beside `X` — so a client imports tolerance
|
|
162
|
+
* rather than building it. Throws if the projection holds a shape this cannot see through; see the
|
|
163
|
+
* module docstring for why that is a refusal rather than a pass-through.
|
|
164
|
+
*/
|
|
165
|
+
function asRead(schema) {
|
|
166
|
+
return readerOf(schema, "response");
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { TOLERATED_MEMBER, asRead };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The gate primitive for a projection that crosses a trust boundary: **nothing left this surface but the
|
|
3
|
+
* facts it publishes.**
|
|
4
|
+
*
|
|
5
|
+
* Every such gate in this kit was first written as its negative — a list of strings that must not appear
|
|
6
|
+
* in the response. A negative list is complete only against the values somebody thought of, and a
|
|
7
|
+
* projection widens by gaining a *field*, which is exactly the event a value list cannot observe. The
|
|
8
|
+
* same argument has been lost here three times over: a tripwire banning `access` while `stat` followed
|
|
9
|
+
* links identically, an escape class that arrived as three different verbs, and a bundle sweep seeded
|
|
10
|
+
* with the credential shapes of the day.
|
|
11
|
+
*
|
|
12
|
+
* So the rule is stated positively and computed rather than enumerated. Two halves, and **both are
|
|
13
|
+
* required, because either alone lets the mistake through**:
|
|
14
|
+
*
|
|
15
|
+
* 1. **Every leaf must be a published fact.** This is what catches a forbidden value arriving under a
|
|
16
|
+
* field name nobody predicted.
|
|
17
|
+
* 2. **Every key must be a published key.** This is what catches a leaf whose *type* has no vocabulary —
|
|
18
|
+
* `true`, `false` and `null` belong to every JSON document, so the first half can never police them,
|
|
19
|
+
* and a new `boolean` field is the cheapest way to widen a projection.
|
|
20
|
+
*
|
|
21
|
+
* ## Two rules for the caller, and both were paid for
|
|
22
|
+
*
|
|
23
|
+
* **Write the permitted key set out by hand.** Deriving it from `Object.keys(Schema.shape)` makes the
|
|
24
|
+
* gate read its own subject: the edit that widens the schema widens the permission, in the same commit,
|
|
25
|
+
* and the test whose entire job is to catch that passes. That happened, on the catalog read.
|
|
26
|
+
*
|
|
27
|
+
* **Seed the source with something forbidden of every JSON type.** A sweep over a document that never
|
|
28
|
+
* held anything secret passes perfectly and proves nothing. The leaf walk below once returned `[]` for
|
|
29
|
+
* every type that was not a string or a number, so booleans and nulls crossed untouched — nothing
|
|
30
|
+
* forbidden happened to be a boolean that day, which is what kept it from being a live leak, not what
|
|
31
|
+
* made it safe.
|
|
32
|
+
*
|
|
33
|
+
* ## Why it throws
|
|
34
|
+
*
|
|
35
|
+
* A value this walk cannot name is a silent exemption for a whole JSON type, granted by a fallthrough.
|
|
36
|
+
* That is the defect above, restated. So {@link unpublishedIn} refuses the document instead: a value the
|
|
37
|
+
* caller hands it must be an array, a plain object, or a JSON leaf, and anything else means a live
|
|
38
|
+
* JavaScript object rather than what a client receives. Round-trip through
|
|
39
|
+
* `JSON.parse(JSON.stringify(...))` first: that is what crosses, and what crosses is what a gate is about.
|
|
40
|
+
*
|
|
41
|
+
* **The rule is what the walk can *see*, not a list of types it distrusts.** `typeof` answers "object"
|
|
42
|
+
* for a `Date`, a `Map`, a `Set` and every class instance, and each of those descends to no keys and no
|
|
43
|
+
* leaves — a `Map`'s entries are not own properties and a getter lives on the prototype — so the descent
|
|
44
|
+
* itself was handing out the same exemption through the one branch that looked safe. So a container is
|
|
45
|
+
* one whose whole contents `Object.entries` returns: prototype `Object.prototype`, `Array.prototype`, or
|
|
46
|
+
* `null`. The first two are what `JSON.parse` produces and the third holds nothing anywhere else to
|
|
47
|
+
* find. Naming the unnameable types instead would be the enumeration this module exists to refuse, and
|
|
48
|
+
* the next class nobody thought of would walk straight through.
|
|
49
|
+
*
|
|
50
|
+
* **And `Object.entries` is then what descends, arrays included.** The rule above was stated once and only
|
|
51
|
+
* half run: the array branch still walked by position, so an own property on a plain array — a prototype
|
|
52
|
+
* this walk accepts, nothing to refuse — was contents the walk could not see. A rule the code disagrees
|
|
53
|
+
* with is a rule in a comment.
|
|
54
|
+
*/
|
|
55
|
+
/** Anything a JSON document holds that is not an object or an array. */
|
|
56
|
+
export type JsonLeaf = string | number | boolean | null;
|
|
57
|
+
/**
|
|
58
|
+
* What one surface is allowed to publish: the facts, and the field names they may sit under.
|
|
59
|
+
*
|
|
60
|
+
* `keys` is a literal written at the call site. Never `Object.keys` of the schema being policed — see
|
|
61
|
+
* the note above.
|
|
62
|
+
*/
|
|
63
|
+
export interface PublishedFacts {
|
|
64
|
+
/** Every value this surface may disclose, from wherever it legitimately came. */
|
|
65
|
+
readonly leaves: Iterable<JsonLeaf>;
|
|
66
|
+
/** Every object key this surface may carry, at any depth. Written out by hand. */
|
|
67
|
+
readonly keys: Iterable<string>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Everything in `document` that {@link PublishedFacts} does not permit, as lines naming what and where.
|
|
71
|
+
*
|
|
72
|
+
* Empty means the projection published only what it declared. A non-empty result is the message: each
|
|
73
|
+
* line carries the offending key or value and its JSON path, so a failure says which field widened
|
|
74
|
+
* rather than that some string was present.
|
|
75
|
+
*
|
|
76
|
+
* @param document A JSON document — what a client actually receives, not a live object.
|
|
77
|
+
* @param published The facts and keys this surface declares.
|
|
78
|
+
* @throws If `document` holds a value JSON cannot express.
|
|
79
|
+
*/
|
|
80
|
+
export declare function unpublishedIn(document: unknown, published: PublishedFacts): string[];
|
|
81
|
+
/**
|
|
82
|
+
* Every leaf in a JSON document, wherever it sits, of every JSON type. Object keys are collected separately.
|
|
83
|
+
*
|
|
84
|
+
* @throws If `document` holds a value JSON cannot express, on the same terms as {@link unpublishedIn} —
|
|
85
|
+
* a caller builds its declaration from this, and a value walked to nothing becomes a permitted set that
|
|
86
|
+
* omits a field.
|
|
87
|
+
*/
|
|
88
|
+
export declare function leavesIn(document: unknown): JsonLeaf[];
|
|
89
|
+
/**
|
|
90
|
+
* Every object key in a JSON document, wherever it sits.
|
|
91
|
+
*
|
|
92
|
+
* @throws If `document` holds a value JSON cannot express, on the same terms as {@link unpublishedIn}.
|
|
93
|
+
*/
|
|
94
|
+
export declare function keysIn(document: unknown): string[];
|
|
95
|
+
//# sourceMappingURL=published.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"published.d.ts","sourceRoot":"","sources":["../../src/projection/published.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAEH,wEAAwE;AACxE,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAExD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,GAAG,MAAM,EAAE,CAMpF;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,QAAQ,EAAE,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,EAAE,CAElD"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
//#region src/projection/published.ts
|
|
2
|
+
/**
|
|
3
|
+
* Everything in `document` that {@link PublishedFacts} does not permit, as lines naming what and where.
|
|
4
|
+
*
|
|
5
|
+
* Empty means the projection published only what it declared. A non-empty result is the message: each
|
|
6
|
+
* line carries the offending key or value and its JSON path, so a failure says which field widened
|
|
7
|
+
* rather than that some string was present.
|
|
8
|
+
*
|
|
9
|
+
* @param document A JSON document — what a client actually receives, not a live object.
|
|
10
|
+
* @param published The facts and keys this surface declares.
|
|
11
|
+
* @throws If `document` holds a value JSON cannot express.
|
|
12
|
+
*/
|
|
13
|
+
function unpublishedIn(document, published) {
|
|
14
|
+
const leaves = new Set(published.leaves);
|
|
15
|
+
const keys = new Set(published.keys);
|
|
16
|
+
const found = [];
|
|
17
|
+
walk(document, "", leaves, keys, found);
|
|
18
|
+
return found;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Every leaf in a JSON document, wherever it sits, of every JSON type. Object keys are collected separately.
|
|
22
|
+
*
|
|
23
|
+
* @throws If `document` holds a value JSON cannot express, on the same terms as {@link unpublishedIn} —
|
|
24
|
+
* a caller builds its declaration from this, and a value walked to nothing becomes a permitted set that
|
|
25
|
+
* omits a field.
|
|
26
|
+
*/
|
|
27
|
+
function leavesIn(document) {
|
|
28
|
+
return collectLeaves(document, "");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Every object key in a JSON document, wherever it sits.
|
|
32
|
+
*
|
|
33
|
+
* @throws If `document` holds a value JSON cannot express, on the same terms as {@link unpublishedIn}.
|
|
34
|
+
*/
|
|
35
|
+
function keysIn(document) {
|
|
36
|
+
return collectKeys(document, "");
|
|
37
|
+
}
|
|
38
|
+
function collectLeaves(value, path) {
|
|
39
|
+
const inside = descend(value, path);
|
|
40
|
+
if (inside === void 0) return [asLeaf(value, path)];
|
|
41
|
+
return inside.flatMap(({ nested, here }) => collectLeaves(nested, here));
|
|
42
|
+
}
|
|
43
|
+
function collectKeys(value, path) {
|
|
44
|
+
const inside = descend(value, path);
|
|
45
|
+
if (inside === void 0) return [];
|
|
46
|
+
return inside.flatMap(({ key, nested, here }) => (key === void 0 ? [] : [key]).concat(collectKeys(nested, here)));
|
|
47
|
+
}
|
|
48
|
+
function walk(value, path, leaves, keys, found) {
|
|
49
|
+
const inside = descend(value, path);
|
|
50
|
+
if (inside === void 0) {
|
|
51
|
+
const leaf = asLeaf(value, path);
|
|
52
|
+
if (!leaves.has(leaf)) found.push(`value ${JSON.stringify(leaf)} at ${where(path)}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const { key, nested, here } of inside) {
|
|
56
|
+
if (key !== void 0 && !keys.has(key)) found.push(`key ${JSON.stringify(key)} at ${here}`);
|
|
57
|
+
walk(nested, here, leaves, keys, found);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The contents of a container, or `undefined` for a leaf — and a refusal for anything else.
|
|
62
|
+
*
|
|
63
|
+
* This is the single place the walk decides what it can see, so the gate, {@link leavesIn} and
|
|
64
|
+
* {@link keysIn} all descend on identical terms. A value is a container only if its prototype is one
|
|
65
|
+
* `JSON.parse` produces; a `Date`, a `Map`, a `Set` or a class instance answers `"object"` to `typeof`
|
|
66
|
+
* and yields nothing to `Object.entries`, and treating that as an empty container is the exemption this
|
|
67
|
+
* module exists to refuse.
|
|
68
|
+
*
|
|
69
|
+
* So **`Object.entries` is what descends, arrays included**. An array walked by `value.map` is walked by
|
|
70
|
+
* position, and a position is not the whole of what an array holds: `rows.cursor = "…"` is an own property
|
|
71
|
+
* on a prototype this walk accepts, and the index walk went straight past it. The rule the module states and
|
|
72
|
+
* the code it runs have to be the same rule, or the exemption comes back through whichever branch still
|
|
73
|
+
* disagrees. An index key stays a position — reported as `[0]`, never as a key a caller must permit — and
|
|
74
|
+
* every other own key is a key.
|
|
75
|
+
*/
|
|
76
|
+
function descend(value, path) {
|
|
77
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
78
|
+
const proto = Object.getPrototypeOf(value);
|
|
79
|
+
if (proto !== Object.prototype && proto !== Array.prototype && proto !== null) refuse(value, path);
|
|
80
|
+
const array = Array.isArray(value);
|
|
81
|
+
return Object.entries(value).map(([key, nested]) => array && isIndex(key) ? {
|
|
82
|
+
key: void 0,
|
|
83
|
+
nested,
|
|
84
|
+
here: `${path}[${key}]`
|
|
85
|
+
} : {
|
|
86
|
+
key,
|
|
87
|
+
nested,
|
|
88
|
+
here: path === "" ? key : `${path}.${key}`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Whether an own key of an array is one of its positions — the canonical decimal form `Object.entries` gives. */
|
|
92
|
+
function isIndex(key) {
|
|
93
|
+
return /^(?:0|[1-9]\d*)$/.test(key);
|
|
94
|
+
}
|
|
95
|
+
/** Narrow to a JSON leaf, or refuse the document. A type this walk cannot name is not a type it may skip. */
|
|
96
|
+
function asLeaf(value, path) {
|
|
97
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
98
|
+
return refuse(value, path);
|
|
99
|
+
}
|
|
100
|
+
function refuse(value, path) {
|
|
101
|
+
throw new Error(`A ${nameOf(value)} sits at ${where(path)}. JSON cannot express it, so this is a live object rather than what a client receives — round-trip it through JSON first.`);
|
|
102
|
+
}
|
|
103
|
+
/** What to call the thing in the refusal — its class where it has one, so the message names what was found. */
|
|
104
|
+
function nameOf(value) {
|
|
105
|
+
if (value === null || typeof value !== "object") return typeof value;
|
|
106
|
+
const named = value.constructor;
|
|
107
|
+
if (typeof named === "function" && named.name !== "") return named.name;
|
|
108
|
+
return "object";
|
|
109
|
+
}
|
|
110
|
+
function where(path) {
|
|
111
|
+
return path === "" ? "(root)" : path;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { keysIn, leavesIn, unpublishedIn };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The children of one schema, by kind. **Throws for a kind this walk has never been taught** — a
|
|
4
|
+
* walker whose unknown case is silently empty cannot report the thing it exists to report.
|
|
5
|
+
*
|
|
6
|
+
* A `pipe` is stepped through; a **codec** is not. This gate reads the schema tree, so wrapping an
|
|
7
|
+
* object in a guard — `refusesVanishingKey`, and anything else that pipes into a schema — would turn
|
|
8
|
+
* an export it checks into an export it silently skips, and losing coverage as a side effect of adding
|
|
9
|
+
* a guard is the wrong direction. A codec is the §Zod exemption spelled as a type rather than as an
|
|
10
|
+
* accident: `z.codec` builds a `ZodCodec`, so the exemption names codec helpers and nothing else.
|
|
11
|
+
* Measured in #326 — stepping through codecs too reports seven fields whose union sides carry no
|
|
12
|
+
* description, every one of them a `SQLiteDate` or a `sqliteJson`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function childSchemas(schema: z.ZodType, path: string): z.ZodType[];
|
|
15
|
+
/**
|
|
16
|
+
* Whether a description sits anywhere in a field's transparent-wrapper chain.
|
|
17
|
+
*
|
|
18
|
+
* `.describe("…").optional()` puts it on the inner type and `.optional().describe("…")` puts it on the
|
|
19
|
+
* wrapper. Both are one described field. Only {@link TRANSPARENT} wrappers are followed — an `array`
|
|
20
|
+
* or a `pipe` describes something other than what it holds.
|
|
21
|
+
*/
|
|
22
|
+
export declare function describedInChain(schema: z.ZodType): boolean;
|
|
23
|
+
/** What one walk found: the complaints, and how much it actually looked at. */
|
|
24
|
+
export interface SchemaWalk {
|
|
25
|
+
/** One line per object, enum, union or field with no `.describe()` anywhere that documents it. */
|
|
26
|
+
missing: string[];
|
|
27
|
+
/** Every field of every object reached, so an empty walk is visible as an empty walk. */
|
|
28
|
+
fields: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Record any object/enum/union missing a `.describe()` — on the schema itself or on any of its
|
|
32
|
+
* fields — following every container Zod has. Codec-helper primitives are exempt (CLAUDE.md §Zod) and
|
|
33
|
+
* are not objects/enums/unions, so they are skipped.
|
|
34
|
+
*
|
|
35
|
+
* `described` carries a transparent wrapper's own description inward; see {@link TRANSPARENT}.
|
|
36
|
+
*/
|
|
37
|
+
export declare function collectMissing(schema: z.ZodType, path: string, walk: SchemaWalk, seen: Set<z.ZodType>, described?: boolean): void;
|
|
38
|
+
/** One schema, walked. The entry point a unit test reaches for. */
|
|
39
|
+
export declare function undescribed(schema: z.ZodType, path?: string): SchemaWalk;
|
|
40
|
+
/** What a package-wide sweep found, including the size of the population it swept. */
|
|
41
|
+
export interface ExportWalk extends SchemaWalk {
|
|
42
|
+
/** Modules the glob handed over. Zero is a broken pattern, not a clean package. */
|
|
43
|
+
modules: number;
|
|
44
|
+
/** Exports that resolved to a Zod schema. */
|
|
45
|
+
schemas: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Every exported Zod schema in a package, walked.
|
|
49
|
+
*
|
|
50
|
+
* Takes the result of `import.meta.glob([...], { eager: true })`. The counts come back with the
|
|
51
|
+
* findings **because a sweep that found nothing and a sweep that looked at nothing are the same green
|
|
52
|
+
* run otherwise** — the caller pins all three against literals of its own.
|
|
53
|
+
*/
|
|
54
|
+
export declare function undescribedExports(modules: Record<string, Record<string, unknown>>): ExportWalk;
|
|
55
|
+
//# sourceMappingURL=describedness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"describedness.d.ts","sourceRoot":"","sources":["../../src/schema/describedness.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgGxB;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,CAsCzE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,OAAO,CAU3D;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,kGAAkG;IAClG,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,yFAAyF;IACzF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,CAAC,CAAC,OAAO,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EACpB,SAAS,UAAQ,GAChB,IAAI,CAsBN;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,SAAW,GAAG,UAAU,CAI1E;AAED,sFAAsF;AACtF,MAAM,WAAW,UAAW,SAAQ,UAAU;IAC5C,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,UAAU,CAY/F"}
|