create-effect-project 0.1.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 +21 -0
- package/README.md +72 -0
- package/dist/bin.js +58245 -0
- package/package.json +34 -0
- package/templates/_shared/_gitignore +4 -0
- package/templates/_shared/config.ts +13 -0
- package/templates/_shared/features/_oxlintrc.json +5 -0
- package/templates/_shared/features/_vscode.settings.json +6 -0
- package/templates/_shared/features/lint.package.json +11 -0
- package/templates/_shared/features/lint.tsconfig.json +16 -0
- package/templates/_shared/features/observability.noop.ts +9 -0
- package/templates/_shared/features/observability.otel.ts +23 -0
- package/templates/_shared/features/slop.oxlintrc.json +25 -0
- package/templates/_shared/httpapi/api/Api.ts +9 -0
- package/templates/_shared/httpapi/api/Notes.ts +25 -0
- package/templates/_shared/httpapi/api/System.ts +7 -0
- package/templates/_shared/httpapi/client/ApiClient.ts +24 -0
- package/templates/_shared/httpapi/server/Notes/http.ts +22 -0
- package/templates/_shared/httpapi/server/http.ts +18 -0
- package/templates/_shared/notes/Notes.ts +42 -0
- package/templates/_shared/notes/domain/Note.ts +19 -0
- package/templates/alchemy-http/README.md +79 -0
- package/templates/alchemy-http/alchemy.run.ts +22 -0
- package/templates/alchemy-http/runtime/bun/_package.json +27 -0
- package/templates/alchemy-http/runtime/bun/_tsconfig.json +27 -0
- package/templates/alchemy-http/runtime/bun/client.ts +37 -0
- package/templates/alchemy-http/runtime/bun/http.test.ts +45 -0
- package/templates/alchemy-http/runtime/node/_package.json +28 -0
- package/templates/alchemy-http/runtime/node/_tsconfig.json +27 -0
- package/templates/alchemy-http/runtime/node/client.ts +37 -0
- package/templates/alchemy-http/runtime/node/http.test.ts +48 -0
- package/templates/alchemy-http/src/worker.ts +22 -0
- package/templates/alchemy-rpc/README.md +83 -0
- package/templates/alchemy-rpc/alchemy.run.ts +22 -0
- package/templates/alchemy-rpc/runtime/bun/_package.json +27 -0
- package/templates/alchemy-rpc/runtime/bun/_tsconfig.json +27 -0
- package/templates/alchemy-rpc/runtime/bun/client.ts +43 -0
- package/templates/alchemy-rpc/runtime/bun/rpc.test.ts +40 -0
- package/templates/alchemy-rpc/runtime/node/_package.json +28 -0
- package/templates/alchemy-rpc/runtime/node/_tsconfig.json +27 -0
- package/templates/alchemy-rpc/runtime/node/client.ts +43 -0
- package/templates/alchemy-rpc/runtime/node/rpc.test.ts +41 -0
- package/templates/alchemy-rpc/src/rpc.ts +29 -0
- package/templates/alchemy-rpc/src/server/Notes/rpc.ts +17 -0
- package/templates/alchemy-rpc/src/worker.ts +24 -0
- package/templates/basic/README.md +53 -0
- package/templates/basic/runtime/bun/Users.test.ts +31 -0
- package/templates/basic/runtime/bun/_package.json +21 -0
- package/templates/basic/runtime/bun/_tsconfig.json +10 -0
- package/templates/basic/runtime/bun/main.ts +43 -0
- package/templates/basic/runtime/node/Users.test.ts +32 -0
- package/templates/basic/runtime/node/_package.json +22 -0
- package/templates/basic/runtime/node/_tsconfig.json +24 -0
- package/templates/basic/runtime/node/main.ts +43 -0
- package/templates/basic/src/Users.ts +49 -0
- package/templates/basic/src/domain/User.ts +21 -0
- package/templates/fullstack/README.md +114 -0
- package/templates/fullstack/_gitignore +7 -0
- package/templates/fullstack/_package.json +6 -0
- package/templates/fullstack/_pnpm-workspace.yaml +2 -0
- package/templates/fullstack/_tsconfig.json +9 -0
- package/templates/fullstack/apps/api/config.ts +10 -0
- package/templates/fullstack/apps/api/http.ts +35 -0
- package/templates/fullstack/apps/api/runtime/bun/_package.json +26 -0
- package/templates/fullstack/apps/api/runtime/bun/_tsconfig.json +5 -0
- package/templates/fullstack/apps/api/runtime/node/_package.json +27 -0
- package/templates/fullstack/apps/api/runtime/node/_tsconfig.json +5 -0
- package/templates/fullstack/apps/web/runtime/bun/NotesApi.test.ts +65 -0
- package/templates/fullstack/apps/web/runtime/bun/_package.json +30 -0
- package/templates/fullstack/apps/web/runtime/bun/_tsconfig.json +9 -0
- package/templates/fullstack/apps/web/runtime/node/NotesApi.test.ts +65 -0
- package/templates/fullstack/apps/web/runtime/node/_package.json +31 -0
- package/templates/fullstack/apps/web/runtime/node/_tsconfig.json +9 -0
- package/templates/fullstack/apps/web/src/atoms/NotesApi.ts +40 -0
- package/templates/fullstack/apps/web/src/router.tsx +64 -0
- package/templates/fullstack/apps/web/src/routes/__root.tsx +38 -0
- package/templates/fullstack/apps/web/src/routes/index.tsx +97 -0
- package/templates/fullstack/apps/web/src/styles.css +40 -0
- package/templates/fullstack/apps/web/vite.config.ts +11 -0
- package/templates/fullstack/lint.package.json +12 -0
- package/templates/http-server/README.md +56 -0
- package/templates/http-server/runtime/bun/_package.json +22 -0
- package/templates/http-server/runtime/bun/_tsconfig.json +10 -0
- package/templates/http-server/runtime/bun/client.ts +40 -0
- package/templates/http-server/runtime/bun/http.test.ts +45 -0
- package/templates/http-server/runtime/bun/index.ts +25 -0
- package/templates/http-server/runtime/node/_package.json +23 -0
- package/templates/http-server/runtime/node/_tsconfig.json +24 -0
- package/templates/http-server/runtime/node/client.ts +40 -0
- package/templates/http-server/runtime/node/http.test.ts +48 -0
- package/templates/http-server/runtime/node/index.ts +26 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client entrypoint — exercises the running server with the generated client.
|
|
3
|
+
*
|
|
4
|
+
* {{runCmd}} dev # in one terminal
|
|
5
|
+
* {{runCmd}} client # in another
|
|
6
|
+
*/
|
|
7
|
+
import { NodeRuntime } from "@effect/platform-node"
|
|
8
|
+
import { Effect, Layer } from "effect"
|
|
9
|
+
import { ApiClient } from "./client/ApiClient.ts"
|
|
10
|
+
import { NoteId } from "./domain/Note.ts"
|
|
11
|
+
import * as Observability from "./observability.ts"
|
|
12
|
+
|
|
13
|
+
const program = Effect.gen(function*() {
|
|
14
|
+
const client = yield* ApiClient
|
|
15
|
+
|
|
16
|
+
// `system` is a top level group, so its endpoints sit at the root.
|
|
17
|
+
yield* client.health()
|
|
18
|
+
yield* Effect.logInfo("health: ok")
|
|
19
|
+
|
|
20
|
+
const created = yield* client.notes.create({ payload: { title: "first", body: "hello" } })
|
|
21
|
+
yield* Effect.logInfo("created", created)
|
|
22
|
+
|
|
23
|
+
const fetched = yield* client.notes.getById({ params: { id: created.id } })
|
|
24
|
+
yield* Effect.logInfo("getById", fetched)
|
|
25
|
+
|
|
26
|
+
const all = yield* client.notes.list()
|
|
27
|
+
yield* Effect.logInfo("list", all)
|
|
28
|
+
|
|
29
|
+
// Errors are typed too: an unknown id fails with `NoteNotFound`, which the
|
|
30
|
+
// server returns as a 404.
|
|
31
|
+
const missing = yield* client.notes.getById({ params: { id: NoteId.make("nope") } }).pipe(
|
|
32
|
+
Effect.catchTag("NoteNotFound", () => Effect.succeed("NoteNotFound (404) as expected"))
|
|
33
|
+
)
|
|
34
|
+
yield* Effect.logInfo("getById?id=nope", missing)
|
|
35
|
+
}).pipe(
|
|
36
|
+
Effect.withSpan("client-demo"),
|
|
37
|
+
Effect.provide(Layer.mergeAll(ApiClient.layer, Observability.layer("notes-client")))
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
program.pipe(NodeRuntime.runMain)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { assert, describe, it } from "@effect/vitest"
|
|
2
|
+
import type { Scope } from "effect"
|
|
3
|
+
import { Effect, Layer, References } from "effect"
|
|
4
|
+
import { HttpServer } from "effect/unstable/http"
|
|
5
|
+
import { HttpApiTest } from "effect/unstable/httpapi"
|
|
6
|
+
import { Api } from "../../api/Api.ts"
|
|
7
|
+
import { NoteId } from "../../domain/Note.ts"
|
|
8
|
+
import { Notes } from "../Notes.ts"
|
|
9
|
+
import { NotesApiHandlersNoDeps } from "./http.ts"
|
|
10
|
+
|
|
11
|
+
// Handlers wired to the in-memory Notes, so the whole HTTP pipeline runs
|
|
12
|
+
// without a server: same encoding, routing and decoding as production.
|
|
13
|
+
const TestLayer = Layer.mergeAll(
|
|
14
|
+
NotesApiHandlersNoDeps.pipe(Layer.provide(Notes.layerMemory)),
|
|
15
|
+
HttpServer.layerServices
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const makeClient = HttpApiTest.groups(Api, ["notes"])
|
|
19
|
+
|
|
20
|
+
const run = <A, E, R>(layer: Layer.Layer<R>, effect: Effect.Effect<A, E, R | Scope.Scope>) =>
|
|
21
|
+
effect.pipe(
|
|
22
|
+
Effect.scoped,
|
|
23
|
+
Effect.provide(layer),
|
|
24
|
+
// Keep the test output to the assertions; raise this to see app logs.
|
|
25
|
+
Effect.provideService(References.MinimumLogLevel, "None")
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
describe("notes api", () => {
|
|
29
|
+
it.effect("creates then lists a note", () =>
|
|
30
|
+
run(TestLayer, Effect.gen(function*() {
|
|
31
|
+
const client = yield* makeClient
|
|
32
|
+
const created = yield* client.notes.create({ payload: { title: "first", body: "hello" } })
|
|
33
|
+
assert.strictEqual(created.title, "first")
|
|
34
|
+
|
|
35
|
+
const listed = yield* client.notes.list()
|
|
36
|
+
assert.strictEqual(listed.length, 1)
|
|
37
|
+
assert.strictEqual(listed[0]?.id, created.id)
|
|
38
|
+
})))
|
|
39
|
+
|
|
40
|
+
it.effect("fails with NoteNotFound for an unknown id", () =>
|
|
41
|
+
run(TestLayer, Effect.gen(function*() {
|
|
42
|
+
const client = yield* makeClient
|
|
43
|
+
const error = yield* Effect.flip(
|
|
44
|
+
client.notes.getById({ params: { id: NoteId.make("nope") } })
|
|
45
|
+
)
|
|
46
|
+
assert.strictEqual(error._tag, "NoteNotFound")
|
|
47
|
+
})))
|
|
48
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server entrypoint.
|
|
3
|
+
*
|
|
4
|
+
* A schema-first API (`src/api`) is implemented by handlers (`src/server`),
|
|
5
|
+
* served over Node, and consumed by a generated typed client (`src/client`).
|
|
6
|
+
*
|
|
7
|
+
* {{runCmd}} dev # http://localhost:3000/docs
|
|
8
|
+
*/
|
|
9
|
+
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node"
|
|
10
|
+
import { Layer } from "effect"
|
|
11
|
+
import { HttpRouter } from "effect/unstable/http"
|
|
12
|
+
import { createServer } from "node:http"
|
|
13
|
+
import { port } from "./config.ts"
|
|
14
|
+
import * as Observability from "./observability.ts"
|
|
15
|
+
import { AllRoutes } from "./server/http.ts"
|
|
16
|
+
|
|
17
|
+
const HttpServerLayer = HttpRouter.serve(AllRoutes).pipe(
|
|
18
|
+
Layer.provide(NodeHttpServer.layerConfig(createServer, { port }))
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const Main = HttpServerLayer.pipe(
|
|
22
|
+
// Provided at the very end, so every span the app creates is exported.
|
|
23
|
+
Layer.provide(Observability.layer("{{name}}"))
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
Layer.launch(Main).pipe(NodeRuntime.runMain)
|