hanzio 2.2.0 → 3.1.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 +133 -7
- package/dist/api-wrapper/connection-auth.d.ts +61 -0
- package/dist/cli/fields.d.ts +30 -0
- package/dist/cli/index.d.ts +122 -0
- package/dist/format/index.d.ts +91 -0
- package/dist/format/phone.d.ts +19 -0
- package/dist/http/index.d.ts +14 -0
- package/dist/i18n/catalog.d.ts +47 -0
- package/dist/i18n/check.d.ts +45 -0
- package/dist/i18n/cli.d.ts +2 -0
- package/dist/i18n/content-rules.d.ts +55 -0
- package/dist/i18n/i18next.d.ts +39 -0
- package/dist/i18n/index.d.ts +16 -1
- package/dist/i18n/locales.d.ts +62 -0
- package/dist/i18n/vite.d.ts +16 -0
- package/dist/i18n/vue.d.ts +93 -0
- package/dist/{index-kj1phgrx.js → index-29mmh343.js} +24 -1
- package/dist/{index-zbj9s3s3.js → index-2kz392vw.js} +1 -1
- package/dist/index-5n2e326q.js +409 -0
- package/dist/{index-hy7kbb99.js → index-5tzqfmnj.js} +1 -1
- package/dist/{index-0zvtcbzy.js → index-6f2w90pp.js} +69 -1
- package/dist/index-6ragggbs.js +473 -0
- package/dist/index-mq2qgc20.js +116 -0
- package/dist/index-najbyzpr.js +99 -0
- package/dist/index-ww09bm49.js +250 -0
- package/dist/iso/countries.d.ts +262 -0
- package/dist/iso/currencies.d.ts +165 -0
- package/dist/iso/index.d.ts +4 -0
- package/dist/iso/languages.d.ts +195 -0
- package/dist/iso/phone-language.d.ts +5 -0
- package/dist/promise/index.d.ts +31 -0
- package/dist/redis/client.d.ts +49 -0
- package/dist/redis/concurrency-limit.d.ts +63 -0
- package/dist/redis/index.d.ts +3 -0
- package/dist/redis/lua-script.d.ts +13 -0
- package/dist/secrets/infisical-identity-file.d.ts +12 -0
- package/dist/src/api-wrapper/connection-auth.js +82 -0
- package/dist/src/api-wrapper/index.js +1 -1
- package/dist/src/cli/index.js +10 -0
- package/dist/src/format/index.js +79 -2
- package/dist/src/format/phone.js +21 -0
- package/dist/src/http/index.js +69 -0
- package/dist/src/i18n/check.js +15 -0
- package/dist/src/i18n/cli.js +71 -0
- package/dist/src/i18n/i18next.js +48 -0
- package/dist/src/i18n/index.js +16 -34
- package/dist/src/i18n/vite.js +25 -0
- package/dist/src/i18n/vue.js +51 -0
- package/dist/src/index.js +22 -6
- package/dist/src/iso/index.js +655 -0
- package/dist/src/redis/index.js +577 -0
- package/dist/src/secrets/index.js +78 -35
- package/dist/src/sitemap/index.js +1 -1
- package/dist/src/storage/index.js +8 -117
- package/dist/src/testing/index.js +348 -0
- package/dist/src/zod/index.js +20 -253
- package/dist/string/index.d.ts +21 -0
- package/dist/testing/connection-auth-contract.d.ts +43 -0
- package/dist/testing/helpers.d.ts +26 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/module-mocker.d.ts +30 -0
- package/dist/testing/prisma.d.ts +30 -0
- package/dist/testing/window.d.ts +18 -0
- package/dist/typedswitch/index.d.ts +55 -34
- package/dist/zod/index.d.ts +10 -0
- package/dist/zod/introspect.d.ts +2 -1
- package/package.json +97 -2
- package/src/api-wrapper/README.md +57 -2
- package/src/api-wrapper/SKILL.md +256 -0
- package/src/api-wrapper/connection-auth.ts +142 -0
- package/src/cli/README.md +220 -0
- package/src/cli/fields.ts +213 -0
- package/src/cli/index.ts +532 -0
- package/src/format/README.md +82 -2
- package/src/format/index.ts +148 -0
- package/src/format/phone.ts +43 -0
- package/src/http/README.md +25 -0
- package/src/http/index.ts +94 -0
- package/src/i18n/README.md +206 -3
- package/src/i18n/catalog.ts +139 -0
- package/src/i18n/check.ts +376 -0
- package/src/i18n/cli.ts +90 -0
- package/src/i18n/content-rules.ts +179 -0
- package/src/i18n/i18next.ts +89 -0
- package/src/i18n/index.ts +34 -0
- package/src/i18n/locales.ts +135 -0
- package/src/i18n/vite.ts +40 -0
- package/src/i18n/vue.ts +82 -0
- package/src/index.ts +1 -1
- package/src/iso/README.md +44 -0
- package/src/iso/countries.ts +272 -0
- package/src/iso/currencies.ts +172 -0
- package/src/iso/index.ts +8 -0
- package/src/iso/languages.ts +208 -0
- package/src/iso/phone-language.ts +46 -0
- package/src/promise/index.ts +100 -0
- package/src/redis/README.md +112 -0
- package/src/redis/client.ts +239 -0
- package/src/redis/concurrency-limit.ts +591 -0
- package/src/redis/index.ts +15 -0
- package/src/redis/lua-script.ts +43 -0
- package/src/secrets/README.md +5 -1
- package/src/secrets/infisical-identity-file.ts +69 -0
- package/src/secrets/infisical.ts +18 -3
- package/src/string/index.ts +59 -0
- package/src/testing/README.md +119 -0
- package/src/testing/connection-auth-contract.ts +303 -0
- package/src/testing/helpers.ts +41 -0
- package/src/testing/index.ts +12 -0
- package/src/testing/module-mocker.ts +50 -0
- package/src/testing/prisma.ts +78 -0
- package/src/testing/window.ts +119 -0
- package/src/typedswitch/README.md +55 -39
- package/src/typedswitch/index.ts +97 -83
- package/src/zod/index.ts +20 -0
- package/src/zod/introspect.ts +1 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/** ISO 639-1 two-letter language codes (lowercase) with English names. */
|
|
2
|
+
export declare const languageNames: {
|
|
3
|
+
readonly aa: "Afar";
|
|
4
|
+
readonly ab: "Abkhazian";
|
|
5
|
+
readonly ae: "Avestan";
|
|
6
|
+
readonly af: "Afrikaans";
|
|
7
|
+
readonly ak: "Akan";
|
|
8
|
+
readonly am: "Amharic";
|
|
9
|
+
readonly an: "Aragonese";
|
|
10
|
+
readonly ar: "Arabic";
|
|
11
|
+
readonly as: "Assamese";
|
|
12
|
+
readonly av: "Avaric";
|
|
13
|
+
readonly ay: "Aymara";
|
|
14
|
+
readonly az: "Azerbaijani";
|
|
15
|
+
readonly ba: "Bashkir";
|
|
16
|
+
readonly be: "Belarusian";
|
|
17
|
+
readonly bg: "Bulgarian";
|
|
18
|
+
readonly bi: "Bislama";
|
|
19
|
+
readonly bm: "Bambara";
|
|
20
|
+
readonly bn: "Bangla";
|
|
21
|
+
readonly bo: "Tibetan";
|
|
22
|
+
readonly br: "Breton";
|
|
23
|
+
readonly bs: "Bosnian";
|
|
24
|
+
readonly ca: "Catalan";
|
|
25
|
+
readonly ce: "Chechen";
|
|
26
|
+
readonly ch: "Chamorro";
|
|
27
|
+
readonly co: "Corsican";
|
|
28
|
+
readonly cr: "Cree";
|
|
29
|
+
readonly cs: "Czech";
|
|
30
|
+
readonly cu: "Church Slavic";
|
|
31
|
+
readonly cv: "Chuvash";
|
|
32
|
+
readonly cy: "Welsh";
|
|
33
|
+
readonly da: "Danish";
|
|
34
|
+
readonly de: "German";
|
|
35
|
+
readonly dv: "Dhivehi";
|
|
36
|
+
readonly dz: "Dzongkha";
|
|
37
|
+
readonly ee: "Ewe";
|
|
38
|
+
readonly el: "Greek";
|
|
39
|
+
readonly en: "English";
|
|
40
|
+
readonly eo: "Esperanto";
|
|
41
|
+
readonly es: "Spanish";
|
|
42
|
+
readonly et: "Estonian";
|
|
43
|
+
readonly eu: "Basque";
|
|
44
|
+
readonly fa: "Persian";
|
|
45
|
+
readonly ff: "Fula";
|
|
46
|
+
readonly fi: "Finnish";
|
|
47
|
+
readonly fj: "Fijian";
|
|
48
|
+
readonly fo: "Faroese";
|
|
49
|
+
readonly fr: "French";
|
|
50
|
+
readonly fy: "Western Frisian";
|
|
51
|
+
readonly ga: "Irish";
|
|
52
|
+
readonly gd: "Scottish Gaelic";
|
|
53
|
+
readonly gl: "Galician";
|
|
54
|
+
readonly gn: "Guarani";
|
|
55
|
+
readonly gu: "Gujarati";
|
|
56
|
+
readonly gv: "Manx";
|
|
57
|
+
readonly ha: "Hausa";
|
|
58
|
+
readonly he: "Hebrew";
|
|
59
|
+
readonly hi: "Hindi";
|
|
60
|
+
readonly ho: "Hiri Motu";
|
|
61
|
+
readonly hr: "Croatian";
|
|
62
|
+
readonly ht: "Haitian Creole";
|
|
63
|
+
readonly hu: "Hungarian";
|
|
64
|
+
readonly hy: "Armenian";
|
|
65
|
+
readonly hz: "Herero";
|
|
66
|
+
readonly ia: "Interlingua";
|
|
67
|
+
readonly id: "Indonesian";
|
|
68
|
+
readonly ie: "Interlingue";
|
|
69
|
+
readonly ig: "Igbo";
|
|
70
|
+
readonly ii: "Liangshan Yi";
|
|
71
|
+
readonly ik: "Inupiaq";
|
|
72
|
+
readonly io: "Ido";
|
|
73
|
+
readonly is: "Icelandic";
|
|
74
|
+
readonly it: "Italian";
|
|
75
|
+
readonly iu: "Inuktitut";
|
|
76
|
+
readonly ja: "Japanese";
|
|
77
|
+
readonly jv: "Javanese";
|
|
78
|
+
readonly ka: "Georgian";
|
|
79
|
+
readonly kg: "Kongo";
|
|
80
|
+
readonly ki: "Kikuyu";
|
|
81
|
+
readonly kj: "Kuanyama";
|
|
82
|
+
readonly kk: "Kazakh";
|
|
83
|
+
readonly kl: "Kalaallisut";
|
|
84
|
+
readonly km: "Khmer";
|
|
85
|
+
readonly kn: "Kannada";
|
|
86
|
+
readonly ko: "Korean";
|
|
87
|
+
readonly kr: "Kanuri";
|
|
88
|
+
readonly ks: "Kashmiri";
|
|
89
|
+
readonly ku: "Kurdish";
|
|
90
|
+
readonly kv: "Komi";
|
|
91
|
+
readonly kw: "Cornish";
|
|
92
|
+
readonly ky: "Kyrgyz";
|
|
93
|
+
readonly la: "Latin";
|
|
94
|
+
readonly lb: "Luxembourgish";
|
|
95
|
+
readonly lg: "Ganda";
|
|
96
|
+
readonly li: "Limburgish";
|
|
97
|
+
readonly ln: "Lingala";
|
|
98
|
+
readonly lo: "Lao";
|
|
99
|
+
readonly lt: "Lithuanian";
|
|
100
|
+
readonly lu: "Luba-Katanga";
|
|
101
|
+
readonly lv: "Latvian";
|
|
102
|
+
readonly mg: "Malagasy";
|
|
103
|
+
readonly mh: "Marshallese";
|
|
104
|
+
readonly mi: "Māori";
|
|
105
|
+
readonly mk: "Macedonian";
|
|
106
|
+
readonly ml: "Malayalam";
|
|
107
|
+
readonly mn: "Mongolian";
|
|
108
|
+
readonly mr: "Marathi";
|
|
109
|
+
readonly ms: "Malay";
|
|
110
|
+
readonly mt: "Maltese";
|
|
111
|
+
readonly my: "Burmese";
|
|
112
|
+
readonly na: "Nauru";
|
|
113
|
+
readonly nb: "Norwegian Bokmål";
|
|
114
|
+
readonly nd: "North Ndebele";
|
|
115
|
+
readonly ne: "Nepali";
|
|
116
|
+
readonly ng: "Ndonga";
|
|
117
|
+
readonly nl: "Dutch";
|
|
118
|
+
readonly nn: "Norwegian Nynorsk";
|
|
119
|
+
readonly no: "Norwegian";
|
|
120
|
+
readonly nr: "South Ndebele";
|
|
121
|
+
readonly nv: "Navajo";
|
|
122
|
+
readonly ny: "Nyanja";
|
|
123
|
+
readonly oc: "Occitan";
|
|
124
|
+
readonly oj: "Ojibwa";
|
|
125
|
+
readonly om: "Oromo";
|
|
126
|
+
readonly or: "Odia";
|
|
127
|
+
readonly os: "Ossetic";
|
|
128
|
+
readonly pa: "Punjabi";
|
|
129
|
+
readonly pi: "Pali";
|
|
130
|
+
readonly pl: "Polish";
|
|
131
|
+
readonly ps: "Pashto";
|
|
132
|
+
readonly pt: "Portuguese";
|
|
133
|
+
readonly qu: "Quechua";
|
|
134
|
+
readonly rm: "Romansh";
|
|
135
|
+
readonly rn: "Rundi";
|
|
136
|
+
readonly ro: "Romanian";
|
|
137
|
+
readonly ru: "Russian";
|
|
138
|
+
readonly rw: "Kinyarwanda";
|
|
139
|
+
readonly sa: "Sanskrit";
|
|
140
|
+
readonly sc: "Sardinian";
|
|
141
|
+
readonly sd: "Sindhi";
|
|
142
|
+
readonly se: "North Sámi";
|
|
143
|
+
readonly sg: "Sango";
|
|
144
|
+
readonly si: "Sinhala";
|
|
145
|
+
readonly sk: "Slovak";
|
|
146
|
+
readonly sl: "Slovenian";
|
|
147
|
+
readonly sm: "Samoan";
|
|
148
|
+
readonly sn: "Shona";
|
|
149
|
+
readonly so: "Somali";
|
|
150
|
+
readonly sq: "Albanian";
|
|
151
|
+
readonly sr: "Serbian";
|
|
152
|
+
readonly ss: "Swati";
|
|
153
|
+
readonly st: "Southern Sotho";
|
|
154
|
+
readonly su: "Sundanese";
|
|
155
|
+
readonly sv: "Swedish";
|
|
156
|
+
readonly sw: "Swahili";
|
|
157
|
+
readonly ta: "Tamil";
|
|
158
|
+
readonly te: "Telugu";
|
|
159
|
+
readonly tg: "Tajik";
|
|
160
|
+
readonly th: "Thai";
|
|
161
|
+
readonly ti: "Tigrinya";
|
|
162
|
+
readonly tk: "Turkmen";
|
|
163
|
+
readonly tl: "Tagalog";
|
|
164
|
+
readonly tn: "Tswana";
|
|
165
|
+
readonly to: "Tongan";
|
|
166
|
+
readonly tr: "Turkish";
|
|
167
|
+
readonly ts: "Tsonga";
|
|
168
|
+
readonly tt: "Tatar";
|
|
169
|
+
readonly tw: "Twi";
|
|
170
|
+
readonly ty: "Tahitian";
|
|
171
|
+
readonly ug: "Uyghur";
|
|
172
|
+
readonly uk: "Ukrainian";
|
|
173
|
+
readonly ur: "Urdu";
|
|
174
|
+
readonly uz: "Uzbek";
|
|
175
|
+
readonly ve: "Venda";
|
|
176
|
+
readonly vi: "Vietnamese";
|
|
177
|
+
readonly vo: "Volapük";
|
|
178
|
+
readonly wa: "Walloon";
|
|
179
|
+
readonly wo: "Wolof";
|
|
180
|
+
readonly xh: "Xhosa";
|
|
181
|
+
readonly yi: "Yiddish";
|
|
182
|
+
readonly yo: "Yoruba";
|
|
183
|
+
readonly za: "Zhuang";
|
|
184
|
+
readonly zh: "Chinese";
|
|
185
|
+
readonly zu: "Zulu";
|
|
186
|
+
};
|
|
187
|
+
export type LanguageCode = keyof typeof languageNames;
|
|
188
|
+
export declare const languageCodes: readonly ("aa" | "ab" | "ae" | "af" | "ak" | "am" | "an" | "ar" | "as" | "av" | "ay" | "az" | "ba" | "be" | "bg" | "bi" | "bm" | "bn" | "bo" | "br" | "bs" | "ca" | "ce" | "ch" | "co" | "cr" | "cs" | "cu" | "cv" | "cy" | "da" | "de" | "dv" | "dz" | "ee" | "el" | "en" | "eo" | "es" | "et" | "eu" | "fa" | "ff" | "fi" | "fj" | "fo" | "fr" | "fy" | "ga" | "gd" | "gl" | "gn" | "gu" | "gv" | "ha" | "he" | "hi" | "ho" | "hr" | "ht" | "hu" | "hy" | "hz" | "ia" | "id" | "ie" | "ig" | "ii" | "ik" | "io" | "is" | "it" | "iu" | "ja" | "jv" | "ka" | "kg" | "ki" | "kj" | "kk" | "kl" | "km" | "kn" | "ko" | "kr" | "ks" | "ku" | "kv" | "kw" | "ky" | "la" | "lb" | "lg" | "li" | "ln" | "lo" | "lt" | "lu" | "lv" | "mg" | "mh" | "mi" | "mk" | "ml" | "mn" | "mr" | "ms" | "mt" | "my" | "na" | "nb" | "nd" | "ne" | "ng" | "nl" | "nn" | "no" | "nr" | "nv" | "ny" | "oc" | "oj" | "om" | "or" | "os" | "pa" | "pi" | "pl" | "ps" | "pt" | "qu" | "rm" | "rn" | "ro" | "ru" | "rw" | "sa" | "sc" | "sd" | "se" | "sg" | "si" | "sk" | "sl" | "sm" | "sn" | "so" | "sq" | "sr" | "ss" | "st" | "su" | "sv" | "sw" | "ta" | "te" | "tg" | "th" | "ti" | "tk" | "tl" | "tn" | "to" | "tr" | "ts" | "tt" | "tw" | "ty" | "ug" | "uk" | "ur" | "uz" | "ve" | "vi" | "vo" | "wa" | "wo" | "xh" | "yi" | "yo" | "za" | "zh" | "zu")[];
|
|
189
|
+
export declare function isLanguageCode(value: unknown): value is LanguageCode;
|
|
190
|
+
/**
|
|
191
|
+
* The ISO 639-1 language of a BCP-47-ish tag (`'en-GB'`, `'da_DK'`), or
|
|
192
|
+
* `null` when it has none. Map tags your app treats as one language (`'no'`
|
|
193
|
+
* and `'nn'` to `'nb'`, say) before or after.
|
|
194
|
+
*/
|
|
195
|
+
export declare function matchLanguageCode(tag: string | null | undefined): LanguageCode | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A default language from an E.164 number's calling code (`+45…` → `'da'`),
|
|
3
|
+
* or `null` when the number is missing, too short, or its code is unmapped.
|
|
4
|
+
*/
|
|
5
|
+
export declare function languageFromE164PhoneNumber(phoneNumber: string | null | undefined): "da" | "de" | "en" | "es" | "fr" | "it" | "nb" | "nl" | "pt" | "sv" | null;
|
package/dist/promise/index.d.ts
CHANGED
|
@@ -79,6 +79,37 @@ export declare function pMap<T, R>(items: Iterable<T>, fn: (item: T, index: numb
|
|
|
79
79
|
concurrency?: number;
|
|
80
80
|
signal?: AbortSignal;
|
|
81
81
|
}): Promise<R[]>;
|
|
82
|
+
export type TaskPool = {
|
|
83
|
+
/** Queues `task`; it starts once a slot is free. */
|
|
84
|
+
run<T>(task: () => Promise<T>): Promise<T>;
|
|
85
|
+
/** Resolves once every queued and active task has settled; rejects with the first failure. */
|
|
86
|
+
drain(): Promise<void>;
|
|
87
|
+
/** Throws the abort reason or the first failure, if any. */
|
|
88
|
+
throwIfFailed(): void;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* A bounded pool for work that arrives over time, such as a crawl that queues
|
|
92
|
+
* pages as it discovers them (`pMap` needs the whole list up front).
|
|
93
|
+
*
|
|
94
|
+
* The first failure, or an abort, discards queued tasks and rejects later
|
|
95
|
+
* `run` calls; `drain` waits for active tasks to settle before rejecting with
|
|
96
|
+
* it. Pass the same `signal` to the tasks' IO so an abort interrupts them too.
|
|
97
|
+
* `stopOnError: false` keeps the pool running when a task fails, for callers
|
|
98
|
+
* that retry or fall back per task.
|
|
99
|
+
*
|
|
100
|
+
* A `run` promise that is never awaited does not cause an unhandled rejection;
|
|
101
|
+
* `drain` still reports its failure.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* const pool = createTaskPool({ concurrency: 4, signal })
|
|
105
|
+
* for await (const page of discoverPages(signal)) void pool.run(() => scrape(page, signal))
|
|
106
|
+
* await pool.drain()
|
|
107
|
+
*/
|
|
108
|
+
export declare function createTaskPool(options: {
|
|
109
|
+
concurrency: number;
|
|
110
|
+
signal?: AbortSignal;
|
|
111
|
+
stopOnError?: boolean;
|
|
112
|
+
}): TaskPool;
|
|
82
113
|
/** Starts a clock; call the returned function to read elapsed ms. */
|
|
83
114
|
export declare function startTimer(): () => number;
|
|
84
115
|
/** Runs `fn` and reports how long it took (throws pass through). */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { RedisClient, RedisOptions } from 'bun';
|
|
2
|
+
export type RedisRole = 'client' | 'subscriber';
|
|
3
|
+
export type RedisErrorContext = {
|
|
4
|
+
role: RedisRole;
|
|
5
|
+
stage: 'close' | 'subscribe' | 'resubscribe' | 'unsubscribe' | 'handler';
|
|
6
|
+
channel?: string;
|
|
7
|
+
};
|
|
8
|
+
export type ManagedRedisOptions = {
|
|
9
|
+
/** Read when a connection is created, so a secret store can resolve it lazily. */
|
|
10
|
+
url: string | (() => string);
|
|
11
|
+
/** Every failure, for logging, metrics and error tracking. Defaults to `console.error`. */
|
|
12
|
+
onError?: (error: unknown, context: RedisErrorContext) => void;
|
|
13
|
+
/** Builds each connection. Defaults to `new Bun.RedisClient(url, options)`. */
|
|
14
|
+
createClient?: (url: string, options: RedisOptions) => RedisClient;
|
|
15
|
+
};
|
|
16
|
+
export type RedisMessageHandler = (message: string, channel: string) => void;
|
|
17
|
+
export type ManagedRedis = {
|
|
18
|
+
/** The shared command connection, created on first use. */
|
|
19
|
+
getClient(): RedisClient;
|
|
20
|
+
/**
|
|
21
|
+
* Subscribes `handler` to `channel` on the shared subscriber connection.
|
|
22
|
+
* Channels are ref-counted: Redis sees one `SUBSCRIBE` however many handlers
|
|
23
|
+
* share it, and the returned function unsubscribes when the last one leaves.
|
|
24
|
+
*/
|
|
25
|
+
subscribe(channel: string, handler: RedisMessageHandler): Promise<() => Promise<void>>;
|
|
26
|
+
isConnected(): boolean;
|
|
27
|
+
getStats(): {
|
|
28
|
+
channelCount: number;
|
|
29
|
+
channels: string[];
|
|
30
|
+
isConnected: boolean;
|
|
31
|
+
isSubscriberConnected: boolean;
|
|
32
|
+
};
|
|
33
|
+
/** Closes both connections. Errors from the shutdown itself are not reported. */
|
|
34
|
+
close(): void;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A command connection plus one shared subscriber connection for a Bun
|
|
38
|
+
* server. Both reconnect on their own, and subscriptions come back after a
|
|
39
|
+
* reconnect.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* export const redis = createManagedRedis({
|
|
43
|
+
* url: () => getSecret('REDIS_URL'),
|
|
44
|
+
* onError: (error, context) => captureException(error, 'redis', context)
|
|
45
|
+
* })
|
|
46
|
+
* await redis.getClient().set('key', 'value')
|
|
47
|
+
* const unsubscribe = await redis.subscribe('events', (message) => handle(message))
|
|
48
|
+
*/
|
|
49
|
+
export declare function createManagedRedis(options: ManagedRedisOptions): ManagedRedis;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type RedisSend } from './lua-script';
|
|
2
|
+
export type ConcurrencyLimitStage = 'slot' | 'start';
|
|
3
|
+
export type RedisConcurrencyLimitOptions = {
|
|
4
|
+
key: string;
|
|
5
|
+
max: number;
|
|
6
|
+
slotTtlMs: number;
|
|
7
|
+
send: RedisSend;
|
|
8
|
+
maxStartsPerSecond?: number;
|
|
9
|
+
failOpenAfterMs?: number;
|
|
10
|
+
commandTimeoutMs?: number;
|
|
11
|
+
/** Receives throttled warnings. Defaults to `console`. */
|
|
12
|
+
logger?: Pick<Console, 'warn'>;
|
|
13
|
+
/** Called when a caller starts queueing; the returned function runs once it stops. */
|
|
14
|
+
onWait?: (stage: ConcurrencyLimitStage) => (() => void) | void;
|
|
15
|
+
};
|
|
16
|
+
export type RedisConcurrencyLimit = {
|
|
17
|
+
<T>(run: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
18
|
+
tripCooldown: (retryAfterMs: number) => Promise<void>;
|
|
19
|
+
/** Callers in this process waiting for a slot or a start, e.g. for a gauge. */
|
|
20
|
+
queueDepth: () => number;
|
|
21
|
+
/** Drop this limiter's timers. See `createRedisConcurrencyLimit`. */
|
|
22
|
+
dispose: () => void;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Redis semaphore shared by every process. Returns `withLimit(run)` - at most
|
|
26
|
+
* `max` callbacks in flight for `key`.
|
|
27
|
+
*
|
|
28
|
+
* A held slot is heartbeated for as long as `run` lasts, so `slotTtlMs` is a
|
|
29
|
+
* crash-detection window rather than a run budget: slots leaked by a killed pod
|
|
30
|
+
* free themselves within one TTL, while slow-but-alive work keeps its slot.
|
|
31
|
+
*
|
|
32
|
+
* `maxStartsPerSecond` is a separate sliding 1s window of *starts*. Use it when
|
|
33
|
+
* the provider quota is requests-per-second (Cloudflare Quick Actions) so
|
|
34
|
+
* in-flight concurrency can sit higher than the start rate.
|
|
35
|
+
*
|
|
36
|
+
* `tripCooldown` writes a shared pause from an upstream `Retry-After` so every
|
|
37
|
+
* process backs off together instead of stampeding into 429s.
|
|
38
|
+
*
|
|
39
|
+
* Waiters inside one process queue locally and are handed to Redis in a single
|
|
40
|
+
* batched call, so a pod with hundreds of callers costs the same Redis traffic
|
|
41
|
+
* as one with a handful.
|
|
42
|
+
*
|
|
43
|
+
* When Redis itself is unreachable for `failOpenAfterMs` the limiter gives up
|
|
44
|
+
* and runs uncapped. Callers guard a third-party quota; degrading to the
|
|
45
|
+
* provider's own rate limit beats hanging every caller until Redis returns.
|
|
46
|
+
* That verdict is shared per limiter: once tripped, later calls degrade
|
|
47
|
+
* immediately while a background probe watches for Redis to come back.
|
|
48
|
+
*
|
|
49
|
+
* `onWait` and `queueDepth` feed metrics. A limiter minted per tenant or per
|
|
50
|
+
* customer host should report under a shared pool label rather than its key,
|
|
51
|
+
* or every key ever touched leaves a permanent series. Call `dispose()` when
|
|
52
|
+
* such a limiter is discarded, and unregister any gauge reading `queueDepth`.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const withLimit = createRedisConcurrencyLimit({
|
|
56
|
+
* key: 'openai',
|
|
57
|
+
* max: 20,
|
|
58
|
+
* slotTtlMs: 30_000,
|
|
59
|
+
* send: (command, args) => redis.getClient().send(command, args)
|
|
60
|
+
* })
|
|
61
|
+
* const reply = await withLimit(() => openai.chat(request), signal)
|
|
62
|
+
*/
|
|
63
|
+
export declare function createRedisConcurrencyLimit(input: RedisConcurrencyLimitOptions): RedisConcurrencyLimit;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createManagedRedis, type ManagedRedis, type ManagedRedisOptions, type RedisErrorContext, type RedisMessageHandler, type RedisRole } from './client';
|
|
2
|
+
export { type ConcurrencyLimitStage, createRedisConcurrencyLimit, type RedisConcurrencyLimit, type RedisConcurrencyLimitOptions } from './concurrency-limit';
|
|
3
|
+
export { createLuaScript, type RedisSend } from './lua-script';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** One raw Redis command, e.g. `(command, args) => redis.getClient().send(command, args)`. */
|
|
2
|
+
export type RedisSend = (command: string, args: string[]) => Promise<unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* Ship a Lua script by SHA instead of resending its source on every call.
|
|
5
|
+
*
|
|
6
|
+
* Redis forgets its script cache on restart and on `SCRIPT FLUSH`, so a
|
|
7
|
+
* `NOSCRIPT` reply falls back to an inline `EVAL` (which repopulates the cache)
|
|
8
|
+
* and drops the stale digest.
|
|
9
|
+
*
|
|
10
|
+
* The digest is cached per instance rather than per module so each limiter -
|
|
11
|
+
* and each test fake - owns its own cache.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createLuaScript(send: RedisSend, source: string): (keys: string[], args: string[]) => Promise<unknown>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EnvironmentVariables } from './environment';
|
|
2
|
+
/**
|
|
3
|
+
* Machine identity (Universal Auth) credentials kept in a private file for every
|
|
4
|
+
* process on this machine, for example by agentgate, which syncs one identity to
|
|
5
|
+
* all of a user's machines. `INFISICAL_UNIVERSAL_AUTH_FILE` overrides the default
|
|
6
|
+
* `~/.infisical/universal-auth.json`. Only credentials for `siteUrl`'s instance are
|
|
7
|
+
* returned, so a secret is never sent to another Infisical instance.
|
|
8
|
+
*/
|
|
9
|
+
export declare function readInfisicalIdentityFile(env: EnvironmentVariables | undefined, siteUrl: string): Promise<{
|
|
10
|
+
clientId: string;
|
|
11
|
+
clientSecret: string;
|
|
12
|
+
} | undefined>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
raceAbort2
|
|
3
|
+
} from "../../index-6f2w90pp.js";
|
|
4
|
+
|
|
5
|
+
// src/api-wrapper/connection-auth.ts
|
|
6
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
7
|
+
class ConnectionAuthCache {
|
|
8
|
+
maxEntries;
|
|
9
|
+
key = randomBytes(32);
|
|
10
|
+
entries = new Map;
|
|
11
|
+
constructor(maxEntries = 1000) {
|
|
12
|
+
this.maxEntries = maxEntries;
|
|
13
|
+
}
|
|
14
|
+
get size() {
|
|
15
|
+
return this.entries.size;
|
|
16
|
+
}
|
|
17
|
+
clear() {
|
|
18
|
+
for (const entry of this.entries.values())
|
|
19
|
+
entry.token = undefined;
|
|
20
|
+
this.entries.clear();
|
|
21
|
+
}
|
|
22
|
+
prune(protectedEntry) {
|
|
23
|
+
for (const [connectionId, entry] of this.entries) {
|
|
24
|
+
if (this.entries.size <= this.maxEntries)
|
|
25
|
+
break;
|
|
26
|
+
if (entry.pending || entry === protectedEntry)
|
|
27
|
+
continue;
|
|
28
|
+
entry.token = undefined;
|
|
29
|
+
this.entries.delete(connectionId);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
bind(input) {
|
|
33
|
+
const identity = createHmac("sha256", this.key).update(JSON.stringify(input.authInputs)).digest("hex");
|
|
34
|
+
let entry = this.entries.get(input.connectionId);
|
|
35
|
+
if (!entry || entry.identity !== identity) {
|
|
36
|
+
if (entry)
|
|
37
|
+
entry.token = undefined;
|
|
38
|
+
entry = { identity };
|
|
39
|
+
}
|
|
40
|
+
this.entries.delete(input.connectionId);
|
|
41
|
+
this.entries.set(input.connectionId, entry);
|
|
42
|
+
this.prune(entry);
|
|
43
|
+
const state = entry;
|
|
44
|
+
const get = (options) => {
|
|
45
|
+
if (options?.signal?.aborted) {
|
|
46
|
+
return Promise.reject(options.signal.reason);
|
|
47
|
+
}
|
|
48
|
+
if (this.entries.get(input.connectionId) === state) {
|
|
49
|
+
this.entries.delete(input.connectionId);
|
|
50
|
+
this.entries.set(input.connectionId, state);
|
|
51
|
+
}
|
|
52
|
+
if (state.pending)
|
|
53
|
+
return raceAbort2(state.pending, options?.signal);
|
|
54
|
+
if (!options?.fresh && state.token && state.token.expiresAt - Date.now() > input.refreshMarginMs) {
|
|
55
|
+
return Promise.resolve(state.token);
|
|
56
|
+
}
|
|
57
|
+
state.token = undefined;
|
|
58
|
+
const pending = input.exchange().then((token) => {
|
|
59
|
+
if (this.entries.get(input.connectionId) === state) {
|
|
60
|
+
state.token = token;
|
|
61
|
+
}
|
|
62
|
+
return token;
|
|
63
|
+
}).finally(() => {
|
|
64
|
+
if (state.pending === pending)
|
|
65
|
+
state.pending = undefined;
|
|
66
|
+
this.prune();
|
|
67
|
+
});
|
|
68
|
+
state.pending = pending;
|
|
69
|
+
return raceAbort2(pending, options?.signal);
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
get,
|
|
73
|
+
reject(token) {
|
|
74
|
+
if (state.token === token)
|
|
75
|
+
state.token = undefined;
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export {
|
|
81
|
+
ConnectionAuthCache
|
|
82
|
+
};
|
package/dist/src/format/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
stableStringify2
|
|
3
|
-
} from "../../index-
|
|
3
|
+
} from "../../index-29mmh343.js";
|
|
4
4
|
|
|
5
5
|
// src/format/index.ts
|
|
6
6
|
var MAX_CACHED_FORMATTERS = 100;
|
|
@@ -44,6 +44,9 @@ function formatCurrency(amount, currency, locale, options) {
|
|
|
44
44
|
currency
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
function formatPrice(amount, currency, locale) {
|
|
48
|
+
return formatCurrency(amount, currency, locale, Number.isInteger(amount) ? { minimumFractionDigits: 0, maximumFractionDigits: 0 } : undefined);
|
|
49
|
+
}
|
|
47
50
|
function formatCompactNumber(value, locale) {
|
|
48
51
|
return formatNumber(value, locale, { notation: "compact" });
|
|
49
52
|
}
|
|
@@ -173,12 +176,68 @@ function formatDateRange(start, end, locale, options = {}) {
|
|
|
173
176
|
const to = new Date(toTime(end));
|
|
174
177
|
return typeof format.formatRange === "function" ? format.formatRange(from, to) : `${format.format(from)} – ${format.format(to)}`;
|
|
175
178
|
}
|
|
179
|
+
var DATE_PRESETS = {
|
|
180
|
+
"DD/MM/YYYY": { day: "2-digit", month: "2-digit", year: "numeric" },
|
|
181
|
+
"DD/MM/YYYY HH:mm": {
|
|
182
|
+
day: "2-digit",
|
|
183
|
+
month: "2-digit",
|
|
184
|
+
year: "numeric",
|
|
185
|
+
hour: "2-digit",
|
|
186
|
+
minute: "2-digit",
|
|
187
|
+
hourCycle: "h23"
|
|
188
|
+
},
|
|
189
|
+
"HH:mm": { hour: "2-digit", minute: "2-digit", hourCycle: "h23" },
|
|
190
|
+
ddd: { weekday: "short" },
|
|
191
|
+
"D MMM": { day: "numeric", month: "short" },
|
|
192
|
+
"D MMM YYYY": { day: "numeric", month: "short", year: "numeric" },
|
|
193
|
+
"dddd, MMMM D": { weekday: "long", day: "numeric", month: "long" },
|
|
194
|
+
"MMMM YYYY": { month: "long", year: "numeric" }
|
|
195
|
+
};
|
|
196
|
+
function formatDate(date, preset, locale) {
|
|
197
|
+
return getDateTimeFormat(locale, DATE_PRESETS[preset]).format(toTime(date));
|
|
198
|
+
}
|
|
199
|
+
function formatDateTime(date, locale) {
|
|
200
|
+
return getDateTimeFormat(locale, {
|
|
201
|
+
dateStyle: "medium",
|
|
202
|
+
timeStyle: "short"
|
|
203
|
+
}).format(toTime(date));
|
|
204
|
+
}
|
|
205
|
+
function dayOffset(date, now) {
|
|
206
|
+
const start = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
207
|
+
return Math.round((start(date) - start(now)) / DAY_MS);
|
|
208
|
+
}
|
|
209
|
+
var relativeDay = (offset, locale) => getRelativeTimeFormat(locale, { numeric: "auto" }).format(offset, "day");
|
|
210
|
+
function formatCompactTime(date, locale, options = {}) {
|
|
211
|
+
const value = new Date(toTime(date));
|
|
212
|
+
if (Number.isNaN(value.getTime()))
|
|
213
|
+
return "";
|
|
214
|
+
const now = new Date(toTime(options.now ?? Date.now()));
|
|
215
|
+
const offset = dayOffset(value, now);
|
|
216
|
+
if (offset === 0)
|
|
217
|
+
return formatDate(value, "HH:mm", locale);
|
|
218
|
+
if (offset === -1)
|
|
219
|
+
return relativeDay(-1, locale);
|
|
220
|
+
const sameYear = value.getFullYear() === now.getFullYear();
|
|
221
|
+
return formatDate(value, sameYear ? "D MMM" : "D MMM YYYY", locale);
|
|
222
|
+
}
|
|
223
|
+
function formatDayDivider(date, locale, options = {}) {
|
|
224
|
+
const value = new Date(toTime(date));
|
|
225
|
+
if (Number.isNaN(value.getTime()))
|
|
226
|
+
return "";
|
|
227
|
+
const now = new Date(toTime(options.now ?? Date.now()));
|
|
228
|
+
const offset = dayOffset(value, now);
|
|
229
|
+
if (offset === 0 || offset === -1)
|
|
230
|
+
return relativeDay(offset, locale);
|
|
231
|
+
const sameYear = value.getFullYear() === now.getFullYear();
|
|
232
|
+
return formatDate(value, sameYear ? "dddd, MMMM D" : "D MMM YYYY", locale);
|
|
233
|
+
}
|
|
176
234
|
function formatList(items, locale, options) {
|
|
177
235
|
if (typeof Intl.ListFormat !== "function") {
|
|
178
236
|
return items.join(options?.style === "narrow" ? " " : ", ");
|
|
179
237
|
}
|
|
180
238
|
return getListFormat(locale, options).format(items);
|
|
181
239
|
}
|
|
240
|
+
var EM_DASH = "—";
|
|
182
241
|
function formatCappedCount(count, max = 99) {
|
|
183
242
|
if (!(count > 0))
|
|
184
243
|
return "0";
|
|
@@ -188,17 +247,34 @@ function pluralize(count, forms, locale) {
|
|
|
188
247
|
const form = forms[getPluralRules(locale).select(count)] ?? forms.other;
|
|
189
248
|
return form.replaceAll("{count}", formatNumber(count, locale));
|
|
190
249
|
}
|
|
250
|
+
function withLocale(getLocale) {
|
|
251
|
+
return {
|
|
252
|
+
formatDate: (date, preset) => formatDate(date, preset, getLocale()),
|
|
253
|
+
formatDateTime: (date) => formatDateTime(date, getLocale()),
|
|
254
|
+
formatCompactTime: (date) => formatCompactTime(date, getLocale()),
|
|
255
|
+
formatDayDivider: (date) => formatDayDivider(date, getLocale()),
|
|
256
|
+
formatRelativeTime: (date, options) => formatRelativeTime(date, getLocale(), options),
|
|
257
|
+
formatNumber: (value, options) => formatNumber(value, getLocale(), options),
|
|
258
|
+
formatPrice: (amount, currency) => formatPrice(amount, currency, getLocale())
|
|
259
|
+
};
|
|
260
|
+
}
|
|
191
261
|
export {
|
|
262
|
+
EM_DASH,
|
|
192
263
|
formatBytes,
|
|
193
264
|
formatCappedCount,
|
|
194
265
|
formatClockDuration,
|
|
195
266
|
formatCompactNumber,
|
|
267
|
+
formatCompactTime,
|
|
196
268
|
formatCurrency,
|
|
269
|
+
formatDate,
|
|
197
270
|
formatDateRange,
|
|
271
|
+
formatDateTime,
|
|
272
|
+
formatDayDivider,
|
|
198
273
|
formatDuration,
|
|
199
274
|
formatList,
|
|
200
275
|
formatNumber,
|
|
201
276
|
formatPercent,
|
|
277
|
+
formatPrice,
|
|
202
278
|
formatRelativeTime,
|
|
203
279
|
formatWait,
|
|
204
280
|
getDateTimeFormat,
|
|
@@ -207,5 +283,6 @@ export {
|
|
|
207
283
|
getPluralRules,
|
|
208
284
|
getRelativeTimeFormat,
|
|
209
285
|
pluralize,
|
|
210
|
-
splitDuration
|
|
286
|
+
splitDuration,
|
|
287
|
+
withLocale
|
|
211
288
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/format/phone.ts
|
|
2
|
+
import {
|
|
3
|
+
isSupportedCountry,
|
|
4
|
+
parsePhoneNumberFromString
|
|
5
|
+
} from "libphonenumber-js";
|
|
6
|
+
function formatPhone(value, country) {
|
|
7
|
+
const raw = value?.trim() ?? "";
|
|
8
|
+
if (!raw)
|
|
9
|
+
return "";
|
|
10
|
+
const region = country && isSupportedCountry(country) ? country : undefined;
|
|
11
|
+
const parsed = parsePhoneNumberFromString(raw, region ? { defaultCountry: region } : undefined);
|
|
12
|
+
if (!parsed)
|
|
13
|
+
return raw;
|
|
14
|
+
if (region && parsed.country === region && parsed.isValid()) {
|
|
15
|
+
return parsed.formatNational();
|
|
16
|
+
}
|
|
17
|
+
return parsed.formatInternational();
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
formatPhone
|
|
21
|
+
};
|