@dumbledor/sdk 0.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.
@@ -0,0 +1,137 @@
1
+ 'use strict';
2
+
3
+ // src/constants.ts
4
+ var DEFAULT_INGEST_URL = "https://ingest.dumbledor.com";
5
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
+
7
+ // src/config.ts
8
+ var DumbledorConfigError = class extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "DumbledorConfigError";
12
+ }
13
+ };
14
+ function normalizeIngestUrl(value) {
15
+ const raw = value?.trim() || DEFAULT_INGEST_URL;
16
+ return raw.replace(/\/$/, "");
17
+ }
18
+ function assertClientConfig(config) {
19
+ const websiteId = config.websiteId?.trim();
20
+ if (!websiteId) {
21
+ throw new DumbledorConfigError("websiteId is required.");
22
+ }
23
+ if (!UUID_PATTERN.test(websiteId)) {
24
+ throw new DumbledorConfigError("websiteId must be a valid UUID.");
25
+ }
26
+ return {
27
+ websiteId,
28
+ ingestUrl: normalizeIngestUrl(config.ingestUrl),
29
+ honorDoNotTrack: config.honorDoNotTrack ?? false,
30
+ disabled: config.disabled ?? false,
31
+ fetch: config.fetch
32
+ };
33
+ }
34
+ function withWebsiteId(payload, websiteId) {
35
+ return {
36
+ ...payload,
37
+ websiteId
38
+ };
39
+ }
40
+ function resolveFetch(fetchImpl) {
41
+ const resolved = fetchImpl ?? globalThis.fetch;
42
+ if (!resolved) {
43
+ throw new DumbledorConfigError("fetch is not available in this runtime.");
44
+ }
45
+ return resolved;
46
+ }
47
+
48
+ // src/server/index.ts
49
+ function applyServerContext(headers, context) {
50
+ if (context?.userAgent) {
51
+ headers.set("User-Agent", context.userAgent);
52
+ }
53
+ if (context?.ip) {
54
+ headers.set("X-Forwarded-For", context.ip);
55
+ }
56
+ if (context?.referrer) {
57
+ headers.set("Referer", context.referrer);
58
+ }
59
+ }
60
+ async function send(config, payload, context) {
61
+ if (config.disabled) {
62
+ return;
63
+ }
64
+ const fetchImpl = resolveFetch(config.fetch);
65
+ const endpoint = `${config.ingestUrl}/v1/collect`;
66
+ const body = withWebsiteId(payload, config.websiteId);
67
+ const headers = new Headers({ "Content-Type": "application/json" });
68
+ applyServerContext(headers, context);
69
+ try {
70
+ const response = await fetchImpl(endpoint, {
71
+ method: "POST",
72
+ headers,
73
+ body: JSON.stringify(body),
74
+ keepalive: false
75
+ });
76
+ if (!response.ok) {
77
+ return {
78
+ ok: false,
79
+ error: `Ingest request failed with status ${response.status}`
80
+ };
81
+ }
82
+ return await response.json();
83
+ } catch {
84
+ return {
85
+ ok: false,
86
+ error: "Ingest request failed"
87
+ };
88
+ }
89
+ }
90
+ function createServerClient(config) {
91
+ const resolved = assertClientConfig(config);
92
+ return {
93
+ page(input) {
94
+ return send(
95
+ resolved,
96
+ {
97
+ type: "pageview",
98
+ url: input.url,
99
+ referrer: input.referrer,
100
+ title: input.title,
101
+ hostname: input.hostname
102
+ },
103
+ {
104
+ url: input.url,
105
+ referrer: input.referrer
106
+ }
107
+ );
108
+ },
109
+ track(name, properties, context) {
110
+ return send(
111
+ resolved,
112
+ {
113
+ type: "track",
114
+ name,
115
+ properties: properties ?? {},
116
+ url: context?.url
117
+ },
118
+ context
119
+ );
120
+ },
121
+ identify(userId, traits, context) {
122
+ return send(
123
+ resolved,
124
+ {
125
+ type: "identify",
126
+ userId,
127
+ traits: traits ?? {}
128
+ },
129
+ context
130
+ );
131
+ }
132
+ };
133
+ }
134
+
135
+ exports.createServerClient = createServerClient;
136
+ //# sourceMappingURL=index.cjs.map
137
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/config.ts","../../src/server/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export const DEFAULT_INGEST_URL = \"https://ingest.dumbledor.com\";\n\nexport const UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n","import { DEFAULT_INGEST_URL, UUID_PATTERN } from \"./constants\";\nimport type {\n CollectPayload,\n CollectPayloadInput,\n DumbledorClientConfig,\n ResolvedDumbledorConfig,\n} from \"./types\";\n\nexport class DumbledorConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DumbledorConfigError\";\n }\n}\n\nexport function normalizeIngestUrl(value?: string): string {\n const raw = value?.trim() || DEFAULT_INGEST_URL;\n return raw.replace(/\\/$/, \"\");\n}\n\nexport function assertClientConfig(config: DumbledorClientConfig): ResolvedDumbledorConfig {\n const websiteId = config.websiteId?.trim();\n\n if (!websiteId) {\n throw new DumbledorConfigError(\"websiteId is required.\");\n }\n\n if (!UUID_PATTERN.test(websiteId)) {\n throw new DumbledorConfigError(\"websiteId must be a valid UUID.\");\n }\n\n return {\n websiteId,\n ingestUrl: normalizeIngestUrl(config.ingestUrl),\n honorDoNotTrack: config.honorDoNotTrack ?? false,\n disabled: config.disabled ?? false,\n fetch: config.fetch,\n };\n}\n\nexport function withWebsiteId(\n payload: CollectPayloadInput,\n websiteId: string,\n): CollectPayload {\n return {\n ...payload,\n websiteId,\n };\n}\n\nexport function resolveFetch(fetchImpl?: typeof fetch): typeof fetch {\n const resolved = fetchImpl ?? globalThis.fetch;\n if (!resolved) {\n throw new DumbledorConfigError(\"fetch is not available in this runtime.\");\n }\n\n return resolved;\n}\n","import { assertClientConfig, resolveFetch, withWebsiteId } from \"../config\";\nimport type {\n CollectPayloadInput,\n CollectResponse,\n DumbledorServerConfig,\n ServerContext,\n} from \"../types\";\n\nexport type DumbledorServer = {\n page: (input: { url: string; referrer?: string; title?: string; hostname?: string }) => Promise<CollectResponse | void>;\n track: (\n name: string,\n properties?: Record<string, unknown>,\n context?: ServerContext,\n ) => Promise<CollectResponse | void>;\n identify: (\n userId: string,\n traits?: Record<string, unknown>,\n context?: ServerContext,\n ) => Promise<CollectResponse | void>;\n};\n\nfunction applyServerContext(headers: Headers, context?: ServerContext) {\n if (context?.userAgent) {\n headers.set(\"User-Agent\", context.userAgent);\n }\n if (context?.ip) {\n headers.set(\"X-Forwarded-For\", context.ip);\n }\n if (context?.referrer) {\n headers.set(\"Referer\", context.referrer);\n }\n}\n\nasync function send(\n config: ReturnType<typeof assertClientConfig>,\n payload: CollectPayloadInput,\n context?: ServerContext,\n): Promise<CollectResponse | void> {\n if (config.disabled) {\n return;\n }\n\n const fetchImpl = resolveFetch(config.fetch);\n const endpoint = `${config.ingestUrl}/v1/collect`;\n const body = withWebsiteId(payload, config.websiteId);\n const headers = new Headers({ \"Content-Type\": \"application/json\" });\n applyServerContext(headers, context);\n\n try {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n keepalive: false,\n });\n\n if (!response.ok) {\n return {\n ok: false,\n error: `Ingest request failed with status ${response.status}`,\n };\n }\n\n return (await response.json()) as CollectResponse;\n } catch {\n return {\n ok: false,\n error: \"Ingest request failed\",\n };\n }\n}\n\nexport function createServerClient(config: DumbledorServerConfig): DumbledorServer {\n const resolved = assertClientConfig(config);\n\n return {\n page(input) {\n return send(\n resolved,\n {\n type: \"pageview\",\n url: input.url,\n referrer: input.referrer,\n title: input.title,\n hostname: input.hostname,\n },\n {\n url: input.url,\n referrer: input.referrer,\n },\n );\n },\n track(name, properties, context) {\n return send(\n resolved,\n {\n type: \"track\",\n name,\n properties: properties ?? {},\n url: context?.url,\n },\n context,\n );\n },\n identify(userId, traits, context) {\n return send(\n resolved,\n {\n type: \"identify\",\n userId,\n traits: traits ?? {},\n },\n context,\n );\n },\n };\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { b as CollectResponse, S as ServerContext, e as DumbledorServerConfig } from '../types-DWa2kNUy.cjs';
2
+
3
+ type DumbledorServer = {
4
+ page: (input: {
5
+ url: string;
6
+ referrer?: string;
7
+ title?: string;
8
+ hostname?: string;
9
+ }) => Promise<CollectResponse | void>;
10
+ track: (name: string, properties?: Record<string, unknown>, context?: ServerContext) => Promise<CollectResponse | void>;
11
+ identify: (userId: string, traits?: Record<string, unknown>, context?: ServerContext) => Promise<CollectResponse | void>;
12
+ };
13
+ declare function createServerClient(config: DumbledorServerConfig): DumbledorServer;
14
+
15
+ export { type DumbledorServer, createServerClient };
@@ -0,0 +1,15 @@
1
+ import { b as CollectResponse, S as ServerContext, e as DumbledorServerConfig } from '../types-DWa2kNUy.js';
2
+
3
+ type DumbledorServer = {
4
+ page: (input: {
5
+ url: string;
6
+ referrer?: string;
7
+ title?: string;
8
+ hostname?: string;
9
+ }) => Promise<CollectResponse | void>;
10
+ track: (name: string, properties?: Record<string, unknown>, context?: ServerContext) => Promise<CollectResponse | void>;
11
+ identify: (userId: string, traits?: Record<string, unknown>, context?: ServerContext) => Promise<CollectResponse | void>;
12
+ };
13
+ declare function createServerClient(config: DumbledorServerConfig): DumbledorServer;
14
+
15
+ export { type DumbledorServer, createServerClient };
@@ -0,0 +1,135 @@
1
+ // src/constants.ts
2
+ var DEFAULT_INGEST_URL = "https://ingest.dumbledor.com";
3
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
+
5
+ // src/config.ts
6
+ var DumbledorConfigError = class extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "DumbledorConfigError";
10
+ }
11
+ };
12
+ function normalizeIngestUrl(value) {
13
+ const raw = value?.trim() || DEFAULT_INGEST_URL;
14
+ return raw.replace(/\/$/, "");
15
+ }
16
+ function assertClientConfig(config) {
17
+ const websiteId = config.websiteId?.trim();
18
+ if (!websiteId) {
19
+ throw new DumbledorConfigError("websiteId is required.");
20
+ }
21
+ if (!UUID_PATTERN.test(websiteId)) {
22
+ throw new DumbledorConfigError("websiteId must be a valid UUID.");
23
+ }
24
+ return {
25
+ websiteId,
26
+ ingestUrl: normalizeIngestUrl(config.ingestUrl),
27
+ honorDoNotTrack: config.honorDoNotTrack ?? false,
28
+ disabled: config.disabled ?? false,
29
+ fetch: config.fetch
30
+ };
31
+ }
32
+ function withWebsiteId(payload, websiteId) {
33
+ return {
34
+ ...payload,
35
+ websiteId
36
+ };
37
+ }
38
+ function resolveFetch(fetchImpl) {
39
+ const resolved = fetchImpl ?? globalThis.fetch;
40
+ if (!resolved) {
41
+ throw new DumbledorConfigError("fetch is not available in this runtime.");
42
+ }
43
+ return resolved;
44
+ }
45
+
46
+ // src/server/index.ts
47
+ function applyServerContext(headers, context) {
48
+ if (context?.userAgent) {
49
+ headers.set("User-Agent", context.userAgent);
50
+ }
51
+ if (context?.ip) {
52
+ headers.set("X-Forwarded-For", context.ip);
53
+ }
54
+ if (context?.referrer) {
55
+ headers.set("Referer", context.referrer);
56
+ }
57
+ }
58
+ async function send(config, payload, context) {
59
+ if (config.disabled) {
60
+ return;
61
+ }
62
+ const fetchImpl = resolveFetch(config.fetch);
63
+ const endpoint = `${config.ingestUrl}/v1/collect`;
64
+ const body = withWebsiteId(payload, config.websiteId);
65
+ const headers = new Headers({ "Content-Type": "application/json" });
66
+ applyServerContext(headers, context);
67
+ try {
68
+ const response = await fetchImpl(endpoint, {
69
+ method: "POST",
70
+ headers,
71
+ body: JSON.stringify(body),
72
+ keepalive: false
73
+ });
74
+ if (!response.ok) {
75
+ return {
76
+ ok: false,
77
+ error: `Ingest request failed with status ${response.status}`
78
+ };
79
+ }
80
+ return await response.json();
81
+ } catch {
82
+ return {
83
+ ok: false,
84
+ error: "Ingest request failed"
85
+ };
86
+ }
87
+ }
88
+ function createServerClient(config) {
89
+ const resolved = assertClientConfig(config);
90
+ return {
91
+ page(input) {
92
+ return send(
93
+ resolved,
94
+ {
95
+ type: "pageview",
96
+ url: input.url,
97
+ referrer: input.referrer,
98
+ title: input.title,
99
+ hostname: input.hostname
100
+ },
101
+ {
102
+ url: input.url,
103
+ referrer: input.referrer
104
+ }
105
+ );
106
+ },
107
+ track(name, properties, context) {
108
+ return send(
109
+ resolved,
110
+ {
111
+ type: "track",
112
+ name,
113
+ properties: properties ?? {},
114
+ url: context?.url
115
+ },
116
+ context
117
+ );
118
+ },
119
+ identify(userId, traits, context) {
120
+ return send(
121
+ resolved,
122
+ {
123
+ type: "identify",
124
+ userId,
125
+ traits: traits ?? {}
126
+ },
127
+ context
128
+ );
129
+ }
130
+ };
131
+ }
132
+
133
+ export { createServerClient };
134
+ //# sourceMappingURL=index.js.map
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants.ts","../../src/config.ts","../../src/server/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export const DEFAULT_INGEST_URL = \"https://ingest.dumbledor.com\";\n\nexport const UUID_PATTERN =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n","import { DEFAULT_INGEST_URL, UUID_PATTERN } from \"./constants\";\nimport type {\n CollectPayload,\n CollectPayloadInput,\n DumbledorClientConfig,\n ResolvedDumbledorConfig,\n} from \"./types\";\n\nexport class DumbledorConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DumbledorConfigError\";\n }\n}\n\nexport function normalizeIngestUrl(value?: string): string {\n const raw = value?.trim() || DEFAULT_INGEST_URL;\n return raw.replace(/\\/$/, \"\");\n}\n\nexport function assertClientConfig(config: DumbledorClientConfig): ResolvedDumbledorConfig {\n const websiteId = config.websiteId?.trim();\n\n if (!websiteId) {\n throw new DumbledorConfigError(\"websiteId is required.\");\n }\n\n if (!UUID_PATTERN.test(websiteId)) {\n throw new DumbledorConfigError(\"websiteId must be a valid UUID.\");\n }\n\n return {\n websiteId,\n ingestUrl: normalizeIngestUrl(config.ingestUrl),\n honorDoNotTrack: config.honorDoNotTrack ?? false,\n disabled: config.disabled ?? false,\n fetch: config.fetch,\n };\n}\n\nexport function withWebsiteId(\n payload: CollectPayloadInput,\n websiteId: string,\n): CollectPayload {\n return {\n ...payload,\n websiteId,\n };\n}\n\nexport function resolveFetch(fetchImpl?: typeof fetch): typeof fetch {\n const resolved = fetchImpl ?? globalThis.fetch;\n if (!resolved) {\n throw new DumbledorConfigError(\"fetch is not available in this runtime.\");\n }\n\n return resolved;\n}\n","import { assertClientConfig, resolveFetch, withWebsiteId } from \"../config\";\nimport type {\n CollectPayloadInput,\n CollectResponse,\n DumbledorServerConfig,\n ServerContext,\n} from \"../types\";\n\nexport type DumbledorServer = {\n page: (input: { url: string; referrer?: string; title?: string; hostname?: string }) => Promise<CollectResponse | void>;\n track: (\n name: string,\n properties?: Record<string, unknown>,\n context?: ServerContext,\n ) => Promise<CollectResponse | void>;\n identify: (\n userId: string,\n traits?: Record<string, unknown>,\n context?: ServerContext,\n ) => Promise<CollectResponse | void>;\n};\n\nfunction applyServerContext(headers: Headers, context?: ServerContext) {\n if (context?.userAgent) {\n headers.set(\"User-Agent\", context.userAgent);\n }\n if (context?.ip) {\n headers.set(\"X-Forwarded-For\", context.ip);\n }\n if (context?.referrer) {\n headers.set(\"Referer\", context.referrer);\n }\n}\n\nasync function send(\n config: ReturnType<typeof assertClientConfig>,\n payload: CollectPayloadInput,\n context?: ServerContext,\n): Promise<CollectResponse | void> {\n if (config.disabled) {\n return;\n }\n\n const fetchImpl = resolveFetch(config.fetch);\n const endpoint = `${config.ingestUrl}/v1/collect`;\n const body = withWebsiteId(payload, config.websiteId);\n const headers = new Headers({ \"Content-Type\": \"application/json\" });\n applyServerContext(headers, context);\n\n try {\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n keepalive: false,\n });\n\n if (!response.ok) {\n return {\n ok: false,\n error: `Ingest request failed with status ${response.status}`,\n };\n }\n\n return (await response.json()) as CollectResponse;\n } catch {\n return {\n ok: false,\n error: \"Ingest request failed\",\n };\n }\n}\n\nexport function createServerClient(config: DumbledorServerConfig): DumbledorServer {\n const resolved = assertClientConfig(config);\n\n return {\n page(input) {\n return send(\n resolved,\n {\n type: \"pageview\",\n url: input.url,\n referrer: input.referrer,\n title: input.title,\n hostname: input.hostname,\n },\n {\n url: input.url,\n referrer: input.referrer,\n },\n );\n },\n track(name, properties, context) {\n return send(\n resolved,\n {\n type: \"track\",\n name,\n properties: properties ?? {},\n url: context?.url,\n },\n context,\n );\n },\n identify(userId, traits, context) {\n return send(\n resolved,\n {\n type: \"identify\",\n userId,\n traits: traits ?? {},\n },\n context,\n );\n },\n };\n}\n"]}
@@ -0,0 +1,68 @@
1
+ type CollectEventType = "pageview" | "track" | "identify";
2
+ type PageviewPayload = {
3
+ type: "pageview";
4
+ websiteId: string;
5
+ url: string;
6
+ referrer?: string;
7
+ title?: string;
8
+ hostname?: string;
9
+ language?: string;
10
+ screen?: string;
11
+ };
12
+ type TrackPayload = {
13
+ type: "track";
14
+ websiteId: string;
15
+ name: string;
16
+ properties?: Record<string, unknown>;
17
+ url?: string;
18
+ hostname?: string;
19
+ };
20
+ type IdentifyPayload = {
21
+ type: "identify";
22
+ websiteId: string;
23
+ userId: string;
24
+ traits?: Record<string, unknown>;
25
+ };
26
+ type CollectPayload = PageviewPayload | TrackPayload | IdentifyPayload;
27
+ type CollectPayloadInput = Omit<PageviewPayload, "websiteId"> | Omit<TrackPayload, "websiteId"> | Omit<IdentifyPayload, "websiteId">;
28
+ type DumbledorClientConfig = {
29
+ /** Project website ID from tracking settings. */
30
+ websiteId: string;
31
+ /** Ingest API origin. Defaults to `https://ingest.dumbledor.com`. */
32
+ ingestUrl?: string;
33
+ /** Skip all tracking when the browser sends Do Not Track. Default: `false`. */
34
+ honorDoNotTrack?: boolean;
35
+ /** Disable tracking entirely. Useful for local development. */
36
+ disabled?: boolean;
37
+ /** Custom fetch implementation (testing, edge runtimes). */
38
+ fetch?: typeof fetch;
39
+ };
40
+ type DumbledorServerConfig = DumbledorClientConfig;
41
+ type DumbledorClient = {
42
+ readonly config: Readonly<ResolvedDumbledorConfig>;
43
+ page: (input?: PageviewInput) => void;
44
+ track: (name: string, properties?: Record<string, unknown>) => void;
45
+ identify: (userId: string, traits?: Record<string, unknown>) => void;
46
+ };
47
+ type ResolvedDumbledorConfig = Required<Pick<DumbledorClientConfig, "websiteId" | "ingestUrl" | "honorDoNotTrack" | "disabled">> & Pick<DumbledorClientConfig, "fetch">;
48
+ type PageviewInput = {
49
+ url?: string;
50
+ referrer?: string;
51
+ title?: string;
52
+ hostname?: string;
53
+ language?: string;
54
+ screen?: string;
55
+ };
56
+ type ServerContext = {
57
+ url?: string;
58
+ referrer?: string;
59
+ userAgent?: string;
60
+ ip?: string;
61
+ };
62
+ type CollectResponse = {
63
+ ok: boolean;
64
+ skipped?: string;
65
+ error?: string;
66
+ };
67
+
68
+ export type { CollectPayloadInput as C, DumbledorClientConfig as D, IdentifyPayload as I, PageviewInput as P, ResolvedDumbledorConfig as R, ServerContext as S, TrackPayload as T, DumbledorClient as a, CollectResponse as b, CollectPayload as c, CollectEventType as d, DumbledorServerConfig as e, PageviewPayload as f };
@@ -0,0 +1,68 @@
1
+ type CollectEventType = "pageview" | "track" | "identify";
2
+ type PageviewPayload = {
3
+ type: "pageview";
4
+ websiteId: string;
5
+ url: string;
6
+ referrer?: string;
7
+ title?: string;
8
+ hostname?: string;
9
+ language?: string;
10
+ screen?: string;
11
+ };
12
+ type TrackPayload = {
13
+ type: "track";
14
+ websiteId: string;
15
+ name: string;
16
+ properties?: Record<string, unknown>;
17
+ url?: string;
18
+ hostname?: string;
19
+ };
20
+ type IdentifyPayload = {
21
+ type: "identify";
22
+ websiteId: string;
23
+ userId: string;
24
+ traits?: Record<string, unknown>;
25
+ };
26
+ type CollectPayload = PageviewPayload | TrackPayload | IdentifyPayload;
27
+ type CollectPayloadInput = Omit<PageviewPayload, "websiteId"> | Omit<TrackPayload, "websiteId"> | Omit<IdentifyPayload, "websiteId">;
28
+ type DumbledorClientConfig = {
29
+ /** Project website ID from tracking settings. */
30
+ websiteId: string;
31
+ /** Ingest API origin. Defaults to `https://ingest.dumbledor.com`. */
32
+ ingestUrl?: string;
33
+ /** Skip all tracking when the browser sends Do Not Track. Default: `false`. */
34
+ honorDoNotTrack?: boolean;
35
+ /** Disable tracking entirely. Useful for local development. */
36
+ disabled?: boolean;
37
+ /** Custom fetch implementation (testing, edge runtimes). */
38
+ fetch?: typeof fetch;
39
+ };
40
+ type DumbledorServerConfig = DumbledorClientConfig;
41
+ type DumbledorClient = {
42
+ readonly config: Readonly<ResolvedDumbledorConfig>;
43
+ page: (input?: PageviewInput) => void;
44
+ track: (name: string, properties?: Record<string, unknown>) => void;
45
+ identify: (userId: string, traits?: Record<string, unknown>) => void;
46
+ };
47
+ type ResolvedDumbledorConfig = Required<Pick<DumbledorClientConfig, "websiteId" | "ingestUrl" | "honorDoNotTrack" | "disabled">> & Pick<DumbledorClientConfig, "fetch">;
48
+ type PageviewInput = {
49
+ url?: string;
50
+ referrer?: string;
51
+ title?: string;
52
+ hostname?: string;
53
+ language?: string;
54
+ screen?: string;
55
+ };
56
+ type ServerContext = {
57
+ url?: string;
58
+ referrer?: string;
59
+ userAgent?: string;
60
+ ip?: string;
61
+ };
62
+ type CollectResponse = {
63
+ ok: boolean;
64
+ skipped?: string;
65
+ error?: string;
66
+ };
67
+
68
+ export type { CollectPayloadInput as C, DumbledorClientConfig as D, IdentifyPayload as I, PageviewInput as P, ResolvedDumbledorConfig as R, ServerContext as S, TrackPayload as T, DumbledorClient as a, CollectResponse as b, CollectPayload as c, CollectEventType as d, DumbledorServerConfig as e, PageviewPayload as f };
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@dumbledor/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official Dumbledor analytics SDK for browser, React, Next.js, and server runtimes.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js",
16
+ "require": "./dist/index.cjs"
17
+ },
18
+ "./react": {
19
+ "types": "./dist/react/index.d.ts",
20
+ "import": "./dist/react/index.js",
21
+ "require": "./dist/react/index.cjs"
22
+ },
23
+ "./next": {
24
+ "types": "./dist/next/index.d.ts",
25
+ "import": "./dist/next/index.js",
26
+ "require": "./dist/next/index.cjs"
27
+ },
28
+ "./server": {
29
+ "types": "./dist/server/index.d.ts",
30
+ "import": "./dist/server/index.js",
31
+ "require": "./dist/server/index.cjs"
32
+ }
33
+ },
34
+ "main": "./dist/index.cjs",
35
+ "module": "./dist/index.js",
36
+ "types": "./dist/index.d.ts",
37
+ "peerDependencies": {
38
+ "next": ">=13.0.0",
39
+ "react": ">=18.0.0"
40
+ },
41
+ "peerDependenciesMeta": {
42
+ "next": {
43
+ "optional": true
44
+ },
45
+ "react": {
46
+ "optional": true
47
+ }
48
+ },
49
+ "devDependencies": {
50
+ "@types/react": "^19.2.7",
51
+ "next": "^16.1.6",
52
+ "react": "^19.2.3",
53
+ "tsup": "^8.5.0",
54
+ "typescript": "^5.9.3",
55
+ "vitest": "^4.0.16"
56
+ },
57
+ "engines": {
58
+ "node": ">=18"
59
+ },
60
+ "keywords": [
61
+ "analytics",
62
+ "dumbledor",
63
+ "privacy",
64
+ "tracking",
65
+ "nextjs",
66
+ "react"
67
+ ],
68
+ "repository": {
69
+ "type": "git",
70
+ "url": "https://github.com/dumbledor/dumbledor.com",
71
+ "directory": "sdk"
72
+ },
73
+ "publishConfig": {
74
+ "access": "public"
75
+ },
76
+ "scripts": {
77
+ "build": "tsup && node scripts/stamp-client.mjs",
78
+ "dev": "tsup --watch",
79
+ "test": "vitest run",
80
+ "test:watch": "vitest",
81
+ "typecheck": "tsc --noEmit"
82
+ }
83
+ }