@nuetzliches/croniq-runner 0.1.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/CHANGELOG.md +14 -0
- package/README.md +172 -0
- package/dist/abort.d.ts +7 -0
- package/dist/abort.d.ts.map +1 -0
- package/dist/abort.js +31 -0
- package/dist/abort.js.map +1 -0
- package/dist/client.d.ts +32 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +147 -0
- package/dist/client.js.map +1 -0
- package/dist/context.d.ts +82 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +52 -0
- package/dist/context.js.map +1 -0
- package/dist/deferred.d.ts +18 -0
- package/dist/deferred.d.ts.map +1 -0
- package/dist/deferred.js +40 -0
- package/dist/deferred.js.map +1 -0
- package/dist/dispatcher.d.ts +33 -0
- package/dist/dispatcher.d.ts.map +1 -0
- package/dist/dispatcher.js +159 -0
- package/dist/dispatcher.js.map +1 -0
- package/dist/duration.d.ts +2 -0
- package/dist/duration.d.ts.map +1 -0
- package/dist/duration.js +23 -0
- package/dist/duration.js.map +1 -0
- package/dist/enrichment.d.ts +15 -0
- package/dist/enrichment.d.ts.map +1 -0
- package/dist/enrichment.js +30 -0
- package/dist/enrichment.js.map +1 -0
- package/dist/handler.d.ts +35 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +47 -0
- package/dist/handler.js.map +1 -0
- package/dist/identity.d.ts +23 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +64 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/log-writer.d.ts +38 -0
- package/dist/log-writer.d.ts.map +1 -0
- package/dist/log-writer.js +153 -0
- package/dist/log-writer.js.map +1 -0
- package/dist/logger.d.ts +27 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +65 -0
- package/dist/logger.js.map +1 -0
- package/dist/options.d.ts +89 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +50 -0
- package/dist/options.js.map +1 -0
- package/dist/protocol.d.ts +54 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +8 -0
- package/dist/protocol.js.map +1 -0
- package/dist/runner.d.ts +40 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +226 -0
- package/dist/runner.js.map +1 -0
- package/dist/url.d.ts +10 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +20 -0
- package/dist/url.js.map +1 -0
- package/package.json +63 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - Unreleased
|
|
4
|
+
|
|
5
|
+
Initial release. Implements the Croniq runner protocol:
|
|
6
|
+
|
|
7
|
+
- Poll / ack / renew / events / register endpoints (`/v1/work/*`, `/v1/jobs/register`).
|
|
8
|
+
- ESM-first, Node 18+, native `fetch` and `AbortController`.
|
|
9
|
+
- Streaming `LogWriter` with batch-by-count, batch-by-time, drain-before-ack semantics.
|
|
10
|
+
- Per-execution `AbortSignal` honouring `PollResponse.cancel`.
|
|
11
|
+
- Self-registration of schedule-bearing handlers at startup.
|
|
12
|
+
- `Authorization: ApiKey {key}` and `Bearer {token}` precedence.
|
|
13
|
+
- Persistent runner-id resolution (env → state file → generated).
|
|
14
|
+
- Conformance against all 12 cases in [`sdks/conformance/cases/`](../conformance/cases).
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Croniq Runner SDK for Node.js
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@nuetzliches/croniq-runner)
|
|
4
|
+
[](#license)
|
|
5
|
+
|
|
6
|
+
Build job execution runners for [Croniq](https://github.com/nuetzliches/croniq) in Node.js / TypeScript. The SDK polls a Croniq server for work, dispatches handlers, streams structured logs back, and reports completion — using native `fetch` and `AbortController`, no extra HTTP dependency.
|
|
7
|
+
|
|
8
|
+
Aimed at workloads that are Node-native: headless-browser automation (Playwright / Puppeteer), web scraping, npm-ecosystem builds, anything that lives most naturally in a JS toolchain.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @nuetzliches/croniq-runner
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
(The package name is org-scoped under `@nuetzliches` to align with the project's other npm artefacts. Import path: `import { createRunner } from '@nuetzliches/croniq-runner';`.)
|
|
17
|
+
|
|
18
|
+
Requires Node.js ≥ 18 (ESM, native `fetch`, `AbortController`).
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { createRunner } from '@nuetzliches/croniq-runner';
|
|
24
|
+
|
|
25
|
+
const runner = createRunner({
|
|
26
|
+
serverUrl: 'http://localhost:4000',
|
|
27
|
+
apiKey: process.env.CRONIQ_API_KEY,
|
|
28
|
+
capabilities: ['demo'],
|
|
29
|
+
tags: ['env=dev', 'lang=typescript'],
|
|
30
|
+
maxInflight: 5,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
runner.handle('hello:world', async (ctx) => {
|
|
34
|
+
ctx.logger.info(`Hello from ${ctx.jobKey} (attempt ${ctx.attempt})`);
|
|
35
|
+
await new Promise((r) => setTimeout(r, 1_000));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Wire SIGTERM/SIGINT to graceful shutdown.
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
for (const sig of ['SIGTERM', 'SIGINT'] as const) {
|
|
41
|
+
process.on(sig, () => controller.abort());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await runner.run(controller.signal);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
- **ESM-first**, native `fetch` + `AbortController` — no `axios`, no `node-fetch` shim.
|
|
50
|
+
- **Two handler styles**: delegate (`runner.handle('key', fn)`) and a default catch-all (`runner.setDefaultHandler(fn)`).
|
|
51
|
+
- **Server-side cancellation** — `PollResponse.cancel` aborts the handler's `ctx.signal`.
|
|
52
|
+
- **Streaming `LogWriter`** — bounded queue with backpressure, batch by count (32) / by time (200 ms) / max 100 events per POST, **drain-before-ack**.
|
|
53
|
+
- **Self-registration** — `runner.handle('key', fn, { schedule: '5m' })` calls `POST /v1/jobs/register` once at startup (DSL precedence applies server-side).
|
|
54
|
+
- **Auth precedence** — `Authorization: ApiKey {key}` if set, else `Bearer {token}`.
|
|
55
|
+
- **Persistent runner-id** — env var → state file → generated `{prefix}-{hex8}`, persisted under `$XDG_STATE_HOME/croniq-runner` (Linux/macOS) or `%LOCALAPPDATA%\croniq-runner` (Windows).
|
|
56
|
+
- **OpenTelemetry-ready** — bring your own `@opentelemetry/sdk-node` setup; the SDK plays nicely with the standard `@opentelemetry/api` no-op fallback.
|
|
57
|
+
|
|
58
|
+
## Capabilities vs Tags
|
|
59
|
+
|
|
60
|
+
Same rule as the .NET / Rust SDKs — **don't put implementation details into capabilities**. Capabilities drive routing (`require` / `prefer` in the Croniqfile); tags are filter-only.
|
|
61
|
+
|
|
62
|
+
| Good capability | Bad capability |
|
|
63
|
+
| -------------------------------------------- | ----------------------------- |
|
|
64
|
+
| `billing`, `reporting`, `gpu`, `sandboxed` | `nodejs`, `typescript`, `linux-x64` |
|
|
65
|
+
|
|
66
|
+
If your runner is Node-based, put that into **tags** (`lang=typescript`, `platform=linux-x64`) so a future Go or Python runner with the same business capabilities can take over without rewriting Croniqfile entries.
|
|
67
|
+
|
|
68
|
+
## Handler API
|
|
69
|
+
|
|
70
|
+
The handler receives an `ExecutionContext`:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import type { ExecutionContext } from '@nuetzliches/croniq-runner';
|
|
74
|
+
|
|
75
|
+
runner.handle('billing:invoice', async (ctx: ExecutionContext) => {
|
|
76
|
+
const meta = ctx.metadata as { customer_id: string };
|
|
77
|
+
|
|
78
|
+
// Structured logger pre-scoped with execution_id, job_key, runner_id, attempt.
|
|
79
|
+
ctx.logger.info('generating invoice', { customer_id: meta.customer_id });
|
|
80
|
+
|
|
81
|
+
// Streaming events visible in the Croniq UI execution log:
|
|
82
|
+
for await (const line of generateInvoice(meta.customer_id, ctx.signal)) {
|
|
83
|
+
await ctx.logWriter.write('info', line);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ctx.signal aborts when the server cancels OR the runner drain-timeout fires.
|
|
87
|
+
if (ctx.signal.aborted) return;
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`ctx.metadata` is the raw server-provided JSON — cast to your expected shape. Field names are the original snake_case sent by the server.
|
|
92
|
+
|
|
93
|
+
## Streaming logs
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
runner.handle('export:report', async (ctx) => {
|
|
97
|
+
const writer = ctx.logWriter;
|
|
98
|
+
for (let i = 0; i < 100; i++) {
|
|
99
|
+
await writer.write('info', `processing row ${i}`, { row: String(i) });
|
|
100
|
+
}
|
|
101
|
+
// No explicit flush() needed — the runner drains the writer before ack.
|
|
102
|
+
// Call writer.flush() if you need a synchronization barrier mid-handler.
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The writer batches by count and time and survives transient POST failures (drops the batch, logs a warning, keeps going). On per-execution shutdown the runner drains queued events with a configurable timeout (`logWriter.shutdownTimeoutMs`, default 5 s) — late events never arrive after the ack.
|
|
107
|
+
|
|
108
|
+
## Self-register a schedule
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
runner.handle(
|
|
112
|
+
'reports:daily',
|
|
113
|
+
async (ctx) => { /* … */ },
|
|
114
|
+
{ schedule: '1h', timeout: '10m', description: 'rolling 24-hour digest' },
|
|
115
|
+
);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
On startup the SDK calls `POST /v1/jobs/register`. If a Croniqfile (DSL) entry with the same `job_key` exists, the server returns `status=skipped_dsl_precedence` and the SDK logs an info message — your runner still polls and executes the job normally, just driven by the DSL schedule rather than the runner-registered one.
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
| Option | Default | Description |
|
|
123
|
+
| --------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
124
|
+
| `serverUrl` | _(required)_ | Croniq server base URL. |
|
|
125
|
+
| `runnerId` | resolved | Stable id. Falls back to `RUNNER_ID` env var → state file → generated. |
|
|
126
|
+
| `runnerIdPrefix` | `"runner"` | Used when generating an id. |
|
|
127
|
+
| `runnerDataDir` | platform default | Where the persistent `runner-id` file lives. |
|
|
128
|
+
| `apiKey` | — | `Authorization: ApiKey {…}`. Takes precedence over `bearerToken`. |
|
|
129
|
+
| `bearerToken` | — | `Authorization: Bearer {…}`. |
|
|
130
|
+
| `capabilities` | `[]` | Routing capabilities. |
|
|
131
|
+
| `tags` | `[]` | Free-form `key=value` tags. Filter-only. |
|
|
132
|
+
| `maxInflight` | `5` | Max concurrent in-flight executions. |
|
|
133
|
+
| `pollTimeoutMs` | `35_000` | Per-request timeout on the long-poll. |
|
|
134
|
+
| `renewIntervalMs` | `15_000` | Heartbeat cadence for in-flight executions. |
|
|
135
|
+
| `drainTimeoutMs` | `30_000` | Graceful shutdown budget before hard-cancel. |
|
|
136
|
+
| `pollRetryDelayMs` | `5_000` | Back-off after a failed poll. |
|
|
137
|
+
| `capacityBackoffMs` | `500` | Idle wait when at `maxInflight`. |
|
|
138
|
+
| `logWriter` | see below | Streaming log-writer tunables. |
|
|
139
|
+
|
|
140
|
+
LogWriter sub-options: `channelCapacity` (256), `batchSizeThreshold` (32), `batchTimeThresholdMs` (200), `maxBatchPerPost` (100), `shutdownTimeoutMs` (5_000).
|
|
141
|
+
|
|
142
|
+
## Wire-protocol conformance
|
|
143
|
+
|
|
144
|
+
Validated against the shared, language-neutral suite at [`sdks/conformance/`](../conformance/) — the same 12 YAML cases the .NET SDK passes. The TypeScript binding lives at [`sdks/conformance/bindings/typescript/`](../conformance/bindings/typescript).
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
cd sdks/conformance/bindings/typescript
|
|
148
|
+
npm install
|
|
149
|
+
npm test
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Releasing
|
|
153
|
+
|
|
154
|
+
Publishing is fully automated by [`typescript-sdk-release.yml`](../../.github/workflows/typescript-sdk-release.yml). To ship a new version:
|
|
155
|
+
|
|
156
|
+
1. Bump `version` in [`package.json`](package.json) and update the [CHANGELOG](CHANGELOG.md).
|
|
157
|
+
2. Merge to `main`.
|
|
158
|
+
3. Tag the commit: `git tag ts-sdk-v0.2.0 && git push --tags`.
|
|
159
|
+
|
|
160
|
+
The workflow re-runs typecheck, lint, unit tests, build, and the full conformance suite against the freshly built artefact, then `npm publish --provenance --access public`. Pre-release tags (anything with a `-`, e.g. `ts-sdk-v0.2.0-rc.1`) publish under the `next` npm dist-tag so `npm install @nuetzliches/croniq-runner` keeps pointing at the latest stable.
|
|
161
|
+
|
|
162
|
+
Prerequisite: a repo admin must set the `NPM_TOKEN` secret (an npm automation or granular token with publish rights to the `@croniq` scope) once.
|
|
163
|
+
|
|
164
|
+
## Compatibility matrix
|
|
165
|
+
|
|
166
|
+
| SDK Version | Node.js | Croniq Server (min) | Croniq Server (max tested) |
|
|
167
|
+
| ----------- | ----------- | ------------------- | -------------------------- |
|
|
168
|
+
| 0.1.x | ≥ 18.0.0 | 0.14.0 | 0.14.0 |
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
Dual-licensed under MIT OR Apache-2.0. See [LICENSE-MIT](../../LICENSE-MIT) and [LICENSE-APACHE](../../LICENSE-APACHE).
|
package/dist/abort.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose multiple AbortSignals into one that aborts as soon as any of the
|
|
3
|
+
* inputs aborts. A polyfill for `AbortSignal.any()` (Node 20.3+) so this
|
|
4
|
+
* SDK works on Node 18.
|
|
5
|
+
*/
|
|
6
|
+
export declare function anySignal(...signals: AbortSignal[]): AbortSignal;
|
|
7
|
+
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,CAuBhE"}
|
package/dist/abort.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose multiple AbortSignals into one that aborts as soon as any of the
|
|
3
|
+
* inputs aborts. A polyfill for `AbortSignal.any()` (Node 20.3+) so this
|
|
4
|
+
* SDK works on Node 18.
|
|
5
|
+
*/
|
|
6
|
+
export function anySignal(...signals) {
|
|
7
|
+
// Fast path: native AbortSignal.any when available.
|
|
8
|
+
const anyFn = AbortSignal.any;
|
|
9
|
+
if (typeof anyFn === 'function')
|
|
10
|
+
return anyFn(signals);
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const abort = (reason) => {
|
|
13
|
+
if (controller.signal.aborted)
|
|
14
|
+
return;
|
|
15
|
+
controller.abort(reason);
|
|
16
|
+
for (const s of signals)
|
|
17
|
+
s.removeEventListener('abort', listeners.get(s));
|
|
18
|
+
};
|
|
19
|
+
const listeners = new Map();
|
|
20
|
+
for (const signal of signals) {
|
|
21
|
+
if (signal.aborted) {
|
|
22
|
+
controller.abort(signal.reason);
|
|
23
|
+
return controller.signal;
|
|
24
|
+
}
|
|
25
|
+
const fn = () => abort(signal.reason);
|
|
26
|
+
listeners.set(signal, fn);
|
|
27
|
+
signal.addEventListener('abort', fn, { once: true });
|
|
28
|
+
}
|
|
29
|
+
return controller.signal;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../src/abort.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAG,OAAsB;IACjD,oDAAoD;IACpD,MAAM,KAAK,GAAI,WAAsE,CAAC,GAAG,CAAC;IAC1F,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC;IAEvD,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,CAAC,MAAe,EAAQ,EAAE;QACtC,IAAI,UAAU,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO;QACtC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC;IAC7E,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;IACrD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAChC,OAAO,UAAU,CAAC,MAAM,CAAC;QAC3B,CAAC;QACD,MAAM,EAAE,GAAG,GAAS,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC5C,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC1B,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC;AAC3B,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AckRequest, PollRequest, PollResponse, RegisterJobRequest, RegisterJobResponse, RenewRequest, WorkEvent } from './protocol.js';
|
|
2
|
+
import type { Logger } from './logger.js';
|
|
3
|
+
export interface CroniqClientOptions {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
apiKey?: string | undefined;
|
|
6
|
+
bearerToken?: string | undefined;
|
|
7
|
+
/** Custom `fetch` implementation. Defaults to the global `fetch`. */
|
|
8
|
+
fetchImpl?: typeof fetch;
|
|
9
|
+
logger?: Logger | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare class HttpError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly statusText: string;
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly body: string;
|
|
16
|
+
constructor(status: number, statusText: string, path: string, body: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* HTTP client for the Croniq runner API. Adds `Authorization` per request
|
|
20
|
+
* (ApiKey takes precedence over Bearer when both are set) and serialises
|
|
21
|
+
* snake_case JSON in/out.
|
|
22
|
+
*/
|
|
23
|
+
export declare class CroniqClient {
|
|
24
|
+
#private;
|
|
25
|
+
constructor(opts: CroniqClientOptions);
|
|
26
|
+
poll(req: PollRequest, timeoutMs: number, signal: AbortSignal): Promise<PollResponse>;
|
|
27
|
+
ack(req: AckRequest, signal: AbortSignal): Promise<void>;
|
|
28
|
+
renew(req: RenewRequest, signal: AbortSignal): Promise<void>;
|
|
29
|
+
pushEvents(executionId: string, events: WorkEvent[], signal: AbortSignal): Promise<void>;
|
|
30
|
+
registerJob(req: RegisterJobRequest, signal: AbortSignal): Promise<RegisterJobResponse | undefined>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,SAAS,EACV,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,qBAAa,SAAU,SAAQ,KAAK;aAEhB,MAAM,EAAE,MAAM;aACd,UAAU,EAAE,MAAM;aAClB,IAAI,EAAE,MAAM;aACZ,IAAI,EAAE,MAAM;gBAHZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;CAK/B;AAED;;;;GAIG;AACH,qBAAa,YAAY;;gBAOX,IAAI,EAAE,mBAAmB;IAQ/B,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAKrF,GAAG,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxD,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAMxF,WAAW,CAAC,GAAG,EAAE,kBAAkB,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;CAgE1G"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { AbortError } from './deferred.js';
|
|
2
|
+
import { trimTrailingSlashes } from './url.js';
|
|
3
|
+
export class HttpError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
statusText;
|
|
6
|
+
path;
|
|
7
|
+
body;
|
|
8
|
+
constructor(status, statusText, path, body) {
|
|
9
|
+
super(`${status} ${statusText} on ${path}`);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.statusText = statusText;
|
|
12
|
+
this.path = path;
|
|
13
|
+
this.body = body;
|
|
14
|
+
this.name = 'HttpError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* HTTP client for the Croniq runner API. Adds `Authorization` per request
|
|
19
|
+
* (ApiKey takes precedence over Bearer when both are set) and serialises
|
|
20
|
+
* snake_case JSON in/out.
|
|
21
|
+
*/
|
|
22
|
+
export class CroniqClient {
|
|
23
|
+
#baseUrl;
|
|
24
|
+
#apiKey;
|
|
25
|
+
#bearerToken;
|
|
26
|
+
#fetch;
|
|
27
|
+
#logger;
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
this.#baseUrl = trimTrailingSlashes(opts.baseUrl);
|
|
30
|
+
this.#apiKey = opts.apiKey;
|
|
31
|
+
this.#bearerToken = opts.bearerToken;
|
|
32
|
+
this.#fetch = opts.fetchImpl ?? fetch;
|
|
33
|
+
this.#logger = opts.logger;
|
|
34
|
+
}
|
|
35
|
+
async poll(req, timeoutMs, signal) {
|
|
36
|
+
const body = await this.#send('POST', '/v1/work/poll', req, signal, timeoutMs);
|
|
37
|
+
return body ?? { work: [], cancel: [] };
|
|
38
|
+
}
|
|
39
|
+
async ack(req, signal) {
|
|
40
|
+
await this.#send('POST', '/v1/work/ack', req, signal);
|
|
41
|
+
}
|
|
42
|
+
async renew(req, signal) {
|
|
43
|
+
await this.#send('POST', '/v1/work/renew', req, signal);
|
|
44
|
+
}
|
|
45
|
+
async pushEvents(executionId, events, signal) {
|
|
46
|
+
if (events.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
const path = `/v1/work/${encodeURIComponent(executionId)}/events`;
|
|
49
|
+
await this.#send('POST', path, events, signal);
|
|
50
|
+
}
|
|
51
|
+
async registerJob(req, signal) {
|
|
52
|
+
const body = await this.#send('POST', '/v1/jobs/register', req, signal);
|
|
53
|
+
if (body?.status === 'skipped_dsl_precedence') {
|
|
54
|
+
this.#logger?.info(`job ${body.job_key} is managed by the Croniqfile (DSL precedence) — schedule registration skipped`, { job_key: body.job_key });
|
|
55
|
+
}
|
|
56
|
+
return body;
|
|
57
|
+
}
|
|
58
|
+
async #send(method, path, body, outerSignal, timeoutMs) {
|
|
59
|
+
const url = `${this.#baseUrl}${path}`;
|
|
60
|
+
const headers = {
|
|
61
|
+
'content-type': 'application/json',
|
|
62
|
+
accept: 'application/json',
|
|
63
|
+
};
|
|
64
|
+
if (this.#apiKey) {
|
|
65
|
+
headers.authorization = `ApiKey ${this.#apiKey}`;
|
|
66
|
+
}
|
|
67
|
+
else if (this.#bearerToken) {
|
|
68
|
+
headers.authorization = `Bearer ${this.#bearerToken}`;
|
|
69
|
+
}
|
|
70
|
+
const { signal, dispose } = composeSignals(outerSignal, timeoutMs);
|
|
71
|
+
let response;
|
|
72
|
+
try {
|
|
73
|
+
response = await this.#fetch(url, {
|
|
74
|
+
method,
|
|
75
|
+
headers,
|
|
76
|
+
body: JSON.stringify(body),
|
|
77
|
+
signal,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
if (isAbortLikeError(err)) {
|
|
82
|
+
throw new AbortError(`request to ${path} aborted`);
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
dispose();
|
|
88
|
+
}
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const text = await response.text().catch(() => '');
|
|
91
|
+
throw new HttpError(response.status, response.statusText, path, text);
|
|
92
|
+
}
|
|
93
|
+
// 204 No Content / empty body — return undefined so callers see `null`-ish.
|
|
94
|
+
if (response.status === 204)
|
|
95
|
+
return undefined;
|
|
96
|
+
const text = await response.text();
|
|
97
|
+
if (text.length === 0)
|
|
98
|
+
return undefined;
|
|
99
|
+
try {
|
|
100
|
+
return JSON.parse(text);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Some endpoints respond 200 with a non-JSON body — treat as void.
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function composeSignals(outer, timeoutMs) {
|
|
109
|
+
if (timeoutMs == null) {
|
|
110
|
+
return { signal: outer, dispose: () => { } };
|
|
111
|
+
}
|
|
112
|
+
// Node 18 doesn't have AbortSignal.any; build one by hand. The caller MUST
|
|
113
|
+
// invoke dispose() in a finally — otherwise long-lived outer signals
|
|
114
|
+
// (e.g. the runner's loop signal) accumulate one listener per poll.
|
|
115
|
+
const ac = new AbortController();
|
|
116
|
+
let timer;
|
|
117
|
+
const onOuterAbort = () => {
|
|
118
|
+
if (timer)
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
ac.abort(outer.reason);
|
|
121
|
+
};
|
|
122
|
+
const dispose = () => {
|
|
123
|
+
if (timer)
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
outer.removeEventListener('abort', onOuterAbort);
|
|
126
|
+
};
|
|
127
|
+
if (outer.aborted) {
|
|
128
|
+
ac.abort(outer.reason);
|
|
129
|
+
return { signal: ac.signal, dispose };
|
|
130
|
+
}
|
|
131
|
+
timer = setTimeout(() => {
|
|
132
|
+
outer.removeEventListener('abort', onOuterAbort);
|
|
133
|
+
ac.abort(new AbortError('timeout'));
|
|
134
|
+
}, timeoutMs);
|
|
135
|
+
outer.addEventListener('abort', onOuterAbort, { once: true });
|
|
136
|
+
return { signal: ac.signal, dispose };
|
|
137
|
+
}
|
|
138
|
+
function isAbortLikeError(err) {
|
|
139
|
+
if (!(err instanceof Error))
|
|
140
|
+
return false;
|
|
141
|
+
if (err.name === 'AbortError')
|
|
142
|
+
return true;
|
|
143
|
+
// undici exposes the original cause for fetch aborts in some Node versions.
|
|
144
|
+
const cause = err.cause;
|
|
145
|
+
return cause instanceof Error && cause.name === 'AbortError';
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { CroniqClient } from './client.js';
|
|
2
|
+
import type { LogEnrichment } from './enrichment.js';
|
|
3
|
+
import type { Logger } from './logger.js';
|
|
4
|
+
import { StreamingLogWriter, type LogWriter } from './log-writer.js';
|
|
5
|
+
import type { ResolvedLogWriterOptions } from './options.js';
|
|
6
|
+
import type { WorkEvent, WorkEventLevel } from './protocol.js';
|
|
7
|
+
export interface ExecutionContext {
|
|
8
|
+
/** Server-assigned execution identifier. */
|
|
9
|
+
readonly executionId: string;
|
|
10
|
+
/** Job key, e.g. `billing:invoice`. */
|
|
11
|
+
readonly jobKey: string;
|
|
12
|
+
/** 1-based attempt counter, incremented on each retry. */
|
|
13
|
+
readonly attempt: number;
|
|
14
|
+
/**
|
|
15
|
+
* Raw metadata payload from the server. The shape is job-specific — cast
|
|
16
|
+
* to your expected type. Field names are the original snake_case sent by
|
|
17
|
+
* the server (no transformation).
|
|
18
|
+
*/
|
|
19
|
+
readonly metadata: unknown;
|
|
20
|
+
/** Server-declared timeout for this execution, in milliseconds. */
|
|
21
|
+
readonly timeoutMs: number;
|
|
22
|
+
/** The runner's stable identifier. */
|
|
23
|
+
readonly runnerId: string;
|
|
24
|
+
/** Free-form tags this runner self-declared. */
|
|
25
|
+
readonly runnerTags: readonly string[];
|
|
26
|
+
/**
|
|
27
|
+
* Combined abort signal: aborts when the server cancels the execution via
|
|
28
|
+
* `PollResponse.cancel`, OR when the runner's drain timeout elapses while
|
|
29
|
+
* still in-flight. Handlers should propagate this to downstream awaits.
|
|
30
|
+
*/
|
|
31
|
+
readonly signal: AbortSignal;
|
|
32
|
+
/** Logger pre-scoped with `execution_id`, `job_key`, `runner_id`, `attempt`. */
|
|
33
|
+
readonly logger: Logger;
|
|
34
|
+
/**
|
|
35
|
+
* Streaming log writer that POSTs events to the Croniq server's execution
|
|
36
|
+
* log (visible in the UI). Lazily initialised on first access; the runner
|
|
37
|
+
* drains the writer (bounded by `logWriter.shutdownTimeoutMs`) before
|
|
38
|
+
* sending the ack.
|
|
39
|
+
*/
|
|
40
|
+
readonly logWriter: LogWriter;
|
|
41
|
+
/**
|
|
42
|
+
* Push a single event inline (awaits the HTTP POST). For high-volume
|
|
43
|
+
* scenarios, prefer {@link logWriter}.
|
|
44
|
+
*/
|
|
45
|
+
pushEvent(level: WorkEventLevel, message: string, fields?: Record<string, string>): Promise<void>;
|
|
46
|
+
/** Push pre-built events inline. */
|
|
47
|
+
pushEvents(events: WorkEvent[]): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export interface ExecutionContextInput {
|
|
50
|
+
executionId: string;
|
|
51
|
+
jobKey: string;
|
|
52
|
+
attempt: number;
|
|
53
|
+
metadata: unknown;
|
|
54
|
+
timeoutMs: number;
|
|
55
|
+
runnerId: string;
|
|
56
|
+
runnerTags: readonly string[];
|
|
57
|
+
signal: AbortSignal;
|
|
58
|
+
logger: Logger;
|
|
59
|
+
client: CroniqClient;
|
|
60
|
+
enrichment: LogEnrichment;
|
|
61
|
+
logWriterOptions: ResolvedLogWriterOptions;
|
|
62
|
+
sdkLogger: Logger;
|
|
63
|
+
}
|
|
64
|
+
export declare class ExecutionContextImpl implements ExecutionContext {
|
|
65
|
+
#private;
|
|
66
|
+
readonly executionId: string;
|
|
67
|
+
readonly jobKey: string;
|
|
68
|
+
readonly attempt: number;
|
|
69
|
+
readonly metadata: unknown;
|
|
70
|
+
readonly timeoutMs: number;
|
|
71
|
+
readonly runnerId: string;
|
|
72
|
+
readonly runnerTags: readonly string[];
|
|
73
|
+
readonly signal: AbortSignal;
|
|
74
|
+
readonly logger: Logger;
|
|
75
|
+
constructor(input: ExecutionContextInput);
|
|
76
|
+
get logWriter(): LogWriter;
|
|
77
|
+
/** Internal accessor — returns the writer only if a handler actually used it. */
|
|
78
|
+
get logWriterIfCreated(): StreamingLogWriter | null;
|
|
79
|
+
pushEvent(level: WorkEventLevel, message: string, fields?: Record<string, string>): Promise<void>;
|
|
80
|
+
pushEvents(events: WorkEvent[]): Promise<void>;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { StreamingLogWriter } from './log-writer.js';
|
|
2
|
+
export class ExecutionContextImpl {
|
|
3
|
+
executionId;
|
|
4
|
+
jobKey;
|
|
5
|
+
attempt;
|
|
6
|
+
metadata;
|
|
7
|
+
timeoutMs;
|
|
8
|
+
runnerId;
|
|
9
|
+
runnerTags;
|
|
10
|
+
signal;
|
|
11
|
+
logger;
|
|
12
|
+
#client;
|
|
13
|
+
#enrichment;
|
|
14
|
+
#logWriterOptions;
|
|
15
|
+
#sdkLogger;
|
|
16
|
+
#logWriter = null;
|
|
17
|
+
constructor(input) {
|
|
18
|
+
this.executionId = input.executionId;
|
|
19
|
+
this.jobKey = input.jobKey;
|
|
20
|
+
this.attempt = input.attempt;
|
|
21
|
+
this.metadata = input.metadata;
|
|
22
|
+
this.timeoutMs = input.timeoutMs;
|
|
23
|
+
this.runnerId = input.runnerId;
|
|
24
|
+
this.runnerTags = input.runnerTags;
|
|
25
|
+
this.signal = input.signal;
|
|
26
|
+
this.logger = input.logger;
|
|
27
|
+
this.#client = input.client;
|
|
28
|
+
this.#enrichment = input.enrichment;
|
|
29
|
+
this.#logWriterOptions = input.logWriterOptions;
|
|
30
|
+
this.#sdkLogger = input.sdkLogger;
|
|
31
|
+
}
|
|
32
|
+
get logWriter() {
|
|
33
|
+
this.#logWriter ??= new StreamingLogWriter(this.#client, this.executionId, this.#enrichment, this.#logWriterOptions, this.#sdkLogger);
|
|
34
|
+
return this.#logWriter;
|
|
35
|
+
}
|
|
36
|
+
/** Internal accessor — returns the writer only if a handler actually used it. */
|
|
37
|
+
get logWriterIfCreated() {
|
|
38
|
+
return this.#logWriter;
|
|
39
|
+
}
|
|
40
|
+
pushEvent(level, message, fields) {
|
|
41
|
+
const event = fields ? { level, message, fields } : { level, message };
|
|
42
|
+
return this.pushEvents([event]);
|
|
43
|
+
}
|
|
44
|
+
async pushEvents(events) {
|
|
45
|
+
if (events.length === 0)
|
|
46
|
+
return;
|
|
47
|
+
const enriched = events.map((e) => this.#enrichment.enrich(e));
|
|
48
|
+
const ac = new AbortController();
|
|
49
|
+
await this.#client.pushEvents(this.executionId, enriched, ac.signal);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAkB,MAAM,iBAAiB,CAAC;AAgErE,MAAM,OAAO,oBAAoB;IACtB,WAAW,CAAS;IACpB,MAAM,CAAS;IACf,OAAO,CAAS;IAChB,QAAQ,CAAU;IAClB,SAAS,CAAS;IAClB,QAAQ,CAAS;IACjB,UAAU,CAAoB;IAC9B,MAAM,CAAc;IACpB,MAAM,CAAS;IAEf,OAAO,CAAe;IACtB,WAAW,CAAgB;IAC3B,iBAAiB,CAA2B;IAC5C,UAAU,CAAS;IAC5B,UAAU,GAA8B,IAAI,CAAC;IAE7C,YAAY,KAA4B;QACtC,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;QACjC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;QACnC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,UAAU,CAAC;QACpC,IAAI,CAAC,iBAAiB,GAAG,KAAK,CAAC,gBAAgB,CAAC;QAChD,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;IACpC,CAAC;IAED,IAAI,SAAS;QACX,IAAI,CAAC,UAAU,KAAK,IAAI,kBAAkB,CACxC,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,iBAAiB,EACtB,IAAI,CAAC,UAAU,CAChB,CAAC;QACF,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,iFAAiF;IACjF,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED,SAAS,CAAC,KAAqB,EAAE,OAAe,EAAE,MAA+B;QAC/E,MAAM,KAAK,GAAc,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAClF,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAmB;QAClC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/D,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;QACjC,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IACvE,CAAC;CACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Deferred<T> {
|
|
2
|
+
readonly promise: Promise<T>;
|
|
3
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
4
|
+
reject: (reason?: unknown) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function deferred<T = void>(): Deferred<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Promise-based sleep that resolves after `ms` milliseconds, or rejects with
|
|
9
|
+
* an `AbortError` if `signal` aborts first. A resolved AbortSignal at call
|
|
10
|
+
* time short-circuits to an immediate rejection.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
13
|
+
export declare class AbortError extends Error {
|
|
14
|
+
readonly name = "AbortError";
|
|
15
|
+
constructor(message?: string);
|
|
16
|
+
}
|
|
17
|
+
export declare function isAbortError(err: unknown): boolean;
|
|
18
|
+
//# sourceMappingURL=deferred.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred.d.ts","sourceRoot":"","sources":["../src/deferred.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAC7C,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC;AAED,wBAAgB,QAAQ,CAAC,CAAC,GAAG,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAQhD;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAerE;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAkB,IAAI,gBAAgB;gBAC1B,OAAO,SAA8B;CAGlD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAElD"}
|
package/dist/deferred.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function deferred() {
|
|
2
|
+
let resolve;
|
|
3
|
+
let reject;
|
|
4
|
+
const promise = new Promise((res, rej) => {
|
|
5
|
+
resolve = res;
|
|
6
|
+
reject = rej;
|
|
7
|
+
});
|
|
8
|
+
return { promise, resolve, reject };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Promise-based sleep that resolves after `ms` milliseconds, or rejects with
|
|
12
|
+
* an `AbortError` if `signal` aborts first. A resolved AbortSignal at call
|
|
13
|
+
* time short-circuits to an immediate rejection.
|
|
14
|
+
*/
|
|
15
|
+
export function sleep(ms, signal) {
|
|
16
|
+
if (signal?.aborted) {
|
|
17
|
+
return Promise.reject(new AbortError());
|
|
18
|
+
}
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
signal?.removeEventListener('abort', onAbort);
|
|
22
|
+
resolve();
|
|
23
|
+
}, ms);
|
|
24
|
+
const onAbort = () => {
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
reject(new AbortError());
|
|
27
|
+
};
|
|
28
|
+
signal?.addEventListener('abort', onAbort, { once: true });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export class AbortError extends Error {
|
|
32
|
+
name = 'AbortError';
|
|
33
|
+
constructor(message = 'The operation was aborted') {
|
|
34
|
+
super(message);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function isAbortError(err) {
|
|
38
|
+
return err instanceof Error && (err.name === 'AbortError' || err.name === 'AbortError');
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=deferred.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred.js","sourceRoot":"","sources":["../src/deferred.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,QAAQ;IACtB,IAAI,OAA6C,CAAC;IAClD,IAAI,MAAmC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1C,OAAO,GAAG,GAAG,CAAC;QACd,MAAM,GAAG,GAAG,CAAC;IACf,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC;QACZ,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC;QAC3B,CAAC,CAAC;QACF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,OAAO,UAAW,SAAQ,KAAK;IACjB,IAAI,GAAG,YAAY,CAAC;IACtC,YAAY,OAAO,GAAG,2BAA2B;QAC/C,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,OAAO,GAAG,YAAY,KAAK,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC;AAC1F,CAAC"}
|