@donotdev/functions 0.0.6 → 0.0.7

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 (56) hide show
  1. package/lib/firebase/index.js +8026 -32339
  2. package/lib/firebase/index.js.map +4 -4
  3. package/lib/shared/index.js +5973 -31507
  4. package/lib/shared/index.js.map +4 -4
  5. package/lib/vercel/api/index.js +4173 -30252
  6. package/lib/vercel/api/index.js.map +4 -4
  7. package/package.json +3 -3
  8. package/src/firebase/auth/getCustomClaims.ts +2 -1
  9. package/src/firebase/auth/getUserAuthStatus.ts +2 -1
  10. package/src/firebase/auth/removeCustomClaims.ts +2 -1
  11. package/src/firebase/auth/setCustomClaims.ts +3 -1
  12. package/src/firebase/baseFunction.ts +167 -65
  13. package/src/firebase/billing/cancelSubscription.ts +2 -1
  14. package/src/firebase/billing/changePlan.ts +1 -1
  15. package/src/firebase/billing/createCheckoutSession.ts +2 -2
  16. package/src/firebase/billing/createCustomerPortal.ts +2 -1
  17. package/src/firebase/billing/refreshSubscriptionStatus.ts +1 -1
  18. package/src/firebase/billing/webhookHandler.ts +3 -1
  19. package/src/firebase/config/constants.ts +12 -2
  20. package/src/firebase/crud/aggregate.ts +2 -2
  21. package/src/firebase/crud/create.ts +10 -12
  22. package/src/firebase/crud/delete.ts +9 -11
  23. package/src/firebase/crud/get.ts +21 -11
  24. package/src/firebase/crud/list.ts +80 -25
  25. package/src/firebase/crud/update.ts +9 -11
  26. package/src/firebase/helpers/githubAccessHelper.ts +2 -1
  27. package/src/firebase/helpers/githubTeamAccessHelper.ts +2 -1
  28. package/src/firebase/index.ts +7 -0
  29. package/src/firebase/oauth/disconnect.ts +1 -1
  30. package/src/firebase/oauth/exchangeToken.ts +4 -4
  31. package/src/firebase/oauth/getConnections.ts +1 -1
  32. package/src/firebase/oauth/githubAccess.ts +3 -2
  33. package/src/firebase/oauth/refreshToken.ts +4 -4
  34. package/src/firebase/registerCrudFunctions.ts +127 -0
  35. package/src/firebase/scheduled/checkExpiredSubscriptions.ts +3 -2
  36. package/src/shared/billing/webhookHandler.ts +1 -1
  37. package/src/shared/errorHandling.ts +1 -1
  38. package/src/shared/utils/external/subscription.ts +4 -5
  39. package/src/shared/utils/firebaseHelpers.ts +3 -1
  40. package/src/shared/utils/internal/idempotency.ts +2 -1
  41. package/src/shared/utils/internal/monitoring.ts +2 -1
  42. package/src/shared/utils/internal/rateLimiter.ts +2 -1
  43. package/src/shared/utils.ts +56 -7
  44. package/src/vercel/api/billing/cancel.ts +2 -1
  45. package/src/vercel/api/billing/change-plan.ts +1 -1
  46. package/src/vercel/api/billing/create-checkout-session.ts +2 -2
  47. package/src/vercel/api/billing/customer-portal.ts +2 -1
  48. package/src/vercel/api/billing/refresh-subscription-status.ts +1 -1
  49. package/src/vercel/api/crud/create.ts +2 -3
  50. package/src/vercel/api/crud/delete.ts +0 -1
  51. package/src/vercel/api/crud/get.ts +2 -3
  52. package/src/vercel/api/crud/list.ts +2 -3
  53. package/src/vercel/api/crud/update.ts +2 -3
  54. package/src/vercel/api/oauth/check-github-access.ts +1 -1
  55. package/src/vercel/api/oauth/grant-github-access.ts +1 -1
  56. package/src/vercel/api/oauth/revoke-github-access.ts +1 -1
@@ -10,15 +10,19 @@
10
10
  */
11
11
 
12
12
  import * as v from 'valibot';
13
- import { HIDDEN_STATUSES } from '@donotdev/core/server';
14
13
 
15
- import { transformFirestoreData } from '../../shared/index.js';
16
- import { filterVisibleFields } from '../../shared/index.js';
14
+ import {
15
+ filterVisibleFields,
16
+ hasRoleAccess,
17
+ HIDDEN_STATUSES,
18
+ } from '@donotdev/core/server';
19
+ import type { UserRole } from '@donotdev/core/server';
17
20
  import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
18
21
 
22
+ import { transformFirestoreData } from '../../shared/index.js';
19
23
  import { DoNotDevError } from '../../shared/utils.js';
20
24
  import { createBaseFunction } from '../baseFunction.js';
21
- import { CRUD_CONFIG } from '../config/constants.js';
25
+ import { CRUD_READ_CONFIG } from '../config/constants.js';
22
26
 
23
27
  import type {
24
28
  CallableFunction,
@@ -37,9 +41,10 @@ function getEntityLogicFactory(
37
41
  ) {
38
42
  return async function getEntityLogic(
39
43
  data: GetEntityRequest,
40
- context: { uid: string; request: CallableRequest<any> }
44
+ context: { uid: string; userRole: UserRole; request: CallableRequest<any> }
41
45
  ) {
42
46
  const { id } = data;
47
+ const { userRole } = context;
43
48
 
44
49
  // Get the document reference
45
50
  const db = getFirebaseAdminFirestore();
@@ -53,12 +58,14 @@ function getEntityLogicFactory(
53
58
  throw new DoNotDevError('Entity not found', 'not-found');
54
59
  }
55
60
 
56
- // Check if the user is an admin
57
- const isAdmin = context.request.auth?.token?.isAdmin === true;
61
+ const isAdmin = hasRoleAccess(userRole, 'admin'); // Uses role hierarchy
58
62
 
59
63
  // Hide drafts/deleted from non-admin users (security: hidden statuses never reach public)
60
64
  const docData = doc.data();
61
- if (!isAdmin && (HIDDEN_STATUSES as readonly string[]).includes(docData?.status)) {
65
+ if (
66
+ !isAdmin &&
67
+ (HIDDEN_STATUSES as readonly string[]).includes(docData?.status)
68
+ ) {
62
69
  throw new DoNotDevError('Entity not found', 'not-found');
63
70
  }
64
71
 
@@ -66,7 +73,7 @@ function getEntityLogicFactory(
66
73
  const filteredData = filterVisibleFields(
67
74
  docData || {},
68
75
  documentSchema,
69
- isAdmin
76
+ userRole
70
77
  );
71
78
 
72
79
  // Transform the document data back to the application format
@@ -81,12 +88,14 @@ function getEntityLogicFactory(
81
88
  * Generic function to get entities from any Firestore collection
82
89
  * @param collection - The Firestore collection name
83
90
  * @param documentSchema - The Valibot schema for document validation
91
+ * @param requiredRole - Minimum role required for this operation
84
92
  * @param customSchema - Optional custom request schema
85
93
  * @returns Firebase callable function
86
94
  */
87
95
  export const getEntity = (
88
96
  collection: string,
89
97
  documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
98
+ requiredRole: UserRole,
90
99
  customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
91
100
  ): CallableFunction<GetEntityRequest, Promise<any>> => {
92
101
  const requestSchema =
@@ -96,9 +105,10 @@ export const getEntity = (
96
105
  });
97
106
 
98
107
  return createBaseFunction(
99
- CRUD_CONFIG,
108
+ CRUD_READ_CONFIG,
100
109
  requestSchema,
101
110
  'get_entity',
102
- getEntityLogicFactory(collection, documentSchema)
111
+ getEntityLogicFactory(collection, documentSchema),
112
+ requiredRole
103
113
  );
104
114
  };
@@ -9,12 +9,25 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { Query } from '@donotdev/firebase/server';
13
- import { HIDDEN_STATUSES } from '@donotdev/core/server';
12
+ import * as v from 'valibot';
14
13
 
15
- import type { CallableRequest } from 'firebase-functions/v2/https';
14
+ import {
15
+ filterVisibleFields,
16
+ hasRoleAccess,
17
+ HIDDEN_STATUSES,
18
+ } from '@donotdev/core/server';
19
+ import type { UserRole } from '@donotdev/core/server';
20
+ import { getFirebaseAdminFirestore, Query } from '@donotdev/firebase/server';
16
21
 
17
- import * as v from 'valibot';
22
+ import { transformFirestoreData } from '../../shared/index.js';
23
+ import { DoNotDevError } from '../../shared/utils.js';
24
+ import { createBaseFunction } from '../baseFunction.js';
25
+ import { CRUD_READ_CONFIG } from '../config/constants.js';
26
+
27
+ import type {
28
+ CallableFunction,
29
+ CallableRequest,
30
+ } from 'firebase-functions/v2/https';
18
31
 
19
32
  export interface ListEntityRequest {
20
33
  where?: Array<[string, any, any]>;
@@ -26,15 +39,6 @@ export interface ListEntityRequest {
26
39
  query: string;
27
40
  };
28
41
  }
29
- import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
30
-
31
- import { transformFirestoreData } from '../../shared/index.js';
32
- import { filterVisibleFields } from '../../shared/index.js';
33
- import { DoNotDevError } from '../../shared/utils.js';
34
- import { createBaseFunction } from '../baseFunction.js';
35
- import { CRUD_CONFIG } from '../config/constants.js';
36
-
37
- import type { CallableFunction } from 'firebase-functions/v2/https';
38
42
 
39
43
  /**
40
44
  * Generic business logic for listing entities
@@ -42,17 +46,21 @@ import type { CallableFunction } from 'firebase-functions/v2/https';
42
46
  */
43
47
  function listEntitiesLogicFactory(
44
48
  collection: string,
45
- documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
49
+ documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
50
+ listFields?: string[]
46
51
  ) {
47
52
  return async function listEntitiesLogic(
48
53
  data: ListEntityRequest,
49
- context: { uid: string; request: CallableRequest<ListEntityRequest> }
54
+ context: {
55
+ uid: string;
56
+ userRole: UserRole;
57
+ request: CallableRequest<ListEntityRequest>;
58
+ }
50
59
  ) {
51
60
  const { where = [], orderBy = [], limit = 50, startAfterId, search } = data;
61
+ const { userRole } = context;
52
62
 
53
- // Check if the user is an admin (needed early for draft filtering)
54
- const user = context.request.auth;
55
- const isAdmin = user?.token?.isAdmin === true;
63
+ const isAdmin = hasRoleAccess(userRole, 'admin'); // Uses role hierarchy
56
64
 
57
65
  // Start with a Query (not a CollectionReference)
58
66
  const db = getFirebaseAdminFirestore();
@@ -98,14 +106,56 @@ function listEntitiesLogicFactory(
98
106
  // Apply limit for page size
99
107
  query = query.limit(limit);
100
108
 
109
+ // DEBUG: Log query details
110
+ console.log(
111
+ `[listEntities] Collection: ${collection}, UserRole: ${userRole}, IsAdmin: ${isAdmin}`
112
+ );
113
+ console.log(
114
+ `[listEntities] Filters - where: ${JSON.stringify(where)}, orderBy: ${JSON.stringify(orderBy)}, limit: ${limit}`
115
+ );
116
+
101
117
  // Execute the query
102
118
  const snapshot = await query.get();
103
119
 
120
+ // DEBUG: Log result count
121
+ console.log(
122
+ `[listEntities] Query returned ${snapshot.docs.length} documents`
123
+ );
124
+
125
+ // DEBUG: Log schema info
126
+ const schemaHasEntries = !!(documentSchema as any)?.entries;
127
+ console.log(`[listEntities] Schema has entries: ${schemaHasEntries}`);
128
+
104
129
  // Filter document fields based on visibility and user role
105
- const docs = snapshot.docs.map((doc: any) => ({
106
- id: doc.id,
107
- ...filterVisibleFields(doc.data() || {}, documentSchema, isAdmin),
108
- }));
130
+ const docs = snapshot.docs.map((doc: any) => {
131
+ const visibleData = filterVisibleFields(
132
+ doc.data() || {},
133
+ documentSchema,
134
+ userRole
135
+ );
136
+
137
+ // If listFields specified, filter to only those fields (plus id always)
138
+ if (listFields && listFields.length > 0) {
139
+ const filtered: Record<string, any> = { id: doc.id };
140
+ for (const field of listFields) {
141
+ if (field in visibleData) {
142
+ filtered[field] = visibleData[field];
143
+ }
144
+ }
145
+ return filtered;
146
+ }
147
+
148
+ // No listFields restriction, return all visible fields
149
+ return {
150
+ id: doc.id,
151
+ ...visibleData,
152
+ };
153
+ });
154
+
155
+ // DEBUG: Log filtered docs
156
+ console.log(
157
+ `[listEntities] Filtered docs count: ${docs.length}, first doc keys: ${docs[0] ? Object.keys(docs[0]).join(', ') : 'N/A'}`
158
+ );
109
159
 
110
160
  // Return the paginated result with metadata
111
161
  return {
@@ -121,13 +171,17 @@ function listEntitiesLogicFactory(
121
171
  * Generic function to list entities from any Firestore collection
122
172
  * @param collection - The Firestore collection name
123
173
  * @param documentSchema - The Valibot schema for document validation
174
+ * @param requiredRole - Minimum role required for this operation
124
175
  * @param customSchema - Optional custom request schema
176
+ * @param listFields - Optional array of field names to include (plus id). If not provided, all visible fields are returned.
125
177
  * @returns Firebase callable function
126
178
  */
127
179
  export const listEntities = (
128
180
  collection: string,
129
181
  documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
130
- customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
182
+ requiredRole: UserRole,
183
+ customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
184
+ listFields?: string[]
131
185
  ): CallableFunction<ListEntityRequest, Promise<any>> => {
132
186
  const requestSchema =
133
187
  customSchema ||
@@ -147,9 +201,10 @@ export const listEntities = (
147
201
  });
148
202
 
149
203
  return createBaseFunction(
150
- CRUD_CONFIG,
204
+ CRUD_READ_CONFIG,
151
205
  requestSchema,
152
206
  'list_entities',
153
- listEntitiesLogicFactory(collection, documentSchema)
207
+ listEntitiesLogicFactory(collection, documentSchema, listFields),
208
+ requiredRole
154
209
  );
155
210
  };
@@ -11,19 +11,16 @@
11
11
 
12
12
  import * as v from 'valibot';
13
13
 
14
- import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
15
14
  import { DEFAULT_STATUS_VALUE } from '@donotdev/core/server';
15
+ import type { UserRole } from '@donotdev/core/server';
16
+ import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
16
17
 
17
18
  import {
18
19
  prepareForFirestore,
19
20
  transformFirestoreData,
20
21
  } from '../../shared/index.js';
21
22
  import { updateMetadata } from '../../shared/index.js';
22
- import {
23
- assertAdmin,
24
- validateDocument,
25
- DoNotDevError,
26
- } from '../../shared/utils.js';
23
+ import { DoNotDevError, validateDocument } from '../../shared/utils.js';
27
24
  import { createBaseFunction } from '../baseFunction.js';
28
25
  import { CRUD_CONFIG } from '../config/constants.js';
29
26
 
@@ -48,12 +45,10 @@ function updateEntityLogicFactory(
48
45
  ) {
49
46
  return async function updateEntityLogic(
50
47
  data: UpdateEntityRequest,
51
- context: { uid: string; request: CallableRequest<any> }
48
+ context: { uid: string; userRole: UserRole; request: CallableRequest<any> }
52
49
  ) {
53
50
  const { id, payload, idempotencyKey } = data;
54
-
55
- // Ensure the user is an admin
56
- const uid = await assertAdmin(context.uid);
51
+ const { uid } = context;
57
52
 
58
53
  // Idempotency check if key provided
59
54
  if (idempotencyKey) {
@@ -129,12 +124,14 @@ function updateEntityLogicFactory(
129
124
  * Generic function to update entities in any Firestore collection
130
125
  * @param collection - The Firestore collection name
131
126
  * @param documentSchema - The Valibot schema for document validation
127
+ * @param requiredRole - Minimum role required for this operation
132
128
  * @param customSchema - Optional custom request schema
133
129
  * @returns Firebase callable function
134
130
  */
135
131
  export const updateEntity = (
136
132
  collection: string,
137
133
  documentSchema: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>,
134
+ requiredRole: UserRole,
138
135
  customSchema?: v.BaseSchema<unknown, any, v.BaseIssue<unknown>>
139
136
  ): CallableFunction<UpdateEntityRequest, Promise<any>> => {
140
137
  const requestSchema =
@@ -149,6 +146,7 @@ export const updateEntity = (
149
146
  CRUD_CONFIG,
150
147
  requestSchema,
151
148
  'update_entity',
152
- updateEntityLogicFactory(collection, documentSchema)
149
+ updateEntityLogicFactory(collection, documentSchema),
150
+ requiredRole
153
151
  );
154
152
  };
@@ -9,11 +9,12 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
+ import { logger } from 'firebase-functions/v2';
13
+
12
14
  import {
13
15
  getFirebaseAdminAuth,
14
16
  getFirebaseAdminFirestore,
15
17
  } from '@donotdev/firebase/server';
16
- import { logger } from 'firebase-functions/v2';
17
18
 
18
19
  import { handleError } from '../../shared/errorHandling.js';
19
20
  import { GitHubApiService } from '../../shared/index.js';
@@ -9,9 +9,10 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
13
12
  import { logger } from 'firebase-functions/v2';
14
13
 
14
+ import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
15
+
15
16
  import { handleError } from '../../shared/errorHandling.js';
16
17
  import { GitHubApiService } from '../../shared/index.js';
17
18
 
@@ -19,3 +19,10 @@ export * from './scheduled/index.js';
19
19
 
20
20
  // Base function for creating Firebase functions
21
21
  export { createBaseFunction } from './baseFunction.js';
22
+
23
+ // CRUD function registration utility
24
+ export {
25
+ registerCrudFunctions,
26
+ createCrudFunctions,
27
+ type CrudFunctions,
28
+ } from './registerCrudFunctions.js';
@@ -9,11 +9,11 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
13
12
  import { onCall, HttpsError } from 'firebase-functions/v2/https';
14
13
 
15
14
  import { OAUTH_PARTNERS } from '@donotdev/core/server';
16
15
  import type { OAuthPartnerId, OAuthPurpose } from '@donotdev/core/server';
16
+ import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
17
17
 
18
18
  import { assertAuthenticated } from '../../shared/utils.js';
19
19
 
@@ -9,10 +9,6 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import {
13
- getFirebaseAdminFirestore,
14
- FieldValue,
15
- } from '@donotdev/firebase/server';
16
12
  import { onCall, HttpsError } from 'firebase-functions/v2/https';
17
13
 
18
14
  import {
@@ -20,6 +16,10 @@ import {
20
16
  type ExchangeTokenRequest,
21
17
  type OAuthPartnerId,
22
18
  } from '@donotdev/core/server';
19
+ import {
20
+ getFirebaseAdminFirestore,
21
+ FieldValue,
22
+ } from '@donotdev/firebase/server';
23
23
 
24
24
  import { assertAuthenticated } from '../../shared/utils.js';
25
25
 
@@ -9,10 +9,10 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
13
12
  import { onCall, HttpsError } from 'firebase-functions/v2/https';
14
13
 
15
14
  import type { OAuthPurpose } from '@donotdev/core/server';
15
+ import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
16
16
 
17
17
  import { assertAuthenticated } from '../../shared/utils.js';
18
18
 
@@ -9,9 +9,8 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
13
12
  import { logger } from 'firebase-functions/v2';
14
- import { onCall, type CallableRequest } from 'firebase-functions/v2/https';
13
+ import { onCall } from 'firebase-functions/v2/https';
15
14
  import * as v from 'valibot';
16
15
 
17
16
  import {
@@ -22,6 +21,7 @@ import {
22
21
  type RevokeGitHubAccessRequest,
23
22
  type CheckGitHubAccessRequest,
24
23
  } from '@donotdev/core/server';
24
+ import { getFirebaseAdminAuth } from '@donotdev/firebase/server';
25
25
 
26
26
  import { handleError } from '../../shared/errorHandling.js';
27
27
  import { GitHubApiService } from '../../shared/index.js';
@@ -29,6 +29,7 @@ import { assertAuthenticated } from '../../shared/utils.js';
29
29
  import { AUTH_CONFIG } from '../config/constants.js';
30
30
  import { githubPersonalAccessToken } from '../config/secrets.js';
31
31
 
32
+ import type { CallableRequest } from 'firebase-functions/v2/https';
32
33
  import type { CallableFunction } from 'firebase-functions/v2/https';
33
34
 
34
35
  const OAUTH_CONFIG = {
@@ -9,16 +9,16 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import {
13
- getFirebaseAdminFirestore,
14
- FieldValue,
15
- } from '@donotdev/firebase/server';
16
12
  import { onCall, HttpsError } from 'firebase-functions/v2/https';
17
13
 
18
14
  import {
19
15
  OAUTH_PARTNERS,
20
16
  type OAuthRefreshRequest,
21
17
  } from '@donotdev/core/server';
18
+ import {
19
+ getFirebaseAdminFirestore,
20
+ FieldValue,
21
+ } from '@donotdev/firebase/server';
22
22
 
23
23
  import { assertAuthenticated } from '../../shared/utils.js';
24
24
 
@@ -0,0 +1,127 @@
1
+ // packages/functions/src/firebase/registerCrudFunctions.ts
2
+
3
+ /**
4
+ * @fileoverview Auto-register CRUD functions from entities
5
+ * @description Utility to automatically generate CRUD Cloud Functions for all entities
6
+ *
7
+ * @version 0.0.2
8
+ * @since 0.0.1
9
+ * @author AMBROISE PARK Consulting
10
+ */
11
+
12
+ import { createSchemas } from '@donotdev/core/server';
13
+ import type { Entity } from '@donotdev/core/server';
14
+
15
+ import {
16
+ createEntity,
17
+ getEntity,
18
+ updateEntity,
19
+ deleteEntity,
20
+ listEntities,
21
+ } from './crud/index.js';
22
+
23
+ import type { HttpsFunction } from 'firebase-functions/v2/https';
24
+
25
+ interface RegisterOptions {
26
+ prefix?: string;
27
+ }
28
+
29
+ /** CRUD functions object returned by createCrudFunctions */
30
+ export type CrudFunctions = Record<string, HttpsFunction>;
31
+
32
+ /**
33
+ * Create CRUD functions for all entities (ESM-friendly)
34
+ * Returns an object of functions that can be spread/exported
35
+ *
36
+ * @param entities - Object of { key: Entity } (from `import * as entities from 'entities'`)
37
+ * @param options - Optional configuration
38
+ * @returns Object of CRUD functions keyed by name
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // ESM pattern - use with build-time export generation
43
+ * import * as entities from 'entities';
44
+ * import { createCrudFunctions } from '@donotdev/functions/firebase';
45
+ *
46
+ * export const crud = createCrudFunctions(entities);
47
+ * // Build process generates: export const { create_cars, ... } = crud;
48
+ * ```
49
+ */
50
+ export function createCrudFunctions(
51
+ entities: Record<string, Entity | unknown>,
52
+ options: RegisterOptions = {}
53
+ ): CrudFunctions {
54
+ const { prefix = '' } = options;
55
+ const functions: CrudFunctions = {};
56
+
57
+ for (const [key, value] of Object.entries(entities)) {
58
+ if (!isEntity(value)) continue;
59
+
60
+ const entity = value as Entity;
61
+ const col = entity.collection;
62
+ const schemas = createSchemas(entity);
63
+ const access = entity.access;
64
+
65
+ functions[`${prefix}create_${col}`] = createEntity(
66
+ col,
67
+ schemas.create,
68
+ access.create
69
+ );
70
+ functions[`${prefix}get_${col}`] = getEntity(col, schemas.get, access.read);
71
+ // Use schemas.get for visibility filtering, entity.listFields for field selection
72
+ functions[`${prefix}list_${col}`] = listEntities(
73
+ col,
74
+ schemas.get,
75
+ access.read,
76
+ undefined,
77
+ entity.listFields
78
+ );
79
+ // Always create listCard - uses same schemas.get, field selection via listCardFields ?? listFields ?? undefined
80
+ functions[`${prefix}listCard_${col}`] = listEntities(
81
+ col,
82
+ schemas.get,
83
+ access.read,
84
+ undefined,
85
+ entity.listCardFields ?? entity.listFields ?? undefined
86
+ );
87
+ functions[`${prefix}update_${col}`] = updateEntity(
88
+ col,
89
+ schemas.update,
90
+ access.update
91
+ );
92
+ functions[`${prefix}delete_${col}`] = deleteEntity(col, access.delete);
93
+ }
94
+
95
+ return functions;
96
+ }
97
+
98
+ /**
99
+ * @deprecated Use createCrudFunctions() for ESM. This mutates target which doesn't work in ESM.
100
+ * Auto-register CRUD functions for all entities (CJS pattern)
101
+ */
102
+ export function registerCrudFunctions(
103
+ entities: Record<string, Entity | unknown>,
104
+ target?: Record<string, any>,
105
+ options: RegisterOptions = {}
106
+ ): CrudFunctions {
107
+ const functions = createCrudFunctions(entities, options);
108
+
109
+ // If target provided, mutate it (CJS compat)
110
+ if (target) {
111
+ Object.assign(target, functions);
112
+ }
113
+
114
+ return functions;
115
+ }
116
+
117
+ /**
118
+ * Type guard to check if a value is an Entity
119
+ */
120
+ function isEntity(value: unknown): value is Entity {
121
+ return (
122
+ typeof value === 'object' &&
123
+ value !== null &&
124
+ 'collection' in value &&
125
+ 'fields' in value
126
+ );
127
+ }
@@ -9,12 +9,13 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
+ import { logger } from 'firebase-functions/v2';
13
+ import { onSchedule } from 'firebase-functions/v2/scheduler';
14
+
12
15
  import {
13
16
  getFirebaseAdminAuth,
14
17
  getFirebaseAdminFirestore,
15
18
  } from '@donotdev/firebase/server';
16
- import { logger } from 'firebase-functions/v2';
17
- import { onSchedule } from 'firebase-functions/v2/scheduler';
18
19
 
19
20
  import { handleError } from '../../shared/errorHandling.js';
20
21
 
@@ -11,12 +11,12 @@
11
11
 
12
12
  import Stripe from 'stripe';
13
13
 
14
- import type { StripeBackConfig } from '@donotdev/core/server';
15
14
  import {
16
15
  validateStripeBackConfig,
17
16
  SUBSCRIPTION_STATUS,
18
17
  SUBSCRIPTION_TIERS,
19
18
  } from '@donotdev/core/server';
19
+ import type { StripeBackConfig } from '@donotdev/core/server';
20
20
 
21
21
  import { handleError } from '../errorHandling.js';
22
22
  import { logger } from '../logger.js';
@@ -9,8 +9,8 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import * as v from 'valibot';
13
12
  import { HttpsError } from 'firebase-functions/v2/https';
13
+ import * as v from 'valibot';
14
14
 
15
15
  import type { ErrorCode } from '@donotdev/core/server';
16
16
  import { EntityHookError } from '@donotdev/core/server';
@@ -9,15 +9,14 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import {
13
- getFirebaseAdminAuth,
14
- getFirebaseAdminFirestore,
15
- } from '@donotdev/firebase/server';
16
-
17
12
  import type {
18
13
  SubscriptionStatus,
19
14
  SubscriptionClaims,
20
15
  } from '@donotdev/core/server';
16
+ import {
17
+ getFirebaseAdminAuth,
18
+ getFirebaseAdminFirestore,
19
+ } from '@donotdev/firebase/server';
21
20
 
22
21
  /**
23
22
  * Maps Stripe price ID to subscription tier
@@ -9,11 +9,13 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { onCall, type CallableRequest } from 'firebase-functions/v2/https';
12
+ import { onCall } from 'firebase-functions/v2/https';
13
13
  import * as v from 'valibot';
14
14
 
15
15
  import { withSchemaValidation } from './schemaValidation.js';
16
16
 
17
+ import type { CallableRequest } from 'firebase-functions/v2/https';
18
+
17
19
  /**
18
20
  * Creates a Firebase onCall function with schema validation
19
21
  *
@@ -9,9 +9,10 @@
9
9
  * @author AMBROISE PARK Consulting
10
10
  */
11
11
 
12
- import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
13
12
  import { logger } from 'firebase-functions/v2';
14
13
 
14
+ import { getFirebaseAdminFirestore } from '@donotdev/firebase/server';
15
+
15
16
  export interface IdempotencyResult<T = any> {
16
17
  result: T;
17
18
  processedAt: string;