syntro-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.
package/src/index.ts ADDED
@@ -0,0 +1,361 @@
1
+ import type {
2
+ SyntroConfig,
3
+ EventType,
4
+ SyntroEvent,
5
+ AuthResult,
6
+ PaymentResult,
7
+ EventResult,
8
+ UserResult,
9
+ MetadataResult,
10
+ VerifyResult,
11
+ VerifyPaymentResult,
12
+ VerifyPaymentOptions,
13
+ } from './types';
14
+
15
+ export type {
16
+ SyntroConfig, EventType, SyntroEvent, AuthResult,
17
+ PaymentResult, EventResult, UserResult, MetadataResult, VerifyResult,
18
+ VerifyPaymentResult, VerifyPaymentOptions,
19
+ };
20
+
21
+ /**
22
+ * Syntro SDK — Official JavaScript/TypeScript client for api.syntro.run
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { Syntro } from 'syntro-sdk';
27
+ * const syntro = new Syntro('sk_your_api_key');
28
+ *
29
+ * // Events
30
+ * await syntro.event('CUSTOM', 'cart_add', 'User added to cart');
31
+ * await syntro.sendError('checkout_failed', 'Checkout error', { page: '/checkout' });
32
+ *
33
+ * // Auth
34
+ * const { token } = await syntro.login('username', 'password');
35
+ * const username = await syntro.getUsername(userId);
36
+ * const email = await syntro.getUserEmail(userId);
37
+ * const meta = await syntro.getMetadata(userId);
38
+ *
39
+ * // Billing
40
+ * const { url } = await syntro.createPayment('Pro Plan', 999);
41
+ * ```
42
+ */
43
+ export class Syntro {
44
+ private readonly apiKey: string;
45
+ private readonly baseUrl: string;
46
+
47
+ constructor(apiKey: string, config: SyntroConfig = {}) {
48
+ if (!apiKey) throw new Error('[Syntro] apiKey is required');
49
+ this.apiKey = apiKey;
50
+ this.baseUrl = (config.baseUrl ?? 'https://api.syntro.run').replace(/\/$/, '');
51
+ }
52
+
53
+ // ─── Helpers ────────────────────────────────────────────────────────────────
54
+
55
+ private get headers(): Record<string, string> {
56
+ return { 'Content-Type': 'application/json', Authorization: `Bearer ${this.apiKey}` };
57
+ }
58
+
59
+ private async post<T>(path: string, body: unknown): Promise<T> {
60
+ const res = await fetch(`${this.baseUrl}${path}`, {
61
+ method: 'POST', headers: this.headers, body: JSON.stringify(body),
62
+ });
63
+ return res.json() as Promise<T>;
64
+ }
65
+
66
+ private async put<T>(path: string, body: unknown): Promise<T> {
67
+ const res = await fetch(`${this.baseUrl}${path}`, {
68
+ method: 'PUT', headers: this.headers, body: JSON.stringify(body),
69
+ });
70
+ return res.json() as Promise<T>;
71
+ }
72
+
73
+ private async patch<T>(path: string, body: unknown): Promise<T> {
74
+ const res = await fetch(`${this.baseUrl}${path}`, {
75
+ method: 'PATCH', headers: this.headers, body: JSON.stringify(body),
76
+ });
77
+ return res.json() as Promise<T>;
78
+ }
79
+
80
+ private async del<T>(path: string): Promise<T> {
81
+ const res = await fetch(`${this.baseUrl}${path}`, {
82
+ method: 'DELETE', headers: this.headers,
83
+ });
84
+ return res.json() as Promise<T>;
85
+ }
86
+
87
+ private async get<T>(path: string, params?: Record<string, string | number>): Promise<T> {
88
+ const url = new URL(`${this.baseUrl}${path}`);
89
+ if (params) Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, String(v)));
90
+ const res = await fetch(url.toString(), { method: 'GET', headers: this.headers });
91
+ return res.json() as Promise<T>;
92
+ }
93
+
94
+ // ─── Events ─────────────────────────────────────────────────────────────────
95
+
96
+ /**
97
+ * Track an event.
98
+ * @param type - 'CUSTOM' | 'ERROR' | 'AUTH'
99
+ * @param name - Short event key, e.g. 'cart_add'
100
+ * @param message - Optional description
101
+ * @param metadata - Optional arbitrary JSON
102
+ * @param userId - Optional ProjectUser ID
103
+ */
104
+ async event(
105
+ type: EventType,
106
+ name: string,
107
+ message?: string,
108
+ metadata?: Record<string, unknown>,
109
+ userId?: string,
110
+ ): Promise<EventResult> {
111
+ try {
112
+ return await this.post<EventResult>('/v1/events', { type, name, message, metadata, userId });
113
+ } catch (err) {
114
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Track an ERROR event.
120
+ * @example
121
+ * await syntro.sendError('checkout_failed', 'Error loading page', { code: 500 });
122
+ */
123
+ async sendError(name: string, message?: string, metadata?: Record<string, unknown>): Promise<EventResult> {
124
+ return this.event('ERROR', name, message, metadata);
125
+ }
126
+
127
+ /** Get event statistics grouped by name (for analytics dashboard). */
128
+ async getStats(type?: EventType) {
129
+ return this.get('/v1/events/stats', type ? { type } : {});
130
+ }
131
+
132
+ /** List raw events with pagination. */
133
+ async listEvents(options?: { type?: EventType; limit?: number; skip?: number }) {
134
+ return this.get('/v1/events', {
135
+ ...(options?.type ? { type: options.type } : {}),
136
+ ...(options?.limit !== undefined ? { limit: options.limit } : {}),
137
+ ...(options?.skip !== undefined ? { skip: options.skip } : {}),
138
+ });
139
+ }
140
+
141
+ // ─── Auth ────────────────────────────────────────────────────────────────────
142
+
143
+ /**
144
+ * Register a new end-user.
145
+ * @example
146
+ * const { user, token, error } = await syntro.register('john', 'john@email.com', 'pass123');
147
+ */
148
+ async register(username: string, email: string, password: string): Promise<AuthResult> {
149
+ try {
150
+ const res = await this.post<AuthResult & { error?: string; message?: string }>(
151
+ '/v1/auth/register', { username, email, password }
152
+ );
153
+ return (res.error || res.message && !res.success)
154
+ ? { success: false, error: res.error ?? res.message }
155
+ : res;
156
+ } catch (err) {
157
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Login with username + password.
163
+ * @example
164
+ * const { token, user, error } = await syntro.login('john', 'pass123');
165
+ */
166
+ async login(username: string, password: string): Promise<AuthResult> {
167
+ try {
168
+ const res = await this.post<AuthResult & { error?: string }>('/v1/auth/login', { username, password });
169
+ return res.error ? { success: false, error: res.error } : res;
170
+ } catch (err) {
171
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Login with email + password.
177
+ */
178
+ async loginWithEmail(email: string, password: string): Promise<AuthResult> {
179
+ try {
180
+ const res = await this.post<AuthResult & { error?: string }>('/v1/auth/login', { email, password });
181
+ return res.error ? { success: false, error: res.error } : res;
182
+ } catch (err) {
183
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Verify a JWT token and get the associated user.
189
+ * @example
190
+ * const { valid, user } = await syntro.verifyToken(token);
191
+ */
192
+ async verifyToken(token: string): Promise<VerifyResult> {
193
+ try {
194
+ return await this.post<VerifyResult>('/v1/auth/verify', { token });
195
+ } catch (err) {
196
+ return { valid: false, error: err instanceof Error ? err.message : 'Unknown error' };
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Get a ProjectUser by ID.
202
+ * @example
203
+ * const { user } = await syntro.getUser(userId);
204
+ */
205
+ async getUser(userId: string): Promise<UserResult> {
206
+ try {
207
+ return await this.get<UserResult>(`/v1/auth/users/${userId}`);
208
+ } catch (err) {
209
+ return { error: err instanceof Error ? err.message : 'Unknown error' };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Get a user's username by ID.
215
+ * @example
216
+ * const username = await syntro.getUsername(userId);
217
+ */
218
+ async getUsername(userId: string): Promise<string | null> {
219
+ const { user, error } = await this.getUser(userId);
220
+ if (error || !user) return null;
221
+ return user.username ?? null;
222
+ }
223
+
224
+ /**
225
+ * Get a user's email by ID.
226
+ * @example
227
+ * const email = await syntro.getUserEmail(userId);
228
+ */
229
+ async getUserEmail(userId: string): Promise<string | null> {
230
+ const { user, error } = await this.getUser(userId);
231
+ if (error || !user) return null;
232
+ return user.email ?? null;
233
+ }
234
+
235
+ /**
236
+ * Get a user's metadata by ID.
237
+ * @example
238
+ * const meta = await syntro.getMetadata(userId);
239
+ * console.log(meta?.plan, meta?.role);
240
+ */
241
+ async getMetadata(userId: string): Promise<Record<string, unknown> | null> {
242
+ const { user, error } = await this.getUser(userId);
243
+ if (error || !user) return null;
244
+ return (user.metadata as Record<string, unknown>) ?? {};
245
+ }
246
+
247
+ /**
248
+ * Merge-update a user's metadata. Existing keys are preserved.
249
+ * @example
250
+ * await syntro.updateMetadata(userId, { plan: 'pro', role: 'admin' });
251
+ */
252
+ async updateMetadata(userId: string, metadata: Record<string, unknown>): Promise<MetadataResult> {
253
+ try {
254
+ return await this.patch<MetadataResult>(`/v1/auth/users/${userId}/metadata`, metadata);
255
+ } catch (err) {
256
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Update a user's username or fully replace their metadata.
262
+ * @example
263
+ * await syntro.updateUser(userId, { username: 'new_name' });
264
+ */
265
+ async updateUser(
266
+ userId: string,
267
+ data: { username?: string; metadata?: Record<string, unknown> },
268
+ ): Promise<UserResult> {
269
+ try {
270
+ return await this.put<UserResult>(`/v1/auth/users/${userId}`, data);
271
+ } catch (err) {
272
+ return { error: err instanceof Error ? err.message : 'Unknown error' };
273
+ }
274
+ }
275
+
276
+ /**
277
+ * Delete a ProjectUser permanently.
278
+ * @example
279
+ * const { success } = await syntro.deleteUser(userId);
280
+ */
281
+ async deleteUser(userId: string): Promise<{ success: boolean; error?: string }> {
282
+ try {
283
+ return await this.del<{ success: boolean; error?: string }>(`/v1/auth/users/${userId}`);
284
+ } catch (err) {
285
+ return { success: false, error: err instanceof Error ? err.message : 'Unknown error' };
286
+ }
287
+ }
288
+
289
+ /** List users with pagination. */
290
+ async listUsers(options?: { limit?: number; skip?: number }) {
291
+ return this.get('/v1/auth/users', {
292
+ ...(options?.limit !== undefined ? { limit: options.limit } : {}),
293
+ ...(options?.skip !== undefined ? { skip: options.skip } : {}),
294
+ });
295
+ }
296
+
297
+ // ─── Billing ─────────────────────────────────────────────────────────────────
298
+
299
+ /**
300
+ * Create a Stripe Checkout payment session.
301
+ * Returns a `url` to redirect the customer to.
302
+ *
303
+ * @param name - Product/payment name
304
+ * @param amount - Amount in cents (e.g. 999 = $9.99)
305
+ * @param options - currency, successUrl, cancelUrl, customerEmail
306
+ *
307
+ * @example
308
+ * const { url, error } = await syntro.createPayment('Pro Plan', 999);
309
+ * if (url) window.location.href = url;
310
+ */
311
+ async createPayment(
312
+ name: string,
313
+ amount: number,
314
+ options?: {
315
+ currency?: string;
316
+ successUrl?: string;
317
+ cancelUrl?: string;
318
+ customerEmail?: string;
319
+ },
320
+ ): Promise<PaymentResult> {
321
+ try {
322
+ const res = await this.post<PaymentResult & { error?: string }>('/v1/billing/create-payment', {
323
+ name, amount,
324
+ currency: options?.currency ?? 'usd',
325
+ successUrl: options?.successUrl,
326
+ cancelUrl: options?.cancelUrl,
327
+ customerEmail: options?.customerEmail,
328
+ });
329
+ return res.error ? { error: res.error } : res;
330
+ } catch (err) {
331
+ return { error: err instanceof Error ? err.message : 'Unknown error' };
332
+ }
333
+ }
334
+
335
+ /** List payment transactions. */
336
+ async listTransactions(options?: { limit?: number; skip?: number; status?: string }) {
337
+ return this.get('/v1/billing/transactions', {
338
+ ...(options?.limit !== undefined ? { limit: options.limit } : {}),
339
+ ...(options?.skip !== undefined ? { skip: options.skip } : {}),
340
+ ...(options?.status ? { status: options.status } : {}),
341
+ });
342
+ }
343
+
344
+ /**
345
+ * Verify if a customer has paid for a specific product or any product.
346
+ *
347
+ * @example
348
+ * const { paid, transaction } = await syntro.verifyPayment('customer@email.com');
349
+ * if (paid) console.log('Customer paid!', transaction.amount);
350
+ */
351
+ async verifyPayment(customerEmail: string, options?: VerifyPaymentOptions): Promise<VerifyPaymentResult> {
352
+ try {
353
+ return await this.get<VerifyPaymentResult>('/v1/billing/verify-payment', {
354
+ customerEmail,
355
+ ...(options?.name ? { name: options.name } : {}),
356
+ });
357
+ } catch (err) {
358
+ return { paid: false, error: err instanceof Error ? err.message : 'Unknown error' };
359
+ }
360
+ }
361
+ }
package/src/types.ts ADDED
@@ -0,0 +1,76 @@
1
+ export interface SyntroConfig {
2
+ baseUrl?: string;
3
+ }
4
+
5
+ export type EventType = 'CUSTOM' | 'ERROR' | 'AUTH';
6
+
7
+ export interface SyntroEvent {
8
+ type: EventType;
9
+ name: string;
10
+ message?: string;
11
+ metadata?: Record<string, unknown>;
12
+ userId?: string;
13
+ }
14
+
15
+ export interface SyntroUser {
16
+ id: string;
17
+ username: string;
18
+ email: string;
19
+ createdAt?: string;
20
+ updatedAt?: string;
21
+ metadata?: unknown;
22
+ provider?: string;
23
+ }
24
+
25
+ export interface AuthResult {
26
+ success: boolean;
27
+ user?: SyntroUser;
28
+ token?: string;
29
+ error?: string;
30
+ }
31
+
32
+ export interface UserResult {
33
+ user?: SyntroUser;
34
+ success?: boolean;
35
+ error?: string;
36
+ }
37
+
38
+ export interface MetadataResult {
39
+ success: boolean;
40
+ metadata?: Record<string, unknown>;
41
+ error?: string;
42
+ }
43
+
44
+ export interface VerifyResult {
45
+ valid: boolean;
46
+ user?: SyntroUser;
47
+ error?: string;
48
+ }
49
+
50
+ export interface PaymentResult {
51
+ url?: string | null;
52
+ sessionId?: string;
53
+ error?: string;
54
+ }
55
+
56
+ export interface EventResult {
57
+ success: boolean;
58
+ eventId?: string;
59
+ error?: string;
60
+ }
61
+
62
+ export interface VerifyPaymentResult {
63
+ paid: boolean;
64
+ transaction?: {
65
+ id: string;
66
+ amount: number;
67
+ currency: string;
68
+ createdAt: string;
69
+ } | null;
70
+ error?: string;
71
+ }
72
+
73
+ export interface VerifyPaymentOptions {
74
+ name?: string;
75
+ }
76
+
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2020", "DOM"],
7
+ "strict": true,
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true
12
+ },
13
+ "include": ["src"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }