@effect-gql/cli 1.1.0 → 1.2.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/index.cjs CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  var effect = require('effect');
4
4
  var core = require('@effect-gql/core');
5
- var fs = require('fs');
6
- var path = require('path');
5
+ var fs3 = require('fs');
6
+ var path2 = require('path');
7
+ var readline = require('readline');
8
+ var child_process = require('child_process');
7
9
 
8
10
  function _interopNamespace(e) {
9
11
  if (e && e.__esModule) return e;
@@ -23,8 +25,9 @@ function _interopNamespace(e) {
23
25
  return Object.freeze(n);
24
26
  }
25
27
 
26
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
27
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
28
+ var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
29
+ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
30
+ var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
28
31
 
29
32
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
30
33
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -33,8 +36,8 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
33
36
  throw Error('Dynamic require of "' + x + '" is not supported');
34
37
  });
35
38
  var loadSchema = (modulePath) => effect.Effect.gen(function* () {
36
- const absolutePath = path__namespace.resolve(process.cwd(), modulePath);
37
- if (!fs__namespace.existsSync(absolutePath)) {
39
+ const absolutePath = path2__namespace.resolve(process.cwd(), modulePath);
40
+ if (!fs3__namespace.existsSync(absolutePath)) {
38
41
  return yield* effect.Effect.fail(new Error(`File not found: ${absolutePath}`));
39
42
  }
40
43
  const module = yield* effect.Effect.tryPromise({
@@ -73,8 +76,1130 @@ var generateSDL = (schema, options = {}) => {
73
76
  };
74
77
  var generateSDLFromModule = (modulePath, options = {}) => loadSchema(modulePath).pipe(effect.Effect.map((schema) => generateSDL(schema, options)));
75
78
 
79
+ // src/commands/create/types.ts
80
+ var SERVER_TYPES = ["node", "bun", "express", "web"];
81
+ var isValidServerType = (value) => SERVER_TYPES.includes(value);
82
+ var PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
83
+ var isValidPackageManager = (value) => PACKAGE_MANAGERS.includes(value);
84
+
85
+ // src/commands/create/args.ts
86
+ var parseArgs = (args) => {
87
+ const positional = [];
88
+ let serverType;
89
+ let directory = effect.Option.none();
90
+ let monorepo = effect.Option.none();
91
+ let skipInstall = false;
92
+ let packageManager = effect.Option.none();
93
+ let interactive = false;
94
+ for (let i = 0; i < args.length; i++) {
95
+ const arg = args[i];
96
+ if (arg === "-h" || arg === "--help") {
97
+ return { help: true };
98
+ } else if (arg === "-i" || arg === "--interactive") {
99
+ interactive = true;
100
+ } else if (arg === "-s" || arg === "--server-type") {
101
+ const value = args[++i];
102
+ if (!value) {
103
+ return { error: "Missing value for --server-type" };
104
+ }
105
+ if (!isValidServerType(value)) {
106
+ return {
107
+ error: `Invalid server type: ${value}. Must be one of: ${SERVER_TYPES.join(", ")}`
108
+ };
109
+ }
110
+ serverType = value;
111
+ } else if (arg === "-d" || arg === "--directory") {
112
+ const value = args[++i];
113
+ if (!value) {
114
+ return { error: "Missing value for --directory" };
115
+ }
116
+ directory = effect.Option.some(value);
117
+ } else if (arg === "--monorepo") {
118
+ monorepo = effect.Option.some(true);
119
+ } else if (arg === "--skip-install") {
120
+ skipInstall = true;
121
+ } else if (arg === "--package-manager") {
122
+ const value = args[++i];
123
+ if (!value) {
124
+ return { error: "Missing value for --package-manager" };
125
+ }
126
+ if (!isValidPackageManager(value)) {
127
+ return { error: `Invalid package manager: ${value}. Must be one of: npm, pnpm, yarn, bun` };
128
+ }
129
+ packageManager = effect.Option.some(value);
130
+ } else if (!arg.startsWith("-")) {
131
+ positional.push(arg);
132
+ } else {
133
+ return { error: `Unknown option: ${arg}` };
134
+ }
135
+ }
136
+ if (interactive) {
137
+ return { interactive: true };
138
+ }
139
+ if (args.length === 0) {
140
+ return { interactive: true };
141
+ }
142
+ if (positional.length === 0) {
143
+ return {
144
+ error: "Missing project name. Use --interactive or provide: effect-gql create <name> --server-type <type>"
145
+ };
146
+ }
147
+ if (!serverType) {
148
+ return { error: "Missing --server-type. Must be one of: " + SERVER_TYPES.join(", ") };
149
+ }
150
+ const options = {
151
+ name: positional[0],
152
+ serverType,
153
+ directory,
154
+ monorepo,
155
+ skipInstall,
156
+ packageManager
157
+ };
158
+ return { options };
159
+ };
160
+
161
+ // src/commands/create/templates/package-json.ts
162
+ var VERSIONS = {
163
+ core: "^1.1.0",
164
+ node: "^1.1.0",
165
+ bun: "^1.1.0",
166
+ express: "^1.1.0",
167
+ web: "^1.1.0",
168
+ effect: "^3.19.0",
169
+ platform: "^0.94.0",
170
+ platformNode: "^0.104.0",
171
+ platformBun: "^0.87.0",
172
+ graphql: "^16.0.0",
173
+ expressLib: "^5.0.0",
174
+ tsx: "^4.19.0",
175
+ typescript: "^5.0.0"
176
+ };
177
+ var getDependencies = (serverType, isMonorepo) => {
178
+ const toVersion = (pkg, version) => isMonorepo && pkg.startsWith("@effect-gql/") ? "workspace:*" : version;
179
+ const baseDeps = {
180
+ "@effect-gql/core": toVersion("@effect-gql/core", VERSIONS.core),
181
+ "@effect/platform": VERSIONS.platform,
182
+ effect: VERSIONS.effect,
183
+ graphql: VERSIONS.graphql
184
+ };
185
+ const baseDevDeps = {
186
+ tsx: VERSIONS.tsx,
187
+ typescript: VERSIONS.typescript
188
+ };
189
+ switch (serverType) {
190
+ case "node":
191
+ return {
192
+ dependencies: {
193
+ ...baseDeps,
194
+ "@effect-gql/node": toVersion("@effect-gql/node", VERSIONS.node),
195
+ "@effect/platform-node": VERSIONS.platformNode
196
+ },
197
+ devDependencies: baseDevDeps
198
+ };
199
+ case "bun":
200
+ return {
201
+ dependencies: {
202
+ ...baseDeps,
203
+ "@effect-gql/bun": toVersion("@effect-gql/bun", VERSIONS.bun),
204
+ "@effect/platform-bun": VERSIONS.platformBun
205
+ },
206
+ devDependencies: {
207
+ typescript: VERSIONS.typescript
208
+ // Bun has built-in TypeScript support, no tsx needed
209
+ }
210
+ };
211
+ case "express":
212
+ return {
213
+ dependencies: {
214
+ ...baseDeps,
215
+ "@effect-gql/express": toVersion("@effect-gql/express", VERSIONS.express),
216
+ express: VERSIONS.expressLib
217
+ },
218
+ devDependencies: {
219
+ ...baseDevDeps,
220
+ "@types/express": "^5.0.0"
221
+ }
222
+ };
223
+ case "web":
224
+ return {
225
+ dependencies: {
226
+ ...baseDeps,
227
+ "@effect-gql/web": toVersion("@effect-gql/web", VERSIONS.web)
228
+ },
229
+ devDependencies: baseDevDeps
230
+ };
231
+ }
232
+ };
233
+ var getScripts = (serverType) => {
234
+ switch (serverType) {
235
+ case "node":
236
+ return {
237
+ start: "tsx src/index.ts",
238
+ dev: "tsx watch src/index.ts",
239
+ build: "tsc",
240
+ typecheck: "tsc --noEmit"
241
+ };
242
+ case "bun":
243
+ return {
244
+ start: "bun run src/index.ts",
245
+ dev: "bun --watch src/index.ts",
246
+ build: "bun build src/index.ts --outdir dist --target bun",
247
+ typecheck: "tsc --noEmit"
248
+ };
249
+ case "express":
250
+ return {
251
+ start: "tsx src/index.ts",
252
+ dev: "tsx watch src/index.ts",
253
+ build: "tsc",
254
+ typecheck: "tsc --noEmit"
255
+ };
256
+ case "web":
257
+ return {
258
+ start: "tsx src/index.ts",
259
+ dev: "tsx watch src/index.ts",
260
+ build: "tsc",
261
+ typecheck: "tsc --noEmit"
262
+ // Users will typically add wrangler/deno commands as needed
263
+ };
264
+ }
265
+ };
266
+ var generatePackageJson = (ctx) => {
267
+ const deps = getDependencies(ctx.serverType, ctx.isMonorepo);
268
+ const pkg = {
269
+ name: ctx.name,
270
+ version: "0.0.1",
271
+ private: true,
272
+ type: "module",
273
+ scripts: getScripts(ctx.serverType),
274
+ dependencies: deps.dependencies,
275
+ devDependencies: deps.devDependencies
276
+ };
277
+ return JSON.stringify(pkg, null, 2) + "\n";
278
+ };
279
+
280
+ // src/commands/create/templates/tsconfig.ts
281
+ var generateTsConfig = (ctx) => {
282
+ const config = ctx.isMonorepo ? {
283
+ // In a monorepo, extend from root tsconfig
284
+ extends: "../../tsconfig.json",
285
+ compilerOptions: {
286
+ outDir: "./dist",
287
+ rootDir: "./src",
288
+ noEmit: true
289
+ },
290
+ include: ["src/**/*"],
291
+ exclude: ["node_modules", "dist"]
292
+ } : {
293
+ // Standalone project config
294
+ compilerOptions: {
295
+ target: "ES2022",
296
+ module: "NodeNext",
297
+ moduleResolution: "NodeNext",
298
+ lib: ["ES2022"],
299
+ outDir: "./dist",
300
+ rootDir: "./src",
301
+ strict: true,
302
+ esModuleInterop: true,
303
+ skipLibCheck: true,
304
+ forceConsistentCasingInFileNames: true,
305
+ declaration: true,
306
+ declarationMap: true,
307
+ sourceMap: true,
308
+ noEmit: true,
309
+ // Effect requires these for decorators
310
+ experimentalDecorators: true,
311
+ emitDecoratorMetadata: true
312
+ },
313
+ include: ["src/**/*"],
314
+ exclude: ["node_modules", "dist"]
315
+ };
316
+ return JSON.stringify(config, null, 2) + "\n";
317
+ };
318
+
319
+ // src/commands/create/templates/server.ts
320
+ var generateNodeServer = (ctx) => `/**
321
+ * ${ctx.name} - GraphQL Server
322
+ *
323
+ * A GraphQL server built with Effect GQL and Node.js.
324
+ */
325
+
326
+ import { Effect, Layer } from "effect"
327
+ import * as S from "effect/Schema"
328
+ import { HttpRouter, HttpServerResponse } from "@effect/platform"
329
+ import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
330
+ import { serve } from "@effect-gql/node"
331
+
332
+ // =============================================================================
333
+ // Domain Models
334
+ // =============================================================================
335
+
336
+ /**
337
+ * Define your types with Effect Schema.
338
+ * This single definition is used for both TypeScript types AND GraphQL types.
339
+ */
340
+ const User = S.Struct({
341
+ id: S.String,
342
+ name: S.String,
343
+ email: S.String,
344
+ })
345
+
346
+ type User = S.Schema.Type<typeof User>
347
+
348
+ // =============================================================================
349
+ // In-Memory Data Store (replace with your database)
350
+ // =============================================================================
351
+
352
+ const users: User[] = [
353
+ { id: "1", name: "Alice", email: "alice@example.com" },
354
+ { id: "2", name: "Bob", email: "bob@example.com" },
355
+ ]
356
+
357
+ // =============================================================================
358
+ // GraphQL Schema
359
+ // =============================================================================
360
+
361
+ const schema = GraphQLSchemaBuilder.empty
362
+ .pipe(
363
+ // Simple query
364
+ query("hello", {
365
+ type: S.String,
366
+ description: "Returns a friendly greeting",
367
+ resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
368
+ }),
369
+
370
+ // Query with arguments
371
+ query("user", {
372
+ args: S.Struct({ id: S.String }),
373
+ type: S.NullOr(User),
374
+ description: "Get a user by ID",
375
+ resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
376
+ }),
377
+
378
+ // Query that returns a list
379
+ query("users", {
380
+ type: S.Array(User),
381
+ description: "Get all users",
382
+ resolve: () => Effect.succeed(users),
383
+ }),
384
+
385
+ // Mutation
386
+ mutation("createUser", {
387
+ args: S.Struct({
388
+ name: S.String,
389
+ email: S.String,
390
+ }),
391
+ type: User,
392
+ description: "Create a new user",
393
+ resolve: (args) =>
394
+ Effect.sync(() => {
395
+ const newUser: User = {
396
+ id: String(users.length + 1),
397
+ name: args.name,
398
+ email: args.email,
399
+ }
400
+ users.push(newUser)
401
+ return newUser
402
+ }),
403
+ })
404
+ )
405
+ .buildSchema()
406
+
407
+ // =============================================================================
408
+ // HTTP Router
409
+ // =============================================================================
410
+
411
+ const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
412
+ path: "/graphql",
413
+ graphiql: {
414
+ path: "/graphiql",
415
+ endpoint: "/graphql",
416
+ },
417
+ })
418
+
419
+ const router = HttpRouter.empty.pipe(
420
+ HttpRouter.get("/health", HttpServerResponse.json({ status: "ok" })),
421
+ HttpRouter.concat(graphqlRouter)
422
+ )
423
+
424
+ // =============================================================================
425
+ // Server Startup
426
+ // =============================================================================
427
+
428
+ serve(router, Layer.empty, {
429
+ port: 4000,
430
+ onStart: (url: string) => {
431
+ console.log(\`Server ready at \${url}\`)
432
+ console.log(\`GraphQL endpoint: \${url}/graphql\`)
433
+ console.log(\`GraphiQL playground: \${url}/graphiql\`)
434
+ console.log(\`Health check: \${url}/health\`)
435
+ },
436
+ })
437
+ `;
438
+ var generateBunServer = (ctx) => `/**
439
+ * ${ctx.name} - GraphQL Server
440
+ *
441
+ * A GraphQL server built with Effect GQL and Bun.
442
+ */
443
+
444
+ import { Effect, Layer } from "effect"
445
+ import * as S from "effect/Schema"
446
+ import { HttpRouter, HttpServerResponse } from "@effect/platform"
447
+ import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
448
+ import { serve } from "@effect-gql/bun"
449
+
450
+ // =============================================================================
451
+ // Domain Models
452
+ // =============================================================================
453
+
454
+ const User = S.Struct({
455
+ id: S.String,
456
+ name: S.String,
457
+ email: S.String,
458
+ })
459
+
460
+ type User = S.Schema.Type<typeof User>
461
+
462
+ // =============================================================================
463
+ // In-Memory Data Store (replace with your database)
464
+ // =============================================================================
465
+
466
+ const users: User[] = [
467
+ { id: "1", name: "Alice", email: "alice@example.com" },
468
+ { id: "2", name: "Bob", email: "bob@example.com" },
469
+ ]
470
+
471
+ // =============================================================================
472
+ // GraphQL Schema
473
+ // =============================================================================
474
+
475
+ const schema = GraphQLSchemaBuilder.empty
476
+ .pipe(
477
+ query("hello", {
478
+ type: S.String,
479
+ description: "Returns a friendly greeting",
480
+ resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
481
+ }),
482
+
483
+ query("user", {
484
+ args: S.Struct({ id: S.String }),
485
+ type: S.NullOr(User),
486
+ description: "Get a user by ID",
487
+ resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
488
+ }),
489
+
490
+ query("users", {
491
+ type: S.Array(User),
492
+ description: "Get all users",
493
+ resolve: () => Effect.succeed(users),
494
+ }),
495
+
496
+ mutation("createUser", {
497
+ args: S.Struct({
498
+ name: S.String,
499
+ email: S.String,
500
+ }),
501
+ type: User,
502
+ description: "Create a new user",
503
+ resolve: (args) =>
504
+ Effect.sync(() => {
505
+ const newUser: User = {
506
+ id: String(users.length + 1),
507
+ name: args.name,
508
+ email: args.email,
509
+ }
510
+ users.push(newUser)
511
+ return newUser
512
+ }),
513
+ })
514
+ )
515
+ .buildSchema()
516
+
517
+ // =============================================================================
518
+ // HTTP Router
519
+ // =============================================================================
520
+
521
+ const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
522
+ path: "/graphql",
523
+ graphiql: {
524
+ path: "/graphiql",
525
+ endpoint: "/graphql",
526
+ },
527
+ })
528
+
529
+ const router = HttpRouter.empty.pipe(
530
+ HttpRouter.get("/health", HttpServerResponse.json({ status: "ok" })),
531
+ HttpRouter.concat(graphqlRouter)
532
+ )
533
+
534
+ // =============================================================================
535
+ // Server Startup
536
+ // =============================================================================
537
+
538
+ serve(router, Layer.empty, {
539
+ port: 4000,
540
+ onStart: (url: string) => {
541
+ console.log(\`Server ready at \${url}\`)
542
+ console.log(\`GraphQL endpoint: \${url}/graphql\`)
543
+ console.log(\`GraphiQL playground: \${url}/graphiql\`)
544
+ console.log(\`Health check: \${url}/health\`)
545
+ },
546
+ })
547
+ `;
548
+ var generateExpressServer = (ctx) => `/**
549
+ * ${ctx.name} - GraphQL Server
550
+ *
551
+ * A GraphQL server built with Effect GQL and Express.
552
+ */
553
+
554
+ import express from "express"
555
+ import { Effect, Layer } from "effect"
556
+ import * as S from "effect/Schema"
557
+ import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
558
+ import { toMiddleware } from "@effect-gql/express"
559
+
560
+ // =============================================================================
561
+ // Domain Models
562
+ // =============================================================================
563
+
564
+ const User = S.Struct({
565
+ id: S.String,
566
+ name: S.String,
567
+ email: S.String,
568
+ })
569
+
570
+ type User = S.Schema.Type<typeof User>
571
+
572
+ // =============================================================================
573
+ // In-Memory Data Store (replace with your database)
574
+ // =============================================================================
575
+
576
+ const users: User[] = [
577
+ { id: "1", name: "Alice", email: "alice@example.com" },
578
+ { id: "2", name: "Bob", email: "bob@example.com" },
579
+ ]
580
+
581
+ // =============================================================================
582
+ // GraphQL Schema
583
+ // =============================================================================
584
+
585
+ const schema = GraphQLSchemaBuilder.empty
586
+ .pipe(
587
+ query("hello", {
588
+ type: S.String,
589
+ description: "Returns a friendly greeting",
590
+ resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
591
+ }),
592
+
593
+ query("user", {
594
+ args: S.Struct({ id: S.String }),
595
+ type: S.NullOr(User),
596
+ description: "Get a user by ID",
597
+ resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
598
+ }),
599
+
600
+ query("users", {
601
+ type: S.Array(User),
602
+ description: "Get all users",
603
+ resolve: () => Effect.succeed(users),
604
+ }),
605
+
606
+ mutation("createUser", {
607
+ args: S.Struct({
608
+ name: S.String,
609
+ email: S.String,
610
+ }),
611
+ type: User,
612
+ description: "Create a new user",
613
+ resolve: (args) =>
614
+ Effect.sync(() => {
615
+ const newUser: User = {
616
+ id: String(users.length + 1),
617
+ name: args.name,
618
+ email: args.email,
619
+ }
620
+ users.push(newUser)
621
+ return newUser
622
+ }),
623
+ })
624
+ )
625
+ .buildSchema()
626
+
627
+ // =============================================================================
628
+ // GraphQL Router
629
+ // =============================================================================
630
+
631
+ const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
632
+ path: "/graphql",
633
+ graphiql: {
634
+ path: "/graphiql",
635
+ endpoint: "/graphql",
636
+ },
637
+ })
638
+
639
+ // =============================================================================
640
+ // Express App
641
+ // =============================================================================
642
+
643
+ const app = express()
644
+ app.use(express.json())
645
+
646
+ // Health check
647
+ app.get("/health", (_, res) => {
648
+ res.json({ status: "ok" })
649
+ })
650
+
651
+ // Mount GraphQL middleware
652
+ app.use(toMiddleware(graphqlRouter, Layer.empty))
653
+
654
+ // =============================================================================
655
+ // Server Startup
656
+ // =============================================================================
657
+
658
+ const port = process.env.PORT || 4000
659
+
660
+ app.listen(port, () => {
661
+ console.log(\`Server ready at http://localhost:\${port}\`)
662
+ console.log(\`GraphQL endpoint: http://localhost:\${port}/graphql\`)
663
+ console.log(\`GraphiQL playground: http://localhost:\${port}/graphiql\`)
664
+ console.log(\`Health check: http://localhost:\${port}/health\`)
665
+ })
666
+ `;
667
+ var generateWebHandler = (ctx) => `/**
668
+ * ${ctx.name} - GraphQL Server
669
+ *
670
+ * A GraphQL server built with Effect GQL using Web standard APIs.
671
+ * Compatible with Cloudflare Workers, Deno, and other WASM runtimes.
672
+ */
673
+
674
+ import { Effect, Layer } from "effect"
675
+ import * as S from "effect/Schema"
676
+ import { GraphQLSchemaBuilder, query, mutation, makeGraphQLRouter } from "@effect-gql/core"
677
+ import { toHandler } from "@effect-gql/web"
678
+
679
+ // =============================================================================
680
+ // Domain Models
681
+ // =============================================================================
682
+
683
+ const User = S.Struct({
684
+ id: S.String,
685
+ name: S.String,
686
+ email: S.String,
687
+ })
688
+
689
+ type User = S.Schema.Type<typeof User>
690
+
691
+ // =============================================================================
692
+ // In-Memory Data Store (replace with your database)
693
+ // =============================================================================
694
+
695
+ const users: User[] = [
696
+ { id: "1", name: "Alice", email: "alice@example.com" },
697
+ { id: "2", name: "Bob", email: "bob@example.com" },
698
+ ]
699
+
700
+ // =============================================================================
701
+ // GraphQL Schema
702
+ // =============================================================================
703
+
704
+ const schema = GraphQLSchemaBuilder.empty
705
+ .pipe(
706
+ query("hello", {
707
+ type: S.String,
708
+ description: "Returns a friendly greeting",
709
+ resolve: () => Effect.succeed("Hello from ${ctx.name}!"),
710
+ }),
711
+
712
+ query("user", {
713
+ args: S.Struct({ id: S.String }),
714
+ type: S.NullOr(User),
715
+ description: "Get a user by ID",
716
+ resolve: (args) => Effect.succeed(users.find((u) => u.id === args.id) ?? null),
717
+ }),
718
+
719
+ query("users", {
720
+ type: S.Array(User),
721
+ description: "Get all users",
722
+ resolve: () => Effect.succeed(users),
723
+ }),
724
+
725
+ mutation("createUser", {
726
+ args: S.Struct({
727
+ name: S.String,
728
+ email: S.String,
729
+ }),
730
+ type: User,
731
+ description: "Create a new user",
732
+ resolve: (args) =>
733
+ Effect.sync(() => {
734
+ const newUser: User = {
735
+ id: String(users.length + 1),
736
+ name: args.name,
737
+ email: args.email,
738
+ }
739
+ users.push(newUser)
740
+ return newUser
741
+ }),
742
+ })
743
+ )
744
+ .buildSchema()
745
+
746
+ // =============================================================================
747
+ // GraphQL Router
748
+ // =============================================================================
749
+
750
+ const graphqlRouter = makeGraphQLRouter(schema, Layer.empty, {
751
+ path: "/graphql",
752
+ graphiql: {
753
+ path: "/graphiql",
754
+ endpoint: "/graphql",
755
+ },
756
+ })
757
+
758
+ // =============================================================================
759
+ // Web Handler
760
+ // =============================================================================
761
+
762
+ const { handler } = toHandler(graphqlRouter, Layer.empty)
763
+
764
+ /**
765
+ * Export for Cloudflare Workers
766
+ */
767
+ export default {
768
+ async fetch(request: Request): Promise<Response> {
769
+ const url = new URL(request.url)
770
+
771
+ // Health check
772
+ if (url.pathname === "/health") {
773
+ return new Response(JSON.stringify({ status: "ok" }), {
774
+ headers: { "Content-Type": "application/json" },
775
+ })
776
+ }
777
+
778
+ return handler(request)
779
+ },
780
+ }
781
+
782
+ /**
783
+ * For local development with Deno or other runtimes, you can use:
784
+ *
785
+ * Deno.serve((request) => {
786
+ * const url = new URL(request.url)
787
+ * if (url.pathname === "/health") {
788
+ * return new Response(JSON.stringify({ status: "ok" }), {
789
+ * headers: { "Content-Type": "application/json" },
790
+ * })
791
+ * }
792
+ * return handler(request)
793
+ * })
794
+ */
795
+ `;
796
+ var generateServerTemplate = (ctx) => {
797
+ switch (ctx.serverType) {
798
+ case "node":
799
+ return generateNodeServer(ctx);
800
+ case "bun":
801
+ return generateBunServer(ctx);
802
+ case "express":
803
+ return generateExpressServer(ctx);
804
+ case "web":
805
+ return generateWebHandler(ctx);
806
+ }
807
+ };
808
+
809
+ // src/commands/create/templates/index.ts
810
+ var generateGitignore = () => `# Dependencies
811
+ node_modules/
812
+
813
+ # Build output
814
+ dist/
815
+ *.tsbuildinfo
816
+
817
+ # IDE
818
+ .idea/
819
+ .vscode/
820
+ *.swp
821
+ *.swo
822
+
823
+ # OS
824
+ .DS_Store
825
+ Thumbs.db
826
+
827
+ # Environment
828
+ .env
829
+ .env.local
830
+ .env.*.local
831
+
832
+ # Logs
833
+ *.log
834
+ npm-debug.log*
835
+ yarn-debug.log*
836
+ yarn-error.log*
837
+
838
+ # Testing
839
+ coverage/
840
+ `;
841
+ var generateProject = (ctx) => [
842
+ {
843
+ path: "package.json",
844
+ content: generatePackageJson(ctx)
845
+ },
846
+ {
847
+ path: "tsconfig.json",
848
+ content: generateTsConfig(ctx)
849
+ },
850
+ {
851
+ path: "src/index.ts",
852
+ content: generateServerTemplate(ctx)
853
+ },
854
+ {
855
+ path: ".gitignore",
856
+ content: generateGitignore()
857
+ }
858
+ ];
859
+ var fileExists = (filePath) => effect.Effect.sync(() => fs3__namespace.existsSync(filePath));
860
+ var readFile = (filePath) => effect.Effect.try({
861
+ try: () => fs3__namespace.readFileSync(filePath, "utf-8"),
862
+ catch: (error) => new Error(`Failed to read ${filePath}: ${error}`)
863
+ });
864
+ var detectPackageManager = (dir) => effect.Effect.gen(function* () {
865
+ if (yield* fileExists(path2__namespace.join(dir, "pnpm-lock.yaml"))) return "pnpm";
866
+ if (yield* fileExists(path2__namespace.join(dir, "bun.lockb"))) return "bun";
867
+ if (yield* fileExists(path2__namespace.join(dir, "yarn.lock"))) return "yarn";
868
+ return "npm";
869
+ });
870
+ var detectMonorepo = (targetDir) => effect.Effect.gen(function* () {
871
+ let currentDir = path2__namespace.dirname(path2__namespace.resolve(targetDir));
872
+ const root = path2__namespace.parse(currentDir).root;
873
+ while (currentDir !== root) {
874
+ const pnpmWorkspacePath = path2__namespace.join(currentDir, "pnpm-workspace.yaml");
875
+ if (yield* fileExists(pnpmWorkspacePath)) {
876
+ return {
877
+ isMonorepo: true,
878
+ packageManager: effect.Option.some("pnpm"),
879
+ workspacesRoot: effect.Option.some(currentDir)
880
+ };
881
+ }
882
+ const pkgPath = path2__namespace.join(currentDir, "package.json");
883
+ if (yield* fileExists(pkgPath)) {
884
+ const content = yield* readFile(pkgPath).pipe(effect.Effect.catchAll(() => effect.Effect.succeed("")));
885
+ if (content) {
886
+ try {
887
+ const pkg = JSON.parse(content);
888
+ if (pkg.workspaces) {
889
+ const pm = yield* detectPackageManager(currentDir);
890
+ return {
891
+ isMonorepo: true,
892
+ packageManager: effect.Option.some(pm),
893
+ workspacesRoot: effect.Option.some(currentDir)
894
+ };
895
+ }
896
+ } catch {
897
+ }
898
+ }
899
+ }
900
+ const turboPath = path2__namespace.join(currentDir, "turbo.json");
901
+ if (yield* fileExists(turboPath)) {
902
+ const pm = yield* detectPackageManager(currentDir);
903
+ return {
904
+ isMonorepo: true,
905
+ packageManager: effect.Option.some(pm),
906
+ workspacesRoot: effect.Option.some(currentDir)
907
+ };
908
+ }
909
+ currentDir = path2__namespace.dirname(currentDir);
910
+ }
911
+ return {
912
+ isMonorepo: false,
913
+ packageManager: effect.Option.none(),
914
+ workspacesRoot: effect.Option.none()
915
+ };
916
+ });
917
+ var getInstallCommand = (pm) => {
918
+ switch (pm) {
919
+ case "npm":
920
+ return "npm install";
921
+ case "pnpm":
922
+ return "pnpm install";
923
+ case "yarn":
924
+ return "yarn";
925
+ case "bun":
926
+ return "bun install";
927
+ }
928
+ };
929
+ var getRunPrefix = (pm) => {
930
+ switch (pm) {
931
+ case "npm":
932
+ return "npm run";
933
+ case "pnpm":
934
+ return "pnpm";
935
+ case "yarn":
936
+ return "yarn";
937
+ case "bun":
938
+ return "bun run";
939
+ }
940
+ };
941
+
942
+ // src/commands/create/scaffolder.ts
943
+ var validateDirectory = (targetDir) => effect.Effect.try({
944
+ try: () => {
945
+ if (fs3__namespace.existsSync(targetDir)) {
946
+ const files = fs3__namespace.readdirSync(targetDir);
947
+ if (files.length > 0) {
948
+ throw new Error(`Directory ${targetDir} is not empty`);
949
+ }
950
+ }
951
+ },
952
+ catch: (error) => error instanceof Error ? error : new Error(`Failed to validate directory: ${error}`)
953
+ });
954
+ var mkdirp = (dir) => effect.Effect.try({
955
+ try: () => fs3__namespace.mkdirSync(dir, { recursive: true }),
956
+ catch: (error) => new Error(`Failed to create directory ${dir}: ${error}`)
957
+ });
958
+ var writeFile = (filePath, content) => effect.Effect.gen(function* () {
959
+ yield* mkdirp(path2__namespace.dirname(filePath));
960
+ yield* effect.Effect.try({
961
+ try: () => fs3__namespace.writeFileSync(filePath, content, "utf-8"),
962
+ catch: (error) => new Error(`Failed to write ${filePath}: ${error}`)
963
+ });
964
+ });
965
+ var runCommand = (command, args, cwd) => effect.Effect.async((resume) => {
966
+ const child = child_process.spawn(command, args, {
967
+ cwd,
968
+ stdio: "inherit",
969
+ shell: true
970
+ });
971
+ child.on("close", (code) => {
972
+ if (code === 0) {
973
+ resume(effect.Effect.succeed(void 0));
974
+ } else {
975
+ resume(effect.Effect.fail(new Error(`Command failed with exit code ${code}`)));
976
+ }
977
+ });
978
+ child.on("error", (error) => {
979
+ resume(effect.Effect.fail(new Error(`Failed to run command: ${error.message}`)));
980
+ });
981
+ return effect.Effect.sync(() => {
982
+ child.kill();
983
+ });
984
+ });
985
+ var installDependencies = (targetDir, packageManager) => effect.Effect.gen(function* () {
986
+ yield* effect.Console.log("");
987
+ yield* effect.Console.log("Installing dependencies...");
988
+ const installCmd = getInstallCommand(packageManager);
989
+ const [cmd, ...args] = installCmd.split(" ");
990
+ yield* runCommand(cmd, args, targetDir);
991
+ yield* effect.Console.log("Dependencies installed successfully!");
992
+ });
993
+ var printSuccessMessage = (ctx, targetDir) => effect.Effect.gen(function* () {
994
+ const relativePath = path2__namespace.relative(process.cwd(), targetDir) || ".";
995
+ const runPrefix = getRunPrefix(ctx.packageManager);
996
+ yield* effect.Console.log("");
997
+ yield* effect.Console.log(`Successfully created ${ctx.name}!`);
998
+ yield* effect.Console.log("");
999
+ yield* effect.Console.log("Next steps:");
1000
+ yield* effect.Console.log(` cd ${relativePath}`);
1001
+ yield* effect.Console.log(` ${runPrefix} dev`);
1002
+ yield* effect.Console.log("");
1003
+ yield* effect.Console.log("Your GraphQL server will be available at:");
1004
+ yield* effect.Console.log(" http://localhost:4000/graphql");
1005
+ yield* effect.Console.log(" http://localhost:4000/graphiql (playground)");
1006
+ yield* effect.Console.log("");
1007
+ });
1008
+ var scaffold = (options) => effect.Effect.gen(function* () {
1009
+ const targetDir = effect.pipe(
1010
+ options.directory,
1011
+ effect.Option.map((dir) => path2__namespace.resolve(process.cwd(), dir)),
1012
+ effect.Option.getOrElse(() => path2__namespace.resolve(process.cwd(), options.name))
1013
+ );
1014
+ const monorepoInfo = yield* detectMonorepo(targetDir);
1015
+ const isMonorepo = effect.pipe(
1016
+ options.monorepo,
1017
+ effect.Option.getOrElse(() => monorepoInfo.isMonorepo)
1018
+ );
1019
+ const packageManager = yield* effect.pipe(
1020
+ options.packageManager,
1021
+ effect.Option.orElse(() => monorepoInfo.packageManager),
1022
+ effect.Option.match({
1023
+ onNone: () => detectPackageManager(process.cwd()),
1024
+ onSome: (pm) => effect.Effect.succeed(pm)
1025
+ })
1026
+ );
1027
+ const ctx = {
1028
+ name: options.name,
1029
+ serverType: options.serverType,
1030
+ isMonorepo,
1031
+ packageManager
1032
+ };
1033
+ yield* effect.Console.log("");
1034
+ yield* effect.Console.log(`Creating ${ctx.name} with ${ctx.serverType} server...`);
1035
+ yield* validateDirectory(targetDir);
1036
+ const files = generateProject(ctx);
1037
+ for (const file of files) {
1038
+ const filePath = path2__namespace.join(targetDir, file.path);
1039
+ yield* writeFile(filePath, file.content);
1040
+ yield* effect.Console.log(` Created ${file.path}`);
1041
+ }
1042
+ if (!options.skipInstall) {
1043
+ yield* installDependencies(targetDir, packageManager).pipe(
1044
+ effect.Effect.catchAll(
1045
+ (error) => effect.Console.log(`Warning: Failed to install dependencies: ${error.message}`).pipe(
1046
+ effect.Effect.andThen(
1047
+ effect.Console.log(
1048
+ "You can install them manually by running: " + getInstallCommand(packageManager)
1049
+ )
1050
+ )
1051
+ )
1052
+ )
1053
+ );
1054
+ } else {
1055
+ yield* effect.Console.log("");
1056
+ yield* effect.Console.log(
1057
+ `Skipping dependency installation. Run '${getInstallCommand(packageManager)}' to install.`
1058
+ );
1059
+ }
1060
+ yield* printSuccessMessage(ctx, targetDir);
1061
+ });
1062
+
1063
+ // src/commands/create/index.ts
1064
+ var printCreateHelp = () => {
1065
+ console.log(`
1066
+ Usage: effect-gql create <name> --server-type <type> [options]
1067
+
1068
+ Create a new Effect GraphQL project.
1069
+
1070
+ Arguments:
1071
+ name Package/project name
1072
+
1073
+ Required Options:
1074
+ -s, --server-type Server type: node, bun, express, web
1075
+
1076
+ Optional Options:
1077
+ -d, --directory Target directory (default: ./<name>)
1078
+ --monorepo Create as workspace package (auto-detected)
1079
+ --skip-install Skip dependency installation
1080
+ --package-manager Package manager: npm, pnpm, yarn, bun
1081
+ -i, --interactive Interactive mode (prompts for options)
1082
+ -h, --help Show this help message
1083
+
1084
+ Server Types:
1085
+ node Node.js server using @effect-gql/node
1086
+ Best for: General Node.js deployments
1087
+
1088
+ bun Bun server using @effect-gql/bun
1089
+ Best for: Bun runtime with native WebSocket support
1090
+
1091
+ express Express middleware using @effect-gql/express
1092
+ Best for: Integrating into existing Express apps
1093
+
1094
+ web Web standard handler using @effect-gql/web
1095
+ Best for: Cloudflare Workers, Deno, edge runtimes
1096
+
1097
+ Examples:
1098
+ effect-gql create my-api --server-type node
1099
+ effect-gql create my-api -s bun
1100
+ effect-gql create my-api -s express -d ./packages/api --monorepo
1101
+ effect-gql create my-api -s web --skip-install
1102
+ effect-gql create --interactive
1103
+ `);
1104
+ };
1105
+ var prompt = (question) => effect.Effect.async((resume) => {
1106
+ const rl = readline__namespace.createInterface({
1107
+ input: process.stdin,
1108
+ output: process.stdout
1109
+ });
1110
+ rl.question(question, (answer) => {
1111
+ rl.close();
1112
+ resume(effect.Effect.succeed(answer.trim()));
1113
+ });
1114
+ rl.on("error", (error) => {
1115
+ rl.close();
1116
+ resume(effect.Effect.fail(new Error(`Input error: ${error.message}`)));
1117
+ });
1118
+ return effect.Effect.sync(() => rl.close());
1119
+ });
1120
+ var selectPrompt = (question, options) => effect.Effect.gen(function* () {
1121
+ yield* effect.Console.log(question);
1122
+ options.forEach((opt, i) => {
1123
+ console.log(` ${i + 1}) ${opt.label}`);
1124
+ });
1125
+ const answer = yield* prompt("Enter number: ");
1126
+ const index = parseInt(answer, 10) - 1;
1127
+ if (isNaN(index) || index < 0 || index >= options.length) {
1128
+ yield* effect.Console.log(`Invalid selection. Please enter 1-${options.length}.`);
1129
+ return yield* selectPrompt(question, options);
1130
+ }
1131
+ return options[index].value;
1132
+ });
1133
+ var confirmPrompt = (question, defaultValue) => effect.Effect.gen(function* () {
1134
+ const hint = "[y/N]";
1135
+ const answer = yield* prompt(`${question} ${hint} `);
1136
+ if (answer === "") return defaultValue;
1137
+ const lower = answer.toLowerCase();
1138
+ if (lower === "y" || lower === "yes") return true;
1139
+ if (lower === "n" || lower === "no") return false;
1140
+ yield* effect.Console.log("Please answer 'y' or 'n'.");
1141
+ return yield* confirmPrompt(question, defaultValue);
1142
+ });
1143
+ var runInteractive = () => effect.Effect.gen(function* () {
1144
+ yield* effect.Console.log("");
1145
+ yield* effect.Console.log("Create a new Effect GraphQL project");
1146
+ yield* effect.Console.log("====================================");
1147
+ yield* effect.Console.log("");
1148
+ const name = yield* prompt("Project name: ");
1149
+ if (!name) {
1150
+ return yield* effect.Effect.fail(new Error("Project name is required"));
1151
+ }
1152
+ if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name)) {
1153
+ yield* effect.Console.log("Warning: Name may not be a valid npm package name.");
1154
+ }
1155
+ const serverType = yield* selectPrompt("Select server type:", [
1156
+ { label: "Node.js (@effect-gql/node)", value: "node" },
1157
+ { label: "Bun (@effect-gql/bun)", value: "bun" },
1158
+ { label: "Express (@effect-gql/express)", value: "express" },
1159
+ { label: "Web/Workers (@effect-gql/web)", value: "web" }
1160
+ ]);
1161
+ const monorepo = yield* confirmPrompt("Create as monorepo workspace package?", false);
1162
+ const packageManager = monorepo ? effect.Option.some(
1163
+ yield* selectPrompt("Select package manager:", [
1164
+ { label: "pnpm", value: "pnpm" },
1165
+ { label: "npm", value: "npm" },
1166
+ { label: "yarn", value: "yarn" },
1167
+ { label: "bun", value: "bun" }
1168
+ ])
1169
+ ) : effect.Option.none();
1170
+ return {
1171
+ name,
1172
+ serverType,
1173
+ directory: effect.Option.none(),
1174
+ monorepo: effect.Option.some(monorepo),
1175
+ skipInstall: false,
1176
+ packageManager
1177
+ };
1178
+ });
1179
+ var runCreate = (args) => effect.Effect.gen(function* () {
1180
+ const parsed = parseArgs(args);
1181
+ if ("help" in parsed) {
1182
+ printCreateHelp();
1183
+ return;
1184
+ }
1185
+ if ("error" in parsed) {
1186
+ yield* effect.Console.error(`Error: ${parsed.error}`);
1187
+ yield* effect.Console.log("");
1188
+ printCreateHelp();
1189
+ process.exitCode = 1;
1190
+ return;
1191
+ }
1192
+ const options = "interactive" in parsed ? yield* runInteractive() : parsed.options;
1193
+ yield* scaffold(options);
1194
+ });
1195
+
1196
+ exports.SERVER_TYPES = SERVER_TYPES;
76
1197
  exports.generateSDL = generateSDL;
77
1198
  exports.generateSDLFromModule = generateSDLFromModule;
1199
+ exports.isValidServerType = isValidServerType;
78
1200
  exports.loadSchema = loadSchema;
1201
+ exports.printCreateHelp = printCreateHelp;
1202
+ exports.runCreate = runCreate;
1203
+ exports.scaffold = scaffold;
79
1204
  //# sourceMappingURL=index.cjs.map
80
1205
  //# sourceMappingURL=index.cjs.map