@rexezuge/shared 0.0.0-stage → 1.0.1

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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/dist/Base64.d.ts +52 -0
  3. package/dist/Base64.d.ts.map +1 -0
  4. package/dist/Base64.js +81 -0
  5. package/dist/Base64.js.map +1 -0
  6. package/dist/Clock.d.ts +56 -0
  7. package/dist/Clock.d.ts.map +1 -0
  8. package/dist/Clock.js +59 -0
  9. package/dist/Clock.js.map +1 -0
  10. package/dist/CryptoUtil.d.ts +79 -0
  11. package/dist/CryptoUtil.d.ts.map +1 -0
  12. package/dist/CryptoUtil.js +97 -0
  13. package/dist/CryptoUtil.js.map +1 -0
  14. package/dist/CursorCodec.d.ts +61 -0
  15. package/dist/CursorCodec.d.ts.map +1 -0
  16. package/dist/CursorCodec.js +60 -0
  17. package/dist/CursorCodec.js.map +1 -0
  18. package/dist/EmailUtil.d.ts +29 -0
  19. package/dist/EmailUtil.d.ts.map +1 -0
  20. package/dist/EmailUtil.js +31 -0
  21. package/dist/EmailUtil.js.map +1 -0
  22. package/dist/ErrorSanitizationUtil.d.ts +69 -0
  23. package/dist/ErrorSanitizationUtil.d.ts.map +1 -0
  24. package/dist/ErrorSanitizationUtil.js +98 -0
  25. package/dist/ErrorSanitizationUtil.js.map +1 -0
  26. package/dist/IdGenerator.d.ts +27 -0
  27. package/dist/IdGenerator.d.ts.map +1 -0
  28. package/dist/IdGenerator.js +28 -0
  29. package/dist/IdGenerator.js.map +1 -0
  30. package/dist/Identity.d.ts +36 -0
  31. package/dist/Identity.d.ts.map +1 -0
  32. package/dist/Identity.js +39 -0
  33. package/dist/Identity.js.map +1 -0
  34. package/dist/LanguageTag.d.ts +25 -0
  35. package/dist/LanguageTag.d.ts.map +1 -0
  36. package/dist/LanguageTag.js +29 -0
  37. package/dist/LanguageTag.js.map +1 -0
  38. package/dist/LocaleUtil.d.ts +35 -0
  39. package/dist/LocaleUtil.d.ts.map +1 -0
  40. package/dist/LocaleUtil.js +97 -0
  41. package/dist/LocaleUtil.js.map +1 -0
  42. package/dist/PasswordFingerprint.d.ts +30 -0
  43. package/dist/PasswordFingerprint.d.ts.map +1 -0
  44. package/dist/PasswordFingerprint.js +33 -0
  45. package/dist/PasswordFingerprint.js.map +1 -0
  46. package/dist/RemoteUrlPolicy.d.ts +121 -0
  47. package/dist/RemoteUrlPolicy.d.ts.map +1 -0
  48. package/dist/RemoteUrlPolicy.js +339 -0
  49. package/dist/RemoteUrlPolicy.js.map +1 -0
  50. package/dist/Result.d.ts +32 -0
  51. package/dist/Result.d.ts.map +1 -0
  52. package/dist/Result.js +19 -0
  53. package/dist/Result.js.map +1 -0
  54. package/dist/SubrequestMeter.d.ts +204 -0
  55. package/dist/SubrequestMeter.d.ts.map +1 -0
  56. package/dist/SubrequestMeter.js +169 -0
  57. package/dist/SubrequestMeter.js.map +1 -0
  58. package/dist/TimeZoneUtil.d.ts +47 -0
  59. package/dist/TimeZoneUtil.d.ts.map +1 -0
  60. package/dist/TimeZoneUtil.js +101 -0
  61. package/dist/TimeZoneUtil.js.map +1 -0
  62. package/dist/TimestampUtil.d.ts +53 -0
  63. package/dist/TimestampUtil.d.ts.map +1 -0
  64. package/dist/TimestampUtil.js +69 -0
  65. package/dist/TimestampUtil.js.map +1 -0
  66. package/dist/TokenHashUtil.d.ts +34 -0
  67. package/dist/TokenHashUtil.d.ts.map +1 -0
  68. package/dist/TokenHashUtil.js +37 -0
  69. package/dist/TokenHashUtil.js.map +1 -0
  70. package/dist/UUIDUtil.d.ts +70 -0
  71. package/dist/UUIDUtil.d.ts.map +1 -0
  72. package/dist/UUIDUtil.js +79 -0
  73. package/dist/UUIDUtil.js.map +1 -0
  74. package/dist/index.d.ts +38 -0
  75. package/dist/index.d.ts.map +1 -0
  76. package/dist/index.js +42 -0
  77. package/dist/index.js.map +1 -0
  78. package/package.json +34 -3
  79. package/README.md +0 -3
@@ -0,0 +1,204 @@
1
+ /**
2
+ * What one Worker invocation may spend, counted.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Taken from Edge-Sonic's `packages/shared/src/utils/SubrequestMeter.ts`. It is
7
+ * the only subrequest-accounting helper among the nine repos and the most
8
+ * heavily measured; it is reproduced with its code unchanged and its prose
9
+ * generalised away from Edge-Sonic's own layer names and issue docs. No other
10
+ * repo carries a competing copy.
11
+ *
12
+ * ### Why this exists at all
13
+ *
14
+ * Cloudflare counts a **subrequest** per invocation and kills the invocation when
15
+ * the count is exceeded — it does not throw something a `catch` can see, so the
16
+ * whole invocation dies with `Too many subrequests by single Worker invocation`
17
+ * and everything after the ceiling is lost. The only defence is to never issue the
18
+ * request that crosses it.
19
+ *
20
+ * A subrequest is not only `fetch`. The platform's own D1 limits page states:
21
+ *
22
+ * > Queries per Worker invocation (read subrequest limits) — 1000 (Workers Paid) / **50 (Free)**
23
+ *
24
+ * So a D1 query is a subrequest, and on the Free plan a `KV` read, a Durable
25
+ * Object RPC and a Secrets Store read are subrequests too. Budgeting **only**
26
+ * `fetch` shipped a real bug: a scan chunk cost ~40 counted requests and ~200
27
+ * uncounted ones, every chunk died at the ceiling, and the scan only ever
28
+ * "finished" because each dead invocation left a little progress behind.
29
+ *
30
+ * ### Why the count is pessimistic
31
+ *
32
+ * The platform does not say whether a D1 `batch()` of N statements costs 1 or N.
33
+ * The D1 page counts *queries*, which reads as N; the Workers page counts
34
+ * subrequests, which reads as 1. So the meter charges **one per statement**, which
35
+ * is the reading that cannot kill an invocation. If the friendlier reading turns
36
+ * out to be right, the meter over-counts and a chunk does slightly less work per
37
+ * poll — a cost in throughput, never a correctness or availability problem. That
38
+ * asymmetry is the whole reason to choose it: an over-count is a slow scan, an
39
+ * under-count is a dead one.
40
+ *
41
+ * Measured on a Free account on 2026-10-05 there are two budgets: external `fetch`
42
+ * is 50 and **D1 statements are 1,000**, in a separate pool — 1,000 D1 statements
43
+ * and 50 outbound requests in one invocation survive together. A D1 overrun
44
+ * *throws* and an ordinary `catch` sees it; the external ceiling does not. This
45
+ * counter's single ceiling covers both, which is the conservative choice: bounding
46
+ * both by the resource that cannot be handled when it runs out is the choice that
47
+ * cannot take the product down. That is not a licence to catch and continue — a
48
+ * swallowed limit is still a limit.
49
+ *
50
+ * ### Why it is an interface and not a class everywhere
51
+ *
52
+ * Charge points below the layer that decides the budget accept the **interface**
53
+ * as a constructor dependency, and the composition root hands them the one counter
54
+ * for the invocation. Nothing that only spends subrequests constructs a meter.
55
+ */
56
+ /**
57
+ * What spent the subrequests, for the operator-facing breakdown.
58
+ *
59
+ * `d1` is separated from `kv` and `fetch` because the three have different
60
+ * remedies: a chunk that ran out on `fetch` needs a bigger request budget on a
61
+ * Paid plan, and one that ran out on `d1` needs a smaller page size. Collapsing
62
+ * them into a single number is how the ceiling became invisible in the first
63
+ * place.
64
+ */
65
+ type SubrequestKind = 'd1' | 'kv' | 'fetch' | 'rpc' | 'secret';
66
+ declare const SUBREQUEST_KINDS: readonly SubrequestKind[];
67
+ /**
68
+ * The counter every charge point writes to.
69
+ *
70
+ * A method rather than a mutable field so a charge point can be handed the
71
+ * *interface* and the test that owns the ceiling keeps the only real instance.
72
+ * `charge` is the whole write surface; `spent` and `byKind` are the only read
73
+ * surface a charge point needs.
74
+ */
75
+ interface SubrequestMeter {
76
+ /**
77
+ * Record `count` subrequests issued.
78
+ *
79
+ * Never throws and never refuses. A meter that could reject would turn "the
80
+ * budget ran out" into an error at a charge point deep inside a DAO, which is
81
+ * the one place that cannot afford to decide anything — the decision belongs
82
+ * *before* the work, in `canAfford`.
83
+ */
84
+ charge(count?: number, kind?: SubrequestKind): void;
85
+ /**
86
+ * Subrequests issued so far in this invocation.
87
+ */
88
+ readonly spent: number;
89
+ /**
90
+ * The ceiling this counter enforces, or `Infinity` when it enforces none.
91
+ */
92
+ readonly ceiling: number;
93
+ /**
94
+ * How many more fit, floored at zero.
95
+ */
96
+ readonly remaining: number;
97
+ /**
98
+ * Whether the ceiling has been reached.
99
+ */
100
+ readonly exhausted: boolean;
101
+ /**
102
+ * Whether `count` more would still fit.
103
+ */
104
+ canAfford(count?: number): boolean;
105
+ /**
106
+ * Lower this meter's effective ceiling for the current unit of work. Never
107
+ * raises it — a narrower budget may only tighten the platform's ceiling, so a
108
+ * caller asking for more than the platform allows is clamped rather than
109
+ * trusted. The same asymmetry as everywhere else here: an over-count costs
110
+ * throughput, an under-count costs an invocation.
111
+ */
112
+ setCeiling(limit: number): void;
113
+ }
114
+ /**
115
+ * One counter per Worker invocation.
116
+ *
117
+ * `reset()` rather than a fresh instance per chunk because the counter is owned by
118
+ * the **request scope**, which is created once per invocation: a chunk does not own
119
+ * the budget, it borrows the invocation's. Constructing a new counter per chunk
120
+ * would be a second place that decides what the ceiling is.
121
+ */
122
+ declare class SubrequestCounter implements SubrequestMeter {
123
+ private readonly limit;
124
+ private counts;
125
+ /**
126
+ * Spend per kind, so a result can report *what* ran out rather than only that
127
+ * something did. A single total is what made this ceiling un-diagnosable: the
128
+ * operator saw "paused" and the possible causes have different fixes.
129
+ */
130
+ private readonly byKindSpent;
131
+ /**
132
+ * The platform's ceiling, or a narrower one `setCeiling` imposed for the current
133
+ * unit of work. Restored to `limit` by `reset()`, so a narrowed ceiling cannot
134
+ * leak past the chunk that asked for it.
135
+ */
136
+ private effective;
137
+ constructor(limit?: number);
138
+ charge(count?: number, kind?: SubrequestKind): void;
139
+ get spent(): number;
140
+ /**
141
+ * The **effective** ceiling: the platform's, or a narrower one a caller imposed.
142
+ */
143
+ get ceiling(): number;
144
+ get remaining(): number;
145
+ get exhausted(): boolean;
146
+ canAfford(count?: number): boolean;
147
+ setCeiling(limit: number): void;
148
+ /**
149
+ * Zero the count and restore the platform's ceiling.
150
+ *
151
+ * One method rather than a public `counts` field so "start measuring again" is a
152
+ * named operation, and so the per-kind breakdown cannot drift out of step with
153
+ * the total — exactly the kind of second-answer defect this file exists to
154
+ * prevent. The ceiling is **restored**, not kept: a narrowed ceiling belongs to
155
+ * the unit of work that asked for it.
156
+ */
157
+ reset(): void;
158
+ /**
159
+ * Spend per kind, as a plain object. A copy, so a caller cannot reach in and
160
+ * change the totals the operator is shown.
161
+ */
162
+ breakdown(): Record<SubrequestKind, number>;
163
+ }
164
+ /**
165
+ * A meter that enforces nothing, for a caller that has not been given one.
166
+ *
167
+ * `Infinity` rather than a small number, because the alternative is a default
168
+ * ceiling that a test would satisfy by accident — the failure this file is about
169
+ * is a path that spends nothing because nothing was watching it, and a limit of 50
170
+ * would hide that behind a passing assertion.
171
+ */
172
+ declare const UNMETERED_SUBREQUESTS: SubrequestMeter;
173
+ /**
174
+ * What an invocation spent, broken down.
175
+ *
176
+ * A total alone is what made this ceiling undiagnosable. An operator whose scan
177
+ * keeps pausing was told "paused" and nothing else, while the causes have
178
+ * different fixes.
179
+ */
180
+ interface SubrequestSpend extends Record<SubrequestKind, number> {
181
+ /**
182
+ * Every subrequest spent, whatever its kind. The number the ceiling is enforced
183
+ * against.
184
+ */
185
+ readonly total: number;
186
+ }
187
+ /**
188
+ * Flatten a meter's breakdown into a report, total included.
189
+ *
190
+ * A function rather than a method on the counter so the *shape* the rest of the
191
+ * system passes around — including the zeroes for kinds that spent nothing — is
192
+ * built in one place. A caller that assembled it by hand would omit an absent kind,
193
+ * and the operator surface would render a missing field as a missing measurement
194
+ * rather than as `0`.
195
+ */
196
+ declare function subrequestSpend(byKind: Record<SubrequestKind, number>, total: number): SubrequestSpend;
197
+ /**
198
+ * Nothing spent, for a code path that did no work — so a zero report is a
199
+ * *measurement* rather than an absent field.
200
+ */
201
+ declare const NO_SUBREQUESTS_SPENT: SubrequestSpend;
202
+ export { SubrequestCounter, UNMETERED_SUBREQUESTS, SUBREQUEST_KINDS, subrequestSpend, NO_SUBREQUESTS_SPENT };
203
+ export type { SubrequestMeter, SubrequestKind, SubrequestSpend };
204
+ //# sourceMappingURL=SubrequestMeter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubrequestMeter.d.ts","sourceRoot":"","sources":["../src/SubrequestMeter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH;;;;;;;;GAQG;AACH,KAAK,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,GAAG,QAAQ,CAAC;AAE/D,QAAA,MAAM,gBAAgB,EAAE,SAAS,cAAc,EAA2C,CAAC;AAE3F;;;;;;;GAOG;AACH,UAAU,eAAe;IACvB;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACpD;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;OAEG;IACH,SAAS,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnC;;;;;;OAMG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED;;;;;;;GAOG;AACH,cAAM,iBAAkB,YAAW,eAAe;IAgBpC,OAAO,CAAC,QAAQ,CAAC,KAAK;IAflC,OAAO,CAAC,MAAM,CAAa;IAE3B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiF;IAC7G;;;;OAIG;IACH,OAAO,CAAC,SAAS,CAAS;gBAEG,KAAK,GAAE,MAAiB;IAI9C,MAAM,CAAC,KAAK,SAAI,EAAE,IAAI,GAAE,cAAqB,GAAG,IAAI;IAM3D,IAAW,KAAK,IAAI,MAAM,CAEzB;IAED;;OAEG;IACH,IAAW,OAAO,IAAI,MAAM,CAE3B;IAED,IAAW,SAAS,IAAI,MAAM,CAG7B;IAED,IAAW,SAAS,IAAI,OAAO,CAE9B;IAEM,SAAS,CAAC,KAAK,SAAI,GAAG,OAAO;IAI7B,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAStC;;;;;;;;OAQG;IACI,KAAK,IAAI,IAAI;IAMpB;;;OAGG;IACI,SAAS,IAAI,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC;CAGnD;AAED;;;;;;;GAOG;AACH,QAAA,MAAM,qBAAqB,EAAE,eAAiD,CAAC;AAE/E;;;;;;GAMG;AACH,UAAU,eAAgB,SAAQ,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC;IAC9D;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,iBAAS,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,EAAE,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAE/F;AAED;;;GAGG;AACH,QAAA,MAAM,oBAAoB,EAAE,eAAmF,CAAC;AAEhH,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,CAAC;AAC7G,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * What one Worker invocation may spend, counted.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Taken from Edge-Sonic's `packages/shared/src/utils/SubrequestMeter.ts`. It is
7
+ * the only subrequest-accounting helper among the nine repos and the most
8
+ * heavily measured; it is reproduced with its code unchanged and its prose
9
+ * generalised away from Edge-Sonic's own layer names and issue docs. No other
10
+ * repo carries a competing copy.
11
+ *
12
+ * ### Why this exists at all
13
+ *
14
+ * Cloudflare counts a **subrequest** per invocation and kills the invocation when
15
+ * the count is exceeded — it does not throw something a `catch` can see, so the
16
+ * whole invocation dies with `Too many subrequests by single Worker invocation`
17
+ * and everything after the ceiling is lost. The only defence is to never issue the
18
+ * request that crosses it.
19
+ *
20
+ * A subrequest is not only `fetch`. The platform's own D1 limits page states:
21
+ *
22
+ * > Queries per Worker invocation (read subrequest limits) — 1000 (Workers Paid) / **50 (Free)**
23
+ *
24
+ * So a D1 query is a subrequest, and on the Free plan a `KV` read, a Durable
25
+ * Object RPC and a Secrets Store read are subrequests too. Budgeting **only**
26
+ * `fetch` shipped a real bug: a scan chunk cost ~40 counted requests and ~200
27
+ * uncounted ones, every chunk died at the ceiling, and the scan only ever
28
+ * "finished" because each dead invocation left a little progress behind.
29
+ *
30
+ * ### Why the count is pessimistic
31
+ *
32
+ * The platform does not say whether a D1 `batch()` of N statements costs 1 or N.
33
+ * The D1 page counts *queries*, which reads as N; the Workers page counts
34
+ * subrequests, which reads as 1. So the meter charges **one per statement**, which
35
+ * is the reading that cannot kill an invocation. If the friendlier reading turns
36
+ * out to be right, the meter over-counts and a chunk does slightly less work per
37
+ * poll — a cost in throughput, never a correctness or availability problem. That
38
+ * asymmetry is the whole reason to choose it: an over-count is a slow scan, an
39
+ * under-count is a dead one.
40
+ *
41
+ * Measured on a Free account on 2026-10-05 there are two budgets: external `fetch`
42
+ * is 50 and **D1 statements are 1,000**, in a separate pool — 1,000 D1 statements
43
+ * and 50 outbound requests in one invocation survive together. A D1 overrun
44
+ * *throws* and an ordinary `catch` sees it; the external ceiling does not. This
45
+ * counter's single ceiling covers both, which is the conservative choice: bounding
46
+ * both by the resource that cannot be handled when it runs out is the choice that
47
+ * cannot take the product down. That is not a licence to catch and continue — a
48
+ * swallowed limit is still a limit.
49
+ *
50
+ * ### Why it is an interface and not a class everywhere
51
+ *
52
+ * Charge points below the layer that decides the budget accept the **interface**
53
+ * as a constructor dependency, and the composition root hands them the one counter
54
+ * for the invocation. Nothing that only spends subrequests constructs a meter.
55
+ */
56
+ const SUBREQUEST_KINDS = ['d1', 'kv', 'fetch', 'rpc', 'secret'];
57
+ /**
58
+ * One counter per Worker invocation.
59
+ *
60
+ * `reset()` rather than a fresh instance per chunk because the counter is owned by
61
+ * the **request scope**, which is created once per invocation: a chunk does not own
62
+ * the budget, it borrows the invocation's. Constructing a new counter per chunk
63
+ * would be a second place that decides what the ceiling is.
64
+ */
65
+ class SubrequestCounter {
66
+ limit;
67
+ counts = 0;
68
+ /**
69
+ * Spend per kind, so a result can report *what* ran out rather than only that
70
+ * something did. A single total is what made this ceiling un-diagnosable: the
71
+ * operator saw "paused" and the possible causes have different fixes.
72
+ */
73
+ byKindSpent = { d1: 0, kv: 0, fetch: 0, rpc: 0, secret: 0 };
74
+ /**
75
+ * The platform's ceiling, or a narrower one `setCeiling` imposed for the current
76
+ * unit of work. Restored to `limit` by `reset()`, so a narrowed ceiling cannot
77
+ * leak past the chunk that asked for it.
78
+ */
79
+ effective;
80
+ constructor(limit = Infinity) {
81
+ this.limit = limit;
82
+ this.effective = limit;
83
+ }
84
+ charge(count = 1, kind = 'd1') {
85
+ if (count <= 0)
86
+ return;
87
+ this.counts += count;
88
+ this.byKindSpent[kind] += count;
89
+ }
90
+ get spent() {
91
+ return this.counts;
92
+ }
93
+ /**
94
+ * The **effective** ceiling: the platform's, or a narrower one a caller imposed.
95
+ */
96
+ get ceiling() {
97
+ return this.effective;
98
+ }
99
+ get remaining() {
100
+ if (this.effective === Infinity)
101
+ return Infinity;
102
+ return Math.max(0, this.effective - this.counts);
103
+ }
104
+ get exhausted() {
105
+ return this.counts >= this.effective;
106
+ }
107
+ canAfford(count = 1) {
108
+ return this.counts + count <= this.effective;
109
+ }
110
+ setCeiling(limit) {
111
+ // An unmetered counter stays unmetered: a ceiling of `Infinity` cannot be
112
+ // lowered to a finite one, because the callers that would impose one are the
113
+ // *production* ones and a test double asking for a bound must not start
114
+ // refusing statements.
115
+ if (!Number.isFinite(this.effective) || !Number.isFinite(limit))
116
+ return;
117
+ this.effective = Math.max(0, Math.min(this.effective, Math.floor(limit)));
118
+ }
119
+ /**
120
+ * Zero the count and restore the platform's ceiling.
121
+ *
122
+ * One method rather than a public `counts` field so "start measuring again" is a
123
+ * named operation, and so the per-kind breakdown cannot drift out of step with
124
+ * the total — exactly the kind of second-answer defect this file exists to
125
+ * prevent. The ceiling is **restored**, not kept: a narrowed ceiling belongs to
126
+ * the unit of work that asked for it.
127
+ */
128
+ reset() {
129
+ this.counts = 0;
130
+ this.effective = this.limit;
131
+ for (const kind of SUBREQUEST_KINDS)
132
+ this.byKindSpent[kind] = 0;
133
+ }
134
+ /**
135
+ * Spend per kind, as a plain object. A copy, so a caller cannot reach in and
136
+ * change the totals the operator is shown.
137
+ */
138
+ breakdown() {
139
+ return { ...this.byKindSpent };
140
+ }
141
+ }
142
+ /**
143
+ * A meter that enforces nothing, for a caller that has not been given one.
144
+ *
145
+ * `Infinity` rather than a small number, because the alternative is a default
146
+ * ceiling that a test would satisfy by accident — the failure this file is about
147
+ * is a path that spends nothing because nothing was watching it, and a limit of 50
148
+ * would hide that behind a passing assertion.
149
+ */
150
+ const UNMETERED_SUBREQUESTS = new SubrequestCounter(Infinity);
151
+ /**
152
+ * Flatten a meter's breakdown into a report, total included.
153
+ *
154
+ * A function rather than a method on the counter so the *shape* the rest of the
155
+ * system passes around — including the zeroes for kinds that spent nothing — is
156
+ * built in one place. A caller that assembled it by hand would omit an absent kind,
157
+ * and the operator surface would render a missing field as a missing measurement
158
+ * rather than as `0`.
159
+ */
160
+ function subrequestSpend(byKind, total) {
161
+ return { total, d1: byKind.d1, kv: byKind.kv, fetch: byKind.fetch, rpc: byKind.rpc, secret: byKind.secret };
162
+ }
163
+ /**
164
+ * Nothing spent, for a code path that did no work — so a zero report is a
165
+ * *measurement* rather than an absent field.
166
+ */
167
+ const NO_SUBREQUESTS_SPENT = subrequestSpend({ d1: 0, kv: 0, fetch: 0, rpc: 0, secret: 0 }, 0);
168
+ export { SubrequestCounter, UNMETERED_SUBREQUESTS, SUBREQUEST_KINDS, subrequestSpend, NO_SUBREQUESTS_SPENT };
169
+ //# sourceMappingURL=SubrequestMeter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SubrequestMeter.js","sourceRoot":"","sources":["../src/SubrequestMeter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Time-zone validation and wall-clock helpers.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Taken from Mail-Otter's `TimeZoneUtil.ts` (the only repo that factored the
7
+ * zone-local day start behind a helper). It is well-documented and IANA-correct;
8
+ * no other repo carries a competing copy, so it is kept with one fix.
9
+ *
10
+ * ## Design decision
11
+ *
12
+ * Mail-Otter's `normalize` used `this.isValid(timeZone) ? (timeZone as string).trim() : …`,
13
+ * a type-cast to paper over the fact that `isValid` returns `boolean` and so
14
+ * does not narrow `string | null | undefined`. The kit bans that pattern (no
15
+ * non-null assertions, no `as`-laundering), so the runtime check is a module-level
16
+ * **type guard** and `normalize` runs on the narrowed value. Same behaviour, no
17
+ * cast.
18
+ *
19
+ * `getLocalDayStartUnixSeconds` keeps the two-pass DST handling and the warning
20
+ * not to build it with `new Date('YYYY-MM-DDT00:00:00')` — a zone-less date-time
21
+ * string parses in the runtime's zone, which is always UTC in Workers, silently
22
+ * ignoring `timeZone` entirely.
23
+ */
24
+ declare const DEFAULT_TIME_ZONE = "UTC";
25
+ declare class TimeZoneUtil {
26
+ static isValid(timeZone: string | null | undefined): boolean;
27
+ static normalize(timeZone: string | null | undefined): string;
28
+ static todayInZone(timeZone: string | null | undefined, now?: Date): string;
29
+ /**
30
+ * Unix seconds of local midnight for the calendar day containing `now`.
31
+ *
32
+ * Two passes are needed because the zone offset depends on the instant: read
33
+ * the wall clock in the target zone, treat that as UTC to derive the offset,
34
+ * then re-anchor local midnight with the offset. The offset is sampled at
35
+ * `now` rather than at the target midnight, which is exact except within a
36
+ * few hours of a DST transition — irrelevant for a 24-hour window.
37
+ *
38
+ * Do NOT build this with `new Date('YYYY-MM-DDT00:00:00')`. A date-time
39
+ * string with no zone designator parses in the *runtime's* zone, and the
40
+ * Workers runtime is always UTC, which silently ignores `timeZone` entirely.
41
+ *
42
+ * An unknown zone falls back to UTC rather than throwing.
43
+ */
44
+ static getLocalDayStartUnixSeconds(now: Date, timeZone: string | null | undefined): number;
45
+ }
46
+ export { TimeZoneUtil, DEFAULT_TIME_ZONE };
47
+ //# sourceMappingURL=TimeZoneUtil.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeZoneUtil.d.ts","sourceRoot":"","sources":["../src/TimeZoneUtil.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,QAAA,MAAM,iBAAiB,QAAQ,CAAC;AAkChC,cAAM,YAAY;WACF,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;WAIrD,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;WAItD,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM;IAK9F;;;;;;;;;;;;;;OAcG;WACW,2BAA2B,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM;CAelG;AAED,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Time-zone validation and wall-clock helpers.
3
+ *
4
+ * ## Provenance
5
+ *
6
+ * Taken from Mail-Otter's `TimeZoneUtil.ts` (the only repo that factored the
7
+ * zone-local day start behind a helper). It is well-documented and IANA-correct;
8
+ * no other repo carries a competing copy, so it is kept with one fix.
9
+ *
10
+ * ## Design decision
11
+ *
12
+ * Mail-Otter's `normalize` used `this.isValid(timeZone) ? (timeZone as string).trim() : …`,
13
+ * a type-cast to paper over the fact that `isValid` returns `boolean` and so
14
+ * does not narrow `string | null | undefined`. The kit bans that pattern (no
15
+ * non-null assertions, no `as`-laundering), so the runtime check is a module-level
16
+ * **type guard** and `normalize` runs on the narrowed value. Same behaviour, no
17
+ * cast.
18
+ *
19
+ * `getLocalDayStartUnixSeconds` keeps the two-pass DST handling and the warning
20
+ * not to build it with `new Date('YYYY-MM-DDT00:00:00')` — a zone-less date-time
21
+ * string parses in the runtime's zone, which is always UTC in Workers, silently
22
+ * ignoring `timeZone` entirely.
23
+ */
24
+ const DEFAULT_TIME_ZONE = 'UTC';
25
+ const WALL_CLOCK_FORMAT = {
26
+ hour12: false,
27
+ year: 'numeric',
28
+ month: '2-digit',
29
+ day: '2-digit',
30
+ hour: '2-digit',
31
+ minute: '2-digit',
32
+ second: '2-digit',
33
+ };
34
+ /**
35
+ * A run-time check that narrows to `string`, so callers do not have to cast.
36
+ *
37
+ * `new Intl.DateTimeFormat` throws a `RangeError` on an unknown zone; the guard
38
+ * catches it and answers `false` for `null`/`undefined`/non-strings in one place.
39
+ */
40
+ function isTimeZoneString(value) {
41
+ if (!value || typeof value !== 'string')
42
+ return false;
43
+ const trimmed = value.trim();
44
+ if (trimmed === '')
45
+ return false;
46
+ // Trim-then-validate, so `normalize`'s `.trim()` is load-bearing: Mail-Otter's
47
+ // guard validated the *untrimmed* string, so a whitespace-padded IANA zone
48
+ // threw in `Intl` and silently fell back to UTC. A padded zone is a typo, not an
49
+ // unknown zone, so it is trimmed and accepted rather than defaulted.
50
+ try {
51
+ new Intl.DateTimeFormat('en-US', { timeZone: trimmed });
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ class TimeZoneUtil {
59
+ static isValid(timeZone) {
60
+ return isTimeZoneString(timeZone);
61
+ }
62
+ static normalize(timeZone) {
63
+ return isTimeZoneString(timeZone) ? timeZone.trim() : DEFAULT_TIME_ZONE;
64
+ }
65
+ static todayInZone(timeZone, now = new Date()) {
66
+ const zone = this.normalize(timeZone);
67
+ return new Intl.DateTimeFormat('en-CA', { timeZone: zone, year: 'numeric', month: '2-digit', day: '2-digit' }).format(now);
68
+ }
69
+ /**
70
+ * Unix seconds of local midnight for the calendar day containing `now`.
71
+ *
72
+ * Two passes are needed because the zone offset depends on the instant: read
73
+ * the wall clock in the target zone, treat that as UTC to derive the offset,
74
+ * then re-anchor local midnight with the offset. The offset is sampled at
75
+ * `now` rather than at the target midnight, which is exact except within a
76
+ * few hours of a DST transition — irrelevant for a 24-hour window.
77
+ *
78
+ * Do NOT build this with `new Date('YYYY-MM-DDT00:00:00')`. A date-time
79
+ * string with no zone designator parses in the *runtime's* zone, and the
80
+ * Workers runtime is always UTC, which silently ignores `timeZone` entirely.
81
+ *
82
+ * An unknown zone falls back to UTC rather than throwing.
83
+ */
84
+ static getLocalDayStartUnixSeconds(now, timeZone) {
85
+ const zone = this.normalize(timeZone);
86
+ const parts = new Intl.DateTimeFormat('en-CA', { timeZone: zone, ...WALL_CLOCK_FORMAT }).formatToParts(now);
87
+ const part = (type) => parts.find((p) => p.type === type)?.value ?? '0';
88
+ const year = Number(part('year'));
89
+ const month = Number(part('month'));
90
+ const day = Number(part('day'));
91
+ // Some ICU builds report midnight as hour 24 under `hour12: false`.
92
+ const hour = Number(part('hour')) % 24;
93
+ const minute = Number(part('minute'));
94
+ const second = Number(part('second'));
95
+ const wallClockAsUtcMs = Date.UTC(year, month - 1, day, hour, minute, second);
96
+ const zoneOffsetMs = wallClockAsUtcMs - now.getTime();
97
+ return Math.floor((Date.UTC(year, month - 1, day, 0, 0, 0) - zoneOffsetMs) / 1000);
98
+ }
99
+ }
100
+ export { TimeZoneUtil, DEFAULT_TIME_ZONE };
101
+ //# sourceMappingURL=TimeZoneUtil.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeZoneUtil.js","sourceRoot":"","sources":["../src/TimeZoneUtil.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import { type Clock } from './Clock';
2
+ /**
3
+ * Unix timestamps, in seconds and milliseconds.
4
+ *
5
+ * ## Provenance
6
+ *
7
+ * Converged from all nine `TimestampUtil.ts` copies. They fall into three
8
+ * shapes: Durable-DAV's minimal two-method set (`getCurrentUnixTimestampInSeconds`
9
+ * + `addDays`) with a deliberate "seconds everywhere" argument; AWS's compact set
10
+ * plus an **injectable `Clock`** on the current-time readers; and the full
11
+ * ChordDHT/Mail-Otter set (`…InMilliseconds`, `addMinutes`, `addHours`,
12
+ * `subtractMinutes`, `subtractDays`, `convertIso…`) reading `Date.now()` directly.
13
+ *
14
+ * ## Design decisions
15
+ *
16
+ * - The **full method set** is kept: the union of the three shapes loses nothing
17
+ * and each method exists because at least two repos independently reached for
18
+ * it.
19
+ * - The current-time reads go through an **injectable `Clock`** defaulting to
20
+ * `SYSTEM_CLOCK` (AWS's shape). Reading the clock through one seam lets a test
21
+ * pin "now" without racing `Date.now()`, and a caller cannot accidentally store
22
+ * milliseconds in a column compared against seconds.
23
+ * - Seconds is the storage unit (Durable-DAV's argument): where millisecond
24
+ * precision is genuinely wanted — ordering within a single pass — reach for
25
+ * `…InMilliseconds` (or `clock.now()`) explicitly, so the unit is visible at the
26
+ * call site. `add*`/`subtract*` operate on the **second** unit and are pure
27
+ * calendar arithmetic, never `Date` arithmetic, so they cannot pick up a
28
+ * timezone or a DST transition.
29
+ */
30
+ declare class TimestampUtil {
31
+ /**
32
+ * Now, in whole milliseconds.
33
+ *
34
+ * @param clock Inject a `FixedClock` to pin "now" in a test; production leaves
35
+ * it defaulted.
36
+ */
37
+ static getCurrentUnixTimestampInMilliseconds(clock?: Clock): number;
38
+ /**
39
+ * Now, in whole seconds. The default storage unit.
40
+ *
41
+ * @param clock Inject a `FixedClock` to pin "now" in a test; production leaves
42
+ * it defaulted.
43
+ */
44
+ static getCurrentUnixTimestampInSeconds(clock?: Clock): number;
45
+ static addMinutes(timestamp: number, minutes: number): number;
46
+ static addHours(timestamp: number, hours: number): number;
47
+ static addDays(timestamp: number, days: number): number;
48
+ static subtractMinutes(timestamp: number, minutes: number): number;
49
+ static subtractDays(timestamp: number, days: number): number;
50
+ static convertIsoToUnixTimestampInSeconds(isoString: string): number;
51
+ }
52
+ export { TimestampUtil };
53
+ //# sourceMappingURL=TimestampUtil.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimestampUtil.d.ts","sourceRoot":"","sources":["../src/TimestampUtil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,cAAM,aAAa;IACjB;;;;;OAKG;WACW,qCAAqC,CAAC,KAAK,GAAE,KAAoB,GAAG,MAAM;IAIxF;;;;;OAKG;WACW,gCAAgC,CAAC,KAAK,GAAE,KAAoB,GAAG,MAAM;WAIrE,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;WAItD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;WAIlD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;WAIhD,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;WAI3D,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM;WAIrD,kCAAkC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAG5E;AAED,OAAO,EAAE,aAAa,EAAE,CAAC"}