@voyantjs/workflows 0.0.0 → 0.6.8
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/dist/auth/index.d.ts +26 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +137 -0
- package/dist/conditions.d.ts +29 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/conditions.js +5 -0
- package/dist/handler/index.d.ts +104 -0
- package/dist/handler/index.d.ts.map +1 -0
- package/dist/handler/index.js +238 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/protocol/index.d.ts +187 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +7 -0
- package/dist/rate-limit/index.d.ts +40 -0
- package/dist/rate-limit/index.d.ts.map +1 -0
- package/dist/rate-limit/index.js +139 -0
- package/dist/runtime/ctx.d.ts +102 -0
- package/dist/runtime/ctx.d.ts.map +1 -0
- package/dist/runtime/ctx.js +607 -0
- package/dist/runtime/determinism.d.ts +19 -0
- package/dist/runtime/determinism.d.ts.map +1 -0
- package/dist/runtime/determinism.js +61 -0
- package/dist/runtime/errors.d.ts +21 -0
- package/dist/runtime/errors.d.ts.map +1 -0
- package/dist/runtime/errors.js +45 -0
- package/dist/runtime/executor.d.ts +159 -0
- package/dist/runtime/executor.d.ts.map +1 -0
- package/dist/runtime/executor.js +225 -0
- package/dist/runtime/journal.d.ts +55 -0
- package/dist/runtime/journal.d.ts.map +1 -0
- package/dist/runtime/journal.js +28 -0
- package/dist/testing/index.d.ts +117 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +595 -0
- package/dist/trigger.d.ts +122 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +23 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/workflow.d.ts +212 -0
- package/dist/workflow.d.ts.map +1 -0
- package/dist/workflow.js +46 -0
- package/package.json +30 -30
- package/src/auth/index.ts +46 -52
- package/src/conditions.ts +13 -13
- package/src/handler/index.ts +110 -106
- package/src/index.ts +7 -7
- package/src/protocol/index.ts +137 -71
- package/src/rate-limit/index.ts +77 -78
- package/src/runtime/ctx.ts +354 -342
- package/src/runtime/determinism.ts +27 -27
- package/src/runtime/errors.ts +17 -17
- package/src/runtime/executor.ts +179 -172
- package/src/runtime/journal.ts +25 -25
- package/src/testing/index.ts +268 -202
- package/src/trigger.ts +64 -71
- package/src/types.ts +16 -18
- package/src/workflow.ts +154 -152
package/src/protocol/index.ts
CHANGED
|
@@ -5,18 +5,18 @@
|
|
|
5
5
|
// (test harness, adapters, dashboards) can build and inspect wire
|
|
6
6
|
// payloads without reaching into runtime internals.
|
|
7
7
|
|
|
8
|
-
export type ProtocolVersion = 1
|
|
9
|
-
export const PROTOCOL_VERSION: ProtocolVersion = 1
|
|
8
|
+
export type ProtocolVersion = 1
|
|
9
|
+
export const PROTOCOL_VERSION: ProtocolVersion = 1
|
|
10
10
|
|
|
11
11
|
// Journal types: shape of the tenant-side view of a run's state.
|
|
12
12
|
// Re-exported so orchestrators and tools can build/inspect journals
|
|
13
13
|
// without reaching into the runtime subpath.
|
|
14
14
|
export type {
|
|
15
|
+
CompensationJournalEntry,
|
|
15
16
|
JournalSlice,
|
|
16
17
|
StepJournalEntry,
|
|
17
18
|
WaitpointResolutionEntry,
|
|
18
|
-
|
|
19
|
-
} from "../runtime/journal.js";
|
|
19
|
+
} from "../runtime/journal.js"
|
|
20
20
|
|
|
21
21
|
export type ExecutionStatus =
|
|
22
22
|
| "CREATED"
|
|
@@ -25,109 +25,175 @@ export type ExecutionStatus =
|
|
|
25
25
|
| "EXECUTING_WITH_WAITPOINTS"
|
|
26
26
|
| "SUSPENDED"
|
|
27
27
|
| "PENDING_CANCEL"
|
|
28
|
-
| "FINISHED"
|
|
28
|
+
| "FINISHED"
|
|
29
29
|
|
|
30
|
-
export type WaitpointKind = "DATETIME" | "EVENT" | "SIGNAL" | "RUN" | "MANUAL"
|
|
30
|
+
export type WaitpointKind = "DATETIME" | "EVENT" | "SIGNAL" | "RUN" | "MANUAL"
|
|
31
31
|
|
|
32
32
|
export interface SerializedError {
|
|
33
|
-
category: "USER_ERROR" | "RUNTIME_ERROR"
|
|
34
|
-
code: string
|
|
35
|
-
message: string
|
|
36
|
-
name?: string
|
|
37
|
-
stack?: string
|
|
38
|
-
cause?: SerializedError
|
|
39
|
-
data?: Record<string, unknown
|
|
33
|
+
category: "USER_ERROR" | "RUNTIME_ERROR"
|
|
34
|
+
code: string
|
|
35
|
+
message: string
|
|
36
|
+
name?: string
|
|
37
|
+
stack?: string
|
|
38
|
+
cause?: SerializedError
|
|
39
|
+
data?: Record<string, unknown>
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export type PayloadLocation = "INLINE" | "EXTERNAL"
|
|
42
|
+
export type PayloadLocation = "INLINE" | "EXTERNAL"
|
|
43
43
|
|
|
44
44
|
export interface WorkflowManifest {
|
|
45
|
-
schemaVersion: 1
|
|
46
|
-
projectId: string
|
|
47
|
-
versionId: string
|
|
48
|
-
builtAt: number
|
|
49
|
-
builderVersion: string
|
|
50
|
-
capabilities: string[]
|
|
51
|
-
workflows: WorkflowManifestEntry[]
|
|
52
|
-
eventFilters: EventFilterManifestEntry[]
|
|
53
|
-
bindings: Record<string, { type: "d1" | "r2" | "kv" | "queue"; name: string }
|
|
54
|
-
environments: Record<string, { customDomain?: string }
|
|
45
|
+
schemaVersion: 1
|
|
46
|
+
projectId: string
|
|
47
|
+
versionId: string
|
|
48
|
+
builtAt: number
|
|
49
|
+
builderVersion: string
|
|
50
|
+
capabilities: string[]
|
|
51
|
+
workflows: WorkflowManifestEntry[]
|
|
52
|
+
eventFilters: EventFilterManifestEntry[]
|
|
53
|
+
bindings: Record<string, { type: "d1" | "r2" | "kv" | "queue"; name: string }>
|
|
54
|
+
environments: Record<string, { customDomain?: string }>
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export interface WorkflowManifestEntry {
|
|
58
|
-
id: string
|
|
59
|
-
version: string
|
|
60
|
-
inputSchema?: unknown
|
|
61
|
-
outputSchema?: unknown
|
|
62
|
-
steps: ManifestStep[]
|
|
63
|
-
schedules: ManifestSchedule[]
|
|
64
|
-
defaultRuntime: "edge" | "node"
|
|
65
|
-
hasCompensation: boolean
|
|
66
|
-
sourceLocation: { file: string; line: number }
|
|
58
|
+
id: string
|
|
59
|
+
version: string
|
|
60
|
+
inputSchema?: unknown
|
|
61
|
+
outputSchema?: unknown
|
|
62
|
+
steps: ManifestStep[]
|
|
63
|
+
schedules: ManifestSchedule[]
|
|
64
|
+
defaultRuntime: "edge" | "node"
|
|
65
|
+
hasCompensation: boolean
|
|
66
|
+
sourceLocation: { file: string; line: number }
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export interface ManifestStep {
|
|
70
|
-
id: string
|
|
71
|
-
runtime: "edge" | "node"
|
|
72
|
-
hasCompensation: boolean
|
|
73
|
-
sourceLocation: { file: string; line: number }
|
|
70
|
+
id: string
|
|
71
|
+
runtime: "edge" | "node"
|
|
72
|
+
hasCompensation: boolean
|
|
73
|
+
sourceLocation: { file: string; line: number }
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
export interface ManifestSchedule {
|
|
77
|
-
cron?: string
|
|
78
|
-
every?: string
|
|
79
|
-
at?: string
|
|
80
|
-
timezone?: string
|
|
81
|
-
environments?: ("production" | "preview" | "development")[]
|
|
82
|
-
name?: string
|
|
77
|
+
cron?: string
|
|
78
|
+
every?: string
|
|
79
|
+
at?: string
|
|
80
|
+
timezone?: string
|
|
81
|
+
environments?: ("production" | "preview" | "development")[]
|
|
82
|
+
name?: string
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
export interface EventFilterManifestEntry {
|
|
86
|
-
id: string
|
|
87
|
-
eventType: string
|
|
88
|
-
scope?: string
|
|
89
|
-
matchExpression?: string
|
|
90
|
-
payloadHash: string
|
|
91
|
-
targetWorkflowId: string
|
|
86
|
+
id: string
|
|
87
|
+
eventType: string
|
|
88
|
+
scope?: string
|
|
89
|
+
matchExpression?: string
|
|
90
|
+
payloadHash: string
|
|
91
|
+
targetWorkflowId: string
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// WebSocket stream events — full union in docs/runtime-protocol.md §6.2.
|
|
95
95
|
export type StreamEvent =
|
|
96
|
-
| {
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
| {
|
|
97
|
+
kind: "step.started"
|
|
98
|
+
eventId: string
|
|
99
|
+
at: number
|
|
100
|
+
stepId: string
|
|
101
|
+
runtime: "edge" | "node"
|
|
102
|
+
machine?: string
|
|
103
|
+
}
|
|
104
|
+
| {
|
|
105
|
+
kind: "step.ok"
|
|
106
|
+
eventId: string
|
|
107
|
+
at: number
|
|
108
|
+
stepId: string
|
|
109
|
+
attempt: number
|
|
110
|
+
durationMs: number
|
|
111
|
+
output?: unknown
|
|
112
|
+
}
|
|
113
|
+
| {
|
|
114
|
+
kind: "step.err"
|
|
115
|
+
eventId: string
|
|
116
|
+
at: number
|
|
117
|
+
stepId: string
|
|
118
|
+
attempt: number
|
|
119
|
+
error: SerializedError
|
|
120
|
+
}
|
|
99
121
|
| { kind: "step.skipped"; eventId: string; at: number; stepId: string; reason: string }
|
|
100
|
-
| {
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
| {
|
|
123
|
+
kind: "step.compensated"
|
|
124
|
+
eventId: string
|
|
125
|
+
at: number
|
|
126
|
+
stepId: string
|
|
127
|
+
status: "ok" | "err"
|
|
128
|
+
error?: SerializedError
|
|
129
|
+
}
|
|
130
|
+
| {
|
|
131
|
+
kind: "waitpoint.registered"
|
|
132
|
+
eventId: string
|
|
133
|
+
at: number
|
|
134
|
+
waitpointId: string
|
|
135
|
+
waitpointKind: WaitpointKind
|
|
136
|
+
meta: Record<string, unknown>
|
|
137
|
+
}
|
|
138
|
+
| {
|
|
139
|
+
kind: "waitpoint.resolved"
|
|
140
|
+
eventId: string
|
|
141
|
+
at: number
|
|
142
|
+
waitpointId: string
|
|
143
|
+
payload?: unknown
|
|
144
|
+
source: "live" | "inbox" | "replay"
|
|
145
|
+
}
|
|
103
146
|
| { kind: "metadata.changed"; eventId: string; at: number; metadata: Record<string, unknown> }
|
|
104
|
-
| {
|
|
105
|
-
|
|
147
|
+
| {
|
|
148
|
+
kind: "stream.chunk"
|
|
149
|
+
eventId: string
|
|
150
|
+
at: number
|
|
151
|
+
streamId: string
|
|
152
|
+
chunk: unknown
|
|
153
|
+
encoding: "json" | "text" | "base64"
|
|
154
|
+
final: boolean
|
|
155
|
+
}
|
|
156
|
+
| {
|
|
157
|
+
kind: "log"
|
|
158
|
+
eventId: string
|
|
159
|
+
at: number
|
|
160
|
+
level: "info" | "warn" | "error"
|
|
161
|
+
message: string
|
|
162
|
+
stepId?: string
|
|
163
|
+
data?: object
|
|
164
|
+
}
|
|
106
165
|
| { kind: "version.rebased"; eventId: string; at: number; fromVersion: string; toVersion: string }
|
|
107
166
|
| { kind: "run.cancelled"; eventId: string; at: number; reason?: string }
|
|
108
|
-
| {
|
|
167
|
+
| {
|
|
168
|
+
kind: "run.finished"
|
|
169
|
+
eventId: string
|
|
170
|
+
at: number
|
|
171
|
+
status: string
|
|
172
|
+
output?: unknown
|
|
173
|
+
error?: SerializedError
|
|
174
|
+
}
|
|
109
175
|
|
|
110
176
|
// Shared envelope for journal events written by the orchestrator,
|
|
111
177
|
// the tenant worker, or a node-runtime container. Concrete `kind`
|
|
112
178
|
// discriminants are owned by the emitting layer.
|
|
113
179
|
export interface JournalEventEnvelope<TKind extends string = string, TData = unknown> {
|
|
114
|
-
eventId: string
|
|
115
|
-
runId: string
|
|
116
|
-
createdAt: number
|
|
117
|
-
kind: TKind
|
|
118
|
-
data: TData
|
|
119
|
-
snapshotId?: string
|
|
120
|
-
writtenBy: "orchestrator" | "tenant" | "node"
|
|
180
|
+
eventId: string
|
|
181
|
+
runId: string
|
|
182
|
+
createdAt: number
|
|
183
|
+
kind: TKind
|
|
184
|
+
data: TData
|
|
185
|
+
snapshotId?: string
|
|
186
|
+
writtenBy: "orchestrator" | "tenant" | "node"
|
|
121
187
|
}
|
|
122
188
|
|
|
123
189
|
export interface PublicAccessTokenClaims {
|
|
124
|
-
sub: "pat"
|
|
125
|
-
tenantId: string
|
|
126
|
-
environment: "production" | "preview" | "development"
|
|
127
|
-
scope: ("read" | "trigger" | "cancel")[]
|
|
190
|
+
sub: "pat"
|
|
191
|
+
tenantId: string
|
|
192
|
+
environment: "production" | "preview" | "development"
|
|
193
|
+
scope: ("read" | "trigger" | "cancel")[]
|
|
128
194
|
target:
|
|
129
195
|
| { kind: "run"; runId: string }
|
|
130
196
|
| { kind: "workflow"; workflowId: string }
|
|
131
|
-
| { kind: "tag"; tag: string }
|
|
132
|
-
exp: number
|
|
197
|
+
| { kind: "tag"; tag: string }
|
|
198
|
+
exp: number
|
|
133
199
|
}
|
package/src/rate-limit/index.ts
CHANGED
|
@@ -14,50 +14,50 @@
|
|
|
14
14
|
// should swap in a Durable-Object or Redis-backed implementation that
|
|
15
15
|
// shares state across isolates.
|
|
16
16
|
|
|
17
|
-
import type { Duration } from "../types.js"
|
|
17
|
+
import type { Duration } from "../types.js"
|
|
18
18
|
|
|
19
19
|
export interface AcquireArgs {
|
|
20
20
|
/** Bucket key — usually a tenant id, a url host, a user id, etc. */
|
|
21
|
-
key: string
|
|
21
|
+
key: string
|
|
22
22
|
/** Maximum units the bucket can hold. */
|
|
23
|
-
limit: number
|
|
23
|
+
limit: number
|
|
24
24
|
/** Units the current call consumes. */
|
|
25
|
-
units: number
|
|
25
|
+
units: number
|
|
26
26
|
/** Refill window in ms. `limit` tokens per `windowMs`. */
|
|
27
|
-
windowMs: number
|
|
27
|
+
windowMs: number
|
|
28
28
|
/** `queue` → wait until capacity; `fail` → throw immediately. */
|
|
29
|
-
onLimit: "queue" | "fail"
|
|
29
|
+
onLimit: "queue" | "fail"
|
|
30
30
|
/** Forwarded from the run; limiter observes aborts during queue waits. */
|
|
31
|
-
signal?: AbortSignal
|
|
31
|
+
signal?: AbortSignal
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
export interface RateLimiter {
|
|
35
|
-
acquire(args: AcquireArgs): Promise<void
|
|
35
|
+
acquire(args: AcquireArgs): Promise<void>
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** Error thrown when `onLimit === "fail"` and the bucket is empty. */
|
|
39
39
|
export class RateLimitExceededError extends Error {
|
|
40
|
-
readonly code = "RATE_LIMITED"
|
|
41
|
-
readonly retryAfterMs: number
|
|
40
|
+
readonly code = "RATE_LIMITED"
|
|
41
|
+
readonly retryAfterMs: number
|
|
42
42
|
constructor(key: string, retryAfterMs: number) {
|
|
43
|
-
super(`rate limit exceeded for key "${key}" (retry after ${retryAfterMs}ms)`)
|
|
44
|
-
this.name = "RateLimitExceededError"
|
|
45
|
-
this.retryAfterMs = retryAfterMs
|
|
43
|
+
super(`rate limit exceeded for key "${key}" (retry after ${retryAfterMs}ms)`)
|
|
44
|
+
this.name = "RateLimitExceededError"
|
|
45
|
+
this.retryAfterMs = retryAfterMs
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
interface Bucket {
|
|
50
|
-
tokens: number
|
|
51
|
-
capacity: number
|
|
52
|
-
refillPerMs: number
|
|
53
|
-
lastRefillAt: number
|
|
50
|
+
tokens: number
|
|
51
|
+
capacity: number
|
|
52
|
+
refillPerMs: number
|
|
53
|
+
lastRefillAt: number
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
export interface InMemoryLimiterOptions {
|
|
57
57
|
/** Injectable clock, ms since epoch. Defaults to Date.now. */
|
|
58
|
-
now?: () => number
|
|
58
|
+
now?: () => number
|
|
59
59
|
/** Injectable delay; defaults to setTimeout. Tests override this. */
|
|
60
|
-
delay?: (ms: number, signal?: AbortSignal) => Promise<void
|
|
60
|
+
delay?: (ms: number, signal?: AbortSignal) => Promise<void>
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
@@ -66,117 +66,116 @@ export interface InMemoryLimiterOptions {
|
|
|
66
66
|
* `limit` / `windowMs` of the first `acquire` call and are updated on
|
|
67
67
|
* subsequent calls that change them.
|
|
68
68
|
*/
|
|
69
|
-
export function createInMemoryRateLimiter(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
const delay = opts.delay ?? defaultDelay;
|
|
74
|
-
const buckets = new Map<string, Bucket>();
|
|
69
|
+
export function createInMemoryRateLimiter(opts: InMemoryLimiterOptions = {}): RateLimiter {
|
|
70
|
+
const now = opts.now ?? (() => Date.now())
|
|
71
|
+
const delay = opts.delay ?? defaultDelay
|
|
72
|
+
const buckets = new Map<string, Bucket>()
|
|
75
73
|
|
|
76
74
|
return {
|
|
77
75
|
async acquire(args) {
|
|
78
|
-
if (args.units <= 0) return
|
|
76
|
+
if (args.units <= 0) return
|
|
79
77
|
if (args.limit <= 0) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`rate-limit: "limit" must be > 0 (got ${args.limit}) for key "${args.key}"`,
|
|
82
|
-
);
|
|
78
|
+
throw new Error(`rate-limit: "limit" must be > 0 (got ${args.limit}) for key "${args.key}"`)
|
|
83
79
|
}
|
|
84
80
|
if (args.windowMs <= 0) {
|
|
85
81
|
throw new Error(
|
|
86
82
|
`rate-limit: "windowMs" must be > 0 (got ${args.windowMs}) for key "${args.key}"`,
|
|
87
|
-
)
|
|
83
|
+
)
|
|
88
84
|
}
|
|
89
85
|
if (args.units > args.limit) {
|
|
90
86
|
// The step will never be admissible — short-circuit regardless
|
|
91
87
|
// of onLimit to avoid hanging indefinitely under queue mode.
|
|
92
88
|
throw new Error(
|
|
93
89
|
`rate-limit: units (${args.units}) > limit (${args.limit}) for key "${args.key}" — step can never be admitted`,
|
|
94
|
-
)
|
|
90
|
+
)
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
while (true) {
|
|
98
|
-
const bucket = refill(buckets, args, now())
|
|
94
|
+
const bucket = refill(buckets, args, now())
|
|
99
95
|
if (bucket.tokens >= args.units) {
|
|
100
|
-
bucket.tokens -= args.units
|
|
101
|
-
return
|
|
96
|
+
bucket.tokens -= args.units
|
|
97
|
+
return
|
|
102
98
|
}
|
|
103
|
-
const missing = args.units - bucket.tokens
|
|
104
|
-
const waitMs = Math.ceil(missing / bucket.refillPerMs)
|
|
99
|
+
const missing = args.units - bucket.tokens
|
|
100
|
+
const waitMs = Math.ceil(missing / bucket.refillPerMs)
|
|
105
101
|
if (args.onLimit === "fail") {
|
|
106
|
-
throw new RateLimitExceededError(args.key, waitMs)
|
|
102
|
+
throw new RateLimitExceededError(args.key, waitMs)
|
|
107
103
|
}
|
|
108
|
-
await delay(waitMs, args.signal)
|
|
104
|
+
await delay(waitMs, args.signal)
|
|
109
105
|
if (args.signal?.aborted) {
|
|
110
|
-
throw args.signal.reason ?? new Error("rate-limit: aborted while queued")
|
|
106
|
+
throw args.signal.reason ?? new Error("rate-limit: aborted while queued")
|
|
111
107
|
}
|
|
112
108
|
}
|
|
113
109
|
},
|
|
114
|
-
}
|
|
110
|
+
}
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
function refill(
|
|
118
|
-
|
|
119
|
-
args
|
|
120
|
-
nowMs: number,
|
|
121
|
-
): Bucket {
|
|
122
|
-
const refillPerMs = args.limit / args.windowMs;
|
|
123
|
-
let b = buckets.get(args.key);
|
|
113
|
+
function refill(buckets: Map<string, Bucket>, args: AcquireArgs, nowMs: number): Bucket {
|
|
114
|
+
const refillPerMs = args.limit / args.windowMs
|
|
115
|
+
let b = buckets.get(args.key)
|
|
124
116
|
if (!b) {
|
|
125
117
|
b = {
|
|
126
118
|
tokens: args.limit,
|
|
127
119
|
capacity: args.limit,
|
|
128
120
|
refillPerMs,
|
|
129
121
|
lastRefillAt: nowMs,
|
|
130
|
-
}
|
|
131
|
-
buckets.set(args.key, b)
|
|
132
|
-
return b
|
|
122
|
+
}
|
|
123
|
+
buckets.set(args.key, b)
|
|
124
|
+
return b
|
|
133
125
|
}
|
|
134
126
|
// Re-parameterize if the caller changed limit / windowMs. Clamp
|
|
135
127
|
// tokens to the new capacity so a shrink doesn't leave stale excess.
|
|
136
128
|
if (b.capacity !== args.limit || b.refillPerMs !== refillPerMs) {
|
|
137
|
-
b.capacity = args.limit
|
|
138
|
-
b.refillPerMs = refillPerMs
|
|
139
|
-
if (b.tokens > b.capacity) b.tokens = b.capacity
|
|
129
|
+
b.capacity = args.limit
|
|
130
|
+
b.refillPerMs = refillPerMs
|
|
131
|
+
if (b.tokens > b.capacity) b.tokens = b.capacity
|
|
140
132
|
}
|
|
141
|
-
const elapsed = Math.max(0, nowMs - b.lastRefillAt)
|
|
133
|
+
const elapsed = Math.max(0, nowMs - b.lastRefillAt)
|
|
142
134
|
if (elapsed > 0) {
|
|
143
|
-
b.tokens = Math.min(b.capacity, b.tokens + elapsed * b.refillPerMs)
|
|
144
|
-
b.lastRefillAt = nowMs
|
|
135
|
+
b.tokens = Math.min(b.capacity, b.tokens + elapsed * b.refillPerMs)
|
|
136
|
+
b.lastRefillAt = nowMs
|
|
145
137
|
}
|
|
146
|
-
return b
|
|
138
|
+
return b
|
|
147
139
|
}
|
|
148
140
|
|
|
149
141
|
function defaultDelay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
150
142
|
return new Promise((resolve, reject) => {
|
|
151
143
|
if (signal?.aborted) {
|
|
152
|
-
reject(signal.reason ?? new Error("aborted"))
|
|
153
|
-
return
|
|
144
|
+
reject(signal.reason ?? new Error("aborted"))
|
|
145
|
+
return
|
|
154
146
|
}
|
|
155
147
|
const timer = setTimeout(() => {
|
|
156
|
-
signal?.removeEventListener("abort", onAbort)
|
|
157
|
-
resolve()
|
|
158
|
-
}, ms)
|
|
148
|
+
signal?.removeEventListener("abort", onAbort)
|
|
149
|
+
resolve()
|
|
150
|
+
}, ms)
|
|
159
151
|
const onAbort = () => {
|
|
160
|
-
clearTimeout(timer)
|
|
161
|
-
reject(signal?.reason ?? new Error("aborted"))
|
|
162
|
-
}
|
|
163
|
-
signal?.addEventListener("abort", onAbort, { once: true })
|
|
164
|
-
})
|
|
152
|
+
clearTimeout(timer)
|
|
153
|
+
reject(signal?.reason ?? new Error("aborted"))
|
|
154
|
+
}
|
|
155
|
+
signal?.addEventListener("abort", onAbort, { once: true })
|
|
156
|
+
})
|
|
165
157
|
}
|
|
166
158
|
|
|
167
159
|
/** Normalize a Duration to milliseconds. Same units as `toMs` in ctx.ts. */
|
|
168
160
|
export function durationToMs(d: Duration): number {
|
|
169
|
-
if (typeof d === "number") return d
|
|
170
|
-
const m = /^(\d+)(ms|s|m|h|d|w)$/.exec(d)
|
|
171
|
-
if (!m) throw new Error(`rate-limit: invalid duration "${d}"`)
|
|
172
|
-
const n = Number(m[1])
|
|
161
|
+
if (typeof d === "number") return d
|
|
162
|
+
const m = /^(\d+)(ms|s|m|h|d|w)$/.exec(d)
|
|
163
|
+
if (!m) throw new Error(`rate-limit: invalid duration "${d}"`)
|
|
164
|
+
const n = Number(m[1])
|
|
173
165
|
switch (m[2]) {
|
|
174
|
-
case "ms":
|
|
175
|
-
|
|
176
|
-
case "
|
|
177
|
-
|
|
178
|
-
case "
|
|
179
|
-
|
|
180
|
-
|
|
166
|
+
case "ms":
|
|
167
|
+
return n
|
|
168
|
+
case "s":
|
|
169
|
+
return n * 1_000
|
|
170
|
+
case "m":
|
|
171
|
+
return n * 60_000
|
|
172
|
+
case "h":
|
|
173
|
+
return n * 3_600_000
|
|
174
|
+
case "d":
|
|
175
|
+
return n * 86_400_000
|
|
176
|
+
case "w":
|
|
177
|
+
return n * 604_800_000
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`rate-limit: invalid duration "${d}"`)
|
|
181
180
|
}
|
|
182
181
|
}
|