@scryme/sdk 9.71.2-next.0 → 9.72.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,171 +1,331 @@
1
1
  # Scryme V3 SDK
2
2
 
3
- The official TypeScript SDK for the **Scryme V3 API**—engineered for scalability, security, and developer convenience. This SDK provides complete, type-safe coverage of all Scryme V3 services, allowing developers to seamlessly integrate their applications with the Scryme ecosystem.
3
+ The official TypeScript SDK for the **Scryme V3 API**—engineered for scalability, security, type safety, and developer convenience. This SDK provides complete, type-safe coverage of all Scryme V3 services, allowing developers to seamlessly integrate storefronts, mobile apps, connected applications, and server-side workflows with the Scryme ecosystem.
4
4
 
5
5
  For the complete, interactive documentation, live sandbox playgrounds, and detailed OpenAPI schemas, please visit our documentation portal:
6
6
  👉 **[https://docs.scryme.tech](https://docs.scryme.tech)**
7
7
 
8
+ ---
9
+
8
10
  ## 🚀 Features
9
11
 
10
12
  - **End-to-End Type Safety**: Direct compilation from the core OpenAPI 3.0 specification.
11
- - **Axios-Based Client**: Built-in support for request/response interceptors, customizable base URLs, and timeout configurations.
12
13
  - **Strictly Isolated Client & Server SDKs**: Prevents request state and session pollution in multi-tenant environments.
13
- - **Comprehensive API Coverage**:
14
- - **Auth**: Token exchange (Client Credentials Flow) & OAuth2 proxy support.
15
- - **Inventory**: Stock queries, multi-branch listings, batch tracking (trace, split, merge), B2B availability checks, and integrity verify/fix logic.
16
- - **Orders & B2B**: Quote requests, quote-to-order conversions, and order management.
17
- - **CRM & Customers**: Customer registration, custom CRM definitions, custom fields, relationships, associations, notes, and activity timelines.
18
- - **Loyalty**: Loyalty status (tiers & points), voucher validation, and reward redemption.
19
- - **Finance**: Corporate expenses, utility account tracking, and petty cash fund management (allocations, transactions, top-ups).
20
- - **POS**: POS device provisioning, staff login, and petty cash expense logging.
21
- - **Members & Roles**: Staff rosters, custom roles, permission sets, departments, and attendance logging (check-in/check-out).
22
- - **Services & Bookings**: Resource utilization, booking funnel, public-facing OTP-verified booking, shifts, and materials consumption.
23
- - **Integrations & Webhooks**: Subscription management and Strapi E-commerce integration (storefront customer registration, storefront token exchange, sync queues).
14
+ - **Stateful Customer Engine & Auth**: Full support for customer registration (`signUp`), credential sign-in (`signIn`), proactive/reactive token refresh, active session tracking, multi-session revocation, and reactive React hooks (`useSession`).
15
+ - **Stateful Shopping Cart**: Dynamic `customerId` resolution, automatic line item difference calculations (`cart.update`), totals metrics, guest-to-customer cart deep-merging (`mergeGuestCart`), and multi-location checkout.
16
+ - **Axios-Based Network Layer**: Interceptor-based automatic token exchange, proactive/reactive expiration handling, and multi-tenant `orgSlug` auto-injection.
17
+ - **Comprehensive Domain Coverage**:
18
+ - **Auth & Customer Engine**: Customer login, token refresh, active session management, address directory, and client credentials flow.
19
+ - **Catalog & Services**: Products, services, CMS customizations, product reviews with auto-resolved customer context, service categories, resources, availability slots, and public OTP booking flows.
20
+ - **Orders & Cart**: Stateful shopping cart, sales orders, quote requests, B2B quotes, STK push payments, and checkout.
21
+ - **Inventory**: Stock queries, multi-branch listings, batch tracking (trace, split, merge), B2B availability checks, physical reconciliation, assemblies, and lead time/waste analysis.
22
+ - **CRM**: Custom CRM record definitions, custom fields, relationships, associations, notes logging, and timeline activities.
23
+ - **Loyalty**: Loyalty status (tiers & points), voucher validation, customer favorites, and reward redemption.
24
+ - **Finance & Accounting**: Invoices, corporate expenses, utility account tracking, petty cash funds, Profit & Loss reports, balance sheets, and cash flow analysis.
25
+ - **POS**: POS device provisioning, standalone POS keys, staff login, terminal sync, and petty cash expense logging.
26
+ - **Members & Roles**: Staff rosters, custom roles, permission sets, departments, invitations, and attendance logging (check-in/check-out).
27
+ - **Integrations & Webhooks**: Subscription management, Strapi E-commerce integration, and Windmill workflow callback orchestration.
24
28
 
25
29
  ---
26
30
 
27
31
  ## 📦 Installation
28
32
 
29
- To use the Scryme V3 SDK in your project, install it from the workspace repository:
33
+ Install `@scryme/sdk` into your project:
30
34
 
31
35
  ```bash
32
36
  pnpm add @scryme/sdk
37
+ # or
38
+ npm install @scryme/sdk
39
+ # or
40
+ yarn add @scryme/sdk
33
41
  ```
34
42
 
35
43
  ---
36
44
 
37
- ## 🔑 Client & Server SDK Isolation
45
+ ## 🔑 Client & Server SDK Architecture
38
46
 
39
- The SDK supports distinct, fully isolated client-side and server-side setup options to prevent request state and session pollution in multi-tenant environments.
47
+ The SDK provides distinct, fully isolated client-side (`@scryme/sdk/client`) and server-side (`@scryme/sdk/server`) constructors to prevent request state and session pollution in multi-tenant environments.
48
+
49
+ ---
40
50
 
41
- ### 🌐 Server-Side Setup (`@scryme/sdk/server`)
42
- Strictly isolates requests and Axios instances. Ideal for Next.js API routes, edge functions, backend microservices, or Windmill workflows.
51
+ ## 📱 Client-Side Setup (`@scryme/sdk/client`)
43
52
 
44
- #### Class-Based Constructor (`ScrymeServerSDK`)
45
- Requires `clientId`, `clientSecret`, and `orgSlug` strictly to initialize correctly:
53
+ The `ScrymeClientSDK` provides stateful session persistence (`localStorage` or custom `StorageProvider`), reactive auth event listeners, and stateful customer cart operations.
54
+
55
+ ### Initialization
46
56
 
47
57
  ```typescript
48
- import { ScrymeServerSDK } from "@scryme/sdk/server";
58
+ import { ScrymeClientSDK, createClientSDK } from "@scryme/sdk/client";
49
59
 
50
- const scrymeServer = new ScrymeServerSDK({
51
- baseURL: "https://api.scryme.tech",
52
- orgSlug: "your-org-slug", // Automatic orgSlug injection on all API calls!
53
- clientId: "your_client_id_123",
54
- clientSecret: "your_client_secret_456",
60
+ // Class-based constructor
61
+ const scrymeClient = new ScrymeClientSDK({
62
+ clientId: "storefront_client_id",
63
+ orgSlug: "my-organization",
64
+ baseURL: "https://api.scryme.tech", // Optional, defaults to https://api.scryme.tech
55
65
  });
56
66
 
57
- async function run() {
58
- // 1. Call APIs directly—the SDK handles token retrieval, refresh, and auto-injection of orgSlug automatically!
59
- const products = await scrymeServer.catalog.getProducts({ limit: 10 });
60
- console.log("Server Products:", products.data);
67
+ // Or using factory helper
68
+ const scrymeClientAlt = createClientSDK({
69
+ clientId: "storefront_client_id",
70
+ orgSlug: "my-organization",
71
+ });
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 👤 Customer Authentication & Session Engine (`scrymeClient.customer.auth`)
77
+
78
+ `ScrymeClientSDK` includes a stateful Customer Authentication engine that manages customer login credentials, local session storage, JWT auto-refresh, active session tracking, and reactive UI hooks.
79
+
80
+ ### Customer Registration & Sign-Up (`signUp`)
81
+
82
+ Registers a new customer profile. If a `password` is provided, `signUp` automatically signs the customer in and initializes an active customer session:
83
+
84
+ ```typescript
85
+ const response = await scrymeClient.customer.auth.signUp({
86
+ name: "Jane Smith",
87
+ email: "jane.smith@example.com",
88
+ password: "securepassword123", // Establishes customer login credentials
89
+ phone: "+254700000123",
90
+ company: "Acme Commerce Inc",
91
+ customerType: "B2B_PREMIUM",
92
+ taxId: "PIN-KRA-123456",
93
+ address: {
94
+ label: "Headquarters",
95
+ street1: "123 Commercial Way",
96
+ city: "Nairobi",
97
+ country: "Kenya",
98
+ isDefault: true,
99
+ },
100
+ });
101
+
102
+ console.log("Customer registered:", response.data);
103
+ ```
104
+
105
+ ### Customer Sign-In (`signIn`)
106
+
107
+ Authenticates a customer using email and password, starting an active session in Redis, saving the JWT token to storage, and firing a `SIGNED_IN` event:
108
+
109
+ ```typescript
110
+ try {
111
+ const authResponse = await scrymeClient.customer.auth.signIn({
112
+ email: "jane.smith@example.com",
113
+ password: "securepassword123",
114
+ });
115
+
116
+ console.log("Bearer Token:", authResponse.token);
117
+ console.log("Session Metadata:", authResponse.session);
118
+ console.log("Customer Profile:", authResponse.user);
119
+ } catch (error) {
120
+ console.error("Login failed:", error);
61
121
  }
62
122
  ```
63
123
 
64
- Or use the helper factory `createServerSDK`, which provides fallback defaults:
124
+ ### Sign-Out (`signOut`)
125
+
126
+ Clears persisted session tokens, resets memory state, and fires a `SIGNED_OUT` event:
65
127
 
66
128
  ```typescript
67
- import { createServerSDK } from "@scryme/sdk/server";
129
+ await scrymeClient.customer.auth.signOut();
130
+ ```
68
131
 
69
- const scrymeServer = createServerSDK({
70
- baseURL: "https://api.scryme.tech",
71
- orgSlug: "your-org-slug",
72
- clientId: "your_client_id_123",
73
- clientSecret: "your_client_secret_456",
132
+ ### Session Refresh & Inspection
133
+
134
+ ```typescript
135
+ // Explicitly refresh the current customer session
136
+ const freshSession = await scrymeClient.customer.auth.refreshSession();
137
+
138
+ // Get internal session state synchronously/asynchronously
139
+ const sessionState = await scrymeClient.customer.auth.getSession();
140
+ console.log("Token:", sessionState.token);
141
+ console.log("Active User:", sessionState.user);
142
+
143
+ // Listen for authentication state changes (SIGNED_IN, SIGNED_OUT, INITIAL_SESSION)
144
+ const { unsubscribe } = scrymeClient.customer.auth.onAuthStateChange((event, session) => {
145
+ console.log(`Auth state event: ${event}`, session);
74
146
  });
147
+ // Clean up listener when done
148
+ unsubscribe();
75
149
  ```
76
150
 
77
- ### 📱 Client-Side Setup (`@scryme/sdk/client`)
78
- Provides stateful and reactive state persistence (localStorage / StorageProviders) with login listeners and automatic session recoveries.
151
+ ### Concurrent Session Revocation
79
152
 
80
- #### Class-Based Constructor (`ScrymeClientSDK`)
81
- Requires `clientId`, `clientSecret`, and `orgSlug` strictly to initialize correctly:
153
+ Customers can view and destroy active sessions across devices:
82
154
 
83
155
  ```typescript
84
- import { ScrymeClientSDK } from "@scryme/sdk/client";
156
+ // List all active concurrent sessions
157
+ const sessions = await scrymeClient.customer.auth.getSessions();
158
+ console.log("Active sessions count:", sessions.length);
85
159
 
86
- const scrymeClient = new ScrymeClientSDK({
87
- orgSlug: "your-org-slug",
88
- clientId: "your_client_id_123",
89
- clientSecret: "your_client_secret_456",
160
+ // Revoke a specific session by ID
161
+ await scrymeClient.customer.auth.revokeSession("sess_abc12345");
162
+
163
+ // Revoke all other sessions except the active one
164
+ await scrymeClient.customer.auth.revokeAllSessions("other");
165
+
166
+ // Revoke all sessions
167
+ await scrymeClient.customer.auth.revokeAllSessions();
168
+ ```
169
+
170
+ ### Reactive React Hook (`useSession`)
171
+
172
+ React storefront components can use `useSession` for immediate, real-time synchronization with customer authentication state:
173
+
174
+ ```tsx
175
+ import React from "react";
176
+ import { scrymeClient } from "./scryme";
177
+
178
+ export function UserProfileHeader() {
179
+ const { data, isPending, error, refetch } = scrymeClient.customer.auth.useSession();
180
+
181
+ if (isPending) return <div>Loading customer session...</div>;
182
+ if (error) return <div>Error loading session: {error.message}</div>;
183
+ if (!data?.user) return <div>Welcome, Guest! <button onClick={() => scrymeClient.customer.auth.signIn(...)}>Sign In</button></div>;
184
+
185
+ return (
186
+ <div>
187
+ <span>Welcome back, {data.user.name}!</span>
188
+ <button onClick={() => scrymeClient.customer.auth.signOut()}>Sign Out</button>
189
+ </div>
190
+ );
191
+ }
192
+ ```
193
+
194
+ ---
195
+
196
+ ## 👤 Customer Profile & Address Directory (`scrymeClient.customer`)
197
+
198
+ Manage the logged-in customer's profile details and address book:
199
+
200
+ ```typescript
201
+ // Fetch current logged-in customer profile
202
+ const profile = await scrymeClient.customer.getProfile();
203
+ console.log("Profile:", profile);
204
+
205
+ // Update profile details
206
+ const updated = await scrymeClient.customer.updateProfile({
207
+ phone: "+254799999999",
208
+ company: "Acme Holdings Ltd",
90
209
  });
91
210
 
92
- // Reactively listen to auth state changes
93
- scrymeClient.auth.onAuthStateChange((event, session) => {
94
- console.log(`Auth Event: ${event}`, session);
211
+ // Manage saved addresses
212
+ const addresses = await scrymeClient.customer.getAddresses();
213
+ await scrymeClient.customer.addAddress({
214
+ label: "Office Branch",
215
+ street1: "45 Westlands Rd",
216
+ city: "Nairobi",
217
+ country: "Kenya",
218
+ isDefault: false,
95
219
  });
220
+ ```
96
221
 
97
- async function runClient() {
98
- // Call APIs directly—the SDK handles token retrieval, refresh, and auto-injection of orgSlug automatically!
99
- const stock = await scrymeClient.inventory.getInventory({ limit: 5 });
100
- console.log("Client Stock:", stock.data);
101
- }
222
+ ---
223
+
224
+ ## 🛒 Stateful Shopping Cart Engine (`scrymeClient.cart`)
225
+
226
+ The SDK provides a stateful shopping cart submodule with automatic `customerId` resolution from active customer sessions, line item quantity delta calculation, and guest-to-customer cart deep merging.
227
+
228
+ ### Dynamic `customerId` Resolution
229
+ For operations such as `cart.add`, `cart.remove`, `cart.update`, and `bookings.create`, the SDK automatically resolves `customerId` from the active user session (`user?.customerId || user?.id || user?.customer?.id`) if omitted from call arguments. If no authenticated customer session exists and no explicit `customerId` is passed, operations requiring customer context safely throw an error.
230
+
231
+ ### Cart Mutations & Updates
232
+
233
+ ```typescript
234
+ // 1. Fetch current active cart
235
+ const cartResponse = await scrymeClient.cart.get({ sessionId: "guest_session_123" });
236
+
237
+ // 2. Add product variant or service to cart
238
+ await scrymeClient.cart.add({
239
+ productId: "prod_sourdough_bread",
240
+ variantId: "var_large_500g",
241
+ quantity: 2,
242
+ sessionId: "guest_session_123", // Optional if customer is signed in
243
+ });
244
+
245
+ // 3. Smart quantity update (computes delta, calls add/remove appropriately)
246
+ await scrymeClient.cart.update({
247
+ productId: "prod_sourdough_bread",
248
+ variantId: "var_large_500g",
249
+ quantity: 5, // Automatically increments by +3
250
+ });
251
+
252
+ // 4. Retrieve flat array of cart items
253
+ const items = await scrymeClient.cart.getItems();
254
+
255
+ // 5. Calculate summary metrics and total item count
256
+ const totals = await scrymeClient.cart.getTotals();
257
+ console.log("Total Items Count:", totals.itemsCount);
102
258
  ```
103
259
 
104
- Or use the helper factory `createClientSDK`, which provides fallback defaults:
260
+ ### Guest-to-Customer Cart Deep-Merging (`mergeGuestCart`)
261
+
262
+ When an anonymous guest customer signs in, call `mergeGuestCart` to aggregate their guest items into their permanent customer cart and clear the guest cart:
105
263
 
106
264
  ```typescript
107
- import { createClientSDK } from "@scryme/sdk/client";
265
+ // Sign in customer
266
+ await scrymeClient.customer.auth.signIn({
267
+ email: "john@example.com",
268
+ password: "password123",
269
+ });
108
270
 
109
- const scrymeClient = createClientSDK({
110
- orgSlug: "your-org-slug",
111
- clientId: "your_client_id_123",
112
- clientSecret: "your_client_secret_456",
271
+ // Merge guest cart items into customer cart
272
+ const mergedCart = await scrymeClient.cart.mergeGuestCart(
273
+ "guest_session_123", // Guest session ID
274
+ "cust_abc123" // Target customer ID
275
+ );
276
+
277
+ console.log("Merged Cart Items:", mergedCart.data.items);
278
+ ```
279
+
280
+ ### Checkout & Sales Order Generation (`checkout`)
281
+
282
+ Converts the active shopping cart into an official enterprise Sales Order and deducts inventory at the target location:
283
+
284
+ ```typescript
285
+ const order = await scrymeClient.cart.checkout({
286
+ locationId: "loc_nairobi_main",
287
+ notes: "Deliver before 2 PM",
288
+ channel: "ONLINE_STOREFRONT",
113
289
  });
290
+
291
+ console.log("Sales Order Created:", order.id);
114
292
  ```
115
293
 
116
294
  ---
117
295
 
118
- ## 🔑 Global / Legacy API client (`getScrymeV3API`)
296
+ ## 🌐 Server-Side Setup (`@scryme/sdk/server`)
119
297
 
120
- Alternatively, if you prefer utilizing a global request client or overriding behavior manually, you can initialize the custom Orval proxy with `getScrymeV3API`. It supports optional auto-injection of `orgSlug` from environment variables (`SCRYME_ORG_SLUG`, etc.) or custom default configurations.
298
+ The `ScrymeServerSDK` strictly isolates requests and Axios instances for multi-tenant server environments (Next.js Server Components, API routes, Windmill workflows, backend services).
121
299
 
122
- ### Complete Initialization Example:
300
+ ### Initialization
123
301
 
124
302
  ```typescript
125
- import { getScrymeV3API } from "@scryme/sdk";
126
- import axios from "axios";
127
-
128
- // 1. Initialize the API instance (optionally passing a custom Axios instance)
129
- const apiBaseUrl = process.env.SCRYME_API_URL || "https://api.scryme.tech";
130
- axios.defaults.baseURL = apiBaseUrl;
131
-
132
- const scryme = getScrymeV3API(axios);
133
-
134
- async function runFlow() {
135
- try {
136
- // 2. Perform Client Credentials flow to retrieve access token
137
- const tokenResponse = await scryme.authExchangeToken({
138
- clientId: process.env.SCRYME_CLIENT_ID || "your_id",
139
- clientSecret: process.env.SCRYME_CLIENT_SECRET || "your_secret"
140
- });
141
-
142
- const accessToken = tokenResponse.data.accessToken;
143
- console.log("Successfully logged in! Token retrieved.");
144
-
145
- // 3. Register the token in the Axios headers
146
- axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
147
-
148
- // 4. Perform type-safe V3 operations (orgSlug is auto-injected from environment!)
149
- const products = await scryme.catalogGetProducts({ limit: 10 });
150
- console.log("Catalog Products:", products.data);
151
- } catch (error) {
152
- console.error("SDK execution failed:", error);
153
- }
303
+ import { ScrymeServerSDK, createServerSDK } from "@scryme/sdk/server";
304
+
305
+ const scrymeServer = new ScrymeServerSDK({
306
+ baseURL: "https://api.scryme.tech",
307
+ orgSlug: "my-organization",
308
+ clientId: "your_client_id_123",
309
+ clientSecret: "your_client_secret_456",
310
+ });
311
+
312
+ async function runServerFlow() {
313
+ // Call APIs directly—the SDK handles Client Credentials token exchange and orgSlug auto-injection!
314
+ const products = await scrymeServer.catalog.getProducts({ limit: 10 });
315
+ const customers = await scrymeServer.admin.getCustomers({ limit: 20 });
316
+ console.log("Products count:", products.data.length);
154
317
  }
155
318
  ```
156
319
 
157
- For more domain examples, detailed schemas, and active playgrounds, head over to:
158
- 👉 **[docs.scryme.tech](https://docs.scryme.tech)**
159
-
160
320
  ---
161
321
 
162
- ## 🔒 Best Practices
322
+ ## 🔒 Best Practices & Security
163
323
 
164
- ### Multi-tenant Organization Scoping
165
- All core resources are strictly isolated by organization. Ensure you always pass the correct `orgSlug` parameter as required by the endpoints. Passing an invalid or unauthorized `orgSlug` will yield a `401 Unauthorized` or `404 Not Found` response.
324
+ ### Multi-Tenant Organization Scoping
325
+ All domain resources are isolated by organization. The SDK automatically injects the configured `orgSlug` parameter into all API calls.
166
326
 
167
- ### Secure Webhook Verification
168
- When receiving webhook callbacks from Scryme, always verify the webhook signature before processing the payload to prevent spoofing attacks.
327
+ ### Webhook Verification
328
+ When receiving webhooks from Scryme, verify payload HMAC signatures to prevent spoofing:
169
329
 
170
330
  ```typescript
171
331
  import * as crypto from "crypto";
@@ -175,46 +335,19 @@ function verifySignature(
175
335
  signature: string,
176
336
  webhookSecret: string
177
337
  ): boolean {
178
- // Generate the expected HMAC signature
179
338
  const hmac = crypto.createHmac("sha256", webhookSecret);
180
339
  const expectedSignature = hmac.update(payload).digest("hex");
181
340
 
182
- // Pre-hash both signatures using SHA-256 to ensure identical 32-byte buffer length.
183
- // This prevents timing side-channels, signature length leakage, and RangeError exceptions on unequal buffer lengths.
184
- const expectedHash = crypto
185
- .createHash("sha256")
186
- .update(expectedSignature)
187
- .digest();
188
- const actualHash = crypto
189
- .createHash("sha256")
190
- .update(signature || "")
191
- .digest();
341
+ // Pre-hash signatures to SHA-256 to guarantee equal length buffers and prevent timing attacks
342
+ const expectedHash = crypto.createHash("sha256").update(expectedSignature).digest();
343
+ const actualHash = crypto.createHash("sha256").update(signature || "").digest();
192
344
 
193
345
  return crypto.timingSafeEqual(expectedHash, actualHash);
194
346
  }
195
347
  ```
196
348
 
197
- ### Robust Error Handling
198
- Scryme V3 API returns standardized error structures. Always wrap your SDK calls in `try/catch` blocks and parse the error fields to display helpful messages to users.
199
-
200
- ```typescript
201
- import { isAxiosError } from "axios";
202
-
203
- try {
204
- await scrymeClient.inventory.getInventory({ limit: 5 });
205
- } catch (error) {
206
- if (isAxiosError(error) && error.response) {
207
- const apiError = error.response.data;
208
- console.error(`Error (${apiError.error.code}): ${apiError.error.message}`);
209
- console.error("Details:", apiError.error.details);
210
- } else {
211
- console.error("Unexpected Error:", error);
212
- }
213
- }
214
- ```
215
-
216
349
  ---
217
350
 
218
351
  ## 📄 License
219
352
 
220
- This package is licensed under the GNU Affero General Public License version 3 (AGPL-3.0). Please see the [LICENSE](../../LICENSE) file for more details.
353
+ This package is licensed under the GNU Affero General Public License version 3 (AGPL-3.0). Please see the [LICENSE](../../LICENSE) file for details.