@donotdev/functions 0.0.12 → 0.0.13

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 (50) hide show
  1. package/package.json +4 -4
  2. package/src/firebase/auth/setCustomClaims.ts +19 -5
  3. package/src/firebase/baseFunction.ts +11 -3
  4. package/src/firebase/billing/changePlan.ts +5 -1
  5. package/src/firebase/billing/createCustomerPortal.ts +6 -2
  6. package/src/firebase/billing/webhookHandler.ts +4 -1
  7. package/src/firebase/crud/aggregate.ts +5 -1
  8. package/src/firebase/crud/create.ts +17 -4
  9. package/src/firebase/crud/list.ts +9 -4
  10. package/src/firebase/crud/update.ts +17 -4
  11. package/src/firebase/oauth/exchangeToken.ts +17 -4
  12. package/src/shared/__tests__/validation.test.ts +5 -3
  13. package/src/shared/billing/__tests__/createCheckout.test.ts +123 -22
  14. package/src/shared/billing/__tests__/webhookHandler.test.ts +121 -30
  15. package/src/shared/firebase.ts +1 -1
  16. package/src/shared/logger.ts +7 -1
  17. package/src/shared/oauth/__tests__/exchangeToken.test.ts +6 -8
  18. package/src/shared/oauth/__tests__/grantAccess.test.ts +31 -14
  19. package/src/shared/utils/internal/auth.ts +10 -3
  20. package/src/shared/utils/internal/rateLimiter.ts +8 -2
  21. package/src/shared/utils.ts +5 -1
  22. package/src/supabase/auth/deleteAccount.ts +1 -1
  23. package/src/supabase/auth/getCustomClaims.ts +5 -3
  24. package/src/supabase/auth/getUserAuthStatus.ts +5 -3
  25. package/src/supabase/auth/removeCustomClaims.ts +10 -5
  26. package/src/supabase/auth/setCustomClaims.ts +9 -4
  27. package/src/supabase/baseFunction.ts +77 -22
  28. package/src/supabase/billing/cancelSubscription.ts +9 -3
  29. package/src/supabase/billing/changePlan.ts +20 -5
  30. package/src/supabase/billing/createCheckoutSession.ts +20 -5
  31. package/src/supabase/billing/createCustomerPortal.ts +14 -4
  32. package/src/supabase/billing/refreshSubscriptionStatus.ts +29 -9
  33. package/src/supabase/crud/aggregate.ts +14 -4
  34. package/src/supabase/crud/create.ts +30 -11
  35. package/src/supabase/crud/delete.ts +11 -3
  36. package/src/supabase/crud/get.ts +25 -3
  37. package/src/supabase/crud/list.ts +76 -22
  38. package/src/supabase/crud/update.ts +32 -10
  39. package/src/supabase/helpers/authProvider.ts +5 -2
  40. package/src/supabase/index.ts +1 -4
  41. package/src/supabase/registerCrudFunctions.ts +11 -9
  42. package/src/supabase/utils/idempotency.ts +13 -15
  43. package/src/supabase/utils/monitoring.ts +5 -1
  44. package/src/supabase/utils/rateLimiter.ts +13 -3
  45. package/src/vercel/api/billing/webhook-handler.ts +6 -2
  46. package/src/vercel/api/crud/create.ts +7 -2
  47. package/src/vercel/api/crud/delete.ts +3 -1
  48. package/src/vercel/api/crud/get.ts +3 -1
  49. package/src/vercel/api/crud/list.ts +3 -1
  50. package/src/vercel/api/crud/update.ts +7 -2
@@ -22,7 +22,11 @@ import type {
22
22
  UserRole,
23
23
  } from '@donotdev/core/server';
24
24
 
25
- import { defaultFieldMapper } from '@donotdev/supabase';
25
+ import {
26
+ createEntityAwareMapper,
27
+ defaultFieldMapper,
28
+ getEntityFieldNames,
29
+ } from '@donotdev/supabase';
26
30
 
27
31
  import { DoNotDevError } from '../../shared/utils.js';
28
32
  import { createSupabaseHandler } from '../baseFunction.js';
@@ -32,7 +36,9 @@ const mapper = defaultFieldMapper;
32
36
 
33
37
  /** Ensure we only pass strings to the mapper (entity listFields/ownership can be mis-typed at runtime). */
34
38
  function toBackendColumn(field: unknown): string {
35
- return mapper.toBackendField(typeof field === 'string' ? field : String(field));
39
+ return mapper.toBackendField(
40
+ typeof field === 'string' ? field : String(field)
41
+ );
36
42
  }
37
43
 
38
44
  export interface ListEntityRequest {
@@ -58,7 +64,10 @@ function encodeCursor(id: string, orderBy: Record<string, any>): string {
58
64
  /**
59
65
  * Decode cursor for keyset pagination
60
66
  */
61
- function decodeCursor(cursor: string): { id: string; orderBy: Record<string, any> } {
67
+ function decodeCursor(cursor: string): {
68
+ id: string;
69
+ orderBy: Record<string, any>;
70
+ } {
62
71
  try {
63
72
  return JSON.parse(Buffer.from(cursor, 'base64').toString());
64
73
  } catch (error) {
@@ -132,7 +141,12 @@ export function createSupabaseListEntities(
132
141
  const requestSchema = v.object({
133
142
  where: v.optional(v.array(v.tuple([v.string(), v.any(), v.any()]))),
134
143
  orderBy: v.optional(
135
- v.array(v.tuple([v.pipe(v.string(), v.minLength(1)), v.picklist(['asc', 'desc'])]))
144
+ v.array(
145
+ v.tuple([
146
+ v.pipe(v.string(), v.minLength(1)),
147
+ v.picklist(['asc', 'desc']),
148
+ ])
149
+ )
136
150
  ),
137
151
  limit: v.optional(v.pipe(v.number(), v.minValue(1))),
138
152
  startAfterId: v.optional(v.string()), // Offset-based (legacy)
@@ -149,18 +163,31 @@ export function createSupabaseListEntities(
149
163
  isListCard ? `listCard_${collection}` : `list_${collection}`,
150
164
  requestSchema,
151
165
  async (data: ListEntityRequest, ctx) => {
152
- const { where = [], orderBy = [], limit = 50, startAfterId, startAfterCursor, search } = data;
166
+ const {
167
+ where = [],
168
+ orderBy = [],
169
+ limit = 50,
170
+ startAfterId,
171
+ startAfterCursor,
172
+ search,
173
+ } = data;
153
174
  const { userRole, uid, supabaseAdmin } = ctx;
154
175
 
155
176
  const isAdmin = hasRoleAccess(userRole, 'admin');
156
177
 
157
178
  // Build query - select fields (listFields + id + status, or *). Only use string entries (entity may be mis-typed).
158
- const safeListFields = listFields?.filter((f): f is string => typeof f === 'string');
159
- const selectFields = safeListFields && safeListFields.length > 0
160
- ? safeListFields.map((f) => toBackendColumn(f)).join(', ') + ', id, status'
161
- : '*';
162
-
163
- let query = supabaseAdmin.from(collection).select(selectFields, { count: 'exact' });
179
+ const safeListFields = listFields?.filter(
180
+ (f): f is string => typeof f === 'string'
181
+ );
182
+ const selectFields =
183
+ safeListFields && safeListFields.length > 0
184
+ ? safeListFields.map((f) => toBackendColumn(f)).join(', ') +
185
+ ', id, status'
186
+ : '*';
187
+
188
+ let query = supabaseAdmin
189
+ .from(collection)
190
+ .select(selectFields, { count: 'exact' });
164
191
 
165
192
  // Filter out hidden statuses for non-admin users
166
193
  if (!isAdmin) {
@@ -198,14 +225,20 @@ export function createSupabaseListEntities(
198
225
  );
199
226
  }
200
227
  // Escape SQL ILIKE wildcards to prevent wildcard injection
201
- const escapedQuery = search.query.replace(/%/g, '\\%').replace(/_/g, '\\_');
228
+ const escapedQuery = search.query
229
+ .replace(/%/g, '\\%')
230
+ .replace(/_/g, '\\_');
202
231
  query = query.ilike(toBackendColumn(search.field), `%${escapedQuery}%`);
203
232
  }
204
233
 
205
234
  // Validate where clause fields against entity schema
206
235
  for (const [field, operator, value] of where) {
207
236
  if (safeListFields && safeListFields.length > 0) {
208
- if (!safeListFields.includes(field) && field !== 'status' && field !== 'id') {
237
+ if (
238
+ !safeListFields.includes(field) &&
239
+ field !== 'status' &&
240
+ field !== 'id'
241
+ ) {
209
242
  throw new DoNotDevError(
210
243
  `Where field '${field}' is not allowed`,
211
244
  'invalid-argument'
@@ -220,9 +253,13 @@ export function createSupabaseListEntities(
220
253
  query = applyOperator(query, toBackendColumn(field), operator, value);
221
254
  }
222
255
 
223
- const hasIdInOrderBy = orderBy.some(([field]) => toBackendColumn(field) === 'id');
256
+ const hasIdInOrderBy = orderBy.some(
257
+ ([field]) => toBackendColumn(field) === 'id'
258
+ );
224
259
  for (const [field, direction] of orderBy) {
225
- query = query.order(toBackendColumn(field), { ascending: direction === 'asc' });
260
+ query = query.order(toBackendColumn(field), {
261
+ ascending: direction === 'asc',
262
+ });
226
263
  }
227
264
  // Add id as tiebreaker if not already in orderBy
228
265
  if (!hasIdInOrderBy && orderBy.length > 0) {
@@ -236,7 +273,7 @@ export function createSupabaseListEntities(
236
273
  useKeysetPagination = true;
237
274
  try {
238
275
  const cursor = decodeCursor(startAfterCursor);
239
-
276
+
240
277
  if (orderBy.length === 0) {
241
278
  query = query.gt('id', cursor.id);
242
279
  } else {
@@ -271,12 +308,17 @@ export function createSupabaseListEntities(
271
308
  const { data: rows, error, count } = await query;
272
309
 
273
310
  if (error) {
274
- throw new DoNotDevError(`Failed to list entities: ${error.message}`, 'internal');
311
+ throw new DoNotDevError(
312
+ `Failed to list entities: ${error.message}`,
313
+ 'internal'
314
+ );
275
315
  }
276
316
 
277
317
  let items = (rows || []) as Record<string, any>[];
278
-
279
- // Filter out cursor item for keyset pagination
318
+
319
+ // Filter out cursor item for keyset pagination.
320
+ // Note: items are still raw DB rows here (pre-normalization), so mapper.toBackendField
321
+ // is correct for accessing backend column values. entityMapper is used later for normalization.
280
322
  if (useKeysetPagination && startAfterCursor && items.length > 0) {
281
323
  try {
282
324
  const cursor = decodeCursor(startAfterCursor);
@@ -320,11 +362,23 @@ export function createSupabaseListEntities(
320
362
  return value;
321
363
  };
322
364
 
323
- const visibilityOptions = ownership && uid ? { uid, ownership } : undefined;
365
+ const visibilityOptions =
366
+ ownership && uid ? { uid, ownership } : undefined;
367
+
368
+ // Build entity-aware mapper from schema entries so fromBackendRow returns
369
+ // entity field names (not blind camelCase), matching filterVisibleFields expectations.
370
+ const entityFieldNames = getEntityFieldNames(documentSchema);
371
+ const entityMapper =
372
+ entityFieldNames.length > 0
373
+ ? createEntityAwareMapper(entityFieldNames)
374
+ : mapper;
324
375
 
325
376
  // Filter document fields based on visibility and user role
326
377
  const filteredItems = items.map((row) => {
327
- const camelRow = mapper.fromBackendRow(row) as Record<string, any>;
378
+ const camelRow = entityMapper.fromBackendRow(row) as Record<
379
+ string,
380
+ any
381
+ >;
328
382
  const visibleData = filterVisibleFields(
329
383
  camelRow,
330
384
  documentSchema,
@@ -355,7 +409,7 @@ export function createSupabaseListEntities(
355
409
  });
356
410
 
357
411
  const hasMore = items.length === limit;
358
-
412
+
359
413
  // Generate cursor for keyset pagination or offset for legacy
360
414
  let lastVisible: string | null = null;
361
415
  if (hasMore && filteredItems.length > 0) {
@@ -18,10 +18,7 @@ import { defaultFieldMapper } from '@donotdev/supabase';
18
18
  import { updateMetadata } from '../../shared/index.js';
19
19
  import { DoNotDevError, validateDocument } from '../../shared/utils.js';
20
20
  import { createSupabaseHandler } from '../baseFunction.js';
21
- import {
22
- checkIdempotency,
23
- storeIdempotency,
24
- } from '../utils/idempotency.js';
21
+ import { checkIdempotency, storeIdempotency } from '../utils/idempotency.js';
25
22
 
26
23
  const mapper = defaultFieldMapper;
27
24
 
@@ -68,7 +65,10 @@ async function checkUniqueKeys(
68
65
  // Build query excluding current document
69
66
  let query = supabaseAdmin.from(collection).select('*');
70
67
  for (const field of uniqueKey.fields) {
71
- query = query.eq(mapper.toBackendField(field), normalizeValue(payload[field]));
68
+ query = query.eq(
69
+ mapper.toBackendField(field),
70
+ normalizeValue(payload[field])
71
+ );
72
72
  }
73
73
  query = query.neq('id', id);
74
74
 
@@ -139,7 +139,10 @@ export function createSupabaseUpdateEntity(
139
139
  throw new DoNotDevError('Entity not found', 'not-found');
140
140
  }
141
141
 
142
- const merged = { ...(mapper.fromBackendRow(existing) as Record<string, any>), ...payload };
142
+ const merged = {
143
+ ...(mapper.fromBackendRow(existing) as Record<string, any>),
144
+ ...payload,
145
+ };
143
146
  const status = merged.status ?? existing.status;
144
147
  const isDraft = status === 'draft';
145
148
 
@@ -150,7 +153,14 @@ export function createSupabaseUpdateEntity(
150
153
  const uniqueKeys = schemaWithMeta.metadata?.uniqueKeys;
151
154
 
152
155
  if (uniqueKeys && uniqueKeys.length > 0) {
153
- await checkUniqueKeys(collection, id, merged, uniqueKeys, isDraft, supabaseAdmin);
156
+ await checkUniqueKeys(
157
+ collection,
158
+ id,
159
+ merged,
160
+ uniqueKeys,
161
+ isDraft,
162
+ supabaseAdmin
163
+ );
154
164
  }
155
165
 
156
166
  // Validate merged document (skip for drafts)
@@ -159,9 +169,18 @@ export function createSupabaseUpdateEntity(
159
169
  }
160
170
 
161
171
  const metadata = updateMetadata(uid);
162
- const snakeMetadata = mapper.toBackendKeys(metadata as Record<string, unknown>);
172
+ const snakeMetadata = mapper.toBackendKeys(
173
+ metadata as Record<string, unknown>
174
+ );
163
175
 
164
- const { createdAt, updatedAt, created_at, updated_at, id: _id, ...payloadWithoutTimestamps } = payload;
176
+ const {
177
+ createdAt,
178
+ updatedAt,
179
+ created_at,
180
+ updated_at,
181
+ id: _id,
182
+ ...payloadWithoutTimestamps
183
+ } = payload;
165
184
  const snakePayload = mapper.toBackendKeys(payloadWithoutTimestamps);
166
185
 
167
186
  // Update document (DB sets updated_at via trigger)
@@ -176,7 +195,10 @@ export function createSupabaseUpdateEntity(
176
195
  .single();
177
196
 
178
197
  if (error) {
179
- throw new DoNotDevError(`Failed to update entity: ${error.message}`, 'internal');
198
+ throw new DoNotDevError(
199
+ `Failed to update entity: ${error.message}`,
200
+ 'internal'
201
+ );
180
202
  }
181
203
 
182
204
  const result = mapper.fromBackendRow(updated) as Record<string, any>;
@@ -28,10 +28,13 @@ import type { SupabaseClient } from '@supabase/supabase-js';
28
28
  * @version 0.0.1
29
29
  * @since 0.5.0
30
30
  */
31
- export function createSupabaseAuthProvider(supabaseAdmin: SupabaseClient): AuthProvider {
31
+ export function createSupabaseAuthProvider(
32
+ supabaseAdmin: SupabaseClient
33
+ ): AuthProvider {
32
34
  return {
33
35
  async getUser(userId: string) {
34
- const { data, error } = await supabaseAdmin.auth.admin.getUserById(userId);
36
+ const { data, error } =
37
+ await supabaseAdmin.auth.admin.getUserById(userId);
35
38
  if (error) throw error;
36
39
  return { customClaims: data.user?.app_metadata ?? {} };
37
40
  },
@@ -60,10 +60,7 @@ export {
60
60
  checkRateLimitWithPostgres,
61
61
  DEFAULT_RATE_LIMITS,
62
62
  } from './utils/rateLimiter.js';
63
- export type {
64
- RateLimitConfig,
65
- RateLimitResult,
66
- } from './utils/rateLimiter.js';
63
+ export type { RateLimitConfig, RateLimitResult } from './utils/rateLimiter.js';
67
64
  export {
68
65
  recordOperationMetrics,
69
66
  getFailureRate,
@@ -89,7 +89,7 @@ export function createSupabaseCrudFunctions(
89
89
  schemas.update,
90
90
  access.update
91
91
  );
92
-
92
+
93
93
  // Extract reference metadata from entity if available
94
94
  const schemaWithMeta = schemas.get as {
95
95
  metadata?: {
@@ -108,7 +108,7 @@ export function createSupabaseCrudFunctions(
108
108
  };
109
109
  };
110
110
  const referenceMetadata = schemaWithMeta.metadata?.references;
111
-
111
+
112
112
  handlers[`delete_${col}`] = createSupabaseDeleteEntity(
113
113
  col,
114
114
  access.delete,
@@ -127,7 +127,8 @@ export function createSupabaseCrudFunctions(
127
127
  const serve = async (req: Request): Promise<Response> => {
128
128
  try {
129
129
  const body = await req.json().catch(() => ({}));
130
- const functionName = (body as Record<string, unknown>)._functionName as string;
130
+ const functionName = (body as Record<string, unknown>)
131
+ ._functionName as string;
131
132
 
132
133
  if (!functionName) {
133
134
  return new Response(
@@ -146,7 +147,7 @@ export function createSupabaseCrudFunctions(
146
147
 
147
148
  // Remove _functionName from body before passing to handler
148
149
  const { _functionName, ...handlerData } = body as Record<string, unknown>;
149
-
150
+
150
151
  // Create new request with cleaned body
151
152
  const handlerReq = new Request(req.url, {
152
153
  method: req.method,
@@ -156,11 +157,12 @@ export function createSupabaseCrudFunctions(
156
157
 
157
158
  return handler(handlerReq);
158
159
  } catch (error) {
159
- const message = error instanceof Error ? error.message : 'Internal server error';
160
- return new Response(
161
- JSON.stringify({ error: message }),
162
- { status: 500, headers: { 'Content-Type': 'application/json' } }
163
- );
160
+ const message =
161
+ error instanceof Error ? error.message : 'Internal server error';
162
+ return new Response(JSON.stringify({ error: message }), {
163
+ status: 500,
164
+ headers: { 'Content-Type': 'application/json' },
165
+ });
164
166
  }
165
167
  };
166
168
 
@@ -86,21 +86,19 @@ export async function storeIdempotency<T>(
86
86
  const expiresAt = new Date();
87
87
  expiresAt.setHours(expiresAt.getHours() + ttl);
88
88
 
89
- const { error } = await supabaseAdmin
90
- .from('idempotency')
91
- .upsert(
92
- {
93
- id,
94
- operation,
95
- idempotency_key: idempotencyKey,
96
- result: result as any,
97
- processed_by: uid,
98
- expires_at: expiresAt.toISOString(),
99
- },
100
- {
101
- onConflict: 'idempotency_key',
102
- }
103
- );
89
+ const { error } = await supabaseAdmin.from('idempotency').upsert(
90
+ {
91
+ id,
92
+ operation,
93
+ idempotency_key: idempotencyKey,
94
+ result: result as any,
95
+ processed_by: uid,
96
+ expires_at: expiresAt.toISOString(),
97
+ },
98
+ {
99
+ onConflict: 'idempotency_key',
100
+ }
101
+ );
104
102
 
105
103
  if (error) {
106
104
  console.error('[idempotency] Store failed:', error);
@@ -167,7 +167,11 @@ export async function getSlowOperations(
167
167
  grouped[metric.operation].count += 1;
168
168
  }
169
169
 
170
- const results: Array<{ operation: string; avgDuration: number; count: number }> = [];
170
+ const results: Array<{
171
+ operation: string;
172
+ avgDuration: number;
173
+ count: number;
174
+ }> = [];
171
175
  for (const [operation, stats] of Object.entries(grouped)) {
172
176
  const avgDuration = stats.sum / stats.count;
173
177
  if (avgDuration >= thresholdMs) {
@@ -24,7 +24,10 @@
24
24
  */
25
25
 
26
26
  import type { SupabaseClient } from '@supabase/supabase-js';
27
- import type { ServerRateLimitConfig as RateLimitConfig, ServerRateLimitResult as RateLimitResult } from '@donotdev/core';
27
+ import type {
28
+ ServerRateLimitConfig as RateLimitConfig,
29
+ ServerRateLimitResult as RateLimitResult,
30
+ } from '@donotdev/core';
28
31
 
29
32
  export type { RateLimitConfig, RateLimitResult };
30
33
 
@@ -198,7 +201,11 @@ export async function checkRateLimitWithPostgres(
198
201
 
199
202
  if (error) {
200
203
  // Log for observability — infrastructure errors here need immediate attention.
201
- console.error('[rateLimit] rate_limit_check RPC failed:', error.message, error.code);
204
+ console.error(
205
+ '[rateLimit] rate_limit_check RPC failed:',
206
+ error.message,
207
+ error.code
208
+ );
202
209
  return failClosed();
203
210
  }
204
211
 
@@ -210,7 +217,10 @@ export async function checkRateLimitWithPostgres(
210
217
  blockRemainingSeconds: result.block_remaining_seconds,
211
218
  };
212
219
  } catch (error) {
213
- console.error('[rateLimit] Unexpected error in checkRateLimitWithPostgres:', error);
220
+ console.error(
221
+ '[rateLimit] Unexpected error in checkRateLimitWithPostgres:',
222
+ error
223
+ );
214
224
  return failClosed();
215
225
  }
216
226
  }
@@ -55,7 +55,10 @@ export function createStripeWebhook(billingConfig: StripeBackConfig) {
55
55
  async getUser(userId: string) {
56
56
  return getFirebaseAdminAuth().getUser(userId);
57
57
  },
58
- async setCustomUserClaims(userId: string, claims: Record<string, unknown>) {
58
+ async setCustomUserClaims(
59
+ userId: string,
60
+ claims: Record<string, unknown>
61
+ ) {
59
62
  await getFirebaseAdminAuth().setCustomUserClaims(userId, claims);
60
63
  },
61
64
  };
@@ -90,7 +93,8 @@ async function getRawBody(req: NextApiRequest): Promise<Buffer> {
90
93
  const chunks: Buffer[] = [];
91
94
  let totalBytes = 0;
92
95
  for await (const chunk of req) {
93
- const buf = typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer);
96
+ const buf =
97
+ typeof chunk === 'string' ? Buffer.from(chunk) : (chunk as Buffer);
94
98
  totalBytes += buf.length;
95
99
  if (totalBytes > MAX_BODY_BYTES) {
96
100
  throw new Error('Request body too large');
@@ -19,7 +19,10 @@ import {
19
19
  transformFirestoreData,
20
20
  } from '../../../shared/index.js';
21
21
  import { createMetadata } from '../../../shared/index.js';
22
- import { validateCollectionName, validateDocument } from '../../../shared/utils.js';
22
+ import {
23
+ validateCollectionName,
24
+ validateDocument,
25
+ } from '../../../shared/utils.js';
23
26
  import { verifyAuthToken } from '../../../shared/utils/internal/auth.js';
24
27
 
25
28
  import type { NextApiRequest, NextApiResponse } from 'next';
@@ -84,7 +87,9 @@ export default async function handler(
84
87
  handleError(error);
85
88
  } catch (handledError: any) {
86
89
  const status = handledError.code === 'invalid-argument' ? 400 : 500;
87
- return res.status(status).json({ error: handledError.message, code: handledError.code });
90
+ return res
91
+ .status(status)
92
+ .json({ error: handledError.message, code: handledError.code });
88
93
  }
89
94
  }
90
95
  }
@@ -60,7 +60,9 @@ export default async function handler(
60
60
  handleError(error);
61
61
  } catch (handledError: any) {
62
62
  const status = handledError.code === 'invalid-argument' ? 400 : 500;
63
- return res.status(status).json({ error: handledError.message, code: handledError.code });
63
+ return res
64
+ .status(status)
65
+ .json({ error: handledError.message, code: handledError.code });
64
66
  }
65
67
  }
66
68
  }
@@ -74,7 +74,9 @@ export default async function handler(
74
74
  handleError(error);
75
75
  } catch (handledError: any) {
76
76
  const status = handledError.code === 'invalid-argument' ? 400 : 500;
77
- return res.status(status).json({ error: handledError.message, code: handledError.code });
77
+ return res
78
+ .status(status)
79
+ .json({ error: handledError.message, code: handledError.code });
78
80
  }
79
81
  }
80
82
  }
@@ -87,7 +87,9 @@ export default async function handler(
87
87
  handleError(error);
88
88
  } catch (handledError: any) {
89
89
  const status = handledError.code === 'invalid-argument' ? 400 : 500;
90
- return res.status(status).json({ error: handledError.message, code: handledError.code });
90
+ return res
91
+ .status(status)
92
+ .json({ error: handledError.message, code: handledError.code });
91
93
  }
92
94
  }
93
95
  }
@@ -19,7 +19,10 @@ import {
19
19
  transformFirestoreData,
20
20
  } from '../../../shared/index.js';
21
21
  import { updateMetadata } from '../../../shared/index.js';
22
- import { validateCollectionName, validateDocument } from '../../../shared/utils.js';
22
+ import {
23
+ validateCollectionName,
24
+ validateDocument,
25
+ } from '../../../shared/utils.js';
23
26
  import { verifyAuthToken } from '../../../shared/utils/internal/auth.js';
24
27
 
25
28
  import type { NextApiRequest, NextApiResponse } from 'next';
@@ -100,7 +103,9 @@ export default async function handler(
100
103
  handleError(error);
101
104
  } catch (handledError: any) {
102
105
  const status = handledError.code === 'invalid-argument' ? 400 : 500;
103
- return res.status(status).json({ error: handledError.message, code: handledError.code });
106
+ return res
107
+ .status(status)
108
+ .json({ error: handledError.message, code: handledError.code });
104
109
  }
105
110
  }
106
111
  }