@tapstack/db 1.0.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +655 -0
  2. package/dist/adapters/index.d.ts +6 -0
  3. package/dist/adapters/index.js +22 -0
  4. package/dist/adapters/nodejs.adapter.d.ts +63 -0
  5. package/dist/adapters/nodejs.adapter.js +204 -0
  6. package/dist/adapters/types.d.ts +77 -0
  7. package/dist/adapters/types.js +19 -0
  8. package/dist/index.d.ts +101 -21
  9. package/dist/index.js +114 -41
  10. package/dist/modules/automations.d.ts +109 -0
  11. package/dist/modules/automations.js +59 -0
  12. package/dist/modules/conversations.d.ts +82 -0
  13. package/dist/modules/conversations.js +54 -0
  14. package/dist/modules/fields.d.ts +30 -9
  15. package/dist/modules/fields.js +31 -13
  16. package/dist/modules/files.d.ts +68 -0
  17. package/dist/modules/files.js +115 -0
  18. package/dist/modules/index.d.ts +12 -0
  19. package/dist/modules/index.js +28 -0
  20. package/dist/modules/objects.d.ts +30 -9
  21. package/dist/modules/objects.js +35 -13
  22. package/dist/modules/organizations.d.ts +69 -0
  23. package/dist/modules/organizations.js +83 -0
  24. package/dist/modules/records.d.ts +47 -5
  25. package/dist/modules/records.js +70 -5
  26. package/dist/modules/workspaces.d.ts +44 -0
  27. package/dist/modules/workspaces.js +57 -0
  28. package/dist/types.d.ts +159 -10
  29. package/dist/types.js +19 -0
  30. package/package.json +16 -7
  31. package/src/__tests__/client.test.ts +305 -49
  32. package/src/adapters/index.ts +13 -0
  33. package/src/adapters/nodejs.adapter.ts +298 -0
  34. package/src/adapters/types.ts +108 -0
  35. package/src/index.ts +132 -44
  36. package/src/modules/automations.ts +157 -0
  37. package/src/modules/conversations.ts +134 -0
  38. package/src/modules/fields.ts +64 -14
  39. package/src/modules/files.ts +144 -0
  40. package/src/modules/index.ts +19 -0
  41. package/src/modules/objects.ts +46 -14
  42. package/src/modules/organizations.ts +137 -0
  43. package/src/modules/records.ts +119 -6
  44. package/src/modules/workspaces.ts +95 -0
  45. package/src/types.ts +229 -9
  46. package/dist/request.d.ts +0 -2
  47. package/dist/request.js +0 -20
  48. package/src/request.ts +0 -14
package/README.md ADDED
@@ -0,0 +1,655 @@
1
+ # @tapstack/db
2
+
3
+ Official SDK for the [Tapstack API](https://api.tapstack.com). Build applications with a Notion-like database backend featuring objects (models), fields, records, file storage, automations, and more.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @tapstack/db
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { createClient } from '@tapstack/db';
15
+
16
+ // Create a client with API key authentication
17
+ const client = createClient({
18
+ apiKey: 'your-api-key',
19
+ workspaceId: 'your-workspace-id',
20
+ });
21
+
22
+ // List all objects in the workspace
23
+ const { objects } = await client.objects.list();
24
+
25
+ // Create a record
26
+ const record = await client.records.create('contacts', {
27
+ data: {
28
+ name: 'John Doe',
29
+ email: 'john@example.com',
30
+ },
31
+ });
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ ### Basic Configuration
37
+
38
+ ```typescript
39
+ import { createClient } from '@tapstack/db';
40
+
41
+ const client = createClient({
42
+ // API URL (optional, defaults to https://api.tapstack.com)
43
+ baseUrl: 'https://api.tapstack.com',
44
+
45
+ // Authentication (choose one)
46
+ apiKey: 'your-api-key', // For server-side/programmatic access
47
+ userToken: 'jwt-token', // For authenticated user sessions
48
+
49
+ // Workspace context
50
+ workspaceId: 'workspace-uuid',
51
+
52
+ // Error handling
53
+ onAuthError: () => {
54
+ // Handle authentication errors (e.g., redirect to login)
55
+ },
56
+ });
57
+ ```
58
+
59
+ ### Frontend Configuration (Dynamic Token)
60
+
61
+ For frontend applications where the access token may change:
62
+
63
+ ```typescript
64
+ const client = createClient({
65
+ getUserToken: async () => {
66
+ // Return the current access token from your auth state
67
+ return localStorage.getItem('access_token');
68
+ },
69
+ workspaceId: 'workspace-uuid',
70
+ onAuthError: () => {
71
+ // Redirect to login
72
+ window.location.href = '/login';
73
+ },
74
+ });
75
+ ```
76
+
77
+ ### Switching Workspaces
78
+
79
+ ```typescript
80
+ // Set workspace context for all subsequent requests
81
+ client.setWorkspaceId('new-workspace-id');
82
+
83
+ // Get current workspace ID
84
+ const workspaceId = client.getWorkspaceId();
85
+ ```
86
+
87
+ ## Objects (Models)
88
+
89
+ Objects define your data schema, similar to tables in a database or models in Notion.
90
+
91
+ ### List Objects
92
+
93
+ ```typescript
94
+ const { objects } = await client.objects.list();
95
+
96
+ for (const obj of objects) {
97
+ console.log(`${obj.icon} ${obj.pluralName} (${obj.slug})`);
98
+ }
99
+ ```
100
+
101
+ ### Get Object by Slug
102
+
103
+ ```typescript
104
+ const contacts = await client.objects.get('contacts');
105
+ console.log(contacts.singleName); // "Contact"
106
+ ```
107
+
108
+ ### Create Object
109
+
110
+ ```typescript
111
+ const newObject = await client.objects.create({
112
+ singleName: 'Contact',
113
+ pluralName: 'Contacts',
114
+ slug: 'contacts', // Optional, auto-generated if not provided
115
+ icon: '👤', // Optional
116
+ });
117
+ ```
118
+
119
+ ### Update Object
120
+
121
+ ```typescript
122
+ const updated = await client.objects.update('contacts', {
123
+ singleName: 'Customer',
124
+ pluralName: 'Customers',
125
+ icon: '🧑‍💼',
126
+ });
127
+ ```
128
+
129
+ ### Delete Object
130
+
131
+ ```typescript
132
+ // Soft delete (default)
133
+ await client.objects.delete('contacts');
134
+
135
+ // Hard delete
136
+ await client.objects.delete('contacts', false);
137
+ ```
138
+
139
+ ## Fields
140
+
141
+ Fields define the schema of an object, similar to columns in a database.
142
+
143
+ ### Supported Field Types
144
+
145
+ - `text` - Plain text
146
+ - `number` - Numeric values
147
+ - `boolean` - True/false
148
+ - `date` - Date only
149
+ - `datetime` - Date and time
150
+ - `email` - Email address
151
+ - `phone` - Phone number
152
+ - `url` - URL/link
153
+ - `select` / `single_select` - Single selection dropdown
154
+ - `multi_select` - Multiple selection
155
+ - `checkbox` - Checkbox
156
+ - `relation` - Relationship to another object
157
+ - `currency` - Currency values
158
+ - `rating` - Star rating
159
+ - `textarea` - Long text
160
+ - `json` - JSON data
161
+ - `files` - File attachments
162
+ - `person` - User reference
163
+ - `ai` - AI-generated field
164
+
165
+ ### List Fields
166
+
167
+ ```typescript
168
+ const { fields } = await client.fields.list('contacts');
169
+
170
+ for (const field of fields) {
171
+ console.log(`${field.label}: ${field.type}`);
172
+ }
173
+ ```
174
+
175
+ ### Create Field
176
+
177
+ ```typescript
178
+ const emailField = await client.fields.create('contacts', {
179
+ label: 'Email',
180
+ value: 'email', // Field key (used in record data)
181
+ type: 'email',
182
+ data: {
183
+ required: true,
184
+ placeholder: 'Enter email address',
185
+ },
186
+ });
187
+
188
+ // Create a select field with options
189
+ const statusField = await client.fields.create('contacts', {
190
+ label: 'Status',
191
+ value: 'status',
192
+ type: 'single_select',
193
+ data: {
194
+ options: [
195
+ { label: 'Active', value: 'active', color: '#22c55e' },
196
+ { label: 'Inactive', value: 'inactive', color: '#ef4444' },
197
+ ],
198
+ },
199
+ });
200
+
201
+ // Create a relation field
202
+ const companyField = await client.fields.create('contacts', {
203
+ label: 'Company',
204
+ value: 'company',
205
+ type: 'relation',
206
+ data: {
207
+ relatedObjectId: 'companies-object-uuid',
208
+ relationType: 'many-to-one',
209
+ },
210
+ });
211
+ ```
212
+
213
+ ### Update Field
214
+
215
+ ```typescript
216
+ const updated = await client.fields.update('contacts', 'field-id', {
217
+ label: 'Email Address',
218
+ data: {
219
+ required: false,
220
+ },
221
+ });
222
+ ```
223
+
224
+ ### Delete Field
225
+
226
+ ```typescript
227
+ await client.fields.delete('contacts', 'field-id');
228
+ ```
229
+
230
+ ## Records
231
+
232
+ Records are the data entries within an object.
233
+
234
+ ### List Records
235
+
236
+ ```typescript
237
+ const response = await client.records.list('contacts', {
238
+ page: 1,
239
+ pageSize: 50,
240
+ sortBy: 'createdAt',
241
+ sortOrder: 'desc',
242
+ });
243
+
244
+ console.log(`Total: ${response.total}`);
245
+ for (const record of response.items) {
246
+ console.log(record.data.name);
247
+ }
248
+ ```
249
+
250
+ ### Get Record
251
+
252
+ ```typescript
253
+ const record = await client.records.get('contacts', 'record-id');
254
+ console.log(record.data);
255
+ ```
256
+
257
+ ### Create Record
258
+
259
+ ```typescript
260
+ const record = await client.records.create('contacts', {
261
+ data: {
262
+ name: 'Jane Smith',
263
+ email: 'jane@example.com',
264
+ status: 'active',
265
+ company: 'company-record-id', // Relation field
266
+ },
267
+ status: 'published', // Optional: 'draft' | 'published'
268
+ });
269
+ ```
270
+
271
+ ### Update Record
272
+
273
+ ```typescript
274
+ const updated = await client.records.update('contacts', 'record-id', {
275
+ data: {
276
+ name: 'Jane Doe',
277
+ },
278
+ });
279
+ ```
280
+
281
+ ### Delete Record
282
+
283
+ ```typescript
284
+ // Soft delete (default)
285
+ await client.records.delete('contacts', 'record-id');
286
+
287
+ // Hard delete
288
+ await client.records.delete('contacts', 'record-id', false);
289
+ ```
290
+
291
+ ### Bulk Operations
292
+
293
+ ```typescript
294
+ // Bulk create
295
+ const { records, count } = await client.records.bulkCreate('contacts', [
296
+ { data: { name: 'Contact 1', email: 'contact1@example.com' } },
297
+ { data: { name: 'Contact 2', email: 'contact2@example.com' } },
298
+ { data: { name: 'Contact 3', email: 'contact3@example.com' } },
299
+ ]);
300
+
301
+ // Bulk delete
302
+ const { deleted } = await client.records.bulkDelete('contacts', [
303
+ 'record-id-1',
304
+ 'record-id-2',
305
+ ]);
306
+ ```
307
+
308
+ ### Advanced Queries
309
+
310
+ ```typescript
311
+ const results = await client.records.query('contacts', {
312
+ filter: {
313
+ and: [
314
+ { field: 'status', op: 'eq', value: 'active' },
315
+ { field: 'createdAt', op: 'gte', value: Date.now() - 86400000 },
316
+ ],
317
+ },
318
+ sort: [
319
+ { field: 'name', direction: 'asc' },
320
+ ],
321
+ limit: 100,
322
+ });
323
+ ```
324
+
325
+ ## Organizations & Workspaces
326
+
327
+ Tapstack uses a multi-tenant architecture with Organizations containing Workspaces.
328
+
329
+ ### Organizations
330
+
331
+ ```typescript
332
+ // List organizations the user belongs to
333
+ const { organizations } = await client.organizations.list();
334
+
335
+ // Create an organization
336
+ const org = await client.organizations.create({
337
+ name: 'Acme Corp',
338
+ slug: 'acme-corp',
339
+ });
340
+
341
+ // Get organization details
342
+ const org = await client.organizations.get('org-id');
343
+
344
+ // Update organization
345
+ await client.organizations.update('org-id', {
346
+ name: 'Acme Corporation',
347
+ });
348
+
349
+ // List organization members
350
+ const { members } = await client.organizations.listMembers('org-id');
351
+
352
+ // Add a member
353
+ await client.organizations.addMember('org-id', {
354
+ email: 'newuser@example.com',
355
+ role: 'member',
356
+ });
357
+ ```
358
+
359
+ ### Workspaces
360
+
361
+ ```typescript
362
+ // List all workspaces
363
+ const { workspaces } = await client.workspaces.list();
364
+
365
+ // Create a workspace in an organization
366
+ const workspace = await client.workspaces.create('org-id', {
367
+ name: 'Production',
368
+ slug: 'production',
369
+ description: 'Production environment',
370
+ });
371
+
372
+ // Get workspace by ID
373
+ const ws = await client.workspaces.get('workspace-id');
374
+
375
+ // Resolve workspace by slugs (useful for routing)
376
+ const ws = await client.workspaces.resolveBySlug('acme-corp', 'production');
377
+
378
+ // Get workspace statistics
379
+ const stats = await client.workspaces.getStats('workspace-id');
380
+ console.log(`Objects: ${stats.objectCount}, Records: ${stats.recordCount}`);
381
+ ```
382
+
383
+ ## File Storage
384
+
385
+ Upload and manage files within your workspace.
386
+
387
+ ### Upload Files
388
+
389
+ ```typescript
390
+ // Upload a file
391
+ const file = await client.files.upload(fileBlob, {
392
+ folderId: 'folder-id', // Optional
393
+ });
394
+
395
+ console.log(`Uploaded: ${file.name} (${file.size} bytes)`);
396
+ ```
397
+
398
+ ### List Files
399
+
400
+ ```typescript
401
+ // List all files
402
+ const { files } = await client.files.list();
403
+
404
+ // List files in a specific folder
405
+ const { files } = await client.files.list('folder-id');
406
+ ```
407
+
408
+ ### Download Files
409
+
410
+ ```typescript
411
+ // Get a signed download URL
412
+ const { url } = await client.files.getDownloadUrl('file-id', 3600); // 1 hour expiry
413
+ ```
414
+
415
+ ### File Operations
416
+
417
+ ```typescript
418
+ // Rename a file
419
+ await client.files.rename('file-id', 'new-name.pdf');
420
+
421
+ // Move to another folder
422
+ await client.files.move('file-id', 'target-folder-id');
423
+
424
+ // Delete a file
425
+ await client.files.delete('file-id');
426
+ ```
427
+
428
+ ### Folders
429
+
430
+ ```typescript
431
+ // List folders
432
+ const { folders } = await client.files.listFolders();
433
+
434
+ // List subfolders
435
+ const { folders } = await client.files.listFolders('parent-folder-id');
436
+
437
+ // Create a folder
438
+ const folder = await client.files.createFolder('Documents', 'parent-folder-id');
439
+
440
+ // Rename folder
441
+ await client.files.renameFolder('folder-id', 'New Name');
442
+
443
+ // Move folder
444
+ await client.files.moveFolder('folder-id', 'new-parent-id');
445
+
446
+ // Delete folder (and all contents)
447
+ await client.files.deleteFolder('folder-id');
448
+ ```
449
+
450
+ ## Automations
451
+
452
+ Create automated workflows triggered by record events.
453
+
454
+ ### List Automations
455
+
456
+ ```typescript
457
+ const { automations } = await client.automations.list();
458
+ ```
459
+
460
+ ### Create Automation
461
+
462
+ ```typescript
463
+ const automation = await client.automations.create({
464
+ name: 'Send welcome email',
465
+ description: 'Sends a welcome email when a new contact is created',
466
+ triggerObjectId: 'contacts-object-id',
467
+ triggerType: 'record.created',
468
+ triggerConditions: [
469
+ { field: 'status', operator: 'eq', value: 'active' },
470
+ ],
471
+ actions: [
472
+ {
473
+ type: 'webhook',
474
+ config: {
475
+ url: 'https://api.example.com/send-email',
476
+ method: 'POST',
477
+ headers: { 'Authorization': 'Bearer token' },
478
+ body: '{"email": "{{record.email}}"}',
479
+ },
480
+ },
481
+ ],
482
+ });
483
+ ```
484
+
485
+ ### Automation Triggers
486
+
487
+ - `record.created` - When a new record is created
488
+ - `record.updated` - When a record is updated
489
+ - `record.deleted` - When a record is deleted
490
+
491
+ ### Automation Actions
492
+
493
+ - `create_record` - Create a record in another object
494
+ - `update_record` - Update related records
495
+ - `delete_record` - Delete related records
496
+ - `webhook` - Call an external webhook
497
+
498
+ ### Manage Automations
499
+
500
+ ```typescript
501
+ // Toggle enabled/disabled
502
+ await client.automations.toggle('automation-id');
503
+
504
+ // Update automation
505
+ await client.automations.update('automation-id', {
506
+ name: 'Updated name',
507
+ isEnabled: true,
508
+ });
509
+
510
+ // Test execute (for debugging)
511
+ const result = await client.automations.execute('automation-id', {
512
+ // Test data matching trigger schema
513
+ });
514
+
515
+ // Delete
516
+ await client.automations.delete('automation-id');
517
+ ```
518
+
519
+ ## AI Conversations
520
+
521
+ Manage AI chat conversations for building chatbots and assistants.
522
+
523
+ ### Create Conversation
524
+
525
+ ```typescript
526
+ const { conversation } = await client.conversations.create({
527
+ title: 'Support Chat',
528
+ });
529
+ ```
530
+
531
+ ### List Conversations
532
+
533
+ ```typescript
534
+ const { conversations, total } = await client.conversations.list({
535
+ limit: 20,
536
+ offset: 0,
537
+ });
538
+ ```
539
+
540
+ ### Add Messages
541
+
542
+ ```typescript
543
+ // Add user message
544
+ await client.conversations.addMessage('conversation-id', {
545
+ role: 'user',
546
+ content: 'Hello, I need help!',
547
+ });
548
+
549
+ // Add assistant response
550
+ await client.conversations.addMessage('conversation-id', {
551
+ role: 'assistant',
552
+ content: 'Hi! How can I assist you today?',
553
+ });
554
+ ```
555
+
556
+ ### Get Conversation with Messages
557
+
558
+ ```typescript
559
+ const conversation = await client.conversations.get('conversation-id');
560
+
561
+ for (const message of conversation.messages) {
562
+ console.log(`${message.role}: ${message.content}`);
563
+ }
564
+ ```
565
+
566
+ ## TypeScript Support
567
+
568
+ The SDK is fully typed. Import types as needed:
569
+
570
+ ```typescript
571
+ import {
572
+ TapstackObject,
573
+ TapstackField,
574
+ TapstackRecord,
575
+ TapstackOrganization,
576
+ TapstackWorkspace,
577
+ TapstackFile,
578
+ FieldType,
579
+ RecordStatus,
580
+ CreateObjectPayload,
581
+ CreateFieldPayload,
582
+ CreateRecordPayload,
583
+ } from '@tapstack/db';
584
+ ```
585
+
586
+ ## Error Handling
587
+
588
+ The SDK throws `AdapterError` for API errors:
589
+
590
+ ```typescript
591
+ import { AdapterError } from '@tapstack/db';
592
+
593
+ try {
594
+ await client.records.get('contacts', 'invalid-id');
595
+ } catch (error) {
596
+ if (error instanceof AdapterError) {
597
+ console.error(`Error: ${error.message}`);
598
+ console.error(`Code: ${error.code}`);
599
+ console.error(`Status: ${error.statusCode}`);
600
+
601
+ if (error.code === 'UNAUTHORIZED') {
602
+ // Handle auth error
603
+ }
604
+ }
605
+ }
606
+ ```
607
+
608
+ ## Custom Adapters
609
+
610
+ For advanced use cases, you can create a custom adapter:
611
+
612
+ ```typescript
613
+ import { createCustomClient, IInstanceAdapter } from '@tapstack/db';
614
+
615
+ class MyCustomAdapter implements IInstanceAdapter {
616
+ async request<T>(method: string, path: string, options?: RequestOptions): Promise<T> {
617
+ // Your implementation
618
+ }
619
+
620
+ async upload<T>(path: string, file: File | Blob, metadata?: Record<string, string>): Promise<T> {
621
+ // Your implementation
622
+ }
623
+
624
+ setWorkspaceId(id: string | null): void {
625
+ // Your implementation
626
+ }
627
+
628
+ getWorkspaceId(): string | null {
629
+ // Your implementation
630
+ }
631
+ }
632
+
633
+ const client = createCustomClient(new MyCustomAdapter());
634
+ ```
635
+
636
+ ## Environment Variables
637
+
638
+ For Next.js or other frameworks, configure via environment variables:
639
+
640
+ ```env
641
+ # .env.local
642
+ NEXT_PUBLIC_TAPSTACK_API_URL=https://api.tapstack.com
643
+ TAPSTACK_API_KEY=your-api-key
644
+ ```
645
+
646
+ ```typescript
647
+ const client = createClient({
648
+ baseUrl: process.env.NEXT_PUBLIC_TAPSTACK_API_URL,
649
+ apiKey: process.env.TAPSTACK_API_KEY,
650
+ });
651
+ ```
652
+
653
+ ## License
654
+
655
+ MIT
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Adapters Index
3
+ * Export all adapter types and implementations
4
+ */
5
+ export * from './types';
6
+ export * from './nodejs.adapter';
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * Adapters Index
4
+ * Export all adapter types and implementations
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ __exportStar(require("./types"), exports);
22
+ __exportStar(require("./nodejs.adapter"), exports);