@gurulu/node 0.1.1 → 1.0.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 (48) 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 -12
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +947 -21
  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 -43
  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 +105 -11
  44. package/dist/business-events.d.ts +0 -73
  45. package/dist/business-events.js +0 -113
  46. package/dist/client.d.ts +0 -90
  47. package/dist/client.js +0 -307
  48. package/dist/types.js +0 -30
package/package.json CHANGED
@@ -1,17 +1,111 @@
1
1
  {
2
2
  "name": "@gurulu/node",
3
- "version": "0.1.1",
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.0",
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",
39
+ "exports": {
40
+ ".": {
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./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"
68
+ },
69
+ "./middleware/next": {
70
+ "types": "./dist/middleware/next.d.ts",
71
+ "import": "./dist/middleware/next.js"
72
+ },
73
+ "./package.json": "./package.json"
74
+ },
8
75
  "scripts": {
9
- "build": "tsc",
10
- "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"
11
83
  },
12
- "engines": {
13
- "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"
14
89
  },
15
- "license": "MIT",
16
- "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
+ }
17
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,113 +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
- };
84
- if (args.amount !== undefined)
85
- props.amount = args.amount;
86
- if (args.currency !== undefined)
87
- props.currency = args.currency;
88
- const env = baseEnvelope('subscription_started', args.userId, props);
89
- return toBusinessEvent(env, args.userId);
90
- }
91
- function orderPlaced(args) {
92
- const props = {
93
- order_id: args.orderId,
94
- total: args.total,
95
- currency: args.currency,
96
- };
97
- if (args.items !== undefined)
98
- props.items = args.items;
99
- const env = baseEnvelope('order_placed', args.userId, props);
100
- return toBusinessEvent(env, args.userId);
101
- }
102
- function leadCaptured(args) {
103
- const props = {};
104
- if (args.email !== undefined)
105
- props.email = args.email;
106
- if (args.source !== undefined)
107
- props.source = args.source;
108
- const env = baseEnvelope('lead_captured', args.anonymousId, props);
109
- // Lead capture uses anonymous_id as the user_id placeholder until the lead
110
- // is resolved to a real account; the server route accepts any non-empty
111
- // user_id string and will look up the canonical profile from it.
112
- return toBusinessEvent(env, args.anonymousId);
113
- }
package/dist/client.d.ts DELETED
@@ -1,90 +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 GuruluClient {
23
- track(event: ServerEvent): void;
24
- flush(): Promise<void>;
25
- shutdown(): Promise<void>;
26
- captureException(error: Error, context?: Record<string, unknown>): void;
27
- installGlobalHandlers(): void;
28
- /** Current queue length — exposed for tests and observability. */
29
- readonly queueSize: number;
30
- }
31
- /**
32
- * Deterministic idempotency key for a server event. Hashes the tuple
33
- * (site_id, event_name, timestamp, anonymous_id/user_id) so replayed events
34
- * across retries produce the same key and the server can dedupe.
35
- */
36
- export declare function createIdempotencyKey(event: ServerEvent, siteId: string): string;
37
- export declare class Gurulu implements GuruluClient {
38
- private readonly siteId;
39
- private readonly apiKey;
40
- private readonly endpoint;
41
- private readonly flushInterval;
42
- private readonly batchSize;
43
- private readonly timeout;
44
- private readonly onError?;
45
- private readonly onDeadLetter?;
46
- private readonly fetchImpl;
47
- private readonly debug;
48
- private queue;
49
- private flushTimer;
50
- private inFlight;
51
- private shutdownHandlers;
52
- constructor(config: GuruluClientConfig);
53
- get queueSize(): number;
54
- /**
55
- * Enqueue a server event. Auto-flushes synchronously when the queue reaches
56
- * `batchSize`. Each event is stamped with a timestamp if missing so the
57
- * idempotency key is stable across retries.
58
- */
59
- track(event: ServerEvent): void;
60
- /**
61
- * POST all queued events in one batch. On 5xx / network error retries with
62
- * exponential backoff (200ms, 600ms, 1800ms). On 4xx drops the batch and
63
- * fires the dead-letter callback. Concurrent flush calls are serialized.
64
- */
65
- flush(): Promise<void>;
66
- private sendBatch;
67
- /**
68
- * Capture an exception and send it as an error event.
69
- */
70
- captureException(error: Error, context?: Record<string, unknown>): void;
71
- /**
72
- * Install global error handlers for uncaught exceptions and unhandled rejections.
73
- * Call once at app startup.
74
- */
75
- installGlobalHandlers(): void;
76
- /**
77
- * Stop the periodic flush timer, detach process listeners, and perform a
78
- * final flush. Safe to call multiple times.
79
- */
80
- shutdown(): Promise<void>;
81
- }
82
- /**
83
- * Set the default Gurulu instance used by the convenience `captureException`.
84
- */
85
- export declare function setDefaultInstance(instance: Gurulu): void;
86
- /**
87
- * Convenience function — captures an exception via the default instance.
88
- * Call `setDefaultInstance()` first (or create a Gurulu client).
89
- */
90
- export declare function captureException(error: Error, context?: Record<string, unknown>): void;