@01.software/sdk 0.21.0 → 0.23.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 CHANGED
@@ -10,7 +10,6 @@ npm install @01.software/sdk
10
10
  pnpm add @01.software/sdk
11
11
  ```
12
12
 
13
-
14
13
  ## Features
15
14
 
16
15
  - Full TypeScript type inference
@@ -18,9 +17,9 @@ pnpm add @01.software/sdk
18
17
  - React Query integration (both Client and ServerClient)
19
18
  - Mutation hooks (useCreate, useUpdate, useRemove) with automatic cache invalidation
20
19
  - Customer auth hooks (useCustomerMe, useCustomerLogin, etc.) with cache management
21
- - Automatic retry with exponential backoff (non-retryable: 401, 403, 404, 422)
20
+ - Automatic retry with exponential backoff (non-retryable: 400, 401, 403, 404, 409, 422)
22
21
  - Webhook handling with HMAC-SHA256 signature verification
23
- - Sub-path imports (`./webhook`, `./realtime`, `./ui/*`) for tree-shaking
22
+ - Sub-path imports (`./server`, `./webhook`, `./realtime`, `./ui/*`) for tree-shaking
24
23
  - Type-safe read-only `collections.from()` for Client (compile-time write prevention)
25
24
 
26
25
  ### Sub-path Imports
@@ -34,8 +33,11 @@ analytics.track('signup', { plan: 'pro', trial: false }) // custom event with op
34
33
  ```
35
34
 
36
35
  ```typescript
37
- // Main entry - clients, query builder, hooks, utilities
38
- import { createClient, createServerClient } from '@01.software/sdk'
36
+ // Main entry - browser client, query builder, hooks, utilities
37
+ import { createClient } from '@01.software/sdk'
38
+
39
+ // Server-only entry - avoids importing browser Client APIs
40
+ import { createServerClient } from '@01.software/sdk/server'
39
41
 
40
42
  // Webhook only - webhook handlers
41
43
  import {
@@ -76,9 +78,9 @@ const { docs } = await client.collections.from('products').find({
76
78
  ### Server Client
77
79
 
78
80
  ```typescript
79
- import { createServerClient } from '@01.software/sdk'
81
+ import { createServerClient } from '@01.software/sdk/server'
80
82
 
81
- const client = createServerClient({
83
+ const server = createServerClient({
82
84
  publishableKey: process.env.SOFTWARE_PUBLISHABLE_KEY,
83
85
  secretKey: process.env.SOFTWARE_SECRET_KEY, // sk01_... opaque API key from Console
84
86
  })
@@ -112,7 +114,7 @@ const client = createClient({
112
114
 
113
115
  const server = createServerClient({
114
116
  publishableKey: string,
115
- secretKey: string, // sk01_... or pat01_...
117
+ secretKey: string, // sk01_... or pat01_...
116
118
  })
117
119
  ```
118
120
 
@@ -134,14 +136,16 @@ Access collections via `client.collections.from(slug)`.
134
136
 
135
137
  ```typescript
136
138
  // List query - returns PayloadFindResponse
137
- const { docs, totalDocs, hasNextPage } = await client.collections.from('products').find({
138
- limit: 20,
139
- page: 1,
140
- sort: '-createdAt',
141
- where: { status: { equals: 'published' } },
142
- depth: 2,
143
- select: { title: true, slug: true },
144
- })
139
+ const { docs, totalDocs, hasNextPage } = await client.collections
140
+ .from('products')
141
+ .find({
142
+ limit: 20,
143
+ page: 1,
144
+ sort: '-createdAt',
145
+ where: { status: { equals: 'published' } },
146
+ depth: 2,
147
+ select: { title: true, slug: true },
148
+ })
145
149
 
146
150
  // Query with populate/joins control
147
151
  const { docs } = await client.collections.from('products').find({
@@ -169,10 +173,14 @@ const { doc } = await server.collections
169
173
  .create({ alt: 'Hero image' }, { file: imageFile, filename: 'hero.jpg' })
170
174
 
171
175
  // Update (server only) - returns PayloadMutationResponse
172
- const { doc } = await server.collections.from('products').update('id', { name: 'Updated' })
176
+ const { doc } = await server.collections
177
+ .from('products')
178
+ .update('id', { name: 'Updated' })
173
179
 
174
180
  // Update with file replacement (server only)
175
- await server.collections.from('images').update('id', { alt: 'New alt' }, { file: newFile })
181
+ await server.collections
182
+ .from('images')
183
+ .update('id', { alt: 'New alt' }, { file: newFile })
176
184
 
177
185
  // Delete (server only) - returns document directly
178
186
  const deletedDoc = await server.collections.from('products').remove('id')
@@ -188,9 +196,11 @@ const metadata = await client.collections
188
196
  { siteName: 'My Store' },
189
197
  )
190
198
 
191
- const metadataById = await client.collections.from('products').findMetadataById('id', {
192
- siteName: 'My Store',
193
- })
199
+ const metadataById = await client.collections
200
+ .from('products')
201
+ .findMetadataById('id', {
202
+ siteName: 'My Store',
203
+ })
194
204
 
195
205
  // Bulk operations (server only)
196
206
  await server.collections.from('products').updateMany(where, data)
@@ -397,9 +407,7 @@ Available on both Client and ServerClient via `commerce.product.*`.
397
407
  ```typescript
398
408
  // Batch stock check (point-in-time read, NOT a reservation)
399
409
  const { results, allAvailable } = await client.commerce.product.stockCheck({
400
- items: [
401
- { variantId: '...', quantity: 2 },
402
- ],
410
+ items: [{ variantId: '...', quantity: 2 }],
403
411
  })
404
412
  ```
405
413
 
@@ -409,7 +417,13 @@ Available on both Client and ServerClient via `commerce.cart.*`.
409
417
 
410
418
  ```typescript
411
419
  // Add item to cart
412
- await client.commerce.cart.addItem({ cartId, product, variant, option, quantity })
420
+ await client.commerce.cart.addItem({
421
+ cartId,
422
+ product,
423
+ variant,
424
+ option,
425
+ quantity,
426
+ })
413
427
 
414
428
  // Update item quantity
415
429
  await client.commerce.cart.updateItem({ cartItemId, quantity })
@@ -478,24 +492,26 @@ const customerAuthHandler = createCustomerAuthWebhookHandler({
478
492
 
479
493
  Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 75).
480
494
 
481
- | Category | Collections |
482
- | --- | --- |
483
- | Tenant | `tenants`, `tenant-metadata`, `tenant-logos` |
484
- | Products | `products`, `product-variants`, `product-options`, `product-option-values`, `product-categories`, `product-tags`, `product-collections`, `brands`, `brand-logos` |
485
- | Orders | `orders`, `order-items`, `returns`, `return-items`, `fulfillments`, `fulfillment-items`, `transactions` |
486
- | Customers | `customers`, `customer-profiles`, `customer-addresses`, `customer-groups` |
487
- | Carts | `carts`, `cart-items` |
488
- | Commerce | `discounts`, `promotions`, `shipping-policies` |
489
- | Content | `documents`, `document-categories`, `document-types`, `articles`, `article-authors`, `article-categories`, `article-tags`, `links`, `link-categories`, `link-tags` |
490
- | Playlists / Tracks | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
491
- | Galleries | `galleries`, `gallery-categories`, `gallery-tags`, `gallery-items` |
492
- | Canvas | `canvases`, `canvas-node-types`, `canvas-edge-types`, `canvas-categories`, `canvas-tags`, `canvas-nodes`, `canvas-edges` |
493
- | Videos | `videos`, `video-categories`, `video-tags` |
494
- | Live Streams | `live-streams` |
495
- | Media | `images` |
496
- | Forms | `forms`, `form-submissions` |
497
- | Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories`, `reports`, `community-bans` |
498
- | Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
495
+ | Category | Collections |
496
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
497
+ | Tenant | `tenants`, `tenant-metadata`, `tenant-logos` |
498
+ | Products | `products`, `product-variants`, `product-options`, `product-option-values`, `product-categories`, `product-tags`, `product-collections`, `brands`, `brand-logos` |
499
+ | Orders | `orders`, `order-items`, `returns`, `return-items`, `fulfillments`, `fulfillment-items`, `transactions` |
500
+ | Customers | `customers`, `customer-profiles`, `customer-profile-lists`, `customer-addresses` |
501
+ | Carts | `carts`, `cart-items` |
502
+ | Commerce | `discounts`, `promotions`, `shipping-policies` |
503
+ | Content | `documents`, `document-categories`, `document-types`, `articles`, `article-authors`, `article-categories`, `article-tags`, `links`, `link-categories`, `link-tags` |
504
+ | Playlists / Tracks | `playlists`, `playlist-categories`, `playlist-tags`, `tracks`, `track-categories`, `track-tags` |
505
+ | Galleries | `galleries`, `gallery-categories`, `gallery-tags`, `gallery-items` |
506
+ | Canvas | `canvases`, `canvas-node-types`, `canvas-edge-types`, `canvas-categories`, `canvas-tags`, `canvas-nodes`, `canvas-edges` |
507
+ | Videos | `videos`, `video-categories`, `video-tags` |
508
+ | Live Streams | `live-streams` |
509
+ | Media | `images` |
510
+ | Forms | `forms`, `form-submissions` |
511
+ | Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories`, `reports`, `community-bans` |
512
+ | Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
513
+
514
+ Server-only collections: `customer-groups` is available from `createServerClient().collections` for segmentation and campaign targeting, but is intentionally absent from browser collection discovery.
499
515
 
500
516
  ## Utilities
501
517
 
@@ -609,19 +625,27 @@ API keys created without explicit scopes use the default `['read', 'write']`. Co
609
625
 
610
626
  > **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.
611
627
 
628
+ ## Changelog
629
+
630
+ ### v0.23.0 (Product option-value visuals)
631
+
632
+ - Added reusable option-value visuals (`swatchColor`, `thumbnail`, `images`) to Payload types and ecommerce utility shapes.
633
+ - Listing group summaries now include option-value visual metadata and can use one colorway image across every size variant.
634
+ - Product/listing sellability now uses `stock - reservedStock`, matching checkout stock checks.
635
+
612
636
  ## Migration Guide
613
637
 
614
638
  ### v0.16.0 (Phase 1–7 sync — additive)
615
639
 
616
640
  New error codes propagated via `SDKError.code` (no breaking change; existing callers ignore unknown codes safely):
617
641
 
618
- | Code | Phase | Trigger |
619
- |---|---|---|
620
- | `account_suspended` | P1 | Suspended session / `sk01_` / `pat01_` / customer JWT — 401 |
621
- | `pat_tenant_header_forbidden` | P1 | `pat01_` request carrying any `X-Tenant-Id` header — 401 |
622
- | `tenant_mismatch` | P3 | Cross-tenant FK rejection (forms / community / orders) |
623
- | `server_derived` | P3 | Body-driven write into a server-derived state field — 422 |
624
- | `scope_denied` | P5 | `pat01_` whose `ApiKeys.scopes` lacks the operation |
642
+ | Code | Phase | Trigger |
643
+ | ----------------------------- | ----- | ----------------------------------------------------------- |
644
+ | `account_suspended` | P1 | Suspended session / `sk01_` / `pat01_` / customer JWT — 401 |
645
+ | `pat_tenant_header_forbidden` | P1 | `pat01_` request carrying any `X-Tenant-Id` header — 401 |
646
+ | `tenant_mismatch` | P3 | Cross-tenant FK rejection (forms / community / orders) |
647
+ | `server_derived` | P3 | Body-driven write into a server-derived state field — 422 |
648
+ | `scope_denied` | P5 | `pat01_` whose `ApiKeys.scopes` lacks the operation |
625
649
 
626
650
  P5 also adds JWT-`jti` revocation: `revokeCustomerJti(jti, ttl)` on the server invalidates a token immediately; subsequent SDK calls receive `401 { code: 'token_revoked' }`.
627
651
 
@@ -631,33 +655,35 @@ P5 also adds JWT-`jti` revocation: `revokeCustomerJti(jti, ttl)` on the server i
631
655
 
632
656
  **Field renames** — update any code that reads these fields from API responses:
633
657
 
634
- | Collection | Old | New |
635
- |---|---|---|
636
- | Customers | `socialId` | `providerUserId` |
637
- | Customers | `loginAttempts` | `loginAttemptCount` |
638
- | Customers | `resetPasswordExpiry` | `resetPasswordExpiresAt` |
639
- | Orders, Carts | `shippingFee` | `shippingAmount` |
640
- | Carts | `itemsTotal` | `subtotalAmount` |
641
- | Transactions | `paymentId` | `pgPaymentId` |
642
- | Discounts | `type` | `discountType` |
643
- | Discounts | `value` | `discountValue` |
644
- | Discounts | `usageLimit` | `maxUses` |
645
- | Discounts | `usageCount` | `usesCount` |
646
- | Discounts | `perCustomerLimit` | `maxUsesPerCustomer` |
647
- | ShippingPolicies | `baseFee` | `baseAmount` |
648
- | ShippingPolicies | `freeShippingThreshold` | `freeShippingMinAmount` |
649
- | Documents | `effectiveDate` | `effectiveAt` |
650
- | Documents | `expiryDate` | `expiresAt` |
651
- | Articles | `readTime` | `readingMinutes` |
652
- | ApiUsage | `count` | `apiCallCount` |
653
- | ApiUsage | `storageUsed` | `storageUsedBytes` |
654
- | ApiUsage | `totalDocuments` | `documentCount` |
658
+ | Collection | Old | New |
659
+ | ---------------- | ----------------------- | ------------------------ |
660
+ | Customers | `socialId` | `providerUserId` |
661
+ | Customers | `loginAttempts` | `loginAttemptCount` |
662
+ | Customers | `resetPasswordExpiry` | `resetPasswordExpiresAt` |
663
+ | Orders, Carts | `shippingFee` | `shippingAmount` |
664
+ | Carts | `itemsTotal` | `subtotalAmount` |
665
+ | Transactions | `paymentId` | `pgPaymentId` |
666
+ | Discounts | `type` | `discountType` |
667
+ | Discounts | `value` | `discountValue` |
668
+ | Discounts | `usageLimit` | `maxUses` |
669
+ | Discounts | `usageCount` | `usesCount` |
670
+ | Discounts | `perCustomerLimit` | `maxUsesPerCustomer` |
671
+ | ShippingPolicies | `baseFee` | `baseAmount` |
672
+ | ShippingPolicies | `freeShippingThreshold` | `freeShippingMinAmount` |
673
+ | Documents | `effectiveDate` | `effectiveAt` |
674
+ | Documents | `expiryDate` | `expiresAt` |
675
+ | Articles | `readTime` | `readingMinutes` |
676
+ | ApiUsage | `count` | `apiCallCount` |
677
+ | ApiUsage | `storageUsed` | `storageUsedBytes` |
678
+ | ApiUsage | `totalDocuments` | `documentCount` |
655
679
 
656
680
  **Collection renames:**
681
+
657
682
  - `order-products` → `order-items`
658
683
  - `return-products` → `return-items`
659
684
  - Removed: `exchanges`, `exchange-products`
660
685
  - Added: `product-option-values`
661
686
 
662
687
  **Boolean field renames** (6 collections):
688
+
663
689
  - `status: 'active' | 'inactive'` → `isActive: boolean` on Forms, ArticleAuthors, CustomerGroups, ShippingPolicies, ProductVariants
@@ -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\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 {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n} from '../collection/const'\n\nexport type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n}\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\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// 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 +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\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 {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n} from '../collection/const'\n\nexport type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n}\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\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// 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":[]}
@@ -0,0 +1,34 @@
1
+ import { c as Config } from './payload-types-D8-G1PiT.js';
2
+
3
+ /**
4
+ * Collection type derived from Payload Config.
5
+ * This ensures type safety and automatic synchronization with payload-types.ts
6
+ */
7
+ type Collection = keyof Config['collections'];
8
+ /**
9
+ * Internal collections that should not be exposed via SDK.
10
+ * Includes Payload system collections and admin-only collections.
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", "api-usage", "tenant-analytics-daily", "analytics-event-schemas", "subscriptions", "billing-history", "order-status-logs", "api-keys", "personal-access-tokens", "tenant-entitlements", "direct-upload-sessions", "webhook-events", "webhook-deliveries", "audit-logs", "plans", "webhooks", "event-registrations"];
13
+ type InternalCollection = (typeof INTERNAL_COLLECTIONS)[number];
14
+ /**
15
+ * Array of all public collection names for runtime use (e.g., Zod enum validation).
16
+ * This is the single source of truth for which collections are publicly accessible via SDK.
17
+ */
18
+ 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-profile-lists", "customer-addresses", "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-categories", "event-occurrences", "event-tags"];
19
+ /**
20
+ * Server-auth collection names for runtime use. These collections are safe for
21
+ * secret-key/PAT SDK and MCP server tools, but must not appear in browser or
22
+ * publishable-key collection discovery.
23
+ */
24
+ declare const SERVER_ONLY_COLLECTIONS: readonly ["customer-groups"];
25
+ /**
26
+ * Public collections available for SDK access.
27
+ * Derived from the COLLECTIONS array (single source of truth).
28
+ */
29
+ type PublicCollection = (typeof COLLECTIONS)[number];
30
+ type ServerOnlyCollection = (typeof SERVER_ONLY_COLLECTIONS)[number];
31
+ declare const SERVER_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-profile-lists", "customer-addresses", "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-categories", "event-occurrences", "event-tags", "customer-groups"];
32
+ type ServerCollection = (typeof SERVER_COLLECTIONS)[number];
33
+
34
+ export { type Collection as C, INTERNAL_COLLECTIONS as I, type PublicCollection as P, type ServerCollection as S, type ServerOnlyCollection as a, COLLECTIONS as b, SERVER_ONLY_COLLECTIONS as c, SERVER_COLLECTIONS as d, type InternalCollection as e };
@@ -0,0 +1,34 @@
1
+ import { c as Config } from './payload-types-D8-G1PiT.cjs';
2
+
3
+ /**
4
+ * Collection type derived from Payload Config.
5
+ * This ensures type safety and automatic synchronization with payload-types.ts
6
+ */
7
+ type Collection = keyof Config['collections'];
8
+ /**
9
+ * Internal collections that should not be exposed via SDK.
10
+ * Includes Payload system collections and admin-only collections.
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", "api-usage", "tenant-analytics-daily", "analytics-event-schemas", "subscriptions", "billing-history", "order-status-logs", "api-keys", "personal-access-tokens", "tenant-entitlements", "direct-upload-sessions", "webhook-events", "webhook-deliveries", "audit-logs", "plans", "webhooks", "event-registrations"];
13
+ type InternalCollection = (typeof INTERNAL_COLLECTIONS)[number];
14
+ /**
15
+ * Array of all public collection names for runtime use (e.g., Zod enum validation).
16
+ * This is the single source of truth for which collections are publicly accessible via SDK.
17
+ */
18
+ 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-profile-lists", "customer-addresses", "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-categories", "event-occurrences", "event-tags"];
19
+ /**
20
+ * Server-auth collection names for runtime use. These collections are safe for
21
+ * secret-key/PAT SDK and MCP server tools, but must not appear in browser or
22
+ * publishable-key collection discovery.
23
+ */
24
+ declare const SERVER_ONLY_COLLECTIONS: readonly ["customer-groups"];
25
+ /**
26
+ * Public collections available for SDK access.
27
+ * Derived from the COLLECTIONS array (single source of truth).
28
+ */
29
+ type PublicCollection = (typeof COLLECTIONS)[number];
30
+ type ServerOnlyCollection = (typeof SERVER_ONLY_COLLECTIONS)[number];
31
+ declare const SERVER_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-profile-lists", "customer-addresses", "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-categories", "event-occurrences", "event-tags", "customer-groups"];
32
+ type ServerCollection = (typeof SERVER_COLLECTIONS)[number];
33
+
34
+ export { type Collection as C, INTERNAL_COLLECTIONS as I, type PublicCollection as P, type ServerCollection as S, type ServerOnlyCollection as a, COLLECTIONS as b, SERVER_ONLY_COLLECTIONS as c, SERVER_COLLECTIONS as d, type InternalCollection as e };