@01.software/sdk 0.41.1 → 0.43.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/CHANGELOG.md +246 -0
- package/README.md +88 -19
- package/dist/analytics/react.cjs +71 -3
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.d.cts +18 -2
- package/dist/analytics/react.d.ts +18 -2
- package/dist/analytics/react.js +67 -3
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs +6 -2
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.d.cts +17 -4
- package/dist/analytics.d.ts +17 -4
- package/dist/analytics.js +6 -2
- package/dist/analytics.js.map +1 -1
- package/dist/client.cjs +233 -47
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +9 -6
- package/dist/client.d.ts +9 -6
- package/dist/client.js +233 -47
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-Bk--pNzW.d.cts → collection-client-U70KizLf.d.cts} +3 -3
- package/dist/{collection-client-BdcxkW7l.d.ts → collection-client-gWsA7BVP.d.ts} +3 -3
- package/dist/{const-qGGI7Uii.d.cts → const-BBynifk0.d.cts} +5 -5
- package/dist/{const-HwdntWtG.d.ts → const-_WWHU-gX.d.ts} +5 -5
- package/dist/{index-B1oW50RY.d.ts → index-DPi5NWU8.d.ts} +4 -4
- package/dist/{index-DiXtjVab.d.cts → index-Dg4Y6Aq7.d.cts} +4 -4
- package/dist/index.cjs +225 -358
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +225 -358
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-CqJOgON7.d.cts → payload-types-DLxkB7Mv.d.cts} +561 -123
- package/dist/{payload-types-CqJOgON7.d.ts → payload-types-DLxkB7Mv.d.ts} +561 -123
- package/dist/query.cjs +59 -7
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +5 -5
- package/dist/query.d.ts +5 -5
- package/dist/query.js +59 -7
- package/dist/query.js.map +1 -1
- package/dist/realtime.cjs +1 -1
- 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 +1 -1
- package/dist/realtime.js.map +1 -1
- package/dist/server.cjs +318 -61
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +12 -7
- package/dist/server.d.ts +12 -7
- package/dist/server.js +318 -61
- package/dist/server.js.map +1 -1
- package/dist/{types-ByLSG-O3.d.cts → types-B1ngKbWI.d.cts} +1 -1
- package/dist/{types-BKa9z9s2.d.cts → types-B9lGhZlB.d.ts} +229 -15
- package/dist/{types-BMI3M0nb.d.ts → types-BiKNBwRA.d.ts} +1 -1
- package/dist/{types-DgfXb7iV.d.ts → types-Bpx5sYKo.d.cts} +229 -15
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.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.cjs +3 -1
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +4 -4
- package/dist/webhook.d.ts +4 -4
- package/dist/webhook.js +3 -1
- package/dist/webhook.js.map +1 -1
- package/package.json +1 -1
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/client/types.ts","../src/analytics.ts"],"sourcesContent":["import type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n} from '../collection/const'\nimport type { CollectionType } from '../collection/types'\nimport type { CommunityClient } from '../community/community-client'\nimport type {\n BanCustomerParams,\n CommunityBan,\n UnbanCustomerParams,\n} from '../community/moderation-api'\nimport type { CommerceClient } from '../commerce/commerce-client'\nimport type { ContentClient } from '../content/content-client'\nimport type { ServerCommerceClient } from '../commerce/server-commerce-client'\nimport type { CustomerNamespace } from '../customer/customer-namespace'\nimport type { EventsClient } from '../events/events-client'\nimport type { TenantIntrospectionClient } from '../api/tenant-introspection-api'\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(apiUrl?: string): string {\n if (apiUrl) {\n return apiUrl.replace(/\\/$/, '')\n }\n\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 /** API base URL for staging, self-hosted, preview, or proxy deployments. */\n apiUrl?: 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\nexport type Sort = string | string[]\nexport type Where = Record<string, unknown>\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload` or import Payload\n * types here. Payload's generic query types depend on `PayloadTypes` module\n * augmentation; external SDK consumers who only use `createClient` should not\n * install Payload just to type REST query objects. Excluded vs native:\n * Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n /**\n * Filter documents. Id-based relation filters (`where: { product: { equals: id } }`) are the\n * most reliable pattern. Dotted-path relation filters (`where: { 'product.slug': { equals } }`)\n * are Payload-native but may silently return empty when access control restricts the related\n * document or when the relation is polymorphic. String shorthand (`where: { slug: 'x' }`)\n * silently matches nothing — always use `{ slug: { equals: 'x' } }`.\n */\n where?: Where\n /**\n * Controls how deeply relationship fields are populated. This is the primary control for\n * populating relationships like `category`, `images`, `brand`. The configured Payload default\n * applies when unset.\n */\n depth?: number\n select?: Record<string, boolean>\n /**\n * Controls which fields are returned for already-populated relationships, keyed by collection\n * slug. Does NOT control which relationships to populate — that is `depth`.\n *\n * @example\n * // depth: 2 populates category; populate trims which fields come back\n * populate: { categories: { title: true, slug: true } }\n */\n populate?: Record<string, boolean | Record<string, boolean>>\n /**\n * Controls Payload `type: 'join'` virtual reverse-relation fields only (pagination, sort,\n * filter, count per join field, or `false` to disable all join-field population).\n *\n * Does NOT populate normal relationship fields like `category`, `images`, or `brand`.\n * For normal relationship population use `depth` (and optionally `populate` for field\n * selection).\n *\n * Pass `joins: false` to disable all join-field population — useful for lightweight list\n * queries where join fields are not needed.\n *\n * @example\n * // `article-authors` has a `type: 'join'` field `articles` (reverse-relation)\n * joins: { articles: { limit: 10, sort: '-publishedAt' } }\n *\n * // depth: 2 populates product.category — joins has no effect on this\n * depth: 2\n *\n * // Disable all join-field population\n * joins: false\n */\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\nexport type ApiDeleteOptions = Pick<ApiQueryOptions, 'trash'>\n\nexport type PublicReadQueryOptions = Omit<\n ApiQueryOptions,\n 'depth' | 'joins' | 'populate'\n> & {\n /**\n * Publishable collection reads stay unpopulated. Use shaped commerce/community\n * endpoints or a server client when populated relations are required.\n */\n depth?: 0\n /**\n * Publishable collection reads disable Payload join fields to avoid accidental\n * exposure and expensive generic list responses.\n */\n joins?: false\n /** Publishable collection reads do not support relationship populate maps. */\n populate?: never\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// Lightweight root entry contracts\n// ============================================================================\n\ninterface RootQueryLookup<\n T extends string,\n Options extends ApiQueryOptions = ApiQueryOptions,\n> {\n find(\n options?: Options,\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n findById(\n id: string | number,\n options?: Options,\n ): Promise<CollectionType<T>>\n count(options?: Options): Promise<{ totalDocs: number }>\n}\n\nexport type RootReadOnlyQueryBuilder<T extends PublicCollection> =\n RootQueryLookup<T, PublicReadQueryOptions>\n\nexport interface RootServerQueryBuilder<\n T extends ServerCollection,\n> extends RootQueryLookup<T> {\n create(\n data: Partial<CollectionType<T>>,\n options?: { file?: File | Blob; filename?: string },\n ): Promise<PayloadMutationResponse<CollectionType<T>>>\n update(\n id: string,\n data: Partial<CollectionType<T>>,\n options?: { file?: File | Blob; filename?: string },\n ): Promise<PayloadMutationResponse<CollectionType<T>>>\n updateMany(\n where: ApiQueryOptions['where'],\n data: Partial<CollectionType<T>>,\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n remove(id: string, options?: ApiDeleteOptions): Promise<CollectionType<T>>\n removeMany(\n where: ApiQueryOptions['where'],\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n}\n\nexport interface RootCollectionClient {\n from<T extends PublicCollection>(collection: T): RootReadOnlyQueryBuilder<T>\n}\n\nexport interface RootServerCollectionClient {\n from<T extends ServerCollection>(collection: T): RootServerQueryBuilder<T>\n}\n\nexport interface RootClient {\n commerce: CommerceClient\n community: CommunityClient\n content: ContentClient\n /** Set on {@link createClient} return values; optional for structural mocks. */\n events?: EventsClient\n customer: CustomerNamespace\n collections: RootCollectionClient\n lastRequestId: string | null\n getState(): ClientState\n getConfig(): ClientConfig\n}\n\nexport interface RootServerClient {\n commerce: ServerCommerceClient\n tenant: TenantIntrospectionClient\n /** Set on {@link createServerClient} return values; optional for structural mocks. */\n events?: EventsClient\n community: CommunityClient & {\n moderation: {\n banCustomer: (p: BanCustomerParams) => Promise<CommunityBan>\n unbanCustomer: (p: UnbanCustomerParams) => Promise<{ success: true }>\n }\n }\n collections: RootServerCollectionClient\n lastRequestId: string | null\n getState(): ClientState\n getConfig(): Omit<ClientServerConfig, 'secretKey'>\n}\n\nexport type RootClientWithEvents = RootClient & { events: EventsClient }\n\nexport type RootServerClientWithEvents = RootServerClient & { events: EventsClient }\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 *\n * 9. Dev / local-host send skip: in 'auto' mode (the default) all send paths\n * become no-ops when the page runs on a local host — location.hostname is\n * 'localhost', '127.0.0.1', or ends with '.local'. Validation and dev\n * warnings still run; only the network send is suppressed, and a one-time\n * console notice is emitted. SSR (6), DNT/GPC (1), and prerender (2) skips\n * are evaluated first. Overrides: the SDK accepts mode 'production' (always\n * send, even locally) and 'development' (never send, on any host); the\n * mirrored hosted snippet accepts captureOnLocalhost === true (via\n * window.__01_analytics__.captureOnLocalhost or the script's\n * data-capture-localhost attribute) to force sending on a local host.\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 * Send mode. 'auto' (default) skips all sends on local hosts\n * (localhost / 127.0.0.1 / *.local). 'production' always sends, even\n * locally. 'development' never sends, on any host. See INVARIANT 9.\n */\n mode?: 'auto' | 'production' | 'development'\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 9: local-host detection drives both dev warnings and the\n // auto send-skip. Hostname is the one signal shared with the hosted snippet.\n const isLocalHost: boolean = (() => {\n try {\n const h = location.hostname\n return h === 'localhost' || h === '127.0.0.1' || h.endsWith('.local')\n } catch {\n return false\n }\n })()\n\n const mode = config.mode ?? 'auto'\n const sendSuppressed =\n mode === 'development' || (mode === 'auto' && isLocalHost)\n\n let suppressNoticeShown = false\n function devSuppressNotice(): void {\n if (suppressNoticeShown) return\n suppressNoticeShown = true\n try {\n console.info(\n `[01 analytics] events disabled on local host (mode: '${mode}'). ` +\n `Pass mode: 'production' to send while developing.`,\n )\n } catch {\n // INVARIANT 7: swallow\n }\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 9: skip sends on local hosts in auto mode\n if (sendSuppressed) {\n devSuppressNotice()\n return\n }\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 for warnings: warn in dev, silent in production.\n // Reuses the same local-host signal as the send-skip (INVARIANT 9).\n const isProduction = !isLocalHost\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 // INVARIANT 9: skip sends on local hosts in auto mode (validation above\n // still runs so dev warnings fire)\n if (sendSuppressed) {\n devSuppressNotice()\n return\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/internal/utils/api-url.ts","../src/analytics.ts"],"sourcesContent":["declare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(apiUrl?: string): string {\n if (apiUrl) {\n return apiUrl.replace(/\\/$/, '')\n }\n\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 * @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 *\n * 9. Dev / local-host send skip: in 'auto' mode (the default) all send paths\n * become no-ops when the page runs on a local host — location.hostname is\n * 'localhost', '127.0.0.1', or ends with '.local'. Validation and dev\n * warnings still run; only the network send is suppressed, and a one-time\n * console notice is emitted. SSR (6), DNT/GPC (1), and prerender (2) skips\n * are evaluated first. Overrides: both the SDK and the mirrored hosted\n * snippet accept mode 'production' (always send, even locally) and\n * 'development' (never send, on any host); 'auto' is the default. The\n * snippet reads mode from window.__01_analytics__.mode or the script's\n * data-mode attribute. For backward compatibility the snippet also accepts\n * captureOnLocalhost === true (via window.__01_analytics__.captureOnLocalhost\n * or data-capture-localhost), equivalent to mode 'production' when mode is\n * unset; an explicit mode wins.\n * ANALYTICS INVARIANTS END */\n\nimport { resolveApiUrl } from './core/internal/utils/api-url'\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 * Send mode. 'auto' (default) skips all sends on local hosts\n * (localhost / 127.0.0.1 / *.local). 'production' always sends, even\n * locally. 'development' never sends, on any host. See INVARIANT 9.\n */\n mode?: 'auto' | 'production' | 'development'\n}\n\nexport type AnalyticsEventProps = Record<string, string | number | boolean>\n\nexport type AnalyticsEventMap = Record<string, AnalyticsEventProps | undefined>\n\n/**\n * Argument tuple for `track`. The default open map keeps `props` optional\n * (back-compat). A developer-declared closed map makes `props` required when\n * the event declares props, and omittable when the event value is `undefined`.\n */\nexport type TrackArgs<\n TEvents extends AnalyticsEventMap,\n K extends keyof TEvents & string,\n> = string extends keyof TEvents\n ? [props?: AnalyticsEventProps]\n : undefined extends TEvents[K]\n ? [props?: Exclude<TEvents[K], undefined>]\n : [props: TEvents[K]]\n\nexport interface Analytics<\n TEvents extends AnalyticsEventMap = AnalyticsEventMap,\n> {\n pageview(path?: string): void\n track<K extends keyof TEvents & string>(\n name: K,\n ...args: TrackArgs<TEvents, K>\n ): void\n destroy(): void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createAnalytics<\n TEvents extends AnalyticsEventMap = AnalyticsEventMap,\n>(config: AnalyticsConfig): Analytics<TEvents> {\n // INVARIANT 6: SSR no-op\n if (typeof window === 'undefined') {\n return { pageview() {}, track() {}, destroy() {} } as Analytics<TEvents>\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 9: local-host detection drives both dev warnings and the\n // auto send-skip. Hostname is the one signal shared with the hosted snippet.\n const isLocalHost: boolean = (() => {\n try {\n const h = location.hostname\n return h === 'localhost' || h === '127.0.0.1' || h.endsWith('.local')\n } catch {\n return false\n }\n })()\n\n const mode = config.mode ?? 'auto'\n const sendSuppressed =\n mode === 'development' || (mode === 'auto' && isLocalHost)\n\n let suppressNoticeShown = false\n function devSuppressNotice(): void {\n if (suppressNoticeShown) return\n suppressNoticeShown = true\n try {\n console.info(\n `[01 analytics] events disabled on local host (mode: '${mode}'). ` +\n `Pass mode: 'production' to send while developing.`,\n )\n } catch {\n // INVARIANT 7: swallow\n }\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 9: skip sends on local hosts in auto mode\n if (sendSuppressed) {\n devSuppressNotice()\n return\n }\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 for warnings: warn in dev, silent in production.\n // Reuses the same local-host signal as the send-skip (INVARIANT 9).\n const isProduction = !isLocalHost\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 // INVARIANT 9: skip sends on local hosts in auto mode (validation above\n // still runs so dev warnings fire)\n if (sendSuppressed) {\n devSuppressNotice()\n return\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 } as Analytics<TEvents>\n}\n\n/**\n * Bind a typed event map once, then create instances without repeating the\n * generic. Equivalent to `createAnalytics<TEvents>(config)`.\n */\nexport function defineAnalyticsEvents<\n TEvents extends AnalyticsEventMap,\n>(): (config: AnalyticsConfig) => Analytics<TEvents> {\n return (config) => createAnalytics<TEvents>(config)\n}\n"],"mappings":"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","names":[]}
|
package/dist/client.cjs
CHANGED
|
@@ -98,6 +98,26 @@ var TimeoutError = class extends SDKError {
|
|
|
98
98
|
this.name = "TimeoutError";
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
|
+
var GoneError = class extends SDKError {
|
|
102
|
+
constructor(message = "The requested resource is no longer available.", details, userMessage, suggestion) {
|
|
103
|
+
super("GONE_ERROR", message, 410, details, userMessage, suggestion);
|
|
104
|
+
this.name = "GoneError";
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var ServiceUnavailableError = class extends SDKError {
|
|
108
|
+
constructor(message = "Service temporarily unavailable.", retryAfter, details, userMessage, suggestion) {
|
|
109
|
+
super(
|
|
110
|
+
"SERVICE_UNAVAILABLE_ERROR",
|
|
111
|
+
message,
|
|
112
|
+
503,
|
|
113
|
+
details,
|
|
114
|
+
userMessage,
|
|
115
|
+
suggestion
|
|
116
|
+
);
|
|
117
|
+
this.name = "ServiceUnavailableError";
|
|
118
|
+
this.retryAfter = retryAfter;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
101
121
|
var UsageLimitError = class extends SDKError {
|
|
102
122
|
constructor(message, usage, details, userMessage, suggestion) {
|
|
103
123
|
super("USAGE_LIMIT_ERROR", message, 429, details, userMessage, suggestion);
|
|
@@ -179,6 +199,14 @@ var createValidationError = (message, details, userMessage, suggestion, status)
|
|
|
179
199
|
var createApiError = (message, status, details, userMessage, suggestion, requestId) => new ApiError(message, status, details, userMessage, suggestion, requestId);
|
|
180
200
|
var createConfigError = (message, details, userMessage, suggestion) => new ConfigError(message, details, userMessage, suggestion);
|
|
181
201
|
var createTimeoutError = (message, details, userMessage, suggestion) => new TimeoutError(message, details, userMessage, suggestion);
|
|
202
|
+
var createGoneError = (message, details, userMessage, suggestion) => new GoneError(message, details, userMessage, suggestion);
|
|
203
|
+
var createServiceUnavailableError = (message, retryAfter, details, userMessage, suggestion) => new ServiceUnavailableError(
|
|
204
|
+
message,
|
|
205
|
+
retryAfter,
|
|
206
|
+
details,
|
|
207
|
+
userMessage,
|
|
208
|
+
suggestion
|
|
209
|
+
);
|
|
182
210
|
var createUsageLimitError = (message, usage, details, userMessage, suggestion) => new UsageLimitError(message, usage, details, userMessage, suggestion);
|
|
183
211
|
var createAuthError = (message, details, userMessage, suggestion, requestId) => new AuthError(message, details, userMessage, suggestion, requestId);
|
|
184
212
|
var createPermissionError = (message, details, userMessage, suggestion, requestId) => new PermissionError(message, details, userMessage, suggestion, requestId);
|
|
@@ -203,7 +231,7 @@ function requirePublishableKeyForSecret(apiName, publishableKey, secretKey) {
|
|
|
203
231
|
return publishableKey ?? "";
|
|
204
232
|
}
|
|
205
233
|
|
|
206
|
-
// src/core/
|
|
234
|
+
// src/core/internal/utils/api-url.ts
|
|
207
235
|
function resolveApiUrl(apiUrl) {
|
|
208
236
|
if (apiUrl) {
|
|
209
237
|
return apiUrl.replace(/\/$/, "");
|
|
@@ -220,7 +248,7 @@ function resolveApiUrl(apiUrl) {
|
|
|
220
248
|
// src/core/internal/utils/http.ts
|
|
221
249
|
var DEFAULT_TIMEOUT = 3e4;
|
|
222
250
|
var STOREFRONT_BROWSER_TIMEOUT = 15e3;
|
|
223
|
-
var DEFAULT_RETRYABLE_STATUSES = [408,
|
|
251
|
+
var DEFAULT_RETRYABLE_STATUSES = [408, 500, 502, 503, 504];
|
|
224
252
|
var NON_RETRYABLE_STATUSES = [400, 401, 403, 404, 409, 422];
|
|
225
253
|
var SAFE_METHODS = ["GET", "HEAD", "OPTIONS"];
|
|
226
254
|
var DEFAULT_MAX_RETRIES = 3;
|
|
@@ -337,7 +365,12 @@ function attachRequestId(err, id) {
|
|
|
337
365
|
if (id) err.requestId = id;
|
|
338
366
|
return err;
|
|
339
367
|
}
|
|
340
|
-
function
|
|
368
|
+
function parseRetryAfter(response) {
|
|
369
|
+
const raw = response.headers.get("Retry-After");
|
|
370
|
+
if (!raw) return void 0;
|
|
371
|
+
return parseInt(raw, 10) || void 0;
|
|
372
|
+
}
|
|
373
|
+
function mapResponseToError(status, parsed, details, requestId, retryAfter) {
|
|
341
374
|
const errorDetails = {
|
|
342
375
|
...details,
|
|
343
376
|
...parsed.errors && { errors: parsed.errors },
|
|
@@ -400,6 +433,41 @@ function createHttpStatusError(status, parsed, details, requestId) {
|
|
|
400
433
|
requestId
|
|
401
434
|
);
|
|
402
435
|
}
|
|
436
|
+
if (status === 410) {
|
|
437
|
+
return attachRequestId(
|
|
438
|
+
createGoneError(
|
|
439
|
+
parsed.errorMessage,
|
|
440
|
+
errorDetails,
|
|
441
|
+
parsed.userMessage,
|
|
442
|
+
suggestion
|
|
443
|
+
),
|
|
444
|
+
requestId
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (status === 429) {
|
|
448
|
+
return attachRequestId(
|
|
449
|
+
createRateLimitError(
|
|
450
|
+
parsed.errorMessage,
|
|
451
|
+
retryAfter,
|
|
452
|
+
errorDetails,
|
|
453
|
+
parsed.userMessage,
|
|
454
|
+
suggestion
|
|
455
|
+
),
|
|
456
|
+
requestId
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (status === 503) {
|
|
460
|
+
return attachRequestId(
|
|
461
|
+
createServiceUnavailableError(
|
|
462
|
+
parsed.errorMessage,
|
|
463
|
+
retryAfter,
|
|
464
|
+
errorDetails,
|
|
465
|
+
parsed.userMessage,
|
|
466
|
+
suggestion
|
|
467
|
+
),
|
|
468
|
+
requestId
|
|
469
|
+
);
|
|
470
|
+
}
|
|
403
471
|
return attachRequestId(
|
|
404
472
|
createNetworkError(
|
|
405
473
|
parsed.errorMessage,
|
|
@@ -518,26 +586,17 @@ async function httpFetch(url, options) {
|
|
|
518
586
|
method: requestInit.method || "GET",
|
|
519
587
|
attempt: attempt + 1
|
|
520
588
|
};
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
const error = attachRequestId(
|
|
530
|
-
createNetworkError(
|
|
531
|
-
parsed.errorMessage,
|
|
532
|
-
response.status,
|
|
533
|
-
details,
|
|
534
|
-
parsed.userMessage,
|
|
535
|
-
getErrorSuggestion(response.status)
|
|
536
|
-
),
|
|
537
|
-
requestId
|
|
589
|
+
const retryAfter = parseRetryAfter(response);
|
|
590
|
+
const error = mapResponseToError(
|
|
591
|
+
response.status,
|
|
592
|
+
parsed,
|
|
593
|
+
details,
|
|
594
|
+
requestId,
|
|
595
|
+
retryAfter
|
|
538
596
|
);
|
|
539
597
|
const method2 = (requestInit.method || "GET").toUpperCase();
|
|
540
|
-
|
|
598
|
+
const isTerminal = error instanceof RateLimitError || error instanceof UsageLimitError;
|
|
599
|
+
if (attempt < retryConfig.maxRetries && SAFE_METHODS.includes(method2) && !isTerminal && !NON_RETRYABLE_STATUSES.includes(response.status) && retryConfig.retryableStatuses.includes(response.status)) {
|
|
541
600
|
lastError = error;
|
|
542
601
|
const retryDelay = retryConfig.retryDelay(attempt);
|
|
543
602
|
debugLog(debug, "error", `Retrying in ${retryDelay}ms...`, error);
|
|
@@ -609,6 +668,64 @@ async function httpFetch(url, options) {
|
|
|
609
668
|
throw lastError ?? new NetworkError("Request failed after retries");
|
|
610
669
|
}
|
|
611
670
|
|
|
671
|
+
// src/core/api/endpoints.ts
|
|
672
|
+
var COMMERCE_ENDPOINTS = {
|
|
673
|
+
orders: {
|
|
674
|
+
create: "/api/orders/create",
|
|
675
|
+
update: "/api/orders/update",
|
|
676
|
+
confirmPayment: "/api/orders/confirm-payment",
|
|
677
|
+
byPayment: "/api/orders/by-payment",
|
|
678
|
+
cancel: "/api/orders/cancel",
|
|
679
|
+
resolveCancelRefund: "/api/orders/resolve-cancel-refund",
|
|
680
|
+
checkout: "/api/orders/checkout",
|
|
681
|
+
createFulfillment: "/api/orders/create-fulfillment",
|
|
682
|
+
updateFulfillment: "/api/orders/update-fulfillment"
|
|
683
|
+
},
|
|
684
|
+
transactions: {
|
|
685
|
+
update: "/api/transactions/update"
|
|
686
|
+
},
|
|
687
|
+
returns: {
|
|
688
|
+
create: "/api/returns/create",
|
|
689
|
+
update: "/api/returns/update",
|
|
690
|
+
returnRefund: "/api/returns/return-refund"
|
|
691
|
+
},
|
|
692
|
+
fulfillmentOrders: {
|
|
693
|
+
prepare: "/api/fulfillment-orders/prepare-fulfillment-order"
|
|
694
|
+
},
|
|
695
|
+
fulfillments: {
|
|
696
|
+
bulkImport: "/api/fulfillments/bulk-import"
|
|
697
|
+
},
|
|
698
|
+
carts: {
|
|
699
|
+
mine: "/api/carts/mine",
|
|
700
|
+
merge: "/api/carts/merge",
|
|
701
|
+
addItem: "/api/carts/add-item",
|
|
702
|
+
updateItem: "/api/carts/update-item",
|
|
703
|
+
removeItem: "/api/carts/remove-item",
|
|
704
|
+
applyDiscount: "/api/carts/apply-discount",
|
|
705
|
+
removeDiscount: "/api/carts/remove-discount",
|
|
706
|
+
clear: "/api/carts/clear"
|
|
707
|
+
},
|
|
708
|
+
products: {
|
|
709
|
+
stockCheck: "/api/products/stock-check",
|
|
710
|
+
stockSnapshot: "/api/products/stock",
|
|
711
|
+
listingGroups: "/api/products/listing-groups",
|
|
712
|
+
listingGroupsCatalog: "/api/products/listing-groups/catalog",
|
|
713
|
+
listingGroupsQuery: "/api/products/listing-groups/query",
|
|
714
|
+
listingGroupsQueryCatalog: "/api/products/listing-groups/query/catalog",
|
|
715
|
+
detail: "/api/products/detail",
|
|
716
|
+
detailCatalog: "/api/products/detail/catalog",
|
|
717
|
+
upsert: "/api/products/upsert"
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
var COMMERCE_ENDPOINT_PATHS = Object.values(
|
|
721
|
+
COMMERCE_ENDPOINTS
|
|
722
|
+
).flatMap((group) => Object.values(group));
|
|
723
|
+
var MEMBERSHIP_ENDPOINTS = {
|
|
724
|
+
subscribe: "/api/memberships/subscribe",
|
|
725
|
+
cancel: (id) => `/api/memberships/${id}/cancel`,
|
|
726
|
+
me: "/api/memberships/me"
|
|
727
|
+
};
|
|
728
|
+
|
|
612
729
|
// src/core/internal/utils/query-string.ts
|
|
613
730
|
function productDetailQuery(params) {
|
|
614
731
|
const search = new URLSearchParams();
|
|
@@ -617,7 +734,7 @@ function productDetailQuery(params) {
|
|
|
617
734
|
} else {
|
|
618
735
|
search.set("id", params.id);
|
|
619
736
|
}
|
|
620
|
-
return
|
|
737
|
+
return `${COMMERCE_ENDPOINTS.products.detail}?${search}`;
|
|
621
738
|
}
|
|
622
739
|
function productDetailCatalogQuery(params) {
|
|
623
740
|
const search = new URLSearchParams();
|
|
@@ -626,20 +743,17 @@ function productDetailCatalogQuery(params) {
|
|
|
626
743
|
} else {
|
|
627
744
|
search.set("id", params.id);
|
|
628
745
|
}
|
|
629
|
-
return
|
|
746
|
+
return `${COMMERCE_ENDPOINTS.products.detailCatalog}?${search}`;
|
|
630
747
|
}
|
|
631
748
|
function listingGroupsCatalogQuery(params) {
|
|
632
|
-
return
|
|
749
|
+
return `${COMMERCE_ENDPOINTS.products.listingGroupsCatalog}?ids=${params.productIds.map(encodeURIComponent).join(",")}`;
|
|
633
750
|
}
|
|
634
751
|
function appendListingGroupsQuerySearchParams(search, options) {
|
|
635
752
|
if (options?.page != null) search.set("page", String(options.page));
|
|
636
753
|
if (options?.limit != null) search.set("limit", String(options.limit));
|
|
637
754
|
if (options?.sort != null) {
|
|
638
755
|
const sort = options.sort;
|
|
639
|
-
search.set(
|
|
640
|
-
"sort",
|
|
641
|
-
Array.isArray(sort) ? sort.join(",") : sort
|
|
642
|
-
);
|
|
756
|
+
search.set("sort", Array.isArray(sort) ? sort.join(",") : sort);
|
|
643
757
|
}
|
|
644
758
|
if (options?.where != null) {
|
|
645
759
|
search.set("whereJson", JSON.stringify(options.where));
|
|
@@ -649,16 +763,16 @@ function listingGroupsQueryUrl(options) {
|
|
|
649
763
|
const search = new URLSearchParams();
|
|
650
764
|
appendListingGroupsQuerySearchParams(search, options);
|
|
651
765
|
const query = search.toString();
|
|
652
|
-
return
|
|
766
|
+
return `${COMMERCE_ENDPOINTS.products.listingGroupsQuery}${query ? `?${query}` : ""}`;
|
|
653
767
|
}
|
|
654
768
|
function listingGroupsQueryCatalogUrl(options) {
|
|
655
769
|
const search = new URLSearchParams();
|
|
656
770
|
appendListingGroupsQuerySearchParams(search, options);
|
|
657
771
|
const query = search.toString();
|
|
658
|
-
return
|
|
772
|
+
return `${COMMERCE_ENDPOINTS.products.listingGroupsQueryCatalog}${query ? `?${query}` : ""}`;
|
|
659
773
|
}
|
|
660
774
|
function stockSnapshotQuery(params) {
|
|
661
|
-
return
|
|
775
|
+
return `${COMMERCE_ENDPOINTS.products.stockSnapshot}?variantIds=${params.variantIds.map(encodeURIComponent).join(",")}`;
|
|
662
776
|
}
|
|
663
777
|
|
|
664
778
|
// src/core/collection/http-client.ts
|
|
@@ -1550,39 +1664,78 @@ var CustomerNamespace = class {
|
|
|
1550
1664
|
// src/core/api/cart-api.ts
|
|
1551
1665
|
var CartApi = class extends CustomerScopedApi {
|
|
1552
1666
|
constructor(options) {
|
|
1553
|
-
super("CartApi", options);
|
|
1667
|
+
super("CartApi", { ...options, requiresCredential: false });
|
|
1554
1668
|
}
|
|
1555
1669
|
async execute(endpoint, method, body) {
|
|
1556
1670
|
return this.request(endpoint, { method, body });
|
|
1557
1671
|
}
|
|
1558
|
-
|
|
1672
|
+
/**
|
|
1673
|
+
* Create a cart and receive its `cartToken`. Store the token and pass it to
|
|
1674
|
+
* every subsequent cart operation.
|
|
1675
|
+
*/
|
|
1676
|
+
createCart(params = {}) {
|
|
1677
|
+
return this.execute("/api/carts/create", "POST", params);
|
|
1678
|
+
}
|
|
1679
|
+
getCart(cartToken) {
|
|
1680
|
+
return this.execute("/api/carts/get", "POST", { cartToken });
|
|
1681
|
+
}
|
|
1682
|
+
merge(params) {
|
|
1559
1683
|
return this.execute(
|
|
1560
|
-
|
|
1561
|
-
"
|
|
1684
|
+
COMMERCE_ENDPOINTS.carts.merge,
|
|
1685
|
+
"POST",
|
|
1686
|
+
params
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Resolve the authenticated customer's active cart by JWT (no `cartToken`
|
|
1691
|
+
* required). Returns the cart and its capability handle, or both `null` when
|
|
1692
|
+
* the customer has no active cart. Requires a customer token.
|
|
1693
|
+
*/
|
|
1694
|
+
mine() {
|
|
1695
|
+
return this.execute(
|
|
1696
|
+
COMMERCE_ENDPOINTS.carts.mine,
|
|
1697
|
+
"POST",
|
|
1698
|
+
{}
|
|
1562
1699
|
);
|
|
1563
1700
|
}
|
|
1564
1701
|
addItem(params) {
|
|
1565
|
-
return this.execute(
|
|
1702
|
+
return this.execute(
|
|
1703
|
+
COMMERCE_ENDPOINTS.carts.addItem,
|
|
1704
|
+
"POST",
|
|
1705
|
+
params
|
|
1706
|
+
);
|
|
1566
1707
|
}
|
|
1567
1708
|
updateItem(params) {
|
|
1568
|
-
return this.execute(
|
|
1709
|
+
return this.execute(
|
|
1710
|
+
COMMERCE_ENDPOINTS.carts.updateItem,
|
|
1711
|
+
"POST",
|
|
1712
|
+
params
|
|
1713
|
+
);
|
|
1569
1714
|
}
|
|
1570
1715
|
removeItem(params) {
|
|
1571
1716
|
return this.execute(
|
|
1572
|
-
|
|
1717
|
+
COMMERCE_ENDPOINTS.carts.removeItem,
|
|
1573
1718
|
"POST",
|
|
1574
1719
|
params
|
|
1575
1720
|
);
|
|
1576
1721
|
}
|
|
1577
1722
|
applyDiscount(params) {
|
|
1578
|
-
return this.execute(
|
|
1723
|
+
return this.execute(
|
|
1724
|
+
COMMERCE_ENDPOINTS.carts.applyDiscount,
|
|
1725
|
+
"POST",
|
|
1726
|
+
params
|
|
1727
|
+
);
|
|
1579
1728
|
}
|
|
1580
1729
|
removeDiscount(params) {
|
|
1581
|
-
return this.execute(
|
|
1730
|
+
return this.execute(
|
|
1731
|
+
COMMERCE_ENDPOINTS.carts.removeDiscount,
|
|
1732
|
+
"POST",
|
|
1733
|
+
params
|
|
1734
|
+
);
|
|
1582
1735
|
}
|
|
1583
1736
|
clearCart(params) {
|
|
1584
1737
|
return this.execute(
|
|
1585
|
-
|
|
1738
|
+
COMMERCE_ENDPOINTS.carts.clear,
|
|
1586
1739
|
"POST",
|
|
1587
1740
|
params
|
|
1588
1741
|
);
|
|
@@ -2393,6 +2546,7 @@ function compareVariantOrder(a, b) {
|
|
|
2393
2546
|
function getVariantAvailableForSale(variant) {
|
|
2394
2547
|
if (variant.isActive === false) return false;
|
|
2395
2548
|
if (variant.isUnlimited) return true;
|
|
2549
|
+
if (variant.inventoryPolicy === "continue") return true;
|
|
2396
2550
|
return (variant.stock ?? 0) - (variant.reservedStock ?? 0) > 0;
|
|
2397
2551
|
}
|
|
2398
2552
|
function isVariantAvailableForSale(variant) {
|
|
@@ -2730,7 +2884,10 @@ var CommerceClient = class {
|
|
|
2730
2884
|
onRequestId: options.onRequestId
|
|
2731
2885
|
});
|
|
2732
2886
|
this.product = {
|
|
2733
|
-
stockCheck: (params) => api.post(
|
|
2887
|
+
stockCheck: (params) => api.post(
|
|
2888
|
+
COMMERCE_ENDPOINTS.products.stockCheck,
|
|
2889
|
+
params
|
|
2890
|
+
),
|
|
2734
2891
|
stockSnapshot: (params) => api.get(stockSnapshotQuery(params)),
|
|
2735
2892
|
listingGroups: (params) => api.get(
|
|
2736
2893
|
listingGroupsCatalogQuery(params)
|
|
@@ -2748,7 +2905,9 @@ var CommerceClient = class {
|
|
|
2748
2905
|
},
|
|
2749
2906
|
detail: async (params) => {
|
|
2750
2907
|
try {
|
|
2751
|
-
const product = await api.get(
|
|
2908
|
+
const product = await api.get(
|
|
2909
|
+
productDetailQuery(params)
|
|
2910
|
+
);
|
|
2752
2911
|
return { found: true, product };
|
|
2753
2912
|
} catch (err) {
|
|
2754
2913
|
const notFoundResult = productDetailResultFromError(err);
|
|
@@ -2770,13 +2929,16 @@ var CommerceClient = class {
|
|
|
2770
2929
|
}
|
|
2771
2930
|
};
|
|
2772
2931
|
this.cart = {
|
|
2932
|
+
create: cartApi.createCart.bind(cartApi),
|
|
2773
2933
|
get: cartApi.getCart.bind(cartApi),
|
|
2774
2934
|
addItem: cartApi.addItem.bind(cartApi),
|
|
2775
2935
|
updateItem: cartApi.updateItem.bind(cartApi),
|
|
2776
2936
|
removeItem: cartApi.removeItem.bind(cartApi),
|
|
2777
2937
|
applyDiscount: cartApi.applyDiscount.bind(cartApi),
|
|
2778
2938
|
removeDiscount: cartApi.removeDiscount.bind(cartApi),
|
|
2779
|
-
clear: cartApi.clearCart.bind(cartApi)
|
|
2939
|
+
clear: cartApi.clearCart.bind(cartApi),
|
|
2940
|
+
merge: cartApi.merge.bind(cartApi),
|
|
2941
|
+
mine: cartApi.mine.bind(cartApi)
|
|
2780
2942
|
};
|
|
2781
2943
|
this.orders = {
|
|
2782
2944
|
checkout: (params) => {
|
|
@@ -2793,7 +2955,10 @@ var CommerceClient = class {
|
|
|
2793
2955
|
validate: (params) => api.post("/api/discounts/validate", params)
|
|
2794
2956
|
};
|
|
2795
2957
|
this.shipping = {
|
|
2796
|
-
calculate: (params) => api.post(
|
|
2958
|
+
calculate: (params) => api.post(
|
|
2959
|
+
"/api/shipping-policies/calculate",
|
|
2960
|
+
params
|
|
2961
|
+
)
|
|
2797
2962
|
};
|
|
2798
2963
|
}
|
|
2799
2964
|
};
|
|
@@ -2882,8 +3047,12 @@ function buildGuestCancelRequestBody(token, params) {
|
|
|
2882
3047
|
}
|
|
2883
3048
|
function buildRangeEndpoint(params) {
|
|
2884
3049
|
const urlParams = new URLSearchParams();
|
|
2885
|
-
|
|
2886
|
-
|
|
3050
|
+
if (params.start !== void 0) {
|
|
3051
|
+
urlParams.set("start", formatDateParam(params.start));
|
|
3052
|
+
}
|
|
3053
|
+
if (params.end !== void 0) {
|
|
3054
|
+
urlParams.set("end", formatDateParam(params.end));
|
|
3055
|
+
}
|
|
2887
3056
|
if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
|
|
2888
3057
|
if (params.page !== void 0) urlParams.set("page", String(params.page));
|
|
2889
3058
|
appendValues(urlParams, "calendar", params.calendar);
|
|
@@ -2906,6 +3075,13 @@ function appendValues(params, key, value) {
|
|
|
2906
3075
|
}
|
|
2907
3076
|
|
|
2908
3077
|
// src/core/client/client.ts
|
|
3078
|
+
var BrowserMembershipApi = class extends CustomerScopedApi {
|
|
3079
|
+
me() {
|
|
3080
|
+
return this.request(MEMBERSHIP_ENDPOINTS.me, {
|
|
3081
|
+
method: "GET"
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
2909
3085
|
var Client = class {
|
|
2910
3086
|
constructor(options) {
|
|
2911
3087
|
this.lastRequestId = null;
|
|
@@ -2970,6 +3146,16 @@ var Client = class {
|
|
|
2970
3146
|
onRequestId,
|
|
2971
3147
|
this.config.apiUrl
|
|
2972
3148
|
);
|
|
3149
|
+
const membershipApi = new BrowserMembershipApi("MembershipApi", {
|
|
3150
|
+
publishableKey: this.config.publishableKey,
|
|
3151
|
+
apiUrl: this.config.apiUrl,
|
|
3152
|
+
customerToken: () => this.customer.auth.getToken(),
|
|
3153
|
+
onUnauthorized,
|
|
3154
|
+
onRequestId
|
|
3155
|
+
});
|
|
3156
|
+
this.memberships = {
|
|
3157
|
+
me: membershipApi.me.bind(membershipApi)
|
|
3158
|
+
};
|
|
2973
3159
|
}
|
|
2974
3160
|
getState() {
|
|
2975
3161
|
return { ...this.state };
|