@jarenjs/locales 0.49.2 → 0.66.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.
package/README.md CHANGED
@@ -139,6 +139,59 @@ object, and a bundler drops the provider from a bundle that never calls
139
139
  it. ICU has no date-format display names, so that member carries the
140
140
  repository's English ones unless `options.formatNames` overrides them.
141
141
 
142
+ ### The optional `Intl` zone provider
143
+
144
+ The same idea one seam over. `@jarenjs/core/series` reads its calendar
145
+ boundaries on a clock, and a clock that is a named zone needs a
146
+ `provider` — a tzdb — that the kernel refuses to bundle, because a
147
+ bundled table is megabytes that go stale on a government's timetable.
148
+ `createIntlZoneProvider(options?)` is that provider, over the host's ICU:
149
+
150
+ ```js
151
+ import { createIntlZoneProvider } from '@jarenjs/locales/intl-zones';
152
+ import { resolveClock, compileBuckets } from '@jarenjs/core/series';
153
+
154
+ const provider = createIntlZoneProvider();
155
+
156
+ const clock = resolveClock({ zone: 'Europe/Amsterdam', provider, disambiguation: 'later' });
157
+ clock.epochOf({ year: 2026, month: 3, day: 29, hours: 2, minutes: 30, seconds: 0 });
158
+ // 2026-03-29T01:30:00Z — 02:30 never happened that morning; 'later' is 03:30 CEST
159
+
160
+ compileBuckets('P1D', { zone: 'Australia/Adelaide', provider }).floor(Date.UTC(2026, 3, 5, 12));
161
+ // 2026-04-04T13:30:00Z — local midnight on the day the clock went back, 25 hours long
162
+ ```
163
+
164
+ The seam is unchanged: `@jarenjs/core` still bundles no zone data, a
165
+ named zone with no provider is still a refusal, and this module is what
166
+ a host passes to answer it. Three rules hold it honest. **It never reads
167
+ the host's own zone** — every call names its zone, and there is no
168
+ default that asks the process where it is, because that hidden clock is
169
+ exactly what the seam exists to forbid. **An unknown name is a refusal
170
+ naming it**, never a quiet UTC that is right for eight months of the
171
+ year. And **the hard half is answered exactly**: on a spring-forward day
172
+ 02:30 never happens and on a fall-back day it happens twice, so
173
+ `toEpoch(parts, zone, disambiguation)` verifies every candidate instant
174
+ by reading its wall clock back. Two that read back are a fold, and
175
+ `'earlier'`/`'later'` are the smaller and the larger; none is a gap, and
176
+ `'earlier'` is the wall clock read with the offset in force after the
177
+ transition (that far *before* the gap), `'later'` the reading with the
178
+ offset before it (that far after) — one transition apart either way.
179
+ `'reject'`, the kernel's default, answers both with `NaN`, which the
180
+ kernel turns into a refusal naming the local time. Offsets are derived
181
+ from the wall clock rather than parsed from a `GMT+02:00` string, so a
182
+ historical `-00:44:30` is exact.
183
+
184
+ Like `createIntlDateLocale`, it is opt-in for the reason the header of
185
+ that module gives: ICU output moves between Node versions and between a
186
+ browser and a server. Here that means the *tzdata* — a zone whose rules
187
+ changed last year answers differently on a host whose ICU predates the
188
+ change — so a consumer that needs the same instant on every host pins
189
+ the tzdata its hosts run. Nothing runs at module load, and a bundler
190
+ drops the provider from a bundle that never calls it. Formatters are
191
+ built once per zone and kept in a bounded, least-recently-used cache of
192
+ `ZONE_CACHE_LIMIT` zones (`options.zones` resizes it), so a generated
193
+ stream of zone names cannot grow it without limit.
194
+
142
195
  ## Authoring a pack
143
196
 
144
197
  A catalog is a plain flat object; each entry is either a **template
@@ -212,3 +265,29 @@ Rules of the road:
212
265
 
213
266
  The named export is the locale tag camel-cased (`zhTW`), the subpath keeps
214
267
  the tag itself (`@jarenjs/locales/zh-tw`).
268
+
269
+ ## Exports
270
+
271
+ Every subpath a consumer can import, derived from the manifest by
272
+ `npm run docs:derive` (`npm run docs:check` fails when the two drift):
273
+
274
+ <!--fact:exports.locales-->
275
+ | Import | Kind | Declarations |
276
+ |---|---|---|
277
+ | `@jarenjs/locales` | JavaScript | declared |
278
+ | `@jarenjs/locales/dates` | JavaScript | declared |
279
+ | `@jarenjs/locales/intl-dates` | JavaScript | declared |
280
+ | `@jarenjs/locales/intl-zones` | JavaScript | declared |
281
+ | `@jarenjs/locales/ar` | JavaScript | declared |
282
+ | `@jarenjs/locales/de` | JavaScript | declared |
283
+ | `@jarenjs/locales/es` | JavaScript | declared |
284
+ | `@jarenjs/locales/fr` | JavaScript | declared |
285
+ | `@jarenjs/locales/ja` | JavaScript | declared |
286
+ | `@jarenjs/locales/ko` | JavaScript | declared |
287
+ | `@jarenjs/locales/nl` | JavaScript | declared |
288
+ | `@jarenjs/locales/pt` | JavaScript | declared |
289
+ | `@jarenjs/locales/ru` | JavaScript | declared |
290
+ | `@jarenjs/locales/tr` | JavaScript | declared |
291
+ | `@jarenjs/locales/zh-tw` | JavaScript | declared |
292
+ | `@jarenjs/locales/package.json` | metadata | — |
293
+ <!--/fact-->
@@ -17,7 +17,11 @@
17
17
  * formatter and a UI read; importing that subpath directly costs no
18
18
  * `Intl` construction at all, which is what keeps server-rendered output
19
19
  * byte-stable. `createIntlDateLocale` (`./intl-dates`) is the opt-in
20
- * provider for hosts that want the platform's locales instead.
20
+ * provider for hosts that want the platform's locales instead, and
21
+ * `createIntlZoneProvider` (`./intl-zones`) is its twin one seam over:
22
+ * the zone provider `@jarenjs/core/series` takes for a named zone, over
23
+ * the host's ICU. It is reached only by its own subpath and an explicit
24
+ * call, which is what keeps the default path free of `Intl` work.
21
25
  */
22
26
  export { dateMessagesEn, compileDateLocale, RELATIVE_UNITS } from './dates.js';
23
27
  export { createIntlDateLocale } from './intl-dates.js';
@@ -0,0 +1,41 @@
1
+ /**
2
+ * How many zones' formatters stay resident at once. A formatter is
3
+ * expensive to build and cheap to reuse, and a host serving a handful of
4
+ * zones wants each built once; the bound is what keeps a generated or
5
+ * hostile stream of zone names from growing the map without limit.
6
+ */
7
+ export declare const ZONE_CACHE_LIMIT = 32;
8
+ /**
9
+ * Build a zone provider from the host's ICU time-zone data.
10
+ *
11
+ * `toParts(epoch, zone)` is a formatted read. `toEpoch(parts, zone,
12
+ * disambiguation)` inverts a function that is neither injective nor
13
+ * total: it guesses an instant by reading the wall clock as UTC,
14
+ * corrects it by the offset in force there and again by the offset in
15
+ * force at the corrected instant, adds the offsets in force a day either
16
+ * side so both sides of any transition are represented, and then
17
+ * verifies every distinct candidate by reading its wall clock back.
18
+ * Exactly one candidate that reads back as asked is the instant. Two
19
+ * are a fold - the hour that happened twice - and `earlier` and `later`
20
+ * are the smaller and the larger. None is a gap - the hour that never
21
+ * happened - and `earlier` is the wall clock read with the offset in
22
+ * force after the transition (the instant that far before the gap),
23
+ * `later` the reading with the offset before it (that far after); the
24
+ * two are one transition apart. `reject`, the kernel's default, answers
25
+ * either case with `NaN`, which `@jarenjs/core/series` turns into a
26
+ * refusal naming the local time.
27
+ *
28
+ * An unknown zone name is a refusal naming it, never a quiet UTC, and
29
+ * no call reads the host's own zone.
30
+ *
31
+ * @param {{ zones?: number }} [options] - `zones`: how many zones'
32
+ * formatters stay resident (default `ZONE_CACHE_LIMIT`)
33
+ * @returns {import('@jarenjs/core/series').ZoneProvider}
34
+ * @example
35
+ * const provider = createIntlZoneProvider();
36
+ * resolveClock({ zone: 'Europe/Amsterdam', provider, disambiguation: 'later' });
37
+ * compileBuckets('P1M', { zone: 'Australia/Adelaide', provider });
38
+ */
39
+ export declare function createIntlZoneProvider(options?: {
40
+ zones?: number;
41
+ }): import('@jarenjs/core/series').ZoneProvider;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jarenjs/locales",
3
3
  "private": false,
4
- "version": "0.49.2",
4
+ "version": "0.66.1",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
7
7
  "types": "./dist/types/index.d.ts",
@@ -19,6 +19,10 @@
19
19
  "types": "./dist/types/intl-dates.d.ts",
20
20
  "default": "./src/intl-dates.js"
21
21
  },
22
+ "./intl-zones": {
23
+ "types": "./dist/types/intl-zones.d.ts",
24
+ "default": "./src/intl-zones.js"
25
+ },
22
26
  "./ar": {
23
27
  "types": "./dist/types/ar.d.ts",
24
28
  "default": "./src/ar.js"
@@ -98,6 +102,6 @@
98
102
  "prepack": "npm run build:types"
99
103
  },
100
104
  "dependencies": {
101
- "@jarenjs/core": "^0.49.2"
105
+ "@jarenjs/core": "^0.66.1"
102
106
  }
103
107
  }
package/src/ar.js CHANGED
@@ -224,6 +224,8 @@ export const ar = {
224
224
  'contract/seq-regression': 'خالف دفق العملية ⁨{op}⁩ ترتيب seq الخاص به',
225
225
  'contract/stream-error': 'انتهى دفق العملية ⁨{op}⁩ بخطأ من الخادم (⁨{code}⁩)',
226
226
  'contract/heartbeat-missed': 'ظل دفق العملية ⁨{op}⁩ صامتًا لمدة {ms} مللي ثانية',
227
+ 'contract/slow-consumer': 'انتهى دفق العملية ⁨{op}⁩: تأخر المستهلك عن طابوره المحدود',
228
+ 'contract/reconnect-exhausted': 'تعذّر إعادة إنشاء دفق العملية ⁨{op}⁩ بعد {attempts} محاولات (الأخيرة: {lastCode})',
227
229
  //#endregion
228
230
 
229
231
  //#region calendar language (the date names, relative phrasing and
package/src/de.js CHANGED
@@ -167,6 +167,8 @@ export const de = {
167
167
  'contract/seq-regression': 'der Stream der Operation {op} hat seine seq-Reihenfolge verletzt',
168
168
  'contract/stream-error': 'der Stream der Operation {op} endete mit einem Serverfehler ({code})',
169
169
  'contract/heartbeat-missed': 'der Stream der Operation {op} blieb {ms} ms lang still',
170
+ 'contract/slow-consumer': 'der Stream der Operation {op} wurde beendet: der Verbraucher blieb hinter seiner begrenzten Warteschlange zurück',
171
+ 'contract/reconnect-exhausted': 'der Stream der Operation {op} konnte nach {attempts} Versuchen nicht wiederhergestellt werden (zuletzt: {lastCode})',
170
172
  //#endregion
171
173
 
172
174
  //#region calendar language (the date names, relative phrasing and
package/src/es.js CHANGED
@@ -167,6 +167,8 @@ export const es = {
167
167
  'contract/seq-regression': 'el flujo de la operación {op} violó el orden de sus seq',
168
168
  'contract/stream-error': 'el flujo de la operación {op} terminó con un error del servidor ({code})',
169
169
  'contract/heartbeat-missed': 'el flujo de la operación {op} quedó en silencio durante {ms} ms',
170
+ 'contract/slow-consumer': 'el flujo de la operación {op} terminó: el consumidor se quedó atrás de su cola acotada',
171
+ 'contract/reconnect-exhausted': 'el flujo de la operación {op} no pudo restablecerse tras {attempts} intentos (último: {lastCode})',
170
172
  //#endregion
171
173
 
172
174
  //#region calendar language (the date names, relative phrasing and
package/src/fr.js CHANGED
@@ -167,6 +167,8 @@ export const fr = {
167
167
  'contract/seq-regression': "le flux de l'opération {op} a violé l'ordre de ses seq",
168
168
  'contract/stream-error': "le flux de l'opération {op} s'est terminé par une erreur serveur ({code})",
169
169
  'contract/heartbeat-missed': "le flux de l'opération {op} est resté silencieux pendant {ms} ms",
170
+ 'contract/slow-consumer': "le flux de l'opération {op} s'est terminé : le consommateur a pris du retard sur sa file bornée",
171
+ 'contract/reconnect-exhausted': "le flux de l'opération {op} n'a pas pu être rétabli après {attempts} tentatives (dernière : {lastCode})",
170
172
  //#endregion
171
173
 
172
174
  //#region calendar language (the date names, relative phrasing and
package/src/index.js CHANGED
@@ -19,7 +19,11 @@
19
19
  * formatter and a UI read; importing that subpath directly costs no
20
20
  * `Intl` construction at all, which is what keeps server-rendered output
21
21
  * byte-stable. `createIntlDateLocale` (`./intl-dates`) is the opt-in
22
- * provider for hosts that want the platform's locales instead.
22
+ * provider for hosts that want the platform's locales instead, and
23
+ * `createIntlZoneProvider` (`./intl-zones`) is its twin one seam over:
24
+ * the zone provider `@jarenjs/core/series` takes for a named zone, over
25
+ * the host's ICU. It is reached only by its own subpath and an explicit
26
+ * call, which is what keeps the default path free of `Intl` work.
23
27
  */
24
28
 
25
29
  export { dateMessagesEn, compileDateLocale, RELATIVE_UNITS } from './dates.js';
@@ -0,0 +1,271 @@
1
+ //@ts-check
2
+
3
+ /**
4
+ * The opt-in `Intl` zone provider: the `ZoneProvider` the temporal
5
+ * kernel's clock seam asks for, sourced from the host's ICU time-zone
6
+ * data instead of from a database this repository would have to ship.
7
+ *
8
+ * It is a separate module and an explicit call because it is the one
9
+ * thing the default may not be. A named zone in `@jarenjs/core/series`
10
+ * is a refusal until a provider is passed, and that refusal is the
11
+ * point: a tzdb goes stale on a government's timetable, so the only
12
+ * honest copy is the host's own, and the host must say so by
13
+ * constructing one. ICU output also moves between Node versions and
14
+ * between a browser and a server - a zone whose rules changed last
15
+ * year answers differently on a host whose ICU predates the change -
16
+ * so a consumer that needs the same instant on every host pins the
17
+ * tzdata its hosts run, not this module.
18
+ *
19
+ * Nothing here runs at module load: the `Intl` objects are built inside
20
+ * the factory, so importing the module allocates nothing and a bundle
21
+ * that never calls it carries no ICU work.
22
+ *
23
+ * The provider never asks the host what zone it is in. Every call names
24
+ * its zone, and there is no default that reads the process's - that is
25
+ * the hidden clock the seam exists to forbid, and a provider that read
26
+ * it would smuggle the non-determinism back in invisibly.
27
+ */
28
+
29
+ import { createBoundedCache } from '@jarenjs/core/cache';
30
+ import { daysFromCivil } from '@jarenjs/core/dates/civil';
31
+ import { epochOfRFC3339Parts } from '@jarenjs/core/dates/rfc3339';
32
+
33
+ /**
34
+ * How many zones' formatters stay resident at once. A formatter is
35
+ * expensive to build and cheap to reuse, and a host serving a handful of
36
+ * zones wants each built once; the bound is what keeps a generated or
37
+ * hostile stream of zone names from growing the map without limit.
38
+ */
39
+ export const ZONE_CACHE_LIMIT = 32;
40
+
41
+ /** What a caller may ask for when a local time is ambiguous or absent. */
42
+ const DISAMBIGUATION = Object.freeze(['reject', 'earlier', 'later']);
43
+
44
+ const MINUTE = 60000;
45
+ const DAY = 86400000;
46
+
47
+ /** The instants a `Date` can hold: beyond it ICU has nothing to read. */
48
+ const RANGE = 8.64e15;
49
+
50
+ /**
51
+ * Refuse an instant the platform cannot represent by name, rather than
52
+ * letting ICU's own `Invalid time value` — which names nothing — escape.
53
+ * @param {any} epoch
54
+ * @returns {number}
55
+ */
56
+ function checkInstant(epoch) {
57
+ if (typeof epoch !== 'number' || !Number.isFinite(epoch) || Math.abs(epoch) > RANGE)
58
+ throw new TypeError(`an instant is a finite number of epoch milliseconds within ±${RANGE}, not ${epoch}`);
59
+ return epoch;
60
+ }
61
+
62
+ /**
63
+ * The formatter that reads a wall clock in one zone. The locale is
64
+ * pinned to `en-US` with Latin digits because the output is parsed, not
65
+ * shown; the calendar to `gregory` because the kernel's dates are
66
+ * proleptic Gregorian; the hour cycle to `h23` so midnight reads `00`
67
+ * rather than `24`; and the era is read so a year before 1 comes back
68
+ * as the kernel counts it.
69
+ * @param {string} zone
70
+ * @returns {Intl.DateTimeFormat}
71
+ */
72
+ function createFormatter(zone) {
73
+ try {
74
+ return new Intl.DateTimeFormat('en-US', {
75
+ timeZone: zone,
76
+ calendar: 'gregory',
77
+ numberingSystem: 'latn',
78
+ hourCycle: 'h23',
79
+ era: 'short',
80
+ year: 'numeric',
81
+ month: 'numeric',
82
+ day: 'numeric',
83
+ hour: 'numeric',
84
+ minute: 'numeric',
85
+ second: 'numeric',
86
+ });
87
+ }
88
+ catch {
89
+ // ICU throws for a name it does not know; a name it would quietly
90
+ // map onto something else is exactly what the caller must not get
91
+ throw new TypeError(`'${zone}' is not a time zone this host's ICU knows`);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * The wall clock at an instant, and the offset that produced it. The
97
+ * offset is not read from a formatted `GMT+02:00` string but derived:
98
+ * the wall clock re-read as UTC minus the instant is the offset, exact
99
+ * to the second, which is what a historical `-00:44:30` needs.
100
+ * @param {Intl.DateTimeFormat} format
101
+ * @param {number} epoch
102
+ * @returns {{ year: number, month: number, day: number, hours: number,
103
+ * minutes: number, seconds: number, offset: number }}
104
+ */
105
+ function readParts(format, epoch) {
106
+ let year = 0;
107
+ let month = 0;
108
+ let day = 0;
109
+ let hours = 0;
110
+ let minutes = 0;
111
+ let seconds = 0;
112
+ let beforeChrist = false;
113
+ for (const part of format.formatToParts(epoch)) {
114
+ switch (part.type) {
115
+ case 'year': year = Number(part.value); break;
116
+ case 'month': month = Number(part.value); break;
117
+ case 'day': day = Number(part.value); break;
118
+ case 'hour': hours = Number(part.value); break;
119
+ case 'minute': minutes = Number(part.value); break;
120
+ case 'second': seconds = Number(part.value); break;
121
+ case 'era': beforeChrist = part.value.charCodeAt(0) === 0x42; break; // 'B'
122
+ default: break;
123
+ }
124
+ }
125
+ if (beforeChrist)
126
+ year = 1 - year;
127
+ const wholeSecond = Math.floor(epoch / 1000) * 1000;
128
+ // Use the core calendar for every year, including year zero and BCE.
129
+ const wall = daysFromCivil(year, month, day) * DAY
130
+ + ((hours * 60 + minutes) * 60 + seconds) * 1000;
131
+ const offset = (wall - wholeSecond) / MINUTE;
132
+ return { year, month, day, hours, minutes, seconds: seconds + (epoch - wholeSecond) / 1000, offset };
133
+ }
134
+
135
+ /**
136
+ * The wall clock a caller asked for, in the kernel's shape: an absent or
137
+ * negative time member is midnight's, as `epochOfRFC3339Parts` reads a
138
+ * negative one.
139
+ * @param {any} parts
140
+ * @returns {{ year: number, month: number, day: number, hours: number,
141
+ * minutes: number, seconds: number }}
142
+ */
143
+ function wantedWall(parts) {
144
+ const clamp = (value) => (typeof value === 'number' && value > 0 ? value : 0);
145
+ return {
146
+ year: parts.year,
147
+ month: parts.month,
148
+ day: parts.day,
149
+ hours: clamp(parts.hours),
150
+ minutes: clamp(parts.minutes),
151
+ seconds: clamp(parts.seconds),
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Whether a wall clock the zone reports is the one that was asked for.
157
+ * @param {ReturnType<typeof readParts>} read
158
+ * @param {ReturnType<typeof wantedWall>} wanted
159
+ * @returns {boolean}
160
+ */
161
+ function sameWall(read, wanted) {
162
+ return read.year === wanted.year && read.month === wanted.month && read.day === wanted.day
163
+ && read.hours === wanted.hours && read.minutes === wanted.minutes
164
+ && Math.floor(read.seconds) === Math.floor(wanted.seconds);
165
+ }
166
+
167
+ /**
168
+ * Build a zone provider from the host's ICU time-zone data.
169
+ *
170
+ * `toParts(epoch, zone)` is a formatted read. `toEpoch(parts, zone,
171
+ * disambiguation)` inverts a function that is neither injective nor
172
+ * total: it guesses an instant by reading the wall clock as UTC,
173
+ * corrects it by the offset in force there and again by the offset in
174
+ * force at the corrected instant, adds the offsets in force a day either
175
+ * side so both sides of any transition are represented, and then
176
+ * verifies every distinct candidate by reading its wall clock back.
177
+ * Exactly one candidate that reads back as asked is the instant. Two
178
+ * are a fold - the hour that happened twice - and `earlier` and `later`
179
+ * are the smaller and the larger. None is a gap - the hour that never
180
+ * happened - and `earlier` is the wall clock read with the offset in
181
+ * force after the transition (the instant that far before the gap),
182
+ * `later` the reading with the offset before it (that far after); the
183
+ * two are one transition apart. `reject`, the kernel's default, answers
184
+ * either case with `NaN`, which `@jarenjs/core/series` turns into a
185
+ * refusal naming the local time.
186
+ *
187
+ * An unknown zone name is a refusal naming it, never a quiet UTC, and
188
+ * no call reads the host's own zone.
189
+ *
190
+ * @param {{ zones?: number }} [options] - `zones`: how many zones'
191
+ * formatters stay resident (default `ZONE_CACHE_LIMIT`)
192
+ * @returns {import('@jarenjs/core/series').ZoneProvider}
193
+ * @example
194
+ * const provider = createIntlZoneProvider();
195
+ * resolveClock({ zone: 'Europe/Amsterdam', provider, disambiguation: 'later' });
196
+ * compileBuckets('P1M', { zone: 'Australia/Adelaide', provider });
197
+ */
198
+ export function createIntlZoneProvider(options = undefined) {
199
+ const limit = options?.zones ?? ZONE_CACHE_LIMIT;
200
+ if (typeof limit !== 'number' || !Number.isInteger(limit) || limit <= 0)
201
+ throw new TypeError('zones is a positive whole number of resident zone formatters');
202
+ /** @type {import('@jarenjs/core/cache').BoundedCache<string, Intl.DateTimeFormat>} */
203
+ const formatters = createBoundedCache(limit);
204
+
205
+ /**
206
+ * @param {string} zone
207
+ * @returns {Intl.DateTimeFormat}
208
+ */
209
+ const formatterFor = (zone) => {
210
+ if (typeof zone !== 'string' || zone === '')
211
+ throw new TypeError('a zone is an IANA name, given explicitly');
212
+ return formatters.getOrCreate(zone, createFormatter);
213
+ };
214
+
215
+ /**
216
+ * @param {number} epoch
217
+ * @param {string} zone
218
+ */
219
+ function toParts(epoch, zone) {
220
+ const format = formatterFor(zone);
221
+ return readParts(format, checkInstant(epoch));
222
+ }
223
+
224
+ /**
225
+ * @param {any} parts
226
+ * @param {string} zone
227
+ * @param {string} [disambiguation]
228
+ * @returns {number}
229
+ */
230
+ function toEpoch(parts, zone, disambiguation = 'reject') {
231
+ const format = formatterFor(zone);
232
+ if (!DISAMBIGUATION.includes(disambiguation)) {
233
+ throw new TypeError(`disambiguation is ${
234
+ DISAMBIGUATION.map((d) => `'${d}'`).join(', ')}, not '${disambiguation}'`);
235
+ }
236
+ if (parts === null || typeof parts !== 'object')
237
+ throw new TypeError('a wall clock is a parts record');
238
+ const wanted = wantedWall(parts);
239
+ const guess = epochOfRFC3339Parts({ ...wanted, offset: 0 });
240
+ // a wall clock the platform cannot hold, or one so near its edge
241
+ // that a probe a day away would fall off it, names no instant
242
+ if (!Number.isFinite(guess) || Math.abs(guess) + DAY > RANGE)
243
+ return NaN;
244
+ const offsetAt = (at) => readParts(format, at).offset;
245
+
246
+ /** @type {number[]} */
247
+ const candidates = [];
248
+ const consider = (offset) => {
249
+ const at = guess - offset * MINUTE;
250
+ if (!candidates.includes(at))
251
+ candidates.push(at);
252
+ };
253
+ const first = offsetAt(guess);
254
+ consider(first);
255
+ consider(offsetAt(guess - first * MINUTE));
256
+ consider(offsetAt(guess - DAY));
257
+ consider(offsetAt(guess + DAY));
258
+
259
+ const verified = candidates.filter((at) => sameWall(readParts(format, at), wanted));
260
+ if (verified.length === 1)
261
+ return verified[0];
262
+ if (disambiguation === 'reject')
263
+ return NaN;
264
+ const choices = verified.length > 1 ? verified : candidates;
265
+ if (choices.length < 2)
266
+ return NaN;
267
+ return disambiguation === 'earlier' ? Math.min(...choices) : Math.max(...choices);
268
+ }
269
+
270
+ return Object.freeze({ toParts, toEpoch });
271
+ }
package/src/ja.js CHANGED
@@ -162,6 +162,8 @@ export const ja = {
162
162
  'contract/seq-regression': '操作 {op} のストリームが seq の順序に違反しました',
163
163
  'contract/stream-error': '操作 {op} のストリームはサーバーエラーで終了しました ({code})',
164
164
  'contract/heartbeat-missed': '操作 {op} のストリームが {ms} ミリ秒間沈黙しました',
165
+ 'contract/slow-consumer': '操作 {op} のストリームは終了しました: 消費側が有界キューに追いつけませんでした',
166
+ 'contract/reconnect-exhausted': '操作 {op} のストリームは {attempts} 回の試行後も再確立できませんでした(最後: {lastCode})',
165
167
  //#endregion
166
168
 
167
169
  //#region calendar language (the date names, relative phrasing and
package/src/ko.js CHANGED
@@ -165,6 +165,8 @@ export const ko = {
165
165
  'contract/seq-regression': '작업 {op}의 스트림이 seq 순서를 위반했습니다',
166
166
  'contract/stream-error': '작업 {op}의 스트림이 서버 오류로 종료되었습니다 ({code})',
167
167
  'contract/heartbeat-missed': '작업 {op}의 스트림이 {ms}ms 동안 침묵했습니다',
168
+ 'contract/slow-consumer': '작업 {op}의 스트림이 종료되었습니다: 소비자가 제한된 큐를 따라가지 못했습니다',
169
+ 'contract/reconnect-exhausted': '작업 {op}의 스트림을 {attempts}회 시도 후에도 다시 연결하지 못했습니다 (마지막: {lastCode})',
168
170
  //#endregion
169
171
 
170
172
  //#region calendar language (the date names, relative phrasing and
package/src/nl.js CHANGED
@@ -166,6 +166,8 @@ export const nl = {
166
166
  'contract/seq-regression': 'de stream van operatie {op} heeft de seq-volgorde geschonden',
167
167
  'contract/stream-error': 'de stream van operatie {op} is geëindigd met een serverfout ({code})',
168
168
  'contract/heartbeat-missed': 'de stream van operatie {op} is {ms} ms stil gebleven',
169
+ 'contract/slow-consumer': 'de stream van operatie {op} is geëindigd: de afnemer bleef achter bij zijn begrensde wachtrij',
170
+ 'contract/reconnect-exhausted': 'de stream van operatie {op} kon na {attempts} pogingen niet worden hersteld (laatste: {lastCode})',
169
171
  //#endregion
170
172
 
171
173
  //#region calendar language (the date names, relative phrasing and
package/src/pt.js CHANGED
@@ -167,6 +167,8 @@ export const pt = {
167
167
  'contract/seq-regression': 'o fluxo da operação {op} violou a ordem de seus seq',
168
168
  'contract/stream-error': 'o fluxo da operação {op} terminou com um erro do servidor ({code})',
169
169
  'contract/heartbeat-missed': 'o fluxo da operação {op} ficou em silêncio por {ms} ms',
170
+ 'contract/slow-consumer': 'o fluxo da operação {op} terminou: o consumidor ficou atrás da sua fila limitada',
171
+ 'contract/reconnect-exhausted': 'o fluxo da operação {op} não pôde ser restabelecido após {attempts} tentativas (última: {lastCode})',
170
172
  //#endregion
171
173
 
172
174
  //#region calendar language (the date names, relative phrasing and
package/src/ru.js CHANGED
@@ -185,6 +185,8 @@ export const ru = {
185
185
  'contract/seq-regression': 'поток операции {op} нарушил порядок своих seq',
186
186
  'contract/stream-error': 'поток операции {op} завершился ошибкой сервера ({code})',
187
187
  'contract/heartbeat-missed': 'поток операции {op} молчал {ms} мс',
188
+ 'contract/slow-consumer': 'поток операции {op} завершён: потребитель отстал от своей ограниченной очереди',
189
+ 'contract/reconnect-exhausted': 'поток операции {op} не удалось восстановить после {attempts} попыток (последняя: {lastCode})',
188
190
  //#endregion
189
191
 
190
192
  //#region calendar language (the date names, relative phrasing and
package/src/tr.js CHANGED
@@ -164,6 +164,8 @@ export const tr = {
164
164
  'contract/seq-regression': '{op} işleminin akışı seq sırasını ihlal etti',
165
165
  'contract/stream-error': '{op} işleminin akışı bir sunucu hatasıyla sona erdi ({code})',
166
166
  'contract/heartbeat-missed': '{op} işleminin akışı {ms} ms boyunca sessiz kaldı',
167
+ 'contract/slow-consumer': '{op} işleminin akışı sona erdi: tüketici sınırlı kuyruğunun gerisinde kaldı',
168
+ 'contract/reconnect-exhausted': '{op} işleminin akışı {attempts} denemeden sonra yeniden kurulamadı (son: {lastCode})',
167
169
  //#endregion
168
170
 
169
171
  //#region calendar language (the date names, relative phrasing and
package/src/zh-tw.js CHANGED
@@ -165,6 +165,8 @@ export const zhTW = {
165
165
  'contract/seq-regression': '操作 {op} 的串流違反了其 seq 順序',
166
166
  'contract/stream-error': '操作 {op} 的串流以伺服器錯誤結束({code})',
167
167
  'contract/heartbeat-missed': '操作 {op} 的串流沉默了 {ms} 毫秒',
168
+ 'contract/slow-consumer': '操作 {op} 的串流已結束:消費端落後於其有界佇列',
169
+ 'contract/reconnect-exhausted': '操作 {op} 的串流在 {attempts} 次嘗試後仍無法重新建立(最後:{lastCode})',
168
170
  //#endregion
169
171
 
170
172
  //#region calendar language (the date names, relative phrasing and