@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,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One naming rule, in one module: **`<project>-<env>-<thing>`**, kebab-case.
|
|
3
|
+
*
|
|
4
|
+
* Every Cloudflare namespace Pithy writes into is flat and account-wide — D1, KV, R2, Worker
|
|
5
|
+
* scripts, Workflows, the single Secrets Store, the API-token list. None of them can be
|
|
6
|
+
* partitioned, so the *name* is the partition, and the project segment is the only thing keeping
|
|
7
|
+
* two Pithy projects in one account from adopting or overwriting each other's resources.
|
|
8
|
+
*
|
|
9
|
+
* **Project first** because that is the ownership boundary everything turns on: teardown, the
|
|
10
|
+
* token listing's prefix filter, and the reaper's reservation all key on it. **Environment second**
|
|
11
|
+
* because these end up in listings nobody can filter — `wrangler d1 list`, the account dashboard,
|
|
12
|
+
* the Secrets Store — and sorting by name should group a project's resources and then that
|
|
13
|
+
* project's environments, so everything belonging to prod sits together. Environment last
|
|
14
|
+
* would scatter prod through the listing, interleaved with staging, which is exactly when
|
|
15
|
+
* someone acts on the wrong one.
|
|
16
|
+
*
|
|
17
|
+
* A `global`-scoped thing puts the literal `global` in the environment slot rather than omitting
|
|
18
|
+
* it, so the scheme has no exception to remember.
|
|
19
|
+
*
|
|
20
|
+
* This module is in `core` because it has no dependencies and every layer needs it — capabilities,
|
|
21
|
+
* the CLI, and the Workers runtime alike. It therefore uses **no `node:` builtin**: core is bundled
|
|
22
|
+
* into the adopter's Worker, where `node:crypto` is not free.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The default budget of the un-namespaced {@link resourceName} composer: the strictest cap of any
|
|
26
|
+
* namespace Pithy writes into, which is R2's 63.
|
|
27
|
+
*
|
|
28
|
+
* A caller that knows its namespace does **not** use this — it passes that namespace's own limit
|
|
29
|
+
* from `./limits`, or, better, goes through the facade in `./resourceNames`, which cannot pick the
|
|
30
|
+
* wrong budget. This number is the conservative answer for a call site that has no namespace at all.
|
|
31
|
+
*/
|
|
32
|
+
export declare const MAX_RESOURCE_NAME: 63;
|
|
33
|
+
/**
|
|
34
|
+
* The cap on a project name — **derived, not chosen, and derived from two shapes rather than one**.
|
|
35
|
+
*
|
|
36
|
+
* A project name has to survive every name it will ever be the head of, so it gets the *smaller* of
|
|
37
|
+
* the two worst cases (`./limits` carries both derivations with their arithmetic):
|
|
38
|
+
*
|
|
39
|
+
* - {@link WORKFLOW_DERIVED_PROJECT_NAME} — `<project>-<env>-<capability>-<job>` against a Workflow's
|
|
40
|
+
* 64 characters, after the longest environment and the longest `<capability>-<job>`.
|
|
41
|
+
* - {@link FEATURE_DERIVED_PROJECT_NAME} — `<project>-f<issue>-<slug>-<binding>-<kind>` against R2's
|
|
42
|
+
* 63, with room for the reserved issue digits, a legible slug, and a binding kept whole.
|
|
43
|
+
*
|
|
44
|
+
* Neither number is repeated here. Each is a computed constant with its arithmetic in `./limits`, and a
|
|
45
|
+
* copy in this sentence is a copy that goes stale the first time a term of the derivation moves.
|
|
46
|
+
*
|
|
47
|
+
* The feature shape is the binding one, and it used to be invisible: the cap was derived from the
|
|
48
|
+
* Workflow alone, so a project inside it could still compose a feature name whose slug was hashed
|
|
49
|
+
* away to nothing. Taking the minimum is the only version that is true of both.
|
|
50
|
+
*
|
|
51
|
+
* **Why the cap belongs on the project name rather than on each composed name:** without it the
|
|
52
|
+
* refusal lands at the first `pithy <capability> provision`, by which time `pithy add` has created
|
|
53
|
+
* real R2 buckets and D1 databases under a name that fits *their* budget. The project is
|
|
54
|
+
* half-provisioned, and the only documented remedy — renaming — orphans everything already made.
|
|
55
|
+
* Capped here, `pithy init` and `requireProjectName` refuse it before anything is written.
|
|
56
|
+
*/
|
|
57
|
+
export declare const MAX_PROJECT_NAME: number;
|
|
58
|
+
/** Lowercase, collapse any run of non-`[a-z0-9]` to one `-`, and trim leading/trailing `-`. */
|
|
59
|
+
export declare function kebab(input: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* A stable 6-hex-character digest, FNV-1a. Pure arithmetic rather than `node:crypto` so this module
|
|
62
|
+
* runs unchanged in a Worker. It is a *disambiguator for truncated names*, never a security
|
|
63
|
+
* primitive — collision resistance beyond "two different capability names look different" is not
|
|
64
|
+
* claimed and must not be relied on.
|
|
65
|
+
*/
|
|
66
|
+
export declare function hash6(input: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Fit a kebab segment into `budget` characters: verbatim when it fits, otherwise a truncated head
|
|
69
|
+
* plus a short stable hash so it stays distinct and within budget. Deterministic in `segment`
|
|
70
|
+
* alone, so two commands computing the same name agree without storing it. The hash shrinks for
|
|
71
|
+
* tiny budgets, and the result never exceeds `budget` nor trails a hyphen.
|
|
72
|
+
*/
|
|
73
|
+
export declare function fitSegment(segment: string, budget: number): string;
|
|
74
|
+
/** The parts of a provisioned name. Every field is required — an absent segment is a naming bug, not a default. */
|
|
75
|
+
export interface ResourceNameParts {
|
|
76
|
+
/**
|
|
77
|
+
* The project name — the root `pithy.config.ts` `name`, resolved by `requireProjectName` and
|
|
78
|
+
* **never guessed**. This is the ownership boundary; a wrong value here writes into another
|
|
79
|
+
* project's resources.
|
|
80
|
+
*/
|
|
81
|
+
project: string;
|
|
82
|
+
/** The environment (`dev`, `staging`, `prod`), or the literal `global` for a value shared across all of them. */
|
|
83
|
+
env: string;
|
|
84
|
+
/** What this resource is — a capability, a binding, a secret, a token profile. The only segment ever truncated. */
|
|
85
|
+
thing: string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Compose `<project>-<env>-<thing>`, kebab-cased and fitted to `budget`.
|
|
89
|
+
*
|
|
90
|
+
* **Only `thing` is ever truncated.** The project and environment segments are taken verbatim,
|
|
91
|
+
* because a hashed project segment would let two long project names share a prefix — and the token
|
|
92
|
+
* listing filters on exactly that prefix, so a collision there means one project enumerating and
|
|
93
|
+
* acting on another's credentials. If project and environment alone cannot fit, that is refused
|
|
94
|
+
* with the limit named rather than quietly hashed into an ambiguous name.
|
|
95
|
+
*
|
|
96
|
+
* `budget` defaults to {@link MAX_RESOURCE_NAME}; a namespace with its own limit passes it in.
|
|
97
|
+
*
|
|
98
|
+
* The project segment is held to {@link isValidProjectName} — the same rule the Worker and Workflow
|
|
99
|
+
* namers apply — so a project this accepts is one every namespace accepts. It is a backstop, not the
|
|
100
|
+
* gate: the real refusal belongs at `pithy init` and `requireProjectName`, before anything is written.
|
|
101
|
+
*/
|
|
102
|
+
export declare function resourceName(parts: ResourceNameParts, budget?: number): string;
|
|
103
|
+
/**
|
|
104
|
+
* Could every Cloudflare name this project generates be created? The one rule, in one place, so no
|
|
105
|
+
* namespace re-derives a looser version of it.
|
|
106
|
+
*
|
|
107
|
+
* **Read after kebabbing**, for the same reason {@link isReservedProjectName} is: the raw string never
|
|
108
|
+
* reaches an account, only what the composer made of it. `Acme Corp` is legal (it becomes `acme-corp`);
|
|
109
|
+
* `2026 Launch` is not, and neither answer can be read off the raw characters.
|
|
110
|
+
*
|
|
111
|
+
* **Two dimensions, one answer: charset and length.** Both have the same failure mode and so must have
|
|
112
|
+
* the same home — {@link NAME_SEGMENT} for the charset, {@link MAX_PROJECT_NAME} for the number. The
|
|
113
|
+
* charset is the shared segment rule rather than a project-specific one: a project name is the head of
|
|
114
|
+
* every composed name, so it is held to exactly what every other segment is held to.
|
|
115
|
+
*
|
|
116
|
+
* The rule matters at the *source* — `pithy init` and `requireProjectName` — because the namespaces
|
|
117
|
+
* disagree about it. A digit-leading project composes a legal D1 database name, a legal KV title, and a
|
|
118
|
+
* legal bucket name, so `pithy add` provisions all three for real; only the first host-worker deploy
|
|
119
|
+
* refuses it, by which point the documented remedy — rename the project — orphans everything already
|
|
120
|
+
* created. A 33-character project fails exactly the same way, one deploy later. One rule, enforced
|
|
121
|
+
* before anything is written, is the only version of this that is safe.
|
|
122
|
+
*/
|
|
123
|
+
export declare function isValidProjectName(name: string): boolean;
|
|
124
|
+
/**
|
|
125
|
+
* Refuse a project name no Cloudflare namespace could carry, as a **`ValidationError`**: a project name
|
|
126
|
+
* is something a human typed, so it is a 400 with an action, never an internal 500 blamed on the toolkit.
|
|
127
|
+
*
|
|
128
|
+
* Length and charset are one rule with two sentences, because they are two different mistakes: a name
|
|
129
|
+
* that is merely long is otherwise perfect, and "use lowercase letters" would be no help at all.
|
|
130
|
+
*/
|
|
131
|
+
export declare function assertValidProjectName(name: string): void;
|
|
132
|
+
/**
|
|
133
|
+
* **`pithy-int-` is reserved for Pithy's own live integration tests, on any account.**
|
|
134
|
+
*
|
|
135
|
+
* Every Cloudflare name a live test creates begins with it, no real project's name may, and the
|
|
136
|
+
* test-debris reaper deletes nothing without it. The reservation is what makes automatic reaping safe
|
|
137
|
+
* in both directions: without it the reaper is guessing — either it fails to recognize the names a
|
|
138
|
+
* suite invented, and debris accumulates silently on a real account, or it matches loosely enough to
|
|
139
|
+
* one day delete a customer's database. With it, membership is decidable from the name alone.
|
|
140
|
+
*
|
|
141
|
+
* It lives here, next to the composer, because both halves of the reservation are naming facts.
|
|
142
|
+
* `<project>-<env>-<thing>` puts the project first and takes it **verbatim**, so the prefix of every
|
|
143
|
+
* generated name is decided by the project name and nothing else — which is what lets one guard, on
|
|
144
|
+
* `pithy init`, keep the two sets from ever overlapping. See {@link isReservedProjectName}.
|
|
145
|
+
*
|
|
146
|
+
* This is deliberately narrower than the product's own `pithy` prefix. A project may legitimately be
|
|
147
|
+
* named `pithy-app`, and everything it provisions is real; `pithy-int-…` is debris by definition.
|
|
148
|
+
*/
|
|
149
|
+
export declare const RESERVED_TEST_PREFIX = "pithy-int-";
|
|
150
|
+
/**
|
|
151
|
+
* Would a project of this name generate resource names inside the reservation? The one rule
|
|
152
|
+
* `pithy init` enforces, stated once so no call site re-derives it.
|
|
153
|
+
*
|
|
154
|
+
* Two traps make the obvious `name.startsWith(RESERVED_TEST_PREFIX)` wrong, and both are reachable
|
|
155
|
+
* with a name a human would type.
|
|
156
|
+
*
|
|
157
|
+
* **Kebabbing.** The composer kebabs the project before using it, so the raw string is not what
|
|
158
|
+
* Cloudflare sees. `"Pithy Int Ruse"` is not prefixed by `pithy-int-`, and it normalizes straight into
|
|
159
|
+
* the reservation.
|
|
160
|
+
*
|
|
161
|
+
* **The trailing hyphen.** The composer appends one after the project. A project named exactly
|
|
162
|
+
* `pithy-int` is not prefixed by `pithy-int-` either, and it generates `pithy-int-dev-db` — inside the
|
|
163
|
+
* namespace the reaper deletes.
|
|
164
|
+
*
|
|
165
|
+
* Testing `` `${kebab(name)}-` `` against the prefix closes both: it compares the form the composer
|
|
166
|
+
* actually uses, including the separator it actually adds.
|
|
167
|
+
*/
|
|
168
|
+
export declare function isReservedProjectName(name: string): boolean;
|
|
169
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/naming/resource.ts"],"names":[],"mappings":"AAaA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,IAAgC,CAAC;AAK/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,QAAwE,CAAC;AAEtG,+FAA+F;AAC/F,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAK3C;AAED;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO3C;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOlE;AAED,mHAAmH;AACnH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,iHAAiH;IACjH,GAAG,EAAE,MAAM,CAAC;IACZ,mHAAmH;IACnH,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,EAAE,MAAM,GAAE,MAA0B,GAAG,MAAM,CAgCjG;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGxD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAezD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,oBAAoB,eAAe,CAAC;AAEjD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { NAME_SEGMENT } from "./segment.js";
|
|
2
|
+
import { InternalError, ValidationError } from "../error/pithyError.js";
|
|
3
|
+
import { MAX_ENVIRONMENT_NAME } from "./environment.js";
|
|
4
|
+
import { FEATURE_DERIVED_PROJECT_NAME, NAMESPACE_LIMITS, WORKFLOW_DERIVED_PROJECT_NAME } from "./limits.js";
|
|
5
|
+
//#region src/naming/resource.ts
|
|
6
|
+
/**
|
|
7
|
+
* One naming rule, in one module: **`<project>-<env>-<thing>`**, kebab-case.
|
|
8
|
+
*
|
|
9
|
+
* Every Cloudflare namespace Pithy writes into is flat and account-wide — D1, KV, R2, Worker
|
|
10
|
+
* scripts, Workflows, the single Secrets Store, the API-token list. None of them can be
|
|
11
|
+
* partitioned, so the *name* is the partition, and the project segment is the only thing keeping
|
|
12
|
+
* two Pithy projects in one account from adopting or overwriting each other's resources.
|
|
13
|
+
*
|
|
14
|
+
* **Project first** because that is the ownership boundary everything turns on: teardown, the
|
|
15
|
+
* token listing's prefix filter, and the reaper's reservation all key on it. **Environment second**
|
|
16
|
+
* because these end up in listings nobody can filter — `wrangler d1 list`, the account dashboard,
|
|
17
|
+
* the Secrets Store — and sorting by name should group a project's resources and then that
|
|
18
|
+
* project's environments, so everything belonging to prod sits together. Environment last
|
|
19
|
+
* would scatter prod through the listing, interleaved with staging, which is exactly when
|
|
20
|
+
* someone acts on the wrong one.
|
|
21
|
+
*
|
|
22
|
+
* A `global`-scoped thing puts the literal `global` in the environment slot rather than omitting
|
|
23
|
+
* it, so the scheme has no exception to remember.
|
|
24
|
+
*
|
|
25
|
+
* This module is in `core` because it has no dependencies and every layer needs it — capabilities,
|
|
26
|
+
* the CLI, and the Workers runtime alike. It therefore uses **no `node:` builtin**: core is bundled
|
|
27
|
+
* into the adopter's Worker, where `node:crypto` is not free.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* The default budget of the un-namespaced {@link resourceName} composer: the strictest cap of any
|
|
31
|
+
* namespace Pithy writes into, which is R2's 63.
|
|
32
|
+
*
|
|
33
|
+
* A caller that knows its namespace does **not** use this — it passes that namespace's own limit
|
|
34
|
+
* from `./limits`, or, better, goes through the facade in `./resourceNames`, which cannot pick the
|
|
35
|
+
* wrong budget. This number is the conservative answer for a call site that has no namespace at all.
|
|
36
|
+
*/
|
|
37
|
+
const MAX_RESOURCE_NAME = NAMESPACE_LIMITS.r2.maxLength;
|
|
38
|
+
/** Below this the trailing segment is not worth keeping legible, so a name this tight is refused instead. */
|
|
39
|
+
const MIN_THING_BUDGET = 1;
|
|
40
|
+
/**
|
|
41
|
+
* The cap on a project name — **derived, not chosen, and derived from two shapes rather than one**.
|
|
42
|
+
*
|
|
43
|
+
* A project name has to survive every name it will ever be the head of, so it gets the *smaller* of
|
|
44
|
+
* the two worst cases (`./limits` carries both derivations with their arithmetic):
|
|
45
|
+
*
|
|
46
|
+
* - {@link WORKFLOW_DERIVED_PROJECT_NAME} — `<project>-<env>-<capability>-<job>` against a Workflow's
|
|
47
|
+
* 64 characters, after the longest environment and the longest `<capability>-<job>`.
|
|
48
|
+
* - {@link FEATURE_DERIVED_PROJECT_NAME} — `<project>-f<issue>-<slug>-<binding>-<kind>` against R2's
|
|
49
|
+
* 63, with room for the reserved issue digits, a legible slug, and a binding kept whole.
|
|
50
|
+
*
|
|
51
|
+
* Neither number is repeated here. Each is a computed constant with its arithmetic in `./limits`, and a
|
|
52
|
+
* copy in this sentence is a copy that goes stale the first time a term of the derivation moves.
|
|
53
|
+
*
|
|
54
|
+
* The feature shape is the binding one, and it used to be invisible: the cap was derived from the
|
|
55
|
+
* Workflow alone, so a project inside it could still compose a feature name whose slug was hashed
|
|
56
|
+
* away to nothing. Taking the minimum is the only version that is true of both.
|
|
57
|
+
*
|
|
58
|
+
* **Why the cap belongs on the project name rather than on each composed name:** without it the
|
|
59
|
+
* refusal lands at the first `pithy <capability> provision`, by which time `pithy add` has created
|
|
60
|
+
* real R2 buckets and D1 databases under a name that fits *their* budget. The project is
|
|
61
|
+
* half-provisioned, and the only documented remedy — renaming — orphans everything already made.
|
|
62
|
+
* Capped here, `pithy init` and `requireProjectName` refuse it before anything is written.
|
|
63
|
+
*/
|
|
64
|
+
const MAX_PROJECT_NAME = Math.min(WORKFLOW_DERIVED_PROJECT_NAME, FEATURE_DERIVED_PROJECT_NAME);
|
|
65
|
+
/** Lowercase, collapse any run of non-`[a-z0-9]` to one `-`, and trim leading/trailing `-`. */
|
|
66
|
+
function kebab(input) {
|
|
67
|
+
return input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A stable 6-hex-character digest, FNV-1a. Pure arithmetic rather than `node:crypto` so this module
|
|
71
|
+
* runs unchanged in a Worker. It is a *disambiguator for truncated names*, never a security
|
|
72
|
+
* primitive — collision resistance beyond "two different capability names look different" is not
|
|
73
|
+
* claimed and must not be relied on.
|
|
74
|
+
*/
|
|
75
|
+
function hash6(input) {
|
|
76
|
+
let h = 2166136261;
|
|
77
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
78
|
+
h ^= input.charCodeAt(i);
|
|
79
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
80
|
+
}
|
|
81
|
+
return h.toString(16).padStart(8, "0").slice(0, 6);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Fit a kebab segment into `budget` characters: verbatim when it fits, otherwise a truncated head
|
|
85
|
+
* plus a short stable hash so it stays distinct and within budget. Deterministic in `segment`
|
|
86
|
+
* alone, so two commands computing the same name agree without storing it. The hash shrinks for
|
|
87
|
+
* tiny budgets, and the result never exceeds `budget` nor trails a hyphen.
|
|
88
|
+
*/
|
|
89
|
+
function fitSegment(segment, budget) {
|
|
90
|
+
if (budget <= 0) return "";
|
|
91
|
+
if (segment.length <= budget) return segment;
|
|
92
|
+
if (budget < 4) return hash6(segment).slice(0, budget);
|
|
93
|
+
const hashLen = Math.min(6, Math.max(2, budget - 2));
|
|
94
|
+
const headLen = Math.max(1, budget - hashLen - 1);
|
|
95
|
+
return `${segment.slice(0, headLen).replace(/-+$/, "")}-${hash6(segment).slice(0, hashLen)}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Compose `<project>-<env>-<thing>`, kebab-cased and fitted to `budget`.
|
|
99
|
+
*
|
|
100
|
+
* **Only `thing` is ever truncated.** The project and environment segments are taken verbatim,
|
|
101
|
+
* because a hashed project segment would let two long project names share a prefix — and the token
|
|
102
|
+
* listing filters on exactly that prefix, so a collision there means one project enumerating and
|
|
103
|
+
* acting on another's credentials. If project and environment alone cannot fit, that is refused
|
|
104
|
+
* with the limit named rather than quietly hashed into an ambiguous name.
|
|
105
|
+
*
|
|
106
|
+
* `budget` defaults to {@link MAX_RESOURCE_NAME}; a namespace with its own limit passes it in.
|
|
107
|
+
*
|
|
108
|
+
* The project segment is held to {@link isValidProjectName} — the same rule the Worker and Workflow
|
|
109
|
+
* namers apply — so a project this accepts is one every namespace accepts. It is a backstop, not the
|
|
110
|
+
* gate: the real refusal belongs at `pithy init` and `requireProjectName`, before anything is written.
|
|
111
|
+
*/
|
|
112
|
+
function resourceName(parts, budget = MAX_RESOURCE_NAME) {
|
|
113
|
+
const project = kebab(parts.project);
|
|
114
|
+
const env = kebab(parts.env);
|
|
115
|
+
const thing = kebab(parts.thing);
|
|
116
|
+
for (const [role, value] of [
|
|
117
|
+
["project", project],
|
|
118
|
+
["environment", env],
|
|
119
|
+
["thing", thing]
|
|
120
|
+
]) if (!value) throw new ValidationError({
|
|
121
|
+
message: `A resource name needs a ${role}.`,
|
|
122
|
+
action: role === "project" ? "Set `name` in pithy.config.ts." : void 0,
|
|
123
|
+
detail: `resourceName received an empty ${role} segment (raw: ${JSON.stringify(parts)})`
|
|
124
|
+
});
|
|
125
|
+
assertValidProjectName(parts.project);
|
|
126
|
+
const head = `${project}-${env}`;
|
|
127
|
+
const thingBudget = budget - head.length - 1;
|
|
128
|
+
if (thingBudget < MIN_THING_BUDGET) throw new InternalError({
|
|
129
|
+
message: `The project and environment names alone exceed the ${budget}-character limit for this resource.`,
|
|
130
|
+
action: "Shorten `name` in pithy.config.ts.",
|
|
131
|
+
detail: `"${head}" leaves ${thingBudget} characters for "${thing}"`
|
|
132
|
+
});
|
|
133
|
+
return `${head}-${fitSegment(thing, thingBudget)}`;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Could every Cloudflare name this project generates be created? The one rule, in one place, so no
|
|
137
|
+
* namespace re-derives a looser version of it.
|
|
138
|
+
*
|
|
139
|
+
* **Read after kebabbing**, for the same reason {@link isReservedProjectName} is: the raw string never
|
|
140
|
+
* reaches an account, only what the composer made of it. `Acme Corp` is legal (it becomes `acme-corp`);
|
|
141
|
+
* `2026 Launch` is not, and neither answer can be read off the raw characters.
|
|
142
|
+
*
|
|
143
|
+
* **Two dimensions, one answer: charset and length.** Both have the same failure mode and so must have
|
|
144
|
+
* the same home — {@link NAME_SEGMENT} for the charset, {@link MAX_PROJECT_NAME} for the number. The
|
|
145
|
+
* charset is the shared segment rule rather than a project-specific one: a project name is the head of
|
|
146
|
+
* every composed name, so it is held to exactly what every other segment is held to.
|
|
147
|
+
*
|
|
148
|
+
* The rule matters at the *source* — `pithy init` and `requireProjectName` — because the namespaces
|
|
149
|
+
* disagree about it. A digit-leading project composes a legal D1 database name, a legal KV title, and a
|
|
150
|
+
* legal bucket name, so `pithy add` provisions all three for real; only the first host-worker deploy
|
|
151
|
+
* refuses it, by which point the documented remedy — rename the project — orphans everything already
|
|
152
|
+
* created. A 33-character project fails exactly the same way, one deploy later. One rule, enforced
|
|
153
|
+
* before anything is written, is the only version of this that is safe.
|
|
154
|
+
*/
|
|
155
|
+
function isValidProjectName(name) {
|
|
156
|
+
const normalized = kebab(name);
|
|
157
|
+
return normalized.length <= MAX_PROJECT_NAME && NAME_SEGMENT.test(normalized);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Refuse a project name no Cloudflare namespace could carry, as a **`ValidationError`**: a project name
|
|
161
|
+
* is something a human typed, so it is a 400 with an action, never an internal 500 blamed on the toolkit.
|
|
162
|
+
*
|
|
163
|
+
* Length and charset are one rule with two sentences, because they are two different mistakes: a name
|
|
164
|
+
* that is merely long is otherwise perfect, and "use lowercase letters" would be no help at all.
|
|
165
|
+
*/
|
|
166
|
+
function assertValidProjectName(name) {
|
|
167
|
+
if (isValidProjectName(name)) return;
|
|
168
|
+
const normalized = kebab(name);
|
|
169
|
+
if (normalized.length > MAX_PROJECT_NAME && NAME_SEGMENT.test(normalized)) throw new ValidationError({
|
|
170
|
+
message: `"${name}" is ${normalized.length} characters. A project name stops at ${MAX_PROJECT_NAME}.`,
|
|
171
|
+
action: `Shorten \`name\` in pithy.config.ts to ${MAX_PROJECT_NAME} characters or fewer.`,
|
|
172
|
+
detail: `${MAX_PROJECT_NAME} is the smaller of what a 64-character Workflow name leaves after a ${MAX_ENVIRONMENT_NAME}-character environment and a 22-character <capability>-<job> (${WORKFLOW_DERIVED_PROJECT_NAME}), and what a 63-character feature resource name leaves after a six-digit issue, a legible slug, and a binding (${FEATURE_DERIVED_PROJECT_NAME}).`
|
|
173
|
+
});
|
|
174
|
+
throw new ValidationError({
|
|
175
|
+
message: `"${name}" can't be a project name.`,
|
|
176
|
+
action: "Use lowercase letters, digits, and single hyphens, starting with a letter.",
|
|
177
|
+
detail: `"${name}" kebabs to "${normalized}", which does not match ${NAME_SEGMENT.source}.`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* **`pithy-int-` is reserved for Pithy's own live integration tests, on any account.**
|
|
182
|
+
*
|
|
183
|
+
* Every Cloudflare name a live test creates begins with it, no real project's name may, and the
|
|
184
|
+
* test-debris reaper deletes nothing without it. The reservation is what makes automatic reaping safe
|
|
185
|
+
* in both directions: without it the reaper is guessing — either it fails to recognize the names a
|
|
186
|
+
* suite invented, and debris accumulates silently on a real account, or it matches loosely enough to
|
|
187
|
+
* one day delete a customer's database. With it, membership is decidable from the name alone.
|
|
188
|
+
*
|
|
189
|
+
* It lives here, next to the composer, because both halves of the reservation are naming facts.
|
|
190
|
+
* `<project>-<env>-<thing>` puts the project first and takes it **verbatim**, so the prefix of every
|
|
191
|
+
* generated name is decided by the project name and nothing else — which is what lets one guard, on
|
|
192
|
+
* `pithy init`, keep the two sets from ever overlapping. See {@link isReservedProjectName}.
|
|
193
|
+
*
|
|
194
|
+
* This is deliberately narrower than the product's own `pithy` prefix. A project may legitimately be
|
|
195
|
+
* named `pithy-app`, and everything it provisions is real; `pithy-int-…` is debris by definition.
|
|
196
|
+
*/
|
|
197
|
+
const RESERVED_TEST_PREFIX = "pithy-int-";
|
|
198
|
+
/**
|
|
199
|
+
* Would a project of this name generate resource names inside the reservation? The one rule
|
|
200
|
+
* `pithy init` enforces, stated once so no call site re-derives it.
|
|
201
|
+
*
|
|
202
|
+
* Two traps make the obvious `name.startsWith(RESERVED_TEST_PREFIX)` wrong, and both are reachable
|
|
203
|
+
* with a name a human would type.
|
|
204
|
+
*
|
|
205
|
+
* **Kebabbing.** The composer kebabs the project before using it, so the raw string is not what
|
|
206
|
+
* Cloudflare sees. `"Pithy Int Ruse"` is not prefixed by `pithy-int-`, and it normalizes straight into
|
|
207
|
+
* the reservation.
|
|
208
|
+
*
|
|
209
|
+
* **The trailing hyphen.** The composer appends one after the project. A project named exactly
|
|
210
|
+
* `pithy-int` is not prefixed by `pithy-int-` either, and it generates `pithy-int-dev-db` — inside the
|
|
211
|
+
* namespace the reaper deletes.
|
|
212
|
+
*
|
|
213
|
+
* Testing `` `${kebab(name)}-` `` against the prefix closes both: it compares the form the composer
|
|
214
|
+
* actually uses, including the separator it actually adds.
|
|
215
|
+
*/
|
|
216
|
+
function isReservedProjectName(name) {
|
|
217
|
+
return `${kebab(name)}-`.startsWith(RESERVED_TEST_PREFIX);
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
export { MAX_PROJECT_NAME, MAX_RESOURCE_NAME, RESERVED_TEST_PREFIX, assertValidProjectName, fitSegment, hash6, isReservedProjectName, isValidProjectName, kebab, resourceName };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type FeatureIdentity, type FeatureResourceKind } from "./feature";
|
|
2
|
+
/**
|
|
3
|
+
* Every Cloudflare name a project composes, behind one object — **the facade**.
|
|
4
|
+
*
|
|
5
|
+
* The free functions underneath are correct and stay exported, but they share one flaw: each takes a
|
|
6
|
+
* budget, so every call site decides for itself which limit applies. That is how one number, 63, ended
|
|
7
|
+
* up standing in for eight different namespaces. Here the budget is not an argument at all. You pick a
|
|
8
|
+
* *kind of thing* — a bucket, a KV namespace, a Workflow — and the kind carries its own cap and its own
|
|
9
|
+
* refuse-or-truncate policy from `./limits`. There is no wrong budget to pass, because there is no
|
|
10
|
+
* budget to pass.
|
|
11
|
+
*
|
|
12
|
+
* **Shape: project, then scope, then kind.**
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const names = resourceNames(config.name); // the project, validated once
|
|
16
|
+
* const prod = names.env("prod"); // the environment, validated once
|
|
17
|
+
* prod.d1("DB"); // acme-prod-db
|
|
18
|
+
* prod.workflow("email", "send"); // acme-prod-email-send
|
|
19
|
+
* names.global.secretEntry("secrets-manager-cf-api-token");
|
|
20
|
+
* names.feature({ issue, slug }).resource("DB", "d1");
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Three reasons for that order rather than a flat `worker(capability, env)`:
|
|
24
|
+
*
|
|
25
|
+
* 1. **Each thing is validated once, at the point it is named.** A bad project fails at
|
|
26
|
+
* `resourceNames`, before a single name exists; a bad environment fails at `env`, not on the fourth
|
|
27
|
+
* getter that happened to use it.
|
|
28
|
+
* 2. **The environment is threaded once, not through every call.** Commands are environment-scoped —
|
|
29
|
+
* `pithy deploy --env prod` names a dozen resources in one environment — so binding it once removes a
|
|
30
|
+
* dozen chances to pass the wrong one.
|
|
31
|
+
* 3. **`global` becomes a property, not a magic string.** It is the same interface as an environment
|
|
32
|
+
* (`names.global.secretEntry(…)`), so no call site special-cases the literal, and no call site can
|
|
33
|
+
* typo it into a real environment named `globl`.
|
|
34
|
+
*
|
|
35
|
+
* The feature shape hangs off the same object for the same reason it exists at all: it is the *other*
|
|
36
|
+
* scope a project's resources live in, and it shares the project segment with everything above.
|
|
37
|
+
*/
|
|
38
|
+
/** The names available within one scope — an environment, or the global scope beside them. */
|
|
39
|
+
export interface ScopedNames {
|
|
40
|
+
/** A capability's Worker script — `<project>-<env>-<capability>`. Refused, not truncated, past 63. */
|
|
41
|
+
worker(capability: string): string;
|
|
42
|
+
/** One durable job's Workflow — `<project>-<env>-<capability>-<job>`. Refused, not truncated, past 64. */
|
|
43
|
+
workflow(capability: string, job: string): string;
|
|
44
|
+
/** A D1 database. No Cloudflare cap; Pithy's ceiling, truncated with a stable hash. */
|
|
45
|
+
d1(binding: string): string;
|
|
46
|
+
/** A KV namespace title. Cloudflare's 512, not the 63 this used to be held to. */
|
|
47
|
+
kv(binding: string): string;
|
|
48
|
+
/** An R2 bucket. Cloudflare's 63 — the one namespace where that number was always right. */
|
|
49
|
+
r2(binding: string): string;
|
|
50
|
+
/** A Vectorize index. 64 bytes, refused rather than truncated: a renamed index has no vectors. */
|
|
51
|
+
vectorizeIndex(index: string): string;
|
|
52
|
+
/** A Secrets Store entry. No Cloudflare cap; Pithy's ceiling. */
|
|
53
|
+
secretEntry(secret: string): string;
|
|
54
|
+
/** A Cloudflare API token label. No Cloudflare cap; Pithy's ceiling. */
|
|
55
|
+
apiToken(profile: string): string;
|
|
56
|
+
}
|
|
57
|
+
/** The names a feature environment provisions under, with the project already bound. */
|
|
58
|
+
export interface FeatureNames {
|
|
59
|
+
/** A feature's D1, KV, or R2 resource — `<project>-f<issue>-<slug>-<binding>-<kind>`. */
|
|
60
|
+
resource(binding: string, kind: FeatureResourceKind): string;
|
|
61
|
+
/** A feature's Worker script — `<project>-f<issue>-<slug>-<worker>`. */
|
|
62
|
+
worker(worker: string): string;
|
|
63
|
+
}
|
|
64
|
+
/** Everything a project can be asked to name. Created once, from the project, by {@link resourceNames}. */
|
|
65
|
+
export interface ProjectNames {
|
|
66
|
+
/** The kebabbed project name every name below leads with. */
|
|
67
|
+
readonly project: string;
|
|
68
|
+
/** The names in one environment. Throws if the environment is not one this scheme accepts. */
|
|
69
|
+
env(environment: string): ScopedNames;
|
|
70
|
+
/** The names shared by every environment — the `global` scope, without typing the word. */
|
|
71
|
+
readonly global: ScopedNames;
|
|
72
|
+
/** The names a feature environment provisions under. */
|
|
73
|
+
feature(identity: Omit<FeatureIdentity, "project">): FeatureNames;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Bind a project to every name it can compose.
|
|
77
|
+
*
|
|
78
|
+
* The project is validated here — charset *and* length, the one project rule — so a project that cannot
|
|
79
|
+
* carry every namespace fails before it has provisioned any of them. That is the whole reason the
|
|
80
|
+
* project is supplied once instead of per call.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resourceNames(project: string): ProjectNames;
|
|
83
|
+
//# sourceMappingURL=resourceNames.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resourceNames.d.ts","sourceRoot":"","sources":["../../src/naming/resourceNames.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAA0C,MAAM,WAAW,CAAC;AAInH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,sGAAsG;IACtG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,0GAA0G;IAC1G,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,uFAAuF;IACvF,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kFAAkF;IAClF,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,4FAA4F;IAC5F,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kGAAkG;IAClG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,iEAAiE;IACjE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAC7D,wEAAwE;IACxE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAED,2GAA2G;AAC3G,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAAC;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,wDAAwD;IACxD,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,CAAC;CACnE;AA8DD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAmB3D"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { InternalError, ValidationError } from "../error/pithyError.js";
|
|
2
|
+
import { GLOBAL_SCOPE, assertValidEnvironment } from "./environment.js";
|
|
3
|
+
import { NAMESPACE_LIMITS } from "./limits.js";
|
|
4
|
+
import { assertValidProjectName, kebab, resourceName } from "./resource.js";
|
|
5
|
+
import { workflowHostName, workflowScriptName } from "../workflow/naming.js";
|
|
6
|
+
import { featureResourceName, featureWorkerName } from "./feature.js";
|
|
7
|
+
//#region src/naming/resourceNames.ts
|
|
8
|
+
/**
|
|
9
|
+
* Compose `<project>-<scope>-<thing>` under one namespace's rule.
|
|
10
|
+
*
|
|
11
|
+
* A `truncate` namespace hands off to {@link resourceName} with that namespace's cap, so there is still
|
|
12
|
+
* exactly one truncation rule. A `refuse` namespace composes verbatim and throws with the limit named —
|
|
13
|
+
* as an `InternalError`, because by this point the project and scope have both been validated, so an
|
|
14
|
+
* over-long name means author-controlled code asked for one.
|
|
15
|
+
*
|
|
16
|
+
* **The refuse branch here is deliberately not `../workflow/naming`'s**, though both compose
|
|
17
|
+
* `<project>-<scope>-<thing>` and both refuse rather than truncate. They answer different questions: a
|
|
18
|
+
* Workflow or Worker name is assembled from author-controlled capability and job identifiers, so that
|
|
19
|
+
* module *asserts* each segment against `NAME_SEGMENT` and measures the result in **bytes**, the
|
|
20
|
+
* unit Cloudflare states those two limits in. The kinds routed through here are named after a binding
|
|
21
|
+
* or a secret, which is an adopter's string, so it is kebabbed into shape instead. Merging the two would
|
|
22
|
+
* either start mangling a capability name or stop normalizing a binding.
|
|
23
|
+
*/
|
|
24
|
+
function scopedName(project, scope, thing, limit) {
|
|
25
|
+
if (limit.policy === "truncate") {
|
|
26
|
+
const name = resourceName({
|
|
27
|
+
project,
|
|
28
|
+
env: scope,
|
|
29
|
+
thing
|
|
30
|
+
}, limit.maxLength);
|
|
31
|
+
if (name.length < limit.minLength) throw new InternalError({
|
|
32
|
+
message: `"${name}" is too short for ${limit.label}.`,
|
|
33
|
+
action: `Use a longer project, environment, or resource name — ${limit.label} needs ${limit.minLength} characters.`
|
|
34
|
+
});
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
37
|
+
const segment = kebab(thing);
|
|
38
|
+
if (!segment) throw new ValidationError({
|
|
39
|
+
message: `A name needs something to name.`,
|
|
40
|
+
detail: `${limit.label} was asked for with an empty segment (raw: ${JSON.stringify(thing)}).`
|
|
41
|
+
});
|
|
42
|
+
const name = `${kebab(project)}-${scope}-${segment}`;
|
|
43
|
+
if (name.length > limit.maxLength) throw new InternalError({
|
|
44
|
+
message: `"${name}" is ${name.length} characters. ${limit.label[0]?.toUpperCase()}${limit.label.slice(1)} stops at ${limit.maxLength}.`,
|
|
45
|
+
action: "Shorten the project, environment, or resource name.",
|
|
46
|
+
detail: `${limit.label} is refused rather than truncated — renaming it after a deploy orphans what it addresses.`
|
|
47
|
+
});
|
|
48
|
+
return name;
|
|
49
|
+
}
|
|
50
|
+
/** The getters for one scope. `global` and every environment share this, so nothing special-cases either. */
|
|
51
|
+
function scopedNames(project, scope) {
|
|
52
|
+
return {
|
|
53
|
+
worker: (capability) => workflowHostName({
|
|
54
|
+
project,
|
|
55
|
+
capability,
|
|
56
|
+
env: scope
|
|
57
|
+
}),
|
|
58
|
+
workflow: (capability, job) => workflowScriptName({
|
|
59
|
+
project,
|
|
60
|
+
capability,
|
|
61
|
+
job,
|
|
62
|
+
env: scope
|
|
63
|
+
}),
|
|
64
|
+
d1: (binding) => scopedName(project, scope, binding, NAMESPACE_LIMITS.d1),
|
|
65
|
+
kv: (binding) => scopedName(project, scope, binding, NAMESPACE_LIMITS.kv),
|
|
66
|
+
r2: (binding) => scopedName(project, scope, binding, NAMESPACE_LIMITS.r2),
|
|
67
|
+
vectorizeIndex: (index) => scopedName(project, scope, index, NAMESPACE_LIMITS.vectorizeIndex),
|
|
68
|
+
secretEntry: (secret) => scopedName(project, scope, secret, NAMESPACE_LIMITS.secretEntry),
|
|
69
|
+
apiToken: (profile) => scopedName(project, scope, profile, NAMESPACE_LIMITS.apiToken)
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Bind a project to every name it can compose.
|
|
74
|
+
*
|
|
75
|
+
* The project is validated here — charset *and* length, the one project rule — so a project that cannot
|
|
76
|
+
* carry every namespace fails before it has provisioned any of them. That is the whole reason the
|
|
77
|
+
* project is supplied once instead of per call.
|
|
78
|
+
*/
|
|
79
|
+
function resourceNames(project) {
|
|
80
|
+
assertValidProjectName(project);
|
|
81
|
+
const normalized = kebab(project);
|
|
82
|
+
return {
|
|
83
|
+
project: normalized,
|
|
84
|
+
env(environment) {
|
|
85
|
+
assertValidEnvironment(environment);
|
|
86
|
+
return scopedNames(normalized, environment);
|
|
87
|
+
},
|
|
88
|
+
global: scopedNames(normalized, GLOBAL_SCOPE),
|
|
89
|
+
feature(identity) {
|
|
90
|
+
const full = {
|
|
91
|
+
...identity,
|
|
92
|
+
project: normalized
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
resource: (binding, kind) => featureResourceName(full, binding, kind),
|
|
96
|
+
worker: (worker) => featureWorkerName(full, worker)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { resourceNames };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a single segment of a composed name may look like — one pattern, one home.
|
|
3
|
+
*
|
|
4
|
+
* `<project>-<env>-<thing>` is built out of segments, and every one of them answers to the same
|
|
5
|
+
* shape: a project, an environment, a capability, a job. The pattern lived in three modules,
|
|
6
|
+
* character for character, which is the arrangement where a rule stops being one rule — an edit to
|
|
7
|
+
* the environment copy would silently leave the Workflow copy behind, and the two would disagree
|
|
8
|
+
* about a name the other had already accepted.
|
|
9
|
+
*
|
|
10
|
+
* A capability that genuinely needs a different rule should declare its own pattern beside this one
|
|
11
|
+
* with a comment saying what it allows that this does not, and why. Divergence is a decision, not a
|
|
12
|
+
* stale copy. `scaffoldProject`'s `WORKER_NAME` is the standing example: a Worker *directory* under
|
|
13
|
+
* `apps/` may lead with a digit, because it is a package name rather than a Cloudflare name.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A lowercase letter, then letters, digits, and single inner hyphens — no leading digit, no
|
|
17
|
+
* doubled or trailing hyphen, no underscore, no uppercase.
|
|
18
|
+
*
|
|
19
|
+
* This is the **strictest** rule of any namespace a name reaches, and therefore the rule for all of
|
|
20
|
+
* them. Cloudflare Worker script names, Workflow names, and Vectorize indexes must start with a
|
|
21
|
+
* letter; D1, KV, and R2 would take `1password-clone-dev-db` happily. The loose namespaces are the
|
|
22
|
+
* ones a project touches first, so holding everything to the strict rule is what keeps a name that
|
|
23
|
+
* provisions from being a name that cannot deploy.
|
|
24
|
+
*
|
|
25
|
+
* Not `/g`: `.test()` on a global regex advances `lastIndex`, and one shared instance would answer
|
|
26
|
+
* differently on alternate calls. `segment.test.ts` pins that.
|
|
27
|
+
*/
|
|
28
|
+
export declare const NAME_SEGMENT: RegExp;
|
|
29
|
+
//# sourceMappingURL=segment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segment.d.ts","sourceRoot":"","sources":["../../src/naming/segment.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,QAAkC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/naming/segment.ts
|
|
2
|
+
/**
|
|
3
|
+
* What a single segment of a composed name may look like — one pattern, one home.
|
|
4
|
+
*
|
|
5
|
+
* `<project>-<env>-<thing>` is built out of segments, and every one of them answers to the same
|
|
6
|
+
* shape: a project, an environment, a capability, a job. The pattern lived in three modules,
|
|
7
|
+
* character for character, which is the arrangement where a rule stops being one rule — an edit to
|
|
8
|
+
* the environment copy would silently leave the Workflow copy behind, and the two would disagree
|
|
9
|
+
* about a name the other had already accepted.
|
|
10
|
+
*
|
|
11
|
+
* A capability that genuinely needs a different rule should declare its own pattern beside this one
|
|
12
|
+
* with a comment saying what it allows that this does not, and why. Divergence is a decision, not a
|
|
13
|
+
* stale copy. `scaffoldProject`'s `WORKER_NAME` is the standing example: a Worker *directory* under
|
|
14
|
+
* `apps/` may lead with a digit, because it is a package name rather than a Cloudflare name.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A lowercase letter, then letters, digits, and single inner hyphens — no leading digit, no
|
|
18
|
+
* doubled or trailing hyphen, no underscore, no uppercase.
|
|
19
|
+
*
|
|
20
|
+
* This is the **strictest** rule of any namespace a name reaches, and therefore the rule for all of
|
|
21
|
+
* them. Cloudflare Worker script names, Workflow names, and Vectorize indexes must start with a
|
|
22
|
+
* letter; D1, KV, and R2 would take `1password-clone-dev-db` happily. The loose namespaces are the
|
|
23
|
+
* ones a project touches first, so holding everything to the strict rule is what keeps a name that
|
|
24
|
+
* provisions from being a name that cannot deploy.
|
|
25
|
+
*
|
|
26
|
+
* Not `/g`: `.test()` on a global regex advances `lastIndex`, and one shared instance would answer
|
|
27
|
+
* differently on alternate calls. `segment.test.ts` pins that.
|
|
28
|
+
*/
|
|
29
|
+
const NAME_SEGMENT = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { NAME_SEGMENT };
|