@dereekb/firebase-server 13.6.17 → 13.8.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/index.cjs.js +4233 -1923
- package/index.esm.js +4204 -1904
- package/mailgun/index.cjs.js +91 -1
- package/mailgun/index.esm.js +92 -3
- package/mailgun/package.json +9 -9
- package/mailgun/src/lib/auth.mailgun.d.ts +37 -1
- package/model/package.json +10 -10
- package/model/src/lib/storagefile/storagefile.action.server.d.ts +1 -1
- package/oidc/index.cjs.js +245 -180
- package/oidc/index.esm.js +242 -178
- package/oidc/package.json +12 -12
- package/oidc/src/lib/middleware/oauth-auth.module.d.ts +18 -25
- package/package.json +15 -14
- package/src/lib/auth/auth.service.d.ts +233 -0
- package/src/lib/auth/auth.service.error.d.ts +29 -0
- package/src/lib/auth/auth.service.error.util.d.ts +40 -0
- package/src/lib/auth/index.d.ts +1 -0
- package/src/lib/function/error.d.ts +11 -28
- package/src/lib/nest/app.d.ts +4 -45
- package/src/lib/nest/app.module.d.ts +4 -2
- package/src/lib/nest/auth/auth.util.d.ts +71 -5
- package/src/lib/nest/controller/index.d.ts +1 -0
- package/src/lib/nest/controller/model/index.d.ts +4 -0
- package/src/lib/nest/controller/model/model.api.controller.d.ts +93 -0
- package/src/lib/nest/controller/model/model.api.dispatch.d.ts +73 -0
- package/src/lib/nest/controller/model/model.api.get.service.d.ts +73 -0
- package/src/lib/nest/controller/model/model.api.module.d.ts +32 -0
- package/src/lib/nest/model/analytics.handler.d.ts +2 -0
- package/src/lib/nest/model/api.details.d.ts +53 -1
- package/src/lib/nest/model/call.model.function.d.ts +8 -5
- package/src/lib/nest/model/create.model.function.d.ts +1 -1
- package/src/lib/nest/model/crud.assert.function.d.ts +1 -1
- package/src/lib/nest/model/delete.model.function.d.ts +1 -1
- package/src/lib/nest/model/index.d.ts +1 -0
- package/src/lib/nest/model/query.model.function.d.ts +207 -0
- package/src/lib/nest/model/read.model.function.d.ts +1 -1
- package/src/lib/nest/model/update.model.function.d.ts +1 -1
- package/src/lib/nest/nest.provider.d.ts +19 -0
- package/test/index.cjs.js +1358 -398
- package/test/index.esm.js +1355 -400
- package/test/package.json +14 -12
- package/test/src/lib/firebase/firebase.test.d.ts +1 -1
- package/test/src/lib/index.d.ts +1 -0
- package/test/src/lib/oidc/index.d.ts +2 -0
- package/test/src/lib/oidc/oidc.test.fixture.d.ts +126 -0
- package/test/src/lib/oidc/oidc.test.flow.d.ts +43 -0
- package/zoho/package.json +9 -9
package/src/lib/nest/app.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { type ClassType, type Getter
|
|
2
|
-
import { type INestApplication, type INestApplicationContext, type NestApplicationOptions
|
|
1
|
+
import { type ClassType, type Getter } from '@dereekb/util';
|
|
2
|
+
import { type INestApplication, type INestApplicationContext, type NestApplicationOptions } from '@nestjs/common';
|
|
3
3
|
import express from 'express';
|
|
4
4
|
import type * as admin from 'firebase-admin';
|
|
5
|
-
import { type StorageBucketId } from '@dereekb/firebase';
|
|
6
5
|
import { type FirebaseServerEnvironmentConfig } from '../env/env.config';
|
|
7
|
-
import { type
|
|
8
|
-
import { type NestServerAssetConfig } from './app.module';
|
|
6
|
+
import { type NestServerRootModuleConfig } from './app.module';
|
|
9
7
|
/**
|
|
10
8
|
* A running NestJS server instance backed by Express, paired with a lazy promise getter for the NestJS application context.
|
|
11
9
|
*/
|
|
@@ -58,58 +56,19 @@ export type ConfigureNestServerInstanceFunction = (nestApp: INestApplication) =>
|
|
|
58
56
|
* });
|
|
59
57
|
* ```
|
|
60
58
|
*/
|
|
61
|
-
export interface NestServerInstanceConfig<T> {
|
|
59
|
+
export interface NestServerInstanceConfig<T> extends Omit<NestServerRootModuleConfig, 'firebaseAppGetter'> {
|
|
62
60
|
/**
|
|
63
61
|
* Module to instantiate.
|
|
64
62
|
*/
|
|
65
63
|
readonly moduleClass: ClassType<T>;
|
|
66
|
-
/**
|
|
67
|
-
* Additional providers to provide globally.
|
|
68
|
-
*/
|
|
69
|
-
readonly providers?: Provider<unknown>[];
|
|
70
|
-
/**
|
|
71
|
-
* Whether or not to configure FirebaseServerEnvService to be provided globally.
|
|
72
|
-
*/
|
|
73
|
-
readonly configureEnvService?: boolean;
|
|
74
|
-
/**
|
|
75
|
-
* Whether or not to configure webhook usage.
|
|
76
|
-
*
|
|
77
|
-
* This will configure the webhook routes.
|
|
78
|
-
*/
|
|
79
|
-
readonly configureWebhooks?: boolean;
|
|
80
|
-
/**
|
|
81
|
-
* Default storage bucket to use. If provided, overrides what the app uses in the default FirebaseServerStorageContextModule and default FirebaseStorageContext.
|
|
82
|
-
*/
|
|
83
|
-
readonly defaultStorageBucket?: StorageBucketId;
|
|
84
|
-
/**
|
|
85
|
-
* Whether or not to force using the default storage bucket.
|
|
86
|
-
*/
|
|
87
|
-
readonly forceStorageBucket?: boolean;
|
|
88
|
-
/**
|
|
89
|
-
* Whether or not to verify API calls with app check. Is true by default.
|
|
90
|
-
*/
|
|
91
|
-
readonly appCheckEnabled?: boolean;
|
|
92
64
|
/**
|
|
93
65
|
* Additional nest application options.
|
|
94
66
|
*/
|
|
95
67
|
readonly applicationOptions?: NestApplicationOptions;
|
|
96
|
-
/**
|
|
97
|
-
* Global routing prefix or options.
|
|
98
|
-
*
|
|
99
|
-
* Example: '/api'
|
|
100
|
-
*/
|
|
101
|
-
readonly globalApiRoutePrefix?: WebsitePath | GlobalRoutePrefixConfig;
|
|
102
68
|
/**
|
|
103
69
|
* Optional configuration function
|
|
104
70
|
*/
|
|
105
71
|
readonly configureNestServerInstance?: ConfigureNestServerInstanceFunction;
|
|
106
|
-
/**
|
|
107
|
-
* Optional asset loader configuration.
|
|
108
|
-
*
|
|
109
|
-
* When provided, configures the {@link AssetLoader} with the given settings.
|
|
110
|
-
* The AssetLoader is always provided globally regardless of this config.
|
|
111
|
-
*/
|
|
112
|
-
readonly assets?: Maybe<NestServerAssetConfig>;
|
|
113
72
|
}
|
|
114
73
|
export interface NestFirebaseServerEnvironmentConfig {
|
|
115
74
|
readonly environment: FirebaseServerEnvironmentConfig;
|
|
@@ -41,7 +41,7 @@ export interface NestServerRootModuleConfig {
|
|
|
41
41
|
/**
|
|
42
42
|
* Module(s) to import into the root module.
|
|
43
43
|
*/
|
|
44
|
-
readonly modules
|
|
44
|
+
readonly modules?: Maybe<ArrayOrValue<ClassType | DynamicModule>>;
|
|
45
45
|
/**
|
|
46
46
|
* Getter for the Firebase Admin app instance.
|
|
47
47
|
* When provided, the `FIREBASE_APP_TOKEN` is made available globally.
|
|
@@ -50,7 +50,7 @@ export interface NestServerRootModuleConfig {
|
|
|
50
50
|
/**
|
|
51
51
|
* Additional providers to include globally.
|
|
52
52
|
*/
|
|
53
|
-
readonly
|
|
53
|
+
readonly providers?: Provider[];
|
|
54
54
|
/**
|
|
55
55
|
* Environment configuration. When provided, injects env tokens via `firebaseServerEnvTokenProviders`.
|
|
56
56
|
*/
|
|
@@ -71,6 +71,8 @@ export interface NestServerRootModuleConfig {
|
|
|
71
71
|
/**
|
|
72
72
|
* Global route prefix configuration.
|
|
73
73
|
* The `GlobalRoutePrefixConfig` token is always provided (empty object when no config given).
|
|
74
|
+
*
|
|
75
|
+
* Example: '/api'
|
|
74
76
|
*/
|
|
75
77
|
readonly globalApiRoutePrefix?: WebsitePath | GlobalRoutePrefixConfig;
|
|
76
78
|
/**
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { type UserRelated } from '@dereekb/firebase';
|
|
2
|
-
import { type ArrayOrValue, type AuthRole } from '@dereekb/util';
|
|
2
|
+
import { type ArrayOrValue, type AuthRole, type ErrorMessageOrPartialServerError, type Maybe } from '@dereekb/util';
|
|
3
3
|
import { type NestContextCallableRequestWithOptionalAuth } from '../function/nest';
|
|
4
4
|
import { type AbstractFirebaseNestContext } from '../nest.provider';
|
|
5
5
|
/**
|
|
6
6
|
* Asserts that the caller has admin privileges in the request.
|
|
7
7
|
*
|
|
8
8
|
* @param request - The callable request to check for admin privileges.
|
|
9
|
+
* @param messageOrError - Optional custom error message or partial server error for the forbidden response.
|
|
9
10
|
* @throws {HttpsError} Throws forbidden (403) if the caller is not an admin.
|
|
10
11
|
*/
|
|
11
|
-
export declare function assertIsAdminInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>): void;
|
|
12
|
+
export declare function assertIsAdminInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>, messageOrError?: Maybe<ErrorMessageOrPartialServerError>): void;
|
|
12
13
|
/**
|
|
13
14
|
* Checks whether the caller has admin privileges in the request.
|
|
14
15
|
*
|
|
@@ -23,10 +24,11 @@ export declare function isAdminInRequest<N extends AbstractFirebaseNestContext<a
|
|
|
23
24
|
*
|
|
24
25
|
* @param request - The callable request containing the target UID.
|
|
25
26
|
* @param requireUid - If true, a UID must be present in the request data.
|
|
27
|
+
* @param messageOrError - Optional custom error message or partial server error for the forbidden response.
|
|
26
28
|
* @returns The resolved target UID (from request data or auth).
|
|
27
29
|
* @throws {HttpsError} Throws forbidden (403) if the caller is not authorized.
|
|
28
30
|
*/
|
|
29
|
-
export declare function assertIsAdminOrTargetUserInRequestData<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I extends Partial<UserRelated> = Partial<UserRelated>>(request: NestContextCallableRequestWithOptionalAuth<N, I>, requireUid?: boolean): string | undefined;
|
|
31
|
+
export declare function assertIsAdminOrTargetUserInRequestData<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I extends Partial<UserRelated> = Partial<UserRelated>>(request: NestContextCallableRequestWithOptionalAuth<N, I>, requireUid?: boolean, messageOrError?: Maybe<ErrorMessageOrPartialServerError>): string | undefined;
|
|
30
32
|
/**
|
|
31
33
|
* Checks whether the caller is an admin or is targeting their own user record in the request data.
|
|
32
34
|
*
|
|
@@ -39,9 +41,10 @@ export declare function isAdminOrTargetUserInRequestData<N extends AbstractFireb
|
|
|
39
41
|
* Asserts that the caller has signed the Terms of Service.
|
|
40
42
|
*
|
|
41
43
|
* @param request - The callable request to check for ToS status.
|
|
44
|
+
* @param messageOrError - Optional custom error message or partial server error for the forbidden response.
|
|
42
45
|
* @throws {HttpsError} Throws forbidden (403) if ToS has not been signed.
|
|
43
46
|
*/
|
|
44
|
-
export declare function assertHasSignedTosInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>): void;
|
|
47
|
+
export declare function assertHasSignedTosInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>, messageOrError?: Maybe<ErrorMessageOrPartialServerError>): void;
|
|
45
48
|
/**
|
|
46
49
|
* Checks whether the caller has signed the Terms of Service.
|
|
47
50
|
*
|
|
@@ -54,9 +57,10 @@ export declare function hasSignedTosInRequest<N extends AbstractFirebaseNestCont
|
|
|
54
57
|
*
|
|
55
58
|
* @param request - The callable request to check for auth roles.
|
|
56
59
|
* @param authRoles - One or more roles that must all be present.
|
|
60
|
+
* @param messageOrError - Optional custom error message or partial server error for the forbidden response.
|
|
57
61
|
* @throws {HttpsError} Throws forbidden (403) if any required role is missing.
|
|
58
62
|
*/
|
|
59
|
-
export declare function assertHasRolesInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>, authRoles: ArrayOrValue<AuthRole>): void;
|
|
63
|
+
export declare function assertHasRolesInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>, authRoles: ArrayOrValue<AuthRole>, messageOrError?: Maybe<ErrorMessageOrPartialServerError>): void;
|
|
60
64
|
/**
|
|
61
65
|
* Checks whether the caller has all of the specified auth roles.
|
|
62
66
|
*
|
|
@@ -65,6 +69,68 @@ export declare function assertHasRolesInRequest<N extends AbstractFirebaseNestCo
|
|
|
65
69
|
* @returns True if the caller has all of the specified auth roles.
|
|
66
70
|
*/
|
|
67
71
|
export declare function hasAuthRolesInRequest<N extends AbstractFirebaseNestContext<any, any> = AbstractFirebaseNestContext<any, any>, I = unknown>(request: NestContextCallableRequestWithOptionalAuth<N, I>, authRoles: ArrayOrValue<AuthRole>): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Configuration for {@link resolveAdminOnlyValue}.
|
|
74
|
+
*
|
|
75
|
+
* @typeParam T - The value type being resolved.
|
|
76
|
+
*/
|
|
77
|
+
export interface ResolveAdminOnlyValueConfig<N extends AbstractFirebaseNestContext<any, any>, I, T> {
|
|
78
|
+
/**
|
|
79
|
+
* The callable request to check for admin privileges.
|
|
80
|
+
*/
|
|
81
|
+
readonly request: NestContextCallableRequestWithOptionalAuth<N, I>;
|
|
82
|
+
/**
|
|
83
|
+
* The raw input value from the request data. May be undefined if the caller didn't provide it.
|
|
84
|
+
*/
|
|
85
|
+
readonly value?: Maybe<T>;
|
|
86
|
+
/**
|
|
87
|
+
* The default value to use when {@link value} is undefined and the caller is not an admin.
|
|
88
|
+
*
|
|
89
|
+
* Admins receive no default (undefined) so the query has no restriction.
|
|
90
|
+
*/
|
|
91
|
+
readonly defaultValue?: Maybe<T>;
|
|
92
|
+
/**
|
|
93
|
+
* Predicate that returns true if the given resolved value is restricted to admins only.
|
|
94
|
+
*
|
|
95
|
+
* When this returns true and the caller is not an admin, a forbidden error is thrown.
|
|
96
|
+
*
|
|
97
|
+
* @param value - The resolved value to check.
|
|
98
|
+
* @returns True if the value requires admin privileges.
|
|
99
|
+
*/
|
|
100
|
+
readonly isAdminOnlyValue: (value: Maybe<T>) => boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Optional error message or partial server error to include in the forbidden error.
|
|
103
|
+
*/
|
|
104
|
+
readonly messageOrError?: ErrorMessageOrPartialServerError;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolves a request parameter value with admin-aware defaulting and access control.
|
|
108
|
+
*
|
|
109
|
+
* For non-admins:
|
|
110
|
+
* - If the caller didn't provide a value, the {@link ResolveAdminOnlyValueConfig.defaultValue} is used.
|
|
111
|
+
* - If the resolved value is admin-only (per the predicate), a forbidden error is thrown.
|
|
112
|
+
*
|
|
113
|
+
* For admins:
|
|
114
|
+
* - If the caller didn't provide a value, undefined is returned (no restriction).
|
|
115
|
+
* - Any value is allowed.
|
|
116
|
+
*
|
|
117
|
+
* @param config - Configuration specifying the request, value, defaults, and admin-only predicate.
|
|
118
|
+
* @returns The resolved value, or undefined for admins who didn't provide a value.
|
|
119
|
+
* @throws {HttpsError} Throws forbidden (403) if a non-admin attempts to use an admin-only value.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Non-admins can only query published=true; admins can query anything
|
|
124
|
+
* const published = resolveAdminOnlyValue({
|
|
125
|
+
* request,
|
|
126
|
+
* value: data.published,
|
|
127
|
+
* defaultValue: true,
|
|
128
|
+
* isAdminOnlyValue: (v) => v !== true,
|
|
129
|
+
* messageOrError: { message: 'Users can only search published entries.' }
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export declare function resolveAdminOnlyValue<N extends AbstractFirebaseNestContext<any, any>, I, T>(config: ResolveAdminOnlyValueConfig<N, I, T>): Maybe<T>;
|
|
68
134
|
/**
|
|
69
135
|
* Returns true if the claims have a FIREBASE_SERVER_AUTH_CLAIMS_SETUP_PASSWORD_KEY claims value, indicating they are a newly invited user.
|
|
70
136
|
*
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type Request } from 'express';
|
|
2
|
+
import { type OnCallTypedModelParams, type FirestoreModelKey } from '@dereekb/firebase';
|
|
3
|
+
import { type Maybe } from '@dereekb/util';
|
|
4
|
+
import { ModelApiCallModelDispatchService } from './model.api.dispatch';
|
|
5
|
+
import { ModelApiGetService } from './model.api.get.service';
|
|
6
|
+
/**
|
|
7
|
+
* Body for multi-read POST requests on the `get` route.
|
|
8
|
+
*/
|
|
9
|
+
interface ModelAccessMultiReadBody {
|
|
10
|
+
readonly keys: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* REST API controller that exposes the callModel dispatch chain and direct document access via HTTP.
|
|
14
|
+
*
|
|
15
|
+
* Mounted at `model` — under the `/api` global prefix, routes become `/api/model/*`.
|
|
16
|
+
*
|
|
17
|
+
* Provides three access patterns:
|
|
18
|
+
* 1. **Direct dispatch**: `POST /api/model/call` with an {@link OnCallTypedModelParams} body.
|
|
19
|
+
* 2. **Document access**: `GET /api/model/:modelType/get?key=...` (single) or
|
|
20
|
+
* `POST /api/model/:modelType/get` with `{ keys: [...] }` (multi) via `useModel()`.
|
|
21
|
+
* 3. **Path-based dispatch**: `POST|PUT /api/model/:modelType/:call/:specifier?` dispatches
|
|
22
|
+
* to the callModel chain. `DELETE` is only allowed when the call segment is `'delete'`.
|
|
23
|
+
* The call type comes from the path, not the HTTP method.
|
|
24
|
+
*
|
|
25
|
+
* Auth is provided by the OIDC bearer token middleware on the `req.auth` field.
|
|
26
|
+
*/
|
|
27
|
+
export declare class ModelApiController {
|
|
28
|
+
private readonly dispatchService;
|
|
29
|
+
private readonly accessService;
|
|
30
|
+
constructor(dispatchService: ModelApiCallModelDispatchService, accessService: ModelApiGetService);
|
|
31
|
+
/**
|
|
32
|
+
* Direct dispatch with full OnCallTypedModelParams body.
|
|
33
|
+
*
|
|
34
|
+
* This route MUST be declared before the catch-all to prevent NestJS
|
|
35
|
+
* from matching "call" as a modelType.
|
|
36
|
+
*
|
|
37
|
+
* @param body - The full {@link OnCallTypedModelParams} describing the model call to dispatch.
|
|
38
|
+
* @param req - The Express request containing auth credentials on `req.auth`.
|
|
39
|
+
* @returns The result of the dispatched model call.
|
|
40
|
+
*/
|
|
41
|
+
directDispatch(body: OnCallTypedModelParams, req: Request): Promise<unknown>;
|
|
42
|
+
/**
|
|
43
|
+
* Reads a single document by model type and key via `useModel()` with `'read'` roles.
|
|
44
|
+
*
|
|
45
|
+
* The key is a full Firestore model key (e.g., `pr/abc123`), not just an ID.
|
|
46
|
+
*
|
|
47
|
+
* Declared before the catch-all `{*path}` route so NestJS matches this first.
|
|
48
|
+
*
|
|
49
|
+
* @param modelType - The model type identifier (e.g., 'pr', 'user').
|
|
50
|
+
* @param key - The full Firestore model key to read (e.g., `pr/abc123`).
|
|
51
|
+
* @param req - The Express request containing auth credentials on `req.auth`.
|
|
52
|
+
* @returns The document data for the requested model key.
|
|
53
|
+
*/
|
|
54
|
+
getOne(modelType: string, key: Maybe<FirestoreModelKey>, req: Request): Promise<import("./model.api.get.service").ModelAccessReadResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Reads multiple documents of the same model type via `useMultipleModels()` with `'read'` roles.
|
|
57
|
+
*
|
|
58
|
+
* Keys are full Firestore model keys. Maximum {@link MAX_MODEL_ACCESS_MULTI_READ_KEYS} keys per request.
|
|
59
|
+
*
|
|
60
|
+
* Declared before the catch-all `{*path}` route so NestJS matches this first.
|
|
61
|
+
*
|
|
62
|
+
* @param modelType - The model type identifier (e.g., 'pr', 'user').
|
|
63
|
+
* @param body - Request body containing the array of Firestore model keys to read.
|
|
64
|
+
* @param req - The Express request containing auth credentials on `req.auth`.
|
|
65
|
+
* @returns An object with `results` (document data array) and `errors` (per-key failures).
|
|
66
|
+
*/
|
|
67
|
+
getMany(modelType: string, body: ModelAccessMultiReadBody, req: Request): Promise<import("./model.api.get.service").ModelAccessMultiReadResult>;
|
|
68
|
+
/**
|
|
69
|
+
* Catch-all handler for callModel dispatch via path.
|
|
70
|
+
*
|
|
71
|
+
* Path: `/api/model/:modelType/:call/:specifier?`
|
|
72
|
+
*
|
|
73
|
+
* The call type (create, read, update, delete, query, etc.) is determined by the
|
|
74
|
+
* path segment, not the HTTP method. POST and PUT are allowed for any call type.
|
|
75
|
+
* DELETE is only allowed when the call segment is `'delete'`.
|
|
76
|
+
*
|
|
77
|
+
* @param req - The Express request whose path segments encode modelType, call, and optional specifier.
|
|
78
|
+
* @returns The result of the dispatched model call.
|
|
79
|
+
*/
|
|
80
|
+
handleDispatchRequest(req: Request): Promise<unknown>;
|
|
81
|
+
/**
|
|
82
|
+
* Parses modelType, call, and specifier from the wildcard path segments.
|
|
83
|
+
*
|
|
84
|
+
* Expected path format: `:modelType/:call/:specifier?`
|
|
85
|
+
*
|
|
86
|
+
* @param req - The Express request containing wildcard path params.
|
|
87
|
+
* @returns Parsed path components with modelType, call, and specifier (defaults to '_').
|
|
88
|
+
*/
|
|
89
|
+
private _parsePath;
|
|
90
|
+
private _dispatch;
|
|
91
|
+
private _toHttpException;
|
|
92
|
+
}
|
|
93
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type OnCallTypedModelParams } from '@dereekb/firebase';
|
|
3
|
+
import { type INestApplicationContext } from '@nestjs/common';
|
|
4
|
+
import { type Request } from 'express';
|
|
5
|
+
import { type OnCallWithNestContext } from '../../function/call';
|
|
6
|
+
import { type OnCallApiDetailsRef, type ModelApiDetailsResult } from '../../model/api.details';
|
|
7
|
+
import { type MakeNestContext } from '../../nest.provider';
|
|
8
|
+
import { type FirebaseServerAuthData } from '../auth.context.server';
|
|
9
|
+
/**
|
|
10
|
+
* The combined type of the function returned by onCallModel() with _apiDetails attached.
|
|
11
|
+
*/
|
|
12
|
+
export type OnCallModelFnWithApiDetails = OnCallWithNestContext<unknown, OnCallTypedModelParams> & OnCallApiDetailsRef;
|
|
13
|
+
/**
|
|
14
|
+
* Abstract injectable config token for the Model API dispatch layer.
|
|
15
|
+
*
|
|
16
|
+
* Downstream apps provide this by creating a concrete provider that references
|
|
17
|
+
* their onCallModel() return value and MakeNestContext factory.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* {
|
|
22
|
+
* provide: ModelApiDispatchConfig,
|
|
23
|
+
* useValue: {
|
|
24
|
+
* callModelFn: demoCallModelFn,
|
|
25
|
+
* makeNestContext: mapDemoApiNestContext
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare abstract class ModelApiDispatchConfig {
|
|
31
|
+
/**
|
|
32
|
+
* The onCallModel() return value with _apiDetails attached.
|
|
33
|
+
*/
|
|
34
|
+
readonly callModelFn: OnCallModelFnWithApiDetails;
|
|
35
|
+
/**
|
|
36
|
+
* Factory to create typed nest context from INestApplicationContext.
|
|
37
|
+
*/
|
|
38
|
+
readonly makeNestContext: MakeNestContext<unknown>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Injection token for providing the NestJS application context to the dispatch service.
|
|
42
|
+
*
|
|
43
|
+
* The app module metadata factory creates a provider for this token
|
|
44
|
+
* using the module's own INestApplicationContext.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MODEL_API_NEST_APPLICATION_CONTEXT = "MODEL_API_NEST_APPLICATION_CONTEXT";
|
|
47
|
+
/**
|
|
48
|
+
* Service that bridges HTTP/MCP requests to the callModel dispatch chain.
|
|
49
|
+
*
|
|
50
|
+
* Builds a synthetic {@link CallableRequest} from the HTTP request auth and body,
|
|
51
|
+
* injects the NestJS application context and typed nest context, then invokes
|
|
52
|
+
* the callModel function.
|
|
53
|
+
*/
|
|
54
|
+
export declare class ModelApiCallModelDispatchService {
|
|
55
|
+
private readonly config;
|
|
56
|
+
private readonly nestApplication;
|
|
57
|
+
constructor(config: ModelApiDispatchConfig, nestApplication: INestApplicationContext);
|
|
58
|
+
/**
|
|
59
|
+
* Dispatch to the callModel chain.
|
|
60
|
+
*
|
|
61
|
+
* @param params - The typed model params (call, modelType, specifier, data).
|
|
62
|
+
* @param auth - The authenticated user's auth data from the OIDC middleware.
|
|
63
|
+
* @param rawRequest - The raw Express request.
|
|
64
|
+
* @returns The handler's return value.
|
|
65
|
+
*/
|
|
66
|
+
dispatch(params: OnCallTypedModelParams, auth: Maybe<FirebaseServerAuthData>, rawRequest: Request): Promise<unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Returns the model-first API details view, or undefined if no handlers have _apiDetails.
|
|
69
|
+
*
|
|
70
|
+
* @returns The aggregated API details describing all registered model call handlers, or undefined if unavailable.
|
|
71
|
+
*/
|
|
72
|
+
getApiDetails(): Maybe<ModelApiDetailsResult>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
|
+
import { type FirestoreModelKey, type FirestoreModelType } from '@dereekb/firebase';
|
|
3
|
+
import { type INestApplicationContext } from '@nestjs/common';
|
|
4
|
+
import { ModelApiDispatchConfig } from './model.api.dispatch';
|
|
5
|
+
import { type FirebaseServerAuthData } from '../auth.context.server';
|
|
6
|
+
/**
|
|
7
|
+
* Maximum number of keys allowed in a multi-read request.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MAX_MODEL_ACCESS_MULTI_READ_KEYS = 50;
|
|
10
|
+
/**
|
|
11
|
+
* Result of a single document access read.
|
|
12
|
+
*/
|
|
13
|
+
export interface ModelAccessReadResult {
|
|
14
|
+
readonly key: FirestoreModelKey;
|
|
15
|
+
readonly data: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Result of a multi-document access read.
|
|
19
|
+
*/
|
|
20
|
+
export interface ModelAccessMultiReadResult {
|
|
21
|
+
readonly results: ModelAccessReadResult[];
|
|
22
|
+
readonly errors: ModelAccessReadError[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Error for a single document in a multi-read request.
|
|
26
|
+
*/
|
|
27
|
+
export interface ModelAccessReadError {
|
|
28
|
+
readonly key: FirestoreModelKey;
|
|
29
|
+
readonly message: string;
|
|
30
|
+
readonly code?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Service for direct document reads using the `useModel()` permission-checking pattern.
|
|
34
|
+
*
|
|
35
|
+
* Unlike the dispatch service (which goes through callModel handlers), this service
|
|
36
|
+
* reads documents directly from Firestore via {@link AbstractFirebaseNestContext.useModel},
|
|
37
|
+
* enforcing `'read'` role permissions per document.
|
|
38
|
+
*/
|
|
39
|
+
export declare class ModelApiGetService {
|
|
40
|
+
private readonly _nestContext;
|
|
41
|
+
constructor(config: ModelApiDispatchConfig, nestApplication: INestApplicationContext);
|
|
42
|
+
/**
|
|
43
|
+
* Reads a single document by model type and key with permission checking.
|
|
44
|
+
*
|
|
45
|
+
* @param modelType - The Firestore model type string (e.g., 'profile', 'guestbook').
|
|
46
|
+
* @param key - The full Firestore model key (e.g., 'pr/abc123').
|
|
47
|
+
* @param auth - The authenticated user's auth data from the request.
|
|
48
|
+
* @returns The document key and data.
|
|
49
|
+
* @throws Permission or not-found errors from useModel.
|
|
50
|
+
*/
|
|
51
|
+
readDocument(modelType: FirestoreModelType, key: FirestoreModelKey, auth: Maybe<FirebaseServerAuthData>): Promise<ModelAccessReadResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Reads multiple documents of the same model type with permission checking.
|
|
54
|
+
*
|
|
55
|
+
* Individual document errors (not-found, forbidden) are captured per-key
|
|
56
|
+
* and returned in the errors array rather than throwing.
|
|
57
|
+
*
|
|
58
|
+
* @param modelType - The Firestore model type string.
|
|
59
|
+
* @param keys - Array of Firestore model keys (max {@link MAX_MODEL_ACCESS_MULTI_READ_KEYS}).
|
|
60
|
+
* @param auth - The authenticated user's auth data from the request.
|
|
61
|
+
* @returns Results and errors for each requested key.
|
|
62
|
+
*/
|
|
63
|
+
readDocuments(modelType: FirestoreModelType, keys: FirestoreModelKey[], auth: Maybe<FirebaseServerAuthData>): Promise<ModelAccessMultiReadResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Builds an {@link AuthDataRef} compatible with `useModel()` from the HTTP request auth.
|
|
66
|
+
*
|
|
67
|
+
* Uses the same synthetic auth pattern as {@link ModelApiDispatchService.dispatch}.
|
|
68
|
+
*
|
|
69
|
+
* @param auth - The Firebase server auth data from the HTTP request, or undefined for unauthenticated requests.
|
|
70
|
+
* @returns An object containing a synthetic {@link AuthData} for use with `useModel()`, or undefined auth.
|
|
71
|
+
*/
|
|
72
|
+
private _makeAuthRef;
|
|
73
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import { type ClassType } from '@dereekb/util';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for {@link modelApiModuleMetadata}.
|
|
5
|
+
*/
|
|
6
|
+
export interface ModelApiModuleMetadataConfig extends Pick<ModuleMetadata, 'imports' | 'exports' | 'providers'> {
|
|
7
|
+
/**
|
|
8
|
+
* Module that exports the required dependencies.
|
|
9
|
+
*
|
|
10
|
+
* Must provide {@link ModelApiDispatchConfig} so the dispatch service
|
|
11
|
+
* can access the callModel function and nest context factory.
|
|
12
|
+
*/
|
|
13
|
+
readonly dependencyModule: ClassType;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Generates NestJS module metadata for the Model API controller.
|
|
17
|
+
*
|
|
18
|
+
* Follows the same pattern as {@link oidcModuleMetadata} — takes a dependency module
|
|
19
|
+
* that provides the required tokens, and returns a complete module metadata object.
|
|
20
|
+
*
|
|
21
|
+
* @param metadataConfig - Configuration including the dependency module.
|
|
22
|
+
* @returns NestJS module metadata with the Model API controller, dispatch service, and app context provider.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* @Module(modelApiModuleMetadata({
|
|
27
|
+
* dependencyModule: DemoModelApiDependencyModule
|
|
28
|
+
* }))
|
|
29
|
+
* export class DemoModelApiModule {}
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function modelApiModuleMetadata(metadataConfig: ModelApiModuleMetadataConfig): ModuleMetadata;
|
|
@@ -71,6 +71,8 @@ export declare const ON_CALL_MODEL_ANALYTICS_SERVICE: InjectionToken<OnCallModel
|
|
|
71
71
|
*
|
|
72
72
|
* Used as the default fallback by {@link OnCallModelAnalyticsResolver} when no analytics
|
|
73
73
|
* service is registered.
|
|
74
|
+
*
|
|
75
|
+
* @returns An {@link OnCallModelAnalyticsService} that discards all analytics events.
|
|
74
76
|
*/
|
|
75
77
|
export declare function noopOnCallModelAnalyticsService(): OnCallModelAnalyticsService;
|
|
76
78
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Maybe } from '@dereekb/util';
|
|
2
|
-
import { type OnCallFunctionType, type FirestoreModelType, type ModelFirebaseCrudFunctionSpecifier } from '@dereekb/firebase';
|
|
2
|
+
import { type OnCallFunctionType, type OnCallTypedModelParams, type FirestoreModelType, type ModelFirebaseCrudFunctionSpecifier } from '@dereekb/firebase';
|
|
3
3
|
import { type OnCallModelFunctionAnalyticsDetails } from './analytics.details';
|
|
4
4
|
/**
|
|
5
5
|
* Reference to a type that can produce a JSON Schema representation.
|
|
@@ -11,10 +11,41 @@ import { type OnCallModelFunctionAnalyticsDetails } from './analytics.details';
|
|
|
11
11
|
export interface JsonSchemaRef {
|
|
12
12
|
toJsonSchema(options?: object): object;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* A natural language summary of an MCP tool operation result.
|
|
16
|
+
*/
|
|
17
|
+
export type McpToolResponseSummary = string;
|
|
18
|
+
/**
|
|
19
|
+
* Minimal structural type for MCP tool response content.
|
|
20
|
+
*
|
|
21
|
+
* Mirrors the shape of MCP SDK's CallToolResult without importing the SDK directly,
|
|
22
|
+
* so api.details.ts remains dependency-free.
|
|
23
|
+
*/
|
|
24
|
+
export interface McpToolResponseContent {
|
|
25
|
+
readonly content: ReadonlyArray<McpToolResponseContentBlock>;
|
|
26
|
+
readonly structuredContent?: unknown;
|
|
27
|
+
readonly isError?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A single content block in an MCP tool response.
|
|
31
|
+
*/
|
|
32
|
+
export interface McpToolResponseContentBlock {
|
|
33
|
+
readonly type: string;
|
|
34
|
+
readonly text?: string;
|
|
35
|
+
readonly mimeType?: string;
|
|
36
|
+
readonly data?: string;
|
|
37
|
+
}
|
|
14
38
|
/**
|
|
15
39
|
* MCP-specific customization for a model function.
|
|
16
40
|
*
|
|
17
41
|
* When omitted, defaults are auto-generated from the handler's position in the call model tree.
|
|
42
|
+
*
|
|
43
|
+
* Response formatting uses a tiered system:
|
|
44
|
+
* - **Tier 1 (default)**: Auto-generated summary from the result shape. No config needed.
|
|
45
|
+
* - **Tier 2**: Provide {@link summarizeResponse} to return a natural language string. The framework wraps it into MCP content + structuredContent automatically.
|
|
46
|
+
* - **Tier 3**: Provide {@link formatResponse} for complete control over the MCP response content blocks.
|
|
47
|
+
*
|
|
48
|
+
* Resolution order: formatResponse > summarizeResponse > auto-generated default.
|
|
18
49
|
*/
|
|
19
50
|
export interface OnCallModelFunctionMcpDetails {
|
|
20
51
|
/**
|
|
@@ -25,6 +56,27 @@ export interface OnCallModelFunctionMcpDetails {
|
|
|
25
56
|
* Custom tool name override.
|
|
26
57
|
*/
|
|
27
58
|
readonly name?: string;
|
|
59
|
+
/**
|
|
60
|
+
* Tier 2 response formatter: returns a natural language summary string.
|
|
61
|
+
*
|
|
62
|
+
* The framework wraps the string into a text content block and attaches the raw result
|
|
63
|
+
* as structuredContent automatically.
|
|
64
|
+
*
|
|
65
|
+
* @param result - The handler's return value.
|
|
66
|
+
* @param params - The OnCallTypedModelParams that were dispatched.
|
|
67
|
+
* @returns A human-readable summary of the operation result.
|
|
68
|
+
*/
|
|
69
|
+
readonly summarizeResponse?: (result: unknown, params: OnCallTypedModelParams) => McpToolResponseSummary;
|
|
70
|
+
/**
|
|
71
|
+
* Tier 3 response formatter: complete control over the MCP tool response.
|
|
72
|
+
*
|
|
73
|
+
* When provided, takes precedence over {@link summarizeResponse} and the auto-generated default.
|
|
74
|
+
*
|
|
75
|
+
* @param result - The handler's return value.
|
|
76
|
+
* @param params - The OnCallTypedModelParams that were dispatched.
|
|
77
|
+
* @returns The full MCP tool response content.
|
|
78
|
+
*/
|
|
79
|
+
readonly formatResponse?: (result: unknown, params: OnCallTypedModelParams) => McpToolResponseContent;
|
|
28
80
|
}
|
|
29
81
|
/**
|
|
30
82
|
* API details metadata for a single model call handler function.
|
|
@@ -5,13 +5,13 @@ import { type AssertModelCrudRequestFunctionContextCrudType, type AssertModelCru
|
|
|
5
5
|
import { type NestContextCallableRequest } from '../function/nest';
|
|
6
6
|
import { type OnCallApiDetailsRef } from './api.details';
|
|
7
7
|
/**
|
|
8
|
-
* Maps
|
|
8
|
+
* Maps call type strings (e.g., 'create', 'read', 'update', 'delete', 'query') to their
|
|
9
9
|
* corresponding handler functions.
|
|
10
10
|
*
|
|
11
|
-
* Used by {@link onCallModel} to dispatch incoming requests to the correct
|
|
11
|
+
* Used by {@link onCallModel} to dispatch incoming requests to the correct handler.
|
|
12
12
|
*/
|
|
13
13
|
export type OnCallModelMap = {
|
|
14
|
-
readonly [call: OnCallFunctionType]: OnCallWithNestContext<any, OnCallTypedModelParams
|
|
14
|
+
readonly [call: OnCallFunctionType]: OnCallWithNestContext<any, OnCallTypedModelParams<any>>;
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
17
|
* Configuration for {@link onCallModel}.
|
|
@@ -51,7 +51,8 @@ export interface OnCallModelConfig {
|
|
|
51
51
|
* create: onCallCreateModel({ profile: createProfile, guestbook: createGuestbook }),
|
|
52
52
|
* read: onCallReadModel({ profile: readProfile }),
|
|
53
53
|
* update: onCallUpdateModel({ profile: updateProfile }),
|
|
54
|
-
* delete: onCallDeleteModel({ guestbook: deleteGuestbook })
|
|
54
|
+
* delete: onCallDeleteModel({ guestbook: deleteGuestbook }),
|
|
55
|
+
* query: onCallQueryModel({ profile: queryProfiles })
|
|
55
56
|
* });
|
|
56
57
|
* ```
|
|
57
58
|
*/
|
|
@@ -95,7 +96,9 @@ export interface OnCallWithCallTypeModelConfig<N> {
|
|
|
95
96
|
*/
|
|
96
97
|
readonly callType: string;
|
|
97
98
|
/**
|
|
98
|
-
* The
|
|
99
|
+
* The operation category used by {@link AssertModelCrudRequestFunction}.
|
|
100
|
+
*
|
|
101
|
+
* One of: 'call', 'create', 'read', 'update', 'delete', 'query'.
|
|
99
102
|
*/
|
|
100
103
|
readonly crudType: AssertModelCrudRequestFunctionContextCrudType;
|
|
101
104
|
/**
|
|
@@ -89,6 +89,6 @@ export declare function onCallCreateModel<N>(map: OnCallCreateModelMap<N>, confi
|
|
|
89
89
|
* Creates a bad-request error indicating the requested model type is not valid for creation.
|
|
90
90
|
*
|
|
91
91
|
* @param modelType - The unrecognized model type string.
|
|
92
|
-
* @returns A bad-request error with
|
|
92
|
+
* @returns A bad-request error with {@link UNKNOWN_MODEL_TYPE_ERROR_CODE} code.
|
|
93
93
|
*/
|
|
94
94
|
export declare function createModelUnknownModelTypeError(modelType: FirestoreModelType): import("firebase-functions/https").HttpsError;
|
|
@@ -7,7 +7,7 @@ import { type OnCallFunctionType, type FirestoreModelType } from '@dereekb/fireb
|
|
|
7
7
|
* Used by {@link AssertModelCrudRequestFunction} to let assertions branch on operation type,
|
|
8
8
|
* enabling cross-cutting rules like "block all deletes for archived models."
|
|
9
9
|
*/
|
|
10
|
-
export type AssertModelCrudRequestFunctionContextCrudType = 'call' | 'create' | 'read' | 'update' | 'delete';
|
|
10
|
+
export type AssertModelCrudRequestFunctionContextCrudType = 'call' | 'create' | 'read' | 'update' | 'delete' | 'query';
|
|
11
11
|
/**
|
|
12
12
|
* Context passed to a {@link AssertModelCrudRequestFunction} before a CRUD handler executes.
|
|
13
13
|
*
|
|
@@ -78,6 +78,6 @@ export declare function onCallDeleteModel<N>(map: OnCallDeleteModelMap<N>, confi
|
|
|
78
78
|
* Creates a bad-request error indicating the requested model type is not valid for deletion.
|
|
79
79
|
*
|
|
80
80
|
* @param modelType - The unrecognized model type string.
|
|
81
|
-
* @returns A bad-request error with
|
|
81
|
+
* @returns A bad-request error with {@link UNKNOWN_MODEL_TYPE_ERROR_CODE} code.
|
|
82
82
|
*/
|
|
83
83
|
export declare function deleteModelUnknownModelTypeError(modelType: FirestoreModelType): import("firebase-functions/https").HttpsError;
|