Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 60 additions & 21 deletions spec/function-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('FunctionBuilder', () => {
delete process.env.GCLOUD_PROJECT;
});

it('should allow region to be set', () => {
it('should allow supported region to be set', () => {
let fn = functions
.region('us-east1')
.auth.user()
Expand All @@ -42,7 +42,7 @@ describe('FunctionBuilder', () => {
expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
});

it('should allow multiple regions to be set', () => {
it('should allow multiple supported regions to be set', () => {
let fn = functions
.region('us-east1', 'us-central1')
.auth.user()
Expand All @@ -51,7 +51,30 @@ describe('FunctionBuilder', () => {
expect(fn.__trigger.regions).to.deep.equal(['us-east1', 'us-central1']);
});

it('should allow runtime options to be set', () => {
it('should allow all supported regions to be set', () => {
let fn = functions
.region(
'us-central1',
'us-east1',
'europe-west1',
'europe-west2',
'asia-east2',
'asia-northeast1'
)
.auth.user()
.onCreate(user => user);

expect(fn.__trigger.regions).to.deep.equal([
'us-central1',
'us-east1',
'europe-west1',
'europe-west2',
'asia-east2',
'asia-northeast1',
]);
});

it('should allow valid runtime options to be set', () => {
let fn = functions
.runWith({
timeoutSeconds: 90,
Expand All @@ -64,95 +87,111 @@ describe('FunctionBuilder', () => {
expect(fn.__trigger.timeout).to.deep.equal('90s');
});

it('should allow both region and runtime options to be set', () => {
it('should allow both supported region and valid runtime options to be set', () => {
let fn = functions
.region('us-east1')
.region('europe-west2')
.runWith({
timeoutSeconds: 90,
memory: '256MB',
})
.auth.user()
.onCreate(user => user);

expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
expect(fn.__trigger.regions).to.deep.equal(['europe-west2']);
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
expect(fn.__trigger.timeout).to.deep.equal('90s');
});

it('should allow both region and runtime options to be set (reverse order)', () => {
it('should allow both valid runtime options and supported region to be set in reverse order', () => {
let fn = functions
.runWith({
timeoutSeconds: 90,
memory: '256MB',
})
.region('us-east1')
.region('europe-west1')
.auth.user()
.onCreate(user => user);

expect(fn.__trigger.regions).to.deep.equal(['us-east1']);
expect(fn.__trigger.regions).to.deep.equal(['europe-west1']);
expect(fn.__trigger.availableMemoryMb).to.deep.equal(256);
expect(fn.__trigger.timeout).to.deep.equal('90s');
});

it('should throw an error if user chooses an unsupported memory allocation', () => {
it('should fail if valid runtime options but unsupported region are set (reverse order)', () => {
expect(() => {
functions
.runWith({ timeoutSeconds: 90, memory: '256MB' })
.region('unsupported');
}).to.throw(Error);
});

it('should fail if supported region but invalid runtime options are set (reverse order)', () => {
expect(() => {
functions
.region('asia-northeast1')
.runWith({ timeoutSeconds: 600, memory: '256MB' });
}).to.throw(Error, 'TimeoutSeconds');
});

it('should throw an error if user chooses an invalid memory allocation', () => {
expect(() => {
return functions.runWith({
memory: 'unsupported',
} as any);
}).to.throw(Error);
}).to.throw(Error, 'memory');

expect(() => {
return functions.region('us-east1').runWith({
memory: 'unsupported',
} as any);
}).to.throw(Error);
}).to.throw(Error, 'memory');
});

it('should throw an error if user chooses an invalid timeoutSeconds', () => {
expect(() => {
return functions.runWith({
timeoutSeconds: 1000000,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'TimeoutSeconds');

expect(() => {
return functions.region('us-east1').runWith({
return functions.region('asia-east2').runWith({
timeoutSeconds: 1000000,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'TimeoutSeconds');
});

it('should throw an error if user chooses an invalid region', () => {
expect(() => {
return functions.region('unsupported');
}).to.throw(Error);
}).to.throw(Error, 'region');

expect(() => {
return functions.region('unsupported').runWith({
timeoutSeconds: 500,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'region');

expect(() => {
return functions.region('unsupported', 'us-east1');
}).to.throw(Error);
}).to.throw(Error, 'region');

expect(() => {
return functions.region('unsupported', 'us-east1').runWith({
timeoutSeconds: 500,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'region');
});

it('should throw an error if user chooses no region when using .region()', () => {
expect(() => {
return functions.region();
}).to.throw(Error);
}).to.throw(Error, 'at least one region');

expect(() => {
return functions.region().runWith({
timeoutSeconds: 500,
} as any);
}).to.throw(Error);
}).to.throw(Error, 'at least one region');
});
});
164 changes: 94 additions & 70 deletions src/function-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,72 +32,112 @@ import * as https from './providers/https';
import * as pubsub from './providers/pubsub';
import * as remoteConfig from './providers/remoteConfig';
import * as storage from './providers/storage';
import {
CloudFunction,
EventContext,
Runnable,
TriggerAnnotated,
Schedule,
} from './cloud-functions';
import { CloudFunction, EventContext, Schedule } from './cloud-functions';

/**
* Configure the regions that the function is deployed to.
* @param regions One of more region strings.
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
* List of all regions supported by Cloud Functions.
*/
export function region(...regions: string[]) {
if (!regions.length) {
throw new Error('You must specify at least one region');
const SUPPORTED_REGIONS = [
'us-central1',
'us-east1',
'europe-west1',
'europe-west2',
'asia-east2',
'asia-northeast1',
];

/**
* List of available memory options supported by Cloud Functions.
*/
const VALID_MEMORY_OPTS = ['128MB', '256MB', '512MB', '1GB', '2GB'];

// Adding this memory type here to error on compile for TS users.
// Unfortunately I have not found a way to merge this with VALID_MEMORY_OPS
// without it being super ugly. But here they are right next to each other at least.
type memory = '128MB' | '256MB' | '512MB' | '1GB' | '2GB';

/**
* Cloud Functions max timeout value.
*/
const MAX_TIMEOUT_SECONDS = 540;

/**
* Assert that the runtime options passed in are valid.
* @param runtimeOptions object containing memory and timeout information.
* @throws { Error } Memory and TimeoutSeconds values must be valid.
*/
function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
if (
runtimeOptions.memory &&
!_.includes(VALID_MEMORY_OPTS, runtimeOptions.memory)
) {
throw new Error(
`The only valid memory allocation values are: ${VALID_MEMORY_OPTS.join(
', '
)}`
);
}
if (
_.difference(regions, [
'us-central1',
'us-east1',
'europe-west1',
'asia-northeast1',
]).length
runtimeOptions.timeoutSeconds > MAX_TIMEOUT_SECONDS ||
runtimeOptions.timeoutSeconds < 0
) {
throw new Error(
"The only valid regions are 'us-central1', 'us-east1', 'europe-west1', and 'asia-northeast1'"
`TimeoutSeconds must be between 0 and ${MAX_TIMEOUT_SECONDS}`
);
}
return new FunctionBuilder({ regions });
return true;
}

/**
* Assert regions specified are valid.
* @param regions list of regions.
* @throws { Error } Regions must be in list of supported regions.
*/
function assertRegionsAreValid(regions: string[]): boolean {
if (!regions.length) {
throw new Error('You must specify at least one region');
}
if (_.difference(regions, SUPPORTED_REGIONS).length) {
throw new Error(
`The only valid regions are: ${SUPPORTED_REGIONS.join(', ')}`
);
}
return true;
}

/**
* Configure the regions that the function is deployed to.
* @param regions One of more region strings.
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
*/
export function region(...regions: string[]): FunctionBuilder {
if (assertRegionsAreValid(regions)) {
return new FunctionBuilder({ regions });
}
}

/**
* Configure runtime options for the function.
* @param runtimeOptions Object with 2 optional fields:
* 1. `timeoutSeconds`: timeout for the function in seconds, possible values are 0 to 540
* 2. `memory`: amount of memory to allocate to the function,
* possible values are: '128MB', '256MB', '512MB', '1GB', and '2GB'.
*/
export function runWith(runtimeOptions: {
timeoutSeconds?: number;
memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
}) {
if (
runtimeOptions.memory &&
!_.includes(
['128MB', '256MB', '512MB', '1GB', '2GB'],
runtimeOptions.memory
)
) {
throw new Error(
"The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'"
);
}
if (
runtimeOptions.timeoutSeconds > 540 ||
runtimeOptions.timeoutSeconds < 0
) {
throw new Error('TimeoutSeconds must be between 0 and 540');
export function runWith(runtimeOptions: RuntimeOptions): FunctionBuilder {
if (assertRuntimeOptionsValid(runtimeOptions)) {
return new FunctionBuilder(runtimeOptions);
}
return new FunctionBuilder(runtimeOptions);
}

export interface RuntimeOptions {
timeoutSeconds?: number;
memory?: memory;
}

export interface DeploymentOptions {
regions?: string[];
timeoutSeconds?: number;
memory?: string;
memory?: memory;
schedule?: Schedule;
}

Expand All @@ -109,10 +149,12 @@ export class FunctionBuilder {
* @param regions One or more region strings.
* For example: `functions.region('us-east1')` or `functions.region('us-east1', 'us-central1')`
*/
region = (...regions: string[]) => {
this.options.regions = regions;
return this;
};
region(...regions: string[]): FunctionBuilder {
if (assertRegionsAreValid(regions)) {
this.options.regions = regions;
return this;
}
}

/**
* Configure runtime options for the function.
Expand All @@ -121,30 +163,12 @@ export class FunctionBuilder {
* 2. memory: amount of memory to allocate to the function, possible values are:
* '128MB', '256MB', '512MB', '1GB', and '2GB'.
*/
runWith = (runtimeOptions: {
timeoutSeconds?: number;
memory?: '128MB' | '256MB' | '512MB' | '1GB' | '2GB';
}) => {
if (
runtimeOptions.memory &&
!_.includes(
['128MB', '256MB', '512MB', '1GB', '2GB'],
runtimeOptions.memory
)
) {
throw new Error(
"The only valid memory allocation values are: '128MB', '256MB', '512MB', '1GB', and '2GB'"
);
}
if (
runtimeOptions.timeoutSeconds > 540 ||
runtimeOptions.timeoutSeconds < 0
) {
throw new Error('TimeoutSeconds must be between 0 and 540');
runWith(runtimeOptions: RuntimeOptions): FunctionBuilder {
if (assertRuntimeOptionsValid(runtimeOptions)) {
this.options = _.assign(this.options, runtimeOptions);
return this;
}
this.options = _.assign(this.options, runtimeOptions);
return this;
};
}

get https() {
return {
Expand Down