@fonderie/core 0.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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/config.cjs +33 -0
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +35 -0
- package/dist/config.d.ts +35 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -0
- package/dist/index.cjs +371 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +30 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +335 -0
- package/dist/index.js.map +1 -0
- package/dist/middlewares/index.cjs +201 -0
- package/dist/middlewares/index.cjs.map +1 -0
- package/dist/middlewares/index.d.cts +23 -0
- package/dist/middlewares/index.d.ts +23 -0
- package/dist/middlewares/index.js +167 -0
- package/dist/middlewares/index.js.map +1 -0
- package/dist/parser.cjs +58 -0
- package/dist/parser.cjs.map +1 -0
- package/dist/parser.d.cts +7 -0
- package/dist/parser.d.ts +7 -0
- package/dist/parser.js +29 -0
- package/dist/parser.js.map +1 -0
- package/dist/response.cjs +58 -0
- package/dist/response.cjs.map +1 -0
- package/dist/response.d.cts +33 -0
- package/dist/response.d.ts +33 -0
- package/dist/response.js +32 -0
- package/dist/response.js.map +1 -0
- package/dist/types.cjs +19 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +75 -0
- package/dist/types.d.ts +75 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/package.json +85 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IFonderieModule, IFonderieContext, Middleware } from './types.js';
|
|
2
|
+
export { IAuthUser, ICourierMessage, IFonderieApp, IFonderieContextMeta, IRouteMatch, IRouter, ITenant, IWorkspace } from './types.js';
|
|
3
|
+
import { FonderieConfig } from './config.js';
|
|
4
|
+
export { defineConfig } from './config.js';
|
|
5
|
+
export { arrayOrEmpty, booleanOrFalse, dateOrEmpty, numberOrZero, stringOrEmpty } from './parser.js';
|
|
6
|
+
export { HTTP, HttpStatus, IApiError, setApiResponse } from './response.js';
|
|
7
|
+
|
|
8
|
+
declare class FonderieApp {
|
|
9
|
+
private config;
|
|
10
|
+
private prefix;
|
|
11
|
+
private router;
|
|
12
|
+
private middlewares;
|
|
13
|
+
private modules;
|
|
14
|
+
constructor(config: FonderieConfig);
|
|
15
|
+
listen(port: number, options?: {
|
|
16
|
+
name?: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
env?: string;
|
|
19
|
+
}): void;
|
|
20
|
+
register(module: IFonderieModule): this;
|
|
21
|
+
boot(): Promise<this>;
|
|
22
|
+
buildContext(request: Request): Promise<IFonderieContext>;
|
|
23
|
+
use(middleware: Middleware): this;
|
|
24
|
+
addRoute(method: string, path: string, ...handlers: Middleware[]): void;
|
|
25
|
+
handle(request: Request): Promise<Response>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare function compose(middlewares: Middleware[]): (ctx: IFonderieContext, fallback: () => Promise<Response>) => Promise<Response>;
|
|
29
|
+
|
|
30
|
+
export { FonderieApp, FonderieConfig, IFonderieContext, IFonderieModule, Middleware, compose };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// src/app.ts
|
|
2
|
+
import { networkInterfaces } from "os";
|
|
3
|
+
import { createServer } from "http";
|
|
4
|
+
|
|
5
|
+
// src/router.ts
|
|
6
|
+
var Router = class {
|
|
7
|
+
routes = [];
|
|
8
|
+
add(method, path, handler) {
|
|
9
|
+
this.routes.push({ method: method.toUpperCase(), path, handler });
|
|
10
|
+
}
|
|
11
|
+
match(method, path) {
|
|
12
|
+
for (const route of this.routes) {
|
|
13
|
+
if (route.method !== method.toUpperCase()) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
const params = matchPath(route.path, path);
|
|
17
|
+
if (params !== null) {
|
|
18
|
+
return { handler: route.handler, params };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function matchPath(pattern, path) {
|
|
25
|
+
const clean = (path.split("?")[0] ?? path).replace(/\/$/, "") || "/";
|
|
26
|
+
const pp = pattern.split("/");
|
|
27
|
+
const vp = clean.split("/");
|
|
28
|
+
if (pp.length !== vp.length) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const params = {};
|
|
32
|
+
for (let i = 0; i < pp.length; i++) {
|
|
33
|
+
const ps = pp[i] ?? "";
|
|
34
|
+
const vs = vp[i] ?? "";
|
|
35
|
+
if (ps.startsWith(":")) {
|
|
36
|
+
params[ps.slice(1)] = decodeURIComponent(vs);
|
|
37
|
+
} else if (ps !== vs) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return params;
|
|
42
|
+
}
|
|
43
|
+
function routerMiddleware(router) {
|
|
44
|
+
return async (ctx, next) => {
|
|
45
|
+
const url = new URL(ctx.request.url);
|
|
46
|
+
const match = router.match(ctx.request.method, url.pathname);
|
|
47
|
+
if (!match) {
|
|
48
|
+
return next();
|
|
49
|
+
}
|
|
50
|
+
ctx.meta.params = match.params;
|
|
51
|
+
return match.handler(ctx, next);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/compose.ts
|
|
56
|
+
function compose(middlewares) {
|
|
57
|
+
return function(ctx, fallback) {
|
|
58
|
+
let index = -1;
|
|
59
|
+
function dispatch(i) {
|
|
60
|
+
if (i <= index) {
|
|
61
|
+
throw new Error("next() called multiple times");
|
|
62
|
+
}
|
|
63
|
+
index = i;
|
|
64
|
+
const fn = middlewares[i] ?? fallback;
|
|
65
|
+
return fn(ctx, () => dispatch(i + 1));
|
|
66
|
+
}
|
|
67
|
+
return dispatch(0);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/response.ts
|
|
72
|
+
var HTTP = {
|
|
73
|
+
OK: 200,
|
|
74
|
+
CREATED: 201,
|
|
75
|
+
ACCEPTED: 202,
|
|
76
|
+
NO_CONTENT: 204,
|
|
77
|
+
BAD_REQUEST: 400,
|
|
78
|
+
UNAUTHORIZED: 401,
|
|
79
|
+
PAYMENT_REQUIRED: 402,
|
|
80
|
+
FORBIDDEN: 403,
|
|
81
|
+
NOT_FOUND: 404,
|
|
82
|
+
CONFLICT: 409,
|
|
83
|
+
GONE: 410,
|
|
84
|
+
UNPROCESSABLE: 422,
|
|
85
|
+
TOO_MANY_REQUESTS: 429,
|
|
86
|
+
SERVER_ERROR: 500,
|
|
87
|
+
NOT_IMPLEMENTED: 501,
|
|
88
|
+
BAD_GATEWAY: 502,
|
|
89
|
+
SERVICE_UNAVAILABLE: 503
|
|
90
|
+
};
|
|
91
|
+
function setApiResponse(status, reason, explanation, payload) {
|
|
92
|
+
const body = { reason, explanation };
|
|
93
|
+
if (payload !== void 0) {
|
|
94
|
+
body[status < 400 ? "result" : "details"] = payload;
|
|
95
|
+
}
|
|
96
|
+
return Response.json(body, { status });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/middlewares/not-found.ts
|
|
100
|
+
function notFoundMiddleware() {
|
|
101
|
+
return async (_ctx, _next) => setApiResponse(HTTP.NOT_FOUND, "NOT_FOUND", "Not found");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/middlewares/body-parser.ts
|
|
105
|
+
var withBody = async (ctx, next) => {
|
|
106
|
+
const method = ctx.request.method.toUpperCase();
|
|
107
|
+
if (method === "GET" || method === "HEAD") {
|
|
108
|
+
return next();
|
|
109
|
+
}
|
|
110
|
+
const ct = ctx.request.headers.get("content-type") ?? "";
|
|
111
|
+
try {
|
|
112
|
+
if (ct.includes("application/json")) {
|
|
113
|
+
const text = (await ctx.request.clone().text()).trim();
|
|
114
|
+
ctx.meta.body = text ? JSON.parse(text) : {};
|
|
115
|
+
} else if (ct.includes("application/x-www-form-urlencoded")) {
|
|
116
|
+
const text = await ctx.request.clone().text();
|
|
117
|
+
ctx.meta.body = Object.fromEntries(new URLSearchParams(text));
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
return setApiResponse(HTTP.BAD_REQUEST, "INVALID_REQUEST", "Invalid request body");
|
|
121
|
+
}
|
|
122
|
+
return next();
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/middlewares/error-handler.ts
|
|
126
|
+
function defaultErrorHandler(err) {
|
|
127
|
+
const dev = process.env["NODE_ENV"] !== "production";
|
|
128
|
+
if (err instanceof Error) {
|
|
129
|
+
console.error("[fonderie]", err.message, err.stack);
|
|
130
|
+
return setApiResponse(
|
|
131
|
+
HTTP.SERVER_ERROR,
|
|
132
|
+
"SERVER_ERROR",
|
|
133
|
+
dev ? err.message : "Internal server error"
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
console.error("[fonderie] unknown error", err);
|
|
137
|
+
return setApiResponse(HTTP.SERVER_ERROR, "SERVER_ERROR", "Internal server error");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/app.ts
|
|
141
|
+
var FonderieApp = class {
|
|
142
|
+
config;
|
|
143
|
+
prefix;
|
|
144
|
+
router = new Router();
|
|
145
|
+
middlewares = [];
|
|
146
|
+
modules = /* @__PURE__ */ new Map();
|
|
147
|
+
constructor(config) {
|
|
148
|
+
this.config = config;
|
|
149
|
+
this.prefix = (config.basePath ?? "").replace(/\/$/, "");
|
|
150
|
+
this.middlewares = [withBody];
|
|
151
|
+
}
|
|
152
|
+
listen(port, options = {}) {
|
|
153
|
+
const {
|
|
154
|
+
name = "Fonderie",
|
|
155
|
+
version = "0.0.1",
|
|
156
|
+
env = process.env["NODE_ENV"] ?? "development"
|
|
157
|
+
} = options;
|
|
158
|
+
createServer(async (req, res) => {
|
|
159
|
+
const host = req.headers.host ?? "localhost";
|
|
160
|
+
const url = `http://${host}${req.url ?? "/"}`;
|
|
161
|
+
const headers = new Headers();
|
|
162
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
163
|
+
if (!value) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
Array.isArray(value) ? value.forEach((v) => headers.append(key, v)) : headers.set(key, value);
|
|
167
|
+
}
|
|
168
|
+
const body = await new Promise((resolve, reject) => {
|
|
169
|
+
const chunks = [];
|
|
170
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
171
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
172
|
+
req.on("error", reject);
|
|
173
|
+
});
|
|
174
|
+
const method = req.method ?? "GET";
|
|
175
|
+
const hasBody = !["GET", "HEAD"].includes(method.toUpperCase());
|
|
176
|
+
const request = new Request(url, {
|
|
177
|
+
method,
|
|
178
|
+
headers,
|
|
179
|
+
body: hasBody && body.length > 0 ? new Uint8Array(body) : null
|
|
180
|
+
});
|
|
181
|
+
const response = await this.handle(request);
|
|
182
|
+
res.statusCode = response.status;
|
|
183
|
+
response.headers.forEach((v, k) => res.setHeader(k, v));
|
|
184
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
185
|
+
}).listen(port, () => {
|
|
186
|
+
const ip = getLocalIPv4();
|
|
187
|
+
const mode = env.includes("dev") ? "development" : "production";
|
|
188
|
+
console.log(
|
|
189
|
+
`
|
|
190
|
+
\u0192 ${name} v${version} ${mode}
|
|
191
|
+
|
|
192
|
+
Local http://localhost:${port}
|
|
193
|
+
Network http://${ip}:${port}
|
|
194
|
+
`
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// ─── Module registration ───────────────────────────────
|
|
199
|
+
register(module) {
|
|
200
|
+
this.modules.set(module.name, module);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
async boot() {
|
|
204
|
+
for (const module of topoSort([...this.modules.values()])) {
|
|
205
|
+
await module.install(this);
|
|
206
|
+
}
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
// Runs global middleware only (no routing, no 404).
|
|
210
|
+
// Adapter packages call this to populate user/workspace/meta into their
|
|
211
|
+
// native context before handing off to user-defined route handlers.
|
|
212
|
+
async buildContext(request) {
|
|
213
|
+
const ctx = {
|
|
214
|
+
request,
|
|
215
|
+
tenant: null,
|
|
216
|
+
user: null,
|
|
217
|
+
workspace: null,
|
|
218
|
+
meta: { _buildContext: true },
|
|
219
|
+
_router: this.router
|
|
220
|
+
};
|
|
221
|
+
await compose(this.middlewares)(ctx, async () => new Response());
|
|
222
|
+
delete ctx.meta["_buildContext"];
|
|
223
|
+
return ctx;
|
|
224
|
+
}
|
|
225
|
+
// ─── Middleware ────────────────────────────────────────
|
|
226
|
+
use(middleware) {
|
|
227
|
+
this.middlewares.push(middleware);
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
// Modules call this to register their routes
|
|
231
|
+
addRoute(method, path, ...handlers) {
|
|
232
|
+
this.router.add(method, this.prefix + path, compose(handlers));
|
|
233
|
+
}
|
|
234
|
+
// ─── The core handler ──────────────────────────────────
|
|
235
|
+
// This is the ONE thing every adapter calls.
|
|
236
|
+
// Takes a Web Standard Request, returns a Web Standard Response.
|
|
237
|
+
async handle(request) {
|
|
238
|
+
const ctx = {
|
|
239
|
+
request,
|
|
240
|
+
tenant: null,
|
|
241
|
+
user: null,
|
|
242
|
+
workspace: null,
|
|
243
|
+
meta: {},
|
|
244
|
+
_router: this.router
|
|
245
|
+
};
|
|
246
|
+
const pipeline = compose([
|
|
247
|
+
...this.middlewares,
|
|
248
|
+
routerMiddleware(this.router),
|
|
249
|
+
notFoundMiddleware()
|
|
250
|
+
]);
|
|
251
|
+
try {
|
|
252
|
+
return await pipeline(ctx, async () => new Response("Not Found", { status: 404 }));
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return this.config.onError?.(err) ?? defaultErrorHandler(err);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
function topoSort(modules) {
|
|
259
|
+
const byName = new Map(modules.map((m) => [m.name, m]));
|
|
260
|
+
const result = [];
|
|
261
|
+
const visited = /* @__PURE__ */ new Set();
|
|
262
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
263
|
+
function visit(m, path) {
|
|
264
|
+
if (visited.has(m.name)) return;
|
|
265
|
+
if (visiting.has(m.name)) {
|
|
266
|
+
throw new Error(`[fonderie] circular dependency: ${[...path, m.name].join(" \u2192 ")}`);
|
|
267
|
+
}
|
|
268
|
+
visiting.add(m.name);
|
|
269
|
+
for (const dep of m.deps ?? []) {
|
|
270
|
+
const found = byName.get(dep);
|
|
271
|
+
if (!found)
|
|
272
|
+
throw new Error(`[fonderie] "${m.name}" requires "${dep}" but it is not registered`);
|
|
273
|
+
visit(found, [...path, m.name]);
|
|
274
|
+
}
|
|
275
|
+
visiting.delete(m.name);
|
|
276
|
+
visited.add(m.name);
|
|
277
|
+
result.push(m);
|
|
278
|
+
}
|
|
279
|
+
for (const m of modules) visit(m, []);
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
function getLocalIPv4() {
|
|
283
|
+
const nets = networkInterfaces();
|
|
284
|
+
for (const interfaces of Object.values(nets)) {
|
|
285
|
+
if (!interfaces) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
for (const iface of interfaces) {
|
|
289
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
290
|
+
return iface.address;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return "127.0.0.1";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/config.ts
|
|
298
|
+
function defineConfig(config) {
|
|
299
|
+
return config;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/parser.ts
|
|
303
|
+
function stringOrEmpty(value) {
|
|
304
|
+
return typeof value === "string" ? value : "";
|
|
305
|
+
}
|
|
306
|
+
function booleanOrFalse(value) {
|
|
307
|
+
if (typeof value === "boolean") return value;
|
|
308
|
+
if (value === "true" || value === "1") return true;
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
function arrayOrEmpty(value) {
|
|
312
|
+
return Array.isArray(value) ? value : [];
|
|
313
|
+
}
|
|
314
|
+
function numberOrZero(value) {
|
|
315
|
+
const n = Number(value);
|
|
316
|
+
return Number.isFinite(n) ? n : 0;
|
|
317
|
+
}
|
|
318
|
+
function dateOrEmpty(value) {
|
|
319
|
+
if (typeof value === "string") return value;
|
|
320
|
+
if (value instanceof Date) return value.toISOString();
|
|
321
|
+
return "";
|
|
322
|
+
}
|
|
323
|
+
export {
|
|
324
|
+
FonderieApp,
|
|
325
|
+
HTTP,
|
|
326
|
+
arrayOrEmpty,
|
|
327
|
+
booleanOrFalse,
|
|
328
|
+
compose,
|
|
329
|
+
dateOrEmpty,
|
|
330
|
+
defineConfig,
|
|
331
|
+
numberOrZero,
|
|
332
|
+
setApiResponse,
|
|
333
|
+
stringOrEmpty
|
|
334
|
+
};
|
|
335
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/app.ts","../src/router.ts","../src/compose.ts","../src/response.ts","../src/middlewares/not-found.ts","../src/middlewares/body-parser.ts","../src/middlewares/error-handler.ts","../src/config.ts","../src/parser.ts"],"sourcesContent":["import { networkInterfaces } from 'node:os';\nimport { createServer } from 'node:http';\n\nimport type { Middleware, IFonderieApp, IFonderieContext, IFonderieModule } from './types';\nimport type { FonderieConfig } from './config';\nimport { Router, routerMiddleware } from './router';\nimport { compose } from './compose';\nimport { notFoundMiddleware, defaultErrorHandler } from './middlewares';\nimport { withBody } from './middlewares/body-parser';\n\nexport class FonderieApp {\n\tprivate config: FonderieConfig;\n\tprivate prefix: string;\n\tprivate router: Router = new Router();\n\tprivate middlewares: Middleware[] = [];\n\tprivate modules: Map<string, IFonderieModule> = new Map();\n\n\tconstructor(config: FonderieConfig) {\n\t\tthis.config = config;\n\t\tthis.prefix = (config.basePath ?? '').replace(/\\/$/, '');\n\t\tthis.middlewares = [withBody];\n\t}\n\n\tlisten(\n\t\tport: number,\n\t\toptions: {\n\t\t\tname?: string;\n\t\t\tversion?: string;\n\t\t\tenv?: string;\n\t\t} = {},\n\t): void {\n\t\tconst {\n\t\t\tname = 'Fonderie',\n\t\t\tversion = '0.0.1',\n\t\t\tenv = process.env['NODE_ENV'] ?? 'development',\n\t\t} = options;\n\n\t\tcreateServer(async (req, res) => {\n\t\t\tconst host = req.headers.host ?? 'localhost';\n\t\t\tconst url = `http://${host}${req.url ?? '/'}`;\n\t\t\tconst headers = new Headers();\n\n\t\t\tfor (const [key, value] of Object.entries(req.headers)) {\n\t\t\t\tif (!value) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tArray.isArray(value)\n\t\t\t\t\t? value.forEach((v) => headers.append(key, v))\n\t\t\t\t\t: headers.set(key, value);\n\t\t\t}\n\n\t\t\t// Read the body stream — this was missing\n\t\t\tconst body = await new Promise<Buffer>((resolve, reject) => {\n\t\t\t\tconst chunks: Buffer[] = [];\n\t\t\t\treq.on('data', (chunk: Buffer) => chunks.push(chunk));\n\t\t\t\treq.on('end', () => resolve(Buffer.concat(chunks)));\n\t\t\t\treq.on('error', reject);\n\t\t\t});\n\n\t\t\tconst method = req.method ?? 'GET';\n\t\t\tconst hasBody = !['GET', 'HEAD'].includes(method.toUpperCase());\n\n\t\t\tconst request = new Request(url, {\n\t\t\t\tmethod,\n\t\t\t\theaders,\n\t\t\t\tbody: hasBody && body.length > 0 ? new Uint8Array(body) : null,\n\t\t\t});\n\n\t\t\tconst response = await this.handle(request);\n\n\t\t\tres.statusCode = response.status;\n\t\t\tresponse.headers.forEach((v, k) => res.setHeader(k, v));\n\t\t\tres.end(Buffer.from(await response.arrayBuffer()));\n\t\t}).listen(port, () => {\n\t\t\tconst ip = getLocalIPv4();\n\t\t\tconst mode = env.includes('dev') ? 'development' : 'production';\n\n\t\t\tconsole.log(\n\t\t\t\t`\\n ƒ ${name} v${version} ${mode}\\n` +\n\t\t\t\t\t`\\n Local http://localhost:${port}` +\n\t\t\t\t\t`\\n Network http://${ip}:${port}\\n`,\n\t\t\t);\n\t\t});\n\t}\n\n\t// ─── Module registration ───────────────────────────────\n\n\tregister(module: IFonderieModule): this {\n\t\tthis.modules.set(module.name, module);\n\t\treturn this;\n\t}\n\n\tasync boot(): Promise<this> {\n\t\tfor (const module of topoSort([...this.modules.values()])) {\n\t\t\tawait module.install(this);\n\t\t}\n\t\treturn this;\n\t}\n\n\t// Runs global middleware only (no routing, no 404).\n\t// Adapter packages call this to populate user/workspace/meta into their\n\t// native context before handing off to user-defined route handlers.\n\tasync buildContext(request: Request): Promise<IFonderieContext> {\n\t\tconst ctx: IFonderieContext = {\n\t\t\trequest,\n\t\t\ttenant: null,\n\t\t\tuser: null,\n\t\t\tworkspace: null,\n\t\t\tmeta: { _buildContext: true },\n\t\t\t_router: this.router,\n\t\t};\n\t\tawait compose(this.middlewares)(ctx, async () => new Response());\n\t\tdelete ctx.meta['_buildContext'];\n\t\treturn ctx;\n\t}\n\n\t// ─── Middleware ────────────────────────────────────────\n\n\tuse(middleware: Middleware): this {\n\t\tthis.middlewares.push(middleware);\n\t\treturn this;\n\t}\n\n\t// Modules call this to register their routes\n\taddRoute(method: string, path: string, ...handlers: Middleware[]): void {\n\t\tthis.router.add(method, this.prefix + path, compose(handlers));\n\t}\n\n\t// ─── The core handler ──────────────────────────────────\n\t// This is the ONE thing every adapter calls.\n\t// Takes a Web Standard Request, returns a Web Standard Response.\n\n\tasync handle(request: Request): Promise<Response> {\n\t\tconst ctx: IFonderieContext = {\n\t\t\trequest,\n\t\t\ttenant: null,\n\t\t\tuser: null,\n\t\t\tworkspace: null,\n\t\t\tmeta: {},\n\t\t\t_router: this.router,\n\t\t};\n\n\t\t// Build the pipeline: global middleware → router → 404\n\t\tconst pipeline = compose([\n\t\t\t...this.middlewares,\n\t\t\trouterMiddleware(this.router),\n\t\t\tnotFoundMiddleware(),\n\t\t]);\n\n\t\ttry {\n\t\t\treturn await pipeline(ctx, async () => new Response('Not Found', { status: 404 }));\n\t\t} catch (err) {\n\t\t\treturn this.config.onError?.(err) ?? defaultErrorHandler(err);\n\t\t}\n\t}\n}\n// Framework adapters live in their own packages — no framework deps in core:\n// @fonderie/adapter-hono\n// @fonderie/adapter-express\n// @fonderie/adapter-koa\n\nfunction topoSort(modules: IFonderieModule[]): IFonderieModule[] {\n\tconst byName = new Map(modules.map((m) => [m.name, m]));\n\tconst result: IFonderieModule[] = [];\n\tconst visited = new Set<string>();\n\tconst visiting = new Set<string>();\n\n\tfunction visit(m: IFonderieModule, path: string[]): void {\n\t\tif (visited.has(m.name)) return;\n\t\tif (visiting.has(m.name)) {\n\t\t\tthrow new Error(`[fonderie] circular dependency: ${[...path, m.name].join(' → ')}`);\n\t\t}\n\t\tvisiting.add(m.name);\n\t\tfor (const dep of m.deps ?? []) {\n\t\t\tconst found = byName.get(dep);\n\t\t\tif (!found)\n\t\t\t\tthrow new Error(`[fonderie] \"${m.name}\" requires \"${dep}\" but it is not registered`);\n\t\t\tvisit(found, [...path, m.name]);\n\t\t}\n\t\tvisiting.delete(m.name);\n\t\tvisited.add(m.name);\n\t\tresult.push(m);\n\t}\n\n\tfor (const m of modules) visit(m, []);\n\treturn result;\n}\n\nfunction getLocalIPv4(): string {\n\tconst nets = networkInterfaces();\n\n\tfor (const interfaces of Object.values(nets)) {\n\t\tif (!interfaces) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const iface of interfaces) {\n\t\t\tif (iface.family === 'IPv4' && !iface.internal) {\n\t\t\t\treturn iface.address;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn '127.0.0.1'; // fallback if no external interface found\n}\n","import type { IRouter, Middleware, IRouteMatch, IFonderieContext } from './types';\n\nexport class Router implements IRouter {\n\tprivate routes: Array<{ method: string; path: string; handler: Middleware }> = [];\n\n\tadd(method: string, path: string, handler: Middleware): void {\n\t\tthis.routes.push({ method: method.toUpperCase(), path, handler });\n\t}\n\n\tmatch(method: string, path: string): IRouteMatch | null {\n\t\tfor (const route of this.routes) {\n\t\t\tif (route.method !== method.toUpperCase()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst params = matchPath(route.path, path);\n\t\t\tif (params !== null) {\n\t\t\t\treturn { handler: route.handler, params };\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n}\n\n// Segment-by-segment match with :param extraction\n// /users/:id matches /users/42 → { id: '42' }\nfunction matchPath(pattern: string, path: string): Record<string, string> | null {\n\tconst clean = (path.split('?')[0] ?? path).replace(/\\/$/, '') || '/'; // strip query string and trailing slash\n\tconst pp = pattern.split('/');\n\tconst vp = clean.split('/');\n\n\tif (pp.length !== vp.length) {\n\t\treturn null;\n\t}\n\n\tconst params: Record<string, string> = {};\n\n\tfor (let i = 0; i < pp.length; i++) {\n\t\tconst ps = pp[i] ?? '';\n\t\tconst vs = vp[i] ?? '';\n\t\tif (ps.startsWith(':')) {\n\t\t\tparams[ps.slice(1)] = decodeURIComponent(vs);\n\t\t} else if (ps !== vs) {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\treturn params;\n}\n\n// Middleware that runs the router inside the pipeline\nexport function routerMiddleware(router: Router): Middleware {\n\treturn async (ctx: IFonderieContext, next) => {\n\t\tconst url = new URL(ctx.request.url);\n\t\tconst match = router.match(ctx.request.method, url.pathname);\n\n\t\tif (!match) {\n\t\t\treturn next();\n\t\t}\n\n\t\t// Route params available to handlers via ctx.meta.params\n\t\tctx.meta.params = match.params;\n\t\treturn match.handler(ctx, next);\n\t};\n}\n","import type { IFonderieContext, Middleware } from './types';\n\n// Classic onion middleware compose — same pattern as Koa's\nexport function compose(middlewares: Middleware[]) {\n\treturn function (ctx: IFonderieContext, fallback: () => Promise<Response>): Promise<Response> {\n\t\tlet index = -1;\n\n\t\tfunction dispatch(i: number): Promise<Response> {\n\t\t\tif (i <= index) {\n\t\t\t\tthrow new Error('next() called multiple times');\n\t\t\t}\n\t\t\tindex = i;\n\t\t\tconst fn = middlewares[i] ?? fallback;\n\t\t\treturn fn(ctx, () => dispatch(i + 1));\n\t\t}\n\n\t\treturn dispatch(0);\n\t};\n}\n","export const HTTP = {\n\tOK: 200,\n\tCREATED: 201,\n\tACCEPTED: 202,\n\tNO_CONTENT: 204,\n\tBAD_REQUEST: 400,\n\tUNAUTHORIZED: 401,\n\tPAYMENT_REQUIRED: 402,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tCONFLICT: 409,\n\tGONE: 410,\n\tUNPROCESSABLE: 422,\n\tTOO_MANY_REQUESTS: 429,\n\tSERVER_ERROR: 500,\n\tNOT_IMPLEMENTED: 501,\n\tBAD_GATEWAY: 502,\n\tSERVICE_UNAVAILABLE: 503,\n} as const;\n\nexport type HttpStatus = (typeof HTTP)[keyof typeof HTTP];\n\nexport interface IApiEnvelope {\n\treason: string;\n\texplanation: string;\n\tresult?: unknown;\n}\n\nexport interface IApiError {\n\treason: string;\n\texplanation: string;\n\tdetails?: unknown;\n}\n\nexport function setApiResponse<T>(\n\tstatus: number,\n\treason: string,\n\texplanation: string,\n\tpayload?: T,\n): Response {\n\tconst body: Record<string, unknown> = { reason, explanation };\n\tif (payload !== undefined) {\n\t\tbody[status < 400 ? 'result' : 'details'] = payload;\n\t}\n\treturn Response.json(body, { status });\n}\n","import { setApiResponse, HTTP } from '../response';\nimport type { Middleware } from '../types';\n\nexport function notFoundMiddleware(): Middleware {\n\treturn async (_ctx, _next) => setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Not found');\n}\n","import type { Middleware } from '../types';\nimport { setApiResponse, HTTP } from '../response';\n\nexport const withBody: Middleware = async (ctx, next) => {\n\tconst method = ctx.request.method.toUpperCase();\n\n\tif (method === 'GET' || method === 'HEAD') {\n\t\treturn next();\n\t}\n\n\tconst ct = ctx.request.headers.get('content-type') ?? '';\n\n\ttry {\n\t\tif (ct.includes('application/json')) {\n\t\t\tconst text = (await ctx.request.clone().text()).trim();\n\t\t\tctx.meta.body = text ? JSON.parse(text) : {};\n\t\t} else if (ct.includes('application/x-www-form-urlencoded')) {\n\t\t\tconst text = await ctx.request.clone().text();\n\t\t\tctx.meta.body = Object.fromEntries(new URLSearchParams(text));\n\t\t}\n\t\t// multipart/form-data left to the handler — no dep-free way to parse it\n\t} catch {\n\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_REQUEST', 'Invalid request body');\n\t}\n\n\treturn next();\n};\n","import { setApiResponse, HTTP } from '../response';\n\nexport function defaultErrorHandler(err: unknown): Response {\n\tconst dev = process.env['NODE_ENV'] !== 'production';\n\n\tif (err instanceof Error) {\n\t\tconsole.error('[fonderie]', err.message, err.stack);\n\t\treturn setApiResponse(\n\t\t\tHTTP.SERVER_ERROR,\n\t\t\t'SERVER_ERROR',\n\t\t\tdev ? err.message : 'Internal server error',\n\t\t);\n\t}\n\n\tconsole.error('[fonderie] unknown error', err);\n\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Internal server error');\n}\n","export interface IBillingPlan {\n\tname: string;\n\tprice: number | null; // null = custom/enterprise pricing\n\tseats: number | 'unlimited';\n\tinterval?: 'month' | 'year';\n\ttrialDays?: number;\n}\n\nexport interface ISMTPConfig {\n\thost: string;\n\tport: number;\n\tsecure: boolean; // true = TLS, false = STARTTLS\n\tuser: string;\n\tpass: string;\n}\n\nexport interface FonderieConfig {\n\tbasePath?: string; // e.g. '/v1' — prefixes all routes; defaults to ''\n\n\tdb: {\n\t\turl: string; // Standard postgres:// connection string\n\t\t// Future: adapter pattern for other vendors\n\t\t// adapter?: 'pg' | 'mysql2' | 'oracledb' ← v2 concern\n\t};\n\n\tbilling?: {\n\t\tprovider: 'stripe';\n\t\tplans: IBillingPlan[];\n\t\tstripeSecretKey: string;\n\t};\n\n\temail?: {\n\t\tfrom: string;\n\t\t// Credentials from their .env — Fonderie never stores these\n\t\tapiKey?: string;\n\t\tsmtp?: ISMTPConfig;\n\t\tprovider: 'resend' | 'ses' | 'smtp';\n\t};\n\n\tonError?: (err: unknown) => Response;\n}\n\nexport function defineConfig(config: FonderieConfig): FonderieConfig {\n\treturn config; // typed identity — same pattern as defineConfig in Vite/Nuxt\n}\n","export function stringOrEmpty(value: unknown): string {\n\treturn typeof value === 'string' ? value : '';\n}\n\nexport function booleanOrFalse(value: unknown): boolean {\n\tif (typeof value === 'boolean') return value;\n\tif (value === 'true' || value === '1') return true;\n\treturn false;\n}\n\nexport function arrayOrEmpty<T>(value: unknown): T[] {\n\treturn Array.isArray(value) ? (value as T[]) : [];\n}\n\nexport function numberOrZero(value: unknown): number {\n\tconst n = Number(value);\n\treturn Number.isFinite(n) ? n : 0;\n}\n\nexport function dateOrEmpty(value: unknown): string {\n\tif (typeof value === 'string') return value;\n\tif (value instanceof Date) return value.toISOString();\n\treturn '';\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/middlewares/index.ts
|
|
21
|
+
var middlewares_exports = {};
|
|
22
|
+
__export(middlewares_exports, {
|
|
23
|
+
defaultErrorHandler: () => defaultErrorHandler,
|
|
24
|
+
notFoundMiddleware: () => notFoundMiddleware,
|
|
25
|
+
requireAnyAuth: () => requireAnyAuth,
|
|
26
|
+
requireAuth: () => requireAuth,
|
|
27
|
+
requireVerified: () => requireVerified,
|
|
28
|
+
withBody: () => withBody,
|
|
29
|
+
withCors: () => withCors,
|
|
30
|
+
withLogger: () => withLogger
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(middlewares_exports);
|
|
33
|
+
|
|
34
|
+
// src/middlewares/cors.ts
|
|
35
|
+
function withCors(options = {}) {
|
|
36
|
+
const {
|
|
37
|
+
origin = "*",
|
|
38
|
+
headers = ["Content-Type", "Authorization"],
|
|
39
|
+
methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
40
|
+
} = options;
|
|
41
|
+
return async (ctx, next) => {
|
|
42
|
+
const requestOrigin = ctx.request.headers.get("origin") ?? "";
|
|
43
|
+
const allowOrigin = typeof origin === "function" ? origin(requestOrigin) ? requestOrigin : "" : origin;
|
|
44
|
+
const corsHeaders = {
|
|
45
|
+
"Access-Control-Max-Age": "86400",
|
|
46
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
47
|
+
"Access-Control-Allow-Methods": methods.join(", "),
|
|
48
|
+
"Access-Control-Allow-Headers": headers.join(", ")
|
|
49
|
+
};
|
|
50
|
+
if (ctx.request.method === "OPTIONS") {
|
|
51
|
+
return new Response(null, { status: 204, headers: corsHeaders });
|
|
52
|
+
}
|
|
53
|
+
const response = await next();
|
|
54
|
+
const patched = new Headers(response.headers);
|
|
55
|
+
for (const [k, v] of Object.entries(corsHeaders)) {
|
|
56
|
+
patched.set(k, v);
|
|
57
|
+
}
|
|
58
|
+
return new Response(response.body, {
|
|
59
|
+
headers: patched,
|
|
60
|
+
status: response.status,
|
|
61
|
+
statusText: response.statusText
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/middlewares/logger.ts
|
|
67
|
+
var withLogger = async (ctx, next) => {
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
const { method, url } = ctx.request;
|
|
70
|
+
const { pathname } = new URL(url);
|
|
71
|
+
const response = await next();
|
|
72
|
+
console.log(
|
|
73
|
+
JSON.stringify({
|
|
74
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
75
|
+
method,
|
|
76
|
+
path: pathname,
|
|
77
|
+
status: response.status,
|
|
78
|
+
ms: Date.now() - start
|
|
79
|
+
})
|
|
80
|
+
);
|
|
81
|
+
return response;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// src/response.ts
|
|
85
|
+
var HTTP = {
|
|
86
|
+
OK: 200,
|
|
87
|
+
CREATED: 201,
|
|
88
|
+
ACCEPTED: 202,
|
|
89
|
+
NO_CONTENT: 204,
|
|
90
|
+
BAD_REQUEST: 400,
|
|
91
|
+
UNAUTHORIZED: 401,
|
|
92
|
+
PAYMENT_REQUIRED: 402,
|
|
93
|
+
FORBIDDEN: 403,
|
|
94
|
+
NOT_FOUND: 404,
|
|
95
|
+
CONFLICT: 409,
|
|
96
|
+
GONE: 410,
|
|
97
|
+
UNPROCESSABLE: 422,
|
|
98
|
+
TOO_MANY_REQUESTS: 429,
|
|
99
|
+
SERVER_ERROR: 500,
|
|
100
|
+
NOT_IMPLEMENTED: 501,
|
|
101
|
+
BAD_GATEWAY: 502,
|
|
102
|
+
SERVICE_UNAVAILABLE: 503
|
|
103
|
+
};
|
|
104
|
+
function setApiResponse(status, reason, explanation, payload) {
|
|
105
|
+
const body = { reason, explanation };
|
|
106
|
+
if (payload !== void 0) {
|
|
107
|
+
body[status < 400 ? "result" : "details"] = payload;
|
|
108
|
+
}
|
|
109
|
+
return Response.json(body, { status });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/middlewares/not-found.ts
|
|
113
|
+
function notFoundMiddleware() {
|
|
114
|
+
return async (_ctx, _next) => setApiResponse(HTTP.NOT_FOUND, "NOT_FOUND", "Not found");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/middlewares/body-parser.ts
|
|
118
|
+
var withBody = async (ctx, next) => {
|
|
119
|
+
const method = ctx.request.method.toUpperCase();
|
|
120
|
+
if (method === "GET" || method === "HEAD") {
|
|
121
|
+
return next();
|
|
122
|
+
}
|
|
123
|
+
const ct = ctx.request.headers.get("content-type") ?? "";
|
|
124
|
+
try {
|
|
125
|
+
if (ct.includes("application/json")) {
|
|
126
|
+
const text = (await ctx.request.clone().text()).trim();
|
|
127
|
+
ctx.meta.body = text ? JSON.parse(text) : {};
|
|
128
|
+
} else if (ct.includes("application/x-www-form-urlencoded")) {
|
|
129
|
+
const text = await ctx.request.clone().text();
|
|
130
|
+
ctx.meta.body = Object.fromEntries(new URLSearchParams(text));
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
return setApiResponse(HTTP.BAD_REQUEST, "INVALID_REQUEST", "Invalid request body");
|
|
134
|
+
}
|
|
135
|
+
return next();
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/middlewares/error-handler.ts
|
|
139
|
+
function defaultErrorHandler(err) {
|
|
140
|
+
const dev = process.env["NODE_ENV"] !== "production";
|
|
141
|
+
if (err instanceof Error) {
|
|
142
|
+
console.error("[fonderie]", err.message, err.stack);
|
|
143
|
+
return setApiResponse(
|
|
144
|
+
HTTP.SERVER_ERROR,
|
|
145
|
+
"SERVER_ERROR",
|
|
146
|
+
dev ? err.message : "Internal server error"
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
console.error("[fonderie] unknown error", err);
|
|
150
|
+
return setApiResponse(HTTP.SERVER_ERROR, "SERVER_ERROR", "Internal server error");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/middlewares/require-auth.ts
|
|
154
|
+
var requireAuth = async (ctx, next) => {
|
|
155
|
+
if (!ctx.user) {
|
|
156
|
+
return setApiResponse(HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
157
|
+
}
|
|
158
|
+
if (ctx.user.mfaPending) {
|
|
159
|
+
return setApiResponse(HTTP.FORBIDDEN, "MFA_REQUIRED", "Complete MFA verification to continue");
|
|
160
|
+
}
|
|
161
|
+
return next();
|
|
162
|
+
};
|
|
163
|
+
var requireAnyAuth = async (ctx, next) => {
|
|
164
|
+
if (!ctx.user) {
|
|
165
|
+
return setApiResponse(HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
166
|
+
}
|
|
167
|
+
return next();
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// src/middlewares/require-verified.ts
|
|
171
|
+
var requireVerified = async (ctx, next) => {
|
|
172
|
+
if (!ctx.user) {
|
|
173
|
+
return setApiResponse(HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
|
|
174
|
+
}
|
|
175
|
+
if (ctx.user.loginMethod === "phone") {
|
|
176
|
+
if (!ctx.user.phoneVerified) {
|
|
177
|
+
return setApiResponse(
|
|
178
|
+
HTTP.FORBIDDEN,
|
|
179
|
+
"PHONE_NOT_VERIFIED",
|
|
180
|
+
"Please verify your phone number"
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return next();
|
|
184
|
+
}
|
|
185
|
+
if (!ctx.user.emailVerifiedAt) {
|
|
186
|
+
return setApiResponse(HTTP.FORBIDDEN, "EMAIL_NOT_VERIFIED", "Please verify your email address");
|
|
187
|
+
}
|
|
188
|
+
return next();
|
|
189
|
+
};
|
|
190
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
191
|
+
0 && (module.exports = {
|
|
192
|
+
defaultErrorHandler,
|
|
193
|
+
notFoundMiddleware,
|
|
194
|
+
requireAnyAuth,
|
|
195
|
+
requireAuth,
|
|
196
|
+
requireVerified,
|
|
197
|
+
withBody,
|
|
198
|
+
withCors,
|
|
199
|
+
withLogger
|
|
200
|
+
});
|
|
201
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/middlewares/index.ts","../../src/middlewares/cors.ts","../../src/middlewares/logger.ts","../../src/response.ts","../../src/middlewares/not-found.ts","../../src/middlewares/body-parser.ts","../../src/middlewares/error-handler.ts","../../src/middlewares/require-auth.ts","../../src/middlewares/require-verified.ts"],"sourcesContent":["export type { CorsOptions } from './cors';\nexport { withCors } from './cors';\nexport { withLogger } from './logger';\nexport { notFoundMiddleware } from './not-found';\nexport { withBody } from './body-parser';\nexport { defaultErrorHandler } from './error-handler';\nexport { requireAuth, requireAnyAuth } from './require-auth';\nexport { requireVerified } from './require-verified';\n","import type { Middleware } from '../types';\n\nexport interface CorsOptions {\n\tmethods?: string[];\n\theaders?: string[];\n\torigin?: string | ((requestOrigin: string) => boolean);\n}\n\nexport function withCors(options: CorsOptions = {}): Middleware {\n\tconst {\n\t\torigin = '*',\n\t\theaders = ['Content-Type', 'Authorization'],\n\t\tmethods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n\t} = options;\n\n\treturn async (ctx, next) => {\n\t\tconst requestOrigin = ctx.request.headers.get('origin') ?? '';\n\n\t\tconst allowOrigin =\n\t\t\ttypeof origin === 'function' ? (origin(requestOrigin) ? requestOrigin : '') : origin;\n\n\t\tconst corsHeaders: Record<string, string> = {\n\t\t\t'Access-Control-Max-Age': '86400',\n\t\t\t'Access-Control-Allow-Origin': allowOrigin,\n\t\t\t'Access-Control-Allow-Methods': methods.join(', '),\n\t\t\t'Access-Control-Allow-Headers': headers.join(', '),\n\t\t};\n\n\t\t// Preflight — respond immediately, skip the pipeline\n\t\tif (ctx.request.method === 'OPTIONS') {\n\t\t\treturn new Response(null, { status: 204, headers: corsHeaders });\n\t\t}\n\n\t\tconst response = await next();\n\n\t\tconst patched = new Headers(response.headers);\n\n\t\tfor (const [k, v] of Object.entries(corsHeaders)) {\n\t\t\tpatched.set(k, v);\n\t\t}\n\n\t\treturn new Response(response.body, {\n\t\t\theaders: patched,\n\t\t\tstatus: response.status,\n\t\t\tstatusText: response.statusText,\n\t\t});\n\t};\n}\n","import type { Middleware } from '../types';\n\nexport const withLogger: Middleware = async (ctx, next) => {\n\tconst start = Date.now();\n\n\tconst { method, url } = ctx.request;\n\tconst { pathname } = new URL(url);\n\n\tconst response = await next();\n\n\tconsole.log(\n\t\tJSON.stringify({\n\t\t\tts: new Date().toISOString(),\n\t\t\tmethod,\n\t\t\tpath: pathname,\n\t\t\tstatus: response.status,\n\t\t\tms: Date.now() - start,\n\t\t}),\n\t);\n\n\treturn response;\n};\n","export const HTTP = {\n\tOK: 200,\n\tCREATED: 201,\n\tACCEPTED: 202,\n\tNO_CONTENT: 204,\n\tBAD_REQUEST: 400,\n\tUNAUTHORIZED: 401,\n\tPAYMENT_REQUIRED: 402,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tCONFLICT: 409,\n\tGONE: 410,\n\tUNPROCESSABLE: 422,\n\tTOO_MANY_REQUESTS: 429,\n\tSERVER_ERROR: 500,\n\tNOT_IMPLEMENTED: 501,\n\tBAD_GATEWAY: 502,\n\tSERVICE_UNAVAILABLE: 503,\n} as const;\n\nexport type HttpStatus = (typeof HTTP)[keyof typeof HTTP];\n\nexport interface IApiEnvelope {\n\treason: string;\n\texplanation: string;\n\tresult?: unknown;\n}\n\nexport interface IApiError {\n\treason: string;\n\texplanation: string;\n\tdetails?: unknown;\n}\n\nexport function setApiResponse<T>(\n\tstatus: number,\n\treason: string,\n\texplanation: string,\n\tpayload?: T,\n): Response {\n\tconst body: Record<string, unknown> = { reason, explanation };\n\tif (payload !== undefined) {\n\t\tbody[status < 400 ? 'result' : 'details'] = payload;\n\t}\n\treturn Response.json(body, { status });\n}\n","import { setApiResponse, HTTP } from '../response';\nimport type { Middleware } from '../types';\n\nexport function notFoundMiddleware(): Middleware {\n\treturn async (_ctx, _next) => setApiResponse(HTTP.NOT_FOUND, 'NOT_FOUND', 'Not found');\n}\n","import type { Middleware } from '../types';\nimport { setApiResponse, HTTP } from '../response';\n\nexport const withBody: Middleware = async (ctx, next) => {\n\tconst method = ctx.request.method.toUpperCase();\n\n\tif (method === 'GET' || method === 'HEAD') {\n\t\treturn next();\n\t}\n\n\tconst ct = ctx.request.headers.get('content-type') ?? '';\n\n\ttry {\n\t\tif (ct.includes('application/json')) {\n\t\t\tconst text = (await ctx.request.clone().text()).trim();\n\t\t\tctx.meta.body = text ? JSON.parse(text) : {};\n\t\t} else if (ct.includes('application/x-www-form-urlencoded')) {\n\t\t\tconst text = await ctx.request.clone().text();\n\t\t\tctx.meta.body = Object.fromEntries(new URLSearchParams(text));\n\t\t}\n\t\t// multipart/form-data left to the handler — no dep-free way to parse it\n\t} catch {\n\t\treturn setApiResponse(HTTP.BAD_REQUEST, 'INVALID_REQUEST', 'Invalid request body');\n\t}\n\n\treturn next();\n};\n","import { setApiResponse, HTTP } from '../response';\n\nexport function defaultErrorHandler(err: unknown): Response {\n\tconst dev = process.env['NODE_ENV'] !== 'production';\n\n\tif (err instanceof Error) {\n\t\tconsole.error('[fonderie]', err.message, err.stack);\n\t\treturn setApiResponse(\n\t\t\tHTTP.SERVER_ERROR,\n\t\t\t'SERVER_ERROR',\n\t\t\tdev ? err.message : 'Internal server error',\n\t\t);\n\t}\n\n\tconsole.error('[fonderie] unknown error', err);\n\treturn setApiResponse(HTTP.SERVER_ERROR, 'SERVER_ERROR', 'Internal server error');\n}\n","import { setApiResponse, HTTP } from '../response';\nimport type { Middleware } from '../types';\n\n// Requires a fully-authenticated user. Rejects mfaPending tokens — those are\n// short-lived pre-auth tokens issued mid-MFA-login and must not grant access\n// to any route other than /auth/mfa/verify.\nexport const requireAuth: Middleware = async (ctx, next) => {\n\tif (!ctx.user) {\n\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t}\n\tif (ctx.user.mfaPending) {\n\t\treturn setApiResponse(HTTP.FORBIDDEN, 'MFA_REQUIRED', 'Complete MFA verification to continue');\n\t}\n\treturn next();\n};\n\n// Accepts both fully-authenticated and mfaPending tokens. Only for routes that\n// need to serve both contexts on the same path (e.g. POST /auth/mfa/verify\n// handles setup confirmation with a full token and TOTP login with mfaPending).\nexport const requireAnyAuth: Middleware = async (ctx, next) => {\n\tif (!ctx.user) {\n\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t}\n\treturn next();\n};\n","import { setApiResponse, HTTP } from '../response';\nimport type { Middleware } from '../types';\n\nexport const requireVerified: Middleware = async (ctx, next) => {\n\tif (!ctx.user) {\n\t\treturn setApiResponse(HTTP.UNAUTHORIZED, 'UNAUTHORIZED', 'Unauthorized');\n\t}\n\n\tif (ctx.user.loginMethod === 'phone') {\n\t\tif (!ctx.user.phoneVerified) {\n\t\t\treturn setApiResponse(\n\t\t\t\tHTTP.FORBIDDEN,\n\t\t\t\t'PHONE_NOT_VERIFIED',\n\t\t\t\t'Please verify your phone number',\n\t\t\t);\n\t\t}\n\t\treturn next();\n\t}\n\n\tif (!ctx.user.emailVerifiedAt) {\n\t\treturn setApiResponse(HTTP.FORBIDDEN, 'EMAIL_NOT_VERIFIED', 'Please verify your email address');\n\t}\n\n\treturn next();\n};\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Middleware } from '../types.cjs';
|
|
2
|
+
|
|
3
|
+
interface CorsOptions {
|
|
4
|
+
methods?: string[];
|
|
5
|
+
headers?: string[];
|
|
6
|
+
origin?: string | ((requestOrigin: string) => boolean);
|
|
7
|
+
}
|
|
8
|
+
declare function withCors(options?: CorsOptions): Middleware;
|
|
9
|
+
|
|
10
|
+
declare const withLogger: Middleware;
|
|
11
|
+
|
|
12
|
+
declare function notFoundMiddleware(): Middleware;
|
|
13
|
+
|
|
14
|
+
declare const withBody: Middleware;
|
|
15
|
+
|
|
16
|
+
declare function defaultErrorHandler(err: unknown): Response;
|
|
17
|
+
|
|
18
|
+
declare const requireAuth: Middleware;
|
|
19
|
+
declare const requireAnyAuth: Middleware;
|
|
20
|
+
|
|
21
|
+
declare const requireVerified: Middleware;
|
|
22
|
+
|
|
23
|
+
export { type CorsOptions, defaultErrorHandler, notFoundMiddleware, requireAnyAuth, requireAuth, requireVerified, withBody, withCors, withLogger };
|