@mark1russell7/service 0.5.11 → 0.5.13
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 +110 -1
- package/build/{builder-Dn0cby6Q.d.ts → builder-DawJhq4p.d.ts} +2 -2
- package/build/core-BPLRDcUJ.d.ts +226 -0
- package/build/dial-DA8vJfh2.d.ts +16 -0
- package/build/grpc.d.ts +307 -0
- package/build/grpc.js +859 -0
- package/build/grpc.js.map +1 -0
- package/build/http-client.d.ts +248 -0
- package/build/http-client.js +612 -0
- package/build/http-client.js.map +1 -0
- package/build/http.d.ts +1 -1
- package/build/index.d.ts +8 -149
- package/build/index.js +155 -11
- package/build/index.js.map +1 -1
- package/build/{lifecycle-CGEegMI0.d.ts → lifecycle-fgYfxrHK.d.ts} +17 -2
- package/build/machine-BhTRu3u9.js +552 -0
- package/build/machine-BhTRu3u9.js.map +1 -0
- package/build/mongodb.d.ts +307 -0
- package/build/mongodb.js +688 -0
- package/build/mongodb.js.map +1 -0
- package/build/pg.d.ts +230 -0
- package/build/pg.js +531 -0
- package/build/pg.js.map +1 -0
- package/build/queue-snRHK3Jm.d.ts +150 -0
- package/build/rabbitmq.d.ts +294 -0
- package/build/rabbitmq.js +748 -0
- package/build/rabbitmq.js.map +1 -0
- package/build/redis.d.ts +24 -143
- package/build/redis.js +76 -315
- package/build/redis.js.map +1 -1
- package/build/testing.d.ts +2 -2
- package/build/websocket.d.ts +275 -0
- package/build/websocket.js +780 -0
- package/build/websocket.js.map +1 -0
- package/package.json +46 -1
package/README.md
CHANGED
|
@@ -15,6 +15,9 @@ This release has the declaration, its export, the law, the health runtime, the l
|
|
|
15
15
|
- **The law.** `evaluate` and `verdictOf` decide one rule of the timer-ordering law on one binding. A rule is data: linear inequalities over `symbol@hop`, with a margin. A verdict is `holds`, `violated`, `unbounded` (it holds only because a timer is absent) or `depends` (nothing is bound).
|
|
16
16
|
- **The linter.** `lint(description, scenario, rules)` applies the service rules (`SERVICE_RULES`) to a declaration. The declaration binds the symbols of the service: the polls of its checks, the windows of its policies, its drain. The scenario binds the symbols of the platform and of each dependency. A `depends` finding names the symbols that have no bound.
|
|
17
17
|
- **The scopes of a rule.** A rule about a client applies one time for each dependency. A rule about a consumer, for example O20, applies one time for each consumer and each worker.
|
|
18
|
+
- **The loops of a progress check.** A check of the step `progress` can name the loops that mark it, with its stall time: `marks: { loops, stall }`. Each loop is a check or a worker of the service.
|
|
19
|
+
- **The stall time.** `lint()` compares the stall time with the longest gap between two marks of the slowest loop (`PROGRESS_RULE`). A loop marks at the end of each turn, also a turn that fails. The gap of a check is `interval × (1 + jitter) + timeout`. The gap of a worker is its sleep or its longest backoff, the longer one, plus `unit_processing_time`. At 300 s ± 20 % and a timeout of 10 s, a poll gives 370 s.
|
|
20
|
+
- **A name that is not a loop.** A name that is not a check or a worker gets a `depends` finding. Without `marks`, a check gets no finding of this rule.
|
|
18
21
|
- **Keyed limiters.** `svc.limiter(name, spec)` declares a rate limit for each key, as data. `keyedLimiter` makes its runtime on a store ([The keyed limiter](#the-keyed-limiter)).
|
|
19
22
|
- **The hold.** The cap of the `fail readiness` phase binds `readiness_hold@process`, and a service without that phase holds for zero. The hold keeps the listener open after the signal, as a preStop sleep does. Thus O04 can compare the sum of the two with the propagation of the endpoints.
|
|
20
23
|
|
|
@@ -510,13 +513,117 @@ const life = await app.run(); // the process exits after SIGTERM, with 0 or 1
|
|
|
510
513
|
- **Redis Cluster.** The names of the keys put the limiter in braces, a hash tag. Thus the state and the index of a limiter are in one slot. The prefix has no braces.
|
|
511
514
|
- **The test clock.** With `{ clock }`, the script uses the time of that clock in place of `TIME`. The tests use it to compare each decision of Redis with `decide`.
|
|
512
515
|
|
|
516
|
+
## The PostgreSQL client
|
|
517
|
+
|
|
518
|
+
`@mark1russell7/service/pg` is the PostgreSQL client of the service, over a pool of node-postgres 8. This entry is a prototype of S6. pg is an optional peer dependency. The client loads it only when it connects.
|
|
519
|
+
|
|
520
|
+
- **The declaration.** `pgClient(name, spec)` declares `connect_bound`, `request_timeout`, `statement_timeout`, `pool_idle_expiry`, `keepalive_idle`, `max_connection_age`, `close_bound` and `retrans_giveup_time`. Each knob is necessary, and a bound that nobody gives is "infinite". `specProblems` refuses a knob that pg cannot use, for example a zero, which pg reads as no timer.
|
|
521
|
+
- **The starting knobs.** `PG_STARTING_KNOBS` and `PG_STARTING_POLL` are a declaration from the cells of S1. Each value has its measured reason in its doc comment.
|
|
522
|
+
- **Three outcomes.** `query(operation, text, values)` gives `ok`, `error` or `indeterminate`. An ERROR of the server is certain. A FATAL, a reset or a query timeout is not certain. The client then gives the connection back with an error, thus the pool ends the orphaned query.
|
|
523
|
+
- **Idempotency for each operation.** SQL text does not tell if a statement changes a state. Thus the client sends again only an idempotent operation that its table names, one time at most, in the bound of the request.
|
|
524
|
+
- **The join.** At each connect, the client records the process id of the backend and the socket (`joins`).
|
|
525
|
+
- **No unheard error.** The client listens to the errors of the pool and of each client that it holds. In Node, an error with no listener ends the process.
|
|
526
|
+
- **The checks.** `checks({ poll, steps })` gives `local-state`, `checkout`, `round-trip` (a `SELECT 1` through the pool) and `dial` (an SSLRequest on a new connection).
|
|
527
|
+
- **The close.** At the shutdown, the client ends the pool, and it destroys each socket that stays at its `close_bound`.
|
|
528
|
+
- **The manifestations and the machine.** `meets` lists the manifestations and their detectors. `PG_CLIENT_MACHINE` is the client as a `mark1russell7.machine/1` document, and the tests replay the client against it.
|
|
529
|
+
|
|
530
|
+
## The MongoDB client
|
|
531
|
+
|
|
532
|
+
`@mark1russell7/service/mongodb` is the MongoDB client of the service, over a `MongoClient` of the driver 7. This entry is a part of S6. mongodb is an optional peer dependency. The client loads it only when it connects.
|
|
533
|
+
|
|
534
|
+
- **The declaration.** `mongoClient(name, spec)` declares `connect_bound`, `request_timeout`, `statement_timeout`, `heartbeat_interval`, `client_retry_window`, `checkout_timeout`, `pool_idle_expiry`, `keepalive_idle`, `max_connection_age`, `close_bound` and `retrans_giveup_time`. The client derives `driver_writeoff_time` from the connect bound and the heartbeat.
|
|
535
|
+
- **The problems of a spec.** `specProblems` refuses a knob that the driver cannot use. Examples are a zero, which the driver reads as no timer, a heartbeat below 500 ms, and a finite connection age.
|
|
536
|
+
- **The starting knobs.** `MONGO_STARTING_KNOBS` and `MONGO_STARTING_POLL` are a declaration from the cells of S1. Each value has its measured reason in its doc comment.
|
|
537
|
+
- **The settings of the driver.** The client turns off the retries of the driver, thus the core decides each second send. The client also writes `maxTimeMS` in each command, because `db.command` does not add it.
|
|
538
|
+
- **Three outcomes.** `command(operation, document)` gives `ok`, `error` or `indeterminate`. A server error is certain, except an interruption and a write concern error. A failed selection or a closed pool did not send the command.
|
|
539
|
+
- **Idempotency for each operation.** A command document does not tell if it changes a state. Thus the client sends again only an idempotent operation that its table names. The second send waits for a writable server in `client_retry_window`.
|
|
540
|
+
- **The join.** At each new connection, the client records the connection id of the server (`joins`). The driver gives no local socket, thus the local end is null.
|
|
541
|
+
- **No unheard error.** The client listens to the error event of the driver. In Node, an error with no listener ends the process.
|
|
542
|
+
- **The checks.** `checks({ poll, steps })` gives `local-state` (the view of the topology), `round-trip` (a `ping` on the connection of the application) and `dial` (a hello on a new connection).
|
|
543
|
+
- **The close.** At the shutdown, the client closes the driver, and it records `close.bound` at its `close_bound`. The driver gives no socket to destroy, thus its close goes on in the background.
|
|
544
|
+
- **The manifestations and the machine.** `meets` lists the manifestations and their detectors. `MONGO_CLIENT_MACHINE` is the client as a `mark1russell7.machine/1` document, and the tests replay the client against it.
|
|
545
|
+
|
|
546
|
+
## The RabbitMQ client
|
|
547
|
+
|
|
548
|
+
`@mark1russell7/service/rabbitmq` is the RabbitMQ client of the service, over amqplib 2. This entry is a part of S6. The peer dependency amqplib is optional. The client loads it only when it connects.
|
|
549
|
+
|
|
550
|
+
- **The declaration.** `rabbitClient(name, spec)` declares `connect_bound`, `request_timeout`, `heartbeat_interval`, `retry_base`, `retry_cap`, `keepalive_idle`, `max_connection_age`, `close_bound` and `retrans_giveup_time`. The client derives `heartbeat_detection_time` (3 heartbeats) and `idle_refresh_interval` (a half heartbeat).
|
|
551
|
+
- **The problems of a spec.** `specProblems` refuses a knob that amqplib cannot use. Examples are a zero, which amqplib reads as no timer, a heartbeat that is not a whole number of seconds, and a finite connection age.
|
|
552
|
+
- **The starting knobs.** `RABBIT_STARTING_KNOBS` and `RABBIT_STARTING_POLL` are a declaration from the cells of R3. Each value has its measured reason in its doc comment.
|
|
553
|
+
- **A call.** A call is a publish on a confirm channel until its confirm, or an RPC until its reply, for example `basic.get`. The client ends each call at `request_timeout`, because amqplib has no timer for them.
|
|
554
|
+
- **Three outcomes.** `publish(operation, message)` and `get(operation, queue)` give `ok`, `error` or `indeterminate`. An ack is `ok`. A reply code of the broker is certain. A nack, a timeout and the end of the channel before the confirm are not certain. A call while the client is not ready, or while a resource alarm blocks it, did not leave the client.
|
|
555
|
+
- **Idempotency for each operation.** A message does not tell if its consumers remove duplicates. Thus the client sends again only an idempotent operation that its table names. The second send waits for a ready client, in the bound of the call.
|
|
556
|
+
- **One generation.** The client has one connection and one confirm channel. The end of either one ends the generation. The client then connects again by itself, to the next node, after a backoff from `retry_base` to `retry_cap`. The built-in recovery of amqplib stays off.
|
|
557
|
+
- **The join.** At each generation, the client records the node and the name of the connection, `local:port -> remote:port` (`joins`).
|
|
558
|
+
- **The jobs of a queue drain.** `jobs(operation, queue)` gives the source and the settle of `queueDrain`. The count of deliveries comes from `x-delivery-count`. A message that arrives after its call ended goes back to its queue at once.
|
|
559
|
+
- **No unheard error.** The client listens to the error events of the connection and of the channel. In Node, an error with no listener ends the process.
|
|
560
|
+
- **The checks.** `checks({ poll, steps })` gives `local-state`, `round-trip` (a passive declare of `amq.direct` on the channel of the application) and `dial` (the protocol header on a new connection, with no login).
|
|
561
|
+
- **The close.** At the shutdown, the client sends `connection.close`, and it destroys the socket at its `close_bound`.
|
|
562
|
+
- **The manifestations and the machine.** `meets` lists the manifestations and their detectors. `RABBIT_CLIENT_MACHINE` is the client as a `mark1russell7.machine/1` document, and the tests replay the client against it.
|
|
563
|
+
|
|
564
|
+
## The HTTP client
|
|
565
|
+
|
|
566
|
+
`@mark1russell7/service/http-client` is the HTTP client of the service, over an `http.Agent` of Node with keep-alive. This entry is a part of S6. The entry `./http` is the server edge of a service, thus the client has its own name. `node:http` is a built-in, thus the entry has no peer dependency. The client loads `node:http` only when it connects.
|
|
567
|
+
|
|
568
|
+
- **The declaration.** `httpClient(name, spec)` declares `connect_bound`, `request_timeout`, `pool_idle_expiry`, `hint_buffer`, `keepalive_idle`, `max_connection_age`, `close_bound` and `retrans_giveup_time`. Keepalive is also the `idle_refresh_interval`, because the agent sends nothing on a free socket.
|
|
569
|
+
- **The idle close of the server.** The knob `server_idle_close` is not in the declaration, because the scenario binds the close of the server. `problems` compares it with `pool_idle_expiry` (O01).
|
|
570
|
+
- **The problems of a spec.** `specProblems` refuses a knob that the agent cannot use. An example is a free socket timer of 0, which the agent reads as no timer. Others are a keepalive that is not a whole number of seconds, and a finite connection age.
|
|
571
|
+
- **The options of the agent.** `agentOptions(spec)` gives `timeout`, `agentKeepAliveTimeoutBuffer`, `keepAliveMsecs`, `maxSockets` and `maxFreeSockets` from the declaration, and `scheduling: "lifo"`.
|
|
572
|
+
- **The starting knobs.** `HTTP_CLIENT_STARTING_KNOBS` and `HTTP_CLIENT_STARTING_POLL` are a declaration from the HTTP cells. Each value has its measured reason in its doc comment.
|
|
573
|
+
- **A call.** A call is one request until the end of the body of its response. The client ends each call at `request_timeout`, and each connect of a new socket at `connect_bound`, because the agent has no timer for them.
|
|
574
|
+
- **Three outcomes.** `request(call)` gives `ok`, `error` or `indeterminate`. Each whole response is `ok`, with its status: RFC 9110 does not say that a 5xx did not apply. A request that did not get a connected socket did not leave the client. A failure after that is not certain.
|
|
575
|
+
- **Idempotency for each method.** The table names each method by RFC 9110. GET, HEAD, OPTIONS, TRACE, PUT and DELETE are idempotent, and POST and PATCH are not. A spec can name a keyed operation, for example a POST with an `Idempotency-Key`.
|
|
576
|
+
- **The keep-alive race.** A server can close a free socket while a request goes on it. The client then sends again only an idempotent operation, one time, on a new socket. It records `reuse.failed`.
|
|
577
|
+
- **The join.** At each new socket, the client records the ends of the socket (`joins`). HTTP gives no id of a connection.
|
|
578
|
+
- **The checks.** `checks({ poll, steps })` gives `local-state`, `round-trip` (the check request through the pool, which fails at a status of 500 or more) and `dial` (`OPTIONS *` on a new connection). The check request is `OPTIONS *`, or the `check` of the spec.
|
|
579
|
+
- **The close.** At the shutdown, the client waits for its calls in flight. At its `close_bound`, it destroys each socket of the agent, and each call that stays is indeterminate.
|
|
580
|
+
- **The manifestations and the machine.** `meets` lists the manifestations and their detectors. `HTTP_CLIENT_MACHINE` is the client as a `mark1russell7.machine/1` document, and the tests replay the client against it.
|
|
581
|
+
|
|
582
|
+
## The WebSocket client
|
|
583
|
+
|
|
584
|
+
`@mark1russell7/service/websocket` is the WebSocket client of the service, over ws 8. This entry is a part of S6. The peer dependency ws is optional. The client loads it only when it connects.
|
|
585
|
+
|
|
586
|
+
- **The declaration.** `wsClient(name, spec)` declares `connect_bound`, `request_timeout`, `heartbeat_interval`, `heartbeat_timeout`, `retry_base`, `retry_cap`, `keepalive_idle`, `max_connection_age`, `close_bound` and `retrans_giveup_time`. The client derives `heartbeat_detection_time` and `idle_refresh_interval` (the heartbeat).
|
|
587
|
+
- **The problems of a spec.** `specProblems` refuses a knob that ws cannot use. Examples are a zero heartbeat, a keepalive that is not a whole number of seconds, and a finite connection age. It also refuses a password: give the credentials in `headers`.
|
|
588
|
+
- **The starting knobs.** `WS_CLIENT_STARTING_KNOBS`, `WS_CLIENT_STARTING_MISSES` and `WS_CLIENT_STARTING_POLL` are a declaration from the WS cells. Each value has its measured reason in its doc comment.
|
|
589
|
+
- **A call.** A call is a request message until its reply, or a ping frame until its pong. WebSocket has no reply of its own, thus `replies` of the spec reads the id of a reply in a message. The client ends each call at `request_timeout`.
|
|
590
|
+
- **Three outcomes.** `request(operation, { id, data })` and `ping()` give `ok`, `error` or `indeterminate`. A reply that carries an error is certain. A failed write, a timeout and the end of the generation are not certain. A call while the client is not ready did not leave the client.
|
|
591
|
+
- **Messages with no reply.** `send(data)` writes a message, and it is not a call. `onMessage(listener)` gets each message that answers no request.
|
|
592
|
+
- **Idempotency for each operation.** A message does not tell its idempotency. Thus the client sends again only an idempotent operation that its table names. `WS_OPERATIONS` names `ping`. The second send waits for a ready client.
|
|
593
|
+
- **The heartbeat.** The client sends a ping frame at each `heartbeat_interval`, one at a time. After `heartbeatMisses` pings with no pong in `heartbeat_timeout`, `terminate()` destroys the socket.
|
|
594
|
+
- **One generation.** The generation is one connection after its upgrade. At its end, the client connects again by itself, after a backoff from `retry_base` to `retry_cap`.
|
|
595
|
+
- **The join.** At each generation, the client records the URL, the subprotocol and the ends of the socket (`joins`). WebSocket gives no id of a connection.
|
|
596
|
+
- **The checks.** `checks({ poll, steps })` gives `local-state`, `round-trip` (a ping on the connection of the application) and `dial` (the upgrade GET on a new connection, which must get 101).
|
|
597
|
+
- **The close.** At the shutdown, the client waits for its calls in flight, then it sends a close frame. At its `close_bound`, `terminate()` destroys the socket, and each call that stays is indeterminate.
|
|
598
|
+
- **The manifestations and the machine.** `meets` lists the manifestations and their detectors. `WS_CLIENT_MACHINE` is the client as a `mark1russell7.machine/1` document, and the tests replay the client against it.
|
|
599
|
+
|
|
600
|
+
## The gRPC client
|
|
601
|
+
|
|
602
|
+
`@mark1russell7/service/grpc` is the gRPC client of the service, over grpc-js 1.14. This entry is a part of S6. The peer dependency `@grpc/grpc-js` is optional. The client loads it only when it connects.
|
|
603
|
+
|
|
604
|
+
- **The declaration.** `grpcClient(name, spec)` declares `connect_bound`, `request_timeout`, `keepalive_time`, `keepalive_timeout`, `client_idle_timeout`, `retry_base`, `retry_cap`, `keepalive_idle`, `max_connection_age`, `close_bound` and `retrans_giveup_time`. The client derives `heartbeat_detection_time` (the PING) and `idle_refresh_interval`.
|
|
605
|
+
- **The facts of the server.** The knobs `server_keepalive_min_time` and `server_idle_close` are not in the declaration. `problems` compares them with `keepalive_time` (O26) and `client_idle_timeout` (O01).
|
|
606
|
+
- **The problems of a spec.** `specProblems` refuses a knob that grpc-js cannot use. Examples are a zero keepalive, a timer that is not a whole number of milliseconds, and an idle timeout below 1 s. A timer above the longest timer of Node is a problem too. It also refuses a TCP keepalive and a password: give the credentials in `metadata`.
|
|
607
|
+
- **The options of the channel.** `grpcOptions(spec)` gives the target and each timer of grpc-js from the declaration. It also turns the retries of grpc-js off, and the service config of the resolver.
|
|
608
|
+
- **The starting knobs.** `GRPC_CLIENT_STARTING_KNOBS`, `GRPC_CLIENT_STARTING_WITHOUT_CALLS` and `GRPC_CLIENT_STARTING_POLL` are a declaration from the gRPC cells. Each value has its measured reason in its doc comment.
|
|
609
|
+
- **A call.** A call is one unary call until its status. `request` and the response are the bytes of the messages, thus the caller encodes them. A stream is not a call. The deadline of a call is the time that is left in `request_timeout`, and grpc-js sends it as `grpc-timeout`.
|
|
610
|
+
- **Three outcomes.** `call(method, request)` gives `ok`, `error` or `indeterminate` from the status code (`grpcOutcome`). A code that tells a refusal is certain. A status that tells that the call did not leave the client is not sent. Each other status is not certain.
|
|
611
|
+
- **Idempotency for each method.** `grpcOperations` makes the table from the `idempotency_level` of each method in its proto. The client sends again only an idempotent method, one time, after it is ready again. `GRPC_OPERATIONS` names the Check of the health service.
|
|
612
|
+
- **One channel.** The client has one channel for its life, and each READY is one generation. The grpc-js driver connects again by itself after its backoff. The client connects an IDLE channel again at once.
|
|
613
|
+
- **The join.** At each generation, the client records the target (`joins`). The grpc-js driver gives no socket, thus the ends of the socket are null.
|
|
614
|
+
- **The checks.** `checks({ poll, steps })` gives `local-state`, `round-trip` (a Check of the health service on the channel of the application, which passes only for SERVING) and `dial` (the HTTP/2 preface on a new connection, which must get a SETTINGS frame).
|
|
615
|
+
- **The close.** At the shutdown, the client waits for its calls in flight. At its `close_bound`, it cancels each call that stays, which is then indeterminate. Then it closes the channel.
|
|
616
|
+
- **The manifestations and the machine.** `meets` lists the manifestations and their detectors. `GRPC_CLIENT_MACHINE` is the client as a `mark1russell7.machine/1` document, and the tests replay the client against it.
|
|
617
|
+
|
|
513
618
|
## Installation
|
|
514
619
|
|
|
515
620
|
```sh
|
|
516
621
|
pnpm add @mark1russell7/service
|
|
517
622
|
```
|
|
518
623
|
|
|
519
|
-
For the Redis client, the application also installs `ioredis@6`.
|
|
624
|
+
For the Redis client, the application also installs `ioredis@6`. For the PostgreSQL client, it installs `pg@8`. For the MongoDB client, it installs `mongodb@7`. For the RabbitMQ client, it installs `amqplib@2`. For the WebSocket client, it installs `ws@8`.
|
|
625
|
+
|
|
626
|
+
For the gRPC client, it installs `@grpc/grpc-js@1`. The HTTP client needs no other package.
|
|
520
627
|
|
|
521
628
|
The package uses [`@mark1russell7/quantity`](https://www.npmjs.com/package/@mark1russell7/quantity) for each amount. No entry of the service loads `zod` at run time, thus `zod` is an optional peer dependency. An application that validates amounts with the schemas of `quantity` installs `zod`.
|
|
522
629
|
|
|
@@ -627,6 +734,8 @@ for await (const event of stream) {
|
|
|
627
734
|
- **The machines.** Each document is valid, consistent and readable by the algebra. Eight scenarios replay against the documents and reach each state. The timers of the documents give the times of the runtime. The ordering law (O16) agrees with the clamp of the cooperative abort.
|
|
628
735
|
- **The contract.** The contract of a sample declaration passes. The contract also finds a blind dependency, a check whose timeout is too short, and a check that names the wrong dependency.
|
|
629
736
|
- **The Redis client.** These tests use a fake ioredis and a fake clock. They cover the declaration, the three outcomes, and the commands that the client sends again or not. They also cover the join, the backoff, the watchdog, the close, the checks and the manifestations. Five scenarios replay against the document of the client.
|
|
737
|
+
- **The HTTP client.** These tests use a fake agent and a fake clock. They cover the declaration, the three outcomes, the methods that the client sends again or not, and the keep-alive race. They also cover the free socket timer, the close, the checks and the manifestations. Five scenarios replay against the document of the client. One test sends requests through the agent of Node to a server of the loopback.
|
|
738
|
+
- **The gRPC client.** These tests use a fake channel and a fake clock. They cover the declaration, the outcome of each status code, the methods that the client sends again or not, the keepalive and the IDLE channel. They also cover a GOAWAY, the close, the checks and the manifestations. Seven scenarios replay against the document of the client. One test sends calls through grpc-js to a server of the loopback.
|
|
630
739
|
- **F6 for Redis.** The fault matrix crosses each manifestation (a refusal, a frozen process, a black hole, a death without a close, and an app stall), each gating posture, two timings and two jitters. That is 60 cases on the fake. Each case holds the trip and the release of its policy to `failSpan` and `recoverSpan`. Five more cases shut the service down during each manifestation, and each one exits 0.
|
|
631
740
|
- **The timers.** These tests use a fake clock, and they cover the hooks of the heartbeat and its two kinds of ping. A property test holds the death of a silent peer to `detectionTime` in 300 seeded cases. Another one compares the heartbeat with a WebSocket heartbeat that sends one ping at a time, on 300 schedules of pongs. The backoff gives the delays of three earlier backoffs, number for number, and each delay is in its band.
|
|
632
741
|
- **The entries.** The timer entries reach no other package, no `node:` module and no zod. No entry of the service reaches zod, also through `quantity` and the replay of the algebra. The root reaches no `node:` module. The HTTP edge reaches only `node:http`, `node:net` and `quantity`. The worker loop and the queue drain reach only `quantity`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as Clock } from "./clock-mVqhXD0b.js";
|
|
2
2
|
import { c as LimiterSpec } from "./policy-G4QJ1ZZq.js";
|
|
3
|
-
import { B as Gate,
|
|
3
|
+
import { B as Gate, D as Outcome, E as CheckFn, F as CooperativeFloor, G as ServiceDescription, H as Phase, I as Coupling, L as Dependency, N as Check, R as ExitCodes, T as StartOptions, V as MessageTrigger, W as RepeatSignal, Y as Poll, Z as Timing, d as TransportImpl, ht as ProcessTrigger, k as Trace, lt as ExitTrigger, n as HealthServer, o as PhaseWork, r as Lifecycle, rt as OperationOf, s as RunOptions, t as DependencyHandle, wt as Operation, x as Health } from "./lifecycle-fgYfxrHK.js";
|
|
4
4
|
import { Extent, Quantity } from "@mark1russell7/quantity";
|
|
5
5
|
//#region src/profiles.d.ts
|
|
6
6
|
/**
|
|
@@ -208,4 +208,4 @@ interface ServiceBuilder {
|
|
|
208
208
|
declare const service: (name: string) => ServiceBuilder;
|
|
209
209
|
//#endregion
|
|
210
210
|
export { WatchdogSpec as a, Profile as c, kubernetes as d, localDev as f, Watchdog as i, daemon as l, ServiceBuilder as n, watchdog as o, service as r, PodTiming as s, ControllerBuilder as t, ephemeral as u };
|
|
211
|
-
//# sourceMappingURL=builder-
|
|
211
|
+
//# sourceMappingURL=builder-DawJhq4p.d.ts.map
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { t as Backoff } from "./backoff-B-jGWVDm.js";
|
|
2
|
+
import { n as Clock } from "./clock-mVqhXD0b.js";
|
|
3
|
+
import { B as Gate, E as CheckFn, G as ServiceDescription, K as Step, L as Dependency, M as Recorder, N as Check, Y as Poll, t as DependencyHandle, wt as Operation } from "./lifecycle-fgYfxrHK.js";
|
|
4
|
+
import { n as ServiceBuilder } from "./builder-DawJhq4p.js";
|
|
5
|
+
import { Extent, Quantity } from "@mark1russell7/quantity";
|
|
6
|
+
//#region src/client/meets.d.ts
|
|
7
|
+
/** A situation is what the connection did when the failure started: a command in flight, or idle. */
|
|
8
|
+
type Situation = "in flight" | "idle";
|
|
9
|
+
/** A detector is a mechanism that finds a failure, and the most time that it takes. */
|
|
10
|
+
interface Detector {
|
|
11
|
+
/** This name identifies the mechanism: a knob of the declaration, or the kernel. */
|
|
12
|
+
readonly by: string;
|
|
13
|
+
/** This time is the most that the detector takes, after the failure starts. "infinite" tells that it does not find the failure. */
|
|
14
|
+
readonly within: Extent;
|
|
15
|
+
readonly situation: Situation;
|
|
16
|
+
/** This text tells what the detector does, for example that it fails a command without a change of state. */
|
|
17
|
+
readonly effect: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A meeting is a manifestation that the client can meet, and its detectors (E9). The names of the
|
|
21
|
+
* manifestations are the names of the vocabulary of the harness and the corpus. `app-stall` is a
|
|
22
|
+
* server whose process is alive and does not answer, a proposal of the vocabulary.
|
|
23
|
+
*/
|
|
24
|
+
interface Meeting {
|
|
25
|
+
readonly manifestation: string;
|
|
26
|
+
/** This text tells the failure in words. */
|
|
27
|
+
readonly what: string;
|
|
28
|
+
readonly detectors: readonly Detector[];
|
|
29
|
+
}
|
|
30
|
+
/** These are the manifestations that each client gives a meeting for, in this sequence. */
|
|
31
|
+
declare const MANIFESTATIONS: readonly ["rst", "fin", "blackhole", "freeze-host", "freeze-process", "app-stall"];
|
|
32
|
+
/** A manifestation is one name of `MANIFESTATIONS`. */
|
|
33
|
+
type Manifestation = (typeof MANIFESTATIONS)[number];
|
|
34
|
+
/** This function gives the first detector of a manifestation in a situation, or null when no detector has an end (C3). */
|
|
35
|
+
declare const firstDetector: (meeting: Meeting, situation: Situation) => Detector | null;
|
|
36
|
+
/** This function gives each manifestation and situation that no detector of the client finds: the standing rule, as a query (C3, E9). */
|
|
37
|
+
declare const unwatched: (meets: readonly Meeting[]) => Array<{
|
|
38
|
+
readonly manifestation: string;
|
|
39
|
+
readonly situation: Situation;
|
|
40
|
+
}>;
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/client/core.d.ts
|
|
43
|
+
/**
|
|
44
|
+
* A sent result is the outcome of a call, in three values (C8):
|
|
45
|
+
* - `ok`: the server answered.
|
|
46
|
+
* - `error`: the call did not apply. The server answered with an error (`reply`), or the call did
|
|
47
|
+
* not leave the client.
|
|
48
|
+
* - `indeterminate`: the call can have reached the server, and nothing tells if it applied.
|
|
49
|
+
*/
|
|
50
|
+
type Sent<T> = {
|
|
51
|
+
readonly outcome: "ok";
|
|
52
|
+
readonly value: T;
|
|
53
|
+
readonly attempts: number;
|
|
54
|
+
} | {
|
|
55
|
+
readonly outcome: "error";
|
|
56
|
+
readonly error: string;
|
|
57
|
+
readonly reply: boolean;
|
|
58
|
+
readonly attempts: number;
|
|
59
|
+
} | {
|
|
60
|
+
readonly outcome: "indeterminate";
|
|
61
|
+
readonly error: string;
|
|
62
|
+
readonly attempts: number;
|
|
63
|
+
};
|
|
64
|
+
/** This type is the outcome of one try of a call, before the core counts the tries. */
|
|
65
|
+
type Attempt<T = unknown> = {
|
|
66
|
+
readonly outcome: "ok";
|
|
67
|
+
readonly value: T;
|
|
68
|
+
} | {
|
|
69
|
+
readonly outcome: "error";
|
|
70
|
+
readonly error: string;
|
|
71
|
+
readonly reply: boolean;
|
|
72
|
+
} | {
|
|
73
|
+
readonly outcome: "indeterminate";
|
|
74
|
+
readonly error: string;
|
|
75
|
+
};
|
|
76
|
+
/** Send options tell how the client sends one call. */
|
|
77
|
+
interface SendOptions {
|
|
78
|
+
/** This signal abandons the call. A call that left the client is then indeterminate. */
|
|
79
|
+
readonly signal?: AbortSignal | undefined;
|
|
80
|
+
/** False stops the client: it does not send the call again. A check that must fail at once uses it. */
|
|
81
|
+
readonly reissue?: boolean | undefined;
|
|
82
|
+
}
|
|
83
|
+
/** A dial function opens a new connection to the server, and reads one reply of the protocol. The `dial` check uses it. */
|
|
84
|
+
type DialFn = (host: string, port: number, signal: AbortSignal) => Promise<void>;
|
|
85
|
+
/** The steps of a connection that each client gives a check for (B14). A client can add more steps. */
|
|
86
|
+
type ClientStep = Extract<Step, "dial" | "round-trip" | "local-state">;
|
|
87
|
+
/** Check options tell which checks the client gives the health runtime, and which policy each one controls. */
|
|
88
|
+
interface CheckOptions<S extends Step = ClientStep> {
|
|
89
|
+
readonly poll: Poll;
|
|
90
|
+
/** 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. */
|
|
91
|
+
readonly steps: Partial<Record<S, Gate | null>>;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* These are the knobs that each client has, by the names of the ordering law. Each driver adds its
|
|
95
|
+
* own knobs. The comments of each driver tell which option of the driver a knob sets.
|
|
96
|
+
*/
|
|
97
|
+
interface ClientKnobs {
|
|
98
|
+
/** This time bounds a new connection. */
|
|
99
|
+
readonly connect_bound: Quantity;
|
|
100
|
+
/** This time bounds each call on the connection of the application. */
|
|
101
|
+
readonly request_timeout: Quantity;
|
|
102
|
+
/** This time is the idle time of TCP keepalive. Node then sends 10 probes 1 s apart. */
|
|
103
|
+
readonly keepalive_idle: Extent;
|
|
104
|
+
/** This time is the age at which the client replaces a connection. */
|
|
105
|
+
readonly max_connection_age: Extent;
|
|
106
|
+
/** This time bounds the close of the client at the shutdown. */
|
|
107
|
+
readonly close_bound: Quantity;
|
|
108
|
+
/**
|
|
109
|
+
* This time is when the kernel gives up a segment that it retransmits. Node cannot set it, thus
|
|
110
|
+
* the declaration states it for the ordering law (O17), from the platform.
|
|
111
|
+
*/
|
|
112
|
+
readonly retrans_giveup_time: Extent;
|
|
113
|
+
}
|
|
114
|
+
/** A client spec is the declaration of one client: where it connects, and its knobs. Each driver adds its own fields. */
|
|
115
|
+
interface ClientSpec<K extends ClientKnobs = ClientKnobs> {
|
|
116
|
+
readonly host: string;
|
|
117
|
+
readonly port: number;
|
|
118
|
+
readonly password?: string | undefined;
|
|
119
|
+
/** This identifier is the component in the corpus, for example `systems.component.redis`, or null. */
|
|
120
|
+
readonly system: string | null;
|
|
121
|
+
readonly knobs: K;
|
|
122
|
+
/** These operations add to the table of the client, or change it. */
|
|
123
|
+
readonly operations?: Readonly<Record<string, Operation>> | undefined;
|
|
124
|
+
}
|
|
125
|
+
/** A factory makes the connection or the pool of a client from the options of its driver. */
|
|
126
|
+
type Factory<O, C> = (options: O) => C | Promise<C>;
|
|
127
|
+
/** These options make a client. Without them, a client uses its driver, the real clock, and the dial of its protocol. */
|
|
128
|
+
interface ClientOptions<F> {
|
|
129
|
+
readonly create?: F | undefined;
|
|
130
|
+
readonly clock?: Clock | undefined;
|
|
131
|
+
/** This function receives each transition of the client, for the prober (D3). */
|
|
132
|
+
readonly record?: Recorder | undefined;
|
|
133
|
+
readonly dial?: DialFn | undefined;
|
|
134
|
+
}
|
|
135
|
+
/** A client socket is the part of the socket of a connection that the join of the prober and the bounded close read. */
|
|
136
|
+
interface ClientSocket {
|
|
137
|
+
readonly localAddress?: string | undefined;
|
|
138
|
+
readonly localPort?: number | undefined;
|
|
139
|
+
readonly remoteAddress?: string | undefined;
|
|
140
|
+
readonly remotePort?: number | undefined;
|
|
141
|
+
/** This method closes the socket at once. A close of the driver only half-closes it, and a silent peer does not answer that. */
|
|
142
|
+
destroy?(): void;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A client join is one connection, at the time when it can serve (D1, D2). The prober joins its
|
|
146
|
+
* kernel records to the server's view of the connection through it. Each driver adds the server's id of the connection.
|
|
147
|
+
*/
|
|
148
|
+
interface ClientJoin {
|
|
149
|
+
readonly generation: number;
|
|
150
|
+
/** This time is when the connection became ready, in milliseconds on the monotonic clock. */
|
|
151
|
+
readonly at: number;
|
|
152
|
+
readonly localAddress: string | null;
|
|
153
|
+
readonly localPort: number | null;
|
|
154
|
+
readonly remoteAddress: string | null;
|
|
155
|
+
readonly remotePort: number | null;
|
|
156
|
+
}
|
|
157
|
+
/** A client is a dependency of a service: its declaration, its handle and its checks. Each driver adds the method that sends a call. */
|
|
158
|
+
interface Client<S extends Step = ClientStep, J extends ClientJoin = ClientJoin> {
|
|
159
|
+
readonly name: string;
|
|
160
|
+
/** This declaration goes to `svc.dependency(name, declaration, handle)`. */
|
|
161
|
+
readonly declaration: Omit<Dependency, "name">;
|
|
162
|
+
readonly handle: DependencyHandle;
|
|
163
|
+
/** These are the manifestations that the client can meet, and the detectors of each (E9). */
|
|
164
|
+
readonly meets: readonly Meeting[];
|
|
165
|
+
/** This text is the state of the client, as the machine document of the client names it. */
|
|
166
|
+
readonly status: string;
|
|
167
|
+
/** These are the connections of the client, the latest last. */
|
|
168
|
+
readonly joins: readonly J[];
|
|
169
|
+
/** This method gives the checks of the client and their functions, for `svc.check(check, run)`. */
|
|
170
|
+
checks(options: CheckOptions<S>): ReadonlyArray<readonly [Check, CheckFn]>;
|
|
171
|
+
/** This method declares the client in a service: its dependency, its handle and its checks. */
|
|
172
|
+
attach(svc: ServiceBuilder, options: CheckOptions<S>): ServiceBuilder;
|
|
173
|
+
/** This method gives the problems of the client in a description: a close that does not fit in its phase, for example. */
|
|
174
|
+
problems(description: ServiceDescription): string[];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* A ready wait holds the calls that wait for a ready connection before their second try. A client
|
|
178
|
+
* that connects again by itself uses it: Redis and RabbitMQ.
|
|
179
|
+
*/
|
|
180
|
+
interface ReadyWait {
|
|
181
|
+
/** This method waits until the client is ready, for `within` milliseconds at most. It gives true when the client is ready. */
|
|
182
|
+
wait(within: number, signal?: AbortSignal): Promise<boolean>;
|
|
183
|
+
/** This method ends each wait with true. The client uses it when its connection is ready. */
|
|
184
|
+
wake(): void;
|
|
185
|
+
}
|
|
186
|
+
/** This function makes the ready wait of a client. The function `ready` tells if the client is ready. */
|
|
187
|
+
declare const readyWait: (clock: Clock, ready: () => boolean) => ReadyWait;
|
|
188
|
+
/** These are the knobs of a client that connects again by itself, after a delay that doubles. */
|
|
189
|
+
interface RetryKnobs {
|
|
190
|
+
/** This time is the first delay before a new try to connect. The delay doubles after each try. */
|
|
191
|
+
readonly retry_base: Quantity;
|
|
192
|
+
/** This time is the most that one delay can be, before its jitter. */
|
|
193
|
+
readonly retry_cap: Quantity;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* This function gives the backoff of the tries to connect: from `retry_base`, doubling to
|
|
197
|
+
* `retry_cap`, each with a jitter up to the base. The client rounds each delay to a whole millisecond.
|
|
198
|
+
*/
|
|
199
|
+
declare const retryBackoff: (knobs: RetryKnobs) => Backoff;
|
|
200
|
+
/**
|
|
201
|
+
* A retry schedule holds the next try to connect, for a client that connects again by itself. It
|
|
202
|
+
* counts the tries since the last join. Each delay comes from the backoff, rounded to a whole millisecond.
|
|
203
|
+
*/
|
|
204
|
+
interface RetrySchedule {
|
|
205
|
+
/** This count is the number of tries since the last reset. */
|
|
206
|
+
readonly tries: number;
|
|
207
|
+
/** This method counts one more try, and starts `due` after the delay of that try. It stops a try that waits. */
|
|
208
|
+
schedule(due: () => void): void;
|
|
209
|
+
/** This method stops the try that waits. The close uses it. */
|
|
210
|
+
cancel(): void;
|
|
211
|
+
/** This method sets the count of tries to zero. The client uses it at each join. */
|
|
212
|
+
reset(): void;
|
|
213
|
+
}
|
|
214
|
+
/** This function makes the retry schedule of a client, from its backoff and its random numbers. RabbitMQ and WebSocket use it. */
|
|
215
|
+
declare const retrySchedule: (clock: Clock, backoff: Backoff, random: () => number) => RetrySchedule;
|
|
216
|
+
/** This function gives the problems of the retry knobs: the cap must not be below the base. */
|
|
217
|
+
declare const retryProblems: (name: string, knobs: RetryKnobs) => string[];
|
|
218
|
+
/**
|
|
219
|
+
* This poll is a poll of the checks to start from: each 5 s with a jitter of 20 %, and a timeout of
|
|
220
|
+
* 4 s (B6, O12). Its slowest poll is 4 s + 5 s × 1.2 = 10 s, the slowest poll of the cells of O13.
|
|
221
|
+
* It does not depend on a driver. The starting knobs of each driver fit it.
|
|
222
|
+
*/
|
|
223
|
+
declare const STARTING_POLL: Poll;
|
|
224
|
+
//#endregion
|
|
225
|
+
export { MANIFESTATIONS as C, firstDetector as D, Situation as E, unwatched as O, Detector as S, Meeting as T, Sent as _, ClientKnobs as a, retryProblems as b, ClientSpec as c, Factory as d, ReadyWait as f, SendOptions as g, STARTING_POLL as h, ClientJoin as i, ClientStep as l, RetrySchedule as m, CheckOptions as n, ClientOptions as o, RetryKnobs as p, Client as r, ClientSocket as s, Attempt as t, DialFn as u, readyWait as v, Manifestation as w, retrySchedule as x, retryBackoff as y };
|
|
226
|
+
//# sourceMappingURL=core-BPLRDcUJ.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "./core-BPLRDcUJ.js";
|
|
2
|
+
//#region src/client/dial.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* A judge reads the bytes that the server sent so far. It gives null for a good answer, an error for
|
|
5
|
+
* a bad answer, or "more" when it must wait for more bytes.
|
|
6
|
+
*/
|
|
7
|
+
type Judge = (received: Buffer) => Error | null | "more";
|
|
8
|
+
/**
|
|
9
|
+
* This function makes a judge of the status line of an HTTP/1.1 answer. The judge waits for the end
|
|
10
|
+
* of the line, in the first 8 KiB. Then `accept` reads the status code, and gives null for a good status or an error.
|
|
11
|
+
* The HTTP client accepts each status. The WebSocket client accepts only 101.
|
|
12
|
+
*/
|
|
13
|
+
declare const statusLineJudge: (accept: (status: number, line: string) => Error | null) => Judge;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { statusLineJudge as n, Judge as t };
|
|
16
|
+
//# sourceMappingURL=dial-DA8vJfh2.d.ts.map
|