@authhero/cloudflare-adapter 3.3.0 → 4.0.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/dist/wfp.mjs CHANGED
@@ -1,17 +1,17 @@
1
- import { applyControlPlaneDefaultsPayload as e, applyResolvedConfigBlock as t, buildControlPlaneDefaultsPayload as n, buildResolvedConfigBlock as r, withRuntimeFallback as i } from "@authhero/multi-tenancy";
2
- import { Hono as a } from "hono";
3
- import { SigningCertificateRenewalError as o, TENANT_URI_PREFIX as s, createEncryptedDataAdapterWithKeyRing as c, ensureSigningKey as l, init as u, loadEncryptionKey as d, renewSigningCertificates as f } from "authhero";
1
+ import { applyControlPlaneDefaultsPayload as e, applyResolvedConfigBlock as t, assertDetachEligible as n, buildControlPlaneDefaultsPayload as r, buildResolvedConfigBlock as i, createShadowReporter as a, detachTenantRows as o, emptyDetachResult as s, markTenantDetached as c, withRuntimeFallback as l } from "@authhero/multi-tenancy";
2
+ import { Hono as u } from "hono";
3
+ import { SigningCertificateRenewalError as d, TENANT_URI_PREFIX as f, createEncryptedDataAdapterWithKeyRing as p, ensureSigningKey as m, init as h, loadEncryptionKey as g, renewSigningCertificates as _ } from "authhero";
4
4
  //#region src/wfp/dispatch-sync-defaults.ts
5
- var p = "tenant-{tenant_id}-auth", m = 3e4, h = "/internal/sync-defaults";
6
- function g(e, t) {
5
+ var v = "tenant-{tenant_id}-auth", y = 3e4, b = "/internal/sync-defaults";
6
+ function x(e, t) {
7
7
  return e.replace(/\{tenant_id\}/g, t);
8
8
  }
9
- function _(e) {
10
- let { dispatcher: t, scriptNameTemplate: r = p, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, entities: s, timeoutMs: c = m } = e;
9
+ function S(e) {
10
+ let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, entities: s, timeoutMs: c = y } = e;
11
11
  return async (e) => {
12
- let l = await n(o, a, s, e), u = g(r, e), d = new AbortController(), f = setTimeout(() => d.abort(), c), p;
12
+ let l = await r(o, a, s, e), u = x(n, e), d = new AbortController(), f = setTimeout(() => d.abort(), c), p;
13
13
  try {
14
- p = await t.get(u).fetch(`https://tenant.internal${h}`, {
14
+ p = await t.get(u).fetch(`https://tenant.internal${b}`, {
15
15
  method: "POST",
16
16
  headers: {
17
17
  "content-type": "application/json",
@@ -32,16 +32,54 @@ function _(e) {
32
32
  }
33
33
  //#endregion
34
34
  //#region src/wfp/dispatch-sync-config.ts
35
- var v = "/internal/sync-config";
36
- function y(e) {
37
- let { dispatcher: t, scriptNameTemplate: n = p, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, maxDepth: s, timeoutMs: c = m } = e;
38
- return async (e, { dryRun: l, force: u }) => {
39
- let d = await r(o, e, {
35
+ var C = "/internal/sync-config";
36
+ function w(e) {
37
+ let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: r, controlPlaneTenantId: a, controlPlaneAdapters: o, maxDepth: s, timeoutMs: c = y } = e;
38
+ return async (e, { dryRun: l, force: u, prune: d }) => {
39
+ if ((await o.tenants.get(e))?.config_sync === "detached") throw Error(`Tenant "${e}" is detached from its parent: set config_sync back to "inherit-on-read" or "synced" before syncing it`);
40
+ let f = await i(o, e, {
40
41
  legacyControlPlane: a,
41
42
  maxDepth: s
42
- }), f = g(n, e), p = new AbortController(), m = setTimeout(() => p.abort(), c), h;
43
+ }), p = x(n, e), m = new AbortController(), h = setTimeout(() => m.abort(), c), g;
43
44
  try {
44
- h = await t.get(f).fetch(`https://tenant.internal${v}`, {
45
+ g = await t.get(p).fetch(`https://tenant.internal${C}`, {
46
+ method: "POST",
47
+ headers: {
48
+ "content-type": "application/json",
49
+ authorization: `Bearer ${r}`
50
+ },
51
+ body: JSON.stringify({
52
+ tenantId: e,
53
+ resolved: f,
54
+ dryRun: l,
55
+ force: u,
56
+ prune: d
57
+ }),
58
+ signal: m.signal
59
+ });
60
+ } finally {
61
+ clearTimeout(h);
62
+ }
63
+ if (!g.ok) {
64
+ let e = await g.text().catch(() => ""), t = g.headers.get("x-authhero-error");
65
+ throw Error(`sync-config push to "${p}" failed: ${g.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
66
+ }
67
+ return await g.json();
68
+ };
69
+ }
70
+ //#endregion
71
+ //#region src/wfp/dispatch-detach.ts
72
+ var T = "/internal/detach", E = 12e4;
73
+ function D(e) {
74
+ let { dispatcher: t, scriptNameTemplate: r = v, internalSecret: i, controlPlaneAdapters: a, timeoutMs: o = E } = e;
75
+ return async (e, { dryRun: l, clean: u }) => {
76
+ let d = await a.tenants.get(e);
77
+ if (!d) throw Error(`Tenant "${e}" not found`);
78
+ if (d.config_sync === "detached") return s("already-detached");
79
+ n(e, d);
80
+ let f = x(r, e), p = new AbortController(), m = setTimeout(() => p.abort(), o), h;
81
+ try {
82
+ h = await t.get(f).fetch(`https://tenant.internal${T}`, {
45
83
  method: "POST",
46
84
  headers: {
47
85
  "content-type": "application/json",
@@ -49,9 +87,8 @@ function y(e) {
49
87
  },
50
88
  body: JSON.stringify({
51
89
  tenantId: e,
52
- resolved: d,
53
90
  dryRun: l,
54
- force: u
91
+ clean: u
55
92
  }),
56
93
  signal: p.signal
57
94
  });
@@ -60,20 +97,78 @@ function y(e) {
60
97
  }
61
98
  if (!h.ok) {
62
99
  let e = await h.text().catch(() => ""), t = h.headers.get("x-authhero-error");
63
- throw Error(`sync-config push to "${f}" failed: ${h.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
100
+ throw Error(`detach push to "${f}" failed: ${h.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
101
+ }
102
+ let g = await h.json();
103
+ return !l && g.status === "detached" && await c(a, e), g;
104
+ };
105
+ }
106
+ //#endregion
107
+ //#region src/wfp/dispatch-tenant-member-requests.ts
108
+ var O = "/internal/tenant-member-requests", k = 15e3;
109
+ function A(e) {
110
+ let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: r, timeoutMs: i = k } = e;
111
+ async function a(e, a) {
112
+ let o = x(n, e), s = new AbortController(), c = setTimeout(() => s.abort(), i);
113
+ try {
114
+ return await t.get(o).fetch(`https://tenant.internal${O}`, {
115
+ method: "POST",
116
+ headers: {
117
+ "content-type": "application/json",
118
+ authorization: `Bearer ${r}`
119
+ },
120
+ body: JSON.stringify({
121
+ tenantId: e,
122
+ ...a
123
+ }),
124
+ signal: s.signal
125
+ });
126
+ } finally {
127
+ clearTimeout(c);
128
+ }
129
+ }
130
+ async function o(e, t) {
131
+ let n = await t.text().catch(() => ""), r = t.headers.get("x-authhero-error");
132
+ return /* @__PURE__ */ Error(`tenant member requests of "${e}" answered ${t.status}${r ? ` (${r})` : ""} ${n.slice(0, 256)}`);
133
+ }
134
+ return {
135
+ async get(e, t) {
136
+ let n = await a(e, {
137
+ action: "get",
138
+ id: t
139
+ });
140
+ if (n.status === 404) return null;
141
+ if (!n.ok) throw await o(e, n);
142
+ return await n.json();
143
+ },
144
+ async report(e, t, n) {
145
+ let r = await a(e, {
146
+ action: "report",
147
+ id: t,
148
+ outcome: n
149
+ });
150
+ if (!r.ok) throw await o(e, r);
151
+ return (await r.json()).updated;
152
+ },
153
+ async listPending(e, { olderThan: t }) {
154
+ let n = await a(e, {
155
+ action: "list_pending",
156
+ olderThan: t
157
+ });
158
+ if (!n.ok) throw await o(e, n);
159
+ return (await n.json()).requests;
64
160
  }
65
- return await h.json();
66
161
  };
67
162
  }
68
163
  //#endregion
69
164
  //#region src/wfp/dispatch-renew-signing-certificates.ts
70
- var b = "/internal/renew-signing-certificates";
71
- function x(e) {
72
- let { dispatcher: t, scriptNameTemplate: n = p, internalSecret: r, timeoutMs: i = m } = e;
165
+ var j = "/internal/renew-signing-certificates";
166
+ function M(e) {
167
+ let { dispatcher: t, scriptNameTemplate: n = v, internalSecret: r, timeoutMs: i = y } = e;
73
168
  return async (e) => {
74
- let a = g(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
169
+ let a = x(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
75
170
  try {
76
- let e = await t.get(a).fetch(`https://tenant.internal${b}`, {
171
+ let e = await t.get(a).fetch(`https://tenant.internal${j}`, {
77
172
  method: "POST",
78
173
  headers: { authorization: `Bearer ${r}` },
79
174
  signal: o.signal
@@ -83,7 +178,7 @@ function x(e) {
83
178
  throw Error(`signing-certificate renewal for "${a}" failed: ${e.status}${n ? ` (${n})` : ""} ${t.slice(0, 512)}`);
84
179
  }
85
180
  let n = await e.json();
86
- if (!S(n)) throw Error(`signing-certificate renewal for "${a}" returned an unexpected body`);
181
+ if (!N(n)) throw Error(`signing-certificate renewal for "${a}" returned an unexpected body`);
87
182
  return {
88
183
  renewed: n.renewed,
89
184
  notDue: n.notDue
@@ -93,13 +188,13 @@ function x(e) {
93
188
  }
94
189
  };
95
190
  }
96
- function S(e) {
191
+ function N(e) {
97
192
  return typeof e == "object" && !!e && "renewed" in e && "notDue" in e && Array.isArray(e.renewed) && e.renewed.every((e) => typeof e == "string") && typeof e.notDue == "number";
98
193
  }
99
194
  //#endregion
100
195
  //#region src/wfp/tenant-app.ts
101
- var C = "cp", w = "/internal/sync-defaults", T = "/internal/sync-config", E = "/internal/renew-signing-certificates";
102
- function D(e, t, n, r) {
196
+ var P = "cp", F = "/internal/sync-defaults", I = "/internal/sync-config", L = "/internal/detach", R = "/internal/tenant-member-requests", z = "/internal/renew-signing-certificates";
197
+ function B(e, t, n, r) {
103
198
  console.error(`[wfp-tenant] ${t}:`, n);
104
199
  let i = n instanceof Error ? n.message : String(n);
105
200
  return e.header("X-Authhero-Error", t), e.json({
@@ -108,60 +203,60 @@ function D(e, t, n, r) {
108
203
  result: r
109
204
  }, 500);
110
205
  }
111
- function O(e) {
112
- let t = `${s}${e.tenantId}`;
206
+ function V(e) {
207
+ let t = `${f}${e.tenantId}`;
113
208
  return {
114
209
  issuer: e.issuer,
115
210
  validityDays: e.validityDays,
116
211
  subjectUri: () => t
117
212
  };
118
213
  }
119
- function k(e) {
214
+ function H(e) {
120
215
  return ({ tenant_id: t }) => t === e.CONTROL_PLANE_TENANT_ID ? [e.CONTROL_PLANE_ISSUER] : [];
121
216
  }
122
- async function A(n, r) {
123
- let s = n.CONTROL_PLANE_TENANT_ID, p = await d(n.ENCRYPTION_KEY), m = n.CONTROL_PLANE_ENCRYPTION_KEY ? {
124
- default: p,
125
- keys: { [C]: await d(n.CONTROL_PLANE_ENCRYPTION_KEY) }
126
- } : { default: p }, h = r.createDataAdapter(n), g = c(h, m, { resolveEncryptKeyId: (e) => e === s ? C : void 0 }), _ = i(g, { controlPlaneTenantId: s }), v = r.signingCertificateAuthority?.(n), y = v ? O(v) : void 0, b = {
127
- dataAdapter: _,
128
- ...y ? { signingCertificateAuthority: y } : {},
129
- additionalIssuers: (r.additionalIssuers ?? k)(n)
130
- }, x = r.configure ? r.configure(b, n) : b, S = x.signingCertificateAuthority, { app: A } = u(x), j = new a();
131
- return j.post(w, async (t) => {
132
- let i = n.WFP_INTERNAL_SYNC_SECRET, a = t.req.header("authorization");
133
- if (!i || a !== `Bearer ${i}`) return t.json({ error: "unauthorized" }, 401);
134
- let o;
217
+ async function U(n, r) {
218
+ let i = n.CONTROL_PLANE_TENANT_ID, s = await g(n.ENCRYPTION_KEY), f = n.CONTROL_PLANE_ENCRYPTION_KEY ? {
219
+ default: s,
220
+ keys: { [P]: await g(n.CONTROL_PLANE_ENCRYPTION_KEY) }
221
+ } : { default: s }, v = r.createDataAdapter(n), y = p(v, f, { resolveEncryptKeyId: (e) => e === i ? P : void 0 }), b = l(y, { controlPlaneTenantId: i }), x = r.signingCertificateAuthority?.(n), S = x ? V(x) : void 0, C = {
222
+ dataAdapter: b,
223
+ ...S ? { signingCertificateAuthority: S } : {},
224
+ additionalIssuers: (r.additionalIssuers ?? H)(n)
225
+ }, w = r.configure ? r.configure(C, n) : C, T = w.signingCertificateAuthority, { app: E } = h(w), D = new u();
226
+ if (D.post(F, async (t) => {
227
+ let a = n.WFP_INTERNAL_SYNC_SECRET, o = t.req.header("authorization");
228
+ if (!a || o !== `Bearer ${a}`) return t.json({ error: "unauthorized" }, 401);
229
+ let s;
135
230
  try {
136
- o = await t.req.json();
231
+ s = await t.req.json();
137
232
  } catch {
138
233
  return t.json({ error: "invalid JSON" }, 400);
139
234
  }
140
235
  let c;
141
236
  try {
142
- c = await e(o, g, s, { continueOnError: r.continueOnError ?? !0 });
237
+ c = await e(s, y, i, { continueOnError: r.continueOnError ?? !0 });
143
238
  } catch (e) {
144
- return D(t, "sync_defaults_apply_failed", e);
239
+ return B(t, "sync_defaults_apply_failed", e);
145
240
  }
146
- let u;
241
+ let l;
147
242
  try {
148
- u = { created: (await l(g.keys, {
243
+ l = { created: (await m(y.keys, {
149
244
  name: n.ISSUER,
150
- certificateAuthority: S
245
+ certificateAuthority: T
151
246
  })).created };
152
247
  } catch (e) {
153
- return D(t, "sync_defaults_signing_key_failed", e, c);
248
+ return B(t, "sync_defaults_signing_key_failed", e, c);
154
249
  }
155
250
  if (r.onSyncResult) try {
156
251
  await r.onSyncResult(c, n);
157
252
  } catch (e) {
158
- return D(t, "sync_defaults_on_result_failed", e, c);
253
+ return B(t, "sync_defaults_on_result_failed", e, c);
159
254
  }
160
255
  return t.json({
161
256
  ...c,
162
- signingKey: u
257
+ signingKey: l
163
258
  });
164
- }), j.post(T, async (e) => {
259
+ }), D.post(I, async (e) => {
165
260
  let i = n.WFP_INTERNAL_SYNC_SECRET, a = e.req.header("authorization");
166
261
  if (!i || a !== `Bearer ${i}`) return e.json({ error: "unauthorized" }, 401);
167
262
  let o;
@@ -173,10 +268,11 @@ async function A(n, r) {
173
268
  if (!o.tenantId || !o.resolved) return e.json({ error: "tenantId and resolved are required" }, 400);
174
269
  if (n.TENANT_ID && o.tenantId !== n.TENANT_ID) return console.error(`[wfp-tenant] sync_config_tenant_mismatch: push for "${o.tenantId}" reached the worker of "${n.TENANT_ID}"`), e.header("X-Authhero-Error", "sync_config_tenant_mismatch"), e.json({ error: "sync_config_tenant_mismatch" }, 403);
175
270
  try {
176
- let n = await t(o.resolved, h, o.tenantId, {
271
+ let n = await t(o.resolved, v, o.tenantId, {
177
272
  dryRun: o.dryRun === !0,
178
273
  force: o.force === !0,
179
- keyRing: m,
274
+ prune: o.prune === !0,
275
+ keyRing: f,
180
276
  continueOnError: r.continueOnError ?? !0
181
277
  });
182
278
  return e.json(n);
@@ -184,23 +280,87 @@ async function A(n, r) {
184
280
  return t instanceof Error && /key ring cannot decrypt/.test(t.message) ? (console.error("[wfp-tenant] sync_config_key_ring:", t), e.header("X-Authhero-Error", "sync_config_key_ring"), e.json({
185
281
  error: "sync_config_key_ring",
186
282
  detail: t.message
187
- }, 422)) : D(e, "sync_config_apply_failed", t);
283
+ }, 422)) : B(e, "sync_config_apply_failed", t);
284
+ }
285
+ }), D.post(L, async (e) => {
286
+ let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
287
+ if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
288
+ let i;
289
+ try {
290
+ i = await e.req.json();
291
+ } catch {
292
+ return e.json({ error: "invalid JSON" }, 400);
293
+ }
294
+ if (!i.tenantId) return e.json({ error: "tenantId is required" }, 400);
295
+ if (n.TENANT_ID && i.tenantId !== n.TENANT_ID) return console.error(`[wfp-tenant] detach_tenant_mismatch: detach for "${i.tenantId}" reached the worker of "${n.TENANT_ID}"`), e.header("X-Authhero-Error", "detach_tenant_mismatch"), e.json({ error: "detach_tenant_mismatch" }, 403);
296
+ try {
297
+ let t = await o(v, i.tenantId, {
298
+ dryRun: i.dryRun === !0,
299
+ clean: i.clean === !0
300
+ });
301
+ return t.status === "detached" && await c(v, i.tenantId), e.json(t);
302
+ } catch (t) {
303
+ return B(e, "detach_failed", t);
188
304
  }
189
- }), j.post(E, async (e) => {
305
+ }), D.post(R, async (e) => {
190
306
  let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
191
307
  if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
192
- if (!S) return e.json({ error: "no signing certificate authority" }, 404);
308
+ let i;
309
+ try {
310
+ i = await e.req.json();
311
+ } catch {
312
+ return e.json({ error: "invalid JSON" }, 400);
313
+ }
314
+ if (!i.tenantId) return e.json({ error: "tenantId is required" }, 400);
315
+ if (n.TENANT_ID && i.tenantId !== n.TENANT_ID) return console.error(`[wfp-tenant] member_requests_tenant_mismatch: a request for "${i.tenantId}" reached the worker of "${n.TENANT_ID}"`), e.header("X-Authhero-Error", "member_requests_tenant_mismatch"), e.json({ error: "member_requests_tenant_mismatch" }, 403);
316
+ let a = v.tenantMemberRequests;
317
+ if (!a) return e.json({ error: "tenant member requests are not available" }, 501);
193
318
  try {
194
- let t = await f({
195
- dataAdapter: { keys: g.keys },
196
- certificateAuthority: S
319
+ switch (i.action) {
320
+ case "get": {
321
+ if (!i.id) return e.json({ error: "id is required" }, 400);
322
+ let t = await a.get(i.tenantId, i.id);
323
+ return t ? e.json(t) : e.json({ error: "not_found" }, 404);
324
+ }
325
+ case "report": {
326
+ let { status: t, result: n, error: r } = i.outcome ?? {};
327
+ if (!i.id || t !== "succeeded" && t !== "failed") return e.json({ error: "id and a final status are required" }, 400);
328
+ let o = await a.update(i.tenantId, i.id, {
329
+ status: t,
330
+ result: n ?? null,
331
+ error: r ?? null,
332
+ finished_at: (/* @__PURE__ */ new Date()).toISOString()
333
+ }, "pending");
334
+ return e.json({ updated: o });
335
+ }
336
+ case "list_pending": {
337
+ let t = await a.list(i.tenantId, {
338
+ status: "pending",
339
+ created_before: i.olderThan,
340
+ per_page: 100
341
+ });
342
+ return e.json({ requests: t.requests });
343
+ }
344
+ default: return e.json({ error: "unknown action" }, 400);
345
+ }
346
+ } catch (t) {
347
+ return B(e, "member_requests_failed", t);
348
+ }
349
+ }), D.post(z, async (e) => {
350
+ let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
351
+ if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
352
+ if (!T) return e.json({ error: "no signing certificate authority" }, 404);
353
+ try {
354
+ let t = await _({
355
+ dataAdapter: { keys: y.keys },
356
+ certificateAuthority: T
197
357
  });
198
358
  return e.json({
199
359
  renewed: t.renewed,
200
360
  notDue: t.notDue
201
361
  });
202
362
  } catch (t) {
203
- return t instanceof o ? (console.error("[wfp-tenant] signing_certificate_renewal_failed:", t), e.header("X-Authhero-Error", "signing_certificate_renewal_failed"), e.json({
363
+ return t instanceof d ? (console.error("[wfp-tenant] signing_certificate_renewal_failed:", t), e.header("X-Authhero-Error", "signing_certificate_renewal_failed"), e.json({
204
364
  error: "signing_certificate_renewal_failed",
205
365
  renewed: t.result.renewed,
206
366
  notDue: t.result.notDue,
@@ -208,16 +368,38 @@ async function A(n, r) {
208
368
  kid: e,
209
369
  detail: t instanceof Error ? t.message : String(t)
210
370
  }))
211
- }, 500)) : D(e, "signing_certificate_renewal_failed", t);
371
+ }, 500)) : B(e, "signing_certificate_renewal_failed", t);
212
372
  }
213
- }), j.route("/", A), j;
373
+ }), D.route("/", E), r.inheritanceShadow) {
374
+ let e = a(y, r.inheritanceShadow === !0 ? {} : r.inheritanceShadow), t = n.TENANT_ID || new URL(n.ISSUER).hostname.split(".")[0], o, s = async () => {
375
+ let e = await y.tenants.get(t);
376
+ for (let t = 0; t < 5; t++) {
377
+ let t = e?.parent_tenant_id;
378
+ if (!t || t === i) return !0;
379
+ if (e = await y.tenants.get(t), !e) return !1;
380
+ }
381
+ return !1;
382
+ };
383
+ W.set(D, (n) => {
384
+ if (!new URL(n.url).pathname.startsWith("/internal/") && t !== i) return (async () => {
385
+ if (!await (o ??= s())) return;
386
+ let n = e.schedule(t, i);
387
+ n && await n;
388
+ })();
389
+ });
390
+ }
391
+ return D;
214
392
  }
215
- function j(e) {
393
+ var W = /* @__PURE__ */ new WeakMap();
394
+ function G(e) {
216
395
  let t = /* @__PURE__ */ new WeakMap();
217
396
  return { fetch(n, r, i) {
218
397
  let a = t.get(r);
219
- return a || (a = A(r, e), t.set(r, a)), a.then((e) => e.fetch(n, r, i));
398
+ return a || (a = U(r, e), t.set(r, a)), a.then(async (e) => {
399
+ let t = await e.fetch(n, r, i), a = W.get(e)?.(n);
400
+ return a && i.waitUntil(a), t;
401
+ });
220
402
  } };
221
403
  }
222
404
  //#endregion
223
- export { x as createDispatchRenewSigningCertificates, y as createDispatchSyncConfig, _ as createDispatchSyncDefaults, j as createWfpTenantApp };
405
+ export { O as TENANT_MEMBER_REQUESTS_PATH, D as createDispatchDetach, M as createDispatchRenewSigningCertificates, w as createDispatchSyncConfig, S as createDispatchSyncDefaults, A as createDispatchTenantMemberRequestStore, G as createWfpTenantApp };
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./sync-defaults-errors-pnzkP88r.js");let t=require("@authhero/multi-tenancy");var n={retries:{limit:5,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`},r={retries:{limit:8,delay:`10 seconds`,backoff:`exponential`},timeout:`1 minute`};function i(e){return e instanceof Error?e.message:String(e)}async function a(a,o,s){let c=(0,t.createOperationRecorder)(a.stores),l=a.shouldProvision??(e=>e.deployment_type===`wfp`),u=e=>a.stepConfig?.[e]??(e===`verify`?r:n),d=o.operation_id,f=o.tenant_id,p=await s.do(`mark-running`,u(`mark-running`),async()=>{let e=await a.tenants.get(f);if(!e||!l(e))return await c.appendEvent(d,{step:`mark-running`,outcome:`skipped`,detail:{reason:e?`tenant is not WFP-provisioned`:`tenant row missing (rolled back?)`}}),await c.markSucceeded(d),{skipped:!0,scriptName:``,databaseName:``};let t=a.steps.validate(),n=a.steps.names(f);return await c.markRunning(d,`mark-running`),await c.appendEvent(d,{step:`mark-running`,outcome:`succeeded`}),{skipped:!1,...n,...t}});if(p.skipped)return;let m={worker_script_name:p.scriptName,bundle_configuration:p.bundleConfiguration,worker_version:p.workerVersion,database_version:p.databaseVersion};try{let t=await s.do(`create-database`,u(`create-database`),async()=>{await c.setCurrentStep(d,`create-database`);let{id:e,created:t}=await a.steps.findOrCreateDatabase(p.databaseName);return await c.appendEvent(d,{step:`create-database`,outcome:`succeeded`,detail:{database_id:e,created:t}}),{databaseId:e,created:t}});if(m.d1_database_id=t.databaseId,await s.do(`apply-migrations`,u(`apply-migrations`),async()=>{await c.setCurrentStep(d,`apply-migrations`),await a.steps.applyMigrations(t.databaseId,t.created),await c.appendEvent(d,{step:`apply-migrations`,outcome:`succeeded`,detail:{database_version:p.databaseVersion}})}),await s.do(`upload-script`,u(`upload-script`),async()=>{await c.setCurrentStep(d,`upload-script`),await a.steps.uploadScript(p.scriptName,t.databaseId),await c.appendEvent(d,{step:`upload-script`,outcome:`succeeded`,detail:{worker_version:p.workerVersion}})}),await s.do(`upload-secrets`,u(`upload-secrets`),async()=>{await c.setCurrentStep(d,`upload-secrets`),await a.steps.uploadSecrets(p.scriptName,f),await c.appendEvent(d,{step:`upload-secrets`,outcome:`succeeded`})}),a.syncDefaults){let t=a.syncDefaults;await s.do(`seed-defaults`,u(`seed-defaults`),async()=>{await c.setCurrentStep(d,`seed-defaults`);let n=e.t(await t(f));if(n.length>0)throw Error(`sync-defaults seed reported ${n.length} error(s): ${n.join(`; `)}`);await c.appendEvent(d,{step:`seed-defaults`,outcome:`succeeded`})})}if(a.verify){let e=a.verify;await s.do(`verify`,u(`verify`),async()=>{await c.setCurrentStep(d,`verify`),await e(t.databaseId,f),await c.appendEvent(d,{step:`verify`,outcome:`succeeded`})})}await s.do(`mark-ready`,u(`mark-ready`),async()=>{await c.setCurrentStep(d,`mark-ready`),await a.tenants.update(f,{...m,provisioning_state:`ready`,provisioning_error:void 0,provisioning_state_changed_at:new Date().toISOString()}),await c.appendEvent(d,{step:`mark-ready`,outcome:`succeeded`}),await c.markSucceeded(d)})}catch(e){throw await s.do(`mark-failed`,u(`mark-failed`),async()=>{let t=i(e);try{await a.tenants.update(f,{...m,provisioning_state:`failed`,provisioning_error:t.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}catch(e){a.logger?.warn(`Failed to write provisioning_state="failed" for tenant ${f}:`,e)}await c.appendEvent(d,{step:`mark-failed`,outcome:`failed`,detail:{message:t}}),await c.markFailed(d,e)}),e}}var o={retries:{limit:3,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`};function s(e){return e instanceof Error?e.message:String(e)}async function c(e,n,r){let i=(0,t.createOperationRecorder)(e.stores),a=t=>e.stepConfig?.[t]??o,c=n.operation_id;try{await r.do(`mark-running`,a(`mark-running`),async()=>{await i.markRunning(c,`sync-defaults`)}),await r.do(`sync-defaults`,a(`sync-defaults`),async()=>{await i.setCurrentStep(c,`sync-defaults`);let n=await e.stores.tenantOperations.get(c);if(!n)throw Error(`Operation "${c}" not found`);let r=await(0,t.runSyncDefaultsStep)({operation:n,syncConfig:e.syncConfig,recorder:i});await i.appendEvent(c,{step:`sync-defaults`,outcome:`succeeded`,detail:r})}),await r.do(`mark-succeeded`,a(`mark-succeeded`),async()=>{await i.markSucceeded(c)})}catch(e){throw await r.do(`mark-failed`,a(`mark-failed`),async()=>{await i.appendEvent(c,{step:`mark-failed`,outcome:`failed`,detail:{message:s(e)}}),await i.markFailed(c,e)}),e}}var l=class extends Error{checks;constructor(e,t){super(e),this.name=`TenantProvisionVerificationError`,this.checks=t}};function u(e,t){for(let n of e)for(let e of n.results??[])if(!(!e||typeof e!=`object`)){for(let[n,r]of Object.entries(e))if(n===t){if(typeof r==`number`)return r;if(typeof r==`string`&&r!==``&&!isNaN(Number(r)))return Number(r)}}return 0}function d(t){let n=t.minKeys??1;return async(r,i)=>{let a=await t.client.execD1(r,`SELECT (SELECT COUNT(*) FROM keys) AS key_count, (SELECT COUNT(*) FROM tenants WHERE id = '${e.r(i)}') AS tenant_count;`),o=u(a,`key_count`),s=u(a,`tenant_count`),c=[];if(o<n&&c.push(`expected at least ${n} signing key(s) in "keys", found ${o}`),s<1&&c.push(`tenant row "${i}" missing from "tenants"`),c.length>0)throw new l(`Tenant D1 verification failed for "${i}": ${c.join(`; `)}`,{keyCount:o,tenantRowCount:s})}}function f(e){let t=e instanceof Error?e.message:String(e??``);return/already exists|already been created|duplicate/i.test(t)}function p(e){return{engine:`cloudflare-workflows`,async start(n){if(n.kind!==`provision`&&n.kind!==`sync-defaults`)throw Error(`The Cloudflare Workflows executor does not support "${n.kind}" operations yet (it runs provision and sync-defaults)`);if(!n.tenant_id)throw Error(`${n.kind} operations require a tenant_id`);let r=n.engine_instance_id??(0,t.buildEngineInstanceId)(n);try{await e.binding.create({id:r,params:{operation_id:n.id,tenant_id:n.tenant_id,kind:n.kind}})}catch(e){if(!f(e))throw e}}}}var m=`cloudflare-workflows`;async function h(e){let{stores:n,tenants:r,binding:i}=e,a=(0,t.createOperationRecorder)(n),o=e.minAgeMs??600*1e3,s=e.limit??100,c=new Date(Date.now()-o).toISOString(),{operations:l}=await n.tenantOperations.list({status:[`pending`,`running`],engine:m,updated_before:c,per_page:s}),u={scanned:l.length,markedFailed:0,markedSucceeded:0,stillRunning:0,instanceMissing:0,errors:0};for(let t of l)try{await f(t)}catch(n){u.errors++,e.logger?.warn(`Failed to reconcile tenant operation ${t.id}:`,n)}return u;async function d(e,t){await a.appendEvent(e.id,{step:e.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`failed`,reason:t}}),await a.markFailed(e.id,t),e.kind===`provision`&&e.tenant_id&&(await r.get(e.tenant_id))?.provisioning_state===`pending`&&await r.update(e.tenant_id,{provisioning_state:`failed`,provisioning_error:t.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}async function f(e){let o=await n.tenantOperations.get(e.id);if(!o||(0,t.isTerminalStatus)(o.status))return;let s=o.engine_instance_id??(0,t.buildEngineInstanceId)(o),c;try{c=await i.get(s)}catch{u.instanceMissing++,u.markedFailed++,await d(o,`engine instance "${s}" not found (expired past retention or never started)`);return}let l=await c.status();if(l.status===`errored`||l.status===`terminated`){let e=typeof l.error==`string`?l.error:l.error?.message??`engine reported ${l.status}`;u.markedFailed++,await d(o,e);return}if(l.status===`complete`){if(o.kind===`sync-defaults`){u.markedSucceeded++,await a.appendEvent(o.id,{step:o.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`succeeded`}}),await a.markSucceeded(o.id);return}(o.tenant_id?await r.get(o.tenant_id):null)?.provisioning_state===`ready`?(u.markedSucceeded++,await a.appendEvent(o.id,{step:o.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`succeeded`}}),await a.markSucceeded(o.id)):(u.markedFailed++,await d(o,`engine instance completed but the operation was never finalized and the tenant is not ready`));return}u.stillRunning++}}function g(e){let t=e.shouldProvision??(e=>e.deployment_type===`wfp`);return{async onProvision(n,r){let i=await e.tenants.get(n);i&&t(i)&&await e.enqueueOperation({kind:`provision`,tenant_id:n})},async onUpgrade(t,n){await e.inline.onUpgrade(t,n)},async onDeprovision(t){await e.inline.onDeprovision(t)}}}exports.TENANT_OPERATION_ENGINE=m,exports.TenantProvisionVerificationError=l,exports.createCloudflareWorkflowsExecutor=p,exports.createProvisionVerifier=d,exports.createWfpWorkflowProvisioningHook=g,exports.reconcileTenantOperations=h,exports.runProvisionOperation=a,exports.runSyncDefaultsOperation=c;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./sync-defaults-errors-pnzkP88r.js");let t=require("@authhero/multi-tenancy");var n={retries:{limit:5,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`},r={retries:{limit:8,delay:`10 seconds`,backoff:`exponential`},timeout:`1 minute`};function i(e){return e instanceof Error?e.message:String(e)}async function a(a,o,s){let c=(0,t.createOperationRecorder)(a.stores),l=a.shouldProvision??(e=>e.deployment_type===`wfp`),u=e=>a.stepConfig?.[e]??(e===`verify`?r:n),d=o.operation_id,f=o.tenant_id,p=await s.do(`mark-running`,u(`mark-running`),async()=>{let e=await a.tenants.get(f);if(!e||!l(e))return await c.appendEvent(d,{step:`mark-running`,outcome:`skipped`,detail:{reason:e?`tenant is not WFP-provisioned`:`tenant row missing (rolled back?)`}}),await c.markSucceeded(d),{skipped:!0,scriptName:``,databaseName:``};let t=a.steps.validate(),n=a.steps.names(f);return await c.markRunning(d,`mark-running`),await c.appendEvent(d,{step:`mark-running`,outcome:`succeeded`}),{skipped:!1,...n,...t}});if(p.skipped)return;let m={worker_script_name:p.scriptName,bundle_configuration:p.bundleConfiguration,worker_version:p.workerVersion,database_version:p.databaseVersion};try{let t=await s.do(`create-database`,u(`create-database`),async()=>{await c.setCurrentStep(d,`create-database`);let{id:e,created:t}=await a.steps.findOrCreateDatabase(p.databaseName);return await c.appendEvent(d,{step:`create-database`,outcome:`succeeded`,detail:{database_id:e,created:t}}),{databaseId:e,created:t}});if(m.d1_database_id=t.databaseId,await s.do(`apply-migrations`,u(`apply-migrations`),async()=>{await c.setCurrentStep(d,`apply-migrations`),await a.steps.applyMigrations(t.databaseId,t.created),await c.appendEvent(d,{step:`apply-migrations`,outcome:`succeeded`,detail:{database_version:p.databaseVersion}})}),await s.do(`upload-script`,u(`upload-script`),async()=>{await c.setCurrentStep(d,`upload-script`),await a.steps.uploadScript(p.scriptName,t.databaseId),await c.appendEvent(d,{step:`upload-script`,outcome:`succeeded`,detail:{worker_version:p.workerVersion}})}),await s.do(`upload-secrets`,u(`upload-secrets`),async()=>{await c.setCurrentStep(d,`upload-secrets`),await a.steps.uploadSecrets(p.scriptName,f),await c.appendEvent(d,{step:`upload-secrets`,outcome:`succeeded`})}),a.syncDefaults){let t=a.syncDefaults;await s.do(`seed-defaults`,u(`seed-defaults`),async()=>{await c.setCurrentStep(d,`seed-defaults`);let n=e.t(await t(f));if(n.length>0)throw Error(`sync-defaults seed reported ${n.length} error(s): ${n.join(`; `)}`);await c.appendEvent(d,{step:`seed-defaults`,outcome:`succeeded`})})}if(a.verify){let e=a.verify;await s.do(`verify`,u(`verify`),async()=>{await c.setCurrentStep(d,`verify`),await e(t.databaseId,f),await c.appendEvent(d,{step:`verify`,outcome:`succeeded`})})}await s.do(`mark-ready`,u(`mark-ready`),async()=>{await c.setCurrentStep(d,`mark-ready`),await a.tenants.update(f,{...m,provisioning_state:`ready`,provisioning_error:void 0,provisioning_state_changed_at:new Date().toISOString()}),await c.appendEvent(d,{step:`mark-ready`,outcome:`succeeded`}),await c.markSucceeded(d)})}catch(e){throw await s.do(`mark-failed`,u(`mark-failed`),async()=>{let t=i(e);try{await a.tenants.update(f,{...m,provisioning_state:`failed`,provisioning_error:t.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}catch(e){a.logger?.warn(`Failed to write provisioning_state="failed" for tenant ${f}:`,e)}await c.appendEvent(d,{step:`mark-failed`,outcome:`failed`,detail:{message:t}}),await c.markFailed(d,e)}),e}}var o={retries:{limit:3,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`};function s(e){return e instanceof Error?e.message:String(e)}async function c(e,n,r){let i=(0,t.createOperationRecorder)(e.stores),a=t=>e.stepConfig?.[t]??o,c=n.operation_id;try{await r.do(`mark-running`,a(`mark-running`),async()=>{await i.markRunning(c,`sync-defaults`)}),await r.do(`sync-defaults`,a(`sync-defaults`),async()=>{await i.setCurrentStep(c,`sync-defaults`);let n=await e.stores.tenantOperations.get(c);if(!n)throw Error(`Operation "${c}" not found`);let r=await(0,t.runSyncDefaultsStep)({operation:n,syncConfig:e.syncConfig,recorder:i});await i.appendEvent(c,{step:`sync-defaults`,outcome:`succeeded`,detail:r})}),await r.do(`mark-succeeded`,a(`mark-succeeded`),async()=>{await i.markSucceeded(c)})}catch(e){throw await r.do(`mark-failed`,a(`mark-failed`),async()=>{await i.appendEvent(c,{step:`mark-failed`,outcome:`failed`,detail:{message:s(e)}}),await i.markFailed(c,e)}),e}}var l={retries:{limit:3,delay:`5 seconds`,backoff:`exponential`},timeout:`2 minutes`},u={retries:{limit:0,delay:`5 seconds`},timeout:`5 minutes`};function d(e){return e instanceof Error?e.message:String(e)}async function f(e,n,r){let i=(0,t.createOperationRecorder)(e.stores),a=t=>e.stepConfig?.[t]??(t===`detach`?u:l),o=n.operation_id;try{await r.do(`mark-running`,a(`mark-running`),async()=>{await i.markRunning(o,`detach`)}),await r.do(`detach`,a(`detach`),async()=>{await i.setCurrentStep(o,`detach`);let n=await e.stores.tenantOperations.get(o);if(!n)throw Error(`Operation "${o}" not found`);let r=await(0,t.runDetachStep)({operation:n,detachTenant:e.detachTenant,recorder:i});await i.appendEvent(o,{step:`detach`,outcome:`succeeded`,detail:r})}),await r.do(`mark-succeeded`,a(`mark-succeeded`),async()=>{await i.markSucceeded(o)})}catch(e){throw await r.do(`mark-failed`,a(`mark-failed`),async()=>{await i.appendEvent(o,{step:`mark-failed`,outcome:`failed`,detail:{message:d(e)}}),await i.markFailed(o,e)}),e}}var p=class extends Error{checks;constructor(e,t){super(e),this.name=`TenantProvisionVerificationError`,this.checks=t}};function m(e,t){for(let n of e)for(let e of n.results??[])if(!(!e||typeof e!=`object`)){for(let[n,r]of Object.entries(e))if(n===t){if(typeof r==`number`)return r;if(typeof r==`string`&&r!==``&&!isNaN(Number(r)))return Number(r)}}return 0}function h(t){let n=t.minKeys??1;return async(r,i)=>{let a=await t.client.execD1(r,`SELECT (SELECT COUNT(*) FROM keys) AS key_count, (SELECT COUNT(*) FROM tenants WHERE id = '${e.r(i)}') AS tenant_count;`),o=m(a,`key_count`),s=m(a,`tenant_count`),c=[];if(o<n&&c.push(`expected at least ${n} signing key(s) in "keys", found ${o}`),s<1&&c.push(`tenant row "${i}" missing from "tenants"`),c.length>0)throw new p(`Tenant D1 verification failed for "${i}": ${c.join(`; `)}`,{keyCount:o,tenantRowCount:s})}}function g(e){let t=e instanceof Error?e.message:String(e??``);return/already exists|already been created|duplicate/i.test(t)}function _(e){return{engine:`cloudflare-workflows`,async start(n){if(n.kind!==`provision`&&n.kind!==`sync-defaults`&&n.kind!==`detach`)throw Error(`The Cloudflare Workflows executor does not support "${n.kind}" operations yet (it runs provision, sync-defaults and detach)`);if(!n.tenant_id)throw Error(`${n.kind} operations require a tenant_id`);let r=n.engine_instance_id??(0,t.buildEngineInstanceId)(n);try{await e.binding.create({id:r,params:{operation_id:n.id,tenant_id:n.tenant_id,kind:n.kind}})}catch(e){if(!g(e))throw e}}}}var v=`cloudflare-workflows`;async function y(e){let{stores:n,tenants:r,binding:i}=e,a=(0,t.createOperationRecorder)(n),o=e.minAgeMs??600*1e3,s=e.limit??100,c=new Date(Date.now()-o).toISOString(),{operations:l}=await n.tenantOperations.list({status:[`pending`,`running`],engine:v,updated_before:c,per_page:s}),u={scanned:l.length,markedFailed:0,markedSucceeded:0,stillRunning:0,instanceMissing:0,errors:0};for(let t of l)try{await f(t)}catch(n){u.errors++,e.logger?.warn(`Failed to reconcile tenant operation ${t.id}:`,n)}return u;async function d(e,t){await a.appendEvent(e.id,{step:e.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`failed`,reason:t}}),await a.markFailed(e.id,t),e.kind===`provision`&&e.tenant_id&&(await r.get(e.tenant_id))?.provisioning_state===`pending`&&await r.update(e.tenant_id,{provisioning_state:`failed`,provisioning_error:t.slice(0,2048),provisioning_state_changed_at:new Date().toISOString()})}async function f(e){let o=await n.tenantOperations.get(e.id);if(!o||(0,t.isTerminalStatus)(o.status))return;let s=o.engine_instance_id??(0,t.buildEngineInstanceId)(o),c;try{c=await i.get(s)}catch{u.instanceMissing++,u.markedFailed++,await d(o,`engine instance "${s}" not found (expired past retention or never started)`);return}let l=await c.status();if(l.status===`errored`||l.status===`terminated`){let e=typeof l.error==`string`?l.error:l.error?.message??`engine reported ${l.status}`;u.markedFailed++,await d(o,e);return}if(l.status===`complete`){if(o.kind===`sync-defaults`||o.kind===`detach`){u.markedSucceeded++,await a.appendEvent(o.id,{step:o.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`succeeded`}}),await a.markSucceeded(o.id);return}(o.tenant_id?await r.get(o.tenant_id):null)?.provisioning_state===`ready`?(u.markedSucceeded++,await a.appendEvent(o.id,{step:o.current_step??`reconcile`,outcome:`reconciled`,detail:{resolution:`succeeded`}}),await a.markSucceeded(o.id)):(u.markedFailed++,await d(o,`engine instance completed but the operation was never finalized and the tenant is not ready`));return}u.stillRunning++}}function b(e){let t=e.shouldProvision??(e=>e.deployment_type===`wfp`);return{async onProvision(n,r){let i=await e.tenants.get(n);i&&t(i)&&await e.enqueueOperation({kind:`provision`,tenant_id:n})},async onUpgrade(t,n){await e.inline.onUpgrade(t,n)},async onDeprovision(t){await e.inline.onDeprovision(t)}}}exports.TENANT_OPERATION_ENGINE=v,exports.TenantProvisionVerificationError=p,exports.createCloudflareWorkflowsExecutor=_,exports.createProvisionVerifier=h,exports.createWfpWorkflowProvisioningHook=b,exports.reconcileTenantOperations=y,exports.runDetachOperation=f,exports.runProvisionOperation=a,exports.runSyncDefaultsOperation=c;
@@ -1,5 +1,5 @@
1
1
  import { TenantsDataAdapter, TenantOperation } from '@authhero/adapter-interfaces';
2
- import { TenantOperationStores, StepConfig, StepRunner, SyncConfigFn, TenantOperationExecutor } from '@authhero/multi-tenancy';
2
+ import { TenantOperationStores, StepConfig, StepRunner, SyncConfigFn, DetachConfigFn, TenantOperationExecutor } from '@authhero/multi-tenancy';
3
3
 
4
4
  interface CfApiClientOptions {
5
5
  accountId: string;
@@ -134,7 +134,7 @@ interface TenantOperationWorkflowParams {
134
134
  operation_id: string;
135
135
  tenant_id: string;
136
136
  /** Widened beyond these in later phases (upgrade, backup, …). */
137
- kind: "provision" | "sync-defaults";
137
+ kind: "provision" | "sync-defaults" | "detach";
138
138
  }
139
139
  /**
140
140
  * Instance status as reported by the engine. The exact value set has
@@ -247,6 +247,36 @@ interface SyncDefaultsOperationDeps {
247
247
  */
248
248
  declare function runSyncDefaultsOperation(deps: SyncDefaultsOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
249
249
 
250
+ type DetachStepName = "mark-running" | "detach" | "mark-succeeded" | "mark-failed";
251
+ interface DetachOperationDeps {
252
+ /** Control-plane operation log stores. */
253
+ stores: TenantOperationStores;
254
+ /**
255
+ * How the detach reaches the tenant's database, for example
256
+ * `createDispatchDetach(...)`.
257
+ */
258
+ detachTenant: DetachConfigFn;
259
+ /**
260
+ * Per-step overrides of the retry/timeout defaults. The `detach` step has no
261
+ * automatic retries unless a host sets them.
262
+ */
263
+ stepConfig?: Partial<Record<DetachStepName, StepConfig>>;
264
+ }
265
+ /**
266
+ * Durable `detach` operation: a synced tenant leaves its parent, as a Workflows
267
+ * instance that writes to the control-plane operation log at every step
268
+ * boundary (the same shape as {@link runSyncDefaultsOperation}, and the same
269
+ * step logic as the inline executor). The operation's `input` (`dry_run`,
270
+ * `clean`) is read from its row, so nothing but ids crosses the Workflows
271
+ * boundary.
272
+ *
273
+ * A dry run records what would change in the operation's `result` and writes
274
+ * nothing. Any failure runs `mark-failed` (event + failed row) and rethrows
275
+ * so the instance ends `errored`; if even that dies, the reconciler copies the
276
+ * engine's terminal state into the row.
277
+ */
278
+ declare function runDetachOperation(deps: DetachOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
279
+
250
280
  interface ProvisionVerifierOptions {
251
281
  client: CloudflareApiClient;
252
282
  /** Minimum number of signing keys the tenant D1 must hold. Default 1. */
@@ -415,5 +445,5 @@ interface WfpWorkflowProvisioningHookOptions {
415
445
  */
416
446
  declare function createWfpWorkflowProvisioningHook(options: WfpWorkflowProvisioningHookOptions): WfpTenantProvisioningHook;
417
447
 
418
- export { TENANT_OPERATION_ENGINE, TenantProvisionVerificationError, createCloudflareWorkflowsExecutor, createProvisionVerifier, createWfpWorkflowProvisioningHook, reconcileTenantOperations, runProvisionOperation, runSyncDefaultsOperation };
419
- export type { CloudflareWorkflowsExecutorOptions, ProvisionOperationDeps, ProvisionStepName, ProvisionVerifierOptions, ReconcileTenantOperationsOptions, ReconcileTenantOperationsResult, SyncDefaultsOperationDeps, SyncDefaultsStepName, TenantOperationWorkflowParams, WfpWorkflowProvisioningHookOptions, WorkflowInstanceHandle, WorkflowInstanceStatus, WorkflowsBinding };
448
+ export { TENANT_OPERATION_ENGINE, TenantProvisionVerificationError, createCloudflareWorkflowsExecutor, createProvisionVerifier, createWfpWorkflowProvisioningHook, reconcileTenantOperations, runDetachOperation, runProvisionOperation, runSyncDefaultsOperation };
449
+ export type { CloudflareWorkflowsExecutorOptions, DetachOperationDeps, DetachStepName, ProvisionOperationDeps, ProvisionStepName, ProvisionVerifierOptions, ReconcileTenantOperationsOptions, ReconcileTenantOperationsResult, SyncDefaultsOperationDeps, SyncDefaultsStepName, TenantOperationWorkflowParams, WfpWorkflowProvisioningHookOptions, WorkflowInstanceHandle, WorkflowInstanceStatus, WorkflowsBinding };