@objectifthunes/create-sandstone 0.2.0 → 1.0.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 (58) 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 +210 -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 +7 -0
  19. package/dist/generator.js.map +1 -1
  20. package/dist/index.js +3 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/presets.d.ts +23 -3
  23. package/dist/presets.d.ts.map +1 -1
  24. package/dist/presets.js +48 -1
  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/claude-md.d.ts.map +1 -1
  30. package/dist/templates/claude-md.js +470 -2
  31. package/dist/templates/claude-md.js.map +1 -1
  32. package/dist/templates/docker-compose.d.ts.map +1 -1
  33. package/dist/templates/docker-compose.js +42 -0
  34. package/dist/templates/docker-compose.js.map +1 -1
  35. package/dist/templates/env-example.d.ts.map +1 -1
  36. package/dist/templates/env-example.js +81 -2
  37. package/dist/templates/env-example.js.map +1 -1
  38. package/dist/templates/infrastructure.d.ts.map +1 -1
  39. package/dist/templates/infrastructure.js +431 -27
  40. package/dist/templates/infrastructure.js.map +1 -1
  41. package/dist/templates/main-express.d.ts.map +1 -1
  42. package/dist/templates/main-express.js +41 -6
  43. package/dist/templates/main-express.js.map +1 -1
  44. package/dist/templates/main-fastify.d.ts +3 -0
  45. package/dist/templates/main-fastify.d.ts.map +1 -0
  46. package/dist/templates/main-fastify.js +73 -0
  47. package/dist/templates/main-fastify.js.map +1 -0
  48. package/dist/templates/main-hono.d.ts.map +1 -1
  49. package/dist/templates/main-hono.js +45 -9
  50. package/dist/templates/main-hono.js.map +1 -1
  51. package/dist/templates/package-json.d.ts.map +1 -1
  52. package/dist/templates/package-json.js +67 -3
  53. package/dist/templates/package-json.js.map +1 -1
  54. package/dist/templates/test-setup.d.ts +1 -1
  55. package/dist/templates/test-setup.d.ts.map +1 -1
  56. package/dist/templates/test-setup.js +10 -39
  57. package/dist/templates/test-setup.js.map +1 -1
  58. package/package.json +8 -7
@@ -0,0 +1,683 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ const KNOWN_ADAPTERS = {
5
+ pg: {
6
+ displayName: 'PostgreSQL (pg)',
7
+ subpath: './pg',
8
+ factoryName: 'createPgClient',
9
+ appOption: 'db',
10
+ envVars: {
11
+ DATABASE_URL: 'postgres://user:password@localhost:5432/mydb',
12
+ },
13
+ peerDep: 'pg',
14
+ importExample: `import { createPgClient } from '@objectifthunes/sandstone-sdk/pg';`,
15
+ wiringExample: ` db: createPgClient({ connectionString: process.env.DATABASE_URL! }),`,
16
+ },
17
+ supabase: {
18
+ displayName: 'Supabase',
19
+ subpath: './supabase',
20
+ factoryName: 'createSupabaseClient',
21
+ appOption: 'db',
22
+ envVars: {
23
+ DATABASE_URL: 'postgres://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres',
24
+ },
25
+ peerDep: 'pg',
26
+ importExample: `import { createSupabaseClient } from '@objectifthunes/sandstone-sdk/supabase';`,
27
+ wiringExample: ` db: createSupabaseClient({ connectionString: process.env.DATABASE_URL! }),`,
28
+ },
29
+ jose: {
30
+ displayName: 'Jose (JWT)',
31
+ subpath: './jose',
32
+ factoryName: 'createJoseSigner',
33
+ appOption: 'tokens',
34
+ envVars: {
35
+ JWT_SECRET: 'your-secret-key-at-least-32-characters-long',
36
+ },
37
+ peerDep: 'jose',
38
+ importExample: `import { createJoseSigner } from '@objectifthunes/sandstone-sdk/jose';`,
39
+ wiringExample: ` tokens: createJoseSigner({ secret: process.env.JWT_SECRET! }),`,
40
+ },
41
+ pino: {
42
+ displayName: 'Pino (Logger)',
43
+ subpath: './pino',
44
+ factoryName: 'createPinoLogger',
45
+ appOption: 'logger',
46
+ envVars: {
47
+ LOG_LEVEL: 'info',
48
+ },
49
+ peerDep: 'pino',
50
+ importExample: `import { createPinoLogger } from '@objectifthunes/sandstone-sdk/pino';`,
51
+ wiringExample: ` logger: createPinoLogger({ level: process.env.LOG_LEVEL ?? 'info' }),`,
52
+ },
53
+ resend: {
54
+ displayName: 'Resend (Email)',
55
+ subpath: './resend',
56
+ factoryName: 'createResendTransport',
57
+ appOption: 'email',
58
+ envVars: {
59
+ RESEND_API_KEY: 're_xxxxxxxxxx',
60
+ EMAIL_FROM: 'noreply@yourdomain.com',
61
+ },
62
+ peerDep: 'resend',
63
+ importExample: `import { createResendTransport } from '@objectifthunes/sandstone-sdk/resend';`,
64
+ wiringExample: ` email: {
65
+ transport: createResendTransport({ apiKey: process.env.RESEND_API_KEY! }),
66
+ defaultFrom: process.env.EMAIL_FROM!,
67
+ },`,
68
+ },
69
+ nodemailer: {
70
+ displayName: 'Nodemailer (SMTP)',
71
+ subpath: './nodemailer',
72
+ factoryName: 'createNodemailerTransport',
73
+ appOption: 'email',
74
+ envVars: {
75
+ SMTP_HOST: 'smtp.example.com',
76
+ SMTP_PORT: '587',
77
+ SMTP_USER: 'your-smtp-user',
78
+ SMTP_PASS: 'your-smtp-password',
79
+ EMAIL_FROM: 'noreply@yourdomain.com',
80
+ },
81
+ peerDep: 'nodemailer',
82
+ importExample: `import { createNodemailerTransport } from '@objectifthunes/sandstone-sdk/nodemailer';`,
83
+ wiringExample: ` email: {
84
+ transport: createNodemailerTransport({
85
+ host: process.env.SMTP_HOST!,
86
+ port: parseInt(process.env.SMTP_PORT!, 10),
87
+ auth: { user: process.env.SMTP_USER!, pass: process.env.SMTP_PASS! },
88
+ }),
89
+ defaultFrom: process.env.EMAIL_FROM!,
90
+ },`,
91
+ },
92
+ stripe: {
93
+ displayName: 'Stripe (Payments)',
94
+ subpath: './stripe',
95
+ factoryName: 'createStripeProvider',
96
+ appOption: 'payments',
97
+ envVars: {
98
+ STRIPE_SECRET_KEY: 'sk_test_xxxxxxxxxx',
99
+ STRIPE_WEBHOOK_SECRET: 'whsec_xxxxxxxxxx',
100
+ },
101
+ peerDep: 'stripe',
102
+ importExample: `import { createStripeProvider } from '@objectifthunes/sandstone-sdk/stripe';`,
103
+ wiringExample: ` payments: {
104
+ provider: createStripeProvider({
105
+ secretKey: process.env.STRIPE_SECRET_KEY!,
106
+ webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
107
+ }),
108
+ },`,
109
+ },
110
+ paddle: {
111
+ displayName: 'Paddle (Payments)',
112
+ subpath: './paddle',
113
+ factoryName: 'createPaddleProvider',
114
+ appOption: 'payments',
115
+ envVars: {
116
+ PADDLE_API_KEY: 'pdl_xxxxxxxxxx',
117
+ PADDLE_WEBHOOK_SECRET: 'pdl_ntfset_xxxxxxxxxx',
118
+ },
119
+ peerDep: null,
120
+ importExample: `import { createPaddleProvider } from '@objectifthunes/sandstone-sdk/paddle';`,
121
+ wiringExample: ` payments: {
122
+ provider: createPaddleProvider({
123
+ apiKey: process.env.PADDLE_API_KEY!,
124
+ webhookSecret: process.env.PADDLE_WEBHOOK_SECRET!,
125
+ }),
126
+ },`,
127
+ },
128
+ lemonsqueezy: {
129
+ displayName: 'Lemon Squeezy (Payments)',
130
+ subpath: './lemonsqueezy',
131
+ factoryName: 'createLemonSqueezyProvider',
132
+ appOption: 'payments',
133
+ envVars: {
134
+ LEMONSQUEEZY_API_KEY: 'your-api-key',
135
+ LEMONSQUEEZY_STORE_ID: 'your-store-id',
136
+ LEMONSQUEEZY_WEBHOOK_SECRET: 'your-webhook-secret',
137
+ },
138
+ peerDep: null,
139
+ importExample: `import { createLemonSqueezyProvider } from '@objectifthunes/sandstone-sdk/lemonsqueezy';`,
140
+ wiringExample: ` payments: {
141
+ provider: createLemonSqueezyProvider({
142
+ apiKey: process.env.LEMONSQUEEZY_API_KEY!,
143
+ storeId: process.env.LEMONSQUEEZY_STORE_ID!,
144
+ webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET!,
145
+ }),
146
+ },`,
147
+ },
148
+ revenuecat: {
149
+ displayName: 'RevenueCat (Payments)',
150
+ subpath: './revenuecat',
151
+ factoryName: 'createRevenueCatProvider',
152
+ appOption: 'payments',
153
+ envVars: {
154
+ REVENUECAT_API_KEY: 'your-api-key',
155
+ REVENUECAT_WEBHOOK_SECRET: 'your-webhook-secret',
156
+ },
157
+ peerDep: null,
158
+ importExample: `import { createRevenueCatProvider } from '@objectifthunes/sandstone-sdk/revenuecat';`,
159
+ wiringExample: ` payments: {
160
+ provider: createRevenueCatProvider({
161
+ apiKey: process.env.REVENUECAT_API_KEY!,
162
+ webhookSecret: process.env.REVENUECAT_WEBHOOK_SECRET!,
163
+ }),
164
+ },`,
165
+ },
166
+ s3: {
167
+ displayName: 'AWS S3 (Storage)',
168
+ subpath: './s3',
169
+ factoryName: 'createS3Storage',
170
+ appOption: 'storage',
171
+ envVars: {
172
+ S3_BUCKET: 'my-bucket',
173
+ S3_REGION: 'us-east-1',
174
+ AWS_ACCESS_KEY_ID: 'your-access-key',
175
+ AWS_SECRET_ACCESS_KEY: 'your-secret-key',
176
+ },
177
+ peerDep: '@aws-sdk/client-s3',
178
+ importExample: `import { createS3Storage } from '@objectifthunes/sandstone-sdk/s3';`,
179
+ wiringExample: ` storage: createS3Storage({
180
+ bucket: process.env.S3_BUCKET!,
181
+ region: process.env.S3_REGION!,
182
+ }),`,
183
+ },
184
+ r2: {
185
+ displayName: 'Cloudflare R2 (Storage)',
186
+ subpath: './r2',
187
+ factoryName: 'createR2Storage',
188
+ appOption: 'storage',
189
+ envVars: {
190
+ R2_BUCKET: 'my-bucket',
191
+ R2_ACCOUNT_ID: 'your-account-id',
192
+ R2_ACCESS_KEY_ID: 'your-access-key',
193
+ R2_SECRET_ACCESS_KEY: 'your-secret-key',
194
+ },
195
+ peerDep: '@aws-sdk/client-s3',
196
+ importExample: `import { createR2Storage } from '@objectifthunes/sandstone-sdk/r2';`,
197
+ wiringExample: ` storage: createR2Storage({
198
+ bucket: process.env.R2_BUCKET!,
199
+ accountId: process.env.R2_ACCOUNT_ID!,
200
+ }),`,
201
+ },
202
+ redis: {
203
+ displayName: 'Redis (Cache)',
204
+ subpath: './redis',
205
+ factoryName: 'createRedisCache',
206
+ appOption: 'cache',
207
+ envVars: {
208
+ REDIS_URL: 'redis://localhost:6379',
209
+ },
210
+ peerDep: 'ioredis',
211
+ importExample: `import { createRedisCache } from '@objectifthunes/sandstone-sdk/redis';`,
212
+ wiringExample: ` cache: createRedisCache({ url: process.env.REDIS_URL! }),`,
213
+ },
214
+ upstash: {
215
+ displayName: 'Upstash Redis (Cache)',
216
+ subpath: './upstash',
217
+ factoryName: 'createUpstashCache',
218
+ appOption: 'cache',
219
+ envVars: {
220
+ UPSTASH_REDIS_URL: 'https://xxxxxx.upstash.io',
221
+ UPSTASH_REDIS_TOKEN: 'your-token',
222
+ },
223
+ peerDep: '@upstash/redis',
224
+ importExample: `import { createUpstashCache } from '@objectifthunes/sandstone-sdk/upstash';`,
225
+ wiringExample: ` cache: createUpstashCache({
226
+ url: process.env.UPSTASH_REDIS_URL!,
227
+ token: process.env.UPSTASH_REDIS_TOKEN!,
228
+ }),`,
229
+ },
230
+ argon2: {
231
+ displayName: 'Argon2 (Password Hashing)',
232
+ subpath: './argon2',
233
+ factoryName: 'createArgon2Hasher',
234
+ appOption: 'auth (passwordHasher)',
235
+ envVars: {},
236
+ peerDep: 'argon2',
237
+ importExample: `import { createArgon2Hasher } from '@objectifthunes/sandstone-sdk/argon2';`,
238
+ wiringExample: ` auth: { passwordHasher: createArgon2Hasher() },`,
239
+ },
240
+ 'oauth-google': {
241
+ displayName: 'Google OAuth',
242
+ subpath: './oauth-google',
243
+ factoryName: 'createGoogleOAuthProvider',
244
+ appOption: 'auth (oauthProviders)',
245
+ envVars: {
246
+ GOOGLE_CLIENT_ID: 'your-client-id.apps.googleusercontent.com',
247
+ GOOGLE_CLIENT_SECRET: 'your-client-secret',
248
+ },
249
+ peerDep: null,
250
+ importExample: `import { createGoogleOAuthProvider } from '@objectifthunes/sandstone-sdk/oauth-google';`,
251
+ wiringExample: ` auth: {
252
+ oauthProviders: {
253
+ google: createGoogleOAuthProvider({
254
+ clientId: process.env.GOOGLE_CLIENT_ID!,
255
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
256
+ }),
257
+ },
258
+ },`,
259
+ },
260
+ 'oauth-github': {
261
+ displayName: 'GitHub OAuth',
262
+ subpath: './oauth-github',
263
+ factoryName: 'createGitHubOAuthProvider',
264
+ appOption: 'auth (oauthProviders)',
265
+ envVars: {
266
+ GITHUB_CLIENT_ID: 'your-client-id',
267
+ GITHUB_CLIENT_SECRET: 'your-client-secret',
268
+ },
269
+ peerDep: null,
270
+ importExample: `import { createGitHubOAuthProvider } from '@objectifthunes/sandstone-sdk/oauth-github';`,
271
+ wiringExample: ` auth: {
272
+ oauthProviders: {
273
+ github: createGitHubOAuthProvider({
274
+ clientId: process.env.GITHUB_CLIENT_ID!,
275
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
276
+ }),
277
+ },
278
+ },`,
279
+ },
280
+ 'oauth-apple': {
281
+ displayName: 'Apple OAuth',
282
+ subpath: './oauth-apple',
283
+ factoryName: 'createAppleOAuthProvider',
284
+ appOption: 'auth (oauthProviders)',
285
+ envVars: {
286
+ APPLE_CLIENT_ID: 'com.your.service-id',
287
+ APPLE_TEAM_ID: 'your-team-id',
288
+ APPLE_KEY_ID: 'your-key-id',
289
+ APPLE_PRIVATE_KEY: '-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----',
290
+ },
291
+ peerDep: null,
292
+ importExample: `import { createAppleOAuthProvider } from '@objectifthunes/sandstone-sdk/oauth-apple';`,
293
+ wiringExample: ` auth: {
294
+ oauthProviders: {
295
+ apple: createAppleOAuthProvider({
296
+ clientId: process.env.APPLE_CLIENT_ID!,
297
+ teamId: process.env.APPLE_TEAM_ID!,
298
+ keyId: process.env.APPLE_KEY_ID!,
299
+ privateKey: process.env.APPLE_PRIVATE_KEY!,
300
+ }),
301
+ },
302
+ },`,
303
+ },
304
+ 'oauth-discord': {
305
+ displayName: 'Discord OAuth',
306
+ subpath: './oauth-discord',
307
+ factoryName: 'createDiscordOAuthProvider',
308
+ appOption: 'auth (oauthProviders)',
309
+ envVars: {
310
+ DISCORD_CLIENT_ID: 'your-client-id',
311
+ DISCORD_CLIENT_SECRET: 'your-client-secret',
312
+ },
313
+ peerDep: null,
314
+ importExample: `import { createDiscordOAuthProvider } from '@objectifthunes/sandstone-sdk/oauth-discord';`,
315
+ wiringExample: ` auth: {
316
+ oauthProviders: {
317
+ discord: createDiscordOAuthProvider({
318
+ clientId: process.env.DISCORD_CLIENT_ID!,
319
+ clientSecret: process.env.DISCORD_CLIENT_SECRET!,
320
+ }),
321
+ },
322
+ },`,
323
+ },
324
+ otel: {
325
+ displayName: 'OpenTelemetry (Tracing)',
326
+ subpath: './otel',
327
+ factoryName: 'createOtelTracer',
328
+ appOption: 'tracer',
329
+ envVars: {
330
+ OTEL_SERVICE_NAME: 'my-service',
331
+ },
332
+ peerDep: '@opentelemetry/api',
333
+ importExample: `import { createOtelTracer } from '@objectifthunes/sandstone-sdk/otel';`,
334
+ wiringExample: ` tracer: createOtelTracer({ serviceName: process.env.OTEL_SERVICE_NAME ?? 'my-service' }),`,
335
+ },
336
+ meilisearch: {
337
+ displayName: 'Meilisearch (Search)',
338
+ subpath: './meilisearch',
339
+ factoryName: 'createMeilisearchSearch',
340
+ appOption: 'search',
341
+ envVars: {
342
+ MEILISEARCH_HOST: 'http://localhost:7700',
343
+ MEILISEARCH_API_KEY: 'your-master-key',
344
+ },
345
+ peerDep: 'meilisearch',
346
+ importExample: `import { createMeilisearchSearch } from '@objectifthunes/sandstone-sdk/meilisearch';`,
347
+ wiringExample: ` search: createMeilisearchSearch({
348
+ host: process.env.MEILISEARCH_HOST!,
349
+ apiKey: process.env.MEILISEARCH_API_KEY!,
350
+ }),`,
351
+ },
352
+ typesense: {
353
+ displayName: 'Typesense (Search)',
354
+ subpath: './typesense',
355
+ factoryName: 'createTypesenseSearch',
356
+ appOption: 'search',
357
+ envVars: {
358
+ TYPESENSE_HOST: 'localhost',
359
+ TYPESENSE_PORT: '8108',
360
+ TYPESENSE_API_KEY: 'your-api-key',
361
+ },
362
+ peerDep: 'typesense',
363
+ importExample: `import { createTypesenseSearch } from '@objectifthunes/sandstone-sdk/typesense';`,
364
+ wiringExample: ` search: createTypesenseSearch({
365
+ host: process.env.TYPESENSE_HOST!,
366
+ port: parseInt(process.env.TYPESENSE_PORT!, 10),
367
+ apiKey: process.env.TYPESENSE_API_KEY!,
368
+ }),`,
369
+ },
370
+ bullmq: {
371
+ displayName: 'BullMQ (Queue)',
372
+ subpath: './bullmq',
373
+ factoryName: 'createBullMQQueue',
374
+ appOption: 'queue',
375
+ envVars: {
376
+ REDIS_URL: 'redis://localhost:6379',
377
+ },
378
+ peerDep: 'bullmq',
379
+ importExample: `import { createBullMQQueue } from '@objectifthunes/sandstone-sdk/bullmq';`,
380
+ wiringExample: ` queue: createBullMQQueue({ connection: { url: process.env.REDIS_URL! } }),`,
381
+ },
382
+ 'pg-boss': {
383
+ displayName: 'pg-boss (Queue)',
384
+ subpath: './pg-boss',
385
+ factoryName: 'createPgBossQueue',
386
+ appOption: 'queue',
387
+ envVars: {
388
+ DATABASE_URL: 'postgres://user:password@localhost:5432/mydb',
389
+ },
390
+ peerDep: 'pg-boss',
391
+ importExample: `import { createPgBossQueue } from '@objectifthunes/sandstone-sdk/pg-boss';`,
392
+ wiringExample: ` queue: createPgBossQueue({ connectionString: process.env.DATABASE_URL! }),`,
393
+ },
394
+ 'pg-flags': {
395
+ displayName: 'PostgreSQL Feature Flags',
396
+ subpath: './pg-flags',
397
+ factoryName: 'createPgFeatureFlags',
398
+ appOption: 'flags',
399
+ envVars: {},
400
+ peerDep: 'pg',
401
+ importExample: `import { createPgFeatureFlags } from '@objectifthunes/sandstone-sdk/pg-flags';`,
402
+ wiringExample: ` flags: createPgFeatureFlags({ db }), // pass the DatabaseClient instance`,
403
+ },
404
+ 'env-flags': {
405
+ displayName: 'Environment Variable Feature Flags',
406
+ subpath: './env-flags',
407
+ factoryName: 'createEnvFeatureFlags',
408
+ appOption: 'flags',
409
+ envVars: {},
410
+ peerDep: null,
411
+ importExample: `import { createEnvFeatureFlags } from '@objectifthunes/sandstone-sdk/env-flags';`,
412
+ wiringExample: ` flags: createEnvFeatureFlags({ prefix: 'FLAG_' }),`,
413
+ },
414
+ launchdarkly: {
415
+ displayName: 'LaunchDarkly (Feature Flags)',
416
+ subpath: './launchdarkly',
417
+ factoryName: 'createLaunchDarklyFlags',
418
+ appOption: 'flags',
419
+ envVars: {
420
+ LAUNCHDARKLY_SDK_KEY: 'sdk-xxxxxxxxxx',
421
+ },
422
+ peerDep: '@launchdarkly/node-server-sdk',
423
+ importExample: `import { createLaunchDarklyFlags } from '@objectifthunes/sandstone-sdk/launchdarkly';`,
424
+ wiringExample: ` flags: createLaunchDarklyFlags({ sdkKey: process.env.LAUNCHDARKLY_SDK_KEY! }),`,
425
+ },
426
+ 'pg-authz': {
427
+ displayName: 'PostgreSQL Authorization',
428
+ subpath: './pg-authz',
429
+ factoryName: 'createPgAuthzProvider',
430
+ appOption: 'authz',
431
+ envVars: {},
432
+ peerDep: 'pg',
433
+ importExample: `import { createPgAuthzProvider } from '@objectifthunes/sandstone-sdk/pg-authz';`,
434
+ wiringExample: ` authz: createPgAuthzProvider({ db }), // pass the DatabaseClient instance`,
435
+ },
436
+ casbin: {
437
+ displayName: 'Casbin (Authorization)',
438
+ subpath: './casbin',
439
+ factoryName: 'createCasbinAuthzProvider',
440
+ appOption: 'authz',
441
+ envVars: {},
442
+ peerDep: 'casbin',
443
+ importExample: `import { createCasbinAuthzProvider } from '@objectifthunes/sandstone-sdk/casbin';`,
444
+ wiringExample: ` authz: createCasbinAuthzProvider({ modelPath: './model.conf', policyPath: './policy.csv' }),`,
445
+ },
446
+ twilio: {
447
+ displayName: 'Twilio (SMS)',
448
+ subpath: './twilio',
449
+ factoryName: 'createTwilioSmsTransport',
450
+ appOption: 'sms',
451
+ envVars: {
452
+ TWILIO_ACCOUNT_SID: 'ACxxxxxxxxxx',
453
+ TWILIO_AUTH_TOKEN: 'your-auth-token',
454
+ TWILIO_FROM_NUMBER: '+15551234567',
455
+ },
456
+ peerDep: null,
457
+ importExample: `import { createTwilioSmsTransport } from '@objectifthunes/sandstone-sdk/twilio';`,
458
+ wiringExample: ` sms: createTwilioSmsTransport({
459
+ accountSid: process.env.TWILIO_ACCOUNT_SID!,
460
+ authToken: process.env.TWILIO_AUTH_TOKEN!,
461
+ from: process.env.TWILIO_FROM_NUMBER!,
462
+ }),`,
463
+ },
464
+ sns: {
465
+ displayName: 'AWS SNS (SMS)',
466
+ subpath: './sns',
467
+ factoryName: 'createSnsSmsTransport',
468
+ appOption: 'sms',
469
+ envVars: {
470
+ AWS_REGION: 'us-east-1',
471
+ AWS_ACCESS_KEY_ID: 'your-access-key',
472
+ AWS_SECRET_ACCESS_KEY: 'your-secret-key',
473
+ },
474
+ peerDep: '@aws-sdk/client-sns',
475
+ importExample: `import { createSnsSmsTransport } from '@objectifthunes/sandstone-sdk/sns';`,
476
+ wiringExample: ` sms: createSnsSmsTransport({ region: process.env.AWS_REGION! }),`,
477
+ },
478
+ fcm: {
479
+ displayName: 'Firebase Cloud Messaging (Push)',
480
+ subpath: './fcm',
481
+ factoryName: 'createFcmPushTransport',
482
+ appOption: 'push',
483
+ envVars: {
484
+ FCM_PROJECT_ID: 'your-project-id',
485
+ FCM_CLIENT_EMAIL: 'firebase-adminsdk@your-project.iam.gserviceaccount.com',
486
+ FCM_PRIVATE_KEY: '-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----',
487
+ },
488
+ peerDep: null,
489
+ importExample: `import { createFcmPushTransport } from '@objectifthunes/sandstone-sdk/fcm';`,
490
+ wiringExample: ` push: createFcmPushTransport({
491
+ projectId: process.env.FCM_PROJECT_ID!,
492
+ clientEmail: process.env.FCM_CLIENT_EMAIL!,
493
+ privateKey: process.env.FCM_PRIVATE_KEY!,
494
+ }),`,
495
+ },
496
+ apns: {
497
+ displayName: 'Apple Push Notification (Push)',
498
+ subpath: './apns',
499
+ factoryName: 'createApnsPushTransport',
500
+ appOption: 'push',
501
+ envVars: {
502
+ APNS_KEY_ID: 'your-key-id',
503
+ APNS_TEAM_ID: 'your-team-id',
504
+ APNS_PRIVATE_KEY: '-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----',
505
+ APNS_BUNDLE_ID: 'com.your.app',
506
+ },
507
+ peerDep: null,
508
+ importExample: `import { createApnsPushTransport } from '@objectifthunes/sandstone-sdk/apns';`,
509
+ wiringExample: ` push: createApnsPushTransport({
510
+ keyId: process.env.APNS_KEY_ID!,
511
+ teamId: process.env.APNS_TEAM_ID!,
512
+ privateKey: process.env.APNS_PRIVATE_KEY!,
513
+ bundleId: process.env.APNS_BUNDLE_ID!,
514
+ }),`,
515
+ },
516
+ socketio: {
517
+ displayName: 'Socket.io (Realtime)',
518
+ subpath: './socketio',
519
+ factoryName: 'createSocketIoRealtime',
520
+ appOption: 'realtime',
521
+ envVars: {},
522
+ peerDep: 'socket.io',
523
+ importExample: `import { createSocketIoRealtime } from '@objectifthunes/sandstone-sdk/socketio';`,
524
+ wiringExample: ` realtime: createSocketIoRealtime({ server: httpServer }),`,
525
+ },
526
+ pusher: {
527
+ displayName: 'Pusher (Realtime)',
528
+ subpath: './pusher',
529
+ factoryName: 'createPusherRealtime',
530
+ appOption: 'realtime',
531
+ envVars: {
532
+ PUSHER_APP_ID: 'your-app-id',
533
+ PUSHER_KEY: 'your-key',
534
+ PUSHER_SECRET: 'your-secret',
535
+ PUSHER_CLUSTER: 'us2',
536
+ },
537
+ peerDep: null,
538
+ importExample: `import { createPusherRealtime } from '@objectifthunes/sandstone-sdk/pusher';`,
539
+ wiringExample: ` realtime: createPusherRealtime({
540
+ appId: process.env.PUSHER_APP_ID!,
541
+ key: process.env.PUSHER_KEY!,
542
+ secret: process.env.PUSHER_SECRET!,
543
+ cluster: process.env.PUSHER_CLUSTER!,
544
+ }),`,
545
+ },
546
+ ably: {
547
+ displayName: 'Ably (Realtime)',
548
+ subpath: './ably',
549
+ factoryName: 'createAblyRealtime',
550
+ appOption: 'realtime',
551
+ envVars: {
552
+ ABLY_API_KEY: 'your-api-key',
553
+ },
554
+ peerDep: null,
555
+ importExample: `import { createAblyRealtime } from '@objectifthunes/sandstone-sdk/ably';`,
556
+ wiringExample: ` realtime: createAblyRealtime({ apiKey: process.env.ABLY_API_KEY! }),`,
557
+ },
558
+ 'pg-search': {
559
+ displayName: 'PostgreSQL Full-Text Search',
560
+ subpath: './pg-search',
561
+ factoryName: 'createPgSearch',
562
+ appOption: 'search',
563
+ envVars: {},
564
+ peerDep: 'pg',
565
+ importExample: `import { createPgSearch } from '@objectifthunes/sandstone-sdk/pg-search';`,
566
+ wiringExample: ` search: createPgSearch({ db }), // pass the DatabaseClient instance`,
567
+ },
568
+ 'node-cron': {
569
+ displayName: 'node-cron (Scheduler Tick Source)',
570
+ subpath: './node-cron',
571
+ factoryName: 'createNodeCronTickSource',
572
+ appOption: 'scheduler (tickSource)',
573
+ envVars: {},
574
+ peerDep: 'node-cron',
575
+ importExample: `import { createNodeCronTickSource } from '@objectifthunes/sandstone-sdk/node-cron';`,
576
+ wiringExample: ` scheduler: { tickSource: createNodeCronTickSource() },`,
577
+ },
578
+ 'local-storage': {
579
+ displayName: 'Local Filesystem (Storage)',
580
+ subpath: './local-storage',
581
+ factoryName: 'createLocalStorage',
582
+ appOption: 'storage',
583
+ envVars: {
584
+ STORAGE_DIR: './uploads',
585
+ },
586
+ peerDep: null,
587
+ importExample: `import { createLocalStorage } from '@objectifthunes/sandstone-sdk/local-storage';`,
588
+ wiringExample: ` storage: createLocalStorage({ directory: process.env.STORAGE_DIR ?? './uploads' }),`,
589
+ },
590
+ };
591
+ function parseArgs(args) {
592
+ const name = args.find((a) => !a.startsWith('-'));
593
+ if (!name) {
594
+ console.error('Error: Adapter name is required.');
595
+ console.error('Usage: sandstone add adapter <name>');
596
+ console.error();
597
+ console.error('Available adapters:');
598
+ const sorted = Object.entries(KNOWN_ADAPTERS).sort(([a], [b]) => a.localeCompare(b));
599
+ for (const [key, info] of sorted) {
600
+ console.error(` ${key.padEnd(18)} ${info.displayName}`);
601
+ }
602
+ process.exit(1);
603
+ }
604
+ return { name };
605
+ }
606
+ async function updateEnvExample(envVars) {
607
+ const envPath = join(process.cwd(), '.env.example');
608
+ if (!existsSync(envPath)) {
609
+ return false;
610
+ }
611
+ const content = await readFile(envPath, 'utf-8');
612
+ const lines = content.split('\n');
613
+ const existingKeys = new Set(lines
614
+ .filter((l) => l.includes('=') && !l.startsWith('#'))
615
+ .map((l) => l.split('=')[0].trim()));
616
+ const newVars = [];
617
+ for (const [key, value] of Object.entries(envVars)) {
618
+ if (!existingKeys.has(key)) {
619
+ newVars.push(`${key}=${value}`);
620
+ }
621
+ }
622
+ if (newVars.length === 0) {
623
+ return false;
624
+ }
625
+ const updatedContent = content.trimEnd() + '\n\n# Added by: sandstone add adapter\n' + newVars.join('\n') + '\n';
626
+ await writeFile(envPath, updatedContent, 'utf-8');
627
+ return true;
628
+ }
629
+ export async function addAdapter(args) {
630
+ const { name } = parseArgs(args);
631
+ const adapter = KNOWN_ADAPTERS[name];
632
+ if (!adapter) {
633
+ console.error(`\n Unknown adapter: "${name}"\n`);
634
+ console.error(' Available adapters:');
635
+ const sorted = Object.entries(KNOWN_ADAPTERS).sort(([a], [b]) => a.localeCompare(b));
636
+ for (const [key, info] of sorted) {
637
+ console.error(` ${key.padEnd(18)} ${info.displayName}`);
638
+ }
639
+ console.error();
640
+ console.error(' To create a custom adapter, use: sandstone generate adapter <name> --port <PortName>');
641
+ process.exit(1);
642
+ }
643
+ // Update .env.example if it exists
644
+ const envVarEntries = Object.entries(adapter.envVars);
645
+ let envUpdated = false;
646
+ if (envVarEntries.length > 0) {
647
+ envUpdated = await updateEnvExample(adapter.envVars);
648
+ }
649
+ // Print wiring instructions
650
+ console.log(`\n Add adapter: ${adapter.displayName}\n`);
651
+ if (adapter.peerDep) {
652
+ console.log(` 1. Install peer dependency:\n`);
653
+ console.log(` npm install ${adapter.peerDep}\n`);
654
+ }
655
+ else {
656
+ console.log(` 1. No peer dependency needed (uses fetch or built-in modules).\n`);
657
+ }
658
+ console.log(` 2. Import in src/infrastructure.ts:\n`);
659
+ console.log(` ${adapter.importExample}\n`);
660
+ console.log(` 3. Add to createApp() options:\n`);
661
+ console.log(adapter.wiringExample
662
+ .split('\n')
663
+ .map((line) => ` ${line}`)
664
+ .join('\n'));
665
+ console.log();
666
+ if (envVarEntries.length > 0) {
667
+ console.log(` 4. Environment variables needed:\n`);
668
+ for (const [key, value] of envVarEntries) {
669
+ console.log(` ${key}=${value}`);
670
+ }
671
+ console.log();
672
+ if (envUpdated) {
673
+ console.log(` .env.example has been updated with missing variables.\n`);
674
+ }
675
+ else if (existsSync(join(process.cwd(), '.env.example'))) {
676
+ console.log(` All environment variables already present in .env.example.\n`);
677
+ }
678
+ else {
679
+ console.log(` No .env.example found. Add these variables to your .env file manually.\n`);
680
+ }
681
+ }
682
+ }
683
+ //# sourceMappingURL=add-adapter.js.map