@valentinkolb/sync 2.0.5 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valentinkolb/sync",
3
- "version": "2.0.5",
3
+ "version": "2.1.0",
4
4
  "description": "Distributed synchronization primitives for Bun and TypeScript",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -0,0 +1,101 @@
1
+ import type { z } from "zod";
2
+ export declare class EphemeralCapacityError extends Error {
3
+ constructor(message?: string);
4
+ }
5
+ export declare class EphemeralPayloadTooLargeError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ export type EphemeralConfig<TSchema extends z.ZodTypeAny> = {
9
+ id: string;
10
+ schema: TSchema;
11
+ ttlMs: number;
12
+ tenantId?: string;
13
+ limits?: {
14
+ maxEntries?: number;
15
+ maxPayloadBytes?: number;
16
+ eventRetentionMs?: number;
17
+ eventMaxLen?: number;
18
+ };
19
+ };
20
+ export type EphemeralUpsertConfig<T> = {
21
+ key: string;
22
+ value: T;
23
+ ttlMs?: number;
24
+ tenantId?: string;
25
+ };
26
+ export type EphemeralTouchConfig = {
27
+ key: string;
28
+ ttlMs?: number;
29
+ tenantId?: string;
30
+ };
31
+ export type EphemeralRemoveConfig = {
32
+ key: string;
33
+ reason?: string;
34
+ tenantId?: string;
35
+ };
36
+ export type EphemeralEntry<T> = {
37
+ key: string;
38
+ value: T;
39
+ version: string;
40
+ updatedAt: number;
41
+ expiresAt: number;
42
+ };
43
+ export type EphemeralSnapshot<T> = {
44
+ entries: EphemeralEntry<T>[];
45
+ cursor: string;
46
+ };
47
+ export type EphemeralRecvConfig = {
48
+ wait?: boolean;
49
+ timeoutMs?: number;
50
+ signal?: AbortSignal;
51
+ };
52
+ export type EphemeralEvent<T> = {
53
+ type: "upsert";
54
+ cursor: string;
55
+ entry: EphemeralEntry<T>;
56
+ } | {
57
+ type: "touch";
58
+ cursor: string;
59
+ key: string;
60
+ version: string;
61
+ expiresAt: number;
62
+ } | {
63
+ type: "delete";
64
+ cursor: string;
65
+ key: string;
66
+ version: string;
67
+ deletedAt: number;
68
+ reason?: string;
69
+ } | {
70
+ type: "expire";
71
+ cursor: string;
72
+ key: string;
73
+ version: string;
74
+ expiredAt: number;
75
+ } | {
76
+ type: "overflow";
77
+ cursor: string;
78
+ after: string;
79
+ firstAvailable: string;
80
+ };
81
+ export type EphemeralReader<T> = {
82
+ recv(cfg?: EphemeralRecvConfig): Promise<EphemeralEvent<T> | null>;
83
+ stream(cfg?: EphemeralRecvConfig): AsyncIterable<EphemeralEvent<T>>;
84
+ };
85
+ export type EphemeralStore<T> = {
86
+ upsert(cfg: EphemeralUpsertConfig<T>): Promise<EphemeralEntry<T>>;
87
+ touch(cfg: EphemeralTouchConfig): Promise<{
88
+ ok: boolean;
89
+ version?: string;
90
+ expiresAt?: number;
91
+ }>;
92
+ remove(cfg: EphemeralRemoveConfig): Promise<boolean>;
93
+ snapshot(cfg?: {
94
+ tenantId?: string;
95
+ }): Promise<EphemeralSnapshot<T>>;
96
+ reader(cfg?: {
97
+ after?: string;
98
+ tenantId?: string;
99
+ }): EphemeralReader<T>;
100
+ };
101
+ export declare const ephemeral: <TSchema extends z.ZodTypeAny>(config: EphemeralConfig<TSchema>) => EphemeralStore<z.infer<TSchema>>;
@@ -0,0 +1,3 @@
1
+ export type MisfirePolicy = "skip" | "catch_up_one" | "catch_up_all";
2
+ export declare const nextCronTimestamp: (cron: string, tz: string, afterTimestampMs: number) => number;
3
+ export declare const assertValidTimeZone: (tz: string) => void;
package/src/job.d.ts CHANGED
@@ -14,6 +14,7 @@ export type JobTerminal<Result = unknown> = {
14
14
  };
15
15
  export type SubmitOptions = {
16
16
  key?: string;
17
+ keyTtlMs?: number;
17
18
  delayMs?: number;
18
19
  at?: number;
19
20
  maxAttempts?: number;
@@ -84,6 +85,7 @@ export type JobHandle<Input, Result = unknown> = {
84
85
  submit(cfg: {
85
86
  input: Input;
86
87
  } & SubmitOptions): Promise<JobId>;
88
+ validateInput(input: unknown): void;
87
89
  join(cfg: {
88
90
  id: JobId;
89
91
  } & JoinOptions): Promise<JobTerminal<Result>>;
package/src/retry.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ export type RetryOptions = {
2
+ attempts?: number;
3
+ minDelayMs?: number;
4
+ maxDelayMs?: number;
5
+ factor?: number;
6
+ jitter?: number;
7
+ signal?: AbortSignal;
8
+ retryIf?: (error: unknown) => boolean;
9
+ };
10
+ export declare const isRetryableTransportError: (error: unknown) => boolean;
11
+ export declare const DEFAULT_RETRY_OPTIONS: {
12
+ readonly attempts: 8;
13
+ readonly minDelayMs: 100;
14
+ readonly maxDelayMs: 2000;
15
+ readonly factor: 2;
16
+ readonly jitter: 0.2;
17
+ readonly retryIf: (error: unknown) => boolean;
18
+ };
19
+ export declare const retry: <T>(fn: (attempt: number) => Promise<T> | T, opts?: RetryOptions) => Promise<T>;
@@ -0,0 +1,141 @@
1
+ import { type MisfirePolicy } from "./internal/cron";
2
+ type JobSubmitter = {
3
+ id: string;
4
+ submit(cfg: {
5
+ input: unknown;
6
+ key?: string;
7
+ keyTtlMs?: number;
8
+ at?: number;
9
+ delayMs?: number;
10
+ meta?: Record<string, unknown>;
11
+ }): Promise<string>;
12
+ validateInput?(input: unknown): void;
13
+ };
14
+ export type SchedulerMetric = {
15
+ type: "leader_acquired";
16
+ ts: number;
17
+ } | {
18
+ type: "leader_lost";
19
+ ts: number;
20
+ reason: "extend_failed" | "stop";
21
+ } | {
22
+ type: "tick_error";
23
+ ts: number;
24
+ message: string;
25
+ } | {
26
+ type: "schedule_registered";
27
+ ts: number;
28
+ scheduleId: string;
29
+ created: boolean;
30
+ } | {
31
+ type: "schedule_updated";
32
+ ts: number;
33
+ scheduleId: string;
34
+ } | {
35
+ type: "schedule_unregistered";
36
+ ts: number;
37
+ scheduleId: string;
38
+ } | {
39
+ type: "dispatch_submitted";
40
+ ts: number;
41
+ scheduleId: string;
42
+ slotTs: number;
43
+ jobId: string;
44
+ } | {
45
+ type: "dispatch_skipped";
46
+ ts: number;
47
+ scheduleId: string;
48
+ reason: "missing_handler" | "cas_stale";
49
+ } | {
50
+ type: "dispatch_failed";
51
+ ts: number;
52
+ scheduleId: string;
53
+ message: string;
54
+ } | {
55
+ type: "dispatch_dlq";
56
+ ts: number;
57
+ scheduleId: string;
58
+ slotTs: number;
59
+ message: string;
60
+ } | {
61
+ type: "dispatch_advanced_after_failures";
62
+ ts: number;
63
+ scheduleId: string;
64
+ slotTs: number;
65
+ failures: number;
66
+ };
67
+ export type SchedulerConfig = {
68
+ id: string;
69
+ prefix?: string;
70
+ leader?: {
71
+ leaseMs?: number;
72
+ heartbeatMs?: number;
73
+ };
74
+ dispatch?: {
75
+ tickMs?: number;
76
+ batchSize?: number;
77
+ maxSubmitsPerTick?: number;
78
+ submitRetries?: number;
79
+ submitBackoffBaseMs?: number;
80
+ submitBackoffMaxMs?: number;
81
+ scheduledJobKeyTtlMs?: number;
82
+ dlqMaxEntries?: number;
83
+ maxConsecutiveDispatchFailures?: number;
84
+ };
85
+ strictHandlers?: boolean;
86
+ onMetric?: (metric: SchedulerMetric) => void;
87
+ };
88
+ export type SchedulerRegisterConfig = {
89
+ id: string;
90
+ cron: string;
91
+ tz?: string;
92
+ job: JobSubmitter;
93
+ input: unknown;
94
+ misfire?: MisfirePolicy;
95
+ maxCatchUpRuns?: number;
96
+ meta?: Record<string, unknown>;
97
+ };
98
+ export type SchedulerUnregisterConfig = {
99
+ id: string;
100
+ };
101
+ export type SchedulerGetConfig = {
102
+ id: string;
103
+ };
104
+ export type SchedulerInfo = {
105
+ id: string;
106
+ cron: string;
107
+ tz: string;
108
+ misfire: MisfirePolicy;
109
+ maxCatchUpRuns: number;
110
+ jobId: string;
111
+ nextRunAt: number;
112
+ createdAt: number;
113
+ updatedAt: number;
114
+ };
115
+ export type SchedulerMetricsSnapshot = {
116
+ isLeader: boolean;
117
+ leaderEpoch: number;
118
+ leaderChanges: number;
119
+ dispatchSubmitted: number;
120
+ dispatchFailed: number;
121
+ dispatchRetried: number;
122
+ dispatchSkipped: number;
123
+ dispatchDlq: number;
124
+ tickErrors: number;
125
+ lastTickAt: number | null;
126
+ };
127
+ export type Scheduler = {
128
+ id: string;
129
+ start(): void;
130
+ stop(): Promise<void>;
131
+ register(cfg: SchedulerRegisterConfig): Promise<{
132
+ created: boolean;
133
+ updated: boolean;
134
+ }>;
135
+ unregister(cfg: SchedulerUnregisterConfig): Promise<void>;
136
+ get(cfg: SchedulerGetConfig): Promise<SchedulerInfo | null>;
137
+ list(): Promise<SchedulerInfo[]>;
138
+ metrics(): SchedulerMetricsSnapshot;
139
+ };
140
+ export declare const scheduler: (config: SchedulerConfig) => Scheduler;
141
+ export {};