@01.software/sdk 0.12.1 → 0.14.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.
Files changed (44) hide show
  1. package/README.md +102 -57
  2. package/dist/analytics.cjs +207 -0
  3. package/dist/analytics.cjs.map +1 -0
  4. package/dist/analytics.d.cts +20 -0
  5. package/dist/analytics.d.ts +20 -0
  6. package/dist/analytics.js +184 -0
  7. package/dist/analytics.js.map +1 -0
  8. package/dist/{const-CRjjFJ3o.d.ts → const-CigSm8e_.d.ts} +1 -1
  9. package/dist/{const-CZOY2tsf.d.cts → const-CxpAy8X_.d.cts} +1 -1
  10. package/dist/index.cjs +946 -485
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +640 -439
  13. package/dist/index.d.ts +640 -439
  14. package/dist/index.js +946 -485
  15. package/dist/index.js.map +1 -1
  16. package/dist/{payload-types-D4IWd3ZB.d.cts → payload-types-DH1fKdM3.d.cts} +94 -1
  17. package/dist/{payload-types-D4IWd3ZB.d.ts → payload-types-DH1fKdM3.d.ts} +94 -1
  18. package/dist/realtime.cjs.map +1 -1
  19. package/dist/realtime.d.cts +2 -2
  20. package/dist/realtime.d.ts +2 -2
  21. package/dist/realtime.js.map +1 -1
  22. package/dist/{server-JR9TvKZ5.d.cts → server-DLdbWJVv.d.cts} +5 -3
  23. package/dist/{server-JR9TvKZ5.d.ts → server-DLdbWJVv.d.ts} +5 -3
  24. package/dist/ui/canvas/server.cjs +4 -4
  25. package/dist/ui/canvas/server.cjs.map +1 -1
  26. package/dist/ui/canvas/server.d.cts +1 -1
  27. package/dist/ui/canvas/server.d.ts +1 -1
  28. package/dist/ui/canvas/server.js +4 -4
  29. package/dist/ui/canvas/server.js.map +1 -1
  30. package/dist/ui/canvas.cjs +4 -4
  31. package/dist/ui/canvas.cjs.map +1 -1
  32. package/dist/ui/canvas.d.cts +2 -2
  33. package/dist/ui/canvas.d.ts +2 -2
  34. package/dist/ui/canvas.js +4 -4
  35. package/dist/ui/canvas.js.map +1 -1
  36. package/dist/ui/form.d.cts +1 -1
  37. package/dist/ui/form.d.ts +1 -1
  38. package/dist/ui/video.d.cts +1 -1
  39. package/dist/ui/video.d.ts +1 -1
  40. package/dist/{webhook-D_7bYIKj.d.ts → webhook-CmJWfLjs.d.ts} +2 -2
  41. package/dist/{webhook-C1q3iC6W.d.cts → webhook-IX2MGQj2.d.cts} +2 -2
  42. package/dist/webhook.d.cts +3 -3
  43. package/dist/webhook.d.ts +3 -3
  44. package/package.json +11 -1
package/README.md CHANGED
@@ -21,10 +21,18 @@ pnpm add @01.software/sdk
21
21
  - Automatic retry with exponential backoff (non-retryable: 401, 403, 404, 422)
22
22
  - Webhook handling with HMAC-SHA256 signature verification
23
23
  - Sub-path imports (`./webhook`, `./realtime`, `./ui/*`) for tree-shaking
24
- - Type-safe read-only `from()` for Client (compile-time write prevention)
24
+ - Type-safe read-only `collections.from()` for Client (compile-time write prevention)
25
25
 
26
26
  ### Sub-path Imports
27
27
 
28
+ ```typescript
29
+ // Analytics — browser pageview tracking + custom events
30
+ import { createAnalytics } from '@01.software/sdk/analytics'
31
+ const analytics = createAnalytics({ publishableKey: 'pk_xxx' })
32
+ // auto-tracks pageviews; call analytics.pageview('/custom-path') manually if needed
33
+ analytics.track('signup', { plan: 'pro', trial: false }) // custom event with optional props
34
+ ```
35
+
28
36
  ```typescript
29
37
  // Main entry - clients, query builder, hooks, utilities
30
38
  import { createClient, createServerClient } from '@01.software/sdk'
@@ -59,7 +67,7 @@ const client = createClient({
59
67
  })
60
68
 
61
69
  // Query data (returns Payload native response)
62
- const { docs } = await client.from('products').find({
70
+ const { docs } = await client.collections.from('products').find({
63
71
  limit: 10,
64
72
  where: { status: { equals: 'published' } },
65
73
  })
@@ -76,18 +84,18 @@ const client = createServerClient({
76
84
  })
77
85
 
78
86
  // Create order (server only)
79
- const order = await client.api.createOrder({
87
+ const order = await server.commerce.orders.create({
80
88
  orderNumber: generateOrderNumber(),
81
89
  customerSnapshot: { email: 'user@example.com' },
82
90
  shippingAddress: { recipientName: 'John', phone: '010-1234-5678', postalCode: '12345', address1: 'Seoul', address2: 'Apt 101' },
83
- orderProducts: [...],
91
+ orderItems: [...],
84
92
  totalAmount: 10000,
85
- paymentId: 'pay_123', // optional (omit for free orders)
93
+ pgPaymentId: 'pay_123', // optional (omit for free orders)
86
94
  discountCode: 'WELCOME10', // optional
87
95
  })
88
96
 
89
97
  // SSR prefetch (server)
90
- await client.query.prefetchQuery({
98
+ await server.query.prefetchQuery({
91
99
  collection: 'products',
92
100
  options: { limit: 10 },
93
101
  })
@@ -101,6 +109,12 @@ await client.query.prefetchQuery({
101
109
  const client = createClient({
102
110
  publishableKey: string, // Required
103
111
  })
112
+
113
+ const server = createServerClient({
114
+ publishableKey: string,
115
+ secretKey: string, // sk01_... or pat01_...
116
+ tenantId: string, // required when secretKey is pat01_
117
+ })
104
118
  ```
105
119
 
106
120
  | Option | Type | Description |
@@ -115,13 +129,13 @@ API URL은 환경변수로 오버라이드 가능합니다:
115
129
 
116
130
  ### Query Builder
117
131
 
118
- Access collections via `client.from(collection)` method.
132
+ Access collections via `client.collections.from(slug)`.
119
133
 
120
- > **Note:** `Client.from()` returns a `ReadOnlyQueryBuilder` (only `find`, `findById`, `count`, `findMetadata`, `findMetadataById`). Write operations (`create`, `update`, `remove`, `updateMany`, `removeMany`) are only available on `ServerClient.from()`.
134
+ > **Note:** `client.collections.from()` returns a `ReadOnlyQueryBuilder` (only `find`, `findById`, `count`, `findMetadata`, `findMetadataById`). Write operations (`create`, `update`, `remove`, `updateMany`, `removeMany`) are only available on `server.collections.from()`.
121
135
 
122
136
  ```typescript
123
137
  // List query - returns PayloadFindResponse
124
- const { docs, totalDocs, hasNextPage } = await client.from('products').find({
138
+ const { docs, totalDocs, hasNextPage } = await client.collections.from('products').find({
125
139
  limit: 20,
126
140
  page: 1,
127
141
  sort: '-createdAt',
@@ -131,57 +145,57 @@ const { docs, totalDocs, hasNextPage } = await client.from('products').find({
131
145
  })
132
146
 
133
147
  // Query with populate/joins control
134
- const { docs } = await client.from('products').find({
148
+ const { docs } = await client.collections.from('products').find({
135
149
  select: { title: true, slug: true, price: true, thumbnail: true },
136
150
  joins: false, // disable joins for lightweight list
137
151
  })
138
152
 
139
153
  // Override relationship populate
140
- const product = await client.from('products').findById(id, {
154
+ const product = await client.collections.from('products').findById(id, {
141
155
  populate: { brands: { name: true, logo: true } },
142
156
  joins: { variants: { limit: 50 } },
143
157
  })
144
158
 
145
159
  // Single item query - returns document directly
146
- const product = await client.from('products').findById('id')
160
+ const product = await client.collections.from('products').findById('id')
147
161
 
148
162
  // Create (server only) - returns PayloadMutationResponse
149
- const { doc, message } = await client
163
+ const { doc, message } = await server.collections
150
164
  .from('products')
151
165
  .create({ name: 'Product' })
152
166
 
153
167
  // Create with file upload (server only) - uses multipart/form-data
154
- const { doc } = await client
168
+ const { doc } = await server.collections
155
169
  .from('images')
156
170
  .create({ alt: 'Hero image' }, { file: imageFile, filename: 'hero.jpg' })
157
171
 
158
172
  // Update (server only) - returns PayloadMutationResponse
159
- const { doc } = await client.from('products').update('id', { name: 'Updated' })
173
+ const { doc } = await server.collections.from('products').update('id', { name: 'Updated' })
160
174
 
161
175
  // Update with file replacement (server only)
162
- await client.from('images').update('id', { alt: 'New alt' }, { file: newFile })
176
+ await server.collections.from('images').update('id', { alt: 'New alt' }, { file: newFile })
163
177
 
164
178
  // Delete (server only) - returns document directly
165
- const deletedDoc = await client.from('products').remove('id')
179
+ const deletedDoc = await server.collections.from('products').remove('id')
166
180
 
167
181
  // Count
168
- const { totalDocs } = await client.from('products').count()
182
+ const { totalDocs } = await client.collections.from('products').count()
169
183
 
170
184
  // SEO Metadata (fetch + generate in one call, depth: 1 auto-applied)
171
- const metadata = await client
185
+ const metadata = await client.collections
172
186
  .from('products')
173
187
  .findMetadata(
174
188
  { where: { slug: { equals: 'my-product' } } },
175
189
  { siteName: 'My Store' },
176
190
  )
177
191
 
178
- const metadataById = await client.from('products').findMetadataById('id', {
192
+ const metadataById = await client.collections.from('products').findMetadataById('id', {
179
193
  siteName: 'My Store',
180
194
  })
181
195
 
182
196
  // Bulk operations (server only)
183
- await client.from('products').updateMany(where, data)
184
- await client.from('products').removeMany(where)
197
+ await server.collections.from('products').updateMany(where, data)
198
+ await server.collections.from('products').removeMany(where)
185
199
  ```
186
200
 
187
201
  ### API Response Types (Payload Native)
@@ -303,7 +317,7 @@ client.query.setCustomerData(profile)
303
317
 
304
318
  ### Customer Auth
305
319
 
306
- Available on Client via `client.customer.*`.
320
+ Available on Client via `client.customer.auth.*`.
307
321
 
308
322
  ```typescript
309
323
  const client = createClient({
@@ -312,83 +326,114 @@ const client = createClient({
312
326
  })
313
327
 
314
328
  // Register & login
315
- const { customer } = await client.customer.register({
329
+ const { customer } = await client.customer.auth.register({
316
330
  name: 'John',
317
331
  email: 'john@example.com',
318
332
  password: 'secure123',
319
333
  })
320
- const { token, customer } = await client.customer.login({
334
+ const { token, customer } = await client.customer.auth.login({
321
335
  email: 'john@example.com',
322
336
  password: 'secure123',
323
337
  })
324
338
 
325
339
  // Profile & token management
326
- const profile = await client.customer.me()
327
- client.customer.isAuthenticated()
328
- client.customer.logout()
340
+ const profile = await client.customer.auth.me()
341
+ client.customer.auth.isAuthenticated()
342
+ client.customer.auth.logout()
329
343
 
330
- // Orders
331
- const orders = await client.customer.getMyOrders({
344
+ // Authenticated customer's own orders (Client-only)
345
+ const orders = await client.commerce.orders.listMine({
332
346
  page: 1,
333
347
  limit: 10,
334
348
  status: 'paid',
335
349
  })
336
350
 
337
351
  // Password & email
338
- await client.customer.forgotPassword('john@example.com')
339
- await client.customer.resetPassword(token, newPassword)
340
- await client.customer.changePassword(currentPassword, newPassword)
341
- await client.customer.verifyEmail(verificationToken)
352
+ await client.customer.auth.forgotPassword('john@example.com')
353
+ await client.customer.auth.resetPassword(token, newPassword)
354
+ await client.customer.auth.changePassword(currentPassword, newPassword)
355
+ await client.customer.auth.verifyEmail(verificationToken)
342
356
  ```
343
357
 
344
- ### Server API
358
+ ### Commerce Orders (ServerClient-only writes)
345
359
 
346
- Available only in ServerClient via `client.api.*`.
360
+ Available on ServerClient via `server.commerce.orders.*`. Only `checkout` and `listMine` are also on Client.
347
361
 
348
362
  ```typescript
349
363
  // Orders
350
- await client.api.createOrder(params)
351
- await client.api.updateOrder({ orderNumber, status })
352
- await client.api.getOrder({ orderNumber })
353
- await client.api.checkout({ cartId, paymentId, orderNumber, customerSnapshot, discountCode? })
364
+ await server.commerce.orders.create(params)
365
+ await server.commerce.orders.update({ orderNumber, status })
366
+ await server.commerce.orders.checkout({ cartId, pgPaymentId?, orderNumber, customerSnapshot, discountCode? })
367
+
368
+ // Single-order lookup (getOrder endpoint removed — use collection find)
369
+ const { docs: [order] } = await server.collections.from('orders').find({
370
+ where: { orderNumber: { equals: 'ORD-...' } },
371
+ limit: 1,
372
+ depth: 1,
373
+ })
354
374
 
355
375
  // Fulfillment & transactions
356
- await client.api.createFulfillment({ orderNumber, carrier, trackingNumber, items })
357
- await client.api.updateTransaction({ paymentId, status, paymentMethod, receiptUrl })
376
+ await server.commerce.orders.createFulfillment({ orderNumber, carrier, trackingNumber, items })
377
+ await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier?, trackingNumber? }] })
378
+ await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })
358
379
 
359
380
  // Returns
360
- await client.api.createReturn({ orderNumber, returnProducts, refundAmount, reason? })
361
- await client.api.updateReturn({ returnId, status })
362
- await client.api.returnWithRefund({ orderNumber, returnProducts, refundAmount, paymentId })
381
+ await server.commerce.orders.createReturn({ orderNumber, returnItems, refundAmount, reason? })
382
+ await server.commerce.orders.updateReturn({ returnId, status })
383
+ await server.commerce.orders.returnWithRefund({ orderNumber, returnItems, refundAmount, pgPaymentId })
363
384
  ```
364
385
 
365
- ### Product API
386
+ ### Commerce Discounts / Shipping
366
387
 
367
- Available only in ServerClient via `client.product.*`.
388
+ Available on both Client (customer JWT) and ServerClient (secretKey).
368
389
 
369
390
  ```typescript
370
- // Batch stock check
371
- const { results, allAvailable } = await client.product.stockCheck({
391
+ await client.commerce.discounts.validate({ code, orderAmount })
392
+ await client.commerce.shipping.calculate({ shippingPolicyId?, orderAmount, postalCode? })
393
+ // calculateShipping returns: { shippingAmount, baseShippingAmount, extraShippingAmount, freeShipping, freeShippingMinAmount, isJeju, isRemoteIsland }
394
+ ```
395
+
396
+ ### Commerce Product
397
+
398
+ Available on both Client and ServerClient via `commerce.product.*`.
399
+
400
+ ```typescript
401
+ // Batch stock check (point-in-time read, NOT a reservation)
402
+ const { results, allAvailable } = await client.commerce.product.stockCheck({
372
403
  items: [
373
- { optionId: 1, quantity: 2 },
374
- { optionId: 3, quantity: 1 },
404
+ { variantId: '...', quantity: 2 },
375
405
  ],
376
406
  })
377
407
  ```
378
408
 
379
- ### Cart API
409
+ ### Commerce Cart
380
410
 
381
- Available in both ServerClient and Client via `client.cart.*`.
411
+ Available on both Client and ServerClient via `commerce.cart.*`.
382
412
 
383
413
  ```typescript
384
414
  // Add item to cart
385
- await client.cart.addItem({ cartId, product, variant, option, quantity })
415
+ await client.commerce.cart.addItem({ cartId, product, variant, option, quantity })
386
416
 
387
417
  // Update item quantity
388
- await client.cart.updateItem({ cartItemId, quantity })
418
+ await client.commerce.cart.updateItem({ cartItemId, quantity })
389
419
 
390
420
  // Remove item from cart
391
- await client.cart.removeItem({ cartItemId })
421
+ await client.commerce.cart.removeItem({ cartItemId })
422
+
423
+ // Other cart operations
424
+ await client.commerce.cart.get(cartId)
425
+ await client.commerce.cart.applyDiscount({ cartId, discountCode })
426
+ await client.commerce.cart.removeDiscount({ cartId })
427
+ await client.commerce.cart.clear({ cartId })
428
+ ```
429
+
430
+ ### Community Moderation (ServerClient-only)
431
+
432
+ Available only on ServerClient via `server.community.moderation.*`.
433
+
434
+ ```typescript
435
+ await server.community.moderation.banCustomer({ customerId, isPermanent?, bannedUntil?, reason? })
436
+ await server.community.moderation.unbanCustomer({ customerId })
392
437
  ```
393
438
 
394
439
  ### Webhook
@@ -520,7 +565,7 @@ The SDK throws typed errors instead of returning error responses:
520
565
  import { isNetworkError, isApiError, isValidationError } from '@01.software/sdk'
521
566
 
522
567
  try {
523
- const { docs } = await client.from('products').find()
568
+ const { docs } = await client.collections.from('products').find()
524
569
  } catch (error) {
525
570
  if (isNetworkError(error)) {
526
571
  console.error('Network issue:', error.message)
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/analytics.ts
21
+ var analytics_exports = {};
22
+ __export(analytics_exports, {
23
+ createAnalytics: () => createAnalytics
24
+ });
25
+ module.exports = __toCommonJS(analytics_exports);
26
+
27
+ // src/core/client/types.ts
28
+ function resolveApiUrl() {
29
+ if (typeof process !== "undefined" && process.env) {
30
+ const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
31
+ if (envUrl) {
32
+ return envUrl.replace(/\/$/, "");
33
+ }
34
+ }
35
+ return "https://api.01.software";
36
+ }
37
+
38
+ // src/analytics.ts
39
+ function createAnalytics(config) {
40
+ if (typeof window === "undefined") {
41
+ return { pageview() {
42
+ }, track() {
43
+ }, destroy() {
44
+ } };
45
+ }
46
+ const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
47
+ const respectDnt = config.respectDnt !== false;
48
+ function isDntActive() {
49
+ if (!respectDnt) return false;
50
+ const nav = navigator;
51
+ return nav.doNotTrack === "1" || nav.globalPrivacyControl === true;
52
+ }
53
+ let lastPath = null;
54
+ let lastAt = 0;
55
+ const autoTrack = config.autoTrack !== false;
56
+ const originalPushState = history.pushState;
57
+ const originalReplaceState = history.replaceState;
58
+ let destroyed = false;
59
+ function newEventId() {
60
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : String(Date.now()) + String(Math.random());
61
+ }
62
+ function sendBeaconOrFetch(body) {
63
+ try {
64
+ if (typeof navigator.sendBeacon === "function") {
65
+ const blob = new Blob([body], { type: "text/plain" });
66
+ const sent = navigator.sendBeacon(endpoint, blob);
67
+ if (sent) return;
68
+ }
69
+ fetch(endpoint, {
70
+ method: "POST",
71
+ keepalive: true,
72
+ headers: { "Content-Type": "application/json" },
73
+ body
74
+ }).catch(() => {
75
+ });
76
+ } catch {
77
+ }
78
+ }
79
+ function sendPageview(pathname) {
80
+ if (isDntActive()) return;
81
+ const doc = document;
82
+ if (doc.prerendering === true || document.visibilityState === "prerender") return;
83
+ const now = Date.now();
84
+ if (pathname === lastPath && now - lastAt < 500) return;
85
+ lastPath = pathname;
86
+ lastAt = now;
87
+ const body = JSON.stringify({
88
+ publishableKey: config.publishableKey,
89
+ pathname,
90
+ referrer: document.referrer || "",
91
+ eventId: newEventId()
92
+ });
93
+ sendBeaconOrFetch(body);
94
+ }
95
+ function trackCurrentPath() {
96
+ if (destroyed) return;
97
+ sendPageview(location.pathname);
98
+ }
99
+ function patchedPushState(data, unused, url) {
100
+ originalPushState.apply(this, [data, unused, url]);
101
+ if (!destroyed) setTimeout(trackCurrentPath, 0);
102
+ }
103
+ function patchedReplaceState(data, unused, url) {
104
+ originalReplaceState.apply(this, [data, unused, url]);
105
+ if (!destroyed) setTimeout(trackCurrentPath, 0);
106
+ }
107
+ if (autoTrack) {
108
+ history.pushState = patchedPushState;
109
+ history.replaceState = patchedReplaceState;
110
+ window.addEventListener("popstate", trackCurrentPath);
111
+ if (document.readyState === "complete") {
112
+ trackCurrentPath();
113
+ } else {
114
+ window.addEventListener("load", trackCurrentPath, { once: true });
115
+ }
116
+ }
117
+ const isProduction = (() => {
118
+ try {
119
+ const hostname = location.hostname;
120
+ return hostname !== "localhost" && hostname !== "127.0.0.1" && !hostname.endsWith(".local");
121
+ } catch {
122
+ return true;
123
+ }
124
+ })();
125
+ const warnedReasons = /* @__PURE__ */ new Set();
126
+ function devWarn(name, reason) {
127
+ if (isProduction) return;
128
+ if (warnedReasons.has(reason)) return;
129
+ warnedReasons.add(reason);
130
+ console.warn(`[01 analytics] dropped event ${name}: ${reason}`);
131
+ }
132
+ const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/;
133
+ const RESERVED_PREFIXES = ["__", "_pv_"];
134
+ function validateEventName(name) {
135
+ if (!name || typeof name !== "string") return "name-empty";
136
+ for (const prefix of RESERVED_PREFIXES) {
137
+ if (name.startsWith(prefix)) return "name-reserved";
138
+ }
139
+ if (!EVENT_NAME_RE.test(name)) return "name-regex";
140
+ return null;
141
+ }
142
+ const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/;
143
+ function validateEventProps(props) {
144
+ if (props === void 0 || props === null) return null;
145
+ if (typeof props !== "object" || Array.isArray(props)) return "props-value-type";
146
+ const keys = Object.keys(props);
147
+ if (keys.length > 10) return "props-too-many-keys";
148
+ for (const k of keys) {
149
+ const v = props[k];
150
+ if (!PROP_KEY_RE.test(k)) return "props-key-regex";
151
+ if (typeof v === "string") {
152
+ if (v.length > 80) return "props-value-too-long";
153
+ } else if (typeof v === "number") {
154
+ if (!isFinite(v)) return "props-value-not-finite";
155
+ } else if (typeof v === "boolean") {
156
+ } else {
157
+ return "props-value-type";
158
+ }
159
+ }
160
+ return null;
161
+ }
162
+ return {
163
+ pageview(path) {
164
+ if (destroyed) return;
165
+ sendPageview(path ?? location.pathname);
166
+ },
167
+ track(name, props) {
168
+ if (destroyed) return;
169
+ if (isDntActive()) return;
170
+ const doc = document;
171
+ if (doc.prerendering === true || document.visibilityState === "prerender") return;
172
+ const nameErr = validateEventName(name);
173
+ if (nameErr) {
174
+ devWarn(name, nameErr);
175
+ return;
176
+ }
177
+ if (props !== void 0) {
178
+ const propsErr = validateEventProps(props);
179
+ if (propsErr) {
180
+ devWarn(name, propsErr);
181
+ return;
182
+ }
183
+ }
184
+ const body = JSON.stringify({
185
+ publishableKey: config.publishableKey,
186
+ pathname: location.pathname,
187
+ referrer: document.referrer || "",
188
+ eventId: newEventId(),
189
+ eventName: name,
190
+ eventProps: props
191
+ });
192
+ sendBeaconOrFetch(body);
193
+ },
194
+ destroy() {
195
+ if (destroyed) return;
196
+ destroyed = true;
197
+ if (autoTrack) {
198
+ history.pushState = originalPushState;
199
+ history.replaceState = originalReplaceState;
200
+ window.removeEventListener("popstate", trackCurrentPath);
201
+ }
202
+ lastPath = null;
203
+ lastAt = 0;
204
+ }
205
+ };
206
+ }
207
+ //# sourceMappingURL=analytics.cjs.map
@@ -0,0 +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 * 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 })\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 })\n\n sendBeaconOrFetch(body)\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n if (autoTrack) {\n // Restore original history methods\n history.pushState = originalPushState\n history.replaceState = originalReplaceState\n window.removeEventListener('popstate', trackCurrentPath)\n }\n\n // Null out dedup state\n lastPath = null\n lastAt = 0\n },\n }\n}\n","import type { Sort, Where } from 'payload'\n\nimport type { Collection, PublicCollection } from '../collection/const'\n\nexport type { Collection, PublicCollection }\n\n// ============================================================================\n// API URL Configuration\n// ============================================================================\n\ndeclare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(): string {\n if (typeof process !== 'undefined' && process.env) {\n const envUrl =\n process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL\n if (envUrl) {\n return envUrl.replace(/\\/$/, '')\n }\n }\n return __DEFAULT_API_URL__\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n publishableKey: string\n /**\n * Customer authentication options.\n * Used to initialize CustomerAuth on Client.\n */\n customer?: {\n /**\n * Persist token in localStorage. Defaults to `true`.\n * - `true` (default): uses key `'customer-token'`\n * - `string`: uses the given string as localStorage key\n * - `false`: disables persistence (token/onTokenChange used instead)\n *\n * Handles SSR safely (no-op on server).\n * When enabled, `token` and `onTokenChange` are ignored.\n */\n persist?: boolean | string\n /** Initial token (e.g. from SSR cookie) */\n token?: string\n /** Called when token changes (login/logout) — use to persist in localStorage/cookie */\n onTokenChange?: (token: string | null) => void\n }\n}\n\n// Server client: requires both publishableKey (for CDN routing + rate limit +\n// monthly quota enforcement via the edge proxy) and secretKey (sk01_ opaque\n// bearer token, the authentication credential).\n// The proxy keys its tenant lookup off `X-Publishable-Key`, so omitting\n// publishableKey would silently bypass rate limiting and plan-based quota\n// enforcement.\nexport interface ClientServerConfig extends ClientConfig {\n secretKey: string\n /**\n * Tenant ID for pat01_ API keys. When provided alongside a pat01_ key,\n * every server request includes X-Tenant-Id so the API can scope the\n * request to the correct tenant. Not needed for sk01_ keys (tenant is\n * encoded in the key itself).\n */\n tenantId?: string\n}\n\n\nexport interface ClientMetadata {\n userAgent?: string\n timestamp: number\n}\n\nexport interface ClientState {\n metadata: ClientMetadata\n}\n\nexport interface PaginationMeta {\n page: number\n limit: number\n totalDocs: number\n totalPages: number\n hasNextPage: boolean\n hasPrevPage: boolean\n pagingCounter: number\n prevPage: number | null\n nextPage: number | null\n}\n\n// ============================================================================\n// Payload CMS Native Response Types\n// ============================================================================\n\n/**\n * Payload CMS Find (List) Response\n * GET /api/{collection}\n */\nexport interface PayloadFindResponse<T = unknown> {\n docs: T[]\n totalDocs: number\n limit: number\n totalPages: number\n page: number\n pagingCounter: number\n hasPrevPage: boolean\n hasNextPage: boolean\n prevPage: number | null\n nextPage: number | null\n}\n\n/**\n * Payload CMS Create/Update Response\n * POST /api/{collection}\n * PATCH /api/{collection}/{id}\n */\nexport interface PayloadMutationResponse<T = unknown> {\n message: string\n doc: T\n errors?: unknown[]\n}\n\n// ============================================================================\n// Query Options\n// ============================================================================\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload`. Payload's generic\n * types (`JoinQuery<TSlug>`, `PopulateType`) depend on `PayloadTypes` module\n * augmentation; external SDK consumers who skip that get degenerate types\n * (`never` / `{}`). Only non-generic `Sort`/`Where` are safe to import.\n * Excluded vs native: Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n where?: Where\n depth?: number\n select?: Record<string, boolean>\n /** Per-collection field selection for populated relationships (keyed by collection slug) */\n populate?: Record<string, boolean | Record<string, boolean>>\n /** Join field control: pagination/filter per join, or false to disable */\n joins?:\n | Record<\n string,\n | {\n limit?: number\n page?: number\n sort?: string\n where?: Where\n count?: boolean\n }\n | false\n >\n | false\n /** Set to `false` to skip the count query — returns docs without totalDocs/totalPages */\n pagination?: boolean\n /** Include draft versions (access control still applies on the server) */\n draft?: boolean\n /** Include soft-deleted documents (requires `trash` enabled on the collection) */\n trash?: boolean\n}\n\n// ============================================================================\n// Debug & Retry Configuration\n// ============================================================================\n\nexport interface DebugConfig {\n logRequests?: boolean\n logResponses?: boolean\n logErrors?: boolean\n}\n\nexport interface RetryConfig {\n maxRetries?: number\n retryableStatuses?: number[]\n retryDelay?: (attempt: number) => number\n}\n\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n\nexport type ExtractArrayType<T> = T extends (infer U)[] ? U : never\n"],"mappings":"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","names":[]}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @01.software/sdk — Analytics Helper
3
+ */
4
+ interface AnalyticsConfig {
5
+ publishableKey: string;
6
+ /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */
7
+ endpoint?: string;
8
+ /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */
9
+ autoTrack?: boolean;
10
+ /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */
11
+ respectDnt?: boolean;
12
+ }
13
+ interface Analytics {
14
+ pageview(path?: string): void;
15
+ track(name: string, props?: Record<string, string | number | boolean>): void;
16
+ destroy(): void;
17
+ }
18
+ declare function createAnalytics(config: AnalyticsConfig): Analytics;
19
+
20
+ export { type Analytics, type AnalyticsConfig, createAnalytics };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @01.software/sdk — Analytics Helper
3
+ */
4
+ interface AnalyticsConfig {
5
+ publishableKey: string;
6
+ /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */
7
+ endpoint?: string;
8
+ /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */
9
+ autoTrack?: boolean;
10
+ /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */
11
+ respectDnt?: boolean;
12
+ }
13
+ interface Analytics {
14
+ pageview(path?: string): void;
15
+ track(name: string, props?: Record<string, string | number | boolean>): void;
16
+ destroy(): void;
17
+ }
18
+ declare function createAnalytics(config: AnalyticsConfig): Analytics;
19
+
20
+ export { type Analytics, type AnalyticsConfig, createAnalytics };