hazo_core 2.2.0 → 2.4.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 CHANGED
@@ -70,8 +70,8 @@ import { loadConfig, defineConfig, createLogger, createDebug,
70
70
 
71
71
  ```ts
72
72
  import { withRetry, isUuid, parseListLimit, parseListOffset,
73
- createDebouncedSaver } from 'hazo_core';
74
- // all five are env-agnostic and also available from 'hazo_core/client'
73
+ createDebouncedSaver, rateLimit, withRateLimit, getClientIp } from 'hazo_core';
74
+ // all are env-agnostic and also available from 'hazo_core/client'
75
75
  ```
76
76
 
77
77
  ### Browser-safe subset
@@ -0,0 +1,49 @@
1
+ /** Options for {@link rateLimit}. */
2
+ export interface RateLimitOptions {
3
+ /** Bucket identifier (e.g. IP address or user ID). */
4
+ key: string;
5
+ /** Namespace / feature name (prevents cross-feature key collisions). */
6
+ scope: string;
7
+ /** Maximum requests allowed within the window. */
8
+ limit: number;
9
+ /** Window duration in milliseconds. */
10
+ window_ms: number;
11
+ }
12
+ export type RateLimitResult = {
13
+ ok: true;
14
+ } | {
15
+ ok: false;
16
+ retry_after_seconds: number;
17
+ };
18
+ /**
19
+ * Fixed-window token-bucket check. Increments the bucket for `scope:key` and
20
+ * reports whether the caller is still under `limit` within `window_ms`.
21
+ *
22
+ * @example rateLimit({ key: '1.2.3.4', scope: 'login', limit: 5, window_ms: 60_000 })
23
+ */
24
+ export declare function rateLimit(opts: RateLimitOptions): RateLimitResult;
25
+ /** Clear all buckets. Used in tests only. */
26
+ export declare function _clearBuckets(): void;
27
+ type RateLimitRouteHandler = (req: Request, ...args: unknown[]) => Promise<Response> | Response;
28
+ /**
29
+ * Wraps an HTTP route handler with rate limiting. Returns 429 with
30
+ * `Retry-After`/`X-RateLimit-Limit` headers when the limit is exceeded.
31
+ *
32
+ * Keys by client IP (`getClientIp`) by default; pass `getKey` to key by
33
+ * something else (e.g. a session/user id).
34
+ *
35
+ * Does NOT import from 'next' — uses the standard Request/Response types.
36
+ *
37
+ * @example
38
+ * export const POST = withRateLimit(myHandler, { scope: 'login', limit: 5, window_ms: 15 * 60 * 1000 });
39
+ */
40
+ export declare function withRateLimit(handler: RateLimitRouteHandler, opts: Omit<RateLimitOptions, 'key'> & {
41
+ getKey?: (req: Request) => string;
42
+ }): RateLimitRouteHandler;
43
+ /**
44
+ * Best-effort client IP from standard proxy headers.
45
+ * Checks `x-forwarded-for` (first entry), then `x-real-ip`; falls back to `'unknown'`.
46
+ */
47
+ export declare function getClientIp(req: Request): string;
48
+ export {};
49
+ //# sourceMappingURL=rate_limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate_limit.d.ts","sourceRoot":"","sources":["../../src/http/rate_limit.ts"],"names":[],"mappings":"AAeA,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAC/B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,CAAC;AAS/C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,eAAe,CAsBjE;AAED,6CAA6C;AAC7C,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAMD,KAAK,qBAAqB,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC;AAEhG;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,qBAAqB,EAC9B,IAAI,EAAE,IAAI,CAAC,gBAAgB,EAAE,KAAK,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAA;CAAE,GAC1E,qBAAqB,CAsBvB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAIhD"}
@@ -0,0 +1,84 @@
1
+ // file_description: In-memory token-bucket rate limiter plus a framework-agnostic
2
+ // HTTP route-handler wrapper.
3
+ //
4
+ // `rateLimit`/`_clearBuckets` are a simple per-process Map-backed fixed-window
5
+ // counter — single-process only; swap for a Postgres/Redis-backed store before
6
+ // running multiple instances behind a load balancer.
7
+ //
8
+ // `withRateLimit`/`getClientIp` wrap a route handler using the standard
9
+ // Request/Response types (not 'next/server'), so the same code works as-is in
10
+ // a Next.js App Router route handler — NextRequest extends Request and a
11
+ // returned plain Response is a valid handler return value — mirroring
12
+ // `withRequestIdRouteHandler` in `request_id_middleware.ts`.
13
+ //
14
+ // Promoted from taxclientdocs' lib/rate-limit/{token-bucket,middleware}.ts.
15
+ const _buckets = new Map();
16
+ /**
17
+ * Fixed-window token-bucket check. Increments the bucket for `scope:key` and
18
+ * reports whether the caller is still under `limit` within `window_ms`.
19
+ *
20
+ * @example rateLimit({ key: '1.2.3.4', scope: 'login', limit: 5, window_ms: 60_000 })
21
+ */
22
+ export function rateLimit(opts) {
23
+ const { key, scope, limit, window_ms } = opts;
24
+ const bucketKey = `${scope}:${key}`;
25
+ const now = Date.now();
26
+ const existing = _buckets.get(bucketKey);
27
+ if (!existing || now - existing.window_start >= window_ms) {
28
+ // New window
29
+ _buckets.set(bucketKey, { count: 1, window_start: now });
30
+ return { ok: true };
31
+ }
32
+ if (existing.count < limit) {
33
+ existing.count += 1;
34
+ return { ok: true };
35
+ }
36
+ // Limit exceeded
37
+ const elapsed = now - existing.window_start;
38
+ const retry_after_seconds = Math.ceil((window_ms - elapsed) / 1000);
39
+ return { ok: false, retry_after_seconds };
40
+ }
41
+ /** Clear all buckets. Used in tests only. */
42
+ export function _clearBuckets() {
43
+ _buckets.clear();
44
+ }
45
+ /**
46
+ * Wraps an HTTP route handler with rate limiting. Returns 429 with
47
+ * `Retry-After`/`X-RateLimit-Limit` headers when the limit is exceeded.
48
+ *
49
+ * Keys by client IP (`getClientIp`) by default; pass `getKey` to key by
50
+ * something else (e.g. a session/user id).
51
+ *
52
+ * Does NOT import from 'next' — uses the standard Request/Response types.
53
+ *
54
+ * @example
55
+ * export const POST = withRateLimit(myHandler, { scope: 'login', limit: 5, window_ms: 15 * 60 * 1000 });
56
+ */
57
+ export function withRateLimit(handler, opts) {
58
+ const getKey = opts.getKey ?? getClientIp;
59
+ return async (req, ...args) => {
60
+ const key = getKey(req);
61
+ const result = rateLimit({ ...opts, key });
62
+ if (!result.ok) {
63
+ return Response.json({ error: 'Too many requests. Please try again later.' }, {
64
+ status: 429,
65
+ headers: {
66
+ 'Retry-After': String(result.retry_after_seconds),
67
+ 'X-RateLimit-Limit': String(opts.limit),
68
+ },
69
+ });
70
+ }
71
+ return handler(req, ...args);
72
+ };
73
+ }
74
+ /**
75
+ * Best-effort client IP from standard proxy headers.
76
+ * Checks `x-forwarded-for` (first entry), then `x-real-ip`; falls back to `'unknown'`.
77
+ */
78
+ export function getClientIp(req) {
79
+ const xff = req.headers.get('x-forwarded-for');
80
+ if (xff)
81
+ return xff.split(',')[0].trim();
82
+ return req.headers.get('x-real-ip') ?? 'unknown';
83
+ }
84
+ //# sourceMappingURL=rate_limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate_limit.js","sourceRoot":"","sources":["../../src/http/rate_limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * hazo_core/i18n/client — browser-safe re-export.
3
+ *
4
+ * Safe because `./index.ts` has zero Node.js-only imports (only
5
+ * `optional_import`, already used from hazo_core's own `index.client.ts`).
6
+ */
7
+ export * from './index.js';
8
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/i18n/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,YAAY,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * hazo_core/i18n/client — browser-safe re-export.
3
+ *
4
+ * Safe because `./index.ts` has zero Node.js-only imports (only
5
+ * `optional_import`, already used from hazo_core's own `index.client.ts`).
6
+ */
7
+ export * from './index.js';
8
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/i18n/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,YAAY,CAAC"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * hazo_core/i18n — canonical i18n bridge for hazo_* packages' own baked-in
3
+ * strings.
4
+ *
5
+ * 17+ packages each maintain a ~120-line local copy of this pattern (see
6
+ * hazo_auth/src/lib/create_package_t.ts, the reference this module is
7
+ * modeled on). This is the canonical version so those local copies can
8
+ * eventually be deleted.
9
+ *
10
+ * `hazo_i18n` — the companion package that provides a FULL i18n runtime
11
+ * (ICU, request-scoped locales, DB-authored catalogs, LLM translation
12
+ * pipeline) — does NOT exist as a dependency or peer dependency of
13
+ * hazo_core. It is probed lazily and optionally, exactly like the
14
+ * `hazo_debug` probe in `../debug/index.ts`. This keeps the workspace's
15
+ * peer-dep graph acyclic (a guarded import is not a peer) and guarantees
16
+ * that `createPackageT`'s `t()` NEVER blocks on, awaits, or becomes
17
+ * "aware" of hazo_i18n at read time — it always reads the bundled flat
18
+ * fallback catalog directly. The optional hazo_i18n probe exists purely as
19
+ * a fire-and-forget SIDE EFFECT: if hazo_i18n happens to be installed in
20
+ * the consuming app, we register this package's catalog into hazo_i18n's
21
+ * cross-package registry so the strings show up in its admin panel / LLM
22
+ * translation pipeline / compiled artifacts. That registration can never
23
+ * change what `t()`, `t.has()`, or `t.raw()` return.
24
+ *
25
+ * hazo_i18n has no synchronous "get current locale" function anywhere in
26
+ * its public API — only async, request-scoped `getLocale()`/
27
+ * `getTranslations()` gated behind a runtime object that may not exist.
28
+ * That's a deliberate design decision on hazo_i18n's side (a long-lived
29
+ * process serves many concurrent requests in different locales, so there
30
+ * is nothing safe to peek at synchronously) — not a gap for this module to
31
+ * work around. See `resolvePackageLocale` below.
32
+ */
33
+ export type TFunc = ((key: string, vars?: Record<string, string | number>) => string) & {
34
+ /** True iff `fallbackCatalog[key]` is a string. */
35
+ has(key: string): boolean;
36
+ /** Raw (non-interpolated) template for `key`, or `undefined` if absent. */
37
+ raw(key: string): string | undefined;
38
+ };
39
+ /**
40
+ * Minimal shape we rely on from `hazo_i18n` when it is present. This is a
41
+ * defensive, best-effort probe — hazo_core never imports hazo_i18n's types
42
+ * statically, and treats its module shape as unknown, checked at runtime.
43
+ */
44
+ interface HazoI18nModuleShape {
45
+ registerCatalog?: (pkg: string, catalogs: Record<string, Record<string, unknown>>, owner?: string) => void;
46
+ }
47
+ /**
48
+ * Creates a namespaced translator for a hazo_* package's own baked-in
49
+ * strings.
50
+ *
51
+ * `t()` does a direct flat lookup on `fallbackCatalog` (catalogs are flat
52
+ * objects with literal-dotted string keys, e.g. `{ "login.submit": "Sign
53
+ * in" }` — NOT nested trees), then `{{var}}` interpolation. A missing key
54
+ * returns the bare key unchanged; a placeholder whose variable is missing
55
+ * from `vars` is left literal in the output.
56
+ *
57
+ * Side effect: on every call, kicks off (or reuses) the shared, once-per-
58
+ * process `optional_import('hazo_i18n')` probe. When it resolves and the
59
+ * module exposes `registerCatalog`, this package's fallback catalog is
60
+ * registered under `{ en: fallbackCatalog }` for cross-package visibility
61
+ * (hazo_i18n's admin panel / LLM export / compiled artifacts). This is
62
+ * pure side effect — idempotent on hazo_i18n's side (same-owner
63
+ * re-registrations merge without warning) — and never affects what `t()`/
64
+ * `t.has()`/`t.raw()` return; those always read `fallbackCatalog` directly.
65
+ *
66
+ * @param namespace - Package namespace, e.g. `"hazo_ui"`. Used only when
67
+ * registering into hazo_i18n's cross-package registry.
68
+ * @param fallbackCatalog - Flat `{ key: "English text" }` map bundled with
69
+ * the package.
70
+ */
71
+ export declare function createPackageT(namespace: string, fallbackCatalog: Record<string, string>): TFunc;
72
+ /**
73
+ * Registers additional locale catalogs into hazo_i18n's cross-package
74
+ * registry, for packages that ship non-English locale files beyond their
75
+ * own `createPackageT` default catalog.
76
+ *
77
+ * Uses the same shared, once-per-process hazo_i18n probe as
78
+ * `createPackageT`. If hazo_i18n isn't installed or hasn't resolved yet,
79
+ * this is a documented no-op — there is nothing else to register into, and
80
+ * throwing would defeat the point of an optional peer.
81
+ */
82
+ export declare function registerCatalog(pkg: string, catalogs: Record<string, Record<string, string>>, owner?: string): void;
83
+ /**
84
+ * Always returns `defaultLocale` unchanged.
85
+ *
86
+ * hazo_i18n has no synchronous locale getter by design — a long-lived
87
+ * process serves many concurrent requests in different locales, so there
88
+ * is nothing safe to peek at synchronously (see hazo_i18n's
89
+ * `src/lib/package_t.ts` header for the fuller rationale). This function
90
+ * exists as a stable extension point for the day hazo_i18n (or another
91
+ * provider) exposes one, not because it does anything today.
92
+ */
93
+ export declare function resolvePackageLocale(defaultLocale: string): string;
94
+ /**
95
+ * Test-only escape hatch: lets tests simulate hazo_i18n being installed
96
+ * without actually installing it. Also marks the shared probe as
97
+ * "resolved" so `createPackageT`/`registerCatalog` don't kick off (or wait
98
+ * on) a real dynamic import during tests. Not exported from the package's
99
+ * public entry points (`src/index.ts` / `src/index.client.ts`) — import
100
+ * directly from `hazo_core/i18n`'s source in tests only.
101
+ */
102
+ export declare function __setHazoI18nModuleForTests(mod: HazoI18nModuleShape | null): void;
103
+ export {};
104
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAQH,MAAM,MAAM,KAAK,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG;IACtF,mDAAmD;IACnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,2EAA2E;IAC3E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACtC,CAAC;AAEF;;;;GAIG;AACH,UAAU,mBAAmB;IAC3B,eAAe,CAAC,EAAE,CAChB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACjD,KAAK,CAAC,EAAE,MAAM,KACX,IAAI,CAAC;CACX;AAmED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACtC,KAAK,CAuBP;AAMD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAChD,KAAK,CAAC,EAAE,MAAM,GACb,IAAI,CAaN;AAMD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAElE;AAMD;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,GAAG,IAAI,CAGjF"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * hazo_core/i18n — canonical i18n bridge for hazo_* packages' own baked-in
3
+ * strings.
4
+ *
5
+ * 17+ packages each maintain a ~120-line local copy of this pattern (see
6
+ * hazo_auth/src/lib/create_package_t.ts, the reference this module is
7
+ * modeled on). This is the canonical version so those local copies can
8
+ * eventually be deleted.
9
+ *
10
+ * `hazo_i18n` — the companion package that provides a FULL i18n runtime
11
+ * (ICU, request-scoped locales, DB-authored catalogs, LLM translation
12
+ * pipeline) — does NOT exist as a dependency or peer dependency of
13
+ * hazo_core. It is probed lazily and optionally, exactly like the
14
+ * `hazo_debug` probe in `../debug/index.ts`. This keeps the workspace's
15
+ * peer-dep graph acyclic (a guarded import is not a peer) and guarantees
16
+ * that `createPackageT`'s `t()` NEVER blocks on, awaits, or becomes
17
+ * "aware" of hazo_i18n at read time — it always reads the bundled flat
18
+ * fallback catalog directly. The optional hazo_i18n probe exists purely as
19
+ * a fire-and-forget SIDE EFFECT: if hazo_i18n happens to be installed in
20
+ * the consuming app, we register this package's catalog into hazo_i18n's
21
+ * cross-package registry so the strings show up in its admin panel / LLM
22
+ * translation pipeline / compiled artifacts. That registration can never
23
+ * change what `t()`, `t.has()`, or `t.raw()` return.
24
+ *
25
+ * hazo_i18n has no synchronous "get current locale" function anywhere in
26
+ * its public API — only async, request-scoped `getLocale()`/
27
+ * `getTranslations()` gated behind a runtime object that may not exist.
28
+ * That's a deliberate design decision on hazo_i18n's side (a long-lived
29
+ * process serves many concurrent requests in different locales, so there
30
+ * is nothing safe to peek at synchronously) — not a gap for this module to
31
+ * work around. See `resolvePackageLocale` below.
32
+ */
33
+ // ---------------------------------------------------------------------------
34
+ // Shared, process-wide probe for the optional hazo_i18n peer
35
+ // ---------------------------------------------------------------------------
36
+ let hazoI18nModule = null;
37
+ // Fire-and-forget: populates hazoI18nModule as soon as the microtask queue
38
+ // runs. Using dynamic import() with bundler-ignore hints so Turbopack/
39
+ // webpack/Vite never try to statically resolve this optional peer dep and
40
+ // emit "Module not found" errors/warnings.
41
+ //
42
+ // The specifier MUST be a variable, not a string literal. Vite's
43
+ // import-analysis (which Vitest also runs through) only honours
44
+ // `@vite-ignore` when it can't statically determine the specifier —
45
+ // es-module-lexer resolves a literal `import('hazo_i18n')` to a concrete
46
+ // specifier regardless of any ignore comment, so Vite still tries to
47
+ // resolve it and hard-fails the whole module graph when the package isn't
48
+ // installed, bypassing this try/catch entirely. Routing the name through
49
+ // `hazoI18nPkg` makes the specifier unanalyzable, which is what actually
50
+ // makes Vite skip resolution (the `@vite-ignore` here only suppresses its
51
+ // "cannot analyze" warning). Mirrors `../debug/index.ts`'s `hazoDebugPkg`
52
+ // probe exactly.
53
+ //
54
+ // This probe runs ONCE per process — every `createPackageT()` call shares
55
+ // this same cached promise/module reference, it is not re-triggered per
56
+ // call.
57
+ const hazoI18nPkg = 'hazo_i18n';
58
+ let hazoI18nProbe = null;
59
+ function ensureHazoI18nProbe() {
60
+ if (!hazoI18nProbe) {
61
+ hazoI18nProbe = (async () => {
62
+ try {
63
+ hazoI18nModule = (await import(
64
+ /* turbopackIgnore: true */ /* webpackIgnore: true */ /* @vite-ignore */ hazoI18nPkg));
65
+ }
66
+ catch {
67
+ // hazo_i18n not installed — nothing to register into.
68
+ hazoI18nModule = null;
69
+ }
70
+ })();
71
+ }
72
+ return hazoI18nProbe;
73
+ }
74
+ // ---------------------------------------------------------------------------
75
+ // Interpolation
76
+ // ---------------------------------------------------------------------------
77
+ /**
78
+ * Simple `{{var}}` mustache-style interpolation — intentionally not ICU.
79
+ * Unknown placeholders are left as-is rather than dropped, so a missing var
80
+ * is obvious in the rendered string instead of silently disappearing.
81
+ */
82
+ function interpolate(template, vars) {
83
+ if (!vars)
84
+ return template;
85
+ return template.replace(/\{\{\s*(\w+)\s*\}\}/g, (whole, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : whole);
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // createPackageT
89
+ // ---------------------------------------------------------------------------
90
+ /**
91
+ * Creates a namespaced translator for a hazo_* package's own baked-in
92
+ * strings.
93
+ *
94
+ * `t()` does a direct flat lookup on `fallbackCatalog` (catalogs are flat
95
+ * objects with literal-dotted string keys, e.g. `{ "login.submit": "Sign
96
+ * in" }` — NOT nested trees), then `{{var}}` interpolation. A missing key
97
+ * returns the bare key unchanged; a placeholder whose variable is missing
98
+ * from `vars` is left literal in the output.
99
+ *
100
+ * Side effect: on every call, kicks off (or reuses) the shared, once-per-
101
+ * process `optional_import('hazo_i18n')` probe. When it resolves and the
102
+ * module exposes `registerCatalog`, this package's fallback catalog is
103
+ * registered under `{ en: fallbackCatalog }` for cross-package visibility
104
+ * (hazo_i18n's admin panel / LLM export / compiled artifacts). This is
105
+ * pure side effect — idempotent on hazo_i18n's side (same-owner
106
+ * re-registrations merge without warning) — and never affects what `t()`/
107
+ * `t.has()`/`t.raw()` return; those always read `fallbackCatalog` directly.
108
+ *
109
+ * @param namespace - Package namespace, e.g. `"hazo_ui"`. Used only when
110
+ * registering into hazo_i18n's cross-package registry.
111
+ * @param fallbackCatalog - Flat `{ key: "English text" }` map bundled with
112
+ * the package.
113
+ */
114
+ export function createPackageT(namespace, fallbackCatalog) {
115
+ void ensureHazoI18nProbe().then(() => {
116
+ const mod = hazoI18nModule;
117
+ if (mod && typeof mod.registerCatalog === 'function') {
118
+ try {
119
+ mod.registerCatalog(namespace, { en: fallbackCatalog });
120
+ }
121
+ catch {
122
+ // hazo_i18n present but errored on registration — never let this
123
+ // affect the fallback-catalog read path.
124
+ }
125
+ }
126
+ });
127
+ const t = ((key, vars) => {
128
+ const template = fallbackCatalog[key];
129
+ if (template === undefined)
130
+ return key;
131
+ return interpolate(template, vars);
132
+ });
133
+ t.has = (key) => typeof fallbackCatalog[key] === 'string';
134
+ t.raw = (key) => fallbackCatalog[key];
135
+ return t;
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // registerCatalog (standalone)
139
+ // ---------------------------------------------------------------------------
140
+ /**
141
+ * Registers additional locale catalogs into hazo_i18n's cross-package
142
+ * registry, for packages that ship non-English locale files beyond their
143
+ * own `createPackageT` default catalog.
144
+ *
145
+ * Uses the same shared, once-per-process hazo_i18n probe as
146
+ * `createPackageT`. If hazo_i18n isn't installed or hasn't resolved yet,
147
+ * this is a documented no-op — there is nothing else to register into, and
148
+ * throwing would defeat the point of an optional peer.
149
+ */
150
+ export function registerCatalog(pkg, catalogs, owner) {
151
+ void ensureHazoI18nProbe().then(() => {
152
+ const mod = hazoI18nModule;
153
+ if (mod && typeof mod.registerCatalog === 'function') {
154
+ try {
155
+ mod.registerCatalog(pkg, catalogs, owner);
156
+ }
157
+ catch {
158
+ // hazo_i18n present but errored on registration — swallow, same as
159
+ // createPackageT's side-effect registration.
160
+ }
161
+ }
162
+ // else: hazo_i18n absent/unresolved — no-op by design.
163
+ });
164
+ }
165
+ // ---------------------------------------------------------------------------
166
+ // resolvePackageLocale
167
+ // ---------------------------------------------------------------------------
168
+ /**
169
+ * Always returns `defaultLocale` unchanged.
170
+ *
171
+ * hazo_i18n has no synchronous locale getter by design — a long-lived
172
+ * process serves many concurrent requests in different locales, so there
173
+ * is nothing safe to peek at synchronously (see hazo_i18n's
174
+ * `src/lib/package_t.ts` header for the fuller rationale). This function
175
+ * exists as a stable extension point for the day hazo_i18n (or another
176
+ * provider) exposes one, not because it does anything today.
177
+ */
178
+ export function resolvePackageLocale(defaultLocale) {
179
+ return defaultLocale;
180
+ }
181
+ // ---------------------------------------------------------------------------
182
+ // Test-only escape hatch
183
+ // ---------------------------------------------------------------------------
184
+ /**
185
+ * Test-only escape hatch: lets tests simulate hazo_i18n being installed
186
+ * without actually installing it. Also marks the shared probe as
187
+ * "resolved" so `createPackageT`/`registerCatalog` don't kick off (or wait
188
+ * on) a real dynamic import during tests. Not exported from the package's
189
+ * public entry points (`src/index.ts` / `src/index.client.ts`) — import
190
+ * directly from `hazo_core/i18n`'s source in tests only.
191
+ */
192
+ export function __setHazoI18nModuleForTests(mod) {
193
+ hazoI18nModule = mod;
194
+ hazoI18nProbe = Promise.resolve();
195
+ }
196
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,8 @@ export type { DateFormat, RetryOptions, DebouncedSaveContext, CreateDebouncedSav
17
17
  export { fetchWithRequestId } from './http/fetch_with_request_id.client.js';
18
18
  export { get_origin_url, create_redirect_url } from './http/get_origin_url.js';
19
19
  export { parseListLimit, parseListOffset } from './http/list_params.js';
20
+ export { rateLimit, _clearBuckets, withRateLimit, getClientIp } from './http/rate_limit.js';
21
+ export type { RateLimitOptions, RateLimitResult } from './http/rate_limit.js';
20
22
  export { createLogger } from './logger/index.client.js';
21
23
  export type { HazoCoreLogger } from './logger/index.js';
22
24
  //# sourceMappingURL=index.client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG3F,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EACf,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIxE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG3F,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGvE,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EACf,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAI9E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC"}
@@ -20,6 +20,7 @@ export { generateRequestId, generateId, optional_import, safeJsonParse, sleep, f
20
20
  export { fetchWithRequestId } from './http/fetch_with_request_id.client.js';
21
21
  export { get_origin_url, create_redirect_url } from './http/get_origin_url.js';
22
22
  export { parseListLimit, parseListOffset } from './http/list_params.js';
23
+ export { rateLimit, _clearBuckets, withRateLimit, getClientIp } from './http/rate_limit.js';
23
24
  // Logger — client-side stub emits to the browser console (server version
24
25
  // requires node:module + process.stderr, neither available in browsers/edge)
25
26
  export { createLogger } from './logger/index.client.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.client.js","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,2BAA2B;AAC3B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE3F,sDAAsD;AACtD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEvE,4DAA4D;AAC5D,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAe3B,yDAAyD;AACzD,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAG7B,4BAA4B;AAC5B,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAgB1B,iEAAiE;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExE,yEAAyE;AACzE,6EAA6E;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.client.js","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,2BAA2B;AAC3B,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE3F,sDAAsD;AACtD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEvE,4DAA4D;AAC5D,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAe3B,yDAAyD;AACzD,OAAO,EACL,uBAAuB,EACvB,UAAU,EACV,gBAAgB,EAChB,WAAW,GACZ,MAAM,qBAAqB,CAAC;AAG7B,4BAA4B;AAC5B,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAgB1B,iEAAiE;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAG5F,yEAAyE;AACzE,6EAA6E;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC"}
package/dist/index.d.ts CHANGED
@@ -22,4 +22,6 @@ export { createRequestIdMiddleware, withRequestIdRouteHandler, } from './http/re
22
22
  export { fetchWithRequestId } from './http/fetch_with_request_id.js';
23
23
  export { get_origin_url, create_redirect_url } from './http/get_origin_url.js';
24
24
  export { parseListLimit, parseListOffset } from './http/list_params.js';
25
+ export { rateLimit, _clearBuckets, withRateLimit, getClientIp } from './http/rate_limit.js';
26
+ export type { RateLimitOptions, RateLimitResult } from './http/rate_limit.js';
25
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG3F,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG9G,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EACf,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACjH,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EACL,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG3F,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG9G,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EACf,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,GACzB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAGtD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACjH,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EACL,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC5F,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -25,4 +25,5 @@ export { createRequestIdMiddleware, withRequestIdRouteHandler, } from './http/re
25
25
  export { fetchWithRequestId } from './http/fetch_with_request_id.js';
26
26
  export { get_origin_url, create_redirect_url } from './http/get_origin_url.js';
27
27
  export { parseListLimit, parseListOffset } from './http/list_params.js';
28
+ export { rateLimit, _clearBuckets, withRateLimit, getClientIp } from './http/rate_limit.js';
28
29
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE3F,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE9G,YAAY;AACZ,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAgB1B,SAAS;AACT,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAe3B,uCAAuC;AACvC,OAAO,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAG5B,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGjH,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,QAAQ;AACR,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO;AACP,OAAO,EACL,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE3F,qBAAqB;AACrB,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE9G,YAAY;AACZ,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,EACX,eAAe,EACf,cAAc,EACd,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,mBAAmB,EACnB,SAAS,EACT,MAAM,EACN,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,kBAAkB,CAAC;AAgB1B,SAAS;AACT,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAe3B,uCAAuC;AACvC,OAAO,EACL,WAAW,EACX,UAAU,EACV,gBAAgB,GACjB,MAAM,oBAAoB,CAAC;AAG5B,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGjH,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,QAAQ;AACR,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO;AACP,OAAO,EACL,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_core",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Foundation package for all hazo_* packages — errors, correlation ID, config, logger, debug, singletons, utils",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,6 +22,14 @@
22
22
  "./context": {
23
23
  "types": "./dist/context/index.d.ts",
24
24
  "import": "./dist/context/index.js"
25
+ },
26
+ "./i18n": {
27
+ "types": "./dist/i18n/index.d.ts",
28
+ "import": "./dist/i18n/index.js"
29
+ },
30
+ "./i18n/client": {
31
+ "types": "./dist/i18n/client.d.ts",
32
+ "import": "./dist/i18n/client.js"
25
33
  }
26
34
  },
27
35
  "files": [