@shivaedev/local-postgres 0.7.0 → 0.9.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/CHANGELOG.md +23 -0
- package/README.md +51 -38
- package/dist/server/LocalPostgresError.d.ts +8 -0
- package/dist/server/LocalPostgresError.d.ts.map +1 -0
- package/dist/server/LocalPostgresError.js +6 -0
- package/dist/server/LocalPostgresError.js.map +1 -0
- package/dist/server/PostgresCommandError.d.ts +11 -0
- package/dist/server/PostgresCommandError.d.ts.map +1 -0
- package/dist/server/PostgresCommandError.js +9 -0
- package/dist/server/PostgresCommandError.js.map +1 -0
- package/dist/server/PostgresVersionMismatch.d.ts +8 -5
- package/dist/server/PostgresVersionMismatch.d.ts.map +1 -1
- package/dist/server/PostgresVersionMismatch.js +7 -7
- package/dist/server/PostgresVersionMismatch.js.map +1 -1
- package/dist/server/clientConnection.d.ts +13 -0
- package/dist/server/clientConnection.d.ts.map +1 -0
- package/dist/server/clientConnection.js +19 -0
- package/dist/server/clientConnection.js.map +1 -0
- package/dist/server/localConnections.d.ts +16 -6
- package/dist/server/localConnections.d.ts.map +1 -1
- package/dist/server/localConnections.js +30 -24
- package/dist/server/localConnections.js.map +1 -1
- package/dist/server/localContainer.d.ts +8 -4
- package/dist/server/localContainer.d.ts.map +1 -1
- package/dist/server/localContainer.js +24 -36
- package/dist/server/localContainer.js.map +1 -1
- package/dist/server/localDocker.d.ts +7 -2
- package/dist/server/localDocker.d.ts.map +1 -1
- package/dist/server/localDocker.js +17 -9
- package/dist/server/localDocker.js.map +1 -1
- package/dist/server/localPostgres.d.ts +8 -5
- package/dist/server/localPostgres.d.ts.map +1 -1
- package/dist/server/localPostgres.js +53 -82
- package/dist/server/localPostgres.js.map +1 -1
- package/dist/server/runCommand.d.ts +7 -0
- package/dist/server/runCommand.d.ts.map +1 -0
- package/dist/server/runCommand.js +23 -0
- package/dist/server/runCommand.js.map +1 -0
- package/package.json +8 -4
- package/src/server/LocalPostgresError.ts +5 -0
- package/src/server/PostgresCommandError.ts +8 -0
- package/src/server/PostgresVersionMismatch.ts +7 -7
- package/src/server/clientConnection.ts +20 -0
- package/src/server/localConnections.ts +42 -24
- package/src/server/localContainer.ts +32 -36
- package/src/server/localDocker.ts +18 -10
- package/src/server/localPostgres.ts +62 -85
- package/src/server/runCommand.ts +31 -0
|
@@ -1,51 +1,47 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { clientConnection } from "#clientConnection.ts";
|
|
3
|
+
import { LocalPostgresError } from "#LocalPostgresError.ts";
|
|
4
|
+
import type { docker as runDocker } from "#localDocker.ts";
|
|
5
|
+
import { PostgresCommandError } from "#PostgresCommandError.ts";
|
|
6
|
+
import type { CommandOptions } from "#runCommand.ts";
|
|
3
7
|
|
|
4
8
|
export const POSTGRES_IMAGE = "postgres:18.6-alpine@sha256:77f585114c32fbca283dc835b0596f4e52b51b4c6662d7810b2f4084f60a1873";
|
|
5
9
|
const CONTAINER = "development-postgres";
|
|
6
|
-
type Docker = (args: readonly string[], options?:
|
|
10
|
+
type Docker = (args: readonly string[], options?: CommandOptions) => ReturnType<typeof runDocker>;
|
|
7
11
|
|
|
8
|
-
export
|
|
9
|
-
|
|
12
|
+
export const sqlInContainer = Effect.fn("LocalPostgres.sqlInContainer")(function* (
|
|
13
|
+
docker: Docker,
|
|
14
|
+
url: URL,
|
|
15
|
+
args: readonly string[],
|
|
16
|
+
options: CommandOptions,
|
|
17
|
+
) {
|
|
18
|
+
const id = yield* docker(["ps", "--filter", "publish=55432", "--format", "{{.ID}}"], options);
|
|
10
19
|
if (!id || id.includes("\n")) {
|
|
11
|
-
|
|
20
|
+
return yield* new PostgresCommandError({ command: "psql", message: "Install psql or start the shared PostgreSQL service with Docker." });
|
|
12
21
|
}
|
|
13
|
-
if (docker(["inspect", "--format", "{{.Config.Image}}", id], options)
|
|
14
|
-
|
|
22
|
+
if ((yield* docker(["inspect", "--format", "{{.Config.Image}}", id], options)) !== POSTGRES_IMAGE) {
|
|
23
|
+
return yield* new LocalPostgresError({
|
|
24
|
+
message: "The shared PostgreSQL image differs from the pinned baseline. Upgrade explicitly, preserving data.",
|
|
25
|
+
});
|
|
15
26
|
}
|
|
16
|
-
|
|
17
|
-
const password = new URLSearchParams(url.search.replaceAll("+", "%2B")).getAll("password").at(-1) ?? decodeURIComponent(url.password);
|
|
18
|
-
const inside = new URL(url);
|
|
27
|
+
const { url: inside, options: settings } = yield* clientConnection(url, options);
|
|
19
28
|
inside.hostname = "127.0.0.1";
|
|
20
29
|
inside.port = "5432";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
return docker(["exec", "--env", "PGPASSWORD", id, "psql", ...args, "--dbname", inside.toString()], {
|
|
25
|
-
...options,
|
|
26
|
-
env: { ...env, ...options.env, ...(password && { "PGPASSWORD": password }) },
|
|
27
|
-
}).trim();
|
|
28
|
-
} catch (error) {
|
|
29
|
-
throw new Error(error instanceof Error && "stderr" in error ? String(error.stderr) : "PostgreSQL query failed.", { cause: error });
|
|
30
|
-
}
|
|
31
|
-
}
|
|
30
|
+
return yield* docker(["exec", "--env", "PGPASSWORD", id, "psql", ...args, "--dbname", inside.toString()], settings);
|
|
31
|
+
});
|
|
32
32
|
|
|
33
|
-
export
|
|
34
|
-
docker(["info"]);
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
docker(["container", "inspect", CONTAINER]);
|
|
38
|
-
existing = true;
|
|
39
|
-
} catch {
|
|
40
|
-
existing = false;
|
|
41
|
-
}
|
|
33
|
+
export const startContainer = Effect.fn("LocalPostgres.startContainer")(function* (docker: Docker) {
|
|
34
|
+
yield* docker(["info"]);
|
|
35
|
+
const existing = yield* docker(["container", "ls", "--all", "--filter", `name=^/${CONTAINER}$`, "--format", "{{.ID}}"]);
|
|
42
36
|
if (existing) {
|
|
43
|
-
if (docker(["inspect", "--format", "{{.Config.Image}}", CONTAINER])
|
|
44
|
-
|
|
37
|
+
if ((yield* docker(["inspect", "--format", "{{.Config.Image}}", CONTAINER])) !== POSTGRES_IMAGE) {
|
|
38
|
+
return yield* new LocalPostgresError({
|
|
39
|
+
message: "The shared PostgreSQL container uses a different image. Preserve its volume and upgrade it explicitly.",
|
|
40
|
+
});
|
|
45
41
|
}
|
|
46
|
-
docker(["start", CONTAINER]);
|
|
42
|
+
yield* docker(["start", CONTAINER]);
|
|
47
43
|
} else {
|
|
48
|
-
docker([
|
|
44
|
+
yield* docker([
|
|
49
45
|
"run",
|
|
50
46
|
"--detach",
|
|
51
47
|
"--name",
|
|
@@ -61,4 +57,4 @@ export function startContainer(docker: Docker) {
|
|
|
61
57
|
POSTGRES_IMAGE,
|
|
62
58
|
]);
|
|
63
59
|
}
|
|
64
|
-
}
|
|
60
|
+
});
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { LocalPostgresError } from "#LocalPostgresError.ts";
|
|
3
|
+
import { type CommandOptions, runCommand } from "#runCommand.ts";
|
|
2
4
|
|
|
3
5
|
export type DockerEnvironment = Readonly<{ "DOCKER_CONTEXT"?: string | undefined; "DOCKER_HOST"?: string | undefined }>;
|
|
4
6
|
|
|
5
|
-
export
|
|
7
|
+
export const docker = Effect.fn("LocalPostgres.docker")(function* (
|
|
6
8
|
environment: DockerEnvironment,
|
|
7
9
|
args: readonly string[],
|
|
8
|
-
options:
|
|
10
|
+
options: CommandOptions = {},
|
|
9
11
|
) {
|
|
10
12
|
const context = environment.DOCKER_CONTEXT;
|
|
11
13
|
const host = environment.DOCKER_HOST;
|
|
14
|
+
if ((["DOCKER_CONTEXT", "DOCKER_HOST"] as const).some((key) => options.env?.[key] && options.env[key] !== environment[key])) {
|
|
15
|
+
return yield* new LocalPostgresError({ message: "Docker command environment conflicts with the Docker endpoint settings." });
|
|
16
|
+
}
|
|
17
|
+
const settings = {
|
|
18
|
+
...options,
|
|
19
|
+
env: { ...environment, ...options.env, "DOCKER_CONTEXT": context ?? "", "DOCKER_HOST": host ?? "" },
|
|
20
|
+
extendEnv: true,
|
|
21
|
+
};
|
|
12
22
|
const endpoint =
|
|
13
23
|
(!context && host)
|
|
14
|
-
||
|
|
15
|
-
|
|
16
|
-
}).trim();
|
|
17
|
-
const url = new URL(endpoint);
|
|
24
|
+
|| (yield* runCommand("docker", ["context", "inspect", ...(context ? [context] : []), "--format", "{{.Endpoints.docker.Host}}"], settings));
|
|
25
|
+
const url = yield* Effect.try({ catch: () => new LocalPostgresError({ message: "Invalid Docker endpoint." }), try: () => new URL(endpoint) });
|
|
18
26
|
if (
|
|
19
27
|
!(
|
|
20
28
|
(["unix:", "npipe:"].includes(url.protocol) && ["", "localhost"].includes(url.hostname))
|
|
21
29
|
|| (url.protocol === "tcp:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
|
|
22
30
|
)
|
|
23
31
|
) {
|
|
24
|
-
|
|
32
|
+
return yield* new LocalPostgresError({ message: "Local database setup requires a local Docker endpoint; remote contexts are refused." });
|
|
25
33
|
}
|
|
26
|
-
return
|
|
27
|
-
}
|
|
34
|
+
return yield* runCommand("docker", args, { ...settings, env: { ...settings.env, "DOCKER_CONTEXT": "", "DOCKER_HOST": endpoint } });
|
|
35
|
+
});
|
|
@@ -1,31 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { Effect, Schedule } from "effect";
|
|
2
|
+
import { clientConnection } from "#clientConnection.ts";
|
|
3
|
+
import { LocalPostgresError } from "#LocalPostgresError.ts";
|
|
4
|
+
import { ensureConnections } from "#localConnections.ts";
|
|
5
|
+
import { sqlInContainer, startContainer } from "#localContainer.ts";
|
|
6
|
+
import { type DockerEnvironment, docker } from "#localDocker.ts";
|
|
7
|
+
import { PostgresVersionMismatch } from "#PostgresVersionMismatch.ts";
|
|
8
|
+
import { type CommandOptions, missingCommand, runCommand } from "#runCommand.ts";
|
|
7
9
|
|
|
8
10
|
export const LOCAL_SERVER = "postgresql://postgres:postgres@127.0.0.1:55432";
|
|
9
11
|
|
|
10
|
-
export
|
|
11
|
-
const
|
|
12
|
-
const
|
|
12
|
+
export const assertLocalDatabase = Effect.fn("LocalPostgres.assertLocalDatabase")(function* (value: string, names: readonly string[]) {
|
|
13
|
+
const message = `Use a local database on 127.0.0.1:55432 named ${names.join(" or ")}.`;
|
|
14
|
+
const url = yield* Effect.try({ catch: () => new LocalPostgresError({ message }), try: () => new URL(value) });
|
|
13
15
|
if (
|
|
14
16
|
!(["postgres:", "postgresql:"].includes(url.protocol) && ["localhost", "127.0.0.1"].includes(url.hostname))
|
|
15
17
|
|| url.port !== "55432"
|
|
16
18
|
|| ["host", "hostaddr", "port", "dbname", "service"].some((key) => url.searchParams.has(key))
|
|
17
|
-
|| !names.includes(
|
|
19
|
+
|| !names.includes(url.pathname.slice(1))
|
|
18
20
|
) {
|
|
19
|
-
|
|
21
|
+
return yield* new LocalPostgresError({ message });
|
|
20
22
|
}
|
|
21
23
|
return url;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function clientEnvironment(url: URL) {
|
|
25
|
-
// libpq treats + literally in URI passwords.
|
|
26
|
-
const password = new URLSearchParams(url.search.replaceAll("+", "%2B")).getAll("password").at(-1) ?? decodeURIComponent(url.password);
|
|
27
|
-
return { ...env, ...(password && { "PGPASSWORD": password }) };
|
|
28
|
-
}
|
|
24
|
+
});
|
|
29
25
|
|
|
30
26
|
export interface ReadinessOptions {
|
|
31
27
|
readonly attempts?: number;
|
|
@@ -33,90 +29,71 @@ export interface ReadinessOptions {
|
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
export function localPostgres(environment: DockerEnvironment, { attempts = 30, delayMillis = 1000 }: ReadinessOptions = {}) {
|
|
36
|
-
function localDocker(args: readonly string[], options
|
|
32
|
+
function localDocker(args: readonly string[], options?: CommandOptions) {
|
|
37
33
|
return docker(environment, args, options);
|
|
38
34
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
for (const key of ["schema", "connection_limit", "pool_timeout", "pgbouncer"]) {
|
|
46
|
-
url.searchParams.delete(key);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const args = ["-X", "--set", "ON_ERROR_STOP=1", "-At", "-c", query];
|
|
50
|
-
if (!url.searchParams.has("connect_timeout")) {
|
|
51
|
-
url.searchParams.set("connect_timeout", "3");
|
|
52
|
-
}
|
|
53
|
-
const options = {
|
|
54
|
-
encoding: "utf8",
|
|
55
|
-
env: childEnvironment,
|
|
56
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
57
|
-
} satisfies ExecFileSyncOptionsWithStringEncoding;
|
|
58
|
-
try {
|
|
59
|
-
return execFileSync("psql", [...args, "--dbname", url.toString()], options).trim();
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
62
|
-
return sqlInContainer(localDocker, url, args, options);
|
|
63
|
-
}
|
|
64
|
-
throw new Error(error instanceof Error && "stderr" in error ? String(error.stderr) : "PostgreSQL query failed.", { cause: error });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function startPostgres(connection = LOCAL_SERVER) {
|
|
69
|
-
const server = new URL(connection);
|
|
35
|
+
const sql = localSql(localDocker);
|
|
36
|
+
const startPostgres = Effect.fn("LocalPostgres.startPostgres")(function* (connection = LOCAL_SERVER) {
|
|
37
|
+
const server = yield* Effect.try({
|
|
38
|
+
catch: () => new LocalPostgresError({ message: "Invalid local database URL." }),
|
|
39
|
+
try: () => new URL(connection),
|
|
40
|
+
});
|
|
70
41
|
server.pathname = "/postgres";
|
|
71
|
-
assertLocalDatabase(server.toString(), ["postgres"]);
|
|
42
|
+
yield* assertLocalDatabase(server.toString(), ["postgres"]);
|
|
72
43
|
function waitForVersion() {
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
return sql(server, "SHOW server_version");
|
|
76
|
-
} catch {
|
|
77
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMillis);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return undefined;
|
|
44
|
+
return sql(server, "SHOW server_version").pipe(Effect.retry({ schedule: Schedule.spaced(delayMillis), times: attempts - 1 }));
|
|
81
45
|
}
|
|
82
|
-
function confirmVersion(observed: string
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
let version: string | undefined;
|
|
88
|
-
try {
|
|
89
|
-
version = sql(server, "SHOW server_version");
|
|
90
|
-
} catch {
|
|
91
|
-
startContainer(localDocker);
|
|
92
|
-
version = waitForVersion();
|
|
46
|
+
function confirmVersion(observed: string) {
|
|
47
|
+
return /^18\.6(?:\s|$)/u.test(observed)
|
|
48
|
+
? Effect.void
|
|
49
|
+
: Effect.fail(new PostgresVersionMismatch({ expectedVersion: "18.6", observedVersion: observed }));
|
|
93
50
|
}
|
|
94
|
-
|
|
95
|
-
|
|
51
|
+
const version = yield* sql(server, "SHOW server_version").pipe(
|
|
52
|
+
Effect.catchTag("PostgresCommandError", () => Effect.andThen(startContainer(localDocker), waitForVersion)),
|
|
53
|
+
);
|
|
54
|
+
yield* confirmVersion(version);
|
|
55
|
+
yield* ensureConnections(
|
|
96
56
|
localDocker,
|
|
97
57
|
(query) => sql(server, query),
|
|
98
|
-
() =>
|
|
58
|
+
() => Effect.flatMap(waitForVersion(), confirmVersion),
|
|
99
59
|
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
60
|
+
});
|
|
61
|
+
const prepareDatabases = Effect.fn("LocalPostgres.prepareDatabases")(function* (values: readonly string[]) {
|
|
62
|
+
const urls: URL[] = [];
|
|
103
63
|
for (const value of values) {
|
|
104
|
-
const
|
|
105
|
-
|
|
64
|
+
const parsed = yield* Effect.try({
|
|
65
|
+
catch: () => new LocalPostgresError({ message: "Invalid local database URL." }),
|
|
66
|
+
try: () => new URL(value),
|
|
67
|
+
});
|
|
68
|
+
const name = parsed.pathname.slice(1);
|
|
69
|
+
const url = yield* assertLocalDatabase(value, [name]);
|
|
106
70
|
if (!/^[a-z][a-z0-9_]{0,62}$/u.test(name)) {
|
|
107
|
-
|
|
71
|
+
return yield* new LocalPostgresError({ message: "Invalid local database name." });
|
|
108
72
|
}
|
|
73
|
+
urls.push(url);
|
|
109
74
|
}
|
|
110
|
-
startPostgres(values[0]);
|
|
111
|
-
for (const
|
|
112
|
-
const url = new URL(value);
|
|
75
|
+
yield* startPostgres(values[0]);
|
|
76
|
+
for (const url of urls) {
|
|
113
77
|
const name = url.pathname.slice(1);
|
|
114
78
|
const server = new URL(url);
|
|
115
79
|
server.pathname = "/postgres";
|
|
116
|
-
if (!sql(server, `SELECT 1 FROM pg_database WHERE datname = '${name}'`)) {
|
|
117
|
-
sql(server, `CREATE DATABASE "${name}"`);
|
|
80
|
+
if (!(yield* sql(server, `SELECT 1 FROM pg_database WHERE datname = '${name}'`))) {
|
|
81
|
+
yield* sql(server, `CREATE DATABASE "${name}"`);
|
|
118
82
|
}
|
|
119
83
|
}
|
|
120
|
-
}
|
|
84
|
+
});
|
|
121
85
|
return { prepareDatabases, sql, startPostgres };
|
|
122
86
|
}
|
|
87
|
+
|
|
88
|
+
function localSql(localDocker: (args: readonly string[], options?: CommandOptions) => ReturnType<typeof docker>) {
|
|
89
|
+
return Effect.fn("LocalPostgres.sql")(function* (value: string | URL, query: string) {
|
|
90
|
+
const parsed = yield* Effect.try({ catch: () => new LocalPostgresError({ message: "Invalid local database URL." }), try: () => new URL(value) });
|
|
91
|
+
const { url, options } = yield* clientConnection(yield* assertLocalDatabase(parsed.toString(), [parsed.pathname.slice(1)]));
|
|
92
|
+
const args = ["-X", "--set", "ON_ERROR_STOP=1", "-At", "-c", query];
|
|
93
|
+
return yield* runCommand("psql", [...args, "--dbname", url.toString()], options).pipe(
|
|
94
|
+
Effect.catchTag("PostgresCommandError", (error) =>
|
|
95
|
+
missingCommand(error) ? sqlInContainer(localDocker, url, args, options) : Effect.fail(error),
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Effect, PlatformError, Stream } from "effect";
|
|
2
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
3
|
+
import { PostgresCommandError } from "#PostgresCommandError.ts";
|
|
4
|
+
|
|
5
|
+
export type CommandOptions = Pick<ChildProcess.CommandOptions, "env" | "extendEnv">;
|
|
6
|
+
|
|
7
|
+
export const runCommand = Effect.fn("LocalPostgres.runCommand")(function* (command: string, args: readonly string[], options: CommandOptions = {}) {
|
|
8
|
+
const label = command === "docker" ? `docker ${args[0] ?? ""}`.trim() : command;
|
|
9
|
+
return yield* Effect.scoped(
|
|
10
|
+
Effect.gen(function* () {
|
|
11
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
12
|
+
const child = yield* spawner.spawn(ChildProcess.make(command, args, { ...options, stdin: "ignore" }));
|
|
13
|
+
const [stdout, stderr, code] = yield* Effect.all(
|
|
14
|
+
[Stream.mkString(Stream.decodeText(child.stdout)), Stream.mkString(Stream.decodeText(child.stderr)), child.exitCode],
|
|
15
|
+
{ concurrency: "unbounded" },
|
|
16
|
+
);
|
|
17
|
+
if (code !== 0) {
|
|
18
|
+
return yield* new PostgresCommandError({
|
|
19
|
+
command: label,
|
|
20
|
+
exitCode: Number(code),
|
|
21
|
+
message: stderr.trim() || `${command} exited with code ${code}.`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return stdout.trim();
|
|
25
|
+
}),
|
|
26
|
+
).pipe(Effect.catchTag("PlatformError", (cause) => Effect.fail(new PostgresCommandError({ cause, command: label, message: cause.message }))));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export function missingCommand(error: PostgresCommandError) {
|
|
30
|
+
return error.cause instanceof PlatformError.PlatformError && error.cause.reason._tag === "NotFound";
|
|
31
|
+
}
|