better-ship 0.43.0 → 0.45.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/dist/posthog/server.d.ts.map +1 -1
- package/dist/posthog/server.js +6 -1
- package/dist/posthog/server.js.map +1 -1
- package/dist/posthog/tanstack.d.ts +9 -0
- package/dist/posthog/tanstack.d.ts.map +1 -0
- package/dist/posthog/tanstack.js +19 -0
- package/dist/posthog/tanstack.js.map +1 -0
- package/dist/request-session-D1r9ZvcS.js +3 -0
- package/dist/request-session-Qe_Q_u9G.js +16 -0
- package/dist/request-session-Qe_Q_u9G.js.map +1 -0
- package/dist/stripe/event-map.d.ts +5 -3
- package/dist/stripe/event-map.d.ts.map +1 -1
- package/dist/stripe/event-map.js +13 -3
- package/dist/stripe/event-map.js.map +1 -1
- package/package.json +2 -1
- package/src/infrastructure/posthog/request-session.ts +14 -0
- package/src/infrastructure/posthog/server.ts +6 -1
- package/src/infrastructure/posthog/tanstack.ts +19 -0
- package/src/infrastructure/stripe/event-map.ts +28 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/infrastructure/posthog/server.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../../src/infrastructure/posthog/server.ts"],"mappings":";;;;;;iBAkBiB;WACN;WACA;WACA;WACA,YAAY,SAAS;WACrB,UAAU,KACjB;aAGS,UAAU,KAAK,YAAY;;;;qBAK3B,mCAAmC;;WAMnC,SAAS;mBACD;;WALV,SAAS;UAEX;;SASA,OAAO,QAAQ,yBAAyB;;EAqB/C,SAAS,oBAAoB,aAAa;;EAO1C,MAAM,oBAAoB;;EAK1B,MACE,cACA,YAAY,mCACZ,QAAQ;;EAMV,eACE,cACA,YAAY,mCACZ,QAAQ;UAKF"}
|
package/dist/posthog/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { nt as MissingConfigurationError } from "../core-CGj1ukXV.js";
|
|
2
2
|
import { t as PostHogMetricsService } from "../metrics-CWv37yf9.js";
|
|
3
|
+
import { t as currentPostHogSession } from "../request-session-Qe_Q_u9G.js";
|
|
3
4
|
import { v5, validate } from "uuid";
|
|
4
5
|
import { PostHog } from "posthog-node";
|
|
5
6
|
|
|
@@ -60,7 +61,11 @@ var PostHogAnalyticsService = class PostHogAnalyticsService {
|
|
|
60
61
|
this.capture(name, properties, target, "anonymous");
|
|
61
62
|
}
|
|
62
63
|
capture(name, eventProperties, target, profile) {
|
|
63
|
-
const
|
|
64
|
+
const sessionId = currentPostHogSession();
|
|
65
|
+
const properties = sessionId ? {
|
|
66
|
+
$session_id: sessionId,
|
|
67
|
+
...eventProperties
|
|
68
|
+
} : { ...eventProperties };
|
|
64
69
|
const payload = {
|
|
65
70
|
distinctId: target.distinctId,
|
|
66
71
|
event: name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":["isUuid","uuidv5"],"sources":["../../src/infrastructure/posthog/server.ts"],"sourcesContent":["import { PostHog, type PostHogOptions } from 'posthog-node'\nimport { validate as isUuid, v5 as uuidv5 } from 'uuid'\n\nimport type { AnalyticsService } from '@/application'\nimport {\n MissingConfigurationError,\n type AnalyticsEventInput,\n type AnalyticsProperties,\n type AnalyticsTarget,\n} from '@/core'\n\nimport { PostHogMetricsService } from './metrics.ts'\n\n// Fixed forever: changing it changes every derived UUID, so PostHog would keep a repeat.\nconst EVENT_ID_NAMESPACE = '53d7af30-65cb-4176-b2c9-f1ed42367ab1'\n\n/** Deployment values and SDK options; the runtime owns delivery promises. */\nexport interface PostHogAnalyticsConfig {\n readonly apiKey: string\n readonly environment: string\n readonly source: string\n readonly waitUntil: (promise: Promise<unknown>) => void\n readonly options?: Omit<\n PostHogOptions,\n 'waitUntil' | 'flushAt' | 'flushInterval' | 'metrics' | 'disabled'\n > & {\n readonly metrics?: Omit<NonNullable<PostHogOptions['metrics']>, 'serviceName' | 'environment'>\n }\n}\n\n/** Worker analytics ported from Typist; disabled applications select Porte's NULL_ANALYTICS instead. */\nexport class PostHogAnalyticsService implements AnalyticsService {\n /** Metrics share this service's SDK client and delivery lifetime. */\n readonly metrics: PostHogMetricsService\n\n private constructor(\n /** Raw SDK access for integrations such as PostHog AI tracing. */\n readonly posthog: PostHog,\n private readonly waitUntil: (promise: Promise<unknown>) => void,\n ) {\n this.metrics = new PostHogMetricsService(posthog, waitUntil)\n }\n\n /** Construct once per Worker invocation; a blank key throws MissingConfigurationError. */\n static create(config: PostHogAnalyticsConfig): PostHogAnalyticsService {\n const apiKey = config.apiKey.trim()\n if (apiKey.length === 0) throw new MissingConfigurationError('POSTHOG_API_KEY')\n const client = new PostHog(apiKey, {\n host: 'https://us.i.posthog.com',\n ...config.options,\n flushAt: 1,\n flushInterval: 0,\n waitUntil: config.waitUntil,\n metrics: {\n ...config.options?.metrics,\n environment: config.environment,\n serviceName: config.source,\n },\n })\n // The SDK applies in-memory super properties synchronously before this promise resolves.\n void client.register({ environment: config.environment, source: config.source })\n return new PostHogAnalyticsService(client, config.waitUntil)\n }\n\n /** Schedule person properties; the SDK's waitUntil option protects queued delivery. */\n identify(distinctId: string, properties?: AnalyticsProperties): void {\n const payload: Parameters<PostHog['identify']>[0] = { distinctId }\n if (properties !== undefined) payload.properties = properties\n this.posthog.identify(payload)\n }\n\n /** Schedule an irreversible person merge; only alias IDs the application proves belong to one person. */\n alias(distinctId: string, previousDistinctId: string): void {\n this.waitUntil(this.posthog.aliasImmediate({ distinctId, alias: previousDistinctId }))\n }\n\n /** Schedule an event; without an event ID the SDK sets a new UUID and the capture time. */\n track(\n name: string,\n properties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n ): void {\n this.capture(name, properties, target, 'person')\n }\n\n /** Schedule an event without creating a person profile for an attempt or resource ID. */\n trackAnonymous(\n name: string,\n properties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n ): void {\n this.capture(name, properties, target, 'anonymous')\n }\n\n private capture(\n name: string,\n eventProperties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n profile: 'person' | 'anonymous',\n ): void {\n const properties = { ...eventProperties }\n const payload: Parameters<PostHog['captureImmediate']>[0] = {\n distinctId: target.distinctId,\n event: name,\n properties,\n }\n if (profile === 'anonymous')\n payload.properties = { ...properties, $process_person_profile: false }\n if (target.eventId !== undefined) {\n // PostHog drops a repeat only when its uuid and timestamp match the first event.\n payload.uuid = isUuid(target.eventId)\n ? target.eventId\n : uuidv5(target.eventId, EVENT_ID_NAMESPACE)\n payload.timestamp = new Date(target.occurredAt)\n }\n this.waitUntil(this.posthog.captureImmediate(payload))\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.js","names":["isUuid","uuidv5"],"sources":["../../src/infrastructure/posthog/server.ts"],"sourcesContent":["import { PostHog, type PostHogOptions } from 'posthog-node'\nimport { validate as isUuid, v5 as uuidv5 } from 'uuid'\n\nimport type { AnalyticsService } from '@/application'\nimport {\n MissingConfigurationError,\n type AnalyticsEventInput,\n type AnalyticsProperties,\n type AnalyticsTarget,\n} from '@/core'\n\nimport { PostHogMetricsService } from './metrics.ts'\nimport { currentPostHogSession } from './request-session.ts'\n\n// Fixed forever: changing it changes every derived UUID, so PostHog would keep a repeat.\nconst EVENT_ID_NAMESPACE = '53d7af30-65cb-4176-b2c9-f1ed42367ab1'\n\n/** Deployment values and SDK options; the runtime owns delivery promises. */\nexport interface PostHogAnalyticsConfig {\n readonly apiKey: string\n readonly environment: string\n readonly source: string\n readonly waitUntil: (promise: Promise<unknown>) => void\n readonly options?: Omit<\n PostHogOptions,\n 'waitUntil' | 'flushAt' | 'flushInterval' | 'metrics' | 'disabled'\n > & {\n readonly metrics?: Omit<NonNullable<PostHogOptions['metrics']>, 'serviceName' | 'environment'>\n }\n}\n\n/** Worker analytics ported from Typist; disabled applications select Porte's NULL_ANALYTICS instead. */\nexport class PostHogAnalyticsService implements AnalyticsService {\n /** Metrics share this service's SDK client and delivery lifetime. */\n readonly metrics: PostHogMetricsService\n\n private constructor(\n /** Raw SDK access for integrations such as PostHog AI tracing. */\n readonly posthog: PostHog,\n private readonly waitUntil: (promise: Promise<unknown>) => void,\n ) {\n this.metrics = new PostHogMetricsService(posthog, waitUntil)\n }\n\n /** Construct once per Worker invocation; a blank key throws MissingConfigurationError. */\n static create(config: PostHogAnalyticsConfig): PostHogAnalyticsService {\n const apiKey = config.apiKey.trim()\n if (apiKey.length === 0) throw new MissingConfigurationError('POSTHOG_API_KEY')\n const client = new PostHog(apiKey, {\n host: 'https://us.i.posthog.com',\n ...config.options,\n flushAt: 1,\n flushInterval: 0,\n waitUntil: config.waitUntil,\n metrics: {\n ...config.options?.metrics,\n environment: config.environment,\n serviceName: config.source,\n },\n })\n // The SDK applies in-memory super properties synchronously before this promise resolves.\n void client.register({ environment: config.environment, source: config.source })\n return new PostHogAnalyticsService(client, config.waitUntil)\n }\n\n /** Schedule person properties; the SDK's waitUntil option protects queued delivery. */\n identify(distinctId: string, properties?: AnalyticsProperties): void {\n const payload: Parameters<PostHog['identify']>[0] = { distinctId }\n if (properties !== undefined) payload.properties = properties\n this.posthog.identify(payload)\n }\n\n /** Schedule an irreversible person merge; only alias IDs the application proves belong to one person. */\n alias(distinctId: string, previousDistinctId: string): void {\n this.waitUntil(this.posthog.aliasImmediate({ distinctId, alias: previousDistinctId }))\n }\n\n /** Schedule an event; without an event ID the SDK sets a new UUID and the capture time. */\n track(\n name: string,\n properties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n ): void {\n this.capture(name, properties, target, 'person')\n }\n\n /** Schedule an event without creating a person profile for an attempt or resource ID. */\n trackAnonymous(\n name: string,\n properties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n ): void {\n this.capture(name, properties, target, 'anonymous')\n }\n\n private capture(\n name: string,\n eventProperties: AnalyticsEventInput['properties'],\n target: AnalyticsTarget,\n profile: 'person' | 'anonymous',\n ): void {\n const sessionId = currentPostHogSession()\n // An application's own `$session_id` wins over the request's.\n const properties = sessionId\n ? { $session_id: sessionId, ...eventProperties }\n : { ...eventProperties }\n const payload: Parameters<PostHog['captureImmediate']>[0] = {\n distinctId: target.distinctId,\n event: name,\n properties,\n }\n if (profile === 'anonymous')\n payload.properties = { ...properties, $process_person_profile: false }\n if (target.eventId !== undefined) {\n // PostHog drops a repeat only when its uuid and timestamp match the first event.\n payload.uuid = isUuid(target.eventId)\n ? target.eventId\n : uuidv5(target.eventId, EVENT_ID_NAMESPACE)\n payload.timestamp = new Date(target.occurredAt)\n }\n this.waitUntil(this.posthog.captureImmediate(payload))\n }\n}\n"],"mappings":";;;;;;;AAeA,MAAM,qBAAqB;;AAiB3B,IAAa,0BAAb,MAAa,wBAAoD;CAMpD;CACQ;;CALnB,AAAS;CAET,AAAQ,YAEN,AAAS,SACT,AAAiB,WACjB;EAFS;EACQ;EAEjB,KAAK,UAAU,IAAI,sBAAsB,SAAS,SAAS;CAC7D;;CAGA,OAAO,OAAO,QAAyD;EACrE,MAAM,SAAS,OAAO,OAAO,KAAK;EAClC,IAAI,OAAO,WAAW,GAAG,MAAM,IAAI,0BAA0B,iBAAiB;EAC9E,MAAM,SAAS,IAAI,QAAQ,QAAQ;GACjC,MAAM;GACN,GAAG,OAAO;GACV,SAAS;GACT,eAAe;GACf,WAAW,OAAO;GAClB,SAAS;IACP,GAAG,OAAO,SAAS;IACnB,aAAa,OAAO;IACpB,aAAa,OAAO;GACtB;EACF,CAAC;EAED,AAAK,OAAO,SAAS;GAAE,aAAa,OAAO;GAAa,QAAQ,OAAO;EAAO,CAAC;EAC/E,OAAO,IAAI,wBAAwB,QAAQ,OAAO,SAAS;CAC7D;;CAGA,SAAS,YAAoB,YAAwC;EACnE,MAAM,UAA8C,EAAE,WAAW;EACjE,IAAI,eAAe,QAAW,QAAQ,aAAa;EACnD,KAAK,QAAQ,SAAS,OAAO;CAC/B;;CAGA,MAAM,YAAoB,oBAAkC;EAC1D,KAAK,UAAU,KAAK,QAAQ,eAAe;GAAE;GAAY,OAAO;EAAmB,CAAC,CAAC;CACvF;;CAGA,MACE,MACA,YACA,QACM;EACN,KAAK,QAAQ,MAAM,YAAY,QAAQ,QAAQ;CACjD;;CAGA,eACE,MACA,YACA,QACM;EACN,KAAK,QAAQ,MAAM,YAAY,QAAQ,WAAW;CACpD;CAEA,AAAQ,QACN,MACA,iBACA,QACA,SACM;EACN,MAAM,YAAY,sBAAsB;EAExC,MAAM,aAAa,YACf;GAAE,aAAa;GAAW,GAAG;EAAgB,IAC7C,EAAE,GAAG,gBAAgB;EACzB,MAAM,UAAsD;GAC1D,YAAY,OAAO;GACnB,OAAO;GACP;EACF;EACA,IAAI,YAAY,aACd,QAAQ,aAAa;GAAE,GAAG;GAAY,yBAAyB;EAAM;EACvE,IAAI,OAAO,YAAY,QAAW;GAEhC,QAAQ,OAAOA,SAAO,OAAO,OAAO,IAChC,OAAO,UACPC,GAAO,OAAO,SAAS,kBAAkB;GAC7C,QAAQ,YAAY,IAAI,KAAK,OAAO,UAAU;EAChD;EACA,KAAK,UAAU,KAAK,QAAQ,iBAAiB,OAAO,CAAC;CACvD;AACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/infrastructure/posthog/tanstack.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Link Worker events to the browser session that sent the request.
|
|
4
|
+
* Register it in `requestMiddleware`. Each `PostHogAnalyticsService` event in that request then
|
|
5
|
+
* gets `$session_id`. A request without the header, and queue, cron, and Workflow code, add nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare const posthogRequestMiddleware: import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
|
|
8
|
+
//#endregion
|
|
9
|
+
//# sourceMappingURL=tanstack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tanstack.d.ts","names":[],"sources":["../../src/infrastructure/posthog/tanstack.ts"],"mappings":";;;;;;qBAUa,0DAAwB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createMiddleware } from "@tanstack/react-start";
|
|
2
|
+
|
|
3
|
+
//#region src/infrastructure/posthog/tanstack.ts
|
|
4
|
+
const SESSION_HEADER = "x-posthog-session-id";
|
|
5
|
+
/**
|
|
6
|
+
* Link Worker events to the browser session that sent the request.
|
|
7
|
+
* Register it in `requestMiddleware`. Each `PostHogAnalyticsService` event in that request then
|
|
8
|
+
* gets `$session_id`. A request without the header, and queue, cron, and Workflow code, add nothing.
|
|
9
|
+
*/
|
|
10
|
+
const posthogRequestMiddleware = createMiddleware({ type: "request" }).server(async ({ request, next }) => {
|
|
11
|
+
const sessionId = request.headers.get(SESSION_HEADER)?.trim();
|
|
12
|
+
if (!sessionId) return next();
|
|
13
|
+
const { runWithPostHogSession } = await import("../request-session-D1r9ZvcS.js");
|
|
14
|
+
return runWithPostHogSession(sessionId, () => next());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { posthogRequestMiddleware };
|
|
19
|
+
//# sourceMappingURL=tanstack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tanstack.js","names":[],"sources":["../../src/infrastructure/posthog/tanstack.ts"],"sourcesContent":["import { createMiddleware } from '@tanstack/react-start'\n\n// posthog-js sends this header when the browser sets `tracing_headers` for the application host.\nconst SESSION_HEADER = 'x-posthog-session-id'\n\n/**\n * Link Worker events to the browser session that sent the request.\n * Register it in `requestMiddleware`. Each `PostHogAnalyticsService` event in that request then\n * gets `$session_id`. A request without the header, and queue, cron, and Workflow code, add nothing.\n */\nexport const posthogRequestMiddleware = createMiddleware({ type: 'request' }).server(\n async ({ request, next }) => {\n const sessionId = request.headers.get(SESSION_HEADER)?.trim()\n if (!sessionId) return next()\n // The browser evaluates this module, so the Node async scope loads inside `.server()`.\n const { runWithPostHogSession } = await import('./request-session.ts')\n return runWithPostHogSession(sessionId, () => next())\n },\n)\n"],"mappings":";;;AAGA,MAAM,iBAAiB;;;;;;AAOvB,MAAa,2BAA2B,iBAAiB,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,OAC5E,OAAO,EAAE,SAAS,WAAW;CAC3B,MAAM,YAAY,QAAQ,QAAQ,IAAI,cAAc,CAAC,EAAE,KAAK;CAC5D,IAAI,CAAC,WAAW,OAAO,KAAK;CAE5B,MAAM,EAAE,0BAA0B,MAAM,OAAO;CAC/C,OAAO,sBAAsB,iBAAiB,KAAK,CAAC;AACtD,CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
//#region src/infrastructure/posthog/request-session.ts
|
|
4
|
+
const storage = new AsyncLocalStorage();
|
|
5
|
+
/** Run the rest of one request with the browser's PostHog session ID. */
|
|
6
|
+
function runWithPostHogSession(sessionId, work) {
|
|
7
|
+
return storage.run(sessionId, work);
|
|
8
|
+
}
|
|
9
|
+
/** The current request's PostHog session ID, or undefined outside such a request. */
|
|
10
|
+
function currentPostHogSession() {
|
|
11
|
+
return storage.getStore();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
export { runWithPostHogSession as n, currentPostHogSession as t };
|
|
16
|
+
//# sourceMappingURL=request-session-Qe_Q_u9G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-session-Qe_Q_u9G.js","names":[],"sources":["../src/infrastructure/posthog/request-session.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\n\n// One async scope per request keeps each browser session ID apart from concurrent requests.\nconst storage = new AsyncLocalStorage<string>()\n\n/** Run the rest of one request with the browser's PostHog session ID. */\nexport function runWithPostHogSession<T>(sessionId: string, work: () => T): T {\n return storage.run(sessionId, work)\n}\n\n/** The current request's PostHog session ID, or undefined outside such a request. */\nexport function currentPostHogSession(): string | undefined {\n return storage.getStore()\n}\n"],"mappings":";;;AAGA,MAAM,UAAU,IAAI,kBAA0B;;AAG9C,SAAgB,sBAAyB,WAAmB,MAAkB;CAC5E,OAAO,QAAQ,IAAI,WAAW,IAAI;AACpC;;AAGA,SAAgB,wBAA4C;CAC1D,OAAO,QAAQ,SAAS;AAC1B"}
|
|
@@ -4,10 +4,12 @@ type EventObject<K extends Stripe.Event['type']> = Extract<Stripe.Event, {
|
|
|
4
4
|
type: K;
|
|
5
5
|
}>['data']['object'];
|
|
6
6
|
/**
|
|
7
|
-
* Convert verified Stripe
|
|
8
|
-
*
|
|
7
|
+
* Convert one verified Stripe event with callbacks typed to that event's object.
|
|
8
|
+
* An entry is one callback or an array of callbacks. Results stay in entry order.
|
|
9
|
+
* A callback returns `undefined` to ignore the event; the result omits it.
|
|
10
|
+
* An unmapped event returns `[]`. Applications own validation and payment policy.
|
|
9
11
|
*/
|
|
10
|
-
export declare function stripeEventMap<T>(map: { [K in Stripe.Event['type']]?: (object: EventObject<K>) => T; }): (event: Stripe.Event) => T
|
|
12
|
+
export declare function stripeEventMap<T>(map: { [K in Stripe.Event['type']]?: ((object: EventObject<K>) => T | undefined) | readonly ((object: EventObject<K>) => T | undefined)[]; }): (event: Stripe.Event) => T[];
|
|
11
13
|
/** Identify one Stripe event delivery; different events for one payment retain different IDs. */
|
|
12
14
|
export declare function stripeEventId(event: Stripe.Event): string;
|
|
13
15
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-map.d.ts","names":[],"sources":["../../src/infrastructure/stripe/event-map.ts"],"mappings":";;KAEK,YAAY,UAAU,OAAO,iBAAiB,QACjD,OAAO;EACL,MAAM
|
|
1
|
+
{"version":3,"file":"event-map.d.ts","names":[],"sources":["../../src/infrastructure/stripe/event-map.ts"],"mappings":";;KAEK,YAAY,UAAU,OAAO,iBAAiB,QACjD,OAAO;EACL,MAAM;;;;;;;;wBAWM,eAAe,GAAG,QAC/B,KAAK,OAAO,mBACP,QAAQ,YAAY,OAAO,4BAClB,QAAQ,YAAY,OAAO,uBACvC,OAAO,OAAO,UAAU;;wBAwBb,cAAc,OAAO,OAAO"}
|
package/dist/stripe/event-map.js
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
//#region src/infrastructure/stripe/event-map.ts
|
|
2
2
|
/**
|
|
3
|
-
* Convert verified Stripe
|
|
4
|
-
*
|
|
3
|
+
* Convert one verified Stripe event with callbacks typed to that event's object.
|
|
4
|
+
* An entry is one callback or an array of callbacks. Results stay in entry order.
|
|
5
|
+
* A callback returns `undefined` to ignore the event; the result omits it.
|
|
6
|
+
* An unmapped event returns `[]`. Applications own validation and payment policy.
|
|
5
7
|
*/
|
|
6
8
|
function stripeEventMap(map) {
|
|
7
9
|
const handlers = map;
|
|
8
10
|
return (event) => {
|
|
9
|
-
|
|
11
|
+
const entry = handlers[event.type];
|
|
12
|
+
if (entry === void 0) return [];
|
|
13
|
+
return (isCallbackList(entry) ? entry : [entry]).flatMap((callback) => {
|
|
14
|
+
const result = callback(event.data.object);
|
|
15
|
+
return result === void 0 ? [] : [result];
|
|
16
|
+
});
|
|
10
17
|
};
|
|
11
18
|
}
|
|
19
|
+
function isCallbackList(entry) {
|
|
20
|
+
return Array.isArray(entry);
|
|
21
|
+
}
|
|
12
22
|
/** Identify one Stripe event delivery; different events for one payment retain different IDs. */
|
|
13
23
|
function stripeEventId(event) {
|
|
14
24
|
return `stripe:event:${event.id}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-map.js","names":[],"sources":["../../src/infrastructure/stripe/event-map.ts"],"sourcesContent":["import type Stripe from 'stripe'\n\ntype EventObject<K extends Stripe.Event['type']> = Extract<\n Stripe.Event,\n { type: K }\n>['data']['object']\n\n/**\n * Convert verified Stripe
|
|
1
|
+
{"version":3,"file":"event-map.js","names":[],"sources":["../../src/infrastructure/stripe/event-map.ts"],"sourcesContent":["import type Stripe from 'stripe'\n\ntype EventObject<K extends Stripe.Event['type']> = Extract<\n Stripe.Event,\n { type: K }\n>['data']['object']\n\ntype EventCallback<T> = (object: never) => T | undefined\n\n/**\n * Convert one verified Stripe event with callbacks typed to that event's object.\n * An entry is one callback or an array of callbacks. Results stay in entry order.\n * A callback returns `undefined` to ignore the event; the result omits it.\n * An unmapped event returns `[]`. Applications own validation and payment policy.\n */\nexport function stripeEventMap<T>(map: {\n [K in Stripe.Event['type']]?:\n | ((object: EventObject<K>) => T | undefined)\n | readonly ((object: EventObject<K>) => T | undefined)[]\n}): (event: Stripe.Event) => T[] {\n const handlers: Partial<\n Record<Stripe.Event['type'], EventCallback<T> | readonly EventCallback<T>[]>\n > = map\n return (event) => {\n const entry = handlers[event.type]\n if (entry === undefined) return []\n const callbacks = isCallbackList(entry) ? entry : [entry]\n return callbacks.flatMap((callback) => {\n // SAFETY: The map binds each event type to its object; indexed lookup loses that correlation.\n const result = callback(event.data.object as never)\n return result === undefined ? [] : [result]\n })\n }\n}\n\n// Array.isArray widens to any[], so the predicate restores the callback list type.\nfunction isCallbackList<T>(\n entry: EventCallback<T> | readonly EventCallback<T>[],\n): entry is readonly EventCallback<T>[] {\n return Array.isArray(entry)\n}\n\n/** Identify one Stripe event delivery; different events for one payment retain different IDs. */\nexport function stripeEventId(event: Stripe.Event): string {\n return `stripe:event:${event.id}`\n}\n"],"mappings":";;;;;;;AAeA,SAAgB,eAAkB,KAID;CAC/B,MAAM,WAEF;CACJ,QAAQ,UAAU;EAChB,MAAM,QAAQ,SAAS,MAAM;EAC7B,IAAI,UAAU,QAAW,OAAO,CAAC;EAEjC,QADkB,eAAe,KAAK,IAAI,QAAQ,CAAC,KAAK,EACxC,CAAC,SAAS,aAAa;GAErC,MAAM,SAAS,SAAS,MAAM,KAAK,MAAe;GAClD,OAAO,WAAW,SAAY,CAAC,IAAI,CAAC,MAAM;EAC5C,CAAC;CACH;AACF;AAGA,SAAS,eACP,OACsC;CACtC,OAAO,MAAM,QAAQ,KAAK;AAC5B;;AAGA,SAAgB,cAAc,OAA6B;CACzD,OAAO,gBAAgB,MAAM;AAC/B"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://www.schemastore.org/package.json",
|
|
3
3
|
"name": "better-ship",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.45.0",
|
|
5
5
|
"description": "Foundation for applications on Cloudflare Workers: logger, errors, message bus, infrastructure adapters, and UI primitives",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"./posthog/metrics": "./dist/posthog/metrics.js",
|
|
53
53
|
"./posthog/proxy": "./dist/posthog/proxy.js",
|
|
54
54
|
"./posthog/server": "./dist/posthog/server.js",
|
|
55
|
+
"./posthog/tanstack": "./dist/posthog/tanstack.js",
|
|
55
56
|
"./resend": "./dist/resend.js",
|
|
56
57
|
"./sentry/client": "./dist/sentry/client.js",
|
|
57
58
|
"./sentry/server": "./dist/sentry/server.js",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
|
+
|
|
3
|
+
// One async scope per request keeps each browser session ID apart from concurrent requests.
|
|
4
|
+
const storage = new AsyncLocalStorage<string>()
|
|
5
|
+
|
|
6
|
+
/** Run the rest of one request with the browser's PostHog session ID. */
|
|
7
|
+
export function runWithPostHogSession<T>(sessionId: string, work: () => T): T {
|
|
8
|
+
return storage.run(sessionId, work)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** The current request's PostHog session ID, or undefined outside such a request. */
|
|
12
|
+
export function currentPostHogSession(): string | undefined {
|
|
13
|
+
return storage.getStore()
|
|
14
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from '@/core'
|
|
11
11
|
|
|
12
12
|
import { PostHogMetricsService } from './metrics.ts'
|
|
13
|
+
import { currentPostHogSession } from './request-session.ts'
|
|
13
14
|
|
|
14
15
|
// Fixed forever: changing it changes every derived UUID, so PostHog would keep a repeat.
|
|
15
16
|
const EVENT_ID_NAMESPACE = '53d7af30-65cb-4176-b2c9-f1ed42367ab1'
|
|
@@ -98,7 +99,11 @@ export class PostHogAnalyticsService implements AnalyticsService {
|
|
|
98
99
|
target: AnalyticsTarget,
|
|
99
100
|
profile: 'person' | 'anonymous',
|
|
100
101
|
): void {
|
|
101
|
-
const
|
|
102
|
+
const sessionId = currentPostHogSession()
|
|
103
|
+
// An application's own `$session_id` wins over the request's.
|
|
104
|
+
const properties = sessionId
|
|
105
|
+
? { $session_id: sessionId, ...eventProperties }
|
|
106
|
+
: { ...eventProperties }
|
|
102
107
|
const payload: Parameters<PostHog['captureImmediate']>[0] = {
|
|
103
108
|
distinctId: target.distinctId,
|
|
104
109
|
event: name,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
2
|
+
|
|
3
|
+
// posthog-js sends this header when the browser sets `tracing_headers` for the application host.
|
|
4
|
+
const SESSION_HEADER = 'x-posthog-session-id'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Link Worker events to the browser session that sent the request.
|
|
8
|
+
* Register it in `requestMiddleware`. Each `PostHogAnalyticsService` event in that request then
|
|
9
|
+
* gets `$session_id`. A request without the header, and queue, cron, and Workflow code, add nothing.
|
|
10
|
+
*/
|
|
11
|
+
export const posthogRequestMiddleware = createMiddleware({ type: 'request' }).server(
|
|
12
|
+
async ({ request, next }) => {
|
|
13
|
+
const sessionId = request.headers.get(SESSION_HEADER)?.trim()
|
|
14
|
+
if (!sessionId) return next()
|
|
15
|
+
// The browser evaluates this module, so the Node async scope loads inside `.server()`.
|
|
16
|
+
const { runWithPostHogSession } = await import('./request-session.ts')
|
|
17
|
+
return runWithPostHogSession(sessionId, () => next())
|
|
18
|
+
},
|
|
19
|
+
)
|
|
@@ -5,20 +5,41 @@ type EventObject<K extends Stripe.Event['type']> = Extract<
|
|
|
5
5
|
{ type: K }
|
|
6
6
|
>['data']['object']
|
|
7
7
|
|
|
8
|
+
type EventCallback<T> = (object: never) => T | undefined
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
|
-
* Convert verified Stripe
|
|
10
|
-
*
|
|
11
|
+
* Convert one verified Stripe event with callbacks typed to that event's object.
|
|
12
|
+
* An entry is one callback or an array of callbacks. Results stay in entry order.
|
|
13
|
+
* A callback returns `undefined` to ignore the event; the result omits it.
|
|
14
|
+
* An unmapped event returns `[]`. Applications own validation and payment policy.
|
|
11
15
|
*/
|
|
12
16
|
export function stripeEventMap<T>(map: {
|
|
13
|
-
[K in Stripe.Event['type']]?:
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
[K in Stripe.Event['type']]?:
|
|
18
|
+
| ((object: EventObject<K>) => T | undefined)
|
|
19
|
+
| readonly ((object: EventObject<K>) => T | undefined)[]
|
|
20
|
+
}): (event: Stripe.Event) => T[] {
|
|
21
|
+
const handlers: Partial<
|
|
22
|
+
Record<Stripe.Event['type'], EventCallback<T> | readonly EventCallback<T>[]>
|
|
23
|
+
> = map
|
|
16
24
|
return (event) => {
|
|
17
|
-
|
|
18
|
-
|
|
25
|
+
const entry = handlers[event.type]
|
|
26
|
+
if (entry === undefined) return []
|
|
27
|
+
const callbacks = isCallbackList(entry) ? entry : [entry]
|
|
28
|
+
return callbacks.flatMap((callback) => {
|
|
29
|
+
// SAFETY: The map binds each event type to its object; indexed lookup loses that correlation.
|
|
30
|
+
const result = callback(event.data.object as never)
|
|
31
|
+
return result === undefined ? [] : [result]
|
|
32
|
+
})
|
|
19
33
|
}
|
|
20
34
|
}
|
|
21
35
|
|
|
36
|
+
// Array.isArray widens to any[], so the predicate restores the callback list type.
|
|
37
|
+
function isCallbackList<T>(
|
|
38
|
+
entry: EventCallback<T> | readonly EventCallback<T>[],
|
|
39
|
+
): entry is readonly EventCallback<T>[] {
|
|
40
|
+
return Array.isArray(entry)
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
/** Identify one Stripe event delivery; different events for one payment retain different IDs. */
|
|
23
44
|
export function stripeEventId(event: Stripe.Event): string {
|
|
24
45
|
return `stripe:event:${event.id}`
|