@tanstack/workflow-netlify 0.0.1

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 ADDED
@@ -0,0 +1,51 @@
1
+ # @tanstack/workflow-netlify
2
+
3
+ Experimental Netlify host adapter for TanStack Workflow.
4
+
5
+ See the main [Deployment guide](../../docs/guide/deployment.md) and
6
+ [Host adapters API](../../docs/api/host-adapters.md).
7
+
8
+ The adapter wires a `WorkflowRuntimeDefinition` into a single Netlify scheduled
9
+ function. It materializes registered workflow schedules into the runtime store,
10
+ then calls `runtime.sweep()` to run due scheduled workflows and resume due
11
+ timers.
12
+
13
+ ```ts
14
+ // netlify/functions/workflow-sweep-background.ts
15
+ import {
16
+ createNetlifyWorkflowSweepConfig,
17
+ createNetlifyWorkflowSweepHandler,
18
+ } from '@tanstack/workflow-netlify'
19
+ import { workflowRuntime } from '../../src/workflows/runtime.server'
20
+
21
+ export default createNetlifyWorkflowSweepHandler({
22
+ runtime: workflowRuntime,
23
+ })
24
+
25
+ export const config = createNetlifyWorkflowSweepConfig({
26
+ schedule: '*/5 * * * *',
27
+ })
28
+ ```
29
+
30
+ Workflow schedules stay in the runtime registration:
31
+
32
+ ```ts
33
+ import { defineWorkflowRuntime, every } from '@tanstack/workflow-runtime'
34
+
35
+ export const workflowRuntime = defineWorkflowRuntime({
36
+ store,
37
+ workflows: {
38
+ 'intent-process': {
39
+ load: async () => intentProcessWorkflow,
40
+ schedules: [
41
+ {
42
+ id: 'intent-process-every-15m',
43
+ schedule: every.minutes(15),
44
+ overlapPolicy: 'skip',
45
+ input: { batchSize: 50 },
46
+ },
47
+ ],
48
+ },
49
+ },
50
+ })
51
+ ```
package/dist/index.cjs ADDED
@@ -0,0 +1,8 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_sweep_handler = require('./sweep-handler.cjs');
3
+ let _tanstack_workflow_runtime = require("@tanstack/workflow-runtime");
4
+
5
+ exports.createNetlifyWorkflowSweepConfig = require_sweep_handler.createNetlifyWorkflowSweepConfig;
6
+ exports.createNetlifyWorkflowSweepHandler = require_sweep_handler.createNetlifyWorkflowSweepHandler;
7
+ exports.materializeWorkflowSchedules = _tanstack_workflow_runtime.materializeWorkflowSchedules;
8
+ exports.netlifyWorkflowSweepConfig = require_sweep_handler.netlifyWorkflowSweepConfig;
@@ -0,0 +1,2 @@
1
+ import { MaterializeWorkflowSchedulesOptions, MaterializedWorkflowSchedule, NetlifyWorkflowSweepConfig, NetlifyWorkflowSweepConfigOptions, NetlifyWorkflowSweepHandler, NetlifyWorkflowSweepHandlerOptions, NetlifyWorkflowSweepResponse, createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig } from "./sweep-handler.cjs";
2
+ export { type MaterializeWorkflowSchedulesOptions, type MaterializedWorkflowSchedule, type NetlifyWorkflowSweepConfig, type NetlifyWorkflowSweepConfigOptions, type NetlifyWorkflowSweepHandler, type NetlifyWorkflowSweepHandlerOptions, type NetlifyWorkflowSweepResponse, createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig };
@@ -0,0 +1,2 @@
1
+ import { MaterializeWorkflowSchedulesOptions, MaterializedWorkflowSchedule, NetlifyWorkflowSweepConfig, NetlifyWorkflowSweepConfigOptions, NetlifyWorkflowSweepHandler, NetlifyWorkflowSweepHandlerOptions, NetlifyWorkflowSweepResponse, createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig } from "./sweep-handler.js";
2
+ export { type MaterializeWorkflowSchedulesOptions, type MaterializedWorkflowSchedule, type NetlifyWorkflowSweepConfig, type NetlifyWorkflowSweepConfigOptions, type NetlifyWorkflowSweepHandler, type NetlifyWorkflowSweepHandlerOptions, type NetlifyWorkflowSweepResponse, createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig } from "./sweep-handler.js";
2
+
3
+ export { createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig };
@@ -0,0 +1,64 @@
1
+ let _tanstack_workflow_runtime = require("@tanstack/workflow-runtime");
2
+
3
+ //#region src/sweep-handler.ts
4
+ const DEFAULT_SWEEP_INTERVAL_MINUTES = 5;
5
+ const netlifyWorkflowSweepConfig = createNetlifyWorkflowSweepConfig();
6
+ function createNetlifyWorkflowSweepConfig(options = {}) {
7
+ if (options.schedule) return { schedule: options.schedule };
8
+ const everyMinutes = options.everyMinutes ?? DEFAULT_SWEEP_INTERVAL_MINUTES;
9
+ if (!Number.isInteger(everyMinutes) || everyMinutes <= 0) throw new Error("Netlify workflow sweep interval must be a positive integer.");
10
+ return { schedule: everyMinutes === 1 ? "* * * * *" : `*/${everyMinutes} * * * *` };
11
+ }
12
+ function createNetlifyWorkflowSweepHandler(options) {
13
+ return async (request) => {
14
+ const now = options.now?.() ?? Date.now();
15
+ const materialized = options.materializeSchedules === false ? [] : await (0, _tanstack_workflow_runtime.materializeWorkflowSchedules)(options.runtime, {
16
+ now,
17
+ cronLookbackMs: options.cronLookbackMs
18
+ });
19
+ const leaseOwner = resolveLeaseOwner(options.leaseOwner, request, now);
20
+ const sweepArgs = {
21
+ now,
22
+ leaseOwner,
23
+ limit: options.limit,
24
+ maxScheduledRuns: options.maxScheduledRuns,
25
+ maxTimers: options.maxTimers,
26
+ maxDurationMs: options.maxDurationMs,
27
+ leaseMs: options.leaseMs,
28
+ includeEvents: options.includeEvents ?? false,
29
+ maxEvents: options.maxEvents
30
+ };
31
+ const sweep = await options.runtime.sweep(sweepArgs);
32
+ const response = {
33
+ ok: true,
34
+ now,
35
+ leaseOwner,
36
+ materialized,
37
+ summary: {
38
+ materialized: materialized.length,
39
+ ...sweep.summary
40
+ },
41
+ deadlineReached: sweep.deadlineReached,
42
+ remainingMayExist: sweep.remainingMayExist,
43
+ ...options.includeSweepResult ? { sweep } : void 0
44
+ };
45
+ return Response.json(response);
46
+ };
47
+ }
48
+ function resolveLeaseOwner(leaseOwner, request, now) {
49
+ if (typeof leaseOwner === "string") return leaseOwner;
50
+ if (typeof leaseOwner === "function") return leaseOwner({
51
+ request,
52
+ now
53
+ }) ?? defaultLeaseOwner(request, now);
54
+ return defaultLeaseOwner(request, now);
55
+ }
56
+ function defaultLeaseOwner(request, now) {
57
+ return `netlify:${request.headers.get("x-nf-request-id") ?? now}`;
58
+ }
59
+
60
+ //#endregion
61
+ exports.createNetlifyWorkflowSweepConfig = createNetlifyWorkflowSweepConfig;
62
+ exports.createNetlifyWorkflowSweepHandler = createNetlifyWorkflowSweepHandler;
63
+ exports.netlifyWorkflowSweepConfig = netlifyWorkflowSweepConfig;
64
+ //# sourceMappingURL=sweep-handler.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweep-handler.cjs","names":[],"sources":["../src/sweep-handler.ts"],"sourcesContent":["import { materializeWorkflowSchedules } from '@tanstack/workflow-runtime'\nimport type {\n MaterializedWorkflowSchedule,\n WorkflowRegistrationMap,\n WorkflowRuntimeDefinition,\n WorkflowRuntimeSweepArgs,\n WorkflowRuntimeSweepResult,\n} from '@tanstack/workflow-runtime'\n\nconst DEFAULT_SWEEP_INTERVAL_MINUTES = 5\n\nexport { materializeWorkflowSchedules }\nexport type {\n MaterializedWorkflowSchedule,\n MaterializeWorkflowSchedulesOptions,\n} from '@tanstack/workflow-runtime'\n\nexport interface NetlifyWorkflowSweepConfig {\n schedule: string\n}\n\nexport interface NetlifyWorkflowSweepConfigOptions {\n schedule?: string\n everyMinutes?: number\n}\n\nexport interface NetlifyWorkflowSweepResponse {\n ok: true\n now: number\n leaseOwner: string\n materialized: ReadonlyArray<MaterializedWorkflowSchedule>\n summary: NetlifyWorkflowSweepSummary\n deadlineReached: boolean\n remainingMayExist: boolean\n sweep?: WorkflowRuntimeSweepResult\n}\n\nexport type NetlifyWorkflowSweepSummary =\n WorkflowRuntimeSweepResult['summary'] & {\n materialized: number\n }\n\nexport type NetlifyWorkflowSweepHandler = (\n request: Request,\n) => Promise<Response>\n\nexport interface NetlifyWorkflowSweepHandlerOptions<\n TWorkflows extends WorkflowRegistrationMap = WorkflowRegistrationMap,\n> {\n runtime: WorkflowRuntimeDefinition<TWorkflows>\n now?: () => number\n leaseOwner?:\n | string\n | ((args: { request: Request; now: number }) => string | undefined)\n limit?: number\n maxScheduledRuns?: number\n maxTimers?: number\n maxDurationMs?: number\n leaseMs?: number\n includeEvents?: boolean\n maxEvents?: number\n includeSweepResult?: boolean\n materializeSchedules?: boolean\n cronLookbackMs?: number\n}\n\nexport const netlifyWorkflowSweepConfig = createNetlifyWorkflowSweepConfig()\n\nexport function createNetlifyWorkflowSweepConfig(\n options: NetlifyWorkflowSweepConfigOptions = {},\n): NetlifyWorkflowSweepConfig {\n if (options.schedule) return { schedule: options.schedule }\n\n const everyMinutes = options.everyMinutes ?? DEFAULT_SWEEP_INTERVAL_MINUTES\n if (!Number.isInteger(everyMinutes) || everyMinutes <= 0) {\n throw new Error(\n 'Netlify workflow sweep interval must be a positive integer.',\n )\n }\n\n return {\n schedule: everyMinutes === 1 ? '* * * * *' : `*/${everyMinutes} * * * *`,\n }\n}\n\nexport function createNetlifyWorkflowSweepHandler<\n TWorkflows extends WorkflowRegistrationMap,\n>(\n options: NetlifyWorkflowSweepHandlerOptions<TWorkflows>,\n): NetlifyWorkflowSweepHandler {\n return async (request) => {\n const now = options.now?.() ?? Date.now()\n const materialized =\n options.materializeSchedules === false\n ? []\n : await materializeWorkflowSchedules(options.runtime, {\n now,\n cronLookbackMs: options.cronLookbackMs,\n })\n const leaseOwner = resolveLeaseOwner(options.leaseOwner, request, now)\n const sweepArgs: WorkflowRuntimeSweepArgs = {\n now,\n leaseOwner,\n limit: options.limit,\n maxScheduledRuns: options.maxScheduledRuns,\n maxTimers: options.maxTimers,\n maxDurationMs: options.maxDurationMs,\n leaseMs: options.leaseMs,\n includeEvents: options.includeEvents ?? false,\n maxEvents: options.maxEvents,\n }\n const sweep = await options.runtime.sweep(sweepArgs)\n const response: NetlifyWorkflowSweepResponse = {\n ok: true,\n now,\n leaseOwner,\n materialized,\n summary: {\n materialized: materialized.length,\n ...sweep.summary,\n },\n deadlineReached: sweep.deadlineReached,\n remainingMayExist: sweep.remainingMayExist,\n ...(options.includeSweepResult ? { sweep } : undefined),\n }\n\n return Response.json(response)\n }\n}\n\nfunction resolveLeaseOwner(\n leaseOwner: NetlifyWorkflowSweepHandlerOptions['leaseOwner'],\n request: Request,\n now: number,\n) {\n if (typeof leaseOwner === 'string') return leaseOwner\n if (typeof leaseOwner === 'function') {\n return leaseOwner({ request, now }) ?? defaultLeaseOwner(request, now)\n }\n return defaultLeaseOwner(request, now)\n}\n\nfunction defaultLeaseOwner(request: Request, now: number) {\n return `netlify:${request.headers.get('x-nf-request-id') ?? now}`\n}\n"],"mappings":";;;AASA,MAAM,iCAAiC;AAyDvC,MAAa,6BAA6B,kCAAkC;AAE5E,SAAgB,iCACd,UAA6C,EAAE,EACnB;AAC5B,KAAI,QAAQ,SAAU,QAAO,EAAE,UAAU,QAAQ,UAAU;CAE3D,MAAM,eAAe,QAAQ,gBAAgB;AAC7C,KAAI,CAAC,OAAO,UAAU,aAAa,IAAI,gBAAgB,EACrD,OAAM,IAAI,MACR,8DACD;AAGH,QAAO,EACL,UAAU,iBAAiB,IAAI,cAAc,KAAK,aAAa,WAChE;;AAGH,SAAgB,kCAGd,SAC6B;AAC7B,QAAO,OAAO,YAAY;EACxB,MAAM,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK;EACzC,MAAM,eACJ,QAAQ,yBAAyB,QAC7B,EAAE,GACF,mEAAmC,QAAQ,SAAS;GAClD;GACA,gBAAgB,QAAQ;GACzB,CAAC;EACR,MAAM,aAAa,kBAAkB,QAAQ,YAAY,SAAS,IAAI;EACtE,MAAM,YAAsC;GAC1C;GACA;GACA,OAAO,QAAQ;GACf,kBAAkB,QAAQ;GAC1B,WAAW,QAAQ;GACnB,eAAe,QAAQ;GACvB,SAAS,QAAQ;GACjB,eAAe,QAAQ,iBAAiB;GACxC,WAAW,QAAQ;GACpB;EACD,MAAM,QAAQ,MAAM,QAAQ,QAAQ,MAAM,UAAU;EACpD,MAAM,WAAyC;GAC7C,IAAI;GACJ;GACA;GACA;GACA,SAAS;IACP,cAAc,aAAa;IAC3B,GAAG,MAAM;IACV;GACD,iBAAiB,MAAM;GACvB,mBAAmB,MAAM;GACzB,GAAI,QAAQ,qBAAqB,EAAE,OAAO,GAAG;GAC9C;AAED,SAAO,SAAS,KAAK,SAAS;;;AAIlC,SAAS,kBACP,YACA,SACA,KACA;AACA,KAAI,OAAO,eAAe,SAAU,QAAO;AAC3C,KAAI,OAAO,eAAe,WACxB,QAAO,WAAW;EAAE;EAAS;EAAK,CAAC,IAAI,kBAAkB,SAAS,IAAI;AAExE,QAAO,kBAAkB,SAAS,IAAI;;AAGxC,SAAS,kBAAkB,SAAkB,KAAa;AACxD,QAAO,WAAW,QAAQ,QAAQ,IAAI,kBAAkB,IAAI"}
@@ -0,0 +1,48 @@
1
+ import { MaterializeWorkflowSchedulesOptions, MaterializedWorkflowSchedule, MaterializedWorkflowSchedule as MaterializedWorkflowSchedule$1, WorkflowRegistrationMap, WorkflowRuntimeDefinition, WorkflowRuntimeSweepResult, materializeWorkflowSchedules } from "@tanstack/workflow-runtime";
2
+
3
+ //#region src/sweep-handler.d.ts
4
+ interface NetlifyWorkflowSweepConfig {
5
+ schedule: string;
6
+ }
7
+ interface NetlifyWorkflowSweepConfigOptions {
8
+ schedule?: string;
9
+ everyMinutes?: number;
10
+ }
11
+ interface NetlifyWorkflowSweepResponse {
12
+ ok: true;
13
+ now: number;
14
+ leaseOwner: string;
15
+ materialized: ReadonlyArray<MaterializedWorkflowSchedule>;
16
+ summary: NetlifyWorkflowSweepSummary;
17
+ deadlineReached: boolean;
18
+ remainingMayExist: boolean;
19
+ sweep?: WorkflowRuntimeSweepResult;
20
+ }
21
+ type NetlifyWorkflowSweepSummary = WorkflowRuntimeSweepResult['summary'] & {
22
+ materialized: number;
23
+ };
24
+ type NetlifyWorkflowSweepHandler = (request: Request) => Promise<Response>;
25
+ interface NetlifyWorkflowSweepHandlerOptions<TWorkflows extends WorkflowRegistrationMap = WorkflowRegistrationMap> {
26
+ runtime: WorkflowRuntimeDefinition<TWorkflows>;
27
+ now?: () => number;
28
+ leaseOwner?: string | ((args: {
29
+ request: Request;
30
+ now: number;
31
+ }) => string | undefined);
32
+ limit?: number;
33
+ maxScheduledRuns?: number;
34
+ maxTimers?: number;
35
+ maxDurationMs?: number;
36
+ leaseMs?: number;
37
+ includeEvents?: boolean;
38
+ maxEvents?: number;
39
+ includeSweepResult?: boolean;
40
+ materializeSchedules?: boolean;
41
+ cronLookbackMs?: number;
42
+ }
43
+ declare const netlifyWorkflowSweepConfig: NetlifyWorkflowSweepConfig;
44
+ declare function createNetlifyWorkflowSweepConfig(options?: NetlifyWorkflowSweepConfigOptions): NetlifyWorkflowSweepConfig;
45
+ declare function createNetlifyWorkflowSweepHandler<TWorkflows extends WorkflowRegistrationMap>(options: NetlifyWorkflowSweepHandlerOptions<TWorkflows>): NetlifyWorkflowSweepHandler;
46
+ //#endregion
47
+ export { type MaterializeWorkflowSchedulesOptions, type MaterializedWorkflowSchedule$1 as MaterializedWorkflowSchedule, NetlifyWorkflowSweepConfig, NetlifyWorkflowSweepConfigOptions, NetlifyWorkflowSweepHandler, NetlifyWorkflowSweepHandlerOptions, NetlifyWorkflowSweepResponse, createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig };
48
+ //# sourceMappingURL=sweep-handler.d.cts.map
@@ -0,0 +1,48 @@
1
+ import { MaterializeWorkflowSchedulesOptions, MaterializedWorkflowSchedule, MaterializedWorkflowSchedule as MaterializedWorkflowSchedule$1, WorkflowRegistrationMap, WorkflowRuntimeDefinition, WorkflowRuntimeSweepResult, materializeWorkflowSchedules } from "@tanstack/workflow-runtime";
2
+
3
+ //#region src/sweep-handler.d.ts
4
+ interface NetlifyWorkflowSweepConfig {
5
+ schedule: string;
6
+ }
7
+ interface NetlifyWorkflowSweepConfigOptions {
8
+ schedule?: string;
9
+ everyMinutes?: number;
10
+ }
11
+ interface NetlifyWorkflowSweepResponse {
12
+ ok: true;
13
+ now: number;
14
+ leaseOwner: string;
15
+ materialized: ReadonlyArray<MaterializedWorkflowSchedule>;
16
+ summary: NetlifyWorkflowSweepSummary;
17
+ deadlineReached: boolean;
18
+ remainingMayExist: boolean;
19
+ sweep?: WorkflowRuntimeSweepResult;
20
+ }
21
+ type NetlifyWorkflowSweepSummary = WorkflowRuntimeSweepResult['summary'] & {
22
+ materialized: number;
23
+ };
24
+ type NetlifyWorkflowSweepHandler = (request: Request) => Promise<Response>;
25
+ interface NetlifyWorkflowSweepHandlerOptions<TWorkflows extends WorkflowRegistrationMap = WorkflowRegistrationMap> {
26
+ runtime: WorkflowRuntimeDefinition<TWorkflows>;
27
+ now?: () => number;
28
+ leaseOwner?: string | ((args: {
29
+ request: Request;
30
+ now: number;
31
+ }) => string | undefined);
32
+ limit?: number;
33
+ maxScheduledRuns?: number;
34
+ maxTimers?: number;
35
+ maxDurationMs?: number;
36
+ leaseMs?: number;
37
+ includeEvents?: boolean;
38
+ maxEvents?: number;
39
+ includeSweepResult?: boolean;
40
+ materializeSchedules?: boolean;
41
+ cronLookbackMs?: number;
42
+ }
43
+ declare const netlifyWorkflowSweepConfig: NetlifyWorkflowSweepConfig;
44
+ declare function createNetlifyWorkflowSweepConfig(options?: NetlifyWorkflowSweepConfigOptions): NetlifyWorkflowSweepConfig;
45
+ declare function createNetlifyWorkflowSweepHandler<TWorkflows extends WorkflowRegistrationMap>(options: NetlifyWorkflowSweepHandlerOptions<TWorkflows>): NetlifyWorkflowSweepHandler;
46
+ //#endregion
47
+ export { type MaterializeWorkflowSchedulesOptions, type MaterializedWorkflowSchedule$1 as MaterializedWorkflowSchedule, NetlifyWorkflowSweepConfig, NetlifyWorkflowSweepConfigOptions, NetlifyWorkflowSweepHandler, NetlifyWorkflowSweepHandlerOptions, NetlifyWorkflowSweepResponse, createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig };
48
+ //# sourceMappingURL=sweep-handler.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { materializeWorkflowSchedules } from "@tanstack/workflow-runtime";
2
+
3
+ //#region src/sweep-handler.ts
4
+ const DEFAULT_SWEEP_INTERVAL_MINUTES = 5;
5
+ const netlifyWorkflowSweepConfig = createNetlifyWorkflowSweepConfig();
6
+ function createNetlifyWorkflowSweepConfig(options = {}) {
7
+ if (options.schedule) return { schedule: options.schedule };
8
+ const everyMinutes = options.everyMinutes ?? DEFAULT_SWEEP_INTERVAL_MINUTES;
9
+ if (!Number.isInteger(everyMinutes) || everyMinutes <= 0) throw new Error("Netlify workflow sweep interval must be a positive integer.");
10
+ return { schedule: everyMinutes === 1 ? "* * * * *" : `*/${everyMinutes} * * * *` };
11
+ }
12
+ function createNetlifyWorkflowSweepHandler(options) {
13
+ return async (request) => {
14
+ const now = options.now?.() ?? Date.now();
15
+ const materialized = options.materializeSchedules === false ? [] : await materializeWorkflowSchedules(options.runtime, {
16
+ now,
17
+ cronLookbackMs: options.cronLookbackMs
18
+ });
19
+ const leaseOwner = resolveLeaseOwner(options.leaseOwner, request, now);
20
+ const sweepArgs = {
21
+ now,
22
+ leaseOwner,
23
+ limit: options.limit,
24
+ maxScheduledRuns: options.maxScheduledRuns,
25
+ maxTimers: options.maxTimers,
26
+ maxDurationMs: options.maxDurationMs,
27
+ leaseMs: options.leaseMs,
28
+ includeEvents: options.includeEvents ?? false,
29
+ maxEvents: options.maxEvents
30
+ };
31
+ const sweep = await options.runtime.sweep(sweepArgs);
32
+ const response = {
33
+ ok: true,
34
+ now,
35
+ leaseOwner,
36
+ materialized,
37
+ summary: {
38
+ materialized: materialized.length,
39
+ ...sweep.summary
40
+ },
41
+ deadlineReached: sweep.deadlineReached,
42
+ remainingMayExist: sweep.remainingMayExist,
43
+ ...options.includeSweepResult ? { sweep } : void 0
44
+ };
45
+ return Response.json(response);
46
+ };
47
+ }
48
+ function resolveLeaseOwner(leaseOwner, request, now) {
49
+ if (typeof leaseOwner === "string") return leaseOwner;
50
+ if (typeof leaseOwner === "function") return leaseOwner({
51
+ request,
52
+ now
53
+ }) ?? defaultLeaseOwner(request, now);
54
+ return defaultLeaseOwner(request, now);
55
+ }
56
+ function defaultLeaseOwner(request, now) {
57
+ return `netlify:${request.headers.get("x-nf-request-id") ?? now}`;
58
+ }
59
+
60
+ //#endregion
61
+ export { createNetlifyWorkflowSweepConfig, createNetlifyWorkflowSweepHandler, materializeWorkflowSchedules, netlifyWorkflowSweepConfig };
62
+ //# sourceMappingURL=sweep-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sweep-handler.js","names":[],"sources":["../src/sweep-handler.ts"],"sourcesContent":["import { materializeWorkflowSchedules } from '@tanstack/workflow-runtime'\nimport type {\n MaterializedWorkflowSchedule,\n WorkflowRegistrationMap,\n WorkflowRuntimeDefinition,\n WorkflowRuntimeSweepArgs,\n WorkflowRuntimeSweepResult,\n} from '@tanstack/workflow-runtime'\n\nconst DEFAULT_SWEEP_INTERVAL_MINUTES = 5\n\nexport { materializeWorkflowSchedules }\nexport type {\n MaterializedWorkflowSchedule,\n MaterializeWorkflowSchedulesOptions,\n} from '@tanstack/workflow-runtime'\n\nexport interface NetlifyWorkflowSweepConfig {\n schedule: string\n}\n\nexport interface NetlifyWorkflowSweepConfigOptions {\n schedule?: string\n everyMinutes?: number\n}\n\nexport interface NetlifyWorkflowSweepResponse {\n ok: true\n now: number\n leaseOwner: string\n materialized: ReadonlyArray<MaterializedWorkflowSchedule>\n summary: NetlifyWorkflowSweepSummary\n deadlineReached: boolean\n remainingMayExist: boolean\n sweep?: WorkflowRuntimeSweepResult\n}\n\nexport type NetlifyWorkflowSweepSummary =\n WorkflowRuntimeSweepResult['summary'] & {\n materialized: number\n }\n\nexport type NetlifyWorkflowSweepHandler = (\n request: Request,\n) => Promise<Response>\n\nexport interface NetlifyWorkflowSweepHandlerOptions<\n TWorkflows extends WorkflowRegistrationMap = WorkflowRegistrationMap,\n> {\n runtime: WorkflowRuntimeDefinition<TWorkflows>\n now?: () => number\n leaseOwner?:\n | string\n | ((args: { request: Request; now: number }) => string | undefined)\n limit?: number\n maxScheduledRuns?: number\n maxTimers?: number\n maxDurationMs?: number\n leaseMs?: number\n includeEvents?: boolean\n maxEvents?: number\n includeSweepResult?: boolean\n materializeSchedules?: boolean\n cronLookbackMs?: number\n}\n\nexport const netlifyWorkflowSweepConfig = createNetlifyWorkflowSweepConfig()\n\nexport function createNetlifyWorkflowSweepConfig(\n options: NetlifyWorkflowSweepConfigOptions = {},\n): NetlifyWorkflowSweepConfig {\n if (options.schedule) return { schedule: options.schedule }\n\n const everyMinutes = options.everyMinutes ?? DEFAULT_SWEEP_INTERVAL_MINUTES\n if (!Number.isInteger(everyMinutes) || everyMinutes <= 0) {\n throw new Error(\n 'Netlify workflow sweep interval must be a positive integer.',\n )\n }\n\n return {\n schedule: everyMinutes === 1 ? '* * * * *' : `*/${everyMinutes} * * * *`,\n }\n}\n\nexport function createNetlifyWorkflowSweepHandler<\n TWorkflows extends WorkflowRegistrationMap,\n>(\n options: NetlifyWorkflowSweepHandlerOptions<TWorkflows>,\n): NetlifyWorkflowSweepHandler {\n return async (request) => {\n const now = options.now?.() ?? Date.now()\n const materialized =\n options.materializeSchedules === false\n ? []\n : await materializeWorkflowSchedules(options.runtime, {\n now,\n cronLookbackMs: options.cronLookbackMs,\n })\n const leaseOwner = resolveLeaseOwner(options.leaseOwner, request, now)\n const sweepArgs: WorkflowRuntimeSweepArgs = {\n now,\n leaseOwner,\n limit: options.limit,\n maxScheduledRuns: options.maxScheduledRuns,\n maxTimers: options.maxTimers,\n maxDurationMs: options.maxDurationMs,\n leaseMs: options.leaseMs,\n includeEvents: options.includeEvents ?? false,\n maxEvents: options.maxEvents,\n }\n const sweep = await options.runtime.sweep(sweepArgs)\n const response: NetlifyWorkflowSweepResponse = {\n ok: true,\n now,\n leaseOwner,\n materialized,\n summary: {\n materialized: materialized.length,\n ...sweep.summary,\n },\n deadlineReached: sweep.deadlineReached,\n remainingMayExist: sweep.remainingMayExist,\n ...(options.includeSweepResult ? { sweep } : undefined),\n }\n\n return Response.json(response)\n }\n}\n\nfunction resolveLeaseOwner(\n leaseOwner: NetlifyWorkflowSweepHandlerOptions['leaseOwner'],\n request: Request,\n now: number,\n) {\n if (typeof leaseOwner === 'string') return leaseOwner\n if (typeof leaseOwner === 'function') {\n return leaseOwner({ request, now }) ?? defaultLeaseOwner(request, now)\n }\n return defaultLeaseOwner(request, now)\n}\n\nfunction defaultLeaseOwner(request: Request, now: number) {\n return `netlify:${request.headers.get('x-nf-request-id') ?? now}`\n}\n"],"mappings":";;;AASA,MAAM,iCAAiC;AAyDvC,MAAa,6BAA6B,kCAAkC;AAE5E,SAAgB,iCACd,UAA6C,EAAE,EACnB;AAC5B,KAAI,QAAQ,SAAU,QAAO,EAAE,UAAU,QAAQ,UAAU;CAE3D,MAAM,eAAe,QAAQ,gBAAgB;AAC7C,KAAI,CAAC,OAAO,UAAU,aAAa,IAAI,gBAAgB,EACrD,OAAM,IAAI,MACR,8DACD;AAGH,QAAO,EACL,UAAU,iBAAiB,IAAI,cAAc,KAAK,aAAa,WAChE;;AAGH,SAAgB,kCAGd,SAC6B;AAC7B,QAAO,OAAO,YAAY;EACxB,MAAM,MAAM,QAAQ,OAAO,IAAI,KAAK,KAAK;EACzC,MAAM,eACJ,QAAQ,yBAAyB,QAC7B,EAAE,GACF,MAAM,6BAA6B,QAAQ,SAAS;GAClD;GACA,gBAAgB,QAAQ;GACzB,CAAC;EACR,MAAM,aAAa,kBAAkB,QAAQ,YAAY,SAAS,IAAI;EACtE,MAAM,YAAsC;GAC1C;GACA;GACA,OAAO,QAAQ;GACf,kBAAkB,QAAQ;GAC1B,WAAW,QAAQ;GACnB,eAAe,QAAQ;GACvB,SAAS,QAAQ;GACjB,eAAe,QAAQ,iBAAiB;GACxC,WAAW,QAAQ;GACpB;EACD,MAAM,QAAQ,MAAM,QAAQ,QAAQ,MAAM,UAAU;EACpD,MAAM,WAAyC;GAC7C,IAAI;GACJ;GACA;GACA;GACA,SAAS;IACP,cAAc,aAAa;IAC3B,GAAG,MAAM;IACV;GACD,iBAAiB,MAAM;GACvB,mBAAmB,MAAM;GACzB,GAAI,QAAQ,qBAAqB,EAAE,OAAO,GAAG;GAC9C;AAED,SAAO,SAAS,KAAK,SAAS;;;AAIlC,SAAS,kBACP,YACA,SACA,KACA;AACA,KAAI,OAAO,eAAe,SAAU,QAAO;AAC3C,KAAI,OAAO,eAAe,WACxB,QAAO,WAAW;EAAE;EAAS;EAAK,CAAC,IAAI,kBAAkB,SAAS,IAAI;AAExE,QAAO,kBAAkB,SAAS,IAAI;;AAGxC,SAAS,kBAAkB,SAAkB,KAAa;AACxD,QAAO,WAAW,QAAQ,QAAQ,IAAI,kBAAkB,IAAI"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@tanstack/workflow-netlify",
3
+ "version": "0.0.1",
4
+ "description": "Netlify host adapter for TanStack Workflow runtimes.",
5
+ "author": "Tanner Linsley",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/TanStack/workflow.git",
10
+ "directory": "packages/workflow-netlify"
11
+ },
12
+ "homepage": "https://tanstack.com/workflow",
13
+ "funding": {
14
+ "type": "github",
15
+ "url": "https://github.com/sponsors/tannerlinsley"
16
+ },
17
+ "keywords": [
18
+ "tanstack",
19
+ "workflow",
20
+ "durable-execution",
21
+ "netlify",
22
+ "serverless"
23
+ ],
24
+ "scripts": {
25
+ "clean": "premove ./build ./dist",
26
+ "lint": "eslint ./src",
27
+ "lint:fix": "eslint ./src --fix",
28
+ "test:eslint": "eslint ./src",
29
+ "test:lib": "vitest",
30
+ "test:lib:dev": "pnpm test:lib --watch",
31
+ "test:types": "tsc",
32
+ "build": "tsdown"
33
+ },
34
+ "type": "module",
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.cts",
38
+ "exports": {
39
+ ".": {
40
+ "import": "./dist/index.js",
41
+ "require": "./dist/index.cjs"
42
+ },
43
+ "./package.json": "./package.json"
44
+ },
45
+ "sideEffects": false,
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "files": [
50
+ "dist/",
51
+ "src"
52
+ ],
53
+ "dependencies": {
54
+ "@tanstack/workflow-runtime": "workspace:*"
55
+ }
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export {
2
+ createNetlifyWorkflowSweepConfig,
3
+ createNetlifyWorkflowSweepHandler,
4
+ materializeWorkflowSchedules,
5
+ netlifyWorkflowSweepConfig,
6
+ } from './sweep-handler'
7
+ export type {
8
+ MaterializedWorkflowSchedule,
9
+ MaterializeWorkflowSchedulesOptions,
10
+ NetlifyWorkflowSweepConfig,
11
+ NetlifyWorkflowSweepConfigOptions,
12
+ NetlifyWorkflowSweepHandler,
13
+ NetlifyWorkflowSweepHandlerOptions,
14
+ NetlifyWorkflowSweepResponse,
15
+ } from './sweep-handler'
@@ -0,0 +1,145 @@
1
+ import { materializeWorkflowSchedules } from '@tanstack/workflow-runtime'
2
+ import type {
3
+ MaterializedWorkflowSchedule,
4
+ WorkflowRegistrationMap,
5
+ WorkflowRuntimeDefinition,
6
+ WorkflowRuntimeSweepArgs,
7
+ WorkflowRuntimeSweepResult,
8
+ } from '@tanstack/workflow-runtime'
9
+
10
+ const DEFAULT_SWEEP_INTERVAL_MINUTES = 5
11
+
12
+ export { materializeWorkflowSchedules }
13
+ export type {
14
+ MaterializedWorkflowSchedule,
15
+ MaterializeWorkflowSchedulesOptions,
16
+ } from '@tanstack/workflow-runtime'
17
+
18
+ export interface NetlifyWorkflowSweepConfig {
19
+ schedule: string
20
+ }
21
+
22
+ export interface NetlifyWorkflowSweepConfigOptions {
23
+ schedule?: string
24
+ everyMinutes?: number
25
+ }
26
+
27
+ export interface NetlifyWorkflowSweepResponse {
28
+ ok: true
29
+ now: number
30
+ leaseOwner: string
31
+ materialized: ReadonlyArray<MaterializedWorkflowSchedule>
32
+ summary: NetlifyWorkflowSweepSummary
33
+ deadlineReached: boolean
34
+ remainingMayExist: boolean
35
+ sweep?: WorkflowRuntimeSweepResult
36
+ }
37
+
38
+ export type NetlifyWorkflowSweepSummary =
39
+ WorkflowRuntimeSweepResult['summary'] & {
40
+ materialized: number
41
+ }
42
+
43
+ export type NetlifyWorkflowSweepHandler = (
44
+ request: Request,
45
+ ) => Promise<Response>
46
+
47
+ export interface NetlifyWorkflowSweepHandlerOptions<
48
+ TWorkflows extends WorkflowRegistrationMap = WorkflowRegistrationMap,
49
+ > {
50
+ runtime: WorkflowRuntimeDefinition<TWorkflows>
51
+ now?: () => number
52
+ leaseOwner?:
53
+ | string
54
+ | ((args: { request: Request; now: number }) => string | undefined)
55
+ limit?: number
56
+ maxScheduledRuns?: number
57
+ maxTimers?: number
58
+ maxDurationMs?: number
59
+ leaseMs?: number
60
+ includeEvents?: boolean
61
+ maxEvents?: number
62
+ includeSweepResult?: boolean
63
+ materializeSchedules?: boolean
64
+ cronLookbackMs?: number
65
+ }
66
+
67
+ export const netlifyWorkflowSweepConfig = createNetlifyWorkflowSweepConfig()
68
+
69
+ export function createNetlifyWorkflowSweepConfig(
70
+ options: NetlifyWorkflowSweepConfigOptions = {},
71
+ ): NetlifyWorkflowSweepConfig {
72
+ if (options.schedule) return { schedule: options.schedule }
73
+
74
+ const everyMinutes = options.everyMinutes ?? DEFAULT_SWEEP_INTERVAL_MINUTES
75
+ if (!Number.isInteger(everyMinutes) || everyMinutes <= 0) {
76
+ throw new Error(
77
+ 'Netlify workflow sweep interval must be a positive integer.',
78
+ )
79
+ }
80
+
81
+ return {
82
+ schedule: everyMinutes === 1 ? '* * * * *' : `*/${everyMinutes} * * * *`,
83
+ }
84
+ }
85
+
86
+ export function createNetlifyWorkflowSweepHandler<
87
+ TWorkflows extends WorkflowRegistrationMap,
88
+ >(
89
+ options: NetlifyWorkflowSweepHandlerOptions<TWorkflows>,
90
+ ): NetlifyWorkflowSweepHandler {
91
+ return async (request) => {
92
+ const now = options.now?.() ?? Date.now()
93
+ const materialized =
94
+ options.materializeSchedules === false
95
+ ? []
96
+ : await materializeWorkflowSchedules(options.runtime, {
97
+ now,
98
+ cronLookbackMs: options.cronLookbackMs,
99
+ })
100
+ const leaseOwner = resolveLeaseOwner(options.leaseOwner, request, now)
101
+ const sweepArgs: WorkflowRuntimeSweepArgs = {
102
+ now,
103
+ leaseOwner,
104
+ limit: options.limit,
105
+ maxScheduledRuns: options.maxScheduledRuns,
106
+ maxTimers: options.maxTimers,
107
+ maxDurationMs: options.maxDurationMs,
108
+ leaseMs: options.leaseMs,
109
+ includeEvents: options.includeEvents ?? false,
110
+ maxEvents: options.maxEvents,
111
+ }
112
+ const sweep = await options.runtime.sweep(sweepArgs)
113
+ const response: NetlifyWorkflowSweepResponse = {
114
+ ok: true,
115
+ now,
116
+ leaseOwner,
117
+ materialized,
118
+ summary: {
119
+ materialized: materialized.length,
120
+ ...sweep.summary,
121
+ },
122
+ deadlineReached: sweep.deadlineReached,
123
+ remainingMayExist: sweep.remainingMayExist,
124
+ ...(options.includeSweepResult ? { sweep } : undefined),
125
+ }
126
+
127
+ return Response.json(response)
128
+ }
129
+ }
130
+
131
+ function resolveLeaseOwner(
132
+ leaseOwner: NetlifyWorkflowSweepHandlerOptions['leaseOwner'],
133
+ request: Request,
134
+ now: number,
135
+ ) {
136
+ if (typeof leaseOwner === 'string') return leaseOwner
137
+ if (typeof leaseOwner === 'function') {
138
+ return leaseOwner({ request, now }) ?? defaultLeaseOwner(request, now)
139
+ }
140
+ return defaultLeaseOwner(request, now)
141
+ }
142
+
143
+ function defaultLeaseOwner(request: Request, now: number) {
144
+ return `netlify:${request.headers.get('x-nf-request-id') ?? now}`
145
+ }