firebase-functions 6.1.2 → 6.3.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.
@@ -69,7 +69,7 @@ export interface CallableRequest<T = any> {
69
69
  */
70
70
  data: T;
71
71
  /**
72
- * The result of decoding and verifying a Firebase AppCheck token.
72
+ * The result of decoding and verifying a Firebase App Check token.
73
73
  */
74
74
  app?: AppCheckData;
75
75
  /**
@@ -84,24 +84,29 @@ export interface CallableRequest<T = any> {
84
84
  * The raw request handled by the callable.
85
85
  */
86
86
  rawRequest: Request;
87
+ /**
88
+ * Whether this is a streaming request.
89
+ * Code can be optimized by not trying to generate a stream of chunks to
90
+ * call `response.sendChunk` if `request.acceptsStreaming` is false.
91
+ * It is always safe, however, to call `response.sendChunk` as this will
92
+ * noop if `acceptsStreaming` is false.
93
+ */
94
+ acceptsStreaming: boolean;
87
95
  }
88
96
  /**
89
- * CallableProxyResponse exposes subset of express.Response object
90
- * to allow writing partial, streaming responses back to the client.
97
+ * `CallableProxyResponse` allows streaming response chunks and listening to signals
98
+ * triggered in events such as a disconnect.
91
99
  */
92
- export interface CallableProxyResponse {
100
+ export interface CallableResponse<T = unknown> {
93
101
  /**
94
102
  * Writes a chunk of the response body to the client. This method can be called
95
103
  * multiple times to stream data progressively.
104
+ * Returns a promise of whether the data was written. This can be false, for example,
105
+ * if the request was not a streaming request. Rejects if there is a network error.
96
106
  */
97
- write: express.Response["write"];
98
- /**
99
- * Indicates whether the client has requested and can handle streaming responses.
100
- * This should be checked before attempting to stream data to avoid compatibility issues.
101
- */
102
- acceptsStreaming: boolean;
107
+ sendChunk: (chunk: T) => Promise<boolean>;
103
108
  /**
104
- * An AbortSignal that is triggered when the client disconnects or the
109
+ * An `AbortSignal` that is triggered when the client disconnects or the
105
110
  * request is terminated prematurely.
106
111
  */
107
112
  signal: AbortSignal;
@@ -283,13 +283,9 @@ async function checkTokens(req, ctx, options) {
283
283
  app: "INVALID",
284
284
  auth: "INVALID",
285
285
  };
286
- await Promise.all([
287
- Promise.resolve().then(async () => {
288
- verifications.auth = await checkAuthToken(req, ctx);
289
- }),
290
- Promise.resolve().then(async () => {
291
- verifications.app = await checkAppCheckToken(req, ctx, options);
292
- }),
286
+ [verifications.auth, verifications.app] = await Promise.all([
287
+ checkAuthToken(req, ctx),
288
+ checkAppCheckToken(req, ctx, options),
293
289
  ]);
294
290
  const logPayload = {
295
291
  verifications,
@@ -400,11 +396,12 @@ function onCallHandler(options, handler, version) {
400
396
  }
401
397
  exports.onCallHandler = onCallHandler;
402
398
  function encodeSSE(data) {
403
- return `data: ${JSON.stringify(data)}\n`;
399
+ return `data: ${JSON.stringify(data)}\n\n`;
404
400
  }
405
401
  /** @internal */
406
402
  function wrapOnCallHandler(options, handler, version) {
407
403
  return async (req, res) => {
404
+ var _a;
408
405
  const abortController = new AbortController();
409
406
  let heartbeatInterval = null;
410
407
  const heartbeatSeconds = options.heartbeatSeconds === undefined ? exports.DEFAULT_HEARTBEAT_SECONDS : options.heartbeatSeconds;
@@ -419,7 +416,7 @@ function wrapOnCallHandler(options, handler, version) {
419
416
  if (!abortController.signal.aborted) {
420
417
  heartbeatInterval = setTimeout(() => {
421
418
  if (!abortController.signal.aborted) {
422
- res.write(": ping\n");
419
+ res.write(": ping\n\n");
423
420
  scheduleHeartbeat();
424
421
  }
425
422
  }, heartbeatSeconds * 1000);
@@ -487,6 +484,12 @@ function wrapOnCallHandler(options, handler, version) {
487
484
  throw new HttpsError("invalid-argument", "Unsupported Accept header 'text/event-stream'");
488
485
  }
489
486
  const data = decode(req.body.data);
487
+ if (options.authPolicy) {
488
+ const authorized = await options.authPolicy((_a = context.auth) !== null && _a !== void 0 ? _a : null, data);
489
+ if (!authorized) {
490
+ throw new HttpsError("permission-denied", "Permission Denied");
491
+ }
492
+ }
490
493
  let result;
491
494
  if (version === "gcfv1") {
492
495
  result = await handler(data, context);
@@ -495,26 +498,38 @@ function wrapOnCallHandler(options, handler, version) {
495
498
  const arg = {
496
499
  ...context,
497
500
  data,
501
+ acceptsStreaming,
498
502
  };
499
503
  const responseProxy = {
500
- write(chunk) {
504
+ sendChunk(chunk) {
501
505
  // if client doesn't accept sse-protocol, response.write() is no-op.
502
506
  if (!acceptsStreaming) {
503
- return false;
507
+ return Promise.resolve(false);
504
508
  }
505
509
  // if connection is already closed, response.write() is no-op.
506
510
  if (abortController.signal.aborted) {
507
- return false;
511
+ return Promise.resolve(false);
508
512
  }
509
513
  const formattedData = encodeSSE({ message: chunk });
510
- const wrote = res.write(formattedData);
514
+ let resolve;
515
+ let reject;
516
+ const p = new Promise((res, rej) => {
517
+ resolve = res;
518
+ reject = rej;
519
+ });
520
+ const wrote = res.write(formattedData, (error) => {
521
+ if (error) {
522
+ reject(error);
523
+ return;
524
+ }
525
+ resolve(wrote);
526
+ });
511
527
  // Reset heartbeat timer after successful write
512
528
  if (wrote && heartbeatInterval !== null && heartbeatSeconds > 0) {
513
529
  scheduleHeartbeat();
514
530
  }
515
- return wrote;
531
+ return p;
516
532
  },
517
- acceptsStreaming,
518
533
  signal: abortController.signal,
519
534
  };
520
535
  if (acceptsStreaming) {
@@ -40,8 +40,8 @@ async function loadModule(functionsDir) {
40
40
  return require(path.resolve(absolutePath));
41
41
  }
42
42
  catch (e) {
43
- if (e.code === "ERR_REQUIRE_ESM") {
44
- // This is an ESM package!
43
+ if (e.code === "ERR_REQUIRE_ESM" || e.code === "ERR_REQUIRE_ASYNC_MODULE") {
44
+ // This is an ESM package, or one containing top-level awaits!
45
45
  const modulePath = require.resolve(absolutePath);
46
46
  // Resolve module path to file:// URL. Required for windows support.
47
47
  const moduleURL = url.pathToFileURL(modulePath).href;
@@ -42,7 +42,9 @@ export interface ManifestEndpoint {
42
42
  httpsTrigger?: {
43
43
  invoker?: string[];
44
44
  };
45
- callableTrigger?: Record<string, never>;
45
+ callableTrigger?: {
46
+ genkitAction?: string;
47
+ };
46
48
  eventTrigger?: {
47
49
  eventFilters: Record<string, string | Expression<string>>;
48
50
  eventFilterPathPatterns?: Record<string, string | Expression<string>>;
@@ -60,7 +60,7 @@ export interface FirebaseAlertOptions extends options.EventHandlerOptions {
60
60
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
61
61
  * function depends on the type of function: Event handling functions have a
62
62
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
63
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
63
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
64
64
  * timeout of 1,800s (30 minutes)
65
65
  */
66
66
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -83,7 +83,7 @@ export interface AppDistributionOptions extends options.EventHandlerOptions {
83
83
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
84
84
  * function depends on the type of function: Event handling functions have a
85
85
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
86
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
86
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
87
87
  * timeout of 1,800s (30 minutes)
88
88
  */
89
89
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -149,7 +149,7 @@ export interface CrashlyticsOptions extends options.EventHandlerOptions {
149
149
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
150
150
  * function depends on the type of function: Event handling functions have a
151
151
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
152
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
152
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
153
153
  * timeout of 1,800s (30 minutes)
154
154
  */
155
155
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -48,7 +48,7 @@ export interface EventarcTriggerOptions extends options.EventHandlerOptions {
48
48
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
49
49
  * function depends on the type of function: Event handling functions have a
50
50
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
51
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
51
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
52
52
  * timeout of 1,800s (30 minutes)
53
53
  */
54
54
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -1,12 +1,12 @@
1
1
  import * as express from "express";
2
2
  import { ResetValue } from "../../common/options";
3
- import { CallableRequest, CallableProxyResponse, FunctionsErrorCode, HttpsError, Request } from "../../common/providers/https";
3
+ import { CallableRequest, CallableResponse, FunctionsErrorCode, HttpsError, Request, AuthData } from "../../common/providers/https";
4
4
  import { ManifestEndpoint } from "../../runtime/manifest";
5
5
  import { GlobalOptions, SupportedRegion } from "../options";
6
6
  import { Expression } from "../../params";
7
7
  import { SecretParam } from "../../params/types";
8
8
  import * as options from "../options";
9
- export { Request, CallableRequest, FunctionsErrorCode, HttpsError };
9
+ export { Request, CallableRequest, CallableResponse, FunctionsErrorCode, HttpsError };
10
10
  /**
11
11
  * Options that can be set on an onRequest HTTPS function.
12
12
  */
@@ -35,7 +35,7 @@ export interface HttpsOptions extends Omit<GlobalOptions, "region" | "enforceApp
35
35
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
36
36
  * function depends on the type of function: Event handling functions have a
37
37
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
38
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
38
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
39
39
  * timeout of 1,800s (30 minutes)
40
40
  */
41
41
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -101,7 +101,7 @@ export interface HttpsOptions extends Omit<GlobalOptions, "region" | "enforceApp
101
101
  /**
102
102
  * Options that can be set on a callable HTTPS function.
103
103
  */
104
- export interface CallableOptions extends HttpsOptions {
104
+ export interface CallableOptions<T = any> extends HttpsOptions {
105
105
  /**
106
106
  * Determines whether Firebase AppCheck is enforced.
107
107
  * When true, requests with invalid tokens autorespond with a 401
@@ -139,7 +139,22 @@ export interface CallableOptions extends HttpsOptions {
139
139
  * Defaults to 30 seconds.
140
140
  */
141
141
  heartbeatSeconds?: number | null;
142
+ /**
143
+ * Callback for whether a request is authorized.
144
+ *
145
+ * Designed to allow reusable auth policies to be passed as an options object. Two built-in reusable policies exist:
146
+ * isSignedIn and hasClaim.
147
+ */
148
+ authPolicy?: (auth: AuthData | null, data: T) => boolean | Promise<boolean>;
142
149
  }
150
+ /**
151
+ * An auth policy that requires a user to be signed in.
152
+ */
153
+ export declare const isSignedIn: () => (auth: AuthData | null) => boolean;
154
+ /**
155
+ * An auth policy that requires a user to be both signed in and have a specific claim (optionally with a specific value)
156
+ */
157
+ export declare const hasClaim: (claim: string, value?: string) => (auth: AuthData | null) => boolean;
143
158
  /**
144
159
  * Handles HTTPS requests.
145
160
  */
@@ -156,12 +171,16 @@ res: express.Response) => void | Promise<void>) & {
156
171
  /**
157
172
  * Creates a callable method for clients to call using a Firebase SDK.
158
173
  */
159
- export interface CallableFunction<T, Return> extends HttpsFunction {
174
+ export interface CallableFunction<T, Return, Stream = unknown> extends HttpsFunction {
160
175
  /** Executes the handler function with the provided data as input. Used for unit testing.
161
176
  * @param data - An input for the handler function.
162
177
  * @returns The output of the handler function.
163
178
  */
164
- run(data: CallableRequest<T>): Return;
179
+ run(request: CallableRequest<T>): Return;
180
+ stream(request: CallableRequest<T>, response: CallableResponse<Stream>): {
181
+ stream: AsyncIterable<Stream>;
182
+ output: Return;
183
+ };
165
184
  }
166
185
  /**
167
186
  * Handles HTTPS requests.
@@ -182,10 +201,33 @@ export declare function onRequest(handler: (request: Request, response: express.
182
201
  * @param handler - A function that takes a {@link https.CallableRequest}.
183
202
  * @returns A function that you can export and deploy.
184
203
  */
185
- export declare function onCall<T = any, Return = any | Promise<any>>(opts: CallableOptions, handler: (request: CallableRequest<T>, response?: CallableProxyResponse) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
204
+ export declare function onCall<T = any, Return = any | Promise<any>, Stream = unknown>(opts: CallableOptions<T>, handler: (request: CallableRequest<T>, response?: CallableResponse<Stream>) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>, Stream>;
186
205
  /**
187
206
  * Declares a callable method for clients to call using a Firebase SDK.
188
207
  * @param handler - A function that takes a {@link https.CallableRequest}.
189
208
  * @returns A function that you can export and deploy.
190
209
  */
191
- export declare function onCall<T = any, Return = any | Promise<any>>(handler: (request: CallableRequest<T>, response?: CallableProxyResponse) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
210
+ export declare function onCall<T = any, Return = any | Promise<any>, Stream = unknown>(handler: (request: CallableRequest<T>, response?: CallableResponse<Stream>) => Return): CallableFunction<T, Return extends Promise<unknown> ? Return : Promise<Return>>;
211
+ interface ZodType<T = any> {
212
+ __output: T;
213
+ }
214
+ interface GenkitRunOptions {
215
+ context?: any;
216
+ }
217
+ type GenkitAction<I extends ZodType = ZodType<any>, O extends ZodType = ZodType<any>, S extends ZodType = ZodType<any>> = {
218
+ run(input: I["__output"], options: GenkitRunOptions): Promise<{
219
+ result: O["__output"];
220
+ }>;
221
+ stream(input: I["__output"], options: GenkitRunOptions): {
222
+ stream: AsyncIterable<S["__output"]>;
223
+ output: Promise<O["__output"]>;
224
+ };
225
+ __action: {
226
+ name: string;
227
+ };
228
+ };
229
+ type ActionInput<F extends GenkitAction> = F extends GenkitAction<infer I extends ZodType, any, any> ? I["__output"] : never;
230
+ type ActionOutput<F extends GenkitAction> = F extends GenkitAction<any, infer O extends ZodType, any> ? O["__output"] : never;
231
+ type ActionStream<F extends GenkitAction> = F extends GenkitAction<any, any, infer S extends ZodType> ? S["__output"] : never;
232
+ export declare function onCallGenkit<A extends GenkitAction>(action: A): CallableFunction<ActionInput<A>, Promise<ActionOutput<A>>, ActionStream<A>>;
233
+ export declare function onCallGenkit<A extends GenkitAction>(opts: CallableOptions<ActionInput<A>>, flow: A): CallableFunction<ActionInput<A>, Promise<ActionOutput<A>>, ActionStream<A>>;
@@ -21,7 +21,7 @@
21
21
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
22
  // SOFTWARE.
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- exports.onCall = exports.onRequest = exports.HttpsError = void 0;
24
+ exports.onCallGenkit = exports.onCall = exports.onRequest = exports.hasClaim = exports.isSignedIn = exports.HttpsError = void 0;
25
25
  /**
26
26
  * Cloud functions to handle HTTPS request or callable RPCs.
27
27
  * @packageDocumentation
@@ -35,6 +35,25 @@ Object.defineProperty(exports, "HttpsError", { enumerable: true, get: function (
35
35
  const manifest_1 = require("../../runtime/manifest");
36
36
  const options = require("../options");
37
37
  const onInit_1 = require("../../common/onInit");
38
+ const logger = require("../../logger");
39
+ /**
40
+ * An auth policy that requires a user to be signed in.
41
+ */
42
+ const isSignedIn = () => (auth) => !!auth;
43
+ exports.isSignedIn = isSignedIn;
44
+ /**
45
+ * An auth policy that requires a user to be both signed in and have a specific claim (optionally with a specific value)
46
+ */
47
+ const hasClaim = (claim, value) => (auth) => {
48
+ if (!auth) {
49
+ return false;
50
+ }
51
+ if (!(claim in auth.token)) {
52
+ return false;
53
+ }
54
+ return !value || auth.token[claim] === value;
55
+ };
56
+ exports.hasClaim = hasClaim;
38
57
  function onRequest(optsOrHandler, handler) {
39
58
  let opts;
40
59
  if (arguments.length === 1) {
@@ -131,12 +150,13 @@ function onCall(optsOrHandler, handler) {
131
150
  origin = origin[0];
132
151
  }
133
152
  // fix the length of handler to make the call to handler consistent
134
- const fixedLen = (req, resp) => (0, onInit_1.withInit)(handler)(req, resp);
153
+ const fixedLen = (req, resp) => handler(req, resp);
135
154
  let func = (0, https_1.onCallHandler)({
136
155
  cors: { origin, methods: "POST" },
137
156
  enforceAppCheck: (_a = opts.enforceAppCheck) !== null && _a !== void 0 ? _a : options.getGlobalOptions().enforceAppCheck,
138
157
  consumeAppCheckToken: opts.consumeAppCheckToken,
139
158
  heartbeatSeconds: opts.heartbeatSeconds,
159
+ authPolicy: opts.authPolicy,
140
160
  }, fixedLen, "gcfv2");
141
161
  func = (0, trace_1.wrapTraceContext)((0, onInit_1.withInit)(func));
142
162
  Object.defineProperty(func, "__trigger", {
@@ -175,7 +195,48 @@ function onCall(optsOrHandler, handler) {
175
195
  },
176
196
  callableTrigger: {},
177
197
  };
198
+ // TODO: in the next major version, do auth/appcheck in these helper methods too.
178
199
  func.run = (0, onInit_1.withInit)(handler);
200
+ func.stream = () => {
201
+ return {
202
+ stream: {
203
+ next() {
204
+ return Promise.reject("Coming soon");
205
+ },
206
+ },
207
+ output: Promise.reject("Coming soon"),
208
+ };
209
+ };
179
210
  return func;
180
211
  }
181
212
  exports.onCall = onCall;
213
+ function onCallGenkit(optsOrAction, action) {
214
+ var _a;
215
+ let opts;
216
+ if (arguments.length === 2) {
217
+ opts = optsOrAction;
218
+ }
219
+ else {
220
+ opts = {};
221
+ action = optsOrAction;
222
+ }
223
+ if (!((_a = opts.secrets) === null || _a === void 0 ? void 0 : _a.length)) {
224
+ logger.debug(`Genkit function for ${action.__action.name} is not bound to any secret. This may mean that you are not storing API keys as a secret or that you are not binding your secret to this function. See https://firebase.google.com/docs/functions/config-env?gen=2nd#secret_parameters for more information.`);
225
+ }
226
+ const cloudFunction = onCall(opts, async (req, res) => {
227
+ const context = {};
228
+ (0, encoding_1.copyIfPresent)(context, req, "auth", "app", "instanceIdToken");
229
+ if (!req.acceptsStreaming) {
230
+ const { result } = await action.run(req.data, { context });
231
+ return result;
232
+ }
233
+ const { stream, output } = action.stream(req.data, { context });
234
+ for await (const chunk of stream) {
235
+ await res.sendChunk(chunk);
236
+ }
237
+ return output;
238
+ });
239
+ cloudFunction.__endpoint.callableTrigger.genkitAction = action.__action.name;
240
+ return cloudFunction;
241
+ }
242
+ exports.onCallGenkit = onCallGenkit;
@@ -46,7 +46,7 @@ export interface BlockingOptions {
46
46
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
47
47
  * function depends on the type of function: Event handling functions have a
48
48
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
49
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
49
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
50
50
  * timeout of 1,800s (30 minutes)
51
51
  */
52
52
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -104,7 +104,7 @@ export interface PubSubOptions extends options.EventHandlerOptions {
104
104
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
105
105
  * function depends on the type of function: Event handling functions have a
106
106
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
107
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
107
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
108
108
  * timeout of 1,800s (30 minutes)
109
109
  */
110
110
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -180,7 +180,7 @@ export interface StorageOptions extends options.EventHandlerOptions {
180
180
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
181
181
  * function depends on the type of function: Event handling functions have a
182
182
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
183
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
183
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
184
184
  * timeout of 1,800s (30 minutes)
185
185
  */
186
186
  timeoutSeconds?: number | Expression<number> | ResetValue;
@@ -39,7 +39,7 @@ export interface TaskQueueOptions extends options.EventHandlerOptions {
39
39
  * The minimum timeout for a gen 2 function is 1s. The maximum timeout for a
40
40
  * function depends on the type of function: Event handling functions have a
41
41
  * maximum timeout of 540s (9 minutes). HTTPS and callable functions have a
42
- * maximum timeout of 36,00s (1 hour). Task queue functions have a maximum
42
+ * maximum timeout of 3,600s (1 hour). Task queue functions have a maximum
43
43
  * timeout of 1,800s (30 minutes)
44
44
  */
45
45
  timeoutSeconds?: number | Expression<number> | ResetValue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-functions",
3
- "version": "6.1.2",
3
+ "version": "6.3.0",
4
4
  "description": "Firebase SDK for Cloud Functions",
5
5
  "keywords": [
6
6
  "firebase",
@@ -300,6 +300,7 @@
300
300
  "eslint-plugin-jsdoc": "^39.2.9",
301
301
  "eslint-plugin-prettier": "^4.0.0",
302
302
  "firebase-admin": "^13.0.0",
303
+ "genkit": "^1.0.0-rc.4",
303
304
  "js-yaml": "^3.13.1",
304
305
  "jsdom": "^16.2.1",
305
306
  "jsonwebtoken": "^9.0.0",