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 {
33 OrchestrationEvent,
44 OrchestrationReadModel,
55} from "@t3tools/contracts";
6import { Effect } from "effect";
6import { DateTime, Effect } from "effect";
77
88import { OrchestrationCommandInvariantError } from "./Errors.ts";
99import {
......@@ -17,17 +17,7 @@ import {
1717} from "./commandInvariants.ts";
1818import { projectEvent } from "./projector.ts";
1919
20const nowIso = () => new Date().toISOString();
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};
20const currentIso = DateTime.now.pipe(Effect.map(DateTime.formatIso));
3121
3222function withEventBase(
3323 input: Pick<OrchestrationCommand, "commandId"> & {
......@@ -38,12 +28,12 @@ function withEventBase(
3828 },
3929): Omit<OrchestrationEvent, "sequence" | "type" | "payload"> {
4030 return {
41 ...defaultMetadata,
4231 eventId: crypto.randomUUID() as OrchestrationEvent["eventId"],
4332 aggregateKind: input.aggregateKind,
4433 aggregateId: input.aggregateId,
4534 occurredAt: input.occurredAt,
4635 commandId: input.commandId,
36 causationEventId: null,
4737 correlationId: input.commandId,
4838 metadata: input.metadata ?? {},
4939 };
......@@ -126,7 +116,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
126116 command,
127117 projectId: command.projectId,
128118 });
129 const occurredAt = nowIso();
119 const occurredAt = yield* currentIso;
130120 return {
131121 ...withEventBase({
132122 aggregateKind: "project",
......@@ -183,7 +173,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
183173 });
184174 }
185175
186 const occurredAt = nowIso();
176 const occurredAt = yield* currentIso;
187177 return {
188178 ...withEventBase({
189179 aggregateKind: "project",
......@@ -239,7 +229,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
239229 command,
240230 threadId: command.threadId,
241231 });
242 const occurredAt = nowIso();
232 const occurredAt = yield* currentIso;
243233 return {
244234 ...withEventBase({
245235 aggregateKind: "thread",
......@@ -261,7 +251,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
261251 command,
262252 threadId: command.threadId,
263253 });
264 const occurredAt = nowIso();
254 const occurredAt = yield* currentIso;
265255 return {
266256 ...withEventBase({
267257 aggregateKind: "thread",
......@@ -284,7 +274,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
284274 command,
285275 threadId: command.threadId,
286276 });
287 const occurredAt = nowIso();
277 const occurredAt = yield* currentIso;
288278 return {
289279 ...withEventBase({
290280 aggregateKind: "thread",
......@@ -306,7 +296,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
306296 command,
307297 threadId: command.threadId,
308298 });
309 const occurredAt = nowIso();
299 const occurredAt = yield* currentIso;
310300 return {
311301 ...withEventBase({
312302 aggregateKind: "thread",
......@@ -334,7 +324,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
334324 command,
335325 threadId: command.threadId,
336326 });
337 const occurredAt = nowIso();
327 const occurredAt = yield* currentIso;
338328 return {
339329 ...withEventBase({
340330 aggregateKind: "thread",
......@@ -357,7 +347,7 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand"
357347 command,
358348 threadId: command.threadId,
359349 });
360 const occurredAt = nowIso();
350 const occurredAt = yield* currentIso;
361351 return {
362352 ...withEventBase({
363353 aggregateKind: "thread",
packages/effect-codex-app-server/scripts/generate.ts+34-38
......@@ -4,6 +4,12 @@ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
44import * as NodeServices from "@effect/platform-node/NodeServices";
55import { make as makeJsonSchemaGenerator } from "@effect/openapi-generator/JsonSchemaGenerator";
66import { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";
7import {
8 FetchHttpClient,
9 HttpClient,
10 HttpClientRequest,
11 HttpClientResponse,
12} from "effect/unstable/http";
713import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
814
915const UPSTREAM_REF = "be75785504ff152fa6333e380a2d50642f42fba0";
......@@ -21,6 +27,13 @@ const GithubContentEntries = Schema.Array(
2127);
2228type 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
2437interface GeneratedPaths {
2538 readonly generatedDir: string;
2639 readonly schemaOutputPath: string;
......@@ -143,40 +156,19 @@ const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () {
143156});
144157
145158const fetchText = Effect.fn("fetchText")(function* (url: string) {
146 const response = yield* Effect.tryPromise({
147 try: () =>
148 fetch(url, {
149 headers: {
150 "user-agent": USER_AGENT,
151 },
152 }),
153 catch: (cause) =>
154 new GeneratorError({
155 detail: `Failed to fetch ${url}`,
156 cause,
157 }),
158 });
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 });
159 return yield* HttpClientRequest.get(url).pipe(
160 HttpClientRequest.setHeader("user-agent", USER_AGENT),
161 HttpClient.execute,
162 Effect.flatMap(HttpClientResponse.filterStatusOk),
163 Effect.flatMap((okResponse) => okResponse.text),
164 Effect.mapError(
165 (cause) =>
166 new GeneratorError({
167 detail: `Failed to fetch ${url}`,
168 cause,
169 }),
170 ),
171 );
180172});
181173
182174const fetchDirectoryEntries = Effect.fn("fetchDirectoryEntries")(function* (path: string) {
......@@ -548,9 +540,7 @@ const generateFiles = Effect.fn("generateFiles")(function* () {
548540
549541 for (const file of jsonSchemaFiles) {
550542 const raw = yield* fetchText(file.downloadUrl);
551 const parsed = JSON.parse(raw) as {
552 readonly definitions?: Record<string, typeof Schema.Json.Type>;
553 } & Record<string, typeof Schema.Json.Type>;
543 const parsed = yield* Schema.decodeEffect(Schema.fromJsonString(JsonSchemaDocument))(raw);
554544 const localDefinitionNames = new Map(
555545 Object.keys(parsed.definitions ?? {}).map((definitionName) => [
556546 definitionName,
......@@ -767,6 +757,12 @@ const generateFiles = Effect.fn("generateFiles")(function* () {
767757
768758generateFiles().pipe(
769759 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 ),
771767 NodeRuntime.runMain,
772768);