spaps-sdk 1.6.0 → 1.6.2

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 ADDED
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ - No changes yet.
10
+
11
+ ## [1.6.1] - 2026-02-16
12
+
13
+ ### Fixed
14
+
15
+ - Made the SDK `tsconfig` self-contained after root-config removal.
16
+ - Restored API-key header injection behavior for axios v1+ interceptor semantics.
17
+
18
+ ## [1.6.0] - 2026-02-14
19
+
20
+ ### Added
21
+
22
+ - CFO roles, `company_id`, and affiliate-referral domain support.
23
+
24
+ ### Changed
25
+
26
+ - Consolidated SPAPS integration patterns into published SDK packages.
27
+
28
+ ## [1.5.1] - 2026-02-11
29
+
30
+ ### Changed
31
+
32
+ - Standardized local/dev config defaults, including port `3301`.
33
+
34
+ ## [1.5.0] - 2026-01-10
35
+
36
+ ### Added
37
+
38
+ - Set-password endpoint support for authenticated users.
39
+
40
+ ## [1.4.0] - 2026-01-10
41
+
42
+ ### Added
43
+
44
+ - Email service integration with Mailgun support.
45
+
46
+ ## [1.2.1] - 2025-12-31
47
+
48
+ ### Added
49
+
50
+ - Stripe webhook admin tooling for entitlement operations.
51
+ - CFO auth integration helpers.
52
+
53
+ ## [1.1.52] - 2025-10-15
54
+
55
+ ### Fixed
56
+
57
+ - Build and type-export stability across iterative CI packaging fixes.
58
+
59
+ ## [1.1.0] - 2025-10-12
60
+
61
+ ### Added
62
+
63
+ - Admin API methods for Stripe product/price management and sync flows.
64
+ - Built-in permission utilities: `isAdminAccount`, `canAccessAdmin`, `getUserRole`.
65
+
66
+ ### Changed
67
+
68
+ - Subsequent patch iterations through `1.1.52` focused on CI and type-export stabilization.
69
+
70
+ ## [1.0.0] - 2025-10-08
71
+
72
+ ### Added
73
+
74
+ - Initial npm release as `spaps-sdk`.
75
+ - Zero-config client for SPAPS auth, payments, and permission checks.
76
+ - Local-mode auto-detection (`localhost`/`127.0.0.1`).
77
+ - Email/password, wallet, and magic-link authentication flows.
78
+ - Stripe checkout helpers with legal-consent support.
79
+ - Usage tracking and secure messaging helpers.
80
+ - TypeScript definitions with CJS/ESM dual exports.
81
+ - Node/browser/Next.js/React Native compatibility.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sweet Potato Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PERMISSIONS.md ADDED
@@ -0,0 +1,390 @@
1
+ # 🔐 SPAPS SDK Permission Utilities
2
+
3
+ Client-side permission checking and role management utilities for SPAPS applications.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ```typescript
8
+ import {
9
+ isAdminAccount,
10
+ getUserRole,
11
+ hasPermission,
12
+ canAccessAdmin,
13
+ PermissionChecker,
14
+ createPermissionChecker
15
+ } from 'spaps-sdk';
16
+
17
+ // Check if user is admin
18
+ const isAdmin = isAdminAccount('buildooor@gmail.com'); // true
19
+
20
+ // Get user role
21
+ const role = getUserRole('user@example.com'); // 'user' | 'admin' | 'guest'
22
+
23
+ // Check admin access
24
+ const adminCheck = canAccessAdmin(user);
25
+ if (adminCheck.allowed) {
26
+ // Show admin features
27
+ }
28
+
29
+ // Check specific permissions
30
+ const canEdit = hasPermission(user, 'edit');
31
+ const canManage = hasPermission(user, ['create', 'delete']);
32
+ ```
33
+
34
+ ## 📋 Default Admin Accounts
35
+
36
+ ```typescript
37
+ const DEFAULT_ADMIN_ACCOUNTS = {
38
+ email: 'buildooor@gmail.com',
39
+ wallets: {
40
+ ethereum: '0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba',
41
+ solana: 'HVEbdiYU3Rr34NHBSgKs7q8cvdTeZLqNL77Z1FB2vjLy'
42
+ }
43
+ };
44
+ ```
45
+
46
+ ## 🎯 React Component Examples
47
+
48
+ ### Admin Button with Permission Checking
49
+
50
+ ```tsx
51
+ import React from 'react';
52
+ import { canAccessAdmin, getUserDisplay } from 'spaps-sdk';
53
+
54
+ interface AdminButtonProps {
55
+ user: User | null;
56
+ onClick: () => void;
57
+ }
58
+
59
+ export function AdminButton({ user, onClick }: AdminButtonProps) {
60
+ const permissionCheck = canAccessAdmin(user);
61
+ const userDisplay = getUserDisplay(user);
62
+
63
+ if (!permissionCheck.allowed) {
64
+ return (
65
+ <button
66
+ disabled
67
+ title={permissionCheck.reason}
68
+ className="btn-disabled"
69
+ >
70
+ Admin Panel
71
+ </button>
72
+ );
73
+ }
74
+
75
+ return (
76
+ <button onClick={onClick} className="btn-primary">
77
+ {userDisplay.badge} Admin Panel
78
+ </button>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ### Protected Route Component
84
+
85
+ ```tsx
86
+ import React from 'react';
87
+ import { canAccessAdmin, getRoleAwareErrorMessage } from 'spaps-sdk';
88
+
89
+ interface ProtectedRouteProps {
90
+ user: User | null;
91
+ children: React.ReactNode;
92
+ }
93
+
94
+ export function AdminRoute({ user, children }: ProtectedRouteProps) {
95
+ const permissionCheck = canAccessAdmin(user);
96
+
97
+ if (!permissionCheck.allowed) {
98
+ const errorMessage = getRoleAwareErrorMessage(
99
+ 'admin',
100
+ permissionCheck.userRole,
101
+ 'access this page'
102
+ );
103
+
104
+ return (
105
+ <div className="error-container">
106
+ <h2>Access Denied</h2>
107
+ <p>{errorMessage}</p>
108
+ </div>
109
+ );
110
+ }
111
+
112
+ return <>{children}</>;
113
+ }
114
+ ```
115
+
116
+ ### User Display with Role Badge
117
+
118
+ ```tsx
119
+ import React from 'react';
120
+ import { getUserDisplay } from 'spaps-sdk';
121
+
122
+ export function UserProfile({ user }: { user: User | null }) {
123
+ const display = getUserDisplay(user);
124
+
125
+ return (
126
+ <div className="user-profile">
127
+ <span>{display.displayName}</span>
128
+ {display.badge && (
129
+ <span className="role-badge">{display.badge}</span>
130
+ )}
131
+ <span className="role-text">({display.role})</span>
132
+ </div>
133
+ );
134
+ }
135
+ ```
136
+
137
+ ## 🛠️ Advanced Usage
138
+
139
+ ### Custom Permission Checker
140
+
141
+ ```typescript
142
+ import { createPermissionChecker } from 'spaps-sdk';
143
+
144
+ // Create checker with custom admin accounts
145
+ const customAdmins = [
146
+ 'admin@mycompany.com',
147
+ '0x1234567890abcdef...',
148
+ {
149
+ email: 'ceo@company.com',
150
+ wallets: {
151
+ ethereum: '0xabcdef...',
152
+ solana: 'ABC123...'
153
+ }
154
+ }
155
+ ];
156
+
157
+ const checker = createPermissionChecker(customAdmins);
158
+
159
+ // Use the checker
160
+ const isAdmin = checker.isAdmin('admin@mycompany.com'); // true
161
+ const userDisplay = checker.getUserDisplay(user);
162
+ const canAccess = checker.canAccessAdmin(user);
163
+ ```
164
+
165
+ ### Permission-Based UI Rendering
166
+
167
+ ```tsx
168
+ import React from 'react';
169
+ import { hasPermission, PermissionChecker } from 'spaps-sdk';
170
+
171
+ const checker = new PermissionChecker();
172
+
173
+ function Dashboard({ user }: { user: User | null }) {
174
+ return (
175
+ <div>
176
+ <h1>Dashboard</h1>
177
+
178
+ {/* Show admin panel for admins only */}
179
+ {!checker.requiresAdmin(user) && (
180
+ <AdminPanel />
181
+ )}
182
+
183
+ {/* Show billing section for users with billing permission */}
184
+ {hasPermission(user, 'billing') && (
185
+ <BillingSection />
186
+ )}
187
+
188
+ {/* Show different content based on role */}
189
+ {checker.getRole(user?.email) === 'admin' ? (
190
+ <AdminDashboard />
191
+ ) : (
192
+ <UserDashboard />
193
+ )}
194
+ </div>
195
+ );
196
+ }
197
+ ```
198
+
199
+ ### Form with Permission Validation
200
+
201
+ ```tsx
202
+ import React, { useState } from 'react';
203
+ import { hasPermission, getRoleAwareErrorMessage } from 'spaps-sdk';
204
+
205
+ function ProductForm({ user }: { user: User | null }) {
206
+ const [product, setProduct] = useState({ name: '', price: '' });
207
+ const canCreate = hasPermission(user, 'create');
208
+
209
+ const handleSubmit = (e: React.FormEvent) => {
210
+ e.preventDefault();
211
+
212
+ if (!canCreate) {
213
+ const errorMessage = getRoleAwareErrorMessage(
214
+ 'permission',
215
+ user ? 'user' : 'guest',
216
+ 'create products'
217
+ );
218
+ alert(errorMessage);
219
+ return;
220
+ }
221
+
222
+ // Submit product
223
+ createProduct(product);
224
+ };
225
+
226
+ return (
227
+ <form onSubmit={handleSubmit}>
228
+ <input
229
+ value={product.name}
230
+ onChange={(e) => setProduct({ ...product, name: e.target.value })}
231
+ placeholder="Product name"
232
+ />
233
+ <input
234
+ value={product.price}
235
+ onChange={(e) => setProduct({ ...product, price: e.target.value })}
236
+ placeholder="Price"
237
+ />
238
+ <button
239
+ type="submit"
240
+ disabled={!canCreate}
241
+ title={!canCreate ? 'Insufficient permissions' : undefined}
242
+ >
243
+ {canCreate ? 'Create Product' : 'Create Product (No Permission)'}
244
+ </button>
245
+ </form>
246
+ );
247
+ }
248
+ ```
249
+
250
+ ## 🧪 Testing Examples
251
+
252
+ ```typescript
253
+ import {
254
+ isAdminAccount,
255
+ getUserRole,
256
+ canAccessAdmin,
257
+ createPermissionChecker
258
+ } from 'spaps-sdk';
259
+
260
+ describe('Permission Utilities', () => {
261
+ describe('isAdminAccount', () => {
262
+ it('should recognize default admin accounts', () => {
263
+ expect(isAdminAccount('buildooor@gmail.com')).toBe(true);
264
+ expect(isAdminAccount('0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba')).toBe(true);
265
+ expect(isAdminAccount('user@example.com')).toBe(false);
266
+ });
267
+
268
+ it('should recognize custom admin accounts', () => {
269
+ const customAdmins = ['admin@test.com'];
270
+ expect(isAdminAccount('admin@test.com', customAdmins)).toBe(true);
271
+ expect(isAdminAccount('user@test.com', customAdmins)).toBe(false);
272
+ });
273
+ });
274
+
275
+ describe('getUserRole', () => {
276
+ it('should return correct roles', () => {
277
+ expect(getUserRole('buildooor@gmail.com')).toBe('admin');
278
+ expect(getUserRole('user@example.com')).toBe('user');
279
+ expect(getUserRole()).toBe('guest');
280
+ });
281
+ });
282
+
283
+ describe('canAccessAdmin', () => {
284
+ it('should check admin access correctly', () => {
285
+ const adminUser = { id: '1', email: 'buildooor@gmail.com' };
286
+ const regularUser = { id: '2', email: 'user@example.com' };
287
+
288
+ expect(canAccessAdmin(adminUser).allowed).toBe(true);
289
+ expect(canAccessAdmin(regularUser).allowed).toBe(false);
290
+ expect(canAccessAdmin(null).allowed).toBe(false);
291
+ });
292
+ });
293
+
294
+ describe('PermissionChecker', () => {
295
+ it('should work with custom admins', () => {
296
+ const checker = createPermissionChecker(['admin@test.com']);
297
+
298
+ expect(checker.isAdmin('admin@test.com')).toBe(true);
299
+ expect(checker.getRole('admin@test.com')).toBe('admin');
300
+ expect(checker.canAccessAdmin({
301
+ id: '1',
302
+ email: 'admin@test.com'
303
+ }).allowed).toBe(true);
304
+ });
305
+ });
306
+ });
307
+ ```
308
+
309
+ ## 📚 API Reference
310
+
311
+ ### Core Functions
312
+
313
+ #### `isAdminAccount(identifier: string, customAdmins?: AdminConfig[]): boolean`
314
+ Check if an identifier (email/wallet) is an admin account.
315
+
316
+ #### `getUserRole(identifier?: string, customAdmins?: AdminConfig[]): string`
317
+ Get user role: 'admin', 'user', or 'guest'.
318
+
319
+ #### `hasPermission(user: User | null, permissions: string | string[], customAdmins?: AdminConfig[]): boolean`
320
+ Check if user has specific permissions.
321
+
322
+ #### `canAccessAdmin(user: User | null, customAdmins?: AdminConfig[]): PermissionCheckResult`
323
+ Check if user can access admin features.
324
+
325
+ #### `getRoleAwareErrorMessage(requiredRole: string, userRole: string, action?: string): string`
326
+ Get contextual error message based on roles.
327
+
328
+ #### `getUserDisplay(user: User | null, customAdmins?: AdminConfig[])`
329
+ Get user display information with role indicators.
330
+
331
+ ### PermissionChecker Class
332
+
333
+ ```typescript
334
+ class PermissionChecker {
335
+ constructor(customAdmins?: AdminConfig[]);
336
+
337
+ isAdmin(identifier: string): boolean;
338
+ getRole(identifier?: string): string;
339
+ hasPermission(user: User | null, permissions: string | string[]): boolean;
340
+ canAccessAdmin(user: User | null): PermissionCheckResult;
341
+ getErrorMessage(requiredRole: string, userRole: string, action?: string): string;
342
+ getUserDisplay(user: User | null);
343
+
344
+ // Convenience methods
345
+ requiresAuth(user: User | null): boolean;
346
+ requiresAdmin(user: User | null): boolean;
347
+
348
+ // Admin management
349
+ addCustomAdmin(admin: string | AdminConfig): void;
350
+ removeCustomAdmin(admin: string | AdminConfig): void;
351
+ }
352
+ ```
353
+
354
+ ### Interfaces
355
+
356
+ ```typescript
357
+ interface User {
358
+ id: string;
359
+ email?: string;
360
+ wallet_address?: string;
361
+ role?: string;
362
+ permissions?: string[];
363
+ tier?: string;
364
+ }
365
+
366
+ interface AdminConfig {
367
+ email: string;
368
+ wallets: {
369
+ ethereum: string;
370
+ solana: string;
371
+ };
372
+ }
373
+
374
+ interface PermissionCheckResult {
375
+ allowed: boolean;
376
+ reason?: string;
377
+ userRole: string;
378
+ requiredRole?: string;
379
+ }
380
+ ```
381
+
382
+ ## 🌟 Best Practices
383
+
384
+ 1. **Server-side validation**: Always validate permissions on the server
385
+ 2. **Progressive enhancement**: Use client-side checks for UX, not security
386
+ 3. **Consistent error messages**: Use `getRoleAwareErrorMessage` for user-friendly errors
387
+ 4. **Role-based UI**: Show/hide features based on user permissions
388
+ 5. **Custom admin management**: Use custom admin lists for multi-tenant apps
389
+ 6. **Testing**: Test permission logic with various user states
390
+ 7. **Type safety**: Use TypeScript interfaces for better development experience
package/README.md CHANGED
@@ -1,19 +1,4 @@
1
- ---
2
- id: spaps-typescript-sdk
3
- title: spaps TypeScript SDK
4
- category: sdk
5
- tags:
6
- - sdk
7
- - typescript
8
- - client
9
- ai_summary: |
10
- Reference for the spaps TypeScript SDK covering configuration, admin helpers,
11
- and examples for interacting with authentication, payments, and permission
12
- APIs from JavaScript or TypeScript applications.
13
- last_updated: 2025-02-14
14
- ---
15
-
16
- # @spaps/sdk
1
+ # spaps-sdk
17
2
 
18
3
  <a href="https://www.npmjs.com/package/spaps-sdk"><img alt="npm" src="https://img.shields.io/npm/v/spaps-sdk.svg"></a>
19
4
  <img alt="node" src="https://img.shields.io/badge/node-%3E%3D14-brightgreen">
@@ -21,71 +6,21 @@ last_updated: 2025-02-14
21
6
 
22
7
  > Sweet Potato Authentication & Payment Service SDK
23
8
 
24
- Zero-config client for SPAPS authentication, payments, and permission checking. Works automatically with local development mode.
9
+ Zero-config client for SPAPS authentication, payments, secure messaging, and admin/product workflows.
25
10
 
26
- ## 🔐 New in v1.1.0: Admin API Methods!
11
+ ## Quick Facts
27
12
 
28
- Built-in admin API methods for Stripe product management:
13
+ - Package: `spaps-sdk`
14
+ - Latest version: `1.6.1`
15
+ - Runtime: `Node.js >=14.0.0`
16
+ - Default local API URL: `http://localhost:3301`
29
17
 
30
- ```typescript
31
- import { SPAPSClient } from 'spaps-sdk';
32
-
33
- const spaps = new SPAPSClient({
34
- apiKey: 'your-api-key',
35
- apiUrl: 'https://api.sweetpotato.dev'
36
- });
18
+ ## Metadata
37
19
 
38
- // Authenticate as admin
39
- await spaps.signIn('admin@example.com', 'password');
40
-
41
- // Admin API methods
42
- await spaps.admin.createProduct({
43
- name: 'Premium Plan',
44
- category: 'subscription',
45
- description: 'Advanced features'
46
- });
47
-
48
- await spaps.admin.updateProduct('prod_123', {
49
- name: 'Premium Plan Pro'
50
- });
51
-
52
- await spaps.admin.createPrice({
53
- product_id: 'prod_123',
54
- unit_amount: 2999,
55
- currency: 'usd',
56
- interval: 'month'
57
- });
58
-
59
- // Super admin operations
60
- await spaps.admin.syncProducts();
61
- ```
62
-
63
- ## 🔐 Permission Utilities
64
-
65
- Built-in permission checking and role-based access control:
66
-
67
- ```typescript
68
- import { isAdminAccount, canAccessAdmin, getUserRole } from 'spaps-sdk';
69
-
70
- // Check admin status
71
- const isAdmin = isAdminAccount('buildooor@gmail.com'); // true
72
-
73
- // Check admin access with detailed result
74
- const adminCheck = canAccessAdmin(user);
75
- if (adminCheck.allowed) {
76
- // Show admin UI
77
- }
78
-
79
- // Get user role
80
- const role = getUserRole('user@example.com'); // 'user' | 'admin' | 'guest'
81
-
82
- // Check if authenticated user is admin
83
- if (spaps.isAdmin(currentUser)) {
84
- // Show admin features
85
- }
86
- ```
87
-
88
- See [PERMISSIONS.md](./PERMISSIONS.md) for React examples and complete documentation.
20
+ - `package_name`: `spaps-sdk`
21
+ - `latest_version`: `1.6.1`
22
+ - `minimum_runtime`: `Node.js >=14.0.0`
23
+ - `api_base_url`: `https://api.sweetpotato.dev`
89
24
 
90
25
  ## Installation
91
26
 
@@ -97,349 +32,153 @@ yarn add spaps-sdk
97
32
  pnpm add spaps-sdk
98
33
  ```
99
34
 
100
- ### Compatibility
101
-
102
- - ✅ **Node.js**: Fully supported (v14+) with automatic fetch polyfill
103
- - ✅ **Browser**: Works in all modern browsers
104
- - ✅ **TypeScript**: Full type definitions included
105
- - ✅ **Next.js**: Server and client components supported
106
- - ✅ **React Native**: Compatible with proper setup
107
-
108
- The SDK automatically includes a `cross-fetch` polyfill for Node.js environments that don't have native fetch support.
109
-
110
35
  ## Quick Start
111
36
 
112
- ```javascript
37
+ ```typescript
113
38
  import { SPAPSClient } from 'spaps-sdk';
114
- // or
115
- const { SPAPSClient } = require('spaps-sdk');
116
39
 
117
- // Auto-detects local mode - no API key needed for localhost!
118
40
  const spaps = new SPAPSClient({
119
- apiUrl: 'http://localhost:3301' // Optional, auto-detected
41
+ apiUrl: 'http://localhost:3301',
120
42
  });
121
43
 
122
- // Login
123
44
  const { data } = await spaps.login('user@example.com', 'password');
124
45
  console.log('User:', data.user);
125
46
 
126
- // Check authentication
127
47
  if (spaps.isAuthenticated()) {
128
- const user = await spaps.getUser();
129
- console.log('Current user:', user.data);
48
+ const me = await spaps.getUser();
49
+ console.log(me.data.email);
130
50
  }
131
51
  ```
132
52
 
133
- ## Local Mode Explained
134
-
135
- - If `apiUrl` is omitted or points to `localhost`/`127.0.0.1`, the SDK runs in local mode.
136
- - Local mode integrates seamlessly with the `spaps` CLI (`npx spaps local`), defaulting to `http://localhost:3301`.
137
- - No API key is required in local mode; tokens and data are managed locally for development.
53
+ ## Compatibility
138
54
 
139
- You can check the mode at runtime:
140
-
141
- ```ts
142
- spaps.isLocalMode(); // true | false
143
- ```
55
+ - Node.js (v14+)
56
+ - Modern browsers
57
+ - TypeScript (types bundled)
58
+ - Next.js and React Native
144
59
 
145
- ## Environment Variables
60
+ The SDK includes a `cross-fetch` polyfill for Node environments without native fetch.
146
61
 
147
- The SDK can read configuration from environment variables (useful in Next.js and Node):
62
+ ## Configuration
148
63
 
149
- - `SPAPS_API_URL` or `NEXT_PUBLIC_SPAPS_API_URL` API base URL
150
- - `SPAPS_API_KEY` — API key for production use
64
+ `SPAPSClient` reads config from constructor args first, then environment variables.
151
65
 
152
- Example (Node):
66
+ - `SPAPS_API_URL` or `NEXT_PUBLIC_SPAPS_API_URL` - API base URL
67
+ - `SPAPS_API_KEY` or `NEXT_PUBLIC_SPAPS_API_KEY` - API key
153
68
 
154
69
  ```bash
70
+ # Node
155
71
  export SPAPS_API_URL=https://api.sweetpotato.dev
156
72
  export SPAPS_API_KEY=spaps_xxx
157
- ```
158
73
 
159
- Example (Next.js):
160
-
161
- ```env
74
+ # Next.js
162
75
  NEXT_PUBLIC_SPAPS_API_URL=https://api.sweetpotato.dev
163
76
  ```
164
77
 
165
- ## Features
78
+ Local mode auto-detection:
166
79
 
167
- ### 🚀 Zero Configuration
168
- - **Auto-detects local mode** - No API key needed for localhost
169
- - **Auto-refreshes tokens** - Handles expired tokens automatically
170
- - **TypeScript support** - Full type definitions included
171
- - **Crypto payments ready** - Create invoices, poll status, verify webhooks, trigger reconciliation
80
+ - If `apiUrl` is omitted or points to `localhost`/`127.0.0.1`, local mode is enabled.
81
+ - In local mode, API key is optional.
172
82
 
173
- ### 🔐 Authentication Methods
174
- ```javascript
175
- // Email/Password
176
- await spaps.login(email, password);
177
- await spaps.register(email, password);
83
+ ## API Surface
178
84
 
179
- // Wallet Authentication
180
- await spaps.walletSignIn(walletAddress, signature, message, 'solana');
85
+ - `auth` and top-level auth helpers: password, wallet, magic link, refresh/logout
86
+ - `sessions`: current/list/validate/revoke/touch
87
+ - `payments`: checkout sessions, products/prices, guest checkout, subscriptions, crypto invoices
88
+ - `secureMessages`: create/list secure messages
89
+ - `email`: send/list templates/get template/preview template
90
+ - `entitlements`: list/check/list by resource
91
+ - `admin`: product and price management helpers
92
+ - `dayrate`, `cfo`: specialized domain helpers
181
93
 
182
- // Token Management
183
- await spaps.refresh();
184
- await spaps.logout();
185
-
186
- // Get User
187
- const user = await spaps.getUser();
188
- ```
189
-
190
- ### 💳 Stripe Integration
191
- ```javascript
192
- // Create checkout session with consent control
193
- const session = await spaps.payments.createPaymentCheckout({
194
- price_id: 'price_123',
195
- success_url: 'https://app.example.com/success',
196
- cancel_url: 'https://app.example.com/cancel',
197
- require_legal_consent: true,
198
- legal_consent_text: 'I agree I am 18+ and accept the HTMA Terms & Privacy.'
199
- });
200
- window.location.href = session.url;
94
+ ## Permission Utilities
201
95
 
202
- // Manage subscription
203
- const subscription = await spaps.getSubscription();
204
- await spaps.cancelSubscription();
205
- ```
206
- > `require_legal_consent` forces Stripe’s Terms/Privacy checkbox, and `legal_consent_text` (≤120 chars) customizes the copy. Text is sanitized and defaults to “I agree to the Terms of Service and Privacy Policy.” when omitted.
96
+ ```typescript
97
+ import { isAdminAccount, canAccessAdmin, getUserRole } from 'spaps-sdk';
207
98
 
208
- ### 📊 Usage Tracking
209
- ```javascript
210
- // Check balance
211
- const balance = await spaps.getUsageBalance();
212
- console.log(`Credits: ${balance.data.balance}`);
99
+ const isAdmin = isAdminAccount('buildooor@gmail.com');
100
+ const role = getUserRole('user@example.com');
101
+ const adminCheck = canAccessAdmin({ id: 'u1', email: 'user@example.com' });
213
102
 
214
- // Record usage
215
- await spaps.recordUsage('api-call', 1);
103
+ if (adminCheck.allowed) {
104
+ // render admin UI
105
+ }
216
106
  ```
217
107
 
218
- ### ✉️ Secure Messaging
219
- ```javascript
220
- // Create a secure message (content encrypted server-side when pii_enabled)
221
- const message = await spaps.secureMessages.create({
222
- patientId: '3d6f0a51-8d77-4b38-8248-2d1b2f1f6c7f',
223
- practitionerId: 'a3d7f431-6c9d-4cbc-9f78-4e5b6a7c8d9e',
224
- content: 'Patient is experiencing intermittent headaches.',
225
- metadata: { urgency: 'high' }
226
- });
108
+ See [PERMISSIONS.md](./PERMISSIONS.md) for React-oriented examples.
227
109
 
228
- // List secure messages for the current application
229
- const messages = await spaps.secureMessages.list();
230
- console.log(messages[0].content);
231
- ```
232
- > Ensure your application has `settings.pii_enabled = true` so payloads are encrypted automatically.
110
+ ## Secure Messaging
233
111
 
234
112
  ```typescript
235
- // Provide a strongly typed metadata shape for downstream usage
236
113
  type SecureMessageMetadata = { urgency: 'low' | 'high'; tags?: string[] };
237
114
 
238
- const spaps = new SPAPSClient<SecureMessageMetadata>({ apiKey: process.env.SPAPS_API_KEY });
115
+ const spaps = new SPAPSClient<SecureMessageMetadata>({
116
+ apiUrl: 'https://api.sweetpotato.dev',
117
+ apiKey: process.env.SPAPS_API_KEY,
118
+ });
239
119
 
240
120
  await spaps.secureMessages.create({
241
121
  patientId: '3d6f0a51-8d77-4b38-8248-2d1b2f1f6c7f',
242
122
  practitionerId: 'a3d7f431-6c9d-4cbc-9f78-4e5b6a7c8d9e',
243
123
  content: 'Follow up scheduled for next week.',
244
- metadata: { urgency: 'low', tags: ['follow-up'] }
124
+ metadata: { urgency: 'low', tags: ['follow-up'] },
245
125
  });
246
-
247
- const typedMessages = await spaps.secureMessages.list();
248
- typedMessages[0].metadata.urgency; // "low" | "high"
249
- ```
250
-
251
- Need runtime validation too? Reuse the shared schemas that ship with the SDK:
252
-
253
- ```typescript
254
- import { z } from 'zod';
255
- import { createSecureMessageRequestSchema } from 'spaps-sdk';
256
-
257
- const secureMessageRequestSchema = createSecureMessageRequestSchema(
258
- z.object({ urgency: z.enum(['low', 'high']), tags: z.array(z.string()).optional() })
259
- );
260
-
261
- secureMessageRequestSchema.parse({
262
- patientId: '3d6f0a51-8d77-4b38-8248-2d1b2f1f6c7f',
263
- practitionerId: 'a3d7f431-6c9d-4cbc-9f78-4e5b6a7c8d9e',
264
- content: 'All clear.',
265
- metadata: { urgency: 'low' }
266
- });
267
- ```
268
-
269
- ## Configuration
270
-
271
- ### Production Mode
272
- ```javascript
273
- const spaps = new SPAPSClient({
274
- apiUrl: 'https://api.sweetpotato.dev',
275
- apiKey: 'spaps_your_api_key_here',
276
- timeout: 10000 // Optional timeout in ms
277
- });
278
- ```
279
-
280
- ### Local Development Mode (Auto-detected)
281
- ```javascript
282
- const spaps = new SPAPSClient();
283
- // Automatically uses http://localhost:3301 with no API key
284
- ```
285
-
286
- ### Environment Variables
287
- ```bash
288
- # .env
289
- SPAPS_API_URL=https://api.sweetpotato.dev
290
- SPAPS_API_KEY=spaps_your_api_key_here
291
-
292
- # Next.js
293
- NEXT_PUBLIC_SPAPS_API_URL=https://api.sweetpotato.dev
294
- ```
295
-
296
- ## Helper Methods
297
-
298
- ```javascript
299
- // Check if authenticated
300
- spaps.isAuthenticated() // boolean
301
-
302
- // Get current access token
303
- spaps.getAccessToken() // string | undefined
304
-
305
- // Set access token manually
306
- spaps.setAccessToken(token)
307
-
308
- // Check if in local mode
309
- spaps.isLocalMode() // boolean
310
-
311
- // Health check
312
- await spaps.health()
313
- ```
314
-
315
- ## Types
316
-
317
- The SDK ships with full TypeScript definitions. For most apps, import types directly from `spaps-sdk`:
318
-
319
- ```ts
320
- import { SPAPSClient, type User, type Session, type Role } from 'spaps-sdk';
321
- ```
322
-
323
- For advanced or library use cases, you can also import canonical types from the standalone package:
324
-
325
- ```ts
326
- import type {
327
- User,
328
- Session,
329
- Role,
330
- Permission,
331
- TokenPayload,
332
- } from 'spaps-types';
333
- ```
334
-
335
- Notes:
336
- - `spaps-sdk` re-exports commonly used types for convenience.
337
- - `spaps-types` is useful when building framework integrations, server middleware, or utilities that need to share types without depending on the SDK runtime.
338
-
339
- ## Import Styles
340
-
341
- All these work:
342
- ```javascript
343
- // ES6 Import
344
- import { SPAPSClient } from 'spaps-sdk';
345
- import SPAPSClient from 'spaps-sdk';
346
-
347
- // CommonJS
348
- const { SPAPSClient } = require('spaps-sdk');
349
- const SPAPSClient = require('spaps-sdk');
350
-
351
- // Alternative names
352
- import { SPAPS } from 'spaps-sdk';
353
- import { SweetPotatoSDK } from 'spaps-sdk';
354
126
  ```
355
127
 
356
128
  ## Admin API Methods
357
129
 
358
- The SDK includes comprehensive admin methods for Stripe product management:
359
-
360
- ### Product Management
361
-
362
130
  ```typescript
363
- // Create product (Admin required)
364
- const product = await spaps.admin.createProduct({
131
+ await spaps.admin.createProduct({
365
132
  name: 'Premium Plan',
366
133
  category: 'subscription',
367
- description: 'Advanced features for power users',
368
- images: ['https://example.com/product-image.jpg'],
369
- metadata: { feature_set: 'premium' },
370
- active: true
371
- });
372
-
373
- // Update product (Admin required)
374
- const updatedProduct = await spaps.admin.updateProduct('prod_123', {
375
- name: 'Premium Plan Pro',
376
- description: 'Updated features'
134
+ description: 'Advanced features',
377
135
  });
378
136
 
379
- // Archive product (Admin required)
380
- await spaps.admin.deleteProduct('prod_123');
381
-
382
- // Get products (includes admin metadata if user is admin)
383
- const { data } = await spaps.admin.getProducts();
384
- if (data.adminMetadata) {
385
- console.log('Total revenue:', data.adminMetadata.total_revenue);
386
- }
387
- ```
137
+ await spaps.admin.updateProduct('prod_123', { name: 'Premium Plan Pro' });
388
138
 
389
- ### Price Management
390
-
391
- ```typescript
392
- // Create price (Admin required)
393
- const price = await spaps.admin.createPrice({
139
+ await spaps.admin.createPrice({
394
140
  product_id: 'prod_123',
395
- unit_amount: 2999, // $29.99
141
+ unit_amount: 2999,
396
142
  currency: 'usd',
397
143
  interval: 'month',
398
- interval_count: 1,
399
- nickname: 'Monthly Premium'
400
144
  });
401
145
  ```
402
146
 
403
- ### Super Admin Operations
147
+ ## Helper Methods
404
148
 
405
149
  ```typescript
406
- // Sync all products from Stripe (Super Admin required)
407
- const syncResult = await spaps.admin.syncProducts();
408
- console.log(`Synced ${syncResult.data.synced_count} products`);
150
+ spaps.isAuthenticated(); // boolean
151
+ spaps.getAccessToken(); // string | undefined
152
+ spaps.setAccessToken('token');
153
+ spaps.isLocalMode(); // boolean
154
+ await spaps.health();
409
155
  ```
410
156
 
411
- ### Permission Checking
157
+ ## Troubleshooting
412
158
 
413
- ```typescript
414
- // Check if current user can access admin features
415
- const user = await spaps.getCurrentUser();
416
- if (spaps.isAdmin(user)) {
417
- // Show admin UI components
418
- console.log('User has admin privileges');
419
- } else {
420
- // Show regular user UI
421
- console.log('Regular user');
422
- }
423
- ```
159
+ - `401 Unauthorized`: ensure valid access token and API key for non-local mode.
160
+ - `403 Forbidden`: account lacks admin role for admin endpoints.
161
+ - Local mode confusion: call `spaps.isLocalMode()` and verify `apiUrl`.
424
162
 
425
- ## Error Handling
426
-
427
- ```javascript
428
- try {
429
- await spaps.admin.createProduct(productData);
430
- } catch (error) {
431
- if (error.response?.status === 401) {
432
- console.error('Authentication required');
433
- } else if (error.response?.status === 403) {
434
- console.error('Admin privileges required');
435
- } else if (error.response?.status === 429) {
436
- console.error('Rate limited');
437
- } else {
438
- console.error('Error:', error.message);
439
- }
440
- }
163
+ ## Development
164
+
165
+ From repository root:
166
+
167
+ ```bash
168
+ cd packages/sdk
169
+ npm ci
170
+ npm run build
171
+ npm run test
172
+ npm run typecheck:readme
173
+ npm run test:readme
441
174
  ```
442
175
 
176
+ ## Release
177
+
178
+ - Main publish flow: `.github/workflows/publish.yml` (`publish-sdk` job).
179
+ - Manual publish flow: `.github/workflows/npm-publish-manual.yml`.
180
+ - npm credential source: repository secret `NPM_TOKEN`.
181
+
443
182
  ## License
444
183
 
445
184
  MIT
package/dist/index.js CHANGED
@@ -648,28 +648,23 @@ var SPAPSClient = class {
648
648
  }
649
649
  if (!apiUrl || apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) {
650
650
  this._isLocalMode = true;
651
- this.client = import_axios.default.create({
652
- baseURL: apiUrl || "http://localhost:3301",
653
- timeout: config.timeout || 1e4,
654
- headers: {
655
- "Content-Type": "application/json"
656
- }
657
- });
658
- } else {
659
- if (!effectiveApiKey) {
660
- console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
661
- }
662
- this.apiKey = effectiveApiKey;
663
- this.client = import_axios.default.create({
664
- baseURL: apiUrl,
665
- timeout: config.timeout || 1e4,
666
- headers: {
667
- "Content-Type": "application/json",
668
- ...this.apiKey && { "X-API-Key": this.apiKey }
669
- }
670
- });
671
651
  }
652
+ this.apiKey = effectiveApiKey;
653
+ if (!this.apiKey && !this._isLocalMode) {
654
+ console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
655
+ }
656
+ this.client = import_axios.default.create({
657
+ baseURL: apiUrl || "http://localhost:3301",
658
+ timeout: config.timeout || 1e4,
659
+ headers: {
660
+ "Content-Type": "application/json",
661
+ ...this.apiKey && { "X-API-Key": this.apiKey }
662
+ }
663
+ });
672
664
  this.client.interceptors.request.use((config2) => {
665
+ if (this.apiKey && !config2.headers["X-API-Key"]) {
666
+ config2.headers["X-API-Key"] = this.apiKey;
667
+ }
673
668
  if (this.accessToken && !config2.headers.Authorization) {
674
669
  config2.headers.Authorization = `Bearer ${this.accessToken}`;
675
670
  }
package/dist/index.mjs CHANGED
@@ -618,28 +618,23 @@ var SPAPSClient = class {
618
618
  }
619
619
  if (!apiUrl || apiUrl.includes("localhost") || apiUrl.includes("127.0.0.1")) {
620
620
  this._isLocalMode = true;
621
- this.client = axios.create({
622
- baseURL: apiUrl || "http://localhost:3301",
623
- timeout: config.timeout || 1e4,
624
- headers: {
625
- "Content-Type": "application/json"
626
- }
627
- });
628
- } else {
629
- if (!effectiveApiKey) {
630
- console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
631
- }
632
- this.apiKey = effectiveApiKey;
633
- this.client = axios.create({
634
- baseURL: apiUrl,
635
- timeout: config.timeout || 1e4,
636
- headers: {
637
- "Content-Type": "application/json",
638
- ...this.apiKey && { "X-API-Key": this.apiKey }
639
- }
640
- });
641
621
  }
622
+ this.apiKey = effectiveApiKey;
623
+ if (!this.apiKey && !this._isLocalMode) {
624
+ console.warn("\u26A0\uFE0F SPAPS: No API key provided. Some features may not work.");
625
+ }
626
+ this.client = axios.create({
627
+ baseURL: apiUrl || "http://localhost:3301",
628
+ timeout: config.timeout || 1e4,
629
+ headers: {
630
+ "Content-Type": "application/json",
631
+ ...this.apiKey && { "X-API-Key": this.apiKey }
632
+ }
633
+ });
642
634
  this.client.interceptors.request.use((config2) => {
635
+ if (this.apiKey && !config2.headers["X-API-Key"]) {
636
+ config2.headers["X-API-Key"] = this.apiKey;
637
+ }
643
638
  if (this.accessToken && !config2.headers.Authorization) {
644
639
  config2.headers.Authorization = `Bearer ${this.accessToken}`;
645
640
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spaps-sdk",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking, role-based access control, and dayrate scheduling",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,6 +20,8 @@
20
20
  "build": "tsup --tsconfig tsconfig.json src/index.ts --format cjs,esm --dts --clean",
21
21
  "dev": "tsup --tsconfig tsconfig.json src/index.ts --format cjs,esm --dts --watch",
22
22
  "test": "vitest run",
23
+ "test:readme": "vitest run test/readme-snippets.test.ts",
24
+ "typecheck:readme": "tsc -p tsconfig.readme.json --noEmit",
23
25
  "smoke-test": "npm run build && node smoke-test.js",
24
26
  "prepublishOnly": "npm run build && npm run smoke-test"
25
27
  },
@@ -36,10 +38,10 @@
36
38
  "scheduling"
37
39
  ],
38
40
  "author": "buildooor",
39
- "license": "UNLICENSED",
41
+ "license": "MIT",
40
42
  "repository": {
41
43
  "type": "git",
42
- "url": "https://github.com/build000r"
44
+ "url": "https://github.com/build000r/sweet-potato"
43
45
  },
44
46
  "homepage": "https://www.buildooor.com/services",
45
47
  "bugs": {
@@ -66,7 +68,9 @@
66
68
  },
67
69
  "files": [
68
70
  "dist",
69
- "README.md"
71
+ "README.md",
72
+ "CHANGELOG.md",
73
+ "PERMISSIONS.md"
70
74
  ],
71
75
  "publishConfig": {
72
76
  "access": "public",
@@ -75,4 +79,4 @@
75
79
  "engines": {
76
80
  "node": ">=14.0.0"
77
81
  }
78
- }
82
+ }