@authhero/cloudflare-adapter 3.2.5 → 3.4.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, buildControlPlaneDefaultsPayload as t, withRuntimeFallback as n } from "@authhero/multi-tenancy";
2
- import { Hono as r } from "hono";
3
- import { SigningCertificateRenewalError as i, TENANT_URI_PREFIX as a, createEncryptedDataAdapterWithKeyRing as o, ensureSigningKey as s, init as c, loadEncryptionKey as l, renewSigningCertificates as u } 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 d = "tenant-{tenant_id}-auth", f = 3e4, p = "/internal/sync-defaults";
6
- function m(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 h(e) {
10
- let { dispatcher: n, scriptNameTemplate: r = d, internalSecret: i, controlPlaneTenantId: a, controlPlaneAdapters: o, entities: s, timeoutMs: c = f } = 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 t(o, a, s, e), u = m(r, e), d = new AbortController(), f = setTimeout(() => d.abort(), c), h;
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
- h = await n.get(u).fetch(`https://tenant.internal${p}`, {
14
+ p = await t.get(u).fetch(`https://tenant.internal${b}`, {
15
15
  method: "POST",
16
16
  headers: {
17
17
  "content-type": "application/json",
@@ -23,22 +23,152 @@ function h(e) {
23
23
  } finally {
24
24
  clearTimeout(f);
25
25
  }
26
+ if (!p.ok) {
27
+ let e = await p.text().catch(() => ""), t = p.headers.get("x-authhero-error");
28
+ throw Error(`sync-defaults push to "${u}" failed: ${p.status}${t ? ` (${t})` : ""} ${e.slice(0, 256)}`);
29
+ }
30
+ return await p.json();
31
+ };
32
+ }
33
+ //#endregion
34
+ //#region src/wfp/dispatch-sync-config.ts
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, {
41
+ legacyControlPlane: a,
42
+ maxDepth: s
43
+ }), p = x(n, e), m = new AbortController(), h = setTimeout(() => m.abort(), c), g;
44
+ try {
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}`, {
83
+ method: "POST",
84
+ headers: {
85
+ "content-type": "application/json",
86
+ authorization: `Bearer ${i}`
87
+ },
88
+ body: JSON.stringify({
89
+ tenantId: e,
90
+ dryRun: l,
91
+ clean: u
92
+ }),
93
+ signal: p.signal
94
+ });
95
+ } finally {
96
+ clearTimeout(m);
97
+ }
26
98
  if (!h.ok) {
27
99
  let e = await h.text().catch(() => ""), t = h.headers.get("x-authhero-error");
28
- throw Error(`sync-defaults push to "${u}" 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;
29
160
  }
30
- return await h.json();
31
161
  };
32
162
  }
33
163
  //#endregion
34
164
  //#region src/wfp/dispatch-renew-signing-certificates.ts
35
- var g = "/internal/renew-signing-certificates";
36
- function _(e) {
37
- let { dispatcher: t, scriptNameTemplate: n = d, internalSecret: r, timeoutMs: i = f } = 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;
38
168
  return async (e) => {
39
- let a = m(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
169
+ let a = x(n, e), o = new AbortController(), s = setTimeout(() => o.abort(), i);
40
170
  try {
41
- let e = await t.get(a).fetch(`https://tenant.internal${g}`, {
171
+ let e = await t.get(a).fetch(`https://tenant.internal${j}`, {
42
172
  method: "POST",
43
173
  headers: { authorization: `Bearer ${r}` },
44
174
  signal: o.signal
@@ -48,7 +178,7 @@ function _(e) {
48
178
  throw Error(`signing-certificate renewal for "${a}" failed: ${e.status}${n ? ` (${n})` : ""} ${t.slice(0, 512)}`);
49
179
  }
50
180
  let n = await e.json();
51
- if (!v(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`);
52
182
  return {
53
183
  renewed: n.renewed,
54
184
  notDue: n.notDue
@@ -58,13 +188,13 @@ function _(e) {
58
188
  }
59
189
  };
60
190
  }
61
- function v(e) {
191
+ function N(e) {
62
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";
63
193
  }
64
194
  //#endregion
65
195
  //#region src/wfp/tenant-app.ts
66
- var y = "cp", b = "/internal/sync-defaults", x = "/internal/renew-signing-certificates";
67
- function S(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) {
68
198
  console.error(`[wfp-tenant] ${t}:`, n);
69
199
  let i = n instanceof Error ? n.message : String(n);
70
200
  return e.header("X-Authhero-Error", t), e.json({
@@ -73,74 +203,164 @@ function S(e, t, n, r) {
73
203
  result: r
74
204
  }, 500);
75
205
  }
76
- function C(e) {
77
- let t = `${a}${e.tenantId}`;
206
+ function V(e) {
207
+ let t = `${f}${e.tenantId}`;
78
208
  return {
79
209
  issuer: e.issuer,
80
210
  validityDays: e.validityDays,
81
211
  subjectUri: () => t
82
212
  };
83
213
  }
84
- function w(e) {
214
+ function H(e) {
85
215
  return ({ tenant_id: t }) => t === e.CONTROL_PLANE_TENANT_ID ? [e.CONTROL_PLANE_ISSUER] : [];
86
216
  }
87
- async function T(t, a) {
88
- let d = t.CONTROL_PLANE_TENANT_ID, f = await l(t.ENCRYPTION_KEY), p = t.CONTROL_PLANE_ENCRYPTION_KEY ? {
89
- default: f,
90
- keys: { [y]: await l(t.CONTROL_PLANE_ENCRYPTION_KEY) }
91
- } : { default: f }, m = o(a.createDataAdapter(t), p, { resolveEncryptKeyId: (e) => e === d ? y : void 0 }), h = n(m, { controlPlaneTenantId: d }), g = a.signingCertificateAuthority?.(t), _ = g ? C(g) : void 0, v = {
92
- dataAdapter: h,
93
- ..._ ? { signingCertificateAuthority: _ } : {},
94
- additionalIssuers: (a.additionalIssuers ?? w)(t)
95
- }, T = a.configure ? a.configure(v, t) : v, E = T.signingCertificateAuthority, { app: D } = c(T), O = new r();
96
- return O.post(b, async (n) => {
97
- let r = t.WFP_INTERNAL_SYNC_SECRET, i = n.req.header("authorization");
98
- if (!r || i !== `Bearer ${r}`) return n.json({ error: "unauthorized" }, 401);
99
- 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;
100
230
  try {
101
- o = await n.req.json();
231
+ s = await t.req.json();
102
232
  } catch {
103
- return n.json({ error: "invalid JSON" }, 400);
233
+ return t.json({ error: "invalid JSON" }, 400);
104
234
  }
105
235
  let c;
106
236
  try {
107
- c = await e(o, m, d, { continueOnError: a.continueOnError ?? !0 });
237
+ c = await e(s, y, i, { continueOnError: r.continueOnError ?? !0 });
108
238
  } catch (e) {
109
- return S(n, "sync_defaults_apply_failed", e);
239
+ return B(t, "sync_defaults_apply_failed", e);
110
240
  }
111
241
  let l;
112
242
  try {
113
- l = { created: (await s(m.keys, {
114
- name: t.ISSUER,
115
- certificateAuthority: E
243
+ l = { created: (await m(y.keys, {
244
+ name: n.ISSUER,
245
+ certificateAuthority: T
116
246
  })).created };
117
247
  } catch (e) {
118
- return S(n, "sync_defaults_signing_key_failed", e, c);
248
+ return B(t, "sync_defaults_signing_key_failed", e, c);
119
249
  }
120
- if (a.onSyncResult) try {
121
- await a.onSyncResult(c, t);
250
+ if (r.onSyncResult) try {
251
+ await r.onSyncResult(c, n);
122
252
  } catch (e) {
123
- return S(n, "sync_defaults_on_result_failed", e, c);
253
+ return B(t, "sync_defaults_on_result_failed", e, c);
124
254
  }
125
- return n.json({
255
+ return t.json({
126
256
  ...c,
127
257
  signingKey: l
128
258
  });
129
- }), O.post(x, async (e) => {
130
- let n = t.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
131
- if (!n || r !== `Bearer ${n}`) return e.json({ error: "unauthorized" }, 401);
132
- if (!E) return e.json({ error: "no signing certificate authority" }, 404);
259
+ }), D.post(I, async (e) => {
260
+ let i = n.WFP_INTERNAL_SYNC_SECRET, a = e.req.header("authorization");
261
+ if (!i || a !== `Bearer ${i}`) return e.json({ error: "unauthorized" }, 401);
262
+ let o;
263
+ try {
264
+ o = await e.req.json();
265
+ } catch {
266
+ return e.json({ error: "invalid JSON" }, 400);
267
+ }
268
+ if (!o.tenantId || !o.resolved) return e.json({ error: "tenantId and resolved are required" }, 400);
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);
270
+ try {
271
+ let n = await t(o.resolved, v, o.tenantId, {
272
+ dryRun: o.dryRun === !0,
273
+ force: o.force === !0,
274
+ prune: o.prune === !0,
275
+ keyRing: f,
276
+ continueOnError: r.continueOnError ?? !0
277
+ });
278
+ return e.json(n);
279
+ } catch (t) {
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({
281
+ error: "sync_config_key_ring",
282
+ detail: t.message
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;
133
289
  try {
134
- let t = await u({
135
- dataAdapter: { keys: m.keys },
136
- certificateAuthority: E
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);
304
+ }
305
+ }), D.post(R, async (e) => {
306
+ let t = n.WFP_INTERNAL_SYNC_SECRET, r = e.req.header("authorization");
307
+ if (!t || r !== `Bearer ${t}`) return e.json({ error: "unauthorized" }, 401);
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);
318
+ try {
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
137
357
  });
138
358
  return e.json({
139
359
  renewed: t.renewed,
140
360
  notDue: t.notDue
141
361
  });
142
362
  } catch (t) {
143
- return t instanceof i ? (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({
144
364
  error: "signing_certificate_renewal_failed",
145
365
  renewed: t.result.renewed,
146
366
  notDue: t.result.notDue,
@@ -148,16 +368,38 @@ async function T(t, a) {
148
368
  kid: e,
149
369
  detail: t instanceof Error ? t.message : String(t)
150
370
  }))
151
- }, 500)) : S(e, "signing_certificate_renewal_failed", t);
371
+ }, 500)) : B(e, "signing_certificate_renewal_failed", t);
152
372
  }
153
- }), O.route("/", D), O;
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;
154
392
  }
155
- function E(e) {
393
+ var W = /* @__PURE__ */ new WeakMap();
394
+ function G(e) {
156
395
  let t = /* @__PURE__ */ new WeakMap();
157
396
  return { fetch(n, r, i) {
158
397
  let a = t.get(r);
159
- return a || (a = T(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
+ });
160
402
  } };
161
403
  }
162
404
  //#endregion
163
- export { _ as createDispatchRenewSigningCertificates, h as createDispatchSyncDefaults, E 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-Htp1NWTq.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=class extends Error{checks;constructor(e,t){super(e),this.name=`TenantProvisionVerificationError`,this.checks=t}};function s(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 c(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;`),c=s(a,`key_count`),l=s(a,`tenant_count`),u=[];if(c<n&&u.push(`expected at least ${n} signing key(s) in "keys", found ${c}`),l<1&&u.push(`tenant row "${i}" missing from "tenants"`),u.length>0)throw new o(`Tenant D1 verification failed for "${i}": ${u.join(`; `)}`,{keyCount:c,tenantRowCount:l})}}function l(e){let t=e instanceof Error?e.message:String(e??``);return/already exists|already been created|duplicate/i.test(t)}function u(e){return{engine:`cloudflare-workflows`,async start(n){if(n.kind!==`provision`)throw Error(`The Cloudflare Workflows executor does not support "${n.kind}" operations yet (phase 2 covers provision only)`);if(!n.tenant_id)throw Error(`provision 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(!l(e))throw e}}}}var d=`cloudflare-workflows`;async function f(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:d,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 p(t)}catch(n){u.errors++,e.logger?.warn(`Failed to reconcile tenant operation ${t.id}:`,n)}return u;async function f(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 p(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 f(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 f(o,e);return}if(l.status===`complete`){(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 f(o,`engine instance completed but the operation was never finalized and the tenant is not ready`));return}u.stillRunning++}}function p(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=d,exports.TenantProvisionVerificationError=o,exports.createCloudflareWorkflowsExecutor=u,exports.createProvisionVerifier=c,exports.createWfpWorkflowProvisioningHook=p,exports.reconcileTenantOperations=f,exports.runProvisionOperation=a;
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, 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;
@@ -133,8 +133,8 @@ interface TenantProvisionerSteps {
133
133
  interface TenantOperationWorkflowParams {
134
134
  operation_id: string;
135
135
  tenant_id: string;
136
- /** Widened beyond "provision" in later phases (upgrade, backup, …). */
137
- kind: "provision";
136
+ /** Widened beyond these in later phases (upgrade, backup, …). */
137
+ kind: "provision" | "sync-defaults" | "detach";
138
138
  }
139
139
  /**
140
140
  * Instance status as reported by the engine. The exact value set has
@@ -220,6 +220,63 @@ interface ProvisionOperationDeps {
220
220
  */
221
221
  declare function runProvisionOperation(deps: ProvisionOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
222
222
 
223
+ type SyncDefaultsStepName = "mark-running" | "sync-defaults" | "mark-succeeded" | "mark-failed";
224
+ interface SyncDefaultsOperationDeps {
225
+ /** Control-plane operation log stores. */
226
+ stores: TenantOperationStores;
227
+ /**
228
+ * How the tenant's resolved config reaches its database, for example
229
+ * `createDispatchSyncConfig(...)`.
230
+ */
231
+ syncConfig: SyncConfigFn;
232
+ /** Per-step overrides of the retry/timeout defaults. */
233
+ stepConfig?: Partial<Record<SyncDefaultsStepName, StepConfig>>;
234
+ }
235
+ /**
236
+ * Durable `sync-defaults` operation: sync one tenant's resolved parent-chain
237
+ * config into its database as a Workflows instance, writing to the control-plane
238
+ * operation log at every step boundary (the same shape as
239
+ * {@link runProvisionOperation}, and the same step logic as the inline
240
+ * executor). The operation's `input` (`dry_run`, `force`) is read from its row,
241
+ * so nothing but ids crosses the Workflows boundary.
242
+ *
243
+ * A dry run records its diff in the operation's `result` and writes nothing to
244
+ * the tenant. Any failure runs `mark-failed` (event + failed row) and rethrows
245
+ * so the instance ends `errored`; if even that dies, the reconciler copies the
246
+ * engine's terminal state into the row.
247
+ */
248
+ declare function runSyncDefaultsOperation(deps: SyncDefaultsOperationDeps, params: TenantOperationWorkflowParams, step: StepRunner): Promise<void>;
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
+
223
280
  interface ProvisionVerifierOptions {
224
281
  client: CloudflareApiClient;
225
282
  /** Minimum number of signing keys the tenant D1 must hold. Default 1. */
@@ -388,5 +445,5 @@ interface WfpWorkflowProvisioningHookOptions {
388
445
  */
389
446
  declare function createWfpWorkflowProvisioningHook(options: WfpWorkflowProvisioningHookOptions): WfpTenantProvisioningHook;
390
447
 
391
- export { TENANT_OPERATION_ENGINE, TenantProvisionVerificationError, createCloudflareWorkflowsExecutor, createProvisionVerifier, createWfpWorkflowProvisioningHook, reconcileTenantOperations, runProvisionOperation };
392
- export type { CloudflareWorkflowsExecutorOptions, ProvisionOperationDeps, ProvisionStepName, ProvisionVerifierOptions, ReconcileTenantOperationsOptions, ReconcileTenantOperationsResult, 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 };