@rotateprotocol/sdk 1.0.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 (74) hide show
  1. package/README.md +453 -0
  2. package/dist/catalog.d.ts +112 -0
  3. package/dist/catalog.d.ts.map +1 -0
  4. package/dist/catalog.js +210 -0
  5. package/dist/catalog.js.map +1 -0
  6. package/dist/components/CheckoutForm.d.ts +86 -0
  7. package/dist/components/CheckoutForm.d.ts.map +1 -0
  8. package/dist/components/CheckoutForm.js +332 -0
  9. package/dist/components/CheckoutForm.js.map +1 -0
  10. package/dist/components/HostedCheckout.d.ts +57 -0
  11. package/dist/components/HostedCheckout.d.ts.map +1 -0
  12. package/dist/components/HostedCheckout.js +414 -0
  13. package/dist/components/HostedCheckout.js.map +1 -0
  14. package/dist/components/PaymentButton.d.ts +80 -0
  15. package/dist/components/PaymentButton.d.ts.map +1 -0
  16. package/dist/components/PaymentButton.js +210 -0
  17. package/dist/components/PaymentButton.js.map +1 -0
  18. package/dist/components/RotateProvider.d.ts +115 -0
  19. package/dist/components/RotateProvider.d.ts.map +1 -0
  20. package/dist/components/RotateProvider.js +264 -0
  21. package/dist/components/RotateProvider.js.map +1 -0
  22. package/dist/components/index.d.ts +17 -0
  23. package/dist/components/index.d.ts.map +1 -0
  24. package/dist/components/index.js +27 -0
  25. package/dist/components/index.js.map +1 -0
  26. package/dist/embed.d.ts +85 -0
  27. package/dist/embed.d.ts.map +1 -0
  28. package/dist/embed.js +313 -0
  29. package/dist/embed.js.map +1 -0
  30. package/dist/hooks.d.ts +156 -0
  31. package/dist/hooks.d.ts.map +1 -0
  32. package/dist/hooks.js +280 -0
  33. package/dist/hooks.js.map +1 -0
  34. package/dist/idl/rotate_connect.json +2572 -0
  35. package/dist/index.d.ts +505 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +1197 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/marketplace.d.ts +257 -0
  40. package/dist/marketplace.d.ts.map +1 -0
  41. package/dist/marketplace.js +433 -0
  42. package/dist/marketplace.js.map +1 -0
  43. package/dist/platform.d.ts +234 -0
  44. package/dist/platform.d.ts.map +1 -0
  45. package/dist/platform.js +268 -0
  46. package/dist/platform.js.map +1 -0
  47. package/dist/react.d.ts +140 -0
  48. package/dist/react.d.ts.map +1 -0
  49. package/dist/react.js +429 -0
  50. package/dist/react.js.map +1 -0
  51. package/dist/store.d.ts +213 -0
  52. package/dist/store.d.ts.map +1 -0
  53. package/dist/store.js +404 -0
  54. package/dist/store.js.map +1 -0
  55. package/dist/webhooks.d.ts +149 -0
  56. package/dist/webhooks.d.ts.map +1 -0
  57. package/dist/webhooks.js +371 -0
  58. package/dist/webhooks.js.map +1 -0
  59. package/package.json +114 -0
  60. package/src/catalog.ts +299 -0
  61. package/src/components/CheckoutForm.tsx +608 -0
  62. package/src/components/HostedCheckout.tsx +675 -0
  63. package/src/components/PaymentButton.tsx +348 -0
  64. package/src/components/RotateProvider.tsx +370 -0
  65. package/src/components/index.ts +26 -0
  66. package/src/embed.ts +408 -0
  67. package/src/hooks.ts +518 -0
  68. package/src/idl/rotate_connect.json +2572 -0
  69. package/src/index.ts +1538 -0
  70. package/src/marketplace.ts +642 -0
  71. package/src/platform.ts +403 -0
  72. package/src/react.ts +459 -0
  73. package/src/store.ts +577 -0
  74. package/src/webhooks.ts +506 -0
package/src/catalog.ts ADDED
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Rotate Catalog — Shared product catalog, inventory, and discount logic.
3
+ *
4
+ * Used internally by `RotateStore` and `RotateMarketplace` to avoid
5
+ * duplicating catalog management code. Not exported to SDK consumers —
6
+ * use `RotateStore` or `RotateMarketplace` instead.
7
+ *
8
+ * @internal
9
+ * @packageDocumentation
10
+ */
11
+
12
+ // ==================== TYPES ====================
13
+
14
+ /** Base product shape — extend this for domain-specific fields */
15
+ export interface BaseProduct {
16
+ id: string;
17
+ name: string;
18
+ description?: string;
19
+ price: number;
20
+ compareAtPrice?: number;
21
+ category?: string;
22
+ images?: string[];
23
+ inventory?: number;
24
+ active: boolean;
25
+ metadata?: Record<string, string>;
26
+ createdAt: number;
27
+ updatedAt: number;
28
+ }
29
+
30
+ /** Minimum required input to create a product */
31
+ export interface BaseProductInput {
32
+ id: string;
33
+ name: string;
34
+ description?: string;
35
+ price: number;
36
+ compareAtPrice?: number;
37
+ category?: string;
38
+ images?: string[];
39
+ inventory?: number;
40
+ active?: boolean;
41
+ metadata?: Record<string, string>;
42
+ }
43
+
44
+ /** Discount code */
45
+ export interface Discount {
46
+ code: string;
47
+ type: 'percent' | 'fixed';
48
+ value: number;
49
+ minAmount?: number;
50
+ maxUses?: number;
51
+ usedCount: number;
52
+ expiresAt?: number;
53
+ productIds?: string[];
54
+ active: boolean;
55
+ }
56
+
57
+ export interface DiscountInput {
58
+ code: string;
59
+ type: 'percent' | 'fixed';
60
+ value: number;
61
+ minAmount?: number;
62
+ maxUses?: number;
63
+ expiresAt?: number;
64
+ productIds?: string[];
65
+ active?: boolean;
66
+ }
67
+
68
+ /** Product filter options */
69
+ export interface ProductFilter {
70
+ category?: string;
71
+ active?: boolean;
72
+ search?: string;
73
+ minPrice?: number;
74
+ maxPrice?: number;
75
+ sortBy?: 'name' | 'price' | 'createdAt' | 'rating';
76
+ sortOrder?: 'asc' | 'desc';
77
+ limit?: number;
78
+ offset?: number;
79
+ }
80
+
81
+ // ==================== CATALOG ====================
82
+
83
+ /**
84
+ * Generic product catalog with inventory and discount management.
85
+ *
86
+ * @typeParam P - Product type (must extend BaseProduct).
87
+ * @typeParam I - Product input type (must extend BaseProductInput).
88
+ */
89
+ export class Catalog<P extends BaseProduct, I extends BaseProductInput> {
90
+ private products: Map<string, P> = new Map();
91
+ private discounts: Map<string, Discount> = new Map();
92
+ private categories: Set<string> = new Set();
93
+
94
+ /** Optional hook to transform an input into a full product. */
95
+ private createProduct: (input: I) => P;
96
+
97
+ constructor(createProduct: (input: I) => P) {
98
+ this.createProduct = createProduct;
99
+ }
100
+
101
+ // ==================== PRODUCT MANAGEMENT ====================
102
+
103
+ addProduct(input: I): P {
104
+ const product = this.createProduct(input);
105
+ this.products.set(product.id, product);
106
+ if (product.category) this.categories.add(product.category);
107
+ return product;
108
+ }
109
+
110
+ addProducts(inputs: I[]): P[] {
111
+ return inputs.map((p) => this.addProduct(p));
112
+ }
113
+
114
+ updateProduct(id: string, updates: Partial<I>): P {
115
+ const existing = this.products.get(id);
116
+ if (!existing) throw new Error(`Product '${id}' not found`);
117
+ const updated: P = {
118
+ ...existing,
119
+ ...updates,
120
+ id: existing.id, // immutable
121
+ updatedAt: Date.now(),
122
+ } as P;
123
+ this.products.set(id, updated);
124
+ if (updated.category) this.categories.add(updated.category);
125
+ return updated;
126
+ }
127
+
128
+ removeProduct(id: string): boolean {
129
+ return this.products.delete(id);
130
+ }
131
+
132
+ getProduct(id: string): P | undefined {
133
+ return this.products.get(id);
134
+ }
135
+
136
+ getProducts(filter?: ProductFilter): P[] {
137
+ let results = Array.from(this.products.values());
138
+
139
+ if (filter) {
140
+ if (filter.active !== undefined) {
141
+ results = results.filter((p) => p.active === filter.active);
142
+ }
143
+ if (filter.category) {
144
+ results = results.filter((p) => p.category === filter.category);
145
+ }
146
+ if (filter.search) {
147
+ const q = filter.search.toLowerCase();
148
+ results = results.filter(
149
+ (p) =>
150
+ p.name.toLowerCase().includes(q) ||
151
+ p.description?.toLowerCase().includes(q) ||
152
+ p.id.toLowerCase().includes(q),
153
+ );
154
+ }
155
+ if (filter.minPrice !== undefined) {
156
+ results = results.filter((p) => p.price >= filter.minPrice!);
157
+ }
158
+ if (filter.maxPrice !== undefined) {
159
+ results = results.filter((p) => p.price <= filter.maxPrice!);
160
+ }
161
+
162
+ const sortBy = filter.sortBy || 'createdAt';
163
+ const order = filter.sortOrder === 'desc' ? -1 : 1;
164
+ results.sort((a, b) => {
165
+ if (sortBy === 'name') return a.name.localeCompare(b.name) * order;
166
+ if (sortBy === 'price') return (a.price - b.price) * order;
167
+ return (a.createdAt - b.createdAt) * order;
168
+ });
169
+
170
+ if (filter.offset) results = results.slice(filter.offset);
171
+ if (filter.limit) results = results.slice(0, filter.limit);
172
+ }
173
+
174
+ return results;
175
+ }
176
+
177
+ getCategories(): string[] {
178
+ return Array.from(this.categories).sort();
179
+ }
180
+
181
+ get productCount(): number {
182
+ return this.products.size;
183
+ }
184
+
185
+ /** Direct access to the internal product map (for advanced filtering). */
186
+ entries(): IterableIterator<[string, P]> {
187
+ return this.products.entries();
188
+ }
189
+
190
+ values(): IterableIterator<P> {
191
+ return this.products.values();
192
+ }
193
+
194
+ // ==================== INVENTORY ====================
195
+
196
+ isInStock(productId: string, quantity: number = 1): boolean {
197
+ const product = this.products.get(productId);
198
+ if (!product || !product.active) return false;
199
+ if (product.inventory === undefined) return true;
200
+ return product.inventory >= quantity;
201
+ }
202
+
203
+ reserveInventory(productId: string, quantity: number): void {
204
+ const product = this.products.get(productId);
205
+ if (!product) throw new Error(`Product '${productId}' not found`);
206
+ if (product.inventory !== undefined) {
207
+ if (product.inventory < quantity) {
208
+ throw new Error(
209
+ `Insufficient inventory for '${productId}': need ${quantity}, have ${product.inventory}`,
210
+ );
211
+ }
212
+ product.inventory -= quantity;
213
+ product.updatedAt = Date.now();
214
+ }
215
+ }
216
+
217
+ releaseInventory(productId: string, quantity: number): void {
218
+ const product = this.products.get(productId);
219
+ if (!product) throw new Error(`Product '${productId}' not found`);
220
+ if (product.inventory !== undefined) {
221
+ product.inventory += quantity;
222
+ product.updatedAt = Date.now();
223
+ }
224
+ }
225
+
226
+ // ==================== DISCOUNTS ====================
227
+
228
+ addDiscount(input: DiscountInput): Discount {
229
+ const discount: Discount = {
230
+ ...input,
231
+ usedCount: 0,
232
+ active: input.active ?? true,
233
+ };
234
+ this.discounts.set(discount.code.toUpperCase(), discount);
235
+ return discount;
236
+ }
237
+
238
+ getDiscount(code: string): Discount | undefined {
239
+ return this.discounts.get(code.toUpperCase());
240
+ }
241
+
242
+ isDiscountValid(code: string, subtotal: number): boolean {
243
+ const d = this.discounts.get(code.toUpperCase());
244
+ if (!d || !d.active) return false;
245
+ if (d.expiresAt && Date.now() > d.expiresAt) return false;
246
+ if (d.maxUses !== undefined && d.usedCount >= d.maxUses) return false;
247
+ if (d.minAmount !== undefined && subtotal < d.minAmount) return false;
248
+ return true;
249
+ }
250
+
251
+ useDiscount(code: string): void {
252
+ const d = this.discounts.get(code.toUpperCase());
253
+ if (d) d.usedCount++;
254
+ }
255
+
256
+ // ==================== SERIALIZATION ====================
257
+
258
+ exportProducts(): P[] {
259
+ return Array.from(this.products.values());
260
+ }
261
+
262
+ exportDiscounts(): Discount[] {
263
+ return Array.from(this.discounts.values());
264
+ }
265
+
266
+ importProducts(products: P[]): void {
267
+ this.products.clear();
268
+ this.categories.clear();
269
+ for (const p of products) {
270
+ this.products.set(p.id, p);
271
+ if (p.category) this.categories.add(p.category);
272
+ }
273
+ }
274
+
275
+ importDiscounts(discounts: Discount[]): void {
276
+ this.discounts.clear();
277
+ for (const d of discounts) {
278
+ this.discounts.set(d.code.toUpperCase(), d);
279
+ }
280
+ }
281
+
282
+ clear(): void {
283
+ this.products.clear();
284
+ this.discounts.clear();
285
+ this.categories.clear();
286
+ }
287
+
288
+ /** Remove all products matching a predicate. Returns count removed. */
289
+ removeProductsWhere(predicate: (product: P) => boolean): number {
290
+ let removed = 0;
291
+ for (const [id, product] of this.products.entries()) {
292
+ if (predicate(product)) {
293
+ this.products.delete(id);
294
+ removed++;
295
+ }
296
+ }
297
+ return removed;
298
+ }
299
+ }