@dontcode2/backend 0.1.1 → 0.2.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/README.md +53 -0
- package/dist/chunk-2OGEV57K.js +850 -0
- package/dist/chunk-2OGEV57K.js.map +1 -0
- package/dist/mock/cli.cjs +956 -0
- package/dist/mock/cli.cjs.map +1 -0
- package/dist/mock/cli.d.cts +1 -0
- package/dist/mock/cli.d.ts +1 -0
- package/dist/mock/cli.js +90 -0
- package/dist/mock/cli.js.map +1 -0
- package/dist/mock/index.cjs +886 -0
- package/dist/mock/index.cjs.map +1 -0
- package/dist/mock/index.d.cts +38 -0
- package/dist/mock/index.d.ts +38 -0
- package/dist/mock/index.js +7 -0
- package/dist/mock/index.js.map +1 -0
- package/package.json +14 -1
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/mock/index.ts
|
|
31
|
+
var mock_exports = {};
|
|
32
|
+
__export(mock_exports, {
|
|
33
|
+
startMockServer: () => startMockServer
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(mock_exports);
|
|
36
|
+
|
|
37
|
+
// src/mock/db-query.ts
|
|
38
|
+
var QueryValidationError = class extends Error {
|
|
39
|
+
};
|
|
40
|
+
function ident(name) {
|
|
41
|
+
if (typeof name !== "string" || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
42
|
+
throw new QueryValidationError(`Invalid identifier: ${JSON.stringify(name)}`);
|
|
43
|
+
}
|
|
44
|
+
return `"${name}"`;
|
|
45
|
+
}
|
|
46
|
+
function buildWhereClause(where, startParamIndex = 1) {
|
|
47
|
+
const conditions = [];
|
|
48
|
+
const values = [];
|
|
49
|
+
let paramIndex = startParamIndex;
|
|
50
|
+
for (const [key, value] of Object.entries(where)) {
|
|
51
|
+
if (key === "AND" && Array.isArray(value)) {
|
|
52
|
+
const subClauses = value.map((subWhere) => {
|
|
53
|
+
const result = buildWhereClause(subWhere, paramIndex);
|
|
54
|
+
paramIndex += result.values.length;
|
|
55
|
+
values.push(...result.values);
|
|
56
|
+
return result.clause.replace("WHERE ", "");
|
|
57
|
+
});
|
|
58
|
+
if (subClauses.length > 0) conditions.push(`(${subClauses.join(" AND ")})`);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (key === "OR" && Array.isArray(value)) {
|
|
62
|
+
const subClauses = value.map((subWhere) => {
|
|
63
|
+
const result = buildWhereClause(subWhere, paramIndex);
|
|
64
|
+
paramIndex += result.values.length;
|
|
65
|
+
values.push(...result.values);
|
|
66
|
+
return result.clause.replace("WHERE ", "");
|
|
67
|
+
});
|
|
68
|
+
if (subClauses.length > 0) conditions.push(`(${subClauses.join(" OR ")})`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (key === "NOT" && value && typeof value === "object") {
|
|
72
|
+
const result = buildWhereClause(value, paramIndex);
|
|
73
|
+
paramIndex += result.values.length;
|
|
74
|
+
values.push(...result.values);
|
|
75
|
+
const notClause = result.clause.replace("WHERE ", "");
|
|
76
|
+
if (notClause) conditions.push(`NOT (${notClause})`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const column = ident(key);
|
|
80
|
+
if (value === null) {
|
|
81
|
+
conditions.push(`${column} IS NULL`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
if (value.length === 0) {
|
|
86
|
+
conditions.push("FALSE");
|
|
87
|
+
} else {
|
|
88
|
+
const placeholders = value.map(() => `$${paramIndex++}`).join(", ");
|
|
89
|
+
conditions.push(`${column} IN (${placeholders})`);
|
|
90
|
+
values.push(...value);
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (value && typeof value === "object" && !(value instanceof Date)) {
|
|
95
|
+
const operators = value;
|
|
96
|
+
const like = operators.mode === "insensitive" ? "ILIKE" : "LIKE";
|
|
97
|
+
for (const [operator, operatorValue] of Object.entries(operators)) {
|
|
98
|
+
if (operator === "mode") continue;
|
|
99
|
+
switch (operator) {
|
|
100
|
+
case "equals":
|
|
101
|
+
case "eq":
|
|
102
|
+
if (operatorValue === null) {
|
|
103
|
+
conditions.push(`${column} IS NULL`);
|
|
104
|
+
} else {
|
|
105
|
+
conditions.push(`${column} = $${paramIndex++}`);
|
|
106
|
+
values.push(operatorValue);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
case "not":
|
|
110
|
+
if (operatorValue === null) {
|
|
111
|
+
conditions.push(`${column} IS NOT NULL`);
|
|
112
|
+
} else if (Array.isArray(operatorValue)) {
|
|
113
|
+
if (operatorValue.length === 0) {
|
|
114
|
+
conditions.push("TRUE");
|
|
115
|
+
} else {
|
|
116
|
+
const placeholders = operatorValue.map(() => `$${paramIndex++}`).join(", ");
|
|
117
|
+
conditions.push(`${column} NOT IN (${placeholders})`);
|
|
118
|
+
values.push(...operatorValue);
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
conditions.push(`${column} != $${paramIndex++}`);
|
|
122
|
+
values.push(operatorValue);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
case "gt":
|
|
126
|
+
conditions.push(`${column} > $${paramIndex++}`);
|
|
127
|
+
values.push(operatorValue);
|
|
128
|
+
break;
|
|
129
|
+
case "gte":
|
|
130
|
+
conditions.push(`${column} >= $${paramIndex++}`);
|
|
131
|
+
values.push(operatorValue);
|
|
132
|
+
break;
|
|
133
|
+
case "lt":
|
|
134
|
+
conditions.push(`${column} < $${paramIndex++}`);
|
|
135
|
+
values.push(operatorValue);
|
|
136
|
+
break;
|
|
137
|
+
case "lte":
|
|
138
|
+
conditions.push(`${column} <= $${paramIndex++}`);
|
|
139
|
+
values.push(operatorValue);
|
|
140
|
+
break;
|
|
141
|
+
case "in":
|
|
142
|
+
if (!Array.isArray(operatorValue) || operatorValue.length === 0) {
|
|
143
|
+
conditions.push("FALSE");
|
|
144
|
+
} else {
|
|
145
|
+
const placeholders = operatorValue.map(() => `$${paramIndex++}`).join(", ");
|
|
146
|
+
conditions.push(`${column} IN (${placeholders})`);
|
|
147
|
+
values.push(...operatorValue);
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
case "notIn":
|
|
151
|
+
if (!Array.isArray(operatorValue) || operatorValue.length === 0) {
|
|
152
|
+
conditions.push("TRUE");
|
|
153
|
+
} else {
|
|
154
|
+
const placeholders = operatorValue.map(() => `$${paramIndex++}`).join(", ");
|
|
155
|
+
conditions.push(`${column} NOT IN (${placeholders})`);
|
|
156
|
+
values.push(...operatorValue);
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
case "contains":
|
|
160
|
+
conditions.push(`${column} ${like} $${paramIndex++}`);
|
|
161
|
+
values.push(`%${operatorValue}%`);
|
|
162
|
+
break;
|
|
163
|
+
case "startsWith":
|
|
164
|
+
conditions.push(`${column} ${like} $${paramIndex++}`);
|
|
165
|
+
values.push(`${operatorValue}%`);
|
|
166
|
+
break;
|
|
167
|
+
case "endsWith":
|
|
168
|
+
conditions.push(`${column} ${like} $${paramIndex++}`);
|
|
169
|
+
values.push(`%${operatorValue}`);
|
|
170
|
+
break;
|
|
171
|
+
default:
|
|
172
|
+
throw new QueryValidationError(`Unsupported operator: ${operator}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
conditions.push(`${column} = $${paramIndex++}`);
|
|
178
|
+
values.push(value);
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
clause: conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "",
|
|
182
|
+
values
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function buildOrderByClause(orderBy) {
|
|
186
|
+
if (!orderBy) return "";
|
|
187
|
+
const orders = Object.entries(orderBy).map(([col, dir]) => {
|
|
188
|
+
if (dir !== "asc" && dir !== "desc") {
|
|
189
|
+
throw new QueryValidationError(`Invalid sort direction: ${JSON.stringify(dir)}`);
|
|
190
|
+
}
|
|
191
|
+
return `${ident(col)} ${dir.toUpperCase()}`;
|
|
192
|
+
});
|
|
193
|
+
return `ORDER BY ${orders.join(", ")}`;
|
|
194
|
+
}
|
|
195
|
+
function buildSelectColumns(select) {
|
|
196
|
+
if (!select || select.length === 0) return "*";
|
|
197
|
+
return select.map(ident).join(", ");
|
|
198
|
+
}
|
|
199
|
+
function clampInt(value, max) {
|
|
200
|
+
const n = Number(value);
|
|
201
|
+
if (!Number.isFinite(n) || n < 0) return null;
|
|
202
|
+
return Math.min(Math.floor(n), max);
|
|
203
|
+
}
|
|
204
|
+
var MAX_LIMIT = 1e3;
|
|
205
|
+
function errorResult(err) {
|
|
206
|
+
if (err instanceof QueryValidationError) {
|
|
207
|
+
return { status: 400, body: { error: err.message } };
|
|
208
|
+
}
|
|
209
|
+
const pgErr = err;
|
|
210
|
+
const message = pgErr.message ?? "Database error";
|
|
211
|
+
if (pgErr.code === "23505" || pgErr.code === "23503") {
|
|
212
|
+
return { status: 409, body: { error: message } };
|
|
213
|
+
}
|
|
214
|
+
if (pgErr.code === "42P01" || pgErr.code === "42703") {
|
|
215
|
+
return { status: 400, body: { error: message } };
|
|
216
|
+
}
|
|
217
|
+
return { status: 500, body: { error: message } };
|
|
218
|
+
}
|
|
219
|
+
function rowCount(result) {
|
|
220
|
+
return result.affectedRows ?? result.rows.length;
|
|
221
|
+
}
|
|
222
|
+
async function executeDbOperation(db, schema, operation, tableName, options) {
|
|
223
|
+
try {
|
|
224
|
+
const table = `${ident(schema)}.${ident(tableName)}`;
|
|
225
|
+
if (options.include && (!Array.isArray(options.include) || options.include.length > 0) && Object.keys(options.include).length > 0) {
|
|
226
|
+
return {
|
|
227
|
+
status: 400,
|
|
228
|
+
body: { error: "include is not yet supported on the public API" }
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
switch (operation) {
|
|
232
|
+
case "find":
|
|
233
|
+
case "findMany": {
|
|
234
|
+
const { where, select, orderBy, limit, offset } = options;
|
|
235
|
+
const whereClause = where ? buildWhereClause(where) : { clause: "", values: [] };
|
|
236
|
+
const limitValue = limit !== void 0 ? clampInt(limit, MAX_LIMIT) : MAX_LIMIT;
|
|
237
|
+
const offsetValue = offset !== void 0 ? clampInt(offset, 1e9) : null;
|
|
238
|
+
const query = [
|
|
239
|
+
`SELECT ${buildSelectColumns(select)} FROM ${table}`,
|
|
240
|
+
whereClause.clause,
|
|
241
|
+
buildOrderByClause(orderBy),
|
|
242
|
+
limitValue !== null ? `LIMIT ${limitValue}` : "",
|
|
243
|
+
offsetValue !== null && offsetValue > 0 ? `OFFSET ${offsetValue}` : ""
|
|
244
|
+
].filter(Boolean).join(" ");
|
|
245
|
+
const result = await db.query(query, whereClause.values);
|
|
246
|
+
return { status: 200, body: { data: result.rows } };
|
|
247
|
+
}
|
|
248
|
+
case "findFirst":
|
|
249
|
+
case "findOne": {
|
|
250
|
+
const { where, select, orderBy } = options;
|
|
251
|
+
const whereClause = where ? buildWhereClause(where) : { clause: "", values: [] };
|
|
252
|
+
const query = [
|
|
253
|
+
`SELECT ${buildSelectColumns(select)} FROM ${table}`,
|
|
254
|
+
whereClause.clause,
|
|
255
|
+
buildOrderByClause(orderBy),
|
|
256
|
+
"LIMIT 1"
|
|
257
|
+
].filter(Boolean).join(" ");
|
|
258
|
+
const result = await db.query(query, whereClause.values);
|
|
259
|
+
return { status: 200, body: { data: result.rows[0] ?? null } };
|
|
260
|
+
}
|
|
261
|
+
case "insert": {
|
|
262
|
+
const { data } = options;
|
|
263
|
+
if (!data || typeof data !== "object" || Object.keys(data).length === 0) {
|
|
264
|
+
return { status: 400, body: { error: "Insert requires a data object" } };
|
|
265
|
+
}
|
|
266
|
+
const columns = Object.keys(data).map(ident);
|
|
267
|
+
const values = Object.values(data);
|
|
268
|
+
const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
|
|
269
|
+
const query = `INSERT INTO ${table} (${columns.join(", ")}) VALUES (${placeholders}) RETURNING *`;
|
|
270
|
+
const result = await db.query(query, values);
|
|
271
|
+
const row = result.rows[0];
|
|
272
|
+
return { status: 200, body: { data: { id: row?.id ?? row } } };
|
|
273
|
+
}
|
|
274
|
+
case "update": {
|
|
275
|
+
const { where, data } = options;
|
|
276
|
+
if (!data || typeof data !== "object" || Object.keys(data).length === 0) {
|
|
277
|
+
return { status: 400, body: { error: "Update requires a data object" } };
|
|
278
|
+
}
|
|
279
|
+
if (!where || Object.keys(where).length === 0) {
|
|
280
|
+
return {
|
|
281
|
+
status: 400,
|
|
282
|
+
body: {
|
|
283
|
+
error: "Update requires a WHERE clause to prevent accidental updates of all records"
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const setClauses = [];
|
|
288
|
+
const values = [];
|
|
289
|
+
let paramIndex = 1;
|
|
290
|
+
for (const [key, value] of Object.entries(data)) {
|
|
291
|
+
setClauses.push(`${ident(key)} = $${paramIndex++}`);
|
|
292
|
+
values.push(value);
|
|
293
|
+
}
|
|
294
|
+
const whereClause = buildWhereClause(where, paramIndex);
|
|
295
|
+
values.push(...whereClause.values);
|
|
296
|
+
const query = `UPDATE ${table} SET ${setClauses.join(", ")} ${whereClause.clause} RETURNING *`;
|
|
297
|
+
const result = await db.query(query, values);
|
|
298
|
+
return { status: 200, body: { data: { count: rowCount(result) } } };
|
|
299
|
+
}
|
|
300
|
+
case "delete": {
|
|
301
|
+
const { where } = options;
|
|
302
|
+
const whereClause = where ? buildWhereClause(where) : { clause: "", values: [] };
|
|
303
|
+
if (!whereClause.clause) {
|
|
304
|
+
return {
|
|
305
|
+
status: 400,
|
|
306
|
+
body: {
|
|
307
|
+
error: "Delete requires a WHERE clause to prevent accidental deletion of all records"
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const query = `DELETE FROM ${table} ${whereClause.clause} RETURNING *`;
|
|
312
|
+
const result = await db.query(query, whereClause.values);
|
|
313
|
+
return { status: 200, body: { data: { count: rowCount(result) } } };
|
|
314
|
+
}
|
|
315
|
+
case "count": {
|
|
316
|
+
const { where } = options;
|
|
317
|
+
const whereClause = where ? buildWhereClause(where) : { clause: "", values: [] };
|
|
318
|
+
const query = [`SELECT COUNT(*) as count FROM ${table}`, whereClause.clause].filter(Boolean).join(" ");
|
|
319
|
+
const result = await db.query(query, whereClause.values);
|
|
320
|
+
return { status: 200, body: { data: parseInt(String(result.rows[0].count), 10) } };
|
|
321
|
+
}
|
|
322
|
+
default:
|
|
323
|
+
return { status: 400, body: { error: "Invalid operation" } };
|
|
324
|
+
}
|
|
325
|
+
} catch (err) {
|
|
326
|
+
return errorResult(err);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/mock/server.ts
|
|
331
|
+
var import_node_crypto = require("crypto");
|
|
332
|
+
var import_node_fs = require("fs");
|
|
333
|
+
var import_node_http = require("http");
|
|
334
|
+
var import_node_os = require("os");
|
|
335
|
+
var import_node_path = require("path");
|
|
336
|
+
var ACCESS_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
337
|
+
function b64url(value) {
|
|
338
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
339
|
+
return Buffer.from(str).toString("base64url");
|
|
340
|
+
}
|
|
341
|
+
function mintToken(user) {
|
|
342
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
343
|
+
const header = { alg: "none", typ: "JWT" };
|
|
344
|
+
const payload = {
|
|
345
|
+
sub: user.id,
|
|
346
|
+
email: user.email,
|
|
347
|
+
role: user.role,
|
|
348
|
+
claims: user.claims,
|
|
349
|
+
iat: now,
|
|
350
|
+
exp: now + ACCESS_TOKEN_TTL_SECONDS
|
|
351
|
+
};
|
|
352
|
+
return {
|
|
353
|
+
AccessToken: `${b64url(header)}.${b64url(payload)}.${b64url("dontcode-mock")}`,
|
|
354
|
+
ExpiresIn: ACCESS_TOKEN_TTL_SECONDS
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
function decodeToken(token) {
|
|
358
|
+
const parts = token.split(".");
|
|
359
|
+
if (parts.length < 2) return null;
|
|
360
|
+
try {
|
|
361
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
362
|
+
return typeof payload?.sub === "string" ? payload : null;
|
|
363
|
+
} catch {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function fileName(path) {
|
|
368
|
+
return path.split("/").filter(Boolean).pop() ?? path;
|
|
369
|
+
}
|
|
370
|
+
function normalizePath(value, { allowEmpty = false } = {}) {
|
|
371
|
+
if (typeof value !== "string") return allowEmpty && value === void 0 ? "" : null;
|
|
372
|
+
const path = value.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
373
|
+
if (!allowEmpty && path.length === 0) return null;
|
|
374
|
+
if (path.split("/").some((segment) => segment === ".." || segment === ".")) return null;
|
|
375
|
+
if (/[\x00-\x1f]/.test(path)) return null;
|
|
376
|
+
return path;
|
|
377
|
+
}
|
|
378
|
+
function readBody(req) {
|
|
379
|
+
return new Promise((resolve, reject) => {
|
|
380
|
+
const chunks = [];
|
|
381
|
+
req.on("data", (c) => chunks.push(c));
|
|
382
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
383
|
+
req.on("error", reject);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
async function startMockServer(options = {}) {
|
|
387
|
+
const port = options.port ?? 4e3;
|
|
388
|
+
const host = options.host ?? "127.0.0.1";
|
|
389
|
+
const schema = options.schema ?? "public";
|
|
390
|
+
const quiet = options.quiet ?? false;
|
|
391
|
+
const ephemeral = options.dataDir === null;
|
|
392
|
+
const dataDir = ephemeral ? (0, import_node_fs.mkdtempSync)((0, import_node_path.join)((0, import_node_os.tmpdir)(), "dontcode-mock-")) : options.dataDir ?? ".dontcode-mock";
|
|
393
|
+
if (!ephemeral) (0, import_node_fs.mkdirSync)(dataDir, { recursive: true });
|
|
394
|
+
const pglite = await loadPGlite();
|
|
395
|
+
const pgDir = ephemeral ? void 0 : (0, import_node_path.join)(dataDir, "pgdata");
|
|
396
|
+
const pg = pgDir ? new pglite.PGlite(pgDir) : new pglite.PGlite();
|
|
397
|
+
await pg.query("SELECT 1");
|
|
398
|
+
const db = {
|
|
399
|
+
query: async (sql, params) => {
|
|
400
|
+
const r = await pg.query(sql, params);
|
|
401
|
+
return { rows: r.rows, affectedRows: r.affectedRows };
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
const authFile = (0, import_node_path.join)(dataDir, "auth.json");
|
|
405
|
+
const users = /* @__PURE__ */ new Map();
|
|
406
|
+
if (!ephemeral && (0, import_node_fs.existsSync)(authFile)) {
|
|
407
|
+
try {
|
|
408
|
+
const saved = JSON.parse((0, import_node_fs.readFileSync)(authFile, "utf8"));
|
|
409
|
+
for (const u of saved) users.set(u.email.toLowerCase(), u);
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const persistUsers = () => {
|
|
414
|
+
if (ephemeral) return;
|
|
415
|
+
(0, import_node_fs.writeFileSync)(authFile, JSON.stringify([...users.values()], null, 2));
|
|
416
|
+
};
|
|
417
|
+
const storageDir = (0, import_node_path.join)(dataDir, "storage");
|
|
418
|
+
(0, import_node_fs.mkdirSync)(storageDir, { recursive: true });
|
|
419
|
+
const manifestFile = (0, import_node_path.join)(storageDir, "manifest.json");
|
|
420
|
+
const manifest = /* @__PURE__ */ new Map();
|
|
421
|
+
if ((0, import_node_fs.existsSync)(manifestFile)) {
|
|
422
|
+
try {
|
|
423
|
+
for (const [k, v] of Object.entries(
|
|
424
|
+
JSON.parse((0, import_node_fs.readFileSync)(manifestFile, "utf8"))
|
|
425
|
+
))
|
|
426
|
+
manifest.set(k, v);
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const persistManifest = () => (0, import_node_fs.writeFileSync)(manifestFile, JSON.stringify(Object.fromEntries(manifest), null, 2));
|
|
431
|
+
const objKey = (bucket, path) => `${bucket}/${path}`;
|
|
432
|
+
const objFile = (bucket, path) => (0, import_node_path.join)(storageDir, bucket, path);
|
|
433
|
+
let baseUrl = `http://localhost:${port}`;
|
|
434
|
+
function objectShape(bucket, path) {
|
|
435
|
+
const meta = manifest.get(objKey(bucket, path));
|
|
436
|
+
return {
|
|
437
|
+
key: path,
|
|
438
|
+
name: fileName(path),
|
|
439
|
+
size: meta?.size ?? 0,
|
|
440
|
+
contentType: meta?.contentType ?? "application/octet-stream",
|
|
441
|
+
lastModified: meta?.lastModified ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
442
|
+
isFolder: false
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function writeObject(bucket, path, body, contentType) {
|
|
446
|
+
const file = objFile(bucket, path);
|
|
447
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(file), { recursive: true });
|
|
448
|
+
(0, import_node_fs.writeFileSync)(file, body);
|
|
449
|
+
manifest.set(objKey(bucket, path), {
|
|
450
|
+
contentType,
|
|
451
|
+
size: body.length,
|
|
452
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
453
|
+
});
|
|
454
|
+
persistManifest();
|
|
455
|
+
}
|
|
456
|
+
const server = (0, import_node_http.createServer)((req, res) => {
|
|
457
|
+
handle(req, res).catch((err) => {
|
|
458
|
+
if (!quiet) console.error("[dontcode-mock] handler error:", err);
|
|
459
|
+
sendJson(res, 500, { error: err instanceof Error ? err.message : "Internal error" });
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
function sendJson(res, status, body) {
|
|
463
|
+
const json = JSON.stringify(body);
|
|
464
|
+
res.writeHead(status, {
|
|
465
|
+
"Content-Type": "application/json",
|
|
466
|
+
"Access-Control-Allow-Origin": "*"
|
|
467
|
+
});
|
|
468
|
+
res.end(json);
|
|
469
|
+
}
|
|
470
|
+
function checkApiKey(req) {
|
|
471
|
+
const header = req.headers["authorization"];
|
|
472
|
+
if (typeof header !== "string" || !header.startsWith("Bearer ")) return false;
|
|
473
|
+
const key = header.slice(7).trim();
|
|
474
|
+
if (options.apiKey) return key === options.apiKey;
|
|
475
|
+
return key.startsWith("dc_");
|
|
476
|
+
}
|
|
477
|
+
async function handle(req, res) {
|
|
478
|
+
const url = new URL(req.url ?? "/", baseUrl);
|
|
479
|
+
const path = url.pathname;
|
|
480
|
+
const method = req.method ?? "GET";
|
|
481
|
+
if (method === "OPTIONS") {
|
|
482
|
+
res.writeHead(204, {
|
|
483
|
+
"Access-Control-Allow-Origin": "*",
|
|
484
|
+
"Access-Control-Allow-Methods": "GET,POST,PUT,OPTIONS",
|
|
485
|
+
"Access-Control-Allow-Headers": "Authorization,Content-Type,X-Access-Token"
|
|
486
|
+
});
|
|
487
|
+
res.end();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!quiet) console.log(`[dontcode-mock] ${method} ${path}`);
|
|
491
|
+
if (path === "/" && method === "GET") {
|
|
492
|
+
sendJson(res, 200, { service: "dontcode-mock", ok: true });
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (path.startsWith("/__storage/")) {
|
|
496
|
+
await handleFileEndpoint(req, res, url, method);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (!path.startsWith("/api/v1/")) {
|
|
500
|
+
sendJson(res, 404, { error: "Not found" });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (!checkApiKey(req)) {
|
|
504
|
+
sendJson(res, 401, {
|
|
505
|
+
error: "Missing API key. Send it as: Authorization: Bearer <key>"
|
|
506
|
+
});
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const raw = await readBody(req);
|
|
510
|
+
if (path === "/api/v1/db" && method === "POST") {
|
|
511
|
+
const body = parseJson(raw);
|
|
512
|
+
const {
|
|
513
|
+
operation,
|
|
514
|
+
tableName,
|
|
515
|
+
options: opts
|
|
516
|
+
} = body;
|
|
517
|
+
if (!operation || !tableName) {
|
|
518
|
+
sendJson(res, 400, { error: "Operation and tableName are required" });
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const result = await executeDbOperation(db, schema, operation, tableName, opts ?? {});
|
|
522
|
+
sendJson(res, result.status, result.body);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (path === "/api/v1/db/migrate" && method === "POST") {
|
|
526
|
+
const { sql } = parseJson(raw);
|
|
527
|
+
if (!sql || typeof sql !== "string") {
|
|
528
|
+
sendJson(res, 400, { error: "sql is required" });
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const results = await pg.exec(sql);
|
|
533
|
+
sendJson(res, 200, {
|
|
534
|
+
success: true,
|
|
535
|
+
executedStatements: Array.isArray(results) ? results.length : 1,
|
|
536
|
+
warnings: []
|
|
537
|
+
});
|
|
538
|
+
} catch (err) {
|
|
539
|
+
sendJson(res, 400, {
|
|
540
|
+
success: false,
|
|
541
|
+
error: err instanceof Error ? err.message : "Migration failed"
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (path.startsWith("/api/v1/auth/") && method === "POST") {
|
|
547
|
+
await handleAuth(req, res, path.slice("/api/v1/auth/".length), raw);
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (path === "/api/v1/storage") {
|
|
551
|
+
if (method === "POST") {
|
|
552
|
+
await handleStorageJson(res, parseJson(raw));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (method === "PUT") {
|
|
556
|
+
await handleStorageUpload(req, res, raw);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
sendJson(res, 404, { error: "Unknown endpoint" });
|
|
561
|
+
}
|
|
562
|
+
async function handleAuth(req, res, endpoint, raw) {
|
|
563
|
+
const body = parseJson(raw);
|
|
564
|
+
const accessToken = typeof req.headers["x-access-token"] === "string" ? req.headers["x-access-token"] : void 0;
|
|
565
|
+
switch (endpoint) {
|
|
566
|
+
case "signup": {
|
|
567
|
+
const email = String(body.email ?? "").trim();
|
|
568
|
+
const password = String(body.password ?? "");
|
|
569
|
+
if (!email || !password) {
|
|
570
|
+
sendJson(res, 400, { error: "Email and password are required" });
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (users.has(email.toLowerCase())) {
|
|
574
|
+
sendJson(res, 409, { error: "Email already registered", code: "EmailExists" });
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const user = {
|
|
578
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
579
|
+
email,
|
|
580
|
+
password,
|
|
581
|
+
role: typeof body.role === "string" ? body.role : void 0,
|
|
582
|
+
verified: true
|
|
583
|
+
};
|
|
584
|
+
users.set(email.toLowerCase(), user);
|
|
585
|
+
persistUsers();
|
|
586
|
+
sendJson(res, 200, { success: true, userId: user.id, verified: true });
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
case "login": {
|
|
590
|
+
const email = String(body.email ?? "").trim();
|
|
591
|
+
const password = String(body.password ?? "");
|
|
592
|
+
const user = users.get(email.toLowerCase());
|
|
593
|
+
if (!user || user.password !== password) {
|
|
594
|
+
sendJson(res, 401, { error: "Invalid email or password" });
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
sendJson(res, 200, { success: true, userId: user.id, tokens: mintToken(user) });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
case "me": {
|
|
601
|
+
const decoded = accessToken ? decodeToken(accessToken) : null;
|
|
602
|
+
if (!decoded || decoded.exp && Date.now() / 1e3 >= decoded.exp) {
|
|
603
|
+
sendJson(res, 200, { user: null });
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const user = [...users.values()].find((u) => u.id === decoded.sub);
|
|
607
|
+
if (!user) {
|
|
608
|
+
sendJson(res, 200, { user: null });
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
sendJson(res, 200, {
|
|
612
|
+
user: { id: user.id, email: user.email, role: user.role, claims: user.claims }
|
|
613
|
+
});
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
case "verify-email":
|
|
617
|
+
case "forgot-password":
|
|
618
|
+
case "reset-password":
|
|
619
|
+
sendJson(res, 200, { success: true });
|
|
620
|
+
return;
|
|
621
|
+
case "mfa/enroll":
|
|
622
|
+
sendJson(res, 200, {
|
|
623
|
+
success: true,
|
|
624
|
+
secret: "MOCKMFASECRET",
|
|
625
|
+
otpauth_url: "otpauth://totp/DontCodeMock?secret=MOCKMFASECRET"
|
|
626
|
+
});
|
|
627
|
+
return;
|
|
628
|
+
case "mfa/enroll/confirm":
|
|
629
|
+
sendJson(res, 200, { success: true, recovery_codes: ["mock-recovery-0001"] });
|
|
630
|
+
return;
|
|
631
|
+
case "mfa/disable":
|
|
632
|
+
sendJson(res, 200, { success: true });
|
|
633
|
+
return;
|
|
634
|
+
case "mfa/challenge":
|
|
635
|
+
sendJson(res, 400, {
|
|
636
|
+
error: "MFA is not enabled in the mock",
|
|
637
|
+
code: "MfaNotOffered"
|
|
638
|
+
});
|
|
639
|
+
return;
|
|
640
|
+
default:
|
|
641
|
+
sendJson(res, 404, { error: "Unknown auth endpoint" });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async function handleStorageJson(res, body) {
|
|
645
|
+
const operation = body.operation;
|
|
646
|
+
const bucket = body.bucket === "public" || body.bucket === "private" ? body.bucket : null;
|
|
647
|
+
if (!bucket) {
|
|
648
|
+
sendJson(res, 400, { error: 'bucket must be "public" or "private"' });
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
const bad = (msg) => sendJson(res, 400, { error: msg });
|
|
652
|
+
switch (operation) {
|
|
653
|
+
case "list": {
|
|
654
|
+
const prefix = normalizePath(body.prefix, { allowEmpty: true });
|
|
655
|
+
if (prefix === null) return bad("Invalid prefix");
|
|
656
|
+
const objects = [];
|
|
657
|
+
for (const key of manifest.keys()) {
|
|
658
|
+
if (!key.startsWith(`${bucket}/`)) continue;
|
|
659
|
+
const path = key.slice(bucket.length + 1);
|
|
660
|
+
if (prefix && !path.startsWith(`${prefix}/`) && path !== prefix) continue;
|
|
661
|
+
objects.push(objectShape(bucket, path));
|
|
662
|
+
}
|
|
663
|
+
sendJson(res, 200, {
|
|
664
|
+
objects,
|
|
665
|
+
folders: [],
|
|
666
|
+
prefix: prefix ? `${prefix}/` : "",
|
|
667
|
+
truncated: false,
|
|
668
|
+
continuationToken: null
|
|
669
|
+
});
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
case "remove": {
|
|
673
|
+
const paths = Array.isArray(body.paths) ? body.paths.map((p) => normalizePath(p)) : null;
|
|
674
|
+
if (!paths || paths.length === 0 || paths.some((p) => p === null)) {
|
|
675
|
+
return bad("paths must be a non-empty array of valid paths");
|
|
676
|
+
}
|
|
677
|
+
for (const p of paths) {
|
|
678
|
+
const file = objFile(bucket, p);
|
|
679
|
+
if ((0, import_node_fs.existsSync)(file)) (0, import_node_fs.rmSync)(file);
|
|
680
|
+
manifest.delete(objKey(bucket, p));
|
|
681
|
+
}
|
|
682
|
+
persistManifest();
|
|
683
|
+
sendJson(res, 200, { deleted: paths.length });
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
case "move": {
|
|
687
|
+
const from = normalizePath(body.from);
|
|
688
|
+
const to = normalizePath(body.to);
|
|
689
|
+
if (!from || !to) return bad("from and to are required");
|
|
690
|
+
const fromFile = objFile(bucket, from);
|
|
691
|
+
if (!(0, import_node_fs.existsSync)(fromFile)) return sendJson(res, 404, { error: "File not found" });
|
|
692
|
+
const buf = (0, import_node_fs.readFileSync)(fromFile);
|
|
693
|
+
const meta = manifest.get(objKey(bucket, from));
|
|
694
|
+
writeObject(bucket, to, buf, meta?.contentType ?? "application/octet-stream");
|
|
695
|
+
(0, import_node_fs.rmSync)(fromFile);
|
|
696
|
+
manifest.delete(objKey(bucket, from));
|
|
697
|
+
persistManifest();
|
|
698
|
+
sendJson(res, 200, { object: objectShape(bucket, to) });
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
case "createFolder": {
|
|
702
|
+
const path = normalizePath(body.path);
|
|
703
|
+
if (!path) return bad("path is required");
|
|
704
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.join)(storageDir, bucket, path), { recursive: true });
|
|
705
|
+
sendJson(res, 200, { created: `${path}/` });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
case "download": {
|
|
709
|
+
const path = normalizePath(body.path);
|
|
710
|
+
if (!path) return bad("path is required");
|
|
711
|
+
const file = objFile(bucket, path);
|
|
712
|
+
if (!(0, import_node_fs.existsSync)(file)) return sendJson(res, 404, { error: "File not found" });
|
|
713
|
+
const buf = (0, import_node_fs.readFileSync)(file);
|
|
714
|
+
if (buf.length > 8 * 1024 * 1024) {
|
|
715
|
+
return bad("File is too large to download inline; use getTemporaryUrl instead");
|
|
716
|
+
}
|
|
717
|
+
const meta = manifest.get(objKey(bucket, path));
|
|
718
|
+
sendJson(res, 200, {
|
|
719
|
+
body: buf.toString("base64"),
|
|
720
|
+
contentType: meta?.contentType ?? "application/octet-stream",
|
|
721
|
+
size: buf.length
|
|
722
|
+
});
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
case "getUrl": {
|
|
726
|
+
if (bucket !== "public") return bad("getUrl is only available on the public bucket");
|
|
727
|
+
const path = normalizePath(body.path);
|
|
728
|
+
if (!path) return bad("path is required");
|
|
729
|
+
sendJson(res, 200, { url: `${baseUrl}/__storage/public/${path}` });
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
case "getTemporaryUrl": {
|
|
733
|
+
const path = normalizePath(body.path);
|
|
734
|
+
if (!path) return bad("path is required");
|
|
735
|
+
const requested = Number(body.expiresIn);
|
|
736
|
+
const expiresIn = Number.isFinite(requested) && requested > 0 ? Math.min(Math.floor(requested), 7 * 24 * 60 * 60) : 300;
|
|
737
|
+
sendJson(res, 200, {
|
|
738
|
+
url: `${baseUrl}/__storage/${bucket}/${path}?expires=${expiresIn}`,
|
|
739
|
+
expiresIn
|
|
740
|
+
});
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
case "presignUpload": {
|
|
744
|
+
const path = normalizePath(body.path);
|
|
745
|
+
if (!path) return bad("path is required");
|
|
746
|
+
const contentType = typeof body.contentType === "string" && body.contentType.length > 0 ? body.contentType : "application/octet-stream";
|
|
747
|
+
sendJson(res, 200, {
|
|
748
|
+
url: `${baseUrl}/__storage/${bucket}/${path}?upload=1&contentType=${encodeURIComponent(contentType)}`,
|
|
749
|
+
key: path,
|
|
750
|
+
expiresIn: 600
|
|
751
|
+
});
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
default:
|
|
755
|
+
bad("Invalid operation");
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
async function handleStorageUpload(req, res, raw) {
|
|
759
|
+
const webReq = new Request(`${baseUrl}/api/v1/storage`, {
|
|
760
|
+
method: "PUT",
|
|
761
|
+
headers: nodeHeaders(req),
|
|
762
|
+
body: new Uint8Array(raw)
|
|
763
|
+
});
|
|
764
|
+
let form;
|
|
765
|
+
try {
|
|
766
|
+
form = await webReq.formData();
|
|
767
|
+
} catch {
|
|
768
|
+
sendJson(res, 400, { error: "Upload requires multipart/form-data with a file field" });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const file = form.get("file");
|
|
772
|
+
if (!(file instanceof File)) {
|
|
773
|
+
sendJson(res, 400, { error: "file and path are required" });
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
const bucket = form.get("bucket");
|
|
777
|
+
if (bucket !== "public" && bucket !== "private") {
|
|
778
|
+
sendJson(res, 400, { error: 'bucket must be "public" or "private"' });
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
const path = normalizePath(form.get("path"));
|
|
782
|
+
if (!path) {
|
|
783
|
+
sendJson(res, 400, { error: "file and path are required" });
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const ctField = form.get("contentType");
|
|
787
|
+
const contentType = typeof ctField === "string" && ctField.length > 0 ? ctField : file.type || "application/octet-stream";
|
|
788
|
+
const buf = Buffer.from(await file.arrayBuffer());
|
|
789
|
+
writeObject(bucket, path, buf, contentType);
|
|
790
|
+
sendJson(res, 200, { object: objectShape(bucket, path) });
|
|
791
|
+
}
|
|
792
|
+
async function handleFileEndpoint(req, res, url, method) {
|
|
793
|
+
const rest = url.pathname.slice("/__storage/".length);
|
|
794
|
+
const slash = rest.indexOf("/");
|
|
795
|
+
const bucket = slash === -1 ? rest : rest.slice(0, slash);
|
|
796
|
+
const path = slash === -1 ? "" : rest.slice(slash + 1);
|
|
797
|
+
if (bucket !== "public" && bucket !== "private" || !path) {
|
|
798
|
+
sendJson(res, 404, { error: "Not found" });
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (method === "PUT" && url.searchParams.get("upload") === "1") {
|
|
802
|
+
const buf2 = await readBody(req);
|
|
803
|
+
const contentType = url.searchParams.get("contentType") || (typeof req.headers["content-type"] === "string" ? req.headers["content-type"] : "application/octet-stream");
|
|
804
|
+
writeObject(bucket, decodeURIComponent(path), buf2, contentType);
|
|
805
|
+
res.writeHead(200, { "Access-Control-Allow-Origin": "*" });
|
|
806
|
+
res.end();
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const file = objFile(bucket, decodeURIComponent(path));
|
|
810
|
+
if (!(0, import_node_fs.existsSync)(file)) {
|
|
811
|
+
sendJson(res, 404, { error: "File not found" });
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const meta = manifest.get(objKey(bucket, decodeURIComponent(path)));
|
|
815
|
+
const buf = (0, import_node_fs.readFileSync)(file);
|
|
816
|
+
res.writeHead(200, {
|
|
817
|
+
"Content-Type": meta?.contentType ?? "application/octet-stream",
|
|
818
|
+
"Content-Length": buf.length,
|
|
819
|
+
"Access-Control-Allow-Origin": "*"
|
|
820
|
+
});
|
|
821
|
+
res.end(buf);
|
|
822
|
+
}
|
|
823
|
+
await new Promise((resolve, reject) => {
|
|
824
|
+
server.once("error", reject);
|
|
825
|
+
server.listen(port, host, () => {
|
|
826
|
+
server.off("error", reject);
|
|
827
|
+
resolve();
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
const address = server.address();
|
|
831
|
+
const actualPort = address && typeof address === "object" ? address.port : port;
|
|
832
|
+
baseUrl = `http://localhost:${actualPort}`;
|
|
833
|
+
if (!quiet) {
|
|
834
|
+
const where = ephemeral ? "ephemeral (in-memory)" : dataDir;
|
|
835
|
+
console.log(
|
|
836
|
+
`
|
|
837
|
+
DontCode mock gateway listening on ${baseUrl}
|
|
838
|
+
data: ${where}
|
|
839
|
+
|
|
840
|
+
Point your app at it:
|
|
841
|
+
DONTCODE_API_URL=${baseUrl}
|
|
842
|
+
DONTCODE_API_KEY=dc_local_dev
|
|
843
|
+
`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return {
|
|
847
|
+
url: baseUrl,
|
|
848
|
+
port: actualPort,
|
|
849
|
+
close: async () => {
|
|
850
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
851
|
+
await pg.close?.();
|
|
852
|
+
if (ephemeral) (0, import_node_fs.rmSync)(dataDir, { recursive: true, force: true });
|
|
853
|
+
}
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function parseJson(raw) {
|
|
857
|
+
if (raw.length === 0) return {};
|
|
858
|
+
try {
|
|
859
|
+
const parsed = JSON.parse(raw.toString("utf8"));
|
|
860
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
861
|
+
} catch {
|
|
862
|
+
return {};
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
function nodeHeaders(req) {
|
|
866
|
+
const headers = new Headers();
|
|
867
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
868
|
+
if (Array.isArray(value)) headers.set(key, value.join(", "));
|
|
869
|
+
else if (typeof value === "string") headers.set(key, value);
|
|
870
|
+
}
|
|
871
|
+
return headers;
|
|
872
|
+
}
|
|
873
|
+
async function loadPGlite() {
|
|
874
|
+
try {
|
|
875
|
+
return await import("@electric-sql/pglite");
|
|
876
|
+
} catch {
|
|
877
|
+
throw new Error(
|
|
878
|
+
"The DontCode mock needs an in-process Postgres engine that is not installed.\n Install it with: pnpm add -D @electric-sql/pglite (or npm i -D @electric-sql/pglite)\n It ships as an optional dependency of @dontcode2/backend, so this usually means\n it was skipped (e.g. an install run with --no-optional)."
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
883
|
+
0 && (module.exports = {
|
|
884
|
+
startMockServer
|
|
885
|
+
});
|
|
886
|
+
//# sourceMappingURL=index.cjs.map
|