@mark1russell7/harness 0.11.1 → 0.11.3

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,19 @@
1
+ import { J as Subject, _ as ImageSource, i as ClientImage, r as Client } from "./cell-BmSnJJtn.js";
2
+ import { z } from "zod";
3
+ //#region src/schema.d.ts
4
+ /**
5
+ * This schema accepts the source of an image (`ImageSource`). It is a pinned image with its digest,
6
+ * a build context whose paths stay in it, or an image that the harness ships.
7
+ */
8
+ export declare const imageSourceSchema: z.ZodType<ImageSource>;
9
+ /** This schema accepts a client image (`ClientImage`): a stock image by its name or the source of an image, and its command. */
10
+ export declare const clientImageSchema: z.ZodType<ClientImage>;
11
+ /** This schema accepts the client of a cell (`Cell.client`): a client that the harness ships by its name, or a client image. */
12
+ export declare const clientSchema: z.ZodType<Client | ClientImage>;
13
+ /**
14
+ * This schema accepts a subject (`Subject`): its name, the source of its image and its stock base.
15
+ * It also accepts the command of each of its programs.
16
+ */
17
+ export declare const subjectSchema: z.ZodType<Subject>;
18
+ //#endregion
19
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1,56 @@
1
+ import { a as SHIPPED_NAMES, t as CLIENT_NAMES, u as imageProblems, x as subjectProblems } from "./image-Q_fs3rCC.js";
2
+ import { z } from "zod";
3
+ //#region src/schema.ts
4
+ /** This function adds each problem that a check finds as an issue of a schema. */
5
+ const issues = (check) => (value, ctx) => {
6
+ for (const message of check(value)) ctx.addIssue({
7
+ code: "custom",
8
+ message
9
+ });
10
+ };
11
+ /** This schema accepts a command: argv of one text or more. */
12
+ const command = z.array(z.string().min(1)).min(1);
13
+ /** This schema accepts the shape of each kind of image source. The checks of its values come after it. */
14
+ const sourceShape = z.discriminatedUnion("kind", [
15
+ z.strictObject({
16
+ kind: z.literal("pinned"),
17
+ image: z.string()
18
+ }),
19
+ z.strictObject({
20
+ kind: z.literal("build"),
21
+ name: z.string(),
22
+ context: z.string(),
23
+ dockerfile: z.string().optional(),
24
+ files: z.array(z.string()).optional()
25
+ }),
26
+ z.strictObject({
27
+ kind: z.literal("shipped"),
28
+ name: z.enum(SHIPPED_NAMES)
29
+ })
30
+ ]);
31
+ /**
32
+ * This schema accepts the source of an image (`ImageSource`). It is a pinned image with its digest,
33
+ * a build context whose paths stay in it, or an image that the harness ships.
34
+ */
35
+ const imageSourceSchema = sourceShape.superRefine(issues(imageProblems));
36
+ /** This schema accepts a client image (`ClientImage`): a stock image by its name or the source of an image, and its command. */
37
+ const clientImageSchema = z.strictObject({
38
+ image: z.union([z.string().min(1), imageSourceSchema]),
39
+ command
40
+ });
41
+ /** This schema accepts the client of a cell (`Cell.client`): a client that the harness ships by its name, or a client image. */
42
+ const clientSchema = z.union([z.enum(CLIENT_NAMES), clientImageSchema]);
43
+ /**
44
+ * This schema accepts a subject (`Subject`): its name, the source of its image and its stock base.
45
+ * It also accepts the command of each of its programs.
46
+ */
47
+ const subjectSchema = z.strictObject({
48
+ name: z.string().min(1),
49
+ image: sourceShape,
50
+ base: z.string(),
51
+ programs: z.record(z.string(), command)
52
+ }).superRefine(issues(subjectProblems));
53
+ //#endregion
54
+ export { clientImageSchema, clientSchema, imageSourceSchema, subjectSchema };
55
+
56
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","names":[],"sources":["../src/schema.ts"],"sourcesContent":["// The entry \"@mark1russell7/harness/schema\": the zod schemas of the data that a caller gives the\n// harness as JSON. These are the source of an image, a client and a subject. The root entry of\n// the package does not import zod.\nimport { z } from \"zod\";\nimport type { Client, ClientImage, ImageSource, Subject } from \"./cell.ts\";\nimport { CLIENT_NAMES, SHIPPED_NAMES, imageProblems, subjectProblems } from \"./image.ts\";\n\n/** This function adds each problem that a check finds as an issue of a schema. */\nconst issues =\n <T>(check: (value: T) => string[]) =>\n (value: T, ctx: z.RefinementCtx): void => {\n for (const message of check(value)) ctx.addIssue({ code: \"custom\", message });\n };\n\n/** This schema accepts a command: argv of one text or more. */\nconst command = z.array(z.string().min(1)).min(1);\n\n/** This schema accepts the shape of each kind of image source. The checks of its values come after it. */\nconst sourceShape = z.discriminatedUnion(\"kind\", [\n z.strictObject({ kind: z.literal(\"pinned\"), image: z.string() }),\n z.strictObject({\n kind: z.literal(\"build\"),\n name: z.string(),\n context: z.string(),\n dockerfile: z.string().optional(),\n files: z.array(z.string()).optional(),\n }),\n z.strictObject({ kind: z.literal(\"shipped\"), name: z.enum(SHIPPED_NAMES) }),\n]);\n\n/**\n * This schema accepts the source of an image (`ImageSource`). It is a pinned image with its digest,\n * a build context whose paths stay in it, or an image that the harness ships.\n */\nexport const imageSourceSchema: z.ZodType<ImageSource> = sourceShape.superRefine(issues(imageProblems));\n\n/** This schema accepts a client image (`ClientImage`): a stock image by its name or the source of an image, and its command. */\nexport const clientImageSchema: z.ZodType<ClientImage> = z.strictObject({\n image: z.union([z.string().min(1), imageSourceSchema]),\n command,\n});\n\n/** This schema accepts the client of a cell (`Cell.client`): a client that the harness ships by its name, or a client image. */\nexport const clientSchema: z.ZodType<Client | ClientImage> = z.union([z.enum(CLIENT_NAMES), clientImageSchema]);\n\n/**\n * This schema accepts a subject (`Subject`): its name, the source of its image and its stock base.\n * It also accepts the command of each of its programs.\n */\nexport const subjectSchema: z.ZodType<Subject> = z\n .strictObject({\n name: z.string().min(1),\n image: sourceShape,\n base: z.string(),\n programs: z.record(z.string(), command),\n })\n .superRefine(issues(subjectProblems));\n"],"mappings":";;;;AAQA,MAAM,UACA,WACH,OAAU,QAA+B;CACxC,KAAK,MAAM,WAAW,MAAM,KAAK,GAAG,IAAI,SAAS;EAAE,MAAM;EAAU;CAAQ,CAAC;AAC9E;;AAGF,MAAM,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;;AAGhD,MAAM,cAAc,EAAE,mBAAmB,QAAQ;CAC/C,EAAE,aAAa;EAAE,MAAM,EAAE,QAAQ,QAAQ;EAAG,OAAO,EAAE,OAAO;CAAE,CAAC;CAC/D,EAAE,aAAa;EACb,MAAM,EAAE,QAAQ,OAAO;EACvB,MAAM,EAAE,OAAO;EACf,SAAS,EAAE,OAAO;EAClB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;EAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS;CACtC,CAAC;CACD,EAAE,aAAa;EAAE,MAAM,EAAE,QAAQ,SAAS;EAAG,MAAM,EAAE,KAAK,aAAa;CAAE,CAAC;AAC5E,CAAC;;;;;AAMD,MAAa,oBAA4C,YAAY,YAAY,OAAO,aAAa,CAAC;;AAGtG,MAAa,oBAA4C,EAAE,aAAa;CACtE,OAAO,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC;CACrD;AACF,CAAC;;AAGD,MAAa,eAAgD,EAAE,MAAM,CAAC,EAAE,KAAK,YAAY,GAAG,iBAAiB,CAAC;;;;;AAM9G,MAAa,gBAAoC,EAC9C,aAAa;CACZ,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,OAAO;CACP,MAAM,EAAE,OAAO;CACf,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO;AACxC,CAAC,CAAC,CACD,YAAY,OAAO,eAAe,CAAC"}
@@ -0,0 +1,11 @@
1
+ # The harness's PostgreSQL client: node-postgres (pg 8.23.1 with pg-pool 3.14.0) on Node 24.21.0. It
2
+ # runs as a container in the client's network namespace, as the harness's own TCP client does, and
3
+ # prints the same JSON lines, with the events of the pool beside them.
4
+ # lab.ts stages the build context: this folder, under harness/image/pg. The lockfile is
5
+ # npm-lock.json here, because npm leaves each package-lock.json out of a published tarball.
6
+ FROM node:24.21.0-alpine@sha256:ebfe2f90462722a7a4de65e91990e97fe0d401c70e0e762c5b53302f905ec1c1
7
+ WORKDIR /opt/harness/pg
8
+ COPY harness/image/pg/package.json ./
9
+ COPY harness/image/pg/npm-lock.json ./package-lock.json
10
+ RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund
11
+ COPY harness/image/pg/client-pg.mjs ./
@@ -0,0 +1,166 @@
1
+ // client-pg HOST PORT --mode idle|inflight|stream [--interval-ms N] [--max-s N] [--go PATH]
2
+ // [--query TEXT] [--before] [--options JSON]
3
+ //
4
+ // The client of a PostgreSQL cell: node-postgres (pg 8.23.1 with pg-pool 3.14.0) on Node 24.21.0.
5
+ // Each event is a JSON line with `ev` and `t`, the time on CLOCK_MONOTONIC in nanoseconds
6
+ // (process.hrtime, which libuv reads from it). The prober stamps the records of the kernel on the
7
+ // same clock, and the injector stamps each fault on it.
8
+ //
9
+ // The lines:
10
+ // - start: the versions of pg, pg-pool and Node, the mode and the options of the pool.
11
+ // - connecting: the first query, which makes the first connection of the pool. Its stamp is the
12
+ // stamp of the first sent line. The pool arms its connect timer in that call.
13
+ // - socket, socket-end, socket-error: each socket of the pool, with its local address.
14
+ // - connected: the first socket. The TCP client of the harness names its own socket so.
15
+ // - pool: each event of the pool: connect, acquire, release, remove, and error with its errno.
16
+ // - ready: the reply to the first query: the connection is idle in the pool (idle, inflight), or
17
+ // the stream starts (stream).
18
+ // - sent, ack, fail: each query by its sequence number, with its error.
19
+ // - data, error: the end of the query in flight, as the TCP client of the harness tells it. In idle
20
+ // mode, an error of the pool for the first socket is an error line too: the pool removes it.
21
+ // - done, deadline: the end of the client.
22
+ //
23
+ // The modes: idle sends one query, gives the client back to the pool, then holds until --max-s.
24
+ // inflight sends one query, waits for the file --go, sends one more query into the fault, and holds
25
+ // after its end. stream sends a query each --interval-ms after the reply before, until --max-s or
26
+ // SIGTERM. --before starts the client into a fault that holds already: its first connect meets it.
27
+
28
+ import { readFileSync } from "node:fs";
29
+ import { existsSync } from "node:fs";
30
+
31
+ const now = () => process.hrtime.bigint();
32
+ const emit = (ev, fields = {}, at = now()) => process.stdout.write(`${JSON.stringify({ ev, t: Number(at), ...fields })}\n`);
33
+
34
+ const argv = process.argv.slice(2);
35
+ const flag = (name) => {
36
+ const at = argv.indexOf(name);
37
+ return at === -1 ? undefined : argv[at + 1];
38
+ };
39
+ const [host, port] = argv;
40
+ const mode = flag("--mode");
41
+ if (host === undefined || port === undefined || !["idle", "inflight", "stream"].includes(mode)) {
42
+ emit("fail", { why: "client-pg HOST PORT --mode idle|inflight|stream" });
43
+ process.exit(1);
44
+ }
45
+ const interval = Number(flag("--interval-ms") ?? 50);
46
+ const max = Number(flag("--max-s") ?? 120) * 1000;
47
+ const go = flag("--go") ?? "/tmp/go";
48
+ const query = flag("--query") ?? "SELECT 1";
49
+ const before = argv.includes("--before");
50
+ // the options of the pool. A cell gives each option that it changes, and pg-pool gives the others
51
+ const options = JSON.parse(flag("--options") ?? "{}");
52
+
53
+ const { default: pg } = await import("pg");
54
+ const versionOf = (name) => JSON.parse(readFileSync(new URL(`./node_modules/${name}/package.json`, import.meta.url), "utf8")).version;
55
+
56
+ const bootId = () => {
57
+ try {
58
+ return readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
59
+ } catch {
60
+ return null;
61
+ }
62
+ };
63
+ emit("start", { boot_id: bootId(), client: "pg", driver: versionOf("pg"), pool: versionOf("pg-pool"), node: process.versions.node, mode, query, options });
64
+
65
+ const pool = new pg.Pool({ host, port: Number(port), user: "postgres", database: "postgres", ...options });
66
+
67
+ /** This function gives the error of pg as data: its name, its code, its severity and its message. */
68
+ const failure = (error) =>
69
+ error === undefined || error === null
70
+ ? null
71
+ : { name: error.name, code: error.code ?? null, errno: error.errno ?? null, severity: error.severity ?? null, message: error.message };
72
+ /** This function gives the errno of an error: the code of the socket (ECONNRESET), the SQLSTATE of the server, or the message of pg. */
73
+ const errnoOf = (error) => error?.code ?? error?.message ?? null;
74
+
75
+ // ---- each socket of the pool: pg-pool emits connect after the first ReadyForQuery of a new client
76
+ let connected = null;
77
+ const localOf = (client) => {
78
+ const socket = client?.connection?.stream;
79
+ return socket?.localPort === undefined ? null : `${socket.localAddress}:${socket.localPort}`;
80
+ };
81
+ pool.on("connect", (client) => {
82
+ const socket = client.connection.stream;
83
+ const local = localOf(client);
84
+ emit("socket", { role: "pool", local });
85
+ emit("pool", { event: "connect", local });
86
+ if (connected === null && local !== null) {
87
+ connected = local;
88
+ emit("connected", { local });
89
+ }
90
+ socket.once("close", (hadError) => emit("socket-end", { role: "pool", local, error: hadError }));
91
+ socket.once("error", (error) => emit("socket-error", { role: "pool", local, errno: errnoOf(error), message: error.message }));
92
+ });
93
+ pool.on("acquire", (client) => emit("pool", { event: "acquire", local: localOf(client) }));
94
+ pool.on("release", (error, client) => emit("pool", { event: "release", local: localOf(client), failure: failure(error) }));
95
+ // the pool removes a client: after its idle timer, its lifetime or an error. A driver band names the close CLOSED
96
+ pool.on("remove", (client) => emit("pool", { event: "remove", name: "CLOSED", local: localOf(client) }));
97
+ // an idle client that fails: the pool removes it and emits error. Without this listener, Node throws
98
+ pool.on("error", (error, client) => {
99
+ const local = localOf(client);
100
+ emit("pool", { event: "error", local, failure: failure(error) });
101
+ // the socket of the client in idle mode: its error is the detection, as for the TCP client
102
+ if (mode === "idle" && local !== null && local === connected) emit("error", { errno: errnoOf(error) });
103
+ });
104
+
105
+ // ---- the end of the client
106
+ let stopped = false;
107
+ const stop = (code = 0) => {
108
+ if (stopped) return;
109
+ stopped = true;
110
+ emit("done");
111
+ pool.end().finally(() => setTimeout(() => process.exit(code), 50));
112
+ };
113
+ process.on("SIGTERM", () => stop(0));
114
+ setTimeout(() => {
115
+ emit("deadline");
116
+ stop(0);
117
+ }, max);
118
+
119
+ /** This function sends query n through the pool. Its stamp is before the call. It gives null for a reply, or the error. */
120
+ const operate = async (n) => {
121
+ const at = now();
122
+ const reply = pool.query(query);
123
+ // a band of a cell that starts into a fault counts from the start of the first connect
124
+ if (n === 1) emit("connecting", { at: `${host}:${port}` }, at);
125
+ emit("sent", { n }, at);
126
+ try {
127
+ const result = await reply;
128
+ emit("ack", { n, rows: result.rowCount ?? null });
129
+ return null;
130
+ } catch (error) {
131
+ emit("fail", { n, ...failure(error) });
132
+ return error;
133
+ }
134
+ };
135
+
136
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
137
+
138
+ if (mode === "stream") {
139
+ emit("ready", { at: `${host}:${port}` });
140
+ for (let n = 1; !stopped; n += 1) {
141
+ await operate(n);
142
+ await sleep(interval);
143
+ }
144
+ } else {
145
+ // the first query makes the connection of the pool. Its reply is the last data before the fault
146
+ const failed = await operate(1);
147
+ if (failed !== null) {
148
+ emit("error", { errno: errnoOf(failed), message: failed.message });
149
+ stop(before ? 0 : 1);
150
+ } else {
151
+ // pool.query gives the client back to the pool at its reply: the connection is idle there
152
+ emit("ready", { reply: "ack" });
153
+ if (mode === "inflight") {
154
+ await new Promise((resolve) => {
155
+ const wait = setInterval(() => {
156
+ if (!existsSync(go)) return;
157
+ clearInterval(wait);
158
+ resolve();
159
+ }, 2);
160
+ });
161
+ const error = await operate(2);
162
+ if (error === null) emit("data", { n: 2 });
163
+ else emit("error", { errno: errnoOf(error), message: error.message });
164
+ }
165
+ }
166
+ }
@@ -0,0 +1,160 @@
1
+ {
2
+ "name": "loom-harness-pg-client",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "name": "loom-harness-pg-client",
8
+ "dependencies": {
9
+ "pg": "8.23.1",
10
+ "pg-pool": "3.14.0"
11
+ }
12
+ },
13
+ "node_modules/pg": {
14
+ "version": "8.23.1",
15
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz",
16
+ "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==",
17
+ "license": "MIT",
18
+ "dependencies": {
19
+ "pg-connection-string": "^2.14.1",
20
+ "pg-pool": "^3.14.0",
21
+ "pg-protocol": "^1.16.1",
22
+ "pg-types": "2.2.0",
23
+ "pgpass": "1.0.5"
24
+ },
25
+ "engines": {
26
+ "node": ">= 16.0.0"
27
+ },
28
+ "optionalDependencies": {
29
+ "pg-cloudflare": "^1.4.1"
30
+ },
31
+ "peerDependencies": {
32
+ "pg-native": ">=3.0.1"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "pg-native": {
36
+ "optional": true
37
+ }
38
+ }
39
+ },
40
+ "node_modules/pg-cloudflare": {
41
+ "version": "1.4.1",
42
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz",
43
+ "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==",
44
+ "license": "MIT",
45
+ "optional": true
46
+ },
47
+ "node_modules/pg-connection-string": {
48
+ "version": "2.14.1",
49
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz",
50
+ "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==",
51
+ "license": "MIT"
52
+ },
53
+ "node_modules/pg-int8": {
54
+ "version": "1.0.1",
55
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
56
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
57
+ "license": "ISC",
58
+ "engines": {
59
+ "node": ">=4.0.0"
60
+ }
61
+ },
62
+ "node_modules/pg-pool": {
63
+ "version": "3.14.0",
64
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
65
+ "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
66
+ "license": "MIT",
67
+ "peerDependencies": {
68
+ "pg": ">=8.0"
69
+ }
70
+ },
71
+ "node_modules/pg-protocol": {
72
+ "version": "1.16.1",
73
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz",
74
+ "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==",
75
+ "license": "MIT"
76
+ },
77
+ "node_modules/pg-types": {
78
+ "version": "2.2.0",
79
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
80
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
81
+ "license": "MIT",
82
+ "dependencies": {
83
+ "pg-int8": "1.0.1",
84
+ "postgres-array": "~2.0.0",
85
+ "postgres-bytea": "~1.0.0",
86
+ "postgres-date": "~1.0.4",
87
+ "postgres-interval": "^1.1.0"
88
+ },
89
+ "engines": {
90
+ "node": ">=4"
91
+ }
92
+ },
93
+ "node_modules/pgpass": {
94
+ "version": "1.0.5",
95
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
96
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
97
+ "license": "MIT",
98
+ "dependencies": {
99
+ "split2": "^4.1.0"
100
+ }
101
+ },
102
+ "node_modules/postgres-array": {
103
+ "version": "2.0.0",
104
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
105
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
106
+ "license": "MIT",
107
+ "engines": {
108
+ "node": ">=4"
109
+ }
110
+ },
111
+ "node_modules/postgres-bytea": {
112
+ "version": "1.0.1",
113
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
114
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
115
+ "license": "MIT",
116
+ "engines": {
117
+ "node": ">=0.10.0"
118
+ }
119
+ },
120
+ "node_modules/postgres-date": {
121
+ "version": "1.0.7",
122
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
123
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
124
+ "license": "MIT",
125
+ "engines": {
126
+ "node": ">=0.10.0"
127
+ }
128
+ },
129
+ "node_modules/postgres-interval": {
130
+ "version": "1.2.0",
131
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
132
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
133
+ "license": "MIT",
134
+ "dependencies": {
135
+ "xtend": "^4.0.0"
136
+ },
137
+ "engines": {
138
+ "node": ">=0.10.0"
139
+ }
140
+ },
141
+ "node_modules/split2": {
142
+ "version": "4.2.0",
143
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
144
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
145
+ "license": "ISC",
146
+ "engines": {
147
+ "node": ">= 10.x"
148
+ }
149
+ },
150
+ "node_modules/xtend": {
151
+ "version": "4.0.2",
152
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
153
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
154
+ "license": "MIT",
155
+ "engines": {
156
+ "node": ">=0.4"
157
+ }
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "loom-harness-pg-client",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "pg": "8.23.1",
7
+ "pg-pool": "3.14.0"
8
+ }
9
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/harness",
4
- "version": "0.11.1",
4
+ "version": "0.11.3",
5
5
  "description": "A fault-injection harness for connection timers: a cell runs a client against a stock target in Docker, through a topology of routers and proxies, a replicated group under Sentinels or a replica set, injects a fault, measures the kernel's own records and judges a prediction. HTTP clients of Go, Node and undici race the keep-alive of a Node server, and the MongoDB driver meets the faults of a replica set. The WebSocket client of @procedures/client drops a silent connection on its own heartbeat, and the harness times that detection and the reconnect from the program's lines and the kernel's stamps.",
6
6
  "keywords": [
7
7
  "fault-injection",
@@ -33,6 +33,10 @@
33
33
  "types": "./build/index.d.ts",
34
34
  "default": "./build/index.js"
35
35
  },
36
+ "./schema": {
37
+ "types": "./build/schema.d.ts",
38
+ "default": "./build/schema.js"
39
+ },
36
40
  "./package.json": "./package.json"
37
41
  },
38
42
  "files": [
@@ -61,6 +65,10 @@
61
65
  "image/mongo/stepdown-mongo.mjs",
62
66
  "image/mongo/package.json",
63
67
  "image/mongo/npm-lock.json",
68
+ "image/pg/Dockerfile",
69
+ "image/pg/client-pg.mjs",
70
+ "image/pg/package.json",
71
+ "image/pg/npm-lock.json",
64
72
  "image/procedures/Dockerfile",
65
73
  "image/procedures/package.json",
66
74
  "image/procedures/npm-lock.json",