@gurulu/node 0.1.2 → 1.0.1

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 (50) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +64 -31
  3. package/dist/context.d.ts +12 -0
  4. package/dist/context.d.ts.map +1 -0
  5. package/dist/core.d.ts +60 -0
  6. package/dist/core.d.ts.map +1 -0
  7. package/dist/errors.d.ts +32 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/identify.d.ts +9 -0
  10. package/dist/identify.d.ts.map +1 -0
  11. package/dist/index.d.ts +13 -13
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +947 -28
  14. package/dist/middleware/express.d.ts +25 -0
  15. package/dist/middleware/express.d.ts.map +1 -0
  16. package/dist/middleware/express.js +66 -0
  17. package/dist/middleware/fastify.d.ts +20 -0
  18. package/dist/middleware/fastify.d.ts.map +1 -0
  19. package/dist/middleware/fastify.js +68 -0
  20. package/dist/middleware/next.d.ts +10 -0
  21. package/dist/middleware/next.d.ts.map +1 -0
  22. package/dist/middleware/next.js +69 -0
  23. package/dist/queue.d.ts +20 -0
  24. package/dist/queue.d.ts.map +1 -0
  25. package/dist/track.d.ts +10 -0
  26. package/dist/track.d.ts.map +1 -0
  27. package/dist/transport.d.ts +16 -0
  28. package/dist/transport.d.ts.map +1 -0
  29. package/dist/types.d.ts +129 -45
  30. package/dist/types.d.ts.map +1 -0
  31. package/dist/webhooks/custom.d.ts +30 -0
  32. package/dist/webhooks/custom.d.ts.map +1 -0
  33. package/dist/webhooks/custom.js +123 -0
  34. package/dist/webhooks/lemonsqueezy.d.ts +30 -0
  35. package/dist/webhooks/lemonsqueezy.d.ts.map +1 -0
  36. package/dist/webhooks/lemonsqueezy.js +140 -0
  37. package/dist/webhooks/shopify.d.ts +18 -0
  38. package/dist/webhooks/shopify.d.ts.map +1 -0
  39. package/dist/webhooks/shopify.js +142 -0
  40. package/dist/webhooks/stripe.d.ts +31 -0
  41. package/dist/webhooks/stripe.d.ts.map +1 -0
  42. package/dist/webhooks/stripe.js +160 -0
  43. package/package.json +97 -16
  44. package/dist/business-events.d.ts +0 -73
  45. package/dist/business-events.js +0 -111
  46. package/dist/client.d.ts +0 -150
  47. package/dist/client.js +0 -442
  48. package/dist/middleware.d.ts +0 -31
  49. package/dist/middleware.js +0 -138
  50. package/dist/types.js +0 -30
package/package.json CHANGED
@@ -1,30 +1,111 @@
1
1
  {
2
2
  "name": "@gurulu/node",
3
- "version": "0.1.2",
4
- "description": "Gurulu.io server-side analytics SDK for Node.js",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "files": ["dist"],
3
+ "version": "1.0.1",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "type": "module",
10
+ "description": "Gurulu server SDK. Outcome verification (purchase_completed, signup_completed). Express/Fastify/Next.js Route Handler + webhook helpers (Stripe/Shopify/Lemon Squeezy/custom). K16 4-kanal producer compliant.",
11
+ "homepage": "https://gurulu.io",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Preatan/gurulu.io.git",
15
+ "directory": "packages/sdk-node"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "keywords": [
23
+ "analytics",
24
+ "outcome-verification",
25
+ "webhook",
26
+ "stripe",
27
+ "shopify",
28
+ "lemonsqueezy",
29
+ "express",
30
+ "fastify",
31
+ "next.js",
32
+ "bun",
33
+ "node",
34
+ "gurulu"
35
+ ],
36
+ "main": "./dist/index.js",
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
8
39
  "exports": {
9
40
  ".": {
10
41
  "types": "./dist/index.d.ts",
11
42
  "import": "./dist/index.js",
12
- "require": "./dist/index.js"
43
+ "default": "./dist/index.js"
44
+ },
45
+ "./webhooks/stripe": {
46
+ "types": "./dist/webhooks/stripe.d.ts",
47
+ "import": "./dist/webhooks/stripe.js"
48
+ },
49
+ "./webhooks/shopify": {
50
+ "types": "./dist/webhooks/shopify.d.ts",
51
+ "import": "./dist/webhooks/shopify.js"
52
+ },
53
+ "./webhooks/lemonsqueezy": {
54
+ "types": "./dist/webhooks/lemonsqueezy.d.ts",
55
+ "import": "./dist/webhooks/lemonsqueezy.js"
56
+ },
57
+ "./webhooks/custom": {
58
+ "types": "./dist/webhooks/custom.d.ts",
59
+ "import": "./dist/webhooks/custom.js"
60
+ },
61
+ "./middleware/express": {
62
+ "types": "./dist/middleware/express.d.ts",
63
+ "import": "./dist/middleware/express.js"
64
+ },
65
+ "./middleware/fastify": {
66
+ "types": "./dist/middleware/fastify.d.ts",
67
+ "import": "./dist/middleware/fastify.js"
13
68
  },
14
- "./middleware": {
15
- "types": "./dist/middleware.d.ts",
16
- "import": "./dist/middleware.js",
17
- "require": "./dist/middleware.js"
69
+ "./middleware/next": {
70
+ "types": "./dist/middleware/next.d.ts",
71
+ "import": "./dist/middleware/next.js"
18
72
  },
19
73
  "./package.json": "./package.json"
20
74
  },
21
75
  "scripts": {
22
- "build": "tsc",
23
- "dev": "tsc --watch"
76
+ "build": "rm -rf dist && bun run build:js && bun run build:types",
77
+ "build:js": "bun build ./src/index.ts ./src/webhooks/stripe.ts ./src/webhooks/shopify.ts ./src/webhooks/lemonsqueezy.ts ./src/webhooks/custom.ts ./src/middleware/express.ts ./src/middleware/fastify.ts ./src/middleware/next.ts --outdir ./dist --target node --format=esm --external express --external fastify --external next --external undici",
78
+ "build:types": "tsc -p tsconfig.build.json",
79
+ "typecheck": "tsc --noEmit",
80
+ "test": "bun test",
81
+ "prepublishOnly": "bun run build",
82
+ "clean": "rm -rf dist .turbo"
24
83
  },
25
- "engines": {
26
- "node": ">=18.0.0"
84
+ "peerDependencies": {
85
+ "undici": "^7.0.0",
86
+ "express": "^4.0.0 || ^5.0.0",
87
+ "fastify": "^4.0.0 || ^5.0.0",
88
+ "next": "^14.0.0 || ^15.0.0"
27
89
  },
28
- "license": "MIT",
29
- "keywords": ["analytics", "tracking", "server-side", "gurulu"]
90
+ "peerDependenciesMeta": {
91
+ "undici": {
92
+ "optional": true
93
+ },
94
+ "express": {
95
+ "optional": true
96
+ },
97
+ "fastify": {
98
+ "optional": true
99
+ },
100
+ "next": {
101
+ "optional": true
102
+ }
103
+ },
104
+ "devDependencies": {
105
+ "typescript": "^5.6.0"
106
+ },
107
+ "engines": {
108
+ "node": ">=20",
109
+ "bun": ">=1.0.0"
110
+ }
30
111
  }
@@ -1,73 +0,0 @@
1
- /**
2
- * Business event emitters — Phase 10 W4.2.
3
- *
4
- * These are tiny factories that return envelope-shaped `ServerEvent` objects
5
- * for common server-authored events (user signup, payment, subscription,
6
- * order, lead capture). They stamp the canonical `event_tier = 'verified'`
7
- * and `event_source = 'server_sdk'` fields so downstream readers can trust
8
- * the provenance without guessing.
9
- *
10
- * The shape intentionally satisfies BOTH:
11
- * - the simple `ServerEvent` contract the /api/ingest/v1/server route
12
- * consumes (event_name, user_id, properties, timestamp,
13
- * idempotency_key?, correlation_id?), AND
14
- * - the richer canonical Envelope fields consumers may want to read
15
- * (event_tier, event_source) — we attach these at the top level so they
16
- * travel alongside the event without collapsing into properties.
17
- *
18
- * `createEnvelope` from @gurulu/shared-core is used to validate/normalize the
19
- * shared envelope defaults where practical (event_type, timestamp,
20
- * consent_level, etc.), but the ServerEvent-specific keys (user_id) remain at
21
- * the root.
22
- *
23
- * Related docs: PHASE-10-ROADMAP.md §W4.2
24
- */
25
- import { type Envelope, type ServerEvent } from '@gurulu/shared-core';
26
- /**
27
- * Concrete shape returned by every business-event factory below:
28
- * a ServerEvent with the canonical envelope provenance fields attached.
29
- */
30
- export type BusinessEvent = ServerEvent & {
31
- event_tier: Envelope['event_tier'];
32
- event_source: Envelope['event_source'];
33
- event_type: Envelope['event_type'];
34
- };
35
- export interface UserCreatedArgs {
36
- userId: string;
37
- email?: string;
38
- traits?: Record<string, unknown>;
39
- }
40
- export declare function userCreated(args: UserCreatedArgs): BusinessEvent;
41
- export interface PaymentSucceededArgs {
42
- userId: string;
43
- amount: number;
44
- currency: string;
45
- orderId: string;
46
- properties?: Record<string, unknown>;
47
- }
48
- export declare function paymentSucceeded(args: PaymentSucceededArgs): BusinessEvent;
49
- export interface SubscriptionStartedArgs {
50
- userId: string;
51
- plan: string;
52
- amount: number;
53
- currency: string;
54
- }
55
- export declare function subscriptionStarted(args: SubscriptionStartedArgs): BusinessEvent;
56
- export interface OrderPlacedArgs {
57
- userId: string;
58
- orderId: string;
59
- total: number;
60
- currency: string;
61
- items?: Array<{
62
- id: string;
63
- qty: number;
64
- price: number;
65
- }>;
66
- }
67
- export declare function orderPlaced(args: OrderPlacedArgs): BusinessEvent;
68
- export interface LeadCapturedArgs {
69
- anonymousId: string;
70
- email?: string;
71
- source?: string;
72
- }
73
- export declare function leadCaptured(args: LeadCapturedArgs): BusinessEvent;
@@ -1,111 +0,0 @@
1
- "use strict";
2
- /**
3
- * Business event emitters — Phase 10 W4.2.
4
- *
5
- * These are tiny factories that return envelope-shaped `ServerEvent` objects
6
- * for common server-authored events (user signup, payment, subscription,
7
- * order, lead capture). They stamp the canonical `event_tier = 'verified'`
8
- * and `event_source = 'server_sdk'` fields so downstream readers can trust
9
- * the provenance without guessing.
10
- *
11
- * The shape intentionally satisfies BOTH:
12
- * - the simple `ServerEvent` contract the /api/ingest/v1/server route
13
- * consumes (event_name, user_id, properties, timestamp,
14
- * idempotency_key?, correlation_id?), AND
15
- * - the richer canonical Envelope fields consumers may want to read
16
- * (event_tier, event_source) — we attach these at the top level so they
17
- * travel alongside the event without collapsing into properties.
18
- *
19
- * `createEnvelope` from @gurulu/shared-core is used to validate/normalize the
20
- * shared envelope defaults where practical (event_type, timestamp,
21
- * consent_level, etc.), but the ServerEvent-specific keys (user_id) remain at
22
- * the root.
23
- *
24
- * Related docs: PHASE-10-ROADMAP.md §W4.2
25
- */
26
- Object.defineProperty(exports, "__esModule", { value: true });
27
- exports.userCreated = userCreated;
28
- exports.paymentSucceeded = paymentSucceeded;
29
- exports.subscriptionStarted = subscriptionStarted;
30
- exports.orderPlaced = orderPlaced;
31
- exports.leadCaptured = leadCaptured;
32
- const shared_core_1 = require("@gurulu/shared-core");
33
- const SDK_VERSION = 'node@0.1.0';
34
- function baseEnvelope(eventName, anonymousId, properties) {
35
- return (0, shared_core_1.createEnvelope)({
36
- site_id: '', // supplied at transport time by the client
37
- anonymous_id: anonymousId,
38
- session_id: '', // server SDK has no browser session
39
- event_name: eventName,
40
- event_type: 'server',
41
- event_tier: 'verified',
42
- event_source: 'server_sdk',
43
- consent_level: 'accepted',
44
- sdk_version: SDK_VERSION,
45
- timestamp: new Date(Date.now()).toISOString(),
46
- properties,
47
- });
48
- }
49
- function toBusinessEvent(envelope, userId, extras = {}) {
50
- return {
51
- event_name: envelope.event_name,
52
- user_id: userId,
53
- properties: envelope.properties,
54
- timestamp: envelope.timestamp,
55
- event_type: envelope.event_type,
56
- event_tier: envelope.event_tier,
57
- event_source: envelope.event_source,
58
- ...extras,
59
- };
60
- }
61
- function userCreated(args) {
62
- const props = {
63
- ...(args.traits ?? {}),
64
- };
65
- if (args.email !== undefined)
66
- props.email = args.email;
67
- const env = baseEnvelope('user_created', args.userId, props);
68
- return toBusinessEvent(env, args.userId);
69
- }
70
- function paymentSucceeded(args) {
71
- const props = {
72
- ...(args.properties ?? {}),
73
- amount: args.amount,
74
- currency: args.currency,
75
- order_id: args.orderId,
76
- };
77
- const env = baseEnvelope('payment_succeeded', args.userId, props);
78
- return toBusinessEvent(env, args.userId);
79
- }
80
- function subscriptionStarted(args) {
81
- const props = {
82
- plan: args.plan,
83
- amount: args.amount,
84
- currency: args.currency,
85
- };
86
- const env = baseEnvelope('subscription_started', args.userId, props);
87
- return toBusinessEvent(env, args.userId);
88
- }
89
- function orderPlaced(args) {
90
- const props = {
91
- order_id: args.orderId,
92
- total: args.total,
93
- currency: args.currency,
94
- };
95
- if (args.items !== undefined)
96
- props.items = args.items;
97
- const env = baseEnvelope('order_placed', args.userId, props);
98
- return toBusinessEvent(env, args.userId);
99
- }
100
- function leadCaptured(args) {
101
- const props = {};
102
- if (args.email !== undefined)
103
- props.email = args.email;
104
- if (args.source !== undefined)
105
- props.source = args.source;
106
- const env = baseEnvelope('lead_captured', args.anonymousId, props);
107
- // Lead capture uses anonymous_id as the user_id placeholder until the lead
108
- // is resolved to a real account; the server route accepts any non-empty
109
- // user_id string and will look up the canonical profile from it.
110
- return toBusinessEvent(env, args.anonymousId);
111
- }
package/dist/client.d.ts DELETED
@@ -1,150 +0,0 @@
1
- /**
2
- * @gurulu/node — server-side SDK client.
3
- *
4
- * Responsibilities (Phase 10 W4.2):
5
- * - Queue + batch server events.
6
- * - Auto-flush at batchSize boundary and on a periodic timer.
7
- * - Exponential-backoff retry (200ms, 600ms, 1800ms — 3 attempts total) on
8
- * 5xx / network errors. No retry on 4xx.
9
- * - Attach deterministic Idempotency-Key headers so the server can dedupe
10
- * replayed batches across retries.
11
- * - Dead-letter callback for batches that exhaust retries or are rejected
12
- * with a client error.
13
- * - Flush on process.beforeExit + SIGTERM.
14
- *
15
- * Event shapes are pulled from @gurulu/shared-core (W1.2). Only the transport
16
- * config (`GuruluClientConfig`) is local.
17
- *
18
- * Related docs: PHASE-10-ROADMAP.md §W4.2
19
- */
20
- import type { ServerEvent } from '@gurulu/shared-core';
21
- import type { GuruluClientConfig } from './types';
22
- export interface IdentifyParams {
23
- /** The known user ID (e.g. database ID, email, etc.) */
24
- userId: string;
25
- /** An anonymous/session ID to link with this user. Required by the identify endpoint. */
26
- anonymousId: string;
27
- /** User traits (email, name, plan, etc.) to persist on the identity profile. */
28
- traits?: Record<string, unknown>;
29
- /** Device ID for cross-device identity resolution. */
30
- deviceId?: string;
31
- /** OAuth provider name (e.g. 'google', 'github'). */
32
- oauthProvider?: string;
33
- /** OAuth user ID from the provider. */
34
- oauthId?: string;
35
- /** Consent level: 'none' | 'analytics' | 'marketing' | 'full'. */
36
- consentLevel?: string;
37
- }
38
- export interface IdentifyResponse {
39
- status: string;
40
- canonical_id: string | null;
41
- consent_skipped?: boolean;
42
- merges?: Array<{
43
- winner: string;
44
- loser: string;
45
- reason: string;
46
- claim_types: string[];
47
- }>;
48
- }
49
- export interface Breadcrumb {
50
- timestamp: string;
51
- category: string;
52
- message: string;
53
- data?: Record<string, unknown>;
54
- }
55
- export interface GuruluClient {
56
- track(event: ServerEvent): void;
57
- identify(params: IdentifyParams): Promise<IdentifyResponse>;
58
- flush(): Promise<void>;
59
- shutdown(): Promise<void>;
60
- captureException(error: Error, context?: Record<string, unknown>): void;
61
- addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void;
62
- installGlobalHandlers(): void;
63
- /** Current queue length — exposed for tests and observability. */
64
- readonly queueSize: number;
65
- }
66
- /**
67
- * Deterministic idempotency key for a server event. Hashes the tuple
68
- * (site_id, event_name, timestamp, anonymous_id/user_id) so replayed events
69
- * across retries produce the same key and the server can dedupe.
70
- */
71
- export declare function createIdempotencyKey(event: ServerEvent, siteId: string): string;
72
- export declare class Gurulu implements GuruluClient {
73
- private readonly siteId;
74
- private readonly apiKey;
75
- private readonly endpoint;
76
- private readonly flushInterval;
77
- private readonly batchSize;
78
- private readonly timeout;
79
- private readonly onError?;
80
- private readonly onDeadLetter?;
81
- private readonly fetchImpl;
82
- private readonly debug;
83
- private readonly release;
84
- private queue;
85
- private breadcrumbs;
86
- private readonly maxBreadcrumbs;
87
- private flushTimer;
88
- private inFlight;
89
- private shutdownHandlers;
90
- constructor(config: GuruluClientConfig);
91
- get queueSize(): number;
92
- private static readonly REVENUE_EVENTS;
93
- /**
94
- * Enqueue a server event. Auto-flushes synchronously when the queue reaches
95
- * `batchSize`. Each event is stamped with a timestamp if missing so the
96
- * idempotency key is stable across retries.
97
- */
98
- track(event: ServerEvent): void;
99
- /**
100
- * Identify a user — links an anonymous session to a known user ID and
101
- * persists traits on the identity profile. Sends immediately to the
102
- * dedicated `/api/ingest/v1/identify` endpoint (not batched with track
103
- * events) because identity resolution is latency-sensitive and returns
104
- * the canonical_id synchronously.
105
- *
106
- * Retries with the same exponential-backoff strategy as batch flushes.
107
- */
108
- identify(params: IdentifyParams): Promise<IdentifyResponse>;
109
- /**
110
- * POST all queued events in one batch. On 5xx / network error retries with
111
- * exponential backoff (200ms, 600ms, 1800ms). On 4xx drops the batch and
112
- * fires the dead-letter callback. Concurrent flush calls are serialized.
113
- */
114
- flush(): Promise<void>;
115
- private sendBatch;
116
- /**
117
- * Add a custom breadcrumb to the trail. Breadcrumbs are sent with error
118
- * events to provide context about what happened before the error occurred.
119
- */
120
- addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void;
121
- /**
122
- * Capture an exception and send it as an error event.
123
- * Includes any accumulated breadcrumbs for debugging context.
124
- */
125
- captureException(error: Error, context?: Record<string, unknown>): void;
126
- /**
127
- * Install global error handlers for uncaught exceptions and unhandled rejections.
128
- * Call once at app startup.
129
- */
130
- installGlobalHandlers(): void;
131
- /**
132
- * Stop the periodic flush timer, detach process listeners, and perform a
133
- * final flush. Safe to call multiple times.
134
- */
135
- shutdown(): Promise<void>;
136
- }
137
- /**
138
- * Set the default Gurulu instance used by the convenience `captureException`.
139
- */
140
- export declare function setDefaultInstance(instance: Gurulu): void;
141
- /**
142
- * Convenience function — captures an exception via the default instance.
143
- * Call `setDefaultInstance()` first (or create a Gurulu client).
144
- */
145
- export declare function captureException(error: Error, context?: Record<string, unknown>): void;
146
- /**
147
- * Convenience function — adds a breadcrumb via the default instance.
148
- * Call `setDefaultInstance()` first (or create a Gurulu client).
149
- */
150
- export declare function addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void;