@blinkdotnew/sdk 0.13.2 → 0.14.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 CHANGED
@@ -195,23 +195,31 @@ const unsubscribe = blink.auth.onAuthStateChanged((state) => {
195
195
 
196
196
  ### Database Operations
197
197
 
198
+ **🎉 NEW: Automatic Case Conversion!**
199
+ The SDK now automatically converts between JavaScript camelCase and SQL snake_case:
200
+ - **Write code in camelCase**: `userId`, `createdAt`, `isCompleted`
201
+ - **Stored as snake_case**: `user_id`, `created_at`, `is_completed`
202
+ - **No manual conversion needed!**
203
+
198
204
  ```typescript
199
205
  // Create (ID auto-generated if not provided)
200
206
  const todo = await blink.db.todos.create({
201
207
  id: 'todo_12345', // Optional - auto-generated if not provided
202
208
  title: 'Learn Blink SDK',
203
- user_id: user.id
209
+ userId: user.id, // camelCase in code
210
+ createdAt: new Date(), // camelCase in code
211
+ isCompleted: false // camelCase in code
204
212
  })
205
213
 
206
- // Read with filtering - returns a plain array of records
214
+ // Read with filtering - returns camelCase fields
207
215
  const todos = await blink.db.todos.list({
208
216
  where: {
209
217
  AND: [
210
- { user_id: user.id },
218
+ { userId: user.id }, // camelCase in filters
211
219
  { OR: [{ status: 'open' }, { priority: 'high' }] }
212
220
  ]
213
221
  },
214
- orderBy: { created_at: 'desc' },
222
+ orderBy: { createdAt: 'desc' }, // camelCase in orderBy
215
223
  limit: 20
216
224
  })
217
225
  // `todos` is a direct array: Todo[]
package/dist/index.js CHANGED
@@ -67,6 +67,28 @@ var BlinkNotificationsError = class extends BlinkError {
67
67
  };
68
68
 
69
69
  // ../core/src/query-builder.ts
70
+ function camelToSnake(str) {
71
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
72
+ }
73
+ function convertFilterKeysToSnakeCase(condition) {
74
+ if (!condition) return condition;
75
+ if ("AND" in condition) {
76
+ return {
77
+ AND: condition.AND?.map(convertFilterKeysToSnakeCase)
78
+ };
79
+ }
80
+ if ("OR" in condition) {
81
+ return {
82
+ OR: condition.OR?.map(convertFilterKeysToSnakeCase)
83
+ };
84
+ }
85
+ const converted = {};
86
+ for (const [field, value] of Object.entries(condition)) {
87
+ const snakeField = camelToSnake(field);
88
+ converted[snakeField] = value;
89
+ }
90
+ return converted;
91
+ }
70
92
  function buildFilterQuery(condition) {
71
93
  if (!condition) return "";
72
94
  if ("AND" in condition) {
@@ -140,12 +162,14 @@ function encodeQueryValue(value) {
140
162
  function buildQuery(options = {}) {
141
163
  const params = {};
142
164
  if (options.select && options.select.length > 0) {
143
- params.select = options.select.join(",");
165
+ const snakeFields = options.select.map(camelToSnake);
166
+ params.select = snakeFields.join(",");
144
167
  } else {
145
168
  params.select = "*";
146
169
  }
147
170
  if (options.where) {
148
- const filterQuery = buildFilterQuery(options.where);
171
+ const convertedWhere = convertFilterKeysToSnakeCase(options.where);
172
+ const filterQuery = buildFilterQuery(convertedWhere);
149
173
  if (filterQuery) {
150
174
  const filterParams = filterQuery.split("&");
151
175
  for (const param of filterParams) {
@@ -160,7 +184,7 @@ function buildQuery(options = {}) {
160
184
  if (typeof options.orderBy === "string") {
161
185
  params.order = options.orderBy;
162
186
  } else {
163
- const orderClauses = Object.entries(options.orderBy).map(([field, direction]) => `${field}.${direction}`);
187
+ const orderClauses = Object.entries(options.orderBy).map(([field, direction]) => `${camelToSnake(field)}.${direction}`);
164
188
  params.order = orderClauses.join(",");
165
189
  }
166
190
  }
@@ -177,6 +201,34 @@ function buildQuery(options = {}) {
177
201
  }
178
202
 
179
203
  // ../core/src/http-client.ts
204
+ function camelToSnake2(str) {
205
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
206
+ }
207
+ function snakeToCamel(str) {
208
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
209
+ }
210
+ function convertKeysToSnakeCase(obj) {
211
+ if (obj === null || obj === void 0) return obj;
212
+ if (typeof obj !== "object") return obj;
213
+ if (Array.isArray(obj)) return obj.map(convertKeysToSnakeCase);
214
+ const converted = {};
215
+ for (const [key, value] of Object.entries(obj)) {
216
+ const snakeKey = camelToSnake2(key);
217
+ converted[snakeKey] = convertKeysToSnakeCase(value);
218
+ }
219
+ return converted;
220
+ }
221
+ function convertKeysToCamelCase(obj) {
222
+ if (obj === null || obj === void 0) return obj;
223
+ if (typeof obj !== "object") return obj;
224
+ if (Array.isArray(obj)) return obj.map(convertKeysToCamelCase);
225
+ const converted = {};
226
+ for (const [key, value] of Object.entries(obj)) {
227
+ const camelKey = snakeToCamel(key);
228
+ converted[camelKey] = convertKeysToCamelCase(value);
229
+ }
230
+ return converted;
231
+ }
180
232
  var HttpClient = class {
181
233
  authUrl = "https://blink.new";
182
234
  coreUrl = "https://core.blink.new";
@@ -260,45 +312,86 @@ var HttpClient = class {
260
312
  */
261
313
  // Table operations (PostgREST-compatible)
262
314
  async dbGet(table, searchParams) {
263
- return this.get(`/api/db/${this.projectId}/rest/v1/${table}`, searchParams);
315
+ const response = await this.get(`/api/db/${this.projectId}/rest/v1/${table}`, searchParams);
316
+ const convertedData = convertKeysToCamelCase(response.data);
317
+ return {
318
+ ...response,
319
+ data: convertedData
320
+ };
264
321
  }
265
322
  async dbPost(table, body, options = {}) {
266
323
  const headers = {};
267
324
  if (options.returning) {
268
325
  headers.Prefer = "return=representation";
269
326
  }
270
- return this.post(`/api/db/${this.projectId}/rest/v1/${table}`, body, headers);
327
+ const convertedBody = convertKeysToSnakeCase(body);
328
+ const response = await this.post(`/api/db/${this.projectId}/rest/v1/${table}`, convertedBody, headers);
329
+ const convertedData = convertKeysToCamelCase(response.data);
330
+ return {
331
+ ...response,
332
+ data: convertedData
333
+ };
271
334
  }
272
335
  async dbPatch(table, body, searchParams, options = {}) {
273
336
  const headers = {};
274
337
  if (options.returning) {
275
338
  headers.Prefer = "return=representation";
276
339
  }
277
- return this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
340
+ const convertedBody = convertKeysToSnakeCase(body);
341
+ const response = await this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
278
342
  method: "PATCH",
279
- body,
343
+ body: convertedBody,
280
344
  headers,
281
345
  searchParams
282
346
  });
347
+ const convertedData = convertKeysToCamelCase(response.data);
348
+ return {
349
+ ...response,
350
+ data: convertedData
351
+ };
283
352
  }
284
353
  async dbDelete(table, searchParams, options = {}) {
285
354
  const headers = {};
286
355
  if (options.returning) {
287
356
  headers.Prefer = "return=representation";
288
357
  }
289
- return this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
358
+ const response = await this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
290
359
  method: "DELETE",
291
360
  headers,
292
361
  searchParams
293
362
  });
363
+ const convertedData = convertKeysToCamelCase(response.data);
364
+ return {
365
+ ...response,
366
+ data: convertedData
367
+ };
294
368
  }
295
369
  // Raw SQL operations
296
370
  async dbSql(query, params) {
297
- return this.post(`/api/db/${this.projectId}/sql`, { query, params });
371
+ const response = await this.post(`/api/db/${this.projectId}/sql`, { query, params });
372
+ const convertedData = {
373
+ ...response.data,
374
+ rows: convertKeysToCamelCase(response.data.rows)
375
+ };
376
+ return {
377
+ ...response,
378
+ data: convertedData
379
+ };
298
380
  }
299
381
  // Batch SQL operations
300
382
  async dbBatch(statements, mode = "write") {
301
- return this.post(`/api/db/${this.projectId}/batch`, { statements, mode });
383
+ const response = await this.post(`/api/db/${this.projectId}/batch`, { statements, mode });
384
+ const convertedData = {
385
+ ...response.data,
386
+ results: response.data.results.map((result) => ({
387
+ ...result,
388
+ rows: convertKeysToCamelCase(result.rows)
389
+ }))
390
+ };
391
+ return {
392
+ ...response,
393
+ data: convertedData
394
+ };
302
395
  }
303
396
  /**
304
397
  * Upload file with progress tracking
package/dist/index.mjs CHANGED
@@ -65,6 +65,28 @@ var BlinkNotificationsError = class extends BlinkError {
65
65
  };
66
66
 
67
67
  // ../core/src/query-builder.ts
68
+ function camelToSnake(str) {
69
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
70
+ }
71
+ function convertFilterKeysToSnakeCase(condition) {
72
+ if (!condition) return condition;
73
+ if ("AND" in condition) {
74
+ return {
75
+ AND: condition.AND?.map(convertFilterKeysToSnakeCase)
76
+ };
77
+ }
78
+ if ("OR" in condition) {
79
+ return {
80
+ OR: condition.OR?.map(convertFilterKeysToSnakeCase)
81
+ };
82
+ }
83
+ const converted = {};
84
+ for (const [field, value] of Object.entries(condition)) {
85
+ const snakeField = camelToSnake(field);
86
+ converted[snakeField] = value;
87
+ }
88
+ return converted;
89
+ }
68
90
  function buildFilterQuery(condition) {
69
91
  if (!condition) return "";
70
92
  if ("AND" in condition) {
@@ -138,12 +160,14 @@ function encodeQueryValue(value) {
138
160
  function buildQuery(options = {}) {
139
161
  const params = {};
140
162
  if (options.select && options.select.length > 0) {
141
- params.select = options.select.join(",");
163
+ const snakeFields = options.select.map(camelToSnake);
164
+ params.select = snakeFields.join(",");
142
165
  } else {
143
166
  params.select = "*";
144
167
  }
145
168
  if (options.where) {
146
- const filterQuery = buildFilterQuery(options.where);
169
+ const convertedWhere = convertFilterKeysToSnakeCase(options.where);
170
+ const filterQuery = buildFilterQuery(convertedWhere);
147
171
  if (filterQuery) {
148
172
  const filterParams = filterQuery.split("&");
149
173
  for (const param of filterParams) {
@@ -158,7 +182,7 @@ function buildQuery(options = {}) {
158
182
  if (typeof options.orderBy === "string") {
159
183
  params.order = options.orderBy;
160
184
  } else {
161
- const orderClauses = Object.entries(options.orderBy).map(([field, direction]) => `${field}.${direction}`);
185
+ const orderClauses = Object.entries(options.orderBy).map(([field, direction]) => `${camelToSnake(field)}.${direction}`);
162
186
  params.order = orderClauses.join(",");
163
187
  }
164
188
  }
@@ -175,6 +199,34 @@ function buildQuery(options = {}) {
175
199
  }
176
200
 
177
201
  // ../core/src/http-client.ts
202
+ function camelToSnake2(str) {
203
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
204
+ }
205
+ function snakeToCamel(str) {
206
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
207
+ }
208
+ function convertKeysToSnakeCase(obj) {
209
+ if (obj === null || obj === void 0) return obj;
210
+ if (typeof obj !== "object") return obj;
211
+ if (Array.isArray(obj)) return obj.map(convertKeysToSnakeCase);
212
+ const converted = {};
213
+ for (const [key, value] of Object.entries(obj)) {
214
+ const snakeKey = camelToSnake2(key);
215
+ converted[snakeKey] = convertKeysToSnakeCase(value);
216
+ }
217
+ return converted;
218
+ }
219
+ function convertKeysToCamelCase(obj) {
220
+ if (obj === null || obj === void 0) return obj;
221
+ if (typeof obj !== "object") return obj;
222
+ if (Array.isArray(obj)) return obj.map(convertKeysToCamelCase);
223
+ const converted = {};
224
+ for (const [key, value] of Object.entries(obj)) {
225
+ const camelKey = snakeToCamel(key);
226
+ converted[camelKey] = convertKeysToCamelCase(value);
227
+ }
228
+ return converted;
229
+ }
178
230
  var HttpClient = class {
179
231
  authUrl = "https://blink.new";
180
232
  coreUrl = "https://core.blink.new";
@@ -258,45 +310,86 @@ var HttpClient = class {
258
310
  */
259
311
  // Table operations (PostgREST-compatible)
260
312
  async dbGet(table, searchParams) {
261
- return this.get(`/api/db/${this.projectId}/rest/v1/${table}`, searchParams);
313
+ const response = await this.get(`/api/db/${this.projectId}/rest/v1/${table}`, searchParams);
314
+ const convertedData = convertKeysToCamelCase(response.data);
315
+ return {
316
+ ...response,
317
+ data: convertedData
318
+ };
262
319
  }
263
320
  async dbPost(table, body, options = {}) {
264
321
  const headers = {};
265
322
  if (options.returning) {
266
323
  headers.Prefer = "return=representation";
267
324
  }
268
- return this.post(`/api/db/${this.projectId}/rest/v1/${table}`, body, headers);
325
+ const convertedBody = convertKeysToSnakeCase(body);
326
+ const response = await this.post(`/api/db/${this.projectId}/rest/v1/${table}`, convertedBody, headers);
327
+ const convertedData = convertKeysToCamelCase(response.data);
328
+ return {
329
+ ...response,
330
+ data: convertedData
331
+ };
269
332
  }
270
333
  async dbPatch(table, body, searchParams, options = {}) {
271
334
  const headers = {};
272
335
  if (options.returning) {
273
336
  headers.Prefer = "return=representation";
274
337
  }
275
- return this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
338
+ const convertedBody = convertKeysToSnakeCase(body);
339
+ const response = await this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
276
340
  method: "PATCH",
277
- body,
341
+ body: convertedBody,
278
342
  headers,
279
343
  searchParams
280
344
  });
345
+ const convertedData = convertKeysToCamelCase(response.data);
346
+ return {
347
+ ...response,
348
+ data: convertedData
349
+ };
281
350
  }
282
351
  async dbDelete(table, searchParams, options = {}) {
283
352
  const headers = {};
284
353
  if (options.returning) {
285
354
  headers.Prefer = "return=representation";
286
355
  }
287
- return this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
356
+ const response = await this.request(`/api/db/${this.projectId}/rest/v1/${table}`, {
288
357
  method: "DELETE",
289
358
  headers,
290
359
  searchParams
291
360
  });
361
+ const convertedData = convertKeysToCamelCase(response.data);
362
+ return {
363
+ ...response,
364
+ data: convertedData
365
+ };
292
366
  }
293
367
  // Raw SQL operations
294
368
  async dbSql(query, params) {
295
- return this.post(`/api/db/${this.projectId}/sql`, { query, params });
369
+ const response = await this.post(`/api/db/${this.projectId}/sql`, { query, params });
370
+ const convertedData = {
371
+ ...response.data,
372
+ rows: convertKeysToCamelCase(response.data.rows)
373
+ };
374
+ return {
375
+ ...response,
376
+ data: convertedData
377
+ };
296
378
  }
297
379
  // Batch SQL operations
298
380
  async dbBatch(statements, mode = "write") {
299
- return this.post(`/api/db/${this.projectId}/batch`, { statements, mode });
381
+ const response = await this.post(`/api/db/${this.projectId}/batch`, { statements, mode });
382
+ const convertedData = {
383
+ ...response.data,
384
+ results: response.data.results.map((result) => ({
385
+ ...result,
386
+ rows: convertKeysToCamelCase(result.rows)
387
+ }))
388
+ };
389
+ return {
390
+ ...response,
391
+ data: convertedData
392
+ };
300
393
  }
301
394
  /**
302
395
  * Upload file with progress tracking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps",
5
5
  "keywords": [
6
6
  "blink",