@tapstack/db 1.0.6 → 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
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Tapstack API Adapter
3
+ * Implements IInstanceAdapter for the Tapstack API at api.tapstack.com
4
+ */
5
+
6
+ import {
7
+ IInstanceAdapter,
8
+ AdapterConfig,
9
+ HttpMethod,
10
+ RequestOptions,
11
+ ApiResponse,
12
+ AdapterError,
13
+ } from './types';
14
+
15
+ /**
16
+ * Default Tapstack API URL
17
+ */
18
+ export const DEFAULT_API_URL = 'https://api.tapstack.com';
19
+
20
+ /**
21
+ * Configuration for the Tapstack API adapter
22
+ */
23
+ export interface NodeJSAdapterConfig extends AdapterConfig {
24
+ /**
25
+ * Base URL of the Tapstack API
26
+ * @default 'https://api.tapstack.com'
27
+ */
28
+ baseUrl?: string;
29
+
30
+ /**
31
+ * API key for authentication (for programmatic access)
32
+ */
33
+ apiKey?: string;
34
+
35
+ /**
36
+ * Service token for service-to-service calls
37
+ */
38
+ serviceToken?: string;
39
+
40
+ /**
41
+ * User token for frontend calls (static)
42
+ */
43
+ userToken?: string;
44
+
45
+ /**
46
+ * Function to get dynamic user token (alternative to static userToken)
47
+ * Use this in frontend apps where the token may change
48
+ */
49
+ getUserToken?: () => Promise<string | null>;
50
+ }
51
+
52
+ export class NodeJSAdapter implements IInstanceAdapter {
53
+ private workspaceId: string | null;
54
+ private baseUrl: string;
55
+
56
+ constructor(private config: NodeJSAdapterConfig) {
57
+ this.workspaceId = config.workspaceId ?? null;
58
+ this.baseUrl = config.baseUrl ?? DEFAULT_API_URL;
59
+ }
60
+
61
+ /**
62
+ * Map SDK paths to REST API paths
63
+ * Converts paths like 'records/contacts' to '/api/v1/objects/contacts/records'
64
+ */
65
+ private mapPath(path: string): string {
66
+ // Already a full path
67
+ if (path.startsWith('/api/')) {
68
+ return path;
69
+ }
70
+
71
+ // Schema operations
72
+ if (path.startsWith('schema/objects')) {
73
+ return `/api/v1/objects${path.replace('schema/objects', '')}`;
74
+ }
75
+ if (path.startsWith('schema/fields/')) {
76
+ const parts = path.split('/');
77
+ const objectSlug = parts[2];
78
+ const rest = parts.slice(3).join('/');
79
+ return `/api/v1/objects/${objectSlug}/fields${rest ? `/${rest}` : ''}`;
80
+ }
81
+
82
+ // Record operations: records/{objectSlug}/... -> /api/v1/objects/{objectSlug}/records/...
83
+ if (path.startsWith('records/')) {
84
+ const parts = path.split('/');
85
+ const objectSlug = parts[1];
86
+ const rest = parts.slice(2).join('/');
87
+ return `/api/v1/objects/${objectSlug}/records${rest ? `/${rest}` : ''}`;
88
+ }
89
+
90
+ // File operations
91
+ if (path.startsWith('files')) {
92
+ return `/api/v1${path.startsWith('/') ? path : `/${path}`}`;
93
+ }
94
+
95
+ // Automation operations
96
+ if (path.startsWith('automations')) {
97
+ return `/api/v1${path.startsWith('/') ? path : `/${path}`}`;
98
+ }
99
+
100
+ // Conversation operations
101
+ if (path.startsWith('conversations')) {
102
+ return `/api/v1${path.startsWith('/') ? path : `/${path}`}`;
103
+ }
104
+
105
+ // AI operations
106
+ if (path.startsWith('ai')) {
107
+ return `/api/v1${path.startsWith('/') ? path : `/${path}`}`;
108
+ }
109
+
110
+ // Organization operations
111
+ if (path.startsWith('organizations')) {
112
+ return `/api${path.startsWith('/') ? path : `/${path}`}`;
113
+ }
114
+
115
+ // Workspace operations
116
+ if (path.startsWith('workspaces')) {
117
+ return `/api${path.startsWith('/') ? path : `/${path}`}`;
118
+ }
119
+
120
+ // Activity operations
121
+ if (path.startsWith('activity')) {
122
+ return `/api/v1${path.startsWith('/') ? path : `/${path}`}`;
123
+ }
124
+
125
+ // Default: add /api prefix
126
+ return `/api/${path}`;
127
+ }
128
+
129
+ /**
130
+ * Make a request to the Tapstack API
131
+ */
132
+ async request<T>(
133
+ method: HttpMethod,
134
+ path: string,
135
+ options?: RequestOptions
136
+ ): Promise<T> {
137
+ // Build the URL - API uses /api/v1 prefix for data operations
138
+ const apiPath = this.mapPath(path);
139
+ let url = `${this.baseUrl}${apiPath}`;
140
+
141
+ // Add query parameters
142
+ if (options?.params) {
143
+ const params = new URLSearchParams(options.params);
144
+ if (this.workspaceId && !options.params.workspaceId) {
145
+ params.set('workspaceId', this.workspaceId);
146
+ }
147
+ url += `?${params.toString()}`;
148
+ } else if (this.workspaceId) {
149
+ url += `?workspaceId=${this.workspaceId}`;
150
+ }
151
+
152
+ // Build headers
153
+ const headers: Record<string, string> = {
154
+ 'Content-Type': 'application/json',
155
+ ...this.config.defaultHeaders,
156
+ ...options?.headers,
157
+ };
158
+
159
+ // Add authentication
160
+ if (this.config.apiKey) {
161
+ headers['X-API-Key'] = this.config.apiKey;
162
+ }
163
+
164
+ if (this.config.serviceToken) {
165
+ headers['Authorization'] = `Bearer ${this.config.serviceToken}`;
166
+ } else if (this.config.userToken) {
167
+ headers['Authorization'] = `Bearer ${this.config.userToken}`;
168
+ } else if (this.config.getUserToken) {
169
+ const token = await this.config.getUserToken();
170
+ if (token) {
171
+ headers['Authorization'] = `Bearer ${token}`;
172
+ }
173
+ }
174
+
175
+ // Add workspace header
176
+ if (this.workspaceId) {
177
+ headers['X-Workspace-Id'] = this.workspaceId;
178
+ }
179
+
180
+ // Make the request
181
+ const response = await fetch(url, {
182
+ method,
183
+ headers,
184
+ body: options?.body ? JSON.stringify(options.body) : undefined,
185
+ });
186
+
187
+ // Handle authentication errors
188
+ if (response.status === 401) {
189
+ this.config.onAuthError?.();
190
+ throw new AdapterError('Unauthorized', 'UNAUTHORIZED', 401);
191
+ }
192
+
193
+ // Parse response
194
+ const data = (await response.json()) as ApiResponse<T>;
195
+
196
+ // Handle API errors
197
+ if (!data.success) {
198
+ throw new AdapterError(
199
+ data.error?.message || 'Request failed',
200
+ data.error?.code || 'UNKNOWN_ERROR',
201
+ response.status
202
+ );
203
+ }
204
+
205
+ return data.data as T;
206
+ }
207
+
208
+ /**
209
+ * Upload a file to the Tapstack API
210
+ */
211
+ async upload<T>(
212
+ path: string,
213
+ file: File | Blob,
214
+ metadata?: Record<string, string>
215
+ ): Promise<T> {
216
+ const apiPath = this.mapPath(path);
217
+ let url = `${this.baseUrl}${apiPath}`;
218
+
219
+ if (this.workspaceId) {
220
+ url += `?workspaceId=${this.workspaceId}`;
221
+ }
222
+
223
+ const headers: Record<string, string> = {
224
+ ...this.config.defaultHeaders,
225
+ };
226
+
227
+ if (this.config.apiKey) {
228
+ headers['X-API-Key'] = this.config.apiKey;
229
+ }
230
+
231
+ if (this.config.serviceToken) {
232
+ headers['Authorization'] = `Bearer ${this.config.serviceToken}`;
233
+ } else if (this.config.userToken) {
234
+ headers['Authorization'] = `Bearer ${this.config.userToken}`;
235
+ } else if (this.config.getUserToken) {
236
+ const token = await this.config.getUserToken();
237
+ if (token) {
238
+ headers['Authorization'] = `Bearer ${token}`;
239
+ }
240
+ }
241
+
242
+ if (this.workspaceId) {
243
+ headers['X-Workspace-Id'] = this.workspaceId;
244
+ }
245
+
246
+ const formData = new FormData();
247
+ formData.append('file', file);
248
+
249
+ if (metadata) {
250
+ for (const [key, value] of Object.entries(metadata)) {
251
+ formData.append(key, value);
252
+ }
253
+ }
254
+
255
+ const response = await fetch(url, {
256
+ method: 'POST',
257
+ headers,
258
+ body: formData,
259
+ });
260
+
261
+ if (response.status === 401) {
262
+ this.config.onAuthError?.();
263
+ throw new AdapterError('Unauthorized', 'UNAUTHORIZED', 401);
264
+ }
265
+
266
+ const data = (await response.json()) as ApiResponse<T>;
267
+
268
+ if (!data.success) {
269
+ throw new AdapterError(
270
+ data.error?.message || 'Upload failed',
271
+ data.error?.code || 'UPLOAD_ERROR',
272
+ response.status
273
+ );
274
+ }
275
+
276
+ return data.data as T;
277
+ }
278
+
279
+ /**
280
+ * Set the workspace context
281
+ */
282
+ setWorkspaceId(workspaceId: string | null): void {
283
+ this.workspaceId = workspaceId;
284
+ }
285
+
286
+ /**
287
+ * Get the current workspace ID
288
+ */
289
+ getWorkspaceId(): string | null {
290
+ return this.workspaceId;
291
+ }
292
+ }
293
+
294
+
295
+
296
+
297
+
298
+
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Instance Adapter Interface
3
+ * Defines the contract for backend adapters (Edge Functions, Node.js API, etc.)
4
+ */
5
+
6
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
7
+
8
+ export interface RequestOptions {
9
+ body?: unknown;
10
+ params?: Record<string, string>;
11
+ headers?: Record<string, string>;
12
+ }
13
+
14
+ /**
15
+ * Core adapter interface that all backend implementations must implement
16
+ */
17
+ export interface IInstanceAdapter {
18
+ /**
19
+ * Make an HTTP request to the backend
20
+ */
21
+ request<T>(
22
+ method: HttpMethod,
23
+ path: string,
24
+ options?: RequestOptions
25
+ ): Promise<T>;
26
+
27
+ /**
28
+ * Upload a file to the backend
29
+ */
30
+ upload<T>(
31
+ path: string,
32
+ file: File | Blob,
33
+ metadata?: Record<string, string>
34
+ ): Promise<T>;
35
+
36
+ /**
37
+ * Set the workspace context for subsequent requests
38
+ */
39
+ setWorkspaceId(workspaceId: string | null): void;
40
+
41
+ /**
42
+ * Get the current workspace ID
43
+ */
44
+ getWorkspaceId(): string | null;
45
+ }
46
+
47
+ /**
48
+ * Base configuration shared by all adapters
49
+ */
50
+ export interface AdapterConfig {
51
+ /**
52
+ * Workspace ID for scoping requests
53
+ */
54
+ workspaceId?: string;
55
+
56
+ /**
57
+ * Callback when authentication fails
58
+ */
59
+ onAuthError?: () => void;
60
+
61
+ /**
62
+ * Custom headers to include in all requests
63
+ */
64
+ defaultHeaders?: Record<string, string>;
65
+ }
66
+
67
+ /**
68
+ * Standard API response format from Instance API
69
+ */
70
+ export interface ApiResponse<T> {
71
+ success: boolean;
72
+ data?: T;
73
+ error?: {
74
+ code: string;
75
+ message: string;
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Paginated response format
81
+ */
82
+ export interface PaginatedResponse<T> {
83
+ items: T[];
84
+ total: number;
85
+ page: number;
86
+ pageSize: number;
87
+ totalPages: number;
88
+ }
89
+
90
+ /**
91
+ * Error thrown by adapters
92
+ */
93
+ export class AdapterError extends Error {
94
+ constructor(
95
+ message: string,
96
+ public code: string,
97
+ public statusCode?: number
98
+ ) {
99
+ super(message);
100
+ this.name = 'AdapterError';
101
+ }
102
+ }
103
+
104
+
105
+
106
+
107
+
108
+
package/src/index.ts CHANGED
@@ -1,70 +1,158 @@
1
+ /**
2
+ * @tapstack/db - Tapstack Database Client
3
+ *
4
+ * A client for interacting with the Tapstack API at api.tapstack.com.
5
+ * Provides typed access to objects, fields, records, and more.
6
+ */
7
+
8
+ // Export all types
1
9
  export * from './types';
2
- export * from './modules/objects';
3
- export * from './modules/fields';
4
- export * from './modules/records';
5
10
 
6
- import { AxiosResponse } from 'axios';
7
- import { BaseClient, BaseClientConfig } from '@tapstack/core';
11
+ // Export adapters
12
+ export * from './adapters';
13
+
14
+ // Export modules
15
+ export * from './modules';
16
+
17
+ // Import for client class
18
+ import { IInstanceAdapter } from './adapters/types';
19
+ import { NodeJSAdapter, NodeJSAdapterConfig } from './adapters/nodejs.adapter';
8
20
  import { ObjectModule } from './modules/objects';
9
21
  import { FieldModule } from './modules/fields';
10
22
  import { RecordModule } from './modules/records';
11
- import { CreateFieldPayload, CreateObjectPayload } from './types';
12
-
13
- export interface TapstackDBClientConfig extends BaseClientConfig {}
23
+ import { OrganizationModule } from './modules/organizations';
24
+ import { WorkspaceModule } from './modules/workspaces';
25
+ import { FileModule } from './modules/files';
26
+ import { AutomationModule } from './modules/automations';
27
+ import { ConversationModule } from './modules/conversations';
14
28
 
15
- export class TapstackDBClient extends BaseClient {
29
+ /**
30
+ * Main Tapstack Database Client
31
+ * Uses an adapter to communicate with the Tapstack API
32
+ */
33
+ export class TapstackDBClient {
16
34
  readonly objects: ObjectModule;
17
35
  readonly fields: FieldModule;
18
36
  readonly records: RecordModule;
37
+ readonly organizations: OrganizationModule;
38
+ readonly workspaces: WorkspaceModule;
39
+ readonly files: FileModule;
40
+ readonly automations: AutomationModule;
41
+ readonly conversations: ConversationModule;
19
42
 
20
- constructor(config: TapstackDBClientConfig) {
21
- super(config);
22
- this.objects = new ObjectModule(this.client);
23
- this.fields = new FieldModule(this.client);
24
- this.records = new RecordModule(this.client);
25
- }
43
+ private adapter: IInstanceAdapter;
26
44
 
27
- async getObject(slug: string): Promise<AxiosResponse<any>> {
28
- return this.client.get(`/system/${slug}`);
45
+ constructor(adapter: IInstanceAdapter) {
46
+ this.adapter = adapter;
47
+ this.objects = new ObjectModule(adapter);
48
+ this.fields = new FieldModule(adapter);
49
+ this.records = new RecordModule(adapter);
50
+ this.organizations = new OrganizationModule(adapter);
51
+ this.workspaces = new WorkspaceModule(adapter);
52
+ this.files = new FileModule(adapter);
53
+ this.automations = new AutomationModule(adapter);
54
+ this.conversations = new ConversationModule(adapter);
29
55
  }
30
56
 
31
- async getAllObjects(): Promise<AxiosResponse<any>> {
32
- return this.client.get(`/system`);
57
+ /**
58
+ * Set the workspace context for all subsequent requests
59
+ */
60
+ setWorkspaceId(workspaceId: string | null): void {
61
+ this.adapter.setWorkspaceId(workspaceId);
33
62
  }
34
63
 
35
- async createObject(data: CreateObjectPayload): Promise<AxiosResponse<any>> {
36
- return this.client.post(`/system`, data);
64
+ /**
65
+ * Get the current workspace ID
66
+ */
67
+ getWorkspaceId(): string | null {
68
+ return this.adapter.getWorkspaceId();
37
69
  }
38
70
 
39
- async updateObject(slug: string, data: Partial<CreateObjectPayload>): Promise<AxiosResponse<any>> {
40
- return this.client.put(`/system/${slug}`, data);
71
+ /**
72
+ * Get the underlying adapter (for advanced use cases)
73
+ */
74
+ getAdapter(): IInstanceAdapter {
75
+ return this.adapter;
41
76
  }
77
+ }
42
78
 
43
- async deleteObject(slug: string): Promise<AxiosResponse<any>> {
44
- return this.client.delete(`/system/${slug}`);
45
- }
79
+ /**
80
+ * Client configuration options
81
+ */
82
+ export type TapstackClientConfig = NodeJSAdapterConfig;
46
83
 
47
- async getFields(slug: string): Promise<AxiosResponse<any>> {
48
- return this.client.get(`/system/${slug}/fields`);
49
- }
84
+ /**
85
+ * Create a Tapstack API client
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * // With API key authentication
90
+ * const client = createClient({
91
+ * apiKey: 'your-api-key',
92
+ * workspaceId: 'ws-123',
93
+ * });
94
+ *
95
+ * // With user token (for frontend apps)
96
+ * const client = createClient({
97
+ * getUserToken: async () => accessToken,
98
+ * workspaceId: 'ws-123',
99
+ * });
100
+ *
101
+ * // With custom base URL
102
+ * const client = createClient({
103
+ * baseUrl: 'https://custom-api.example.com',
104
+ * apiKey: 'your-api-key',
105
+ * });
106
+ *
107
+ * // Usage
108
+ * const objects = await client.objects.list();
109
+ * const records = await client.records.list('contacts');
110
+ * ```
111
+ */
112
+ export function createClient(config: TapstackClientConfig): TapstackDBClient {
113
+ const adapter = new NodeJSAdapter(config);
114
+ return new TapstackDBClient(adapter);
115
+ }
50
116
 
51
- async getField(slug: string, fieldID: string): Promise<AxiosResponse<any>> {
52
- return this.client.get(`/system/${slug}/fields/${fieldID}`);
53
- }
117
+ /**
118
+ * Create a client with a custom adapter
119
+ * Use this for advanced scenarios or custom backends
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * class CustomAdapter implements IInstanceAdapter {
124
+ * // ... implementation
125
+ * }
126
+ *
127
+ * const client = createCustomClient(new CustomAdapter());
128
+ * ```
129
+ */
130
+ export function createCustomClient(adapter: IInstanceAdapter): TapstackDBClient {
131
+ return new TapstackDBClient(adapter);
132
+ }
54
133
 
55
- async createField(slug: string, data: CreateFieldPayload): Promise<AxiosResponse<any>> {
56
- return this.client.post(`/system/${slug}/fields`, data);
57
- }
134
+ // ==================== Legacy Exports (for backwards compatibility) ====================
58
135
 
59
- async updateField(slug: string, fieldID: string, data: Partial<CreateFieldPayload>): Promise<AxiosResponse<any>> {
60
- return this.client.put(`/system/${slug}/fields/${fieldID}`, data);
61
- }
136
+ /**
137
+ * @deprecated Use createClient instead
138
+ */
139
+ export const createNodeJSClient = createClient;
62
140
 
63
- async deleteField(slug: string, fieldID: string): Promise<AxiosResponse<any>> {
64
- return this.client.delete(`/system/${slug}/fields/${fieldID}`);
65
- }
141
+ /**
142
+ * @deprecated Use TapstackClientConfig instead
143
+ */
144
+ export interface TapstackDBClientConfig {
145
+ baseURL: string;
146
+ apiKey: string;
147
+ }
66
148
 
67
- async query(slug: string, variables: any): Promise<AxiosResponse<any>> {
68
- return this.client.post(`/${slug}`, variables);
69
- }
149
+ /**
150
+ * @deprecated Use createClient instead
151
+ */
152
+ export function createLegacyClient(config: TapstackDBClientConfig): TapstackDBClient {
153
+ const adapter = new NodeJSAdapter({
154
+ baseUrl: config.baseURL.replace('/api', ''),
155
+ apiKey: config.apiKey,
156
+ });
157
+ return new TapstackDBClient(adapter);
70
158
  }