@dereekb/firebase-server 13.2.0 → 13.2.1
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/index.cjs.js +340 -24
- package/index.cjs.js.map +1 -1
- package/index.esm.js +332 -25
- package/index.esm.js.map +1 -1
- package/mailgun/package.json +8 -8
- package/model/package.json +8 -8
- package/package.json +9 -9
- package/src/lib/auth/auth.service.d.ts +370 -85
- package/src/lib/nest/model/api.details.d.ts +249 -0
- package/src/lib/nest/model/call.model.function.d.ts +3 -2
- package/src/lib/nest/model/index.d.ts +1 -0
- package/src/lib/nest/model/specifier.function.d.ts +2 -1
- package/test/package.json +8 -8
- package/zoho/package.json +8 -8
package/index.cjs.js
CHANGED
|
@@ -272,7 +272,34 @@ class FirebaseServerAuthNewUserSendSetupDetailsSendOnceError extends makeError.B
|
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Generates a random 6-digit number for use as a temporary password or reset token.
|
|
277
|
+
*
|
|
278
|
+
* Used internally by {@link AbstractFirebaseServerAuthUserContext.beginResetPassword} and
|
|
279
|
+
* {@link AbstractFirebaseServerNewUserService.generateRandomSetupPassword} for one-time codes.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```typescript
|
|
283
|
+
* const pin = DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR(); // e.g. 482910
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
const DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR = util.randomNumberFactory({ min: 100000, max: 1000000, round: 'floor' });
|
|
287
|
+
/**
|
|
288
|
+
* Base implementation of {@link FirebaseServerAuthUserContext} that manages a single user's
|
|
289
|
+
* auth state (record, claims, roles, password) through the Firebase Admin Auth API.
|
|
290
|
+
*
|
|
291
|
+
* Caches the user record on first load and resets the cache automatically when claims are modified
|
|
292
|
+
* via {@link setClaims}. Subclass this to bind it to a specific {@link FirebaseServerAuthService} type.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* export class MyAuthUserContext extends AbstractFirebaseServerAuthUserContext<MyAuthService> {}
|
|
297
|
+
*
|
|
298
|
+
* const ctx = new MyAuthUserContext(authService, 'some-uid');
|
|
299
|
+
* const roles = await ctx.loadRoles();
|
|
300
|
+
* await ctx.addRoles(AUTH_ADMIN_ROLE);
|
|
301
|
+
* ```
|
|
302
|
+
*/
|
|
276
303
|
class AbstractFirebaseServerAuthUserContext {
|
|
277
304
|
_service;
|
|
278
305
|
_uid;
|
|
@@ -296,6 +323,9 @@ class AbstractFirebaseServerAuthUserContext {
|
|
|
296
323
|
loadDetails() {
|
|
297
324
|
return this.loadRecord().then((record) => this.service.authDetailsForRecord(record));
|
|
298
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Generates a random numeric string for use as a temporary reset password.
|
|
328
|
+
*/
|
|
299
329
|
_generateResetPasswordKey() {
|
|
300
330
|
return String(DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR());
|
|
301
331
|
}
|
|
@@ -320,9 +350,6 @@ class AbstractFirebaseServerAuthUserContext {
|
|
|
320
350
|
return undefined;
|
|
321
351
|
}
|
|
322
352
|
}
|
|
323
|
-
/**
|
|
324
|
-
* Sets the user's password.
|
|
325
|
-
*/
|
|
326
353
|
async setPassword(password) {
|
|
327
354
|
const record = await this.updateUser({ password });
|
|
328
355
|
// clear password reset claims
|
|
@@ -355,15 +382,19 @@ class AbstractFirebaseServerAuthUserContext {
|
|
|
355
382
|
return this.updateClaims(claims);
|
|
356
383
|
}
|
|
357
384
|
/**
|
|
358
|
-
*
|
|
385
|
+
* Replaces all role-based claims with those derived from the given roles.
|
|
359
386
|
*
|
|
360
|
-
* All
|
|
387
|
+
* All existing claims are cleared first. Use `claimsToRetain` to preserve non-role claims
|
|
388
|
+
* (e.g., setup or application-specific claims) through the replacement.
|
|
361
389
|
*
|
|
362
|
-
*
|
|
390
|
+
* @param roles - The complete set of roles to assign.
|
|
391
|
+
* @param claimsToRetain - Additional claims to merge in alongside the role-derived claims.
|
|
363
392
|
*
|
|
364
|
-
* @
|
|
365
|
-
*
|
|
366
|
-
*
|
|
393
|
+
* @example
|
|
394
|
+
* ```typescript
|
|
395
|
+
* // Set roles while preserving a custom claim
|
|
396
|
+
* await userCtx.setRoles([AUTH_ADMIN_ROLE], { customFlag: 1 });
|
|
397
|
+
* ```
|
|
367
398
|
*/
|
|
368
399
|
async setRoles(roles, claimsToRetain) {
|
|
369
400
|
const claims = {
|
|
@@ -372,8 +403,11 @@ class AbstractFirebaseServerAuthUserContext {
|
|
|
372
403
|
};
|
|
373
404
|
return this.setClaims(claims);
|
|
374
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* Converts roles to their corresponding claim keys, filtering out null/undefined entries
|
|
408
|
+
* that represent unrelated claims in the service's {@link FirebaseServerAuthService.claimsForRoles} output.
|
|
409
|
+
*/
|
|
375
410
|
_claimsForRolesChange(roles) {
|
|
376
|
-
// filter null/undefined since the claims will contain null values for claims that are not related.
|
|
377
411
|
return util.filterNullAndUndefinedValues(this.service.claimsForRoles(util.asSet(roles)));
|
|
378
412
|
}
|
|
379
413
|
loadClaims() {
|
|
@@ -403,6 +437,15 @@ class AbstractFirebaseServerAuthUserContext {
|
|
|
403
437
|
});
|
|
404
438
|
}
|
|
405
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Base implementation of {@link FirebaseServerAuthContext} with cached getters for roles, admin status,
|
|
442
|
+
* and ToS status to avoid redundant computation within a single request.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* ```typescript
|
|
446
|
+
* export class MyAuthContext extends AbstractFirebaseServerAuthContext<MyAuthContext, MyUserContext, MyAuthService> {}
|
|
447
|
+
* ```
|
|
448
|
+
*/
|
|
406
449
|
class AbstractFirebaseServerAuthContext {
|
|
407
450
|
_service;
|
|
408
451
|
_context;
|
|
@@ -444,15 +487,52 @@ class AbstractFirebaseServerAuthContext {
|
|
|
444
487
|
}
|
|
445
488
|
}
|
|
446
489
|
/**
|
|
447
|
-
* 1 hour
|
|
490
|
+
* Default throttle duration (1 hour) between setup content sends to prevent spam.
|
|
491
|
+
*
|
|
492
|
+
* Used by {@link AbstractFirebaseServerNewUserService.sendSetupContent} to rate-limit delivery.
|
|
448
493
|
*/
|
|
449
494
|
const DEFAULT_SETUP_COM_THROTTLE_TIME = date.hoursToMs(1);
|
|
495
|
+
/**
|
|
496
|
+
* Resolves a {@link UserContextOrUid} to a concrete user context instance.
|
|
497
|
+
*
|
|
498
|
+
* If a string UID is provided, creates a new user context via the auth service.
|
|
499
|
+
* If an existing context is provided, returns it as-is.
|
|
500
|
+
*
|
|
501
|
+
* @param authService - The auth service to create a context from if needed.
|
|
502
|
+
* @param userContextOrUid - A user context or UID string.
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```typescript
|
|
506
|
+
* const ctx = userContextFromUid(authService, 'some-uid');
|
|
507
|
+
* const sameCtx = userContextFromUid(authService, ctx); // returns ctx unchanged
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
450
510
|
function userContextFromUid(authService, userContextOrUid) {
|
|
451
511
|
const userContext = typeof userContextOrUid === 'string' ? authService.userContext(userContextOrUid) : userContextOrUid;
|
|
452
512
|
return userContext;
|
|
453
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Base implementation of {@link FirebaseServerNewUserService} that handles user creation,
|
|
516
|
+
* setup claims management, throttled setup content delivery, and setup completion.
|
|
517
|
+
*
|
|
518
|
+
* Subclasses must implement {@link sendSetupContentToUser} to define how setup content
|
|
519
|
+
* (e.g., invitation email, SMS) is delivered to the user.
|
|
520
|
+
*
|
|
521
|
+
* @example
|
|
522
|
+
* ```typescript
|
|
523
|
+
* export class MyNewUserService extends AbstractFirebaseServerNewUserService<MyUserContext> {
|
|
524
|
+
* protected async sendSetupContentToUser(details: FirebaseServerAuthNewUserSetupDetails<MyUserContext>): Promise<void> {
|
|
525
|
+
* await this.emailService.sendInvite(details.userContext.uid, details.claims.setupPassword);
|
|
526
|
+
* }
|
|
527
|
+
* }
|
|
528
|
+
* ```
|
|
529
|
+
*/
|
|
454
530
|
class AbstractFirebaseServerNewUserService {
|
|
455
531
|
_authService;
|
|
532
|
+
/**
|
|
533
|
+
* Minimum time between setup content sends. Defaults to {@link DEFAULT_SETUP_COM_THROTTLE_TIME} (1 hour).
|
|
534
|
+
* Override in subclasses to customize the throttle window.
|
|
535
|
+
*/
|
|
456
536
|
setupThrottleTime = DEFAULT_SETUP_COM_THROTTLE_TIME;
|
|
457
537
|
constructor(authService) {
|
|
458
538
|
this._authService = authService;
|
|
@@ -564,19 +644,15 @@ class AbstractFirebaseServerNewUserService {
|
|
|
564
644
|
}
|
|
565
645
|
return details;
|
|
566
646
|
}
|
|
647
|
+
/**
|
|
648
|
+
* Records the current timestamp as the last setup content communication date in the user's claims.
|
|
649
|
+
*/
|
|
567
650
|
async updateSetupContentSentTime(details) {
|
|
568
651
|
const setupCommunicationAt = date.toISODateString(new Date());
|
|
569
652
|
await details.userContext.updateClaims({
|
|
570
653
|
setupCommunicationAt
|
|
571
654
|
});
|
|
572
655
|
}
|
|
573
|
-
/**
|
|
574
|
-
* Update a user's claims to clear any setup-related content.
|
|
575
|
-
*
|
|
576
|
-
* Returns true if a user was updated.
|
|
577
|
-
*
|
|
578
|
-
* @param uid
|
|
579
|
-
*/
|
|
580
656
|
async markUserSetupAsComplete(uid) {
|
|
581
657
|
const userContext = this.authService.userContext(uid);
|
|
582
658
|
const userExists = await userContext.exists();
|
|
@@ -585,6 +661,13 @@ class AbstractFirebaseServerNewUserService {
|
|
|
585
661
|
}
|
|
586
662
|
return userExists;
|
|
587
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Creates a new Firebase Auth user from the initialization input.
|
|
666
|
+
*
|
|
667
|
+
* Generates a random setup password if none is provided. Override to customize user creation behavior.
|
|
668
|
+
*
|
|
669
|
+
* @throws Throws if the Firebase Admin SDK rejects the user creation.
|
|
670
|
+
*/
|
|
588
671
|
async createNewUser(input) {
|
|
589
672
|
const { uid, displayName, email, phone: phoneNumber, setupPassword: inputPassword } = input;
|
|
590
673
|
const password = inputPassword ?? this.generateRandomSetupPassword();
|
|
@@ -603,6 +686,9 @@ class AbstractFirebaseServerNewUserService {
|
|
|
603
686
|
generateRandomSetupPassword() {
|
|
604
687
|
return `${DEFAULT_FIREBASE_PASSWORD_NUMBER_GENERATOR()}`;
|
|
605
688
|
}
|
|
689
|
+
/**
|
|
690
|
+
* Clears setup-related claims (setup password and last communication date) from the user.
|
|
691
|
+
*/
|
|
606
692
|
async updateClaimsToClearUser(userContext) {
|
|
607
693
|
await userContext.updateClaims({
|
|
608
694
|
[firebase.FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY]: null,
|
|
@@ -610,18 +696,65 @@ class AbstractFirebaseServerNewUserService {
|
|
|
610
696
|
});
|
|
611
697
|
}
|
|
612
698
|
}
|
|
699
|
+
/**
|
|
700
|
+
* No-op implementation of {@link AbstractFirebaseServerNewUserService} that skips sending setup content.
|
|
701
|
+
*
|
|
702
|
+
* Used as the default {@link FirebaseServerNewUserService} when no custom delivery mechanism is configured.
|
|
703
|
+
*/
|
|
613
704
|
class NoSetupContentFirebaseServerNewUserService extends AbstractFirebaseServerNewUserService {
|
|
614
|
-
async sendSetupContentToUser(
|
|
705
|
+
async sendSetupContentToUser(_details) {
|
|
615
706
|
// send nothing.
|
|
616
707
|
}
|
|
617
708
|
}
|
|
618
709
|
/**
|
|
619
|
-
*
|
|
710
|
+
* Abstract contract for a Firebase Server authentication service.
|
|
711
|
+
*
|
|
712
|
+
* Provides the core API for creating auth contexts from callable requests, managing user contexts,
|
|
713
|
+
* checking admin/ToS status, converting between roles and claims, and creating new users.
|
|
714
|
+
*
|
|
715
|
+
* Implement this by extending {@link AbstractFirebaseServerAuthService}, which provides default
|
|
716
|
+
* implementations for most methods and only requires `readRoles`, `claimsForRoles`,
|
|
717
|
+
* `userContext`, and `_context` to be defined.
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```typescript
|
|
721
|
+
* class MyAuthService extends AbstractFirebaseServerAuthService<MyUserContext, MyAuthContext> {
|
|
722
|
+
* readRoles(claims: AuthClaims): AuthRoleSet { ... }
|
|
723
|
+
* claimsForRoles(roles: AuthRoleSet): AuthClaimsUpdate { ... }
|
|
724
|
+
* userContext(uid: string): MyUserContext { ... }
|
|
725
|
+
* protected _context(ctx: CallableContextWithAuthData): MyAuthContext { ... }
|
|
726
|
+
* }
|
|
727
|
+
* ```
|
|
620
728
|
*/
|
|
621
729
|
class FirebaseServerAuthService {
|
|
622
730
|
}
|
|
623
731
|
/**
|
|
624
|
-
*
|
|
732
|
+
* Base implementation of {@link FirebaseServerAuthService} providing standard admin/ToS checks,
|
|
733
|
+
* auth context creation with assertion, and a default no-op new user service.
|
|
734
|
+
*
|
|
735
|
+
* Subclasses must implement:
|
|
736
|
+
* - {@link _context} - to create the concrete auth context type.
|
|
737
|
+
* - {@link userContext} - to create the concrete user context type.
|
|
738
|
+
* - {@link readRoles} - to define the claims-to-roles mapping.
|
|
739
|
+
* - {@link claimsForRoles} - to define the roles-to-claims mapping.
|
|
740
|
+
*
|
|
741
|
+
* @example
|
|
742
|
+
* ```typescript
|
|
743
|
+
* export class MyAuthService extends AbstractFirebaseServerAuthService<MyUserContext, MyAuthContext> {
|
|
744
|
+
* protected _context(context: CallableContextWithAuthData): MyAuthContext {
|
|
745
|
+
* return new MyAuthContext(this, context);
|
|
746
|
+
* }
|
|
747
|
+
* userContext(uid: string): MyUserContext {
|
|
748
|
+
* return new MyUserContext(this, uid);
|
|
749
|
+
* }
|
|
750
|
+
* readRoles(claims: AuthClaims): AuthRoleSet {
|
|
751
|
+
* return MY_CLAIMS_SERVICE.toRoles(claims);
|
|
752
|
+
* }
|
|
753
|
+
* claimsForRoles(roles: AuthRoleSet): AuthClaimsUpdate {
|
|
754
|
+
* return MY_CLAIMS_SERVICE.toClaims(roles);
|
|
755
|
+
* }
|
|
756
|
+
* }
|
|
757
|
+
* ```
|
|
625
758
|
*/
|
|
626
759
|
class AbstractFirebaseServerAuthService {
|
|
627
760
|
_auth;
|
|
@@ -2027,6 +2160,163 @@ exports.ConfigureFirebaseWebhookMiddlewareModule = __decorate([
|
|
|
2027
2160
|
common.Module({})
|
|
2028
2161
|
], exports.ConfigureFirebaseWebhookMiddlewareModule);
|
|
2029
2162
|
|
|
2163
|
+
// MARK: Type Guards
|
|
2164
|
+
/**
|
|
2165
|
+
* Whether the details are specifier-level (has specifiers map).
|
|
2166
|
+
*/
|
|
2167
|
+
function isOnCallSpecifierApiDetails(details) {
|
|
2168
|
+
return details != null && 'specifiers' in details;
|
|
2169
|
+
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Whether the details are CRUD-model-level (has modelTypes map).
|
|
2172
|
+
*/
|
|
2173
|
+
function isOnCallCrudModelApiDetails(details) {
|
|
2174
|
+
return details != null && 'modelTypes' in details;
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* Whether the details are handler-level (leaf node — no specifiers or modelTypes).
|
|
2178
|
+
*/
|
|
2179
|
+
function isOnCallHandlerApiDetails(details) {
|
|
2180
|
+
return details != null && !('specifiers' in details) && !('modelTypes' in details);
|
|
2181
|
+
}
|
|
2182
|
+
/**
|
|
2183
|
+
* Attaches API details metadata to a handler function.
|
|
2184
|
+
*
|
|
2185
|
+
* The handler function is provided in the config object alongside its metadata.
|
|
2186
|
+
* The function is returned unchanged but with the _apiDetails property set.
|
|
2187
|
+
* Compatible with all handler types (create, read, update, delete, specifier).
|
|
2188
|
+
*
|
|
2189
|
+
* When `optionalAuth: true` is set, also marks the function as not requiring auth
|
|
2190
|
+
* (same effect as optionalAuthContext). This avoids the composition issue where
|
|
2191
|
+
* optionalAuthContext(withApiDetails(...)) would lose the _apiDetails.
|
|
2192
|
+
*
|
|
2193
|
+
* @example
|
|
2194
|
+
* ```typescript
|
|
2195
|
+
* // Handler with api details (auth required by default)
|
|
2196
|
+
* export const createGuestbook: DemoCreateModelFunction<CreateGuestbookParams> = withApiDetails({
|
|
2197
|
+
* inputType: createGuestbookParamsType,
|
|
2198
|
+
* fn: async (request) => {
|
|
2199
|
+
* const { nest, auth, data } = request;
|
|
2200
|
+
* const result = await nest.guestbookActions.createGuestbook(data);
|
|
2201
|
+
* return onCallCreateModelResultWithDocs(await result());
|
|
2202
|
+
* }
|
|
2203
|
+
* });
|
|
2204
|
+
*
|
|
2205
|
+
* // Handler with optional auth
|
|
2206
|
+
* export const profileCreate: DemoCreateModelFunction<{}> = withApiDetails({
|
|
2207
|
+
* optionalAuth: true,
|
|
2208
|
+
* fn: async (request) => { ... }
|
|
2209
|
+
* });
|
|
2210
|
+
* ```
|
|
2211
|
+
*/
|
|
2212
|
+
function withApiDetails(config) {
|
|
2213
|
+
const { optionalAuth, fn, ...apiDetails } = config;
|
|
2214
|
+
fn._apiDetails = apiDetails;
|
|
2215
|
+
if (optionalAuth) {
|
|
2216
|
+
fn._requireAuth = false;
|
|
2217
|
+
}
|
|
2218
|
+
return fn;
|
|
2219
|
+
}
|
|
2220
|
+
// MARK: Aggregation Utilities
|
|
2221
|
+
/**
|
|
2222
|
+
* Reads _apiDetails from a function if present.
|
|
2223
|
+
*/
|
|
2224
|
+
function readApiDetails(fn) {
|
|
2225
|
+
return fn?._apiDetails;
|
|
2226
|
+
}
|
|
2227
|
+
/**
|
|
2228
|
+
* Aggregates _apiDetails from a specifier handler config object.
|
|
2229
|
+
*
|
|
2230
|
+
* Returns OnCallSpecifierApiDetails if any handlers have _apiDetails, otherwise undefined.
|
|
2231
|
+
*/
|
|
2232
|
+
function aggregateSpecifierApiDetails(config) {
|
|
2233
|
+
const specifiers = {};
|
|
2234
|
+
let hasAny = false;
|
|
2235
|
+
for (const [key, handler] of Object.entries(config)) {
|
|
2236
|
+
const details = readApiDetails(handler);
|
|
2237
|
+
if (details != null) {
|
|
2238
|
+
// At the specifier level, details should be handler-level (OnCallModelFunctionApiDetails)
|
|
2239
|
+
specifiers[key] = details;
|
|
2240
|
+
hasAny = true;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
return hasAny ? { specifiers } : undefined;
|
|
2244
|
+
}
|
|
2245
|
+
/**
|
|
2246
|
+
* Aggregates _apiDetails from a model type map (used by onCallCreateModel, etc.).
|
|
2247
|
+
*
|
|
2248
|
+
* Returns OnCallCrudModelApiDetails if any handlers have _apiDetails, otherwise undefined.
|
|
2249
|
+
*/
|
|
2250
|
+
function aggregateCrudModelApiDetails(map) {
|
|
2251
|
+
const modelTypes = {};
|
|
2252
|
+
let hasAny = false;
|
|
2253
|
+
for (const [key, handler] of Object.entries(map)) {
|
|
2254
|
+
const details = readApiDetails(handler);
|
|
2255
|
+
if (details != null) {
|
|
2256
|
+
modelTypes[key] = details;
|
|
2257
|
+
hasAny = true;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return hasAny ? { modelTypes } : undefined;
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Aggregates _apiDetails from the top-level call model map.
|
|
2264
|
+
*
|
|
2265
|
+
* Returns OnCallModelApiDetails if any CRUD handlers have _apiDetails, otherwise undefined.
|
|
2266
|
+
*/
|
|
2267
|
+
function aggregateModelApiDetails(map) {
|
|
2268
|
+
const result = {};
|
|
2269
|
+
let hasAny = false;
|
|
2270
|
+
for (const [call, handler] of Object.entries(map)) {
|
|
2271
|
+
const details = readApiDetails(handler);
|
|
2272
|
+
if (details != null) {
|
|
2273
|
+
result[call] = details;
|
|
2274
|
+
hasAny = true;
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
return hasAny ? result : undefined;
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Extracts and pivots API details from a call model function into a model-first view.
|
|
2281
|
+
*
|
|
2282
|
+
* The internal aggregation tree is organized as CRUD → modelType. This function
|
|
2283
|
+
* pivots it to modelType → CRUD, which is the natural shape for MCP tool generation
|
|
2284
|
+
* and schema introspection.
|
|
2285
|
+
*
|
|
2286
|
+
* @param callModelFn The function returned by onCallModel(), or any object with _apiDetails.
|
|
2287
|
+
* @returns Model-first API details, or undefined if no _apiDetails are present.
|
|
2288
|
+
*
|
|
2289
|
+
* @example
|
|
2290
|
+
* ```typescript
|
|
2291
|
+
* const details = getModelApiDetails(demoCallModel);
|
|
2292
|
+
* // details.models['guestbook'].calls.create => { inputType: createGuestbookParamsType }
|
|
2293
|
+
* // details.models['profile'].calls.update => { specifiers: { _: {...}, username: {...} } }
|
|
2294
|
+
* ```
|
|
2295
|
+
*/
|
|
2296
|
+
function getModelApiDetails(callModelFn) {
|
|
2297
|
+
const topDetails = readApiDetails(callModelFn);
|
|
2298
|
+
if (topDetails == null) {
|
|
2299
|
+
return undefined;
|
|
2300
|
+
}
|
|
2301
|
+
const models = {};
|
|
2302
|
+
// Pivot: iterate CRUD types, then model types within each
|
|
2303
|
+
for (const [callType, crudDetails] of Object.entries(topDetails)) {
|
|
2304
|
+
if (crudDetails == null) {
|
|
2305
|
+
continue;
|
|
2306
|
+
}
|
|
2307
|
+
for (const [modelType, modelDetails] of Object.entries(crudDetails.modelTypes)) {
|
|
2308
|
+
if (modelDetails == null) {
|
|
2309
|
+
continue;
|
|
2310
|
+
}
|
|
2311
|
+
if (!models[modelType]) {
|
|
2312
|
+
models[modelType] = { calls: {} };
|
|
2313
|
+
}
|
|
2314
|
+
models[modelType].calls[callType] = modelDetails;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
return Object.keys(models).length > 0 ? { models } : undefined;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2030
2320
|
const nestFirebaseDoesNotExistError = (firebaseContextGrantedModelRoles) => {
|
|
2031
2321
|
return modelNotAvailableError({
|
|
2032
2322
|
data: {
|
|
@@ -2059,6 +2349,11 @@ function onCallSpecifierHandler(config) {
|
|
|
2059
2349
|
}
|
|
2060
2350
|
};
|
|
2061
2351
|
fn._requireAuth = false;
|
|
2352
|
+
// Aggregate _apiDetails from handler functions in the config
|
|
2353
|
+
const specifierApiDetails = aggregateSpecifierApiDetails(config);
|
|
2354
|
+
if (specifierApiDetails != null) {
|
|
2355
|
+
fn._apiDetails = specifierApiDetails;
|
|
2356
|
+
}
|
|
2062
2357
|
return fn;
|
|
2063
2358
|
}
|
|
2064
2359
|
function unknownModelCrudFunctionSpecifierError(specifier) {
|
|
@@ -2080,7 +2375,7 @@ function unknownModelCrudFunctionSpecifierError(specifier) {
|
|
|
2080
2375
|
*/
|
|
2081
2376
|
function onCallModel(map, config = {}) {
|
|
2082
2377
|
const { preAssert = () => undefined } = config;
|
|
2083
|
-
|
|
2378
|
+
const fn = (request) => {
|
|
2084
2379
|
const call = request.data?.call;
|
|
2085
2380
|
if (call) {
|
|
2086
2381
|
const callFn = map[call];
|
|
@@ -2097,6 +2392,12 @@ function onCallModel(map, config = {}) {
|
|
|
2097
2392
|
throw onCallModelMissingCallTypeError();
|
|
2098
2393
|
}
|
|
2099
2394
|
};
|
|
2395
|
+
// Aggregate _apiDetails from CRUD handlers in the map
|
|
2396
|
+
const modelApiDetails = aggregateModelApiDetails(map);
|
|
2397
|
+
if (modelApiDetails != null) {
|
|
2398
|
+
fn._apiDetails = modelApiDetails;
|
|
2399
|
+
}
|
|
2400
|
+
return fn;
|
|
2100
2401
|
}
|
|
2101
2402
|
function onCallModelMissingCallTypeError() {
|
|
2102
2403
|
return badRequestError(util.serverError({
|
|
@@ -2117,7 +2418,7 @@ function onCallModelUnknownCallTypeError(call) {
|
|
|
2117
2418
|
}
|
|
2118
2419
|
function _onCallWithCallTypeFunction(map, config) {
|
|
2119
2420
|
const { callType, crudType, preAssert = () => undefined, throwOnUnknownModelType } = config;
|
|
2120
|
-
|
|
2421
|
+
const fn = (request) => {
|
|
2121
2422
|
const modelType = request.data?.modelType;
|
|
2122
2423
|
const crudFn = map[modelType];
|
|
2123
2424
|
if (crudFn) {
|
|
@@ -2134,6 +2435,12 @@ function _onCallWithCallTypeFunction(map, config) {
|
|
|
2134
2435
|
throw throwOnUnknownModelType(modelType);
|
|
2135
2436
|
}
|
|
2136
2437
|
};
|
|
2438
|
+
// Aggregate _apiDetails from model type handlers in the map
|
|
2439
|
+
const crudModelApiDetails = aggregateCrudModelApiDetails(map);
|
|
2440
|
+
if (crudModelApiDetails != null) {
|
|
2441
|
+
fn._apiDetails = crudModelApiDetails;
|
|
2442
|
+
}
|
|
2443
|
+
return fn;
|
|
2137
2444
|
}
|
|
2138
2445
|
|
|
2139
2446
|
function onCallCreateModel(map, config = {}) {
|
|
@@ -2862,6 +3169,9 @@ exports.UNAVAILABLE_OR_DEACTIVATED_FUNCTION_ERROR_CODE = UNAVAILABLE_OR_DEACTIVA
|
|
|
2862
3169
|
exports.UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_NAME_CODE = UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_NAME_CODE;
|
|
2863
3170
|
exports.UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_TYPE_CODE = UNKNOWN_SCHEDULED_FUNCTION_DEVELOPMENT_FUNCTION_TYPE_CODE;
|
|
2864
3171
|
exports._onCallWithCallTypeFunction = _onCallWithCallTypeFunction;
|
|
3172
|
+
exports.aggregateCrudModelApiDetails = aggregateCrudModelApiDetails;
|
|
3173
|
+
exports.aggregateModelApiDetails = aggregateModelApiDetails;
|
|
3174
|
+
exports.aggregateSpecifierApiDetails = aggregateSpecifierApiDetails;
|
|
2865
3175
|
exports.alreadyExistsError = alreadyExistsError;
|
|
2866
3176
|
exports.appFirestoreModuleMetadata = appFirestoreModuleMetadata;
|
|
2867
3177
|
exports.assertContextHasAuth = assertContextHasAuth;
|
|
@@ -2905,6 +3215,7 @@ exports.firestoreEncryptedField = firestoreEncryptedField;
|
|
|
2905
3215
|
exports.firestoreServerIncrementUpdateToUpdateData = firestoreServerIncrementUpdateToUpdateData;
|
|
2906
3216
|
exports.forbiddenError = forbiddenError;
|
|
2907
3217
|
exports.getAuthUserOrUndefined = getAuthUserOrUndefined;
|
|
3218
|
+
exports.getModelApiDetails = getModelApiDetails;
|
|
2908
3219
|
exports.googleCloudFileMetadataToStorageMetadata = googleCloudFileMetadataToStorageMetadata;
|
|
2909
3220
|
exports.googleCloudFirebaseStorageContextFactory = googleCloudFirebaseStorageContextFactory;
|
|
2910
3221
|
exports.googleCloudFirebaseStorageDrivers = googleCloudFirebaseStorageDrivers;
|
|
@@ -2933,6 +3244,9 @@ exports.isAdminOrTargetUserInRequestData = isAdminOrTargetUserInRequestData;
|
|
|
2933
3244
|
exports.isContextWithAuthData = isContextWithAuthData;
|
|
2934
3245
|
exports.isFirebaseError = isFirebaseError;
|
|
2935
3246
|
exports.isFirebaseHttpsError = isFirebaseHttpsError;
|
|
3247
|
+
exports.isOnCallCrudModelApiDetails = isOnCallCrudModelApiDetails;
|
|
3248
|
+
exports.isOnCallHandlerApiDetails = isOnCallHandlerApiDetails;
|
|
3249
|
+
exports.isOnCallSpecifierApiDetails = isOnCallSpecifierApiDetails;
|
|
2936
3250
|
exports.makeBlockingFunctionWithHandler = makeBlockingFunctionWithHandler;
|
|
2937
3251
|
exports.makeOnScheduleHandlerWithNestApplicationRequest = makeOnScheduleHandlerWithNestApplicationRequest;
|
|
2938
3252
|
exports.makeScheduledFunctionDevelopmentFunction = makeScheduledFunctionDevelopmentFunction;
|
|
@@ -2965,6 +3279,7 @@ exports.preconditionConflictError = preconditionConflictError;
|
|
|
2965
3279
|
exports.provideAppFirestoreCollections = provideAppFirestoreCollections;
|
|
2966
3280
|
exports.provideFirebaseServerAuthService = provideFirebaseServerAuthService;
|
|
2967
3281
|
exports.provideFirebaseServerStorageService = provideFirebaseServerStorageService;
|
|
3282
|
+
exports.readApiDetails = readApiDetails;
|
|
2968
3283
|
exports.readModelUnknownModelTypeError = readModelUnknownModelTypeError;
|
|
2969
3284
|
exports.setNestContextOnRequest = setNestContextOnRequest;
|
|
2970
3285
|
exports.setNestContextOnScheduleRequest = setNestContextOnScheduleRequest;
|
|
@@ -2980,4 +3295,5 @@ exports.unknownScheduledFunctionDevelopmentFunctionType = unknownScheduledFuncti
|
|
|
2980
3295
|
exports.updateModelUnknownModelTypeError = updateModelUnknownModelTypeError;
|
|
2981
3296
|
exports.userContextFromUid = userContextFromUid;
|
|
2982
3297
|
exports.verifyAppCheckInRequest = verifyAppCheckInRequest;
|
|
3298
|
+
exports.withApiDetails = withApiDetails;
|
|
2983
3299
|
//# sourceMappingURL=index.cjs.js.map
|