@01.software/sdk 0.17.0 → 0.19.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/README.md +29 -14
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/{const-D2K5HxpP.d.cts → const-B9oeZoDy.d.ts} +3 -3
- package/dist/{const-DG8TrouX.d.ts → const-CkhnGqnb.d.cts} +3 -3
- package/dist/index.cjs +96 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -52
- package/dist/index.d.ts +27 -52
- package/dist/index.js +96 -79
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-CMoyAOjJ.d.cts → payload-types-DfQct8Dj.d.cts} +927 -195
- package/dist/{payload-types-CMoyAOjJ.d.ts → payload-types-DfQct8Dj.d.ts} +927 -195
- package/dist/realtime.cjs.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/realtime.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook-CJq7v5Da.d.cts +61 -0
- package/dist/webhook-CKqXQInG.d.ts +61 -0
- package/dist/webhook.cjs +53 -4
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +3 -3
- package/dist/webhook.d.ts +3 -3
- package/dist/webhook.js +53 -4
- package/dist/webhook.js.map +1 -1
- package/package.json +3 -3
- package/dist/webhook-Dm2zz7FQ.d.cts +0 -20
- package/dist/webhook-IhuUWnt5.d.ts +0 -20
package/README.md
CHANGED
|
@@ -113,7 +113,6 @@ const client = createClient({
|
|
|
113
113
|
const server = createServerClient({
|
|
114
114
|
publishableKey: string,
|
|
115
115
|
secretKey: string, // sk01_... or pat01_...
|
|
116
|
-
tenantId?: string, // for PAT workflows, must match the pinned tenant when supplied
|
|
117
116
|
})
|
|
118
117
|
```
|
|
119
118
|
|
|
@@ -121,7 +120,6 @@ const server = createServerClient({
|
|
|
121
120
|
| ---------------- | -------- | ---------------------------- |
|
|
122
121
|
| `publishableKey` | `string` | API publishable key |
|
|
123
122
|
| `secretKey` | `string` | API secret key (server only) |
|
|
124
|
-
| `tenantId` | `string` | Optional generally; required when a PAT must disambiguate multi-tenant access. Must match the pinned tenant when supplied. |
|
|
125
123
|
|
|
126
124
|
API URL은 환경변수로 오버라이드 가능합니다:
|
|
127
125
|
|
|
@@ -307,7 +305,6 @@ login({ email: 'user@example.com', password: 'password' })
|
|
|
307
305
|
// Other customer mutations
|
|
308
306
|
client.query.useCustomerForgotPassword()
|
|
309
307
|
client.query.useCustomerResetPassword()
|
|
310
|
-
client.query.useCustomerVerifyEmail()
|
|
311
308
|
client.query.useCustomerChangePassword()
|
|
312
309
|
|
|
313
310
|
// Customer cache utilities
|
|
@@ -349,11 +346,10 @@ const orders = await client.commerce.orders.listMine({
|
|
|
349
346
|
status: 'paid',
|
|
350
347
|
})
|
|
351
348
|
|
|
352
|
-
// Password
|
|
349
|
+
// Password
|
|
353
350
|
await client.customer.auth.forgotPassword('john@example.com')
|
|
354
351
|
await client.customer.auth.resetPassword(token, newPassword)
|
|
355
352
|
await client.customer.auth.changePassword(currentPassword, newPassword)
|
|
356
|
-
await client.customer.auth.verifyEmail(verificationToken)
|
|
357
353
|
```
|
|
358
354
|
|
|
359
355
|
### Commerce Orders (ServerClient-only writes)
|
|
@@ -440,7 +436,11 @@ await server.community.moderation.unbanCustomer({ customerId })
|
|
|
440
436
|
### Webhook
|
|
441
437
|
|
|
442
438
|
```typescript
|
|
443
|
-
import {
|
|
439
|
+
import {
|
|
440
|
+
handleWebhook,
|
|
441
|
+
createCustomerAuthWebhookHandler,
|
|
442
|
+
createTypedWebhookHandler,
|
|
443
|
+
} from '@01.software/sdk'
|
|
444
444
|
|
|
445
445
|
// Basic handler
|
|
446
446
|
export async function POST(request: Request) {
|
|
@@ -456,16 +456,27 @@ export async function POST(request: Request) {
|
|
|
456
456
|
})
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
// Signed deliveries include x-webhook-signature, x-webhook-timestamp,
|
|
460
|
+
// and x-webhook-delivery-id. handleWebhook rejects stale or unsigned
|
|
461
|
+
// deliveries when secret is set.
|
|
462
|
+
|
|
459
463
|
// Type-safe handler
|
|
460
464
|
const handler = createTypedWebhookHandler('orders', async (event) => {
|
|
461
465
|
// event.data is typed as Order
|
|
462
466
|
console.log(event.data.orderNumber)
|
|
463
467
|
})
|
|
468
|
+
|
|
469
|
+
// Customer auth helper
|
|
470
|
+
const customerAuthHandler = createCustomerAuthWebhookHandler({
|
|
471
|
+
passwordReset: async ({ email, resetPasswordToken }) => {
|
|
472
|
+
await sendPasswordResetEmail(email, resetPasswordToken)
|
|
473
|
+
},
|
|
474
|
+
})
|
|
464
475
|
```
|
|
465
476
|
|
|
466
477
|
## Supported Collections
|
|
467
478
|
|
|
468
|
-
Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`:
|
|
479
|
+
Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 74).
|
|
469
480
|
|
|
470
481
|
| Category | Collections |
|
|
471
482
|
| --- | --- |
|
|
@@ -475,7 +486,7 @@ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 70)
|
|
|
475
486
|
| Customers | `customers`, `customer-profiles`, `customer-addresses`, `customer-groups` |
|
|
476
487
|
| Carts | `carts`, `cart-items` |
|
|
477
488
|
| Commerce | `discounts`, `promotions`, `shipping-policies` |
|
|
478
|
-
| Content | `documents`, `document-categories`, `document-types`, `
|
|
489
|
+
| Content | `documents`, `document-categories`, `document-types`, `articles`, `article-authors`, `article-categories`, `article-tags`, `links`, `link-categories`, `link-tags` |
|
|
479
490
|
| Playlists / Tracks | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
|
|
480
491
|
| Galleries | `galleries`, `gallery-categories`, `gallery-tags`, `gallery-items` |
|
|
481
492
|
| Canvas | `canvases`, `canvas-node-types`, `canvas-edge-types`, `canvas-categories`, `canvas-tags`, `canvas-nodes`, `canvas-edges` |
|
|
@@ -483,7 +494,8 @@ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 70)
|
|
|
483
494
|
| Live Streams | `live-streams` |
|
|
484
495
|
| Media | `images` |
|
|
485
496
|
| Forms | `forms`, `form-submissions` |
|
|
486
|
-
| Community | `
|
|
497
|
+
| Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories`, `reports`, `community-bans` |
|
|
498
|
+
| Events | `event-calendars`, `events`, `event-occurrences`, `event-tags` |
|
|
487
499
|
|
|
488
500
|
## Utilities
|
|
489
501
|
|
|
@@ -537,7 +549,7 @@ import { RichTextContent, StyledRichTextContent } from '@01.software/sdk/ui/rich
|
|
|
537
549
|
<RichTextContent
|
|
538
550
|
data={content}
|
|
539
551
|
className="prose"
|
|
540
|
-
internalDocToHref={({ linkNode }) => `/
|
|
552
|
+
internalDocToHref={({ linkNode }) => `/articles/${linkNode.fields.doc?.value?.slug}`}
|
|
541
553
|
blocks={{
|
|
542
554
|
Iframe: ({ node }) => <iframe src={node.fields.url} />,
|
|
543
555
|
Player: ({ node }) => <VideoPlayer url={node.fields.url} />,
|
|
@@ -591,6 +603,10 @@ SOFTWARE_SECRET_KEY=sk01_... # Server only — opaque API key from Console
|
|
|
591
603
|
|
|
592
604
|
> `secretKey` should only be used in server environments. Never expose it to the browser.
|
|
593
605
|
|
|
606
|
+
> `secretKey` may be an `sk01_...` API key or a `pat01_...` personal access token. Server SDK calls must also send the matching `publishableKey`; PAT tenant selection is pinned server-side, and callers must not send `X-Tenant-Id`.
|
|
607
|
+
|
|
608
|
+
API keys created without explicit scopes keep the compatibility default `['read', 'write']`. Console API-key creation can request narrower `scopes[]`; in the first explicit-scope slice tenant admins may grant only `read` and `write`. Higher-authority scopes such as `webhook`, `analytics`, and `super-admin` require platform authority and otherwise return `scope_grant_forbidden`.
|
|
609
|
+
|
|
594
610
|
> **SDK 0.9.0**: Server auth now uses opaque bearer tokens (`sk01_...`). Generate API keys from the Console. `createServerToken`, `createApiKey`, and `parseApiKey` are no longer part of the SDK surface.
|
|
595
611
|
|
|
596
612
|
## Migration Guide
|
|
@@ -602,7 +618,7 @@ New error codes propagated via `SDKError.code` (no breaking change; existing cal
|
|
|
602
618
|
| Code | Phase | Trigger |
|
|
603
619
|
|---|---|---|
|
|
604
620
|
| `account_suspended` | P1 | Suspended session / `sk01_` / `pat01_` / customer JWT — 401 |
|
|
605
|
-
| `pat_tenant_header_forbidden` | P1 | `pat01_` request
|
|
621
|
+
| `pat_tenant_header_forbidden` | P1 | `pat01_` request carrying any `X-Tenant-Id` header — 401 |
|
|
606
622
|
| `tenant_mismatch` | P3 | Cross-tenant FK rejection (forms / community / orders) |
|
|
607
623
|
| `server_derived` | P3 | Body-driven write into a server-derived state field — 422 |
|
|
608
624
|
| `scope_denied` | P5 | `pat01_` whose `ApiKeys.scopes` lacks the operation |
|
|
@@ -618,7 +634,6 @@ P5 also adds JWT-`jti` revocation: `revokeCustomerJti(jti, ttl)` on the server i
|
|
|
618
634
|
| Collection | Old | New |
|
|
619
635
|
|---|---|---|
|
|
620
636
|
| Customers | `socialId` | `providerUserId` |
|
|
621
|
-
| Customers | `isVerified` | `isEmailVerified` |
|
|
622
637
|
| Customers | `loginAttempts` | `loginAttemptCount` |
|
|
623
638
|
| Customers | `resetPasswordExpiry` | `resetPasswordExpiresAt` |
|
|
624
639
|
| Orders, Carts | `shippingFee` | `shippingAmount` |
|
|
@@ -633,7 +648,7 @@ P5 also adds JWT-`jti` revocation: `revokeCustomerJti(jti, ttl)` on the server i
|
|
|
633
648
|
| ShippingPolicies | `freeShippingThreshold` | `freeShippingMinAmount` |
|
|
634
649
|
| Documents | `effectiveDate` | `effectiveAt` |
|
|
635
650
|
| Documents | `expiryDate` | `expiresAt` |
|
|
636
|
-
|
|
|
651
|
+
| Articles | `readTime` | `readingMinutes` |
|
|
637
652
|
| ApiUsage | `count` | `apiCallCount` |
|
|
638
653
|
| ApiUsage | `storageUsed` | `storageUsedBytes` |
|
|
639
654
|
| ApiUsage | `totalDocuments` | `documentCount` |
|
|
@@ -645,4 +660,4 @@ P5 also adds JWT-`jti` revocation: `revokeCustomerJti(jti, ttl)` on the server i
|
|
|
645
660
|
- Added: `product-option-values`
|
|
646
661
|
|
|
647
662
|
**Boolean field renames** (6 collections):
|
|
648
|
-
- `status: 'active' | 'inactive'` → `isActive: boolean` on Forms,
|
|
663
|
+
- `status: 'active' | 'inactive'` → `isActive: boolean` on Forms, ArticleAuthors, CustomerGroups, ShippingPolicies, ProductVariants
|
package/dist/analytics.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analytics.ts","../src/core/client/types.ts"],"sourcesContent":["/**\n * @01.software/sdk — Analytics Helper\n */\n\n/* ANALYTICS INVARIANTS START\n * @01.software/sdk — Analytics Helper\n *\n * ANALYTICS INVARIANTS\n * ====================\n * These invariants are the single source of truth for observable behavior.\n * They are mirrored verbatim in apps/console/src/app/api/analytics/script.js/route.ts.\n * Any change here MUST be reflected there, and vice versa.\n *\n * 1. DNT/GPC respect: when config.respectDnt !== false (default true) AND\n * (navigator.doNotTrack === '1' OR navigator.globalPrivacyControl === true),\n * all methods become no-ops. Zero network requests are made.\n *\n * 2. Prerender skip: when document.prerendering === true OR\n * document.visibilityState === 'prerender', pageview() sends zero requests.\n *\n * 3. 500ms same-path dedup: a pageview for the same pathname within 500ms of\n * the previous send is silently dropped. After 500ms the next call sends.\n *\n * 4. Transport: sendBeacon → fetch keepalive fallback.\n * Primary: navigator.sendBeacon(endpoint, new Blob([json], { type: 'text/plain' })).\n * Fallback (sendBeacon unavailable OR returns false):\n * fetch(endpoint, { method: 'POST', keepalive: true,\n * headers: { 'Content-Type': 'application/json' }, body: json }).catch(() => {})\n *\n * 5. Body-only publishableKey: publishableKey is always in the request body,\n * never in any HTTP header.\n *\n * 6. SSR no-op: when typeof window === 'undefined', createAnalytics() returns\n * a stub where all methods are no-ops. No side effects occur.\n *\n * 7. Error swallowing: all transport errors are caught and swallowed.\n * createAnalytics() and all returned methods never throw into the caller.\n *\n * 8. Client timestamp: every send carries eventTs (milliseconds since epoch)\n * captured with Date.now() immediately before transport. The collect\n * endpoint uses eventTs (a) to bucket the event into the client's\n * tenant-local day and (b) to enforce the late-arrival cutoff; events\n * submitted after the local-day-end grace window are dropped with\n * reason \"late\".\n * ANALYTICS INVARIANTS END */\n\nimport { resolveApiUrl } from './core/client/types'\n\n// ============================================================================\n// Public Types\n// ============================================================================\n\nexport interface AnalyticsConfig {\n publishableKey: string\n /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */\n endpoint?: string\n /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */\n autoTrack?: boolean\n /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */\n respectDnt?: boolean\n}\n\nexport interface Analytics {\n pageview(path?: string): void\n track(name: string, props?: Record<string, string | number | boolean>): void\n destroy(): void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createAnalytics(config: AnalyticsConfig): Analytics {\n // INVARIANT 6: SSR no-op\n if (typeof window === 'undefined') {\n return { pageview() {}, track() {}, destroy() {} }\n }\n\n const endpoint =\n config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`\n\n // INVARIANT 1: DNT/GPC check (evaluated once at init; stays as closure)\n const respectDnt = config.respectDnt !== false\n function isDntActive(): boolean {\n if (!respectDnt) return false\n const nav = navigator as Navigator & { globalPrivacyControl?: boolean }\n return nav.doNotTrack === '1' || nav.globalPrivacyControl === true\n }\n\n // INVARIANT 3: 500ms same-path dedup state\n let lastPath: string | null = null\n let lastAt = 0\n\n // autoTrack state — save originals for destroy()\n const autoTrack = config.autoTrack !== false\n const originalPushState = history.pushState\n const originalReplaceState = history.replaceState\n let destroyed = false\n\n // -------------------------------------------------------------------------\n // Core send logic\n // -------------------------------------------------------------------------\n\n // Generate a unique event ID (crypto.randomUUID when available, Date+Math.random fallback)\n function newEventId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : String(Date.now()) + String(Math.random())\n }\n\n // INVARIANT 4: sendBeacon → fetch keepalive fallback\n // INVARIANT 5: publishableKey in body only\n function sendBeaconOrFetch(body: string): void {\n try {\n if (typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'text/plain' })\n const sent = navigator.sendBeacon(endpoint, blob)\n if (sent) return\n // sent === false → fall through to fetch\n }\n // Fetch fallback\n fetch(endpoint, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json' },\n body,\n }).catch(() => {})\n } catch {\n // INVARIANT 7: swallow all errors\n }\n }\n\n function sendPageview(pathname: string): void {\n // INVARIANT 1: DNT/GPC\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n // visibilityState cast to string to accommodate non-standard 'prerender' value\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // INVARIANT 3: 500ms same-path dedup\n const now = Date.now()\n if (pathname === lastPath && now - lastAt < 500) return\n lastPath = pathname\n lastAt = now\n\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n }\n\n // -------------------------------------------------------------------------\n // autoTrack: patch history methods + listen to popstate\n // -------------------------------------------------------------------------\n function trackCurrentPath(): void {\n if (destroyed) return\n sendPageview(location.pathname)\n }\n\n function patchedPushState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalPushState.apply(this, [data, unused, url] as Parameters<typeof history.pushState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n function patchedReplaceState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalReplaceState.apply(this, [data, unused, url] as Parameters<typeof history.replaceState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n if (autoTrack) {\n history.pushState = patchedPushState\n history.replaceState = patchedReplaceState\n window.addEventListener('popstate', trackCurrentPath)\n\n // Initial pageview\n if (document.readyState === 'complete') {\n trackCurrentPath()\n } else {\n window.addEventListener('load', trackCurrentPath, { once: true })\n }\n }\n\n // -------------------------------------------------------------------------\n // track() — client-side validation + send\n // -------------------------------------------------------------------------\n\n // Dev-mode detection: warn in dev, silent in production.\n // process.env.NODE_ENV is unreliable in browser bundles (tsup does not replace it\n // by default). Instead we detect production at runtime via hostname heuristics.\n // SSR (window undefined) is caught at the top of createAnalytics and returns a\n // stub, so window is always defined here.\n const isProduction: boolean = (() => {\n try {\n const hostname = location.hostname\n return (\n hostname !== 'localhost' &&\n hostname !== '127.0.0.1' &&\n !hostname.endsWith('.local')\n )\n } catch {\n // hostname access failed (non-browser) — default to silent\n return true\n }\n })()\n\n // One-shot warn dedup per reason per page load (keyed by reason only)\n const warnedReasons = new Set<string>()\n\n function devWarn(name: string, reason: string): void {\n if (isProduction) return\n if (warnedReasons.has(reason)) return\n warnedReasons.add(reason)\n console.warn(`[01 analytics] dropped event ${name}: ${reason}`)\n }\n\n const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/\n const RESERVED_PREFIXES = ['__', '_pv_']\n\n function validateEventName(name: string): string | null {\n if (!name || typeof name !== 'string') return 'name-empty'\n for (const prefix of RESERVED_PREFIXES) {\n if (name.startsWith(prefix)) return 'name-reserved'\n }\n if (!EVENT_NAME_RE.test(name)) return 'name-regex'\n return null\n }\n\n const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/\n\n function validateEventProps(\n props: Record<string, string | number | boolean> | undefined,\n ): string | null {\n if (props === undefined || props === null) return null\n if (typeof props !== 'object' || Array.isArray(props)) return 'props-value-type'\n const keys = Object.keys(props)\n if (keys.length > 10) return 'props-too-many-keys'\n for (const k of keys) {\n const v = props[k]\n if (!PROP_KEY_RE.test(k)) return 'props-key-regex'\n if (typeof v === 'string') {\n if (v.length > 80) return 'props-value-too-long'\n } else if (typeof v === 'number') {\n if (!isFinite(v)) return 'props-value-not-finite'\n } else if (typeof v === 'boolean') {\n // ok\n } else {\n return 'props-value-type'\n }\n }\n return null\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n return {\n pageview(path?: string): void {\n if (destroyed) return\n sendPageview(path ?? location.pathname)\n },\n\n track(name: string, props?: Record<string, string | number | boolean>): void {\n if (destroyed) return\n\n // INVARIANT 1: DNT/GPC (same as pageview)\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // Client-side validation\n const nameErr = validateEventName(name)\n if (nameErr) {\n devWarn(name, nameErr)\n return\n }\n\n if (props !== undefined) {\n const propsErr = validateEventProps(props)\n if (propsErr) {\n devWarn(name, propsErr)\n return\n }\n }\n\n // Build body — no dedup for track() events\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname: location.pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventName: name,\n eventProps: props,\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n if (autoTrack) {\n // Restore original history methods\n history.pushState = originalPushState\n history.replaceState = originalReplaceState\n window.removeEventListener('popstate', trackCurrentPath)\n }\n\n // Null out dedup state\n lastPath = null\n lastAt = 0\n },\n }\n}\n","import type { Sort, Where } from 'payload'\n\nimport type { Collection, PublicCollection } from '../collection/const'\n\nexport type { Collection, PublicCollection }\n\n// ============================================================================\n// API URL Configuration\n// ============================================================================\n\ndeclare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(): string {\n if (typeof process !== 'undefined' && process.env) {\n const envUrl =\n process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL\n if (envUrl) {\n return envUrl.replace(/\\/$/, '')\n }\n }\n return __DEFAULT_API_URL__\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n publishableKey: string\n /**\n * Customer authentication options.\n * Used to initialize CustomerAuth on Client.\n */\n customer?: {\n /**\n * Persist token in localStorage. Defaults to `true`.\n * - `true` (default): uses key `'customer-token'`\n * - `string`: uses the given string as localStorage key\n * - `false`: disables persistence (token/onTokenChange used instead)\n *\n * Handles SSR safely (no-op on server).\n * When enabled, `token` and `onTokenChange` are ignored.\n */\n persist?: boolean | string\n /** Initial token (e.g. from SSR cookie) */\n token?: string\n /** Called when token changes (login/logout) — use to persist in localStorage/cookie */\n onTokenChange?: (token: string | null) => void\n }\n}\n\n// Server client: requires both publishableKey (for CDN routing + rate limit +\n// monthly quota enforcement via the edge proxy) and secretKey (sk01_ opaque\n// bearer token, the authentication credential).\n// The proxy keys its tenant lookup off `X-Publishable-Key`, so omitting\n// publishableKey would silently bypass rate limiting and plan-based quota\n// enforcement.\nexport interface ClientServerConfig extends ClientConfig {\n secretKey: string\n /**\n * Tenant ID for pat01_ API keys. When provided alongside a pat01_ key,\n * every server request includes X-Tenant-Id so the API can scope the\n * request to the correct tenant. Not needed for sk01_ keys (tenant is\n * encoded in the key itself).\n */\n tenantId?: string\n}\n\n\nexport interface ClientMetadata {\n userAgent?: string\n timestamp: number\n}\n\nexport interface ClientState {\n metadata: ClientMetadata\n}\n\nexport interface PaginationMeta {\n page: number\n limit: number\n totalDocs: number\n totalPages: number\n hasNextPage: boolean\n hasPrevPage: boolean\n pagingCounter: number\n prevPage: number | null\n nextPage: number | null\n}\n\n// ============================================================================\n// Payload CMS Native Response Types\n// ============================================================================\n\n/**\n * Payload CMS Find (List) Response\n * GET /api/{collection}\n */\nexport interface PayloadFindResponse<T = unknown> {\n docs: T[]\n totalDocs: number\n limit: number\n totalPages: number\n page: number\n pagingCounter: number\n hasPrevPage: boolean\n hasNextPage: boolean\n prevPage: number | null\n nextPage: number | null\n}\n\n/**\n * Payload CMS Create/Update Response\n * POST /api/{collection}\n * PATCH /api/{collection}/{id}\n */\nexport interface PayloadMutationResponse<T = unknown> {\n message: string\n doc: T\n errors?: unknown[]\n}\n\n// ============================================================================\n// Query Options\n// ============================================================================\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload`. Payload's generic\n * types (`JoinQuery<TSlug>`, `PopulateType`) depend on `PayloadTypes` module\n * augmentation; external SDK consumers who skip that get degenerate types\n * (`never` / `{}`). Only non-generic `Sort`/`Where` are safe to import.\n * Excluded vs native: Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n where?: Where\n depth?: number\n select?: Record<string, boolean>\n /** Per-collection field selection for populated relationships (keyed by collection slug) */\n populate?: Record<string, boolean | Record<string, boolean>>\n /** Join field control: pagination/filter per join, or false to disable */\n joins?:\n | Record<\n string,\n | {\n limit?: number\n page?: number\n sort?: string\n where?: Where\n count?: boolean\n }\n | false\n >\n | false\n /** Set to `false` to skip the count query — returns docs without totalDocs/totalPages */\n pagination?: boolean\n /** Include draft versions (access control still applies on the server) */\n draft?: boolean\n /** Include soft-deleted documents (requires `trash` enabled on the collection) */\n trash?: boolean\n}\n\n// ============================================================================\n// Debug & Retry Configuration\n// ============================================================================\n\nexport interface DebugConfig {\n logRequests?: boolean\n logResponses?: boolean\n logErrors?: boolean\n}\n\nexport interface RetryConfig {\n maxRetries?: number\n retryableStatuses?: number[]\n retryDelay?: (attempt: number) => number\n}\n\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n\nexport type ExtractArrayType<T> = T extends (infer U)[] ? U : never\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/analytics.ts","../src/core/client/types.ts"],"sourcesContent":["/**\n * @01.software/sdk — Analytics Helper\n */\n\n/* ANALYTICS INVARIANTS START\n * @01.software/sdk — Analytics Helper\n *\n * ANALYTICS INVARIANTS\n * ====================\n * These invariants are the single source of truth for observable behavior.\n * They are mirrored verbatim in apps/console/src/app/api/analytics/script.js/route.ts.\n * Any change here MUST be reflected there, and vice versa.\n *\n * 1. DNT/GPC respect: when config.respectDnt !== false (default true) AND\n * (navigator.doNotTrack === '1' OR navigator.globalPrivacyControl === true),\n * all methods become no-ops. Zero network requests are made.\n *\n * 2. Prerender skip: when document.prerendering === true OR\n * document.visibilityState === 'prerender', pageview() sends zero requests.\n *\n * 3. 500ms same-path dedup: a pageview for the same pathname within 500ms of\n * the previous send is silently dropped. After 500ms the next call sends.\n *\n * 4. Transport: sendBeacon → fetch keepalive fallback.\n * Primary: navigator.sendBeacon(endpoint, new Blob([json], { type: 'text/plain' })).\n * Fallback (sendBeacon unavailable OR returns false):\n * fetch(endpoint, { method: 'POST', keepalive: true,\n * headers: { 'Content-Type': 'application/json' }, body: json }).catch(() => {})\n *\n * 5. Body-only publishableKey: publishableKey is always in the request body,\n * never in any HTTP header.\n *\n * 6. SSR no-op: when typeof window === 'undefined', createAnalytics() returns\n * a stub where all methods are no-ops. No side effects occur.\n *\n * 7. Error swallowing: all transport errors are caught and swallowed.\n * createAnalytics() and all returned methods never throw into the caller.\n *\n * 8. Client timestamp: every send carries eventTs (milliseconds since epoch)\n * captured with Date.now() immediately before transport. The collect\n * endpoint uses eventTs (a) to bucket the event into the client's\n * tenant-local day and (b) to enforce the late-arrival cutoff; events\n * submitted after the local-day-end grace window are dropped with\n * reason \"late\".\n * ANALYTICS INVARIANTS END */\n\nimport { resolveApiUrl } from './core/client/types'\n\n// ============================================================================\n// Public Types\n// ============================================================================\n\nexport interface AnalyticsConfig {\n publishableKey: string\n /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */\n endpoint?: string\n /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */\n autoTrack?: boolean\n /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */\n respectDnt?: boolean\n}\n\nexport interface Analytics {\n pageview(path?: string): void\n track(name: string, props?: Record<string, string | number | boolean>): void\n destroy(): void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createAnalytics(config: AnalyticsConfig): Analytics {\n // INVARIANT 6: SSR no-op\n if (typeof window === 'undefined') {\n return { pageview() {}, track() {}, destroy() {} }\n }\n\n const endpoint =\n config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`\n\n // INVARIANT 1: DNT/GPC check (evaluated once at init; stays as closure)\n const respectDnt = config.respectDnt !== false\n function isDntActive(): boolean {\n if (!respectDnt) return false\n const nav = navigator as Navigator & { globalPrivacyControl?: boolean }\n return nav.doNotTrack === '1' || nav.globalPrivacyControl === true\n }\n\n // INVARIANT 3: 500ms same-path dedup state\n let lastPath: string | null = null\n let lastAt = 0\n\n // autoTrack state — save originals for destroy()\n const autoTrack = config.autoTrack !== false\n const originalPushState = history.pushState\n const originalReplaceState = history.replaceState\n let destroyed = false\n\n // -------------------------------------------------------------------------\n // Core send logic\n // -------------------------------------------------------------------------\n\n // Generate a unique event ID (crypto.randomUUID when available, Date+Math.random fallback)\n function newEventId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : String(Date.now()) + String(Math.random())\n }\n\n // INVARIANT 4: sendBeacon → fetch keepalive fallback\n // INVARIANT 5: publishableKey in body only\n function sendBeaconOrFetch(body: string): void {\n try {\n if (typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'text/plain' })\n const sent = navigator.sendBeacon(endpoint, blob)\n if (sent) return\n // sent === false → fall through to fetch\n }\n // Fetch fallback\n fetch(endpoint, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json' },\n body,\n }).catch(() => {})\n } catch {\n // INVARIANT 7: swallow all errors\n }\n }\n\n function sendPageview(pathname: string): void {\n // INVARIANT 1: DNT/GPC\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n // visibilityState cast to string to accommodate non-standard 'prerender' value\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // INVARIANT 3: 500ms same-path dedup\n const now = Date.now()\n if (pathname === lastPath && now - lastAt < 500) return\n lastPath = pathname\n lastAt = now\n\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n }\n\n // -------------------------------------------------------------------------\n // autoTrack: patch history methods + listen to popstate\n // -------------------------------------------------------------------------\n function trackCurrentPath(): void {\n if (destroyed) return\n sendPageview(location.pathname)\n }\n\n function patchedPushState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalPushState.apply(this, [data, unused, url] as Parameters<typeof history.pushState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n function patchedReplaceState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalReplaceState.apply(this, [data, unused, url] as Parameters<typeof history.replaceState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n if (autoTrack) {\n history.pushState = patchedPushState\n history.replaceState = patchedReplaceState\n window.addEventListener('popstate', trackCurrentPath)\n\n // Initial pageview\n if (document.readyState === 'complete') {\n trackCurrentPath()\n } else {\n window.addEventListener('load', trackCurrentPath, { once: true })\n }\n }\n\n // -------------------------------------------------------------------------\n // track() — client-side validation + send\n // -------------------------------------------------------------------------\n\n // Dev-mode detection: warn in dev, silent in production.\n // process.env.NODE_ENV is unreliable in browser bundles (tsup does not replace it\n // by default). Instead we detect production at runtime via hostname heuristics.\n // SSR (window undefined) is caught at the top of createAnalytics and returns a\n // stub, so window is always defined here.\n const isProduction: boolean = (() => {\n try {\n const hostname = location.hostname\n return (\n hostname !== 'localhost' &&\n hostname !== '127.0.0.1' &&\n !hostname.endsWith('.local')\n )\n } catch {\n // hostname access failed (non-browser) — default to silent\n return true\n }\n })()\n\n // One-shot warn dedup per reason per page load (keyed by reason only)\n const warnedReasons = new Set<string>()\n\n function devWarn(name: string, reason: string): void {\n if (isProduction) return\n if (warnedReasons.has(reason)) return\n warnedReasons.add(reason)\n console.warn(`[01 analytics] dropped event ${name}: ${reason}`)\n }\n\n const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/\n const RESERVED_PREFIXES = ['__', '_pv_']\n\n function validateEventName(name: string): string | null {\n if (!name || typeof name !== 'string') return 'name-empty'\n for (const prefix of RESERVED_PREFIXES) {\n if (name.startsWith(prefix)) return 'name-reserved'\n }\n if (!EVENT_NAME_RE.test(name)) return 'name-regex'\n return null\n }\n\n const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/\n\n function validateEventProps(\n props: Record<string, string | number | boolean> | undefined,\n ): string | null {\n if (props === undefined || props === null) return null\n if (typeof props !== 'object' || Array.isArray(props)) return 'props-value-type'\n const keys = Object.keys(props)\n if (keys.length > 10) return 'props-too-many-keys'\n for (const k of keys) {\n const v = props[k]\n if (!PROP_KEY_RE.test(k)) return 'props-key-regex'\n if (typeof v === 'string') {\n if (v.length > 80) return 'props-value-too-long'\n } else if (typeof v === 'number') {\n if (!isFinite(v)) return 'props-value-not-finite'\n } else if (typeof v === 'boolean') {\n // ok\n } else {\n return 'props-value-type'\n }\n }\n return null\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n return {\n pageview(path?: string): void {\n if (destroyed) return\n sendPageview(path ?? location.pathname)\n },\n\n track(name: string, props?: Record<string, string | number | boolean>): void {\n if (destroyed) return\n\n // INVARIANT 1: DNT/GPC (same as pageview)\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // Client-side validation\n const nameErr = validateEventName(name)\n if (nameErr) {\n devWarn(name, nameErr)\n return\n }\n\n if (props !== undefined) {\n const propsErr = validateEventProps(props)\n if (propsErr) {\n devWarn(name, propsErr)\n return\n }\n }\n\n // Build body — no dedup for track() events\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname: location.pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventName: name,\n eventProps: props,\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n if (autoTrack) {\n // Restore original history methods\n history.pushState = originalPushState\n history.replaceState = originalReplaceState\n window.removeEventListener('popstate', trackCurrentPath)\n }\n\n // Null out dedup state\n lastPath = null\n lastAt = 0\n },\n }\n}\n","import type { Sort, Where } from 'payload'\n\nimport type { Collection, PublicCollection } from '../collection/const'\n\nexport type { Collection, PublicCollection }\n\n// ============================================================================\n// API URL Configuration\n// ============================================================================\n\ndeclare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(): string {\n if (typeof process !== 'undefined' && process.env) {\n const envUrl =\n process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL\n if (envUrl) {\n return envUrl.replace(/\\/$/, '')\n }\n }\n return __DEFAULT_API_URL__\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n publishableKey: string\n /**\n * Customer authentication options.\n * Used to initialize CustomerAuth on Client.\n */\n customer?: {\n /**\n * Persist token in localStorage. Defaults to `true`.\n * - `true` (default): uses key `'customer-token'`\n * - `string`: uses the given string as localStorage key\n * - `false`: disables persistence (token/onTokenChange used instead)\n *\n * Handles SSR safely (no-op on server).\n * When enabled, `token` and `onTokenChange` are ignored.\n */\n persist?: boolean | string\n /** Initial token (e.g. from SSR cookie) */\n token?: string\n /** Called when token changes (login/logout) — use to persist in localStorage/cookie */\n onTokenChange?: (token: string | null) => void\n }\n}\n\n// Server client: requires both publishableKey (for CDN routing + rate limit +\n// monthly quota enforcement via the edge proxy) and secretKey (sk01_ opaque\n// bearer token, the authentication credential).\n// The proxy keys its tenant lookup off `X-Publishable-Key`, so omitting\n// publishableKey would silently bypass rate limiting and plan-based quota\n// enforcement.\nexport interface ClientServerConfig extends ClientConfig {\n secretKey: string\n}\n\n\nexport interface ClientMetadata {\n userAgent?: string\n timestamp: number\n}\n\nexport interface ClientState {\n metadata: ClientMetadata\n}\n\nexport interface PaginationMeta {\n page: number\n limit: number\n totalDocs: number\n totalPages: number\n hasNextPage: boolean\n hasPrevPage: boolean\n pagingCounter: number\n prevPage: number | null\n nextPage: number | null\n}\n\n// ============================================================================\n// Payload CMS Native Response Types\n// ============================================================================\n\n/**\n * Payload CMS Find (List) Response\n * GET /api/{collection}\n */\nexport interface PayloadFindResponse<T = unknown> {\n docs: T[]\n totalDocs: number\n limit: number\n totalPages: number\n page: number\n pagingCounter: number\n hasPrevPage: boolean\n hasNextPage: boolean\n prevPage: number | null\n nextPage: number | null\n}\n\n/**\n * Payload CMS Create/Update Response\n * POST /api/{collection}\n * PATCH /api/{collection}/{id}\n */\nexport interface PayloadMutationResponse<T = unknown> {\n message: string\n doc: T\n errors?: unknown[]\n}\n\n// ============================================================================\n// Query Options\n// ============================================================================\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload`. Payload's generic\n * types (`JoinQuery<TSlug>`, `PopulateType`) depend on `PayloadTypes` module\n * augmentation; external SDK consumers who skip that get degenerate types\n * (`never` / `{}`). Only non-generic `Sort`/`Where` are safe to import.\n * Excluded vs native: Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n where?: Where\n depth?: number\n select?: Record<string, boolean>\n /** Per-collection field selection for populated relationships (keyed by collection slug) */\n populate?: Record<string, boolean | Record<string, boolean>>\n /** Join field control: pagination/filter per join, or false to disable */\n joins?:\n | Record<\n string,\n | {\n limit?: number\n page?: number\n sort?: string\n where?: Where\n count?: boolean\n }\n | false\n >\n | false\n /** Set to `false` to skip the count query — returns docs without totalDocs/totalPages */\n pagination?: boolean\n /** Include draft versions (access control still applies on the server) */\n draft?: boolean\n /** Include soft-deleted documents (requires `trash` enabled on the collection) */\n trash?: boolean\n}\n\n// ============================================================================\n// Debug & Retry Configuration\n// ============================================================================\n\nexport interface DebugConfig {\n logRequests?: boolean\n logResponses?: boolean\n logErrors?: boolean\n}\n\nexport interface RetryConfig {\n maxRetries?: number\n retryableStatuses?: number[]\n retryDelay?: (attempt: number) => number\n}\n\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n\nexport type ExtractArrayType<T> = T extends (infer U)[] ? U : never\n"],"mappings":"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","names":[]}
|
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/client/types.ts","../src/analytics.ts"],"sourcesContent":["import type { Sort, Where } from 'payload'\n\nimport type { Collection, PublicCollection } from '../collection/const'\n\nexport type { Collection, PublicCollection }\n\n// ============================================================================\n// API URL Configuration\n// ============================================================================\n\ndeclare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(): string {\n if (typeof process !== 'undefined' && process.env) {\n const envUrl =\n process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL\n if (envUrl) {\n return envUrl.replace(/\\/$/, '')\n }\n }\n return __DEFAULT_API_URL__\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n publishableKey: string\n /**\n * Customer authentication options.\n * Used to initialize CustomerAuth on Client.\n */\n customer?: {\n /**\n * Persist token in localStorage. Defaults to `true`.\n * - `true` (default): uses key `'customer-token'`\n * - `string`: uses the given string as localStorage key\n * - `false`: disables persistence (token/onTokenChange used instead)\n *\n * Handles SSR safely (no-op on server).\n * When enabled, `token` and `onTokenChange` are ignored.\n */\n persist?: boolean | string\n /** Initial token (e.g. from SSR cookie) */\n token?: string\n /** Called when token changes (login/logout) — use to persist in localStorage/cookie */\n onTokenChange?: (token: string | null) => void\n }\n}\n\n// Server client: requires both publishableKey (for CDN routing + rate limit +\n// monthly quota enforcement via the edge proxy) and secretKey (sk01_ opaque\n// bearer token, the authentication credential).\n// The proxy keys its tenant lookup off `X-Publishable-Key`, so omitting\n// publishableKey would silently bypass rate limiting and plan-based quota\n// enforcement.\nexport interface ClientServerConfig extends ClientConfig {\n secretKey: string\n /**\n * Tenant ID for pat01_ API keys. When provided alongside a pat01_ key,\n * every server request includes X-Tenant-Id so the API can scope the\n * request to the correct tenant. Not needed for sk01_ keys (tenant is\n * encoded in the key itself).\n */\n tenantId?: string\n}\n\n\nexport interface ClientMetadata {\n userAgent?: string\n timestamp: number\n}\n\nexport interface ClientState {\n metadata: ClientMetadata\n}\n\nexport interface PaginationMeta {\n page: number\n limit: number\n totalDocs: number\n totalPages: number\n hasNextPage: boolean\n hasPrevPage: boolean\n pagingCounter: number\n prevPage: number | null\n nextPage: number | null\n}\n\n// ============================================================================\n// Payload CMS Native Response Types\n// ============================================================================\n\n/**\n * Payload CMS Find (List) Response\n * GET /api/{collection}\n */\nexport interface PayloadFindResponse<T = unknown> {\n docs: T[]\n totalDocs: number\n limit: number\n totalPages: number\n page: number\n pagingCounter: number\n hasPrevPage: boolean\n hasNextPage: boolean\n prevPage: number | null\n nextPage: number | null\n}\n\n/**\n * Payload CMS Create/Update Response\n * POST /api/{collection}\n * PATCH /api/{collection}/{id}\n */\nexport interface PayloadMutationResponse<T = unknown> {\n message: string\n doc: T\n errors?: unknown[]\n}\n\n// ============================================================================\n// Query Options\n// ============================================================================\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload`. Payload's generic\n * types (`JoinQuery<TSlug>`, `PopulateType`) depend on `PayloadTypes` module\n * augmentation; external SDK consumers who skip that get degenerate types\n * (`never` / `{}`). Only non-generic `Sort`/`Where` are safe to import.\n * Excluded vs native: Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n where?: Where\n depth?: number\n select?: Record<string, boolean>\n /** Per-collection field selection for populated relationships (keyed by collection slug) */\n populate?: Record<string, boolean | Record<string, boolean>>\n /** Join field control: pagination/filter per join, or false to disable */\n joins?:\n | Record<\n string,\n | {\n limit?: number\n page?: number\n sort?: string\n where?: Where\n count?: boolean\n }\n | false\n >\n | false\n /** Set to `false` to skip the count query — returns docs without totalDocs/totalPages */\n pagination?: boolean\n /** Include draft versions (access control still applies on the server) */\n draft?: boolean\n /** Include soft-deleted documents (requires `trash` enabled on the collection) */\n trash?: boolean\n}\n\n// ============================================================================\n// Debug & Retry Configuration\n// ============================================================================\n\nexport interface DebugConfig {\n logRequests?: boolean\n logResponses?: boolean\n logErrors?: boolean\n}\n\nexport interface RetryConfig {\n maxRetries?: number\n retryableStatuses?: number[]\n retryDelay?: (attempt: number) => number\n}\n\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n\nexport type ExtractArrayType<T> = T extends (infer U)[] ? U : never\n","/**\n * @01.software/sdk — Analytics Helper\n */\n\n/* ANALYTICS INVARIANTS START\n * @01.software/sdk — Analytics Helper\n *\n * ANALYTICS INVARIANTS\n * ====================\n * These invariants are the single source of truth for observable behavior.\n * They are mirrored verbatim in apps/console/src/app/api/analytics/script.js/route.ts.\n * Any change here MUST be reflected there, and vice versa.\n *\n * 1. DNT/GPC respect: when config.respectDnt !== false (default true) AND\n * (navigator.doNotTrack === '1' OR navigator.globalPrivacyControl === true),\n * all methods become no-ops. Zero network requests are made.\n *\n * 2. Prerender skip: when document.prerendering === true OR\n * document.visibilityState === 'prerender', pageview() sends zero requests.\n *\n * 3. 500ms same-path dedup: a pageview for the same pathname within 500ms of\n * the previous send is silently dropped. After 500ms the next call sends.\n *\n * 4. Transport: sendBeacon → fetch keepalive fallback.\n * Primary: navigator.sendBeacon(endpoint, new Blob([json], { type: 'text/plain' })).\n * Fallback (sendBeacon unavailable OR returns false):\n * fetch(endpoint, { method: 'POST', keepalive: true,\n * headers: { 'Content-Type': 'application/json' }, body: json }).catch(() => {})\n *\n * 5. Body-only publishableKey: publishableKey is always in the request body,\n * never in any HTTP header.\n *\n * 6. SSR no-op: when typeof window === 'undefined', createAnalytics() returns\n * a stub where all methods are no-ops. No side effects occur.\n *\n * 7. Error swallowing: all transport errors are caught and swallowed.\n * createAnalytics() and all returned methods never throw into the caller.\n *\n * 8. Client timestamp: every send carries eventTs (milliseconds since epoch)\n * captured with Date.now() immediately before transport. The collect\n * endpoint uses eventTs (a) to bucket the event into the client's\n * tenant-local day and (b) to enforce the late-arrival cutoff; events\n * submitted after the local-day-end grace window are dropped with\n * reason \"late\".\n * ANALYTICS INVARIANTS END */\n\nimport { resolveApiUrl } from './core/client/types'\n\n// ============================================================================\n// Public Types\n// ============================================================================\n\nexport interface AnalyticsConfig {\n publishableKey: string\n /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */\n endpoint?: string\n /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */\n autoTrack?: boolean\n /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */\n respectDnt?: boolean\n}\n\nexport interface Analytics {\n pageview(path?: string): void\n track(name: string, props?: Record<string, string | number | boolean>): void\n destroy(): void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createAnalytics(config: AnalyticsConfig): Analytics {\n // INVARIANT 6: SSR no-op\n if (typeof window === 'undefined') {\n return { pageview() {}, track() {}, destroy() {} }\n }\n\n const endpoint =\n config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`\n\n // INVARIANT 1: DNT/GPC check (evaluated once at init; stays as closure)\n const respectDnt = config.respectDnt !== false\n function isDntActive(): boolean {\n if (!respectDnt) return false\n const nav = navigator as Navigator & { globalPrivacyControl?: boolean }\n return nav.doNotTrack === '1' || nav.globalPrivacyControl === true\n }\n\n // INVARIANT 3: 500ms same-path dedup state\n let lastPath: string | null = null\n let lastAt = 0\n\n // autoTrack state — save originals for destroy()\n const autoTrack = config.autoTrack !== false\n const originalPushState = history.pushState\n const originalReplaceState = history.replaceState\n let destroyed = false\n\n // -------------------------------------------------------------------------\n // Core send logic\n // -------------------------------------------------------------------------\n\n // Generate a unique event ID (crypto.randomUUID when available, Date+Math.random fallback)\n function newEventId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : String(Date.now()) + String(Math.random())\n }\n\n // INVARIANT 4: sendBeacon → fetch keepalive fallback\n // INVARIANT 5: publishableKey in body only\n function sendBeaconOrFetch(body: string): void {\n try {\n if (typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'text/plain' })\n const sent = navigator.sendBeacon(endpoint, blob)\n if (sent) return\n // sent === false → fall through to fetch\n }\n // Fetch fallback\n fetch(endpoint, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json' },\n body,\n }).catch(() => {})\n } catch {\n // INVARIANT 7: swallow all errors\n }\n }\n\n function sendPageview(pathname: string): void {\n // INVARIANT 1: DNT/GPC\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n // visibilityState cast to string to accommodate non-standard 'prerender' value\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // INVARIANT 3: 500ms same-path dedup\n const now = Date.now()\n if (pathname === lastPath && now - lastAt < 500) return\n lastPath = pathname\n lastAt = now\n\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n }\n\n // -------------------------------------------------------------------------\n // autoTrack: patch history methods + listen to popstate\n // -------------------------------------------------------------------------\n function trackCurrentPath(): void {\n if (destroyed) return\n sendPageview(location.pathname)\n }\n\n function patchedPushState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalPushState.apply(this, [data, unused, url] as Parameters<typeof history.pushState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n function patchedReplaceState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalReplaceState.apply(this, [data, unused, url] as Parameters<typeof history.replaceState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n if (autoTrack) {\n history.pushState = patchedPushState\n history.replaceState = patchedReplaceState\n window.addEventListener('popstate', trackCurrentPath)\n\n // Initial pageview\n if (document.readyState === 'complete') {\n trackCurrentPath()\n } else {\n window.addEventListener('load', trackCurrentPath, { once: true })\n }\n }\n\n // -------------------------------------------------------------------------\n // track() — client-side validation + send\n // -------------------------------------------------------------------------\n\n // Dev-mode detection: warn in dev, silent in production.\n // process.env.NODE_ENV is unreliable in browser bundles (tsup does not replace it\n // by default). Instead we detect production at runtime via hostname heuristics.\n // SSR (window undefined) is caught at the top of createAnalytics and returns a\n // stub, so window is always defined here.\n const isProduction: boolean = (() => {\n try {\n const hostname = location.hostname\n return (\n hostname !== 'localhost' &&\n hostname !== '127.0.0.1' &&\n !hostname.endsWith('.local')\n )\n } catch {\n // hostname access failed (non-browser) — default to silent\n return true\n }\n })()\n\n // One-shot warn dedup per reason per page load (keyed by reason only)\n const warnedReasons = new Set<string>()\n\n function devWarn(name: string, reason: string): void {\n if (isProduction) return\n if (warnedReasons.has(reason)) return\n warnedReasons.add(reason)\n console.warn(`[01 analytics] dropped event ${name}: ${reason}`)\n }\n\n const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/\n const RESERVED_PREFIXES = ['__', '_pv_']\n\n function validateEventName(name: string): string | null {\n if (!name || typeof name !== 'string') return 'name-empty'\n for (const prefix of RESERVED_PREFIXES) {\n if (name.startsWith(prefix)) return 'name-reserved'\n }\n if (!EVENT_NAME_RE.test(name)) return 'name-regex'\n return null\n }\n\n const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/\n\n function validateEventProps(\n props: Record<string, string | number | boolean> | undefined,\n ): string | null {\n if (props === undefined || props === null) return null\n if (typeof props !== 'object' || Array.isArray(props)) return 'props-value-type'\n const keys = Object.keys(props)\n if (keys.length > 10) return 'props-too-many-keys'\n for (const k of keys) {\n const v = props[k]\n if (!PROP_KEY_RE.test(k)) return 'props-key-regex'\n if (typeof v === 'string') {\n if (v.length > 80) return 'props-value-too-long'\n } else if (typeof v === 'number') {\n if (!isFinite(v)) return 'props-value-not-finite'\n } else if (typeof v === 'boolean') {\n // ok\n } else {\n return 'props-value-type'\n }\n }\n return null\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n return {\n pageview(path?: string): void {\n if (destroyed) return\n sendPageview(path ?? location.pathname)\n },\n\n track(name: string, props?: Record<string, string | number | boolean>): void {\n if (destroyed) return\n\n // INVARIANT 1: DNT/GPC (same as pageview)\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // Client-side validation\n const nameErr = validateEventName(name)\n if (nameErr) {\n devWarn(name, nameErr)\n return\n }\n\n if (props !== undefined) {\n const propsErr = validateEventProps(props)\n if (propsErr) {\n devWarn(name, propsErr)\n return\n }\n }\n\n // Build body — no dedup for track() events\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname: location.pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventName: name,\n eventProps: props,\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n if (autoTrack) {\n // Restore original history methods\n history.pushState = originalPushState\n history.replaceState = originalReplaceState\n window.removeEventListener('popstate', trackCurrentPath)\n }\n\n // Null out dedup state\n lastPath = null\n lastAt = 0\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/client/types.ts","../src/analytics.ts"],"sourcesContent":["import type { Sort, Where } from 'payload'\n\nimport type { Collection, PublicCollection } from '../collection/const'\n\nexport type { Collection, PublicCollection }\n\n// ============================================================================\n// API URL Configuration\n// ============================================================================\n\ndeclare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(): string {\n if (typeof process !== 'undefined' && process.env) {\n const envUrl =\n process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL\n if (envUrl) {\n return envUrl.replace(/\\/$/, '')\n }\n }\n return __DEFAULT_API_URL__\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n publishableKey: string\n /**\n * Customer authentication options.\n * Used to initialize CustomerAuth on Client.\n */\n customer?: {\n /**\n * Persist token in localStorage. Defaults to `true`.\n * - `true` (default): uses key `'customer-token'`\n * - `string`: uses the given string as localStorage key\n * - `false`: disables persistence (token/onTokenChange used instead)\n *\n * Handles SSR safely (no-op on server).\n * When enabled, `token` and `onTokenChange` are ignored.\n */\n persist?: boolean | string\n /** Initial token (e.g. from SSR cookie) */\n token?: string\n /** Called when token changes (login/logout) — use to persist in localStorage/cookie */\n onTokenChange?: (token: string | null) => void\n }\n}\n\n// Server client: requires both publishableKey (for CDN routing + rate limit +\n// monthly quota enforcement via the edge proxy) and secretKey (sk01_ opaque\n// bearer token, the authentication credential).\n// The proxy keys its tenant lookup off `X-Publishable-Key`, so omitting\n// publishableKey would silently bypass rate limiting and plan-based quota\n// enforcement.\nexport interface ClientServerConfig extends ClientConfig {\n secretKey: string\n}\n\n\nexport interface ClientMetadata {\n userAgent?: string\n timestamp: number\n}\n\nexport interface ClientState {\n metadata: ClientMetadata\n}\n\nexport interface PaginationMeta {\n page: number\n limit: number\n totalDocs: number\n totalPages: number\n hasNextPage: boolean\n hasPrevPage: boolean\n pagingCounter: number\n prevPage: number | null\n nextPage: number | null\n}\n\n// ============================================================================\n// Payload CMS Native Response Types\n// ============================================================================\n\n/**\n * Payload CMS Find (List) Response\n * GET /api/{collection}\n */\nexport interface PayloadFindResponse<T = unknown> {\n docs: T[]\n totalDocs: number\n limit: number\n totalPages: number\n page: number\n pagingCounter: number\n hasPrevPage: boolean\n hasNextPage: boolean\n prevPage: number | null\n nextPage: number | null\n}\n\n/**\n * Payload CMS Create/Update Response\n * POST /api/{collection}\n * PATCH /api/{collection}/{id}\n */\nexport interface PayloadMutationResponse<T = unknown> {\n message: string\n doc: T\n errors?: unknown[]\n}\n\n// ============================================================================\n// Query Options\n// ============================================================================\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload`. Payload's generic\n * types (`JoinQuery<TSlug>`, `PopulateType`) depend on `PayloadTypes` module\n * augmentation; external SDK consumers who skip that get degenerate types\n * (`never` / `{}`). Only non-generic `Sort`/`Where` are safe to import.\n * Excluded vs native: Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n where?: Where\n depth?: number\n select?: Record<string, boolean>\n /** Per-collection field selection for populated relationships (keyed by collection slug) */\n populate?: Record<string, boolean | Record<string, boolean>>\n /** Join field control: pagination/filter per join, or false to disable */\n joins?:\n | Record<\n string,\n | {\n limit?: number\n page?: number\n sort?: string\n where?: Where\n count?: boolean\n }\n | false\n >\n | false\n /** Set to `false` to skip the count query — returns docs without totalDocs/totalPages */\n pagination?: boolean\n /** Include draft versions (access control still applies on the server) */\n draft?: boolean\n /** Include soft-deleted documents (requires `trash` enabled on the collection) */\n trash?: boolean\n}\n\n// ============================================================================\n// Debug & Retry Configuration\n// ============================================================================\n\nexport interface DebugConfig {\n logRequests?: boolean\n logResponses?: boolean\n logErrors?: boolean\n}\n\nexport interface RetryConfig {\n maxRetries?: number\n retryableStatuses?: number[]\n retryDelay?: (attempt: number) => number\n}\n\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n\nexport type ExtractArrayType<T> = T extends (infer U)[] ? U : never\n","/**\n * @01.software/sdk — Analytics Helper\n */\n\n/* ANALYTICS INVARIANTS START\n * @01.software/sdk — Analytics Helper\n *\n * ANALYTICS INVARIANTS\n * ====================\n * These invariants are the single source of truth for observable behavior.\n * They are mirrored verbatim in apps/console/src/app/api/analytics/script.js/route.ts.\n * Any change here MUST be reflected there, and vice versa.\n *\n * 1. DNT/GPC respect: when config.respectDnt !== false (default true) AND\n * (navigator.doNotTrack === '1' OR navigator.globalPrivacyControl === true),\n * all methods become no-ops. Zero network requests are made.\n *\n * 2. Prerender skip: when document.prerendering === true OR\n * document.visibilityState === 'prerender', pageview() sends zero requests.\n *\n * 3. 500ms same-path dedup: a pageview for the same pathname within 500ms of\n * the previous send is silently dropped. After 500ms the next call sends.\n *\n * 4. Transport: sendBeacon → fetch keepalive fallback.\n * Primary: navigator.sendBeacon(endpoint, new Blob([json], { type: 'text/plain' })).\n * Fallback (sendBeacon unavailable OR returns false):\n * fetch(endpoint, { method: 'POST', keepalive: true,\n * headers: { 'Content-Type': 'application/json' }, body: json }).catch(() => {})\n *\n * 5. Body-only publishableKey: publishableKey is always in the request body,\n * never in any HTTP header.\n *\n * 6. SSR no-op: when typeof window === 'undefined', createAnalytics() returns\n * a stub where all methods are no-ops. No side effects occur.\n *\n * 7. Error swallowing: all transport errors are caught and swallowed.\n * createAnalytics() and all returned methods never throw into the caller.\n *\n * 8. Client timestamp: every send carries eventTs (milliseconds since epoch)\n * captured with Date.now() immediately before transport. The collect\n * endpoint uses eventTs (a) to bucket the event into the client's\n * tenant-local day and (b) to enforce the late-arrival cutoff; events\n * submitted after the local-day-end grace window are dropped with\n * reason \"late\".\n * ANALYTICS INVARIANTS END */\n\nimport { resolveApiUrl } from './core/client/types'\n\n// ============================================================================\n// Public Types\n// ============================================================================\n\nexport interface AnalyticsConfig {\n publishableKey: string\n /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */\n endpoint?: string\n /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */\n autoTrack?: boolean\n /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */\n respectDnt?: boolean\n}\n\nexport interface Analytics {\n pageview(path?: string): void\n track(name: string, props?: Record<string, string | number | boolean>): void\n destroy(): void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createAnalytics(config: AnalyticsConfig): Analytics {\n // INVARIANT 6: SSR no-op\n if (typeof window === 'undefined') {\n return { pageview() {}, track() {}, destroy() {} }\n }\n\n const endpoint =\n config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`\n\n // INVARIANT 1: DNT/GPC check (evaluated once at init; stays as closure)\n const respectDnt = config.respectDnt !== false\n function isDntActive(): boolean {\n if (!respectDnt) return false\n const nav = navigator as Navigator & { globalPrivacyControl?: boolean }\n return nav.doNotTrack === '1' || nav.globalPrivacyControl === true\n }\n\n // INVARIANT 3: 500ms same-path dedup state\n let lastPath: string | null = null\n let lastAt = 0\n\n // autoTrack state — save originals for destroy()\n const autoTrack = config.autoTrack !== false\n const originalPushState = history.pushState\n const originalReplaceState = history.replaceState\n let destroyed = false\n\n // -------------------------------------------------------------------------\n // Core send logic\n // -------------------------------------------------------------------------\n\n // Generate a unique event ID (crypto.randomUUID when available, Date+Math.random fallback)\n function newEventId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : String(Date.now()) + String(Math.random())\n }\n\n // INVARIANT 4: sendBeacon → fetch keepalive fallback\n // INVARIANT 5: publishableKey in body only\n function sendBeaconOrFetch(body: string): void {\n try {\n if (typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'text/plain' })\n const sent = navigator.sendBeacon(endpoint, blob)\n if (sent) return\n // sent === false → fall through to fetch\n }\n // Fetch fallback\n fetch(endpoint, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json' },\n body,\n }).catch(() => {})\n } catch {\n // INVARIANT 7: swallow all errors\n }\n }\n\n function sendPageview(pathname: string): void {\n // INVARIANT 1: DNT/GPC\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n // visibilityState cast to string to accommodate non-standard 'prerender' value\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // INVARIANT 3: 500ms same-path dedup\n const now = Date.now()\n if (pathname === lastPath && now - lastAt < 500) return\n lastPath = pathname\n lastAt = now\n\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n }\n\n // -------------------------------------------------------------------------\n // autoTrack: patch history methods + listen to popstate\n // -------------------------------------------------------------------------\n function trackCurrentPath(): void {\n if (destroyed) return\n sendPageview(location.pathname)\n }\n\n function patchedPushState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalPushState.apply(this, [data, unused, url] as Parameters<typeof history.pushState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n function patchedReplaceState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalReplaceState.apply(this, [data, unused, url] as Parameters<typeof history.replaceState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n if (autoTrack) {\n history.pushState = patchedPushState\n history.replaceState = patchedReplaceState\n window.addEventListener('popstate', trackCurrentPath)\n\n // Initial pageview\n if (document.readyState === 'complete') {\n trackCurrentPath()\n } else {\n window.addEventListener('load', trackCurrentPath, { once: true })\n }\n }\n\n // -------------------------------------------------------------------------\n // track() — client-side validation + send\n // -------------------------------------------------------------------------\n\n // Dev-mode detection: warn in dev, silent in production.\n // process.env.NODE_ENV is unreliable in browser bundles (tsup does not replace it\n // by default). Instead we detect production at runtime via hostname heuristics.\n // SSR (window undefined) is caught at the top of createAnalytics and returns a\n // stub, so window is always defined here.\n const isProduction: boolean = (() => {\n try {\n const hostname = location.hostname\n return (\n hostname !== 'localhost' &&\n hostname !== '127.0.0.1' &&\n !hostname.endsWith('.local')\n )\n } catch {\n // hostname access failed (non-browser) — default to silent\n return true\n }\n })()\n\n // One-shot warn dedup per reason per page load (keyed by reason only)\n const warnedReasons = new Set<string>()\n\n function devWarn(name: string, reason: string): void {\n if (isProduction) return\n if (warnedReasons.has(reason)) return\n warnedReasons.add(reason)\n console.warn(`[01 analytics] dropped event ${name}: ${reason}`)\n }\n\n const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/\n const RESERVED_PREFIXES = ['__', '_pv_']\n\n function validateEventName(name: string): string | null {\n if (!name || typeof name !== 'string') return 'name-empty'\n for (const prefix of RESERVED_PREFIXES) {\n if (name.startsWith(prefix)) return 'name-reserved'\n }\n if (!EVENT_NAME_RE.test(name)) return 'name-regex'\n return null\n }\n\n const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/\n\n function validateEventProps(\n props: Record<string, string | number | boolean> | undefined,\n ): string | null {\n if (props === undefined || props === null) return null\n if (typeof props !== 'object' || Array.isArray(props)) return 'props-value-type'\n const keys = Object.keys(props)\n if (keys.length > 10) return 'props-too-many-keys'\n for (const k of keys) {\n const v = props[k]\n if (!PROP_KEY_RE.test(k)) return 'props-key-regex'\n if (typeof v === 'string') {\n if (v.length > 80) return 'props-value-too-long'\n } else if (typeof v === 'number') {\n if (!isFinite(v)) return 'props-value-not-finite'\n } else if (typeof v === 'boolean') {\n // ok\n } else {\n return 'props-value-type'\n }\n }\n return null\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n return {\n pageview(path?: string): void {\n if (destroyed) return\n sendPageview(path ?? location.pathname)\n },\n\n track(name: string, props?: Record<string, string | number | boolean>): void {\n if (destroyed) return\n\n // INVARIANT 1: DNT/GPC (same as pageview)\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // Client-side validation\n const nameErr = validateEventName(name)\n if (nameErr) {\n devWarn(name, nameErr)\n return\n }\n\n if (props !== undefined) {\n const propsErr = validateEventProps(props)\n if (propsErr) {\n devWarn(name, propsErr)\n return\n }\n }\n\n // Build body — no dedup for track() events\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname: location.pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventName: name,\n eventProps: props,\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n if (autoTrack) {\n // Restore original history methods\n history.pushState = originalPushState\n history.replaceState = originalReplaceState\n window.removeEventListener('popstate', trackCurrentPath)\n }\n\n // Null out dedup state\n lastPath = null\n lastAt = 0\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as Config } from './payload-types-
|
|
1
|
+
import { C as Config } from './payload-types-DfQct8Dj.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Collection type derived from Payload Config.
|
|
@@ -9,12 +9,12 @@ type Collection = keyof Config['collections'];
|
|
|
9
9
|
* Internal collections that should not be exposed via SDK.
|
|
10
10
|
* Includes Payload system collections and admin-only collections.
|
|
11
11
|
*/
|
|
12
|
-
declare const INTERNAL_COLLECTIONS: readonly ["users", "payload-kv", "payload-locked-documents", "payload-preferences", "payload-migrations", "field-configs", "system-media", "track-assets", "audiences", "email-logs", "
|
|
12
|
+
declare const INTERNAL_COLLECTIONS: readonly ["users", "payload-kv", "payload-locked-documents", "payload-preferences", "payload-migrations", "field-configs", "system-media", "track-assets", "audiences", "email-logs", "api-usage", "tenant-analytics-daily", "analytics-event-schemas", "subscriptions", "billing-history", "order-status-logs", "api-keys", "personal-access-tokens", "tenant-entitlements", "webhook-events", "webhook-deliveries", "audit-logs", "plans", "webhooks", "event-registrations"];
|
|
13
13
|
/**
|
|
14
14
|
* Array of all public collection names for runtime use (e.g., Zod enum validation).
|
|
15
15
|
* This is the single source of truth for which collections are publicly accessible via SDK.
|
|
16
16
|
*/
|
|
17
|
-
declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-profiles", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "promotions", "shipping-policies", "documents", "document-categories", "document-types", "
|
|
17
|
+
declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-profiles", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "promotions", "shipping-policies", "documents", "document-categories", "document-types", "articles", "article-authors", "article-categories", "article-tags", "playlists", "playlist-categories", "playlist-tags", "tracks", "track-categories", "track-tags", "galleries", "gallery-categories", "gallery-tags", "gallery-items", "links", "link-categories", "link-tags", "canvases", "canvas-node-types", "canvas-edge-types", "canvas-categories", "canvas-tags", "canvas-nodes", "canvas-edges", "videos", "video-categories", "video-tags", "live-streams", "images", "forms", "form-submissions", "posts", "comments", "reactions", "reaction-types", "bookmarks", "post-categories", "reports", "community-bans", "event-calendars", "events", "event-occurrences", "event-tags"];
|
|
18
18
|
/**
|
|
19
19
|
* Public collections available for SDK access.
|
|
20
20
|
* Derived from the COLLECTIONS array (single source of truth).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as Config } from './payload-types-
|
|
1
|
+
import { C as Config } from './payload-types-DfQct8Dj.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Collection type derived from Payload Config.
|
|
@@ -9,12 +9,12 @@ type Collection = keyof Config['collections'];
|
|
|
9
9
|
* Internal collections that should not be exposed via SDK.
|
|
10
10
|
* Includes Payload system collections and admin-only collections.
|
|
11
11
|
*/
|
|
12
|
-
declare const INTERNAL_COLLECTIONS: readonly ["users", "payload-kv", "payload-locked-documents", "payload-preferences", "payload-migrations", "field-configs", "system-media", "track-assets", "audiences", "email-logs", "
|
|
12
|
+
declare const INTERNAL_COLLECTIONS: readonly ["users", "payload-kv", "payload-locked-documents", "payload-preferences", "payload-migrations", "field-configs", "system-media", "track-assets", "audiences", "email-logs", "api-usage", "tenant-analytics-daily", "analytics-event-schemas", "subscriptions", "billing-history", "order-status-logs", "api-keys", "personal-access-tokens", "tenant-entitlements", "webhook-events", "webhook-deliveries", "audit-logs", "plans", "webhooks", "event-registrations"];
|
|
13
13
|
/**
|
|
14
14
|
* Array of all public collection names for runtime use (e.g., Zod enum validation).
|
|
15
15
|
* This is the single source of truth for which collections are publicly accessible via SDK.
|
|
16
16
|
*/
|
|
17
|
-
declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-profiles", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "promotions", "shipping-policies", "documents", "document-categories", "document-types", "
|
|
17
|
+
declare const COLLECTIONS: readonly ["tenants", "tenant-metadata", "tenant-logos", "products", "product-variants", "product-options", "product-option-values", "product-categories", "product-tags", "product-collections", "brands", "brand-logos", "orders", "order-items", "returns", "return-items", "fulfillments", "fulfillment-items", "transactions", "customers", "customer-profiles", "customer-addresses", "customer-groups", "carts", "cart-items", "discounts", "promotions", "shipping-policies", "documents", "document-categories", "document-types", "articles", "article-authors", "article-categories", "article-tags", "playlists", "playlist-categories", "playlist-tags", "tracks", "track-categories", "track-tags", "galleries", "gallery-categories", "gallery-tags", "gallery-items", "links", "link-categories", "link-tags", "canvases", "canvas-node-types", "canvas-edge-types", "canvas-categories", "canvas-tags", "canvas-nodes", "canvas-edges", "videos", "video-categories", "video-tags", "live-streams", "images", "forms", "form-submissions", "posts", "comments", "reactions", "reaction-types", "bookmarks", "post-categories", "reports", "community-bans", "event-calendars", "events", "event-occurrences", "event-tags"];
|
|
18
18
|
/**
|
|
19
19
|
* Public collections available for SDK access.
|
|
20
20
|
* Derived from the COLLECTIONS array (single source of truth).
|