@authhero/cloudflare-adapter 2.33.2 → 2.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -330,4 +330,4 @@ export default {
330
330
  }
331
331
  },
332
332
  };
333
- `}var Lh=class{loader;compatibilityDate;constructor(e){this.loader=e.loader,this.compatibilityDate=e.compatibilityDate||`2025-01-01`}async execute(e){let t=Date.now();try{let t=Ih(e.code),n={compatibilityDate:this.compatibilityDate,mainModule:`hook.js`,modules:{"hook.js":t}},r=e.hookCodeId?`${e.hookCodeId}-${await Fh(e.code)}`:null;return await(await(r?this.loader.get(r,async()=>n):this.loader.load(n)).getEntrypoint().fetch(new Request(`https://hook/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({event:e.event,triggerId:e.triggerId})}))).json()}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e),durationMs:Date.now()-t,apiCalls:[]}}}};function Rh(e){let t={customDomains:Dm(e),cache:km({...e.cacheName&&{cacheName:e.cacheName},...e.defaultTtlSeconds!==void 0&&{defaultTtlSeconds:e.defaultTtlSeconds},...e.keyPrefix&&{keyPrefix:e.keyPrefix}}),geo:Dh()};e.r2SqlLogs?t.logs=Um(e.r2SqlLogs):e.analyticsEngineLogs&&(t.logs=mh(e.analyticsEngineLogs)),e.analyticsEngineLogs&&(t.analytics=ph(e.analyticsEngineLogs)),e.analyticsEngineActionExecutions&&(t.actionExecutions=Eh(e.analyticsEngineActionExecutions));let n=kh(e.rateLimitBindings);return n&&(t.rateLimit=n),t}exports.CloudflareCodeExecutor=Mh,exports.DispatchNamespaceCodeExecutor=jh,exports.WorkerLoaderCodeExecutor=Lh,exports.createAnalyticsEngineActionExecutionsAdapter=Eh,exports.createAnalyticsEngineAnalyticsAdapter=ph,exports.createAnalyticsEngineLogsAdapter=mh,exports.createAnalyticsEngineStatsAdapter=ih,exports.createCloudflareRateLimitAdapter=kh,exports.createR2SQLLogsAdapter=Um,exports.createR2SQLStatsAdapter=Hm,exports.default=Rh,exports.generateWorkerScript=Ah;
333
+ `}var Lh=class{loader;compatibilityDate;constructor(e){this.loader=e.loader,this.compatibilityDate=e.compatibilityDate||`2025-01-01`}async execute(e){let t=Date.now();try{let t=Ih(e.code),n={compatibilityDate:this.compatibilityDate,mainModule:`hook.js`,modules:{"hook.js":t}},r=e.hookCodeId?`${e.hookCodeId}-${await Fh(e.code)}`:null;return await(await(r?this.loader.get(r,async()=>n):this.loader.load(n)).getEntrypoint().fetch(new Request(`https://hook/execute`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({event:e.event,triggerId:e.triggerId})}))).json()}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e),durationMs:Date.now()-t,apiCalls:[]}}}},Rh=class extends Error{status;endpoint;errors;body;constructor(e,t,n,r=[]){super(`Cloudflare API ${e} ${t}: ${zh(n,256)}`),this.name=`CloudflareApiError`,this.status=e,this.endpoint=t,this.body=n,this.errors=r}};function zh(e,t){return e.length<=t?e:`${e.slice(0,t)}…`}var Bh=class{accountId;apiToken;fetchImpl;timeoutMs;baseUrl;constructor(e){this.accountId=e.accountId,this.apiToken=e.apiToken,this.fetchImpl=e.fetch??fetch,this.timeoutMs=e.timeoutMs??3e4,this.baseUrl=(e.baseUrl??`https://api.cloudflare.com/client/v4`).replace(/\/+$/,``)}async createD1Database(e){let t=`/accounts/${encodeURIComponent(this.accountId)}/d1/database`;return(await this.request(`POST`,t,{body:JSON.stringify({name:e})})).result}async listD1Databases(e){let t=e?`?name=${encodeURIComponent(e)}`:``,n=`/accounts/${encodeURIComponent(this.accountId)}/d1/database${t}`;return(await this.request(`GET`,n)).result??[]}async deleteD1Database(e){let t=`/accounts/${encodeURIComponent(this.accountId)}/d1/database/${encodeURIComponent(e)}`;await this.request(`DELETE`,t)}async execD1(e,t){let n=`/accounts/${encodeURIComponent(this.accountId)}/d1/database/${encodeURIComponent(e)}/query`;return(await this.request(`POST`,n,{body:JSON.stringify({sql:t})})).result??[]}async uploadNamespacedScript(e,t,n){let r=`/accounts/${this.accountId}/workers/dispatch/namespaces/${encodeURIComponent(e)}/scripts/${encodeURIComponent(t)}`,i={main_module:n.mainModule,compatibility_date:n.compatibilityDate,compatibility_flags:n.compatibilityFlags??[],bindings:n.bindings??[],tags:n.tags},a=new FormData;a.append(`metadata`,new Blob([JSON.stringify(i)],{type:`application/json`})),a.append(n.mainModule,new Blob([n.script],{type:`application/javascript+module`}),n.mainModule),await this.request(`PUT`,r,{body:a})}async deleteNamespacedScript(e,t){let n=`/accounts/${this.accountId}/workers/dispatch/namespaces/${encodeURIComponent(e)}/scripts/${encodeURIComponent(t)}`;await this.request(`DELETE`,n)}async setNamespacedScriptSecret(e,t,n,r){let i=`/accounts/${this.accountId}/workers/dispatch/namespaces/${encodeURIComponent(e)}/scripts/${encodeURIComponent(t)}/secrets`;await this.request(`PUT`,i,{body:JSON.stringify({name:n,text:r,type:`secret_text`})})}async request(e,t,n){let r=`${this.baseUrl}${t}`,i=new AbortController,a=setTimeout(()=>i.abort(),this.timeoutMs),o={Authorization:`Bearer ${this.apiToken}`,...n?.headers??{}};n?.body&&!(n.body instanceof FormData)&&!o[`Content-Type`]&&(o[`Content-Type`]=`application/json`);let s;try{s=await this.fetchImpl(r,{method:e,headers:o,body:n?.body,signal:i.signal})}finally{clearTimeout(a)}let c=await s.text();if(!s.ok){let e=[];try{let t=JSON.parse(c);Array.isArray(t.errors)&&(e=t.errors)}catch{}throw new Rh(s.status,t,c,e)}if(!(c===``||s.status===204))return JSON.parse(c)}},Vh=`{tenant_id}`,Hh=`tenant-{tenant_id}`,Uh=`index.js`,Wh=`2026-05-01`;function Gh(e,t){return e.replace(/\{tenant_id\}/g,t)}function Kh(e){if(!(e instanceof Rh))return!1;let t=e.body.toLowerCase();return e.status===409||t.includes(`already exists`)||t.includes(`name is already taken`)||t.includes(`already in use`)}function qh(e){return e instanceof Rh?e.status===404:!1}function Jh(e){let t=new Bh({accountId:e.accountId,apiToken:e.apiToken,fetch:e.fetch,timeoutMs:e.timeoutMs}),n=e.scriptNameTemplate??Vh,r=e.d1NameTemplate??Hh,i=e.scriptMetadata?.main_module??Uh,a=e.scriptMetadata?.compatibility_date??Wh,o=e.scriptMetadata?.compatibility_flags??[`nodejs_compat`],s=e.dispatchNamespace;async function c(e){let n=(await t.listD1Databases(e)).find(t=>t.name===e);if(n)return n.uuid;try{return(await t.createD1Database(e)).uuid}catch(n){if(!Kh(n))throw n;let r=(await t.listD1Databases(e)).find(t=>t.name===e);if(r)return r.uuid;throw n}}async function l(n){for(let r of e.migrations)try{await t.execD1(n,r.sql)}catch(e){throw e instanceof Error?Error(`Failed to apply migration "${r.name}" to D1 ${n}: ${e.message}`,{cause:e}):e}}async function u(n,r){let c=[{type:`d1`,name:`AUTH_DB`,id:r},{type:`plain_text`,name:`CONTROL_PLANE_BASE_URL`,text:e.controlPlaneBaseUrl}];await t.uploadNamespacedScript(s,n,{script:e.tenantWorkerScript,mainModule:i,compatibilityDate:a,compatibilityFlags:o,bindings:c,tags:[`authhero-tenant`,`tenant:${n}`]})}async function d(n,r){let i=await e.secrets(r);for(let[e,r]of Object.entries(i))await t.setNamespacedScriptSecret(s,n,e,r)}return{async onProvision(e){let t=Gh(n,e),i=Gh(r,e),a=await c(i);return await l(a),await u(t,a),await d(t,e),{d1DatabaseId:a,scriptName:t,d1Name:i}},async onDeprovision(e){let i=Gh(n,e),a=Gh(r,e);try{await t.deleteNamespacedScript(s,i)}catch(e){if(!qh(e))throw e}let o=(await t.listD1Databases(a)).find(e=>e.name===a);if(o)try{await t.deleteD1Database(o.uuid)}catch(e){if(!qh(e))throw e}}}}function Yh(e){return e.deployment_type===`wfp`}function Xh(e){let{provisioner:t,tenants:n}=e,r=e.shouldProvision??Yh,i=e.logger;async function a(e,t){let r=t instanceof Error?t.message:String(t);try{await n.update(e,{provisioning_state:`failed`,provisioning_error:r.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}catch(t){i?.warn(`Failed to write provisioning_state="failed" for tenant ${e}:`,t)}}return{async onProvision(e){let i=await n.get(e);if(i&&r(i))try{let r=await t.onProvision(e);await n.update(e,{d1_database_id:r.d1DatabaseId,worker_script_name:r.scriptName,provisioning_state:`ready`,provisioning_error:void 0,provisioning_state_changed_at:new Date().toISOString()})}catch(t){throw await a(e,t),t}},async onDeprovision(e){let i=await n.get(e);i&&!r(i)||await t.onDeprovision(e)}}}function Zh(e){let t={customDomains:Dm(e),cache:km({...e.cacheName&&{cacheName:e.cacheName},...e.defaultTtlSeconds!==void 0&&{defaultTtlSeconds:e.defaultTtlSeconds},...e.keyPrefix&&{keyPrefix:e.keyPrefix}}),geo:Dh()};e.r2SqlLogs?t.logs=Um(e.r2SqlLogs):e.analyticsEngineLogs&&(t.logs=mh(e.analyticsEngineLogs)),e.analyticsEngineLogs&&(t.analytics=ph(e.analyticsEngineLogs)),e.analyticsEngineActionExecutions&&(t.actionExecutions=Eh(e.analyticsEngineActionExecutions));let n=kh(e.rateLimitBindings);return n&&(t.rateLimit=n),t}exports.CloudflareApiClient=Bh,exports.CloudflareApiError=Rh,exports.CloudflareCodeExecutor=Mh,exports.DispatchNamespaceCodeExecutor=jh,exports.WorkerLoaderCodeExecutor=Lh,exports.createAnalyticsEngineActionExecutionsAdapter=Eh,exports.createAnalyticsEngineAnalyticsAdapter=ph,exports.createAnalyticsEngineLogsAdapter=mh,exports.createAnalyticsEngineStatsAdapter=ih,exports.createCloudflareRateLimitAdapter=kh,exports.createCloudflareWfpD1Provisioner=Jh,exports.createR2SQLLogsAdapter=Um,exports.createR2SQLStatsAdapter=Hm,exports.createWfpTenantProvisioningHook=Xh,exports.default=Zh,exports.generateWorkerScript=Ah;
@@ -1,4 +1,4 @@
1
- import { StatsAdapter, LogsDataAdapter, AnalyticsAdapter, ActionExecutionsAdapter, RateLimitScope, RateLimitAdapter, CustomDomainsAdapter, CodeExecutor, CodeExecutionResult, CacheAdapter, GeoAdapter } from '@authhero/adapter-interfaces';
1
+ import { StatsAdapter, LogsDataAdapter, AnalyticsAdapter, ActionExecutionsAdapter, RateLimitScope, RateLimitAdapter, CustomDomainsAdapter, CodeExecutor, CodeExecutionResult, TenantsDataAdapter, CacheAdapter, GeoAdapter } from '@authhero/adapter-interfaces';
2
2
 
3
3
  interface R2SQLLogsAdapterConfig {
4
4
  /**
@@ -508,6 +508,345 @@ declare class WorkerLoaderCodeExecutor implements CodeExecutor {
508
508
  }): Promise<CodeExecutionResult>;
509
509
  }
510
510
 
511
+ /**
512
+ * Public types for the Workers-for-Platforms + D1 tenant provisioner.
513
+ *
514
+ * Wire-up shape — the operator constructs a provisioner once at control-plane
515
+ * boot and passes its `onProvision` / `onDeprovision` to
516
+ * `@authhero/multi-tenancy`'s `databaseIsolation` config. Every tenant
517
+ * create/delete in the management API then drives a CF API call sequence
518
+ * that:
519
+ * 1. (create) provisions a per-tenant D1, applies migrations, deploys a
520
+ * namespaced worker bound to that D1, and uploads secrets to it.
521
+ * 2. (delete) removes the namespaced worker and its D1.
522
+ *
523
+ * Failures throw — the multi-tenancy hook wraps `onProvision` such that a
524
+ * thrown error rolls the tenant row back. Idempotency on retry is best-effort:
525
+ * each step checks for "already exists" and continues, but the operator
526
+ * should treat the provisioning sequence as restartable rather than
527
+ * transactional.
528
+ */
529
+ /**
530
+ * SQL migration to run on every new tenant D1, in order.
531
+ *
532
+ * Operators bundle these from `@authhero/drizzle`'s `drizzle/sqlite/` files
533
+ * via their build tool of choice (vite's `?raw`, esbuild loader, webpack's
534
+ * raw-loader, etc.) — the provisioner is agnostic about how the SQL gets
535
+ * loaded.
536
+ */
537
+ interface ProvisionerMigration {
538
+ /** Filename, used only for error messages and audit logging. */
539
+ name: string;
540
+ /** Full SQL text of the migration. May contain multiple statements. */
541
+ sql: string;
542
+ }
543
+ /**
544
+ * Resolver that returns the secret values to upload onto a newly-provisioned
545
+ * tenant worker. Called once per tenant during `onProvision`.
546
+ *
547
+ * Implementations typically pull from a secret store (Vault, GCP Secret
548
+ * Manager, etc.) — the values must match the control-plane authhero's
549
+ * expectations for that tenant (notably `ENCRYPTION_KEY` and JWT signing
550
+ * material, which must be byte-stable to keep encrypted-at-rest data and
551
+ * issued JWTs valid).
552
+ */
553
+ type TenantSecretsResolver = (tenantId: string) => Promise<Record<string, string>>;
554
+ interface CloudflareWfpD1ProvisionerOptions {
555
+ /** Cloudflare account id that owns the namespace, D1s, and tenant workers. */
556
+ accountId: string;
557
+ /**
558
+ * API token with at least these permissions on `accountId`:
559
+ * - Workers Scripts:Edit
560
+ * - D1:Edit
561
+ * - Workers for Platforms:Edit (for namespace ops)
562
+ */
563
+ apiToken: string;
564
+ /** Name of the dispatch namespace tenant workers are deployed into. */
565
+ dispatchNamespace: string;
566
+ /**
567
+ * Base URL of the control-plane authhero. Passed to the tenant worker via
568
+ * the `CONTROL_PLANE_BASE_URL` env var so its `controlPlaneSync` destination
569
+ * knows where to POST `controlplane.sync.*` events.
570
+ */
571
+ controlPlaneBaseUrl: string;
572
+ /**
573
+ * Full JavaScript bundle of the tenant worker. The operator builds this
574
+ * (typically via esbuild/vite of a thin wrapper that calls
575
+ * `authhero.init({ ... })`), and passes the resulting JS string here.
576
+ *
577
+ * The bundle MUST be self-contained — Cloudflare's script upload doesn't
578
+ * resolve npm dependencies. Use your bundler's `format: 'esm'` + `external`
579
+ * lists to inline `authhero`, `@authhero/drizzle`, and friends.
580
+ */
581
+ tenantWorkerScript: string;
582
+ /**
583
+ * Optional script metadata override. Defaults to
584
+ * `{ main_module: "index.js", compatibility_date, compatibility_flags: ["nodejs_compat"] }`.
585
+ * Set `compatibility_date` to the same date the rest of your workers use.
586
+ */
587
+ scriptMetadata?: {
588
+ main_module?: string;
589
+ compatibility_date?: string;
590
+ compatibility_flags?: string[];
591
+ };
592
+ /**
593
+ * SQL migrations applied in array order to every new tenant D1. Typically
594
+ * loaded from `@authhero/drizzle`'s shipped migrations via your build tool.
595
+ */
596
+ migrations: ProvisionerMigration[];
597
+ /**
598
+ * Resolver that returns the secret values to set on the tenant worker.
599
+ * Called once per `onProvision`. The provisioner uploads each entry via
600
+ * the per-script secrets API.
601
+ */
602
+ secrets: TenantSecretsResolver;
603
+ /**
604
+ * Naming convention for the namespaced script. Supports `{tenant_id}`
605
+ * placeholder. Defaults to `"{tenant_id}"`.
606
+ *
607
+ * Must match whatever the dispatcher synthesizes as `script_name` in its
608
+ * `dispatch_namespace` handler — otherwise the dispatcher can't reach the
609
+ * worker after provisioning.
610
+ */
611
+ scriptNameTemplate?: string;
612
+ /**
613
+ * Naming convention for the per-tenant D1. Supports `{tenant_id}`.
614
+ * Defaults to `"tenant-{tenant_id}"`. CF accepts most ASCII names; keep
615
+ * it stable so a re-provision finds the existing D1.
616
+ */
617
+ d1NameTemplate?: string;
618
+ /**
619
+ * Fetch override (tests only). Defaults to global `fetch`.
620
+ */
621
+ fetch?: typeof fetch;
622
+ /**
623
+ * Per-request timeout (ms) on the CF API. Defaults to 30s. Individual
624
+ * D1 migrations can take a few seconds each; the upload of a multi-MB
625
+ * tenant bundle also takes a noticeable chunk of that.
626
+ */
627
+ timeoutMs?: number;
628
+ }
629
+ /**
630
+ * Outcome of a successful `onProvision` — returned so the caller can persist
631
+ * the resource IDs back onto the tenant row (`tenants.d1_database_id`,
632
+ * `tenants.worker_script_name`). The control-plane authhero ships these
633
+ * fields in its schema; `createWfpTenantProvisioningHook` writes them
634
+ * automatically.
635
+ */
636
+ interface ProvisionResult {
637
+ d1DatabaseId: string;
638
+ scriptName: string;
639
+ d1Name: string;
640
+ }
641
+ /**
642
+ * What `createCloudflareWfpD1Provisioner` returns — the two lifecycle
643
+ * callbacks that plug into `databaseIsolation` from `@authhero/multi-tenancy`
644
+ * via the `createWfpTenantProvisioningHook` wrapper (which handles
645
+ * deployment-type guarding and tenant-row writebacks).
646
+ */
647
+ interface CloudflareWfpD1Provisioner {
648
+ onProvision(tenantId: string): Promise<ProvisionResult>;
649
+ onDeprovision(tenantId: string): Promise<void>;
650
+ }
651
+
652
+ /**
653
+ * Construct the lifecycle hooks for provisioning + deprovisioning a tenant
654
+ * on Cloudflare Workers-for-Platforms backed by a per-tenant D1.
655
+ *
656
+ * Wiring on the control-plane authhero:
657
+ *
658
+ * ```ts
659
+ * import createAdapters from "@authhero/cloudflare-adapter";
660
+ * import { createCloudflareWfpD1Provisioner } from "@authhero/cloudflare-adapter";
661
+ * import { initMultiTenant } from "@authhero/multi-tenancy";
662
+ * import tenantWorkerScript from "./tenant-worker.dist.js?raw";
663
+ * import migration0001 from "@authhero/drizzle/drizzle/sqlite/0000_initial.sql?raw";
664
+ *
665
+ * const provisioner = createCloudflareWfpD1Provisioner({
666
+ * accountId: env.CLOUDFLARE_ACCOUNT_ID,
667
+ * apiToken: env.CLOUDFLARE_API_TOKEN,
668
+ * dispatchNamespace: "authhero-tenants",
669
+ * controlPlaneBaseUrl: env.PUBLIC_BASE_URL,
670
+ * tenantWorkerScript,
671
+ * migrations: [{ name: "0000_initial.sql", sql: migration0001 }],
672
+ * secrets: async (tenantId) => ({
673
+ * ENCRYPTION_KEY: env.SHARED_ENCRYPTION_KEY,
674
+ * ISSUER: `https://${tenantId}.tokens.example.com`,
675
+ * }),
676
+ * });
677
+ *
678
+ * const { app } = initMultiTenant({
679
+ * dataAdapter,
680
+ * controlPlane: { tenantId: "main", clientId: "platform" },
681
+ * databaseIsolation: {
682
+ * getAdapters: async (tenantId) => { ... }, // resolve per-tenant adapter
683
+ * onProvision: provisioner.onProvision,
684
+ * onDeprovision: provisioner.onDeprovision,
685
+ * },
686
+ * });
687
+ * ```
688
+ *
689
+ * On tenant create, the management API row write fires
690
+ * `databaseIsolation.onProvision(tenantId)` which runs the full sequence
691
+ * below. If any step throws, the upstream `createProvisioningHooks` rolls
692
+ * back the tenant row — though side effects already taken (D1 created,
693
+ * partial migrations applied) are NOT rolled back. The operator should
694
+ * treat re-running `onProvision(tenantId)` as safe; each step is idempotent
695
+ * on "already exists".
696
+ */
697
+ declare function createCloudflareWfpD1Provisioner(options: CloudflareWfpD1ProvisionerOptions): CloudflareWfpD1Provisioner;
698
+
699
+ /**
700
+ * Adapt the provisioner to `@authhero/multi-tenancy`'s
701
+ * `databaseIsolation.onProvision` / `onDeprovision` contract by:
702
+ *
703
+ * 1. Looking up the tenant row first and gating on
704
+ * `tenant.deployment_type === "wfp"` — shared tenants short-circuit so
705
+ * the same control plane can host both kinds without code branches.
706
+ * 2. Running the provisioner sequence (D1 + script + secrets).
707
+ * 3. Writing the resulting `d1_database_id` + `worker_script_name` +
708
+ * `provisioning_state` back onto the tenant row so the admin UI can
709
+ * show real status, and so a redeploy / re-provision knows which
710
+ * resource ids to operate on.
711
+ * 4. On failure, marking `provisioning_state = "failed"` with the error
712
+ * message, then re-throwing — the multi-tenancy hook treats the throw
713
+ * as a signal to roll back the tenant row.
714
+ *
715
+ * Typical wiring on the control-plane authhero:
716
+ *
717
+ * ```ts
718
+ * import { initMultiTenant } from "@authhero/multi-tenancy";
719
+ * import {
720
+ * createCloudflareWfpD1Provisioner,
721
+ * createWfpTenantProvisioningHook,
722
+ * } from "@authhero/cloudflare-adapter";
723
+ *
724
+ * const provisioner = createCloudflareWfpD1Provisioner({ ... });
725
+ * const hook = createWfpTenantProvisioningHook({
726
+ * provisioner,
727
+ * tenants: dataAdapter.tenants,
728
+ * });
729
+ *
730
+ * const { app } = initMultiTenant({
731
+ * dataAdapter,
732
+ * databaseIsolation: {
733
+ * getAdapters: async (tenantId) => { ... },
734
+ * onProvision: hook.onProvision,
735
+ * onDeprovision: hook.onDeprovision,
736
+ * },
737
+ * });
738
+ * ```
739
+ */
740
+ interface WfpTenantProvisioningHookOptions {
741
+ provisioner: CloudflareWfpD1Provisioner;
742
+ tenants: TenantsDataAdapter;
743
+ /**
744
+ * Optional override of "should this tenant be WFP-provisioned?". Defaults
745
+ * to `tenant.deployment_type === "wfp"`. Provide a custom predicate when
746
+ * the gating signal lives elsewhere (a feature flag, a config table, etc.).
747
+ */
748
+ shouldProvision?: (tenant: {
749
+ id: string;
750
+ deployment_type?: string;
751
+ storage_kind?: string;
752
+ }) => boolean;
753
+ /**
754
+ * Optional `console`-compatible logger for warnings emitted when the
755
+ * tenant row write-back fails after a successful provision. Defaults to
756
+ * a silent no-op so this module stays test-quiet.
757
+ */
758
+ logger?: Pick<Console, "warn">;
759
+ }
760
+ interface WfpTenantProvisioningHook {
761
+ onProvision(tenantId: string): Promise<void>;
762
+ onDeprovision(tenantId: string): Promise<void>;
763
+ }
764
+ declare function createWfpTenantProvisioningHook(options: WfpTenantProvisioningHookOptions): WfpTenantProvisioningHook;
765
+
766
+ /**
767
+ * Thin Cloudflare REST API client for the WFP+D1 provisioner.
768
+ *
769
+ * Each method maps 1:1 to a documented CF endpoint and returns the parsed
770
+ * response body. Errors surface as `CloudflareApiError` carrying the HTTP
771
+ * status, endpoint, and (when JSON) the CF error array — making them easy
772
+ * to log without re-fetching the response.
773
+ *
774
+ * Idempotency is the caller's responsibility — the provisioner sequences
775
+ * calls and tolerates "already exists" / "not found" depending on the
776
+ * operation (see `provisioner.ts`).
777
+ */
778
+ declare class CloudflareApiError extends Error {
779
+ readonly status: number;
780
+ readonly endpoint: string;
781
+ readonly errors: unknown[];
782
+ readonly body: string;
783
+ constructor(status: number, endpoint: string, body: string, errors?: unknown[]);
784
+ }
785
+ interface CfApiClientOptions {
786
+ accountId: string;
787
+ apiToken: string;
788
+ fetch?: typeof fetch;
789
+ timeoutMs?: number;
790
+ baseUrl?: string;
791
+ }
792
+ interface D1Database {
793
+ uuid: string;
794
+ name: string;
795
+ }
796
+ interface D1QueryResult {
797
+ success: boolean;
798
+ meta?: Record<string, unknown>;
799
+ results?: unknown[];
800
+ }
801
+ interface ScriptBinding {
802
+ type: "d1" | "plain_text" | "secret_text";
803
+ name: string;
804
+ id?: string;
805
+ text?: string;
806
+ }
807
+ interface ScriptUploadOptions {
808
+ /** Script source (JavaScript ES module). */
809
+ script: string;
810
+ /** Main module filename (must match part name in form data). */
811
+ mainModule: string;
812
+ /** Compatibility date, ISO yyyy-mm-dd. */
813
+ compatibilityDate: string;
814
+ /** Compatibility flags (e.g. `["nodejs_compat"]`). */
815
+ compatibilityFlags?: string[];
816
+ /** Bindings to attach (D1, plain_text, etc.). Secrets go via setSecret(). */
817
+ bindings?: ScriptBinding[];
818
+ /** Optional tags, stored on the script for operator-side lookup. */
819
+ tags?: string[];
820
+ }
821
+ declare class CloudflareApiClient {
822
+ private readonly accountId;
823
+ private readonly apiToken;
824
+ private readonly fetchImpl;
825
+ private readonly timeoutMs;
826
+ private readonly baseUrl;
827
+ constructor(options: CfApiClientOptions);
828
+ createD1Database(name: string): Promise<D1Database>;
829
+ listD1Databases(name?: string): Promise<D1Database[]>;
830
+ deleteD1Database(databaseId: string): Promise<void>;
831
+ /**
832
+ * Execute a single SQL statement (or batch of `;`-separated statements
833
+ * permitted by D1) against the given database. Use for applying
834
+ * migrations one file at a time — the per-call response size cap means
835
+ * very large single calls can fail; splitting per file keeps each call
836
+ * bounded by the migration author.
837
+ */
838
+ execD1(databaseId: string, sql: string): Promise<D1QueryResult[]>;
839
+ uploadNamespacedScript(namespace: string, scriptName: string, options: ScriptUploadOptions): Promise<void>;
840
+ deleteNamespacedScript(namespace: string, scriptName: string): Promise<void>;
841
+ /**
842
+ * Set a single secret on a namespaced script. The CF API replaces the
843
+ * value if a secret with that name already exists, so this is safely
844
+ * re-runnable.
845
+ */
846
+ setNamespacedScriptSecret(namespace: string, scriptName: string, secretName: string, secretValue: string): Promise<void>;
847
+ private request;
848
+ }
849
+
511
850
  interface CloudflareAdapters {
512
851
  customDomains: CustomDomainsAdapter;
513
852
  cache: CacheAdapter;
@@ -519,5 +858,5 @@ interface CloudflareAdapters {
519
858
  }
520
859
  declare function createAdapters(config: CloudflareConfig): CloudflareAdapters;
521
860
 
522
- export { CloudflareCodeExecutor, DispatchNamespaceCodeExecutor, WorkerLoaderCodeExecutor, createAnalyticsEngineActionExecutionsAdapter, createAnalyticsEngineAnalyticsAdapter, createAnalyticsEngineLogsAdapter, createAnalyticsEngineStatsAdapter, createCloudflareRateLimitAdapter, createR2SQLLogsAdapter, createR2SQLStatsAdapter, createAdapters as default, generateWorkerScript };
523
- export type { AnalyticsEngineActionExecutionsAdapterConfig, AnalyticsEngineDataset, AnalyticsEngineLogsAdapterConfig, CloudflareAdapters, CloudflareCodeExecutorConfig, CloudflareConfig, CloudflareRateLimitBinding, CloudflareRateLimitBindings, DispatchNamespace, DispatchNamespaceCodeExecutorConfig, R2SQLLogsAdapterConfig, WorkerCode, WorkerLoader, WorkerLoaderCodeExecutorOptions, WorkerStub };
861
+ export { CloudflareApiClient, CloudflareApiError, CloudflareCodeExecutor, DispatchNamespaceCodeExecutor, WorkerLoaderCodeExecutor, createAnalyticsEngineActionExecutionsAdapter, createAnalyticsEngineAnalyticsAdapter, createAnalyticsEngineLogsAdapter, createAnalyticsEngineStatsAdapter, createCloudflareRateLimitAdapter, createCloudflareWfpD1Provisioner, createR2SQLLogsAdapter, createR2SQLStatsAdapter, createWfpTenantProvisioningHook, createAdapters as default, generateWorkerScript };
862
+ export type { AnalyticsEngineActionExecutionsAdapterConfig, AnalyticsEngineDataset, AnalyticsEngineLogsAdapterConfig, CfApiClientOptions, CloudflareAdapters, CloudflareCodeExecutorConfig, CloudflareConfig, CloudflareRateLimitBinding, CloudflareRateLimitBindings, CloudflareWfpD1Provisioner, CloudflareWfpD1ProvisionerOptions, D1Database, D1QueryResult, DispatchNamespace, DispatchNamespaceCodeExecutorConfig, ProvisionResult, ProvisionerMigration, R2SQLLogsAdapterConfig, ScriptBinding, ScriptUploadOptions, TenantSecretsResolver, WfpTenantProvisioningHook, WfpTenantProvisioningHookOptions, WorkerCode, WorkerLoader, WorkerLoaderCodeExecutorOptions, WorkerStub };