@authhero/cloudflare-adapter 3.0.12 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/cloudflare-adapter.cjs +28 -28
- package/dist/cloudflare-adapter.d.ts +141 -2
- package/dist/cloudflare-adapter.mjs +282 -180
- package/dist/tsconfig.types.tsbuildinfo +1 -1
- package/dist/types/analytics-engine-outbox-metrics/index.d.ts +61 -0
- package/dist/types/customDomains/index.d.ts +24 -1
- package/dist/types/customDomains/sync.d.ts +78 -0
- package/dist/types/index.d.ts +4 -0
- package/package.json +5 -5
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AnalyticsEngineDataset } from "../analytics-engine-logs/types";
|
|
2
|
+
/**
|
|
3
|
+
* One metric emission from the authhero outbox relay.
|
|
4
|
+
*
|
|
5
|
+
* Structurally identical to authhero's `OutboxMetric`. It is redeclared here
|
|
6
|
+
* so this adapter keeps working without `authhero` installed — it is only an
|
|
7
|
+
* optional peer dependency of this package.
|
|
8
|
+
*/
|
|
9
|
+
export interface OutboxMetricRecord {
|
|
10
|
+
/**
|
|
11
|
+
* `outbox_events_processed_total`, `outbox_events_dead_lettered_total` or
|
|
12
|
+
* `outbox_retry_delay_seconds`.
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/** Counter increment, or the observed retry delay in seconds. */
|
|
16
|
+
value: number;
|
|
17
|
+
tenantId: string;
|
|
18
|
+
eventType: string;
|
|
19
|
+
source: "request" | "cron";
|
|
20
|
+
destination?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
retryCount?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface AnalyticsEngineOutboxMetricsConfig {
|
|
25
|
+
/**
|
|
26
|
+
* Cloudflare Analytics Engine dataset binding (e.g. `env.OUTBOX_METRICS`).
|
|
27
|
+
* When absent the sink is a no-op, so the same wiring works locally.
|
|
28
|
+
*/
|
|
29
|
+
analyticsEngineBinding?: AnalyticsEngineDataset;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create an Analytics Engine sink for outbox relay metrics.
|
|
33
|
+
*
|
|
34
|
+
* Pass the returned function to `init({ outbox: { metrics } })` and to
|
|
35
|
+
* `runOutboxRelay({ metrics })` so both the inline per-request relay and the
|
|
36
|
+
* cron drain report to the same dataset. Rows are indexed by tenant, matching
|
|
37
|
+
* `createAnalyticsEngineLogsAdapter`.
|
|
38
|
+
*
|
|
39
|
+
* Column layout:
|
|
40
|
+
* - blob1 `name`, blob2 `tenant_id`, blob3 `event_type`, blob4 `source`,
|
|
41
|
+
* blob5 `destination`, blob6 `error`
|
|
42
|
+
* - double1 `value`, double2 `retry_count`, double3 `timestamp` (ms)
|
|
43
|
+
* - index1 `tenant_id`
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // wrangler.toml:
|
|
48
|
+
* // [[analytics_engine_datasets]]
|
|
49
|
+
* // binding = "OUTBOX_METRICS"
|
|
50
|
+
* // dataset = "authhero_outbox_metrics"
|
|
51
|
+
*
|
|
52
|
+
* import { createAnalyticsEngineOutboxMetricsSink } from "@authhero/cloudflare-adapter";
|
|
53
|
+
*
|
|
54
|
+
* const metrics = createAnalyticsEngineOutboxMetricsSink({
|
|
55
|
+
* analyticsEngineBinding: env.OUTBOX_METRICS,
|
|
56
|
+
* });
|
|
57
|
+
*
|
|
58
|
+
* const app = init({ dataAdapter, outbox: { enabled: true, metrics } });
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function createAnalyticsEngineOutboxMetricsSink(config: AnalyticsEngineOutboxMetricsConfig): (metric: OutboxMetricRecord) => void;
|
|
@@ -1,3 +1,26 @@
|
|
|
1
|
-
import { CustomDomainsAdapter } from "@authhero/adapter-interfaces";
|
|
1
|
+
import { CustomDomain, CustomDomainsAdapter } from "@authhero/adapter-interfaces";
|
|
2
2
|
import { CloudflareConfig } from "../types/CloudflareConfig";
|
|
3
|
+
import { CustomDomainResult } from "../types/CustomDomain";
|
|
4
|
+
/** @internal Shared with the `syncCustomDomains` sweep. */
|
|
5
|
+
export declare function getClient(config: CloudflareConfig): import("wretch").Wretch<unknown, unknown, undefined, undefined, never>;
|
|
6
|
+
/**
|
|
7
|
+
* Merge a fresh Cloudflare hostname result onto the stored row and mirror the
|
|
8
|
+
* result back to the DB, so `list()` and `getByDomain()` — which never call
|
|
9
|
+
* Cloudflare — converge on it.
|
|
10
|
+
*
|
|
11
|
+
* Shared by `get()` and `syncCustomDomains()` so the interactive refresh and
|
|
12
|
+
* the cron sweep can never drift apart on what a Cloudflare payload means.
|
|
13
|
+
*
|
|
14
|
+
* Never throws — both callers are refresh paths, and neither should fail
|
|
15
|
+
* because Cloudflare returned something surprising. Failure is reported in
|
|
16
|
+
* `outcome` rather than raised, and is kept distinct from `"unchanged"`: a
|
|
17
|
+
* sweep whose writebacks are all failing (a varchar overflow on `verification`
|
|
18
|
+
* is one we have actually seen) would otherwise be indistinguishable from a
|
|
19
|
+
* sweep that found nothing to do.
|
|
20
|
+
*/
|
|
21
|
+
export type RefreshOutcome = "unchanged" | "updated" | "failed";
|
|
22
|
+
export declare function refreshFromCloudflare(config: CloudflareConfig, tenant_id: string, stored: CustomDomain, result: CustomDomainResult): Promise<{
|
|
23
|
+
domain: CustomDomain;
|
|
24
|
+
outcome: RefreshOutcome;
|
|
25
|
+
}>;
|
|
3
26
|
export declare function createCustomDomainsAdapter(config: CloudflareConfig): CustomDomainsAdapter;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { CloudflareConfig } from "../types/CloudflareConfig";
|
|
2
|
+
export interface SyncCustomDomainsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Hostnames fetched per Cloudflare API call. Cloudflare accepts 5 to 1000
|
|
5
|
+
* (its own default is 20); values outside that are clamped rather than
|
|
6
|
+
* rejected, since a cron should not die on a config typo. Defaults to 50.
|
|
7
|
+
*/
|
|
8
|
+
perPage?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Safety stop, in pages. A zone larger than `perPage * maxPages` is swept
|
|
11
|
+
* partially rather than looping forever on a paginating API that never
|
|
12
|
+
* reports a short page. Default 200 (10 000 hostnames at the default size).
|
|
13
|
+
*/
|
|
14
|
+
maxPages?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SyncCustomDomainsResult {
|
|
17
|
+
/** Hostnames returned by Cloudflare across every page. */
|
|
18
|
+
scanned: number;
|
|
19
|
+
/** Hostnames that matched a stored custom-domain row. */
|
|
20
|
+
matched: number;
|
|
21
|
+
/** Rows whose `status` or `verification` actually changed. */
|
|
22
|
+
updated: number;
|
|
23
|
+
/**
|
|
24
|
+
* Hostnames in the zone with no stored row. Expected to be 0 in a zone
|
|
25
|
+
* AuthHero owns exclusively; a non-zero count means either a hostname
|
|
26
|
+
* registered outside AuthHero or a `create` that died between the Cloudflare
|
|
27
|
+
* call and the DB write.
|
|
28
|
+
*/
|
|
29
|
+
unknown: number;
|
|
30
|
+
/**
|
|
31
|
+
* Stored rows whose `custom_domain_id` no longer matches the hostname's id
|
|
32
|
+
* at the edge — the hostname was deleted and re-registered behind our back.
|
|
33
|
+
* Left untouched: adopting the new id would rewrite a primary key that
|
|
34
|
+
* `proxy_routes` and the KV host blobs point at.
|
|
35
|
+
*/
|
|
36
|
+
mismatched: number;
|
|
37
|
+
/**
|
|
38
|
+
* Hostnames that could not be reconciled — a throw, or a merge/writeback
|
|
39
|
+
* that failed without throwing. Each one is logged. A sweep reporting
|
|
40
|
+
* `updated: 0` is only healthy when this is 0 too.
|
|
41
|
+
*/
|
|
42
|
+
errors: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reconcile every custom hostname in the Cloudflare zone against the stored
|
|
46
|
+
* custom-domain rows.
|
|
47
|
+
*
|
|
48
|
+
* Without this, `status` and `verification` only ever refresh when someone
|
|
49
|
+
* reads a single domain by id (`get()`): `list()` and `getByDomain()` are
|
|
50
|
+
* deliberately DB-only so they stay fast and survive a Cloudflare outage. A
|
|
51
|
+
* hostname that finishes validation at the edge therefore stays `pending` in
|
|
52
|
+
* the database until a human happens to open its detail page. This closes that
|
|
53
|
+
* gap on a schedule.
|
|
54
|
+
*
|
|
55
|
+
* Enumerates Cloudflare-first — one paginated list call per 50 hostnames,
|
|
56
|
+
* rather than one request per stored domain — then resolves each hostname's
|
|
57
|
+
* tenant through `getByDomain`, which every adapter indexes because it is the
|
|
58
|
+
* request-routing path.
|
|
59
|
+
*
|
|
60
|
+
* One hostname's failure never aborts the sweep, and the result is returned
|
|
61
|
+
* rather than thrown: a cron that dies halfway leaves the rest of the zone
|
|
62
|
+
* stale until the next run.
|
|
63
|
+
*
|
|
64
|
+
* Deletion is deliberately out of scope. A hostname removed at the edge simply
|
|
65
|
+
* stops appearing in the listing, and "absent from a page I may have failed to
|
|
66
|
+
* fetch" is not evidence a domain is gone — removals go through `remove()`.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* export default {
|
|
71
|
+
* async scheduled(event, env) {
|
|
72
|
+
* const config = buildCloudflareConfig(env);
|
|
73
|
+
* console.log("custom domain sync", await syncCustomDomains(config));
|
|
74
|
+
* },
|
|
75
|
+
* };
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function syncCustomDomains(config: CloudflareConfig, options?: SyncCustomDomainsOptions): Promise<SyncCustomDomainsResult>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type { AnalyticsEngineActionExecutionsAdapterConfig };
|
|
|
10
10
|
export type { CloudflareRateLimitBinding, CloudflareRateLimitBindings };
|
|
11
11
|
export { createCloudflareRateLimitAdapter } from "./rate-limit";
|
|
12
12
|
export type { CloudflareConfig };
|
|
13
|
+
export { syncCustomDomains } from "./customDomains/sync";
|
|
14
|
+
export type { SyncCustomDomainsOptions, SyncCustomDomainsResult, } from "./customDomains/sync";
|
|
13
15
|
export { DispatchNamespaceCodeExecutor, type DispatchNamespaceCodeExecutorConfig, type DispatchNamespace, CloudflareCodeExecutor, type CloudflareCodeExecutorConfig, } from "./code-executor";
|
|
14
16
|
export { generateWorkerScript } from "./code-executor/worker-template";
|
|
15
17
|
export { WorkerLoaderCodeExecutor, type WorkerLoader, type WorkerCode, type WorkerStub, type WorkerLoaderCodeExecutorOptions, } from "./code-executor/worker-loader";
|
|
@@ -17,6 +19,8 @@ export { createAnalyticsEngineLogsAdapter } from "./analytics-engine-logs";
|
|
|
17
19
|
export { createAnalyticsEngineStatsAdapter } from "./analytics-engine-logs";
|
|
18
20
|
export { createAnalyticsEngineAnalyticsAdapter } from "./analytics-engine-logs";
|
|
19
21
|
export { createAnalyticsEngineActionExecutionsAdapter } from "./analytics-engine-action-executions";
|
|
22
|
+
export { createAnalyticsEngineOutboxMetricsSink } from "./analytics-engine-outbox-metrics";
|
|
23
|
+
export type { AnalyticsEngineOutboxMetricsConfig, OutboxMetricRecord, } from "./analytics-engine-outbox-metrics";
|
|
20
24
|
export { createR2SQLLogsAdapter } from "./r2-sql-logs";
|
|
21
25
|
export { createR2SQLStatsAdapter } from "./r2-sql-logs";
|
|
22
26
|
export { createCloudflareWfpD1Provisioner, createWfpProvisionerSteps, createWfpTenantProvisioningHook, createWfpForwardMiddleware, CloudflareApiClient, CloudflareApiError, } from "./wfp-provisioner";
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "https://github.com/markusahlstrand/authhero"
|
|
13
13
|
},
|
|
14
|
-
"version": "3.0
|
|
14
|
+
"version": "3.1.0",
|
|
15
15
|
"files": [
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"typescript": "^5.9.3",
|
|
48
48
|
"vite": "^8.0.14",
|
|
49
49
|
"vitest": "^4.1.7",
|
|
50
|
-
"@authhero/multi-tenancy": "15.0.
|
|
51
|
-
"authhero": "9.
|
|
50
|
+
"@authhero/multi-tenancy": "15.0.1",
|
|
51
|
+
"authhero": "9.10.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@authhero/multi-tenancy": "^14.0.0",
|
|
@@ -67,8 +67,8 @@
|
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"nanoid": "^5.1.11",
|
|
69
69
|
"wretch": "^3.0.8",
|
|
70
|
-
"@authhero/
|
|
71
|
-
"@authhero/adapter
|
|
70
|
+
"@authhero/adapter-interfaces": "4.12.0",
|
|
71
|
+
"@authhero/kysely-adapter": "12.6.2"
|
|
72
72
|
},
|
|
73
73
|
"license": "AGPL-3.0-only",
|
|
74
74
|
"scripts": {
|