shokupan 0.0.1
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/README.md +1669 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli.cjs +154 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.js +136 -0
- package/dist/cli.js.map +1 -0
- package/dist/context.d.ts +88 -0
- package/dist/decorators.d.ts +23 -0
- package/dist/di.d.ts +18 -0
- package/dist/index.cjs +2305 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +2288 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/plugins/auth.d.ts +58 -0
- package/dist/plugins/compression.d.ts +5 -0
- package/dist/plugins/cors.d.ts +11 -0
- package/dist/plugins/express.d.ts +6 -0
- package/dist/plugins/rate-limit.d.ts +12 -0
- package/dist/plugins/scalar.d.ts +13 -0
- package/dist/plugins/security-headers.d.ts +36 -0
- package/dist/plugins/session.d.ts +87 -0
- package/dist/plugins/validation.d.ts +18 -0
- package/dist/request.d.ts +34 -0
- package/dist/response.d.ts +42 -0
- package/dist/router.d.ts +237 -0
- package/dist/shokupan.d.ts +41 -0
- package/dist/symbol.d.ts +13 -0
- package/dist/types.d.ts +142 -0
- package/dist/util/async-hooks.d.ts +3 -0
- package/dist/util/deep-merge.d.ts +12 -0
- package/dist/util/instrumentation.d.ts +9 -0
- package/package.json +82 -0
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
"use strict";
|
|
3
|
+
const p = require("@clack/prompts");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const promises = require("node:timers/promises");
|
|
7
|
+
function _interopNamespaceDefault(e) {
|
|
8
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
9
|
+
if (e) {
|
|
10
|
+
for (const k in e) {
|
|
11
|
+
if (k !== "default") {
|
|
12
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
13
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: () => e[k]
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
n.default = e;
|
|
21
|
+
return Object.freeze(n);
|
|
22
|
+
}
|
|
23
|
+
const p__namespace = /* @__PURE__ */ _interopNamespaceDefault(p);
|
|
24
|
+
const templates = {
|
|
25
|
+
controller: (name) => `import { Controller, Get, Ctx } from 'shokupan';
|
|
26
|
+
import { ShokupanContext } from 'shokupan';
|
|
27
|
+
|
|
28
|
+
@Controller('/${name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}')
|
|
29
|
+
export class ${name}Controller {
|
|
30
|
+
@Get('/')
|
|
31
|
+
public index(@Ctx() ctx: ShokupanContext) {
|
|
32
|
+
return { message: 'Hello from ${name}Controller' };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`,
|
|
36
|
+
middleware: (name) => `import { ShokupanContext, NextFn } from 'shokupan';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ${name} Middleware
|
|
40
|
+
*/
|
|
41
|
+
export const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {
|
|
42
|
+
// Before next
|
|
43
|
+
// console.log('${name} Middleware - Request');
|
|
44
|
+
|
|
45
|
+
const result = await next();
|
|
46
|
+
|
|
47
|
+
// After next
|
|
48
|
+
// console.log('${name} Middleware - Response');
|
|
49
|
+
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
`,
|
|
53
|
+
plugin: (name) => `import { ShokupanRouter } from 'shokupan';
|
|
54
|
+
import { ShokupanContext } from 'shokupan';
|
|
55
|
+
|
|
56
|
+
export interface ${name}Options {
|
|
57
|
+
// Define options here
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class ${name}Plugin extends ShokupanRouter {
|
|
61
|
+
constructor(private options: ${name}Options = {}) {
|
|
62
|
+
super();
|
|
63
|
+
this.init();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private init() {
|
|
67
|
+
this.get('/', (ctx: ShokupanContext) => {
|
|
68
|
+
return { message: '${name} Plugin Active' };
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`
|
|
73
|
+
};
|
|
74
|
+
async function main() {
|
|
75
|
+
console.clear();
|
|
76
|
+
p__namespace.intro(`Shokupan CLI Scaffolder`);
|
|
77
|
+
if (!fs.existsSync("package.json")) {
|
|
78
|
+
p__namespace.note("Warning: No package.json found in current directory. Are you in the project root?");
|
|
79
|
+
}
|
|
80
|
+
const project = await p__namespace.group(
|
|
81
|
+
{
|
|
82
|
+
type: () => p__namespace.select({
|
|
83
|
+
message: "What do you want to scaffold?",
|
|
84
|
+
options: [
|
|
85
|
+
{ value: "controller", label: "Controller" },
|
|
86
|
+
{ value: "middleware", label: "Middleware" },
|
|
87
|
+
{ value: "plugin", label: "Plugin" }
|
|
88
|
+
]
|
|
89
|
+
}),
|
|
90
|
+
name: () => p__namespace.text({
|
|
91
|
+
message: "Name (PascalCase, e.g. UserAuth):",
|
|
92
|
+
validate: (value) => {
|
|
93
|
+
if (!value) return "Name is required";
|
|
94
|
+
if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return "Please use PascalCase";
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}),
|
|
98
|
+
dir: () => p__namespace.text({
|
|
99
|
+
message: "Output directory (leave empty for default):",
|
|
100
|
+
placeholder: "src/controllers"
|
|
101
|
+
})
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
onCancel: () => {
|
|
105
|
+
p__namespace.cancel("Operation cancelled.");
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
const type = project.type;
|
|
111
|
+
const name = project.name;
|
|
112
|
+
let dir = project.dir;
|
|
113
|
+
if (!dir || dir.trim() === "") {
|
|
114
|
+
switch (type) {
|
|
115
|
+
case "controller":
|
|
116
|
+
dir = "src/controllers";
|
|
117
|
+
break;
|
|
118
|
+
case "middleware":
|
|
119
|
+
dir = "src/middleware";
|
|
120
|
+
break;
|
|
121
|
+
case "plugin":
|
|
122
|
+
dir = "src/plugins";
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const kebabName = name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
127
|
+
const fileName = `${kebabName}.ts`;
|
|
128
|
+
const finalPath = path.join(process.cwd(), dir, fileName);
|
|
129
|
+
if (!fs.existsSync(path.dirname(finalPath))) {
|
|
130
|
+
fs.mkdirSync(path.dirname(finalPath), { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
if (fs.existsSync(finalPath)) {
|
|
133
|
+
const overwrite = await p__namespace.confirm({
|
|
134
|
+
message: `File ${finalPath} already exists. Overwrite?`,
|
|
135
|
+
initialValue: false
|
|
136
|
+
});
|
|
137
|
+
if (p__namespace.isCancel(overwrite) || !overwrite) {
|
|
138
|
+
p__namespace.cancel("Operation cancelled.");
|
|
139
|
+
process.exit(0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const s = p__namespace.spinner();
|
|
143
|
+
s.start(`Creating ${type}...`);
|
|
144
|
+
await promises.setTimeout(500);
|
|
145
|
+
const content = templates[type](name);
|
|
146
|
+
fs.writeFileSync(finalPath, content);
|
|
147
|
+
s.stop(`Created ${type}`);
|
|
148
|
+
const nextSteps = ` -> ${finalPath}
|
|
149
|
+
Make sure to register it in your main application file if necessary.`;
|
|
150
|
+
p__namespace.note(nextSteps, "Next steps");
|
|
151
|
+
p__namespace.outro(`Problems? Open an issue at https://github.com/dotglitch/express.ts`);
|
|
152
|
+
}
|
|
153
|
+
main().catch(console.error);
|
|
154
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.cjs","sources":["../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport * as p from '@clack/prompts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\n\nconst templates = {\n controller: (name: string) => `import { Controller, Get, Ctx } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\n@Controller('/${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}')\nexport class ${name}Controller {\n @Get('/')\n public index(@Ctx() ctx: ShokupanContext) {\n return { message: 'Hello from ${name}Controller' };\n }\n}\n`,\n middleware: (name: string) => `import { ShokupanContext, NextFn } from 'shokupan';\n\n/**\n * ${name} Middleware\n */\nexport const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {\n // Before next\n // console.log('${name} Middleware - Request');\n\n const result = await next();\n\n // After next\n // console.log('${name} Middleware - Response');\n \n return result;\n};\n`,\n plugin: (name: string) => `import { ShokupanRouter } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\nexport interface ${name}Options {\n // Define options here\n}\n\nexport class ${name}Plugin extends ShokupanRouter {\n constructor(private options: ${name}Options = {}) {\n super();\n this.init();\n }\n\n private init() {\n this.get('/', (ctx: ShokupanContext) => {\n return { message: '${name} Plugin Active' };\n });\n }\n}\n`\n};\n\nasync function main() {\n console.clear();\n p.intro(`Shokupan CLI Scaffolder`);\n\n // Check if running in a project root\n if (!fs.existsSync('package.json')) {\n p.note('Warning: No package.json found in current directory. Are you in the project root?');\n }\n\n const project = await p.group(\n {\n type: () => p.select({\n message: 'What do you want to scaffold?',\n options: [\n { value: 'controller', label: 'Controller' },\n { value: 'middleware', label: 'Middleware' },\n { value: 'plugin', label: 'Plugin' },\n ],\n }),\n name: () => p.text({\n message: 'Name (PascalCase, e.g. UserAuth):',\n validate: (value) => {\n if (!value) return 'Name is required';\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Please use PascalCase';\n return undefined;\n },\n }),\n dir: () => p.text({\n message: 'Output directory (leave empty for default):',\n placeholder: 'src/controllers',\n }),\n },\n {\n onCancel: () => {\n p.cancel('Operation cancelled.');\n process.exit(0);\n },\n }\n );\n\n const type = project.type as keyof typeof templates;\n const name = project.name;\n let dir = project.dir;\n\n if (!dir || dir.trim() === '') {\n switch (type) {\n case 'controller': dir = 'src/controllers'; break;\n case 'middleware': dir = 'src/middleware'; break;\n case 'plugin': dir = 'src/plugins'; break;\n }\n }\n\n // Convert PascalCase to kebab-case for filename\n const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n const fileName = `${kebabName}.ts`;\n\n const finalPath = path.join(process.cwd(), dir, fileName);\n\n // Ensure directory exists\n if (!fs.existsSync(path.dirname(finalPath))) {\n fs.mkdirSync(path.dirname(finalPath), { recursive: true });\n }\n\n // Check for overwrite\n if (fs.existsSync(finalPath)) {\n const overwrite = await p.confirm({\n message: `File ${finalPath} already exists. Overwrite?`,\n initialValue: false\n });\n\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Operation cancelled.');\n process.exit(0);\n }\n }\n\n const s = p.spinner();\n s.start(`Creating ${type}...`);\n\n await setTimeout(500); // Artificial delay to show spinner\n\n const content = templates[type](name);\n fs.writeFileSync(finalPath, content);\n\n s.stop(`Created ${type}`);\n\n const nextSteps = ` -> ${finalPath}\nMake sure to register it in your main application file if necessary.`;\n\n p.note(nextSteps, 'Next steps');\n\n p.outro(`Problems? Open an issue at https://github.com/dotglitch/express.ts`);\n}\n\nmain().catch(console.error);;\n"],"names":["p","setTimeout"],"mappings":"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"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import * as p from "@clack/prompts";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { setTimeout } from "node:timers/promises";
|
|
6
|
+
const templates = {
|
|
7
|
+
controller: (name) => `import { Controller, Get, Ctx } from 'shokupan';
|
|
8
|
+
import { ShokupanContext } from 'shokupan';
|
|
9
|
+
|
|
10
|
+
@Controller('/${name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase()}')
|
|
11
|
+
export class ${name}Controller {
|
|
12
|
+
@Get('/')
|
|
13
|
+
public index(@Ctx() ctx: ShokupanContext) {
|
|
14
|
+
return { message: 'Hello from ${name}Controller' };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
`,
|
|
18
|
+
middleware: (name) => `import { ShokupanContext, NextFn } from 'shokupan';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* ${name} Middleware
|
|
22
|
+
*/
|
|
23
|
+
export const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {
|
|
24
|
+
// Before next
|
|
25
|
+
// console.log('${name} Middleware - Request');
|
|
26
|
+
|
|
27
|
+
const result = await next();
|
|
28
|
+
|
|
29
|
+
// After next
|
|
30
|
+
// console.log('${name} Middleware - Response');
|
|
31
|
+
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
`,
|
|
35
|
+
plugin: (name) => `import { ShokupanRouter } from 'shokupan';
|
|
36
|
+
import { ShokupanContext } from 'shokupan';
|
|
37
|
+
|
|
38
|
+
export interface ${name}Options {
|
|
39
|
+
// Define options here
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class ${name}Plugin extends ShokupanRouter {
|
|
43
|
+
constructor(private options: ${name}Options = {}) {
|
|
44
|
+
super();
|
|
45
|
+
this.init();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private init() {
|
|
49
|
+
this.get('/', (ctx: ShokupanContext) => {
|
|
50
|
+
return { message: '${name} Plugin Active' };
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
};
|
|
56
|
+
async function main() {
|
|
57
|
+
console.clear();
|
|
58
|
+
p.intro(`Shokupan CLI Scaffolder`);
|
|
59
|
+
if (!fs.existsSync("package.json")) {
|
|
60
|
+
p.note("Warning: No package.json found in current directory. Are you in the project root?");
|
|
61
|
+
}
|
|
62
|
+
const project = await p.group(
|
|
63
|
+
{
|
|
64
|
+
type: () => p.select({
|
|
65
|
+
message: "What do you want to scaffold?",
|
|
66
|
+
options: [
|
|
67
|
+
{ value: "controller", label: "Controller" },
|
|
68
|
+
{ value: "middleware", label: "Middleware" },
|
|
69
|
+
{ value: "plugin", label: "Plugin" }
|
|
70
|
+
]
|
|
71
|
+
}),
|
|
72
|
+
name: () => p.text({
|
|
73
|
+
message: "Name (PascalCase, e.g. UserAuth):",
|
|
74
|
+
validate: (value) => {
|
|
75
|
+
if (!value) return "Name is required";
|
|
76
|
+
if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return "Please use PascalCase";
|
|
77
|
+
return void 0;
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
dir: () => p.text({
|
|
81
|
+
message: "Output directory (leave empty for default):",
|
|
82
|
+
placeholder: "src/controllers"
|
|
83
|
+
})
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
onCancel: () => {
|
|
87
|
+
p.cancel("Operation cancelled.");
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
const type = project.type;
|
|
93
|
+
const name = project.name;
|
|
94
|
+
let dir = project.dir;
|
|
95
|
+
if (!dir || dir.trim() === "") {
|
|
96
|
+
switch (type) {
|
|
97
|
+
case "controller":
|
|
98
|
+
dir = "src/controllers";
|
|
99
|
+
break;
|
|
100
|
+
case "middleware":
|
|
101
|
+
dir = "src/middleware";
|
|
102
|
+
break;
|
|
103
|
+
case "plugin":
|
|
104
|
+
dir = "src/plugins";
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const kebabName = name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
109
|
+
const fileName = `${kebabName}.ts`;
|
|
110
|
+
const finalPath = path.join(process.cwd(), dir, fileName);
|
|
111
|
+
if (!fs.existsSync(path.dirname(finalPath))) {
|
|
112
|
+
fs.mkdirSync(path.dirname(finalPath), { recursive: true });
|
|
113
|
+
}
|
|
114
|
+
if (fs.existsSync(finalPath)) {
|
|
115
|
+
const overwrite = await p.confirm({
|
|
116
|
+
message: `File ${finalPath} already exists. Overwrite?`,
|
|
117
|
+
initialValue: false
|
|
118
|
+
});
|
|
119
|
+
if (p.isCancel(overwrite) || !overwrite) {
|
|
120
|
+
p.cancel("Operation cancelled.");
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const s = p.spinner();
|
|
125
|
+
s.start(`Creating ${type}...`);
|
|
126
|
+
await setTimeout(500);
|
|
127
|
+
const content = templates[type](name);
|
|
128
|
+
fs.writeFileSync(finalPath, content);
|
|
129
|
+
s.stop(`Created ${type}`);
|
|
130
|
+
const nextSteps = ` -> ${finalPath}
|
|
131
|
+
Make sure to register it in your main application file if necessary.`;
|
|
132
|
+
p.note(nextSteps, "Next steps");
|
|
133
|
+
p.outro(`Problems? Open an issue at https://github.com/dotglitch/express.ts`);
|
|
134
|
+
}
|
|
135
|
+
main().catch(console.error);
|
|
136
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sources":["../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env bun\nimport * as p from '@clack/prompts';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { setTimeout } from 'node:timers/promises';\n\nconst templates = {\n controller: (name: string) => `import { Controller, Get, Ctx } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\n@Controller('/${name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}')\nexport class ${name}Controller {\n @Get('/')\n public index(@Ctx() ctx: ShokupanContext) {\n return { message: 'Hello from ${name}Controller' };\n }\n}\n`,\n middleware: (name: string) => `import { ShokupanContext, NextFn } from 'shokupan';\n\n/**\n * ${name} Middleware\n */\nexport const ${name}Middleware = async (ctx: ShokupanContext, next: NextFn) => {\n // Before next\n // console.log('${name} Middleware - Request');\n\n const result = await next();\n\n // After next\n // console.log('${name} Middleware - Response');\n \n return result;\n};\n`,\n plugin: (name: string) => `import { ShokupanRouter } from 'shokupan';\nimport { ShokupanContext } from 'shokupan';\n\nexport interface ${name}Options {\n // Define options here\n}\n\nexport class ${name}Plugin extends ShokupanRouter {\n constructor(private options: ${name}Options = {}) {\n super();\n this.init();\n }\n\n private init() {\n this.get('/', (ctx: ShokupanContext) => {\n return { message: '${name} Plugin Active' };\n });\n }\n}\n`\n};\n\nasync function main() {\n console.clear();\n p.intro(`Shokupan CLI Scaffolder`);\n\n // Check if running in a project root\n if (!fs.existsSync('package.json')) {\n p.note('Warning: No package.json found in current directory. Are you in the project root?');\n }\n\n const project = await p.group(\n {\n type: () => p.select({\n message: 'What do you want to scaffold?',\n options: [\n { value: 'controller', label: 'Controller' },\n { value: 'middleware', label: 'Middleware' },\n { value: 'plugin', label: 'Plugin' },\n ],\n }),\n name: () => p.text({\n message: 'Name (PascalCase, e.g. UserAuth):',\n validate: (value) => {\n if (!value) return 'Name is required';\n if (!/^[A-Z][a-zA-Z0-9]*$/.test(value)) return 'Please use PascalCase';\n return undefined;\n },\n }),\n dir: () => p.text({\n message: 'Output directory (leave empty for default):',\n placeholder: 'src/controllers',\n }),\n },\n {\n onCancel: () => {\n p.cancel('Operation cancelled.');\n process.exit(0);\n },\n }\n );\n\n const type = project.type as keyof typeof templates;\n const name = project.name;\n let dir = project.dir;\n\n if (!dir || dir.trim() === '') {\n switch (type) {\n case 'controller': dir = 'src/controllers'; break;\n case 'middleware': dir = 'src/middleware'; break;\n case 'plugin': dir = 'src/plugins'; break;\n }\n }\n\n // Convert PascalCase to kebab-case for filename\n const kebabName = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n const fileName = `${kebabName}.ts`;\n\n const finalPath = path.join(process.cwd(), dir, fileName);\n\n // Ensure directory exists\n if (!fs.existsSync(path.dirname(finalPath))) {\n fs.mkdirSync(path.dirname(finalPath), { recursive: true });\n }\n\n // Check for overwrite\n if (fs.existsSync(finalPath)) {\n const overwrite = await p.confirm({\n message: `File ${finalPath} already exists. Overwrite?`,\n initialValue: false\n });\n\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Operation cancelled.');\n process.exit(0);\n }\n }\n\n const s = p.spinner();\n s.start(`Creating ${type}...`);\n\n await setTimeout(500); // Artificial delay to show spinner\n\n const content = templates[type](name);\n fs.writeFileSync(finalPath, content);\n\n s.stop(`Created ${type}`);\n\n const nextSteps = ` -> ${finalPath}\nMake sure to register it in your main application file if necessary.`;\n\n p.note(nextSteps, 'Next steps');\n\n p.outro(`Problems? Open an issue at https://github.com/dotglitch/express.ts`);\n}\n\nmain().catch(console.error);;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { BodyInit } from 'bun';
|
|
2
|
+
import { ShokupanRequest } from './request';
|
|
3
|
+
import { ShokupanResponse } from './response';
|
|
4
|
+
import { CookieOptions } from './types';
|
|
5
|
+
type HeadersInit = Headers | Record<string, string> | [string, string][];
|
|
6
|
+
export declare class ShokupanContext<State extends Record<string, any> = Record<string, any>> {
|
|
7
|
+
readonly request: ShokupanRequest<any>;
|
|
8
|
+
readonly url: URL;
|
|
9
|
+
params: Record<string, string>;
|
|
10
|
+
state: State;
|
|
11
|
+
readonly response: ShokupanResponse;
|
|
12
|
+
constructor(request: ShokupanRequest<any>, state?: State);
|
|
13
|
+
/**
|
|
14
|
+
* Base request
|
|
15
|
+
*/
|
|
16
|
+
get req(): any;
|
|
17
|
+
/**
|
|
18
|
+
* HTTP method
|
|
19
|
+
*/
|
|
20
|
+
get method(): any;
|
|
21
|
+
/**
|
|
22
|
+
* Request path
|
|
23
|
+
*/
|
|
24
|
+
get path(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Request query params
|
|
27
|
+
*/
|
|
28
|
+
get query(): {
|
|
29
|
+
[k: string]: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Request headers
|
|
33
|
+
*/
|
|
34
|
+
get headers(): any;
|
|
35
|
+
/**
|
|
36
|
+
* Base response object
|
|
37
|
+
*/
|
|
38
|
+
get res(): ShokupanResponse;
|
|
39
|
+
/**
|
|
40
|
+
* Helper to set a header on the response
|
|
41
|
+
*/
|
|
42
|
+
set(key: string, value: string): this;
|
|
43
|
+
/**
|
|
44
|
+
* Set a cookie
|
|
45
|
+
* @param name Cookie name
|
|
46
|
+
* @param value Cookie value
|
|
47
|
+
* @param options Cookie options
|
|
48
|
+
*/
|
|
49
|
+
setCookie(name: string, value: string, options?: CookieOptions): this;
|
|
50
|
+
private mergeHeaders;
|
|
51
|
+
/**
|
|
52
|
+
* Send a response
|
|
53
|
+
* @param body Response body
|
|
54
|
+
* @param options Response options
|
|
55
|
+
* @returns Response
|
|
56
|
+
*/
|
|
57
|
+
send(body?: BodyInit, options?: ResponseInit): Response;
|
|
58
|
+
/**
|
|
59
|
+
* Read request body
|
|
60
|
+
*/
|
|
61
|
+
body<T = any>(): Promise<T>;
|
|
62
|
+
/**
|
|
63
|
+
* Respond with a JSON object
|
|
64
|
+
*/
|
|
65
|
+
json(data: any, status?: number, headers?: HeadersInit): Response;
|
|
66
|
+
/**
|
|
67
|
+
* Respond with a text string
|
|
68
|
+
*/
|
|
69
|
+
text(data: string, status?: number, headers?: HeadersInit): Response;
|
|
70
|
+
/**
|
|
71
|
+
* Respond with HTML content
|
|
72
|
+
*/
|
|
73
|
+
html(html: string, status?: number, headers?: HeadersInit): Response;
|
|
74
|
+
/**
|
|
75
|
+
* Respond with a redirect
|
|
76
|
+
*/
|
|
77
|
+
redirect(url: string, status?: number): Response;
|
|
78
|
+
/**
|
|
79
|
+
* Respond with a status code
|
|
80
|
+
* DOES NOT CHAIN!
|
|
81
|
+
*/
|
|
82
|
+
status(status: number): Response;
|
|
83
|
+
/**
|
|
84
|
+
* Respond with a file
|
|
85
|
+
*/
|
|
86
|
+
file(path: string, fileOptions?: BlobPropertyBag, responseOptions?: ResponseInit): Response;
|
|
87
|
+
}
|
|
88
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Middleware } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Class Decorator: Defines the base path for a controller.
|
|
4
|
+
*/
|
|
5
|
+
export declare function Controller(path?: string): (target: any) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Decorator: Applies middleware to a class or method.
|
|
8
|
+
*/
|
|
9
|
+
export declare function Use(...middleware: Middleware[]): (target: any, propertyKey?: string, descriptor?: PropertyDescriptor) => void;
|
|
10
|
+
export declare const Body: (name?: string) => (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
11
|
+
export declare const Param: (name?: string) => (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
12
|
+
export declare const Query: (name?: string) => (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
13
|
+
export declare const Headers: (name?: string) => (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
14
|
+
export declare const Req: (name?: string) => (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
15
|
+
export declare const Ctx: (name?: string) => (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
16
|
+
export declare const Get: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
17
|
+
export declare const Post: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
18
|
+
export declare const Put: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
19
|
+
export declare const Delete: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
20
|
+
export declare const Patch: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
21
|
+
export declare const Options: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
22
|
+
export declare const Head: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
23
|
+
export declare const All: (path?: string) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
package/dist/di.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Dependency Injection Container
|
|
3
|
+
*/
|
|
4
|
+
export declare class Container {
|
|
5
|
+
private static services;
|
|
6
|
+
static register<T>(target: new (...args: any[]) => T, instance: T): void;
|
|
7
|
+
static get<T>(target: new (...args: any[]) => T): T | undefined;
|
|
8
|
+
static has(target: any): boolean;
|
|
9
|
+
static resolve<T>(target: new (...args: any[]) => T): T;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Decorator to mark a class as injectable (Service).
|
|
13
|
+
*/
|
|
14
|
+
export declare function Injectable(): (target: any) => void;
|
|
15
|
+
/**
|
|
16
|
+
* Property Decorator: Injects a service.
|
|
17
|
+
*/
|
|
18
|
+
export declare function Inject(token: any): (target: any, key: string) => void;
|