@nimbus-sh/platform 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/heavy-alloc-coord.d.ts +5 -0
- package/dist/heavy-alloc-coord.d.ts.map +1 -1
- package/dist/heavy-alloc-coord.js +12 -1
- package/dist/limits.d.ts +1 -6
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +7 -6
- package/dist/lost-call.d.ts +62 -0
- package/dist/lost-call.d.ts.map +1 -0
- package/dist/lost-call.js +67 -0
- package/dist/oom-classify.d.ts +14 -0
- package/dist/oom-classify.d.ts.map +1 -1
- package/dist/oom-classify.js +30 -0
- package/dist/retry.d.ts +49 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +59 -0
- package/dist/utf8.d.ts +11 -0
- package/dist/utf8.d.ts.map +1 -0
- package/dist/utf8.js +35 -0
- package/dist/w7-frame.d.ts +278 -6
- package/dist/w7-frame.d.ts.map +1 -1
- package/dist/w7-frame.js +657 -134
- package/dist/wave-writer.d.ts +372 -0
- package/dist/wave-writer.d.ts.map +1 -0
- package/dist/wave-writer.js +879 -0
- package/package.json +1 -1
- package/src/heavy-alloc-coord.ts +15 -1
- package/src/limits.ts +8 -7
- package/src/lost-call.ts +80 -0
- package/src/oom-classify.ts +32 -0
- package/src/retry.ts +74 -0
- package/src/utf8.ts +31 -0
- package/src/w7-frame.ts +780 -149
- package/src/wave-writer.ts +1065 -0
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ export function* slices(bytes: Uint8Array): Generator<Uint8Array> {
|
|
|
124
124
|
| `MAX_TX_LOGICAL_ROWS` / `MAX_TX_SQL_EXECS` | 256 / 64 | rows and execs in that same transaction |
|
|
125
125
|
| `SQLITE_MAX_ROW_BYTES` | 2,000,000 | bytes per ROW, key length included; budget values against the row bound |
|
|
126
126
|
| `SQLITE_MAX_STATEMENT_BYTES` | 100 KiB | SQL text per exec |
|
|
127
|
-
| `
|
|
127
|
+
| `SQL_MAX_BOUND_PARAMETERS` | 100 | bound parameters per statement (101 fail); a batched insert breaches this first |
|
|
128
128
|
| `DO_STORAGE_LIMIT_BYTES` | 10,000,000,000 | storage per Durable Object, shared by every facet and clone under it, with no copy-on-write credit |
|
|
129
129
|
| `BLOCK_CONCURRENCY_CANCEL_MS` | 30,000 | how long a `blockConcurrencyWhile()` callback may run before the object is reset |
|
|
130
130
|
| `WS_ATTACHMENT_LIMIT_BYTES` | 16,384 | serialized bytes per hibernatable WebSocket attachment |
|
|
@@ -51,7 +51,10 @@ export declare class SupervisorAllocationBudget {
|
|
|
51
51
|
* stops a large read overlapping a large write.
|
|
52
52
|
*/
|
|
53
53
|
acquireWithoutLifecycle(bytes: number, signal?: AbortSignal): Promise<ResizableCreditLease>;
|
|
54
|
+
/** Reserve bytes now or not at all, driving the lifecycle as acquire does. */
|
|
55
|
+
tryAcquire(bytes: number): ResizableCreditLease | null;
|
|
54
56
|
private _acquire;
|
|
57
|
+
private wrap;
|
|
55
58
|
}
|
|
56
59
|
/**
|
|
57
60
|
* A registered observer (typically a SqliteVFS) receives shrink/restore
|
|
@@ -77,6 +80,8 @@ export declare function registerAllocObserver(o: AllocObserver): () => void;
|
|
|
77
80
|
* Reserve an exact number of supervisor-resident bytes.
|
|
78
81
|
*/
|
|
79
82
|
export declare function acquireSupervisorAllocation(bytes: number, signal?: AbortSignal): Promise<ResizableCreditLease>;
|
|
83
|
+
/** Reserve bytes now if they can be granted without waiting, else null. */
|
|
84
|
+
export declare function tryAcquireSupervisorAllocation(bytes: number): ResizableCreditLease | null;
|
|
80
85
|
/**
|
|
81
86
|
* Reserve bytes for a filesystem READ. Takes the same byte credit as any
|
|
82
87
|
* other owner but does not shrink the disposable VFS cache, which reads are
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"heavy-alloc-coord.d.ts","sourceRoot":"","sources":["../src/heavy-alloc-coord.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC/B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,yBAAyB;IACjC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,0BAA0B;IAWnC,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAX5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB,CAAK;gBAGlB,QAAQ,EAAE,MAAM,EACR,SAAS,GAAE,yBAA8B,EAC1D,OAAO,GAAE,yBAA8B;IAKzC,IAAI,KAAK,IAAI,+BAA+B,CAK3C;IAED;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAI3E;;;;;;;;OAQG;IACH,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"heavy-alloc-coord.d.ts","sourceRoot":"","sources":["../src/heavy-alloc-coord.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAOH,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC/B,MAAM,2BAA2B,CAAC;AAEnC,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,UAAU,yBAAyB;IACjC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,0BAA0B;IAWnC,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAX5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,MAAM,CAAS;IACvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB,CAAK;gBAGlB,QAAQ,EAAE,MAAM,EACR,SAAS,GAAE,yBAA8B,EAC1D,OAAO,GAAE,yBAA8B;IAKzC,IAAI,KAAK,IAAI,+BAA+B,CAK3C;IAED;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAI3E;;;;;;;;OAQG;IACH,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAI3F,8EAA8E;IAC9E,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI;YAKxC,QAAQ;IAQtB,OAAO,CAAC,IAAI;CA8Bb;AAED;;;GAGG;AACH,UAAU,aAAa;IACrB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAiBD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,IAAI,CAWlE;AAqCD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAE/B;AAED,2EAA2E;AAC3E,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAEzF;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAE/B;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAMjF;AAED,wBAAgB,8BAA8B,IAAI,+BAA+B,CAEhF"}
|
|
@@ -55,8 +55,15 @@ export class SupervisorAllocationBudget {
|
|
|
55
55
|
acquireWithoutLifecycle(bytes, signal) {
|
|
56
56
|
return this._acquire(bytes, signal, false);
|
|
57
57
|
}
|
|
58
|
+
/** Reserve bytes now or not at all, driving the lifecycle as acquire does. */
|
|
59
|
+
tryAcquire(bytes) {
|
|
60
|
+
const credit = this.credits.tryAcquire(bytes);
|
|
61
|
+
return credit ? this.wrap(credit, true) : null;
|
|
62
|
+
}
|
|
58
63
|
async _acquire(bytes, signal, drivesLifecycle) {
|
|
59
|
-
|
|
64
|
+
return this.wrap(await this.credits.acquire(bytes, signal), drivesLifecycle);
|
|
65
|
+
}
|
|
66
|
+
wrap(credit, drivesLifecycle) {
|
|
60
67
|
if (drivesLifecycle) {
|
|
61
68
|
this.lifecycleHolders++;
|
|
62
69
|
if (!this.active) {
|
|
@@ -160,6 +167,10 @@ const supervisorAllocationBudget = new SupervisorAllocationBudget(SUPERVISOR_IN_
|
|
|
160
167
|
export function acquireSupervisorAllocation(bytes, signal) {
|
|
161
168
|
return supervisorAllocationBudget.acquire(bytes, signal);
|
|
162
169
|
}
|
|
170
|
+
/** Reserve bytes now if they can be granted without waiting, else null. */
|
|
171
|
+
export function tryAcquireSupervisorAllocation(bytes) {
|
|
172
|
+
return supervisorAllocationBudget.tryAcquire(bytes);
|
|
173
|
+
}
|
|
163
174
|
/**
|
|
164
175
|
* Reserve bytes for a filesystem READ. Takes the same byte credit as any
|
|
165
176
|
* other owner but does not shrink the disposable VFS cache, which reads are
|
package/dist/limits.d.ts
CHANGED
|
@@ -46,12 +46,6 @@ export declare const HYDRATION_DEADLINE_MS = 30000;
|
|
|
46
46
|
* statements, a batched multi-VALUES insert being the realistic breach).
|
|
47
47
|
*/
|
|
48
48
|
export declare const SQLITE_MAX_STATEMENT_BYTES: number;
|
|
49
|
-
/**
|
|
50
|
-
* Maximum bound parameters per query. The easiest cap to hit accidentally:
|
|
51
|
-
* a batched insert of more than 100/columns rows in one statement breaches
|
|
52
|
-
* it.
|
|
53
|
-
*/
|
|
54
|
-
export declare const SQLITE_MAX_BOUND_PARAMETERS = 100;
|
|
55
49
|
/**
|
|
56
50
|
* Maximum bytes of one string, BLOB, or table ROW — the bound is per ROW,
|
|
57
51
|
* key length included, not per value. The measured single-value ceiling is
|
|
@@ -107,6 +101,7 @@ export declare const MAX_RPC_SAFE_PAYLOAD_BYTES: number;
|
|
|
107
101
|
export declare const ONE_SHOT_MODULE_MAP_MAX_BYTES: number;
|
|
108
102
|
export declare const SUPERVISOR_HEAP_CEILING_BYTES: number;
|
|
109
103
|
export declare const FACET_OWN_WRITE_MEMORY_BYTES: number;
|
|
104
|
+
export declare const WASI_RESIDENT_STORE_BYTES: number;
|
|
110
105
|
export declare const SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES: number;
|
|
111
106
|
export declare const SUPERVISOR_READ_RESERVE_BYTES: number;
|
|
112
107
|
export declare const MAX_GLOBAL_WRITE_STREAM_CREDIT_BYTES: number;
|
package/dist/limits.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAS,CAAC;AAEjC,eAAO,MAAM,iBAAiB,QAAkB,CAAC;AACjD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAI5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,cAAiB,CAAC;AAMrD,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAa,CAAC;AAErD
|
|
1
|
+
{"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,QAAS,CAAC;AAEjC,eAAO,MAAM,iBAAiB,QAAkB,CAAC;AACjD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAI5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,cAAiB,CAAC;AAMrD,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,QAAa,CAAC;AAErD;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,UAAY,CAAC;AAI9C;;;;;;;;GAQG;AACH,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAIlD;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,OAAQ,CAAC;AAE3C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAIrD;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,QAAS,CAAC;AAOhD,eAAO,MAAM,0BAA0B,QAAmB,CAAC;AAmB3D,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AA6B9D,eAAO,MAAM,6BAA6B,QAAmB,CAAC;AAM9D,eAAO,MAAM,4BAA4B,QAAmB,CAAC;AAQ7D,eAAO,MAAM,yBAAyB,QAAmB,CAAC;AAO1D,eAAO,MAAM,4CAA4C,QAAmB,CAAC;AAY7E,eAAO,MAAM,6BAA6B,QAAc,CAAC;AAKzD,eAAO,MAAM,oCAAoC,QAAkB,CAAC;AAQpE,eAAO,MAAM,0BAA0B,QAA6B,CAAC;AACrE,eAAO,MAAM,sBAAsB,IAAI,CAAC"}
|
package/dist/limits.js
CHANGED
|
@@ -57,12 +57,6 @@ export const HYDRATION_DEADLINE_MS = 30_000;
|
|
|
57
57
|
* statements, a batched multi-VALUES insert being the realistic breach).
|
|
58
58
|
*/
|
|
59
59
|
export const SQLITE_MAX_STATEMENT_BYTES = 100 * 1024;
|
|
60
|
-
/**
|
|
61
|
-
* Maximum bound parameters per query. The easiest cap to hit accidentally:
|
|
62
|
-
* a batched insert of more than 100/columns rows in one statement breaches
|
|
63
|
-
* it.
|
|
64
|
-
*/
|
|
65
|
-
export const SQLITE_MAX_BOUND_PARAMETERS = 100;
|
|
66
60
|
/**
|
|
67
61
|
* Maximum bytes of one string, BLOB, or table ROW — the bound is per ROW,
|
|
68
62
|
* key length included, not per value. The measured single-value ceiling is
|
|
@@ -173,6 +167,13 @@ export const SUPERVISOR_HEAP_CEILING_BYTES = 64 * 1024 * 1024;
|
|
|
173
167
|
// works: at most this many bytes, least recently used out first. An eighth
|
|
174
168
|
// of a facet's measured ~208-256 MiB, the rest left to the program.
|
|
175
169
|
export const FACET_OWN_WRITE_MEMORY_BYTES = 32 * 1024 * 1024;
|
|
170
|
+
// A WASI process's copy of the namespace and of the files it reads
|
|
171
|
+
// (core runtime/wasi/resident-filesystem.ts): held in its facet's heap beside
|
|
172
|
+
// the guest's linear memory, and never past this. Names are listed and bytes
|
|
173
|
+
// fetched only as the program asks for them, so the cap bounds a tree walker
|
|
174
|
+
// or a program that reads much, not what a short run holds. Past it the store
|
|
175
|
+
// holds nothing more and the session answers.
|
|
176
|
+
export const WASI_RESIDENT_STORE_BYTES = 32 * 1024 * 1024;
|
|
176
177
|
// Shared allowance for transient allocations in the supervisor DO. With the
|
|
177
178
|
// VFS LRU shrunk to 8 MiB during an active reservation, 40 MiB of admitted
|
|
178
179
|
// payload plus the 9 MiB bundle baseline stays below the 64 MiB soft ceiling
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lost-call.ts — the one policy for a call that never reaches the session.
|
|
3
|
+
*
|
|
4
|
+
* The platform drops calls into a Durable Object now and then, and the
|
|
5
|
+
* caller cannot tell a call that never arrived from one that is slow:
|
|
6
|
+
* - reads, SupervisorRPC → session, under three concurrent sessions
|
|
7
|
+
* (2026-09-28): 11 of 521 read batches never reached the session and
|
|
8
|
+
* were still pending 110-560 s later; every read that answered did so
|
|
9
|
+
* within 5 s;
|
|
10
|
+
* - write waves, a facet's writeBatchStream (2026-10-06, two next.js
|
|
11
|
+
* clones): the transport read the stream's first ~1 MiB within 0.1 s
|
|
12
|
+
* and nothing more, while the session held no stream, credit,
|
|
13
|
+
* allocation or transaction for it and kept committing other waves;
|
|
14
|
+
* healthy, with 8 producers saturating one session, the longest gap
|
|
15
|
+
* between a wave's reads was 2.5 s and the slowest wave 5.3 s;
|
|
16
|
+
* - calls that fail at once, "Network connection lost." (`retryable`),
|
|
17
|
+
* which classifyDoCall names.
|
|
18
|
+
*
|
|
19
|
+
* Detection and timings live here; the recovery depends on what a second
|
|
20
|
+
* delivery would do. A read is idempotent, so it is hedged: the same call
|
|
21
|
+
* on a fresh stub, the first answer taken (fabric do-calls `hedgeAfterMs`).
|
|
22
|
+
* A write wave carries a stream the first attempt consumes, so it cannot be
|
|
23
|
+
* hedged: it is re-encoded and re-sent under a fence (`WaveFence`), and the
|
|
24
|
+
* session refuses an attempt older than one it has seen from the same
|
|
25
|
+
* writer, so a late original never applies over its re-send.
|
|
26
|
+
*/
|
|
27
|
+
/** An idempotent call unanswered this long is hedged on a fresh stub. */
|
|
28
|
+
export declare const LOST_CALL_HEDGE_AFTER_MS = 5000;
|
|
29
|
+
/** A stream nothing has read for this long, before its end, never reached the session. */
|
|
30
|
+
export declare const LOST_STREAM_STALL_MS = 10000;
|
|
31
|
+
/** A call unanswered this long after its stream ended is taken as lost. */
|
|
32
|
+
export declare const LOST_STREAM_ANSWER_MS = 20000;
|
|
33
|
+
/**
|
|
34
|
+
* How long a write-wave epoch the session issued admits attempts. Anything
|
|
35
|
+
* sent under an epoch the session does not know, or one past this, is
|
|
36
|
+
* refused: a call lost for longer than this can only be refused, never
|
|
37
|
+
* admitted. A writer opens a new epoch once half of this has passed.
|
|
38
|
+
*/
|
|
39
|
+
export declare const WAVE_EPOCH_TTL_MS: number;
|
|
40
|
+
/**
|
|
41
|
+
* Waits before each re-send of a lost non-idempotent call (±25% jitter):
|
|
42
|
+
* ~42 s in all, to outlast a coordinator queue deep enough to shed. Its
|
|
43
|
+
* length is the bound on re-sends.
|
|
44
|
+
*/
|
|
45
|
+
export declare const LOST_CALL_RESEND_BACKOFF_MS: readonly number[];
|
|
46
|
+
/**
|
|
47
|
+
* Whether `error` is the platform losing a fenced call: a dropped
|
|
48
|
+
* connection, a replaced isolate, a storage reset, or an object that shed
|
|
49
|
+
* it. Overloaded is included for fenced re-sends only: the platform's advice
|
|
50
|
+
* is not to retry an overloaded object, but these are few and backed off,
|
|
51
|
+
* and npm measured one re-send recover a 119-package install that one shed
|
|
52
|
+
* cost 31 packages (do-calls' unfenced verbs still never retry it).
|
|
53
|
+
*/
|
|
54
|
+
export declare function isLostFencedCall(error: unknown): boolean;
|
|
55
|
+
/** The attributes a lost call is logged and traced under, by every caller. */
|
|
56
|
+
export declare function lostCallAttributes(lost: {
|
|
57
|
+
operation: string;
|
|
58
|
+
attempt: number;
|
|
59
|
+
of: number;
|
|
60
|
+
reason: string;
|
|
61
|
+
}): Record<string, string | number>;
|
|
62
|
+
//# sourceMappingURL=lost-call.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lost-call.d.ts","sourceRoot":"","sources":["../src/lost-call.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,0FAA0F;AAC1F,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,2EAA2E;AAC3E,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,QAAc,CAAC;AAE7C;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAA+C,CAAC;AAEzG;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGxD;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAOlC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lost-call.ts — the one policy for a call that never reaches the session.
|
|
3
|
+
*
|
|
4
|
+
* The platform drops calls into a Durable Object now and then, and the
|
|
5
|
+
* caller cannot tell a call that never arrived from one that is slow:
|
|
6
|
+
* - reads, SupervisorRPC → session, under three concurrent sessions
|
|
7
|
+
* (2026-09-28): 11 of 521 read batches never reached the session and
|
|
8
|
+
* were still pending 110-560 s later; every read that answered did so
|
|
9
|
+
* within 5 s;
|
|
10
|
+
* - write waves, a facet's writeBatchStream (2026-10-06, two next.js
|
|
11
|
+
* clones): the transport read the stream's first ~1 MiB within 0.1 s
|
|
12
|
+
* and nothing more, while the session held no stream, credit,
|
|
13
|
+
* allocation or transaction for it and kept committing other waves;
|
|
14
|
+
* healthy, with 8 producers saturating one session, the longest gap
|
|
15
|
+
* between a wave's reads was 2.5 s and the slowest wave 5.3 s;
|
|
16
|
+
* - calls that fail at once, "Network connection lost." (`retryable`),
|
|
17
|
+
* which classifyDoCall names.
|
|
18
|
+
*
|
|
19
|
+
* Detection and timings live here; the recovery depends on what a second
|
|
20
|
+
* delivery would do. A read is idempotent, so it is hedged: the same call
|
|
21
|
+
* on a fresh stub, the first answer taken (fabric do-calls `hedgeAfterMs`).
|
|
22
|
+
* A write wave carries a stream the first attempt consumes, so it cannot be
|
|
23
|
+
* hedged: it is re-encoded and re-sent under a fence (`WaveFence`), and the
|
|
24
|
+
* session refuses an attempt older than one it has seen from the same
|
|
25
|
+
* writer, so a late original never applies over its re-send.
|
|
26
|
+
*/
|
|
27
|
+
import { classifyDoCall, isRetryableDoCall } from './oom-classify.js';
|
|
28
|
+
/** An idempotent call unanswered this long is hedged on a fresh stub. */
|
|
29
|
+
export const LOST_CALL_HEDGE_AFTER_MS = 5_000;
|
|
30
|
+
/** A stream nothing has read for this long, before its end, never reached the session. */
|
|
31
|
+
export const LOST_STREAM_STALL_MS = 10_000;
|
|
32
|
+
/** A call unanswered this long after its stream ended is taken as lost. */
|
|
33
|
+
export const LOST_STREAM_ANSWER_MS = 20_000;
|
|
34
|
+
/**
|
|
35
|
+
* How long a write-wave epoch the session issued admits attempts. Anything
|
|
36
|
+
* sent under an epoch the session does not know, or one past this, is
|
|
37
|
+
* refused: a call lost for longer than this can only be refused, never
|
|
38
|
+
* admitted. A writer opens a new epoch once half of this has passed.
|
|
39
|
+
*/
|
|
40
|
+
export const WAVE_EPOCH_TTL_MS = 10 * 60_000;
|
|
41
|
+
/**
|
|
42
|
+
* Waits before each re-send of a lost non-idempotent call (±25% jitter):
|
|
43
|
+
* ~42 s in all, to outlast a coordinator queue deep enough to shed. Its
|
|
44
|
+
* length is the bound on re-sends.
|
|
45
|
+
*/
|
|
46
|
+
export const LOST_CALL_RESEND_BACKOFF_MS = [250, 1_000, 3_000, 6_000, 12_000, 20_000];
|
|
47
|
+
/**
|
|
48
|
+
* Whether `error` is the platform losing a fenced call: a dropped
|
|
49
|
+
* connection, a replaced isolate, a storage reset, or an object that shed
|
|
50
|
+
* it. Overloaded is included for fenced re-sends only: the platform's advice
|
|
51
|
+
* is not to retry an overloaded object, but these are few and backed off,
|
|
52
|
+
* and npm measured one re-send recover a 119-package install that one shed
|
|
53
|
+
* cost 31 packages (do-calls' unfenced verbs still never retry it).
|
|
54
|
+
*/
|
|
55
|
+
export function isLostFencedCall(error) {
|
|
56
|
+
const kind = classifyDoCall(error);
|
|
57
|
+
return isRetryableDoCall(kind) || kind === 'overloaded';
|
|
58
|
+
}
|
|
59
|
+
/** The attributes a lost call is logged and traced under, by every caller. */
|
|
60
|
+
export function lostCallAttributes(lost) {
|
|
61
|
+
return {
|
|
62
|
+
'do_call.operation': lost.operation,
|
|
63
|
+
'do_call.attempts': lost.attempt,
|
|
64
|
+
'do_call.max_attempts': lost.of,
|
|
65
|
+
'do_call.lost_reason': lost.reason,
|
|
66
|
+
};
|
|
67
|
+
}
|
package/dist/oom-classify.d.ts
CHANGED
|
@@ -110,6 +110,20 @@ export declare function classifyMessage(msg: string): OomCause;
|
|
|
110
110
|
* (`loaders/process-fabric.ts`) — recurs, exhausts the budget and surfaces.
|
|
111
111
|
*/
|
|
112
112
|
export declare function isTransientDoReset(input: unknown): boolean;
|
|
113
|
+
/**
|
|
114
|
+
* The platform's own word that the actor a call reached was reset or killed:
|
|
115
|
+
* its `durableObjectReset` flag, or one of its termination sentences, whole.
|
|
116
|
+
* Text a program wrote never counts, however it reads: a process whose host
|
|
117
|
+
* this matches is ended (fabric process-host.ts HostLoss).
|
|
118
|
+
*/
|
|
119
|
+
export declare function isHostReset(input: unknown): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* A failure the platform reports without a cause: workerd's "internal error;
|
|
122
|
+
* reference = <id>", whose reference only Cloudflare can look up. The error
|
|
123
|
+
* may carry `durableObjectReset` (the object it reached was reset), and
|
|
124
|
+
* nothing else; the caller has to name what it was doing.
|
|
125
|
+
*/
|
|
126
|
+
export declare function isUnexplainedPlatformError(input: unknown): boolean;
|
|
113
127
|
/**
|
|
114
128
|
* Workerd shed the call because the target Durable Object's input-gate
|
|
115
129
|
* queue was too deep or too old: "Durable Object is overloaded."
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oom-classify.d.ts","sourceRoot":"","sources":["../src/oom-classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,QAAA,MAAM,UAAU,kLAYN,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAEjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAItD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAmErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO1D;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW;AACrB,8CAA8C;AAC5C,oBAAoB;AACtB,8CAA8C;GAC5C,iBAAiB;AACnB,uEAAuE;GACrE,eAAe;AACjB,wDAAwD;GACtD,gBAAgB;AAClB;kDACkD;GAChD,YAAY;AACd,kEAAkE;GAChE,WAAW,CAAC;AAEhB;2DAC2D;AAC3D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAK7D;AAmBD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsB1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiBpD"}
|
|
1
|
+
{"version":3,"file":"oom-classify.d.ts","sourceRoot":"","sources":["../src/oom-classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6DG;AAEH,QAAA,MAAM,UAAU,kLAYN,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAEjD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,QAAQ,CAItD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAmErD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO1D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAInD;AAYD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAElE;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW;AACrB,8CAA8C;AAC5C,oBAAoB;AACtB,8CAA8C;GAC5C,iBAAiB;AACnB,uEAAuE;GACrE,eAAe;AACjB,wDAAwD;GACtD,gBAAgB;AAClB;kDACkD;GAChD,YAAY;AACd,kEAAkE;GAChE,WAAW,CAAC;AAEhB;2DAC2D;AAC3D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAK7D;AAmBD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAsB1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiBpD"}
|
package/dist/oom-classify.js
CHANGED
|
@@ -214,6 +214,36 @@ export function isTransientDoReset(input) {
|
|
|
214
214
|
return true;
|
|
215
215
|
return false;
|
|
216
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* The platform's own word that the actor a call reached was reset or killed:
|
|
219
|
+
* its `durableObjectReset` flag, or one of its termination sentences, whole.
|
|
220
|
+
* Text a program wrote never counts, however it reads: a process whose host
|
|
221
|
+
* this matches is ended (fabric process-host.ts HostLoss).
|
|
222
|
+
*/
|
|
223
|
+
export function isHostReset(input) {
|
|
224
|
+
if (typeof input === 'object' && input !== null && Reflect.get(input, 'durableObjectReset') === true)
|
|
225
|
+
return true;
|
|
226
|
+
const message = readMessage(input).trim().replace(/^broken\.\w+;\s*jsg\.\w+:\s*/, '');
|
|
227
|
+
return HOST_TERMINATIONS.some((signature) => signature.test(message));
|
|
228
|
+
}
|
|
229
|
+
const HOST_TERMINATIONS = [
|
|
230
|
+
/^Durable Object's isolate exceeded its memory limit (?:and was reset\.|due to )/,
|
|
231
|
+
/^Worker (?:has )?exceeded memory limit\.$/,
|
|
232
|
+
/^(?:Python )?Worker exceeded CPU time limit\.?$/,
|
|
233
|
+
/^Durable Object exceeded its CPU time limit and was reset\.$/,
|
|
234
|
+
/^Durable Object reset because its code was updated\.$/,
|
|
235
|
+
/^Internal error (?:in|while starting up) Durable Object storage caused object to be reset; reference = \S+$/,
|
|
236
|
+
/^Durable Object storage operation exceeded timeout which caused the object to be reset\.$/,
|
|
237
|
+
];
|
|
238
|
+
/**
|
|
239
|
+
* A failure the platform reports without a cause: workerd's "internal error;
|
|
240
|
+
* reference = <id>", whose reference only Cloudflare can look up. The error
|
|
241
|
+
* may carry `durableObjectReset` (the object it reached was reset), and
|
|
242
|
+
* nothing else; the caller has to name what it was doing.
|
|
243
|
+
*/
|
|
244
|
+
export function isUnexplainedPlatformError(input) {
|
|
245
|
+
return /^internal error; reference = \S+$/i.test(readMessage(input).trim());
|
|
246
|
+
}
|
|
217
247
|
/**
|
|
218
248
|
* Workerd shed the call because the target Durable Object's input-gate
|
|
219
249
|
* queue was too deep or too old: "Durable Object is overloaded."
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* retry.ts — the one retry policy: the loop that tries an operation again
|
|
3
|
+
* when its outcome is worth another try, and the wait between tries.
|
|
4
|
+
*
|
|
5
|
+
* Each caller names what is worth another try and how far apart, because
|
|
6
|
+
* that is what differs and was measured per transport:
|
|
7
|
+
* - the npm registry (npm/registry-retry.ts): a 5xx or a request that
|
|
8
|
+
* never answered, 500/1500/4500 ms;
|
|
9
|
+
* - a git server (git/pack/transport.ts): an idempotent request that
|
|
10
|
+
* failed in transit, stalled, or met a transient edge status, 1/3 s;
|
|
11
|
+
* - a lost write wave (lost-call.ts LOST_CALL_RESEND_BACKOFF_MS).
|
|
12
|
+
* Every wait is its schedule's entry ±25%, uniformly, so concurrent callers
|
|
13
|
+
* that failed together do not try again together.
|
|
14
|
+
*
|
|
15
|
+
* Self-contained, both functions: a facet that cannot import carries them
|
|
16
|
+
* by source (fn.toString(); the npm install facet's preamble does).
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The wait before re-try `attempt` (0-based, the try that just failed):
|
|
20
|
+
* `schedule[attempt]`, its last entry once past the end, ±25%.
|
|
21
|
+
*/
|
|
22
|
+
export declare function retryDelayMs(schedule: readonly number[], attempt: number): number;
|
|
23
|
+
/** An attempt's outcome: what it returned, or what it threw. */
|
|
24
|
+
export type RetryOutcome<T> = {
|
|
25
|
+
readonly ok: true;
|
|
26
|
+
readonly value: T;
|
|
27
|
+
} | {
|
|
28
|
+
readonly ok: false;
|
|
29
|
+
readonly error: unknown;
|
|
30
|
+
};
|
|
31
|
+
export interface RetryPolicy<T> {
|
|
32
|
+
/** How many times to try again after the first try. */
|
|
33
|
+
readonly retries: number;
|
|
34
|
+
/** The waits before each re-try, for retryDelayMs. */
|
|
35
|
+
readonly schedule: readonly number[];
|
|
36
|
+
/** Why `outcome` is worth another try, or null to keep it. Not asked of the last try. */
|
|
37
|
+
retryReason(outcome: RetryOutcome<T>): string | null;
|
|
38
|
+
/** A returned value given up for another try (its body cancelled, its stub disposed). */
|
|
39
|
+
discard?(value: T): unknown;
|
|
40
|
+
/** Each re-try, 1-based, with its wait and why. */
|
|
41
|
+
onRetry?(retry: number, of: number, delayMs: number, reason: string): void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* `attempt(n)` for n = 0, 1, … until its outcome is not worth another try
|
|
45
|
+
* or the policy's re-tries are spent; then that outcome: its value
|
|
46
|
+
* returned, its error thrown.
|
|
47
|
+
*/
|
|
48
|
+
export declare function retrying<T>(attempt: (n: number) => Promise<T>, policy: RetryPolicy<T>): Promise<T>;
|
|
49
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAGjF;AAED,gEAAgE;AAChE,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzH,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,yFAAyF;IACzF,WAAW,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IACrD,yFAAyF;IACzF,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,OAAO,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5E;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAyBxG"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* retry.ts — the one retry policy: the loop that tries an operation again
|
|
3
|
+
* when its outcome is worth another try, and the wait between tries.
|
|
4
|
+
*
|
|
5
|
+
* Each caller names what is worth another try and how far apart, because
|
|
6
|
+
* that is what differs and was measured per transport:
|
|
7
|
+
* - the npm registry (npm/registry-retry.ts): a 5xx or a request that
|
|
8
|
+
* never answered, 500/1500/4500 ms;
|
|
9
|
+
* - a git server (git/pack/transport.ts): an idempotent request that
|
|
10
|
+
* failed in transit, stalled, or met a transient edge status, 1/3 s;
|
|
11
|
+
* - a lost write wave (lost-call.ts LOST_CALL_RESEND_BACKOFF_MS).
|
|
12
|
+
* Every wait is its schedule's entry ±25%, uniformly, so concurrent callers
|
|
13
|
+
* that failed together do not try again together.
|
|
14
|
+
*
|
|
15
|
+
* Self-contained, both functions: a facet that cannot import carries them
|
|
16
|
+
* by source (fn.toString(); the npm install facet's preamble does).
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The wait before re-try `attempt` (0-based, the try that just failed):
|
|
20
|
+
* `schedule[attempt]`, its last entry once past the end, ±25%.
|
|
21
|
+
*/
|
|
22
|
+
export function retryDelayMs(schedule, attempt) {
|
|
23
|
+
const base = schedule[Math.min(attempt, schedule.length - 1)] ?? 0;
|
|
24
|
+
return Math.max(0, Math.round(base + (Math.random() * 2 - 1) * base * 0.25));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* `attempt(n)` for n = 0, 1, … until its outcome is not worth another try
|
|
28
|
+
* or the policy's re-tries are spent; then that outcome: its value
|
|
29
|
+
* returned, its error thrown.
|
|
30
|
+
*/
|
|
31
|
+
export async function retrying(attempt, policy) {
|
|
32
|
+
for (let n = 0;; n++) {
|
|
33
|
+
let outcome;
|
|
34
|
+
try {
|
|
35
|
+
outcome = { ok: true, value: await attempt(n) };
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
outcome = { ok: false, error };
|
|
39
|
+
}
|
|
40
|
+
const reason = n < policy.retries ? policy.retryReason(outcome) : null;
|
|
41
|
+
if (reason === null) {
|
|
42
|
+
if (outcome.ok)
|
|
43
|
+
return outcome.value;
|
|
44
|
+
throw outcome.error;
|
|
45
|
+
}
|
|
46
|
+
if (outcome.ok && policy.discard) {
|
|
47
|
+
try {
|
|
48
|
+
await policy.discard(outcome.value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// It is being given up either way.
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const delayMs = retryDelayMs(policy.schedule, n);
|
|
55
|
+
policy.onRetry?.(n + 1, policy.retries, delayMs, reason);
|
|
56
|
+
// The executor form: this package's lib (ES2022) has no Promise.withResolvers.
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/utf8.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utf8.ts — the UTF-8 length of a string, without encoding it.
|
|
3
|
+
*
|
|
4
|
+
* Bounds on names and payloads are in bytes, and the hot ones (every path a
|
|
5
|
+
* W7 wave or a listing page carries) were measured by encoding the string
|
|
6
|
+
* into a new buffer only to read its length. This counts the same bytes
|
|
7
|
+
* TextEncoder would write: one to four per code point, and three for a
|
|
8
|
+
* lone surrogate, which it writes as U+FFFD.
|
|
9
|
+
*/
|
|
10
|
+
export declare function utf8Length(text: string): number;
|
|
11
|
+
//# sourceMappingURL=utf8.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utf8.d.ts","sourceRoot":"","sources":["../src/utf8.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB/C"}
|
package/dist/utf8.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utf8.ts — the UTF-8 length of a string, without encoding it.
|
|
3
|
+
*
|
|
4
|
+
* Bounds on names and payloads are in bytes, and the hot ones (every path a
|
|
5
|
+
* W7 wave or a listing page carries) were measured by encoding the string
|
|
6
|
+
* into a new buffer only to read its length. This counts the same bytes
|
|
7
|
+
* TextEncoder would write: one to four per code point, and three for a
|
|
8
|
+
* lone surrogate, which it writes as U+FFFD.
|
|
9
|
+
*/
|
|
10
|
+
export function utf8Length(text) {
|
|
11
|
+
let bytes = text.length;
|
|
12
|
+
for (let index = 0; index < text.length; index++) {
|
|
13
|
+
const unit = text.charCodeAt(index);
|
|
14
|
+
if (unit < 0x80)
|
|
15
|
+
continue;
|
|
16
|
+
if (unit < 0x800) {
|
|
17
|
+
bytes += 1;
|
|
18
|
+
}
|
|
19
|
+
else if (unit >= 0xd800 && unit <= 0xdbff && index + 1 < text.length) {
|
|
20
|
+
const next = text.charCodeAt(index + 1);
|
|
21
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
22
|
+
// A pair: two units, four bytes.
|
|
23
|
+
bytes += 2;
|
|
24
|
+
index++;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
bytes += 2;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
bytes += 2;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return bytes;
|
|
35
|
+
}
|