@objectifthunes/create-sandstone 0.2.0 → 1.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.
Files changed (114) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +31 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/add-adapter.d.ts +2 -0
  6. package/dist/commands/add-adapter.d.ts.map +1 -0
  7. package/dist/commands/add-adapter.js +683 -0
  8. package/dist/commands/add-adapter.js.map +1 -0
  9. package/dist/commands/generate-adapter.d.ts +2 -0
  10. package/dist/commands/generate-adapter.d.ts.map +1 -0
  11. package/dist/commands/generate-adapter.js +467 -0
  12. package/dist/commands/generate-adapter.js.map +1 -0
  13. package/dist/commands/generate-entity.d.ts +2 -0
  14. package/dist/commands/generate-entity.d.ts.map +1 -0
  15. package/dist/commands/generate-entity.js +217 -0
  16. package/dist/commands/generate-entity.js.map +1 -0
  17. package/dist/generator.d.ts.map +1 -1
  18. package/dist/generator.js +92 -25
  19. package/dist/generator.js.map +1 -1
  20. package/dist/index.js +6 -5
  21. package/dist/index.js.map +1 -1
  22. package/dist/presets.d.ts +24 -4
  23. package/dist/presets.d.ts.map +1 -1
  24. package/dist/presets.js +42 -19
  25. package/dist/presets.js.map +1 -1
  26. package/dist/prompts.d.ts.map +1 -1
  27. package/dist/prompts.js +181 -7
  28. package/dist/prompts.js.map +1 -1
  29. package/dist/templates/auth-test.d.ts +3 -0
  30. package/dist/templates/auth-test.d.ts.map +1 -0
  31. package/dist/templates/auth-test.js +52 -0
  32. package/dist/templates/auth-test.js.map +1 -0
  33. package/dist/templates/claude-md.d.ts.map +1 -1
  34. package/dist/templates/claude-md.js +472 -4
  35. package/dist/templates/claude-md.js.map +1 -1
  36. package/dist/templates/docker-compose.d.ts.map +1 -1
  37. package/dist/templates/docker-compose.js +42 -0
  38. package/dist/templates/docker-compose.js.map +1 -1
  39. package/dist/templates/entities-repositories.d.ts +3 -0
  40. package/dist/templates/entities-repositories.d.ts.map +1 -0
  41. package/dist/templates/entities-repositories.js +19 -0
  42. package/dist/templates/entities-repositories.js.map +1 -0
  43. package/dist/templates/entities-types.d.ts +3 -0
  44. package/dist/templates/entities-types.d.ts.map +1 -0
  45. package/dist/templates/entities-types.js +24 -0
  46. package/dist/templates/entities-types.js.map +1 -0
  47. package/dist/templates/env-example.d.ts.map +1 -1
  48. package/dist/templates/env-example.js +81 -2
  49. package/dist/templates/env-example.js.map +1 -1
  50. package/dist/templates/graphql-auth.d.ts +3 -0
  51. package/dist/templates/graphql-auth.d.ts.map +1 -0
  52. package/dist/templates/graphql-auth.js +160 -0
  53. package/dist/templates/graphql-auth.js.map +1 -0
  54. package/dist/templates/graphql-enums.d.ts +2 -0
  55. package/dist/templates/graphql-enums.d.ts.map +1 -0
  56. package/dist/templates/graphql-enums.js +28 -0
  57. package/dist/templates/graphql-enums.js.map +1 -0
  58. package/dist/templates/graphql-helpers.d.ts +2 -0
  59. package/dist/templates/graphql-helpers.d.ts.map +1 -0
  60. package/dist/templates/graphql-helpers.js +134 -0
  61. package/dist/templates/graphql-helpers.js.map +1 -0
  62. package/dist/templates/graphql-inputs.d.ts +2 -0
  63. package/dist/templates/graphql-inputs.d.ts.map +1 -0
  64. package/dist/templates/graphql-inputs.js +28 -0
  65. package/dist/templates/graphql-inputs.js.map +1 -0
  66. package/dist/templates/graphql-schema.d.ts +3 -0
  67. package/dist/templates/graphql-schema.d.ts.map +1 -0
  68. package/dist/templates/graphql-schema.js +33 -0
  69. package/dist/templates/graphql-schema.js.map +1 -0
  70. package/dist/templates/graphql-types.d.ts +3 -0
  71. package/dist/templates/graphql-types.d.ts.map +1 -0
  72. package/dist/templates/graphql-types.js +61 -0
  73. package/dist/templates/graphql-types.js.map +1 -0
  74. package/dist/templates/graphql-validation.d.ts +3 -0
  75. package/dist/templates/graphql-validation.d.ts.map +1 -0
  76. package/dist/templates/graphql-validation.js +37 -0
  77. package/dist/templates/graphql-validation.js.map +1 -0
  78. package/dist/templates/health-e2e-test.d.ts +3 -0
  79. package/dist/templates/health-e2e-test.d.ts.map +1 -0
  80. package/dist/templates/health-e2e-test.js +50 -0
  81. package/dist/templates/health-e2e-test.js.map +1 -0
  82. package/dist/templates/infrastructure.d.ts.map +1 -1
  83. package/dist/templates/infrastructure.js +433 -32
  84. package/dist/templates/infrastructure.js.map +1 -1
  85. package/dist/templates/main-express.d.ts.map +1 -1
  86. package/dist/templates/main-express.js +87 -8
  87. package/dist/templates/main-express.js.map +1 -1
  88. package/dist/templates/main-fastify.d.ts +3 -0
  89. package/dist/templates/main-fastify.d.ts.map +1 -0
  90. package/dist/templates/main-fastify.js +73 -0
  91. package/dist/templates/main-fastify.js.map +1 -0
  92. package/dist/templates/main-hono.d.ts.map +1 -1
  93. package/dist/templates/main-hono.js +45 -9
  94. package/dist/templates/main-hono.js.map +1 -1
  95. package/dist/templates/package-json.d.ts.map +1 -1
  96. package/dist/templates/package-json.js +67 -3
  97. package/dist/templates/package-json.js.map +1 -1
  98. package/dist/templates/test-setup.d.ts +1 -1
  99. package/dist/templates/test-setup.d.ts.map +1 -1
  100. package/dist/templates/test-setup.js +10 -39
  101. package/dist/templates/test-setup.js.map +1 -1
  102. package/dist/templates/vitest-config.d.ts +2 -0
  103. package/dist/templates/vitest-config.d.ts.map +1 -0
  104. package/dist/templates/vitest-config.js +13 -0
  105. package/dist/templates/vitest-config.js.map +1 -0
  106. package/dist/templates/vitest-e2e-config.d.ts +2 -0
  107. package/dist/templates/vitest-e2e-config.d.ts.map +1 -0
  108. package/dist/templates/vitest-e2e-config.js +16 -0
  109. package/dist/templates/vitest-e2e-config.js.map +1 -0
  110. package/dist/templates/workers-index.d.ts +3 -0
  111. package/dist/templates/workers-index.d.ts.map +1 -0
  112. package/dist/templates/workers-index.js +19 -0
  113. package/dist/templates/workers-index.js.map +1 -0
  114. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"docker-compose.d.ts","sourceRoot":"","sources":["../../src/templates/docker-compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAmCnE"}
1
+ {"version":3,"file":"docker-compose.d.ts","sourceRoot":"","sources":["../../src/templates/docker-compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAmFnE"}
@@ -23,7 +23,49 @@ export function dockerComposeTemplate(config) {
23
23
  - '1025:1025'
24
24
  - '8025:8025'`);
25
25
  }
26
+ if (config.cache === 'redis' || config.queue === 'bullmq') {
27
+ services.push(`
28
+ redis:
29
+ image: redis:7-alpine
30
+ ports:
31
+ - '6379:6379'
32
+ volumes:
33
+ - redisdata:/var/lib/redis/data`);
34
+ }
35
+ if (config.search === 'meilisearch') {
36
+ services.push(`
37
+ meilisearch:
38
+ image: getmeili/meilisearch:latest
39
+ ports:
40
+ - '7700:7700'
41
+ environment:
42
+ MEILI_ENV: development
43
+ MEILI_MASTER_KEY: development-master-key
44
+ volumes:
45
+ - meilidata:/meili_data`);
46
+ }
47
+ if (config.search === 'typesense') {
48
+ services.push(`
49
+ typesense:
50
+ image: typesense/typesense:27.1
51
+ ports:
52
+ - '8108:8108'
53
+ environment:
54
+ TYPESENSE_API_KEY: development-api-key
55
+ TYPESENSE_DATA_DIR: /data
56
+ volumes:
57
+ - typesensedata:/data`);
58
+ }
26
59
  const volumes = ['volumes:', ' pgdata:'];
60
+ if (config.cache === 'redis' || config.queue === 'bullmq') {
61
+ volumes.push(' redisdata:');
62
+ }
63
+ if (config.search === 'meilisearch') {
64
+ volumes.push(' meilidata:');
65
+ }
66
+ if (config.search === 'typesense') {
67
+ volumes.push(' typesensedata:');
68
+ }
27
69
  return `services:
28
70
  ${services.join('\n')}
29
71
 
@@ -1 +1 @@
1
- {"version":3,"file":"docker-compose.js","sourceRoot":"","sources":["../../src/templates/docker-compose.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CAAC,MAAqB;IACzD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,QAAQ,CAAC,IAAI,CAAC;;;;;qBAKK,MAAM,CAAC,IAAI;;;;;;;;;iBASf,CAAC,CAAC;IAEjB,IAAI,MAAM,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC;;;;;oBAKE,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAE1C,OAAO;EACP,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;;EAEnB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;CACnB,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"docker-compose.js","sourceRoot":"","sources":["../../src/templates/docker-compose.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CAAC,MAAqB;IACzD,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,QAAQ,CAAC,IAAI,CAAC;;;;;qBAKK,MAAM,CAAC,IAAI;;;;;;;;;iBASf,CAAC,CAAC;IAEjB,IAAI,MAAM,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC;;;;;oBAKE,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC1D,QAAQ,CAAC,IAAI,CAAC;;;;;;sCAMoB,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACpC,QAAQ,CAAC,IAAI,CAAC;;;;;;;;;8BASY,CAAC,CAAC;IAC9B,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,QAAQ,CAAC,IAAI,CAAC;;;;;;;;;4BASU,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAE1C,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO,IAAI,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC1D,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACnC,CAAC;IAED,OAAO;EACP,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;;EAEnB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;CACnB,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfig } from '../presets.js';
2
+ export declare function entitiesRepositoriesTemplate(_config: ProjectConfig): string;
3
+ //# sourceMappingURL=entities-repositories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entities-repositories.d.ts","sourceRoot":"","sources":["../../src/templates/entities-repositories.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAiB3E"}
@@ -0,0 +1,19 @@
1
+ export function entitiesRepositoriesTemplate(_config) {
2
+ return `// Define your createRepository() calls here.
3
+ // Each repository maps to a database table.
4
+ // All repo methods take db: Queryable as first arg (explicit dependency injection).
5
+ //
6
+ // import { createRepository } from '@objectifthunes/sandstone-sdk';
7
+ // import type { Client } from './types.js';
8
+ //
9
+ // export const clientRepo = createRepository<Client>('clients', { primaryKey: 'id' });
10
+ //
11
+ // Usage in resolvers:
12
+ // await clientRepo.findById(ctx.db, id);
13
+ // await clientRepo.paginate(ctx.db, { where: { user_id }, page, perPage });
14
+ // await clientRepo.create(tx, { user_id, name, email }); // inside transaction
15
+ // await clientRepo.update(ctx.db, id, { name, updated_at: new Date() });
16
+ // await clientRepo.delete(ctx.db, id);
17
+ `;
18
+ }
19
+ //# sourceMappingURL=entities-repositories.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entities-repositories.js","sourceRoot":"","sources":["../../src/templates/entities-repositories.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,4BAA4B,CAAC,OAAsB;IACjE,OAAO;;;;;;;;;;;;;;;CAeR,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfig } from '../presets.js';
2
+ export declare function entitiesTypesTemplate(_config: ProjectConfig): string;
3
+ //# sourceMappingURL=entities-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entities-types.d.ts","sourceRoot":"","sources":["../../src/templates/entities-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAsBpE"}
@@ -0,0 +1,24 @@
1
+ export function entitiesTypesTemplate(_config) {
2
+ return `// Define your entity TypeScript interfaces here.
3
+ // Each interface should match your database table columns exactly.
4
+ //
5
+ // Standards:
6
+ // - Every table: id (string/UUID), created_at (Date)
7
+ // - Every mutable table: updated_at (Date)
8
+ // - FKs reference auth_users(id) for user ownership
9
+ // - Money fields: number (stored as NUMERIC in DB, never float)
10
+ //
11
+ // Example:
12
+ // export interface Client {
13
+ // id: string;
14
+ // user_id: string;
15
+ // name: string;
16
+ // email: string;
17
+ // phone: string | null;
18
+ // address: Record<string, unknown> | null;
19
+ // created_at: Date;
20
+ // updated_at: Date;
21
+ // }
22
+ `;
23
+ }
24
+ //# sourceMappingURL=entities-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entities-types.js","sourceRoot":"","sources":["../../src/templates/entities-types.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CAAC,OAAsB;IAC1D,OAAO;;;;;;;;;;;;;;;;;;;;CAoBR,CAAC;AACF,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"env-example.d.ts","sourceRoot":"","sources":["../../src/templates/env-example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAqGhE"}
1
+ {"version":3,"file":"env-example.d.ts","sourceRoot":"","sources":["../../src/templates/env-example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CA2OhE"}
@@ -19,13 +19,27 @@ export function envExampleTemplate(config) {
19
19
  '# Auth',
20
20
  'JWT_SECRET=change-me-to-a-random-secret-at-least-32-chars',
21
21
  ];
22
+ // Dedup trackers
23
+ let hasRedisUrl = false;
24
+ let hasAwsCredentials = false;
22
25
  if (config.email === 'resend') {
23
26
  lines.push('', '# Email (Resend)', 'RESEND_API_KEY=re_xxx', 'EMAIL_FROM=noreply@example.com');
24
27
  }
25
28
  else if (config.email === 'nodemailer') {
26
29
  lines.push('', '# Email (SMTP)', 'SMTP_HOST=localhost', 'SMTP_PORT=1025', 'SMTP_USER=', 'SMTP_PASS=', 'EMAIL_FROM=noreply@example.com');
27
30
  }
28
- if (config.auth === 'oauth+otp' || config.auth === 'all') {
31
+ // OAuth providers loop over config.oauthProviders
32
+ if (config.oauthProviders.length > 0) {
33
+ for (const provider of config.oauthProviders) {
34
+ const upper = provider.toUpperCase();
35
+ lines.push('', `# OAuth (${provider.charAt(0).toUpperCase() + provider.slice(1)})`, `${upper}_CLIENT_ID=`, `${upper}_CLIENT_SECRET=`, `${upper}_REDIRECT_URI=http://localhost:3000/auth/${provider}/callback`);
36
+ if (provider === 'apple') {
37
+ lines.push('APPLE_TEAM_ID=', 'APPLE_KEY_ID=', 'APPLE_PRIVATE_KEY=');
38
+ }
39
+ }
40
+ }
41
+ else if (config.auth === 'oauth+otp' || config.auth === 'all') {
42
+ // Fallback: no specific providers chosen but OAuth auth strategy is selected
29
43
  lines.push('', '# OAuth (Google)', 'GOOGLE_CLIENT_ID=', 'GOOGLE_CLIENT_SECRET=', 'GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback');
30
44
  }
31
45
  if (config.payments === 'stripe') {
@@ -41,7 +55,8 @@ export function envExampleTemplate(config) {
41
55
  lines.push('', '# Payments (Lemon Squeezy)', 'LEMONSQUEEZY_API_KEY=', 'LEMONSQUEEZY_WEBHOOK_SECRET=');
42
56
  }
43
57
  if (config.storage === 's3') {
44
- lines.push('', '# Storage (S3)', 'S3_BUCKET=my-bucket', 'S3_REGION=us-east-1', 'S3_ACCESS_KEY_ID=', 'S3_SECRET_ACCESS_KEY=');
58
+ lines.push('', '# Storage (S3)', 'S3_BUCKET=my-bucket', 'S3_REGION=us-east-1', 'AWS_ACCESS_KEY_ID=', 'AWS_SECRET_ACCESS_KEY=');
59
+ hasAwsCredentials = true;
45
60
  }
46
61
  else if (config.storage === 'r2') {
47
62
  lines.push('', '# Storage (Cloudflare R2)', 'R2_ACCOUNT_ID=', 'R2_ACCESS_KEY_ID=', 'R2_SECRET_ACCESS_KEY=', 'R2_BUCKET=my-bucket');
@@ -52,6 +67,70 @@ export function envExampleTemplate(config) {
52
67
  if (config.cache === 'upstash') {
53
68
  lines.push('', '# Cache (Upstash Redis)', 'UPSTASH_REDIS_URL=', 'UPSTASH_REDIS_TOKEN=');
54
69
  }
70
+ else if (config.cache === 'redis') {
71
+ lines.push('', '# Cache (Redis)', 'REDIS_URL=redis://localhost:6379');
72
+ hasRedisUrl = true;
73
+ }
74
+ // SMS
75
+ if (config.sms === 'twilio') {
76
+ lines.push('', '# SMS (Twilio)', 'TWILIO_ACCOUNT_SID=ACxxxxxxxxxx', 'TWILIO_AUTH_TOKEN=', 'TWILIO_FROM_NUMBER=+15551234567');
77
+ }
78
+ else if (config.sms === 'sns') {
79
+ lines.push('', '# SMS (AWS SNS)', 'AWS_REGION=us-east-1');
80
+ if (!hasAwsCredentials) {
81
+ lines.push('AWS_ACCESS_KEY_ID=', 'AWS_SECRET_ACCESS_KEY=');
82
+ hasAwsCredentials = true;
83
+ }
84
+ }
85
+ // Push notifications
86
+ if (config.push === 'fcm' || config.push === 'both') {
87
+ lines.push('', '# Push (FCM)', 'FCM_PROJECT_ID=', 'FCM_CLIENT_EMAIL=', 'FCM_PRIVATE_KEY=');
88
+ }
89
+ if (config.push === 'apns' || config.push === 'both') {
90
+ lines.push('', '# Push (APNs)', 'APNS_KEY_ID=', 'APNS_TEAM_ID=', 'APNS_PRIVATE_KEY=', 'APNS_BUNDLE_ID=com.example.app');
91
+ }
92
+ // Realtime
93
+ if (config.realtime === 'pusher') {
94
+ lines.push('', '# Realtime (Pusher)', 'PUSHER_APP_ID=', 'PUSHER_KEY=', 'PUSHER_SECRET=', 'PUSHER_CLUSTER=us2');
95
+ }
96
+ else if (config.realtime === 'ably') {
97
+ lines.push('', '# Realtime (Ably)', 'ABLY_API_KEY=');
98
+ }
99
+ // socketio: no env vars needed
100
+ // Search
101
+ if (config.search === 'meilisearch') {
102
+ lines.push('', '# Search (Meilisearch)', 'MEILISEARCH_HOST=http://localhost:7700', 'MEILISEARCH_API_KEY=');
103
+ }
104
+ else if (config.search === 'typesense') {
105
+ lines.push('', '# Search (Typesense)', 'TYPESENSE_HOST=localhost', 'TYPESENSE_PORT=8108', 'TYPESENSE_API_KEY=');
106
+ }
107
+ // pg-search: no env vars, reuses DATABASE_URL
108
+ // Queue
109
+ if (config.queue === 'bullmq') {
110
+ if (!hasRedisUrl) {
111
+ lines.push('', '# Queue (BullMQ)', 'REDIS_URL=redis://localhost:6379');
112
+ hasRedisUrl = true;
113
+ }
114
+ else {
115
+ lines.push('', '# Queue (BullMQ)', '# Uses REDIS_URL defined above');
116
+ }
117
+ }
118
+ // pg-boss: no env vars, reuses DATABASE_URL
119
+ // Feature flags
120
+ if (config.flags === 'launchdarkly') {
121
+ lines.push('', '# Feature Flags (LaunchDarkly)', 'LAUNCHDARKLY_SDK_KEY=sdk-xxxxxxxxxx');
122
+ }
123
+ else if (config.flags === 'env-flags') {
124
+ lines.push('', '# Feature Flags (env-flags)', '# Set flags via environment variables with FLAG_ prefix, e.g.:', '# FLAG_MY_FEATURE=true');
125
+ }
126
+ // pg-flags: no env vars
127
+ // Tracing
128
+ if (config.tracing) {
129
+ lines.push('', '# Tracing (OpenTelemetry)', `OTEL_SERVICE_NAME=${config.name}`, 'OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318');
130
+ }
131
+ // Suppress unused variable warnings — both trackers are read above via conditionals
132
+ void hasRedisUrl;
133
+ void hasAwsCredentials;
55
134
  lines.push('');
56
135
  return lines.join('\n');
57
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"env-example.js","sourceRoot":"","sources":["../../src/templates/env-example.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"env-example.js","sourceRoot":"","sources":["../../src/templates/env-example.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfig } from '../presets.js';
2
+ export declare function graphqlAuthTemplate(config: ProjectConfig): string;
3
+ //# sourceMappingURL=graphql-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-auth.d.ts","sourceRoot":"","sources":["../../src/templates/graphql-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CA8KjE"}
@@ -0,0 +1,160 @@
1
+ export function graphqlAuthTemplate(config) {
2
+ if (!config.auth) {
3
+ return `// Auth is not enabled for this project.
4
+ // To add auth, re-run create-sandstone with an auth strategy.
5
+ export const authQueries = {};
6
+ export const authMutations = {};
7
+ `;
8
+ }
9
+ const hasPassword = config.auth === 'password+otp' || config.auth === 'all';
10
+ const hasOAuth = (config.auth === 'oauth+otp' || config.auth === 'all') && config.oauthProviders.length > 0;
11
+ const imports = [];
12
+ const queryFields = [];
13
+ const mutationFields = [];
14
+ // ── Imports ──────────────────────────────────────────────────────────────
15
+ imports.push(`import {
16
+ GraphQLObjectType,
17
+ GraphQLString,
18
+ GraphQLNonNull,
19
+ GraphQLBoolean,
20
+ } from '@objectifthunes/sandstone-sdk';`);
21
+ imports.push(`import type { GraphQLContext } from '@objectifthunes/sandstone-sdk';`);
22
+ imports.push(`import { requireAuth } from './helpers.js';`);
23
+ imports.push(`import { AuthResultType, MeType } from './types.js';`);
24
+ imports.push(`import { app as sandstoneApp } from '../infrastructure.js';`);
25
+ if (hasPassword) {
26
+ imports.push(`import { validate } from '@objectifthunes/sandstone-sdk';`);
27
+ imports.push(`import { RegisterSchema } from './validation.js';`);
28
+ }
29
+ // ── me query ─────────────────────────────────────────────────────────────
30
+ queryFields.push(` me: {
31
+ type: MeType,
32
+ resolve: async (_parent: unknown, _args: unknown, ctx: GraphQLContext) => {
33
+ const user = requireAuth(ctx);
34
+ const fullUser = await sandstoneApp.auth!.getUser(user.sub);
35
+ return fullUser;
36
+ },
37
+ }`);
38
+ // ── sendCode mutation ────────────────────────────────────────────────────
39
+ mutationFields.push(` sendCode: {
40
+ type: GraphQLBoolean,
41
+ args: {
42
+ email: { type: new GraphQLNonNull(GraphQLString) },
43
+ },
44
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
45
+ await sandstoneApp.auth!.sendCode(args.email);
46
+ return true;
47
+ },
48
+ }`);
49
+ // ── verifyCode mutation ──────────────────────────────────────────────────
50
+ mutationFields.push(` verifyCode: {
51
+ type: AuthResultType,
52
+ args: {
53
+ email: { type: new GraphQLNonNull(GraphQLString) },
54
+ code: { type: new GraphQLNonNull(GraphQLString) },
55
+ },
56
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
57
+ const result = await sandstoneApp.auth!.verifyCode(args.email, args.code);
58
+ return result;
59
+ },
60
+ }`);
61
+ // ── register mutation (password auth) ────────────────────────────────────
62
+ if (hasPassword) {
63
+ mutationFields.push(` register: {
64
+ type: AuthResultType,
65
+ args: {
66
+ email: { type: new GraphQLNonNull(GraphQLString) },
67
+ password: { type: new GraphQLNonNull(GraphQLString) },
68
+ },
69
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
70
+ validate(RegisterSchema, { email: args.email, password: args.password });
71
+ const result = await sandstoneApp.auth!.register({ email: args.email, password: args.password });
72
+ return result;
73
+ },
74
+ }`);
75
+ }
76
+ // ── loginWithPassword mutation (password auth) ───────────────────────────
77
+ if (hasPassword) {
78
+ mutationFields.push(` loginWithPassword: {
79
+ type: AuthResultType,
80
+ args: {
81
+ email: { type: new GraphQLNonNull(GraphQLString) },
82
+ password: { type: new GraphQLNonNull(GraphQLString) },
83
+ },
84
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
85
+ const result = await sandstoneApp.auth!.loginWithPassword({ email: args.email, password: args.password });
86
+ return result;
87
+ },
88
+ }`);
89
+ }
90
+ // ── getOAuthUrl mutation (OAuth) ─────────────────────────────────────────
91
+ if (hasOAuth) {
92
+ mutationFields.push(` getOAuthUrl: {
93
+ type: new GraphQLNonNull(GraphQLString),
94
+ args: {
95
+ provider: { type: new GraphQLNonNull(GraphQLString) },
96
+ redirectUri: { type: new GraphQLNonNull(GraphQLString) },
97
+ },
98
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
99
+ const { url } = await sandstoneApp.auth!.getOAuthUrl(args.provider, { redirectUri: args.redirectUri });
100
+ return url;
101
+ },
102
+ }`);
103
+ }
104
+ // ── handleOAuthCallback mutation (OAuth) ─────────────────────────────────
105
+ if (hasOAuth) {
106
+ mutationFields.push(` handleOAuthCallback: {
107
+ type: AuthResultType,
108
+ args: {
109
+ provider: { type: new GraphQLNonNull(GraphQLString) },
110
+ code: { type: new GraphQLNonNull(GraphQLString) },
111
+ state: { type: new GraphQLNonNull(GraphQLString) },
112
+ redirectUri: { type: new GraphQLNonNull(GraphQLString) },
113
+ },
114
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
115
+ const result = await sandstoneApp.auth!.handleOAuthCallback(
116
+ args.provider,
117
+ { code: args.code, redirectUri: args.redirectUri, state: args.state },
118
+ );
119
+ return result;
120
+ },
121
+ }`);
122
+ }
123
+ // ── refreshToken mutation ────────────────────────────────────────────────
124
+ mutationFields.push(` refreshToken: {
125
+ type: AuthResultType,
126
+ args: {
127
+ refreshToken: { type: new GraphQLNonNull(GraphQLString) },
128
+ },
129
+ resolve: async (_parent: unknown, args: Record<string, string>) => {
130
+ const result = await sandstoneApp.auth!.refresh(args.refreshToken);
131
+ return result;
132
+ },
133
+ }`);
134
+ // ── logout mutation ──────────────────────────────────────────────────────
135
+ mutationFields.push(` logout: {
136
+ type: GraphQLBoolean,
137
+ args: {
138
+ sessionId: { type: new GraphQLNonNull(GraphQLString) },
139
+ },
140
+ resolve: async (_parent: unknown, args: Record<string, string>, ctx: GraphQLContext) => {
141
+ requireAuth(ctx);
142
+ await sandstoneApp.auth!.logout(args.sessionId);
143
+ return true;
144
+ },
145
+ }`);
146
+ // ── Build output ─────────────────────────────────────────────────────────
147
+ const parts = [];
148
+ parts.push(imports.join('\n'));
149
+ parts.push('');
150
+ parts.push(`export const authQueries = {`);
151
+ parts.push(queryFields.join(',\n'));
152
+ parts.push(`};`);
153
+ parts.push('');
154
+ parts.push(`export const authMutations = {`);
155
+ parts.push(mutationFields.join(',\n'));
156
+ parts.push(`};`);
157
+ parts.push('');
158
+ return parts.join('\n');
159
+ }
160
+ //# sourceMappingURL=graphql-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-auth.js","sourceRoot":"","sources":["../../src/templates/graphql-auth.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,MAAqB;IACvD,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,OAAO;;;;CAIV,CAAC;IACA,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,KAAK,cAAc,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC;IAC5E,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC;IAE5G,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,4EAA4E;IAC5E,OAAO,CAAC,IAAI,CAAC;;;;;wCAKyB,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;IACrF,OAAO,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC;IAC5D,OAAO,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,6DAA6D,CAAC,CAAC;IAE5E,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;IACpE,CAAC;IAED,4EAA4E;IAC5E,WAAW,CAAC,IAAI,CAAC;;;;;;;MAOb,CAAC,CAAC;IAEN,4EAA4E;IAC5E,cAAc,CAAC,IAAI,CAAC;;;;;;;;;MAShB,CAAC,CAAC;IAEN,4EAA4E;IAC5E,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;MAUhB,CAAC,CAAC;IAEN,4EAA4E;IAC5E,IAAI,WAAW,EAAE,CAAC;QAChB,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;;MAWlB,CAAC,CAAC;IACN,CAAC;IAED,4EAA4E;IAC5E,IAAI,WAAW,EAAE,CAAC;QAChB,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;MAUlB,CAAC,CAAC;IACN,CAAC;IAED,4EAA4E;IAC5E,IAAI,QAAQ,EAAE,CAAC;QACb,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;MAUlB,CAAC,CAAC;IACN,CAAC;IAED,4EAA4E;IAC5E,IAAI,QAAQ,EAAE,CAAC;QACb,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;MAelB,CAAC,CAAC;IACN,CAAC;IAED,4EAA4E;IAC5E,cAAc,CAAC,IAAI,CAAC;;;;;;;;;MAShB,CAAC,CAAC;IAEN,4EAA4E;IAC5E,cAAc,CAAC,IAAI,CAAC;;;;;;;;;;MAUhB,CAAC,CAAC;IAEN,4EAA4E;IAC5E,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC3C,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;IAC7C,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function graphqlEnumsTemplate(): string;
2
+ //# sourceMappingURL=graphql-enums.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-enums.d.ts","sourceRoot":"","sources":["../../src/templates/graphql-enums.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,IAAI,MAAM,CA0B7C"}
@@ -0,0 +1,28 @@
1
+ export function graphqlEnumsTemplate() {
2
+ return `// Add your GraphQL enum types here.
3
+ // Enums enforce a fixed set of values at the GraphQL schema level.
4
+ //
5
+ // Example:
6
+ // import { GraphQLEnumType } from '@objectifthunes/sandstone-sdk';
7
+ //
8
+ // export const InvoiceStatusEnum = new GraphQLEnumType({
9
+ // name: 'InvoiceStatus',
10
+ // values: {
11
+ // DRAFT: { value: 'draft' },
12
+ // SENT: { value: 'sent' },
13
+ // PAID: { value: 'paid' },
14
+ // VOID: { value: 'void' },
15
+ // },
16
+ // });
17
+ //
18
+ // export const CurrencyEnum = new GraphQLEnumType({
19
+ // name: 'Currency',
20
+ // values: {
21
+ // EUR: { value: 'EUR' },
22
+ // USD: { value: 'USD' },
23
+ // GBP: { value: 'GBP' },
24
+ // },
25
+ // });
26
+ `;
27
+ }
28
+ //# sourceMappingURL=graphql-enums.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-enums.js","sourceRoot":"","sources":["../../src/templates/graphql-enums.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB;IAClC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;CAwBR,CAAC;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function graphqlHelpersTemplate(): string;
2
+ //# sourceMappingURL=graphql-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-helpers.d.ts","sourceRoot":"","sources":["../../src/templates/graphql-helpers.ts"],"names":[],"mappings":"AAAA,wBAAgB,sBAAsB,IAAI,MAAM,CAoI/C"}
@@ -0,0 +1,134 @@
1
+ export function graphqlHelpersTemplate() {
2
+ return `import { UnauthorizedError, NotFoundError } from '@objectifthunes/sandstone-sdk';
3
+ import type { GraphQLContext } from '@objectifthunes/sandstone-sdk';
4
+ import { app as sandstoneApp } from '../infrastructure.js';
5
+
6
+ // ── 1. Auth guard (implemented) ──────────────────────────────────────────────
7
+ export function requireAuth(ctx: GraphQLContext) {
8
+ if (!ctx.user) throw new UnauthorizedError('Authentication required');
9
+ return ctx.user;
10
+ }
11
+
12
+ // ── 2. Ownership check pattern ───────────────────────────────────────────────
13
+ // Checks authz adapter first, then falls back to manual user_id comparison.
14
+ //
15
+ // export async function getOwnedEntity(ctx: GraphQLContext, userId: string, entityId: string) {
16
+ // const entity = await entityRepo.findById(ctx.db, entityId);
17
+ // if (!entity) throw new NotFoundError('Entity', entityId);
18
+ // if (sandstoneApp.authz) {
19
+ // const allowed = await sandstoneApp.authz.can(userId, 'read', 'entity', entityId);
20
+ // if (allowed) return entity;
21
+ // }
22
+ // if (entity.user_id !== userId) throw new NotFoundError('Entity', entityId);
23
+ // return entity;
24
+ // }
25
+
26
+ // ── 3. Cache read-through pattern ────────────────────────────────────────────
27
+ // Reads from cache first, falls back to DB, then populates cache with TTL.
28
+ // Always invalidate on mutation.
29
+ //
30
+ // export async function getCachedEntities(userId: string, page: number, perPage: number) {
31
+ // const cacheKey = \`entities:\${userId}:\${page}:\${perPage}\`;
32
+ // if (sandstoneApp.cache) {
33
+ // const cached = await sandstoneApp.cache.get(cacheKey);
34
+ // if (cached) return cached;
35
+ // }
36
+ // const result = await entityRepo.paginate(sandstoneApp.db, {
37
+ // where: { user_id: userId },
38
+ // page,
39
+ // perPage,
40
+ // });
41
+ // if (sandstoneApp.cache) {
42
+ // await sandstoneApp.cache.set(cacheKey, result, 60); // 60s TTL
43
+ // }
44
+ // return result;
45
+ // }
46
+ //
47
+ // export async function invalidateEntityCache(userId: string) {
48
+ // if (sandstoneApp.cache) {
49
+ // await sandstoneApp.cache.delete(\`entities:\${userId}:*\`);
50
+ // }
51
+ // }
52
+
53
+ // ── 4. Search indexing pattern ───────────────────────────────────────────────
54
+ // Index documents on create/update, remove on delete.
55
+ //
56
+ // export async function indexEntityInSearch(entity: { id: string; name: string; status: string; user_id: string }) {
57
+ // if (sandstoneApp.search) {
58
+ // await sandstoneApp.search.index('entity_idx', [{
59
+ // id: entity.id,
60
+ // document: {
61
+ // name: entity.name,
62
+ // status: entity.status,
63
+ // user_id: entity.user_id,
64
+ // },
65
+ // }]);
66
+ // }
67
+ // }
68
+ //
69
+ // export async function removeEntityFromSearch(entityId: string) {
70
+ // if (sandstoneApp.search) {
71
+ // await sandstoneApp.search.delete('entity_idx', [entityId]);
72
+ // }
73
+ // }
74
+
75
+ // ── 5. Queue enqueue pattern ─────────────────────────────────────────────────
76
+ // Offload heavy work to background workers.
77
+ //
78
+ // export async function enqueueEntityJob(entityId: string, action: string) {
79
+ // if (sandstoneApp.queue) {
80
+ // await sandstoneApp.queue.enqueue('entity-jobs', { entityId, action }, {
81
+ // maxAttempts: 3,
82
+ // backoff: 'exponential',
83
+ // backoffDelayMs: 1000,
84
+ // });
85
+ // }
86
+ // }
87
+
88
+ // ── 6. Audit logging pattern ─────────────────────────────────────────────────
89
+ // Log every mutation for compliance and debugging.
90
+ //
91
+ // export async function auditLog(actor: string, action: string, resource: string, resourceId: string) {
92
+ // if (sandstoneApp.audit) {
93
+ // await sandstoneApp.audit.log({
94
+ // actor,
95
+ // action,
96
+ // resource,
97
+ // resourceId,
98
+ // });
99
+ // }
100
+ // }
101
+
102
+ // ── 7. Tracing pattern ──────────────────────────────────────────────────────
103
+ // Wrap business operations in spans for observability.
104
+ //
105
+ // export async function withTrace<T>(name: string, fn: () => Promise<T>): Promise<T> {
106
+ // return sandstoneApp.tracer.withSpan(name, async (span) => {
107
+ // try {
108
+ // const result = await fn();
109
+ // span.setStatus('ok');
110
+ // return result;
111
+ // } catch (err) {
112
+ // span.setStatus('error', err instanceof Error ? err.message : 'unknown');
113
+ // throw err;
114
+ // }
115
+ // });
116
+ // }
117
+
118
+ // ── 8. Feature flag pattern ─────────────────────────────────────────────────
119
+ // Branch logic based on feature flags with user-level targeting.
120
+ //
121
+ // export async function isFeatureEnabled(flag: string, userId?: string): Promise<boolean> {
122
+ // if (!sandstoneApp.flags) return false;
123
+ // return sandstoneApp.flags.isEnabled(flag, userId ? { userId } : undefined);
124
+ // }
125
+ //
126
+ // Usage in a resolver:
127
+ // const useNewFlow = await isFeatureEnabled('new_checkout_flow', user.sub);
128
+ // if (useNewFlow) {
129
+ // return handleNewCheckoutFlow(args);
130
+ // }
131
+ // return handleLegacyCheckoutFlow(args);
132
+ `;
133
+ }
134
+ //# sourceMappingURL=graphql-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-helpers.js","sourceRoot":"","sources":["../../src/templates/graphql-helpers.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkIR,CAAC;AACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function graphqlInputsTemplate(): string;
2
+ //# sourceMappingURL=graphql-inputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graphql-inputs.d.ts","sourceRoot":"","sources":["../../src/templates/graphql-inputs.ts"],"names":[],"mappings":"AAAA,wBAAgB,qBAAqB,IAAI,MAAM,CA0B9C"}