@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.
@@ -0,0 +1,23 @@
1
+ import { Middleware } from '../types.js';
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 };
@@ -0,0 +1,167 @@
1
+ // src/middlewares/cors.ts
2
+ function withCors(options = {}) {
3
+ const {
4
+ origin = "*",
5
+ headers = ["Content-Type", "Authorization"],
6
+ methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
7
+ } = options;
8
+ return async (ctx, next) => {
9
+ const requestOrigin = ctx.request.headers.get("origin") ?? "";
10
+ const allowOrigin = typeof origin === "function" ? origin(requestOrigin) ? requestOrigin : "" : origin;
11
+ const corsHeaders = {
12
+ "Access-Control-Max-Age": "86400",
13
+ "Access-Control-Allow-Origin": allowOrigin,
14
+ "Access-Control-Allow-Methods": methods.join(", "),
15
+ "Access-Control-Allow-Headers": headers.join(", ")
16
+ };
17
+ if (ctx.request.method === "OPTIONS") {
18
+ return new Response(null, { status: 204, headers: corsHeaders });
19
+ }
20
+ const response = await next();
21
+ const patched = new Headers(response.headers);
22
+ for (const [k, v] of Object.entries(corsHeaders)) {
23
+ patched.set(k, v);
24
+ }
25
+ return new Response(response.body, {
26
+ headers: patched,
27
+ status: response.status,
28
+ statusText: response.statusText
29
+ });
30
+ };
31
+ }
32
+
33
+ // src/middlewares/logger.ts
34
+ var withLogger = async (ctx, next) => {
35
+ const start = Date.now();
36
+ const { method, url } = ctx.request;
37
+ const { pathname } = new URL(url);
38
+ const response = await next();
39
+ console.log(
40
+ JSON.stringify({
41
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
42
+ method,
43
+ path: pathname,
44
+ status: response.status,
45
+ ms: Date.now() - start
46
+ })
47
+ );
48
+ return response;
49
+ };
50
+
51
+ // src/response.ts
52
+ var HTTP = {
53
+ OK: 200,
54
+ CREATED: 201,
55
+ ACCEPTED: 202,
56
+ NO_CONTENT: 204,
57
+ BAD_REQUEST: 400,
58
+ UNAUTHORIZED: 401,
59
+ PAYMENT_REQUIRED: 402,
60
+ FORBIDDEN: 403,
61
+ NOT_FOUND: 404,
62
+ CONFLICT: 409,
63
+ GONE: 410,
64
+ UNPROCESSABLE: 422,
65
+ TOO_MANY_REQUESTS: 429,
66
+ SERVER_ERROR: 500,
67
+ NOT_IMPLEMENTED: 501,
68
+ BAD_GATEWAY: 502,
69
+ SERVICE_UNAVAILABLE: 503
70
+ };
71
+ function setApiResponse(status, reason, explanation, payload) {
72
+ const body = { reason, explanation };
73
+ if (payload !== void 0) {
74
+ body[status < 400 ? "result" : "details"] = payload;
75
+ }
76
+ return Response.json(body, { status });
77
+ }
78
+
79
+ // src/middlewares/not-found.ts
80
+ function notFoundMiddleware() {
81
+ return async (_ctx, _next) => setApiResponse(HTTP.NOT_FOUND, "NOT_FOUND", "Not found");
82
+ }
83
+
84
+ // src/middlewares/body-parser.ts
85
+ var withBody = async (ctx, next) => {
86
+ const method = ctx.request.method.toUpperCase();
87
+ if (method === "GET" || method === "HEAD") {
88
+ return next();
89
+ }
90
+ const ct = ctx.request.headers.get("content-type") ?? "";
91
+ try {
92
+ if (ct.includes("application/json")) {
93
+ const text = (await ctx.request.clone().text()).trim();
94
+ ctx.meta.body = text ? JSON.parse(text) : {};
95
+ } else if (ct.includes("application/x-www-form-urlencoded")) {
96
+ const text = await ctx.request.clone().text();
97
+ ctx.meta.body = Object.fromEntries(new URLSearchParams(text));
98
+ }
99
+ } catch {
100
+ return setApiResponse(HTTP.BAD_REQUEST, "INVALID_REQUEST", "Invalid request body");
101
+ }
102
+ return next();
103
+ };
104
+
105
+ // src/middlewares/error-handler.ts
106
+ function defaultErrorHandler(err) {
107
+ const dev = process.env["NODE_ENV"] !== "production";
108
+ if (err instanceof Error) {
109
+ console.error("[fonderie]", err.message, err.stack);
110
+ return setApiResponse(
111
+ HTTP.SERVER_ERROR,
112
+ "SERVER_ERROR",
113
+ dev ? err.message : "Internal server error"
114
+ );
115
+ }
116
+ console.error("[fonderie] unknown error", err);
117
+ return setApiResponse(HTTP.SERVER_ERROR, "SERVER_ERROR", "Internal server error");
118
+ }
119
+
120
+ // src/middlewares/require-auth.ts
121
+ var requireAuth = async (ctx, next) => {
122
+ if (!ctx.user) {
123
+ return setApiResponse(HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
124
+ }
125
+ if (ctx.user.mfaPending) {
126
+ return setApiResponse(HTTP.FORBIDDEN, "MFA_REQUIRED", "Complete MFA verification to continue");
127
+ }
128
+ return next();
129
+ };
130
+ var requireAnyAuth = async (ctx, next) => {
131
+ if (!ctx.user) {
132
+ return setApiResponse(HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
133
+ }
134
+ return next();
135
+ };
136
+
137
+ // src/middlewares/require-verified.ts
138
+ var requireVerified = async (ctx, next) => {
139
+ if (!ctx.user) {
140
+ return setApiResponse(HTTP.UNAUTHORIZED, "UNAUTHORIZED", "Unauthorized");
141
+ }
142
+ if (ctx.user.loginMethod === "phone") {
143
+ if (!ctx.user.phoneVerified) {
144
+ return setApiResponse(
145
+ HTTP.FORBIDDEN,
146
+ "PHONE_NOT_VERIFIED",
147
+ "Please verify your phone number"
148
+ );
149
+ }
150
+ return next();
151
+ }
152
+ if (!ctx.user.emailVerifiedAt) {
153
+ return setApiResponse(HTTP.FORBIDDEN, "EMAIL_NOT_VERIFIED", "Please verify your email address");
154
+ }
155
+ return next();
156
+ };
157
+ export {
158
+ defaultErrorHandler,
159
+ notFoundMiddleware,
160
+ requireAnyAuth,
161
+ requireAuth,
162
+ requireVerified,
163
+ withBody,
164
+ withCors,
165
+ withLogger
166
+ };
167
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../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":["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,58 @@
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/parser.ts
21
+ var parser_exports = {};
22
+ __export(parser_exports, {
23
+ arrayOrEmpty: () => arrayOrEmpty,
24
+ booleanOrFalse: () => booleanOrFalse,
25
+ dateOrEmpty: () => dateOrEmpty,
26
+ numberOrZero: () => numberOrZero,
27
+ stringOrEmpty: () => stringOrEmpty
28
+ });
29
+ module.exports = __toCommonJS(parser_exports);
30
+ function stringOrEmpty(value) {
31
+ return typeof value === "string" ? value : "";
32
+ }
33
+ function booleanOrFalse(value) {
34
+ if (typeof value === "boolean") return value;
35
+ if (value === "true" || value === "1") return true;
36
+ return false;
37
+ }
38
+ function arrayOrEmpty(value) {
39
+ return Array.isArray(value) ? value : [];
40
+ }
41
+ function numberOrZero(value) {
42
+ const n = Number(value);
43
+ return Number.isFinite(n) ? n : 0;
44
+ }
45
+ function dateOrEmpty(value) {
46
+ if (typeof value === "string") return value;
47
+ if (value instanceof Date) return value.toISOString();
48
+ return "";
49
+ }
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ arrayOrEmpty,
53
+ booleanOrFalse,
54
+ dateOrEmpty,
55
+ numberOrZero,
56
+ stringOrEmpty
57
+ });
58
+ //# sourceMappingURL=parser.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser.ts"],"sourcesContent":["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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,SAAS,cAAc,OAAwB;AACrD,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC5C;AAEO,SAAS,eAAe,OAAyB;AACvD,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,UAAU,UAAU,UAAU,IAAK,QAAO;AAC9C,SAAO;AACR;AAEO,SAAS,aAAgB,OAAqB;AACpD,SAAO,MAAM,QAAQ,KAAK,IAAK,QAAgB,CAAC;AACjD;AAEO,SAAS,aAAa,OAAwB;AACpD,QAAM,IAAI,OAAO,KAAK;AACtB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AACjC;AAEO,SAAS,YAAY,OAAwB;AACnD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,SAAO;AACR;","names":[]}
@@ -0,0 +1,7 @@
1
+ declare function stringOrEmpty(value: unknown): string;
2
+ declare function booleanOrFalse(value: unknown): boolean;
3
+ declare function arrayOrEmpty<T>(value: unknown): T[];
4
+ declare function numberOrZero(value: unknown): number;
5
+ declare function dateOrEmpty(value: unknown): string;
6
+
7
+ export { arrayOrEmpty, booleanOrFalse, dateOrEmpty, numberOrZero, stringOrEmpty };
@@ -0,0 +1,7 @@
1
+ declare function stringOrEmpty(value: unknown): string;
2
+ declare function booleanOrFalse(value: unknown): boolean;
3
+ declare function arrayOrEmpty<T>(value: unknown): T[];
4
+ declare function numberOrZero(value: unknown): number;
5
+ declare function dateOrEmpty(value: unknown): string;
6
+
7
+ export { arrayOrEmpty, booleanOrFalse, dateOrEmpty, numberOrZero, stringOrEmpty };
package/dist/parser.js ADDED
@@ -0,0 +1,29 @@
1
+ // src/parser.ts
2
+ function stringOrEmpty(value) {
3
+ return typeof value === "string" ? value : "";
4
+ }
5
+ function booleanOrFalse(value) {
6
+ if (typeof value === "boolean") return value;
7
+ if (value === "true" || value === "1") return true;
8
+ return false;
9
+ }
10
+ function arrayOrEmpty(value) {
11
+ return Array.isArray(value) ? value : [];
12
+ }
13
+ function numberOrZero(value) {
14
+ const n = Number(value);
15
+ return Number.isFinite(n) ? n : 0;
16
+ }
17
+ function dateOrEmpty(value) {
18
+ if (typeof value === "string") return value;
19
+ if (value instanceof Date) return value.toISOString();
20
+ return "";
21
+ }
22
+ export {
23
+ arrayOrEmpty,
24
+ booleanOrFalse,
25
+ dateOrEmpty,
26
+ numberOrZero,
27
+ stringOrEmpty
28
+ };
29
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parser.ts"],"sourcesContent":["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":";AAAO,SAAS,cAAc,OAAwB;AACrD,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC5C;AAEO,SAAS,eAAe,OAAyB;AACvD,MAAI,OAAO,UAAU,UAAW,QAAO;AACvC,MAAI,UAAU,UAAU,UAAU,IAAK,QAAO;AAC9C,SAAO;AACR;AAEO,SAAS,aAAgB,OAAqB;AACpD,SAAO,MAAM,QAAQ,KAAK,IAAK,QAAgB,CAAC;AACjD;AAEO,SAAS,aAAa,OAAwB;AACpD,QAAM,IAAI,OAAO,KAAK;AACtB,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AACjC;AAEO,SAAS,YAAY,OAAwB;AACnD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,MAAI,iBAAiB,KAAM,QAAO,MAAM,YAAY;AACpD,SAAO;AACR;","names":[]}
@@ -0,0 +1,58 @@
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/response.ts
21
+ var response_exports = {};
22
+ __export(response_exports, {
23
+ HTTP: () => HTTP,
24
+ setApiResponse: () => setApiResponse
25
+ });
26
+ module.exports = __toCommonJS(response_exports);
27
+ var HTTP = {
28
+ OK: 200,
29
+ CREATED: 201,
30
+ ACCEPTED: 202,
31
+ NO_CONTENT: 204,
32
+ BAD_REQUEST: 400,
33
+ UNAUTHORIZED: 401,
34
+ PAYMENT_REQUIRED: 402,
35
+ FORBIDDEN: 403,
36
+ NOT_FOUND: 404,
37
+ CONFLICT: 409,
38
+ GONE: 410,
39
+ UNPROCESSABLE: 422,
40
+ TOO_MANY_REQUESTS: 429,
41
+ SERVER_ERROR: 500,
42
+ NOT_IMPLEMENTED: 501,
43
+ BAD_GATEWAY: 502,
44
+ SERVICE_UNAVAILABLE: 503
45
+ };
46
+ function setApiResponse(status, reason, explanation, payload) {
47
+ const body = { reason, explanation };
48
+ if (payload !== void 0) {
49
+ body[status < 400 ? "result" : "details"] = payload;
50
+ }
51
+ return Response.json(body, { status });
52
+ }
53
+ // Annotate the CommonJS export names for ESM import in node:
54
+ 0 && (module.exports = {
55
+ HTTP,
56
+ setApiResponse
57
+ });
58
+ //# sourceMappingURL=response.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/response.ts"],"sourcesContent":["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"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,OAAO;AAAA,EACnB,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,MAAM;AAAA,EACN,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,qBAAqB;AACtB;AAgBO,SAAS,eACf,QACA,QACA,aACA,SACW;AACX,QAAM,OAAgC,EAAE,QAAQ,YAAY;AAC5D,MAAI,YAAY,QAAW;AAC1B,SAAK,SAAS,MAAM,WAAW,SAAS,IAAI;AAAA,EAC7C;AACA,SAAO,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC;AACtC;","names":[]}
@@ -0,0 +1,33 @@
1
+ declare const HTTP: {
2
+ readonly OK: 200;
3
+ readonly CREATED: 201;
4
+ readonly ACCEPTED: 202;
5
+ readonly NO_CONTENT: 204;
6
+ readonly BAD_REQUEST: 400;
7
+ readonly UNAUTHORIZED: 401;
8
+ readonly PAYMENT_REQUIRED: 402;
9
+ readonly FORBIDDEN: 403;
10
+ readonly NOT_FOUND: 404;
11
+ readonly CONFLICT: 409;
12
+ readonly GONE: 410;
13
+ readonly UNPROCESSABLE: 422;
14
+ readonly TOO_MANY_REQUESTS: 429;
15
+ readonly SERVER_ERROR: 500;
16
+ readonly NOT_IMPLEMENTED: 501;
17
+ readonly BAD_GATEWAY: 502;
18
+ readonly SERVICE_UNAVAILABLE: 503;
19
+ };
20
+ type HttpStatus = (typeof HTTP)[keyof typeof HTTP];
21
+ interface IApiEnvelope {
22
+ reason: string;
23
+ explanation: string;
24
+ result?: unknown;
25
+ }
26
+ interface IApiError {
27
+ reason: string;
28
+ explanation: string;
29
+ details?: unknown;
30
+ }
31
+ declare function setApiResponse<T>(status: number, reason: string, explanation: string, payload?: T): Response;
32
+
33
+ export { HTTP, type HttpStatus, type IApiEnvelope, type IApiError, setApiResponse };
@@ -0,0 +1,33 @@
1
+ declare const HTTP: {
2
+ readonly OK: 200;
3
+ readonly CREATED: 201;
4
+ readonly ACCEPTED: 202;
5
+ readonly NO_CONTENT: 204;
6
+ readonly BAD_REQUEST: 400;
7
+ readonly UNAUTHORIZED: 401;
8
+ readonly PAYMENT_REQUIRED: 402;
9
+ readonly FORBIDDEN: 403;
10
+ readonly NOT_FOUND: 404;
11
+ readonly CONFLICT: 409;
12
+ readonly GONE: 410;
13
+ readonly UNPROCESSABLE: 422;
14
+ readonly TOO_MANY_REQUESTS: 429;
15
+ readonly SERVER_ERROR: 500;
16
+ readonly NOT_IMPLEMENTED: 501;
17
+ readonly BAD_GATEWAY: 502;
18
+ readonly SERVICE_UNAVAILABLE: 503;
19
+ };
20
+ type HttpStatus = (typeof HTTP)[keyof typeof HTTP];
21
+ interface IApiEnvelope {
22
+ reason: string;
23
+ explanation: string;
24
+ result?: unknown;
25
+ }
26
+ interface IApiError {
27
+ reason: string;
28
+ explanation: string;
29
+ details?: unknown;
30
+ }
31
+ declare function setApiResponse<T>(status: number, reason: string, explanation: string, payload?: T): Response;
32
+
33
+ export { HTTP, type HttpStatus, type IApiEnvelope, type IApiError, setApiResponse };
@@ -0,0 +1,32 @@
1
+ // src/response.ts
2
+ var HTTP = {
3
+ OK: 200,
4
+ CREATED: 201,
5
+ ACCEPTED: 202,
6
+ NO_CONTENT: 204,
7
+ BAD_REQUEST: 400,
8
+ UNAUTHORIZED: 401,
9
+ PAYMENT_REQUIRED: 402,
10
+ FORBIDDEN: 403,
11
+ NOT_FOUND: 404,
12
+ CONFLICT: 409,
13
+ GONE: 410,
14
+ UNPROCESSABLE: 422,
15
+ TOO_MANY_REQUESTS: 429,
16
+ SERVER_ERROR: 500,
17
+ NOT_IMPLEMENTED: 501,
18
+ BAD_GATEWAY: 502,
19
+ SERVICE_UNAVAILABLE: 503
20
+ };
21
+ function setApiResponse(status, reason, explanation, payload) {
22
+ const body = { reason, explanation };
23
+ if (payload !== void 0) {
24
+ body[status < 400 ? "result" : "details"] = payload;
25
+ }
26
+ return Response.json(body, { status });
27
+ }
28
+ export {
29
+ HTTP,
30
+ setApiResponse
31
+ };
32
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/response.ts"],"sourcesContent":["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"],"mappings":";AAAO,IAAM,OAAO;AAAA,EACnB,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,cAAc;AAAA,EACd,kBAAkB;AAAA,EAClB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,MAAM;AAAA,EACN,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,qBAAqB;AACtB;AAgBO,SAAS,eACf,QACA,QACA,aACA,SACW;AACX,QAAM,OAAgC,EAAE,QAAQ,YAAY;AAC5D,MAAI,YAAY,QAAW;AAC1B,SAAK,SAAS,MAAM,WAAW,SAAS,IAAI;AAAA,EAC7C;AACA,SAAO,SAAS,KAAK,MAAM,EAAE,OAAO,CAAC;AACtC;","names":[]}
package/dist/types.cjs ADDED
@@ -0,0 +1,19 @@
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 __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+
16
+ // src/types.ts
17
+ var types_exports = {};
18
+ module.exports = __toCommonJS(types_exports);
19
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["// ── Stubbed until @fonderie-labs/auth ships ──────────────────────\nexport interface ITenant {\n\tid: string;\n\tslug: string;\n\tplan: string;\n}\n\nexport interface IAuthUser {\n\tid: string;\n\temail: string | null;\n\tphone: string | null;\n\tsuspended: boolean;\n\tmfaEnabled: boolean;\n\tdeletedAt: Date | null;\n\temailVerifiedAt: Date | null;\n\tloginMethod: 'email' | 'phone' | 'google'; // sourced from JWT payload\n\tphoneVerified: boolean; // per-session, sourced from JWT payload\n\tmfaPending?: boolean; // true on the short-lived pre-auth token issued during MFA login\n}\n\nexport interface IWorkspace {\n\tid: string;\n\tname: string;\n\tisPersonal?: boolean;\n}\n\n// ── Courier contract — lives in core because auth + workspaces emit\n// messages without importing @fonderie/courier.\nexport interface ICourierMessage {\n\ttype: string;\n\tlocale?: string;\n\trecipient: {\n\t\temail: string | null;\n\t\tphone: string | null;\n\t\tdeviceToken: string | null;\n\t};\n\tdata: Record<string, unknown>;\n}\n\n// ── Router interface — avoids circular dep with router.ts ────────\nexport interface IRouteMatch {\n\thandler: Middleware;\n\tparams: Record<string, string>;\n}\n\nexport interface IRouter {\n\tmatch(method: string, path: string): IRouteMatch | null;\n\tadd(method: string, path: string, handler: Middleware): void;\n}\n\n// ── Typed well-known ctx.meta keys ───────────────────────────────\nexport interface IFonderieContextMeta {\n\tparams?: Record<string, string>;\n\tbody?: unknown;\n\tworkspaceId?: string;\n\tuserId?: string;\n\tuserWorkspaceRoles?: string[];\n\tmessage?: ICourierMessage;\n\t[key: string]: unknown;\n}\n\n// ── Core types ───────────────────────────────────────────────────\nexport interface IFonderieContext {\n\trequest: Request;\n\tmeta: IFonderieContextMeta;\n\treadonly tenant: ITenant | null;\n\treadonly user: IAuthUser | null;\n\treadonly workspace: IWorkspace | null;\n\t_router: IRouter;\n}\n\nexport type Middleware = (\n\tctx: IFonderieContext,\n\tnext: () => Promise<Response>,\n) => Promise<Response>;\n\n// ── App + module contracts ────────────────────────────────────────\nexport interface IFonderieApp {\n\tuse(middleware: Middleware): IFonderieApp;\n\tregister(module: IFonderieModule): IFonderieApp;\n\taddRoute(method: string, path: string, ...handlers: Middleware[]): void;\n\tlisten(port: number, options?: { name?: string; version?: string; env?: string }): void;\n}\n\nexport interface IFonderieModule {\n\tname: string;\n\tdeps?: string[];\n\tinstall(app: IFonderieApp): void | Promise<void>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -0,0 +1,75 @@
1
+ interface ITenant {
2
+ id: string;
3
+ slug: string;
4
+ plan: string;
5
+ }
6
+ interface IAuthUser {
7
+ id: string;
8
+ email: string | null;
9
+ phone: string | null;
10
+ suspended: boolean;
11
+ mfaEnabled: boolean;
12
+ deletedAt: Date | null;
13
+ emailVerifiedAt: Date | null;
14
+ loginMethod: 'email' | 'phone' | 'google';
15
+ phoneVerified: boolean;
16
+ mfaPending?: boolean;
17
+ }
18
+ interface IWorkspace {
19
+ id: string;
20
+ name: string;
21
+ isPersonal?: boolean;
22
+ }
23
+ interface ICourierMessage {
24
+ type: string;
25
+ locale?: string;
26
+ recipient: {
27
+ email: string | null;
28
+ phone: string | null;
29
+ deviceToken: string | null;
30
+ };
31
+ data: Record<string, unknown>;
32
+ }
33
+ interface IRouteMatch {
34
+ handler: Middleware;
35
+ params: Record<string, string>;
36
+ }
37
+ interface IRouter {
38
+ match(method: string, path: string): IRouteMatch | null;
39
+ add(method: string, path: string, handler: Middleware): void;
40
+ }
41
+ interface IFonderieContextMeta {
42
+ params?: Record<string, string>;
43
+ body?: unknown;
44
+ workspaceId?: string;
45
+ userId?: string;
46
+ userWorkspaceRoles?: string[];
47
+ message?: ICourierMessage;
48
+ [key: string]: unknown;
49
+ }
50
+ interface IFonderieContext {
51
+ request: Request;
52
+ meta: IFonderieContextMeta;
53
+ readonly tenant: ITenant | null;
54
+ readonly user: IAuthUser | null;
55
+ readonly workspace: IWorkspace | null;
56
+ _router: IRouter;
57
+ }
58
+ type Middleware = (ctx: IFonderieContext, next: () => Promise<Response>) => Promise<Response>;
59
+ interface IFonderieApp {
60
+ use(middleware: Middleware): IFonderieApp;
61
+ register(module: IFonderieModule): IFonderieApp;
62
+ addRoute(method: string, path: string, ...handlers: Middleware[]): void;
63
+ listen(port: number, options?: {
64
+ name?: string;
65
+ version?: string;
66
+ env?: string;
67
+ }): void;
68
+ }
69
+ interface IFonderieModule {
70
+ name: string;
71
+ deps?: string[];
72
+ install(app: IFonderieApp): void | Promise<void>;
73
+ }
74
+
75
+ export type { IAuthUser, ICourierMessage, IFonderieApp, IFonderieContext, IFonderieContextMeta, IFonderieModule, IRouteMatch, IRouter, ITenant, IWorkspace, Middleware };