@drivemetadata-ai/sdk 0.1.1-beta.2 → 0.1.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.
@@ -0,0 +1,109 @@
1
+ # SDK Architecture
2
+
3
+ This package separates shared SDK logic from runtime-specific integrations.
4
+
5
+ ## Runtime Entry Points
6
+
7
+ | Entry point | Runtime | Responsibility |
8
+ |---|---|---|
9
+ | `@drivemetadata-ai/sdk/browser` | Browser | Client-side analytics, identity, consent, persistence, delivery, autocapture |
10
+ | `@drivemetadata-ai/sdk/react` | React | Provider and hooks over the browser SDK |
11
+ | `@drivemetadata-ai/sdk/next` | Next.js | Client-safe provider and route tracking helpers over the browser SDK |
12
+ | `@drivemetadata-ai/sdk/angular` | Angular | Provider and injectable service over the browser SDK |
13
+ | `@drivemetadata-ai/sdk/node` | Node.js | Server-side event delivery without browser APIs |
14
+
15
+ ## Source Layout
16
+
17
+ ```text
18
+ src/core/
19
+ backend-payload.ts
20
+ backend-schema.ts
21
+ consent.ts
22
+ diagnostics.ts
23
+ environment.ts
24
+ event-schema.ts
25
+ types.ts
26
+ uuid.ts
27
+
28
+ src/browser/
29
+ client.ts
30
+ core/
31
+ DriveMetaDataSDK.ts
32
+ attribution.ts
33
+ autocapture.ts
34
+ browser-config.ts
35
+ delivery.ts
36
+ identity.ts
37
+ persistence.ts
38
+ privacy.ts
39
+ session.ts
40
+ types.ts
41
+
42
+ src/react/
43
+ src/next/
44
+ src/angular/
45
+ src/node/
46
+ ```
47
+
48
+ ## What Belongs In `src/core`
49
+
50
+ Use `src/core` for runtime-neutral code only:
51
+
52
+ - Backend collector payload construction.
53
+ - Backend payload validation.
54
+ - Consent normalization and purpose checks.
55
+ - Event envelope validation.
56
+ - Shared diagnostics and public types.
57
+ - UUID helpers.
58
+
59
+ Code in `src/core` must not depend on `window`, `document`, browser storage, cookies, `navigator`, DOM events, framework APIs, or Node-only APIs.
60
+
61
+ ## What Belongs In `src/browser/core`
62
+
63
+ Use `src/browser/core` for browser-specific behavior:
64
+
65
+ - Browser identity and session persistence.
66
+ - Cookie/localStorage/sessionStorage/memory persistence.
67
+ - Attribution and UTM capture from URLs and cookies.
68
+ - Autocapture, history listeners, page lifecycle, and page leave.
69
+ - Browser delivery, Beacon/fetch transport, queueing, and retry.
70
+ - Browser privacy redaction behavior.
71
+ - Browser config normalization.
72
+
73
+ ## Framework Adapter Rule
74
+
75
+ React, Next.js, and Angular adapters must stay thin. They should call the browser SDK and should not reimplement:
76
+
77
+ - Consent logic.
78
+ - Identity/session logic.
79
+ - Delivery queues.
80
+ - Payload construction.
81
+ - Attribution extraction.
82
+ - Privacy filtering.
83
+
84
+ Expected flow:
85
+
86
+ ```text
87
+ React / Next / Angular
88
+ -> src/browser/client.ts
89
+ -> src/browser/core/DriveMetaDataSDK.ts
90
+ -> src/browser/core/*
91
+ -> src/core/*
92
+ ```
93
+
94
+ ## Node Boundary Rule
95
+
96
+ The Node entry point must remain server-only and must not import browser runtime modules. It can use `src/core` for payload construction, backend payload validation, UUIDs, and shared types.
97
+
98
+ Expected flow:
99
+
100
+ ```text
101
+ src/node/*
102
+ -> src/core/backend-payload.ts
103
+ -> src/core/backend-schema.ts
104
+ -> src/core/uuid.ts
105
+ ```
106
+
107
+ ## Release Rule
108
+
109
+ Before publishing, runtime boundaries should be verified by package and SSR import tests. Browser-only modules must not be imported by the Node entry point.
package/docs/index.md CHANGED
@@ -13,5 +13,6 @@
13
13
 
14
14
  ## Enterprise Readiness
15
15
 
16
+ - [Architecture](./architecture.md)
16
17
  - [Security and Privacy](./security-privacy.md)
17
18
  - [Release Checklist](./release-checklist.md)
@@ -33,12 +33,72 @@ const browserConfig = {
33
33
  };
34
34
  ```
35
35
 
36
+ Use camelCase config keys in npm integrations. The backend payload still sends the backend-required `metaData` shape, but SDK configuration should use `clientId`, `workspaceId`, `appId`, `apiHost`, `capturePageview`, `capturePageleave`, `schemaValidation`, and `beforeSend`.
37
+
36
38
  Node integrations use a server token stored only in backend environment variables:
37
39
 
38
40
  ```bash
39
41
  DMD_SERVER_TOKEN=server_write_key
40
42
  ```
41
43
 
44
+ ### Environment Variables
45
+
46
+ For React/Vite-style browser builds, expose only public browser values:
47
+
48
+ ```bash
49
+ VITE_DMD_CLIENT_ID=client_xxx
50
+ VITE_DMD_WORKSPACE_ID=workspace_xxx
51
+ VITE_DMD_APP_ID=app_xxx
52
+ VITE_DMD_TOKEN=public_token
53
+ ```
54
+
55
+ For Next.js browser integrations, use `NEXT_PUBLIC_` only for public browser values:
56
+
57
+ ```bash
58
+ NEXT_PUBLIC_DMD_CLIENT_ID=client_xxx
59
+ NEXT_PUBLIC_DMD_WORKSPACE_ID=workspace_xxx
60
+ NEXT_PUBLIC_DMD_APP_ID=app_xxx
61
+ NEXT_PUBLIC_DMD_TOKEN=public_token
62
+ ```
63
+
64
+ For Node/server integrations, do not use `NEXT_PUBLIC_`:
65
+
66
+ ```bash
67
+ DMD_SERVER_TOKEN=server_write_key
68
+ ```
69
+
70
+ ## Common Browser Config
71
+
72
+ ```ts
73
+ const config = {
74
+ clientId: 'client_xxx',
75
+ workspaceId: 'workspace_xxx',
76
+ appId: 'app_xxx',
77
+ token: 'public_token',
78
+ apiHost: 'https://sdk.drivemetadata.com/v2',
79
+ consent: { analytics: 'pending', advertising: 'denied' },
80
+ capturePageview: true,
81
+ capturePageleave: true,
82
+ schemaValidation: 'warn',
83
+ delivery: {
84
+ retryDelayMs: 1000,
85
+ maxRetryDelayMs: 30000,
86
+ maxQueueSize: 100,
87
+ useBeacon: true
88
+ }
89
+ };
90
+ ```
91
+
92
+ Recommended defaults:
93
+
94
+ | Option | Recommended | Notes |
95
+ |---|---|---|
96
+ | `consent.analytics` | `pending` | Grant after consent manager resolves |
97
+ | `capturePageview` | `true` | Sends automatic page views in browser runtimes |
98
+ | `capturePageleave` | `true` | Sends lifecycle/page leave events |
99
+ | `schemaValidation` | `warn` | Use `strict` in controlled integrations after testing |
100
+ | `delivery.useBeacon` | `true` | Helps lifecycle delivery during navigation |
101
+
42
102
  ## Consent First Setup
43
103
 
44
104
  Enterprise integrations should start with analytics consent as `pending` or `denied`, then grant it after the consent manager resolves.
@@ -92,6 +152,68 @@ await flush();
92
152
  console.log(getDmdHealth());
93
153
  ```
94
154
 
155
+ Browser API methods:
156
+
157
+ | Method | Use for |
158
+ |---|---|
159
+ | `track(event, properties, options)` | custom events |
160
+ | `page(name, properties, options)` | page views |
161
+ | `identify(userId, traits, options)` | known users |
162
+ | `group(groupId, traits, options)` | accounts/workspaces/organizations |
163
+ | `alias(previousId, userId, options)` | identity merge |
164
+ | `flush()` | force queued delivery |
165
+ | `reset()` | reset identity/session state |
166
+ | `setConsent(consent)` / `consent.update(consent)` | consent updates |
167
+ | `getDmdHealth()` | diagnostics |
168
+
169
+ ## Backend Timestamp Fields
170
+
171
+ Every collector request sends these timestamp fields inside `metaData`:
172
+
173
+ | Field | Format | Notes |
174
+ |---|---|---|
175
+ | `timestamp` | `YYYY-MM-DD HH:mm:ss` | UTC, no milliseconds, no timezone suffix |
176
+ | `requestSentAt` | `YYYY-MM-DD HH:mm:ss` | UTC, no milliseconds, no timezone suffix |
177
+ | `requestReceivedAt` | `YYYY-MM-DD HH:mm:ss` | UTC, no milliseconds, no timezone suffix |
178
+
179
+ If a caller provides an invalid timestamp string, the SDK normalizes it to the current UTC time instead of sending the invalid value to the backend.
180
+
181
+ ## Default Page Context
182
+
183
+ Browser, React, Next.js, and Angular integrations automatically add browser page context to each collector payload.
184
+
185
+ ```json
186
+ {
187
+ "metaData": {
188
+ "page": {
189
+ "url": "https://example.com/products/sku-123?utm_source=paid",
190
+ "path": "/products/sku-123",
191
+ "search": "?utm_source=paid",
192
+ "title": "Product Detail",
193
+ "referrer": "https://google.com/search?q=%5BREDACTED%5D"
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ The SDK reads this from `window.location`, `document.title`, and `document.referrer`. Query strings in `search` and `referrer` are redacted by default except common attribution parameters such as `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, and `utm_content`.
200
+
201
+ Node/server integrations do not automatically capture page context. Pass page details explicitly in server event properties if needed.
202
+
203
+ ## Default Identity, Session, Attribution
204
+
205
+ Browser, React, Next.js, and Angular integrations also add these fields by default:
206
+
207
+ | Field | Behavior |
208
+ |---|---|
209
+ | `requestId` | generated UUID for each event |
210
+ | `anonymousId` | persisted anonymous browser visitor ID |
211
+ | `sessionId` | persisted browser session ID with idle timeout |
212
+ | `attributionData` | stored click IDs and `_fbc`/`_fbp` when available |
213
+ | `utmParameter` | stored UTM values when available |
214
+
215
+ The SDK stores anonymous/session/attribution state using the configured browser persistence mode and falls back safely when browser storage is blocked.
216
+
95
217
  ## React Integration
96
218
 
97
219
  Wrap the app once:
@@ -116,6 +238,18 @@ export function App() {
116
238
  }
117
239
  ```
118
240
 
241
+ For Vite React apps, the provider config commonly comes from `import.meta.env`:
242
+
243
+ ```tsx
244
+ const config = {
245
+ clientId: import.meta.env.VITE_DMD_CLIENT_ID,
246
+ workspaceId: import.meta.env.VITE_DMD_WORKSPACE_ID,
247
+ appId: import.meta.env.VITE_DMD_APP_ID,
248
+ token: import.meta.env.VITE_DMD_TOKEN,
249
+ consent: { analytics: 'pending' as const, advertising: 'denied' as const }
250
+ };
251
+ ```
252
+
119
253
  Track from components:
120
254
 
121
255
  ```tsx
@@ -128,7 +262,7 @@ export function CheckoutButton() {
128
262
  const consent = useDmdConsent();
129
263
 
130
264
  async function onClick() {
131
- consent.update({ analytics: 'granted' });
265
+ consent({ analytics: 'granted' });
132
266
  identify('user_123', { plan: 'enterprise' });
133
267
  track('Checkout Started', { source: 'cart' });
134
268
  await flush();
@@ -147,13 +281,18 @@ Create a client provider:
147
281
 
148
282
  import { DmdProvider, useDmdAppRouterPageTracking } from '@drivemetadata-ai/sdk/next';
149
283
  import { usePathname, useSearchParams } from 'next/navigation';
284
+ import type { ReactNode } from 'react';
150
285
 
151
- export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
286
+ function RouteTracking() {
152
287
  const pathname = usePathname();
153
288
  const searchParams = useSearchParams();
154
289
 
155
290
  useDmdAppRouterPageTracking(pathname, searchParams.toString());
156
291
 
292
+ return null;
293
+ }
294
+
295
+ export function AnalyticsProvider({ children }: { children: ReactNode }) {
157
296
  return (
158
297
  <DmdProvider
159
298
  config={{
@@ -164,6 +303,7 @@ export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
164
303
  consent: { analytics: 'pending', advertising: 'denied' }
165
304
  }}
166
305
  >
306
+ <RouteTracking />
167
307
  {children}
168
308
  </DmdProvider>
169
309
  );
@@ -174,8 +314,9 @@ Use it from `app/layout.tsx`:
174
314
 
175
315
  ```tsx
176
316
  import { AnalyticsProvider } from './AnalyticsProvider';
317
+ import type { ReactNode } from 'react';
177
318
 
178
- export default function RootLayout({ children }: { children: React.ReactNode }) {
319
+ export default function RootLayout({ children }: { children: ReactNode }) {
179
320
  return (
180
321
  <html lang="en">
181
322
  <body>
@@ -193,6 +334,14 @@ import type { AppProps } from 'next/app';
193
334
  import { useRouter } from 'next/router';
194
335
  import { DmdProvider, useDmdPagesRouterPageTracking } from '@drivemetadata-ai/sdk/next';
195
336
 
337
+ const config = {
338
+ clientId: process.env.NEXT_PUBLIC_DMD_CLIENT_ID!,
339
+ workspaceId: process.env.NEXT_PUBLIC_DMD_WORKSPACE_ID!,
340
+ appId: process.env.NEXT_PUBLIC_DMD_APP_ID!,
341
+ token: process.env.NEXT_PUBLIC_DMD_TOKEN!,
342
+ consent: { analytics: 'pending' as const, advertising: 'denied' as const }
343
+ };
344
+
196
345
  function PageTracking() {
197
346
  const router = useRouter();
198
347
  useDmdPagesRouterPageTracking(router);
@@ -230,10 +379,24 @@ export const appConfig: ApplicationConfig = {
230
379
  };
231
380
  ```
232
381
 
382
+ For Angular environment files, keep only the public browser token in frontend config:
383
+
384
+ ```ts
385
+ export const environment = {
386
+ dmd: {
387
+ clientId: 'client_xxx',
388
+ workspaceId: 'workspace_xxx',
389
+ appId: 'app_xxx',
390
+ token: 'public_token'
391
+ }
392
+ };
393
+ ```
394
+
233
395
  Initialize and track:
234
396
 
235
397
  ```ts
236
- import { Component, OnInit } from '@angular/core';
398
+ import { isPlatformBrowser } from '@angular/common';
399
+ import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
237
400
  import { DmdAnalyticsService } from '@drivemetadata-ai/sdk/angular';
238
401
 
239
402
  @Component({
@@ -241,10 +404,16 @@ import { DmdAnalyticsService } from '@drivemetadata-ai/sdk/angular';
241
404
  template: '<router-outlet />'
242
405
  })
243
406
  export class AppComponent implements OnInit {
244
- constructor(private readonly dmd: DmdAnalyticsService) {}
407
+ constructor(
408
+ private readonly dmd: DmdAnalyticsService,
409
+ @Inject(PLATFORM_ID) private readonly platformId: object
410
+ ) {}
245
411
 
246
412
  ngOnInit() {
413
+ if (!isPlatformBrowser(this.platformId)) return;
414
+
247
415
  this.dmd.init();
416
+ this.dmd.setConsent({ analytics: 'granted' });
248
417
  this.dmd.track('App Loaded');
249
418
  }
250
419
  }
@@ -284,6 +453,21 @@ await dmd.track({
284
453
 
285
454
  Use `messageId` for a unique event ID and `idempotencyKey` for business events that may retry, such as orders, invoices, subscriptions, and webhooks.
286
455
 
456
+ Server-side page context is explicit:
457
+
458
+ ```ts
459
+ await dmd.page({
460
+ userId: 'user_123',
461
+ properties: {
462
+ page: {
463
+ url: 'https://example.com/orders/123',
464
+ path: '/orders/123',
465
+ title: 'Order Detail'
466
+ }
467
+ }
468
+ });
469
+ ```
470
+
287
471
  ## Diagnostics
288
472
 
289
473
  Use health diagnostics when events do not appear:
@@ -304,6 +488,20 @@ Common drop reasons:
304
488
  | `queue_limit_exceeded` | Offline queue is full | Flush more often or increase queue size |
305
489
  | `queue_ttl_expired` | Event stayed offline past TTL | Increase TTL only if business requirements allow it |
306
490
 
491
+ ## Backend Payload IDs
492
+
493
+ The SDK sends `requestId`, `anonymousId`, and `sessionId` as UUID strings in `metaData`.
494
+
495
+ ```json
496
+ {
497
+ "metaData": {
498
+ "requestId": "019de75e-dd28-779b-8084-2b3091b6de32",
499
+ "anonymousId": "019c8fe4-1fc7-7898-8089-f951a8bbfefb",
500
+ "sessionId": "019de75e-db3f-75e2-8054-0167411aa4ba"
501
+ }
502
+ }
503
+ ```
504
+
307
505
  ## Production Checklist
308
506
 
309
507
  - Use `@drivemetadata-ai/sdk/browser`, `/react`, `/next`, or `/angular` only for public browser tokens.
@@ -137,3 +137,7 @@ Common drop reasons:
137
137
  Delivery behavior:
138
138
 
139
139
  The browser SDK adds message IDs and idempotency keys to delivery payloads. Queue diagnostics include expired records, and queue flushing uses a cross-tab lease to avoid duplicate ownership when multiple tabs are open.
140
+
141
+ `metaData.requestId`, `metaData.anonymousId`, and `metaData.sessionId` are UUID strings.
142
+
143
+ `metaData.timestamp`, `metaData.requestSentAt`, and `metaData.requestReceivedAt` use UTC `YYYY-MM-DD HH:mm:ss` format. Invalid timestamp strings are normalized to the current UTC time before sending.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@drivemetadata-ai/sdk",
3
- "version": "0.1.1-beta.2",
4
- "description": "Enterprise-grade DriveMetaData browser SDK.",
3
+ "version": "0.1.1",
4
+ "description": "DriveMetaData JavaScript and TypeScript SDK.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "publishConfig": {
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "docs/index.md",
13
+ "docs/architecture.md",
13
14
  "docs/integration.md",
14
15
  "docs/npm-browser-sdk.md",
15
16
  "docs/react-next-integration.md",
@@ -58,6 +59,7 @@
58
59
  "test": "vitest run",
59
60
  "test:package": "npm run build && vitest run tests/package/package-contents.test.ts tests/package/package-install-smoke.test.ts",
60
61
  "pack:check": "npm run build && npm pack --dry-run",
62
+ "release:version": "node scripts/release-version.mjs",
61
63
  "release:check": "npm run ci && npm_config_cache=/tmp/npm-cache npm run pack:check",
62
64
  "ci": "npm run typecheck && npm run test && npm run build && npm run test:package",
63
65
  "ci:phase1": "npm run typecheck && npm run test && npm run build"