@vercel/schedules 0.0.0-alpha.7 → 0.0.0-alpha.9

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/README.md CHANGED
@@ -14,6 +14,38 @@ await Schedules.create({
14
14
  });
15
15
  ```
16
16
 
17
+ ## Schedule in a timezone
18
+
19
+ Set `timezone` to an IANA timezone to evaluate a cron expression in local time. For example, run a schedule at 9 AM Pacific time each day:
20
+
21
+ ```ts
22
+ import { Schedules } from '@vercel/schedules';
23
+
24
+ await Schedules.create({
25
+ name: 'daily-cleanup',
26
+ target: { topic: 'scheduled-cleanup' },
27
+ expression: { type: 'cron', cron: '0 9 * * *' },
28
+ timezone: 'America/Los_Angeles',
29
+ });
30
+ ```
31
+
32
+ Omitting `timezone` uses UTC. Named timezones follow daylight-saving changes.
33
+
34
+ For a one-time schedule, pass `at` in `YYYY-MM-DDTHH:mm:ss` format. The datetime has no timezone information; set `timezone` separately or omit it to use UTC:
35
+
36
+ ```ts
37
+ await Schedules.create({
38
+ name: 'one-time-cleanup',
39
+ target: { topic: 'scheduled-cleanup' },
40
+ expression: { type: 'single', at: '2026-09-16T09:00:00' },
41
+ timezone: 'America/Los_Angeles',
42
+ });
43
+ ```
44
+
45
+ This schedules one invocation at 9 AM Pacific on September 16. The `at` field does not accept `Z`, numeric offsets, or fractional seconds.
46
+
47
+ Schedules returned by `get()` and `list()` include the IANA timezone.
48
+
17
49
  Update schedule configuration by name and optional namespace:
18
50
 
19
51
  ```ts
@@ -26,6 +58,14 @@ await Schedules.update({
26
58
 
27
59
  Use `Schedules.enable` and `Schedules.disable` to change schedule state.
28
60
 
61
+ Invoke a schedule immediately:
62
+
63
+ ```ts
64
+ await Schedules.invoke({ name: 'cleanup' });
65
+ ```
66
+
67
+ The method returns once the asynchronous invocation has been accepted.
68
+
29
69
  ## Handle a scheduled function
30
70
 
31
71
  Use `handleSchedule` in a function targeted by a schedule:
@@ -34,31 +74,22 @@ Use `handleSchedule` in a function targeted by a schedule:
34
74
  // api/cleanup.ts
35
75
  import { handleSchedule } from '@vercel/schedules';
36
76
 
37
- interface CleanupPayload {
38
- maxAgeDays: number;
39
- }
40
-
41
- export const POST = handleSchedule<CleanupPayload>(
42
- async (payload, metadata) => {
43
- console.log(`Running ${metadata.name}, fired at ${metadata.firedAt}`);
44
-
45
- if (payload) {
46
- await deleteExpiredRecords(payload.maxAgeDays);
47
- }
48
- },
49
- );
77
+ export const POST = handleSchedule(async (metadata) => {
78
+ console.log(`Running ${metadata.name}, scheduled at ${metadata.scheduledAt}`);
79
+ await deleteExpiredRecords();
80
+ });
50
81
  ```
51
82
 
52
- The handler receives the configured schedule payload and metadata about the current firing. It returns a `200` response when the callback completes successfully.
83
+ The handler receives metadata about the current firing. It returns a `200` response when the callback completes successfully.
53
84
 
54
85
  For a custom client:
55
86
 
56
87
  ```ts
57
88
  import { SchedulesClient } from '@vercel/schedules';
58
89
 
59
- const schedules = new SchedulesClient({ baseUrl: 'https://vss-server.vercel.sh' });
90
+ const schedules = new SchedulesClient();
60
91
  ```
61
92
 
62
- The default client uses `https://vss-server.vercel.sh`, overridable via `VERCEL_SCHEDULE_BASE_URL`.
93
+ The default client connects to the Vercel Schedules API at `https://vercel-schedules.com`. Override the endpoint with `baseUrl` or `VERCEL_SCHEDULE_BASE_URL`.
63
94
 
64
95
  Requests are authenticated with the Vercel OIDC token from `@vercel/oidc`, or an explicit `token` option.
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  var _oidc = require('@vercel/oidc');
3
3
 
4
4
  // src/constants.ts
5
- var DEFAULT_SCHEDULES_BASE_URL = "https://vss-server.vercel.sh";
5
+ var DEFAULT_SCHEDULES_BASE_URL = "https://vercel-schedules.com";
6
6
 
7
7
  // src/api-client.ts
8
8
  var SchedulesApiError = class extends Error {
@@ -135,6 +135,9 @@ var SchedulesClient = class extends SchedulesApiClient {
135
135
  disable({ name, namespace }) {
136
136
  return this.request("POST", schedulePath(name, namespace, "/disable"));
137
137
  }
138
+ async invoke({ name, namespace }) {
139
+ await this.request("POST", schedulePath(name, namespace, "/invoke"));
140
+ }
138
141
  };
139
142
 
140
143
  // src/default-client.ts
@@ -146,7 +149,7 @@ var CLOUD_EVENT_SPEC_VERSION_HEADER = "ce-specversion";
146
149
  var CLOUD_EVENT_TYPE_HEADER = "ce-type";
147
150
  var CLOUD_EVENT_SOURCE_HEADER = "ce-source";
148
151
  var CLOUD_EVENT_ID_HEADER = "ce-id";
149
- var SCHEDULE_FIRED_AT_HEADER = "ce-time";
152
+ var SCHEDULE_SCHEDULED_AT_HEADER = "ce-vssscheduledat";
150
153
  var SCHEDULE_ID_HEADER = "ce-vssscheduleid";
151
154
  var SCHEDULE_NAME_HEADER = "ce-vssschedulename";
152
155
  var SCHEDULE_NAMESPACE_HEADER = "ce-vssnamespace";
@@ -188,49 +191,31 @@ async function parseScheduleEvent(request) {
188
191
  );
189
192
  }
190
193
  requiredHeader(request, CLOUD_EVENT_SOURCE_HEADER);
191
- requiredHeader(request, CLOUD_EVENT_ID_HEADER);
192
- const firedAtValue = requiredHeader(request, SCHEDULE_FIRED_AT_HEADER);
194
+ const executionId = requiredHeader(request, CLOUD_EVENT_ID_HEADER);
195
+ const scheduledAtValue = requiredHeader(
196
+ request,
197
+ SCHEDULE_SCHEDULED_AT_HEADER
198
+ );
193
199
  const metadata = {
194
200
  scheduleId: requiredHeader(request, SCHEDULE_ID_HEADER),
201
+ executionId,
195
202
  name: requiredHeader(request, SCHEDULE_NAME_HEADER),
196
203
  namespace: requiredHeader(request, SCHEDULE_NAMESPACE_HEADER),
197
- firedAt: new Date(firedAtValue),
204
+ scheduledAt: new Date(scheduledAtValue),
198
205
  source: requiredHeader(request, SCHEDULE_SOURCE_HEADER)
199
206
  };
200
- if (Number.isNaN(metadata.firedAt.getTime())) {
201
- throw new ScheduleEventParseError(
202
- `Schedule dispatch time is not a valid timestamp: ${firedAtValue}`
203
- );
204
- }
205
- if (request.body === null) {
206
- return { payload: void 0, metadata };
207
- }
208
- const contentType = request.headers.get("content-type");
209
- if (!contentType) {
210
- const body = await request.text();
211
- if (body === "") {
212
- return { payload: void 0, metadata };
213
- }
214
- }
215
- if ((contentType == null ? void 0 : contentType.split(";", 1)[0].trim().toLowerCase()) !== "application/json") {
216
- throw new ScheduleEventParseError(
217
- "Schedule dispatch payload must use the application/json content type"
218
- );
219
- }
220
- try {
221
- return { payload: await request.json(), metadata };
222
- } catch (cause) {
207
+ if (Number.isNaN(metadata.scheduledAt.getTime())) {
223
208
  throw new ScheduleEventParseError(
224
- "Schedule dispatch payload is not valid JSON",
225
- { cause }
209
+ `Schedule scheduledAt is not a valid timestamp: ${scheduledAtValue}`
226
210
  );
227
211
  }
212
+ return metadata;
228
213
  }
229
214
  function handleSchedule(handler) {
230
215
  return async (request) => {
231
- let event;
216
+ let metadata;
232
217
  try {
233
- event = await parseScheduleEvent(request);
218
+ metadata = await parseScheduleEvent(request);
234
219
  } catch (error) {
235
220
  if (error instanceof ScheduleEventParseError) {
236
221
  return Response.json({ error: error.message }, { status: 400 });
@@ -241,7 +226,7 @@ function handleSchedule(handler) {
241
226
  );
242
227
  }
243
228
  try {
244
- await handler(event.payload, event.metadata);
229
+ await handler(metadata);
245
230
  return new Response(null, { status: 200 });
246
231
  } catch (e) {
247
232
  return Response.json(
@@ -269,5 +254,5 @@ function handleSchedule(handler) {
269
254
 
270
255
 
271
256
 
272
- exports.CLOUD_EVENT_ID_HEADER = CLOUD_EVENT_ID_HEADER; exports.CLOUD_EVENT_SOURCE_HEADER = CLOUD_EVENT_SOURCE_HEADER; exports.CLOUD_EVENT_SPEC_VERSION_HEADER = CLOUD_EVENT_SPEC_VERSION_HEADER; exports.CLOUD_EVENT_TYPE_HEADER = CLOUD_EVENT_TYPE_HEADER; exports.DEFAULT_SCHEDULES_BASE_URL = DEFAULT_SCHEDULES_BASE_URL; exports.SCHEDULE_CLOUD_EVENT_TYPE = SCHEDULE_CLOUD_EVENT_TYPE; exports.SCHEDULE_FIRED_AT_HEADER = SCHEDULE_FIRED_AT_HEADER; exports.SCHEDULE_ID_HEADER = SCHEDULE_ID_HEADER; exports.SCHEDULE_NAMESPACE_HEADER = SCHEDULE_NAMESPACE_HEADER; exports.SCHEDULE_NAME_HEADER = SCHEDULE_NAME_HEADER; exports.SCHEDULE_SOURCE_HEADER = SCHEDULE_SOURCE_HEADER; exports.ScheduleEventParseError = ScheduleEventParseError; exports.Schedules = schedules; exports.SchedulesApiError = SchedulesApiError; exports.SchedulesClient = SchedulesClient; exports.handleSchedule = handleSchedule; exports.parseScheduleEvent = parseScheduleEvent;
257
+ exports.CLOUD_EVENT_ID_HEADER = CLOUD_EVENT_ID_HEADER; exports.CLOUD_EVENT_SOURCE_HEADER = CLOUD_EVENT_SOURCE_HEADER; exports.CLOUD_EVENT_SPEC_VERSION_HEADER = CLOUD_EVENT_SPEC_VERSION_HEADER; exports.CLOUD_EVENT_TYPE_HEADER = CLOUD_EVENT_TYPE_HEADER; exports.DEFAULT_SCHEDULES_BASE_URL = DEFAULT_SCHEDULES_BASE_URL; exports.SCHEDULE_CLOUD_EVENT_TYPE = SCHEDULE_CLOUD_EVENT_TYPE; exports.SCHEDULE_ID_HEADER = SCHEDULE_ID_HEADER; exports.SCHEDULE_NAMESPACE_HEADER = SCHEDULE_NAMESPACE_HEADER; exports.SCHEDULE_NAME_HEADER = SCHEDULE_NAME_HEADER; exports.SCHEDULE_SCHEDULED_AT_HEADER = SCHEDULE_SCHEDULED_AT_HEADER; exports.SCHEDULE_SOURCE_HEADER = SCHEDULE_SOURCE_HEADER; exports.ScheduleEventParseError = ScheduleEventParseError; exports.Schedules = schedules; exports.SchedulesApiError = SchedulesApiError; exports.SchedulesClient = SchedulesClient; exports.handleSchedule = handleSchedule; exports.parseScheduleEvent = parseScheduleEvent;
273
258
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/schedules/schedules/packages/schedules/dist/index.cjs","../src/api-client.ts","../src/constants.ts","../src/schedules.ts","../src/default-client.ts","../src/schedule-event.ts"],"names":[],"mappings":"AAAA;ACAA,oCAAmC;ADEnC;AACA;AEHO,IAAM,2BAAA,EAA6B,8BAAA;AFK1C;AACA;ACGO,IAAM,kBAAA,EAAN,MAAA,QAAgC,MAAM;AAAA,EAG3C,WAAA,CAAY,MAAA,EAAgB,OAAA,EAAiB;AAC3C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,KAAA,EAAO,mBAAA;AACZ,IAAA,IAAA,CAAK,OAAA,EAAS,MAAA;AAAA,EAChB;AACF,CAAA;AAEO,IAAM,mBAAA,EAAN,MAAyB;AAAA,EAK9B,WAAA,CAAY,QAAA,EAAqC,CAAC,CAAA,EAAG;AAxBvD,IAAA,IAAA,EAAA,EAAA,EAAA,EAAA,EAAA;AAyBI,IAAA,IAAA,CAAK,QAAA,EAAU,IAAI,GAAA;AAAA,MAAA,CACjB,GAAA,EAAA,CAAA,GAAA,EAAA,OAAA,CAAQ,OAAA,EAAA,GAAR,KAAA,EAAA,GAAA,EACE,OAAA,CAAQ,GAAA,CAAI,wBAAA,EAAA,GADd,KAAA,EAAA,GAAA,EAEE;AAAA,IACJ,CAAA;AACA,IAAA,IAAA,CAAK,MAAA,EAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,UAAA,EAAA,CAAY,GAAA,EAAA,OAAA,CAAQ,KAAA,EAAA,GAAR,KAAA,EAAA,GAAA,EAAiB,KAAA;AAAA,EACpC;AAAA,EAEA,MAAgB,QAAA,CAAA,EAA4B;AAC1C,IAAA,GAAA,CAAI,IAAA,CAAK,KAAA,EAAO;AACd,MAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACd;AAEA,IAAA,IAAI;AACF,MAAA,OAAO,MAAM,sCAAA,CAAmB;AAAA,IAClC,EAAA,MAAA,CAAS,GAAA,EAAK;AACZ,MAAA,MAAM,MAAA,EAAQ,IAAA,WAAe,MAAA,EAAQ,GAAA,CAAI,QAAA,EAAU,MAAA,CAAO,GAAG,CAAA;AAC7D,MAAA,MAAM,MAAA,EAAQ,OAAA,CAAQ,GAAA,CAAI,SAAA,IAAa,aAAA;AAEvC,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,MAAA,EACI,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,EAGY,KAAK,CAAA,EAAA;AACjB;AAAA;AAAA;AAAA;AAAA;AAKiB,OAAA;AACvB,MAAA;AACF,IAAA;AACF,EAAA;AAEgB,EAAA;AAKR,IAAA;AACJ,MAAA;AACD,IAAA;AAEG,IAAA;AACM,MAAA;AACV,IAAA;AAEM,IAAA;AACJ,MAAA;AACA,MAAA;AACM,MAAA;AACP,IAAA;AAEI,IAAA;AACG,MAAA;AACA,MAAA;AACR,IAAA;AAEI,IAAA;AACK,MAAA;AACT,IAAA;AAEQ,IAAA;AACV,EAAA;AACF;ADrBe;AACA;AG3DN;AACD,EAAA;AAEF,EAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AACO,EAAA;AAEN,EAAA;AACT;AAEa;AACJ,EAAA;AACL,IAAA;AACG,IAAA;AACqD,EAAA;AACjD,IAAA;AACF,MAAA;AACK,MAAA;AACA,QAAA;AACC,QAAA;AACT,MAAA;AACD,IAAA;AACH,EAAA;AAEK,EAAA;AACG,IAAA;AAEK,IAAA;AACT,MAAA;AACF,IAAA;AACW,IAAA;AACT,MAAA;AACF,IAAA;AACW,IAAA;AACT,MAAA;AACF,IAAA;AAEM,IAAA;AACA,IAAA;AAEC,IAAA;AACT,EAAA;AAEY,EAAA;AACH,IAAA;AACT,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACG,IAAA;AACqD,EAAA;AACjD,IAAA;AACF,MAAA;AACC,MAAA;AAGE,QAAA;AACE,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACL,IAAA;AACH,EAAA;AAEa,EAAA;AACA,IAAA;AACb,EAAA;AAES,EAAA;AACA,IAAA;AACT,EAAA;AAEU,EAAA;AACD,IAAA;AACT,EAAA;AACF;AH8Ce;AACA;AIzIF;AJ2IE;AACA;AK7IF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAyBA;AAGC,EAAA;AACJ,IAAA;AACD,IAAA;AACD,IAAA;AACG,MAAA;AACP,IAAA;AACF,EAAA;AACF;AAES;AACD,EAAA;AACM,EAAA;AACA,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAGsB;AAGR,EAAA;AACA,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AACF,EAAA;AACQ,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AACF,EAAA;AACQ,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACA,EAAA;AACM,EAAA;AACA,EAAA;AACJ,IAAA;AACM,IAAA;AACK,IAAA;AACF,IAAA;AACD,IAAA;AACV,EAAA;AAEW,EAAA;AACC,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEY,EAAA;AACD,IAAA;AACX,EAAA;AAEM,EAAA;AACD,EAAA;AACG,IAAA;AACF,IAAA;AACO,MAAA;AACX,IAAA;AACF,EAAA;AAEE,EAAA;AAEU,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEI,EAAA;AACO,IAAA;AACF,EAAA;AACG,IAAA;AACR,MAAA;AACQ,MAAA;AACV,IAAA;AACF,EAAA;AACF;AAWgB;AAGP,EAAA;AACD,IAAA;AACA,IAAA;AACM,MAAA;AACD,IAAA;AACH,MAAA;AACK,QAAA;AACT,MAAA;AACO,MAAA;AACH,QAAA;AACA,QAAA;AACJ,MAAA;AACF,IAAA;AAEI,IAAA;AACI,MAAA;AACC,MAAA;AACD,IAAA;AACC,MAAA;AACH,QAAA;AACA,QAAA;AACJ,MAAA;AACF,IAAA;AACF,EAAA;AACF;AL0Fe;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/schedules/schedules/packages/schedules/dist/index.cjs","sourcesContent":[null,"import { getVercelOidcToken } from '@vercel/oidc';\nimport { DEFAULT_SCHEDULES_BASE_URL } from './constants';\n\nexport interface SchedulesApiClientOptions {\n baseUrl?: string;\n token?: string;\n fetch?: typeof fetch;\n}\n\nexport class SchedulesApiError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'SchedulesApiError';\n this.status = status;\n }\n}\n\nexport class SchedulesApiClient {\n private readonly baseUrl: URL;\n private readonly token?: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: SchedulesApiClientOptions = {}) {\n this.baseUrl = new URL(\n options.baseUrl ??\n process.env.VERCEL_SCHEDULE_BASE_URL ??\n DEFAULT_SCHEDULES_BASE_URL,\n );\n this.token = options.token;\n this.fetchImpl = options.fetch ?? fetch;\n }\n\n protected async getToken(): Promise<string> {\n if (this.token) {\n return this.token;\n }\n\n try {\n return await getVercelOidcToken();\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n const isDev = process.env.NODE_ENV === 'development';\n\n throw new Error(\n isDev\n ? 'Failed to get OIDC token for local development.\\n\\n' +\n 'To fix this, pull your environment variables with Vercel CLI:\\n' +\n ' `vercel env pull`\\n\\n' +\n `Cause: ${cause}`\n : 'Failed to get OIDC token. This usually means the function is running ' +\n 'outside of a Vercel Function environment.\\n\\n' +\n 'To fix this, either:\\n' +\n ' - Deploy to Vercel (OIDC tokens are provisioned automatically)\\n' +\n ' - Provide a token explicitly: `new Schedules({ token: \"...\" })`\\n\\n' +\n `Cause: ${cause}`,\n );\n }\n }\n\n protected async request<T>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const headers = new Headers({\n Authorization: `Bearer ${await this.getToken()}`,\n });\n\n if (body !== undefined) {\n headers.set('Content-Type', 'application/json');\n }\n\n const response = await this.fetchImpl(new URL(path, this.baseUrl), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new SchedulesApiError(response.status, text || response.statusText);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return (await response.json()) as T;\n }\n}\n","export const DEFAULT_SCHEDULES_BASE_URL = 'https://vss-server.vercel.sh';\n","import { SchedulesApiClient } from './api-client';\nimport type {\n CreateScheduleParams,\n CreateScheduleResponse,\n ListSchedulesParams,\n ListSchedulesResponse,\n Schedule,\n ScheduleIdentifier,\n UpdateScheduleParams,\n UpdateScheduleResponse,\n} from './types';\n\nfunction schedulePath(name: string, namespace?: string, suffix = ''): string {\n const searchParams = new URLSearchParams();\n\n if (namespace !== undefined) {\n searchParams.set('namespace', namespace);\n }\n\n const query = searchParams.toString();\n const path = `/v1/schedules/${encodeURIComponent(name)}${suffix}`;\n\n return query ? `${path}?${query}` : path;\n}\n\nexport class SchedulesClient extends SchedulesApiClient {\n create({\n target,\n ...params\n }: CreateScheduleParams): Promise<CreateScheduleResponse> {\n return this.request('POST', '/v1/schedules', {\n ...params,\n target: {\n type: 'queue',\n topic: target.topic,\n },\n });\n }\n\n list(params: ListSchedulesParams = {}): Promise<ListSchedulesResponse> {\n const searchParams = new URLSearchParams();\n\n if (params.namespace !== undefined) {\n searchParams.set('namespace', params.namespace);\n }\n if (params.cursor !== undefined) {\n searchParams.set('cursor', params.cursor);\n }\n if (params.limit !== undefined) {\n searchParams.set('limit', String(params.limit));\n }\n\n const query = searchParams.toString();\n const path = query ? `/v1/schedules?${query}` : '/v1/schedules';\n\n return this.request('GET', path);\n }\n\n get({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('GET', schedulePath(name, namespace));\n }\n\n update({\n name,\n namespace,\n target,\n ...params\n }: UpdateScheduleParams): Promise<UpdateScheduleResponse> {\n return this.request('PATCH', schedulePath(name, namespace), {\n ...params,\n ...(target === undefined\n ? {}\n : {\n target: {\n type: 'queue',\n topic: target.topic,\n },\n }),\n });\n }\n\n async delete({ name, namespace }: ScheduleIdentifier): Promise<void> {\n await this.request('DELETE', schedulePath(name, namespace));\n }\n\n enable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/enable'));\n }\n\n disable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/disable'));\n }\n}\n","import { SchedulesClient } from './schedules';\n\nexport const schedules = new SchedulesClient();\n","/** CloudEvent type used for schedule function dispatches. */\nexport const SCHEDULE_CLOUD_EVENT_TYPE = 'com.vercel.schedule.v1beta';\nexport const CLOUD_EVENT_SPEC_VERSION_HEADER = 'ce-specversion';\nexport const CLOUD_EVENT_TYPE_HEADER = 'ce-type';\nexport const CLOUD_EVENT_SOURCE_HEADER = 'ce-source';\nexport const CLOUD_EVENT_ID_HEADER = 'ce-id';\nexport const SCHEDULE_FIRED_AT_HEADER = 'ce-time';\nexport const SCHEDULE_ID_HEADER = 'ce-vssscheduleid';\nexport const SCHEDULE_NAME_HEADER = 'ce-vssschedulename';\nexport const SCHEDULE_NAMESPACE_HEADER = 'ce-vssnamespace';\nexport const SCHEDULE_SOURCE_HEADER = 'ce-vssschedulesource';\n\nexport interface ScheduleMetadata {\n /** Unique identifier of the schedule. */\n scheduleId: string;\n /** Schedule name, unique within its project and namespace. */\n name: string;\n /** Namespace the schedule belongs to. */\n namespace: string;\n /** When the schedule fired. */\n firedAt: Date;\n /** How the schedule was defined, such as `static`. */\n source: string;\n}\n\nexport interface ParsedScheduleEvent<T = unknown> {\n payload: T | undefined;\n metadata: ScheduleMetadata;\n}\n\nexport type ScheduleHandler<T = unknown> = (\n payload: T | undefined,\n metadata: ScheduleMetadata,\n) => void | Promise<void>;\n\nexport class ScheduleEventParseError extends Error {\n cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = 'ScheduleEventParseError';\n if (options && 'cause' in options) {\n this.cause = options.cause;\n }\n }\n}\n\nfunction requiredHeader(request: Request, name: string): string {\n const value = request.headers.get(name);\n if (!value) {\n throw new ScheduleEventParseError(\n `Request is missing the ${name} header; it does not look like a schedule dispatch`,\n );\n }\n return value;\n}\n\n/** Parses a binary-mode schedule CloudEvent. */\nexport async function parseScheduleEvent<T = unknown>(\n request: Request,\n): Promise<ParsedScheduleEvent<T>> {\n if (request.method !== 'POST') {\n throw new ScheduleEventParseError(\n `Schedule dispatches are POST requests, received ${request.method}`,\n );\n }\n\n const specVersion = requiredHeader(request, CLOUD_EVENT_SPEC_VERSION_HEADER);\n if (specVersion !== '1.0') {\n throw new ScheduleEventParseError(\n `Unsupported CloudEvent spec version: ${specVersion}`,\n );\n }\n\n const eventType = requiredHeader(request, CLOUD_EVENT_TYPE_HEADER);\n if (eventType !== SCHEDULE_CLOUD_EVENT_TYPE) {\n throw new ScheduleEventParseError(\n `Unsupported schedule CloudEvent type: ${eventType}`,\n );\n }\n\n requiredHeader(request, CLOUD_EVENT_SOURCE_HEADER);\n requiredHeader(request, CLOUD_EVENT_ID_HEADER);\n const firedAtValue = requiredHeader(request, SCHEDULE_FIRED_AT_HEADER);\n const metadata: ScheduleMetadata = {\n scheduleId: requiredHeader(request, SCHEDULE_ID_HEADER),\n name: requiredHeader(request, SCHEDULE_NAME_HEADER),\n namespace: requiredHeader(request, SCHEDULE_NAMESPACE_HEADER),\n firedAt: new Date(firedAtValue),\n source: requiredHeader(request, SCHEDULE_SOURCE_HEADER),\n };\n\n if (Number.isNaN(metadata.firedAt.getTime())) {\n throw new ScheduleEventParseError(\n `Schedule dispatch time is not a valid timestamp: ${firedAtValue}`,\n );\n }\n\n if (request.body === null) {\n return { payload: undefined, metadata };\n }\n\n const contentType = request.headers.get('content-type');\n if (!contentType) {\n const body = await request.text();\n if (body === '') {\n return { payload: undefined, metadata };\n }\n }\n if (\n contentType?.split(';', 1)[0].trim().toLowerCase() !== 'application/json'\n ) {\n throw new ScheduleEventParseError(\n 'Schedule dispatch payload must use the application/json content type',\n );\n }\n\n try {\n return { payload: (await request.json()) as T, metadata };\n } catch (cause) {\n throw new ScheduleEventParseError(\n 'Schedule dispatch payload is not valid JSON',\n { cause },\n );\n }\n}\n\n/**\n * Creates a web-standard route handler for schedule dispatches.\n *\n * ```ts\n * export const POST = handleSchedule(async (payload, metadata) => {\n * console.log(metadata.name, payload);\n * });\n * ```\n */\nexport function handleSchedule<T = unknown>(\n handler: ScheduleHandler<T>,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n let event: ParsedScheduleEvent<T>;\n try {\n event = await parseScheduleEvent<T>(request);\n } catch (error) {\n if (error instanceof ScheduleEventParseError) {\n return Response.json({ error: error.message }, { status: 400 });\n }\n return Response.json(\n { error: 'Failed to parse schedule dispatch' },\n { status: 500 },\n );\n }\n\n try {\n await handler(event.payload, event.metadata);\n return new Response(null, { status: 200 });\n } catch {\n return Response.json(\n { error: 'Schedule handler failed' },\n { status: 500 },\n );\n }\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/schedules/schedules/packages/schedules/dist/index.cjs","../src/api-client.ts","../src/constants.ts","../src/schedules.ts","../src/default-client.ts","../src/schedule-event.ts"],"names":[],"mappings":"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,EAAA;AACA,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AACF,EAAA;AACQ,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEM,EAAA;AACF,EAAA;AACQ,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA;AACM,EAAA;AACA,EAAA;AACJ,IAAA;AACA,IAAA;AACF,EAAA;AACM,EAAA;AACJ,IAAA;AACA,IAAA;AACM,IAAA;AACK,IAAA;AACX,IAAA;AACQ,IAAA;AACV,EAAA;AAEW,EAAA;AACC,IAAA;AACR,MAAA;AACF,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AAWgB;AAGP,EAAA;AACD,IAAA;AACA,IAAA;AACF,MAAA;AACO,IAAA;AACH,MAAA;AACK,QAAA;AACT,MAAA;AACO,MAAA;AACH,QAAA;AACA,QAAA;AACJ,MAAA;AACF,IAAA;AAEI,IAAA;AACI,MAAA;AACC,MAAA;AACD,IAAA;AACC,MAAA;AACH,QAAA;AACA,QAAA;AACJ,MAAA;AACF,IAAA;AACF,EAAA;AACF;ALqGe;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/schedules/schedules/packages/schedules/dist/index.cjs","sourcesContent":[null,"import { getVercelOidcToken } from '@vercel/oidc';\nimport { DEFAULT_SCHEDULES_BASE_URL } from './constants';\n\nexport interface SchedulesApiClientOptions {\n baseUrl?: string;\n token?: string;\n fetch?: typeof fetch;\n}\n\nexport class SchedulesApiError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'SchedulesApiError';\n this.status = status;\n }\n}\n\nexport class SchedulesApiClient {\n private readonly baseUrl: URL;\n private readonly token?: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: SchedulesApiClientOptions = {}) {\n this.baseUrl = new URL(\n options.baseUrl ??\n process.env.VERCEL_SCHEDULE_BASE_URL ??\n DEFAULT_SCHEDULES_BASE_URL,\n );\n this.token = options.token;\n this.fetchImpl = options.fetch ?? fetch;\n }\n\n protected async getToken(): Promise<string> {\n if (this.token) {\n return this.token;\n }\n\n try {\n return await getVercelOidcToken();\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n const isDev = process.env.NODE_ENV === 'development';\n\n throw new Error(\n isDev\n ? 'Failed to get OIDC token for local development.\\n\\n' +\n 'To fix this, pull your environment variables with Vercel CLI:\\n' +\n ' `vercel env pull`\\n\\n' +\n `Cause: ${cause}`\n : 'Failed to get OIDC token. This usually means the function is running ' +\n 'outside of a Vercel Function environment.\\n\\n' +\n 'To fix this, either:\\n' +\n ' - Deploy to Vercel (OIDC tokens are provisioned automatically)\\n' +\n ' - Provide a token explicitly: `new Schedules({ token: \"...\" })`\\n\\n' +\n `Cause: ${cause}`,\n );\n }\n }\n\n protected async request<T>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const headers = new Headers({\n Authorization: `Bearer ${await this.getToken()}`,\n });\n\n if (body !== undefined) {\n headers.set('Content-Type', 'application/json');\n }\n\n const response = await this.fetchImpl(new URL(path, this.baseUrl), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new SchedulesApiError(response.status, text || response.statusText);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return (await response.json()) as T;\n }\n}\n","export const DEFAULT_SCHEDULES_BASE_URL = 'https://vercel-schedules.com';\n","import { SchedulesApiClient } from './api-client';\nimport type {\n CreateScheduleParams,\n CreateScheduleResponse,\n InvokeScheduleParams,\n ListSchedulesParams,\n ListSchedulesResponse,\n Schedule,\n ScheduleIdentifier,\n UpdateScheduleParams,\n UpdateScheduleResponse,\n} from './types';\n\nfunction schedulePath(name: string, namespace?: string, suffix = ''): string {\n const searchParams = new URLSearchParams();\n\n if (namespace !== undefined) {\n searchParams.set('namespace', namespace);\n }\n\n const query = searchParams.toString();\n const path = `/v1/schedules/${encodeURIComponent(name)}${suffix}`;\n\n return query ? `${path}?${query}` : path;\n}\n\nexport class SchedulesClient extends SchedulesApiClient {\n create({\n target,\n ...params\n }: CreateScheduleParams): Promise<CreateScheduleResponse> {\n return this.request('POST', '/v1/schedules', {\n ...params,\n target: {\n type: 'queue',\n topic: target.topic,\n },\n });\n }\n\n list(params: ListSchedulesParams = {}): Promise<ListSchedulesResponse> {\n const searchParams = new URLSearchParams();\n\n if (params.namespace !== undefined) {\n searchParams.set('namespace', params.namespace);\n }\n if (params.cursor !== undefined) {\n searchParams.set('cursor', params.cursor);\n }\n if (params.limit !== undefined) {\n searchParams.set('limit', String(params.limit));\n }\n\n const query = searchParams.toString();\n const path = query ? `/v1/schedules?${query}` : '/v1/schedules';\n\n return this.request('GET', path);\n }\n\n get({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('GET', schedulePath(name, namespace));\n }\n\n update({\n name,\n namespace,\n target,\n ...params\n }: UpdateScheduleParams): Promise<UpdateScheduleResponse> {\n return this.request('PATCH', schedulePath(name, namespace), {\n ...params,\n ...(target === undefined\n ? {}\n : {\n target: {\n type: 'queue',\n topic: target.topic,\n },\n }),\n });\n }\n\n async delete({ name, namespace }: ScheduleIdentifier): Promise<void> {\n await this.request('DELETE', schedulePath(name, namespace));\n }\n\n enable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/enable'));\n }\n\n disable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/disable'));\n }\n\n async invoke({ name, namespace }: InvokeScheduleParams): Promise<void> {\n await this.request('POST', schedulePath(name, namespace, '/invoke'));\n }\n}\n","import { SchedulesClient } from './schedules';\n\nexport const schedules = new SchedulesClient();\n","/** CloudEvent type used for schedule function dispatches. */\nexport const SCHEDULE_CLOUD_EVENT_TYPE = 'com.vercel.schedule.v1beta';\nexport const CLOUD_EVENT_SPEC_VERSION_HEADER = 'ce-specversion';\nexport const CLOUD_EVENT_TYPE_HEADER = 'ce-type';\nexport const CLOUD_EVENT_SOURCE_HEADER = 'ce-source';\nexport const CLOUD_EVENT_ID_HEADER = 'ce-id';\nexport const SCHEDULE_SCHEDULED_AT_HEADER = 'ce-vssscheduledat';\nexport const SCHEDULE_ID_HEADER = 'ce-vssscheduleid';\nexport const SCHEDULE_NAME_HEADER = 'ce-vssschedulename';\nexport const SCHEDULE_NAMESPACE_HEADER = 'ce-vssnamespace';\nexport const SCHEDULE_SOURCE_HEADER = 'ce-vssschedulesource';\n\nexport interface ScheduleMetadata {\n /** Unique identifier of the schedule. */\n scheduleId: string;\n /** Unique identifier for this schedule occurrence. */\n executionId: string;\n /** Schedule name, unique within its project and namespace. */\n name: string;\n /** Namespace the schedule belongs to. */\n namespace: string;\n /** When the schedule was scheduled to run. */\n scheduledAt: Date;\n /** How the schedule was defined, such as `static`. */\n source: string;\n}\n\nexport type ScheduleHandler = (\n metadata: ScheduleMetadata,\n) => void | Promise<void>;\n\nexport class ScheduleEventParseError extends Error {\n cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = 'ScheduleEventParseError';\n if (options && 'cause' in options) {\n this.cause = options.cause;\n }\n }\n}\n\nfunction requiredHeader(request: Request, name: string): string {\n const value = request.headers.get(name);\n if (!value) {\n throw new ScheduleEventParseError(\n `Request is missing the ${name} header; it does not look like a schedule dispatch`,\n );\n }\n return value;\n}\n\n/** Parses schedule metadata from a binary-mode schedule CloudEvent. */\nexport async function parseScheduleEvent(\n request: Request,\n): Promise<ScheduleMetadata> {\n if (request.method !== 'POST') {\n throw new ScheduleEventParseError(\n `Schedule dispatches are POST requests, received ${request.method}`,\n );\n }\n\n const specVersion = requiredHeader(request, CLOUD_EVENT_SPEC_VERSION_HEADER);\n if (specVersion !== '1.0') {\n throw new ScheduleEventParseError(\n `Unsupported CloudEvent spec version: ${specVersion}`,\n );\n }\n\n const eventType = requiredHeader(request, CLOUD_EVENT_TYPE_HEADER);\n if (eventType !== SCHEDULE_CLOUD_EVENT_TYPE) {\n throw new ScheduleEventParseError(\n `Unsupported schedule CloudEvent type: ${eventType}`,\n );\n }\n\n requiredHeader(request, CLOUD_EVENT_SOURCE_HEADER);\n const executionId = requiredHeader(request, CLOUD_EVENT_ID_HEADER);\n const scheduledAtValue = requiredHeader(\n request,\n SCHEDULE_SCHEDULED_AT_HEADER,\n );\n const metadata: ScheduleMetadata = {\n scheduleId: requiredHeader(request, SCHEDULE_ID_HEADER),\n executionId,\n name: requiredHeader(request, SCHEDULE_NAME_HEADER),\n namespace: requiredHeader(request, SCHEDULE_NAMESPACE_HEADER),\n scheduledAt: new Date(scheduledAtValue),\n source: requiredHeader(request, SCHEDULE_SOURCE_HEADER),\n };\n\n if (Number.isNaN(metadata.scheduledAt.getTime())) {\n throw new ScheduleEventParseError(\n `Schedule scheduledAt is not a valid timestamp: ${scheduledAtValue}`,\n );\n }\n\n return metadata;\n}\n\n/**\n * Creates a web-standard route handler for schedule dispatches.\n *\n * ```ts\n * export const POST = handleSchedule(async (metadata) => {\n * console.log(metadata.name, metadata.scheduledAt);\n * });\n * ```\n */\nexport function handleSchedule(\n handler: ScheduleHandler,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n let metadata: ScheduleMetadata;\n try {\n metadata = await parseScheduleEvent(request);\n } catch (error) {\n if (error instanceof ScheduleEventParseError) {\n return Response.json({ error: error.message }, { status: 400 });\n }\n return Response.json(\n { error: 'Failed to parse schedule dispatch' },\n { status: 500 },\n );\n }\n\n try {\n await handler(metadata);\n return new Response(null, { status: 200 });\n } catch {\n return Response.json(\n { error: 'Schedule handler failed' },\n { status: 500 },\n );\n }\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -16,13 +16,14 @@ declare class SchedulesApiClient {
16
16
  protected request<T>(method: string, path: string, body?: unknown): Promise<T>;
17
17
  }
18
18
 
19
- declare const DEFAULT_SCHEDULES_BASE_URL = "https://vss-server.vercel.sh";
19
+ declare const DEFAULT_SCHEDULES_BASE_URL = "https://vercel-schedules.com";
20
20
 
21
21
  type ScheduleExpression = {
22
22
  type: 'cron';
23
23
  cron: string;
24
24
  } | {
25
25
  type: 'single';
26
+ /** Datetime in YYYY-MM-DDTHH:mm:ss format, with no timezone suffix. */
26
27
  at: string;
27
28
  };
28
29
  type ScheduleTarget = {
@@ -41,8 +42,9 @@ interface Schedule {
41
42
  trackId: string;
42
43
  name: string;
43
44
  namespace: string;
44
- timezone: string;
45
45
  expression: ScheduleExpression;
46
+ /** IANA timezone used for cron expressions and one-time schedules. */
47
+ timezone: string;
46
48
  jitter?: number;
47
49
  target: ScheduleTarget;
48
50
  state: ScheduleState;
@@ -54,6 +56,7 @@ interface CreateScheduleParams {
54
56
  name: string;
55
57
  namespace?: string;
56
58
  expression: ScheduleExpression;
59
+ /** IANA timezone for cron expressions and local one-time schedules. Defaults to UTC. */
57
60
  timezone?: string;
58
61
  jitter?: number;
59
62
  target: CreateScheduleTarget;
@@ -72,6 +75,7 @@ interface UpdateScheduleParams extends ScheduleIdentifier {
72
75
  payload?: unknown;
73
76
  }
74
77
  type UpdateScheduleResponse = Schedule;
78
+ type InvokeScheduleParams = ScheduleIdentifier;
75
79
  interface ListSchedulesParams {
76
80
  namespace?: string;
77
81
  cursor?: string;
@@ -90,6 +94,7 @@ declare class SchedulesClient extends SchedulesApiClient {
90
94
  delete({ name, namespace }: ScheduleIdentifier): Promise<void>;
91
95
  enable({ name, namespace }: ScheduleIdentifier): Promise<Schedule>;
92
96
  disable({ name, namespace }: ScheduleIdentifier): Promise<Schedule>;
97
+ invoke({ name, namespace }: InvokeScheduleParams): Promise<void>;
93
98
  }
94
99
 
95
100
  declare const schedules: SchedulesClient;
@@ -100,7 +105,7 @@ declare const CLOUD_EVENT_SPEC_VERSION_HEADER = "ce-specversion";
100
105
  declare const CLOUD_EVENT_TYPE_HEADER = "ce-type";
101
106
  declare const CLOUD_EVENT_SOURCE_HEADER = "ce-source";
102
107
  declare const CLOUD_EVENT_ID_HEADER = "ce-id";
103
- declare const SCHEDULE_FIRED_AT_HEADER = "ce-time";
108
+ declare const SCHEDULE_SCHEDULED_AT_HEADER = "ce-vssscheduledat";
104
109
  declare const SCHEDULE_ID_HEADER = "ce-vssscheduleid";
105
110
  declare const SCHEDULE_NAME_HEADER = "ce-vssschedulename";
106
111
  declare const SCHEDULE_NAMESPACE_HEADER = "ce-vssnamespace";
@@ -108,37 +113,35 @@ declare const SCHEDULE_SOURCE_HEADER = "ce-vssschedulesource";
108
113
  interface ScheduleMetadata {
109
114
  /** Unique identifier of the schedule. */
110
115
  scheduleId: string;
116
+ /** Unique identifier for this schedule occurrence. */
117
+ executionId: string;
111
118
  /** Schedule name, unique within its project and namespace. */
112
119
  name: string;
113
120
  /** Namespace the schedule belongs to. */
114
121
  namespace: string;
115
- /** When the schedule fired. */
116
- firedAt: Date;
122
+ /** When the schedule was scheduled to run. */
123
+ scheduledAt: Date;
117
124
  /** How the schedule was defined, such as `static`. */
118
125
  source: string;
119
126
  }
120
- interface ParsedScheduleEvent<T = unknown> {
121
- payload: T | undefined;
122
- metadata: ScheduleMetadata;
123
- }
124
- type ScheduleHandler<T = unknown> = (payload: T | undefined, metadata: ScheduleMetadata) => void | Promise<void>;
127
+ type ScheduleHandler = (metadata: ScheduleMetadata) => void | Promise<void>;
125
128
  declare class ScheduleEventParseError extends Error {
126
129
  cause?: unknown;
127
130
  constructor(message: string, options?: {
128
131
  cause?: unknown;
129
132
  });
130
133
  }
131
- /** Parses a binary-mode schedule CloudEvent. */
132
- declare function parseScheduleEvent<T = unknown>(request: Request): Promise<ParsedScheduleEvent<T>>;
134
+ /** Parses schedule metadata from a binary-mode schedule CloudEvent. */
135
+ declare function parseScheduleEvent(request: Request): Promise<ScheduleMetadata>;
133
136
  /**
134
137
  * Creates a web-standard route handler for schedule dispatches.
135
138
  *
136
139
  * ```ts
137
- * export const POST = handleSchedule(async (payload, metadata) => {
138
- * console.log(metadata.name, payload);
140
+ * export const POST = handleSchedule(async (metadata) => {
141
+ * console.log(metadata.name, metadata.scheduledAt);
139
142
  * });
140
143
  * ```
141
144
  */
142
- declare function handleSchedule<T = unknown>(handler: ScheduleHandler<T>): (request: Request) => Promise<Response>;
145
+ declare function handleSchedule(handler: ScheduleHandler): (request: Request) => Promise<Response>;
143
146
 
144
- export { CLOUD_EVENT_ID_HEADER, CLOUD_EVENT_SOURCE_HEADER, CLOUD_EVENT_SPEC_VERSION_HEADER, CLOUD_EVENT_TYPE_HEADER, type CreateScheduleParams, type CreateScheduleResponse, type CreateScheduleTarget, DEFAULT_SCHEDULES_BASE_URL, type ListSchedulesParams, type ListSchedulesResponse, type ParsedScheduleEvent, SCHEDULE_CLOUD_EVENT_TYPE, SCHEDULE_FIRED_AT_HEADER, SCHEDULE_ID_HEADER, SCHEDULE_NAMESPACE_HEADER, SCHEDULE_NAME_HEADER, SCHEDULE_SOURCE_HEADER, type Schedule, ScheduleEventParseError, type ScheduleExpression, type ScheduleHandler, type ScheduleIdentifier, type ScheduleMetadata, type ScheduleSource, type ScheduleState, type ScheduleTarget, schedules as Schedules, type SchedulesApiClientOptions, SchedulesApiError, SchedulesClient, type UpdateScheduleParams, type UpdateScheduleResponse, handleSchedule, parseScheduleEvent };
147
+ export { CLOUD_EVENT_ID_HEADER, CLOUD_EVENT_SOURCE_HEADER, CLOUD_EVENT_SPEC_VERSION_HEADER, CLOUD_EVENT_TYPE_HEADER, type CreateScheduleParams, type CreateScheduleResponse, type CreateScheduleTarget, DEFAULT_SCHEDULES_BASE_URL, type InvokeScheduleParams, type ListSchedulesParams, type ListSchedulesResponse, SCHEDULE_CLOUD_EVENT_TYPE, SCHEDULE_ID_HEADER, SCHEDULE_NAMESPACE_HEADER, SCHEDULE_NAME_HEADER, SCHEDULE_SCHEDULED_AT_HEADER, SCHEDULE_SOURCE_HEADER, type Schedule, ScheduleEventParseError, type ScheduleExpression, type ScheduleHandler, type ScheduleIdentifier, type ScheduleMetadata, type ScheduleSource, type ScheduleState, type ScheduleTarget, schedules as Schedules, type SchedulesApiClientOptions, SchedulesApiError, SchedulesClient, type UpdateScheduleParams, type UpdateScheduleResponse, handleSchedule, parseScheduleEvent };
package/dist/index.d.ts CHANGED
@@ -16,13 +16,14 @@ declare class SchedulesApiClient {
16
16
  protected request<T>(method: string, path: string, body?: unknown): Promise<T>;
17
17
  }
18
18
 
19
- declare const DEFAULT_SCHEDULES_BASE_URL = "https://vss-server.vercel.sh";
19
+ declare const DEFAULT_SCHEDULES_BASE_URL = "https://vercel-schedules.com";
20
20
 
21
21
  type ScheduleExpression = {
22
22
  type: 'cron';
23
23
  cron: string;
24
24
  } | {
25
25
  type: 'single';
26
+ /** Datetime in YYYY-MM-DDTHH:mm:ss format, with no timezone suffix. */
26
27
  at: string;
27
28
  };
28
29
  type ScheduleTarget = {
@@ -41,8 +42,9 @@ interface Schedule {
41
42
  trackId: string;
42
43
  name: string;
43
44
  namespace: string;
44
- timezone: string;
45
45
  expression: ScheduleExpression;
46
+ /** IANA timezone used for cron expressions and one-time schedules. */
47
+ timezone: string;
46
48
  jitter?: number;
47
49
  target: ScheduleTarget;
48
50
  state: ScheduleState;
@@ -54,6 +56,7 @@ interface CreateScheduleParams {
54
56
  name: string;
55
57
  namespace?: string;
56
58
  expression: ScheduleExpression;
59
+ /** IANA timezone for cron expressions and local one-time schedules. Defaults to UTC. */
57
60
  timezone?: string;
58
61
  jitter?: number;
59
62
  target: CreateScheduleTarget;
@@ -72,6 +75,7 @@ interface UpdateScheduleParams extends ScheduleIdentifier {
72
75
  payload?: unknown;
73
76
  }
74
77
  type UpdateScheduleResponse = Schedule;
78
+ type InvokeScheduleParams = ScheduleIdentifier;
75
79
  interface ListSchedulesParams {
76
80
  namespace?: string;
77
81
  cursor?: string;
@@ -90,6 +94,7 @@ declare class SchedulesClient extends SchedulesApiClient {
90
94
  delete({ name, namespace }: ScheduleIdentifier): Promise<void>;
91
95
  enable({ name, namespace }: ScheduleIdentifier): Promise<Schedule>;
92
96
  disable({ name, namespace }: ScheduleIdentifier): Promise<Schedule>;
97
+ invoke({ name, namespace }: InvokeScheduleParams): Promise<void>;
93
98
  }
94
99
 
95
100
  declare const schedules: SchedulesClient;
@@ -100,7 +105,7 @@ declare const CLOUD_EVENT_SPEC_VERSION_HEADER = "ce-specversion";
100
105
  declare const CLOUD_EVENT_TYPE_HEADER = "ce-type";
101
106
  declare const CLOUD_EVENT_SOURCE_HEADER = "ce-source";
102
107
  declare const CLOUD_EVENT_ID_HEADER = "ce-id";
103
- declare const SCHEDULE_FIRED_AT_HEADER = "ce-time";
108
+ declare const SCHEDULE_SCHEDULED_AT_HEADER = "ce-vssscheduledat";
104
109
  declare const SCHEDULE_ID_HEADER = "ce-vssscheduleid";
105
110
  declare const SCHEDULE_NAME_HEADER = "ce-vssschedulename";
106
111
  declare const SCHEDULE_NAMESPACE_HEADER = "ce-vssnamespace";
@@ -108,37 +113,35 @@ declare const SCHEDULE_SOURCE_HEADER = "ce-vssschedulesource";
108
113
  interface ScheduleMetadata {
109
114
  /** Unique identifier of the schedule. */
110
115
  scheduleId: string;
116
+ /** Unique identifier for this schedule occurrence. */
117
+ executionId: string;
111
118
  /** Schedule name, unique within its project and namespace. */
112
119
  name: string;
113
120
  /** Namespace the schedule belongs to. */
114
121
  namespace: string;
115
- /** When the schedule fired. */
116
- firedAt: Date;
122
+ /** When the schedule was scheduled to run. */
123
+ scheduledAt: Date;
117
124
  /** How the schedule was defined, such as `static`. */
118
125
  source: string;
119
126
  }
120
- interface ParsedScheduleEvent<T = unknown> {
121
- payload: T | undefined;
122
- metadata: ScheduleMetadata;
123
- }
124
- type ScheduleHandler<T = unknown> = (payload: T | undefined, metadata: ScheduleMetadata) => void | Promise<void>;
127
+ type ScheduleHandler = (metadata: ScheduleMetadata) => void | Promise<void>;
125
128
  declare class ScheduleEventParseError extends Error {
126
129
  cause?: unknown;
127
130
  constructor(message: string, options?: {
128
131
  cause?: unknown;
129
132
  });
130
133
  }
131
- /** Parses a binary-mode schedule CloudEvent. */
132
- declare function parseScheduleEvent<T = unknown>(request: Request): Promise<ParsedScheduleEvent<T>>;
134
+ /** Parses schedule metadata from a binary-mode schedule CloudEvent. */
135
+ declare function parseScheduleEvent(request: Request): Promise<ScheduleMetadata>;
133
136
  /**
134
137
  * Creates a web-standard route handler for schedule dispatches.
135
138
  *
136
139
  * ```ts
137
- * export const POST = handleSchedule(async (payload, metadata) => {
138
- * console.log(metadata.name, payload);
140
+ * export const POST = handleSchedule(async (metadata) => {
141
+ * console.log(metadata.name, metadata.scheduledAt);
139
142
  * });
140
143
  * ```
141
144
  */
142
- declare function handleSchedule<T = unknown>(handler: ScheduleHandler<T>): (request: Request) => Promise<Response>;
145
+ declare function handleSchedule(handler: ScheduleHandler): (request: Request) => Promise<Response>;
143
146
 
144
- export { CLOUD_EVENT_ID_HEADER, CLOUD_EVENT_SOURCE_HEADER, CLOUD_EVENT_SPEC_VERSION_HEADER, CLOUD_EVENT_TYPE_HEADER, type CreateScheduleParams, type CreateScheduleResponse, type CreateScheduleTarget, DEFAULT_SCHEDULES_BASE_URL, type ListSchedulesParams, type ListSchedulesResponse, type ParsedScheduleEvent, SCHEDULE_CLOUD_EVENT_TYPE, SCHEDULE_FIRED_AT_HEADER, SCHEDULE_ID_HEADER, SCHEDULE_NAMESPACE_HEADER, SCHEDULE_NAME_HEADER, SCHEDULE_SOURCE_HEADER, type Schedule, ScheduleEventParseError, type ScheduleExpression, type ScheduleHandler, type ScheduleIdentifier, type ScheduleMetadata, type ScheduleSource, type ScheduleState, type ScheduleTarget, schedules as Schedules, type SchedulesApiClientOptions, SchedulesApiError, SchedulesClient, type UpdateScheduleParams, type UpdateScheduleResponse, handleSchedule, parseScheduleEvent };
147
+ export { CLOUD_EVENT_ID_HEADER, CLOUD_EVENT_SOURCE_HEADER, CLOUD_EVENT_SPEC_VERSION_HEADER, CLOUD_EVENT_TYPE_HEADER, type CreateScheduleParams, type CreateScheduleResponse, type CreateScheduleTarget, DEFAULT_SCHEDULES_BASE_URL, type InvokeScheduleParams, type ListSchedulesParams, type ListSchedulesResponse, SCHEDULE_CLOUD_EVENT_TYPE, SCHEDULE_ID_HEADER, SCHEDULE_NAMESPACE_HEADER, SCHEDULE_NAME_HEADER, SCHEDULE_SCHEDULED_AT_HEADER, SCHEDULE_SOURCE_HEADER, type Schedule, ScheduleEventParseError, type ScheduleExpression, type ScheduleHandler, type ScheduleIdentifier, type ScheduleMetadata, type ScheduleSource, type ScheduleState, type ScheduleTarget, schedules as Schedules, type SchedulesApiClientOptions, SchedulesApiError, SchedulesClient, type UpdateScheduleParams, type UpdateScheduleResponse, handleSchedule, parseScheduleEvent };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { getVercelOidcToken } from "@vercel/oidc";
3
3
 
4
4
  // src/constants.ts
5
- var DEFAULT_SCHEDULES_BASE_URL = "https://vss-server.vercel.sh";
5
+ var DEFAULT_SCHEDULES_BASE_URL = "https://vercel-schedules.com";
6
6
 
7
7
  // src/api-client.ts
8
8
  var SchedulesApiError = class extends Error {
@@ -135,6 +135,9 @@ var SchedulesClient = class extends SchedulesApiClient {
135
135
  disable({ name, namespace }) {
136
136
  return this.request("POST", schedulePath(name, namespace, "/disable"));
137
137
  }
138
+ async invoke({ name, namespace }) {
139
+ await this.request("POST", schedulePath(name, namespace, "/invoke"));
140
+ }
138
141
  };
139
142
 
140
143
  // src/default-client.ts
@@ -146,7 +149,7 @@ var CLOUD_EVENT_SPEC_VERSION_HEADER = "ce-specversion";
146
149
  var CLOUD_EVENT_TYPE_HEADER = "ce-type";
147
150
  var CLOUD_EVENT_SOURCE_HEADER = "ce-source";
148
151
  var CLOUD_EVENT_ID_HEADER = "ce-id";
149
- var SCHEDULE_FIRED_AT_HEADER = "ce-time";
152
+ var SCHEDULE_SCHEDULED_AT_HEADER = "ce-vssscheduledat";
150
153
  var SCHEDULE_ID_HEADER = "ce-vssscheduleid";
151
154
  var SCHEDULE_NAME_HEADER = "ce-vssschedulename";
152
155
  var SCHEDULE_NAMESPACE_HEADER = "ce-vssnamespace";
@@ -188,49 +191,31 @@ async function parseScheduleEvent(request) {
188
191
  );
189
192
  }
190
193
  requiredHeader(request, CLOUD_EVENT_SOURCE_HEADER);
191
- requiredHeader(request, CLOUD_EVENT_ID_HEADER);
192
- const firedAtValue = requiredHeader(request, SCHEDULE_FIRED_AT_HEADER);
194
+ const executionId = requiredHeader(request, CLOUD_EVENT_ID_HEADER);
195
+ const scheduledAtValue = requiredHeader(
196
+ request,
197
+ SCHEDULE_SCHEDULED_AT_HEADER
198
+ );
193
199
  const metadata = {
194
200
  scheduleId: requiredHeader(request, SCHEDULE_ID_HEADER),
201
+ executionId,
195
202
  name: requiredHeader(request, SCHEDULE_NAME_HEADER),
196
203
  namespace: requiredHeader(request, SCHEDULE_NAMESPACE_HEADER),
197
- firedAt: new Date(firedAtValue),
204
+ scheduledAt: new Date(scheduledAtValue),
198
205
  source: requiredHeader(request, SCHEDULE_SOURCE_HEADER)
199
206
  };
200
- if (Number.isNaN(metadata.firedAt.getTime())) {
201
- throw new ScheduleEventParseError(
202
- `Schedule dispatch time is not a valid timestamp: ${firedAtValue}`
203
- );
204
- }
205
- if (request.body === null) {
206
- return { payload: void 0, metadata };
207
- }
208
- const contentType = request.headers.get("content-type");
209
- if (!contentType) {
210
- const body = await request.text();
211
- if (body === "") {
212
- return { payload: void 0, metadata };
213
- }
214
- }
215
- if ((contentType == null ? void 0 : contentType.split(";", 1)[0].trim().toLowerCase()) !== "application/json") {
216
- throw new ScheduleEventParseError(
217
- "Schedule dispatch payload must use the application/json content type"
218
- );
219
- }
220
- try {
221
- return { payload: await request.json(), metadata };
222
- } catch (cause) {
207
+ if (Number.isNaN(metadata.scheduledAt.getTime())) {
223
208
  throw new ScheduleEventParseError(
224
- "Schedule dispatch payload is not valid JSON",
225
- { cause }
209
+ `Schedule scheduledAt is not a valid timestamp: ${scheduledAtValue}`
226
210
  );
227
211
  }
212
+ return metadata;
228
213
  }
229
214
  function handleSchedule(handler) {
230
215
  return async (request) => {
231
- let event;
216
+ let metadata;
232
217
  try {
233
- event = await parseScheduleEvent(request);
218
+ metadata = await parseScheduleEvent(request);
234
219
  } catch (error) {
235
220
  if (error instanceof ScheduleEventParseError) {
236
221
  return Response.json({ error: error.message }, { status: 400 });
@@ -241,7 +226,7 @@ function handleSchedule(handler) {
241
226
  );
242
227
  }
243
228
  try {
244
- await handler(event.payload, event.metadata);
229
+ await handler(metadata);
245
230
  return new Response(null, { status: 200 });
246
231
  } catch {
247
232
  return Response.json(
@@ -258,10 +243,10 @@ export {
258
243
  CLOUD_EVENT_TYPE_HEADER,
259
244
  DEFAULT_SCHEDULES_BASE_URL,
260
245
  SCHEDULE_CLOUD_EVENT_TYPE,
261
- SCHEDULE_FIRED_AT_HEADER,
262
246
  SCHEDULE_ID_HEADER,
263
247
  SCHEDULE_NAMESPACE_HEADER,
264
248
  SCHEDULE_NAME_HEADER,
249
+ SCHEDULE_SCHEDULED_AT_HEADER,
265
250
  SCHEDULE_SOURCE_HEADER,
266
251
  ScheduleEventParseError,
267
252
  schedules as Schedules,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api-client.ts","../src/constants.ts","../src/schedules.ts","../src/default-client.ts","../src/schedule-event.ts"],"sourcesContent":["import { getVercelOidcToken } from '@vercel/oidc';\nimport { DEFAULT_SCHEDULES_BASE_URL } from './constants';\n\nexport interface SchedulesApiClientOptions {\n baseUrl?: string;\n token?: string;\n fetch?: typeof fetch;\n}\n\nexport class SchedulesApiError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'SchedulesApiError';\n this.status = status;\n }\n}\n\nexport class SchedulesApiClient {\n private readonly baseUrl: URL;\n private readonly token?: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: SchedulesApiClientOptions = {}) {\n this.baseUrl = new URL(\n options.baseUrl ??\n process.env.VERCEL_SCHEDULE_BASE_URL ??\n DEFAULT_SCHEDULES_BASE_URL,\n );\n this.token = options.token;\n this.fetchImpl = options.fetch ?? fetch;\n }\n\n protected async getToken(): Promise<string> {\n if (this.token) {\n return this.token;\n }\n\n try {\n return await getVercelOidcToken();\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n const isDev = process.env.NODE_ENV === 'development';\n\n throw new Error(\n isDev\n ? 'Failed to get OIDC token for local development.\\n\\n' +\n 'To fix this, pull your environment variables with Vercel CLI:\\n' +\n ' `vercel env pull`\\n\\n' +\n `Cause: ${cause}`\n : 'Failed to get OIDC token. This usually means the function is running ' +\n 'outside of a Vercel Function environment.\\n\\n' +\n 'To fix this, either:\\n' +\n ' - Deploy to Vercel (OIDC tokens are provisioned automatically)\\n' +\n ' - Provide a token explicitly: `new Schedules({ token: \"...\" })`\\n\\n' +\n `Cause: ${cause}`,\n );\n }\n }\n\n protected async request<T>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const headers = new Headers({\n Authorization: `Bearer ${await this.getToken()}`,\n });\n\n if (body !== undefined) {\n headers.set('Content-Type', 'application/json');\n }\n\n const response = await this.fetchImpl(new URL(path, this.baseUrl), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new SchedulesApiError(response.status, text || response.statusText);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return (await response.json()) as T;\n }\n}\n","export const DEFAULT_SCHEDULES_BASE_URL = 'https://vss-server.vercel.sh';\n","import { SchedulesApiClient } from './api-client';\nimport type {\n CreateScheduleParams,\n CreateScheduleResponse,\n ListSchedulesParams,\n ListSchedulesResponse,\n Schedule,\n ScheduleIdentifier,\n UpdateScheduleParams,\n UpdateScheduleResponse,\n} from './types';\n\nfunction schedulePath(name: string, namespace?: string, suffix = ''): string {\n const searchParams = new URLSearchParams();\n\n if (namespace !== undefined) {\n searchParams.set('namespace', namespace);\n }\n\n const query = searchParams.toString();\n const path = `/v1/schedules/${encodeURIComponent(name)}${suffix}`;\n\n return query ? `${path}?${query}` : path;\n}\n\nexport class SchedulesClient extends SchedulesApiClient {\n create({\n target,\n ...params\n }: CreateScheduleParams): Promise<CreateScheduleResponse> {\n return this.request('POST', '/v1/schedules', {\n ...params,\n target: {\n type: 'queue',\n topic: target.topic,\n },\n });\n }\n\n list(params: ListSchedulesParams = {}): Promise<ListSchedulesResponse> {\n const searchParams = new URLSearchParams();\n\n if (params.namespace !== undefined) {\n searchParams.set('namespace', params.namespace);\n }\n if (params.cursor !== undefined) {\n searchParams.set('cursor', params.cursor);\n }\n if (params.limit !== undefined) {\n searchParams.set('limit', String(params.limit));\n }\n\n const query = searchParams.toString();\n const path = query ? `/v1/schedules?${query}` : '/v1/schedules';\n\n return this.request('GET', path);\n }\n\n get({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('GET', schedulePath(name, namespace));\n }\n\n update({\n name,\n namespace,\n target,\n ...params\n }: UpdateScheduleParams): Promise<UpdateScheduleResponse> {\n return this.request('PATCH', schedulePath(name, namespace), {\n ...params,\n ...(target === undefined\n ? {}\n : {\n target: {\n type: 'queue',\n topic: target.topic,\n },\n }),\n });\n }\n\n async delete({ name, namespace }: ScheduleIdentifier): Promise<void> {\n await this.request('DELETE', schedulePath(name, namespace));\n }\n\n enable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/enable'));\n }\n\n disable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/disable'));\n }\n}\n","import { SchedulesClient } from './schedules';\n\nexport const schedules = new SchedulesClient();\n","/** CloudEvent type used for schedule function dispatches. */\nexport const SCHEDULE_CLOUD_EVENT_TYPE = 'com.vercel.schedule.v1beta';\nexport const CLOUD_EVENT_SPEC_VERSION_HEADER = 'ce-specversion';\nexport const CLOUD_EVENT_TYPE_HEADER = 'ce-type';\nexport const CLOUD_EVENT_SOURCE_HEADER = 'ce-source';\nexport const CLOUD_EVENT_ID_HEADER = 'ce-id';\nexport const SCHEDULE_FIRED_AT_HEADER = 'ce-time';\nexport const SCHEDULE_ID_HEADER = 'ce-vssscheduleid';\nexport const SCHEDULE_NAME_HEADER = 'ce-vssschedulename';\nexport const SCHEDULE_NAMESPACE_HEADER = 'ce-vssnamespace';\nexport const SCHEDULE_SOURCE_HEADER = 'ce-vssschedulesource';\n\nexport interface ScheduleMetadata {\n /** Unique identifier of the schedule. */\n scheduleId: string;\n /** Schedule name, unique within its project and namespace. */\n name: string;\n /** Namespace the schedule belongs to. */\n namespace: string;\n /** When the schedule fired. */\n firedAt: Date;\n /** How the schedule was defined, such as `static`. */\n source: string;\n}\n\nexport interface ParsedScheduleEvent<T = unknown> {\n payload: T | undefined;\n metadata: ScheduleMetadata;\n}\n\nexport type ScheduleHandler<T = unknown> = (\n payload: T | undefined,\n metadata: ScheduleMetadata,\n) => void | Promise<void>;\n\nexport class ScheduleEventParseError extends Error {\n cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = 'ScheduleEventParseError';\n if (options && 'cause' in options) {\n this.cause = options.cause;\n }\n }\n}\n\nfunction requiredHeader(request: Request, name: string): string {\n const value = request.headers.get(name);\n if (!value) {\n throw new ScheduleEventParseError(\n `Request is missing the ${name} header; it does not look like a schedule dispatch`,\n );\n }\n return value;\n}\n\n/** Parses a binary-mode schedule CloudEvent. */\nexport async function parseScheduleEvent<T = unknown>(\n request: Request,\n): Promise<ParsedScheduleEvent<T>> {\n if (request.method !== 'POST') {\n throw new ScheduleEventParseError(\n `Schedule dispatches are POST requests, received ${request.method}`,\n );\n }\n\n const specVersion = requiredHeader(request, CLOUD_EVENT_SPEC_VERSION_HEADER);\n if (specVersion !== '1.0') {\n throw new ScheduleEventParseError(\n `Unsupported CloudEvent spec version: ${specVersion}`,\n );\n }\n\n const eventType = requiredHeader(request, CLOUD_EVENT_TYPE_HEADER);\n if (eventType !== SCHEDULE_CLOUD_EVENT_TYPE) {\n throw new ScheduleEventParseError(\n `Unsupported schedule CloudEvent type: ${eventType}`,\n );\n }\n\n requiredHeader(request, CLOUD_EVENT_SOURCE_HEADER);\n requiredHeader(request, CLOUD_EVENT_ID_HEADER);\n const firedAtValue = requiredHeader(request, SCHEDULE_FIRED_AT_HEADER);\n const metadata: ScheduleMetadata = {\n scheduleId: requiredHeader(request, SCHEDULE_ID_HEADER),\n name: requiredHeader(request, SCHEDULE_NAME_HEADER),\n namespace: requiredHeader(request, SCHEDULE_NAMESPACE_HEADER),\n firedAt: new Date(firedAtValue),\n source: requiredHeader(request, SCHEDULE_SOURCE_HEADER),\n };\n\n if (Number.isNaN(metadata.firedAt.getTime())) {\n throw new ScheduleEventParseError(\n `Schedule dispatch time is not a valid timestamp: ${firedAtValue}`,\n );\n }\n\n if (request.body === null) {\n return { payload: undefined, metadata };\n }\n\n const contentType = request.headers.get('content-type');\n if (!contentType) {\n const body = await request.text();\n if (body === '') {\n return { payload: undefined, metadata };\n }\n }\n if (\n contentType?.split(';', 1)[0].trim().toLowerCase() !== 'application/json'\n ) {\n throw new ScheduleEventParseError(\n 'Schedule dispatch payload must use the application/json content type',\n );\n }\n\n try {\n return { payload: (await request.json()) as T, metadata };\n } catch (cause) {\n throw new ScheduleEventParseError(\n 'Schedule dispatch payload is not valid JSON',\n { cause },\n );\n }\n}\n\n/**\n * Creates a web-standard route handler for schedule dispatches.\n *\n * ```ts\n * export const POST = handleSchedule(async (payload, metadata) => {\n * console.log(metadata.name, payload);\n * });\n * ```\n */\nexport function handleSchedule<T = unknown>(\n handler: ScheduleHandler<T>,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n let event: ParsedScheduleEvent<T>;\n try {\n event = await parseScheduleEvent<T>(request);\n } catch (error) {\n if (error instanceof ScheduleEventParseError) {\n return Response.json({ error: error.message }, { status: 400 });\n }\n return Response.json(\n { error: 'Failed to parse schedule dispatch' },\n { status: 500 },\n );\n }\n\n try {\n await handler(event.payload, event.metadata);\n return new Response(null, { status: 200 });\n } catch {\n return Response.json(\n { error: 'Schedule handler failed' },\n { status: 500 },\n );\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/api-client.ts","../src/constants.ts","../src/schedules.ts","../src/default-client.ts","../src/schedule-event.ts"],"sourcesContent":["import { getVercelOidcToken } from '@vercel/oidc';\nimport { DEFAULT_SCHEDULES_BASE_URL } from './constants';\n\nexport interface SchedulesApiClientOptions {\n baseUrl?: string;\n token?: string;\n fetch?: typeof fetch;\n}\n\nexport class SchedulesApiError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = 'SchedulesApiError';\n this.status = status;\n }\n}\n\nexport class SchedulesApiClient {\n private readonly baseUrl: URL;\n private readonly token?: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(options: SchedulesApiClientOptions = {}) {\n this.baseUrl = new URL(\n options.baseUrl ??\n process.env.VERCEL_SCHEDULE_BASE_URL ??\n DEFAULT_SCHEDULES_BASE_URL,\n );\n this.token = options.token;\n this.fetchImpl = options.fetch ?? fetch;\n }\n\n protected async getToken(): Promise<string> {\n if (this.token) {\n return this.token;\n }\n\n try {\n return await getVercelOidcToken();\n } catch (err) {\n const cause = err instanceof Error ? err.message : String(err);\n const isDev = process.env.NODE_ENV === 'development';\n\n throw new Error(\n isDev\n ? 'Failed to get OIDC token for local development.\\n\\n' +\n 'To fix this, pull your environment variables with Vercel CLI:\\n' +\n ' `vercel env pull`\\n\\n' +\n `Cause: ${cause}`\n : 'Failed to get OIDC token. This usually means the function is running ' +\n 'outside of a Vercel Function environment.\\n\\n' +\n 'To fix this, either:\\n' +\n ' - Deploy to Vercel (OIDC tokens are provisioned automatically)\\n' +\n ' - Provide a token explicitly: `new Schedules({ token: \"...\" })`\\n\\n' +\n `Cause: ${cause}`,\n );\n }\n }\n\n protected async request<T>(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<T> {\n const headers = new Headers({\n Authorization: `Bearer ${await this.getToken()}`,\n });\n\n if (body !== undefined) {\n headers.set('Content-Type', 'application/json');\n }\n\n const response = await this.fetchImpl(new URL(path, this.baseUrl), {\n method,\n headers,\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new SchedulesApiError(response.status, text || response.statusText);\n }\n\n if (response.status === 204) {\n return undefined as T;\n }\n\n return (await response.json()) as T;\n }\n}\n","export const DEFAULT_SCHEDULES_BASE_URL = 'https://vercel-schedules.com';\n","import { SchedulesApiClient } from './api-client';\nimport type {\n CreateScheduleParams,\n CreateScheduleResponse,\n InvokeScheduleParams,\n ListSchedulesParams,\n ListSchedulesResponse,\n Schedule,\n ScheduleIdentifier,\n UpdateScheduleParams,\n UpdateScheduleResponse,\n} from './types';\n\nfunction schedulePath(name: string, namespace?: string, suffix = ''): string {\n const searchParams = new URLSearchParams();\n\n if (namespace !== undefined) {\n searchParams.set('namespace', namespace);\n }\n\n const query = searchParams.toString();\n const path = `/v1/schedules/${encodeURIComponent(name)}${suffix}`;\n\n return query ? `${path}?${query}` : path;\n}\n\nexport class SchedulesClient extends SchedulesApiClient {\n create({\n target,\n ...params\n }: CreateScheduleParams): Promise<CreateScheduleResponse> {\n return this.request('POST', '/v1/schedules', {\n ...params,\n target: {\n type: 'queue',\n topic: target.topic,\n },\n });\n }\n\n list(params: ListSchedulesParams = {}): Promise<ListSchedulesResponse> {\n const searchParams = new URLSearchParams();\n\n if (params.namespace !== undefined) {\n searchParams.set('namespace', params.namespace);\n }\n if (params.cursor !== undefined) {\n searchParams.set('cursor', params.cursor);\n }\n if (params.limit !== undefined) {\n searchParams.set('limit', String(params.limit));\n }\n\n const query = searchParams.toString();\n const path = query ? `/v1/schedules?${query}` : '/v1/schedules';\n\n return this.request('GET', path);\n }\n\n get({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('GET', schedulePath(name, namespace));\n }\n\n update({\n name,\n namespace,\n target,\n ...params\n }: UpdateScheduleParams): Promise<UpdateScheduleResponse> {\n return this.request('PATCH', schedulePath(name, namespace), {\n ...params,\n ...(target === undefined\n ? {}\n : {\n target: {\n type: 'queue',\n topic: target.topic,\n },\n }),\n });\n }\n\n async delete({ name, namespace }: ScheduleIdentifier): Promise<void> {\n await this.request('DELETE', schedulePath(name, namespace));\n }\n\n enable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/enable'));\n }\n\n disable({ name, namespace }: ScheduleIdentifier): Promise<Schedule> {\n return this.request('POST', schedulePath(name, namespace, '/disable'));\n }\n\n async invoke({ name, namespace }: InvokeScheduleParams): Promise<void> {\n await this.request('POST', schedulePath(name, namespace, '/invoke'));\n }\n}\n","import { SchedulesClient } from './schedules';\n\nexport const schedules = new SchedulesClient();\n","/** CloudEvent type used for schedule function dispatches. */\nexport const SCHEDULE_CLOUD_EVENT_TYPE = 'com.vercel.schedule.v1beta';\nexport const CLOUD_EVENT_SPEC_VERSION_HEADER = 'ce-specversion';\nexport const CLOUD_EVENT_TYPE_HEADER = 'ce-type';\nexport const CLOUD_EVENT_SOURCE_HEADER = 'ce-source';\nexport const CLOUD_EVENT_ID_HEADER = 'ce-id';\nexport const SCHEDULE_SCHEDULED_AT_HEADER = 'ce-vssscheduledat';\nexport const SCHEDULE_ID_HEADER = 'ce-vssscheduleid';\nexport const SCHEDULE_NAME_HEADER = 'ce-vssschedulename';\nexport const SCHEDULE_NAMESPACE_HEADER = 'ce-vssnamespace';\nexport const SCHEDULE_SOURCE_HEADER = 'ce-vssschedulesource';\n\nexport interface ScheduleMetadata {\n /** Unique identifier of the schedule. */\n scheduleId: string;\n /** Unique identifier for this schedule occurrence. */\n executionId: string;\n /** Schedule name, unique within its project and namespace. */\n name: string;\n /** Namespace the schedule belongs to. */\n namespace: string;\n /** When the schedule was scheduled to run. */\n scheduledAt: Date;\n /** How the schedule was defined, such as `static`. */\n source: string;\n}\n\nexport type ScheduleHandler = (\n metadata: ScheduleMetadata,\n) => void | Promise<void>;\n\nexport class ScheduleEventParseError extends Error {\n cause?: unknown;\n\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n this.name = 'ScheduleEventParseError';\n if (options && 'cause' in options) {\n this.cause = options.cause;\n }\n }\n}\n\nfunction requiredHeader(request: Request, name: string): string {\n const value = request.headers.get(name);\n if (!value) {\n throw new ScheduleEventParseError(\n `Request is missing the ${name} header; it does not look like a schedule dispatch`,\n );\n }\n return value;\n}\n\n/** Parses schedule metadata from a binary-mode schedule CloudEvent. */\nexport async function parseScheduleEvent(\n request: Request,\n): Promise<ScheduleMetadata> {\n if (request.method !== 'POST') {\n throw new ScheduleEventParseError(\n `Schedule dispatches are POST requests, received ${request.method}`,\n );\n }\n\n const specVersion = requiredHeader(request, CLOUD_EVENT_SPEC_VERSION_HEADER);\n if (specVersion !== '1.0') {\n throw new ScheduleEventParseError(\n `Unsupported CloudEvent spec version: ${specVersion}`,\n );\n }\n\n const eventType = requiredHeader(request, CLOUD_EVENT_TYPE_HEADER);\n if (eventType !== SCHEDULE_CLOUD_EVENT_TYPE) {\n throw new ScheduleEventParseError(\n `Unsupported schedule CloudEvent type: ${eventType}`,\n );\n }\n\n requiredHeader(request, CLOUD_EVENT_SOURCE_HEADER);\n const executionId = requiredHeader(request, CLOUD_EVENT_ID_HEADER);\n const scheduledAtValue = requiredHeader(\n request,\n SCHEDULE_SCHEDULED_AT_HEADER,\n );\n const metadata: ScheduleMetadata = {\n scheduleId: requiredHeader(request, SCHEDULE_ID_HEADER),\n executionId,\n name: requiredHeader(request, SCHEDULE_NAME_HEADER),\n namespace: requiredHeader(request, SCHEDULE_NAMESPACE_HEADER),\n scheduledAt: new Date(scheduledAtValue),\n source: requiredHeader(request, SCHEDULE_SOURCE_HEADER),\n };\n\n if (Number.isNaN(metadata.scheduledAt.getTime())) {\n throw new ScheduleEventParseError(\n `Schedule scheduledAt is not a valid timestamp: ${scheduledAtValue}`,\n );\n }\n\n return metadata;\n}\n\n/**\n * Creates a web-standard route handler for schedule dispatches.\n *\n * ```ts\n * export const POST = handleSchedule(async (metadata) => {\n * console.log(metadata.name, metadata.scheduledAt);\n * });\n * ```\n */\nexport function handleSchedule(\n handler: ScheduleHandler,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n let metadata: ScheduleMetadata;\n try {\n metadata = await parseScheduleEvent(request);\n } catch (error) {\n if (error instanceof ScheduleEventParseError) {\n return Response.json({ error: error.message }, { status: 400 });\n }\n return Response.json(\n { error: 'Failed to parse schedule dispatch' },\n { status: 500 },\n );\n }\n\n try {\n await handler(metadata);\n return new Response(null, { status: 200 });\n } catch {\n return Response.json(\n { error: 'Schedule handler failed' },\n { status: 500 },\n );\n }\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/schedules",
3
- "version": "0.0.0-alpha.7",
3
+ "version": "0.0.0-alpha.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },