create-sprint 0.0.138 → 0.0.139
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/dist/index.js +110 -54
- package/dist/templates/configFiles.js +39 -49
- package/dist/templates/index.js +2 -0
- package/dist/templates/packageJson.js +22 -4
- package/dist/templates/routes.js +34 -25
- package/dist/templates/services.js +262 -0
- package/package.json +1 -1
- package/src/index.ts +142 -89
- package/src/templates/configFiles.ts +50 -51
- package/src/templates/index.ts +3 -0
- package/src/templates/packageJson.ts +21 -6
- package/src/templates/routes.ts +46 -23
- package/src/templates/services.ts +270 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ProjectFeatures } from "../index.js";
|
|
2
|
+
|
|
1
3
|
export function getTsConfig() {
|
|
2
4
|
return `
|
|
3
5
|
{
|
|
@@ -28,59 +30,61 @@ export function getTsConfig() {
|
|
|
28
30
|
`;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
function buildCorsBlock(corsInput: string): string {
|
|
34
|
+
const trimmed = (corsInput ?? "").trim();
|
|
35
|
+
if (trimmed === "" || trimmed.toLowerCase() === "deny" || trimmed.toLowerCase() === "none") {
|
|
36
|
+
return ` cors: false,`;
|
|
37
|
+
}
|
|
38
|
+
if (trimmed === "*") {
|
|
39
|
+
return ` cors: { origin: "*" },`;
|
|
40
|
+
}
|
|
41
|
+
const origins = trimmed.split(",").map(o => o.trim()).filter(Boolean);
|
|
42
|
+
return ` cors: {
|
|
43
|
+
origin: ${JSON.stringify(origins)},
|
|
44
|
+
credentials: true,
|
|
45
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
46
|
+
allowedHeaders: ["Content-Type", "Authorization"]
|
|
47
|
+
},`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSprintConfigFile(
|
|
51
|
+
language: string,
|
|
52
|
+
telemetry: string,
|
|
53
|
+
swagger: boolean,
|
|
54
|
+
graphql: boolean,
|
|
55
|
+
features?: Partial<ProjectFeatures>
|
|
56
|
+
): string {
|
|
57
|
+
const corsInput = features?.cors ?? "";
|
|
32
58
|
const swaggerEnabled = swagger ? "true" : "false";
|
|
33
59
|
const swaggerUiEnabled = swagger ? '["development"]' : "false";
|
|
34
60
|
const graphqlEnabled = graphql ? "true" : "false";
|
|
35
61
|
const graphiqlEnabled = graphql ? '["development"]' : "false";
|
|
36
|
-
|
|
37
|
-
if (language === "typescript") {
|
|
38
|
-
let config = `import type { SprintOptions } from "sprint-es";
|
|
39
62
|
|
|
40
|
-
|
|
41
|
-
openapi: {
|
|
42
|
-
enabled: ${swaggerEnabled},
|
|
43
|
-
generateOnBuild: ${swaggerEnabled},
|
|
44
|
-
swaggerUi: {
|
|
45
|
-
enabled: ${swaggerUiEnabled}
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
graphql: {
|
|
49
|
-
enabled: ${graphqlEnabled},
|
|
50
|
-
graphiql: {
|
|
51
|
-
enabled: ${graphiqlEnabled}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
63
|
+
const corsBlock = buildCorsBlock(corsInput);
|
|
55
64
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
`;
|
|
65
|
+
const isTs = language === "typescript";
|
|
66
|
+
const importLine = isTs ? `import type { SprintOptions } from "sprint-es";\n\n` : "";
|
|
67
|
+
const exportLine = isTs ? `export const config: SprintOptions = {` : `export const config = {`;
|
|
60
68
|
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
let config = `${importLine}${exportLine}
|
|
70
|
+
${corsBlock}
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
environment: process.env.NODE_ENV || "development"
|
|
68
|
-
});
|
|
69
|
-
`;
|
|
70
|
-
} else if (telemetry === "discord") {
|
|
71
|
-
config += `import { initTelemetry } from "sprint-es/telemetry";
|
|
72
|
+
security: {
|
|
73
|
+
hsts: { maxAge: 63072000, includeSubDomains: true }
|
|
74
|
+
},
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
`;
|
|
78
|
-
}
|
|
76
|
+
context: {
|
|
77
|
+
trustIncomingRequestId: true
|
|
78
|
+
},
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
errorHandler: {
|
|
81
|
+
includeStack: process.env.NODE_ENV !== "production"
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
shutdown: {
|
|
85
|
+
timeoutMs: 30_000
|
|
86
|
+
},
|
|
82
87
|
|
|
83
|
-
let config = `export const config = {
|
|
84
88
|
openapi: {
|
|
85
89
|
enabled: ${swaggerEnabled},
|
|
86
90
|
generateOnBuild: ${swaggerEnabled},
|
|
@@ -88,6 +92,7 @@ initTelemetry({
|
|
|
88
92
|
enabled: ${swaggerUiEnabled}
|
|
89
93
|
}
|
|
90
94
|
},
|
|
95
|
+
|
|
91
96
|
graphql: {
|
|
92
97
|
enabled: ${graphqlEnabled},
|
|
93
98
|
graphiql: {
|
|
@@ -95,15 +100,10 @@ initTelemetry({
|
|
|
95
100
|
}
|
|
96
101
|
}
|
|
97
102
|
};
|
|
98
|
-
|
|
99
|
-
// To use GraphQL, create a schema at src/graphql/schema.js and import it here
|
|
100
|
-
// import { GraphQLSchema } from "graphql";
|
|
101
|
-
// export const graphqlSchema = new GraphQLSchema({ ... });
|
|
102
103
|
`;
|
|
103
104
|
|
|
104
105
|
if (telemetry === "sentry" || telemetry === "glitchtip") {
|
|
105
|
-
config +=
|
|
106
|
-
import { initTelemetry } from "sprint-es/telemetry";
|
|
106
|
+
config += `\nimport { initTelemetry } from "sprint-es/telemetry";
|
|
107
107
|
|
|
108
108
|
initTelemetry({
|
|
109
109
|
provider: "${telemetry}",
|
|
@@ -112,8 +112,7 @@ initTelemetry({
|
|
|
112
112
|
});
|
|
113
113
|
`;
|
|
114
114
|
} else if (telemetry === "discord") {
|
|
115
|
-
config +=
|
|
116
|
-
import { initTelemetry } from "sprint-es/telemetry";
|
|
115
|
+
config += `\nimport { initTelemetry } from "sprint-es/telemetry";
|
|
117
116
|
|
|
118
117
|
initTelemetry({
|
|
119
118
|
provider: "discord",
|
|
@@ -123,4 +122,4 @@ initTelemetry({
|
|
|
123
122
|
}
|
|
124
123
|
|
|
125
124
|
return config;
|
|
126
|
-
};
|
|
125
|
+
};
|
package/src/templates/index.ts
CHANGED
|
@@ -28,5 +28,8 @@ export { getDockerfile, getDockerCompose } from "./docker.js";
|
|
|
28
28
|
// GraphQL
|
|
29
29
|
export { getGraphQLFiles } from "./graphql.js";
|
|
30
30
|
|
|
31
|
+
// Services (queue, cache, trpc, grpc, websocket scaffolds)
|
|
32
|
+
export { getQueueService, getCacheService, getTrpcRouter, getGrpcServer, getWebSocketScaffold } from "./services.js";
|
|
33
|
+
|
|
31
34
|
// Misc
|
|
32
35
|
export { getGitignore, getDockerIgnore } from "./misc.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as crypto from "node:crypto";
|
|
2
|
+
import type { ProjectFeatures } from "../index.js";
|
|
2
3
|
|
|
3
4
|
export interface JWTKeys {
|
|
4
5
|
publicKey: string;
|
|
@@ -14,9 +15,18 @@ export function generateJWTKeys(): JWTKeys {
|
|
|
14
15
|
return keys;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
function applyFeatureDeps(deps: Record<string, string>, features?: Partial<ProjectFeatures>): void {
|
|
19
|
+
if (!features) return;
|
|
20
|
+
if (features.queue === "bullmq") deps["bullmq"] = "^5.0.0";
|
|
21
|
+
if (features.queue === "bullmq" || features.cache === "redis") deps["ioredis"] = "^5.0.0";
|
|
22
|
+
if (features.websocket) deps["ws"] = "^8.18.0";
|
|
23
|
+
if (features.trpc) deps["@trpc/server"] = "^11.0.0";
|
|
24
|
+
if (features.grpc) deps["@grpc/grpc-js"] = "^1.12.0";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getTypeScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean, features?: Partial<ProjectFeatures>) {
|
|
18
28
|
const deps: Record<string, string> = {
|
|
19
|
-
"sprint-es": "^0.0
|
|
29
|
+
"sprint-es": "^1.0.0"
|
|
20
30
|
};
|
|
21
31
|
|
|
22
32
|
const devDeps: Record<string, string> = {
|
|
@@ -41,6 +51,9 @@ export function getTypeScriptPackageJson(name: string, telemetry: string, swagge
|
|
|
41
51
|
devDeps["@types/swagger-ui-express"] = "^4.1.8";
|
|
42
52
|
}
|
|
43
53
|
|
|
54
|
+
applyFeatureDeps(deps, features);
|
|
55
|
+
if (features?.websocket) devDeps["@types/ws"] = "^8.5.10";
|
|
56
|
+
|
|
44
57
|
return {
|
|
45
58
|
name: name === "." ? "sprint-app" : name,
|
|
46
59
|
version: "0.0.1",
|
|
@@ -74,14 +87,14 @@ export function getTypeScriptPackageJson(name: string, telemetry: string, swagge
|
|
|
74
87
|
};
|
|
75
88
|
};
|
|
76
89
|
|
|
77
|
-
export function getJavaScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean) {
|
|
90
|
+
export function getJavaScriptPackageJson(name: string, telemetry: string, swagger: boolean, graphql: boolean, features?: Partial<ProjectFeatures>) {
|
|
78
91
|
const deps: Record<string, string> = {
|
|
79
|
-
"sprint-es": "^0.0
|
|
92
|
+
"sprint-es": "^1.0.0"
|
|
80
93
|
};
|
|
81
94
|
|
|
82
95
|
if (telemetry === "sentry" || telemetry === "glitchtip") deps["@sentry/node"] = "^8.0.0";
|
|
83
96
|
else if (telemetry === "discord") deps["axios"] = "^1.6.0";
|
|
84
|
-
|
|
97
|
+
|
|
85
98
|
if (swagger) deps["swagger-ui-express"] = "^5.0.0";
|
|
86
99
|
|
|
87
100
|
if (graphql) {
|
|
@@ -90,6 +103,8 @@ export function getJavaScriptPackageJson(name: string, telemetry: string, swagge
|
|
|
90
103
|
deps["ruru"] = "^2.0.0-rc.6";
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
applyFeatureDeps(deps, features);
|
|
107
|
+
|
|
93
108
|
return {
|
|
94
109
|
name: name === "." ? "sprint-app" : name,
|
|
95
110
|
version: "0.0.1",
|
|
@@ -105,4 +120,4 @@ export function getJavaScriptPackageJson(name: string, telemetry: string, swagge
|
|
|
105
120
|
},
|
|
106
121
|
dependencies: deps
|
|
107
122
|
};
|
|
108
|
-
};
|
|
123
|
+
};
|
package/src/templates/routes.ts
CHANGED
|
@@ -1,34 +1,57 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface MainFileFeatures {
|
|
2
|
+
queue?: "none" | "memory" | "bullmq";
|
|
3
|
+
cache?: "none" | "memory" | "redis";
|
|
4
|
+
websocket?: boolean;
|
|
5
|
+
trpc?: boolean;
|
|
6
|
+
grpc?: boolean;
|
|
7
|
+
csrf?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getMainFile(language: string, graphql: boolean = false, features?: MainFileFeatures): string {
|
|
2
11
|
const isTs = language === "typescript";
|
|
3
|
-
|
|
4
|
-
if (isTs) {
|
|
5
|
-
if (graphql) {
|
|
6
|
-
return `import Sprint from "sprint-es";
|
|
7
|
-
import { graphqlSchema } from "./graphql/schema";
|
|
8
|
-
|
|
9
|
-
const app = new Sprint();
|
|
10
|
-
app.setGraphQLSchema(graphqlSchema);
|
|
11
|
-
`;
|
|
12
|
-
}
|
|
13
|
-
return `import Sprint from "sprint-es";
|
|
12
|
+
const ext = isTs ? "" : ".js";
|
|
14
13
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
}
|
|
14
|
+
const imports: string[] = [`import Sprint from "sprint-es";`];
|
|
15
|
+
const setup: string[] = [`const app = new Sprint();`];
|
|
18
16
|
|
|
19
17
|
if (graphql) {
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
imports.push(`import { graphqlSchema } from "./graphql/schema${ext}";`);
|
|
19
|
+
setup.push(`app.setGraphQLSchema(graphqlSchema);`);
|
|
20
|
+
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
if (features?.queue && features.queue !== "none") {
|
|
23
|
+
imports.push(`import "./services/queue${ext}";`);
|
|
24
|
+
}
|
|
25
|
+
if (features?.cache && features.cache !== "none") {
|
|
26
|
+
imports.push(`import "./services/cache${ext}";`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (features?.csrf) {
|
|
30
|
+
// CSRF is registered via defineMiddleware in src/middlewares/csrf.ts; just hint here
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
if (features?.trpc) {
|
|
34
|
+
imports.push(`import { attachTrpc } from "sprint-es/trpc";`);
|
|
35
|
+
imports.push(`import { appRouter } from "./trpc/router${ext}";`);
|
|
36
|
+
setup.push(`await app.ready;`);
|
|
37
|
+
setup.push(`await attachTrpc({ app: app.app, router: appRouter });`);
|
|
38
|
+
}
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
40
|
+
if (features?.websocket) {
|
|
41
|
+
imports.push(`import { attachWebSocket } from "sprint-es/ws";`);
|
|
42
|
+
imports.push(`import { chatHandler } from "./ws/chat${ext}";`);
|
|
43
|
+
setup.push(`{
|
|
44
|
+
const server = await app.onListen();
|
|
45
|
+
await attachWebSocket({ server, handlers: { "/ws/chat": chatHandler } });
|
|
46
|
+
}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (features?.grpc) {
|
|
50
|
+
imports.push(`import { startGrpcServer } from "./grpc/server${ext}";`);
|
|
51
|
+
setup.push(`startGrpcServer().catch(err => console.error("[grpc] failed to start", err));`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return imports.join("\n") + "\n\n" + setup.join("\n") + "\n";
|
|
32
55
|
};
|
|
33
56
|
|
|
34
57
|
export function getHomeRoute(language: string) {
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
type Lang = "typescript" | "javascript";
|
|
2
|
+
|
|
3
|
+
export function getQueueService(language: Lang, choice: "memory" | "bullmq"): string {
|
|
4
|
+
if (choice === "bullmq") {
|
|
5
|
+
if (language === "typescript") {
|
|
6
|
+
return `import { defineQueue, BullMQQueue } from "sprint-es/queue";
|
|
7
|
+
import { Queue, Worker } from "bullmq";
|
|
8
|
+
import IORedis from "ioredis";
|
|
9
|
+
|
|
10
|
+
const connection = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
|
|
11
|
+
maxRetriesPerRequest: null
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export interface EmailJob {
|
|
15
|
+
to: string;
|
|
16
|
+
subject: string;
|
|
17
|
+
body: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const emails = defineQueue<EmailJob>({
|
|
21
|
+
name: "emails",
|
|
22
|
+
adapter: new BullMQQueue<EmailJob>({
|
|
23
|
+
queue: new Queue("emails", { connection }),
|
|
24
|
+
workerFactory: (proc) => new Worker("emails", proc, { connection, concurrency: 4 }),
|
|
25
|
+
defaultAttempts: 5
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
emails.process(async (job) => {
|
|
30
|
+
console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
|
|
31
|
+
// TODO: integrate your SMTP / provider here.
|
|
32
|
+
});
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
return `import { defineQueue, BullMQQueue } from "sprint-es/queue";
|
|
36
|
+
import { Queue, Worker } from "bullmq";
|
|
37
|
+
import IORedis from "ioredis";
|
|
38
|
+
|
|
39
|
+
const connection = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379", {
|
|
40
|
+
maxRetriesPerRequest: null
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const emails = defineQueue({
|
|
44
|
+
name: "emails",
|
|
45
|
+
adapter: new BullMQQueue({
|
|
46
|
+
queue: new Queue("emails", { connection }),
|
|
47
|
+
workerFactory: (proc) => new Worker("emails", proc, { connection, concurrency: 4 }),
|
|
48
|
+
defaultAttempts: 5
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
emails.process(async (job) => {
|
|
53
|
+
console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
|
|
54
|
+
// TODO: integrate your SMTP / provider here.
|
|
55
|
+
});
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// memory
|
|
60
|
+
if (language === "typescript") {
|
|
61
|
+
return `import { defineQueue } from "sprint-es/queue";
|
|
62
|
+
|
|
63
|
+
export interface EmailJob {
|
|
64
|
+
to: string;
|
|
65
|
+
subject: string;
|
|
66
|
+
body: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export const emails = defineQueue<EmailJob>({ name: "emails" });
|
|
70
|
+
|
|
71
|
+
emails.process(async (job) => {
|
|
72
|
+
console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
|
|
73
|
+
// TODO: integrate your SMTP / provider here.
|
|
74
|
+
});
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
return `import { defineQueue } from "sprint-es/queue";
|
|
78
|
+
|
|
79
|
+
export const emails = defineQueue({ name: "emails" });
|
|
80
|
+
|
|
81
|
+
emails.process(async (job) => {
|
|
82
|
+
console.log(\`[emails] sending \${job.data.subject} to \${job.data.to} (attempt \${job.attemptsMade})\`);
|
|
83
|
+
// TODO: integrate your SMTP / provider here.
|
|
84
|
+
});
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getCacheService(language: Lang, choice: "memory" | "redis"): string {
|
|
89
|
+
if (choice === "redis") {
|
|
90
|
+
if (language === "typescript") {
|
|
91
|
+
return `import { defineCache, RedisCache } from "sprint-es/cache";
|
|
92
|
+
import IORedis from "ioredis";
|
|
93
|
+
|
|
94
|
+
const client = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
|
|
95
|
+
|
|
96
|
+
export const cache = defineCache({
|
|
97
|
+
name: "main",
|
|
98
|
+
adapter: new RedisCache({ client, prefix: "app:cache:", defaultTtlMs: 60_000 })
|
|
99
|
+
});
|
|
100
|
+
`;
|
|
101
|
+
}
|
|
102
|
+
return `import { defineCache, RedisCache } from "sprint-es/cache";
|
|
103
|
+
import IORedis from "ioredis";
|
|
104
|
+
|
|
105
|
+
const client = new IORedis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
|
|
106
|
+
|
|
107
|
+
export const cache = defineCache({
|
|
108
|
+
name: "main",
|
|
109
|
+
adapter: new RedisCache({ client, prefix: "app:cache:", defaultTtlMs: 60_000 })
|
|
110
|
+
});
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// memory
|
|
115
|
+
if (language === "typescript") {
|
|
116
|
+
return `import { defineCache, MemoryCache } from "sprint-es/cache";
|
|
117
|
+
|
|
118
|
+
export const cache = defineCache({
|
|
119
|
+
name: "main",
|
|
120
|
+
adapter: new MemoryCache({ maxEntries: 10_000, defaultTtlMs: 60_000 })
|
|
121
|
+
});
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
return `import { defineCache, MemoryCache } from "sprint-es/cache";
|
|
125
|
+
|
|
126
|
+
export const cache = defineCache({
|
|
127
|
+
name: "main",
|
|
128
|
+
adapter: new MemoryCache({ maxEntries: 10_000, defaultTtlMs: 60_000 })
|
|
129
|
+
});
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function getTrpcRouter(language: Lang): string {
|
|
134
|
+
if (language === "typescript") {
|
|
135
|
+
return `import { initTRPC } from "@trpc/server";
|
|
136
|
+
import { z } from "sprint-es/schemas";
|
|
137
|
+
|
|
138
|
+
const t = initTRPC.create();
|
|
139
|
+
|
|
140
|
+
export const appRouter = t.router({
|
|
141
|
+
hello: t.procedure
|
|
142
|
+
.input(z.object({ name: z.string() }))
|
|
143
|
+
.query(({ input }) => ({ greeting: \`Hello, \${input.name}!\` })),
|
|
144
|
+
|
|
145
|
+
createUser: t.procedure
|
|
146
|
+
.input(z.object({ email: z.string().email(), name: z.string().min(1) }))
|
|
147
|
+
.mutation(async ({ input }) => {
|
|
148
|
+
// TODO: persist user
|
|
149
|
+
return { id: crypto.randomUUID(), ...input };
|
|
150
|
+
})
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
export type AppRouter = typeof appRouter;
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
return `import { initTRPC } from "@trpc/server";
|
|
157
|
+
import { z } from "sprint-es/schemas";
|
|
158
|
+
|
|
159
|
+
const t = initTRPC.create();
|
|
160
|
+
|
|
161
|
+
export const appRouter = t.router({
|
|
162
|
+
hello: t.procedure
|
|
163
|
+
.input(z.object({ name: z.string() }))
|
|
164
|
+
.query(({ input }) => ({ greeting: \`Hello, \${input.name}!\` })),
|
|
165
|
+
|
|
166
|
+
createUser: t.procedure
|
|
167
|
+
.input(z.object({ email: z.string().email(), name: z.string().min(1) }))
|
|
168
|
+
.mutation(async ({ input }) => {
|
|
169
|
+
return { id: crypto.randomUUID(), ...input };
|
|
170
|
+
})
|
|
171
|
+
});
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function getGrpcServer(language: Lang): string {
|
|
176
|
+
if (language === "typescript") {
|
|
177
|
+
return `import { createGrpcServer } from "sprint-es/grpc";
|
|
178
|
+
import * as grpc from "@grpc/grpc-js";
|
|
179
|
+
|
|
180
|
+
// Minimal greeter service using a service definition.
|
|
181
|
+
// Replace with proto-loaded service in production via @grpc/proto-loader.
|
|
182
|
+
const greeterServiceDefinition: grpc.ServiceDefinition = {
|
|
183
|
+
SayHello: {
|
|
184
|
+
path: "/Greeter/SayHello",
|
|
185
|
+
requestStream: false,
|
|
186
|
+
responseStream: false,
|
|
187
|
+
requestSerialize: (v: { name: string }) => Buffer.from(JSON.stringify(v)),
|
|
188
|
+
requestDeserialize: (b: Buffer) => JSON.parse(b.toString()),
|
|
189
|
+
responseSerialize: (v: { message: string }) => Buffer.from(JSON.stringify(v)),
|
|
190
|
+
responseDeserialize: (b: Buffer) => JSON.parse(b.toString())
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export async function startGrpcServer() {
|
|
195
|
+
return await createGrpcServer({
|
|
196
|
+
address: process.env.GRPC_ADDRESS ?? "0.0.0.0:50051",
|
|
197
|
+
services: [
|
|
198
|
+
{
|
|
199
|
+
service: greeterServiceDefinition,
|
|
200
|
+
implementation: {
|
|
201
|
+
SayHello: (call: grpc.ServerUnaryCall<{ name: string }, { message: string }>, callback: grpc.sendUnaryData<{ message: string }>) => {
|
|
202
|
+
callback(null, { message: \`Hello, \${call.request.name}!\` });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
return `import { createGrpcServer } from "sprint-es/grpc";
|
|
212
|
+
|
|
213
|
+
const greeterServiceDefinition = {
|
|
214
|
+
SayHello: {
|
|
215
|
+
path: "/Greeter/SayHello",
|
|
216
|
+
requestStream: false,
|
|
217
|
+
responseStream: false,
|
|
218
|
+
requestSerialize: (v) => Buffer.from(JSON.stringify(v)),
|
|
219
|
+
requestDeserialize: (b) => JSON.parse(b.toString()),
|
|
220
|
+
responseSerialize: (v) => Buffer.from(JSON.stringify(v)),
|
|
221
|
+
responseDeserialize: (b) => JSON.parse(b.toString())
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
export async function startGrpcServer() {
|
|
226
|
+
return await createGrpcServer({
|
|
227
|
+
address: process.env.GRPC_ADDRESS ?? "0.0.0.0:50051",
|
|
228
|
+
services: [
|
|
229
|
+
{
|
|
230
|
+
service: greeterServiceDefinition,
|
|
231
|
+
implementation: {
|
|
232
|
+
SayHello: (call, callback) => {
|
|
233
|
+
callback(null, { message: \`Hello, \${call.request.name}!\` });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function getWebSocketScaffold(language: Lang): string {
|
|
244
|
+
if (language === "typescript") {
|
|
245
|
+
return `import type { WebSocketHandler, WebSocket } from "sprint-es/ws";
|
|
246
|
+
|
|
247
|
+
export const chatHandler: WebSocketHandler = {
|
|
248
|
+
onConnection: (socket: WebSocket, request) => {
|
|
249
|
+
console.log(\`[ws] client connected from \${request.socket.remoteAddress}\`);
|
|
250
|
+
socket.on("message", (raw) => {
|
|
251
|
+
const text = raw.toString();
|
|
252
|
+
socket.send(JSON.stringify({ echo: text, ts: Date.now() }));
|
|
253
|
+
});
|
|
254
|
+
socket.on("close", () => console.log("[ws] client disconnected"));
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
return `export const chatHandler = {
|
|
260
|
+
onConnection: (socket, request) => {
|
|
261
|
+
console.log(\`[ws] client connected from \${request.socket.remoteAddress}\`);
|
|
262
|
+
socket.on("message", (raw) => {
|
|
263
|
+
const text = raw.toString();
|
|
264
|
+
socket.send(JSON.stringify({ echo: text, ts: Date.now() }));
|
|
265
|
+
});
|
|
266
|
+
socket.on("close", () => console.log("[ws] client disconnected"));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
`;
|
|
270
|
+
}
|