luchy 0.2.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,6 @@
1
- import { createLuchyClient } from "./api/index.js";
1
+ import { createElement } from "react";
2
+ import { createContext } from "react-router";
3
+ import { DEFAULT_ENDPOINT, createLuchy, signIdentity } from "./server.js";
2
4
  const MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
3
5
  const DEFAULT_IGNORE_PREFIXES = ["/__manifest"];
4
6
  const INTENT_PATTERN = /^[A-Za-z0-9_-]{1,40}$/;
@@ -38,70 +40,205 @@ function serverEventName(input, config = {}) {
38
40
  if ((config.ignoreEvents ?? []).includes(name)) return null;
39
41
  return name;
40
42
  }
41
- const NOOP_FINISH = () => {
42
- };
43
- function createRequestTracker(options) {
43
+ const IDENTIFY_TTL_MS = 60 * 60 * 1e3;
44
+ const IDENTIFY_MAX_ENTRIES = 1e3;
45
+ const identified = /* @__PURE__ */ new Map();
46
+ function traitsHash(traits) {
47
+ const json = JSON.stringify(
48
+ Object.keys(traits).sort().map((key) => [key, traits[key]])
49
+ );
50
+ let hash = 2166136261;
51
+ for (let i = 0; i < json.length; i++) {
52
+ hash ^= json.charCodeAt(i);
53
+ hash = Math.imul(hash, 16777619);
54
+ }
55
+ return (hash >>> 0).toString(36);
56
+ }
57
+ function claimIdentify(key, traits, now) {
58
+ const hash = traitsHash(traits);
59
+ const seen = identified.get(key);
60
+ if (seen && seen.hash === hash && now - seen.at < IDENTIFY_TTL_MS) {
61
+ return false;
62
+ }
63
+ identified.delete(key);
64
+ if (identified.size >= IDENTIFY_MAX_ENTRIES) {
65
+ const oldest = identified.keys().next().value;
66
+ if (oldest !== void 0) identified.delete(oldest);
67
+ }
68
+ identified.set(key, { hash, at: now });
69
+ return true;
70
+ }
71
+ function resetIdentifyCache() {
72
+ identified.clear();
73
+ }
74
+ const luchyContext = createContext(null);
75
+ function clientInfo(request) {
76
+ const headers = request.headers;
77
+ const cf = request.cf;
78
+ const cfCountry = typeof cf?.country === "string" ? cf.country : void 0;
79
+ const language = headers.get("accept-language")?.split(",")[0]?.split(";")[0]?.trim();
80
+ return {
81
+ ip: headers.get("cf-connecting-ip") ?? void 0,
82
+ country: cfCountry ?? headers.get("cf-ipcountry") ?? void 0,
83
+ language: language || void 0,
84
+ userAgent: headers.get("user-agent") ?? void 0
85
+ };
86
+ }
87
+ function luchyMiddleware(options) {
44
88
  const enabled = options.enabled ?? true;
89
+ const endpoint = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, "");
45
90
  const prefixes = [
46
91
  ...DEFAULT_IGNORE_PREFIXES,
47
92
  ...options.ignorePrefixes ?? []
48
93
  ];
49
- const client = createLuchyClient({
94
+ const luchy = createLuchy({
50
95
  apiKey: options.apiKey,
51
- endpoint: options.endpoint,
96
+ secret: options.secret,
97
+ endpoint,
98
+ fetch: options.fetch,
52
99
  onError: options.onError
53
100
  });
54
- return {
55
- begin(request) {
56
- if (!enabled) return NOOP_FINISH;
57
- if (!isMutatingMethod(request.method)) return NOOP_FINISH;
58
- const url = new URL(request.url);
59
- if (prefixes.some((p) => url.pathname.startsWith(p))) return NOOP_FINISH;
60
- const clone = carriesIntent(request.headers.get("content-type")) ? request.clone() : null;
61
- return (response, ctx) => {
62
- ctx.waitUntil(
63
- (async () => {
64
- const intent = clone ? new URLSearchParams(await clone.text()).get("intent") : null;
65
- const name = serverEventName(
66
- {
67
- method: request.method,
68
- pathname: url.pathname,
69
- status: response.status,
70
- intent
71
- },
72
- options
73
- );
74
- if (!name) return;
75
- let payload = { status: response.status };
76
- if (options.payload) {
77
- try {
78
- const extra = await options.payload(request, response);
79
- payload = { ...payload, ...extra };
80
- } catch (error) {
81
- options.onError?.(error);
82
- }
83
- }
84
- await client.trackEvent({
85
- name,
86
- type: "server",
87
- // The raw pathname, not the normalized route: the name carries
88
- // the shape, the pathname keeps the detail.
89
- pathname: url.pathname,
90
- payload,
91
- userAgent: request.headers.get("user-agent") ?? void 0
92
- });
93
- })().catch((error) => {
101
+ function identify(args, user, traits) {
102
+ if (!claimIdentify(`${options.apiKey}\0${user}`, traits, Date.now())) {
103
+ return;
104
+ }
105
+ try {
106
+ options.waitUntil(args)(luchy.identify(user, traits));
107
+ } catch (error) {
108
+ options.onError?.(error);
109
+ }
110
+ }
111
+ function identityResolver(args) {
112
+ let memo;
113
+ async function resolve() {
114
+ if (!options.identity) {
115
+ return void 0;
116
+ }
117
+ try {
118
+ const resolved = await options.identity(args);
119
+ if (!resolved) {
120
+ return void 0;
121
+ }
122
+ const { props, traits, ...identity } = resolved;
123
+ if (enabled && traits) {
124
+ identify(args, identity.user, traits);
125
+ }
126
+ const token = options.secret ? await signIdentity(identity, options.secret) : void 0;
127
+ return { token, props };
128
+ } catch (error) {
129
+ options.onError?.(error);
130
+ return void 0;
131
+ }
132
+ }
133
+ return () => {
134
+ memo ?? (memo = resolve());
135
+ return memo;
136
+ };
137
+ }
138
+ return async (args, next) => {
139
+ const { request } = args;
140
+ const resolveIdentity = identityResolver(args);
141
+ args.context.set(luchyContext, {
142
+ apiKey: options.apiKey,
143
+ endpoint,
144
+ resolveIdentity
145
+ });
146
+ if (!enabled) {
147
+ return next();
148
+ }
149
+ if (!isMutatingMethod(request.method)) {
150
+ return next();
151
+ }
152
+ const url = new URL(request.url);
153
+ if (prefixes.some((p) => url.pathname.startsWith(p))) {
154
+ return next();
155
+ }
156
+ const clone = carriesIntent(request.headers.get("content-type")) ? request.clone() : null;
157
+ async function ship(status) {
158
+ const intent = clone ? new URLSearchParams(await clone.text()).get("intent") : null;
159
+ const name = serverEventName(
160
+ { method: request.method, pathname: url.pathname, status, intent },
161
+ options
162
+ );
163
+ if (!name) {
164
+ return;
165
+ }
166
+ const who = await resolveIdentity();
167
+ await luchy.track(
168
+ {
169
+ name,
170
+ type: "server",
171
+ // The raw pathname, not the normalized route: the name carries the
172
+ // shape, the pathname keeps the detail.
173
+ pathname: url.pathname,
174
+ // `status` last: it is the tracker's own field, props cannot fake it.
175
+ payload: { ...who?.props, status },
176
+ ...clientInfo(request)
177
+ },
178
+ who?.token
179
+ );
180
+ }
181
+ function schedule(status) {
182
+ try {
183
+ options.waitUntil(args)(
184
+ ship(status).catch((error) => {
94
185
  options.onError?.(error);
95
186
  })
96
187
  );
97
- };
188
+ } catch (error) {
189
+ options.onError?.(error);
190
+ }
191
+ }
192
+ let result;
193
+ try {
194
+ result = await next();
195
+ } catch (thrown) {
196
+ schedule(thrown instanceof Response ? thrown.status : 500);
197
+ throw thrown;
98
198
  }
199
+ schedule(result instanceof Response ? result.status : 200);
200
+ return result;
99
201
  };
100
202
  }
203
+ async function getLuchy(context) {
204
+ const state = context.get(luchyContext);
205
+ if (!state) {
206
+ throw new Error(
207
+ "getLuchy: luchyMiddleware is not installed on the root route"
208
+ );
209
+ }
210
+ const config = {
211
+ apiKey: state.apiKey,
212
+ endpoint: state.endpoint
213
+ };
214
+ const resolved = await state.resolveIdentity();
215
+ if (resolved?.token) {
216
+ config.identity = resolved.token;
217
+ }
218
+ if (resolved?.props) {
219
+ config.props = resolved.props;
220
+ }
221
+ return config;
222
+ }
223
+ const DEFAULT_SRC = "https://cdn.luchy.app/luchy.min.js";
224
+ function LuchyScript(props) {
225
+ return createElement("script", {
226
+ src: props.src ?? DEFAULT_SRC,
227
+ defer: true,
228
+ nonce: props.nonce,
229
+ "data-api-key": props.apiKey,
230
+ "data-endpoint": props.endpoint,
231
+ "data-identity": props.identity,
232
+ "data-props": props.props ? JSON.stringify(props.props) : void 0
233
+ });
234
+ }
101
235
  export {
236
+ LuchyScript,
102
237
  carriesIntent,
103
- createRequestTracker,
238
+ getLuchy,
104
239
  isMutatingMethod,
240
+ luchyMiddleware,
105
241
  normalizeRoute,
242
+ resetIdentifyCache,
106
243
  serverEventName
107
244
  };
@@ -62,9 +62,58 @@ var __async = (__this, __arguments, generator) => {
62
62
  language: navigator.language || ((_a = navigator.languages) == null ? void 0 : _a[0]) || "en"
63
63
  };
64
64
  }
65
+ function parseProps(raw) {
66
+ if (!raw) return void 0;
67
+ try {
68
+ const value = JSON.parse(raw);
69
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
70
+ return void 0;
71
+ }
72
+ return value;
73
+ } catch (e) {
74
+ return void 0;
75
+ }
76
+ }
65
77
  function luchy(options) {
66
78
  const endpoint = options.endpoint || "https://api.luchy.app/ingest";
67
- const { apiKey, trackLocalhost = false } = options;
79
+ const { apiKey, trackLocalhost = false, script } = options;
80
+ let identity = options.identity;
81
+ let props = options.props;
82
+ function currentIdentity() {
83
+ if (!script) return identity;
84
+ return script.getAttribute("data-identity") || void 0;
85
+ }
86
+ function currentProps() {
87
+ if (!script) return props;
88
+ return parseProps(script.getAttribute("data-props"));
89
+ }
90
+ function identify(token, nextProps) {
91
+ if (!script) {
92
+ identity = token;
93
+ props = nextProps;
94
+ return;
95
+ }
96
+ script.setAttribute("data-identity", token);
97
+ if (nextProps) {
98
+ script.setAttribute("data-props", JSON.stringify(nextProps));
99
+ } else {
100
+ script.removeAttribute("data-props");
101
+ }
102
+ }
103
+ function reset() {
104
+ if (!script) {
105
+ identity = void 0;
106
+ props = void 0;
107
+ return;
108
+ }
109
+ script.removeAttribute("data-identity");
110
+ script.removeAttribute("data-props");
111
+ }
112
+ function withProps(payload) {
113
+ const global = currentProps();
114
+ if (!global) return payload;
115
+ return __spreadValues(__spreadValues({}, global), payload);
116
+ }
68
117
  const shouldTrack = () => {
69
118
  if (trackLocalhost) return true;
70
119
  return location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
@@ -80,7 +129,9 @@ var __async = (__this, __arguments, generator) => {
80
129
  apiKey,
81
130
  data: __spreadValues({
82
131
  pathname,
83
- referrer
132
+ referrer,
133
+ payload: withProps(data == null ? void 0 : data.payload),
134
+ identity: currentIdentity()
84
135
  }, browserData)
85
136
  });
86
137
  });
@@ -100,7 +151,8 @@ var __async = (__this, __arguments, generator) => {
100
151
  // `payload`, NOT `props`. The ingest endpoint validates with a
101
152
  // non-passthrough zod object, so an unknown key is stripped and the
102
153
  // event is stored with no properties at all — 200, success, no data.
103
- payload
154
+ payload: withProps(payload),
155
+ identity: currentIdentity()
104
156
  }, browserData)
105
157
  });
106
158
  });
@@ -264,11 +316,9 @@ var __async = (__this, __arguments, generator) => {
264
316
  const key = attr.name.slice(prefix.length).replace(/-/g, "_");
265
317
  payload[key] = attr.value;
266
318
  }
267
- trackEvent(
268
- name,
269
- Object.keys(payload).length > 0 ? payload : void 0,
270
- { type: "click" }
271
- );
319
+ trackEvent(name, Object.keys(payload).length > 0 ? payload : void 0, {
320
+ type: "click"
321
+ });
272
322
  };
273
323
  document.addEventListener("click", dataEventClickListener);
274
324
  }
@@ -313,6 +363,8 @@ var __async = (__this, __arguments, generator) => {
313
363
  return {
314
364
  trackPageview,
315
365
  trackEvent,
366
+ identify,
367
+ reset,
316
368
  setOptions
317
369
  };
318
370
  }
@@ -325,7 +377,8 @@ var __async = (__this, __arguments, generator) => {
325
377
  const tracker = luchy({
326
378
  apiKey,
327
379
  endpoint,
328
- trackLocalhost
380
+ trackLocalhost,
381
+ script
329
382
  });
330
383
  const autoPageviewsAttr = script.getAttribute("data-auto-pageviews");
331
384
  const autoOutboundAttr = script.getAttribute("data-auto-outbound");
Binary file
Binary file
@@ -62,9 +62,58 @@ var __async = (__this, __arguments, generator) => {
62
62
  language: navigator.language || ((_a = navigator.languages) == null ? void 0 : _a[0]) || "en"
63
63
  };
64
64
  }
65
+ function parseProps(raw) {
66
+ if (!raw) return void 0;
67
+ try {
68
+ const value = JSON.parse(raw);
69
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
70
+ return void 0;
71
+ }
72
+ return value;
73
+ } catch (e) {
74
+ return void 0;
75
+ }
76
+ }
65
77
  function luchy(options) {
66
78
  const endpoint = options.endpoint || "https://api.luchy.app/ingest";
67
- const { apiKey, trackLocalhost = false } = options;
79
+ const { apiKey, trackLocalhost = false, script } = options;
80
+ let identity = options.identity;
81
+ let props = options.props;
82
+ function currentIdentity() {
83
+ if (!script) return identity;
84
+ return script.getAttribute("data-identity") || void 0;
85
+ }
86
+ function currentProps() {
87
+ if (!script) return props;
88
+ return parseProps(script.getAttribute("data-props"));
89
+ }
90
+ function identify(token, nextProps) {
91
+ if (!script) {
92
+ identity = token;
93
+ props = nextProps;
94
+ return;
95
+ }
96
+ script.setAttribute("data-identity", token);
97
+ if (nextProps) {
98
+ script.setAttribute("data-props", JSON.stringify(nextProps));
99
+ } else {
100
+ script.removeAttribute("data-props");
101
+ }
102
+ }
103
+ function reset() {
104
+ if (!script) {
105
+ identity = void 0;
106
+ props = void 0;
107
+ return;
108
+ }
109
+ script.removeAttribute("data-identity");
110
+ script.removeAttribute("data-props");
111
+ }
112
+ function withProps(payload) {
113
+ const global = currentProps();
114
+ if (!global) return payload;
115
+ return __spreadValues(__spreadValues({}, global), payload);
116
+ }
68
117
  const shouldTrack = () => {
69
118
  if (trackLocalhost) return true;
70
119
  return location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
@@ -80,7 +129,9 @@ var __async = (__this, __arguments, generator) => {
80
129
  apiKey,
81
130
  data: __spreadValues({
82
131
  pathname,
83
- referrer
132
+ referrer,
133
+ payload: withProps(data == null ? void 0 : data.payload),
134
+ identity: currentIdentity()
84
135
  }, browserData)
85
136
  });
86
137
  });
@@ -100,7 +151,8 @@ var __async = (__this, __arguments, generator) => {
100
151
  // `payload`, NOT `props`. The ingest endpoint validates with a
101
152
  // non-passthrough zod object, so an unknown key is stripped and the
102
153
  // event is stored with no properties at all — 200, success, no data.
103
- payload
154
+ payload: withProps(payload),
155
+ identity: currentIdentity()
104
156
  }, browserData)
105
157
  });
106
158
  });
@@ -264,11 +316,9 @@ var __async = (__this, __arguments, generator) => {
264
316
  const key = attr.name.slice(prefix.length).replace(/-/g, "_");
265
317
  payload[key] = attr.value;
266
318
  }
267
- trackEvent(
268
- name,
269
- Object.keys(payload).length > 0 ? payload : void 0,
270
- { type: "click" }
271
- );
319
+ trackEvent(name, Object.keys(payload).length > 0 ? payload : void 0, {
320
+ type: "click"
321
+ });
272
322
  };
273
323
  document.addEventListener("click", dataEventClickListener);
274
324
  }
@@ -313,6 +363,8 @@ var __async = (__this, __arguments, generator) => {
313
363
  return {
314
364
  trackPageview,
315
365
  trackEvent,
366
+ identify,
367
+ reset,
316
368
  setOptions
317
369
  };
318
370
  }
@@ -325,7 +377,8 @@ var __async = (__this, __arguments, generator) => {
325
377
  const tracker = luchy({
326
378
  apiKey,
327
379
  endpoint,
328
- trackLocalhost
380
+ trackLocalhost,
381
+ script
329
382
  });
330
383
  const autoPageviewsAttr = script.getAttribute("data-auto-pageviews");
331
384
  const autoOutboundAttr = script.getAttribute("data-auto-outbound");
Binary file
Binary file
package/dist/server.d.ts CHANGED
@@ -1,35 +1,61 @@
1
+ import { signIdentity, LuchyIdentity } from './identity.js';
1
2
  /**
2
3
  * Server-side tracking.
3
4
  *
4
- * The browser tracker (`./index.ts`) can only see what happens in a page. A
5
- * server sees what the application actually *did* — an order placed, a payment
6
- * recorded, a webhook processed — and those are usually the events worth
7
- * having. This entry point carries no browser globals (`window`, `document`,
8
- * `location`, `navigator`), so it runs in Node, Bun, Deno and Cloudflare
9
- * Workers.
5
+ * The browser script only sees what happens in a page. A server sees what the
6
+ * application actually *did* — an order placed, a payment recorded, a webhook
7
+ * processed — and those are usually the events worth having. No browser
8
+ * globals here, so it runs in Node, Bun, Deno and Cloudflare Workers.
10
9
  *
11
- * It exists mostly so that callers stop hand-rolling `fetch` against the
12
- * ingest API: doing that is how the `props`/`payload` field-name split shipped
13
- * unnoticed, since nothing typed the wire format for a non-browser sender.
10
+ * The wire format lives in `./api` (generated from the API's OpenAPI
11
+ * document); this module is the ergonomic face of it plus identity signing
12
+ * and person profiles (`identify`).
14
13
  *
15
- * The transport now lives in `./api`, generated from the API's own OpenAPI
16
- * document, so the wire format is no longer restated here at all. This module
17
- * is the ergonomic face of it: two methods, no client object to hold.
14
+ * @module
18
15
  */
19
- export type LuchyServerOptions = {
16
+ export { signIdentity, type LuchyIdentity };
17
+ /** A property bag. Nested objects are not supported by the ingest API. */
18
+ export type LuchyPayload = Record<string, string | number | boolean>;
19
+ /**
20
+ * Person profile traits. Merged into the stored profile; `null` deletes a
21
+ * trait. The dashboard labels a person by `name`, else `email`, else the id.
22
+ */
23
+ export type LuchyTraits = Record<string, string | number | boolean | null>;
24
+ export type LuchyOptions = {
20
25
  apiKey: string;
21
- /** Defaults to the hosted ingest API. */
26
+ /**
27
+ * The project's secret key (`lsk_…`, from the dashboard settings).
28
+ * Without it identities passed to `track`/`pageview` are not sent (ingest
29
+ * drops unsigned identities anyway, so sending one would only look like it
30
+ * worked) and `identify` is a no-op. Server-side only: never ship it.
31
+ */
32
+ secret?: string;
33
+ /** The ingest base, without a trailing slash. Defaults to the hosted API. */
22
34
  endpoint?: string;
23
35
  /**
24
- * Called when an event fails to reach the ingest API. Analytics must never
25
- * break the request it is describing, so failures are swallowed by default —
26
- * pass this to log them.
36
+ * Defaults to `true`. When false every call is a no-op, so apps can pass
37
+ * `env.APP_ENV === 'production'` and leave the wiring in place.
38
+ */
39
+ enabled?: boolean;
40
+ /**
41
+ * Called when a hit fails to reach ingest. Analytics must never break the
42
+ * request it describes, so failures are swallowed unless you log them here.
27
43
  */
28
44
  onError?: (error: unknown) => void;
45
+ /** Transport override, for tests and scoped runtimes (service bindings). */
46
+ fetch?: typeof globalThis.fetch;
29
47
  };
30
- /** A property bag. Nested objects are not supported by the ingest API. */
31
- export type LuchyPayload = Record<string, string | number | boolean>;
32
- export type LuchyServerEvent = {
48
+ /** What the end user's request looked like, for hits caused by one. */
49
+ export type LuchyRequestInfo = {
50
+ /** Forward the end user's UA so the hit joins their browser session. */
51
+ userAgent?: string;
52
+ /** The end user's IP. Only used for the visitor hash, never stored. */
53
+ ip?: string;
54
+ /** ISO country code. Wins over the sender's own `cf-ipcountry`. */
55
+ country?: string;
56
+ language?: string;
57
+ };
58
+ export type LuchyServerEvent = LuchyRequestInfo & {
33
59
  /** The event name, e.g. `order:placed`. */
34
60
  name: string;
35
61
  /** Where it happened — a route, not a raw URL with ids in it. */
@@ -37,35 +63,41 @@ export type LuchyServerEvent = {
37
63
  /** Free-form category, e.g. `server`, `webhook`, `cron`. */
38
64
  type?: string;
39
65
  payload?: LuchyPayload;
40
- /** Forward the end user's UA when the event was caused by a request. */
41
- userAgent?: string;
42
- language?: string;
43
- /** ISO country code. Cloudflare deployments can leave this to the server. */
44
- country?: string;
45
66
  };
46
- export type LuchyServerPageview = {
67
+ export type LuchyServerPageview = LuchyRequestInfo & {
47
68
  pathname: string;
48
69
  referrer?: string;
49
- userAgent?: string;
50
- language?: string;
51
- country?: string;
70
+ payload?: LuchyPayload;
52
71
  };
53
- export type LuchyServerTracker = {
54
- trackEvent(event: LuchyServerEvent): Promise<void>;
55
- trackPageview(pageview: LuchyServerPageview): Promise<void>;
72
+ /**
73
+ * An identity to sign, or a token that is already signed (e.g. one rendered
74
+ * into the page and posted back).
75
+ */
76
+ export type LuchyIdentityInput = LuchyIdentity | string;
77
+ export type Luchy = {
78
+ track(event: LuchyServerEvent, identity?: LuchyIdentityInput): Promise<void>;
79
+ pageview(pageview: LuchyServerPageview, identity?: LuchyIdentityInput): Promise<void>;
80
+ /**
81
+ * Stores profile traits for a user (`POST /ingest/identify`, authenticated
82
+ * with the secret key). Idempotent: traits merge, `null` deletes one.
83
+ */
84
+ identify(user: string, traits: LuchyTraits): Promise<void>;
56
85
  };
86
+ /** The hosted ingest base. */
87
+ export declare const DEFAULT_ENDPOINT = "https://dash.luchy.app/api/ingest";
57
88
  /**
58
89
  * Creates a server-side tracker. Every method resolves rather than rejects —
59
90
  * see `onError`.
60
91
  *
61
92
  * ```ts
62
- * const luchy = createServerTracker({ apiKey: process.env.LUCHY_API_KEY! });
63
- * await luchy.trackEvent({
64
- * name: 'order:placed',
65
- * pathname: '/checkout',
66
- * payload: { plan: 'pro', amount: 29 }
67
- * });
93
+ * const luchy = createLuchy({ apiKey: env.LUCHY_API_KEY, secret: env.LUCHY_SECRET });
94
+ * ctx.waitUntil(
95
+ * luchy.track(
96
+ * { name: 'order:placed', pathname: '/checkout', payload: { plan: 'pro' } },
97
+ * { user: user.id, tenant: String(workspace.id) }
98
+ * )
99
+ * );
68
100
  * ```
69
101
  */
70
- export declare function createServerTracker(options: LuchyServerOptions): LuchyServerTracker;
102
+ export declare function createLuchy(options: LuchyOptions): Luchy;
71
103
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAErE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,aAAa,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D,CAAC;AAcF;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,kBAAkB,GAC1B,kBAAkB,CAkCpB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAEjE;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,CAAC;AAE5C,0EAA0E;AAC1E,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAErE;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,4EAA4E;IAC5E,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,CAAC;AAEF,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,gBAAgB,GAAG;IAChD,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,gBAAgB,GAAG;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,YAAY,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,MAAM,CAAC;AAExD,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,QAAQ,CACN,QAAQ,EAAE,mBAAmB,EAC7B,QAAQ,CAAC,EAAE,kBAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D,CAAC;AAEF,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB,sCAAsC,CAAC;AAWpE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,KAAK,CAiHxD"}