authorgravatar for 206951365+cursor[bot]@users.noreply.github.comcursor[bot] <206951365+cursor[bot]@users.noreply.github.com> 2026-05-07 12:19:32-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-05-07 12:19:32-07:00
log1bcfc88f589051c92882151788217c1410141ef7
tree69e71b8e4ade56622ff3147785b3d2c2dc13144b
parent2ba58076fa19e11811b021a479400a8810b9930d
signature Signed by PGP key B5690EEEBB952194

Adopt idiomatic Effect APIs in generator and decider (#2581)

Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Julius Marminge <juliusmarminge@users.noreply.github.com> Co-authored-by: Julius Marminge <julius0216@outlook.com>

2 files changed, 45 insertions(+), 59 deletions(-)

apps/server/src/orchestration/decider.ts+11-21
...@@ -3,7 +3,7 @@ import type {...@@ -3,7 +3,7 @@ import type {
3 OrchestrationEvent,3 OrchestrationEvent,
4 OrchestrationReadModel,4 OrchestrationReadModel,
5} from "@t3tools/contracts";5} from "@t3tools/contracts";
6import { Effect } from "effect";6import { DateTime, Effect } from "effect";
77
8import { OrchestrationCommandInvariantError } from "./Errors.ts";8import { OrchestrationCommandInvariantError } from "./Errors.ts";
9import {9import {
...@@ -17,17 +17,7 @@ import {...@@ -17,17 +17,7 @@ import {
17} from "./commandInvariants.ts";17} from "./commandInvariants.ts";
18import { projectEvent } from "./projector.ts";18import { projectEvent } from "./projector.ts";
1919
20const nowIso = () => new Date().toISOString();20const currentIso = DateTime.now.pipe(Effect.map(DateTime.formatIso));
21const defaultMetadata: Omit<OrchestrationEvent, "sequence" | "type" | "payload"> = {
22 eventId: crypto.randomUUID() as OrchestrationEvent["eventId"],
23 aggregateKind: "thread",
24 aggregateId: "" as OrchestrationEvent["aggregateId"],
25 occurredAt: nowIso(),
26 commandId: null,
27 causationEventId: null,
28 correlationId: null,
29 metadata: {},
30};
3121
32function withEventBase(22function withEventBase(
33 input: Pick<OrchestrationCommand, "commandId"> & {23 input: Pick<OrchestrationCommand, "commandId"> & {
...@@ -38,12 +28,12 @@ function withEventBase(...@@ -38,12 +28,12 @@ function withEventBase(
38 },28 },
39): Omit<OrchestrationEvent, "sequence" | "type" | "payload"> {29): Omit<OrchestrationEvent, "sequence" | "type" | "payload"> {
40 return {30 return {
41 ...defaultMetadata,
42 eventId: crypto.randomUUID() as OrchestrationEvent["eventId"],31 eventId: crypto.randomUUID() as OrchestrationEvent["eventId"],
43 aggregateKind: input.aggregateKind,32 aggregateKind: input.aggregateKind,
44 aggregateId: input.aggregateId,33 aggregateId: input.aggregateId,
45 occurredAt: input.occurredAt,34 occurredAt: input.occurredAt,
46 commandId: input.commandId,35 commandId: input.commandId,
36 causationEventId: null,
47 correlationId: input.commandId,37 correlationId: input.commandId,
48 metadata: input.metadata ?? {},38 metadata: input.metadata ?? {},
49 };39 };
...@@ -126,7 +116,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -126,7 +116,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
126 command,116 command,
127 projectId: command.projectId,117 projectId: command.projectId,
128 });118 });
129 const occurredAt = nowIso();119 const occurredAt = yield* currentIso;
130 return {120 return {
131 ...withEventBase({121 ...withEventBase({
132 aggregateKind: "project",122 aggregateKind: "project",
...@@ -183,7 +173,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -183,7 +173,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
183 });173 });
184 }174 }
185175
186 const occurredAt = nowIso();176 const occurredAt = yield* currentIso;
187 return {177 return {
188 ...withEventBase({178 ...withEventBase({
189 aggregateKind: "project",179 aggregateKind: "project",
...@@ -239,7 +229,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -239,7 +229,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
239 command,229 command,
240 threadId: command.threadId,230 threadId: command.threadId,
241 });231 });
242 const occurredAt = nowIso();232 const occurredAt = yield* currentIso;
243 return {233 return {
244 ...withEventBase({234 ...withEventBase({
245 aggregateKind: "thread",235 aggregateKind: "thread",
...@@ -261,7 +251,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -261,7 +251,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
261 command,251 command,
262 threadId: command.threadId,252 threadId: command.threadId,
263 });253 });
264 const occurredAt = nowIso();254 const occurredAt = yield* currentIso;
265 return {255 return {
266 ...withEventBase({256 ...withEventBase({
267 aggregateKind: "thread",257 aggregateKind: "thread",
...@@ -284,7 +274,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -284,7 +274,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
284 command,274 command,
285 threadId: command.threadId,275 threadId: command.threadId,
286 });276 });
287 const occurredAt = nowIso();277 const occurredAt = yield* currentIso;
288 return {278 return {
289 ...withEventBase({279 ...withEventBase({
290 aggregateKind: "thread",280 aggregateKind: "thread",
...@@ -306,7 +296,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -306,7 +296,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
306 command,296 command,
307 threadId: command.threadId,297 threadId: command.threadId,
308 });298 });
309 const occurredAt = nowIso();299 const occurredAt = yield* currentIso;
310 return {300 return {
311 ...withEventBase({301 ...withEventBase({
312 aggregateKind: "thread",302 aggregateKind: "thread",
...@@ -334,7 +324,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -334,7 +324,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
334 command,324 command,
335 threadId: command.threadId,325 threadId: command.threadId,
336 });326 });
337 const occurredAt = nowIso();327 const occurredAt = yield* currentIso;
338 return {328 return {
339 ...withEventBase({329 ...withEventBase({
340 aggregateKind: "thread",330 aggregateKind: "thread",
...@@ -357,7 +347,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"...@@ -357,7 +347,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
357 command,347 command,
358 threadId: command.threadId,348 threadId: command.threadId,
359 });349 });
360 const occurredAt = nowIso();350 const occurredAt = yield* currentIso;
361 return {351 return {
362 ...withEventBase({352 ...withEventBase({
363 aggregateKind: "thread",353 aggregateKind: "thread",
packages/effect-codex-app-server/scripts/generate.ts+34-38
...@@ -4,6 +4,12 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";...@@ -4,6 +4,12 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
4import * as NodeServices from "@effect/platform-node/NodeServices";4import * as NodeServices from "@effect/platform-node/NodeServices";
5import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator";5import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator";
6import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";6import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";
7import {
8 FetchHttpClient,
9 HttpClient,
10 HttpClientRequest,
11 HttpClientResponse,
12} from "effect/unstable/http";
7import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";13import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
814
9const UPSTREAM_REF = "be75785504ff152fa6333e380a2d50642f42fba0";15const UPSTREAM_REF = "be75785504ff152fa6333e380a2d50642f42fba0";
...@@ -21,6 +27,13 @@ const GithubContentEntries = Schema.Array(...@@ -21,6 +27,13 @@ const GithubContentEntries = Schema.Array(
21);27);
22type GithubContentEntry = (typeof GithubContentEntries.Type)[number];28type GithubContentEntry = (typeof GithubContentEntries.Type)[number];
2329
30const JsonSchemaDocument = Schema.StructWithRest(
31 Schema.Struct({
32 definitions: Schema.optionalKey(Schema.Record(Schema.String, Schema.Json)),
33 }),
34 [Schema.Record(Schema.String, Schema.Json)],
35);
36
24interface GeneratedPaths {37interface GeneratedPaths {
25 readonly generatedDir: string;38 readonly generatedDir: string;
26 readonly schemaOutputPath: string;39 readonly schemaOutputPath: string;
...@@ -143,40 +156,19 @@ const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () {...@@ -143,40 +156,19 @@ const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () {
143});156});
144157
145const fetchText = Effect.fn("fetchText")(function* (url: string) {158const fetchText = Effect.fn("fetchText")(function* (url: string) {
146 const response = yield* Effect.tryPromise({159 return yield* HttpClientRequest.get(url).pipe(
147 try: () =>160 HttpClientRequest.setHeader("user-agent", USER_AGENT),
148 fetch(url, {161 HttpClient.execute,
149 headers: {162 Effect.flatMap(HttpClientResponse.filterStatusOk),
150 "user-agent": USER_AGENT,163 Effect.flatMap((okResponse) => okResponse.text),
151 },164 Effect.mapError(
152 }),165 (cause) =>
153 catch: (cause) =>166 new GeneratorError({
154 new GeneratorError({167 detail: `Failed to fetch ${url}`,
155 detail: `Failed to fetch ${url}`,168 cause,
156 cause,169 }),
157 }),170 ),
158 });171 );
159
160 if (!response.ok) {
161 const detail = yield* Effect.tryPromise({
162 try: () => response.text(),
163 catch: () => "",
164 });
165 return yield* Effect.fail(
166 new GeneratorError({
167 detail: `Failed to download ${url}: ${response.status} ${detail}`,
168 }),
169 );
170 }
171
172 return yield* Effect.tryPromise({
173 try: () => response.text(),
174 catch: (cause) =>
175 new GeneratorError({
176 detail: `Failed to read response body for ${url}`,
177 cause,
178 }),
179 });
180});172});
181173
182const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* (path: string) {174const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* (path: string) {
...@@ -548,9 +540,7 @@ const generateFiles = Effect.fn("generateFiles")(function* () {...@@ -548,9 +540,7 @@ const generateFiles = Effect.fn("generateFiles")(function* () {
548540
549 for (const file of jsonSchemaFiles) {541 for (const file of jsonSchemaFiles) {
550 const raw = yield* fetchText(file.downloadUrl);542 const raw = yield* fetchText(file.downloadUrl);
551 const parsed = JSON.parse(raw) as {543 const parsed = yield* Schema.decodeEffect(Schema.fromJsonString(JsonSchemaDocument))(raw);
552 readonly definitions?: Record<string, typeof Schema.Json.Type>;
553 } & Record<string, typeof Schema.Json.Type>;
554 const localDefinitionNames = new Map(544 const localDefinitionNames = new Map(
555 Object.keys(parsed.definitions ?? {}).map((definitionName) => [545 Object.keys(parsed.definitions ?? {}).map((definitionName) => [
556 definitionName,546 definitionName,
...@@ -767,6 +757,12 @@ const generateFiles = Effect.fn("generateFiles")(function* () {...@@ -767,6 +757,12 @@ const generateFiles = Effect.fn("generateFiles")(function* () {
767757
768generateFiles().pipe(758generateFiles().pipe(
769 Effect.scoped,759 Effect.scoped,
770 Effect.provide(Layer.mergeAll(Logger.layer([Logger.consolePretty()]), NodeServices.layer)),760 Effect.provide(
761 Layer.mergeAll(
762 Logger.layer([Logger.consolePretty()]),
763 NodeServices.layer,
764 FetchHttpClient.layer,
765 ),
766 ),
771 NodeRuntime.runMain,767 NodeRuntime.runMain,
772);768);