@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
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
3
|
+
/** `{param}` interpolation. Unknown params are left as-is. */
|
|
4
|
+
export function interpolate(template, params) {
|
|
5
|
+
if (!params)
|
|
6
|
+
return template;
|
|
7
|
+
return template.replace(/\{([\w.]+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(params, name)
|
|
8
|
+
? String(params[name])
|
|
9
|
+
: match);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Minimal i18n engine: dictionaries per locale, `{param}` interpolation,
|
|
13
|
+
* static bundles + async loader, missing-key fallback to the key itself
|
|
14
|
+
* (frontend-standard §4.2 — user-facing strings are always keys).
|
|
15
|
+
*/
|
|
16
|
+
export function createI18n(options) {
|
|
17
|
+
const dictionaries = new Map();
|
|
18
|
+
const loadedLocales = new Set();
|
|
19
|
+
const listeners = new Set();
|
|
20
|
+
let locale = options.locale;
|
|
21
|
+
let version = 0;
|
|
22
|
+
if (options.bundles) {
|
|
23
|
+
for (const [bundleLocale, bundle] of Object.entries(options.bundles)) {
|
|
24
|
+
dictionaries.set(bundleLocale, { ...bundle });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function notify() {
|
|
28
|
+
version += 1;
|
|
29
|
+
for (const listener of listeners)
|
|
30
|
+
listener();
|
|
31
|
+
}
|
|
32
|
+
async function ensureLoaded(nextLocale) {
|
|
33
|
+
if (!options.loadLocale || loadedLocales.has(nextLocale))
|
|
34
|
+
return;
|
|
35
|
+
loadedLocales.add(nextLocale);
|
|
36
|
+
const bundle = await options.loadLocale(nextLocale);
|
|
37
|
+
const existing = dictionaries.get(nextLocale) ?? {};
|
|
38
|
+
dictionaries.set(nextLocale, { ...existing, ...bundle });
|
|
39
|
+
}
|
|
40
|
+
const engine = {
|
|
41
|
+
get locale() {
|
|
42
|
+
return locale;
|
|
43
|
+
},
|
|
44
|
+
t: (key, params) => {
|
|
45
|
+
const template = dictionaries.get(locale)?.[key];
|
|
46
|
+
if (template === undefined)
|
|
47
|
+
return key;
|
|
48
|
+
return interpolate(template, params);
|
|
49
|
+
},
|
|
50
|
+
setLocale: async (nextLocale) => {
|
|
51
|
+
await ensureLoaded(nextLocale);
|
|
52
|
+
locale = nextLocale;
|
|
53
|
+
notify();
|
|
54
|
+
},
|
|
55
|
+
registerBundle: (bundleLocale, bundle) => {
|
|
56
|
+
const existing = dictionaries.get(bundleLocale) ?? {};
|
|
57
|
+
dictionaries.set(bundleLocale, { ...existing, ...bundle });
|
|
58
|
+
notify();
|
|
59
|
+
},
|
|
60
|
+
subscribe: (listener) => {
|
|
61
|
+
listeners.add(listener);
|
|
62
|
+
return () => {
|
|
63
|
+
listeners.delete(listener);
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
getVersion: () => version,
|
|
67
|
+
};
|
|
68
|
+
return engine;
|
|
69
|
+
}
|
|
70
|
+
const I18nContext = createContext(null);
|
|
71
|
+
export function I18nProvider(props) {
|
|
72
|
+
return (_jsx(I18nContext.Provider, { value: props.i18n, children: props.children }));
|
|
73
|
+
}
|
|
74
|
+
export function useI18n() {
|
|
75
|
+
const engine = useContext(I18nContext);
|
|
76
|
+
if (engine === null) {
|
|
77
|
+
throw new Error("useI18n must be used within an <I18nProvider>");
|
|
78
|
+
}
|
|
79
|
+
return engine;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reactive translate function: re-renders on locale switches and bundle
|
|
83
|
+
* registration.
|
|
84
|
+
*/
|
|
85
|
+
export function useT() {
|
|
86
|
+
const engine = useI18n();
|
|
87
|
+
useSyncExternalStore(engine.subscribe, engine.getVersion, engine.getVersion);
|
|
88
|
+
return engine.t;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export { createStapelClient } from "./client.js";
|
|
2
|
+
export type { StapelClient, StapelClientOptions, StapelRequestOptions, HttpMethod, } from "./client.js";
|
|
3
|
+
export { StapelApiError, parseErrorEnvelope } from "./errors.js";
|
|
4
|
+
export type { StapelErrorEnvelope } from "./errors.js";
|
|
5
|
+
export { extractVerificationChallenge, VERIFICATION_TOKEN_HEADER, } from "./verification.js";
|
|
6
|
+
export type { VerificationChallenge, VerificationOutcome, VerificationChallengeHandler, } from "./verification.js";
|
|
7
|
+
export { StapelConfigProvider, useStapelConfig, useStapelClient, } from "./config.js";
|
|
8
|
+
export type { StapelConfig } from "./config.js";
|
|
9
|
+
export { createStapelQueryClient } from "./query.js";
|
|
10
|
+
export type { StapelQueryRuntime, StapelQueryClientOptions, PersistStorage, } from "./query.js";
|
|
11
|
+
export { createI18n, interpolate, I18nProvider, useI18n, useT } from "./i18n.js";
|
|
12
|
+
export type { I18nEngine, I18nDictionary, LocaleLoader, TranslateFn, CreateI18nOptions, } from "./i18n.js";
|
|
13
|
+
export { createAnalytics } from "./analytics/createAnalytics.js";
|
|
14
|
+
export { consoleProvider, stapelCollectorProvider, } from "./analytics/providers.js";
|
|
15
|
+
export type { StapelCollectorOptions } from "./analytics/providers.js";
|
|
16
|
+
export { trackFlowStep } from "./analytics/flow.js";
|
|
17
|
+
export type { FlowStepPhase } from "./analytics/flow.js";
|
|
18
|
+
export { AnalyticsContext, useAnalytics } from "./analytics/context.js";
|
|
19
|
+
export type { Analytics, AnalyticsEvent, AnalyticsEventKind, AnalyticsProvider, AnalyticsOptions, AnalyticsBatchOptions, ConsentState, PiiGuardMode, } from "./analytics/types.js";
|
|
20
|
+
export { useBreakpoint } from "./useBreakpoint.js";
|
|
21
|
+
export type { Breakpoint } from "@stapel/tokens";
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,oBAAoB,EACpB,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,GAC7B,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACrD,YAAY,EACV,kBAAkB,EAClB,wBAAwB,EACxB,cAAc,GACf,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjF,YAAY,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EACL,eAAe,EACf,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACxE,YAAY,EACV,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,qBAAqB,EACrB,YAAY,EACZ,YAAY,GACb,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// fetch + error envelope
|
|
2
|
+
export { createStapelClient } from "./client.js";
|
|
3
|
+
export { StapelApiError, parseErrorEnvelope } from "./errors.js";
|
|
4
|
+
// verification-403 interception seam
|
|
5
|
+
export { extractVerificationChallenge, VERIFICATION_TOKEN_HEADER, } from "./verification.js";
|
|
6
|
+
// config provider + client injection
|
|
7
|
+
export { StapelConfigProvider, useStapelConfig, useStapelClient, } from "./config.js";
|
|
8
|
+
// query layer + persistence
|
|
9
|
+
export { createStapelQueryClient } from "./query.js";
|
|
10
|
+
// i18n engine
|
|
11
|
+
export { createI18n, interpolate, I18nProvider, useI18n, useT } from "./i18n.js";
|
|
12
|
+
// analytics facade (analytics-standard §2)
|
|
13
|
+
export { createAnalytics } from "./analytics/createAnalytics.js";
|
|
14
|
+
export { consoleProvider, stapelCollectorProvider, } from "./analytics/providers.js";
|
|
15
|
+
export { trackFlowStep } from "./analytics/flow.js";
|
|
16
|
+
export { AnalyticsContext, useAnalytics } from "./analytics/context.js";
|
|
17
|
+
// breakpoints
|
|
18
|
+
export { useBreakpoint } from "./useBreakpoint.js";
|
|
19
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yBAAyB;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAOjD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjE,qCAAqC;AACrC,OAAO,EACL,4BAA4B,EAC5B,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAO3B,qCAAqC;AACrC,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AAGrB,4BAA4B;AAC5B,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAOrD,cAAc;AACd,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjF,2CAA2C;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EACL,eAAe,EACf,uBAAuB,GACxB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAYxE,cAAc;AACd,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { PersistStorage } from "./storage.js";
|
|
3
|
+
export type { PersistStorage } from "./storage.js";
|
|
4
|
+
export interface StapelQueryClientOptions {
|
|
5
|
+
/** Storage key prefix. Default `"stapel-query"`. */
|
|
6
|
+
readonly cacheKeyPrefix?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Cache buster: persisted state written under a different version is
|
|
9
|
+
* discarded on restore. Convention: the consuming package's version.
|
|
10
|
+
*/
|
|
11
|
+
readonly cacheVersion?: string;
|
|
12
|
+
/** Override the storage backend (tests, custom stores). */
|
|
13
|
+
readonly storage?: PersistStorage;
|
|
14
|
+
/** Bring your own QueryClient (defaults applied only when absent). */
|
|
15
|
+
readonly queryClient?: QueryClient;
|
|
16
|
+
/** Debounce for persist writes, ms. Default 100. */
|
|
17
|
+
readonly throttleMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface StapelQueryRuntime {
|
|
20
|
+
readonly queryClient: QueryClient;
|
|
21
|
+
/**
|
|
22
|
+
* Switch the persistence namespace to a user (per-user cache scope,
|
|
23
|
+
* frontend-standard §4.6). Restores that user's persisted state, then
|
|
24
|
+
* mirrors cache changes back to storage. Pass `null` to stop persisting
|
|
25
|
+
* (anonymous / logged out).
|
|
26
|
+
*/
|
|
27
|
+
setPersistUser(userId: string | null): Promise<void>;
|
|
28
|
+
/** Write any pending state now (also useful in tests). */
|
|
29
|
+
flushPersist(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Remove ALL persisted Stapel query state (every user namespace) and
|
|
32
|
+
* clear the in-memory cache. Call on logout and for GDPR erasure.
|
|
33
|
+
*/
|
|
34
|
+
purgePersistedCache(): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* TanStack Query v5 client wrapped with Stapel persistence: per-user
|
|
38
|
+
* namespaces in IndexedDB (localStorage fallback), version-busted restore,
|
|
39
|
+
* and a GDPR-grade purge.
|
|
40
|
+
*/
|
|
41
|
+
export declare function createStapelQueryClient(options?: StapelQueryClientOptions): StapelQueryRuntime;
|
|
42
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAsB,MAAM,uBAAuB,CAAC;AAGxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAOnD,MAAM,WAAW,wBAAwB;IACvC,oDAAoD;IACpD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC;;;;;OAKG;IACH,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,0DAA0D;IAC1D,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;OAGG;IACH,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,wBAA6B,GACrC,kBAAkB,CAiGpB"}
|
package/dist/query.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { QueryClient, dehydrate, hydrate } from "@tanstack/react-query";
|
|
2
|
+
import { defaultPersistStorage } from "./storage.js";
|
|
3
|
+
/**
|
|
4
|
+
* TanStack Query v5 client wrapped with Stapel persistence: per-user
|
|
5
|
+
* namespaces in IndexedDB (localStorage fallback), version-busted restore,
|
|
6
|
+
* and a GDPR-grade purge.
|
|
7
|
+
*/
|
|
8
|
+
export function createStapelQueryClient(options = {}) {
|
|
9
|
+
const prefix = options.cacheKeyPrefix ?? "stapel-query";
|
|
10
|
+
const version = options.cacheVersion ?? "0";
|
|
11
|
+
const storage = options.storage ?? defaultPersistStorage();
|
|
12
|
+
const throttleMs = options.throttleMs ?? 100;
|
|
13
|
+
const queryClient = options.queryClient ??
|
|
14
|
+
new QueryClient({
|
|
15
|
+
defaultOptions: {
|
|
16
|
+
queries: {
|
|
17
|
+
staleTime: 30_000,
|
|
18
|
+
gcTime: 24 * 60 * 60 * 1000,
|
|
19
|
+
retry: (failureCount, error) => {
|
|
20
|
+
// Do not retry envelope errors the app must handle (4xx).
|
|
21
|
+
const status = error.status;
|
|
22
|
+
if (status !== undefined && status >= 400 && status < 500) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return failureCount < 2;
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
let currentKey = null;
|
|
31
|
+
let unsubscribe = null;
|
|
32
|
+
let timer = null;
|
|
33
|
+
const keyFor = (userId) => `${prefix}:${userId}`;
|
|
34
|
+
async function persistNow() {
|
|
35
|
+
if (currentKey === null)
|
|
36
|
+
return;
|
|
37
|
+
const record = {
|
|
38
|
+
version,
|
|
39
|
+
state: dehydrate(queryClient),
|
|
40
|
+
};
|
|
41
|
+
await storage.set(currentKey, record);
|
|
42
|
+
}
|
|
43
|
+
function schedulePersist() {
|
|
44
|
+
if (currentKey === null || timer !== null)
|
|
45
|
+
return;
|
|
46
|
+
timer = setTimeout(() => {
|
|
47
|
+
timer = null;
|
|
48
|
+
void persistNow();
|
|
49
|
+
}, throttleMs);
|
|
50
|
+
}
|
|
51
|
+
function stopPersisting() {
|
|
52
|
+
if (unsubscribe) {
|
|
53
|
+
unsubscribe();
|
|
54
|
+
unsubscribe = null;
|
|
55
|
+
}
|
|
56
|
+
if (timer !== null) {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
timer = null;
|
|
59
|
+
}
|
|
60
|
+
currentKey = null;
|
|
61
|
+
}
|
|
62
|
+
async function setPersistUser(userId) {
|
|
63
|
+
if (currentKey !== null)
|
|
64
|
+
await persistNow();
|
|
65
|
+
stopPersisting();
|
|
66
|
+
if (userId === null)
|
|
67
|
+
return;
|
|
68
|
+
currentKey = keyFor(userId);
|
|
69
|
+
const stored = (await storage.get(currentKey));
|
|
70
|
+
if (stored && stored.version === version) {
|
|
71
|
+
hydrate(queryClient, stored.state);
|
|
72
|
+
}
|
|
73
|
+
else if (stored) {
|
|
74
|
+
await storage.del(currentKey);
|
|
75
|
+
}
|
|
76
|
+
unsubscribe = queryClient.getQueryCache().subscribe(schedulePersist);
|
|
77
|
+
}
|
|
78
|
+
async function flushPersist() {
|
|
79
|
+
if (timer !== null) {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
timer = null;
|
|
82
|
+
}
|
|
83
|
+
await persistNow();
|
|
84
|
+
}
|
|
85
|
+
async function purgePersistedCache() {
|
|
86
|
+
stopPersisting();
|
|
87
|
+
const allKeys = await storage.keys();
|
|
88
|
+
await Promise.all(allKeys
|
|
89
|
+
.filter((key) => key.startsWith(`${prefix}:`))
|
|
90
|
+
.map((key) => storage.del(key)));
|
|
91
|
+
queryClient.clear();
|
|
92
|
+
}
|
|
93
|
+
return { queryClient, setPersistUser, flushPersist, purgePersistedCache };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AA4CrD;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAoC,EAAE;IAEtC,MAAM,MAAM,GAAG,OAAO,CAAC,cAAc,IAAI,cAAc,CAAC;IACxD,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC;IAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,qBAAqB,EAAE,CAAC;IAC3D,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,GAAG,CAAC;IAE7C,MAAM,WAAW,GACf,OAAO,CAAC,WAAW;QACnB,IAAI,WAAW,CAAC;YACd,cAAc,EAAE;gBACd,OAAO,EAAE;oBACP,SAAS,EAAE,MAAM;oBACjB,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;oBAC3B,KAAK,EAAE,CAAC,YAAY,EAAE,KAAK,EAAE,EAAE;wBAC7B,0DAA0D;wBAC1D,MAAM,MAAM,GAAI,KAA6B,CAAC,MAAM,CAAC;wBACrD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,EAAE,CAAC;4BAC1D,OAAO,KAAK,CAAC;wBACf,CAAC;wBACD,OAAO,YAAY,GAAG,CAAC,CAAC;oBAC1B,CAAC;iBACF;aACF;SACF,CAAC,CAAC;IAEL,IAAI,UAAU,GAAkB,IAAI,CAAC;IACrC,IAAI,WAAW,GAAwB,IAAI,CAAC;IAC5C,IAAI,KAAK,GAAyC,IAAI,CAAC;IAEvD,MAAM,MAAM,GAAG,CAAC,MAAc,EAAU,EAAE,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;IAEjE,KAAK,UAAU,UAAU;QACvB,IAAI,UAAU,KAAK,IAAI;YAAE,OAAO;QAChC,MAAM,MAAM,GAAoB;YAC9B,OAAO;YACP,KAAK,EAAE,SAAS,CAAC,WAAW,CAAC;SAC9B,CAAC;QACF,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IAED,SAAS,eAAe;QACtB,IAAI,UAAU,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO;QAClD,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACtB,KAAK,GAAG,IAAI,CAAC;YACb,KAAK,UAAU,EAAE,CAAC;QACpB,CAAC,EAAE,UAAU,CAAC,CAAC;IACjB,CAAC;IAED,SAAS,cAAc;QACrB,IAAI,WAAW,EAAE,CAAC;YAChB,WAAW,EAAE,CAAC;YACd,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;QACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;QACD,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,KAAK,UAAU,cAAc,CAAC,MAAqB;QACjD,IAAI,UAAU,KAAK,IAAI;YAAE,MAAM,UAAU,EAAE,CAAC;QAC5C,cAAc,EAAE,CAAC;QACjB,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAE5B,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAEhC,CAAC;QACd,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YACzC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QACrC,CAAC;aAAM,IAAI,MAAM,EAAE,CAAC;YAClB,MAAM,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChC,CAAC;QACD,WAAW,GAAG,WAAW,CAAC,aAAa,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACvE,CAAC;IAED,KAAK,UAAU,YAAY;QACzB,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;QACD,MAAM,UAAU,EAAE,CAAC;IACrB,CAAC;IAED,KAAK,UAAU,mBAAmB;QAChC,cAAc,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QACrC,MAAM,OAAO,CAAC,GAAG,CACf,OAAO;aACJ,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;aAC7C,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAClC,CAAC;QACF,WAAW,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable persistence backend shared by the query persist layer and the
|
|
3
|
+
* analytics offline queue. Default resolution order:
|
|
4
|
+
* IndexedDB (idb-keyval) → localStorage → in-memory (no-op across reloads).
|
|
5
|
+
*/
|
|
6
|
+
export interface PersistStorage {
|
|
7
|
+
get(key: string): Promise<unknown>;
|
|
8
|
+
set(key: string, value: unknown): Promise<void>;
|
|
9
|
+
del(key: string): Promise<void>;
|
|
10
|
+
keys(): Promise<string[]>;
|
|
11
|
+
}
|
|
12
|
+
export declare function idbStorage(): PersistStorage;
|
|
13
|
+
export declare function localStorageAdapter(storageArea: Storage): PersistStorage;
|
|
14
|
+
export declare function memoryStorage(): PersistStorage;
|
|
15
|
+
export declare function defaultPersistStorage(): PersistStorage;
|
|
16
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAC3B;AAED,wBAAgB,UAAU,IAAI,cAAc,CAO3C;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,OAAO,GAAG,cAAc,CA4BxE;AAED,wBAAgB,aAAa,IAAI,cAAc,CAc9C;AAED,wBAAgB,qBAAqB,IAAI,cAAc,CAMtD"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { get as idbGet, set as idbSet, del as idbDel, keys as idbKeys } from "idb-keyval";
|
|
2
|
+
export function idbStorage() {
|
|
3
|
+
return {
|
|
4
|
+
get: (key) => idbGet(key),
|
|
5
|
+
set: (key, value) => idbSet(key, value),
|
|
6
|
+
del: (key) => idbDel(key),
|
|
7
|
+
keys: async () => (await idbKeys()).map(String),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function localStorageAdapter(storageArea) {
|
|
11
|
+
return {
|
|
12
|
+
get: (key) => {
|
|
13
|
+
const raw = storageArea.getItem(key);
|
|
14
|
+
if (raw === null)
|
|
15
|
+
return Promise.resolve(undefined);
|
|
16
|
+
try {
|
|
17
|
+
return Promise.resolve(JSON.parse(raw));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return Promise.resolve(undefined);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
set: (key, value) => {
|
|
24
|
+
storageArea.setItem(key, JSON.stringify(value));
|
|
25
|
+
return Promise.resolve();
|
|
26
|
+
},
|
|
27
|
+
del: (key) => {
|
|
28
|
+
storageArea.removeItem(key);
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
},
|
|
31
|
+
keys: () => {
|
|
32
|
+
const result = [];
|
|
33
|
+
for (let i = 0; i < storageArea.length; i += 1) {
|
|
34
|
+
const key = storageArea.key(i);
|
|
35
|
+
if (key !== null)
|
|
36
|
+
result.push(key);
|
|
37
|
+
}
|
|
38
|
+
return Promise.resolve(result);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function memoryStorage() {
|
|
43
|
+
const map = new Map();
|
|
44
|
+
return {
|
|
45
|
+
get: (key) => Promise.resolve(map.get(key)),
|
|
46
|
+
set: (key, value) => {
|
|
47
|
+
map.set(key, value);
|
|
48
|
+
return Promise.resolve();
|
|
49
|
+
},
|
|
50
|
+
del: (key) => {
|
|
51
|
+
map.delete(key);
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
},
|
|
54
|
+
keys: () => Promise.resolve([...map.keys()]),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function defaultPersistStorage() {
|
|
58
|
+
if (typeof indexedDB !== "undefined")
|
|
59
|
+
return idbStorage();
|
|
60
|
+
if (typeof localStorage !== "undefined") {
|
|
61
|
+
return localStorageAdapter(localStorage);
|
|
62
|
+
}
|
|
63
|
+
return memoryStorage();
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Breakpoint } from "@stapel/tokens";
|
|
2
|
+
/**
|
|
3
|
+
* Current viewport breakpoint from the three `@stapel/tokens` breakpoints
|
|
4
|
+
* (phone / tablet / desktop). SSR-safe: returns `undefined` until mounted,
|
|
5
|
+
* so server and first client render agree.
|
|
6
|
+
*/
|
|
7
|
+
export declare function useBreakpoint(): Breakpoint | undefined;
|
|
8
|
+
//# sourceMappingURL=useBreakpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBreakpoint.d.ts","sourceRoot":"","sources":["../src/useBreakpoint.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,UAAU,GAAG,SAAS,CAiBtD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { breakpointForWidth } from "@stapel/tokens";
|
|
3
|
+
/**
|
|
4
|
+
* Current viewport breakpoint from the three `@stapel/tokens` breakpoints
|
|
5
|
+
* (phone / tablet / desktop). SSR-safe: returns `undefined` until mounted,
|
|
6
|
+
* so server and first client render agree.
|
|
7
|
+
*/
|
|
8
|
+
export function useBreakpoint() {
|
|
9
|
+
const [breakpoint, setBreakpoint] = useState(undefined);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const update = () => {
|
|
12
|
+
setBreakpoint(breakpointForWidth(window.innerWidth));
|
|
13
|
+
};
|
|
14
|
+
update();
|
|
15
|
+
window.addEventListener("resize", update);
|
|
16
|
+
return () => {
|
|
17
|
+
window.removeEventListener("resize", update);
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
return breakpoint;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=useBreakpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBreakpoint.js","sourceRoot":"","sources":["../src/useBreakpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAGpD;;;;GAIG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAC1C,SAAS,CACV,CAAC;IAEF,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,aAAa,CAAC,kBAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;QACvD,CAAC,CAAC;QACF,MAAM,EAAE,CAAC;QACT,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC1C,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,UAAU,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step-up verification (the flagship cross-module flow, frontend-standard §2):
|
|
3
|
+
* a 403 whose body carries a `verification` object is a challenge, not a
|
|
4
|
+
* terminal error. `@stapel/core` hands the challenge to a configurable
|
|
5
|
+
* handler (implemented by `@stapel/auth-react`'s factor machines or the
|
|
6
|
+
* host) and retries the original request once on success.
|
|
7
|
+
*/
|
|
8
|
+
export interface VerificationChallenge {
|
|
9
|
+
readonly challenge_id: string;
|
|
10
|
+
/** What the challenge protects, e.g. `"billing.payout"`. */
|
|
11
|
+
readonly scope?: string;
|
|
12
|
+
/** Factors the user may satisfy, e.g. `["totp", "webauthn"]`. */
|
|
13
|
+
readonly factors?: readonly string[];
|
|
14
|
+
/** Backends may attach extra fields; kept for forward-compat. */
|
|
15
|
+
readonly [extra: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface VerificationOutcome {
|
|
18
|
+
/** Retry the original request? */
|
|
19
|
+
readonly retry: boolean;
|
|
20
|
+
/** Sent as `X-Verification-Token` on the retry when present. */
|
|
21
|
+
readonly token?: string;
|
|
22
|
+
}
|
|
23
|
+
export type VerificationChallengeHandler = (challenge: VerificationChallenge) => Promise<VerificationOutcome>;
|
|
24
|
+
/** Header carrying the verification proof on retried requests. */
|
|
25
|
+
export declare const VERIFICATION_TOKEN_HEADER = "X-Verification-Token";
|
|
26
|
+
/**
|
|
27
|
+
* Extract a verification challenge from a 403 response body, or `null` when
|
|
28
|
+
* the body is a plain error envelope.
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractVerificationChallenge(body: unknown): VerificationChallenge | null;
|
|
31
|
+
//# sourceMappingURL=verification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification.d.ts","sourceRoot":"","sources":["../src/verification.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,4DAA4D;IAC5D,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,iEAAiE;IACjE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,kCAAkC;IAClC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,4BAA4B,GAAG,CACzC,SAAS,EAAE,qBAAqB,KAC7B,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC,kEAAkE;AAClE,eAAO,MAAM,yBAAyB,yBAAyB,CAAC;AAMhE;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,OAAO,GACZ,qBAAqB,GAAG,IAAI,CAM9B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Header carrying the verification proof on retried requests. */
|
|
2
|
+
export const VERIFICATION_TOKEN_HEADER = "X-Verification-Token";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Extract a verification challenge from a 403 response body, or `null` when
|
|
8
|
+
* the body is a plain error envelope.
|
|
9
|
+
*/
|
|
10
|
+
export function extractVerificationChallenge(body) {
|
|
11
|
+
if (!isRecord(body))
|
|
12
|
+
return null;
|
|
13
|
+
const verification = body["verification"];
|
|
14
|
+
if (!isRecord(verification))
|
|
15
|
+
return null;
|
|
16
|
+
if (typeof verification["challenge_id"] !== "string")
|
|
17
|
+
return null;
|
|
18
|
+
return verification;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=verification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification.js","sourceRoot":"","sources":["../src/verification.ts"],"names":[],"mappings":"AA4BA,kEAAkE;AAClE,MAAM,CAAC,MAAM,yBAAyB,GAAG,sBAAsB,CAAC;AAEhE,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,4BAA4B,CAC1C,IAAa;IAEb,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,OAAO,YAAY,CAAC,cAAc,CAAC,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClE,OAAO,YAAgD,CAAC;AAC1D,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stapel/core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Stapel frontend runtime (L0): typed fetch with the Stapel error envelope, auth token/refresh seams, verification-403 interception, TanStack Query layer with per-user persistence, i18n engine, config provider, breakpoints hook.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/usestapel/stapel-react.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./package.json": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"src",
|
|
25
|
+
"tsconfig.json",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"size-limit": [
|
|
29
|
+
{
|
|
30
|
+
"path": "dist/index.js",
|
|
31
|
+
"limit": "12 KB"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@tanstack/react-query": "^5.0.0",
|
|
36
|
+
"react": ">=19"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"idb-keyval": "^6.2.2",
|
|
40
|
+
"@stapel/tokens": "^0.1.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@size-limit/preset-small-lib": "^11.2.0",
|
|
44
|
+
"@tanstack/react-query": "^5.81.0",
|
|
45
|
+
"@testing-library/react": "^16.3.0",
|
|
46
|
+
"@types/react": "^19.1.0",
|
|
47
|
+
"@types/react-dom": "^19.1.0",
|
|
48
|
+
"jsdom": "^26.1.0",
|
|
49
|
+
"msw": "^2.10.2",
|
|
50
|
+
"react": "^19.1.0",
|
|
51
|
+
"react-dom": "^19.1.0",
|
|
52
|
+
"size-limit": "^11.2.0",
|
|
53
|
+
"typescript": "^5.8.3",
|
|
54
|
+
"vitest": "^3.2.4"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"build": "tsc -p tsconfig.json",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"lint": "eslint .",
|
|
66
|
+
"size": "size-limit"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { Context } from "react";
|
|
3
|
+
import type { Analytics } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Provided by `<StapelConfigProvider analytics={...}>` (or directly via
|
|
7
|
+
* `AnalyticsContext.Provider`).
|
|
8
|
+
*/
|
|
9
|
+
export const AnalyticsContext: Context<Analytics | null> =
|
|
10
|
+
createContext<Analytics | null>(null);
|
|
11
|
+
|
|
12
|
+
export function useAnalytics(): Analytics {
|
|
13
|
+
const analytics = useContext(AnalyticsContext);
|
|
14
|
+
if (analytics === null) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"useAnalytics requires an analytics instance — pass it to <StapelConfigProvider analytics={...}>"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return analytics;
|
|
20
|
+
}
|