@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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/dist/analytics/context.d.ts +9 -0
  4. package/dist/analytics/context.d.ts.map +1 -0
  5. package/dist/analytics/context.js +14 -0
  6. package/dist/analytics/context.js.map +1 -0
  7. package/dist/analytics/createAnalytics.d.ts +10 -0
  8. package/dist/analytics/createAnalytics.d.ts.map +1 -0
  9. package/dist/analytics/createAnalytics.js +273 -0
  10. package/dist/analytics/createAnalytics.js.map +1 -0
  11. package/dist/analytics/flow.d.ts +10 -0
  12. package/dist/analytics/flow.d.ts.map +1 -0
  13. package/dist/analytics/flow.js +10 -0
  14. package/dist/analytics/flow.js.map +1 -0
  15. package/dist/analytics/hash.d.ts +3 -0
  16. package/dist/analytics/hash.d.ts.map +1 -0
  17. package/dist/analytics/hash.js +12 -0
  18. package/dist/analytics/hash.js.map +1 -0
  19. package/dist/analytics/pii.d.ts +9 -0
  20. package/dist/analytics/pii.d.ts.map +1 -0
  21. package/dist/analytics/pii.js +52 -0
  22. package/dist/analytics/pii.js.map +1 -0
  23. package/dist/analytics/providers.d.ts +28 -0
  24. package/dist/analytics/providers.d.ts.map +1 -0
  25. package/dist/analytics/providers.js +82 -0
  26. package/dist/analytics/providers.js.map +1 -0
  27. package/dist/analytics/types.d.ts +94 -0
  28. package/dist/analytics/types.d.ts.map +1 -0
  29. package/dist/analytics/types.js +7 -0
  30. package/dist/analytics/types.js.map +1 -0
  31. package/dist/client.d.ts +49 -0
  32. package/dist/client.d.ts.map +1 -0
  33. package/dist/client.js +135 -0
  34. package/dist/client.js.map +1 -0
  35. package/dist/config.d.ts +32 -0
  36. package/dist/config.d.ts.map +1 -0
  37. package/dist/config.js +28 -0
  38. package/dist/config.js.map +1 -0
  39. package/dist/errors.d.ts +33 -0
  40. package/dist/errors.d.ts.map +1 -0
  41. package/dist/errors.js +46 -0
  42. package/dist/errors.js.map +1 -0
  43. package/dist/i18n.d.ts +51 -0
  44. package/dist/i18n.d.ts.map +1 -0
  45. package/dist/i18n.js +90 -0
  46. package/dist/i18n.js.map +1 -0
  47. package/dist/index.d.ts +22 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +19 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/query.d.ts +42 -0
  52. package/dist/query.d.ts.map +1 -0
  53. package/dist/query.js +95 -0
  54. package/dist/query.js.map +1 -0
  55. package/dist/storage.d.ts +16 -0
  56. package/dist/storage.d.ts.map +1 -0
  57. package/dist/storage.js +65 -0
  58. package/dist/storage.js.map +1 -0
  59. package/dist/useBreakpoint.d.ts +8 -0
  60. package/dist/useBreakpoint.d.ts.map +1 -0
  61. package/dist/useBreakpoint.js +22 -0
  62. package/dist/useBreakpoint.js.map +1 -0
  63. package/dist/verification.d.ts +31 -0
  64. package/dist/verification.d.ts.map +1 -0
  65. package/dist/verification.js +20 -0
  66. package/dist/verification.js.map +1 -0
  67. package/package.json +68 -0
  68. package/src/analytics/context.ts +20 -0
  69. package/src/analytics/createAnalytics.ts +310 -0
  70. package/src/analytics/flow.ts +19 -0
  71. package/src/analytics/hash.ts +16 -0
  72. package/src/analytics/pii.ts +66 -0
  73. package/src/analytics/providers.ts +108 -0
  74. package/src/analytics/types.ts +105 -0
  75. package/src/client.ts +206 -0
  76. package/src/config.tsx +62 -0
  77. package/src/errors.ts +70 -0
  78. package/src/i18n.tsx +147 -0
  79. package/src/index.ts +72 -0
  80. package/src/query.ts +151 -0
  81. package/src/storage.ts +76 -0
  82. package/src/useBreakpoint.ts +27 -0
  83. package/src/verification.ts +48 -0
  84. package/tsconfig.json +26 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stapel contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @stapel/core
2
+
3
+ The Stapel frontend runtime (L0, frontend-standard §1). Everything the
4
+ `@stapel/<module>-react` pairs build on:
5
+
6
+ - **`createStapelClient`** — typed fetch wrapper: base URL, auth token getter
7
+ + refresh seam, parses the Stapel error envelope
8
+ `{localizable_error, error, params}` into `StapelApiError`
9
+ (`code` = the `localizable_error` i18n key, `params` for interpolation).
10
+ - **Verification-403 interception** — when a 403 body carries a
11
+ `verification` object (`challenge_id`/`scope`/`factors`), the configured
12
+ `onVerificationChallenge(challenge)` is invoked; on
13
+ `{ retry: true, token }` the original request is retried once with
14
+ `X-Verification-Token`. This is the seam `@stapel/auth-react`'s factor
15
+ machines plug into (the flagship cross-module flow, standard §2).
16
+ - **`StapelConfigProvider`** — provides the default client plus per-module
17
+ client overrides (client injection, the fork-resolution seam of §7.2).
18
+ - **Query layer** — `createStapelQueryClient()`: TanStack Query v5 client
19
+ with a persistence runtime: IndexedDB via `idb-keyval`, `localStorage`
20
+ fallback, in-memory last resort; **per-user namespace** via
21
+ `setPersistUser(userId)`; `purgePersistedCache()` for logout/GDPR;
22
+ cache-version buster (set it to your package version).
23
+ - **i18n engine** — key→string dictionaries, `{param}` interpolation,
24
+ `I18nProvider` / `useT` / `useI18n`, static bundles + async locale loader
25
+ seam (point it at `translate.resolve` of stapel-translate), missing keys
26
+ fall back to the key itself.
27
+ - **`useBreakpoint()`** — resolves the three `@stapel/tokens` breakpoints;
28
+ SSR-safe (`undefined` until mounted).
29
+ - **Analytics facade** — see the dedicated section below
30
+ (analytics-standard §2).
31
+
32
+ ## Analytics facade
33
+
34
+ Per [analytics-standard](../../../docs/analytics-standard.md) §1–2 and
35
+ frontend-standard §4.7: packages and hosts talk to the facade only, never to
36
+ providers directly.
37
+
38
+ ```ts
39
+ import {
40
+ createAnalytics,
41
+ consoleProvider,
42
+ stapelCollectorProvider,
43
+ trackFlowStep,
44
+ } from "@stapel/core";
45
+
46
+ const analytics = createAnalytics({
47
+ providers: {
48
+ stapel: stapelCollectorProvider({ client, writeKey: "wk_…" }),
49
+ console: consoleProvider(), // dev
50
+ },
51
+ registry: eventsJson.map((e) => e.name), // dev warning; hard gate = eslint
52
+ piiGuard: "strip", // email/phone-like prop values → "[redacted]"
53
+ });
54
+
55
+ analytics.track("cart.checkout", { total: 42 });
56
+ analytics.identify(user.id, { plan: "pro" }); // id is SHA-256-hashed first
57
+ analytics.page("pricing");
58
+ await analytics.setConsent("granted"); // "pending" buffers, "denied" drops
59
+ trackFlowStep(analytics, "onboarding", "otp", "completed"); // flow.<id>.<step>
60
+ ```
61
+
62
+ Semantics: consent gate (buffer while `pending`, flush on `granted`, drop +
63
+ no-op on `denied`; consent persists), offline queue on the shared persist
64
+ storage (IndexedDB → localStorage → memory; survives instance recreation),
65
+ batched fan-out to all registered providers with per-provider delivery
66
+ tracking, exponential-backoff retries (drop with a warning after
67
+ `maxAttempts`), flush on interval / `maxSize` / explicit `flush()` /
68
+ `pagehide` + `visibilitychange: hidden` (the Stapel collector uses
69
+ `navigator.sendBeacon` for that final batch). `register`/`unregister` change
70
+ the fan-out at runtime — extra providers (Mixpanel, GA4, PostHog…) are tiny
71
+ `@stapel/analytics-<provider>` packages or app-layer classes. Wire React via
72
+ `<StapelConfigProvider analytics={analytics}>` and `useAnalytics()`.
73
+
74
+ ## Quick start
75
+
76
+ ```tsx
77
+ import {
78
+ createStapelClient,
79
+ createStapelQueryClient,
80
+ createI18n,
81
+ StapelConfigProvider,
82
+ I18nProvider,
83
+ } from "@stapel/core";
84
+ import { QueryClientProvider } from "@tanstack/react-query";
85
+
86
+ const client = createStapelClient({
87
+ baseUrl: "https://api.example.com",
88
+ getToken: () => auth.accessToken,
89
+ onAuthRefresh: () => auth.refresh(),
90
+ onVerificationChallenge: (challenge) => verificationUi.run(challenge),
91
+ });
92
+
93
+ const query = createStapelQueryClient({ cacheVersion: "0.1.0" });
94
+ const i18n = createI18n({
95
+ locale: "en",
96
+ loadLocale: (locale) => translateClient.resolve(locale), // stapel-translate
97
+ });
98
+
99
+ <StapelConfigProvider config={{ client }}>
100
+ <QueryClientProvider client={query.queryClient}>
101
+ <I18nProvider i18n={i18n}>{app}</I18nProvider>
102
+ </QueryClientProvider>
103
+ </StapelConfigProvider>;
104
+
105
+ // on login: await query.setPersistUser(user.id);
106
+ // on logout: await query.purgePersistedCache();
107
+ ```
108
+
109
+ ## Notes
110
+
111
+ - Peer deps: `react >= 19`, `@tanstack/react-query ^5`. Only runtime dep:
112
+ `idb-keyval` (+ `@stapel/tokens`).
113
+ - Standalone-buildable; the npm tarball ships `src/` (frontend-standard §7).
114
+ - TODO(frontend-standard §4.5): precompile with React Compiler at publish so
115
+ consumers outside the Stapel toolchain get baked-in memoization. The package
116
+ is currently hooks-only (no JSX-heavy render paths), so the compiler yields
117
+ nothing yet; wire it into the publish pipeline together with the first
118
+ headless components.
@@ -0,0 +1,9 @@
1
+ import type { Context } from "react";
2
+ import type { Analytics } from "./types.js";
3
+ /**
4
+ * Provided by `<StapelConfigProvider analytics={...}>` (or directly via
5
+ * `AnalyticsContext.Provider`).
6
+ */
7
+ export declare const AnalyticsContext: Context<Analytics | null>;
8
+ export declare function useAnalytics(): Analytics;
9
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/analytics/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AACrC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,SAAS,GAAG,IAAI,CAChB,CAAC;AAExC,wBAAgB,YAAY,IAAI,SAAS,CAQxC"}
@@ -0,0 +1,14 @@
1
+ import { createContext, useContext } from "react";
2
+ /**
3
+ * Provided by `<StapelConfigProvider analytics={...}>` (or directly via
4
+ * `AnalyticsContext.Provider`).
5
+ */
6
+ export const AnalyticsContext = createContext(null);
7
+ export function useAnalytics() {
8
+ const analytics = useContext(AnalyticsContext);
9
+ if (analytics === null) {
10
+ throw new Error("useAnalytics requires an analytics instance — pass it to <StapelConfigProvider analytics={...}>");
11
+ }
12
+ return analytics;
13
+ }
14
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/analytics/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAIlD;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAC3B,aAAa,CAAmB,IAAI,CAAC,CAAC;AAExC,MAAM,UAAU,YAAY;IAC1B,MAAM,SAAS,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAC/C,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,iGAAiG,CAClG,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Analytics, AnalyticsOptions } from "./types.js";
2
+ /** Exponential backoff delay for a batch delivery attempt (1-based). */
3
+ export declare function backoffDelay(attempt: number, baseMs: number): number;
4
+ /**
5
+ * The analytics facade (analytics-standard §2): fan-out to N providers,
6
+ * consent gate, offline queue on the core persist layer, batched delivery
7
+ * with retry/backoff, PII guard, hashed identify.
8
+ */
9
+ export declare function createAnalytics(options?: AnalyticsOptions): Analytics;
10
+ //# sourceMappingURL=createAnalytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAnalytics.d.ts","sourceRoot":"","sources":["../../src/analytics/createAnalytics.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,SAAS,EAGT,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AAEpB,wEAAwE;AACxE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpE;AAyBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CAuQzE"}
@@ -0,0 +1,273 @@
1
+ import { defaultPersistStorage } from "../storage.js";
2
+ import { guardPii } from "./pii.js";
3
+ import { sha256Hex } from "./hash.js";
4
+ /** Exponential backoff delay for a batch delivery attempt (1-based). */
5
+ export function backoffDelay(attempt, baseMs) {
6
+ return Math.min(30_000, baseMs * 2 ** Math.max(0, attempt - 1));
7
+ }
8
+ async function deliverToProvider(provider, events) {
9
+ for (const event of events) {
10
+ if (event.kind === "identify" && provider.identify) {
11
+ await provider.identify(event.userHash ?? "", event.props);
12
+ }
13
+ else if (event.kind === "page" && provider.page) {
14
+ await provider.page(event.name, event.props);
15
+ }
16
+ else {
17
+ await provider.track(event);
18
+ }
19
+ }
20
+ await provider.flush?.();
21
+ }
22
+ /**
23
+ * The analytics facade (analytics-standard §2): fan-out to N providers,
24
+ * consent gate, offline queue on the core persist layer, batched delivery
25
+ * with retry/backoff, PII guard, hashed identify.
26
+ */
27
+ export function createAnalytics(options = {}) {
28
+ const providers = new Map(Object.entries(options.providers ?? {}));
29
+ const registry = options.registry !== undefined ? new Set(options.registry) : null;
30
+ const piiMode = options.piiGuard ?? "strip";
31
+ const persistKey = options.persistKey ?? "stapel-analytics";
32
+ const storage = options.storage ?? defaultPersistStorage();
33
+ const maxSize = options.batch?.maxSize ?? 20;
34
+ const flushIntervalMs = options.batch?.flushIntervalMs ?? 10_000;
35
+ const maxAttempts = options.batch?.maxAttempts ?? 5;
36
+ const backoffBaseMs = options.batch?.backoffBaseMs ?? 500;
37
+ const eventsKey = `${persistKey}:events`;
38
+ const consentKey = `${persistKey}:consent`;
39
+ let consent = options.consent ?? "pending";
40
+ let queue = [];
41
+ let batch = null;
42
+ let flushing = false;
43
+ let retryTimer = null;
44
+ let seq = 0;
45
+ const warnedPii = new Set();
46
+ const warnedRegistry = new Set();
47
+ /** In-flight async work (identify hashing, persist writes) awaited by flush. */
48
+ let pendingOps = [];
49
+ /** Serializes identify hashing so enqueue order matches call order. */
50
+ let identifyChain = Promise.resolve();
51
+ const ready = (async () => {
52
+ try {
53
+ const storedConsent = await storage.get(consentKey);
54
+ if (storedConsent === "granted" ||
55
+ storedConsent === "denied" ||
56
+ storedConsent === "pending") {
57
+ consent = storedConsent;
58
+ }
59
+ const storedEvents = await storage.get(eventsKey);
60
+ if (Array.isArray(storedEvents)) {
61
+ // Restored events precede anything captured before init finished.
62
+ queue = [...storedEvents, ...queue];
63
+ }
64
+ }
65
+ catch {
66
+ // Storage unavailable — degrade to in-memory only.
67
+ }
68
+ })();
69
+ async function persistQueue() {
70
+ try {
71
+ if (consent === "denied") {
72
+ await storage.del(eventsKey);
73
+ return;
74
+ }
75
+ const pending = batch ? [...batch.events, ...queue] : queue;
76
+ await storage.set(eventsKey, pending);
77
+ }
78
+ catch {
79
+ // Best-effort.
80
+ }
81
+ }
82
+ function schedulePersist() {
83
+ pendingOps.push(ready.then(persistQueue));
84
+ }
85
+ function clearRetryTimer() {
86
+ if (retryTimer !== null) {
87
+ clearTimeout(retryTimer);
88
+ retryTimer = null;
89
+ }
90
+ }
91
+ function scheduleRetry(delayMs) {
92
+ if (retryTimer !== null)
93
+ return;
94
+ retryTimer = setTimeout(() => {
95
+ retryTimer = null;
96
+ void flush();
97
+ }, delayMs);
98
+ retryTimer.unref?.();
99
+ }
100
+ function enqueue(kind, name, props, userHash) {
101
+ seq += 1;
102
+ const event = {
103
+ id: `${String(Date.now())}-${String(seq)}`,
104
+ kind,
105
+ name,
106
+ props,
107
+ ...(userHash !== undefined ? { userHash } : {}),
108
+ ts: Date.now(),
109
+ };
110
+ queue.push(event);
111
+ schedulePersist();
112
+ if (consent === "granted" && queue.length >= maxSize) {
113
+ void flush();
114
+ }
115
+ }
116
+ /** One delivery attempt for the current batch. True = batch settled. */
117
+ async function attemptBatch() {
118
+ const current = batch;
119
+ if (current === null)
120
+ return true;
121
+ current.attempts += 1;
122
+ await Promise.all([...providers.entries()].map(async ([name, provider]) => {
123
+ if (current.delivered.has(name))
124
+ return;
125
+ try {
126
+ await deliverToProvider(provider, current.events);
127
+ current.delivered.add(name);
128
+ }
129
+ catch {
130
+ // Undelivered for this provider; batch will be retried.
131
+ }
132
+ }));
133
+ const undelivered = [...providers.keys()].filter((name) => !current.delivered.has(name));
134
+ if (undelivered.length === 0) {
135
+ batch = null;
136
+ clearRetryTimer();
137
+ return true;
138
+ }
139
+ if (current.attempts >= maxAttempts) {
140
+ console.warn(`[stapel analytics] dropping a batch of ${String(current.events.length)} event(s) ` +
141
+ `after ${String(current.attempts)} attempts; undelivered to: ${undelivered.join(", ")}`);
142
+ batch = null;
143
+ clearRetryTimer();
144
+ return true;
145
+ }
146
+ scheduleRetry(backoffDelay(current.attempts, backoffBaseMs));
147
+ return false;
148
+ }
149
+ async function flush() {
150
+ await ready;
151
+ const ops = pendingOps;
152
+ pendingOps = [];
153
+ await Promise.all(ops);
154
+ if (flushing)
155
+ return;
156
+ flushing = true;
157
+ try {
158
+ if (consent !== "granted")
159
+ return;
160
+ for (;;) {
161
+ if (batch === null) {
162
+ if (queue.length === 0 || providers.size === 0)
163
+ break;
164
+ batch = {
165
+ events: queue.splice(0, maxSize),
166
+ attempts: 0,
167
+ delivered: new Set(),
168
+ };
169
+ }
170
+ const settled = await attemptBatch();
171
+ if (!settled)
172
+ break;
173
+ }
174
+ }
175
+ finally {
176
+ flushing = false;
177
+ await persistQueue();
178
+ }
179
+ }
180
+ async function setConsent(state) {
181
+ await ready;
182
+ consent = state;
183
+ try {
184
+ await storage.set(consentKey, state);
185
+ }
186
+ catch {
187
+ // Best-effort.
188
+ }
189
+ if (state === "denied") {
190
+ queue = [];
191
+ batch = null;
192
+ clearRetryTimer();
193
+ try {
194
+ await storage.del(eventsKey);
195
+ }
196
+ catch {
197
+ // Best-effort.
198
+ }
199
+ return;
200
+ }
201
+ if (state === "granted") {
202
+ await flush();
203
+ }
204
+ }
205
+ function track(event, props) {
206
+ if (consent === "denied")
207
+ return;
208
+ if (registry && !registry.has(event) && !warnedRegistry.has(event)) {
209
+ warnedRegistry.add(event);
210
+ console.warn(`[stapel analytics] event "${event}" is not in the registry ` +
211
+ "(analytics-standard §1.1) — delivered anyway; declare it in events.json.");
212
+ }
213
+ enqueue("track", event, guardPii(event, props ?? {}, piiMode, warnedPii));
214
+ }
215
+ function page(name, props) {
216
+ if (consent === "denied")
217
+ return;
218
+ enqueue("page", name, guardPii(name, props ?? {}, piiMode, warnedPii));
219
+ }
220
+ function identify(userId, traits) {
221
+ if (consent === "denied")
222
+ return;
223
+ const guarded = guardPii("identify", traits ?? {}, piiMode, warnedPii);
224
+ identifyChain = identifyChain
225
+ .then(() => sha256Hex(userId))
226
+ .then((userHash) => {
227
+ enqueue("identify", "identify", guarded, userHash);
228
+ });
229
+ pendingOps.push(identifyChain);
230
+ }
231
+ function finalFlush() {
232
+ // Best-effort teardown flush; batching providers use sendBeacon here.
233
+ void flush();
234
+ for (const provider of providers.values()) {
235
+ try {
236
+ void provider.flush?.();
237
+ }
238
+ catch {
239
+ // Never break page teardown.
240
+ }
241
+ }
242
+ }
243
+ if (typeof window !== "undefined") {
244
+ window.addEventListener("pagehide", finalFlush);
245
+ }
246
+ if (typeof document !== "undefined") {
247
+ document.addEventListener("visibilitychange", () => {
248
+ if (document.visibilityState === "hidden")
249
+ finalFlush();
250
+ });
251
+ }
252
+ const interval = setInterval(() => {
253
+ if (consent === "granted" && (queue.length > 0 || batch !== null)) {
254
+ void flush();
255
+ }
256
+ }, flushIntervalMs);
257
+ interval.unref?.();
258
+ return {
259
+ track,
260
+ identify,
261
+ page,
262
+ flush,
263
+ setConsent,
264
+ getConsent: () => consent,
265
+ register: (name, provider) => {
266
+ providers.set(name, provider);
267
+ },
268
+ unregister: (name) => {
269
+ providers.delete(name);
270
+ },
271
+ };
272
+ }
273
+ //# sourceMappingURL=createAnalytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createAnalytics.js","sourceRoot":"","sources":["../../src/analytics/createAnalytics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Analytics } from "./types.js";
2
+ export type FlowStepPhase = "started" | "completed" | "failed";
3
+ /**
4
+ * Auto-instrumentation seam for flow machines (analytics-standard §1.2):
5
+ * emits `flow.<flowId>.<stepId>` with `{phase, ...props}`. Funnel = flow —
6
+ * the `@stapel/<module>-react` machines call this on every transition, so
7
+ * funnels exist without hand-written instrumentation.
8
+ */
9
+ export declare function trackFlowStep(analytics: Analytics, flowId: string, stepId: string, phase: FlowStepPhase, props?: Record<string, unknown>): void;
10
+ //# sourceMappingURL=flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../../src/analytics/flow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,IAAI,CAEN"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Auto-instrumentation seam for flow machines (analytics-standard §1.2):
3
+ * emits `flow.<flowId>.<stepId>` with `{phase, ...props}`. Funnel = flow —
4
+ * the `@stapel/<module>-react` machines call this on every transition, so
5
+ * funnels exist without hand-written instrumentation.
6
+ */
7
+ export function trackFlowStep(analytics, flowId, stepId, phase, props) {
8
+ analytics.track(`flow.${flowId}.${stepId}`, { phase, ...props });
9
+ }
10
+ //# sourceMappingURL=flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flow.js","sourceRoot":"","sources":["../../src/analytics/flow.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAoB,EACpB,MAAc,EACd,MAAc,EACd,KAAoB,EACpB,KAA+B;IAE/B,SAAS,CAAC,KAAK,CAAC,QAAQ,MAAM,IAAI,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** SHA-256 hex via WebCrypto — user ids are hashed before any provider sees them. */
2
+ export declare function sha256Hex(input: string): Promise<string>;
3
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/analytics/hash.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc9D"}
@@ -0,0 +1,12 @@
1
+ /** SHA-256 hex via WebCrypto — user ids are hashed before any provider sees them. */
2
+ export async function sha256Hex(input) {
3
+ const subtle = globalThis.crypto?.subtle;
4
+ if (!subtle) {
5
+ throw new Error("[stapel analytics] crypto.subtle is unavailable; cannot hash user ids");
6
+ }
7
+ const digest = await subtle.digest("SHA-256", new TextEncoder().encode(input));
8
+ return [...new Uint8Array(digest)]
9
+ .map((byte) => byte.toString(16).padStart(2, "0"))
10
+ .join("");
11
+ }
12
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/analytics/hash.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAa;IAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC;IACzC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,uEAAuE,CACxE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAChC,SAAS,EACT,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAChC,CAAC;IACF,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;SAC/B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;SACjD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { PiiGuardMode } from "./types.js";
2
+ export declare function looksLikePii(value: string): boolean;
3
+ export declare const PII_REDACTED = "[redacted]";
4
+ /**
5
+ * Guard a props/traits object. Warns once per event name (per facade
6
+ * instance) via the caller-owned `warned` set.
7
+ */
8
+ export declare function guardPii(eventName: string, props: Record<string, unknown>, mode: PiiGuardMode, warned: Set<string>): Record<string, unknown>;
9
+ //# sourceMappingURL=pii.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.d.ts","sourceRoot":"","sources":["../../src/analytics/pii.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAY/C,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAKnD;AAED,eAAO,MAAM,YAAY,eAAe,CAAC;AAwBzC;;;GAGG;AACH,wBAAgB,QAAQ,CACtB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,GAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * PII guard heuristics (analytics-standard §1.4): prop VALUES that look
3
+ * like emails or phone numbers are redacted ("strip"), kept with a warning
4
+ * ("warn"), or passed through ("off"). Applies to track/page props and
5
+ * identify traits. Keys are not judged — only values.
6
+ */
7
+ const EMAIL_RE = /[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}/;
8
+ const PHONE_SHAPE_RE = /^\+?[\d\s\-().]{7,}$/;
9
+ export function looksLikePii(value) {
10
+ if (EMAIL_RE.test(value))
11
+ return true;
12
+ const trimmed = value.trim();
13
+ if (!PHONE_SHAPE_RE.test(trimmed))
14
+ return false;
15
+ return trimmed.replace(/\D/g, "").length >= 7;
16
+ }
17
+ export const PII_REDACTED = "[redacted]";
18
+ function sanitizeValue(value, mode, hit) {
19
+ if (typeof value === "string" && looksLikePii(value)) {
20
+ hit.found = true;
21
+ return mode === "strip" ? PII_REDACTED : value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ return value.map((item) => sanitizeValue(item, mode, hit));
25
+ }
26
+ if (typeof value === "object" && value !== null) {
27
+ const result = {};
28
+ for (const [key, nested] of Object.entries(value)) {
29
+ result[key] = sanitizeValue(nested, mode, hit);
30
+ }
31
+ return result;
32
+ }
33
+ return value;
34
+ }
35
+ /**
36
+ * Guard a props/traits object. Warns once per event name (per facade
37
+ * instance) via the caller-owned `warned` set.
38
+ */
39
+ export function guardPii(eventName, props, mode, warned) {
40
+ if (mode === "off")
41
+ return props;
42
+ const hit = { found: false };
43
+ const guarded = sanitizeValue(props, mode, hit);
44
+ if (hit.found && !warned.has(eventName)) {
45
+ warned.add(eventName);
46
+ console.warn(`[stapel analytics] PII-like value in props of "${eventName}" — ` +
47
+ (mode === "strip" ? "redacted" : "kept (piiGuard: warn)") +
48
+ ". PII in analytics props is banned (analytics-standard §1.4).");
49
+ }
50
+ return guarded;
51
+ }
52
+ //# sourceMappingURL=pii.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pii.js","sourceRoot":"","sources":["../../src/analytics/pii.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AACjD,MAAM,cAAc,GAAG,sBAAsB,CAAC;AAE9C,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IAChD,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,MAAM,YAAY,GAAG,YAAY,CAAC;AAEzC,SAAS,aAAa,CACpB,KAAc,EACd,IAAkB,EAClB,GAAuB;IAEvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC;QACjB,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC;IACjD,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CACtB,SAAiB,EACjB,KAA8B,EAC9B,IAAkB,EAClB,MAAmB;IAEnB,IAAI,IAAI,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,GAAG,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAA4B,CAAC;IAC3E,IAAI,GAAG,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtB,OAAO,CAAC,IAAI,CACV,kDAAkD,SAAS,MAAM;YAC/D,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,uBAAuB,CAAC;YACzD,+DAA+D,CAClE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { StapelClient } from "../client.js";
2
+ import type { AnalyticsProvider } from "./types.js";
3
+ /** Dev provider: logs every event via console.debug. Never throws. */
4
+ export declare function consoleProvider(): AnalyticsProvider;
5
+ export interface StapelCollectorOptions {
6
+ /** Collector origin, e.g. `https://api.example.com`. */
7
+ readonly baseUrl?: string;
8
+ /**
9
+ * Alternatively reuse a `StapelClient` (its base URL, auth headers and
10
+ * error envelope handling).
11
+ */
12
+ readonly client?: StapelClient;
13
+ /** Source write key (analytics-standard §3); sent in the batch body. */
14
+ readonly writeKey?: string;
15
+ /** Injectable fetch (tests). */
16
+ readonly fetch?: typeof globalThis.fetch;
17
+ }
18
+ /**
19
+ * Built-in provider for the stapel-analytics ingest endpoint: buffers
20
+ * events handed over by the facade and POSTs `{events: [...]}` to
21
+ * `{base}/analytics/api/events` on flush (one request per facade batch).
22
+ * During page teardown (document hidden) it prefers `navigator.sendBeacon`
23
+ * so the final batch survives navigation; otherwise fetch with keepalive.
24
+ * On a failed send the buffer is surrendered back to the facade's retry
25
+ * (the facade re-delivers the batch, repopulating the buffer).
26
+ */
27
+ export declare function stapelCollectorProvider(options: StapelCollectorOptions): AnalyticsProvider;
28
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/analytics/providers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAkB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpE,sEAAsE;AACtE,wBAAgB,eAAe,IAAI,iBAAiB,CAmBnD;AAED,MAAM,WAAW,sBAAsB;IACrC,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,gCAAgC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CAC1C;AAID;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,sBAAsB,GAC9B,iBAAiB,CAuDnB"}