@dataferry/sdk 0.1.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/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # @dataferry/sdk
2
+
3
+ Node.js SDK for DataFerry - data export infrastructure for SaaS companies.
4
+
5
+ DataFerry helps you provide end-user data export functionality for GDPR/CCPA compliance and data portability.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @dataferry/sdk
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { Ferry } from '@dataferry/sdk';
17
+
18
+ const ferry = new Ferry({
19
+ apiKey: process.env.DATAFERRY_API_KEY,
20
+ });
21
+
22
+ // Create a portal session for an end user
23
+ const session = await ferry.createPortalSession({
24
+ scopeId: 'user_123',
25
+ returnUrl: 'https://app.example.com/settings',
26
+ });
27
+
28
+ // Redirect the user to the portal URL
29
+ // session.url -> https://portal.dataferry.dev/session/...
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ### Creating Portal Sessions
35
+
36
+ The simplest way to let users export their data is through the portal:
37
+
38
+ ```typescript
39
+ // Multi-tenant: specify the scope ID (typically user ID)
40
+ const session = await ferry.createPortalSession({
41
+ scopeId: 'user_123',
42
+ returnUrl: 'https://app.example.com/settings',
43
+ });
44
+
45
+ // Single-tenant: no scopeId needed
46
+ const session = await ferry.createPortalSession({
47
+ returnUrl: 'https://app.example.com/settings',
48
+ });
49
+
50
+ // With all options
51
+ const session = await ferry.createPortalSession({
52
+ scopeId: 'user_123',
53
+ connectionId: 'conn_abc', // Use a specific database connection
54
+ returnUrl: 'https://...', // Where to redirect after export
55
+ expiresIn: 3600, // Session duration in seconds
56
+ metadata: { plan: 'pro' }, // Custom metadata
57
+ });
58
+
59
+ console.log(session.url); // Redirect user here
60
+ console.log(session.expiresAt); // Session expiration
61
+ ```
62
+
63
+ ### Programmatic Exports
64
+
65
+ For server-to-server export workflows:
66
+
67
+ ```typescript
68
+ // Create an export job
69
+ const exportJob = await ferry.exports.create({
70
+ scopeId: 'user_123',
71
+ format: 'csv',
72
+ tables: ['users', 'posts', 'comments'],
73
+ });
74
+
75
+ // Poll until complete
76
+ const completed = await ferry.exports.poll(exportJob.id, {
77
+ maxWait: 300000, // 5 minutes
78
+ interval: 2000, // Check every 2 seconds
79
+ });
80
+
81
+ if (completed.status === 'completed') {
82
+ console.log('Download URL:', completed.downloadUrl);
83
+ }
84
+ ```
85
+
86
+ ### Managing Database Connections
87
+
88
+ ```typescript
89
+ // List all connections
90
+ const connections = await ferry.connections.list();
91
+
92
+ // Create a new connection
93
+ const connection = await ferry.connections.create({
94
+ name: 'Production DB',
95
+ host: 'db.example.com',
96
+ port: '5432',
97
+ database: 'myapp',
98
+ user: 'ferry_readonly',
99
+ password: 'secret',
100
+ ssl: true,
101
+ });
102
+
103
+ // Test a connection
104
+ const result = await ferry.connections.test(connection.id);
105
+ if (result.connected) {
106
+ console.log(`Connected to ${result.database}`);
107
+ }
108
+
109
+ ```
110
+
111
+ ## API Reference
112
+
113
+ ### `Ferry`
114
+
115
+ The main client class.
116
+
117
+ ```typescript
118
+ const ferry = new Ferry({
119
+ apiKey: string, // Required: Your API key
120
+ baseUrl?: string, // Optional: API base URL (default: https://api.dataferry.dev)
121
+ timeout?: number, // Optional: Request timeout in ms (default: 30000)
122
+ });
123
+ ```
124
+
125
+ ### `ferry.createPortalSession(params)`
126
+
127
+ Creates a portal session for an end user.
128
+
129
+ | Parameter | Type | Description |
130
+ |-----------|------|-------------|
131
+ | `scopeId` | `string?` | The scope ID for data filtering (optional for single-tenant) |
132
+ | `connectionId` | `string?` | Database connection ID (defaults to primary) |
133
+ | `returnUrl` | `string?` | URL to redirect after export |
134
+ | `expiresIn` | `number?` | Session duration in seconds |
135
+ | `metadata` | `object?` | Custom metadata |
136
+
137
+ Returns: `{ id: string, url: string, expiresAt: Date }`
138
+
139
+ ### `ferry.exports`
140
+
141
+ Export job management.
142
+
143
+ - `create(params)` - Create a new export job
144
+ - `retrieve(id)` - Get export job details
145
+ - `list(params?)` - List export jobs
146
+ - `poll(id, options?)` - Wait for export to complete
147
+
148
+ ### `ferry.connections`
149
+
150
+ Database connection management.
151
+
152
+ - `list()` - List all connections
153
+ - `create(params)` - Create a new connection
154
+ - `retrieve(id)` - Get connection details
155
+ - `update(id, params)` - Update a connection
156
+ - `delete(id)` - Delete a connection
157
+ - `test(id)` - Test a connection
158
+
159
+ ### `ferry.portalSessions`
160
+
161
+ Portal session management (advanced).
162
+
163
+ - `create(params)` - Create a portal session
164
+ - `retrieve(id)` - Get session details
165
+ - `revoke(id)` - Revoke a session
166
+
167
+ ## Error Handling
168
+
169
+ ```typescript
170
+ import { Ferry, FerryError } from '@dataferry/sdk';
171
+
172
+ try {
173
+ const session = await ferry.createPortalSession({ scopeId: 'user_123' });
174
+ } catch (error) {
175
+ if (error instanceof FerryError) {
176
+ console.error(`API Error: ${error.message}`);
177
+ console.error(`Code: ${error.code}`);
178
+ console.error(`Status: ${error.status}`);
179
+ }
180
+ }
181
+ ```
182
+
183
+ ## TypeScript
184
+
185
+ The SDK is written in TypeScript and includes full type definitions.
186
+
187
+ ```typescript
188
+ import {
189
+ Ferry,
190
+ FerryConfig,
191
+ FerryError,
192
+ CreatePortalSessionParams,
193
+ PortalSessionResult,
194
+ ExportJob,
195
+ DatabaseConnection,
196
+ } from '@dataferry/sdk';
197
+ ```
198
+
199
+ ## License
200
+
201
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,464 @@
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/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Connections: () => Connections,
24
+ Exports: () => Exports,
25
+ Ferry: () => Ferry,
26
+ FerryError: () => FerryError,
27
+ PortalSessions: () => PortalSessions
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+
31
+ // src/resources/portalSessions.ts
32
+ var PortalSessions = class {
33
+ constructor(client) {
34
+ this.client = client;
35
+ }
36
+ /**
37
+ * Create a new portal session for an end user
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * const session = await ferry.portalSessions.create({
42
+ * profile: 'default',
43
+ * scopeId: 'user_123',
44
+ * expiresIn: 3600,
45
+ * returnUrl: 'https://yourapp.com/settings',
46
+ * });
47
+ *
48
+ * // Redirect user to session.url
49
+ * ```
50
+ */
51
+ async create(params) {
52
+ return this.client.request(
53
+ "POST",
54
+ "/api/v1/portal-sessions",
55
+ params
56
+ );
57
+ }
58
+ /**
59
+ * Get portal session details
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const session = await ferry.portalSessions.retrieve('ps_abc123');
64
+ * console.log(session.scopeId, session.expiresAt);
65
+ * ```
66
+ */
67
+ async retrieve(sessionId) {
68
+ return this.client.request(
69
+ "GET",
70
+ `/api/v1/portal-sessions/${sessionId}`
71
+ );
72
+ }
73
+ /**
74
+ * Revoke a portal session
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * await ferry.portalSessions.revoke('ps_abc123');
79
+ * ```
80
+ */
81
+ async revoke(sessionId) {
82
+ return this.client.request(
83
+ "DELETE",
84
+ `/api/v1/portal-sessions/${sessionId}`
85
+ );
86
+ }
87
+ };
88
+
89
+ // src/resources/exports.ts
90
+ var Exports = class {
91
+ constructor(client) {
92
+ this.client = client;
93
+ }
94
+ /**
95
+ * Create a new export job
96
+ *
97
+ * @example
98
+ * ```ts
99
+ * // Create export using primary connection
100
+ * const exportJob = await ferry.exports.create({
101
+ * scopeId: 'user_123',
102
+ * format: 'csv',
103
+ * tables: ['users', 'posts', 'comments'],
104
+ * });
105
+ *
106
+ * // Create export using specific connection
107
+ * const exportJob = await ferry.exports.create({
108
+ * scopeId: 'user_123',
109
+ * connectionId: 'conn_abc123',
110
+ * format: 'csv',
111
+ * tables: ['users', 'posts'],
112
+ * });
113
+ *
114
+ * console.log(exportJob.id, exportJob.status);
115
+ * ```
116
+ */
117
+ async create(params) {
118
+ return this.client.request(
119
+ "POST",
120
+ "/api/v1/exports",
121
+ params
122
+ );
123
+ }
124
+ /**
125
+ * Get export job details
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * const exportJob = await ferry.exports.retrieve('exp_abc123');
130
+ *
131
+ * if (exportJob.status === 'completed') {
132
+ * console.log('Download:', exportJob.downloadUrl);
133
+ * }
134
+ * ```
135
+ */
136
+ async retrieve(exportId) {
137
+ return this.client.request(
138
+ "GET",
139
+ `/api/v1/exports/${exportId}`
140
+ );
141
+ }
142
+ /**
143
+ * List exports
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * // List all exports
148
+ * const { data, meta } = await ferry.exports.list();
149
+ *
150
+ * // Filter by scope
151
+ * const { data } = await ferry.exports.list({ scopeId: 'user_123' });
152
+ *
153
+ * // Filter by status
154
+ * const { data } = await ferry.exports.list({ status: 'completed' });
155
+ *
156
+ * // Filter by connection
157
+ * const { data } = await ferry.exports.list({ connectionId: 'conn_abc123' });
158
+ * ```
159
+ */
160
+ async list(params) {
161
+ return this.client.requestPaginated(
162
+ "/api/v1/exports",
163
+ params
164
+ );
165
+ }
166
+ /**
167
+ * Wait for an export to complete
168
+ *
169
+ * @example
170
+ * ```ts
171
+ * const exportJob = await ferry.exports.create({ scopeId: 'user_123' });
172
+ * const completed = await ferry.exports.poll(exportJob.id, { maxWait: 300000 });
173
+ *
174
+ * if (completed.status === 'completed') {
175
+ * console.log('Download:', completed.downloadUrl);
176
+ * }
177
+ * ```
178
+ */
179
+ async poll(exportId, options = {}) {
180
+ const maxWait = options.maxWait ?? 3e5;
181
+ const interval = options.interval ?? 2e3;
182
+ const startTime = Date.now();
183
+ while (Date.now() - startTime < maxWait) {
184
+ const exportJob = await this.retrieve(exportId);
185
+ if (exportJob.status === "completed" || exportJob.status === "failed" || exportJob.status === "expired") {
186
+ return exportJob;
187
+ }
188
+ await new Promise((resolve) => setTimeout(resolve, interval));
189
+ }
190
+ throw new Error(`Export ${exportId} did not complete within ${maxWait}ms`);
191
+ }
192
+ };
193
+
194
+ // src/resources/connections.ts
195
+ var Connections = class {
196
+ constructor(client) {
197
+ this.client = client;
198
+ }
199
+ /**
200
+ * List all database connections for the organization
201
+ *
202
+ * @example
203
+ * ```ts
204
+ * const connections = await ferry.connections.list();
205
+ * for (const conn of connections) {
206
+ * console.log(conn.name);
207
+ * }
208
+ * ```
209
+ */
210
+ async list() {
211
+ const result = await this.client.requestPaginated(
212
+ "/api/v1/connections"
213
+ );
214
+ return result.data;
215
+ }
216
+ /**
217
+ * Create a new database connection
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * const connection = await ferry.connections.create({
222
+ * name: 'Production DB',
223
+ * host: 'db.example.com',
224
+ * port: '5432',
225
+ * database: 'myapp',
226
+ * user: 'ferry_readonly',
227
+ * password: 'secret',
228
+ * ssl: true,
229
+ * });
230
+ * ```
231
+ */
232
+ async create(params) {
233
+ return this.client.request(
234
+ "POST",
235
+ "/api/v1/connections",
236
+ params
237
+ );
238
+ }
239
+ /**
240
+ * Get a database connection by ID
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const connection = await ferry.connections.retrieve('conn_abc123');
245
+ * console.log(connection.host, connection.database);
246
+ * ```
247
+ */
248
+ async retrieve(connectionId) {
249
+ return this.client.request(
250
+ "GET",
251
+ `/api/v1/connections/${connectionId}`
252
+ );
253
+ }
254
+ /**
255
+ * Update a database connection
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * const connection = await ferry.connections.update('conn_abc123', {
260
+ * name: 'Production DB (Updated)',
261
+ * });
262
+ * ```
263
+ */
264
+ async update(connectionId, params) {
265
+ return this.client.request(
266
+ "PUT",
267
+ `/api/v1/connections/${connectionId}`,
268
+ params
269
+ );
270
+ }
271
+ /**
272
+ * Delete a database connection
273
+ *
274
+ * @example
275
+ * ```ts
276
+ * const result = await ferry.connections.delete('conn_abc123');
277
+ * console.log(result.deleted); // true
278
+ * ```
279
+ */
280
+ async delete(connectionId) {
281
+ return this.client.request(
282
+ "DELETE",
283
+ `/api/v1/connections/${connectionId}`
284
+ );
285
+ }
286
+ /**
287
+ * Test a database connection
288
+ *
289
+ * @example
290
+ * ```ts
291
+ * const result = await ferry.connections.test('conn_abc123');
292
+ * if (result.connected) {
293
+ * console.log(`Connected to ${result.database} as ${result.user}`);
294
+ * } else {
295
+ * console.error(`Connection failed: ${result.error}`);
296
+ * }
297
+ * ```
298
+ */
299
+ async test(connectionId) {
300
+ return this.client.request(
301
+ "POST",
302
+ `/api/v1/connections/${connectionId}/test`
303
+ );
304
+ }
305
+ };
306
+
307
+ // src/client.ts
308
+ var Ferry = class {
309
+ apiKey;
310
+ baseUrl;
311
+ timeout;
312
+ /** Portal session management */
313
+ portalSessions;
314
+ /** Export management */
315
+ exports;
316
+ /** Database connection management */
317
+ connections;
318
+ constructor(config) {
319
+ this.apiKey = config.apiKey;
320
+ this.baseUrl = config.baseUrl ?? "https://api.dataferry.dev";
321
+ this.timeout = config.timeout ?? 3e4;
322
+ this.portalSessions = new PortalSessions(this);
323
+ this.exports = new Exports(this);
324
+ this.connections = new Connections(this);
325
+ }
326
+ /**
327
+ * Create a portal session for an end user to export their data.
328
+ *
329
+ * This is the primary method for initiating a data export. It creates a
330
+ * secure session and returns a URL where the user can select and download
331
+ * their data.
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * const ferry = new Ferry({ apiKey: 'your-api-key' });
336
+ *
337
+ * // Create a session for a specific user
338
+ * const session = await ferry.createPortalSession({
339
+ * profile: 'default',
340
+ * scopeId: 'user_123',
341
+ * returnUrl: 'https://app.example.com/settings',
342
+ * });
343
+ *
344
+ * // Redirect the user to the portal
345
+ * res.redirect(session.url);
346
+ * ```
347
+ *
348
+ * @example
349
+ * ```typescript
350
+ * // Single-tenant mode (no scopeId required)
351
+ * const session = await ferry.createPortalSession({
352
+ * profile: 'default',
353
+ * returnUrl: 'https://app.example.com/settings',
354
+ * });
355
+ * ```
356
+ */
357
+ async createPortalSession(params) {
358
+ const request = { profile: params.profile };
359
+ if (params.scopeId !== void 0) {
360
+ request.scopeId = params.scopeId;
361
+ }
362
+ if (params.returnUrl !== void 0) {
363
+ request.returnUrl = params.returnUrl;
364
+ }
365
+ if (params.expiresIn !== void 0) {
366
+ request.expiresIn = params.expiresIn;
367
+ }
368
+ if (params.email !== void 0) {
369
+ request.email = params.email;
370
+ }
371
+ if (params.metadata !== void 0) {
372
+ request.metadata = params.metadata;
373
+ }
374
+ const response = await this.portalSessions.create(request);
375
+ return {
376
+ id: response.id,
377
+ url: response.url,
378
+ expiresAt: response.expiresAt
379
+ };
380
+ }
381
+ /**
382
+ * Make an authenticated API request
383
+ */
384
+ async request(method, path, body) {
385
+ const url = `${this.baseUrl}${path}`;
386
+ const headers = {
387
+ "Content-Type": "application/json",
388
+ Authorization: `Basic ${Buffer.from(this.apiKey + ":").toString("base64")}`
389
+ };
390
+ const controller = new AbortController();
391
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
392
+ try {
393
+ const fetchOptions = {
394
+ method,
395
+ headers,
396
+ signal: controller.signal
397
+ };
398
+ if (body) {
399
+ fetchOptions.body = JSON.stringify(body);
400
+ }
401
+ const response = await fetch(url, fetchOptions);
402
+ const data = await response.json();
403
+ if (!response.ok || !data.success) {
404
+ const error = new FerryError(
405
+ data.error?.message ?? "An error occurred",
406
+ data.error?.code ?? "UNKNOWN_ERROR",
407
+ response.status
408
+ );
409
+ throw error;
410
+ }
411
+ return data.data;
412
+ } finally {
413
+ clearTimeout(timeoutId);
414
+ }
415
+ }
416
+ /**
417
+ * Make a paginated API request
418
+ */
419
+ async requestPaginated(path, params) {
420
+ const searchParams = new URLSearchParams();
421
+ if (params) {
422
+ for (const [key, value] of Object.entries(params)) {
423
+ if (value !== void 0) {
424
+ searchParams.set(key, String(value));
425
+ }
426
+ }
427
+ }
428
+ const url = searchParams.toString() ? `${path}?${searchParams}` : path;
429
+ const response = await fetch(`${this.baseUrl}${url}`, {
430
+ headers: {
431
+ Authorization: `Basic ${Buffer.from(this.apiKey + ":").toString("base64")}`
432
+ }
433
+ });
434
+ const result = await response.json();
435
+ if (!response.ok || !result.success) {
436
+ throw new FerryError(
437
+ result.error?.message ?? "An error occurred",
438
+ result.error?.code ?? "UNKNOWN_ERROR",
439
+ response.status
440
+ );
441
+ }
442
+ return {
443
+ data: result.data,
444
+ meta: result.meta
445
+ };
446
+ }
447
+ };
448
+ var FerryError = class extends Error {
449
+ constructor(message, code, status) {
450
+ super(message);
451
+ this.code = code;
452
+ this.status = status;
453
+ this.name = "FerryError";
454
+ }
455
+ };
456
+ // Annotate the CommonJS export names for ESM import in node:
457
+ 0 && (module.exports = {
458
+ Connections,
459
+ Exports,
460
+ Ferry,
461
+ FerryError,
462
+ PortalSessions
463
+ });
464
+ //# sourceMappingURL=index.cjs.map