@fabricorg/experience-runtime 0.4.8 → 0.4.10

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/offline.cjs CHANGED
@@ -1,293 +1,330 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // offline.ts
21
- var offline_exports = {};
22
- __export(offline_exports, {
23
- createMemoryOfflineCaptureStore: () => createMemoryOfflineCaptureStore,
24
- createOfflineCaptureQueue: () => createOfflineCaptureQueue
25
- });
26
- module.exports = __toCommonJS(offline_exports);
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region offline.ts
27
3
  function createOfflineCaptureQueue(options) {
28
- if (options.store.protection !== "encrypted-at-rest" && !options.allowEphemeralStore) {
29
- throw new Error("Production offline capture requires an encrypted-at-rest store.");
30
- }
31
- if (!options.gateway.replay) throw new Error("Offline capture requires a gateway replay adapter.");
32
- const fragments = indexFragments(options.plan.root);
33
- const now = options.now ?? (() => /* @__PURE__ */ new Date());
34
- return {
35
- async capture(fragmentId, eventName, parameters, idempotencyKey) {
36
- const fragment = fragments.get(fragmentId);
37
- if (!fragment?.offlineEvents?.includes(eventName)) {
38
- throw new Error(`Fragment event "${fragmentId}.${eventName}" is not offline-capturable.`);
39
- }
40
- if (!idempotencyKey || idempotencyKey.length > 256) throw new Error("Offline capture requires a bounded stable idempotency key.");
41
- const capturedAt = now();
42
- const captureExpiry = capturedAt.getTime() + (options.captureTtlMs ?? 864e5);
43
- const planExpiry = Date.parse(options.plan.expiresAt);
44
- if (!Number.isFinite(planExpiry) || planExpiry <= capturedAt.getTime()) throw new Error("Offline capture requires an unexpired render plan.");
45
- const safeParameters = cloneOfflineParameters(parameters);
46
- const captureId = options.nextCaptureId();
47
- if (!captureId.trim()) throw new Error("Offline capture id is required.");
48
- const record = {
49
- captureId,
50
- planId: options.plan.planId,
51
- releaseDigest: options.plan.releaseDigest,
52
- fragmentId,
53
- eventName,
54
- parameters: safeParameters,
55
- idempotencyKey,
56
- capturedAt: capturedAt.toISOString(),
57
- expiresAt: new Date(Math.min(captureExpiry, planExpiry)).toISOString()
58
- };
59
- await options.store.put(record);
60
- return record;
61
- },
62
- async replay() {
63
- let completed = 0;
64
- let failed = 0;
65
- const parked = [];
66
- const expired = [];
67
- const reconciled = [];
68
- const storeFailures = [];
69
- const replayOne = async (record) => {
70
- if (Date.parse(record.expiresAt) <= now().getTime()) {
71
- await options.store.remove(record.captureId);
72
- expired.push(record);
73
- return;
74
- }
75
- const request = {
76
- planId: record.planId,
77
- fragmentId: record.fragmentId,
78
- eventName: record.eventName,
79
- parameters: record.parameters,
80
- idempotencyKey: record.idempotencyKey,
81
- executionReason: "offline_replay",
82
- capturedAt: record.capturedAt
83
- };
84
- let outcome;
85
- try {
86
- outcome = await options.gateway.replay(request);
87
- } catch (error) {
88
- const thrown = classifyThrownReplayFailure(error);
89
- if (thrown.kind === "transient") {
90
- failed += 1;
91
- return;
92
- }
93
- await options.store.remove(record.captureId);
94
- reconciled.push({
95
- record,
96
- status: thrown.status,
97
- ...thrown.actionInvocationId === void 0 ? {} : { actionInvocationId: thrown.actionInvocationId },
98
- certainty: thrown.certainty,
99
- reason: messageOf(error)
100
- });
101
- return;
102
- }
103
- const disposition = dispositionOf(outcome);
104
- if (disposition.kind === "not-delivered") {
105
- failed += 1;
106
- return;
107
- }
108
- await options.store.remove(record.captureId);
109
- const actionInvocationId = typeof outcome?.actionInvocationId === "string" ? outcome.actionInvocationId : void 0;
110
- if (disposition.kind === "attention") {
111
- reconciled.push({
112
- record,
113
- status: disposition.status,
114
- ...actionInvocationId === void 0 ? {} : { actionInvocationId },
115
- certainty: disposition.certainty,
116
- reason: disposition.reason
117
- });
118
- } else if (disposition.kind === "parked") {
119
- parked.push({ record, ...actionInvocationId === void 0 ? {} : { actionInvocationId } });
120
- } else {
121
- completed += 1;
122
- }
123
- };
124
- for (const record of await options.store.list()) {
125
- try {
126
- await replayOne(record);
127
- } catch (error) {
128
- storeFailures.push({ record, reason: messageOf(error) });
129
- }
130
- }
131
- return { completed, parked, failed, expired, reconciled, storeFailures };
132
- }
133
- };
4
+ if (options.store.protection !== "encrypted-at-rest" && !options.allowEphemeralStore) throw new Error("Production offline capture requires an encrypted-at-rest store.");
5
+ if (!options.gateway.replay) throw new Error("Offline capture requires a gateway replay adapter.");
6
+ const fragments = indexFragments(options.plan.root);
7
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
8
+ return {
9
+ async capture(fragmentId, eventName, parameters, idempotencyKey) {
10
+ if (!fragments.get(fragmentId)?.offlineEvents?.includes(eventName)) throw new Error(`Fragment event "${fragmentId}.${eventName}" is not offline-capturable.`);
11
+ if (!idempotencyKey || idempotencyKey.length > 256) throw new Error("Offline capture requires a bounded stable idempotency key.");
12
+ const capturedAt = now();
13
+ const captureExpiry = capturedAt.getTime() + (options.captureTtlMs ?? 864e5);
14
+ const planExpiry = Date.parse(options.plan.expiresAt);
15
+ if (!Number.isFinite(planExpiry) || planExpiry <= capturedAt.getTime()) throw new Error("Offline capture requires an unexpired render plan.");
16
+ const safeParameters = cloneOfflineParameters(parameters);
17
+ const captureId = options.nextCaptureId();
18
+ if (!captureId.trim()) throw new Error("Offline capture id is required.");
19
+ const record = {
20
+ captureId,
21
+ planId: options.plan.planId,
22
+ releaseDigest: options.plan.releaseDigest,
23
+ fragmentId,
24
+ eventName,
25
+ parameters: safeParameters,
26
+ idempotencyKey,
27
+ capturedAt: capturedAt.toISOString(),
28
+ expiresAt: new Date(Math.min(captureExpiry, planExpiry)).toISOString()
29
+ };
30
+ await options.store.put(record);
31
+ return record;
32
+ },
33
+ async replay() {
34
+ let completed = 0;
35
+ let failed = 0;
36
+ const parked = [];
37
+ const expired = [];
38
+ const reconciled = [];
39
+ const storeFailures = [];
40
+ const replayOne = async (record) => {
41
+ if (Date.parse(record.expiresAt) <= now().getTime()) {
42
+ await options.store.remove(record.captureId);
43
+ expired.push(record);
44
+ return;
45
+ }
46
+ const request = {
47
+ planId: record.planId,
48
+ fragmentId: record.fragmentId,
49
+ eventName: record.eventName,
50
+ parameters: record.parameters,
51
+ idempotencyKey: record.idempotencyKey,
52
+ executionReason: "offline_replay",
53
+ capturedAt: record.capturedAt
54
+ };
55
+ let outcome;
56
+ try {
57
+ outcome = await options.gateway.replay(request);
58
+ } catch (error) {
59
+ const thrown = classifyThrownReplayFailure(error);
60
+ if (thrown.kind === "transient") {
61
+ failed += 1;
62
+ return;
63
+ }
64
+ await options.store.remove(record.captureId);
65
+ reconciled.push({
66
+ record,
67
+ status: thrown.status,
68
+ ...thrown.actionInvocationId === void 0 ? {} : { actionInvocationId: thrown.actionInvocationId },
69
+ certainty: thrown.certainty,
70
+ reason: messageOf(error)
71
+ });
72
+ return;
73
+ }
74
+ const disposition = dispositionOf(outcome);
75
+ if (disposition.kind === "not-delivered") {
76
+ failed += 1;
77
+ return;
78
+ }
79
+ await options.store.remove(record.captureId);
80
+ const actionInvocationId = typeof outcome?.actionInvocationId === "string" ? outcome.actionInvocationId : void 0;
81
+ if (disposition.kind === "attention") reconciled.push({
82
+ record,
83
+ status: disposition.status,
84
+ ...actionInvocationId === void 0 ? {} : { actionInvocationId },
85
+ certainty: disposition.certainty,
86
+ reason: disposition.reason
87
+ });
88
+ else if (disposition.kind === "parked") parked.push({
89
+ record,
90
+ ...actionInvocationId === void 0 ? {} : { actionInvocationId }
91
+ });
92
+ else completed += 1;
93
+ };
94
+ for (const record of await options.store.list()) try {
95
+ await replayOne(record);
96
+ } catch (error) {
97
+ storeFailures.push({
98
+ record,
99
+ reason: messageOf(error)
100
+ });
101
+ }
102
+ return {
103
+ completed,
104
+ parked,
105
+ failed,
106
+ expired,
107
+ reconciled,
108
+ storeFailures
109
+ };
110
+ }
111
+ };
134
112
  }
135
113
  function cloneOfflineParameters(parameters) {
136
- let nodes = 0;
137
- const visit = (value, depth, key) => {
138
- nodes += 1;
139
- if (nodes > 1e4 || depth > 20) throw new Error("Offline capture parameters exceed structural limits.");
140
- if (key !== void 0 && /(?:authorization|bearer|cookie|credential|password|secret|token|claims?)/i.test(key)) {
141
- throw new Error(`Offline capture parameters may not contain credential or claim field "${key}".`);
142
- }
143
- if (value === null || typeof value === "string" || typeof value === "boolean") return;
144
- if (typeof value === "number" && Number.isFinite(value)) return;
145
- if (Array.isArray(value)) {
146
- value.forEach((entry) => visit(entry, depth + 1));
147
- return;
148
- }
149
- if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
150
- for (const [childKey, child] of Object.entries(value)) visit(child, depth + 1, childKey);
151
- return;
152
- }
153
- throw new Error("Offline capture parameters must contain only portable JSON values.");
154
- };
155
- visit(parameters, 0);
156
- const serialized = JSON.stringify(parameters);
157
- if (utf8ByteLength(serialized) > 65536) throw new Error("Offline capture parameters exceed the 65536-byte limit.");
158
- return JSON.parse(serialized);
114
+ let nodes = 0;
115
+ const visit = (value, depth, key) => {
116
+ nodes += 1;
117
+ if (nodes > 1e4 || depth > 20) throw new Error("Offline capture parameters exceed structural limits.");
118
+ if (key !== void 0 && /(?:authorization|bearer|cookie|credential|password|secret|token|claims?)/i.test(key)) throw new Error(`Offline capture parameters may not contain credential or claim field "${key}".`);
119
+ if (value === null || typeof value === "string" || typeof value === "boolean") return;
120
+ if (typeof value === "number" && Number.isFinite(value)) return;
121
+ if (Array.isArray(value)) {
122
+ value.forEach((entry) => visit(entry, depth + 1));
123
+ return;
124
+ }
125
+ if (value && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
126
+ for (const [childKey, child] of Object.entries(value)) visit(child, depth + 1, childKey);
127
+ return;
128
+ }
129
+ throw new Error("Offline capture parameters must contain only portable JSON values.");
130
+ };
131
+ visit(parameters, 0);
132
+ const serialized = JSON.stringify(parameters);
133
+ if (utf8ByteLength(serialized) > 65536) throw new Error("Offline capture parameters exceed the 65536-byte limit.");
134
+ return JSON.parse(serialized);
159
135
  }
136
+ /**
137
+ * UTF-8 length without an encoder global.
138
+ *
139
+ * This package is the interface a client uses to reach capabilities, so it
140
+ * assumes nothing about the host beyond JSON. A React Native runtime, a worker,
141
+ * or an older embedded engine may not supply the encoder or clone globals, and
142
+ * the parameters were validated as portable JSON just above, so a JSON round
143
+ * trip is the clone and a code-point walk is the byte count.
144
+ */
160
145
  function utf8ByteLength(text) {
161
- let bytes = 0;
162
- for (const character of text) {
163
- const codePoint = character.codePointAt(0);
164
- bytes += codePoint < 128 ? 1 : codePoint < 2048 ? 2 : codePoint < 65536 ? 3 : 4;
165
- }
166
- return bytes;
146
+ let bytes = 0;
147
+ for (const character of text) {
148
+ const codePoint = character.codePointAt(0);
149
+ bytes += codePoint < 128 ? 1 : codePoint < 2048 ? 2 : codePoint < 65536 ? 3 : 4;
150
+ }
151
+ return bytes;
167
152
  }
153
+ /** Records are validated JSON, so a round trip is a faithful clone on any host. */
168
154
  function cloneRecord(value) {
169
- return JSON.parse(JSON.stringify(value));
155
+ return JSON.parse(JSON.stringify(value));
170
156
  }
171
157
  function indexFragments(root) {
172
- const result = /* @__PURE__ */ new Map();
173
- const visit = (fragment) => {
174
- result.set(fragment.id, fragment);
175
- for (const child of fragment.children ?? []) visit(child);
176
- };
177
- visit(root);
178
- return result;
158
+ const result = /* @__PURE__ */ new Map();
159
+ const visit = (fragment) => {
160
+ result.set(fragment.id, fragment);
161
+ for (const child of fragment.children ?? []) visit(child);
162
+ };
163
+ visit(root);
164
+ return result;
179
165
  }
180
166
  function createMemoryOfflineCaptureStore() {
181
- const records = /* @__PURE__ */ new Map();
182
- return {
183
- protection: "ephemeral",
184
- async put(record) {
185
- if (records.has(record.captureId)) throw new Error(`Offline capture "${record.captureId}" already exists.`);
186
- records.set(record.captureId, cloneRecord(record));
187
- },
188
- async list() {
189
- return [...records.values()].map((record) => cloneRecord(record));
190
- },
191
- async remove(captureId) {
192
- records.delete(captureId);
193
- }
194
- };
167
+ const records = /* @__PURE__ */ new Map();
168
+ return {
169
+ protection: "ephemeral",
170
+ async put(record) {
171
+ if (records.has(record.captureId)) throw new Error(`Offline capture "${record.captureId}" already exists.`);
172
+ records.set(record.captureId, cloneRecord(record));
173
+ },
174
+ async list() {
175
+ return [...records.values()].map((record) => cloneRecord(record));
176
+ },
177
+ async remove(captureId) {
178
+ records.delete(captureId);
179
+ }
180
+ };
195
181
  }
196
182
  function messageOf(error) {
197
- return error instanceof Error ? error.message : String(error);
183
+ return error instanceof Error ? error.message : String(error);
198
184
  }
185
+ /**
186
+ * Read a returned outcome across the platform's whole status set.
187
+ *
188
+ * Enumerating the statuses that looked like refusals left the rest falling
189
+ * through to "applied", which is how a validation failure got reported to an
190
+ * operator as success. The switch covers every `ActionStatus`, and the default
191
+ * arm surfaces anything else rather than counting it a success — the status
192
+ * arrives as a string over a transport, so this is a runtime guarantee and not
193
+ * a compile-time one.
194
+ *
195
+ * There is no "already applied" branch. A key that legitimately repeats returns
196
+ * the original invocation without complaint, and a genuine idempotency conflict
197
+ * means the key was reused for a materially different command, so that capture
198
+ * was never applied and must not be discarded.
199
+ */
199
200
  function dispositionOf(outcome) {
200
- if (!outcome || typeof outcome !== "object") {
201
- return { kind: "not-delivered" };
202
- }
203
- const status = typeof outcome.status === "string" ? outcome.status : "";
204
- const reason = outcome.reconciliation?.message ?? outcome.reconciliation?.kind ?? (typeof outcome.error === "string" ? outcome.error : status);
205
- switch (status) {
206
- case "pending":
207
- case "running":
208
- case "completed":
209
- return { kind: "delivered" };
210
- case "waiting_for_approval":
211
- return { kind: "parked" };
212
- case "reconciliation_required": {
213
- const recovery = outcome.reconciliation?.executionReason === "recovery";
214
- const unknown = outcome.adapterReconciliation !== void 0 || recovery;
215
- return { kind: "attention", status, certainty: unknown ? "unknown" : "refused", reason };
216
- }
217
- case "blocked_by_policy":
218
- case "validation_failed":
219
- return { kind: "attention", status, certainty: "unknown", reason };
220
- case "failed":
221
- return { kind: "attention", status, certainty: "unknown", reason };
222
- default:
223
- return { kind: "attention", status: status || "unknown", certainty: "unknown", reason };
224
- }
201
+ if (!outcome || typeof outcome !== "object") return { kind: "not-delivered" };
202
+ const status = typeof outcome.status === "string" ? outcome.status : "";
203
+ const reason = outcome.reconciliation?.message ?? outcome.reconciliation?.kind ?? (typeof outcome.error === "string" ? outcome.error : status);
204
+ switch (status) {
205
+ case "pending":
206
+ case "running":
207
+ case "completed": return { kind: "delivered" };
208
+ case "waiting_for_approval": return { kind: "parked" };
209
+ case "reconciliation_required": {
210
+ const recovery = outcome.reconciliation?.executionReason === "recovery";
211
+ return {
212
+ kind: "attention",
213
+ status,
214
+ certainty: outcome.adapterReconciliation !== void 0 || recovery ? "unknown" : "refused",
215
+ reason
216
+ };
217
+ }
218
+ case "blocked_by_policy":
219
+ case "validation_failed": return {
220
+ kind: "attention",
221
+ status,
222
+ certainty: "unknown",
223
+ reason
224
+ };
225
+ case "failed": return {
226
+ kind: "attention",
227
+ status,
228
+ certainty: "unknown",
229
+ reason
230
+ };
231
+ default: return {
232
+ kind: "attention",
233
+ status: status || "unknown",
234
+ certainty: "unknown",
235
+ reason
236
+ };
237
+ }
225
238
  }
226
- var CONFLICT_REASONS = /* @__PURE__ */ new Set(["actor", "authority_binding", "action_version", "parameters"]);
239
+ const CONFLICT_REASONS = /* @__PURE__ */ new Set([
240
+ "actor",
241
+ "authority_binding",
242
+ "action_version",
243
+ "parameters"
244
+ ]);
245
+ /**
246
+ * An idempotency conflict is settled — retrying yields the same conflict
247
+ * forever — but which way depends on *why* the key conflicted. `parameters`
248
+ * or `actor` means the key was reused for a materially different command, so
249
+ * this capture was never applied. `authority_binding` or `action_version`
250
+ * means the *original* invocation exists and may well have applied: the
251
+ * capture-authority adapter mints a fresh binding on every attempt, so a first
252
+ * attempt whose response was lost conflicts with itself on the second. Deleting
253
+ * that and calling it refused is exactly the wrong answer, so it is surfaced as
254
+ * unknown, with the invocation it conflicted with, since that is what
255
+ * `recordExternalReconciliation` needs.
256
+ *
257
+ * A conflict whose reasons cannot be read is still a conflict, so it is still
258
+ * settled; the previous version let it fall through to transient and retried
259
+ * it forever. Unreadable reasons resolve to `unknown`, never to `refused`.
260
+ */
227
261
  function classifyThrownReplayFailure(error) {
228
- const conflict = conflictOf(error);
229
- if (conflict) {
230
- const readable = conflict.reasons.size > 0 && [...conflict.reasons].every((reason) => CONFLICT_REASONS.has(reason));
231
- const neverApplied = readable && (conflict.reasons.has("parameters") || conflict.reasons.has("actor"));
232
- if (neverApplied) return { kind: "settled", status: "not_delivered", certainty: "refused" };
233
- return {
234
- kind: "settled",
235
- status: "conflict_prior_invocation",
236
- certainty: "unknown",
237
- ...conflict.existingInvocationId === void 0 ? {} : { actionInvocationId: conflict.existingInvocationId }
238
- };
239
- }
240
- return isSettledReplayFailure(error) ? { kind: "settled", status: "not_delivered", certainty: "refused" } : { kind: "transient" };
262
+ const conflict = conflictOf(error);
263
+ if (conflict) {
264
+ if (conflict.reasons.size > 0 && [...conflict.reasons].every((reason) => CONFLICT_REASONS.has(reason)) && (conflict.reasons.has("parameters") || conflict.reasons.has("actor"))) return {
265
+ kind: "settled",
266
+ status: "not_delivered",
267
+ certainty: "refused"
268
+ };
269
+ return {
270
+ kind: "settled",
271
+ status: "conflict_prior_invocation",
272
+ certainty: "unknown",
273
+ ...conflict.existingInvocationId === void 0 ? {} : { actionInvocationId: conflict.existingInvocationId }
274
+ };
275
+ }
276
+ return isSettledReplayFailure(error) ? {
277
+ kind: "settled",
278
+ status: "not_delivered",
279
+ certainty: "refused"
280
+ } : { kind: "transient" };
241
281
  }
282
+ /**
283
+ * Read an idempotency conflict off a thrown error.
284
+ *
285
+ * The host's `IdempotencyConflictError` carries the conflict as data, and a
286
+ * gateway that rethrows it (or any error shaped like it) is read structurally.
287
+ * Only when that is absent is the message parsed, and then defensively: the
288
+ * phrase is located from the *end*, because the idempotency key is interpolated
289
+ * before it and a key is whatever the application chose; the reasons are read
290
+ * to the end of their own line, because a wrapped error may append a stack or
291
+ * a detail line below. A message that names the phrase but cannot be parsed
292
+ * further is still reported as a conflict, with no reasons.
293
+ */
242
294
  function conflictOf(error) {
243
- const structural = error !== null && typeof error === "object" ? error.conflict : void 0;
244
- if (structural !== null && typeof structural === "object") {
245
- const { existingInvocationId, reasons } = structural;
246
- if (Array.isArray(reasons)) {
247
- return {
248
- ...typeof existingInvocationId === "string" && existingInvocationId ? { existingInvocationId } : {},
249
- reasons: new Set(reasons.filter((reason) => typeof reason === "string"))
250
- };
251
- }
252
- }
253
- const message = messageOf(error);
254
- const phrase = "conflicts with invocation";
255
- const at = message.toLowerCase().lastIndexOf(phrase);
256
- if (at < 0) return void 0;
257
- const tail = /^\s+([^\s:]+):[ \t]*([^\n]*)/.exec(message.slice(at + phrase.length));
258
- if (!tail) return { reasons: /* @__PURE__ */ new Set() };
259
- return {
260
- existingInvocationId: tail[1],
261
- reasons: new Set(tail[2].split(",").map((reason) => reason.trim()).filter(Boolean))
262
- };
295
+ const structural = error !== null && typeof error === "object" ? error.conflict : void 0;
296
+ if (structural !== null && typeof structural === "object") {
297
+ const { existingInvocationId, reasons } = structural;
298
+ if (Array.isArray(reasons)) return {
299
+ ...typeof existingInvocationId === "string" && existingInvocationId ? { existingInvocationId } : {},
300
+ reasons: new Set(reasons.filter((reason) => typeof reason === "string"))
301
+ };
302
+ }
303
+ const message = messageOf(error);
304
+ const at = message.toLowerCase().lastIndexOf("conflicts with invocation");
305
+ if (at < 0) return void 0;
306
+ const tail = /^\s+([^\s:]+):[ \t]*([^\n]*)/.exec(message.slice(at + 25));
307
+ if (!tail) return { reasons: /* @__PURE__ */ new Set() };
308
+ return {
309
+ existingInvocationId: tail[1],
310
+ reasons: new Set(tail[2].split(",").map((reason) => reason.trim()).filter(Boolean))
311
+ };
263
312
  }
264
313
  function isSettledReplayFailure(error) {
265
- return [
266
- // The action was never offline-eligible.
267
- /not eligible for offline replay/i,
268
- // The plan no longer grants, no longer exists, or was never this actor's.
269
- /is not granted by the render plan/i,
270
- /render plan was not found/i,
271
- /render plan has expired/i,
272
- /scope does not match/i,
273
- /belongs to a different application assembly/i,
274
- // A capture only gets older, so an age refusal can never come good. This
275
- // one is independent of the capture TTL, so nothing else retires it.
276
- /capturedAt is older than/i,
277
- // Malformed beyond repair: passes the grammar, names no real instant.
278
- /capturedAt is not a parseable instant/i,
279
- /capturedAt must be an RFC 3339/i
280
- // Deliberately absent: a capture dated in the future comes good as real
281
- // time advances; a gateway missing its replay window is a deployment
282
- // mistake somebody will fix; and an action-version mismatch fires
283
- // transiently mid-rollout when a client's plan is newer than the pod it
284
- // reached. Discarding any of them would destroy work a clock correction,
285
- // a config change, or the rollout finishing would have replayed.
286
- ].some((pattern) => pattern.test(messageOf(error)));
314
+ return [
315
+ /not eligible for offline replay/i,
316
+ /is not granted by the render plan/i,
317
+ /render plan was not found/i,
318
+ /render plan has expired/i,
319
+ /scope does not match/i,
320
+ /belongs to a different application assembly/i,
321
+ /capturedAt is older than/i,
322
+ /capturedAt is not a parseable instant/i,
323
+ /capturedAt must be an RFC 3339/i
324
+ ].some((pattern) => pattern.test(messageOf(error)));
287
325
  }
288
- // Annotate the CommonJS export names for ESM import in node:
289
- 0 && (module.exports = {
290
- createMemoryOfflineCaptureStore,
291
- createOfflineCaptureQueue
292
- });
326
+ //#endregion
327
+ exports.createMemoryOfflineCaptureStore = createMemoryOfflineCaptureStore;
328
+ exports.createOfflineCaptureQueue = createOfflineCaptureQueue;
329
+
293
330
  //# sourceMappingURL=offline.cjs.map