@ponxa/potatobase-client 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/dist/index.mjs ADDED
@@ -0,0 +1,478 @@
1
+ // src/table-client.ts
2
+ var TableClient = class {
3
+ constructor(apiUrl, apiKey, projectId, tableName, debug = false) {
4
+ this.apiUrl = apiUrl;
5
+ this.apiKey = apiKey;
6
+ this.projectId = projectId;
7
+ this.tableName = tableName;
8
+ this.debug = debug;
9
+ }
10
+ async callAPI(procedure, input, method = "GET") {
11
+ const url = `${this.apiUrl}/${procedure}`;
12
+ if (method === "GET") {
13
+ const params = new URLSearchParams({
14
+ input: JSON.stringify(input)
15
+ });
16
+ const response = await fetch(`${url}?${params}`, {
17
+ method: "GET",
18
+ headers: {
19
+ "x-api-key": this.apiKey
20
+ }
21
+ });
22
+ if (!response.ok) {
23
+ const error = await response.text();
24
+ throw new Error(`API error: ${error}`);
25
+ }
26
+ const data = await response.json();
27
+ return data.result?.data || data;
28
+ } else {
29
+ const response = await fetch(url, {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/json",
33
+ "x-api-key": this.apiKey
34
+ },
35
+ body: JSON.stringify(input)
36
+ });
37
+ if (!response.ok) {
38
+ const error = await response.text();
39
+ throw new Error(`API error: ${error}`);
40
+ }
41
+ const data = await response.json();
42
+ return data.result?.data || data;
43
+ }
44
+ }
45
+ /**
46
+ * Query records from the table
47
+ *
48
+ * @param params - Query parameters (index, where, range, limit, cursor)
49
+ * @returns Query result with data and optional cursor
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * // Query by primary key
54
+ * const result = await products.query({ where: { id: 'prod-1' } });
55
+ *
56
+ * // Query by GSI
57
+ * const result = await products.query({
58
+ * index: 'byCategory',
59
+ * where: { category: 'clothing' },
60
+ * limit: 10
61
+ * });
62
+ *
63
+ * // Query with date range
64
+ * const result = await products.query({
65
+ * index: 'byCategory',
66
+ * where: { category: 'clothing' },
67
+ * range: {
68
+ * field: 'createdAt',
69
+ * gte: '2025-01-01',
70
+ * lte: '2025-01-31'
71
+ * }
72
+ * });
73
+ * ```
74
+ */
75
+ async query(params) {
76
+ if (this.debug) {
77
+ console.log(`[TableClient:${this.tableName}] query:`, params);
78
+ }
79
+ try {
80
+ const result = await this.callAPI("query.execute", {
81
+ projectId: this.projectId,
82
+ tableName: this.tableName,
83
+ index: params?.index,
84
+ where: params?.where,
85
+ range: params?.range,
86
+ limit: params?.limit,
87
+ cursor: params?.cursor
88
+ });
89
+ if (this.debug) {
90
+ console.log(`[TableClient:${this.tableName}] query result:`, {
91
+ count: result.data?.length || 0,
92
+ hasMore: !!result.cursor
93
+ });
94
+ }
95
+ return result.data || result;
96
+ } catch (error) {
97
+ console.error(`[TableClient:${this.tableName}] query error:`, error.message);
98
+ throw new Error(`Failed to query ${this.tableName}: ${error.message}`);
99
+ }
100
+ }
101
+ /**
102
+ * Get a single record by ID
103
+ *
104
+ * @param id - Record ID
105
+ * @returns Record or null if not found
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * const product = await products.get('prod-1');
110
+ * ```
111
+ */
112
+ async get(id) {
113
+ if (this.debug) {
114
+ console.log(`[TableClient:${this.tableName}] get:`, id);
115
+ }
116
+ try {
117
+ const result = await this.callAPI("query.get", {
118
+ projectId: this.projectId,
119
+ tableName: this.tableName,
120
+ recordId: id
121
+ });
122
+ return result || null;
123
+ } catch (error) {
124
+ if (error.message?.includes("not found")) {
125
+ return null;
126
+ }
127
+ console.error(`[TableClient:${this.tableName}] get error:`, error.message);
128
+ throw new Error(`Failed to get record from ${this.tableName}: ${error.message}`);
129
+ }
130
+ }
131
+ /**
132
+ * Insert a new record
133
+ *
134
+ * @param data - Record data (must include 'id' field)
135
+ * @returns Created record
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * const newProduct = await products.insert({
140
+ * id: 'prod-1',
141
+ * title: 'T-Shirt',
142
+ * price: 29.99,
143
+ * category: 'clothing'
144
+ * });
145
+ * ```
146
+ */
147
+ async insert(data) {
148
+ if (this.debug) {
149
+ console.log(`[TableClient:${this.tableName}] insert:`, data);
150
+ }
151
+ if (!data.id) {
152
+ throw new Error('Record must have an "id" field');
153
+ }
154
+ try {
155
+ const result = await this.callAPI("query.create", {
156
+ projectId: this.projectId,
157
+ tableName: this.tableName,
158
+ data
159
+ }, "POST");
160
+ if (this.debug) {
161
+ console.log(`[TableClient:${this.tableName}] insert success:`, result.id);
162
+ }
163
+ return result;
164
+ } catch (error) {
165
+ console.error(`[TableClient:${this.tableName}] insert error:`, error.message);
166
+ throw new Error(`Failed to insert into ${this.tableName}: ${error.message}`);
167
+ }
168
+ }
169
+ /**
170
+ * Update an existing record
171
+ *
172
+ * @param id - Record ID
173
+ * @param data - Fields to update
174
+ * @returns Updated record
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * const updated = await products.update('prod-1', {
179
+ * price: 24.99,
180
+ * stock: 100
181
+ * });
182
+ * ```
183
+ */
184
+ async update(id, data) {
185
+ if (this.debug) {
186
+ console.log(`[TableClient:${this.tableName}] update:`, { id, data });
187
+ }
188
+ try {
189
+ const result = await this.callAPI("query.update", {
190
+ projectId: this.projectId,
191
+ tableName: this.tableName,
192
+ recordId: id,
193
+ data
194
+ }, "POST");
195
+ if (this.debug) {
196
+ console.log(`[TableClient:${this.tableName}] update success`);
197
+ }
198
+ return result;
199
+ } catch (error) {
200
+ console.error(`[TableClient:${this.tableName}] update error:`, error.message);
201
+ throw new Error(`Failed to update record in ${this.tableName}: ${error.message}`);
202
+ }
203
+ }
204
+ /**
205
+ * Delete a record
206
+ *
207
+ * @param id - Record ID
208
+ * @returns Success status
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * await products.delete('prod-1');
213
+ * ```
214
+ */
215
+ async delete(id) {
216
+ if (this.debug) {
217
+ console.log(`[TableClient:${this.tableName}] delete:`, id);
218
+ }
219
+ try {
220
+ const result = await this.callAPI("query.delete", {
221
+ projectId: this.projectId,
222
+ tableName: this.tableName,
223
+ recordId: id
224
+ }, "POST");
225
+ if (this.debug) {
226
+ console.log(`[TableClient:${this.tableName}] delete success`);
227
+ }
228
+ return result;
229
+ } catch (error) {
230
+ console.error(`[TableClient:${this.tableName}] delete error:`, error.message);
231
+ throw new Error(`Failed to delete from ${this.tableName}: ${error.message}`);
232
+ }
233
+ }
234
+ /**
235
+ * Create a new record (alias for insert)
236
+ *
237
+ * @param data - Record data (must include 'id' field)
238
+ * @returns Created record
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const newProduct = await products.create({
243
+ * id: 'prod-1',
244
+ * title: 'T-Shirt',
245
+ * price: 29.99,
246
+ * category: 'clothing'
247
+ * });
248
+ * ```
249
+ */
250
+ async create(data) {
251
+ return this.insert(data);
252
+ }
253
+ /**
254
+ * Alias for query() - chainable method
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * const items = await products.from().query({ limit: 10 });
259
+ * ```
260
+ */
261
+ from() {
262
+ return this;
263
+ }
264
+ };
265
+
266
+ // src/helpers.ts
267
+ function daysAgo(days) {
268
+ const date = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
269
+ return date.toISOString();
270
+ }
271
+ function hoursAgo(hours) {
272
+ const date = new Date(Date.now() - hours * 60 * 60 * 1e3);
273
+ return date.toISOString();
274
+ }
275
+ function minutesAgo(minutes) {
276
+ const date = new Date(Date.now() - minutes * 60 * 1e3);
277
+ return date.toISOString();
278
+ }
279
+ function lastDays(field, days) {
280
+ return {
281
+ field,
282
+ gte: daysAgo(days)
283
+ };
284
+ }
285
+ function between(field, startDate, endDate) {
286
+ return {
287
+ field,
288
+ between: [startDate, endDate]
289
+ };
290
+ }
291
+ function since(field, date) {
292
+ return {
293
+ field,
294
+ gte: date
295
+ };
296
+ }
297
+ function until(field, date) {
298
+ return {
299
+ field,
300
+ lte: date
301
+ };
302
+ }
303
+ function thisMonth(field) {
304
+ const now = /* @__PURE__ */ new Date();
305
+ const start = new Date(now.getFullYear(), now.getMonth(), 1);
306
+ const end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
307
+ return {
308
+ field,
309
+ between: [start.toISOString(), end.toISOString()]
310
+ };
311
+ }
312
+ function thisYear(field) {
313
+ const now = /* @__PURE__ */ new Date();
314
+ const start = new Date(now.getFullYear(), 0, 1);
315
+ const end = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999);
316
+ return {
317
+ field,
318
+ between: [start.toISOString(), end.toISOString()]
319
+ };
320
+ }
321
+ function today(field) {
322
+ const now = /* @__PURE__ */ new Date();
323
+ const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
324
+ const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
325
+ return {
326
+ field,
327
+ between: [start.toISOString(), end.toISOString()]
328
+ };
329
+ }
330
+
331
+ // src/index.ts
332
+ var PotatoBaseClient = class {
333
+ constructor(config) {
334
+ if (!config.projectId) {
335
+ throw new Error("PotatoBase: projectId is required");
336
+ }
337
+ if (!config.apiKey) {
338
+ throw new Error("PotatoBase: apiKey is required");
339
+ }
340
+ this.config = {
341
+ ...config,
342
+ apiUrl: config.apiUrl || "https://api.potatobase.com",
343
+ debug: config.debug || false
344
+ };
345
+ this.tableClients = /* @__PURE__ */ new Map();
346
+ if (this.config.debug) {
347
+ console.log("[PotatoBaseClient] Initializing with config:", {
348
+ projectId: this.config.projectId,
349
+ apiUrl: this.config.apiUrl,
350
+ apiKey: this.config.apiKey.substring(0, 20) + "..."
351
+ });
352
+ }
353
+ }
354
+ /**
355
+ * Access a table by name
356
+ *
357
+ * Returns a TableClient instance for performing CRUD operations.
358
+ * TableClient instances are cached for performance.
359
+ *
360
+ * @param tableName - Name of the table
361
+ * @returns TableClient for the specified table
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const products = pb.table('products');
366
+ * const items = await products.query({ limit: 10 });
367
+ * ```
368
+ */
369
+ table(tableName) {
370
+ const key = tableName;
371
+ if (this.tableClients.has(key)) {
372
+ return this.tableClients.get(key);
373
+ }
374
+ if (this.config.debug) {
375
+ console.log(`[PotatoBaseClient] Creating TableClient for: ${key}`);
376
+ }
377
+ const tableClient = new TableClient(
378
+ this.config.apiUrl,
379
+ this.config.apiKey,
380
+ this.config.projectId,
381
+ key,
382
+ this.config.debug
383
+ );
384
+ this.tableClients.set(key, tableClient);
385
+ return tableClient;
386
+ }
387
+ /**
388
+ * Generate TypeScript types for your project
389
+ *
390
+ * Fetches the current schema and generates TypeScript interface definitions.
391
+ * Save the result to a .d.ts file in your project.
392
+ *
393
+ * @returns Generated TypeScript types as string
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * const types = await pb.generateTypes();
398
+ * fs.writeFileSync('./types/database.d.ts', types);
399
+ * ```
400
+ */
401
+ async generateTypes() {
402
+ if (this.config.debug) {
403
+ console.log("[PotatoBaseClient] Generating types for project:", this.config.projectId);
404
+ }
405
+ try {
406
+ const params = new URLSearchParams({
407
+ input: JSON.stringify(this.config.projectId)
408
+ });
409
+ const response = await fetch(`${this.config.apiUrl}/projects.generateTypes?${params}`, {
410
+ method: "GET",
411
+ headers: {
412
+ "x-api-key": this.config.apiKey
413
+ }
414
+ });
415
+ if (!response.ok) {
416
+ throw new Error(`Failed to generate types: ${response.statusText}`);
417
+ }
418
+ const data = await response.json();
419
+ const result = data.result?.data || data;
420
+ if (this.config.debug) {
421
+ console.log("[PotatoBaseClient] Types generated:", {
422
+ length: result.types.length
423
+ });
424
+ }
425
+ return result.types;
426
+ } catch (error) {
427
+ console.error("[PotatoBaseClient] Failed to generate types:", error.message);
428
+ throw new Error(`Failed to generate types: ${error.message}`);
429
+ }
430
+ }
431
+ /**
432
+ * Get the project ID
433
+ */
434
+ getProjectId() {
435
+ return this.config.projectId;
436
+ }
437
+ /**
438
+ * Get the API URL
439
+ */
440
+ getApiUrl() {
441
+ return this.config.apiUrl;
442
+ }
443
+ /**
444
+ * Clear all cached table clients
445
+ *
446
+ * Useful for testing or when you want to force fresh clients.
447
+ */
448
+ clearCache() {
449
+ if (this.config.debug) {
450
+ console.log("[PotatoBaseClient] Clearing table client cache");
451
+ }
452
+ this.tableClients.clear();
453
+ }
454
+ /**
455
+ * Enable/disable debug logging
456
+ */
457
+ setDebug(enabled) {
458
+ this.config.debug = enabled;
459
+ }
460
+ };
461
+ function createClient(config) {
462
+ return new PotatoBaseClient(config);
463
+ }
464
+ export {
465
+ PotatoBaseClient,
466
+ TableClient,
467
+ between,
468
+ createClient,
469
+ daysAgo,
470
+ hoursAgo,
471
+ lastDays,
472
+ minutesAgo,
473
+ since,
474
+ thisMonth,
475
+ thisYear,
476
+ today,
477
+ until
478
+ };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@ponxa/potatobase-client",
3
+ "version": "0.1.0",
4
+ "description": "Official JavaScript/TypeScript client for PotatoBase",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format esm,cjs --dts",
22
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "publishConfig": {
27
+ "registry": "https://registry.npmjs.org",
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/ponxa/potatobase-client.git"
33
+ },
34
+ "homepage": "https://potatobase.com",
35
+ "bugs": {
36
+ "url": "https://github.com/ponxa/potatobase-client/issues"
37
+ },
38
+ "keywords": [
39
+ "potatobase",
40
+ "dynamodb",
41
+ "database",
42
+ "client",
43
+ "sdk",
44
+ "typescript",
45
+ "sst",
46
+ "serverless",
47
+ "aws"
48
+ ],
49
+ "author": "PotatoBase Team",
50
+ "license": "MIT",
51
+ "dependencies": {
52
+ "@trpc/client": "^10.45.0"
53
+ },
54
+ "devDependencies": {
55
+ "tsup": "^8.0.0",
56
+ "typescript": "^5.5.2"
57
+ },
58
+ "peerDependencies": {
59
+ "typescript": ">=5.0.0"
60
+ }
61
+ }