@openstatus/health-postgres 0.1.4-dev.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 +80 -0
- package/dist/_virtual/rolldown_runtime.cjs +30 -0
- package/dist/mod.cjs +33 -0
- package/dist/mod.d.cts +24 -0
- package/dist/mod.d.cts.map +1 -0
- package/dist/mod.d.ts +24 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +32 -0
- package/dist/mod.js.map +1 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# @openstatus/health-postgres
|
|
2
|
+
|
|
3
|
+
[PostgreSQL](https://www.postgresql.org/) probe for
|
|
4
|
+
[`@openstatus/health`](https://jsr.io/@openstatus/health). Runs `select 1`
|
|
5
|
+
through the client you already have — a [`pg`](https://node-postgres.com/)
|
|
6
|
+
pool or client, a [postgres.js](https://github.com/porsager/postgres) `sql`
|
|
7
|
+
instance, `@neondatabase/serverless` or `@vercel/postgres` — and fails the
|
|
8
|
+
check when the round trip does.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
deno add jsr:@openstatus/health jsr:@openstatus/health-postgres
|
|
12
|
+
npm install @openstatus/health @openstatus/health-postgres
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Pool } from "pg";
|
|
17
|
+
import { createHealthHandler } from "@openstatus/health";
|
|
18
|
+
import { postgresProbe } from "@openstatus/health-postgres";
|
|
19
|
+
|
|
20
|
+
const pool = new Pool({ connectionString: env.DATABASE_URL });
|
|
21
|
+
|
|
22
|
+
Deno.serve(
|
|
23
|
+
createHealthHandler({ probes: [postgresProbe({ client: pool })] }),
|
|
24
|
+
);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The probe picks the first method the client exposes:
|
|
28
|
+
|
|
29
|
+
| Client | Call |
|
|
30
|
+
| ------ | ---- |
|
|
31
|
+
| `pg` `Pool` / `Client`, `@vercel/postgres` `sql`, Neon `Pool` / `Client` / `neon()` | `client.query("select 1")` |
|
|
32
|
+
| postgres.js `sql` | `sql.unsafe("select 1")` |
|
|
33
|
+
|
|
34
|
+
Pass a pool rather than a single connection where you can: a pool checks a
|
|
35
|
+
connection out per probe and hands it back, so the health endpoint never
|
|
36
|
+
holds one open and never collides with request traffic on a shared client.
|
|
37
|
+
|
|
38
|
+
A timed-out check is reported `timeout` and the round moves on; the driver
|
|
39
|
+
query is not cancelled, because none of the supported clients expose a
|
|
40
|
+
portable abort (`pg` and `@vercel/postgres` take query text only, and
|
|
41
|
+
postgres.js cancels through the pending query's `.cancel()` rather than a
|
|
42
|
+
signal). With a pool, the connection returns to it once the query settles.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
postgresProbe({
|
|
46
|
+
client: pool,
|
|
47
|
+
// optional overrides from the Probe contract
|
|
48
|
+
name: "primary",
|
|
49
|
+
critical: false,
|
|
50
|
+
timeoutMs: 2000,
|
|
51
|
+
skip: () => env.DATABASE_URL == null,
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Critical by default: a Postgres that does not answer usually means requests
|
|
56
|
+
cannot be served, so the report turns `unhealthy` and the instance is taken
|
|
57
|
+
out of rotation. Set `critical: false` for a replica or reporting database.
|
|
58
|
+
|
|
59
|
+
The client is typed structurally as `{ query(text) }` or `{ unsafe(text) }`,
|
|
60
|
+
so `pg` and `postgres` are optional peer dependencies for their types only
|
|
61
|
+
and the probe adds no runtime import of either. The factory throws
|
|
62
|
+
`ProbeConfigError` at construction when the client has neither method.
|
|
63
|
+
|
|
64
|
+
## About openstatus
|
|
65
|
+
|
|
66
|
+
[openstatus](https://www.openstatus.dev/) is the open-source uptime monitoring
|
|
67
|
+
and status page platform. This package is part of
|
|
68
|
+
[`@openstatus/health`](https://github.com/openstatusHQ/health), the `/health`
|
|
69
|
+
endpoints behind openstatus's own services, extracted so any JavaScript server
|
|
70
|
+
can expose one. Point an
|
|
71
|
+
[openstatus monitor](https://www.openstatus.dev/docs/reference/http-monitor)
|
|
72
|
+
at the endpoint and assert on `status` in the body to be alerted on
|
|
73
|
+
`degraded` before it becomes `unhealthy`.
|
|
74
|
+
|
|
75
|
+
Source: [github.com/openstatusHQ/health](https://github.com/openstatusHQ/health).
|
|
76
|
+
Issues and PRs welcome.
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
[MIT](https://github.com/openstatusHQ/health/blob/main/LICENSE)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(exports, '__toESM', {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
get: function () {
|
|
28
|
+
return __toESM;
|
|
29
|
+
}
|
|
30
|
+
});
|
package/dist/mod.cjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const __openstatus_health = require_rolldown_runtime.__toESM(require("@openstatus/health"));
|
|
3
|
+
|
|
4
|
+
//#region src/mod.ts
|
|
5
|
+
/** Probe name when `name` is unset. */
|
|
6
|
+
const postgresDefaultName = "database";
|
|
7
|
+
/** A probe that runs `select 1`; critical by default. Throws `ProbeConfigError` without `query()` or `unsafe()`. */
|
|
8
|
+
function postgresProbe(options) {
|
|
9
|
+
const client = options.client;
|
|
10
|
+
let run;
|
|
11
|
+
if (typeof client?.query === "function") run = () => client.query("select 1");
|
|
12
|
+
else if (typeof client?.unsafe === "function") run = () => client.unsafe("select 1");
|
|
13
|
+
else throw new __openstatus_health.ProbeConfigError("postgresProbe", "client", `must expose query() or unsafe(), got ${describe(client)}`);
|
|
14
|
+
return {
|
|
15
|
+
name: options.name ?? postgresDefaultName,
|
|
16
|
+
critical: options.critical ?? true,
|
|
17
|
+
timeoutMs: options.timeoutMs,
|
|
18
|
+
skip: options.skip,
|
|
19
|
+
run: async () => {
|
|
20
|
+
await run();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function describe(client) {
|
|
25
|
+
if (client == null) return String(client);
|
|
26
|
+
if (typeof client !== "object" && typeof client !== "function") return typeof client;
|
|
27
|
+
const keys = Object.keys(client);
|
|
28
|
+
return keys.length === 0 ? `${typeof client === "function" ? "a function" : "an object"} with no keys` : `an object with keys ${keys.slice(0, 8).join(", ")}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
exports.postgresDefaultName = postgresDefaultName;
|
|
33
|
+
exports.postgresProbe = postgresProbe;
|
package/dist/mod.d.cts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Probe, ProbeOverrides, ProbeResult } from "@openstatus/health";
|
|
2
|
+
|
|
3
|
+
//#region src/mod.d.ts
|
|
4
|
+
|
|
5
|
+
/** Probe name when `name` is unset. */
|
|
6
|
+
declare const postgresDefaultName = "database";
|
|
7
|
+
/** The subset of a Postgres client the probe uses; one of the two methods must exist. */
|
|
8
|
+
interface PostgresLikeClient {
|
|
9
|
+
/** `pg` pools and clients, `@vercel/postgres`, Neon's `Pool` and `sql.query()`. */
|
|
10
|
+
query?(text: string): PromiseLike<ProbeResult>;
|
|
11
|
+
/** postgres.js's `sql.unsafe()`. */
|
|
12
|
+
unsafe?(query: string): PromiseLike<ProbeResult>;
|
|
13
|
+
}
|
|
14
|
+
/** Options for `postgresProbe()`. */
|
|
15
|
+
interface PostgresProbeOptions extends ProbeOverrides {
|
|
16
|
+
/** A Postgres pool, client or postgres.js `sql` instance. */
|
|
17
|
+
readonly client: PostgresLikeClient;
|
|
18
|
+
}
|
|
19
|
+
/** A probe that runs `select 1`; critical by default. Throws `ProbeConfigError` without `query()` or `unsafe()`. */
|
|
20
|
+
declare function postgresProbe(options: PostgresProbeOptions): Probe;
|
|
21
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
22
|
+
//#endregion
|
|
23
|
+
export { PostgresLikeClient, PostgresProbeOptions, postgresDefaultName, postgresProbe };
|
|
24
|
+
//# sourceMappingURL=mod.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.cts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;AA2C8D,cAjBjD,mBAAA,GAiBiD,UAAA;AAAK;UAdlD,kBAAA;;wBAEO,YAAY;;0BAEV,YAAY;;;UAIrB,oBAAA,SAA6B;;mBAE3B;;;iBAIH,aAAA,UAAuB,uBAAuB"}
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Probe, ProbeOverrides, ProbeResult } from "@openstatus/health";
|
|
2
|
+
|
|
3
|
+
//#region src/mod.d.ts
|
|
4
|
+
|
|
5
|
+
/** Probe name when `name` is unset. */
|
|
6
|
+
declare const postgresDefaultName = "database";
|
|
7
|
+
/** The subset of a Postgres client the probe uses; one of the two methods must exist. */
|
|
8
|
+
interface PostgresLikeClient {
|
|
9
|
+
/** `pg` pools and clients, `@vercel/postgres`, Neon's `Pool` and `sql.query()`. */
|
|
10
|
+
query?(text: string): PromiseLike<ProbeResult>;
|
|
11
|
+
/** postgres.js's `sql.unsafe()`. */
|
|
12
|
+
unsafe?(query: string): PromiseLike<ProbeResult>;
|
|
13
|
+
}
|
|
14
|
+
/** Options for `postgresProbe()`. */
|
|
15
|
+
interface PostgresProbeOptions extends ProbeOverrides {
|
|
16
|
+
/** A Postgres pool, client or postgres.js `sql` instance. */
|
|
17
|
+
readonly client: PostgresLikeClient;
|
|
18
|
+
}
|
|
19
|
+
/** A probe that runs `select 1`; critical by default. Throws `ProbeConfigError` without `query()` or `unsafe()`. */
|
|
20
|
+
declare function postgresProbe(options: PostgresProbeOptions): Probe;
|
|
21
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
22
|
+
//#endregion
|
|
23
|
+
export { PostgresLikeClient, PostgresProbeOptions, postgresDefaultName, postgresProbe };
|
|
24
|
+
//# sourceMappingURL=mod.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;AA2C8D,cAjBjD,mBAAA,GAiBiD,UAAA;AAAK;UAdlD,kBAAA;;wBAEO,YAAY;;0BAEV,YAAY;;;UAIrB,oBAAA,SAA6B;;mBAE3B;;;iBAIH,aAAA,UAAuB,uBAAuB"}
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ProbeConfigError } from "@openstatus/health";
|
|
2
|
+
|
|
3
|
+
//#region src/mod.ts
|
|
4
|
+
/** Probe name when `name` is unset. */
|
|
5
|
+
const postgresDefaultName = "database";
|
|
6
|
+
/** A probe that runs `select 1`; critical by default. Throws `ProbeConfigError` without `query()` or `unsafe()`. */
|
|
7
|
+
function postgresProbe(options) {
|
|
8
|
+
const client = options.client;
|
|
9
|
+
let run;
|
|
10
|
+
if (typeof client?.query === "function") run = () => client.query("select 1");
|
|
11
|
+
else if (typeof client?.unsafe === "function") run = () => client.unsafe("select 1");
|
|
12
|
+
else throw new ProbeConfigError("postgresProbe", "client", `must expose query() or unsafe(), got ${describe(client)}`);
|
|
13
|
+
return {
|
|
14
|
+
name: options.name ?? postgresDefaultName,
|
|
15
|
+
critical: options.critical ?? true,
|
|
16
|
+
timeoutMs: options.timeoutMs,
|
|
17
|
+
skip: options.skip,
|
|
18
|
+
run: async () => {
|
|
19
|
+
await run();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function describe(client) {
|
|
24
|
+
if (client == null) return String(client);
|
|
25
|
+
if (typeof client !== "object" && typeof client !== "function") return typeof client;
|
|
26
|
+
const keys = Object.keys(client);
|
|
27
|
+
return keys.length === 0 ? `${typeof client === "function" ? "a function" : "an object"} with no keys` : `an object with keys ${keys.slice(0, 8).join(", ")}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
export { postgresDefaultName, postgresProbe };
|
|
32
|
+
//# sourceMappingURL=mod.js.map
|
package/dist/mod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","names":["options: PostgresProbeOptions","run: () => PromiseLike<ProbeResult>","client: PostgresLikeClient"],"sources":["../src/mod.ts"],"sourcesContent":["/**\n * Postgres probe for `@openstatus/health`: runs `select 1` through `pg`,\n * postgres.js, `@neondatabase/serverless`, `@vercel/postgres` or any client\n * with a `query()` or `unsafe()` method.\n *\n * ```ts\n * import { Pool } from \"pg\";\n * import { postgresProbe } from \"@openstatus/health-postgres\";\n *\n * const probe = postgresProbe({ client: new Pool({ connectionString }) });\n * ```\n *\n * A timed-out check is reported `timeout` without cancelling the in-flight\n * driver query: none of the supported clients accept an `AbortSignal`.\n *\n * @module\n */\n\nimport {\n type Probe,\n ProbeConfigError,\n type ProbeOverrides,\n type ProbeResult,\n} from \"@openstatus/health\";\n\n/** Probe name when `name` is unset. */\nexport const postgresDefaultName = \"database\";\n\n/** The subset of a Postgres client the probe uses; one of the two methods must exist. */\nexport interface PostgresLikeClient {\n /** `pg` pools and clients, `@vercel/postgres`, Neon's `Pool` and `sql.query()`. */\n query?(text: string): PromiseLike<ProbeResult>;\n /** postgres.js's `sql.unsafe()`. */\n unsafe?(query: string): PromiseLike<ProbeResult>;\n}\n\n/** Options for `postgresProbe()`. */\nexport interface PostgresProbeOptions extends ProbeOverrides {\n /** A Postgres pool, client or postgres.js `sql` instance. */\n readonly client: PostgresLikeClient;\n}\n\n/** A probe that runs `select 1`; critical by default. Throws `ProbeConfigError` without `query()` or `unsafe()`. */\nexport function postgresProbe(options: PostgresProbeOptions): Probe {\n const client = options.client;\n let run: () => PromiseLike<ProbeResult>;\n if (typeof client?.query === \"function\") {\n run = () => client.query!(\"select 1\");\n } else if (typeof client?.unsafe === \"function\") {\n run = () => client.unsafe!(\"select 1\");\n } else {\n throw new ProbeConfigError(\n \"postgresProbe\",\n \"client\",\n `must expose query() or unsafe(), got ${describe(client)}`,\n );\n }\n return {\n name: options.name ?? postgresDefaultName,\n critical: options.critical ?? true,\n timeoutMs: options.timeoutMs,\n skip: options.skip,\n // The signal is ignored: no supported driver takes an AbortSignal, so a\n // timed-out query keeps running. See the README.\n run: async () => {\n await run();\n },\n };\n}\n\nfunction describe(client: PostgresLikeClient): string {\n if (client == null) return String(client);\n if (typeof client !== \"object\" && typeof client !== \"function\") {\n return typeof client;\n }\n const keys = Object.keys(client);\n return keys.length === 0\n ? `${\n typeof client === \"function\" ? \"a function\" : \"an object\"\n } with no keys`\n : `an object with keys ${keys.slice(0, 8).join(\", \")}`;\n}\n"],"mappings":";;;;AA0BA,MAAa,sBAAsB;;AAiBnC,SAAgB,cAAcA,SAAsC;CAClE,MAAM,SAAS,QAAQ;CACvB,IAAIC;AACJ,YAAW,QAAQ,UAAU,WAC3B,OAAM,MAAM,OAAO,MAAO,WAAW;iBACrB,QAAQ,WAAW,WACnC,OAAM,MAAM,OAAO,OAAQ,WAAW;KAEtC,OAAM,IAAI,iBACR,iBACA,WACC,uCAAuC,SAAS,OAAO,CAAC;AAG7D,QAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,UAAU,QAAQ,YAAY;EAC9B,WAAW,QAAQ;EACnB,MAAM,QAAQ;EAGd,KAAK,YAAY;AACf,SAAM,KAAK;EACZ;CACF;AACF;AAED,SAAS,SAASC,QAAoC;AACpD,KAAI,UAAU,KAAM,QAAO,OAAO,OAAO;AACzC,YAAW,WAAW,mBAAmB,WAAW,WAClD,eAAc;CAEhB,MAAM,OAAO,OAAO,KAAK,OAAO;AAChC,QAAO,KAAK,WAAW,KAClB,SACM,WAAW,aAAa,eAAe,YAC/C,kBACE,sBAAsB,KAAK,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,CAAC;AACxD"}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openstatus/health-postgres",
|
|
3
|
+
"version": "0.1.4-dev.0",
|
|
4
|
+
"description": "Postgres probe for @openstatus/health",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"openstatus",
|
|
7
|
+
"health",
|
|
8
|
+
"healthcheck",
|
|
9
|
+
"postgres",
|
|
10
|
+
"postgresql",
|
|
11
|
+
"pg",
|
|
12
|
+
"database"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "openstatus",
|
|
17
|
+
"url": "https://www.openstatus.dev/"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/openstatusHQ/health",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/openstatusHQ/health.git",
|
|
23
|
+
"directory": "packages/postgres/"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/openstatusHQ/health/issues"
|
|
27
|
+
},
|
|
28
|
+
"type": "module",
|
|
29
|
+
"module": "./dist/mod.js",
|
|
30
|
+
"main": "./dist/mod.cjs",
|
|
31
|
+
"types": "./dist/mod.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": {
|
|
35
|
+
"import": "./dist/mod.d.ts",
|
|
36
|
+
"require": "./dist/mod.d.cts"
|
|
37
|
+
},
|
|
38
|
+
"import": "./dist/mod.js",
|
|
39
|
+
"require": "./dist/mod.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"files": [
|
|
45
|
+
"dist/"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@openstatus/health": "^0.1.4-dev.0",
|
|
52
|
+
"pg": ">=8.0.0",
|
|
53
|
+
"postgres": ">=3.0.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependenciesMeta": {
|
|
56
|
+
"pg": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"postgres": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"pg": "^8.16.0",
|
|
65
|
+
"@types/pg": "^8.15.0",
|
|
66
|
+
"postgres": "^3.4.0",
|
|
67
|
+
"tsdown": "^0.12.7",
|
|
68
|
+
"typescript": "^5.8.3"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "tsdown",
|
|
72
|
+
"prepack": "tsdown",
|
|
73
|
+
"test": "node --experimental-transform-types --test"
|
|
74
|
+
}
|
|
75
|
+
}
|