@mark1russell7/service 0.1.0 → 0.2.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.
@@ -0,0 +1,294 @@
1
+ import { B as Check, G as Gate, I as Recorder, J as Step, R as Clock, U as Dependency, Z as Operation, et as Poll, j as CheckFn, n as ServiceBuilder, q as ServiceDescription, s as DependencyHandle } from "./builder-BFxOuULH.js";
2
+ import { Extent, Quantity } from "@mark1russell7/quantity";
3
+ import { Machine } from "@mark1russell7/algebra";
4
+ //#region src/redis/connection.d.ts
5
+ /**
6
+ * A Redis connection is the part of an ioredis client that the wrapper uses. An ioredis 6 client has
7
+ * each part. A fake for tests has them too. Thus the package depends on no type of ioredis.
8
+ */
9
+ interface RedisConnection {
10
+ /** This text is the state of the client: wait, connecting, connect, ready, close, reconnecting or end. */
11
+ readonly status: string;
12
+ /** This socket is the socket of the current connection, when there is one. */
13
+ readonly stream?: RedisSocket | undefined;
14
+ connect(): Promise<void>;
15
+ /** This method sends one command, and gives the reply. */
16
+ call(command: string, ...args: Array<string | number>): Promise<unknown>;
17
+ quit(): Promise<unknown>;
18
+ /** This method closes the connection at once. With `true`, the client then connects again. */
19
+ disconnect(reconnect?: boolean): void;
20
+ on(event: string, listener: (...args: unknown[]) => void): unknown;
21
+ off(event: string, listener: (...args: unknown[]) => void): unknown;
22
+ }
23
+ /** A Redis socket is the part of the socket of a connection that the join of the prober reads. */
24
+ interface RedisSocket {
25
+ readonly localAddress?: string | undefined;
26
+ readonly localPort?: number | undefined;
27
+ readonly remoteAddress?: string | undefined;
28
+ readonly remotePort?: number | undefined;
29
+ /** This method closes the socket at once. A disconnect only half-closes it, and a silent peer does not answer that. */
30
+ destroy?(): void;
31
+ }
32
+ /**
33
+ * These are the options that the wrapper gives ioredis. Each one comes from the declaration. The
34
+ * wrapper sets each option that has an effect on a timer or on a send again, thus no default of
35
+ * ioredis decides one.
36
+ */
37
+ interface RedisConnectionOptions {
38
+ readonly host: string;
39
+ readonly port: number;
40
+ readonly db?: number | undefined;
41
+ readonly username?: string | undefined;
42
+ readonly password?: string | undefined;
43
+ /** The wrapper connects in its own step of the startup. */
44
+ readonly lazyConnect: true;
45
+ readonly connectTimeout: number;
46
+ readonly commandTimeout: number;
47
+ /** This time destroys a socket that gives no data while a command waits. Undefined is no bound. */
48
+ readonly socketTimeout: number | undefined;
49
+ /** This time is the idle time of TCP keepalive. False is no keepalive. */
50
+ readonly keepAlive: number | false;
51
+ /** A command while the client is not ready fails at once: it did not reach the server. */
52
+ readonly enableOfflineQueue: false;
53
+ /** The client does not send again a command that can have reached the server. */
54
+ readonly autoResendUnfulfilledCommands: false;
55
+ readonly maxRetriesPerRequest: null;
56
+ readonly enableReadyCheck: true;
57
+ readonly connectionName: string;
58
+ /** This function gives the delay before the next try to connect, or null to stop. */
59
+ readonly retryStrategy: (times: number) => number | null;
60
+ }
61
+ /** A connection factory makes the connection of the client from its options. */
62
+ type ConnectionFactory = (options: RedisConnectionOptions) => RedisConnection | Promise<RedisConnection>;
63
+ /**
64
+ * This factory makes an ioredis client. It loads ioredis only when a client connects, thus a
65
+ * service that does not use Redis does not need ioredis.
66
+ */
67
+ export declare const ioredis: ConnectionFactory;
68
+ //#endregion
69
+ //#region src/redis/meets.d.ts
70
+ /** A situation is what the connection did when the failure started: a command in flight, or idle. */
71
+ type Situation = "in flight" | "idle";
72
+ /** A detector is a mechanism that finds a failure, and the most time that it takes. */
73
+ interface Detector {
74
+ /** This name identifies the mechanism: a knob of the declaration, or the kernel. */
75
+ readonly by: string;
76
+ /** This time is the most that the detector takes, after the failure starts. "infinite" tells that it does not find the failure. */
77
+ readonly within: Extent;
78
+ readonly situation: Situation;
79
+ /** This text tells what the detector does, for example that it fails a command without a change of state. */
80
+ readonly effect: string;
81
+ }
82
+ /**
83
+ * A meeting is a manifestation that the client can meet, and its detectors (E9). The names of the
84
+ * manifestations are the names of the vocabulary of the harness and the corpus. `app-stall` is a
85
+ * server whose process is alive and does not answer, a proposal of the vocabulary.
86
+ */
87
+ interface Meeting {
88
+ readonly manifestation: string;
89
+ /** This text tells the failure in words. */
90
+ readonly what: string;
91
+ readonly detectors: readonly Detector[];
92
+ }
93
+ /**
94
+ * This function gives the manifestations that a Redis client can meet, and the detectors of each, from
95
+ * its knobs. A reset or a FIN arrives on the next segment. A black hole and a host that is gone give
96
+ * no answer at all. A frozen process and a stalled server keep a kernel that acknowledges each
97
+ * segment. Thus TCP keepalive does not fire on them, and only the client's own timers find them.
98
+ */
99
+ export declare const meetings: (name: string, knobs: RedisKnobs, heartbeat: Extent) => Meeting[];
100
+ /** This function gives the first detector of a manifestation in a situation, or null when no detector has an end (C3). */
101
+ export declare const firstDetector: (meeting: Meeting, situation: Situation) => Detector | null;
102
+ /** This function gives each manifestation and situation that no detector of the client finds: the standing rule, as a query (C3, E9). */
103
+ export declare const unwatched: (meets: readonly Meeting[]) => Array<{
104
+ readonly manifestation: string;
105
+ readonly situation: Situation;
106
+ }>;
107
+ //#endregion
108
+ //#region src/redis/client.d.ts
109
+ /**
110
+ * These are the knobs of the client, each a time, by the names of the ordering law where the law
111
+ * has them. The runtime has no defaults, thus each knob is necessary, and a bound that nobody gives
112
+ * is "infinite", not zero.
113
+ */
114
+ interface RedisKnobs {
115
+ /** This time bounds a try to connect, until the TCP connection exists (`connectTimeout`). */
116
+ readonly connect_bound: Quantity;
117
+ /** This time bounds each command, the handshake commands too (`commandTimeout`). */
118
+ readonly request_timeout: Quantity;
119
+ /** This time destroys a socket that gives no data while a command waits (`socketTimeout`). */
120
+ readonly driver_writeoff_time: Extent;
121
+ /** This time is the idle time of TCP keepalive. Node then sends 10 probes 1 s apart. */
122
+ readonly keepalive_idle: Extent;
123
+ /** This time is the interval of the in-band watchdog: a PING on the connection of the application. */
124
+ readonly heartbeat_interval: Extent;
125
+ /** This time bounds each PING of the watchdog. */
126
+ readonly heartbeat_timeout: Quantity;
127
+ /** This time is how long the client tries to connect again before it stops. The watchdog then starts it again. */
128
+ readonly client_retry_window: Extent;
129
+ /** This time is the first delay before a new try to connect. The delay doubles after each try. */
130
+ readonly retry_base: Quantity;
131
+ /** This time is the most that one delay can be, before its jitter. */
132
+ readonly retry_cap: Quantity;
133
+ /** This time bounds the close of the client at the shutdown: a QUIT, then a disconnect. */
134
+ readonly close_bound: Quantity;
135
+ /** This time is the age at which the client replaces its connection. The client supports only "infinite". */
136
+ readonly max_connection_age: Extent;
137
+ /**
138
+ * This time is when the kernel gives up a segment that it retransmits. Node cannot set it, thus
139
+ * the declaration states it for the ordering law (O17), from the platform.
140
+ */
141
+ readonly retrans_giveup_time: Extent;
142
+ }
143
+ /** A Redis client spec is the declaration of one Redis client: where it connects, and its knobs. */
144
+ interface RedisClientSpec {
145
+ readonly host: string;
146
+ readonly port: number;
147
+ readonly db?: number | undefined;
148
+ readonly username?: string | undefined;
149
+ readonly password?: string | undefined;
150
+ /** This identifier is the component in the corpus (`systems.component.redis`), or null. */
151
+ readonly system: string | null;
152
+ readonly knobs: RedisKnobs;
153
+ /** This count is the number of PINGs in sequence without an answer that make the watchdog connect again. */
154
+ readonly heartbeatMisses: number;
155
+ /** These operations add to the table of the client, or change it. */
156
+ readonly operations?: Readonly<Record<string, Operation>> | undefined;
157
+ }
158
+ /** A dial function opens a new TCP connection, sends one PING, and reads the reply. The `dial` check uses it. */
159
+ type DialFn = (host: string, port: number, signal: AbortSignal) => Promise<void>;
160
+ /** These options make a client. Without them, it uses ioredis, the real clock and `Math.random`. */
161
+ interface RedisClientOptions {
162
+ readonly create?: ConnectionFactory | undefined;
163
+ readonly clock?: Clock | undefined;
164
+ readonly random?: (() => number) | undefined;
165
+ /** This function receives each transition of the client, for the prober (D3). */
166
+ readonly record?: Recorder | undefined;
167
+ readonly dial?: DialFn | undefined;
168
+ /**
169
+ * This function waits one turn of the event loop. The watchdog waits it before it counts a miss,
170
+ * thus a reply that is in the socket is read first. Without it, the client uses `setImmediate`.
171
+ */
172
+ readonly turn?: (() => Promise<void>) | undefined;
173
+ }
174
+ /**
175
+ * A join is one generation of the connection, at its `ready` (D1, D2). The prober joins its kernel
176
+ * records to the server's view of the connection through it. A join holds the server's id of the
177
+ * connection, and the local address and port of the socket.
178
+ */
179
+ interface Join {
180
+ readonly generation: number;
181
+ /** This time is when the connection became ready, in milliseconds on the monotonic clock. */
182
+ readonly at: number;
183
+ /** This number is the reply of CLIENT ID, or null when the server gave none. */
184
+ readonly clientId: number | null;
185
+ readonly localAddress: string | null;
186
+ readonly localPort: number | null;
187
+ readonly remoteAddress: string | null;
188
+ readonly remotePort: number | null;
189
+ }
190
+ /**
191
+ * A sent result is the outcome of a command, in three values (C8):
192
+ * - `ok`: the server answered.
193
+ * - `error`: the command did not apply. The server answered with an error (`reply`), or the command
194
+ * did not leave the client.
195
+ * - `indeterminate`: the command can have reached the server, and nothing tells if it applied.
196
+ */
197
+ type Sent<T> = {
198
+ readonly outcome: "ok";
199
+ readonly value: T;
200
+ readonly attempts: number;
201
+ } | {
202
+ readonly outcome: "error";
203
+ readonly error: string;
204
+ readonly reply: boolean;
205
+ readonly attempts: number;
206
+ } | {
207
+ readonly outcome: "indeterminate";
208
+ readonly error: string;
209
+ readonly attempts: number;
210
+ };
211
+ /** Send options tell how the client sends one command. */
212
+ interface SendOptions {
213
+ /** This signal abandons the command. A command that left the client is then indeterminate. */
214
+ readonly signal?: AbortSignal | undefined;
215
+ /** False stops the client: it does not send the command again. A check that must fail at once uses it. */
216
+ readonly reissue?: boolean | undefined;
217
+ }
218
+ /** The steps of a connection that the client gives a check for (B14). */
219
+ type ClientStep = Extract<Step, "dial" | "round-trip" | "local-state">;
220
+ /** Check options tell which checks the client gives the health runtime, and which policy each one controls. */
221
+ interface CheckOptions {
222
+ readonly poll: Poll;
223
+ /** For each step, the policy that its check controls, or null for a check that only reports. A step that is absent has no check. */
224
+ readonly steps: Partial<Record<ClientStep, Gate | null>>;
225
+ }
226
+ /** A Redis client is a dependency of a service: its declaration, its handle, its checks and its commands. */
227
+ interface RedisClient {
228
+ readonly name: string;
229
+ /** This declaration goes to `svc.dependency(name, declaration, handle)`. */
230
+ readonly declaration: Omit<Dependency, "name">;
231
+ readonly handle: DependencyHandle;
232
+ /** These are the manifestations that the client can meet, and the detector of each (E9). */
233
+ readonly meets: readonly Meeting[];
234
+ /** This text is the state of the connection, as ioredis names it, or "none" before the connect. */
235
+ readonly status: string;
236
+ /** These are the generations of the connection, the latest last. */
237
+ readonly joins: readonly Join[];
238
+ /**
239
+ * This method sends a command, and gives its outcome in three values. The client sends again
240
+ * only an idempotent operation, one time at most, after an outcome that is not certain. The
241
+ * second try stays in the bound of the request. The client does not send again an operation that
242
+ * the table does not name.
243
+ */
244
+ send<T = unknown>(command: string, args?: ReadonlyArray<string | number>, options?: SendOptions): Promise<Sent<T>>;
245
+ /** This method gives the checks of the client and their functions, for `svc.check(check, run)`. */
246
+ checks(options: CheckOptions): ReadonlyArray<readonly [Check, CheckFn]>;
247
+ /** This method declares the client in a service: its dependency, its handle and its checks. */
248
+ attach(svc: ServiceBuilder, options: CheckOptions): ServiceBuilder;
249
+ /** This method gives the problems of the client in a description: a close that does not fit in its phase, for example. */
250
+ problems(description: ServiceDescription): string[];
251
+ }
252
+ /**
253
+ * This function gives the time that the watchdog takes to find a silent server, at the most. A PING
254
+ * starts each interval, from the start of the one before. Each one fails at its timeout, and the
255
+ * last of `misses` failures in sequence closes the connection: `misses` intervals plus a timeout.
256
+ */
257
+ export declare const heartbeatDetection: (interval: Extent, timeout: Quantity, misses: number) => Extent;
258
+ /** This function gives the problems of a spec: the knobs that the client cannot use. */
259
+ export declare const specProblems: (name: string, spec: RedisClientSpec) => string[];
260
+ /** This dial function opens a TCP connection, sends an inline PING, and waits for +PONG. */
261
+ export declare const tcpDial: DialFn;
262
+ /**
263
+ * This function makes a Redis client. It is a dependency of a service, with ioredis under it:
264
+ * - It sets each timer of ioredis from the declaration, and it does not let ioredis send a command
265
+ * again (R5 §8).
266
+ * - It tells the outcome of a command in three values, and it settles each command in flight as
267
+ * indeterminate when its connection closes (C8, C10).
268
+ * - It records the server's id of each connection at `ready` (D1).
269
+ * - A watchdog sends a PING in the band of the connection, and it connects again after the misses
270
+ * of the declaration. It also starts a client that stopped (principle 19).
271
+ */
272
+ export declare const redisClient: (name: string, spec: RedisClientSpec, options?: RedisClientOptions) => RedisClient;
273
+ //#endregion
274
+ //#region src/redis/machine.d.ts
275
+ /**
276
+ * The Redis client as a `mark1russell7.machine/1` document. Its states are the states of ioredis.
277
+ * The wrapper adds its own events: the join at each `ready`, and the misses of its watchdog. It also
278
+ * adds a command that it sends again, and the bound of its close. A test records the transitions of the
279
+ * client and replays them against this document.
280
+ *
281
+ * The state `ready` hides states that the client cannot see (D4). In a black hole, ioredis stays
282
+ * `ready` while the kernel retransmits, until `driver_writeoff_time` or the watchdog ends that
283
+ * hidden interval. A timer of this document bounds each hidden interval.
284
+ */
285
+ export declare const REDIS_CLIENT_MACHINE: Machine;
286
+ //#endregion
287
+ //#region src/redis/operations.d.ts
288
+ /** These are the operations of the client: the idempotency of each command that the table names. */
289
+ export declare const REDIS_OPERATIONS: Readonly<Record<string, Operation>>;
290
+ /** This function gives the name of a command as the table writes it: upper case, and CLIENT ID with its subcommand. */
291
+ export declare const commandName: (command: string, args: readonly unknown[]) => string;
292
+ //#endregion
293
+ export type { CheckOptions, ClientStep, ConnectionFactory, Detector, DialFn, Join, Meeting, RedisClient, RedisClientOptions, RedisClientSpec, RedisConnection, RedisConnectionOptions, RedisKnobs, RedisSocket, Sent, Situation };
294
+ //# sourceMappingURL=redis.d.ts.map