devsess 0.3.3 → 0.4.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/LICENSE +201 -0
- package/README.md +3 -3
- package/dist/async/index.d.ts +1 -1
- package/dist/async/index.d.ts.map +1 -1
- package/dist/async/index.js +9 -7
- package/dist/async/index.js.map +1 -1
- package/dist/async/platform.d.ts +2 -2
- package/dist/async/platform.d.ts.map +1 -1
- package/dist/async/run-dev-cli.d.ts +1 -1
- package/dist/async/run-dev-cli.d.ts.map +1 -1
- package/dist/chunk-CBRTTFXG.js +29 -0
- package/dist/chunk-CBRTTFXG.js.map +1 -0
- package/dist/chunk-GX4J6JR3.js +214 -0
- package/dist/chunk-GX4J6JR3.js.map +1 -0
- package/dist/chunk-SX5HBQHG.js +226 -0
- package/dist/chunk-SX5HBQHG.js.map +1 -0
- package/dist/dev/subprocess.d.ts +1 -1
- package/dist/dev/subprocess.d.ts.map +1 -1
- package/dist/index.js +10 -26
- package/dist/index.js.map +1 -1
- package/dist/pglite/index.d.ts +4 -5
- package/dist/pglite/index.d.ts.map +1 -1
- package/dist/pglite/index.js +22 -31
- package/dist/pglite/index.js.map +1 -1
- package/dist/services/container.d.ts +17 -0
- package/dist/services/container.d.ts.map +1 -0
- package/dist/services/core.d.ts +18 -0
- package/dist/services/core.d.ts.map +1 -0
- package/dist/services/docker-args.d.ts +31 -0
- package/dist/services/docker-args.d.ts.map +1 -0
- package/dist/services/index.d.ts +45 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +312 -0
- package/dist/services/index.js.map +1 -0
- package/package.json +92 -88
- package/dist/chunk-ULIVG5ID.js +0 -429
- package/dist/chunk-ULIVG5ID.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev-sessions.ts","../src/dev/session-state.ts","../src/dev/sticky-port.ts"],"sourcesContent":["import { Array as A, Context, Data, Effect, Layer, Option } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport type { PlatformError } from 'effect/PlatformError';\nimport { generateSlug } from 'random-word-slugs';\n\nnamespace DevSession {\n\ttype DevSessionInput = {\n\t\tname: string;\n\t\tlastModifiedAt: Date | null;\n\t\trootDir: string;\n\t\tpathJoin: (relativePath: string) => string;\n\t};\n\n\texport function make(input: DevSessionInput) {\n\t\treturn {\n\t\t\tname: input.name,\n\t\t\tlastModifiedAt: input.lastModifiedAt,\n\t\t\tpath: (relativePath: string) =>\n\t\t\t\tEffect.succeed(input.pathJoin(relativePath)),\n\t\t\ttoString: () => input.name,\n\t\t};\n\t}\n}\n\nexport type DevSession = ReturnType<typeof DevSession.make>;\n\nexport class DevSessions extends Context.Service<\n\tDevSessions,\n\t{\n\t\treadonly dir: string;\n\t\treadonly path: (relativePath: string) => string;\n\t\treadonly getSessions: Effect.Effect<Array<DevSession>, PlatformError>;\n\t\treadonly createSession: Effect.Effect<DevSession, PlatformError>;\n\t\treadonly getLatestOrCreate: Effect.Effect<DevSession, PlatformError>;\n\t}\n>()('devsess/DevSessions') {\n\t/**\n\t * `DevSessions` explicitly rooted at `rootDir`: `dir`/`path` resolve against `rootDir`\n\t * itself (e.g. `sessions.path('drizzle')` reaches a project's real `drizzle/` folder),\n\t * while session directories are namespaced under `<rootDir>/.data/sessions`.\n\t *\n\t * Deliberately does *not* also provide `CurrentSession` — a `Layer`'s build effect\n\t * runs as soon as it's provided, before anything downstream (e.g. `cli.Command.run`'s\n\t * argv parsing) gets a say. Bundling session resolution in here would create a\n\t * session for `--help`/`--version`/a bad flag too, since those never reach a command\n\t * handler at all. Provide `CurrentSession.layer` around the handler itself instead.\n\t */\n\tstatic readonly layerAt = (rootDir: string) => buildDevSessionsLayer(rootDir);\n\n\t/**\n\t * `DevSessions.layerAt` rooted at the auto-detected project root — the nearest\n\t * ancestor of `process.cwd()` containing a `package.json`.\n\t */\n\tstatic readonly layer: Layer.Layer<\n\t\tDevSessions,\n\t\tPlatformError | ProjectRootNotFoundError,\n\t\tFileSystem | Path\n\t> = Layer.unwrap(\n\t\tEffect.suspend(() =>\n\t\t\tEffect.map(findProjectRoot, (rootDir) => DevSessions.layerAt(rootDir)),\n\t\t),\n\t);\n}\n\n/**\n * Raised by `DevSessions.layer` when no ancestor of `searchedFrom` contains a\n * `package.json`. Failing loudly beats silently falling back to `process.cwd()` — a\n * wrong root means dev sessions get written to a surprising place.\n */\nexport class ProjectRootNotFoundError extends Data.TaggedError(\n\t'ProjectRootNotFoundError',\n)<{\n\treadonly searchedFrom: string;\n}> {}\n\n/** Walks up from `process.cwd()` to the nearest ancestor containing a `package.json`. */\nconst findProjectRoot = Effect.gen(function* () {\n\tconst fs = yield* FileSystem;\n\tconst path = yield* Path;\n\tconst startDir = process.cwd();\n\n\tconst walkUp = (\n\t\tdir: string,\n\t): Effect.Effect<string, PlatformError | ProjectRootNotFoundError> =>\n\t\tEffect.gen(function* () {\n\t\t\tif (yield* fs.exists(path.join(dir, 'package.json'))) {\n\t\t\t\treturn dir;\n\t\t\t}\n\t\t\tconst parent = path.dirname(dir);\n\t\t\tif (parent === dir) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\tnew ProjectRootNotFoundError({ searchedFrom: startDir }),\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn yield* walkUp(parent);\n\t\t});\n\n\treturn yield* walkUp(startDir);\n});\n\n/** The listing/creation logic shared by every `DevSessions` layer, parameterized on the literal directory session subdirs live under. */\nconst buildDevSessionsCore = (sessionsDir: string) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst path = yield* Path;\n\n\t\tyield* Effect.logDebug(`DevSessions dir: ${sessionsDir}`);\n\n\t\tconst getSessions = Effect.gen(function* () {\n\t\t\tconst entries = yield* fs\n\t\t\t\t.readDirectory(sessionsDir)\n\t\t\t\t.pipe(Effect.catch(() => Effect.succeed([] as Array<string>)));\n\n\t\t\tconst possibleSessions = yield* Effect.all(\n\t\t\t\tentries.map((entry) =>\n\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\tconst stat = yield* fs.stat(path.join(sessionsDir, entry));\n\t\t\t\t\t\tif (stat.type !== 'Directory') return Option.none();\n\t\t\t\t\t\treturn Option.some(\n\t\t\t\t\t\t\tDevSession.make({\n\t\t\t\t\t\t\t\tname: entry,\n\t\t\t\t\t\t\t\tlastModifiedAt: Option.getOrNull(stat.mtime),\n\t\t\t\t\t\t\t\trootDir: sessionsDir,\n\t\t\t\t\t\t\t\tpathJoin: (relativePath: string) =>\n\t\t\t\t\t\t\t\t\tpath.join(sessionsDir, entry, relativePath),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\t{ concurrency: 'unbounded' },\n\t\t\t);\n\n\t\t\treturn A.getSomes(possibleSessions);\n\t\t});\n\n\t\tconst createSession = Effect.gen(function* () {\n\t\t\tconst slug = generateSlug(1, { partsOfSpeech: ['noun'] });\n\t\t\tyield* fs.makeDirectory(path.join(sessionsDir, slug), {\n\t\t\t\trecursive: true,\n\t\t\t});\n\t\t\treturn DevSession.make({\n\t\t\t\tname: slug,\n\t\t\t\tlastModifiedAt: null,\n\t\t\t\trootDir: sessionsDir,\n\t\t\t\tpathJoin: (relativePath: string) =>\n\t\t\t\t\tpath.join(sessionsDir, slug, relativePath),\n\t\t\t});\n\t\t});\n\n\t\tconst getLatestOrCreate = Effect.gen(function* () {\n\t\t\tyield* fs.makeDirectory(sessionsDir, { recursive: true });\n\n\t\t\tconst sessions = yield* getSessions;\n\t\t\tif (sessions.length > 0) {\n\t\t\t\tsessions.sort(\n\t\t\t\t\t(a, b) =>\n\t\t\t\t\t\t(b.lastModifiedAt?.getTime() ?? 0) -\n\t\t\t\t\t\t(a.lastModifiedAt?.getTime() ?? 0),\n\t\t\t\t);\n\t\t\t\tconst latest = sessions[0];\n\t\t\t\treturn latest as DevSession;\n\t\t\t}\n\n\t\t\treturn yield* createSession;\n\t\t});\n\n\t\treturn { getSessions, createSession, getLatestOrCreate };\n\t});\n\n/**\n * The one `DevSessions` layer constructor: `dir`/`path` resolve against `rootDir`\n * itself, while session directories are namespaced under `<rootDir>/.data/sessions` —\n * the library owns that layout, so every caller (`DevSessions.layerAt`, the async\n * facade's `createDevSessions`) means the same thing by `rootDir`.\n */\nconst buildDevSessionsLayer = (rootDir: string) =>\n\tLayer.effect(\n\t\tDevSessions,\n\t\tEffect.gen(function* () {\n\t\t\tconst path = yield* Path;\n\t\t\tconst sessionsDir = path.join(rootDir, '.data', 'sessions');\n\t\t\tconst core = yield* buildDevSessionsCore(sessionsDir);\n\t\t\treturn {\n\t\t\t\tdir: rootDir,\n\t\t\t\tpath: (relativePath: string) => path.join(rootDir, relativePath),\n\t\t\t\t...core,\n\t\t\t};\n\t\t}),\n\t);\n","import { Data, Effect } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport * as S from 'effect/Schema';\nimport type { DevSession } from '../dev-sessions';\n\nexport class SessionStateError extends Data.TaggedError('SessionStateError')<{\n\tmessage: string;\n\tcause?: unknown;\n}> {}\n\nfunction getPath(session: DevSession) {\n\treturn session.path('sess.json');\n}\n\nfunction read(filePath: string) {\n\treturn Effect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tif (!(yield* fs.exists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\treturn yield* fs.readFileString(filePath);\n\t});\n}\n\nexport namespace SessionState {\n\ttype Shape = S.Top;\n\n\texport function slot<T extends Shape>(schema: T) {\n\t\treturn {\n\t\t\tread: (session: DevSession) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst filePath = yield* getPath(session);\n\t\t\t\t\tconst content = yield* read(filePath);\n\t\t\t\t\tif (content == null) return null;\n\t\t\t\t\treturn yield* S.decodeEffect(S.fromJsonString(schema))(content).pipe(\n\t\t\t\t\t\tEffect.catchTag('SchemaError', () => Effect.succeed(null)),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t\twrite: (session: DevSession, data: T['Type']) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst fs = yield* FileSystem;\n\t\t\t\t\tconst path = yield* Path;\n\t\t\t\t\tconst filePath = yield* getPath(session);\n\t\t\t\t\tyield* fs.makeDirectory(path.dirname(filePath), { recursive: true });\n\t\t\t\t\tconst existing = yield* read(filePath);\n\t\t\t\t\tconst parsed = yield* Effect.try({\n\t\t\t\t\t\ttry: () => JSON.parse(existing ?? '{}') as Record<string, unknown>,\n\t\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\t\tnew SessionStateError({\n\t\t\t\t\t\t\t\tmessage: `Failed to parse ${filePath} as JSON`,\n\t\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t});\n\t\t\t\t\tconst merged = { ...parsed, ...(data as Record<string, unknown>) };\n\t\t\t\t\tyield* fs.writeFileString(filePath, JSON.stringify(merged));\n\t\t\t\t}),\n\t\t};\n\t}\n}\n","import { Effect } from 'effect';\nimport * as S from 'effect/Schema';\nimport getPort from 'get-port';\nimport type { DevSession } from '../dev-sessions';\nimport { SessionState } from './session-state';\n\nconst DEFAULT_NAME = 'default';\n\n/**\n * Reads both the current `{ ports: Record<string, number> }` shape and the legacy\n * top-level `{ port: number }` shape, so a session written before named ports existed\n * still yields its remembered port (as `default`). Writes only ever use `ports` —\n * the legacy `port` key is never touched again once the new shape is written.\n */\nconst StickyPorts = SessionState.slot(\n\tS.Struct({\n\t\tports: S.optional(S.Record(S.String, S.Number)),\n\t\tport: S.optional(S.Number),\n\t}),\n);\n\nexport const getStickyPort = (\n\tsession: DevSession,\n\toptions?: { name?: string },\n) =>\n\tEffect.gen(function* () {\n\t\tconst name = options?.name ?? DEFAULT_NAME;\n\n\t\tconst file = yield* StickyPorts.read(session);\n\t\tconst ports =\n\t\t\tfile?.ports ??\n\t\t\t(file?.port !== undefined ? { [DEFAULT_NAME]: file.port } : {});\n\n\t\tconst preferred = ports[name];\n\t\tconst exclude = Object.entries(ports)\n\t\t\t.filter((entry) => entry[0] !== name)\n\t\t\t.map((entry) => entry[1]);\n\n\t\tconst port = yield* Effect.promise(async () =>\n\t\t\tgetPort({\n\t\t\t\tport: preferred !== undefined ? [preferred] : undefined,\n\t\t\t\t...(exclude.length > 0 ? { exclude } : {}),\n\t\t\t}),\n\t\t);\n\n\t\tyield* StickyPorts.write(session, { ports: { ...ports, [name]: port } });\n\n\t\treturn port;\n\t});\n"],"mappings":"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","names":["DevSession","Data","Effect","FileSystem","Path","SessionState","Effect","S","Effect"]}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DevSessions
|
|
3
|
+
} from "./chunk-GX4J6JR3.js";
|
|
4
|
+
|
|
5
|
+
// src/dev/running-signal.ts
|
|
6
|
+
import { watch as fsWatch, realpathSync } from "fs";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { dirname, join, resolve } from "path";
|
|
9
|
+
import { Deferred, Effect as Effect2 } from "effect";
|
|
10
|
+
import { FileSystem } from "effect/FileSystem";
|
|
11
|
+
import { Path } from "effect/Path";
|
|
12
|
+
|
|
13
|
+
// src/dev/daemon-readiness.ts
|
|
14
|
+
import { createConnection } from "net";
|
|
15
|
+
import { StringDecoder } from "string_decoder";
|
|
16
|
+
import { Effect, Exit, Schema } from "effect";
|
|
17
|
+
var RESPONSE_TIMEOUT_MS = 5e3;
|
|
18
|
+
var MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
19
|
+
var DaemonResponse = Schema.fromJsonString(
|
|
20
|
+
Schema.Struct({
|
|
21
|
+
version: Schema.Literal(1),
|
|
22
|
+
requestId: Schema.NonEmptyString,
|
|
23
|
+
ok: Schema.Boolean,
|
|
24
|
+
result: Schema.optional(Schema.Unknown),
|
|
25
|
+
error: Schema.optional(Schema.String)
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
var sendReadiness = (identity, request) => Effect.tryPromise({
|
|
29
|
+
try: () => new Promise((resolve2, reject) => {
|
|
30
|
+
const socket = createConnection(identity.socketPath);
|
|
31
|
+
const decoder = new StringDecoder("utf8");
|
|
32
|
+
let input = "";
|
|
33
|
+
let settled = false;
|
|
34
|
+
const finish = (complete) => {
|
|
35
|
+
if (settled) return;
|
|
36
|
+
settled = true;
|
|
37
|
+
clearTimeout(timer);
|
|
38
|
+
socket.destroy();
|
|
39
|
+
complete();
|
|
40
|
+
};
|
|
41
|
+
const timer = setTimeout(
|
|
42
|
+
() => finish(
|
|
43
|
+
() => reject(new Error("Daemon readiness request timed out"))
|
|
44
|
+
),
|
|
45
|
+
RESPONSE_TIMEOUT_MS
|
|
46
|
+
);
|
|
47
|
+
socket.once(
|
|
48
|
+
"connect",
|
|
49
|
+
() => socket.write(`${JSON.stringify(request)}
|
|
50
|
+
`)
|
|
51
|
+
);
|
|
52
|
+
socket.on("data", (chunk) => {
|
|
53
|
+
input += decoder.write(chunk);
|
|
54
|
+
if (Buffer.byteLength(input) > MAX_RESPONSE_BYTES) {
|
|
55
|
+
finish(
|
|
56
|
+
() => reject(new Error("Daemon readiness response is too large"))
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const boundary = input.indexOf("\n");
|
|
61
|
+
if (boundary !== -1) finish(() => resolve2(input.slice(0, boundary)));
|
|
62
|
+
});
|
|
63
|
+
socket.once("error", (cause) => finish(() => reject(cause)));
|
|
64
|
+
socket.once(
|
|
65
|
+
"close",
|
|
66
|
+
() => finish(
|
|
67
|
+
() => reject(new Error("Daemon closed before acknowledging readiness"))
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
}),
|
|
71
|
+
catch: (cause) => new Error("Could not report readiness to daemon", { cause })
|
|
72
|
+
}).pipe(
|
|
73
|
+
Effect.flatMap(Schema.decodeUnknownEffect(DaemonResponse)),
|
|
74
|
+
Effect.flatMap(
|
|
75
|
+
(response) => response.requestId === request.requestId && response.ok ? Effect.void : Effect.fail(new Error("Daemon did not acknowledge readiness"))
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
var publishDaemonReadiness = (value) => Effect.suspend(() => {
|
|
79
|
+
const socketPath = process.env.DEVSESS_SOCKET;
|
|
80
|
+
const runId = process.env.DEVSESS_RUN_ID;
|
|
81
|
+
const service = process.env.DEVSESS_SERVICE;
|
|
82
|
+
if (socketPath === void 0 || runId === void 0 || service === void 0)
|
|
83
|
+
return Effect.void;
|
|
84
|
+
const identity = { socketPath, runId, service };
|
|
85
|
+
return Effect.acquireRelease(
|
|
86
|
+
Schema.decodeUnknownEffect(Schema.Json)(value).pipe(
|
|
87
|
+
Effect.flatMap(
|
|
88
|
+
(json) => sendReadiness(identity, {
|
|
89
|
+
version: 1,
|
|
90
|
+
requestId: crypto.randomUUID(),
|
|
91
|
+
method: "publish",
|
|
92
|
+
params: { runId, service, value: json }
|
|
93
|
+
})
|
|
94
|
+
),
|
|
95
|
+
Effect.exit
|
|
96
|
+
),
|
|
97
|
+
(result) => Exit.isSuccess(result) ? sendReadiness(identity, {
|
|
98
|
+
version: 1,
|
|
99
|
+
requestId: crypto.randomUUID(),
|
|
100
|
+
method: "unpublish",
|
|
101
|
+
params: { runId, service }
|
|
102
|
+
}).pipe(Effect.catch(() => Effect.void)) : Effect.void
|
|
103
|
+
).pipe(Effect.asVoid);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// src/dev/running-signal.ts
|
|
107
|
+
var RUNNING_SIGNAL_FILE = ".data/running.json";
|
|
108
|
+
var runningSignalPath = (dir) => join(dir, RUNNING_SIGNAL_FILE);
|
|
109
|
+
var resolveSiblingDir = (spec, fromDir) => {
|
|
110
|
+
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
111
|
+
return resolve(fromDir, spec);
|
|
112
|
+
}
|
|
113
|
+
const require2 = createRequire(join(fromDir, "package.json"));
|
|
114
|
+
const pkgJsonPath = require2.resolve(`${spec}/package.json`);
|
|
115
|
+
return dirname(realpathSync(pkgJsonPath));
|
|
116
|
+
};
|
|
117
|
+
var publishRunningSignal = (filePath, value) => Effect2.acquireRelease(
|
|
118
|
+
Effect2.gen(function* () {
|
|
119
|
+
const fs = yield* FileSystem;
|
|
120
|
+
const path = yield* Path;
|
|
121
|
+
const tmp = `${filePath}.tmp`;
|
|
122
|
+
yield* fs.makeDirectory(path.dirname(filePath), { recursive: true });
|
|
123
|
+
yield* fs.writeFileString(tmp, JSON.stringify(value));
|
|
124
|
+
yield* fs.rename(tmp, filePath);
|
|
125
|
+
}),
|
|
126
|
+
() => Effect2.gen(function* () {
|
|
127
|
+
const fs = yield* FileSystem;
|
|
128
|
+
yield* fs.remove(filePath).pipe(Effect2.catch(() => Effect2.void));
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
var awaitRunningSignal = (filePath, opts) => Effect2.scoped(
|
|
132
|
+
Effect2.gen(function* () {
|
|
133
|
+
const fs = yield* FileSystem;
|
|
134
|
+
const path = yield* Path;
|
|
135
|
+
const dir = path.dirname(filePath);
|
|
136
|
+
const filename = path.basename(filePath);
|
|
137
|
+
const deferred = yield* Deferred.make();
|
|
138
|
+
const tryRead = Effect2.gen(function* () {
|
|
139
|
+
if (!(yield* fs.exists(filePath))) return false;
|
|
140
|
+
const raw = yield* fs.readFileString(filePath).pipe(Effect2.catch(() => Effect2.succeed(null)));
|
|
141
|
+
if (raw == null) return false;
|
|
142
|
+
const parsed = yield* Effect2.try(() => opts.parse(raw)).pipe(
|
|
143
|
+
Effect2.catch(() => Effect2.succeed(null))
|
|
144
|
+
);
|
|
145
|
+
if (parsed == null) return false;
|
|
146
|
+
yield* Deferred.succeed(deferred, parsed);
|
|
147
|
+
return true;
|
|
148
|
+
});
|
|
149
|
+
yield* fs.makeDirectory(dir, { recursive: true });
|
|
150
|
+
yield* Effect2.acquireRelease(
|
|
151
|
+
Effect2.sync(
|
|
152
|
+
() => (
|
|
153
|
+
// Watch parent dir — the file may not exist yet, and watching a missing file errors.
|
|
154
|
+
fsWatch(dir, { recursive: false }, (eventType, eventFilename) => {
|
|
155
|
+
if (eventFilename !== filename) return;
|
|
156
|
+
if (eventType !== "rename" && eventType !== "change") return;
|
|
157
|
+
Effect2.runFork(tryRead);
|
|
158
|
+
})
|
|
159
|
+
)
|
|
160
|
+
),
|
|
161
|
+
(watcher) => Effect2.sync(() => watcher.close())
|
|
162
|
+
);
|
|
163
|
+
yield* tryRead;
|
|
164
|
+
return yield* Deferred.await(deferred);
|
|
165
|
+
})
|
|
166
|
+
);
|
|
167
|
+
var publishRunning = (value) => Effect2.gen(function* () {
|
|
168
|
+
const sessions = yield* DevSessions;
|
|
169
|
+
yield* publishRunningSignal(runningSignalPath(sessions.dir), value);
|
|
170
|
+
yield* publishDaemonReadiness(value);
|
|
171
|
+
});
|
|
172
|
+
var awaitRunning = (pkg) => Effect2.gen(function* () {
|
|
173
|
+
const sessions = yield* DevSessions;
|
|
174
|
+
return yield* awaitRunningSignal(
|
|
175
|
+
runningSignalPath(resolveSiblingDir(pkg, sessions.dir)),
|
|
176
|
+
{ parse: (raw) => JSON.parse(raw) }
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// src/dev/subprocess.ts
|
|
181
|
+
import { Effect as Effect3 } from "effect";
|
|
182
|
+
import { ChildProcess } from "effect/process";
|
|
183
|
+
var runManagedSubprocess = (cmd, args, opts) => Effect3.gen(function* () {
|
|
184
|
+
const command = ChildProcess.make(cmd, args, {
|
|
185
|
+
// Keep children in the caller's group so its supervisor can signal them together.
|
|
186
|
+
detached: false,
|
|
187
|
+
stdin: "inherit",
|
|
188
|
+
stdout: "inherit",
|
|
189
|
+
stderr: "inherit",
|
|
190
|
+
env: { ...process.env, ...opts?.env ?? {} }
|
|
191
|
+
});
|
|
192
|
+
const label = [cmd, ...args].join(" ");
|
|
193
|
+
const child = yield* Effect3.acquireRelease(
|
|
194
|
+
Effect3.gen(function* () {
|
|
195
|
+
const proc = yield* command;
|
|
196
|
+
yield* Effect3.logInfo(`[dev] started: ${label} (pid=${proc.pid})`);
|
|
197
|
+
return proc;
|
|
198
|
+
}),
|
|
199
|
+
(proc) => Effect3.gen(function* () {
|
|
200
|
+
if (!(yield* proc.isRunning.pipe(Effect3.orDie))) return;
|
|
201
|
+
yield* Effect3.logInfo(`[dev] stopping: ${label} (pid=${proc.pid})`);
|
|
202
|
+
yield* proc.kill().pipe(
|
|
203
|
+
Effect3.catch(
|
|
204
|
+
(err) => Effect3.gen(function* () {
|
|
205
|
+
if (yield* proc.isRunning.pipe(Effect3.orDie))
|
|
206
|
+
yield* Effect3.logError(
|
|
207
|
+
`[dev] failed to stop ${label} (pid=${proc.pid}): ${err}`
|
|
208
|
+
);
|
|
209
|
+
})
|
|
210
|
+
)
|
|
211
|
+
);
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
return yield* child.exitCode;
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export {
|
|
218
|
+
runningSignalPath,
|
|
219
|
+
resolveSiblingDir,
|
|
220
|
+
publishRunningSignal,
|
|
221
|
+
awaitRunningSignal,
|
|
222
|
+
publishRunning,
|
|
223
|
+
awaitRunning,
|
|
224
|
+
runManagedSubprocess
|
|
225
|
+
};
|
|
226
|
+
//# sourceMappingURL=chunk-SX5HBQHG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dev/running-signal.ts","../src/dev/daemon-readiness.ts","../src/dev/subprocess.ts"],"sourcesContent":["import { watch as fsWatch, realpathSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, join, resolve } from 'node:path';\nimport { Deferred, Effect } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport { DevSessions } from '../dev-sessions';\nimport { publishDaemonReadiness } from './daemon-readiness';\n\nconst RUNNING_SIGNAL_FILE = '.data/running.json';\n\n/** Path to the running-signal file for a dev-session (or sibling package) directory. */\nexport const runningSignalPath = (dir: string) =>\n\tjoin(dir, RUNNING_SIGNAL_FILE);\n\n/**\n * Resolves `spec` to a directory: relative/absolute paths are resolved against\n * `fromDir`, anything else is treated as a package name and resolved via its\n * `package.json`.\n */\nexport const resolveSiblingDir = (spec: string, fromDir: string) => {\n\tif (spec.startsWith('.') || spec.startsWith('/')) {\n\t\treturn resolve(fromDir, spec);\n\t}\n\tconst require = createRequire(join(fromDir, 'package.json'));\n\tconst pkgJsonPath = require.resolve(`${spec}/package.json`);\n\treturn dirname(realpathSync(pkgJsonPath));\n};\n\n/** Atomically writes `value` as JSON to `filePath` and removes it on release. */\nexport const publishRunningSignal = (filePath: string, value: unknown) =>\n\tEffect.acquireRelease(\n\t\tEffect.gen(function* () {\n\t\t\tconst fs = yield* FileSystem;\n\t\t\tconst path = yield* Path;\n\t\t\tconst tmp = `${filePath}.tmp`;\n\t\t\tyield* fs.makeDirectory(path.dirname(filePath), { recursive: true });\n\t\t\t// Write to a temp file then rename atomically so readers never see a partial file.\n\t\t\tyield* fs.writeFileString(tmp, JSON.stringify(value));\n\t\t\tyield* fs.rename(tmp, filePath);\n\t\t}),\n\t\t() =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst fs = yield* FileSystem;\n\t\t\t\tyield* fs.remove(filePath).pipe(Effect.catch(() => Effect.void));\n\t\t\t}),\n\t);\n\n/** Watches parent directory for the signal file and resolves once it can be parsed. */\nexport const awaitRunningSignal = <T>(\n\tfilePath: string,\n\topts: { parse: (raw: string) => T },\n) =>\n\tEffect.scoped(\n\t\tEffect.gen(function* () {\n\t\t\tconst fs = yield* FileSystem;\n\t\t\tconst path = yield* Path;\n\t\t\tconst dir = path.dirname(filePath);\n\t\t\tconst filename = path.basename(filePath);\n\n\t\t\tconst deferred = yield* Deferred.make<T>();\n\n\t\t\tconst tryRead = Effect.gen(function* () {\n\t\t\t\tif (!(yield* fs.exists(filePath))) return false;\n\t\t\t\tconst raw = yield* fs\n\t\t\t\t\t.readFileString(filePath)\n\t\t\t\t\t.pipe(Effect.catch(() => Effect.succeed(null)));\n\t\t\t\tif (raw == null) return false;\n\t\t\t\tconst parsed = yield* Effect.try(() => opts.parse(raw)).pipe(\n\t\t\t\t\tEffect.catch(() => Effect.succeed(null)),\n\t\t\t\t);\n\t\t\t\tif (parsed == null) return false;\n\t\t\t\tyield* Deferred.succeed(deferred, parsed);\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\t\t// fsWatch throws synchronously if `dir` doesn't exist yet — e.g. a sibling\n\t\t\t// package's dev script has never run — so ensure it's there first.\n\t\t\tyield* fs.makeDirectory(dir, { recursive: true });\n\n\t\t\tyield* Effect.acquireRelease(\n\t\t\t\tEffect.sync(() =>\n\t\t\t\t\t// Watch parent dir — the file may not exist yet, and watching a missing file errors.\n\t\t\t\t\tfsWatch(dir, { recursive: false }, (eventType, eventFilename) => {\n\t\t\t\t\t\tif (eventFilename !== filename) return;\n\t\t\t\t\t\tif (eventType !== 'rename' && eventType !== 'change') return;\n\t\t\t\t\t\tEffect.runFork(tryRead);\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\t(watcher) => Effect.sync(() => watcher.close()),\n\t\t\t);\n\n\t\t\tyield* tryRead;\n\t\t\treturn yield* Deferred.await(deferred);\n\t\t}),\n\t);\n\n/**\n * Publishes `value` at the project root's running-signal file, so a sibling package's\n * dev script can `awaitRunning` it. Removed when the enclosing scope closes.\n */\nexport const publishRunning = (value: unknown) =>\n\tEffect.gen(function* () {\n\t\tconst sessions = yield* DevSessions;\n\t\tyield* publishRunningSignal(runningSignalPath(sessions.dir), value);\n\t\tyield* publishDaemonReadiness(value);\n\t});\n\n/**\n * Awaits the running-signal file of another package in the workspace (`pkg`, resolved\n * relative to the project root the same way `resolveSiblingDir` resolves any spec).\n */\nexport const awaitRunning = <T>(pkg: string) =>\n\tEffect.gen(function* () {\n\t\tconst sessions = yield* DevSessions;\n\t\treturn yield* awaitRunningSignal<T>(\n\t\t\trunningSignalPath(resolveSiblingDir(pkg, sessions.dir)),\n\t\t\t{ parse: (raw) => JSON.parse(raw) as T },\n\t\t);\n\t});\n","import { createConnection } from 'node:net';\nimport { StringDecoder } from 'node:string_decoder';\nimport { Effect, Exit, Schema } from 'effect';\n\n// The daemon serves requests one at a time, so a readiness report can queue\n// behind slow ones (e.g. `devsess start` polling the run list while it waits).\nconst RESPONSE_TIMEOUT_MS = 5_000;\nconst MAX_RESPONSE_BYTES = 1024 * 1024;\n\nconst DaemonResponse = Schema.fromJsonString(\n\tSchema.Struct({\n\t\tversion: Schema.Literal(1),\n\t\trequestId: Schema.NonEmptyString,\n\t\tok: Schema.Boolean,\n\t\tresult: Schema.optional(Schema.Unknown),\n\t\terror: Schema.optional(Schema.String),\n\t}),\n);\n\ntype ServiceIdentity = {\n\treadonly socketPath: string;\n\treadonly runId: string;\n\treadonly service: string;\n};\n\nconst sendReadiness = (\n\tidentity: ServiceIdentity,\n\trequest: {\n\t\treadonly version: 1;\n\t\treadonly requestId: string;\n\t\treadonly method: 'publish' | 'unpublish';\n\t\treadonly params: {\n\t\t\treadonly runId: string;\n\t\t\treadonly service: string;\n\t\t\treadonly value?: Schema.Json;\n\t\t};\n\t},\n) =>\n\tEffect.tryPromise({\n\t\ttry: () =>\n\t\t\tnew Promise<string>((resolve, reject) => {\n\t\t\t\tconst socket = createConnection(identity.socketPath);\n\t\t\t\tconst decoder = new StringDecoder('utf8');\n\t\t\t\tlet input = '';\n\t\t\t\tlet settled = false;\n\t\t\t\tconst finish = (complete: () => void) => {\n\t\t\t\t\tif (settled) return;\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tclearTimeout(timer);\n\t\t\t\t\tsocket.destroy();\n\t\t\t\t\tcomplete();\n\t\t\t\t};\n\t\t\t\tconst timer = setTimeout(\n\t\t\t\t\t() =>\n\t\t\t\t\t\tfinish(() =>\n\t\t\t\t\t\t\treject(new Error('Daemon readiness request timed out')),\n\t\t\t\t\t\t),\n\t\t\t\t\tRESPONSE_TIMEOUT_MS,\n\t\t\t\t);\n\t\t\t\tsocket.once('connect', () =>\n\t\t\t\t\tsocket.write(`${JSON.stringify(request)}\\n`),\n\t\t\t\t);\n\t\t\t\tsocket.on('data', (chunk) => {\n\t\t\t\t\tinput += decoder.write(chunk);\n\t\t\t\t\tif (Buffer.byteLength(input) > MAX_RESPONSE_BYTES) {\n\t\t\t\t\t\tfinish(() =>\n\t\t\t\t\t\t\treject(new Error('Daemon readiness response is too large')),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst boundary = input.indexOf('\\n');\n\t\t\t\t\tif (boundary !== -1) finish(() => resolve(input.slice(0, boundary)));\n\t\t\t\t});\n\t\t\t\tsocket.once('error', (cause) => finish(() => reject(cause)));\n\t\t\t\tsocket.once('close', () =>\n\t\t\t\t\tfinish(() =>\n\t\t\t\t\t\treject(new Error('Daemon closed before acknowledging readiness')),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}),\n\t\tcatch: (cause) =>\n\t\t\tnew Error('Could not report readiness to daemon', { cause }),\n\t}).pipe(\n\t\tEffect.flatMap(Schema.decodeUnknownEffect(DaemonResponse)),\n\t\tEffect.flatMap((response) =>\n\t\t\tresponse.requestId === request.requestId && response.ok\n\t\t\t\t? Effect.void\n\t\t\t\t: Effect.fail(new Error('Daemon did not acknowledge readiness')),\n\t\t),\n\t);\n\nexport const publishDaemonReadiness = (value: unknown) =>\n\tEffect.suspend(() => {\n\t\tconst socketPath = process.env.DEVSESS_SOCKET;\n\t\tconst runId = process.env.DEVSESS_RUN_ID;\n\t\tconst service = process.env.DEVSESS_SERVICE;\n\t\tif (\n\t\t\tsocketPath === undefined ||\n\t\t\trunId === undefined ||\n\t\t\tservice === undefined\n\t\t)\n\t\t\treturn Effect.void;\n\t\tconst identity = { socketPath, runId, service };\n\t\treturn Effect.acquireRelease(\n\t\t\tSchema.decodeUnknownEffect(Schema.Json)(value).pipe(\n\t\t\t\tEffect.flatMap((json) =>\n\t\t\t\t\tsendReadiness(identity, {\n\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\trequestId: crypto.randomUUID(),\n\t\t\t\t\t\tmethod: 'publish',\n\t\t\t\t\t\tparams: { runId, service, value: json },\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t\tEffect.exit,\n\t\t\t),\n\t\t\t(result) =>\n\t\t\t\tExit.isSuccess(result)\n\t\t\t\t\t? sendReadiness(identity, {\n\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\trequestId: crypto.randomUUID(),\n\t\t\t\t\t\t\tmethod: 'unpublish',\n\t\t\t\t\t\t\tparams: { runId, service },\n\t\t\t\t\t\t}).pipe(Effect.catch(() => Effect.void))\n\t\t\t\t\t: Effect.void,\n\t\t).pipe(Effect.asVoid);\n\t});\n","import { Effect } from 'effect';\nimport { ChildProcess } from 'effect/process';\n\nexport const runManagedSubprocess = (\n\tcmd: string,\n\targs: string[],\n\topts?: { env?: Record<string, string> },\n) =>\n\tEffect.gen(function* () {\n\t\tconst command = ChildProcess.make(cmd, args, {\n\t\t\t// Keep children in the caller's group so its supervisor can signal them together.\n\t\t\tdetached: false,\n\t\t\tstdin: 'inherit',\n\t\t\tstdout: 'inherit',\n\t\t\tstderr: 'inherit',\n\t\t\tenv: { ...process.env, ...(opts?.env ?? {}) },\n\t\t});\n\n\t\tconst label = [cmd, ...args].join(' ');\n\n\t\t// Registers against the ambient scope, not a scope of its own — the\n\t\t// child must outlive this call (it doesn't resolve until the child\n\t\t// exits) and get killed only when the caller's scope closes.\n\t\tconst child = yield* Effect.acquireRelease(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst proc = yield* command;\n\t\t\t\tyield* Effect.logInfo(`[dev] started: ${label} (pid=${proc.pid})`);\n\t\t\t\treturn proc;\n\t\t\t}),\n\t\t\t(proc) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tif (!(yield* proc.isRunning.pipe(Effect.orDie))) return;\n\t\t\t\t\tyield* Effect.logInfo(`[dev] stopping: ${label} (pid=${proc.pid})`);\n\t\t\t\t\tyield* proc.kill().pipe(\n\t\t\t\t\t\tEffect.catch((err) =>\n\t\t\t\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\t\t\t\tif (yield* proc.isRunning.pipe(Effect.orDie))\n\t\t\t\t\t\t\t\t\tyield* Effect.logError(\n\t\t\t\t\t\t\t\t\t\t`[dev] failed to stop ${label} (pid=${proc.pid}): ${err}`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t}),\n\t\t);\n\n\t\treturn yield* child.exitCode;\n\t});\n"],"mappings":"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","names":["Effect","resolve","require","Effect","Effect"]}
|
package/dist/dev/subprocess.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Effect } from 'effect';
|
|
2
2
|
export declare const runManagedSubprocess: (cmd: string, args: string[], opts?: {
|
|
3
3
|
env?: Record<string, string>;
|
|
4
|
-
}) => Effect.Effect<import("effect/
|
|
4
|
+
}) => Effect.Effect<import("effect/process/ChildProcessSpawner").ExitCode, import("effect/PlatformError").PlatformError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/Scope").Scope>;
|
|
5
5
|
//# sourceMappingURL=subprocess.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subprocess.d.ts","sourceRoot":"","sources":["../../src/dev/subprocess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,eAAO,MAAM,oBAAoB,QAC3B,MAAM,QACL,MAAM,EAAE,SACP;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"subprocess.d.ts","sourceRoot":"","sources":["../../src/dev/subprocess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,eAAO,MAAM,oBAAoB,QAC3B,MAAM,QACL,MAAM,EAAE,SACP;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,wNAyCrC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CurrentSession
|
|
3
|
+
} from "./chunk-CBRTTFXG.js";
|
|
4
|
+
import {
|
|
5
|
+
awaitRunning,
|
|
6
|
+
publishRunning,
|
|
7
|
+
runManagedSubprocess
|
|
8
|
+
} from "./chunk-SX5HBQHG.js";
|
|
1
9
|
import {
|
|
2
10
|
DevSessions,
|
|
3
11
|
ProjectRootNotFoundError,
|
|
4
12
|
SessionState,
|
|
5
13
|
SessionStateError,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
publishRunning,
|
|
9
|
-
runManagedSubprocess
|
|
10
|
-
} from "./chunk-ULIVG5ID.js";
|
|
11
|
-
|
|
12
|
-
// src/current-session.ts
|
|
13
|
-
import { Context, Effect, Layer } from "effect";
|
|
14
|
-
var CurrentSession = class _CurrentSession extends Context.Service()("devsess/CurrentSession") {
|
|
15
|
-
/**
|
|
16
|
-
* Resolves the session via `DevSessions#getLatestOrCreate`. Provide this around a
|
|
17
|
-
* command's own handler effect, not the whole CLI program: a `Layer`'s build effect
|
|
18
|
-
* runs the moment it's provided, regardless of what the effect it wraps ends up
|
|
19
|
-
* doing — wrapping `Command.run` itself would create a session for `--help`,
|
|
20
|
-
* `--version`, or a bad flag too, none of which ever reach a handler.
|
|
21
|
-
*/
|
|
22
|
-
static layer = Layer.effect(
|
|
23
|
-
_CurrentSession,
|
|
24
|
-
Effect.gen(function* () {
|
|
25
|
-
const sessions = yield* DevSessions;
|
|
26
|
-
return yield* sessions.getLatestOrCreate;
|
|
27
|
-
})
|
|
28
|
-
);
|
|
29
|
-
/** Pins an explicit session instead of resolving one via `DevSessions` — for tests. */
|
|
30
|
-
static layerOf = (session) => Layer.succeed(_CurrentSession, session);
|
|
31
|
-
};
|
|
14
|
+
getStickyPort
|
|
15
|
+
} from "./chunk-GX4J6JR3.js";
|
|
32
16
|
export {
|
|
33
17
|
CurrentSession,
|
|
34
18
|
DevSessions,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/pglite/index.d.ts
CHANGED
|
@@ -21,9 +21,9 @@ export declare const createPgliteFromDump: (opts: {
|
|
|
21
21
|
dumpPath: string;
|
|
22
22
|
}) => Effect.Effect<PGlite, PgliteError | import("effect/PlatformError").PlatformError, FileSystem>;
|
|
23
23
|
export declare const dumpPgliteToFile: (client: PGlite, dest: string) => Effect.Effect<void, PgliteError | import("effect/PlatformError").PlatformError, FileSystem | Path>;
|
|
24
|
-
export declare const migratePglite: (client: PGlite, migrations: PgliteMigrations) => Effect.Effect<void, PgliteError, never>;
|
|
24
|
+
export declare const migratePglite: (client: PGlite, migrations: PgliteMigrations) => Effect.Effect<void | import("drizzle-orm/migrator").MigratorInitFailResponse, PgliteError, never>;
|
|
25
25
|
export declare const getDbMigrationCount: (client: PGlite, migrations?: Pick<PgliteMigrations, 'migrationsTable' | 'migrationsSchema'>) => Effect.Effect<number, PgliteError, never>;
|
|
26
|
-
export declare const getExpectedMigrationCount: (migrationsFolder: string) => Effect.Effect<number, PgliteError
|
|
26
|
+
export declare const getExpectedMigrationCount: (migrationsFolder: string) => Effect.Effect<number, PgliteError, never>;
|
|
27
27
|
export declare const buildPgliteDump: (opts: PgliteMigrations & {
|
|
28
28
|
dumpPath: string;
|
|
29
29
|
}) => Effect.Effect<void, PgliteError | import("effect/PlatformError").PlatformError, FileSystem | Path>;
|
|
@@ -38,11 +38,10 @@ export declare const openLitePglite: (opts: PgliteMigrations & {
|
|
|
38
38
|
* Prepares a PGlite database scoped to a dev session: the data dir lives at
|
|
39
39
|
* `<session>/pglite` and the migration dump at `<session>/pglite.dump`. Builds
|
|
40
40
|
* the dump from `migrationsFolder` if missing, hydrates the client, and runs
|
|
41
|
-
* pending migrations.
|
|
42
|
-
*
|
|
41
|
+
* pending migrations. Closes the client before returning paths for another
|
|
42
|
+
* process (e.g. `DATABASE_LITE_PATH`). Use `openLitePglite` for an in-process client.
|
|
43
43
|
*/
|
|
44
44
|
export declare const prepareSessionPglite: (session: DevSession, opts: PgliteMigrations) => Effect.Effect<{
|
|
45
|
-
client: PGlite;
|
|
46
45
|
dataDir: string;
|
|
47
46
|
dumpPath: string;
|
|
48
47
|
}, PgliteError | import("effect/PlatformError").PlatformError, FileSystem | Path>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pglite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pglite/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAI9C,OAAO,EAAQ,MAAM,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;;;;AAElD,qBAAa,WAAY,SAAQ,iBAAgC;IAChE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;CAAG;AAQL,MAAM,MAAM,gBAAgB,GAAG;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAwBF,eAAO,MAAM,oBAAoB,SAAU;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACjB,kGAsBE,CAAC;AAeJ,eAAO,MAAM,gBAAgB,WAAY,MAAM,QAAQ,MAAM,uGAmC1D,CAAC;AAEJ,eAAO,MAAM,aAAa,WAAY,MAAM,cAAc,gBAAgB,sGAavE,CAAC;AAYJ,eAAO,MAAM,mBAAmB,WACvB,MAAM,eACD,IAAI,CAAC,gBAAgB,EAAE,iBAAiB,GAAG,kBAAkB,CAAC,8CA4DzE,CAAC;AAEJ,eAAO,MAAM,yBAAyB,qBAAsB,MAAM,8CAQ/D,CAAC;AAEJ,eAAO,MAAM,eAAe,SACrB,gBAAgB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,uGAO3C,CAAC;AAEJ,eAAO,MAAM,gBAAgB,SACtB,gBAAgB,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,uGAQ3C,CAAC;AAEJ,eAAO,MAAM,cAAc,SACpB,gBAAgB,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,yGAwB5D,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,YACvB,UAAU,QACb,gBAAgB;;;iFAYpB,CAAC"}
|
package/dist/pglite/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/pglite/index.ts
|
|
2
2
|
import { PGlite } from "@electric-sql/pglite";
|
|
3
|
+
import { readMigrationFiles } from "drizzle-orm/migrator";
|
|
3
4
|
import { drizzle } from "drizzle-orm/pglite";
|
|
4
5
|
import { migrate } from "drizzle-orm/pglite/migrator";
|
|
5
6
|
import { Data, Effect } from "effect";
|
|
@@ -62,10 +63,21 @@ var dumpPgliteToFile = (client, dest) => Effect.gen(function* () {
|
|
|
62
63
|
})
|
|
63
64
|
});
|
|
64
65
|
const data = yield* toUint8Array(dump);
|
|
65
|
-
yield*
|
|
66
|
+
const tempName = yield* Effect.sync(
|
|
67
|
+
() => `.${path.basename(dest)}.${crypto.randomUUID()}.tmp`
|
|
68
|
+
);
|
|
69
|
+
const tempPath = path.join(destDir, tempName);
|
|
70
|
+
yield* Effect.gen(function* () {
|
|
71
|
+
yield* fs.writeFile(tempPath, data);
|
|
72
|
+
yield* fs.rename(tempPath, dest);
|
|
73
|
+
}).pipe(
|
|
74
|
+
Effect.onError(
|
|
75
|
+
() => fs.remove(tempPath, { force: true }).pipe(Effect.orDie)
|
|
76
|
+
)
|
|
77
|
+
);
|
|
66
78
|
});
|
|
67
79
|
var migratePglite = (client, migrations) => Effect.tryPromise({
|
|
68
|
-
try: () => migrate(drizzle(client), {
|
|
80
|
+
try: () => migrate(drizzle({ client }), {
|
|
69
81
|
migrationsFolder: migrations.migrationsFolder,
|
|
70
82
|
migrationsTable: migrations.migrationsTable,
|
|
71
83
|
migrationsSchema: migrations.migrationsSchema
|
|
@@ -131,34 +143,12 @@ var getDbMigrationCount = (client, migrations) => Effect.gen(function* () {
|
|
|
131
143
|
}
|
|
132
144
|
return countRow.c;
|
|
133
145
|
});
|
|
134
|
-
var getExpectedMigrationCount = (migrationsFolder) => Effect.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return yield* Effect.fail(
|
|
141
|
-
new PgliteError({
|
|
142
|
-
message: `Migration journal not found at ${journalPath}`
|
|
143
|
-
})
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
const content = yield* fs.readFileString(journalPath);
|
|
147
|
-
const journal = yield* Effect.try({
|
|
148
|
-
try: () => JSON.parse(content),
|
|
149
|
-
catch: (cause) => new PgliteError({
|
|
150
|
-
message: `Failed to parse migration journal at ${journalPath}`,
|
|
151
|
-
cause
|
|
152
|
-
})
|
|
153
|
-
});
|
|
154
|
-
if (!Array.isArray(journal.entries)) {
|
|
155
|
-
return yield* Effect.fail(
|
|
156
|
-
new PgliteError({
|
|
157
|
-
message: `Migration journal at ${journalPath} is missing an "entries" array`
|
|
158
|
-
})
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
|
-
return journal.entries.length;
|
|
146
|
+
var getExpectedMigrationCount = (migrationsFolder) => Effect.try({
|
|
147
|
+
try: () => readMigrationFiles({ migrationsFolder }).length,
|
|
148
|
+
catch: (cause) => new PgliteError({
|
|
149
|
+
message: `Failed to read migrations from ${migrationsFolder}`,
|
|
150
|
+
cause
|
|
151
|
+
})
|
|
162
152
|
});
|
|
163
153
|
var buildPgliteDump = (opts) => Effect.gen(function* () {
|
|
164
154
|
const client = new PGlite(IN_MEMORY_DATA_DIR);
|
|
@@ -201,7 +191,8 @@ var prepareSessionPglite = (session, opts) => Effect.gen(function* () {
|
|
|
201
191
|
dataDir,
|
|
202
192
|
dumpPath
|
|
203
193
|
});
|
|
204
|
-
|
|
194
|
+
yield* closePglite(client);
|
|
195
|
+
return { dataDir, dumpPath };
|
|
205
196
|
});
|
|
206
197
|
export {
|
|
207
198
|
PgliteError,
|
package/dist/pglite/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pglite/index.ts"],"sourcesContent":["import { PGlite } from '@electric-sql/pglite';\nimport { drizzle } from 'drizzle-orm/pglite';\nimport { migrate } from 'drizzle-orm/pglite/migrator';\nimport { Data, Effect } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport type { DevSession } from '../dev-sessions';\n\nexport class PgliteError extends Data.TaggedError('PgliteError')<{\n\tmessage: string;\n\tcause?: unknown;\n}> {}\n\nconst IN_MEMORY_DATA_DIR = 'memory://';\n\n/** Drizzle's own defaults, used whenever a caller doesn't override them. */\nconst DEFAULT_MIGRATIONS_TABLE = '__drizzle_migrations';\nconst DEFAULT_MIGRATIONS_SCHEMA = 'drizzle';\n\nexport type PgliteMigrations = {\n\tmigrationsFolder: string;\n\tmigrationsTable?: string;\n\tmigrationsSchema?: string;\n};\n\n/** Double-quotes an identifier part for safe interpolation into SQL. */\nconst quoteIdentifierPart = (part: string) => `\"${part.replaceAll('\"', '\"\"')}\"`;\n\nconst qualifiedMigrationsTable = (\n\tmigrations: Pick<PgliteMigrations, 'migrationsTable' | 'migrationsSchema'>,\n) =>\n\t`${quoteIdentifierPart(migrations.migrationsSchema ?? DEFAULT_MIGRATIONS_SCHEMA)}.${quoteIdentifierPart(migrations.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE)}`;\n\n/**\n * A persisted data dir that already exists holds a hydrated database, so it is\n * opened as-is rather than re-seeded from the dump. In-memory clients have\n * nothing to reuse.\n */\nconst shouldReuseExistingDatabase = (dataDir: string | undefined) =>\n\tEffect.gen(function* () {\n\t\tif (!dataDir || dataDir === IN_MEMORY_DATA_DIR) {\n\t\t\treturn false;\n\t\t}\n\t\tconst fs = yield* FileSystem;\n\t\treturn yield* fs.exists(dataDir);\n\t});\n\nexport const createPgliteFromDump = (opts: {\n\tdataDir?: string;\n\tdumpPath: string;\n}) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\n\t\tif (yield* shouldReuseExistingDatabase(opts.dataDir)) {\n\t\t\treturn new PGlite(opts.dataDir);\n\t\t}\n\n\t\tif (!(yield* fs.exists(opts.dumpPath))) {\n\t\t\treturn yield* new PgliteError({\n\t\t\t\tmessage: `Dump file not found at ${opts.dumpPath}`,\n\t\t\t});\n\t\t}\n\n\t\tconst buffer = yield* fs.readFile(opts.dumpPath);\n\t\tconst file = new Blob([new Uint8Array(buffer)]);\n\n\t\tconst client = new PGlite(opts.dataDir, {\n\t\t\tloadDataDir: file,\n\t\t});\n\n\t\treturn client;\n\t});\n\nconst toUint8Array = (input: File | Blob) =>\n\tEffect.gen(function* () {\n\t\tconst buf = yield* Effect.tryPromise({\n\t\t\ttry: () => input.arrayBuffer(),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to convert dump to Uint8Array',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\t\treturn new Uint8Array(buf);\n\t});\n\nexport const dumpPgliteToFile = (client: PGlite, dest: string) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst path = yield* Path;\n\n\t\tconst destDir = path.dirname(dest);\n\t\tconst dirExists = yield* fs.exists(destDir);\n\n\t\tif (!dirExists) {\n\t\t\tyield* fs.makeDirectory(destDir, { recursive: true });\n\t\t}\n\n\t\tconst dump = yield* Effect.tryPromise({\n\t\t\ttry: () => client.dumpDataDir(),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to dump PGlite database',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst data = yield* toUint8Array(dump);\n\t\tyield* fs.writeFile(dest, data);\n\t});\n\nexport const migratePglite = (client: PGlite, migrations: PgliteMigrations) =>\n\tEffect.tryPromise({\n\t\ttry: () =>\n\t\t\tmigrate(drizzle(client), {\n\t\t\t\tmigrationsFolder: migrations.migrationsFolder,\n\t\t\t\tmigrationsTable: migrations.migrationsTable,\n\t\t\t\tmigrationsSchema: migrations.migrationsSchema,\n\t\t\t}),\n\t\tcatch: (error) =>\n\t\t\tnew PgliteError({\n\t\t\t\tmessage: `Failed to migrate pglite from ${migrations.migrationsFolder}`,\n\t\t\t\tcause: error,\n\t\t\t}),\n\t});\n\nconst closePglite = (client: PGlite) =>\n\tEffect.tryPromise({\n\t\ttry: () => client.close(),\n\t\tcatch: (error) =>\n\t\t\tnew PgliteError({\n\t\t\t\tmessage: 'Failed to close PGlite client',\n\t\t\t\tcause: error,\n\t\t\t}),\n\t});\n\nexport const getDbMigrationCount = (\n\tclient: PGlite,\n\tmigrations?: Pick<PgliteMigrations, 'migrationsTable' | 'migrationsSchema'>,\n) =>\n\tEffect.gen(function* () {\n\t\tconst qualifiedTable = qualifiedMigrationsTable(migrations ?? {});\n\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => client.waitReady,\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to wait for PGlite ready',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst existsResult = yield* Effect.tryPromise({\n\t\t\ttry: () =>\n\t\t\t\tclient.query<{ exists: boolean }>(\n\t\t\t\t\t'SELECT to_regclass($1) IS NOT NULL AS \"exists\"',\n\t\t\t\t\t[qualifiedTable],\n\t\t\t\t),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to check drizzle migrations table',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst existsRow = existsResult.rows[0];\n\t\tif (!existsRow) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Missing row from drizzle migrations existence check',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (!existsRow.exists) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tconst countResult = yield* Effect.tryPromise({\n\t\t\ttry: () =>\n\t\t\t\tclient.query<{ c: number }>(\n\t\t\t\t\t`SELECT count(*)::int AS c FROM ${qualifiedTable}`,\n\t\t\t\t),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to count drizzle migrations',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst countRow = countResult.rows[0];\n\t\tif (!countRow) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Missing row from drizzle migrations count query',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn countRow.c;\n\t});\n\nexport const getExpectedMigrationCount = (migrationsFolder: string) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst path = yield* Path;\n\t\tconst journalPath = path.join(migrationsFolder, 'meta/_journal.json');\n\t\tconst journalExists = yield* fs.exists(journalPath);\n\t\tif (!journalExists) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: `Migration journal not found at ${journalPath}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst content = yield* fs.readFileString(journalPath);\n\t\tconst journal = yield* Effect.try({\n\t\t\ttry: () => JSON.parse(content) as { entries: unknown[] },\n\t\t\tcatch: (cause) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: `Failed to parse migration journal at ${journalPath}`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t});\n\t\tif (!Array.isArray(journal.entries)) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: `Migration journal at ${journalPath} is missing an \"entries\" array`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn journal.entries.length;\n\t});\n\nexport const buildPgliteDump = (\n\topts: PgliteMigrations & { dumpPath: string },\n) =>\n\tEffect.gen(function* () {\n\t\tconst client = new PGlite(IN_MEMORY_DATA_DIR);\n\t\tyield* migratePglite(client, opts);\n\t\tyield* dumpPgliteToFile(client, opts.dumpPath);\n\t\tyield* closePglite(client);\n\t});\n\nexport const ensurePgliteDump = (\n\topts: PgliteMigrations & { dumpPath: string },\n) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst dumpExists = yield* fs.exists(opts.dumpPath);\n\t\tif (!dumpExists) {\n\t\t\tyield* buildPgliteDump(opts);\n\t\t}\n\t});\n\nexport const openLitePglite = (\n\topts: PgliteMigrations & { dataDir: string; dumpPath: string },\n) =>\n\tEffect.gen(function* () {\n\t\tyield* ensurePgliteDump(opts);\n\t\tconst client = yield* createPgliteFromDump({\n\t\t\tdataDir: opts.dataDir,\n\t\t\tdumpPath: opts.dumpPath,\n\t\t});\n\t\tconst expected = yield* getExpectedMigrationCount(opts.migrationsFolder);\n\t\tconst actual = yield* getDbMigrationCount(client, opts);\n\t\t/** Schema is present but nothing is journaled — it predates journaled migrations. */\n\t\tconst predatesMigrationJournal = expected > 0 && actual === 0;\n\t\tif (predatesMigrationJournal) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Stale local pglite at ${opts.dataDir} (schema present, no migration journal) — ` +\n\t\t\t\t\t\t'it was built by an older scheme. Remove it and the dump, then restart:\\n' +\n\t\t\t\t\t\t` rm -rf ${opts.dataDir} ${opts.dumpPath}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tyield* migratePglite(client, opts);\n\t\treturn client;\n\t});\n\n/**\n * Prepares a PGlite database scoped to a dev session: the data dir lives at\n * `<session>/pglite` and the migration dump at `<session>/pglite.dump`. Builds\n * the dump from `migrationsFolder` if missing, hydrates the client, and runs\n * pending migrations. Returns the open client plus the resolved paths so the\n * caller can pass them to the app (e.g. as `DATABASE_LITE_PATH`).\n */\nexport const prepareSessionPglite = (\n\tsession: DevSession,\n\topts: PgliteMigrations,\n) =>\n\tEffect.gen(function* () {\n\t\tconst dataDir = yield* session.path('pglite');\n\t\tconst dumpPath = yield* session.path('pglite.dump');\n\t\tconst client = yield* openLitePglite({\n\t\t\t...opts,\n\t\t\tdataDir,\n\t\t\tdumpPath,\n\t\t});\n\t\treturn { client, dataDir, dumpPath };\n\t});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/pglite/index.ts"],"sourcesContent":["import { PGlite } from '@electric-sql/pglite';\nimport { readMigrationFiles } from 'drizzle-orm/migrator';\nimport { drizzle } from 'drizzle-orm/pglite';\nimport { migrate } from 'drizzle-orm/pglite/migrator';\nimport { Data, Effect } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport type { DevSession } from '../dev-sessions';\n\nexport class PgliteError extends Data.TaggedError('PgliteError')<{\n\tmessage: string;\n\tcause?: unknown;\n}> {}\n\nconst IN_MEMORY_DATA_DIR = 'memory://';\n\n/** Drizzle's own defaults, used whenever a caller doesn't override them. */\nconst DEFAULT_MIGRATIONS_TABLE = '__drizzle_migrations';\nconst DEFAULT_MIGRATIONS_SCHEMA = 'drizzle';\n\nexport type PgliteMigrations = {\n\tmigrationsFolder: string;\n\tmigrationsTable?: string;\n\tmigrationsSchema?: string;\n};\n\n/** Double-quotes an identifier part for safe interpolation into SQL. */\nconst quoteIdentifierPart = (part: string) => `\"${part.replaceAll('\"', '\"\"')}\"`;\n\nconst qualifiedMigrationsTable = (\n\tmigrations: Pick<PgliteMigrations, 'migrationsTable' | 'migrationsSchema'>,\n) =>\n\t`${quoteIdentifierPart(migrations.migrationsSchema ?? DEFAULT_MIGRATIONS_SCHEMA)}.${quoteIdentifierPart(migrations.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE)}`;\n\n/**\n * A persisted data dir that already exists holds a hydrated database, so it is\n * opened as-is rather than re-seeded from the dump. In-memory clients have\n * nothing to reuse.\n */\nconst shouldReuseExistingDatabase = (dataDir: string | undefined) =>\n\tEffect.gen(function* () {\n\t\tif (!dataDir || dataDir === IN_MEMORY_DATA_DIR) {\n\t\t\treturn false;\n\t\t}\n\t\tconst fs = yield* FileSystem;\n\t\treturn yield* fs.exists(dataDir);\n\t});\n\nexport const createPgliteFromDump = (opts: {\n\tdataDir?: string;\n\tdumpPath: string;\n}) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\n\t\tif (yield* shouldReuseExistingDatabase(opts.dataDir)) {\n\t\t\treturn new PGlite(opts.dataDir);\n\t\t}\n\n\t\tif (!(yield* fs.exists(opts.dumpPath))) {\n\t\t\treturn yield* new PgliteError({\n\t\t\t\tmessage: `Dump file not found at ${opts.dumpPath}`,\n\t\t\t});\n\t\t}\n\n\t\tconst buffer = yield* fs.readFile(opts.dumpPath);\n\t\tconst file = new Blob([new Uint8Array(buffer)]);\n\n\t\tconst client = new PGlite(opts.dataDir, {\n\t\t\tloadDataDir: file,\n\t\t});\n\n\t\treturn client;\n\t});\n\nconst toUint8Array = (input: File | Blob) =>\n\tEffect.gen(function* () {\n\t\tconst buf = yield* Effect.tryPromise({\n\t\t\ttry: () => input.arrayBuffer(),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to convert dump to Uint8Array',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\t\treturn new Uint8Array(buf);\n\t});\n\nexport const dumpPgliteToFile = (client: PGlite, dest: string) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst path = yield* Path;\n\n\t\tconst destDir = path.dirname(dest);\n\t\tconst dirExists = yield* fs.exists(destDir);\n\n\t\tif (!dirExists) {\n\t\t\tyield* fs.makeDirectory(destDir, { recursive: true });\n\t\t}\n\n\t\tconst dump = yield* Effect.tryPromise({\n\t\t\ttry: () => client.dumpDataDir(),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to dump PGlite database',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst data = yield* toUint8Array(dump);\n\t\tconst tempName = yield* Effect.sync(\n\t\t\t() => `.${path.basename(dest)}.${crypto.randomUUID()}.tmp`,\n\t\t);\n\t\tconst tempPath = path.join(destDir, tempName);\n\t\t// A sibling rename keeps concurrent readers from seeing a partial dump.\n\t\tyield* Effect.gen(function* () {\n\t\t\tyield* fs.writeFile(tempPath, data);\n\t\t\tyield* fs.rename(tempPath, dest);\n\t\t}).pipe(\n\t\t\tEffect.onError(() =>\n\t\t\t\tfs.remove(tempPath, { force: true }).pipe(Effect.orDie),\n\t\t\t),\n\t\t);\n\t});\n\nexport const migratePglite = (client: PGlite, migrations: PgliteMigrations) =>\n\tEffect.tryPromise({\n\t\ttry: () =>\n\t\t\tmigrate(drizzle({ client }), {\n\t\t\t\tmigrationsFolder: migrations.migrationsFolder,\n\t\t\t\tmigrationsTable: migrations.migrationsTable,\n\t\t\t\tmigrationsSchema: migrations.migrationsSchema,\n\t\t\t}),\n\t\tcatch: (error) =>\n\t\t\tnew PgliteError({\n\t\t\t\tmessage: `Failed to migrate pglite from ${migrations.migrationsFolder}`,\n\t\t\t\tcause: error,\n\t\t\t}),\n\t});\n\nconst closePglite = (client: PGlite) =>\n\tEffect.tryPromise({\n\t\ttry: () => client.close(),\n\t\tcatch: (error) =>\n\t\t\tnew PgliteError({\n\t\t\t\tmessage: 'Failed to close PGlite client',\n\t\t\t\tcause: error,\n\t\t\t}),\n\t});\n\nexport const getDbMigrationCount = (\n\tclient: PGlite,\n\tmigrations?: Pick<PgliteMigrations, 'migrationsTable' | 'migrationsSchema'>,\n) =>\n\tEffect.gen(function* () {\n\t\tconst qualifiedTable = qualifiedMigrationsTable(migrations ?? {});\n\n\t\tyield* Effect.tryPromise({\n\t\t\ttry: () => client.waitReady,\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to wait for PGlite ready',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst existsResult = yield* Effect.tryPromise({\n\t\t\ttry: () =>\n\t\t\t\tclient.query<{ exists: boolean }>(\n\t\t\t\t\t'SELECT to_regclass($1) IS NOT NULL AS \"exists\"',\n\t\t\t\t\t[qualifiedTable],\n\t\t\t\t),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to check drizzle migrations table',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst existsRow = existsResult.rows[0];\n\t\tif (!existsRow) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Missing row from drizzle migrations existence check',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (!existsRow.exists) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tconst countResult = yield* Effect.tryPromise({\n\t\t\ttry: () =>\n\t\t\t\tclient.query<{ c: number }>(\n\t\t\t\t\t`SELECT count(*)::int AS c FROM ${qualifiedTable}`,\n\t\t\t\t),\n\t\t\tcatch: (error) =>\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Failed to count drizzle migrations',\n\t\t\t\t\tcause: error,\n\t\t\t\t}),\n\t\t});\n\n\t\tconst countRow = countResult.rows[0];\n\t\tif (!countRow) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage: 'Missing row from drizzle migrations count query',\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn countRow.c;\n\t});\n\nexport const getExpectedMigrationCount = (migrationsFolder: string) =>\n\tEffect.try({\n\t\ttry: () => readMigrationFiles({ migrationsFolder }).length,\n\t\tcatch: (cause) =>\n\t\t\tnew PgliteError({\n\t\t\t\tmessage: `Failed to read migrations from ${migrationsFolder}`,\n\t\t\t\tcause,\n\t\t\t}),\n\t});\n\nexport const buildPgliteDump = (\n\topts: PgliteMigrations & { dumpPath: string },\n) =>\n\tEffect.gen(function* () {\n\t\tconst client = new PGlite(IN_MEMORY_DATA_DIR);\n\t\tyield* migratePglite(client, opts);\n\t\tyield* dumpPgliteToFile(client, opts.dumpPath);\n\t\tyield* closePglite(client);\n\t});\n\nexport const ensurePgliteDump = (\n\topts: PgliteMigrations & { dumpPath: string },\n) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst dumpExists = yield* fs.exists(opts.dumpPath);\n\t\tif (!dumpExists) {\n\t\t\tyield* buildPgliteDump(opts);\n\t\t}\n\t});\n\nexport const openLitePglite = (\n\topts: PgliteMigrations & { dataDir: string; dumpPath: string },\n) =>\n\tEffect.gen(function* () {\n\t\tyield* ensurePgliteDump(opts);\n\t\tconst client = yield* createPgliteFromDump({\n\t\t\tdataDir: opts.dataDir,\n\t\t\tdumpPath: opts.dumpPath,\n\t\t});\n\t\tconst expected = yield* getExpectedMigrationCount(opts.migrationsFolder);\n\t\tconst actual = yield* getDbMigrationCount(client, opts);\n\t\t/** Schema is present but nothing is journaled — it predates journaled migrations. */\n\t\tconst predatesMigrationJournal = expected > 0 && actual === 0;\n\t\tif (predatesMigrationJournal) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\tnew PgliteError({\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`Stale local pglite at ${opts.dataDir} (schema present, no migration journal) — ` +\n\t\t\t\t\t\t'it was built by an older scheme. Remove it and the dump, then restart:\\n' +\n\t\t\t\t\t\t` rm -rf ${opts.dataDir} ${opts.dumpPath}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tyield* migratePglite(client, opts);\n\t\treturn client;\n\t});\n\n/**\n * Prepares a PGlite database scoped to a dev session: the data dir lives at\n * `<session>/pglite` and the migration dump at `<session>/pglite.dump`. Builds\n * the dump from `migrationsFolder` if missing, hydrates the client, and runs\n * pending migrations. Closes the client before returning paths for another\n * process (e.g. `DATABASE_LITE_PATH`). Use `openLitePglite` for an in-process client.\n */\nexport const prepareSessionPglite = (\n\tsession: DevSession,\n\topts: PgliteMigrations,\n) =>\n\tEffect.gen(function* () {\n\t\tconst dataDir = yield* session.path('pglite');\n\t\tconst dumpPath = yield* session.path('pglite.dump');\n\t\tconst client = yield* openLitePglite({\n\t\t\t...opts,\n\t\t\tdataDir,\n\t\t\tdumpPath,\n\t\t});\n\t\tyield* closePglite(client);\n\t\treturn { dataDir, dumpPath };\n\t});\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { FileSystem } from 'effect/FileSystem';
|
|
3
|
+
import { Path } from 'effect/Path';
|
|
4
|
+
import { DevSessions } from '../dev-sessions';
|
|
5
|
+
import { type ServiceContext, ServiceError } from './core';
|
|
6
|
+
import { type Healthcheck } from './docker-args';
|
|
7
|
+
export type ContainerSpec<Port extends string> = {
|
|
8
|
+
image: string;
|
|
9
|
+
ports?: Record<Port, number>;
|
|
10
|
+
volumes?: Record<string, string>;
|
|
11
|
+
env?: Record<string, string>;
|
|
12
|
+
healthcheck?: Healthcheck;
|
|
13
|
+
};
|
|
14
|
+
export declare const startContainer: <Port extends string>(name: string, spec: ContainerSpec<Port>) => (ctx: ServiceContext<Port>) => Effect.Effect<{
|
|
15
|
+
container: string;
|
|
16
|
+
}, ServiceError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | DevSessions | FileSystem | Path | import("effect/Scope").Scope>;
|
|
17
|
+
//# sourceMappingURL=container.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../src/services/container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAa,MAAM,QAAQ,CAAC;AACtE,OAAO,EAAgB,KAAK,WAAW,EAAiB,MAAM,eAAe,CAAC;AAE9E,MAAM,MAAM,aAAa,CAAC,IAAI,SAAS,MAAM,IAAI;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,WAAW,CAAC;CAC1B,CAAC;AA2FF,eAAO,MAAM,cAAc,GACzB,IAAI,SAAS,MAAM,QAAQ,MAAM,QAAQ,aAAa,CAAC,IAAI,CAAC,WACvD,cAAc,CAAC,IAAI,CAAC;;mJAoGxB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { DevSession } from '../dev-sessions';
|
|
2
|
+
declare const ServiceError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
3
|
+
readonly _tag: "ServiceError";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
export declare class ServiceError extends ServiceError_base<{
|
|
6
|
+
message: string;
|
|
7
|
+
cause?: unknown;
|
|
8
|
+
}> {
|
|
9
|
+
}
|
|
10
|
+
export type ServiceContext<Port extends string> = {
|
|
11
|
+
readonly session: DevSession;
|
|
12
|
+
readonly ports: Readonly<Record<Port, number>>;
|
|
13
|
+
/** `<session>/services/<service>`, created before `start` runs and kept after it stops. */
|
|
14
|
+
readonly dataDir: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const validName: (name: string) => boolean;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/services/core.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;;;;AAElD,qBAAa,YAAa,SAAQ,kBAAiC;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;CAAG;AAEL,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,MAAM,IAAI;IACjD,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,2FAA2F;IAC3F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,eAAO,MAAM,SAAS,SAAU,MAAM,YACI,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Duration } from 'effect';
|
|
2
|
+
export type Healthcheck = {
|
|
3
|
+
test: readonly string[];
|
|
4
|
+
interval?: Duration.Input;
|
|
5
|
+
timeout?: Duration.Input;
|
|
6
|
+
retries?: number;
|
|
7
|
+
startPeriod?: Duration.Input;
|
|
8
|
+
};
|
|
9
|
+
export declare const buildRunArgs: (input: {
|
|
10
|
+
image: string;
|
|
11
|
+
container: string;
|
|
12
|
+
root: string;
|
|
13
|
+
session: string;
|
|
14
|
+
uid: number;
|
|
15
|
+
gid: number;
|
|
16
|
+
ports: ReadonlyArray<{
|
|
17
|
+
host: number;
|
|
18
|
+
container: number;
|
|
19
|
+
}>;
|
|
20
|
+
volumes: ReadonlyArray<{
|
|
21
|
+
source: string;
|
|
22
|
+
target: string;
|
|
23
|
+
}>;
|
|
24
|
+
env: Record<string, string>;
|
|
25
|
+
healthcheck: Healthcheck | undefined;
|
|
26
|
+
}) => string[];
|
|
27
|
+
export declare const selectOrphans: (containers: ReadonlyArray<{
|
|
28
|
+
id: string;
|
|
29
|
+
session: string;
|
|
30
|
+
}>, existingSessions: ReadonlySet<string>) => string[];
|
|
31
|
+
//# sourceMappingURL=docker-args.d.ts.map
|