@devmoods/postgres-admin 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/.vite/manifest.json +2 -2
- package/dist/client/_index.html +2 -2
- package/dist/client/assets/index-C2qEtXGs.css +1 -0
- package/dist/client/assets/index-CXHyC2NT.js +87 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +90 -109
- package/package.json +22 -29
- package/dist/client/assets/index-B3vmKC1C.js +0 -87
- package/dist/client/assets/index-Dw1OxeYy.css +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,13 +4,14 @@ import { createPostgres } from '@devmoods/express-extras';
|
|
|
4
4
|
interface AdminRouterOptions {
|
|
5
5
|
postgres?: ReturnType<typeof createPostgres>;
|
|
6
6
|
basename?: string;
|
|
7
|
+
indexHtml?: string;
|
|
7
8
|
uiOptions?: AdminUIOptions;
|
|
8
9
|
}
|
|
9
10
|
interface AdminUIOptions {
|
|
10
11
|
title?: string;
|
|
11
12
|
}
|
|
12
|
-
declare function
|
|
13
|
+
declare function createAdminRouter({ postgres: defaultPostgres, basename, indexHtml, uiOptions, }: AdminRouterOptions): express_serve_static_core.Router;
|
|
13
14
|
|
|
14
|
-
declare const admin: typeof
|
|
15
|
+
declare const admin: typeof createAdminRouter;
|
|
15
16
|
|
|
16
17
|
export { admin };
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,11 @@ import {
|
|
|
6
6
|
createPostgres,
|
|
7
7
|
generateCsrfToken,
|
|
8
8
|
joinStatements,
|
|
9
|
-
route,
|
|
10
9
|
sql,
|
|
11
|
-
|
|
10
|
+
staticMiddleware,
|
|
11
|
+
createRouter
|
|
12
12
|
} from "@devmoods/express-extras";
|
|
13
|
-
import
|
|
13
|
+
import { type } from "arktype";
|
|
14
14
|
|
|
15
15
|
// src/api/structure.ts
|
|
16
16
|
import { NotFound, readEnv } from "@devmoods/express-extras";
|
|
@@ -85,13 +85,13 @@ function getDefault(value) {
|
|
|
85
85
|
}
|
|
86
86
|
function toJsonSchemaProperty(column) {
|
|
87
87
|
const defaultValue = getDefault(column.default);
|
|
88
|
-
const
|
|
88
|
+
const type2 = getPrimitiveType(column.type.name);
|
|
89
89
|
const description = column.comment;
|
|
90
90
|
if (column.arrayDimension > 0) {
|
|
91
91
|
return {
|
|
92
92
|
type: "array",
|
|
93
93
|
title: column.name,
|
|
94
|
-
items: { type },
|
|
94
|
+
items: { type: type2 },
|
|
95
95
|
default: defaultValue || [],
|
|
96
96
|
description
|
|
97
97
|
};
|
|
@@ -105,7 +105,7 @@ function toJsonSchemaProperty(column) {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
return {
|
|
108
|
-
type,
|
|
108
|
+
type: type2,
|
|
109
109
|
title: column.name,
|
|
110
110
|
default: defaultValue,
|
|
111
111
|
description,
|
|
@@ -225,74 +225,65 @@ function counter() {
|
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// src/api/app.ts
|
|
228
|
-
function
|
|
228
|
+
function createAdminRouter({
|
|
229
229
|
postgres: defaultPostgres,
|
|
230
230
|
basename = "/admin",
|
|
231
|
+
indexHtml = "node_modules/@devmoods/postgres-admin/dist/client/_index.html",
|
|
231
232
|
uiOptions = {}
|
|
232
233
|
}) {
|
|
233
234
|
const postgres = defaultPostgres ?? createPostgres();
|
|
234
|
-
const
|
|
235
|
+
const router = createRouter();
|
|
235
236
|
if (!basename.startsWith("/")) {
|
|
236
237
|
throw new Error(`Option 'basename' must start with a slash /`);
|
|
237
238
|
}
|
|
238
|
-
|
|
239
|
-
"
|
|
240
|
-
route(async () => {
|
|
241
|
-
const { databaseName, tables } = await readTables();
|
|
242
|
-
const rowCounts = Object.fromEntries(
|
|
243
|
-
await Promise.all(
|
|
244
|
-
tables.map(async (table) => {
|
|
245
|
-
return [
|
|
246
|
-
table.tableName,
|
|
247
|
-
await postgres.get(sql`SELECT COUNT(*) FROM ${sql.identifier(table.tableName)}`).then((r) => +r.count)
|
|
248
|
-
];
|
|
249
|
-
})
|
|
250
|
-
)
|
|
251
|
-
);
|
|
252
|
-
return {
|
|
253
|
-
databaseName,
|
|
254
|
-
tables,
|
|
255
|
-
rowCounts
|
|
256
|
-
};
|
|
257
|
-
})
|
|
239
|
+
const unknownTableRow = type(
|
|
240
|
+
"Record<string, string | number | boolean | null>"
|
|
258
241
|
);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
|
|
242
|
+
router.get("/api/tables", {}, async () => {
|
|
243
|
+
const { databaseName, tables } = await readTables();
|
|
244
|
+
const rowCounts = Object.fromEntries(
|
|
245
|
+
await Promise.all(
|
|
246
|
+
tables.map(async (table) => {
|
|
247
|
+
return [
|
|
248
|
+
table.tableName,
|
|
249
|
+
await postgres.get(sql`SELECT COUNT(*) FROM ${sql.identifier(table.tableName)}`).then((r) => +r.count)
|
|
250
|
+
];
|
|
251
|
+
})
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
return {
|
|
255
|
+
databaseName,
|
|
256
|
+
tables,
|
|
257
|
+
rowCounts
|
|
258
|
+
};
|
|
259
|
+
});
|
|
260
|
+
router.get("/api/tables/:table/suggestions/:column", {}, async (req) => {
|
|
261
|
+
const table = stripIdentifier(req.params.table);
|
|
262
|
+
const definition = await getTable(table);
|
|
263
|
+
const column = stripIdentifier(req.params.column);
|
|
264
|
+
if (!definition.columnNames.includes(column)) {
|
|
265
|
+
throw BadRequest(
|
|
266
|
+
`Invalid column ${column}. Must be one of: ${definition.columnNames.join(", ")}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
const statement = sql`
|
|
271
270
|
SELECT DISTINCT ${sql.identifier(column)}, *
|
|
272
271
|
FROM ${sql.identifier(table)}
|
|
273
272
|
LIMIT 500
|
|
274
273
|
`;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
app.get(
|
|
274
|
+
return postgres.all(statement);
|
|
275
|
+
});
|
|
276
|
+
router.get(
|
|
279
277
|
"/api/tables/:table",
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
limit: { type: "number", minimum: 1, maximum: 1e3 },
|
|
290
|
-
page: { type: "number" },
|
|
291
|
-
search: { type: "string" }
|
|
292
|
-
},
|
|
293
|
-
required: []
|
|
294
|
-
}),
|
|
295
|
-
route(async (req) => {
|
|
278
|
+
{
|
|
279
|
+
query: type({
|
|
280
|
+
order: "string|string[]?",
|
|
281
|
+
limit: "string.numeric?",
|
|
282
|
+
page: "string.numeric?",
|
|
283
|
+
search: "string?"
|
|
284
|
+
})
|
|
285
|
+
},
|
|
286
|
+
async (req) => {
|
|
296
287
|
const table = stripIdentifier(req.params.table);
|
|
297
288
|
const orders = queryParamToArray(req.query.order);
|
|
298
289
|
const limit = parseInt(req.query.limit ?? "50");
|
|
@@ -372,28 +363,26 @@ function createRouter({
|
|
|
372
363
|
}
|
|
373
364
|
statement.append(sql` LIMIT ${limit} OFFSET ${limit * (page - 1)}`);
|
|
374
365
|
return await postgres.all(statement);
|
|
375
|
-
}
|
|
366
|
+
}
|
|
376
367
|
);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
const statement = sql`
|
|
368
|
+
router.get("/api/tables/:table/:pk", {}, async (req) => {
|
|
369
|
+
const table = stripIdentifier(req.params.table);
|
|
370
|
+
const definition = await getTable(table);
|
|
371
|
+
const primaryKey = definition.primaryKey;
|
|
372
|
+
if (!primaryKey) {
|
|
373
|
+
throw BadRequest(`Table ${table} has no primary key`);
|
|
374
|
+
}
|
|
375
|
+
const statement = sql`
|
|
387
376
|
SELECT *
|
|
388
377
|
FROM ${sql.identifier(table)}
|
|
389
378
|
WHERE ${sql.identifier(primaryKey)} = ${req.params.pk}
|
|
390
379
|
`;
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
app.post(
|
|
380
|
+
return postgres.first(statement);
|
|
381
|
+
});
|
|
382
|
+
router.post(
|
|
395
383
|
"/api/tables/:table",
|
|
396
|
-
|
|
384
|
+
{ request: unknownTableRow },
|
|
385
|
+
async (req) => {
|
|
397
386
|
const table = stripIdentifier(req.params.table);
|
|
398
387
|
const definition = await getTable(table);
|
|
399
388
|
const primaryKey = definition.primaryKey;
|
|
@@ -406,11 +395,12 @@ function createRouter({
|
|
|
406
395
|
${sql.spreadInsert(data)}
|
|
407
396
|
`;
|
|
408
397
|
return postgres.first(statement);
|
|
409
|
-
}
|
|
398
|
+
}
|
|
410
399
|
);
|
|
411
|
-
|
|
400
|
+
router.put(
|
|
412
401
|
"/api/tables/:table/:pk",
|
|
413
|
-
|
|
402
|
+
{ request: unknownTableRow },
|
|
403
|
+
async (req) => {
|
|
414
404
|
const table = stripIdentifier(req.params.table);
|
|
415
405
|
const pk = req.params.pk;
|
|
416
406
|
const definition = await getTable(table);
|
|
@@ -440,46 +430,37 @@ function createRouter({
|
|
|
440
430
|
RETURNING *
|
|
441
431
|
`);
|
|
442
432
|
});
|
|
443
|
-
}
|
|
433
|
+
}
|
|
444
434
|
);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
const statement = sql`
|
|
435
|
+
router.delete("/api/tables/:table/:pk", {}, async (req) => {
|
|
436
|
+
const table = stripIdentifier(req.params.table);
|
|
437
|
+
const pk = parseInt(req.params.pk);
|
|
438
|
+
const definition = await getTable(table);
|
|
439
|
+
const primaryKey = definition.primaryKey;
|
|
440
|
+
if (!primaryKey) {
|
|
441
|
+
throw BadRequest(`Table ${table} has no primary key`);
|
|
442
|
+
}
|
|
443
|
+
const statement = sql`
|
|
456
444
|
DELETE FROM ${sql.identifier(table)}
|
|
457
445
|
WHERE ${sql.identifier(primaryKey)} = ${pk}
|
|
458
446
|
`;
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
express.static(
|
|
447
|
+
await postgres.query(statement);
|
|
448
|
+
return 204;
|
|
449
|
+
});
|
|
450
|
+
router.use(
|
|
451
|
+
staticMiddleware(
|
|
465
452
|
path.resolve("node_modules/@devmoods/postgres-admin/dist/client")
|
|
466
453
|
)
|
|
467
454
|
);
|
|
468
|
-
|
|
469
|
-
"
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
"utf-8"
|
|
474
|
-
);
|
|
475
|
-
return content.replace(/{{ csrf_token }}/g, generateCsrfToken(req, res)).replace(/{{ basename }}/g, basename.slice(1)).replace(/{{ title }}/g, uiOptions.title ?? "Posterboi");
|
|
476
|
-
})
|
|
477
|
-
);
|
|
478
|
-
return app;
|
|
455
|
+
router.get("*", {}, async (req, res) => {
|
|
456
|
+
const content = await fs.readFile(indexHtml, "utf-8");
|
|
457
|
+
return content.replace(/{{ csrf_token }}/g, generateCsrfToken(req, res)).replace(/{{ basename }}/g, basename.slice(1)).replace(/{{ title }}/g, uiOptions.title ?? "Posterboi");
|
|
458
|
+
});
|
|
459
|
+
return router.getRouter();
|
|
479
460
|
}
|
|
480
461
|
|
|
481
462
|
// src/index.ts
|
|
482
|
-
var admin =
|
|
463
|
+
var admin = createAdminRouter;
|
|
483
464
|
export {
|
|
484
465
|
admin
|
|
485
466
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/postgres-admin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -12,39 +12,32 @@
|
|
|
12
12
|
"build:server": "tsup-node src/index.ts --format esm --dts",
|
|
13
13
|
"build:client": "tsc --noEmit && vite build",
|
|
14
14
|
"build": "pnpm run build:client && pnpm run build:server",
|
|
15
|
-
"test": "vitest"
|
|
15
|
+
"test": "DOTENV_CONFIG_PATH=.env.test vitest"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
+
"@devmoods/config": "0.2.0",
|
|
19
|
+
"@devmoods/express-extras": "0.68.0",
|
|
20
|
+
"@devmoods/fetch": "^4.2.0",
|
|
21
|
+
"@devmoods/ui": "^2.12.2",
|
|
22
|
+
"@tanstack/react-query": "^5.90.5",
|
|
23
|
+
"@types/react": "^19.2.2",
|
|
24
|
+
"@types/react-dom": "^19.2.2",
|
|
25
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
26
|
+
"@vitest/coverage-v8": "3.2.4",
|
|
27
|
+
"arktype": "^2.1.23",
|
|
28
|
+
"babel-plugin-react-compiler": "1.0.0",
|
|
18
29
|
"date-fns": "^4.1.0",
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
},
|
|
24
|
-
"devDependencies": {
|
|
25
|
-
"@bitiforms/adapter-devmoods-ui": "^0.15.0",
|
|
26
|
-
"@bitiforms/react": "^0.15.0",
|
|
27
|
-
"@devmoods/express-extras": "^0.57.3",
|
|
28
|
-
"@devmoods/fetch": "^4.1.0",
|
|
29
|
-
"@devmoods/ui": "^2.9.1",
|
|
30
|
-
"@tanstack/react-query": "^5.77.2",
|
|
31
|
-
"@types/react": "^19.1.6",
|
|
32
|
-
"@types/react-dom": "^19.1.5",
|
|
33
|
-
"@vitejs/plugin-react": "^4.5.0",
|
|
34
|
-
"@vitest/coverage-v8": "3.1.4",
|
|
35
|
-
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
|
36
|
-
"dotenv": "^16.5.0",
|
|
37
|
-
"express": "^4.21.2",
|
|
38
|
-
"react": "^19.1.0",
|
|
39
|
-
"react-dom": "^19.1.0",
|
|
30
|
+
"dotenv": "^17.2.3",
|
|
31
|
+
"pg-structure": "^7.15.3",
|
|
32
|
+
"react": "^19.2.0",
|
|
33
|
+
"react-dom": "^19.2.0",
|
|
40
34
|
"react-fast-compare": "^3.2.2",
|
|
41
35
|
"react-icons": "^5.5.0",
|
|
42
|
-
"react-router": "^7.
|
|
43
|
-
"sass": "^1.
|
|
36
|
+
"react-router": "^7.9.4",
|
|
37
|
+
"sass": "^1.93.2",
|
|
44
38
|
"tsup": "^8.5.0",
|
|
45
|
-
"vite": "^
|
|
46
|
-
"
|
|
47
|
-
"zustand": "^5.0.5"
|
|
39
|
+
"vite": "^7.1.11",
|
|
40
|
+
"zustand": "^5.0.8"
|
|
48
41
|
},
|
|
49
|
-
"gitHead": "
|
|
42
|
+
"gitHead": "721a709c087904a5cc1332d28a62857a82295fea"
|
|
50
43
|
}
|