@btravstack/temporal-worker 0.9.0 → 0.10.0

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
@@ -13,7 +13,7 @@
13
13
  ```sh
14
14
  pnpm add @btravstack/temporal-worker @btravstack/core @btravstack/config @btravstack/di unthrown \
15
15
  @temporalio/worker @temporalio/activity @temporalio/common \
16
- @temporal-contract/worker @temporal-contract/contract
16
+ @temporal-contract/worker@^8.0.0-beta.7 @temporal-contract/contract@^8.0.0-beta.7
17
17
  ```
18
18
 
19
19
  All nine are peer dependencies — install them. Node `>=22`.
@@ -78,9 +78,9 @@ const orderActivities = TemporalActivities(contract)({
78
78
  inject: { place: PlaceOrder },
79
79
  sync: ({ place }) => ({
80
80
  placeOrder: {
81
- place: (args, { errors }) =>
81
+ place: ({ errors, input }) =>
82
82
  place
83
- .execute(TenantId(args.tenantId), args.orderId, args.quantity)
83
+ .execute(TenantId(input.tenantId), input.orderId, input.quantity)
84
84
  .mapErrCases((matcher) =>
85
85
  matcher
86
86
  .with(P.tag("DuplicateOrder"), (error) =>
@@ -132,6 +132,20 @@ the pieces' ports, not what they close over. Two slices claiming one key are
132
132
  di's duplicate-provider defect at build, which is the point: a workflow's
133
133
  activities belong to exactly one slice.
134
134
 
135
+ Its own test suite needs a **Docker daemon**: the specs run against the shared
136
+ `temporalio/auto-setup` container `internal/test-infra` starts once per machine and every
137
+ workspace reuses. Nothing else here does.
138
+
139
+ ## Schedules
140
+
141
+ `@btravstack/temporal-worker/schedule` exports `ensureSchedule` — the typed
142
+ schedule client's `create`, made idempotent, so a deploy that runs again
143
+ updates the schedule instead of failing on `ScheduleAlreadyExistsError`.
144
+ `@temporal-contract/client` is an optional peer reached only through that
145
+ subpath. Why scheduled work costs a cluster here, and when it should not be
146
+ this stack at all:
147
+ [Run something on a schedule](https://btravstack.github.io/btravstack/how-to/run-something-on-a-schedule).
148
+
135
149
  ## Options
136
150
 
137
151
  `TemporalModule(name)({...})` takes `temporal()`'s options plus `activities`
package/dist/index.cjs CHANGED
@@ -20,7 +20,17 @@ let _temporalio_activity = require("@temporalio/activity");
20
20
  * No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
21
21
  * the mapping from a settled `Result` to an activity failure.
22
22
  */
23
- const activityUnits = (host) => (_invocation, next) => host.run(metaFor(), () => next());
23
+ const activityUnits = (host, observers) => (_invocation, next) => {
24
+ const settle = (0, _btravstack_core.observe)(observers, {
25
+ component: "temporal",
26
+ name: "attempt",
27
+ attributes: { activity: (0, _temporalio_activity.activityInfo)().activityType }
28
+ });
29
+ return host.run(metaFor(), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
30
+ outcome: "error",
31
+ cause: failure.tag === "Err" ? failure.error : failure.cause
32
+ }));
33
+ };
24
34
  /**
25
35
  * `UnitMeta.id` must be unique per unit, and a workflow id is **not** one: an
26
36
  * activity is retried under the same execution. A task token identifies one
@@ -119,16 +129,21 @@ const temporal = (options) => {
119
129
  })),
120
130
  release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
121
131
  }),
132
+ _btravstack_di.Provider.member(_btravstack_core.Observers)({
133
+ inject: {},
134
+ value: _btravstack_core.noObserver
135
+ }),
122
136
  (0, _btravstack_di.Provider)(TemporalRuntime)({
123
137
  inject: {
124
138
  connection: TemporalConnection,
125
139
  config: TemporalConfig,
126
- activities
140
+ activities,
141
+ observers: _btravstack_core.Observers
127
142
  },
128
- sync: ({ connection, config: bound, activities: impls }) => ({
143
+ sync: ({ connection, config: bound, activities: impls, observers }) => ({
129
144
  name: "temporal",
130
145
  resolves: [],
131
- start: (host) => createWorker(host, connection, bound, impls, options)
146
+ start: (host) => createWorker(host, connection, bound, impls, options, observers)
132
147
  })
133
148
  })
134
149
  ],
@@ -144,12 +159,12 @@ const startFailed = (cause) => new _btravstack_core.RuntimeStartFailed({
144
159
  cause
145
160
  });
146
161
  const heldByWorker = (cause) => cause instanceof Error && cause.name === "IllegalStateError";
147
- const createWorker = (host, connection, config, activities, options) => {
162
+ const createWorker = (host, connection, config, activities, options, observers) => {
148
163
  const { taskQueue } = options.contract;
149
164
  const { namespace } = config;
150
165
  return (0, unthrown.fromThrowable)(() => (0, _temporal_contract_worker_activity.declareActivitiesHandler)({
151
166
  contract: options.contract,
152
- middleware: activityUnits(host),
167
+ middleware: activityUnits(host, observers),
153
168
  activities
154
169
  }), startFailed)().toAsync().flatMap((handler) => (0, unthrown.fromPromise)(_temporalio_worker.Worker.create({
155
170
  connection,
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Module, Port, Provider } from "@btravstack/di";
2
2
  import { Config, Env } from "@btravstack/config";
3
- import { RuntimePort, RuntimeStartFailed, releasedBy } from "@btravstack/core";
3
+ import { Observers, RuntimePort, RuntimeStartFailed, noObserver, observe, releasedBy } from "@btravstack/core";
4
4
  import { declareActivitiesHandler } from "@temporal-contract/worker/activity";
5
5
  import { msToNumber } from "@temporalio/common";
6
6
  import { NativeConnection, Worker } from "@temporalio/worker";
@@ -19,7 +19,17 @@ import { activityInfo } from "@temporalio/activity";
19
19
  * No `Result`-unwrapping boundary, deliberately: `declareActivitiesHandler` owns
20
20
  * the mapping from a settled `Result` to an activity failure.
21
21
  */
22
- const activityUnits = (host) => (_invocation, next) => host.run(metaFor(), () => next());
22
+ const activityUnits = (host, observers) => (_invocation, next) => {
23
+ const settle = observe(observers, {
24
+ component: "temporal",
25
+ name: "attempt",
26
+ attributes: { activity: activityInfo().activityType }
27
+ });
28
+ return host.run(metaFor(), () => next()).tap(() => settle({ outcome: "ok" })).tapFailure((failure) => settle({
29
+ outcome: "error",
30
+ cause: failure.tag === "Err" ? failure.error : failure.cause
31
+ }));
32
+ };
23
33
  /**
24
34
  * `UnitMeta.id` must be unique per unit, and a workflow id is **not** one: an
25
35
  * activity is retried under the same execution. A task token identifies one
@@ -118,16 +128,21 @@ const temporal = (options) => {
118
128
  })),
119
129
  release: (connection) => connection.close().catch((cause) => heldByWorker(cause) ? void 0 : Promise.reject(cause))
120
130
  }),
131
+ Provider.member(Observers)({
132
+ inject: {},
133
+ value: noObserver
134
+ }),
121
135
  Provider(TemporalRuntime)({
122
136
  inject: {
123
137
  connection: TemporalConnection,
124
138
  config: TemporalConfig,
125
- activities
139
+ activities,
140
+ observers: Observers
126
141
  },
127
- sync: ({ connection, config: bound, activities: impls }) => ({
142
+ sync: ({ connection, config: bound, activities: impls, observers }) => ({
128
143
  name: "temporal",
129
144
  resolves: [],
130
- start: (host) => createWorker(host, connection, bound, impls, options)
145
+ start: (host) => createWorker(host, connection, bound, impls, options, observers)
131
146
  })
132
147
  })
133
148
  ],
@@ -143,12 +158,12 @@ const startFailed = (cause) => new RuntimeStartFailed({
143
158
  cause
144
159
  });
145
160
  const heldByWorker = (cause) => cause instanceof Error && cause.name === "IllegalStateError";
146
- const createWorker = (host, connection, config, activities, options) => {
161
+ const createWorker = (host, connection, config, activities, options, observers) => {
147
162
  const { taskQueue } = options.contract;
148
163
  const { namespace } = config;
149
164
  return fromThrowable(() => declareActivitiesHandler({
150
165
  contract: options.contract,
151
- middleware: activityUnits(host),
166
+ middleware: activityUnits(host, observers),
152
167
  activities
153
168
  }), startFailed)().toAsync().flatMap((handler) => fromPromise(Worker.create({
154
169
  connection,
@@ -0,0 +1,51 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let unthrown = require("unthrown");
3
+ let _temporal_contract_client = require("@temporal-contract/client");
4
+ //#region src/schedule.ts
5
+ /**
6
+ * Register a schedule, idempotently.
7
+ *
8
+ * `TypedScheduleClient.create` answers `ScheduleAlreadyExistsError` for a
9
+ * schedule id already in use, which is correct and is also the wrong shape for
10
+ * the one place schedules are actually registered: a **deploy**, which runs
11
+ * again on every release. So the second run of a correct deploy script fails,
12
+ * and the usual repair is a `try`/ignore that also swallows the case where the
13
+ * schedule exists with the WRONG spec — a cron nobody notices has stopped
14
+ * matching what the code says.
15
+ *
16
+ * This recovers exactly that one error into an `update`. Every other error
17
+ * stays on the channel, still typed.
18
+ *
19
+ * **`spec` is the only field reconciled**, and the rest of an existing
20
+ * schedule is left as it stands. Two different reasons, and they are worth
21
+ * telling apart:
22
+ *
23
+ * - **`state` is deliberately preserved.** A schedule an operator paused stays
24
+ * paused across a deploy, because unpausing it is a decision a person made
25
+ * and a deploy is not the place to reverse it.
26
+ * - **`args` and the rest of the action are preserved because this cannot
27
+ * reconcile them SAFELY.** `create` validates `args` against the workflow's
28
+ * input schema; the handle's `update` takes Temporal's own
29
+ * `ScheduleUpdateOptions` and validates nothing, so writing them here would
30
+ * push unvalidated input at the server through a door the typed client keeps
31
+ * shut. A deploy that changes what a schedule RUNS — its args, its workflow,
32
+ * its policies — should say so explicitly: delete the schedule and create it,
33
+ * or reach `getHandle(id).update(...)` directly and own the shape.
34
+ *
35
+ * So: after this call the schedule FIRES when the arguments say. What it fires
36
+ * with is whatever it already fired with.
37
+ *
38
+ * ```ts
39
+ * await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
40
+ * scheduleId: "sweep-stale-orders",
41
+ * spec: { cronExpressions: ["0 3 * * *"] },
42
+ * args: [{ olderThanDays: 30 }],
43
+ * });
44
+ * ```
45
+ */
46
+ const ensureSchedule = (schedules, workflow, options) => schedules.create(workflow, options).map(() => "created").flatMapErrCases((matcher) => matcher.with(unthrown.P.tag(_temporal_contract_client.SCHEDULE_ALREADY_EXISTS_ERROR_TAG), () => schedules.getHandle(options.scheduleId).update((previous) => ({
47
+ ...previous,
48
+ spec: options.spec
49
+ })).map(() => "updated")).with(unthrown.P.tag("@temporal-contract/WorkflowNotInContractError"), (error) => (0, unthrown.ErrAsync)(error)).with(unthrown.P.tag("@temporal-contract/WorkflowValidationError"), (error) => (0, unthrown.ErrAsync)(error)));
50
+ //#endregion
51
+ exports.ensureSchedule = ensureSchedule;
@@ -0,0 +1,50 @@
1
+ import { ContractDefinition } from "@temporal-contract/contract";
2
+ import { ScheduleNotFoundError, TypedScheduleClient, TypedScheduleCreateOptions, WorkflowNotInContractError, WorkflowValidationError } from "@temporal-contract/client";
3
+ import { AsyncResult } from "unthrown";
4
+ //#region src/schedule.d.ts
5
+ /** Whether the schedule had to be created, or was already there and had its spec reconciled. */
6
+ type ScheduleOutcome = "created" | "updated";
7
+ /**
8
+ * Register a schedule, idempotently.
9
+ *
10
+ * `TypedScheduleClient.create` answers `ScheduleAlreadyExistsError` for a
11
+ * schedule id already in use, which is correct and is also the wrong shape for
12
+ * the one place schedules are actually registered: a **deploy**, which runs
13
+ * again on every release. So the second run of a correct deploy script fails,
14
+ * and the usual repair is a `try`/ignore that also swallows the case where the
15
+ * schedule exists with the WRONG spec — a cron nobody notices has stopped
16
+ * matching what the code says.
17
+ *
18
+ * This recovers exactly that one error into an `update`. Every other error
19
+ * stays on the channel, still typed.
20
+ *
21
+ * **`spec` is the only field reconciled**, and the rest of an existing
22
+ * schedule is left as it stands. Two different reasons, and they are worth
23
+ * telling apart:
24
+ *
25
+ * - **`state` is deliberately preserved.** A schedule an operator paused stays
26
+ * paused across a deploy, because unpausing it is a decision a person made
27
+ * and a deploy is not the place to reverse it.
28
+ * - **`args` and the rest of the action are preserved because this cannot
29
+ * reconcile them SAFELY.** `create` validates `args` against the workflow's
30
+ * input schema; the handle's `update` takes Temporal's own
31
+ * `ScheduleUpdateOptions` and validates nothing, so writing them here would
32
+ * push unvalidated input at the server through a door the typed client keeps
33
+ * shut. A deploy that changes what a schedule RUNS — its args, its workflow,
34
+ * its policies — should say so explicitly: delete the schedule and create it,
35
+ * or reach `getHandle(id).update(...)` directly and own the shape.
36
+ *
37
+ * So: after this call the schedule FIRES when the arguments say. What it fires
38
+ * with is whatever it already fired with.
39
+ *
40
+ * ```ts
41
+ * await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
42
+ * scheduleId: "sweep-stale-orders",
43
+ * spec: { cronExpressions: ["0 3 * * *"] },
44
+ * args: [{ olderThanDays: 30 }],
45
+ * });
46
+ * ```
47
+ */
48
+ declare const ensureSchedule: <C extends ContractDefinition, W extends keyof C["workflows"] & string>(schedules: TypedScheduleClient<C>, workflow: W, options: TypedScheduleCreateOptions<C, W>) => AsyncResult<ScheduleOutcome, WorkflowNotInContractError | WorkflowValidationError | ScheduleNotFoundError>;
49
+ //#endregion
50
+ export { ScheduleOutcome, ensureSchedule };
@@ -0,0 +1,50 @@
1
+ import { AsyncResult } from "unthrown";
2
+ import { ScheduleNotFoundError, TypedScheduleClient, TypedScheduleCreateOptions, WorkflowNotInContractError, WorkflowValidationError } from "@temporal-contract/client";
3
+ import { ContractDefinition } from "@temporal-contract/contract";
4
+ //#region src/schedule.d.ts
5
+ /** Whether the schedule had to be created, or was already there and had its spec reconciled. */
6
+ type ScheduleOutcome = "created" | "updated";
7
+ /**
8
+ * Register a schedule, idempotently.
9
+ *
10
+ * `TypedScheduleClient.create` answers `ScheduleAlreadyExistsError` for a
11
+ * schedule id already in use, which is correct and is also the wrong shape for
12
+ * the one place schedules are actually registered: a **deploy**, which runs
13
+ * again on every release. So the second run of a correct deploy script fails,
14
+ * and the usual repair is a `try`/ignore that also swallows the case where the
15
+ * schedule exists with the WRONG spec — a cron nobody notices has stopped
16
+ * matching what the code says.
17
+ *
18
+ * This recovers exactly that one error into an `update`. Every other error
19
+ * stays on the channel, still typed.
20
+ *
21
+ * **`spec` is the only field reconciled**, and the rest of an existing
22
+ * schedule is left as it stands. Two different reasons, and they are worth
23
+ * telling apart:
24
+ *
25
+ * - **`state` is deliberately preserved.** A schedule an operator paused stays
26
+ * paused across a deploy, because unpausing it is a decision a person made
27
+ * and a deploy is not the place to reverse it.
28
+ * - **`args` and the rest of the action are preserved because this cannot
29
+ * reconcile them SAFELY.** `create` validates `args` against the workflow's
30
+ * input schema; the handle's `update` takes Temporal's own
31
+ * `ScheduleUpdateOptions` and validates nothing, so writing them here would
32
+ * push unvalidated input at the server through a door the typed client keeps
33
+ * shut. A deploy that changes what a schedule RUNS — its args, its workflow,
34
+ * its policies — should say so explicitly: delete the schedule and create it,
35
+ * or reach `getHandle(id).update(...)` directly and own the shape.
36
+ *
37
+ * So: after this call the schedule FIRES when the arguments say. What it fires
38
+ * with is whatever it already fired with.
39
+ *
40
+ * ```ts
41
+ * await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
42
+ * scheduleId: "sweep-stale-orders",
43
+ * spec: { cronExpressions: ["0 3 * * *"] },
44
+ * args: [{ olderThanDays: 30 }],
45
+ * });
46
+ * ```
47
+ */
48
+ declare const ensureSchedule: <C extends ContractDefinition, W extends keyof C["workflows"] & string>(schedules: TypedScheduleClient<C>, workflow: W, options: TypedScheduleCreateOptions<C, W>) => AsyncResult<ScheduleOutcome, WorkflowNotInContractError | WorkflowValidationError | ScheduleNotFoundError>;
49
+ //#endregion
50
+ export { ScheduleOutcome, ensureSchedule };
@@ -0,0 +1,50 @@
1
+ import { ErrAsync, P } from "unthrown";
2
+ import { SCHEDULE_ALREADY_EXISTS_ERROR_TAG } from "@temporal-contract/client";
3
+ //#region src/schedule.ts
4
+ /**
5
+ * Register a schedule, idempotently.
6
+ *
7
+ * `TypedScheduleClient.create` answers `ScheduleAlreadyExistsError` for a
8
+ * schedule id already in use, which is correct and is also the wrong shape for
9
+ * the one place schedules are actually registered: a **deploy**, which runs
10
+ * again on every release. So the second run of a correct deploy script fails,
11
+ * and the usual repair is a `try`/ignore that also swallows the case where the
12
+ * schedule exists with the WRONG spec — a cron nobody notices has stopped
13
+ * matching what the code says.
14
+ *
15
+ * This recovers exactly that one error into an `update`. Every other error
16
+ * stays on the channel, still typed.
17
+ *
18
+ * **`spec` is the only field reconciled**, and the rest of an existing
19
+ * schedule is left as it stands. Two different reasons, and they are worth
20
+ * telling apart:
21
+ *
22
+ * - **`state` is deliberately preserved.** A schedule an operator paused stays
23
+ * paused across a deploy, because unpausing it is a decision a person made
24
+ * and a deploy is not the place to reverse it.
25
+ * - **`args` and the rest of the action are preserved because this cannot
26
+ * reconcile them SAFELY.** `create` validates `args` against the workflow's
27
+ * input schema; the handle's `update` takes Temporal's own
28
+ * `ScheduleUpdateOptions` and validates nothing, so writing them here would
29
+ * push unvalidated input at the server through a door the typed client keeps
30
+ * shut. A deploy that changes what a schedule RUNS — its args, its workflow,
31
+ * its policies — should say so explicitly: delete the schedule and create it,
32
+ * or reach `getHandle(id).update(...)` directly and own the shape.
33
+ *
34
+ * So: after this call the schedule FIRES when the arguments say. What it fires
35
+ * with is whatever it already fired with.
36
+ *
37
+ * ```ts
38
+ * await ensureSchedule(client.for(orderContract).schedule, "sweepStaleOrders", {
39
+ * scheduleId: "sweep-stale-orders",
40
+ * spec: { cronExpressions: ["0 3 * * *"] },
41
+ * args: [{ olderThanDays: 30 }],
42
+ * });
43
+ * ```
44
+ */
45
+ const ensureSchedule = (schedules, workflow, options) => schedules.create(workflow, options).map(() => "created").flatMapErrCases((matcher) => matcher.with(P.tag(SCHEDULE_ALREADY_EXISTS_ERROR_TAG), () => schedules.getHandle(options.scheduleId).update((previous) => ({
46
+ ...previous,
47
+ spec: options.spec
48
+ })).map(() => "updated")).with(P.tag("@temporal-contract/WorkflowNotInContractError"), (error) => ErrAsync(error)).with(P.tag("@temporal-contract/WorkflowValidationError"), (error) => ErrAsync(error)));
49
+ //#endregion
50
+ export { ensureSchedule };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@btravstack/temporal-worker",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "The Temporal worker runtime for @btravstack/core: one unit per activity attempt, and a drain that honours the kernel's deadline",
5
5
  "keywords": [
6
6
  "graceful-shutdown",
@@ -41,12 +41,23 @@
41
41
  "default": "./dist/index.cjs"
42
42
  }
43
43
  },
44
- "./package.json": "./package.json"
44
+ "./package.json": "./package.json",
45
+ "./schedule": {
46
+ "import": {
47
+ "types": "./dist/schedule.d.mts",
48
+ "default": "./dist/schedule.mjs"
49
+ },
50
+ "require": {
51
+ "types": "./dist/schedule.d.cts",
52
+ "default": "./dist/schedule.cjs"
53
+ }
54
+ }
45
55
  },
46
56
  "devDependencies": {
47
57
  "@btravstack/tsconfig": "0.2.0",
48
- "@temporal-contract/contract": "8.0.0-beta.5",
49
- "@temporal-contract/worker": "8.0.0-beta.5",
58
+ "@temporal-contract/client": "8.0.0-beta.7",
59
+ "@temporal-contract/contract": "8.0.0-beta.7",
60
+ "@temporal-contract/worker": "8.0.0-beta.7",
50
61
  "@temporalio/activity": "1.23.0",
51
62
  "@temporalio/client": "1.23.0",
52
63
  "@temporalio/common": "1.23.0",
@@ -59,30 +70,36 @@
59
70
  "typescript": "7.0.2",
60
71
  "unthrown": "5.7.0",
61
72
  "vitest": "4.1.11",
62
- "zod": "4.5.1",
63
- "@btravstack/config": "0.9.0",
64
- "@btravstack/core": "0.9.0",
65
- "@btravstack/di": "0.9.0",
73
+ "zod": "4.5.3",
74
+ "@btravstack/config": "0.10.0",
75
+ "@btravstack/core": "0.10.0",
76
+ "@btravstack/di": "0.10.0",
66
77
  "@btravstack/internal-test-infra": "0.0.0",
67
- "@btravstack/testing": "0.9.0"
78
+ "@btravstack/testing": "0.10.0"
68
79
  },
69
80
  "peerDependencies": {
70
- "@temporal-contract/contract": "^8.0.0-beta",
71
- "@temporal-contract/worker": "^8.0.0-beta",
81
+ "@temporal-contract/client": "^8.0.0-beta.7",
82
+ "@temporal-contract/contract": "^8.0.0-beta.7",
83
+ "@temporal-contract/worker": "^8.0.0-beta.7",
72
84
  "@temporalio/activity": "^1.22.0",
73
85
  "@temporalio/common": "^1.22.0",
74
86
  "@temporalio/worker": "^1.22.0",
75
87
  "unthrown": "^5.0.0",
76
- "@btravstack/config": "^0.9.0",
77
- "@btravstack/core": "^0.9.0",
78
- "@btravstack/di": "^0.9.0"
88
+ "@btravstack/config": "^0.10.0",
89
+ "@btravstack/core": "^0.10.0",
90
+ "@btravstack/di": "^0.10.0"
91
+ },
92
+ "peerDependenciesMeta": {
93
+ "@temporal-contract/client": {
94
+ "optional": true
95
+ }
79
96
  },
80
97
  "engines": {
81
98
  "node": ">=22"
82
99
  },
83
100
  "scripts": {
84
- "build": "tsdown src/index.ts --format cjs,esm --dts --clean",
85
- "dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
101
+ "build": "tsdown src/index.ts src/schedule.ts --format cjs,esm --dts --clean",
102
+ "dev": "tsdown src/index.ts src/schedule.ts --format cjs,esm --dts --watch",
86
103
  "test": "vitest run --coverage",
87
104
  "test:types": "tsc --noEmit -p tsconfig.test-d.json",
88
105
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test-d.json"