@graftland/plugin-remote 0.3.1 → 0.3.2
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/dist/Auth.d.ts +2 -2
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js.map +1 -1
- package/dist/{Bootstrap-Dr6sX7el.js → Bootstrap-H9gDsgsC.js} +2 -2
- package/dist/{Contract-DL2mHvaT.js → Contract-DsJtisvL.js} +2 -2
- package/dist/Contract.js +1 -1
- package/dist/{Errors-BMKmu5Y7.js → Errors-DYhsGZcZ.js} +3 -1
- package/dist/Errors.d.ts +7 -0
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +3 -1
- package/dist/Errors.js.map +1 -1
- package/dist/{FailureGroup-BcH0XOBi.js → FailureGroup-BgT73AaR.js} +14 -4
- package/dist/{Framing-zaYdOJnT.js → Framing-G9eSaeLb.js} +1 -1
- package/dist/{Graft-CVyueNOf.js → Graft-B71CPA2Y.js} +20 -16
- package/dist/{Packages-JNL34JpS.js → Packages-AgoCGotL.js} +23 -26
- package/dist/{Peer-BT7eAGmh.js → Peer-DXeVrGBh.js} +5 -5
- package/dist/{ProcessObservation-DEGrEMfP.js → ProcessObservation-gLgGirYv.js} +1 -1
- package/dist/{ServeLifecycle-CJM0gG5B.js → ServeLifecycle-Bd1FPJ6e.js} +1 -1
- package/dist/Transport.d.ts +2 -2
- package/dist/Transport.d.ts.map +1 -1
- package/dist/Transport.js.map +1 -1
- package/dist/bin.js +48 -47
- package/dist/bin.js.map +1 -1
- package/dist/{childRuntime-An0HsIzl.js → childRuntime-Bm3iSg_N.js} +29 -26
- package/dist/{connectionEnded-DCpFCjMX.js → connectionEnded-BwcxQ7W8.js} +4 -4
- package/dist/{entry-B89sepMU.js → entry-psOpX3Bj.js} +72 -35
- package/dist/host/auth/bearer.js +1 -1
- package/dist/host/auth/jwt.js +1 -1
- package/dist/host/auth/mtls.js +2 -2
- package/dist/host/auth/none.js +1 -1
- package/dist/host/command.js +4 -4
- package/dist/host/local.js +3 -3
- package/dist/host/unix.js +3 -3
- package/dist/host/websocket.js +3 -3
- package/dist/host/worker.js +4 -5
- package/dist/{importer-Bk6U_sn4.js → importer-Cvf88eAY.js} +5 -8
- package/dist/index.js +9 -9
- package/dist/index.js.map +1 -1
- package/dist/internal/Certificates.js +4 -4
- package/dist/internal/Certificates.js.map +1 -1
- package/dist/internal/FailureGroup.js +3 -2
- package/dist/internal/FailureGroup.js.map +1 -1
- package/dist/internal/assembly.js +9 -2
- package/dist/internal/assembly.js.map +1 -1
- package/dist/internal/auth/mtls.js +1 -1
- package/dist/internal/auth/mtls.js.map +1 -1
- package/dist/internal/child.js +20 -17
- package/dist/internal/child.js.map +1 -1
- package/dist/internal/childRuntime.js +4 -4
- package/dist/internal/childRuntime.js.map +1 -1
- package/dist/internal/directTransport.d.ts +1 -1
- package/dist/internal/entry.d.ts.map +1 -1
- package/dist/internal/entry.js +1 -1
- package/dist/internal/entry.js.map +1 -1
- package/dist/internal/execution.js +40 -5
- package/dist/internal/execution.js.map +1 -1
- package/dist/internal/host/runner.js +7 -6
- package/dist/internal/host/runner.js.map +1 -1
- package/dist/internal/host.js +5 -5
- package/dist/internal/host.js.map +1 -1
- package/dist/internal/port.js +5 -5
- package/dist/internal/port.js.map +1 -1
- package/dist/internal/release.js +1 -1
- package/dist/internal/release.js.map +1 -1
- package/dist/internal/runner/Connection.d.ts +1 -1
- package/dist/internal/runner/Connection.js +4 -4
- package/dist/internal/runner/Connection.js.map +1 -1
- package/dist/internal/runner/Execution.js +3 -3
- package/dist/internal/runner/Execution.js.map +1 -1
- package/dist/internal/runner/Framing.js +1 -1
- package/dist/internal/runner/Framing.js.map +1 -1
- package/dist/internal/runner/Local.js +1 -1
- package/dist/internal/runner/Local.js.map +1 -1
- package/dist/internal/runner/Lock.js +1 -1
- package/dist/internal/runner/Lock.js.map +1 -1
- package/dist/internal/runner/Packages.d.ts +1 -1
- package/dist/internal/runner/Packages.js +22 -25
- package/dist/internal/runner/Packages.js.map +1 -1
- package/dist/internal/runner/Processes.js +7 -9
- package/dist/internal/runner/Processes.js.map +1 -1
- package/dist/internal/runner/Store.js +3 -3
- package/dist/internal/runner/Store.js.map +1 -1
- package/dist/internal/runner/Supervisor.js +46 -47
- package/dist/internal/runner/Supervisor.js.map +1 -1
- package/dist/internal/runner/accept.js +3 -3
- package/dist/internal/runner/accept.js.map +1 -1
- package/dist/internal/runner/model.js +1 -1
- package/dist/internal/runner/model.js.map +1 -1
- package/dist/internal/serve.js +1 -1
- package/dist/internal/serve.js.map +1 -1
- package/dist/internal/supervisorPlugin.js +1 -1
- package/dist/internal/watchdogMain.js +5 -5
- package/dist/internal/watchdogMain.js.map +1 -1
- package/dist/internal/websocketServer.js +3 -1
- package/dist/internal/websocketServer.js.map +1 -1
- package/dist/internal/workerMain.js +3 -3
- package/dist/{port-DSVUY8Bq.js → port--3x2zJ4D.js} +8 -8
- package/dist/{release-BY4Mlluy.js → release-DfZPuIcK.js} +1 -1
- package/dist/runner/auth/bearer.js +2 -2
- package/dist/runner/auth/jwt.js +4 -4
- package/dist/runner/auth/jwt.js.map +1 -1
- package/dist/runner/auth/mtls.js +3 -3
- package/dist/runner/auth/none.js +1 -1
- package/dist/runner/index.js +5 -5
- package/dist/runner/stdio.js +3 -3
- package/dist/runner/unix.js +3 -3
- package/dist/runner/websocket.js +4 -4
- package/dist/{socketChannel-C_ppdSzz.js → socketChannel-CJvL2DiB.js} +2 -2
- package/dist/{supervisorPlugin-CivhM7x5.js → supervisorPlugin-t6Z6PrhD.js} +72 -76
- package/dist/{token-Cc_1CTbE.js → token-CYBNNlhS.js} +1 -1
- package/dist/{tokenCredentials-CI6mMRRd.js → tokenCredentials-CbCuk-Rt.js} +3 -3
- package/dist/{websocketServer-D2Hk7wsA.js → websocketServer-NQhODlEt.js} +5 -3
- package/dist/{workerChannel-cmLc13vm.js → workerChannel-C8hhBJzA.js} +1 -1
- package/package.json +3 -3
- package/src/Auth.ts +9 -2
- package/src/Errors.ts +8 -0
- package/src/Transport.ts +6 -2
- package/src/bin.ts +28 -30
- package/src/index.ts +5 -3
- package/src/internal/Certificates.ts +10 -13
- package/src/internal/FailureGroup.ts +128 -121
- package/src/internal/assembly.ts +19 -10
- package/src/internal/auth/mtls.ts +19 -20
- package/src/internal/child.ts +248 -239
- package/src/internal/childRuntime.ts +81 -80
- package/src/internal/entry.ts +10 -11
- package/src/internal/execution.ts +69 -12
- package/src/internal/host/runner.ts +153 -153
- package/src/internal/host.ts +116 -120
- package/src/internal/port.ts +100 -103
- package/src/internal/release.ts +3 -2
- package/src/internal/runner/Connection.ts +52 -60
- package/src/internal/runner/Execution.ts +163 -164
- package/src/internal/runner/Framing.ts +9 -10
- package/src/internal/runner/Local.ts +26 -27
- package/src/internal/runner/Lock.ts +23 -25
- package/src/internal/runner/Packages.ts +67 -70
- package/src/internal/runner/Processes.ts +89 -92
- package/src/internal/runner/Store.ts +110 -114
- package/src/internal/runner/Supervisor.ts +392 -405
- package/src/internal/runner/accept.ts +76 -84
- package/src/internal/runner/model.ts +20 -21
- package/src/internal/serve.ts +15 -13
- package/src/internal/watchdogMain.ts +16 -11
- package/src/internal/websocketServer.ts +18 -3
- package/src/runner/auth/jwt.ts +10 -6
- package/dist/cleanup-CXLa1Wd9.js +0 -13
|
@@ -8,102 +8,99 @@ import {
|
|
|
8
8
|
type Termination,
|
|
9
9
|
} from "./model"
|
|
10
10
|
|
|
11
|
-
const inspect = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return yield* ExecutionError.make({
|
|
38
|
-
detail: "Cannot inspect process identity: " + stderr,
|
|
39
|
-
})
|
|
40
|
-
if (String.startsWith("Z")(status)) return Option.none()
|
|
41
|
-
return Option.some({ born })
|
|
42
|
-
}),
|
|
43
|
-
).pipe(
|
|
44
|
-
Effect.mapError((error) =>
|
|
45
|
-
error._tag === "ExecutionError"
|
|
46
|
-
? error
|
|
47
|
-
: ExecutionError.make({
|
|
48
|
-
detail: "Inspecting process identity failed",
|
|
49
|
-
cause: Cause.fail(error),
|
|
50
|
-
}),
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
export const identity = (pid: number) =>
|
|
55
|
-
Effect.gen(function* () {
|
|
56
|
-
const current = yield* inspect(pid)
|
|
57
|
-
if (Option.isNone(current))
|
|
11
|
+
const inspect = Effect.fnUntraced(
|
|
12
|
+
function* (pid: number) {
|
|
13
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
14
|
+
const child = yield* spawner.spawn(
|
|
15
|
+
ChildProcess.make(
|
|
16
|
+
"ps",
|
|
17
|
+
["-p", `${pid}`, "-o", "lstart=", "-o", "stat="],
|
|
18
|
+
{
|
|
19
|
+
env: { LC_ALL: "C" },
|
|
20
|
+
extendEnv: true,
|
|
21
|
+
stdin: "ignore",
|
|
22
|
+
},
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
const [code, output, stderr] = yield* Effect.all(
|
|
26
|
+
[
|
|
27
|
+
child.exitCode,
|
|
28
|
+
child.stdout.pipe(Stream.decodeText(), Stream.mkString),
|
|
29
|
+
child.stderr.pipe(Stream.decodeText(), Stream.mkString),
|
|
30
|
+
],
|
|
31
|
+
{ concurrency: "unbounded" },
|
|
32
|
+
)
|
|
33
|
+
if (code === 1 && String.trim(output) === "") return Option.none()
|
|
34
|
+
const [, born, status] =
|
|
35
|
+
/^(.{24})\s+(\S+)$/m.exec(String.trim(output)) ?? []
|
|
36
|
+
if (code !== 0 || born === undefined || status === undefined)
|
|
58
37
|
return yield* ExecutionError.make({
|
|
59
|
-
detail: "
|
|
38
|
+
detail: "Cannot inspect process identity: " + stderr,
|
|
60
39
|
})
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
if (String.startsWith("Z")(status)) return Option.none()
|
|
41
|
+
return Option.some({ born })
|
|
42
|
+
},
|
|
43
|
+
Effect.scoped,
|
|
44
|
+
Effect.mapError((error) =>
|
|
45
|
+
error._tag === "ExecutionError"
|
|
46
|
+
? error
|
|
47
|
+
: ExecutionError.make({
|
|
48
|
+
detail: "Inspecting process identity failed",
|
|
49
|
+
cause: Cause.fail(error),
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
export const identity = Effect.fnUntraced(function* (pid: number) {
|
|
55
|
+
const current = yield* inspect(pid)
|
|
56
|
+
if (Option.isNone(current))
|
|
57
|
+
return yield* ExecutionError.make({
|
|
58
|
+
detail: "The child exited before its identity could be recorded",
|
|
59
|
+
})
|
|
60
|
+
return { pid, start: current.value }
|
|
61
|
+
})
|
|
63
62
|
|
|
64
63
|
// The OS may discard argv before reporting a zombie; PID and start time remain the identity.
|
|
65
|
-
export const matches = (child: ProcessIdentity)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
})
|
|
64
|
+
export const matches = Effect.fnUntraced(function* (child: ProcessIdentity) {
|
|
65
|
+
const current = yield* inspect(child.pid)
|
|
66
|
+
if (Option.isNone(current) || current.value.born !== child.start.born)
|
|
67
|
+
return false
|
|
68
|
+
return true
|
|
69
|
+
})
|
|
72
70
|
|
|
73
|
-
export const recover = (child: ProcessIdentity)
|
|
74
|
-
|
|
75
|
-
if (!(yield* matches(child)))
|
|
76
|
-
return {
|
|
77
|
-
kind: "Exited",
|
|
78
|
-
exit: { code: null, signal: null },
|
|
79
|
-
cause: Cause.empty,
|
|
80
|
-
} satisfies Termination
|
|
81
|
-
// The PID can still be reused between this final check and kill; an external supervisor covers descendants.
|
|
82
|
-
const sent = yield* Effect.exit(
|
|
83
|
-
Effect.try({
|
|
84
|
-
try: () => process.kill(child.pid, "SIGKILL"),
|
|
85
|
-
catch: (cause) =>
|
|
86
|
-
ExecutionError.make({
|
|
87
|
-
detail: "Signaling the orphan failed",
|
|
88
|
-
cause: Cause.die(cause),
|
|
89
|
-
}),
|
|
90
|
-
}),
|
|
91
|
-
)
|
|
92
|
-
const confirmed = yield* matches(child).pipe(
|
|
93
|
-
Effect.repeat({
|
|
94
|
-
schedule: Schedule.spaced("50 millis"),
|
|
95
|
-
while: (value) => value,
|
|
96
|
-
}),
|
|
97
|
-
Effect.timeoutOption("10 seconds"),
|
|
98
|
-
)
|
|
99
|
-
if (Option.isNone(confirmed))
|
|
100
|
-
return yield* TerminationUnconfirmed.make({
|
|
101
|
-
detail: "The orphan did not exit after recovery",
|
|
102
|
-
cause: sent._tag === "Failure" ? sent.cause : Cause.empty,
|
|
103
|
-
})
|
|
71
|
+
export const recover = Effect.fnUntraced(function* (child: ProcessIdentity) {
|
|
72
|
+
if (!(yield* matches(child)))
|
|
104
73
|
return {
|
|
105
|
-
kind: "
|
|
106
|
-
exit: { code: null, signal:
|
|
107
|
-
cause:
|
|
74
|
+
kind: "Exited",
|
|
75
|
+
exit: { code: null, signal: null },
|
|
76
|
+
cause: Cause.empty,
|
|
108
77
|
} satisfies Termination
|
|
109
|
-
|
|
78
|
+
// The PID can still be reused between this final check and kill; an external supervisor covers descendants.
|
|
79
|
+
const sent = yield* Effect.exit(
|
|
80
|
+
Effect.try({
|
|
81
|
+
try: () => process.kill(child.pid, "SIGKILL"),
|
|
82
|
+
catch: (cause) =>
|
|
83
|
+
ExecutionError.make({
|
|
84
|
+
detail: "Signaling the orphan failed",
|
|
85
|
+
cause: Cause.die(cause),
|
|
86
|
+
}),
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
const confirmed = yield* matches(child).pipe(
|
|
90
|
+
Effect.repeat({
|
|
91
|
+
schedule: Schedule.spaced("50 millis"),
|
|
92
|
+
while: (value) => value,
|
|
93
|
+
}),
|
|
94
|
+
Effect.timeoutOption("10 seconds"),
|
|
95
|
+
)
|
|
96
|
+
if (Option.isNone(confirmed))
|
|
97
|
+
return yield* TerminationUnconfirmed.make({
|
|
98
|
+
detail: "The orphan did not exit after recovery",
|
|
99
|
+
cause: sent._tag === "Failure" ? sent.cause : Cause.empty,
|
|
100
|
+
})
|
|
101
|
+
return {
|
|
102
|
+
kind: "Killed",
|
|
103
|
+
exit: { code: null, signal: "SIGKILL" },
|
|
104
|
+
cause: sent._tag === "Failure" ? sent.cause : Cause.empty,
|
|
105
|
+
} satisfies Termination
|
|
106
|
+
})
|
|
@@ -33,127 +33,123 @@ export const memory = Effect.gen(function* () {
|
|
|
33
33
|
* Older terminal request ids expire; Open fences their retries with expectedGeneration.
|
|
34
34
|
* Requires exclusive directory ownership; it is not a multi-writer database.
|
|
35
35
|
*/
|
|
36
|
-
export const directory = (root: string)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
error
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
36
|
+
export const directory = Effect.fnUntraced(function* (root: string) {
|
|
37
|
+
const fs = yield* FileSystem.FileSystem
|
|
38
|
+
const path = yield* Path.Path
|
|
39
|
+
const digest = yield* Digest.Digest
|
|
40
|
+
const json = Schema.fromJsonString(Schema.toCodecJson(SessionRecord))
|
|
41
|
+
const random = yield* SecureRandom.SecureRandom
|
|
42
|
+
const storage = <A, R>(
|
|
43
|
+
effect: Effect.Effect<
|
|
44
|
+
A,
|
|
45
|
+
PlatformError.PlatformError | Schema.SchemaError | StateStorageError,
|
|
46
|
+
R
|
|
47
|
+
>,
|
|
48
|
+
) =>
|
|
49
|
+
effect.pipe(
|
|
50
|
+
Effect.mapError((error) =>
|
|
51
|
+
error._tag === "StateStorageError"
|
|
52
|
+
? error
|
|
53
|
+
: StateStorageError.make({
|
|
54
|
+
detail: "Runner state storage failed",
|
|
55
|
+
cause: Cause.fail(error),
|
|
56
|
+
}),
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
yield* storage(
|
|
60
|
+
fs.makeDirectory(root, { recursive: true }).pipe(Effect.uninterruptible),
|
|
61
|
+
)
|
|
62
|
+
const mutex = yield* Semaphore.make(1)
|
|
63
|
+
const read = Effect.gen(function* () {
|
|
64
|
+
const files = yield* fs.readDirectory(root)
|
|
65
|
+
const rows = yield* Effect.forEach(
|
|
66
|
+
Array.filter(files, String.endsWith(".json")),
|
|
67
|
+
(file) =>
|
|
68
|
+
fs.readFileString(path.join(root, file)).pipe(
|
|
69
|
+
Effect.flatMap(Schema.decodeEffect(json)),
|
|
70
|
+
Effect.map((record) => ({ file, record })),
|
|
58
71
|
),
|
|
59
|
-
)
|
|
60
|
-
yield* storage(
|
|
61
|
-
fs.makeDirectory(root, { recursive: true }).pipe(Effect.uninterruptible),
|
|
62
72
|
)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Effect.map((record) => ({ file, record })),
|
|
72
|
-
),
|
|
73
|
+
return rows
|
|
74
|
+
})
|
|
75
|
+
const compact = Effect.scoped(
|
|
76
|
+
Effect.gen(function* () {
|
|
77
|
+
const rows = yield* read
|
|
78
|
+
const retained = Retention.retain(Array.map(rows, (row) => row.record))
|
|
79
|
+
const tokens = HashSet.fromIterable(
|
|
80
|
+
Array.map(retained, (record) => record.handle.token),
|
|
73
81
|
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const obsolete = Array.filter(
|
|
83
|
+
rows,
|
|
84
|
+
(row) => !HashSet.has(tokens, row.record.handle.token),
|
|
85
|
+
)
|
|
86
|
+
yield* Effect.forEach(
|
|
87
|
+
obsolete,
|
|
88
|
+
(row) =>
|
|
89
|
+
fs.remove(path.join(root, row.file)).pipe(Effect.uninterruptible),
|
|
90
|
+
{ discard: true },
|
|
91
|
+
)
|
|
92
|
+
if (obsolete.length > 0) {
|
|
93
|
+
const directory = yield* fs.open(root, { flag: "r" })
|
|
94
|
+
yield* directory.sync.pipe(Effect.uninterruptible)
|
|
95
|
+
}
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
yield* storage(compact)
|
|
99
|
+
return Store.of({
|
|
100
|
+
list: storage(
|
|
101
|
+
mutex.withPermit(
|
|
102
|
+
read.pipe(Effect.map((rows) => Array.map(rows, (row) => row.record))),
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
save: Effect.fnUntraced(
|
|
106
|
+
function* (record: SessionRecord) {
|
|
107
|
+
const encoded = yield* Schema.encodeEffect(json)(record)
|
|
108
|
+
const address = yield* Schema.encodeEffect(
|
|
109
|
+
Schema.fromJsonString(
|
|
110
|
+
Schema.Struct({ key: Key, generation: Schema.Int }),
|
|
111
|
+
),
|
|
112
|
+
)({
|
|
113
|
+
key: record.handle.key,
|
|
114
|
+
generation: record.handle.generation,
|
|
115
|
+
})
|
|
116
|
+
const name = yield* digest.digestHex(
|
|
117
|
+
Digest.sha256,
|
|
118
|
+
new TextEncoder().encode(address),
|
|
82
119
|
)
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
120
|
+
const nonce = yield* random.uuidV4()
|
|
121
|
+
const temporary = path.join(root, `.${name}.${nonce}.tmp`)
|
|
122
|
+
const destination = path.join(root, name + ".json")
|
|
123
|
+
yield* Effect.acquireRelease(
|
|
124
|
+
fs.open(temporary, { flag: "wx", mode: 0o600 }),
|
|
125
|
+
() => fs.remove(temporary, { force: true }).pipe(Effect.orDie),
|
|
126
|
+
).pipe(
|
|
127
|
+
Effect.flatMap((file) =>
|
|
128
|
+
file
|
|
129
|
+
.writeAll(new TextEncoder().encode(encoded))
|
|
130
|
+
.pipe(
|
|
131
|
+
Effect.uninterruptible,
|
|
132
|
+
Effect.andThen(file.sync.pipe(Effect.uninterruptible)),
|
|
133
|
+
),
|
|
134
|
+
),
|
|
86
135
|
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
(
|
|
90
|
-
fs.
|
|
91
|
-
|
|
136
|
+
// Keep rename and directory synchronization together if the caller is canceled.
|
|
137
|
+
yield* Effect.uninterruptible(
|
|
138
|
+
Effect.gen(function* () {
|
|
139
|
+
yield* fs.rename(temporary, destination)
|
|
140
|
+
const directory = yield* fs.open(root, { flag: "r" })
|
|
141
|
+
yield* directory.sync.pipe(Effect.uninterruptible)
|
|
142
|
+
}),
|
|
92
143
|
)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return Store.of({
|
|
101
|
-
list: storage(
|
|
102
|
-
mutex.withPermit(
|
|
103
|
-
read.pipe(Effect.map((rows) => Array.map(rows, (row) => row.record))),
|
|
104
|
-
),
|
|
105
|
-
),
|
|
106
|
-
save: (record) =>
|
|
107
|
-
storage(
|
|
108
|
-
mutex.withPermit(
|
|
109
|
-
Effect.scoped(
|
|
110
|
-
Effect.gen(function* () {
|
|
111
|
-
const encoded = yield* Schema.encodeEffect(json)(record)
|
|
112
|
-
const address = yield* Schema.encodeEffect(
|
|
113
|
-
Schema.fromJsonString(
|
|
114
|
-
Schema.Struct({ key: Key, generation: Schema.Int }),
|
|
115
|
-
),
|
|
116
|
-
)({
|
|
117
|
-
key: record.handle.key,
|
|
118
|
-
generation: record.handle.generation,
|
|
119
|
-
})
|
|
120
|
-
const name = yield* digest.digestHex(
|
|
121
|
-
Digest.sha256,
|
|
122
|
-
new TextEncoder().encode(address),
|
|
123
|
-
)
|
|
124
|
-
const nonce = yield* random.uuidV4()
|
|
125
|
-
const temporary = path.join(root, `.${name}.${nonce}.tmp`)
|
|
126
|
-
const destination = path.join(root, name + ".json")
|
|
127
|
-
yield* Effect.acquireRelease(
|
|
128
|
-
fs.open(temporary, { flag: "wx", mode: 0o600 }),
|
|
129
|
-
() =>
|
|
130
|
-
fs.remove(temporary, { force: true }).pipe(Effect.orDie),
|
|
131
|
-
).pipe(
|
|
132
|
-
Effect.flatMap((file) =>
|
|
133
|
-
file
|
|
134
|
-
.writeAll(new TextEncoder().encode(encoded))
|
|
135
|
-
.pipe(
|
|
136
|
-
Effect.uninterruptible,
|
|
137
|
-
Effect.andThen(file.sync.pipe(Effect.uninterruptible)),
|
|
138
|
-
),
|
|
139
|
-
),
|
|
140
|
-
)
|
|
141
|
-
// Keep rename and directory synchronization together if the caller is canceled.
|
|
142
|
-
yield* Effect.uninterruptible(
|
|
143
|
-
Effect.gen(function* () {
|
|
144
|
-
yield* fs.rename(temporary, destination)
|
|
145
|
-
const directory = yield* fs.open(root, { flag: "r" })
|
|
146
|
-
yield* directory.sync.pipe(Effect.uninterruptible)
|
|
147
|
-
}),
|
|
148
|
-
)
|
|
149
|
-
// The new high-water file is durable before any old record is removed.
|
|
150
|
-
yield* compact
|
|
151
|
-
}),
|
|
152
|
-
),
|
|
153
|
-
),
|
|
154
|
-
),
|
|
155
|
-
})
|
|
144
|
+
// The new high-water file is durable before any old record is removed.
|
|
145
|
+
yield* compact
|
|
146
|
+
},
|
|
147
|
+
Effect.scoped,
|
|
148
|
+
mutex.withPermit,
|
|
149
|
+
storage,
|
|
150
|
+
),
|
|
156
151
|
})
|
|
152
|
+
})
|
|
157
153
|
|
|
158
154
|
export const layerMemory = Layer.effect(Store, memory)
|
|
159
155
|
export const layerDirectory = (root: string) =>
|