# Using without Effect

You want the session scaffolding — sessions, sticky ports, managed subprocesses — but you don't write Effect, and you're not about to learn it to run a dev script. `devsess/async` is the same machinery as [Getting Started](/getting-started), behind plain `async`/`await` instead of `Effect.gen`.

It still needs `effect` and a platform package (`@effect/platform-node` or `@effect/platform-bun`) sitting in `node_modules` — `devsess/async` is built on the same Effect core underneath, it only keeps that out of your own code. Install is otherwise identical to the [core install](/getting-started#install).

## The same script, rewritten

With Effect:

```ts
// scripts/dev.ts
import { CurrentSession, DevSessions, getStickyPort, runManagedSubprocess } from 'devsess'
import { NodeRuntime, NodeServices } from '@effect/platform-node'
import { Effect } from 'effect'
import { Command } from 'effect/unstable/cli'

const web = Command.make('web', {}, () =>
	Effect.gen(function* () {
		const session = yield* CurrentSession
		yield* Effect.logInfo(`[dev] session: ${session.name}`)

		const port = yield* getStickyPort(session)

		yield* runManagedSubprocess('bunx', ['vite'], {
			env: { PORT: String(port) },
		})
	}).pipe(Effect.provide(CurrentSession.layer)),
)

Command.run(web, { version: '0.1.0' }).pipe(
	Effect.provide(DevSessions.layer),
	Effect.provide(NodeServices.layer),
	Effect.scoped,
	NodeRuntime.runMain,
)
```

With `devsess/async`:

```ts
// scripts/dev.ts
import { join } from 'node:path'
import { defineDevCli } from 'devsess/async'
import { NodeRuntime, NodeServices } from '@effect/platform-node'

const main = defineDevCli({
	name: 'web',
	dir: join(import.meta.dirname, '..'),
	platform: { services: NodeServices.layer, runMain: NodeRuntime.runMain },
	run: async (ctx) => {
		const session = await ctx.session()
		console.log(`[dev] session: ${session.name}`)

		const port = await ctx.getStickyPort()

		await ctx.runManagedSubprocess('bunx', ['vite'], {
			env: { PORT: String(port) },
		})
	},
})

main(process.argv)
```

No `effect` import, no generator function — otherwise the same script. `platform` is still built from `effect`'s platform packages either way; see [Getting Started](/getting-started#install).

## Differences that actually bite

* **`ctx.session` is a function, not a value.** The Effect API hands you `CurrentSession`, a service tag you resolve with `yield* CurrentSession`. `devsess/async` hands you a function: `await ctx.session()`. Call it as many times as you want — it's still resolved once per run internally, so repeat calls are free, not repeat work.
* **`session.path(rel)` is synchronous.** No `await`, no `yield*` — it returns a `string` directly: `const dir = session.path('cache')`. It still doesn't create the directory, same as the Effect version.
* **`createDevSessions(rootDir, services)` replaces `DevSessions` and `CurrentSession`.** No service tag, no Layer of your own to build — for anything you do outside `defineDevCli`. `rootDir` is your project root, same convention as the Effect API's `DevSessions.layerAt(rootDir)` — sessions live under `<rootDir>/.data/sessions`, so don't pre-join that path yourself. `services` is the same platform layer you'd pass as `defineDevCli`'s `platform.services` — `NodeServices.layer` or `BunServices.layer`.
* **`cli` and `Schema` are both re-exported — but only from `devsess/async`.** `import { cli, Schema } from 'devsess/async'` covers flags (`cli.Flag...`) and `SessionState` schemas (`Schema.Struct(...)`) without ever importing `effect`. The core `devsess` package doesn't re-export either — Effect users build a `Command` straight from `effect/unstable/cli` and import `Schema` from `effect` directly.

A script or a test reaches for `createDevSessions` directly:

```ts
// scripts/seed.ts
import { join } from 'node:path'
import { createDevSessions } from 'devsess/async'
import { NodeServices } from '@effect/platform-node'

const sessions = createDevSessions(join(import.meta.dirname, '..'), NodeServices.layer)
const session = await sessions.getLatestOrCreate()
```

## What you give up

`devsess/pglite` doesn't work with `devsess/async` — not "no wrapper exists yet," but structurally incompatible.

:::warning\[devsess/pglite can't take an async session]
`prepareSessionPglite` needs a session whose `path()` returns an `Effect`. The async `DevSession.path()` returns a plain `string` instead, and the bridge back to the Effect-shaped session isn't exported from `devsess/async`. There's no way to hand it an async session, and no async equivalent of `prepareSessionPglite` either.
:::

If your dev script needs [a database per session](/recipes/pglite), that piece has to be written against the Effect API. You can still write the rest of the script with `devsess/async` and drop into `Effect.gen`/`Effect.runPromise` for that one piece — but at that point you're back to an `effect` import, at least in that one file.

Everything else carries over exactly: [dev sessions](/writing-a-dev-cli#dev-sessions) (including `SessionState`), [sticky ports](/recipes/ports), [managed subprocess](/recipes/dev-server) lifetime, and [running signals](/recipes/wiring-services). Same underlying code, wrapped in Promises instead of Effects.
