@valentinkolb/sync 2.2.0 → 3.0.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 +144 -193
- package/browser/ephemeral.js +472 -0
- package/browser/index.js +21 -0
- package/browser/job.js +14687 -0
- package/browser/mutex.js +165 -0
- package/browser/queue.js +342 -0
- package/browser/ratelimit.js +124 -0
- package/browser/registry.js +662 -0
- package/browser/retry.js +94 -0
- package/browser/scheduler.js +988 -0
- package/browser/store.js +61 -0
- package/browser/topic.js +359 -0
- package/index.js +1 -18531
- package/package.json +19 -4
- package/src/browser/ephemeral.d.ts +101 -0
- package/src/browser/index.d.ts +10 -0
- package/src/browser/internal/emitter.d.ts +11 -0
- package/src/browser/internal/event-log.d.ts +33 -0
- package/src/browser/internal/id.d.ts +9 -0
- package/src/browser/internal/sleep.d.ts +2 -0
- package/src/browser/job.d.ts +107 -0
- package/src/browser/mutex.d.ts +28 -0
- package/src/browser/queue.d.ts +67 -0
- package/src/browser/ratelimit.d.ts +24 -0
- package/src/browser/registry.d.ts +131 -0
- package/src/browser/retry.d.ts +19 -0
- package/src/browser/scheduler.d.ts +164 -0
- package/src/browser/store.d.ts +17 -0
- package/src/browser/topic.d.ts +65 -0
package/package.json
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valentinkolb/sync",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Synchronization primitives for TypeScript — server (Redis) and browser (in-memory)",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
9
10
|
"exports": {
|
|
10
11
|
".": {
|
|
11
12
|
"import": "./index.js",
|
|
12
13
|
"types": "./index.d.ts"
|
|
13
|
-
}
|
|
14
|
+
},
|
|
15
|
+
"./browser": {
|
|
16
|
+
"import": "./browser/index.js",
|
|
17
|
+
"types": "./browser/index.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./browser/ratelimit": { "import": "./browser/ratelimit.js", "types": "./browser/ratelimit.d.ts" },
|
|
20
|
+
"./browser/mutex": { "import": "./browser/mutex.js", "types": "./browser/mutex.d.ts" },
|
|
21
|
+
"./browser/queue": { "import": "./browser/queue.js", "types": "./browser/queue.d.ts" },
|
|
22
|
+
"./browser/topic": { "import": "./browser/topic.js", "types": "./browser/topic.d.ts" },
|
|
23
|
+
"./browser/job": { "import": "./browser/job.js", "types": "./browser/job.d.ts" },
|
|
24
|
+
"./browser/scheduler": { "import": "./browser/scheduler.js", "types": "./browser/scheduler.d.ts" },
|
|
25
|
+
"./browser/ephemeral": { "import": "./browser/ephemeral.js", "types": "./browser/ephemeral.d.ts" },
|
|
26
|
+
"./browser/registry": { "import": "./browser/registry.js", "types": "./browser/registry.d.ts" },
|
|
27
|
+
"./browser/retry": { "import": "./browser/retry.js", "types": "./browser/retry.d.ts" },
|
|
28
|
+
"./browser/store": { "import": "./browser/store.js", "types": "./browser/store.d.ts" }
|
|
14
29
|
},
|
|
15
30
|
"peerDependencies": {
|
|
16
31
|
"zod": "^4"
|
|
17
32
|
},
|
|
18
|
-
"keywords": ["bun", "redis", "ratelimit", "mutex", "jobs", "queue", "distributed"],
|
|
33
|
+
"keywords": ["bun", "redis", "ratelimit", "mutex", "jobs", "queue", "distributed", "browser", "local-first"],
|
|
19
34
|
"author": "Valentin Kolb",
|
|
20
35
|
"license": "MIT",
|
|
21
36
|
"repository": {
|
|
@@ -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,10 @@
|
|
|
1
|
+
export { createMemoryStore, MemoryStore, type Store } from "./store";
|
|
2
|
+
export { retry, isRetryableTransportError, DEFAULT_RETRY_OPTIONS, type RetryOptions } from "./retry";
|
|
3
|
+
export { ratelimit, RateLimitError, type RateLimiter, type RateLimitResult, type RateLimitConfig } from "./ratelimit";
|
|
4
|
+
export { mutex, LockError, type Mutex, type Lock, type MutexConfig } from "./mutex";
|
|
5
|
+
export { topic, type Topic, type TopicConfig, type TopicPubConfig, type TopicRecvConfig, type TopicDelivery, type TopicLiveConfig, type TopicLiveEvent, type TopicReader, } from "./topic";
|
|
6
|
+
export { queue, type Queue, type QueueConfig, type QueueSendConfig, type QueueRecvConfig, type QueueReceived, type QueueReader, } from "./queue";
|
|
7
|
+
export { ephemeral, EphemeralCapacityError, EphemeralPayloadTooLargeError, type EphemeralStore, type EphemeralConfig, type EphemeralUpsertConfig, type EphemeralTouchConfig, type EphemeralRemoveConfig, type EphemeralEntry, type EphemeralSnapshot, type EphemeralRecvConfig, type EphemeralEvent, type EphemeralReader, } from "./ephemeral";
|
|
8
|
+
export { registry, RegistryCapacityError, RegistryPayloadTooLargeError, type Registry, type RegistryConfig, type RegistryUpsertConfig, type RegistryTouchConfig, type RegistryRemoveConfig, type RegistryGetConfig, type RegistryListConfig, type RegistryCasConfig, type RegistryEntry, type RegistrySnapshot, type RegistryRecvConfig, type RegistryEvent, type RegistryReader, } from "./registry";
|
|
9
|
+
export { job, type JobId, type JobStatus, type JobTerminal, type SubmitOptions, type JoinOptions, type CancelOptions, type JobEvent, type JobEvents, type JobContext, type JobHandle, type JobDefinition, } from "./job";
|
|
10
|
+
export { scheduler, type Scheduler, type SchedulerConfig, type SchedulerRegisterConfig, type SchedulerUnregisterConfig, type SchedulerTriggerNowConfig, type SchedulerGetConfig, type SchedulerInfo, type SchedulerMetric, type SchedulerMetricsSnapshot, } from "./scheduler";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class Emitter<T = void> {
|
|
2
|
+
private listeners;
|
|
3
|
+
/** Subscribe to events. Returns an unsubscribe function. */
|
|
4
|
+
on(fn: (value: T) => void): () => void;
|
|
5
|
+
/** Emit a value to all current listeners. */
|
|
6
|
+
emit(value: T): void;
|
|
7
|
+
/** Returns a promise that resolves on the next emit. */
|
|
8
|
+
once(): Promise<T>;
|
|
9
|
+
/** Returns a promise that resolves on the next emit, or rejects on abort. */
|
|
10
|
+
onceWithSignal(signal?: AbortSignal): Promise<T>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type EventLogEntry = {
|
|
2
|
+
id: string;
|
|
3
|
+
ts: number;
|
|
4
|
+
fields: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
export type EventLogConfig = {
|
|
7
|
+
maxLen?: number;
|
|
8
|
+
retentionMs?: number;
|
|
9
|
+
};
|
|
10
|
+
export declare class EventLog {
|
|
11
|
+
private entries;
|
|
12
|
+
private seq;
|
|
13
|
+
private emitter;
|
|
14
|
+
private maxLen;
|
|
15
|
+
private retentionMs;
|
|
16
|
+
constructor(config?: EventLogConfig);
|
|
17
|
+
/** Append an entry and return its cursor ID. */
|
|
18
|
+
append(fields: Record<string, unknown>): string;
|
|
19
|
+
/** Get entries after a cursor, optionally limited by count. */
|
|
20
|
+
range(after: string, count?: number): EventLogEntry[];
|
|
21
|
+
/** Get the latest cursor, or "0" if empty. */
|
|
22
|
+
latest(): string;
|
|
23
|
+
/** Get the earliest available cursor, or null if empty. */
|
|
24
|
+
earliest(): string | null;
|
|
25
|
+
/** Check whether a specific cursor still exists in the log. */
|
|
26
|
+
has(cursor: string): boolean;
|
|
27
|
+
/** Subscribe to new entries after a cursor. Yields entries as they arrive. */
|
|
28
|
+
subscribe(after: string, signal?: AbortSignal): AsyncIterable<EventLogEntry>;
|
|
29
|
+
/** Trim old entries based on maxLen and retentionMs. */
|
|
30
|
+
trim(): void;
|
|
31
|
+
/** Number of entries currently in the log. */
|
|
32
|
+
get size(): number;
|
|
33
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Generate a random UUID. */
|
|
2
|
+
export declare const randomId: () => string;
|
|
3
|
+
/** Generate a random hex string of the given byte length. */
|
|
4
|
+
export declare const randomHex: (bytes: number) => string;
|
|
5
|
+
export declare const nextSeq: () => number;
|
|
6
|
+
/** Reset the sequence counter (for tests). */
|
|
7
|
+
export declare const resetSeq: () => void;
|
|
8
|
+
/** Simple non-cryptographic hash for long strings (djb2). */
|
|
9
|
+
export declare const simpleHash: (str: string) => string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z, type ZodTypeAny } from "zod";
|
|
2
|
+
import { type Topic } from "./topic";
|
|
3
|
+
export type JobId = string;
|
|
4
|
+
export type JobStatus = "completed" | "failed" | "cancelled" | "timed_out";
|
|
5
|
+
export type JobTerminal<Result = unknown> = {
|
|
6
|
+
id: JobId;
|
|
7
|
+
status: JobStatus;
|
|
8
|
+
result?: Result;
|
|
9
|
+
error?: {
|
|
10
|
+
message: string;
|
|
11
|
+
code?: string;
|
|
12
|
+
};
|
|
13
|
+
finishedAt: number;
|
|
14
|
+
};
|
|
15
|
+
export type SubmitOptions = {
|
|
16
|
+
key?: string;
|
|
17
|
+
keyTtlMs?: number;
|
|
18
|
+
delayMs?: number;
|
|
19
|
+
at?: number;
|
|
20
|
+
maxAttempts?: number;
|
|
21
|
+
backoff?: {
|
|
22
|
+
kind: "fixed" | "exp";
|
|
23
|
+
baseMs: number;
|
|
24
|
+
maxMs?: number;
|
|
25
|
+
};
|
|
26
|
+
leaseMs?: number;
|
|
27
|
+
meta?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
export type JoinOptions = {
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
};
|
|
32
|
+
export type CancelOptions = {
|
|
33
|
+
reason?: string;
|
|
34
|
+
};
|
|
35
|
+
export type JobEvent = {
|
|
36
|
+
type: "submitted";
|
|
37
|
+
id: JobId;
|
|
38
|
+
ts: number;
|
|
39
|
+
} | {
|
|
40
|
+
type: "started";
|
|
41
|
+
id: JobId;
|
|
42
|
+
runId: string;
|
|
43
|
+
attempt: number;
|
|
44
|
+
ts: number;
|
|
45
|
+
} | {
|
|
46
|
+
type: "heartbeat";
|
|
47
|
+
id: JobId;
|
|
48
|
+
runId: string;
|
|
49
|
+
ts: number;
|
|
50
|
+
} | {
|
|
51
|
+
type: "retry";
|
|
52
|
+
id: JobId;
|
|
53
|
+
runId: string;
|
|
54
|
+
nextAt: number;
|
|
55
|
+
reason?: string;
|
|
56
|
+
ts: number;
|
|
57
|
+
} | {
|
|
58
|
+
type: "completed";
|
|
59
|
+
id: JobId;
|
|
60
|
+
ts: number;
|
|
61
|
+
} | {
|
|
62
|
+
type: "failed";
|
|
63
|
+
id: JobId;
|
|
64
|
+
reason?: string;
|
|
65
|
+
ts: number;
|
|
66
|
+
} | {
|
|
67
|
+
type: "cancelled";
|
|
68
|
+
id: JobId;
|
|
69
|
+
reason?: string;
|
|
70
|
+
ts: number;
|
|
71
|
+
};
|
|
72
|
+
export type JobEvents = Pick<Topic<JobEvent>, "reader" | "live">;
|
|
73
|
+
export type JobContext = {
|
|
74
|
+
step<T>(cfg: {
|
|
75
|
+
id: string;
|
|
76
|
+
run: () => Promise<T> | T;
|
|
77
|
+
}): Promise<T>;
|
|
78
|
+
heartbeat(cfg?: {
|
|
79
|
+
leaseMs?: number;
|
|
80
|
+
}): Promise<void>;
|
|
81
|
+
signal: AbortSignal;
|
|
82
|
+
};
|
|
83
|
+
export type JobHandle<Input, Result = unknown> = {
|
|
84
|
+
id: string;
|
|
85
|
+
submit(cfg: {
|
|
86
|
+
input: Input;
|
|
87
|
+
} & SubmitOptions): Promise<JobId>;
|
|
88
|
+
validateInput(input: unknown): void;
|
|
89
|
+
join(cfg: {
|
|
90
|
+
id: JobId;
|
|
91
|
+
} & JoinOptions): Promise<JobTerminal<Result>>;
|
|
92
|
+
cancel(cfg: {
|
|
93
|
+
id: JobId;
|
|
94
|
+
} & CancelOptions): Promise<void>;
|
|
95
|
+
events(id: JobId): JobEvents;
|
|
96
|
+
stop(): void;
|
|
97
|
+
};
|
|
98
|
+
export type JobDefinition<TSchema extends ZodTypeAny, Result = unknown> = {
|
|
99
|
+
id: string;
|
|
100
|
+
schema: TSchema;
|
|
101
|
+
defaults?: Omit<SubmitOptions, "key" | "delayMs" | "at" | "meta">;
|
|
102
|
+
process: (cfg: {
|
|
103
|
+
ctx: JobContext;
|
|
104
|
+
input: z.infer<TSchema>;
|
|
105
|
+
}) => Promise<Result> | Result;
|
|
106
|
+
};
|
|
107
|
+
export declare const job: <TSchema extends ZodTypeAny, Result = unknown>(definition: JobDefinition<TSchema, Result>) => JobHandle<z.infer<TSchema>, Result>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Store } from "./store";
|
|
2
|
+
export type Lock = {
|
|
3
|
+
resource: string;
|
|
4
|
+
value: string;
|
|
5
|
+
ttl: number;
|
|
6
|
+
expiration: number;
|
|
7
|
+
};
|
|
8
|
+
export type MutexConfig = {
|
|
9
|
+
id: string;
|
|
10
|
+
prefix?: string;
|
|
11
|
+
retryCount?: number;
|
|
12
|
+
retryDelay?: number;
|
|
13
|
+
defaultTtl?: number;
|
|
14
|
+
store?: Store;
|
|
15
|
+
};
|
|
16
|
+
export type Mutex = {
|
|
17
|
+
id: string;
|
|
18
|
+
acquire(resource: string, ttl?: number): Promise<Lock | null>;
|
|
19
|
+
release(lock: Lock): Promise<void>;
|
|
20
|
+
withLock<T>(resource: string, fn: (lock: Lock) => Promise<T> | T, ttl?: number): Promise<T | null>;
|
|
21
|
+
withLockOrThrow<T>(resource: string, fn: (lock: Lock) => Promise<T> | T, ttl?: number): Promise<T>;
|
|
22
|
+
extend(lock: Lock, ttl?: number): Promise<boolean>;
|
|
23
|
+
};
|
|
24
|
+
export declare class LockError extends Error {
|
|
25
|
+
readonly resource: string;
|
|
26
|
+
constructor(resource: string);
|
|
27
|
+
}
|
|
28
|
+
export declare const mutex: (config: MutexConfig) => Mutex;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
export type QueueConfig<TSchema extends z.ZodTypeAny> = {
|
|
3
|
+
id: string;
|
|
4
|
+
schema: TSchema;
|
|
5
|
+
tenantId?: string;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
ordering?: {
|
|
8
|
+
mode?: "best_effort" | "ordering_key_partitioned";
|
|
9
|
+
partitions?: number;
|
|
10
|
+
};
|
|
11
|
+
limits?: {
|
|
12
|
+
payloadBytes?: number;
|
|
13
|
+
maxMessageAgeMs?: number;
|
|
14
|
+
maxNackDelayMs?: number;
|
|
15
|
+
dlqRetentionMs?: number;
|
|
16
|
+
};
|
|
17
|
+
delivery?: {
|
|
18
|
+
defaultLeaseMs?: number;
|
|
19
|
+
maxDeliveries?: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type QueueSendConfig<T> = {
|
|
23
|
+
data: T;
|
|
24
|
+
delayMs?: number;
|
|
25
|
+
orderingKey?: string;
|
|
26
|
+
idempotencyKey?: string;
|
|
27
|
+
idempotencyTtlMs?: number;
|
|
28
|
+
meta?: Record<string, unknown>;
|
|
29
|
+
tenantId?: string;
|
|
30
|
+
};
|
|
31
|
+
export type QueueRecvConfig = {
|
|
32
|
+
wait?: boolean;
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
leaseMs?: number;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
consumerId?: string;
|
|
37
|
+
tenantId?: string;
|
|
38
|
+
};
|
|
39
|
+
export type QueueReceived<T> = {
|
|
40
|
+
data: T;
|
|
41
|
+
messageId: string;
|
|
42
|
+
deliveryId: string;
|
|
43
|
+
attempt: number;
|
|
44
|
+
leaseUntil: number;
|
|
45
|
+
orderingKey?: string;
|
|
46
|
+
meta?: Record<string, unknown>;
|
|
47
|
+
ack(): Promise<boolean>;
|
|
48
|
+
nack(cfg?: {
|
|
49
|
+
delayMs?: number;
|
|
50
|
+
reason?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
}): Promise<boolean>;
|
|
53
|
+
touch(cfg?: {
|
|
54
|
+
leaseMs?: number;
|
|
55
|
+
}): Promise<boolean>;
|
|
56
|
+
};
|
|
57
|
+
export type QueueReader<T> = {
|
|
58
|
+
recv(cfg?: QueueRecvConfig): Promise<QueueReceived<T> | null>;
|
|
59
|
+
stream(cfg?: QueueRecvConfig): AsyncIterable<QueueReceived<T>>;
|
|
60
|
+
};
|
|
61
|
+
export type Queue<T> = QueueReader<T> & {
|
|
62
|
+
send(cfg: QueueSendConfig<T>): Promise<{
|
|
63
|
+
messageId: string;
|
|
64
|
+
}>;
|
|
65
|
+
reader(): QueueReader<T>;
|
|
66
|
+
};
|
|
67
|
+
export declare const queue: <TSchema extends z.ZodTypeAny>(config: QueueConfig<TSchema>) => Queue<z.infer<TSchema>>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Store } from "./store";
|
|
2
|
+
export type RateLimitResult = {
|
|
3
|
+
limited: boolean;
|
|
4
|
+
remaining: number;
|
|
5
|
+
resetIn: number;
|
|
6
|
+
};
|
|
7
|
+
export type RateLimitConfig = {
|
|
8
|
+
id: string;
|
|
9
|
+
limit: number;
|
|
10
|
+
windowSecs?: number;
|
|
11
|
+
prefix?: string;
|
|
12
|
+
store?: Store;
|
|
13
|
+
};
|
|
14
|
+
export type RateLimiter = {
|
|
15
|
+
id: string;
|
|
16
|
+
check(identifier: string): Promise<RateLimitResult>;
|
|
17
|
+
checkOrThrow(identifier: string): Promise<RateLimitResult>;
|
|
18
|
+
};
|
|
19
|
+
export declare class RateLimitError extends Error {
|
|
20
|
+
readonly remaining: number;
|
|
21
|
+
readonly resetIn: number;
|
|
22
|
+
constructor(result: RateLimitResult);
|
|
23
|
+
}
|
|
24
|
+
export declare const ratelimit: (config: RateLimitConfig) => RateLimiter;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
export declare class RegistryCapacityError extends Error {
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class RegistryPayloadTooLargeError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
export type RegistryConfig<TSchema extends z.ZodTypeAny> = {
|
|
9
|
+
id: string;
|
|
10
|
+
schema: TSchema;
|
|
11
|
+
tenantId?: string;
|
|
12
|
+
prefix?: string;
|
|
13
|
+
limits?: {
|
|
14
|
+
maxEntries?: number;
|
|
15
|
+
maxPayloadBytes?: number;
|
|
16
|
+
eventRetentionMs?: number;
|
|
17
|
+
eventMaxLen?: number;
|
|
18
|
+
tombstoneRetentionMs?: number;
|
|
19
|
+
reconcileBatchSize?: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type RegistryUpsertConfig<T> = {
|
|
23
|
+
key: string;
|
|
24
|
+
value: T;
|
|
25
|
+
ttlMs?: number;
|
|
26
|
+
tenantId?: string;
|
|
27
|
+
};
|
|
28
|
+
export type RegistryTouchConfig = {
|
|
29
|
+
key: string;
|
|
30
|
+
ttlMs?: number;
|
|
31
|
+
tenantId?: string;
|
|
32
|
+
};
|
|
33
|
+
export type RegistryRemoveConfig = {
|
|
34
|
+
key: string;
|
|
35
|
+
reason?: string;
|
|
36
|
+
tenantId?: string;
|
|
37
|
+
};
|
|
38
|
+
export type RegistryGetConfig = {
|
|
39
|
+
key: string;
|
|
40
|
+
tenantId?: string;
|
|
41
|
+
includeExpired?: boolean;
|
|
42
|
+
};
|
|
43
|
+
export type RegistryListConfig = {
|
|
44
|
+
prefix?: string;
|
|
45
|
+
status?: "active" | "expired";
|
|
46
|
+
afterKey?: string;
|
|
47
|
+
limit?: number;
|
|
48
|
+
tenantId?: string;
|
|
49
|
+
};
|
|
50
|
+
export type RegistryCasConfig<T> = {
|
|
51
|
+
key: string;
|
|
52
|
+
version: string;
|
|
53
|
+
value: T;
|
|
54
|
+
tenantId?: string;
|
|
55
|
+
};
|
|
56
|
+
export type RegistryEntry<T> = {
|
|
57
|
+
key: string;
|
|
58
|
+
value: T;
|
|
59
|
+
version: string;
|
|
60
|
+
status: "active" | "expired";
|
|
61
|
+
createdAt: number;
|
|
62
|
+
updatedAt: number;
|
|
63
|
+
ttlMs: number | null;
|
|
64
|
+
expiresAt: number | null;
|
|
65
|
+
};
|
|
66
|
+
export type RegistrySnapshot<T> = {
|
|
67
|
+
entries: RegistryEntry<T>[];
|
|
68
|
+
cursor: string;
|
|
69
|
+
nextKey?: string;
|
|
70
|
+
};
|
|
71
|
+
export type RegistryRecvConfig = {
|
|
72
|
+
wait?: boolean;
|
|
73
|
+
timeoutMs?: number;
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
};
|
|
76
|
+
export type RegistryEvent<T> = {
|
|
77
|
+
type: "upsert";
|
|
78
|
+
cursor: string;
|
|
79
|
+
entry: RegistryEntry<T>;
|
|
80
|
+
} | {
|
|
81
|
+
type: "touch";
|
|
82
|
+
cursor: string;
|
|
83
|
+
key: string;
|
|
84
|
+
version: string;
|
|
85
|
+
updatedAt: number;
|
|
86
|
+
expiresAt: number;
|
|
87
|
+
} | {
|
|
88
|
+
type: "delete";
|
|
89
|
+
cursor: string;
|
|
90
|
+
key: string;
|
|
91
|
+
version: string;
|
|
92
|
+
removedAt: number;
|
|
93
|
+
reason?: string;
|
|
94
|
+
} | {
|
|
95
|
+
type: "expire";
|
|
96
|
+
cursor: string;
|
|
97
|
+
key: string;
|
|
98
|
+
version: string;
|
|
99
|
+
removedAt: number;
|
|
100
|
+
} | {
|
|
101
|
+
type: "overflow";
|
|
102
|
+
cursor: string;
|
|
103
|
+
after: string;
|
|
104
|
+
firstAvailable: string;
|
|
105
|
+
};
|
|
106
|
+
export type RegistryReader<T> = {
|
|
107
|
+
recv(cfg?: RegistryRecvConfig): Promise<RegistryEvent<T> | null>;
|
|
108
|
+
stream(cfg?: RegistryRecvConfig): AsyncIterable<RegistryEvent<T>>;
|
|
109
|
+
};
|
|
110
|
+
export type Registry<T> = {
|
|
111
|
+
upsert(cfg: RegistryUpsertConfig<T>): Promise<RegistryEntry<T>>;
|
|
112
|
+
touch(cfg: RegistryTouchConfig): Promise<{
|
|
113
|
+
ok: boolean;
|
|
114
|
+
version?: string;
|
|
115
|
+
expiresAt?: number;
|
|
116
|
+
}>;
|
|
117
|
+
remove(cfg: RegistryRemoveConfig): Promise<boolean>;
|
|
118
|
+
get(cfg: RegistryGetConfig): Promise<RegistryEntry<T> | null>;
|
|
119
|
+
list(cfg?: RegistryListConfig): Promise<RegistrySnapshot<T>>;
|
|
120
|
+
cas(cfg: RegistryCasConfig<T>): Promise<{
|
|
121
|
+
ok: boolean;
|
|
122
|
+
entry?: RegistryEntry<T>;
|
|
123
|
+
}>;
|
|
124
|
+
reader(cfg?: {
|
|
125
|
+
key?: string;
|
|
126
|
+
prefix?: string;
|
|
127
|
+
after?: string;
|
|
128
|
+
tenantId?: string;
|
|
129
|
+
}): RegistryReader<T>;
|
|
130
|
+
};
|
|
131
|
+
export declare const registry: <TSchema extends z.ZodTypeAny>(config: RegistryConfig<TSchema>) => Registry<z.infer<TSchema>>;
|
|
@@ -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>;
|