devsess 0.4.0 → 0.5.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.
Files changed (51) hide show
  1. package/dist/async/index.js +2 -2
  2. package/dist/{chunk-SX5HBQHG.js → chunk-KG24ZAKO.js} +20 -80
  3. package/dist/chunk-KG24ZAKO.js.map +1 -0
  4. package/dist/{chunk-GX4J6JR3.js → chunk-MBPIOYW3.js} +157 -9
  5. package/dist/chunk-MBPIOYW3.js.map +1 -0
  6. package/dist/chunk-OODLZZCC.js +287 -0
  7. package/dist/chunk-OODLZZCC.js.map +1 -0
  8. package/dist/{chunk-CBRTTFXG.js → chunk-UOIMVHGX.js} +2 -2
  9. package/dist/current-session.d.ts +1 -0
  10. package/dist/current-session.d.ts.map +1 -1
  11. package/dist/dev/daemon-client.d.ts +21 -0
  12. package/dist/dev/daemon-client.d.ts.map +1 -0
  13. package/dist/dev/daemon-readiness.d.ts.map +1 -1
  14. package/dist/dev/daemon-services.d.ts +3 -0
  15. package/dist/dev/daemon-services.d.ts.map +1 -0
  16. package/dist/dev/sticky-port.d.ts +4 -1
  17. package/dist/dev/sticky-port.d.ts.map +1 -1
  18. package/dist/dev-sessions.d.ts +2 -0
  19. package/dist/dev-sessions.d.ts.map +1 -1
  20. package/dist/index.js +3 -3
  21. package/dist/lock.d.ts +3 -0
  22. package/dist/lock.d.ts.map +1 -0
  23. package/dist/services/container.d.ts.map +1 -1
  24. package/dist/services/core.d.ts +12 -0
  25. package/dist/services/core.d.ts.map +1 -1
  26. package/dist/services/index.d.ts +9 -4
  27. package/dist/services/index.d.ts.map +1 -1
  28. package/dist/services/index.js +119 -52
  29. package/dist/services/index.js.map +1 -1
  30. package/dist/services/local.d.ts +11 -0
  31. package/dist/services/local.d.ts.map +1 -0
  32. package/dist/services/shared/callsite.d.ts +4 -0
  33. package/dist/services/shared/callsite.d.ts.map +1 -0
  34. package/dist/services/shared/entry.d.ts +2 -0
  35. package/dist/services/shared/entry.d.ts.map +1 -0
  36. package/dist/services/shared/entry.js +137 -0
  37. package/dist/services/shared/entry.js.map +1 -0
  38. package/dist/services/shared/host.d.ts +5 -0
  39. package/dist/services/shared/host.d.ts.map +1 -0
  40. package/dist/services/shared/index.d.ts +11 -0
  41. package/dist/services/shared/index.d.ts.map +1 -0
  42. package/dist/services/shared/protocol.d.ts +39 -0
  43. package/dist/services/shared/protocol.d.ts.map +1 -0
  44. package/dist/services/shared/root.d.ts +5 -0
  45. package/dist/services/shared/root.d.ts.map +1 -0
  46. package/dist/socket-line.d.ts +3 -0
  47. package/dist/socket-line.d.ts.map +1 -0
  48. package/package.json +4 -2
  49. package/dist/chunk-GX4J6JR3.js.map +0 -1
  50. package/dist/chunk-SX5HBQHG.js.map +0 -1
  51. /package/dist/{chunk-CBRTTFXG.js.map → chunk-UOIMVHGX.js.map} +0 -0
@@ -1,15 +1,26 @@
1
1
  import {
2
2
  CurrentSession
3
- } from "../chunk-CBRTTFXG.js";
3
+ } from "../chunk-UOIMVHGX.js";
4
4
  import {
5
- DevSessions,
6
- getStickyPort
7
- } from "../chunk-GX4J6JR3.js";
5
+ ServiceError,
6
+ acquireHost,
7
+ failWhenHostExits,
8
+ hostPaths,
9
+ launchHost,
10
+ readyMessage,
11
+ reportDaemonService,
12
+ runLocal,
13
+ runningService,
14
+ serviceBrand,
15
+ validName,
16
+ withServiceError
17
+ } from "../chunk-OODLZZCC.js";
18
+ import {
19
+ DevSessions
20
+ } from "../chunk-MBPIOYW3.js";
8
21
 
9
22
  // src/services/index.ts
10
- import { Context, Effect as Effect2, Layer } from "effect";
11
- import { FileSystem as FileSystem2 } from "effect/FileSystem";
12
- import { Path as Path2 } from "effect/Path";
23
+ import { Context, Effect as Effect5, Layer } from "effect";
13
24
 
14
25
  // src/services/container.ts
15
26
  import { Effect, Stream } from "effect";
@@ -17,12 +28,6 @@ import { FileSystem } from "effect/FileSystem";
17
28
  import { Path } from "effect/Path";
18
29
  import { ChildProcess } from "effect/process";
19
30
 
20
- // src/services/core.ts
21
- import { Data } from "effect";
22
- var ServiceError = class extends Data.TaggedError("ServiceError") {
23
- };
24
- var validName = (name) => /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name);
25
-
26
31
  // src/services/docker-args.ts
27
32
  import { Duration } from "effect";
28
33
  var dockerDuration = (input) => `${Duration.toMillis(input)}ms`;
@@ -234,59 +239,121 @@ var startContainer = (name, spec) => (ctx) => Effect.gen(function* () {
234
239
  );
235
240
  if (spec.healthcheck) yield* waitHealthy(name, container);
236
241
  return { container };
237
- }).pipe(
238
- Effect.mapError(
239
- (cause) => cause instanceof ServiceError ? cause : new ServiceError({
240
- message: `Failed to start container for service ${name}`,
241
- cause
242
- })
243
- )
244
- );
242
+ }).pipe(withServiceError(`Failed to start container for service ${name}`));
245
243
 
246
- // src/services/index.ts
247
- var run = (session, def) => Effect2.gen(function* () {
248
- if (!validName(def.name) || !validName(session.name)) {
244
+ // src/services/shared/index.ts
245
+ import { Effect as Effect4, Schema } from "effect";
246
+
247
+ // src/services/shared/callsite.ts
248
+ import { resolve } from "path";
249
+ import { fileURLToPath, pathToFileURL } from "url";
250
+ import { Effect as Effect2 } from "effect";
251
+ var packageDir = fileURLToPath(import.meta.url).match(
252
+ /^(.*)\/(?:src|dist)\//
253
+ )?.[1];
254
+ var definitionModule = (stack) => Effect2.gen(function* () {
255
+ if (packageDir === void 0)
249
256
  return yield* new ServiceError({
250
- message: "Service and session names must contain only letters, numbers, dots, underscores, or hyphens"
257
+ message: "Unable to identify devsess internal stack frames"
251
258
  });
259
+ if (stack !== void 0) {
260
+ for (const frame of stack.split("\n").slice(1)) {
261
+ const match = frame.match(
262
+ /(?:\(|\s|@)((?:file:\/\/|\/)[^\n]*?):\d+:\d+\)?$/
263
+ );
264
+ const location = match?.[1];
265
+ if (location === void 0) continue;
266
+ const file = yield* Effect2.try({
267
+ try: () => location.startsWith("file://") ? fileURLToPath(location) : resolve(location),
268
+ catch: (cause) => new ServiceError({
269
+ message: "Invalid service callsite path",
270
+ cause
271
+ })
272
+ });
273
+ if (file.startsWith(`${packageDir}/src/`) || file.startsWith(`${packageDir}/dist/`))
274
+ continue;
275
+ return yield* Effect2.try({
276
+ try: () => pathToFileURL(file).href,
277
+ catch: (cause) => new ServiceError({ message: "Invalid service module URL", cause })
278
+ });
279
+ }
252
280
  }
281
+ return yield* new ServiceError({
282
+ message: "Unable to locate the file that calls Service.make for a shared service"
283
+ });
284
+ });
285
+
286
+ // src/services/shared/root.ts
287
+ import { Effect as Effect3 } from "effect";
288
+ import { FileSystem as FileSystem2 } from "effect/FileSystem";
289
+ import { Path as Path2 } from "effect/Path";
290
+ var findSharedRoot = (projectDir) => Effect3.gen(function* () {
253
291
  const fs = yield* FileSystem2;
254
292
  const path = yield* Path2;
255
- const dataDir = path.join(yield* session.path(""), "services", def.name);
256
- const ports = {};
257
- for (const port of def.ports)
258
- ports[port] = yield* getStickyPort(session, {
259
- name: `${def.name}:${port}`
293
+ const start = path.resolve(projectDir);
294
+ const walk = (dir, candidate) => Effect3.gen(function* () {
295
+ const topmost = yield* fs.exists(path.join(dir, "package.json"));
296
+ const next = topmost ? dir : candidate;
297
+ if (yield* fs.exists(path.join(dir, ".git"))) return next;
298
+ const parent = path.dirname(dir);
299
+ if (parent === dir) return start;
300
+ return yield* walk(parent, next);
301
+ });
302
+ return yield* walk(start, start);
303
+ });
304
+
305
+ // src/services/shared/index.ts
306
+ var locateHost = (session, name, stack) => Effect4.gen(function* () {
307
+ const module = yield* definitionModule(stack);
308
+ const root = yield* findSharedRoot(session.rootDir);
309
+ const sharedSession = yield* DevSessions.use(
310
+ (store) => store.getOrCreate("shared-services")
311
+ ).pipe(Effect4.provide(DevSessions.layerAt(root)));
312
+ const paths = hostPaths(yield* sharedSession.path(""), name);
313
+ return { paths, args: { module, name, root, session: sharedSession.name } };
314
+ });
315
+ var runShared = (session, name, options, stack) => Effect4.gen(function* () {
316
+ if (!validName(name) || !validName(session.name))
317
+ return yield* new ServiceError({
318
+ message: "Invalid shared service or session name"
260
319
  });
261
- yield* fs.makeDirectory(dataDir, { recursive: true });
262
- return { ports, dataDir };
263
- }).pipe(
264
- Effect2.mapError(
265
- (cause) => cause instanceof ServiceError ? cause : new ServiceError({
266
- message: `Failed to prepare service ${def.name}`,
267
- cause
268
- })
269
- ),
270
- Effect2.flatMap(
271
- (prepared) => def.start({ session, ports: prepared.ports, dataDir: prepared.dataDir }).pipe(
272
- Effect2.map(
273
- (value) => ({
274
- ...typeof value === "object" && value !== null ? value : {},
275
- ports: prepared.ports
276
- })
277
- )
320
+ const host = yield* locateHost(session, name, stack);
321
+ const lease = yield* Effect4.acquireRelease(
322
+ acquireHost(host.paths, () => launchHost(host.paths, host.args)),
323
+ (value) => Effect4.sync(() => value.socket.destroy())
324
+ );
325
+ yield* Effect4.forkScoped(
326
+ failWhenHostExits(lease, name).pipe(
327
+ Effect4.catch((error) => Effect4.logError(error))
278
328
  )
279
- ),
280
- Effect2.annotateLogs({ service: def.name })
281
- );
329
+ );
330
+ const ready = yield* Schema.decodeUnknownEffect(
331
+ readyMessage(options.output)
332
+ )(lease.line).pipe(withServiceError(`Invalid shared output from ${name}`));
333
+ yield* reportDaemonService(name, ready.ports);
334
+ return {
335
+ value: ready.output,
336
+ ports: ready.ports
337
+ };
338
+ }).pipe(withServiceError(`Failed to run shared service ${name}`));
339
+
340
+ // src/services/index.ts
341
+ var run = (session, def) => (def.shared !== void 0 ? runShared(session, def.name, def.shared, def.definitionStack) : runLocal(session, def)).pipe(Effect5.map(runningService));
282
342
  var make = (def) => {
283
- const base = { name: def.name, ports: def.ports ?? [], start: def.start };
343
+ const base = {
344
+ [serviceBrand]: true,
345
+ name: def.name,
346
+ ports: def.ports ?? [],
347
+ start: def.start,
348
+ shared: def.shared,
349
+ definitionStack: def.shared === void 0 ? void 0 : new Error().stack
350
+ };
284
351
  const key = Context.Service(
285
352
  `devsess/services/${def.name}`
286
353
  );
287
354
  const layer = Layer.effect(
288
355
  key,
289
- Effect2.gen(function* () {
356
+ Effect5.gen(function* () {
290
357
  const session = yield* CurrentSession;
291
358
  return yield* run(session, base);
292
359
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/index.ts","../../src/services/container.ts","../../src/services/core.ts","../../src/services/docker-args.ts"],"sourcesContent":["import { Context, Effect, Layer } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport type { Scope } from 'effect/Scope';\nimport { CurrentSession } from '../current-session';\nimport { getStickyPort } from '../dev/sticky-port';\nimport type { DevSession } from '../dev-sessions';\nimport { type ContainerSpec, startContainer } from './container';\nimport { type ServiceContext, ServiceError, validName } from './core';\n\nexport type { Healthcheck } from './docker-args';\nexport { type ServiceContext, ServiceError };\n\nexport type RunningService<Port extends string, A> = (A extends object\n\t? Omit<A, 'ports'>\n\t: unknown) & { readonly ports: Readonly<Record<Port, number>> };\n\ntype ServiceIdentifier<Name extends string> = {\n\treadonly devsessService: Name;\n};\n\nexport type ServiceDefinition<\n\tName extends string,\n\tPort extends string,\n\tA,\n\tE,\n\tR,\n> = {\n\treadonly name: Name;\n\treadonly ports: ReadonlyArray<Port>;\n\t/** Resolves once the service is ready; its scope closing stops it. */\n\treadonly start: (ctx: ServiceContext<Port>) => Effect.Effect<A, E, R>;\n\t/** Yield inside another service's `start` (or any effect) to depend on this one. */\n\treadonly key: Context.Key<ServiceIdentifier<Name>, RunningService<Port, A>>;\n\t/** Starts the service once per layer build and shares it with every consumer. */\n\treadonly layer: Layer.Layer<\n\t\tServiceIdentifier<Name>,\n\t\tE | ServiceError,\n\t\tExclude<R, Scope> | CurrentSession | FileSystem | Path\n\t>;\n};\n\nconst run = <Name extends string, Port extends string, A, E, R>(\n\tsession: DevSession,\n\tdef: Pick<ServiceDefinition<Name, Port, A, E, R>, 'name' | 'ports' | 'start'>,\n) =>\n\tEffect.gen(function* () {\n\t\tif (!validName(def.name) || !validName(session.name)) {\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage:\n\t\t\t\t\t'Service and session names must contain only letters, numbers, dots, underscores, or hyphens',\n\t\t\t});\n\t\t}\n\t\tconst fs = yield* FileSystem;\n\t\tconst path = yield* Path;\n\t\tconst dataDir = path.join(yield* session.path(''), 'services', def.name);\n\t\tconst ports = {} as Record<Port, number>;\n\t\tfor (const port of def.ports)\n\t\t\tports[port] = yield* getStickyPort(session, {\n\t\t\t\tname: `${def.name}:${port}`,\n\t\t\t});\n\t\tyield* fs.makeDirectory(dataDir, { recursive: true });\n\t\treturn { ports, dataDir };\n\t}).pipe(\n\t\tEffect.mapError((cause) =>\n\t\t\tcause instanceof ServiceError\n\t\t\t\t? cause\n\t\t\t\t: new ServiceError({\n\t\t\t\t\t\tmessage: `Failed to prepare service ${def.name}`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t),\n\t\tEffect.flatMap((prepared) =>\n\t\t\tdef\n\t\t\t\t.start({ session, ports: prepared.ports, dataDir: prepared.dataDir })\n\t\t\t\t.pipe(\n\t\t\t\t\tEffect.map(\n\t\t\t\t\t\t(value) =>\n\t\t\t\t\t\t\t({\n\t\t\t\t\t\t\t\t...(typeof value === 'object' && value !== null ? value : {}),\n\t\t\t\t\t\t\t\tports: prepared.ports,\n\t\t\t\t\t\t\t}) as RunningService<Port, A>,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t),\n\t\tEffect.annotateLogs({ service: def.name }),\n\t);\n\nconst make = <\n\tconst Name extends string,\n\tA,\n\tE,\n\tR,\n\tconst Port extends string = never,\n>(def: {\n\tname: Name;\n\tports?: ReadonlyArray<Port>;\n\tstart: (ctx: ServiceContext<Port>) => Effect.Effect<A, E, R>;\n}): ServiceDefinition<Name, Port, A, E, R> => {\n\tconst base = { name: def.name, ports: def.ports ?? [], start: def.start };\n\tconst key = Context.Service<ServiceIdentifier<Name>, RunningService<Port, A>>(\n\t\t`devsess/services/${def.name}`,\n\t);\n\tconst layer = Layer.effect(\n\t\tkey,\n\t\tEffect.gen(function* () {\n\t\t\tconst session = yield* CurrentSession;\n\t\t\treturn yield* run(session, base);\n\t\t}),\n\t);\n\treturn { ...base, key, layer };\n};\n\nexport const Service = {\n\t/** Defines a service whose `start` handler you write yourself. */\n\tmake,\n\t/** Defines a service backed by a Docker container, ready once its healthcheck passes. */\n\tcontainer: <const Name extends string, const Port extends string = never>(\n\t\tdef: { name: Name } & ContainerSpec<Port>,\n\t) =>\n\t\tmake({\n\t\t\tname: def.name,\n\t\t\tports: Object.keys(def.ports ?? {}) as Port[],\n\t\t\tstart: startContainer(def.name, def),\n\t\t}),\n\t/** Starts a service in `session`, outside a layer. */\n\trun,\n};\n","import { Effect, Stream } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport { ChildProcess } from 'effect/process';\nimport { DevSessions } from '../dev-sessions';\nimport { type ServiceContext, ServiceError, validName } from './core';\nimport { buildRunArgs, type Healthcheck, selectOrphans } from './docker-args';\n\nexport type ContainerSpec<Port extends string> = {\n\timage: string;\n\tports?: Record<Port, number>;\n\tvolumes?: Record<string, string>;\n\tenv?: Record<string, string>;\n\thealthcheck?: Healthcheck;\n};\n\nconst docker = (\n\targs: string[],\n\toptions?: { progress?: boolean; allowFailure?: boolean },\n) =>\n\tEffect.gen(function* () {\n\t\tconst command = ChildProcess.make('docker', args, {\n\t\t\tstdout: options?.progress ? 'inherit' : 'pipe',\n\t\t\tstderr: 'inherit',\n\t\t});\n\t\tconst result = yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst child = yield* command;\n\t\t\t\tconst output = options?.progress\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (yield* Stream.mkString(Stream.decodeText(child.stdout))).trim();\n\t\t\t\tconst code = yield* child.exitCode;\n\t\t\t\treturn { output, code };\n\t\t\t}),\n\t\t).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause) =>\n\t\t\t\t\tnew ServiceError({\n\t\t\t\t\t\tmessage: `Docker command failed: docker ${args.join(' ')}`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tif (result.code !== 0 && !options?.allowFailure) {\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage: `Docker command failed (exit ${result.code}): docker ${args.join(' ')}`,\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t});\n\nconst listContainers = (root: string) =>\n\tEffect.gen(function* () {\n\t\tconst listed = yield* docker([\n\t\t\t'ps',\n\t\t\t'-a',\n\t\t\t'--filter',\n\t\t\t`label=devsess.root=${root}`,\n\t\t\t'--format',\n\t\t\t'{{.ID}}\\t{{.Label \"devsess.session\"}}',\n\t\t]);\n\t\tif (listed.output === undefined)\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage: 'Docker container listing returned no output',\n\t\t\t});\n\t\tconst containers: Array<{ id: string; session: string }> = [];\n\t\tfor (const line of listed.output.split('\\n').filter(Boolean)) {\n\t\t\tconst fields = line.split('\\t');\n\t\t\tconst id = fields[0];\n\t\t\tconst session = fields[1];\n\t\t\tif (!id || !session)\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Invalid devsess container metadata: ${line}`,\n\t\t\t\t});\n\t\t\tcontainers.push({ id, session });\n\t\t}\n\t\treturn containers;\n\t});\n\nconst waitHealthy = (name: string, container: string) =>\n\tEffect.gen(function* () {\n\t\twhile (true) {\n\t\t\tconst state = yield* docker([\n\t\t\t\t'inspect',\n\t\t\t\t'--format',\n\t\t\t\t'{{.State.Running}} {{.State.Health.Status}}',\n\t\t\t\tcontainer,\n\t\t\t]);\n\t\t\tif (state.output === undefined)\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Docker returned no status for ${container}`,\n\t\t\t\t});\n\t\t\tif (state.output === 'true healthy') return;\n\t\t\tif (state.output.startsWith('false '))\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Service ${name} exited before healthy; services run as your user; this image may require root`,\n\t\t\t\t});\n\t\t\tif (state.output === 'true unhealthy')\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Service ${name} is unhealthy`,\n\t\t\t\t});\n\t\t\tyield* Effect.sleep('500 millis');\n\t\t}\n\t});\n\nexport const startContainer =\n\t<Port extends string>(name: string, spec: ContainerSpec<Port>) =>\n\t(ctx: ServiceContext<Port>) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst fs = yield* FileSystem;\n\t\t\tconst path = yield* Path;\n\t\t\tconst sessions = yield* DevSessions;\n\n\t\t\tconst known = new Set(\n\t\t\t\t(yield* sessions.getSessions).map((existing) => existing.name),\n\t\t\t);\n\t\t\tconst containers = yield* listContainers(sessions.dir);\n\t\t\tfor (const id of selectOrphans(containers, known))\n\t\t\t\tyield* docker(['rm', '-f', id]);\n\n\t\t\tconst container = `devsess-${ctx.session.name}-${name}`;\n\t\t\tconst portBindings = Object.entries<number>(spec.ports ?? {}).map(\n\t\t\t\t(entry) => ({\n\t\t\t\t\thost: ctx.ports[entry[0] as Port],\n\t\t\t\t\tcontainer: entry[1],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst volumes: Array<{ source: string; target: string }> = [];\n\t\t\tfor (const entry of Object.entries(spec.volumes ?? {})) {\n\t\t\t\tif (!validName(entry[0]))\n\t\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\t\tmessage: `Invalid volume name: ${entry[0]}`,\n\t\t\t\t\t});\n\t\t\t\tvolumes.push({\n\t\t\t\t\tsource: path.join(ctx.dataDir, entry[0]),\n\t\t\t\t\ttarget: entry[1],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst start = Effect.gen(function* () {\n\t\t\t\tconst found = yield* docker(\n\t\t\t\t\t[\n\t\t\t\t\t\t'container',\n\t\t\t\t\t\t'inspect',\n\t\t\t\t\t\t'--format',\n\t\t\t\t\t\t'{{.State.Running}}\\t{{index .Config.Labels \"devsess.root\"}}\\t{{index .Config.Labels \"devsess.session\"}}',\n\t\t\t\t\t\tcontainer,\n\t\t\t\t\t],\n\t\t\t\t\t{ allowFailure: true },\n\t\t\t\t);\n\t\t\t\tconst identity = found.output?.split('\\t');\n\t\t\t\tif (\n\t\t\t\t\tfound.code === 0 &&\n\t\t\t\t\t(identity?.[1] !== sessions.dir || identity[2] !== ctx.session.name)\n\t\t\t\t)\n\t\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\t\tmessage: `Container name ${container} belongs to another session or project`,\n\t\t\t\t\t});\n\t\t\t\tif (found.code === 0 && identity?.[0] === 'true') return;\n\t\t\t\tif (found.code === 0) yield* docker(['rm', '-f', container]);\n\t\t\t\tif (!process.getuid || !process.getgid)\n\t\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\t\tmessage: 'Docker services require a POSIX user and group ID',\n\t\t\t\t\t});\n\t\t\t\tfor (const volume of volumes)\n\t\t\t\t\tyield* fs.makeDirectory(volume.source, { recursive: true });\n\t\t\t\tconst image = yield* docker(\n\t\t\t\t\t['image', 'inspect', '--format', '{{.Id}}', spec.image],\n\t\t\t\t\t{ allowFailure: true },\n\t\t\t\t);\n\t\t\t\tif (image.code !== 0) {\n\t\t\t\t\tyield* Effect.logInfo(`[dev] pulling ${spec.image}`);\n\t\t\t\t\tyield* docker(['pull', spec.image], { progress: true });\n\t\t\t\t}\n\t\t\t\tyield* docker(\n\t\t\t\t\tbuildRunArgs({\n\t\t\t\t\t\timage: spec.image,\n\t\t\t\t\t\tcontainer,\n\t\t\t\t\t\troot: sessions.dir,\n\t\t\t\t\t\tsession: ctx.session.name,\n\t\t\t\t\t\tuid: process.getuid(),\n\t\t\t\t\t\tgid: process.getgid(),\n\t\t\t\t\t\tports: portBindings,\n\t\t\t\t\t\tvolumes,\n\t\t\t\t\t\tenv: spec.env ?? {},\n\t\t\t\t\t\thealthcheck: spec.healthcheck,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t});\n\t\t\tyield* Effect.acquireRelease(start, () =>\n\t\t\t\tdocker(['rm', '-f', container]).pipe(\n\t\t\t\t\tEffect.catch((error) =>\n\t\t\t\t\t\tEffect.logError(`[dev] failed to remove ${container}: ${error}`),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tif (spec.healthcheck) yield* waitHealthy(name, container);\n\t\t\treturn { container };\n\t\t}).pipe(\n\t\t\tEffect.mapError((cause) =>\n\t\t\t\tcause instanceof ServiceError\n\t\t\t\t\t? cause\n\t\t\t\t\t: new ServiceError({\n\t\t\t\t\t\t\tmessage: `Failed to start container for service ${name}`,\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n","import { Data } from 'effect';\nimport type { DevSession } from '../dev-sessions';\n\nexport class ServiceError extends Data.TaggedError('ServiceError')<{\n\tmessage: string;\n\tcause?: unknown;\n}> {}\n\nexport type ServiceContext<Port extends string> = {\n\treadonly session: DevSession;\n\treadonly ports: Readonly<Record<Port, number>>;\n\t/** `<session>/services/<service>`, created before `start` runs and kept after it stops. */\n\treadonly dataDir: string;\n};\n\nexport const validName = (name: string) =>\n\t/^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/.test(name);\n","import { Duration } from 'effect';\n\nexport type Healthcheck = {\n\ttest: readonly string[];\n\tinterval?: Duration.Input;\n\ttimeout?: Duration.Input;\n\tretries?: number;\n\tstartPeriod?: Duration.Input;\n};\n\nconst dockerDuration = (input: Duration.Input) =>\n\t`${Duration.toMillis(input)}ms`;\n\nconst shellQuote = (part: string) => `'${part.replaceAll(\"'\", \"'\\\\''\")}'`;\n\n// `docker run --health-cmd` always goes through a shell, unlike compose's exec-form `CMD`.\nconst healthCmd = (test: readonly string[]) => {\n\tif (test[0] === 'CMD-SHELL') return test.slice(1).join(' ');\n\tif (test[0] === 'CMD') return test.slice(1).map(shellQuote).join(' ');\n\treturn test.join(' ');\n};\n\nexport const buildRunArgs = (input: {\n\timage: string;\n\tcontainer: string;\n\troot: string;\n\tsession: string;\n\tuid: number;\n\tgid: number;\n\tports: ReadonlyArray<{ host: number; container: number }>;\n\tvolumes: ReadonlyArray<{ source: string; target: string }>;\n\tenv: Record<string, string>;\n\thealthcheck: Healthcheck | undefined;\n}): string[] => {\n\tconst args = [\n\t\t'run',\n\t\t'-d',\n\t\t'--name',\n\t\tinput.container,\n\t\t'--label',\n\t\t`devsess.session=${input.session}`,\n\t\t'--label',\n\t\t`devsess.root=${input.root}`,\n\t\t'--user',\n\t\t`${input.uid}:${input.gid}`,\n\t];\n\tfor (const port of input.ports)\n\t\targs.push('-p', `127.0.0.1:${port.host}:${port.container}`);\n\tfor (const volume of input.volumes)\n\t\targs.push('-v', `${volume.source}:${volume.target}`);\n\tfor (const entry of Object.entries(input.env))\n\t\targs.push('-e', `${entry[0]}=${entry[1]}`);\n\tconst healthcheck = input.healthcheck;\n\tif (healthcheck) {\n\t\targs.push('--health-cmd', healthCmd(healthcheck.test));\n\t\tif (healthcheck.interval !== undefined)\n\t\t\targs.push('--health-interval', dockerDuration(healthcheck.interval));\n\t\tif (healthcheck.timeout !== undefined)\n\t\t\targs.push('--health-timeout', dockerDuration(healthcheck.timeout));\n\t\tif (healthcheck.retries !== undefined)\n\t\t\targs.push('--health-retries', String(healthcheck.retries));\n\t\tif (healthcheck.startPeriod !== undefined)\n\t\t\targs.push(\n\t\t\t\t'--health-start-period',\n\t\t\t\tdockerDuration(healthcheck.startPeriod),\n\t\t\t);\n\t}\n\targs.push(input.image);\n\treturn args;\n};\n\nexport const selectOrphans = (\n\tcontainers: ReadonlyArray<{ id: string; session: string }>,\n\texistingSessions: ReadonlySet<string>,\n) =>\n\tcontainers\n\t\t.filter((container) => !existingSessions.has(container.session))\n\t\t.map((container) => container.id);\n"],"mappings":"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","names":["Effect","FileSystem","Path","Effect","FileSystem","Path"]}
1
+ {"version":3,"sources":["../../src/services/index.ts","../../src/services/container.ts","../../src/services/docker-args.ts","../../src/services/shared/index.ts","../../src/services/shared/callsite.ts","../../src/services/shared/root.ts"],"sourcesContent":["import { Context, Effect, Layer } from 'effect';\nimport type { FileSystem } from 'effect/FileSystem';\nimport type { Path } from 'effect/Path';\nimport type { Scope } from 'effect/Scope';\nimport { CurrentSession } from '../current-session';\nimport type { DevSession } from '../dev-sessions';\nimport { type ContainerSpec, startContainer } from './container';\nimport {\n\trunningService,\n\ttype ServiceContext,\n\tServiceError,\n\tserviceBrand,\n} from './core';\nimport { runLocal } from './local';\nimport { runShared, type SharedOptions } from './shared/index';\n\nexport type { Healthcheck } from './docker-args';\nexport { type ServiceContext, ServiceError };\n\nexport type RunningService<Port extends string, A> = (A extends object\n\t? Omit<A, 'ports'>\n\t: unknown) & { readonly ports: Readonly<Record<Port, number>> };\n\ntype ServiceIdentifier<Name extends string> = {\n\treadonly devsessService: Name;\n};\n\nexport type ServiceDefinition<\n\tName extends string,\n\tPort extends string,\n\tA,\n\tE,\n\tR,\n> = {\n\treadonly [serviceBrand]: true;\n\treadonly name: Name;\n\treadonly ports: ReadonlyArray<Port>;\n\treadonly shared?: SharedOptions<A>;\n\treadonly definitionStack?: string;\n\t/** Resolves once the service is ready; its scope closing stops it. */\n\treadonly start: (ctx: ServiceContext<Port>) => Effect.Effect<A, E, R>;\n\t/** Yield inside another service's `start` (or any effect) to depend on this one. */\n\treadonly key: Context.Key<ServiceIdentifier<Name>, RunningService<Port, A>>;\n\t/** Starts the service once per layer build and shares it with every consumer. */\n\treadonly layer: Layer.Layer<\n\t\tServiceIdentifier<Name>,\n\t\tE | ServiceError,\n\t\tExclude<R, Scope> | CurrentSession | FileSystem | Path\n\t>;\n};\n\nconst run = <Name extends string, Port extends string, A, E, R>(\n\tsession: DevSession,\n\tdef: Pick<\n\t\tServiceDefinition<Name, Port, A, E, R>,\n\t\t'name' | 'ports' | 'start' | 'shared' | 'definitionStack'\n\t>,\n) =>\n\t(def.shared !== undefined\n\t\t? runShared<Port, A>(session, def.name, def.shared, def.definitionStack)\n\t\t: runLocal(session, def)\n\t).pipe(Effect.map(runningService<Port, A>));\n\nconst make = <\n\tconst Name extends string,\n\tA,\n\tE,\n\tR,\n\tconst Port extends string = never,\n>(def: {\n\tname: Name;\n\tports?: ReadonlyArray<Port>;\n\tshared?: SharedOptions<A>;\n\tstart: (ctx: ServiceContext<Port>) => Effect.Effect<A, E, R>;\n}): ServiceDefinition<Name, Port, A, E, R> => {\n\tconst base = {\n\t\t[serviceBrand]: true as const,\n\t\tname: def.name,\n\t\tports: def.ports ?? [],\n\t\tstart: def.start,\n\t\tshared: def.shared,\n\t\tdefinitionStack: def.shared === undefined ? undefined : new Error().stack,\n\t};\n\tconst key = Context.Service<ServiceIdentifier<Name>, RunningService<Port, A>>(\n\t\t`devsess/services/${def.name}`,\n\t);\n\tconst layer = Layer.effect(\n\t\tkey,\n\t\tEffect.gen(function* () {\n\t\t\tconst session = yield* CurrentSession;\n\t\t\treturn yield* run(session, base);\n\t\t}),\n\t);\n\treturn { ...base, key, layer };\n};\n\nexport const Service = {\n\t/** Defines a service whose `start` handler you write yourself. */\n\tmake,\n\t/** Defines a service backed by a Docker container, ready once its healthcheck passes. */\n\tcontainer: <const Name extends string, const Port extends string = never>(\n\t\tdef: { name: Name } & ContainerSpec<Port>,\n\t) =>\n\t\tmake({\n\t\t\tname: def.name,\n\t\t\tports: Object.keys(def.ports ?? {}) as Port[],\n\t\t\tstart: startContainer(def.name, def),\n\t\t}),\n\t/** Starts a service in `session`, outside a layer. */\n\trun,\n};\n","import { Effect, Stream } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\nimport { ChildProcess } from 'effect/process';\nimport { DevSessions } from '../dev-sessions';\nimport {\n\ttype ServiceContext,\n\tServiceError,\n\tvalidName,\n\twithServiceError,\n} from './core';\nimport { buildRunArgs, type Healthcheck, selectOrphans } from './docker-args';\n\nexport type ContainerSpec<Port extends string> = {\n\timage: string;\n\tports?: Record<Port, number>;\n\tvolumes?: Record<string, string>;\n\tenv?: Record<string, string>;\n\thealthcheck?: Healthcheck;\n};\n\nconst docker = (\n\targs: string[],\n\toptions?: { progress?: boolean; allowFailure?: boolean },\n) =>\n\tEffect.gen(function* () {\n\t\tconst command = ChildProcess.make('docker', args, {\n\t\t\tstdout: options?.progress ? 'inherit' : 'pipe',\n\t\t\tstderr: 'inherit',\n\t\t});\n\t\tconst result = yield* Effect.scoped(\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst child = yield* command;\n\t\t\t\tconst output = options?.progress\n\t\t\t\t\t? undefined\n\t\t\t\t\t: (yield* Stream.mkString(Stream.decodeText(child.stdout))).trim();\n\t\t\t\tconst code = yield* child.exitCode;\n\t\t\t\treturn { output, code };\n\t\t\t}),\n\t\t).pipe(\n\t\t\tEffect.mapError(\n\t\t\t\t(cause) =>\n\t\t\t\t\tnew ServiceError({\n\t\t\t\t\t\tmessage: `Docker command failed: docker ${args.join(' ')}`,\n\t\t\t\t\t\tcause,\n\t\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tif (result.code !== 0 && !options?.allowFailure) {\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage: `Docker command failed (exit ${result.code}): docker ${args.join(' ')}`,\n\t\t\t});\n\t\t}\n\t\treturn result;\n\t});\n\nconst listContainers = (root: string) =>\n\tEffect.gen(function* () {\n\t\tconst listed = yield* docker([\n\t\t\t'ps',\n\t\t\t'-a',\n\t\t\t'--filter',\n\t\t\t`label=devsess.root=${root}`,\n\t\t\t'--format',\n\t\t\t'{{.ID}}\\t{{.Label \"devsess.session\"}}',\n\t\t]);\n\t\tif (listed.output === undefined)\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage: 'Docker container listing returned no output',\n\t\t\t});\n\t\tconst containers: Array<{ id: string; session: string }> = [];\n\t\tfor (const line of listed.output.split('\\n').filter(Boolean)) {\n\t\t\tconst fields = line.split('\\t');\n\t\t\tconst id = fields[0];\n\t\t\tconst session = fields[1];\n\t\t\tif (!id || !session)\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Invalid devsess container metadata: ${line}`,\n\t\t\t\t});\n\t\t\tcontainers.push({ id, session });\n\t\t}\n\t\treturn containers;\n\t});\n\nconst waitHealthy = (name: string, container: string) =>\n\tEffect.gen(function* () {\n\t\twhile (true) {\n\t\t\tconst state = yield* docker([\n\t\t\t\t'inspect',\n\t\t\t\t'--format',\n\t\t\t\t'{{.State.Running}} {{.State.Health.Status}}',\n\t\t\t\tcontainer,\n\t\t\t]);\n\t\t\tif (state.output === undefined)\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Docker returned no status for ${container}`,\n\t\t\t\t});\n\t\t\tif (state.output === 'true healthy') return;\n\t\t\tif (state.output.startsWith('false '))\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Service ${name} exited before healthy; services run as your user; this image may require root`,\n\t\t\t\t});\n\t\t\tif (state.output === 'true unhealthy')\n\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\tmessage: `Service ${name} is unhealthy`,\n\t\t\t\t});\n\t\t\tyield* Effect.sleep('500 millis');\n\t\t}\n\t});\n\nexport const startContainer =\n\t<Port extends string>(name: string, spec: ContainerSpec<Port>) =>\n\t(ctx: ServiceContext<Port>) =>\n\t\tEffect.gen(function* () {\n\t\t\tconst fs = yield* FileSystem;\n\t\t\tconst path = yield* Path;\n\t\t\tconst sessions = yield* DevSessions;\n\n\t\t\tconst known = new Set(\n\t\t\t\t(yield* sessions.getSessions).map((existing) => existing.name),\n\t\t\t);\n\t\t\tconst containers = yield* listContainers(sessions.dir);\n\t\t\tfor (const id of selectOrphans(containers, known))\n\t\t\t\tyield* docker(['rm', '-f', id]);\n\n\t\t\tconst container = `devsess-${ctx.session.name}-${name}`;\n\t\t\tconst portBindings = Object.entries<number>(spec.ports ?? {}).map(\n\t\t\t\t(entry) => ({\n\t\t\t\t\thost: ctx.ports[entry[0] as Port],\n\t\t\t\t\tcontainer: entry[1],\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst volumes: Array<{ source: string; target: string }> = [];\n\t\t\tfor (const entry of Object.entries(spec.volumes ?? {})) {\n\t\t\t\tif (!validName(entry[0]))\n\t\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\t\tmessage: `Invalid volume name: ${entry[0]}`,\n\t\t\t\t\t});\n\t\t\t\tvolumes.push({\n\t\t\t\t\tsource: path.join(ctx.dataDir, entry[0]),\n\t\t\t\t\ttarget: entry[1],\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst start = Effect.gen(function* () {\n\t\t\t\tconst found = yield* docker(\n\t\t\t\t\t[\n\t\t\t\t\t\t'container',\n\t\t\t\t\t\t'inspect',\n\t\t\t\t\t\t'--format',\n\t\t\t\t\t\t'{{.State.Running}}\\t{{index .Config.Labels \"devsess.root\"}}\\t{{index .Config.Labels \"devsess.session\"}}',\n\t\t\t\t\t\tcontainer,\n\t\t\t\t\t],\n\t\t\t\t\t{ allowFailure: true },\n\t\t\t\t);\n\t\t\t\tconst identity = found.output?.split('\\t');\n\t\t\t\tif (\n\t\t\t\t\tfound.code === 0 &&\n\t\t\t\t\t(identity?.[1] !== sessions.dir || identity[2] !== ctx.session.name)\n\t\t\t\t)\n\t\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\t\tmessage: `Container name ${container} belongs to another session or project`,\n\t\t\t\t\t});\n\t\t\t\tif (found.code === 0 && identity?.[0] === 'true') return;\n\t\t\t\tif (found.code === 0) yield* docker(['rm', '-f', container]);\n\t\t\t\tif (!process.getuid || !process.getgid)\n\t\t\t\t\treturn yield* new ServiceError({\n\t\t\t\t\t\tmessage: 'Docker services require a POSIX user and group ID',\n\t\t\t\t\t});\n\t\t\t\tfor (const volume of volumes)\n\t\t\t\t\tyield* fs.makeDirectory(volume.source, { recursive: true });\n\t\t\t\tconst image = yield* docker(\n\t\t\t\t\t['image', 'inspect', '--format', '{{.Id}}', spec.image],\n\t\t\t\t\t{ allowFailure: true },\n\t\t\t\t);\n\t\t\t\tif (image.code !== 0) {\n\t\t\t\t\tyield* Effect.logInfo(`[dev] pulling ${spec.image}`);\n\t\t\t\t\tyield* docker(['pull', spec.image], { progress: true });\n\t\t\t\t}\n\t\t\t\tyield* docker(\n\t\t\t\t\tbuildRunArgs({\n\t\t\t\t\t\timage: spec.image,\n\t\t\t\t\t\tcontainer,\n\t\t\t\t\t\troot: sessions.dir,\n\t\t\t\t\t\tsession: ctx.session.name,\n\t\t\t\t\t\tuid: process.getuid(),\n\t\t\t\t\t\tgid: process.getgid(),\n\t\t\t\t\t\tports: portBindings,\n\t\t\t\t\t\tvolumes,\n\t\t\t\t\t\tenv: spec.env ?? {},\n\t\t\t\t\t\thealthcheck: spec.healthcheck,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t});\n\t\t\tyield* Effect.acquireRelease(start, () =>\n\t\t\t\tdocker(['rm', '-f', container]).pipe(\n\t\t\t\t\tEffect.catch((error) =>\n\t\t\t\t\t\tEffect.logError(`[dev] failed to remove ${container}: ${error}`),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t\tif (spec.healthcheck) yield* waitHealthy(name, container);\n\t\t\treturn { container };\n\t\t}).pipe(withServiceError(`Failed to start container for service ${name}`));\n","import { Duration } from 'effect';\n\nexport type Healthcheck = {\n\ttest: readonly string[];\n\tinterval?: Duration.Input;\n\ttimeout?: Duration.Input;\n\tretries?: number;\n\tstartPeriod?: Duration.Input;\n};\n\nconst dockerDuration = (input: Duration.Input) =>\n\t`${Duration.toMillis(input)}ms`;\n\nconst shellQuote = (part: string) => `'${part.replaceAll(\"'\", \"'\\\\''\")}'`;\n\n// `docker run --health-cmd` always goes through a shell, unlike compose's exec-form `CMD`.\nconst healthCmd = (test: readonly string[]) => {\n\tif (test[0] === 'CMD-SHELL') return test.slice(1).join(' ');\n\tif (test[0] === 'CMD') return test.slice(1).map(shellQuote).join(' ');\n\treturn test.join(' ');\n};\n\nexport const buildRunArgs = (input: {\n\timage: string;\n\tcontainer: string;\n\troot: string;\n\tsession: string;\n\tuid: number;\n\tgid: number;\n\tports: ReadonlyArray<{ host: number; container: number }>;\n\tvolumes: ReadonlyArray<{ source: string; target: string }>;\n\tenv: Record<string, string>;\n\thealthcheck: Healthcheck | undefined;\n}): string[] => {\n\tconst args = [\n\t\t'run',\n\t\t'-d',\n\t\t'--name',\n\t\tinput.container,\n\t\t'--label',\n\t\t`devsess.session=${input.session}`,\n\t\t'--label',\n\t\t`devsess.root=${input.root}`,\n\t\t'--user',\n\t\t`${input.uid}:${input.gid}`,\n\t];\n\tfor (const port of input.ports)\n\t\targs.push('-p', `127.0.0.1:${port.host}:${port.container}`);\n\tfor (const volume of input.volumes)\n\t\targs.push('-v', `${volume.source}:${volume.target}`);\n\tfor (const entry of Object.entries(input.env))\n\t\targs.push('-e', `${entry[0]}=${entry[1]}`);\n\tconst healthcheck = input.healthcheck;\n\tif (healthcheck) {\n\t\targs.push('--health-cmd', healthCmd(healthcheck.test));\n\t\tif (healthcheck.interval !== undefined)\n\t\t\targs.push('--health-interval', dockerDuration(healthcheck.interval));\n\t\tif (healthcheck.timeout !== undefined)\n\t\t\targs.push('--health-timeout', dockerDuration(healthcheck.timeout));\n\t\tif (healthcheck.retries !== undefined)\n\t\t\targs.push('--health-retries', String(healthcheck.retries));\n\t\tif (healthcheck.startPeriod !== undefined)\n\t\t\targs.push(\n\t\t\t\t'--health-start-period',\n\t\t\t\tdockerDuration(healthcheck.startPeriod),\n\t\t\t);\n\t}\n\targs.push(input.image);\n\treturn args;\n};\n\nexport const selectOrphans = (\n\tcontainers: ReadonlyArray<{ id: string; session: string }>,\n\texistingSessions: ReadonlySet<string>,\n) =>\n\tcontainers\n\t\t.filter((container) => !existingSessions.has(container.session))\n\t\t.map((container) => container.id);\n","import { Effect, Schema } from 'effect';\nimport { reportDaemonService } from '../../dev/daemon-services';\nimport { type DevSession, DevSessions } from '../../dev-sessions';\nimport { ServiceError, validName, withServiceError } from '../core';\nimport { definitionModule } from './callsite';\nimport {\n\tacquireHost,\n\tfailWhenHostExits,\n\thostPaths,\n\tlaunchHost,\n\treadyMessage,\n} from './protocol';\nimport { findSharedRoot } from './root';\n\nexport type SharedOptions<A> = { readonly output: Schema.Codec<A, unknown> };\nconst locateHost = (\n\tsession: DevSession,\n\tname: string,\n\tstack: string | undefined,\n) =>\n\tEffect.gen(function* () {\n\t\tconst module = yield* definitionModule(stack);\n\t\tconst root = yield* findSharedRoot(session.rootDir);\n\t\tconst sharedSession = yield* DevSessions.use((store) =>\n\t\t\tstore.getOrCreate('shared-services'),\n\t\t).pipe(Effect.provide(DevSessions.layerAt(root)));\n\t\tconst paths = hostPaths(yield* sharedSession.path(''), name);\n\t\treturn { paths, args: { module, name, root, session: sharedSession.name } };\n\t});\n\nexport const runShared = <Port extends string, A>(\n\tsession: DevSession,\n\tname: string,\n\toptions: SharedOptions<A>,\n\tstack: string | undefined,\n) =>\n\tEffect.gen(function* () {\n\t\tif (!validName(name) || !validName(session.name))\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage: 'Invalid shared service or session name',\n\t\t\t});\n\t\tconst host = yield* locateHost(session, name, stack);\n\t\tconst lease = yield* Effect.acquireRelease(\n\t\t\tacquireHost(host.paths, () => launchHost(host.paths, host.args)),\n\t\t\t(value) => Effect.sync(() => value.socket.destroy()),\n\t\t);\n\t\tyield* Effect.forkScoped(\n\t\t\tfailWhenHostExits(lease, name).pipe(\n\t\t\t\tEffect.catch((error) => Effect.logError(error)),\n\t\t\t),\n\t\t);\n\t\tconst ready = yield* Schema.decodeUnknownEffect(\n\t\t\treadyMessage(options.output),\n\t\t)(lease.line).pipe(withServiceError(`Invalid shared output from ${name}`));\n\t\tyield* reportDaemonService(name, ready.ports);\n\t\treturn {\n\t\t\tvalue: ready.output,\n\t\t\tports: ready.ports as Readonly<Record<Port, number>>,\n\t\t};\n\t}).pipe(withServiceError(`Failed to run shared service ${name}`));\n","import { resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { Effect } from 'effect';\nimport { ServiceError } from '../core';\n\nconst packageDir = fileURLToPath(import.meta.url).match(\n\t/^(.*)\\/(?:src|dist)\\//,\n)?.[1];\n\nexport const definitionModule = (stack: string | undefined) =>\n\tEffect.gen(function* () {\n\t\tif (packageDir === undefined)\n\t\t\treturn yield* new ServiceError({\n\t\t\t\tmessage: 'Unable to identify devsess internal stack frames',\n\t\t\t});\n\t\tif (stack !== undefined) {\n\t\t\tfor (const frame of stack.split('\\n').slice(1)) {\n\t\t\t\tconst match = frame.match(\n\t\t\t\t\t/(?:\\(|\\s|@)((?:file:\\/\\/|\\/)[^\\n]*?):\\d+:\\d+\\)?$/,\n\t\t\t\t);\n\t\t\t\tconst location = match?.[1];\n\t\t\t\tif (location === undefined) continue;\n\t\t\t\tconst file = yield* Effect.try({\n\t\t\t\t\ttry: () =>\n\t\t\t\t\t\tlocation.startsWith('file://')\n\t\t\t\t\t\t\t? fileURLToPath(location)\n\t\t\t\t\t\t\t: resolve(location),\n\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\tnew ServiceError({\n\t\t\t\t\t\t\tmessage: 'Invalid service callsite path',\n\t\t\t\t\t\t\tcause,\n\t\t\t\t\t\t}),\n\t\t\t\t});\n\t\t\t\tif (\n\t\t\t\t\tfile.startsWith(`${packageDir}/src/`) ||\n\t\t\t\t\tfile.startsWith(`${packageDir}/dist/`)\n\t\t\t\t)\n\t\t\t\t\tcontinue;\n\t\t\t\treturn yield* Effect.try({\n\t\t\t\t\ttry: () => pathToFileURL(file).href,\n\t\t\t\t\tcatch: (cause) =>\n\t\t\t\t\t\tnew ServiceError({ message: 'Invalid service module URL', cause }),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn yield* new ServiceError({\n\t\t\tmessage:\n\t\t\t\t'Unable to locate the file that calls Service.make for a shared service',\n\t\t});\n\t});\n","import { Effect } from 'effect';\nimport { FileSystem } from 'effect/FileSystem';\nimport { Path } from 'effect/Path';\n\nexport const findSharedRoot = (projectDir: string) =>\n\tEffect.gen(function* () {\n\t\tconst fs = yield* FileSystem;\n\t\tconst path = yield* Path;\n\t\tconst start = path.resolve(projectDir);\n\t\tconst walk = (\n\t\t\tdir: string,\n\t\t\tcandidate: string,\n\t\t): Effect.Effect<string, import('effect/PlatformError').PlatformError> =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst topmost = yield* fs.exists(path.join(dir, 'package.json'));\n\t\t\t\tconst next = topmost ? dir : candidate;\n\t\t\t\tif (yield* fs.exists(path.join(dir, '.git'))) return next;\n\t\t\t\tconst parent = path.dirname(dir);\n\t\t\t\tif (parent === dir) return start;\n\t\t\t\treturn yield* walk(parent, next);\n\t\t\t});\n\t\treturn yield* walk(start, start);\n\t});\n"],"mappings":"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","names":["Effect","Effect","Effect","Effect","Effect","FileSystem","Path","Effect","Effect"]}
@@ -0,0 +1,11 @@
1
+ import { Effect } from 'effect';
2
+ import { FileSystem } from 'effect/FileSystem';
3
+ import { Path } from 'effect/Path';
4
+ import type { DevSession } from '../dev-sessions';
5
+ import { ServiceError } from './core';
6
+ import type { ServiceDefinition } from './index';
7
+ export declare const runLocal: <Name extends string, Port extends string, A, E, R>(session: DevSession, def: Pick<ServiceDefinition<Name, Port, A, E, R>, 'name' | 'ports' | 'start'>) => Effect.Effect<{
8
+ value: A;
9
+ ports: Record<Port, number>;
10
+ }, E | ServiceError, R | FileSystem | Path | import("effect/Scope").Scope>;
11
+ //# sourceMappingURL=local.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/services/local.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,YAAY,EAA+B,MAAM,QAAQ,CAAC;AACnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAyBjD,eAAO,MAAM,QAAQ,GAAI,IAAI,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,WAChE,UAAU,OACd,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;;;0EAW1B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Effect } from 'effect';
2
+ import { ServiceError } from '../core';
3
+ export declare const definitionModule: (stack: string | undefined) => Effect.Effect<string, ServiceError, never>;
4
+ //# sourceMappingURL=callsite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callsite.d.ts","sourceRoot":"","sources":["../../../src/services/shared/callsite.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMvC,eAAO,MAAM,gBAAgB,UAAW,MAAM,GAAG,SAAS,+CAwCvD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../../src/services/shared/entry.ts"],"names":[],"mappings":""}
@@ -0,0 +1,137 @@
1
+ import {
2
+ ServiceError,
3
+ hostArguments,
4
+ hostPaths,
5
+ io,
6
+ isServiceDefinition,
7
+ readyMessage,
8
+ runLocal,
9
+ sharedGraceMs,
10
+ socketPath,
11
+ startupLock
12
+ } from "../../chunk-OODLZZCC.js";
13
+ import {
14
+ DevSessions
15
+ } from "../../chunk-MBPIOYW3.js";
16
+
17
+ // src/services/shared/entry.ts
18
+ import { NodeServices } from "@effect/platform-node";
19
+ import { Effect as Effect2, Exit, Schema, Scope } from "effect";
20
+
21
+ // src/services/shared/host.ts
22
+ import { rm } from "fs/promises";
23
+ import { createServer } from "net";
24
+ import { Effect } from "effect";
25
+ var serveHost = (paths, line, stop, graceMs = sharedGraceMs) => Effect.gen(function* () {
26
+ const address = yield* io(
27
+ "Failed to resolve shared host socket",
28
+ () => socketPath(paths)
29
+ );
30
+ return yield* Effect.callback((resume) => {
31
+ const state = {
32
+ consumers: 0,
33
+ stopping: false,
34
+ timer: void 0
35
+ };
36
+ const shutdown = Effect.gen(function* () {
37
+ yield* Effect.acquireRelease(
38
+ startupLock(paths),
39
+ (release) => io("Failed to release shared host lock", release).pipe(Effect.orDie)
40
+ );
41
+ if (state.consumers !== 0) return;
42
+ state.stopping = true;
43
+ server.close();
44
+ yield* io(
45
+ "Failed to remove shared host socket",
46
+ () => rm(paths.socket, { force: true })
47
+ ).pipe(Effect.ensuring(stop));
48
+ }).pipe(
49
+ Effect.scoped,
50
+ Effect.matchCauseEffect({
51
+ onFailure: (cause) => Effect.sync(() => resume(Effect.failCause(cause))),
52
+ onSuccess: () => Effect.sync(() => {
53
+ if (state.stopping) resume(Effect.void);
54
+ })
55
+ })
56
+ );
57
+ const schedule = () => {
58
+ if (state.timer !== void 0) clearTimeout(state.timer);
59
+ state.timer = setTimeout(() => Effect.runFork(shutdown), graceMs);
60
+ };
61
+ const server = createServer((socket) => {
62
+ if (state.stopping) {
63
+ socket.destroy();
64
+ return;
65
+ }
66
+ state.consumers++;
67
+ if (state.timer !== void 0) clearTimeout(state.timer);
68
+ socket.on("error", () => socket.destroy());
69
+ socket.once("close", () => {
70
+ state.consumers--;
71
+ if (state.consumers === 0) schedule();
72
+ });
73
+ socket.write(`${line}
74
+ `);
75
+ });
76
+ server.once("error", (cause) => resume(Effect.die(cause)));
77
+ server.listen(address, schedule);
78
+ return Effect.suspend(
79
+ () => state.stopping ? Effect.void : Effect.sync(() => {
80
+ if (state.timer !== void 0) clearTimeout(state.timer);
81
+ server.close();
82
+ }).pipe(
83
+ Effect.ensuring(
84
+ io(
85
+ "Failed to remove shared host socket",
86
+ () => rm(paths.socket, { force: true })
87
+ ).pipe(Effect.orDie)
88
+ )
89
+ )
90
+ );
91
+ });
92
+ });
93
+
94
+ // src/services/shared/entry.ts
95
+ var program = Effect2.gen(function* () {
96
+ const args = yield* Schema.decodeUnknownEffect(hostArguments)(
97
+ process.argv[2]
98
+ );
99
+ const module = yield* io(
100
+ "Failed to import shared service definition",
101
+ () => import(args.module)
102
+ );
103
+ const candidates = Object.values(module).filter(isServiceDefinition).filter((value) => value.name === args.name);
104
+ const def = candidates[0];
105
+ if (candidates.length !== 1 || def === void 0 || def.shared === void 0)
106
+ return yield* new ServiceError({
107
+ message: `export the shared service \`${args.name}\` from the file that calls Service.make (${args.module}); exactly one matching export is required`
108
+ });
109
+ const scope = yield* Scope.make();
110
+ const output = def.shared.output;
111
+ const session = yield* DevSessions.use(
112
+ (sessions) => sessions.getOrCreate(args.session)
113
+ ).pipe(Effect2.provide(DevSessions.layerAt(args.root)));
114
+ const paths = hostPaths(yield* session.path(""), def.name);
115
+ const close = Scope.close(scope, Exit.void);
116
+ yield* Effect2.gen(function* () {
117
+ const result = yield* runLocal(session, def).pipe(
118
+ Effect2.provideService(Scope.Scope, scope)
119
+ );
120
+ const line = yield* Schema.encodeEffect(readyMessage(output))({
121
+ output: result.value,
122
+ ports: result.ports
123
+ });
124
+ yield* serveHost(paths, line, close);
125
+ }).pipe(Effect2.ensuring(close));
126
+ }).pipe(Effect2.provide(NodeServices.layer));
127
+ var lifetime = Effect2.acquireUseRelease(
128
+ Effect2.sync(() => setInterval(() => {
129
+ }, 1e3)),
130
+ () => program,
131
+ (timer) => Effect2.sync(() => clearInterval(timer))
132
+ );
133
+ Effect2.runPromise(lifetime).catch((cause) => {
134
+ Effect2.runSync(Effect2.logError(cause));
135
+ process.exitCode = 1;
136
+ });
137
+ //# sourceMappingURL=entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/services/shared/entry.ts","../../../src/services/shared/host.ts"],"sourcesContent":["import { NodeServices } from '@effect/platform-node';\nimport { Effect, Exit, Schema, Scope } from 'effect';\nimport { DevSessions } from '../../dev-sessions';\nimport { isServiceDefinition, ServiceError } from '../core';\nimport { runLocal } from '../local';\nimport { serveHost } from './host';\nimport { hostArguments, hostPaths, io, readyMessage } from './protocol';\n\nconst program = Effect.gen(function* () {\n\tconst args = yield* Schema.decodeUnknownEffect(hostArguments)(\n\t\tprocess.argv[2],\n\t);\n\tconst module = yield* io(\n\t\t'Failed to import shared service definition',\n\t\t() => import(args.module) as Promise<Record<string, unknown>>,\n\t);\n\tconst candidates = Object.values(module)\n\t\t.filter(isServiceDefinition)\n\t\t.filter((value) => value.name === args.name);\n\tconst def = candidates[0];\n\tif (candidates.length !== 1 || def === undefined || def.shared === undefined)\n\t\treturn yield* new ServiceError({\n\t\t\tmessage: `export the shared service \\`${args.name}\\` from the file that calls Service.make (${args.module}); exactly one matching export is required`,\n\t\t});\n\tconst scope = yield* Scope.make();\n\tconst output = def.shared.output;\n\tconst session = yield* DevSessions.use((sessions) =>\n\t\tsessions.getOrCreate(args.session),\n\t).pipe(Effect.provide(DevSessions.layerAt(args.root)));\n\tconst paths = hostPaths(yield* session.path(''), def.name);\n\tconst close = Scope.close(scope, Exit.void);\n\tyield* Effect.gen(function* () {\n\t\tconst result = yield* runLocal(session, def).pipe(\n\t\t\tEffect.provideService(Scope.Scope, scope),\n\t\t);\n\t\tconst line = yield* Schema.encodeEffect(readyMessage(output))({\n\t\t\toutput: result.value,\n\t\t\tports: result.ports,\n\t\t});\n\t\tyield* serveHost(paths, line, close);\n\t}).pipe(Effect.ensuring(close));\n}).pipe(Effect.provide(NodeServices.layer));\n\n/** A pending Promise and unref'd handler resources cannot keep the process alive during scope close. */\nconst lifetime = Effect.acquireUseRelease(\n\tEffect.sync(() => setInterval(() => {}, 1_000)),\n\t() => program,\n\t(timer) => Effect.sync(() => clearInterval(timer)),\n);\n\nEffect.runPromise(lifetime).catch((cause) => {\n\tEffect.runSync(Effect.logError(cause));\n\tprocess.exitCode = 1;\n});\n","import { rm } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport { Effect } from 'effect';\nimport type { ServiceError } from '../core';\nimport {\n\ttype HostPaths,\n\tio,\n\tsharedGraceMs,\n\tsocketPath,\n\tstartupLock,\n} from './protocol';\n\nexport const serveHost = (\n\tpaths: HostPaths,\n\tline: string,\n\tstop: Effect.Effect<void>,\n\tgraceMs = sharedGraceMs,\n) =>\n\tEffect.gen(function* () {\n\t\tconst address = yield* io('Failed to resolve shared host socket', () =>\n\t\t\tsocketPath(paths),\n\t\t);\n\t\treturn yield* Effect.callback<void, ServiceError>((resume) => {\n\t\t\tconst state = {\n\t\t\t\tconsumers: 0,\n\t\t\t\tstopping: false,\n\t\t\t\ttimer: undefined as ReturnType<typeof setTimeout> | undefined,\n\t\t\t};\n\t\t\tconst shutdown = Effect.gen(function* () {\n\t\t\t\tyield* Effect.acquireRelease(startupLock(paths), (release) =>\n\t\t\t\t\tio('Failed to release shared host lock', release).pipe(Effect.orDie),\n\t\t\t\t);\n\t\t\t\tif (state.consumers !== 0) return;\n\t\t\t\tstate.stopping = true;\n\t\t\t\tserver.close();\n\t\t\t\tyield* io('Failed to remove shared host socket', () =>\n\t\t\t\t\trm(paths.socket, { force: true }),\n\t\t\t\t).pipe(Effect.ensuring(stop));\n\t\t\t}).pipe(\n\t\t\t\tEffect.scoped,\n\t\t\t\tEffect.matchCauseEffect({\n\t\t\t\t\tonFailure: (cause) =>\n\t\t\t\t\t\tEffect.sync(() => resume(Effect.failCause(cause))),\n\t\t\t\t\tonSuccess: () =>\n\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\tif (state.stopping) resume(Effect.void);\n\t\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst schedule = () => {\n\t\t\t\tif (state.timer !== undefined) clearTimeout(state.timer);\n\t\t\t\tstate.timer = setTimeout(() => Effect.runFork(shutdown), graceMs);\n\t\t\t};\n\t\t\tconst server = createServer((socket) => {\n\t\t\t\tif (state.stopping) {\n\t\t\t\t\tsocket.destroy();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tstate.consumers++;\n\t\t\t\tif (state.timer !== undefined) clearTimeout(state.timer);\n\t\t\t\tsocket.on('error', () => socket.destroy());\n\t\t\t\tsocket.once('close', () => {\n\t\t\t\t\tstate.consumers--;\n\t\t\t\t\tif (state.consumers === 0) schedule();\n\t\t\t\t});\n\t\t\t\tsocket.write(`${line}\\n`);\n\t\t\t});\n\t\t\tserver.once('error', (cause) => resume(Effect.die(cause)));\n\t\t\tserver.listen(address, schedule);\n\t\t\treturn Effect.suspend(() =>\n\t\t\t\tstate.stopping\n\t\t\t\t\t? Effect.void\n\t\t\t\t\t: Effect.sync(() => {\n\t\t\t\t\t\t\tif (state.timer !== undefined) clearTimeout(state.timer);\n\t\t\t\t\t\t\tserver.close();\n\t\t\t\t\t\t}).pipe(\n\t\t\t\t\t\t\tEffect.ensuring(\n\t\t\t\t\t\t\t\tio('Failed to remove shared host socket', () =>\n\t\t\t\t\t\t\t\t\trm(paths.socket, { force: true }),\n\t\t\t\t\t\t\t\t).pipe(Effect.orDie),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t);\n\t\t});\n\t});\n"],"mappings":"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","names":["Effect","Effect"]}
@@ -0,0 +1,5 @@
1
+ import { Effect } from 'effect';
2
+ import type { ServiceError } from '../core';
3
+ import { type HostPaths } from './protocol';
4
+ export declare const serveHost: (paths: HostPaths, line: string, stop: Effect.Effect<void>, graceMs?: number) => Effect.Effect<void, ServiceError, never>;
5
+ //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../../src/services/shared/host.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EACN,KAAK,SAAS,EAKd,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,SAAS,UACd,SAAS,QACV,MAAM,QACN,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,+DAqEvB,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { Effect, Schema } from 'effect';
2
+ import { type DevSession } from '../../dev-sessions';
3
+ import { ServiceError } from '../core';
4
+ export type SharedOptions<A> = {
5
+ readonly output: Schema.Codec<A, unknown>;
6
+ };
7
+ export declare const runShared: <Port extends string, A>(session: DevSession, name: string, options: SharedOptions<A>, stack: string | undefined) => Effect.Effect<{
8
+ value: A;
9
+ ports: Readonly<Record<Port, number>>;
10
+ }, ServiceError, import("effect/FileSystem").FileSystem | import("effect/Path").Path | import("effect/Scope").Scope>;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/services/shared/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,YAAY,EAA+B,MAAM,SAAS,CAAC;AAWpE,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AAgB7E,eAAO,MAAM,SAAS,GAAI,IAAI,SAAS,MAAM,EAAE,CAAC,WACtC,UAAU,QACb,MAAM,WACH,aAAa,CAAC,CAAC,CAAC,SAClB,MAAM,GAAG,SAAS;;WAuBD,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;oHAEW,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ import { type Socket } from 'node:net';
3
+ import { Effect, Schema } from 'effect';
4
+ import { ServiceError } from '../core';
5
+ export declare const sharedGraceMs = 5000;
6
+ export declare const hostPaths: (sessionDir: string, name: string) => {
7
+ dir: string;
8
+ socket: string;
9
+ lock: string;
10
+ log: string;
11
+ };
12
+ export type HostPaths = ReturnType<typeof hostPaths>;
13
+ export declare const hostArguments: Schema.fromJsonString<Schema.Struct<{
14
+ readonly module: Schema.String;
15
+ readonly name: Schema.String;
16
+ readonly root: Schema.String;
17
+ readonly session: Schema.String;
18
+ }>>;
19
+ type HostArguments = typeof hostArguments.Type;
20
+ export declare const readyMessage: <A>(output: Schema.Codec<A, unknown>) => Schema.fromJsonString<Schema.Struct<{
21
+ readonly output: Schema.Codec<A, unknown, never, never>;
22
+ readonly ports: Schema.$Record<Schema.String, Schema.Number>;
23
+ }>>;
24
+ export declare const io: <A>(message: string, operation: () => Promise<A>) => Effect.Effect<A, ServiceError, never>;
25
+ export declare const isErrno: (cause: unknown, code: string) => boolean;
26
+ export declare const startupLock: (paths: HostPaths) => Effect.Effect<() => Promise<void>, ServiceError, never>;
27
+ export type Lease = {
28
+ socket: Socket;
29
+ line: string;
30
+ };
31
+ export declare const failWhenHostExits: (lease: Lease, name: string) => Effect.Effect<never, ServiceError, never>;
32
+ /** Unix sockets have tiny pathname limits; a short alias keeps the socket in the host directory. */
33
+ export declare const socketPath: (paths: HostPaths) => Promise<string>;
34
+ export declare const connectHost: (paths: HostPaths, child?: ChildProcess) => Promise<Lease>;
35
+ type Launch = () => Promise<ChildProcess>;
36
+ export declare const acquireHost: (paths: HostPaths, launch: Launch) => Effect.Effect<Lease, ServiceError, never>;
37
+ export declare const launchHost: (paths: HostPaths, args: HostArguments) => Promise<ChildProcess>;
38
+ export {};
39
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/services/shared/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,oBAAoB,CAAC;AAG9D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAIhD,OAAO,EAAE,MAAM,EAAY,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGlD,OAAO,EAAE,YAAY,EAAoB,MAAM,SAAS,CAAC;AAEzD,eAAO,MAAM,aAAa,OAAQ,CAAC;AACnC,eAAO,MAAM,SAAS,eAAgB,MAAM,QAAQ,MAAM;;;;;CAQzD,CAAC;AACF,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AACrD,eAAO,MAAM,aAAa;;;;;GAOzB,CAAC;AACF,KAAK,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAC;AAC/C,eAAO,MAAM,YAAY,GAAI,CAAC,UAAU,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC;;;GAM9D,CAAC;AAEH,eAAO,MAAM,EAAE,GAAI,CAAC,WAAW,MAAM,aAAa,MAAM,OAAO,CAAC,CAAC,CAAC,0CAI/D,CAAC;AACJ,eAAO,MAAM,OAAO,UAAW,OAAO,QAAQ,MAAM,YAIhC,CAAC;AACrB,eAAO,MAAM,WAAW,UAAW,SAAS,4DAG1C,CAAC;AACH,MAAM,MAAM,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,eAAO,MAAM,iBAAiB,UAAW,KAAK,QAAQ,MAAM,8CAUzD,CAAC;AAEJ,oGAAoG;AACpG,eAAO,MAAM,UAAU,UAAiB,SAAS,oBAYhD,CAAC;AAEF,eAAO,MAAM,WAAW,UAChB,SAAS,UACR,YAAY,KAClB,OAAO,CAAC,KAAK,CAoBf,CAAC;AAkBF,KAAK,MAAM,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;AAwC1C,eAAO,MAAM,WAAW,UAAW,SAAS,UAAU,MAAM,8CAYzD,CAAC;AACJ,eAAO,MAAM,UAAU,UAAiB,SAAS,QAAQ,aAAa,0BA+BrE,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Effect } from 'effect';
2
+ import { FileSystem } from 'effect/FileSystem';
3
+ import { Path } from 'effect/Path';
4
+ export declare const findSharedRoot: (projectDir: string) => Effect.Effect<string, import("effect/PlatformError").PlatformError, FileSystem | Path>;
5
+ //# sourceMappingURL=root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../src/services/shared/root.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnC,eAAO,MAAM,cAAc,eAAgB,MAAM,2FAkB9C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Socket } from 'node:net';
2
+ export declare const readSocketLine: (socket: Socket, maxBytes?: number) => Promise<string>;
3
+ //# sourceMappingURL=socket-line.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-line.d.ts","sourceRoot":"","sources":["../src/socket-line.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,eAAO,MAAM,cAAc,WAClB,MAAM,wBAEZ,OAAO,CAAC,MAAM,CA8Bd,CAAC"}