Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
1 change: 0 additions & 1 deletion packages/dev/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -555,7 +555,6 @@ export class NetlifyDev {
projectRoot: this.#projectRoot,
settings: {},
siteId: this.#siteID,
timeouts: {},
userFunctionsPath: userFunctionsPathExists ? userFunctionsPath : undefined,
})
}
Expand Down
152 changes: 152 additions & 0 deletions packages/functions/dev/src/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { describe, expect, test } from 'vitest'

import { SYNCHRONOUS_FUNCTION_TIMEOUT, BACKGROUND_FUNCTION_TIMEOUT } from '@netlify/functions'
import { FunctionsRegistry } from './registry.js'

describe('FunctionsRegistry timeout configuration', () => {
test('uses default timeouts when no config or override provided', () => {
const registry = new FunctionsRegistry({
config: {},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
})

expect(registry.timeouts).toEqual({
syncFunctions: SYNCHRONOUS_FUNCTION_TIMEOUT,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})

test('uses functions_timeout from siteInfo for sync functions only', () => {
const registry = new FunctionsRegistry({
config: {
siteInfo: {
functions_timeout: 60,
},
},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
})

expect(registry.timeouts).toEqual({
syncFunctions: 60,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})

test('uses functions_config.timeout from siteInfo for sync functions only', () => {
const registry = new FunctionsRegistry({
config: {
siteInfo: {
functions_config: {
timeout: 45,
},
},
},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
})

expect(registry.timeouts).toEqual({
syncFunctions: 45,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})

test('prefers functions_timeout over functions_config.timeout for sync functions', () => {
const registry = new FunctionsRegistry({
config: {
siteInfo: {
functions_timeout: 60,
functions_config: {
timeout: 45,
},
},
},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
})

expect(registry.timeouts).toEqual({
syncFunctions: 60,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})

test('uses override timeouts when provided', () => {
const registry = new FunctionsRegistry({
config: {
siteInfo: {
functions_timeout: 60,
},
},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
timeouts: {
syncFunctions: 120,
backgroundFunctions: 1800,
},
})

expect(registry.timeouts).toEqual({
syncFunctions: 120,
backgroundFunctions: 1800,
})
})

test('allows partial override of timeouts', () => {
const registry = new FunctionsRegistry({
config: {
siteInfo: {
functions_timeout: 60,
},
},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
timeouts: {
syncFunctions: 120,
},
})

expect(registry.timeouts).toEqual({
syncFunctions: 120,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})

test('falls back to defaults when siteInfo is undefined', () => {
const registry = new FunctionsRegistry({
config: {
siteInfo: undefined,
},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
})

expect(registry.timeouts).toEqual({
syncFunctions: SYNCHRONOUS_FUNCTION_TIMEOUT,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})

test('falls back to defaults when config is empty object', () => {
const registry = new FunctionsRegistry({
config: {},
destPath: '/tmp/test',
projectRoot: '/tmp/project',
settings: {},
})

expect(registry.timeouts).toEqual({
syncFunctions: SYNCHRONOUS_FUNCTION_TIMEOUT,
backgroundFunctions: BACKGROUND_FUNCTION_TIMEOUT,
})
})
})
14 changes: 12 additions & 2 deletions packages/functions/dev/src/registry.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, I thought we were missing the logic to pass from FunctionsHandler to the registry but it's implicit in this spread:

constructor({ accountId, geolocation, siteId, userFunctionsPath, ...registryOptions }: FunctionsHandlerOptions) {
const registry = new FunctionsRegistry(registryOptions)
👍🏼

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { env } from 'node:process'

import type { EnvironmentContext as BlobsContext } from '@netlify/blobs'
import { DevEventHandler, watchDebounced } from '@netlify/dev-utils'
import { SYNCHRONOUS_FUNCTION_TIMEOUT, BACKGROUND_FUNCTION_TIMEOUT } from '@netlify/functions'
import { ListedFunction, listFunctions, Manifest } from '@netlify/zip-it-and-ship-it'
import extractZip from 'extract-zip'

Expand Down Expand Up @@ -37,7 +38,7 @@ export interface FunctionRegistryOptions {
manifest?: Manifest
projectRoot: string
settings: any
timeouts: any
timeouts?: { syncFunctions?: number; backgroundFunctions?: number }
watch?: boolean
}

Expand Down Expand Up @@ -100,7 +101,16 @@ export class FunctionsRegistry {
this.handleEvent = eventHandler ?? (() => {})
this.internalFunctionsPath = internalFunctionsPath
this.projectRoot = projectRoot
this.timeouts = timeouts

// Calculate timeouts from config if not provided as override
const siteTimeout = config?.siteInfo?.functions_timeout ?? config?.siteInfo?.functions_config?.timeout
this.timeouts = {
syncFunctions: timeouts?.syncFunctions ?? siteTimeout ?? SYNCHRONOUS_FUNCTION_TIMEOUT,
// NOTE: This isn't documented, but the generically named "functions timeout" config fields only
// apply to synchronous Netlify Functions.
backgroundFunctions: timeouts?.backgroundFunctions ?? BACKGROUND_FUNCTION_TIMEOUT,
}

this.settings = settings
this.watch = watch === true

Expand Down
10 changes: 10 additions & 0 deletions packages/functions/prod/src/lib/consts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { describe, expect, test } from 'vitest'

import { BACKGROUND_FUNCTION_TIMEOUT, SYNCHRONOUS_FUNCTION_TIMEOUT } from './consts.js'

describe('Function timeout constants', () => {
test('exports correct timeout values', () => {
expect(SYNCHRONOUS_FUNCTION_TIMEOUT).toBe(30)
expect(BACKGROUND_FUNCTION_TIMEOUT).toBe(900)
})
})
19 changes: 18 additions & 1 deletion packages/functions/prod/src/lib/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,21 @@ const HTTP_STATUS_METHOD_NOT_ALLOWED = 405
const HTTP_STATUS_OK = 200
const METADATA_VERSION = 1

export { BUILDER_FUNCTIONS_FLAG, HTTP_STATUS_METHOD_NOT_ALLOWED, HTTP_STATUS_OK, METADATA_VERSION }
/**
* Default timeout for synchronous functions in seconds
*/
const SYNCHRONOUS_FUNCTION_TIMEOUT = 30

/**
* Default timeout for background functions in seconds
*/
const BACKGROUND_FUNCTION_TIMEOUT = 900

export {
BUILDER_FUNCTIONS_FLAG,
HTTP_STATUS_METHOD_NOT_ALLOWED,
HTTP_STATUS_OK,
METADATA_VERSION,
SYNCHRONOUS_FUNCTION_TIMEOUT,
BACKGROUND_FUNCTION_TIMEOUT,
}
1 change: 1 addition & 0 deletions packages/functions/prod/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export { builder } from './lib/builder.js'
export { purgeCache } from './lib/purge_cache.js'
export { schedule } from './lib/schedule.js'
export { stream } from './lib/stream.js'
export { SYNCHRONOUS_FUNCTION_TIMEOUT, BACKGROUND_FUNCTION_TIMEOUT } from './lib/consts.js'
export * from './function/index.js'
1 change: 1 addition & 0 deletions packages/types/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export type { Context } from './lib/context/context.js'
export type { Cookie } from './lib/context/cookies.js'
export type { EnvironmentVariables } from './lib/environment-variables.js'
export type { NetlifyGlobal } from './lib/globals.js'
export type { Site } from './lib/context/site.js'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean this up, we don't need it

Suggested change
export type { Site } from './lib/context/site.js'

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's fine... it's unrelated but it seems reasonable.