@stapel/core 0.2.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/LICENSE +21 -0
- package/README.md +118 -0
- package/dist/analytics/context.d.ts +9 -0
- package/dist/analytics/context.d.ts.map +1 -0
- package/dist/analytics/context.js +14 -0
- package/dist/analytics/context.js.map +1 -0
- package/dist/analytics/createAnalytics.d.ts +10 -0
- package/dist/analytics/createAnalytics.d.ts.map +1 -0
- package/dist/analytics/createAnalytics.js +273 -0
- package/dist/analytics/createAnalytics.js.map +1 -0
- package/dist/analytics/flow.d.ts +10 -0
- package/dist/analytics/flow.d.ts.map +1 -0
- package/dist/analytics/flow.js +10 -0
- package/dist/analytics/flow.js.map +1 -0
- package/dist/analytics/hash.d.ts +3 -0
- package/dist/analytics/hash.d.ts.map +1 -0
- package/dist/analytics/hash.js +12 -0
- package/dist/analytics/hash.js.map +1 -0
- package/dist/analytics/pii.d.ts +9 -0
- package/dist/analytics/pii.d.ts.map +1 -0
- package/dist/analytics/pii.js +52 -0
- package/dist/analytics/pii.js.map +1 -0
- package/dist/analytics/providers.d.ts +28 -0
- package/dist/analytics/providers.d.ts.map +1 -0
- package/dist/analytics/providers.js +82 -0
- package/dist/analytics/providers.js.map +1 -0
- package/dist/analytics/types.d.ts +94 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/analytics/types.js +7 -0
- package/dist/analytics/types.js.map +1 -0
- package/dist/client.d.ts +49 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +28 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/i18n.d.ts +51 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +90 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/query.d.ts +42 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +95 -0
- package/dist/query.js.map +1 -0
- package/dist/storage.d.ts +16 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +65 -0
- package/dist/storage.js.map +1 -0
- package/dist/useBreakpoint.d.ts +8 -0
- package/dist/useBreakpoint.d.ts.map +1 -0
- package/dist/useBreakpoint.js +22 -0
- package/dist/useBreakpoint.js.map +1 -0
- package/dist/verification.d.ts +31 -0
- package/dist/verification.d.ts.map +1 -0
- package/dist/verification.js +20 -0
- package/dist/verification.js.map +1 -0
- package/package.json +68 -0
- package/src/analytics/context.ts +20 -0
- package/src/analytics/createAnalytics.ts +310 -0
- package/src/analytics/flow.ts +19 -0
- package/src/analytics/hash.ts +16 -0
- package/src/analytics/pii.ts +66 -0
- package/src/analytics/providers.ts +108 -0
- package/src/analytics/types.ts +105 -0
- package/src/client.ts +206 -0
- package/src/config.tsx +62 -0
- package/src/errors.ts +70 -0
- package/src/i18n.tsx +147 -0
- package/src/index.ts +72 -0
- package/src/query.ts +151 -0
- package/src/storage.ts +76 -0
- package/src/useBreakpoint.ts +27 -0
- package/src/verification.ts +48 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Dev provider: logs every event via console.debug. Never throws. */
|
|
2
|
+
export function consoleProvider() {
|
|
3
|
+
const log = (...args) => {
|
|
4
|
+
try {
|
|
5
|
+
console.debug("[stapel analytics]", ...args);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
// Never throws — a broken console must not fail a batch.
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
track: (event) => {
|
|
13
|
+
log(event.kind, event.name, event.props);
|
|
14
|
+
},
|
|
15
|
+
identify: (userHash, traits) => {
|
|
16
|
+
log("identify", userHash, traits ?? {});
|
|
17
|
+
},
|
|
18
|
+
page: (name, props) => {
|
|
19
|
+
log("page", name, props ?? {});
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const COLLECTOR_PATH = "/analytics/api/events";
|
|
24
|
+
/**
|
|
25
|
+
* Built-in provider for the stapel-analytics ingest endpoint: buffers
|
|
26
|
+
* events handed over by the facade and POSTs `{events: [...]}` to
|
|
27
|
+
* `{base}/analytics/api/events` on flush (one request per facade batch).
|
|
28
|
+
* During page teardown (document hidden) it prefers `navigator.sendBeacon`
|
|
29
|
+
* so the final batch survives navigation; otherwise fetch with keepalive.
|
|
30
|
+
* On a failed send the buffer is surrendered back to the facade's retry
|
|
31
|
+
* (the facade re-delivers the batch, repopulating the buffer).
|
|
32
|
+
*/
|
|
33
|
+
export function stapelCollectorProvider(options) {
|
|
34
|
+
const baseUrl = options.baseUrl ?? options.client?.baseUrl;
|
|
35
|
+
if (baseUrl === undefined) {
|
|
36
|
+
throw new Error("stapelCollectorProvider requires a baseUrl or a client");
|
|
37
|
+
}
|
|
38
|
+
const endpoint = `${baseUrl.replace(/\/+$/, "")}${COLLECTOR_PATH}`;
|
|
39
|
+
let buffer = [];
|
|
40
|
+
async function send(events) {
|
|
41
|
+
const body = { events };
|
|
42
|
+
if (options.writeKey !== undefined)
|
|
43
|
+
body["write_key"] = options.writeKey;
|
|
44
|
+
const teardown = typeof document !== "undefined" &&
|
|
45
|
+
document.visibilityState === "hidden" &&
|
|
46
|
+
typeof navigator !== "undefined" &&
|
|
47
|
+
typeof navigator.sendBeacon === "function";
|
|
48
|
+
if (teardown) {
|
|
49
|
+
const accepted = navigator.sendBeacon(endpoint, new Blob([JSON.stringify(body)], { type: "application/json" }));
|
|
50
|
+
if (accepted)
|
|
51
|
+
return;
|
|
52
|
+
// Beacon rejected (payload too large / unsupported) — fall through.
|
|
53
|
+
}
|
|
54
|
+
if (options.client) {
|
|
55
|
+
await options.client.post(COLLECTOR_PATH, body);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
59
|
+
const response = await fetchImpl(endpoint, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
body: JSON.stringify(body),
|
|
63
|
+
keepalive: true,
|
|
64
|
+
});
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new Error(`[stapel analytics] collector responded ${String(response.status)}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
track: (event) => {
|
|
71
|
+
buffer.push(event);
|
|
72
|
+
},
|
|
73
|
+
flush: async () => {
|
|
74
|
+
if (buffer.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
const events = buffer;
|
|
77
|
+
buffer = [];
|
|
78
|
+
await send(events);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.js","sourceRoot":"","sources":["../../src/analytics/providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics facade types (analytics-standard.md §1–2). Packages and hosts
|
|
3
|
+
* talk to the facade only — never to providers directly
|
|
4
|
+
* (frontend-standard §4.7).
|
|
5
|
+
*/
|
|
6
|
+
import type { PersistStorage } from "../storage.js";
|
|
7
|
+
export type AnalyticsEventKind = "track" | "page" | "identify";
|
|
8
|
+
export type ConsentState = "granted" | "denied" | "pending";
|
|
9
|
+
export type PiiGuardMode = "strip" | "warn" | "off";
|
|
10
|
+
/** A captured event as providers receive it (PII-guarded, user id hashed). */
|
|
11
|
+
export interface AnalyticsEvent {
|
|
12
|
+
/** Facade-assigned id, unique per instance lifetime (dedupe aid). */
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly kind: AnalyticsEventKind;
|
|
15
|
+
/** track: registry event name; page: page name; identify: `"identify"`. */
|
|
16
|
+
readonly name: string;
|
|
17
|
+
/** track/page props or identify traits, after the PII guard. */
|
|
18
|
+
readonly props: Record<string, unknown>;
|
|
19
|
+
/** SHA-256 hex of the user id (identify events only). */
|
|
20
|
+
readonly userHash?: string;
|
|
21
|
+
/** Epoch ms at capture time. */
|
|
22
|
+
readonly ts: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A fan-out target. Only `track` is mandatory: events of kind `page` /
|
|
26
|
+
* `identify` fall back to `track(event)` when the dedicated method is
|
|
27
|
+
* absent (the event carries its `kind`). `flush` is called after each
|
|
28
|
+
* delivered batch and on page teardown — batching providers (like the
|
|
29
|
+
* Stapel collector) send there. A rejected/thrown delivery marks the whole
|
|
30
|
+
* batch as undelivered for this provider; it will be retried.
|
|
31
|
+
*/
|
|
32
|
+
export interface AnalyticsProvider {
|
|
33
|
+
track(event: AnalyticsEvent): void | Promise<void>;
|
|
34
|
+
identify?(userHash: string, traits?: Record<string, unknown>): void | Promise<void>;
|
|
35
|
+
page?(name: string, props?: Record<string, unknown>): void | Promise<void>;
|
|
36
|
+
flush?(): void | Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export interface AnalyticsBatchOptions {
|
|
39
|
+
/** Queue length that triggers an automatic flush. Default 20. */
|
|
40
|
+
readonly maxSize?: number;
|
|
41
|
+
/** Periodic flush interval, ms. Default 10000. */
|
|
42
|
+
readonly flushIntervalMs?: number;
|
|
43
|
+
/** Delivery attempts per batch before it is dropped. Default 5. */
|
|
44
|
+
readonly maxAttempts?: number;
|
|
45
|
+
/** Base of the exponential retry backoff, ms. Default 500. */
|
|
46
|
+
readonly backoffBaseMs?: number;
|
|
47
|
+
}
|
|
48
|
+
export interface AnalyticsOptions {
|
|
49
|
+
/** Initial named providers (more via `register`/`unregister`). */
|
|
50
|
+
readonly providers?: Record<string, AnalyticsProvider>;
|
|
51
|
+
/**
|
|
52
|
+
* Event registry (analytics-standard §1.1): `track` with a name outside
|
|
53
|
+
* it logs a dev console.warn but still delivers — the hard gate is the
|
|
54
|
+
* eslint rule against the project's events.json.
|
|
55
|
+
*/
|
|
56
|
+
readonly registry?: readonly string[];
|
|
57
|
+
/**
|
|
58
|
+
* Initial consent when none has been persisted yet. Default `"pending"`.
|
|
59
|
+
* A state persisted by `setConsent` takes precedence on recreation.
|
|
60
|
+
*/
|
|
61
|
+
readonly consent?: ConsentState;
|
|
62
|
+
/** PII guard mode for prop/trait values. Default `"strip"`. */
|
|
63
|
+
readonly piiGuard?: PiiGuardMode;
|
|
64
|
+
readonly batch?: AnalyticsBatchOptions;
|
|
65
|
+
/** Storage key prefix for the offline queue + consent. Default `"stapel-analytics"`. */
|
|
66
|
+
readonly persistKey?: string;
|
|
67
|
+
/** Storage override (tests, custom stores). Default: IndexedDB → localStorage → memory. */
|
|
68
|
+
readonly storage?: PersistStorage;
|
|
69
|
+
}
|
|
70
|
+
export interface Analytics {
|
|
71
|
+
/** Queue a registry event. No-op while consent is `"denied"`. */
|
|
72
|
+
track(event: string, props?: Record<string, unknown>): void;
|
|
73
|
+
/**
|
|
74
|
+
* Queue an identify. The raw `userId` never leaves the facade: providers
|
|
75
|
+
* see its SHA-256 hex. Traits pass the PII guard.
|
|
76
|
+
*/
|
|
77
|
+
identify(userId: string, traits?: Record<string, unknown>): void;
|
|
78
|
+
/** Queue a page view. */
|
|
79
|
+
page(name: string, props?: Record<string, unknown>): void;
|
|
80
|
+
/** Deliver everything queued (one delivery attempt per pending batch). */
|
|
81
|
+
flush(): Promise<void>;
|
|
82
|
+
/**
|
|
83
|
+
* Consent gate (analytics-standard §1.4): `"pending"` buffers,
|
|
84
|
+
* `"granted"` flushes the buffer, `"denied"` drops it (memory +
|
|
85
|
+
* persisted) and turns subsequent calls into no-ops. Persisted.
|
|
86
|
+
*/
|
|
87
|
+
setConsent(state: ConsentState): Promise<void>;
|
|
88
|
+
/** Current consent state (after async restore; see `flush`). */
|
|
89
|
+
getConsent(): ConsentState;
|
|
90
|
+
/** Register a provider at runtime (merge semantics). */
|
|
91
|
+
register(name: string, provider: AnalyticsProvider): void;
|
|
92
|
+
unregister(name: string): void;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/analytics/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/analytics/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type VerificationChallengeHandler } from "./verification.js";
|
|
2
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
3
|
+
export interface StapelRequestOptions {
|
|
4
|
+
readonly method?: HttpMethod;
|
|
5
|
+
/** JSON-serialized unless it is a `BodyInit` (FormData, Blob, string…). */
|
|
6
|
+
readonly body?: unknown;
|
|
7
|
+
readonly headers?: Record<string, string>;
|
|
8
|
+
/** Appended to the URL; `undefined` values are skipped. */
|
|
9
|
+
readonly query?: Record<string, string | number | boolean | undefined>;
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface StapelClient {
|
|
13
|
+
readonly baseUrl: string;
|
|
14
|
+
request<T>(path: string, options?: StapelRequestOptions): Promise<T>;
|
|
15
|
+
get<T>(path: string, options?: Omit<StapelRequestOptions, "method" | "body">): Promise<T>;
|
|
16
|
+
post<T>(path: string, body?: unknown, options?: Omit<StapelRequestOptions, "method" | "body">): Promise<T>;
|
|
17
|
+
put<T>(path: string, body?: unknown, options?: Omit<StapelRequestOptions, "method" | "body">): Promise<T>;
|
|
18
|
+
patch<T>(path: string, body?: unknown, options?: Omit<StapelRequestOptions, "method" | "body">): Promise<T>;
|
|
19
|
+
delete<T>(path: string, options?: Omit<StapelRequestOptions, "method" | "body">): Promise<T>;
|
|
20
|
+
}
|
|
21
|
+
export interface StapelClientOptions {
|
|
22
|
+
/** e.g. `https://api.example.com` or `/api`. */
|
|
23
|
+
readonly baseUrl: string;
|
|
24
|
+
/** Auth seam: current access token (attached as `Authorization: Bearer`). */
|
|
25
|
+
readonly getToken?: () => string | null | undefined | Promise<string | null | undefined>;
|
|
26
|
+
/**
|
|
27
|
+
* Refresh seam: called once per request on a 401. Return the new access
|
|
28
|
+
* token to retry the request with it; return null/undefined to give up
|
|
29
|
+
* (the 401 is then thrown as `StapelApiError`).
|
|
30
|
+
*/
|
|
31
|
+
readonly onAuthRefresh?: () => Promise<string | null | undefined>;
|
|
32
|
+
/**
|
|
33
|
+
* Step-up verification seam: called when a 403 body carries a
|
|
34
|
+
* `verification` challenge. On `{retry: true}` the original request is
|
|
35
|
+
* retried exactly once, with `X-Verification-Token` when a token is given.
|
|
36
|
+
*/
|
|
37
|
+
readonly onVerificationChallenge?: VerificationChallengeHandler;
|
|
38
|
+
/** Merged into every request (overridable per request). */
|
|
39
|
+
readonly defaultHeaders?: Record<string, string>;
|
|
40
|
+
/** Injectable fetch (tests, SSR, instrumentation). Default: global fetch. */
|
|
41
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Typed fetch wrapper around the Stapel API conventions: JSON in/out, bearer
|
|
45
|
+
* auth with a refresh seam, the `{localizable_error, error, params}` error
|
|
46
|
+
* envelope, and verification-403 interception (see `StapelClientOptions`).
|
|
47
|
+
*/
|
|
48
|
+
export declare function createStapelClient(options: StapelClientOptions): StapelClient;
|
|
49
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IACvE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrE,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1F,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3G,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1G,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC5G,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9F;AAED,MAAM,WAAW,mBAAmB;IAClC,gDAAgD;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAChB,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IAClE;;;;OAIG;IACH,QAAQ,CAAC,uBAAuB,CAAC,EAAE,4BAA4B,CAAC;IAChE,2DAA2D;IAC3D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjD,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC1C;AAmDD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CA+F7E"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { parseErrorEnvelope } from "./errors.js";
|
|
2
|
+
import { extractVerificationChallenge, VERIFICATION_TOKEN_HEADER, } from "./verification.js";
|
|
3
|
+
function isBodyInit(body) {
|
|
4
|
+
return (typeof body === "string" ||
|
|
5
|
+
(typeof Blob !== "undefined" && body instanceof Blob) ||
|
|
6
|
+
(typeof FormData !== "undefined" && body instanceof FormData) ||
|
|
7
|
+
(typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) ||
|
|
8
|
+
(typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer) ||
|
|
9
|
+
(typeof ReadableStream !== "undefined" && body instanceof ReadableStream));
|
|
10
|
+
}
|
|
11
|
+
function buildUrl(baseUrl, path, query) {
|
|
12
|
+
const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
13
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
14
|
+
let url = `${base}${suffix}`;
|
|
15
|
+
if (query) {
|
|
16
|
+
const search = new URLSearchParams();
|
|
17
|
+
for (const [key, value] of Object.entries(query)) {
|
|
18
|
+
if (value !== undefined)
|
|
19
|
+
search.set(key, String(value));
|
|
20
|
+
}
|
|
21
|
+
const qs = search.toString();
|
|
22
|
+
if (qs.length > 0)
|
|
23
|
+
url += `?${qs}`;
|
|
24
|
+
}
|
|
25
|
+
return url;
|
|
26
|
+
}
|
|
27
|
+
async function parseBody(response) {
|
|
28
|
+
if (response.status === 204 || response.status === 205)
|
|
29
|
+
return undefined;
|
|
30
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
31
|
+
const text = await response.text();
|
|
32
|
+
if (text.length === 0)
|
|
33
|
+
return undefined;
|
|
34
|
+
if (contentType.includes("json")) {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(text);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Typed fetch wrapper around the Stapel API conventions: JSON in/out, bearer
|
|
51
|
+
* auth with a refresh seam, the `{localizable_error, error, params}` error
|
|
52
|
+
* envelope, and verification-403 interception (see `StapelClientOptions`).
|
|
53
|
+
*/
|
|
54
|
+
export function createStapelClient(options) {
|
|
55
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
56
|
+
async function request(path, requestOptions = {}) {
|
|
57
|
+
const method = requestOptions.method ?? "GET";
|
|
58
|
+
const url = buildUrl(options.baseUrl, path, requestOptions.query);
|
|
59
|
+
let overrideToken;
|
|
60
|
+
let verificationToken;
|
|
61
|
+
let triedRefresh = false;
|
|
62
|
+
let triedVerification = false;
|
|
63
|
+
for (;;) {
|
|
64
|
+
const headers = new Headers(options.defaultHeaders);
|
|
65
|
+
if (requestOptions.headers) {
|
|
66
|
+
for (const [key, value] of Object.entries(requestOptions.headers)) {
|
|
67
|
+
headers.set(key, value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const token = overrideToken ?? (await options.getToken?.());
|
|
71
|
+
if (token != null && token.length > 0 && !headers.has("authorization")) {
|
|
72
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
73
|
+
}
|
|
74
|
+
if (verificationToken !== undefined) {
|
|
75
|
+
headers.set(VERIFICATION_TOKEN_HEADER, verificationToken);
|
|
76
|
+
}
|
|
77
|
+
let body;
|
|
78
|
+
if (requestOptions.body !== undefined) {
|
|
79
|
+
if (isBodyInit(requestOptions.body)) {
|
|
80
|
+
body = requestOptions.body;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
body = JSON.stringify(requestOptions.body);
|
|
84
|
+
if (!headers.has("content-type")) {
|
|
85
|
+
headers.set("Content-Type", "application/json");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const requestInit = { method, headers };
|
|
90
|
+
if (body !== undefined)
|
|
91
|
+
requestInit.body = body;
|
|
92
|
+
if (requestOptions.signal)
|
|
93
|
+
requestInit.signal = requestOptions.signal;
|
|
94
|
+
const response = await fetchImpl(url, requestInit);
|
|
95
|
+
if (response.ok) {
|
|
96
|
+
return (await parseBody(response));
|
|
97
|
+
}
|
|
98
|
+
const errorBody = await parseBody(response);
|
|
99
|
+
if (response.status === 401 && options.onAuthRefresh && !triedRefresh) {
|
|
100
|
+
triedRefresh = true;
|
|
101
|
+
const refreshed = await options.onAuthRefresh();
|
|
102
|
+
if (refreshed != null && refreshed.length > 0) {
|
|
103
|
+
overrideToken = refreshed;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (response.status === 403 &&
|
|
108
|
+
options.onVerificationChallenge &&
|
|
109
|
+
!triedVerification) {
|
|
110
|
+
const challenge = extractVerificationChallenge(errorBody);
|
|
111
|
+
if (challenge) {
|
|
112
|
+
triedVerification = true;
|
|
113
|
+
const outcome = await options.onVerificationChallenge(challenge);
|
|
114
|
+
if (outcome.retry) {
|
|
115
|
+
if (outcome.token !== undefined) {
|
|
116
|
+
verificationToken = outcome.token;
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw parseErrorEnvelope(response.status, errorBody);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
baseUrl: options.baseUrl,
|
|
127
|
+
request,
|
|
128
|
+
get: (path, opts) => request(path, { ...opts, method: "GET" }),
|
|
129
|
+
post: (path, body, opts) => request(path, { ...opts, method: "POST", body }),
|
|
130
|
+
put: (path, body, opts) => request(path, { ...opts, method: "PUT", body }),
|
|
131
|
+
patch: (path, body, opts) => request(path, { ...opts, method: "PATCH", body }),
|
|
132
|
+
delete: (path, opts) => request(path, { ...opts, method: "DELETE" }),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { StapelClient } from "./client.js";
|
|
3
|
+
import type { Analytics } from "./analytics/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* App-level Stapel configuration. `clients` allows per-module client
|
|
6
|
+
* overrides — the client-injection fork-resolution seam of
|
|
7
|
+
* frontend-standard §7.2: a divergent backend gets its own generated client
|
|
8
|
+
* injected into the same package machines.
|
|
9
|
+
*/
|
|
10
|
+
export interface StapelConfig {
|
|
11
|
+
/** Default API client used by all `@stapel/<module>-react` packages. */
|
|
12
|
+
readonly client: StapelClient;
|
|
13
|
+
/** Per-module overrides, keyed by module name (e.g. `"auth"`). */
|
|
14
|
+
readonly clients?: Readonly<Record<string, StapelClient>>;
|
|
15
|
+
}
|
|
16
|
+
export declare function StapelConfigProvider(props: {
|
|
17
|
+
config: StapelConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Optional analytics facade (analytics-standard §2); when provided,
|
|
20
|
+
* `useAnalytics()` works anywhere below. Omitting it keeps the previous
|
|
21
|
+
* behaviour.
|
|
22
|
+
*/
|
|
23
|
+
analytics?: Analytics;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}): ReactElement;
|
|
26
|
+
export declare function useStapelConfig(): StapelConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the API client for a module: the per-module override when
|
|
29
|
+
* configured, otherwise the default client.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useStapelClient(module?: string): StapelClient;
|
|
32
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC3D;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAC1C,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,YAAY,CAQf;AAED,wBAAgB,eAAe,IAAI,YAAY,CAQ9C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CAO7D"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { AnalyticsContext } from "./analytics/context.js";
|
|
4
|
+
const StapelConfigContext = createContext(null);
|
|
5
|
+
export function StapelConfigProvider(props) {
|
|
6
|
+
return (_jsx(StapelConfigContext.Provider, { value: props.config, children: _jsx(AnalyticsContext.Provider, { value: props.analytics ?? null, children: props.children }) }));
|
|
7
|
+
}
|
|
8
|
+
export function useStapelConfig() {
|
|
9
|
+
const config = useContext(StapelConfigContext);
|
|
10
|
+
if (config === null) {
|
|
11
|
+
throw new Error("useStapelConfig must be used within a <StapelConfigProvider>");
|
|
12
|
+
}
|
|
13
|
+
return config;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the API client for a module: the per-module override when
|
|
17
|
+
* configured, otherwise the default client.
|
|
18
|
+
*/
|
|
19
|
+
export function useStapelClient(module) {
|
|
20
|
+
const config = useStapelConfig();
|
|
21
|
+
if (module !== undefined) {
|
|
22
|
+
const override = config.clients?.[module];
|
|
23
|
+
if (override)
|
|
24
|
+
return override;
|
|
25
|
+
}
|
|
26
|
+
return config.client;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAGlD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAgB1D,MAAM,mBAAmB,GAAG,aAAa,CAAsB,IAAI,CAAC,CAAC;AAErE,MAAM,UAAU,oBAAoB,CAAC,KASpC;IACC,OAAO,CACL,KAAC,mBAAmB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,CAAC,MAAM,YAC/C,KAAC,gBAAgB,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI,YACtD,KAAK,CAAC,QAAQ,GACW,GACC,CAChC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC;IAC/C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;IAChC,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC;AACvB,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Stapel backend error envelope:
|
|
3
|
+
* `{ localizable_error: "auth.otp.invalid", error: "Invalid OTP", params: {...} }`
|
|
4
|
+
* `localizable_error` is an i18n key; `params` feed `{param}` interpolation.
|
|
5
|
+
*/
|
|
6
|
+
export interface StapelErrorEnvelope {
|
|
7
|
+
readonly localizable_error?: string;
|
|
8
|
+
readonly error?: string;
|
|
9
|
+
readonly params?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare class StapelApiError extends Error {
|
|
12
|
+
/** i18n key from `localizable_error` (fallback: `stapel.http.<status>`). */
|
|
13
|
+
readonly code: string;
|
|
14
|
+
/** Interpolation params for the i18n key. */
|
|
15
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
16
|
+
/** HTTP status code. */
|
|
17
|
+
readonly status: number;
|
|
18
|
+
/** Raw (parsed) response body, for diagnostics and extensions. */
|
|
19
|
+
readonly body: unknown;
|
|
20
|
+
constructor(args: {
|
|
21
|
+
code: string;
|
|
22
|
+
message: string;
|
|
23
|
+
params?: Record<string, unknown>;
|
|
24
|
+
status: number;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Parse a failed response body (already JSON-decoded; may be anything) into
|
|
30
|
+
* a `StapelApiError`. Tolerant of non-envelope bodies.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseErrorEnvelope(status: number, body: unknown): StapelApiError;
|
|
33
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,qBAAa,cAAe,SAAQ,KAAK;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,IAAI,EAAE;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB;CAQF;AAMD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,GACZ,cAAc,CAqBhB"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class StapelApiError extends Error {
|
|
2
|
+
/** i18n key from `localizable_error` (fallback: `stapel.http.<status>`). */
|
|
3
|
+
code;
|
|
4
|
+
/** Interpolation params for the i18n key. */
|
|
5
|
+
params;
|
|
6
|
+
/** HTTP status code. */
|
|
7
|
+
status;
|
|
8
|
+
/** Raw (parsed) response body, for diagnostics and extensions. */
|
|
9
|
+
body;
|
|
10
|
+
constructor(args) {
|
|
11
|
+
super(args.message);
|
|
12
|
+
this.name = "StapelApiError";
|
|
13
|
+
this.code = args.code;
|
|
14
|
+
this.params = args.params ?? {};
|
|
15
|
+
this.status = args.status;
|
|
16
|
+
this.body = args.body;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function isRecord(value) {
|
|
20
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a failed response body (already JSON-decoded; may be anything) into
|
|
24
|
+
* a `StapelApiError`. Tolerant of non-envelope bodies.
|
|
25
|
+
*/
|
|
26
|
+
export function parseErrorEnvelope(status, body) {
|
|
27
|
+
const fallbackCode = `stapel.http.${String(status)}`;
|
|
28
|
+
if (!isRecord(body)) {
|
|
29
|
+
return new StapelApiError({
|
|
30
|
+
code: fallbackCode,
|
|
31
|
+
message: `Request failed with status ${String(status)}`,
|
|
32
|
+
status,
|
|
33
|
+
body,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const code = typeof body["localizable_error"] === "string" &&
|
|
37
|
+
body["localizable_error"].length > 0
|
|
38
|
+
? body["localizable_error"]
|
|
39
|
+
: fallbackCode;
|
|
40
|
+
const message = typeof body["error"] === "string" && body["error"].length > 0
|
|
41
|
+
? body["error"]
|
|
42
|
+
: code;
|
|
43
|
+
const params = isRecord(body["params"]) ? body["params"] : {};
|
|
44
|
+
return new StapelApiError({ code, message, params, status, body });
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,4EAA4E;IACnE,IAAI,CAAS;IACtB,6CAA6C;IACpC,MAAM,CAAoC;IACnD,wBAAwB;IACf,MAAM,CAAS;IACxB,kEAAkE;IACzD,IAAI,CAAU;IAEvB,YAAY,IAMX;QACC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QAChC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAc,EACd,IAAa;IAEb,MAAM,YAAY,GAAG,eAAe,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,cAAc,CAAC;YACxB,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,8BAA8B,MAAM,CAAC,MAAM,CAAC,EAAE;YACvD,MAAM;YACN,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IACD,MAAM,IAAI,GACR,OAAO,IAAI,CAAC,mBAAmB,CAAC,KAAK,QAAQ;QAC7C,IAAI,CAAC,mBAAmB,CAAC,CAAC,MAAM,GAAG,CAAC;QAClC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC;QAC3B,CAAC,CAAC,YAAY,CAAC;IACnB,MAAM,OAAO,GACX,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC;QAC3D,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QACf,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9D,OAAO,IAAI,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AACrE,CAAC"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
/** Flat key → string dictionary, e.g. `{"auth.otp.invalid": "Invalid code"}`. */
|
|
3
|
+
export type I18nDictionary = Record<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* Async locale loader seam. Point it at the stapel-translate pair:
|
|
6
|
+
* `loadLocale: (locale) => translateClient.resolve(locale)` — the engine
|
|
7
|
+
* calls it once per locale and caches the result as a bundle.
|
|
8
|
+
*/
|
|
9
|
+
export type LocaleLoader = (locale: string) => Promise<I18nDictionary>;
|
|
10
|
+
export type TranslateFn = (key: string, params?: Record<string, unknown>) => string;
|
|
11
|
+
export interface I18nEngine {
|
|
12
|
+
/** Current locale. */
|
|
13
|
+
readonly locale: string;
|
|
14
|
+
/** Translate a key; missing keys fall back to the key itself. */
|
|
15
|
+
t: TranslateFn;
|
|
16
|
+
/** Switch locale; loads it via `loadLocale` when not already registered. */
|
|
17
|
+
setLocale(locale: string): Promise<void>;
|
|
18
|
+
/** Register a static bundle (packages register their keys this way). */
|
|
19
|
+
registerBundle(locale: string, bundle: I18nDictionary): void;
|
|
20
|
+
/** Subscribe to engine changes (locale switches, bundle registration). */
|
|
21
|
+
subscribe(listener: () => void): () => void;
|
|
22
|
+
/** Monotonic change counter (for useSyncExternalStore). */
|
|
23
|
+
getVersion(): number;
|
|
24
|
+
}
|
|
25
|
+
/** `{param}` interpolation. Unknown params are left as-is. */
|
|
26
|
+
export declare function interpolate(template: string, params?: Record<string, unknown>): string;
|
|
27
|
+
export interface CreateI18nOptions {
|
|
28
|
+
/** Initial locale. */
|
|
29
|
+
readonly locale: string;
|
|
30
|
+
/** Static bundles, keyed by locale. */
|
|
31
|
+
readonly bundles?: Readonly<Record<string, I18nDictionary>>;
|
|
32
|
+
/** Async loader for locales not covered by static bundles. */
|
|
33
|
+
readonly loadLocale?: LocaleLoader;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Minimal i18n engine: dictionaries per locale, `{param}` interpolation,
|
|
37
|
+
* static bundles + async loader, missing-key fallback to the key itself
|
|
38
|
+
* (frontend-standard §4.2 — user-facing strings are always keys).
|
|
39
|
+
*/
|
|
40
|
+
export declare function createI18n(options: CreateI18nOptions): I18nEngine;
|
|
41
|
+
export declare function I18nProvider(props: {
|
|
42
|
+
i18n: I18nEngine;
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
}): ReactElement;
|
|
45
|
+
export declare function useI18n(): I18nEngine;
|
|
46
|
+
/**
|
|
47
|
+
* Reactive translate function: re-renders on locale switches and bundle
|
|
48
|
+
* registration.
|
|
49
|
+
*/
|
|
50
|
+
export declare function useT(): TranslateFn;
|
|
51
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,iFAAiF;AACjF,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEpD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEvE,MAAM,MAAM,WAAW,GAAG,CACxB,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC7B,MAAM,CAAC;AAEZ,MAAM,WAAW,UAAU;IACzB,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,CAAC,EAAE,WAAW,CAAC;IACf,4EAA4E;IAC5E,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,wEAAwE;IACxE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7D,0EAA0E;IAC1E,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC5C,2DAA2D;IAC3D,UAAU,IAAI,MAAM,CAAC;CACtB;AAED,8DAA8D;AAC9D,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,MAAM,CAOR;AAED,MAAM,WAAW,iBAAiB;IAChC,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;CACpC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,UAAU,CAuDjE;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,YAAY,CAMf;AAED,wBAAgB,OAAO,IAAI,UAAU,CAMpC;AAED;;;GAGG;AACH,wBAAgB,IAAI,IAAI,WAAW,CAIlC"}
|