diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5f3d22f..bc2ff3550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,2 +1,4 @@ - Fixes access on deeply nested, nonexistent property. (#1432) - Add IteratedDataSnapshot interface to match with firebase admin v12 (#1517). +- Make bucket parameterizeable in storage functions (#1518) +- Introduce helper library for select and multi-select input (#1518) diff --git a/src/params/index.ts b/src/params/index.ts index 550ab9db9..6f19c6d4f 100644 --- a/src/params/index.ts +++ b/src/params/index.ts @@ -38,6 +38,16 @@ import { InternalExpression, } from "./types"; +export { + BUCKET_PICKER, + TextInput, + SelectInput, + SelectOptions, + MultiSelectInput, + select, + multiSelect, +} from "./types"; + export { ParamOptions, Expression }; type SecretOrExpr = Param | SecretParam; diff --git a/src/params/types.ts b/src/params/types.ts index 1eaf553f2..ca6040059 100644 --- a/src/params/types.ts +++ b/src/params/types.ts @@ -171,11 +171,53 @@ export class CompareExpression< /** @hidden */ type ParamValueType = "string" | "list" | "boolean" | "int" | "float" | "secret"; +/** Create a select input from a series of values. */ +export function select(options: T[]): SelectInput; + +/** Create a select input from a map of labels to vaues. */ +export function select(optionsWithLabels: Record): SelectInput; + +/** Create a select input from a series of values or a map of labels to values */ +export function select(options: T[] | Record): SelectInput { + let wireOpts: SelectOptions[]; + if (Array.isArray(options)) { + wireOpts = options.map((opt) => ({ value: opt })); + } else { + wireOpts = Object.entries(options).map(([label, value]) => ({ label, value })); + } + return { + select: { + options: wireOpts, + }, + }; +} + +/** Create a multi-select input from a series of values. */ +export function multiSelect(options: string[]): MultiSelectInput; + +/** Create a multi-select input from map of labels to values. */ +export function multiSelect(options: Record): MultiSelectInput; + +/** Create a multi-select input from a series of values or map of labels to values. */ +export function multiSelect(options: string[] | Record): MultiSelectInput { + let wireOpts: SelectOptions[]; + if (Array.isArray(options)) { + wireOpts = options.map((opt) => ({ value: opt })); + } else { + wireOpts = Object.entries(options).map(([label, value]) => ({ label, value })); + } + return { + multiSelect: { + options: wireOpts, + }, + }; +} + type ParamInput = - | { text: TextInput } - | { select: SelectInput } - | { multiSelect: MultiSelectInput } - | { resource: ResourceInput }; + | TextInput + | SelectInput + | (T extends string[] ? MultiSelectInput : never) + | (T extends string ? ResourceInput : never); /** * Specifies that a Param's value should be determined by prompting the user @@ -184,18 +226,20 @@ type ParamInput = */ // eslint-disable-next-line @typescript-eslint/no-unused-vars export interface TextInput { - example?: string; - /** - * A regular expression (or an escaped string to compile into a regular - * expression) which the prompted text must satisfy; the prompt will retry - * until input matching the regex is provided. - */ - validationRegex?: string | RegExp; - /** - * A custom error message to display when retrying the prompt based on input - * failing to conform to the validationRegex, - */ - validationErrorMessage?: string; + text: { + example?: string; + /** + * A regular expression (or an escaped string to compile into a regular + * expression) which the prompted text must satisfy; the prompt will retry + * until input matching the regex is provided. + */ + validationRegex?: string | RegExp; + /** + * A custom error message to display when retrying the prompt based on input + * failing to conform to the validationRegex, + */ + validationErrorMessage?: string; + }; } /** @@ -205,16 +249,24 @@ export interface TextInput { */ export interface ResourceInput { resource: { - type: string; + type: "storage.googleapis.com/Bucket"; }; } +export const BUCKET_PICKER: ResourceInput = { + resource: { + type: "storage.googleapis.com/Bucket", + }, +}; + /** * Specifies that a Param's value should be determined by having the user select * from a list of pre-canned options interactively at deploy-time. */ export interface SelectInput { - options: Array>; + select: { + options: Array>; + }; } /** @@ -223,7 +275,9 @@ export interface SelectInput { * Will result in errors if used on Params of type other than string[]. */ export interface MultiSelectInput { - options: Array>; + multiSelect: { + options: Array>; + }; } /** diff --git a/src/v2/providers/storage.ts b/src/v2/providers/storage.ts index 451a257b5..e66f6b813 100644 --- a/src/v2/providers/storage.ts +++ b/src/v2/providers/storage.ts @@ -201,7 +201,7 @@ export const metadataUpdatedEvent = "google.cloud.storage.object.v1.metadataUpda /** StorageOptions extend EventHandlerOptions with a bucket name */ export interface StorageOptions extends options.EventHandlerOptions { /** The name of the bucket containing this object. */ - bucket?: string; + bucket?: string | Expression; /** * If true, do not deploy or emulate this function. @@ -324,7 +324,7 @@ export function onObjectArchived( * @param handler - Event handler which is run every time a Google Cloud Storage archival occurs. */ export function onObjectArchived( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -352,7 +352,11 @@ export function onObjectArchived( * @param handler - Event handler which is run every time a Google Cloud Storage archival occurs. */ export function onObjectArchived( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(archivedEvent, bucketOrOptsOrHandler, handler); @@ -384,7 +388,7 @@ export function onObjectFinalized( * @param handler - Event handler which is run every time a Google Cloud Storage object creation occurs. */ export function onObjectFinalized( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -416,7 +420,11 @@ export function onObjectFinalized( * @param handler - Event handler which is run every time a Google Cloud Storage object creation occurs. */ export function onObjectFinalized( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(finalizedEvent, bucketOrOptsOrHandler, handler); @@ -450,7 +458,7 @@ export function onObjectDeleted( * @param handler - Event handler which is run every time a Google Cloud Storage object deletion occurs. */ export function onObjectDeleted( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -484,7 +492,11 @@ export function onObjectDeleted( * @param handler - Event handler which is run every time a Google Cloud Storage object deletion occurs. */ export function onObjectDeleted( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(deletedEvent, bucketOrOptsOrHandler, handler); @@ -509,7 +521,7 @@ export function onObjectMetadataUpdated( * @param handler - Event handler which is run every time a Google Cloud Storage object metadata update occurs. */ export function onObjectMetadataUpdated( - bucket: string, + bucket: string | Expression, handler: (event: StorageEvent) => any | Promise ): CloudFunction; @@ -533,7 +545,11 @@ export function onObjectMetadataUpdated( * @param handler - Event handler which is run every time a Google Cloud Storage object metadata update occurs. */ export function onObjectMetadataUpdated( - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler?: (event: StorageEvent) => any | Promise ): CloudFunction { return onOperation(metadataUpdatedEvent, bucketOrOptsOrHandler, handler); @@ -542,7 +558,11 @@ export function onObjectMetadataUpdated( /** @internal */ export function onOperation( eventType: string, - bucketOrOptsOrHandler: string | StorageOptions | ((event: StorageEvent) => any | Promise), + bucketOrOptsOrHandler: + | string + | Expression + | StorageOptions + | ((event: StorageEvent) => any | Promise), handler: (event: StorageEvent) => any | Promise ): CloudFunction { if (typeof bucketOrOptsOrHandler === "function") { @@ -616,11 +636,12 @@ export function onOperation( /** @internal */ export function getOptsAndBucket( - bucketOrOpts: string | StorageOptions -): [options.EventHandlerOptions, string] { - let bucket: string; + bucketOrOpts: string | Expression | StorageOptions +): [options.EventHandlerOptions, string | Expression] { + let bucket: string | Expression; let opts: options.EventHandlerOptions; - if (typeof bucketOrOpts === "string") { + // If bucket is a string or Expression + if (typeof bucketOrOpts === "string" || "value" in bucketOrOpts) { bucket = bucketOrOpts; opts = {}; } else { @@ -635,7 +656,7 @@ export function getOptsAndBucket( " by providing bucket name directly in the event handler or by setting process.env.FIREBASE_CONFIG." ); } - if (!/^[a-z\d][a-z\d\\._-]{1,230}[a-z\d]$/.test(bucket)) { + if (typeof bucket === "string" && !/^[a-z\d][a-z\d\\._-]{1,230}[a-z\d]$/.test(bucket)) { throw new Error(`Invalid bucket name ${bucket}`); }