@devmoods/postgres-admin 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/dist/client/assets/index-BQSpf7B-.css +1 -0
- package/dist/client/assets/index-D9Xb7F16.js +78 -0
- package/dist/client/index.html +20 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +442 -0
- package/package.json +46 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="no">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<link rel="shortcut icon" href="/favicon.ico" />
|
|
6
|
+
<meta
|
|
7
|
+
name="viewport"
|
|
8
|
+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
|
9
|
+
/>
|
|
10
|
+
<meta name="theme-color" content="#000000" />
|
|
11
|
+
<link rel="manifest" href="/manifest.json" />
|
|
12
|
+
<title>Posterboi</title>
|
|
13
|
+
<script type="module" crossorigin src="/admin/assets/index-D9Xb7F16.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/admin/assets/index-BQSpf7B-.css">
|
|
15
|
+
</head>
|
|
16
|
+
<body>
|
|
17
|
+
<div id="root"></div>
|
|
18
|
+
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as express_serve_static_core from 'express-serve-static-core';
|
|
2
|
+
import { createPostgres } from '@devmoods/express-extras';
|
|
3
|
+
|
|
4
|
+
interface AdminRouterOptions {
|
|
5
|
+
postgres: ReturnType<typeof createPostgres>;
|
|
6
|
+
}
|
|
7
|
+
declare function createRouter({ postgres }: AdminRouterOptions): express_serve_static_core.Router;
|
|
8
|
+
|
|
9
|
+
declare const admin: typeof createRouter;
|
|
10
|
+
|
|
11
|
+
export { admin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
// src/api/app.ts
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
BadRequest,
|
|
5
|
+
joinStatements,
|
|
6
|
+
route,
|
|
7
|
+
sql,
|
|
8
|
+
validateQuery
|
|
9
|
+
} from "@devmoods/express-extras";
|
|
10
|
+
import express from "express";
|
|
11
|
+
|
|
12
|
+
// src/api/structure.ts
|
|
13
|
+
import "@bitiforms/react";
|
|
14
|
+
import { NotFound, readEnv } from "@devmoods/express-extras";
|
|
15
|
+
import "pg-structure";
|
|
16
|
+
import { pgStructure } from "pg-structure/dist/main.js";
|
|
17
|
+
|
|
18
|
+
// src/types.ts
|
|
19
|
+
import "@bitiforms/react";
|
|
20
|
+
|
|
21
|
+
// src/api/structure.ts
|
|
22
|
+
var getDisplayType = (dbType) => {
|
|
23
|
+
switch (dbType) {
|
|
24
|
+
case "jsonb":
|
|
25
|
+
case "json":
|
|
26
|
+
return "json";
|
|
27
|
+
case "character varying":
|
|
28
|
+
case "text":
|
|
29
|
+
case "tstzrange":
|
|
30
|
+
case "bytea":
|
|
31
|
+
case "uuid":
|
|
32
|
+
return "string";
|
|
33
|
+
case "date":
|
|
34
|
+
return "date";
|
|
35
|
+
case "timestamp without time zone":
|
|
36
|
+
case "timestamp with time zone":
|
|
37
|
+
return "datetime";
|
|
38
|
+
case "integer":
|
|
39
|
+
case "numeric":
|
|
40
|
+
case "smallint":
|
|
41
|
+
case "bigint":
|
|
42
|
+
return "number";
|
|
43
|
+
case "boolean":
|
|
44
|
+
return "boolean";
|
|
45
|
+
default:
|
|
46
|
+
return "string";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var getPrimitiveType = (dbType) => {
|
|
50
|
+
switch (dbType) {
|
|
51
|
+
case "character varying":
|
|
52
|
+
case "timestamp without time zone":
|
|
53
|
+
case "timestamp with time zone":
|
|
54
|
+
case "text":
|
|
55
|
+
case "date":
|
|
56
|
+
case "jsonb":
|
|
57
|
+
case "tstzrange":
|
|
58
|
+
case "bytea":
|
|
59
|
+
case "uuid":
|
|
60
|
+
return "string";
|
|
61
|
+
case "integer":
|
|
62
|
+
case "numeric":
|
|
63
|
+
case "smallint":
|
|
64
|
+
case "bigint":
|
|
65
|
+
return "number";
|
|
66
|
+
case "boolean":
|
|
67
|
+
return "boolean";
|
|
68
|
+
default:
|
|
69
|
+
return "string";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
function stripStringValue(value) {
|
|
73
|
+
if (value.startsWith("nextval(")) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (value.includes("now()")) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return value.slice(1, -1);
|
|
80
|
+
}
|
|
81
|
+
function getDefault(value) {
|
|
82
|
+
if (typeof value === "string") {
|
|
83
|
+
return stripStringValue(value);
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function toJsonSchemaProperty(column) {
|
|
88
|
+
const defaultValue = getDefault(column.default);
|
|
89
|
+
const type = getPrimitiveType(column.type.name);
|
|
90
|
+
const description = column.comment;
|
|
91
|
+
if (column.arrayDimension > 0) {
|
|
92
|
+
return {
|
|
93
|
+
type: "array",
|
|
94
|
+
title: column.name,
|
|
95
|
+
items: { type },
|
|
96
|
+
default: defaultValue || [],
|
|
97
|
+
description
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const $bitiforms = {};
|
|
101
|
+
if (column.isForeignKey) {
|
|
102
|
+
$bitiforms.widget = "ForeignKeyWidget";
|
|
103
|
+
$bitiforms.widgetProps = {
|
|
104
|
+
foreignTable: column.foreignKeys.at(0)?.referencedTable.name,
|
|
105
|
+
foreignColumn: column.foreignKeys.at(0)?.referencedColumns.at(0)?.name
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
type,
|
|
110
|
+
title: column.name,
|
|
111
|
+
default: defaultValue,
|
|
112
|
+
description,
|
|
113
|
+
$bitiforms
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
var tableCache = /* @__PURE__ */ new Map();
|
|
117
|
+
function toJsonSchema(table) {
|
|
118
|
+
const required = [];
|
|
119
|
+
const properties = {};
|
|
120
|
+
for (const column of table.columns) {
|
|
121
|
+
properties[column.name] = toJsonSchemaProperty(column);
|
|
122
|
+
if (column.notNull && !column.isSerial) {
|
|
123
|
+
required.push(column.name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties,
|
|
129
|
+
required
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function getTable(tableId) {
|
|
133
|
+
if (!tableCache.has(tableId)) {
|
|
134
|
+
const tables = await readTables();
|
|
135
|
+
for (const table of tables) {
|
|
136
|
+
tableCache.set(table.tableName, table);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const value = tableCache.get(tableId);
|
|
140
|
+
if (value == null) {
|
|
141
|
+
throw NotFound();
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
async function readTables() {
|
|
146
|
+
const structure = await pgStructure({
|
|
147
|
+
connectionString: readEnv("POSTGRES_URL")
|
|
148
|
+
});
|
|
149
|
+
const toMetadata = (column) => ({
|
|
150
|
+
type: getPrimitiveType(column.type.name),
|
|
151
|
+
displayType: getDisplayType(column.type.name),
|
|
152
|
+
dbType: column.type.name,
|
|
153
|
+
foreignKeys: column.foreignKeys.mapToArray((fk) => ({
|
|
154
|
+
table: fk.referencedTable.name,
|
|
155
|
+
column: fk.referencedColumns.at(0).name
|
|
156
|
+
}))
|
|
157
|
+
});
|
|
158
|
+
const toBelongsTo = (table) => {
|
|
159
|
+
return table.belongsToTables.mapToArray((t) => {
|
|
160
|
+
const foreignKey = t.getForeignKeysFrom(table.name).mapToArray((fk) => fk.columns.at(0)?.name).at(0);
|
|
161
|
+
const belongsToTableColumns = t.columns.mapToArray((c) => c.name);
|
|
162
|
+
return {
|
|
163
|
+
foreignKey,
|
|
164
|
+
belongsToTable: t.name,
|
|
165
|
+
includeColumnNames: belongsToTableColumns.map(
|
|
166
|
+
(c) => `${foreignKey.replace(/_id$/, "")}__${c}`
|
|
167
|
+
)
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
const result = [];
|
|
172
|
+
for (const table of structure.tables) {
|
|
173
|
+
const belongsTo = toBelongsTo(table);
|
|
174
|
+
const columnsArray = table.columns.mapToArray((column) => column);
|
|
175
|
+
result.push({
|
|
176
|
+
tableName: table.name,
|
|
177
|
+
primaryKey: table.primaryKey?.columns.at(0)?.name ?? null,
|
|
178
|
+
belongsTo,
|
|
179
|
+
columnNames: columnsArray.map((column) => column.name),
|
|
180
|
+
relatedColumnNames: belongsTo.flatMap((b) => b.includeColumnNames),
|
|
181
|
+
columnMetadata: Object.fromEntries(
|
|
182
|
+
columnsArray.map((column) => [column.name, toMetadata(column)])
|
|
183
|
+
),
|
|
184
|
+
schema: toJsonSchema(table)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/api/utils.ts
|
|
191
|
+
function stripIdentifier(tableName) {
|
|
192
|
+
return (tableName ?? "").replace(/[^\w+]/g, "");
|
|
193
|
+
}
|
|
194
|
+
function parseOrderBy(order) {
|
|
195
|
+
if (!order) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const direction = order.at(0) === "-" ? "DESC" : "ASC";
|
|
199
|
+
const column = order.at(0) === "-" ? order.slice(1) : order;
|
|
200
|
+
return { direction, column };
|
|
201
|
+
}
|
|
202
|
+
function queryParamToArray(queryParam) {
|
|
203
|
+
if (Array.isArray(queryParam)) {
|
|
204
|
+
return queryParam.filter((value) => typeof value === "string");
|
|
205
|
+
}
|
|
206
|
+
if (typeof queryParam === "string") {
|
|
207
|
+
return [queryParam];
|
|
208
|
+
}
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// src/api/app.ts
|
|
213
|
+
function createRouter({ postgres }) {
|
|
214
|
+
const app = express.Router();
|
|
215
|
+
app.get(
|
|
216
|
+
"/api/tables",
|
|
217
|
+
route(async () => {
|
|
218
|
+
const tables = await readTables();
|
|
219
|
+
const rowCounts = Object.fromEntries(
|
|
220
|
+
await Promise.all(
|
|
221
|
+
tables.map(async (table) => {
|
|
222
|
+
return [
|
|
223
|
+
table.tableName,
|
|
224
|
+
await postgres.get(sql`SELECT COUNT(*) FROM ${sql.identifier(table.tableName)}`).then((r) => r.count)
|
|
225
|
+
];
|
|
226
|
+
})
|
|
227
|
+
)
|
|
228
|
+
);
|
|
229
|
+
return {
|
|
230
|
+
tables,
|
|
231
|
+
rowCounts
|
|
232
|
+
};
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
app.get(
|
|
236
|
+
"/api/tables/:table/suggestions/:column",
|
|
237
|
+
route(async (req) => {
|
|
238
|
+
const table = stripIdentifier(req.params.table);
|
|
239
|
+
const definition = await getTable(table);
|
|
240
|
+
const column = stripIdentifier(req.params.column);
|
|
241
|
+
if (!definition.columnNames.includes(column)) {
|
|
242
|
+
throw BadRequest(
|
|
243
|
+
`Invalid colimn ${column}. Must be one of: ${definition.columnNames.join(", ")}`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
const statement = sql`
|
|
247
|
+
SELECT DISTINCT ${sql.identifier(column)}, *
|
|
248
|
+
FROM ${sql.identifier(table)}
|
|
249
|
+
LIMIT 20
|
|
250
|
+
`;
|
|
251
|
+
return postgres.all(statement);
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
app.get(
|
|
255
|
+
"/api/tables/:table",
|
|
256
|
+
validateQuery({
|
|
257
|
+
type: "object",
|
|
258
|
+
properties: {
|
|
259
|
+
order: {
|
|
260
|
+
oneOf: [
|
|
261
|
+
{ type: "string" },
|
|
262
|
+
{ type: "array", items: { type: "string" } }
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
limit: { type: "number", minimum: 1, maximum: 1e3 },
|
|
266
|
+
page: { type: "number" },
|
|
267
|
+
search: { type: "string" }
|
|
268
|
+
},
|
|
269
|
+
required: []
|
|
270
|
+
}),
|
|
271
|
+
route(async (req) => {
|
|
272
|
+
const table = stripIdentifier(req.params.table);
|
|
273
|
+
const orders = queryParamToArray(req.query.order);
|
|
274
|
+
const limit = parseInt(req.query.limit ?? "50");
|
|
275
|
+
const page = parseInt(req.query.page ?? "1");
|
|
276
|
+
const search = String(req.query.search || "");
|
|
277
|
+
const definition = await getTable(table);
|
|
278
|
+
const orderBy = orders.map((order) => parseOrderBy(order)).filter((x) => x != null);
|
|
279
|
+
const allowedColumnNames = definition.columnNames.concat(
|
|
280
|
+
definition.relatedColumnNames
|
|
281
|
+
);
|
|
282
|
+
if (orderBy && !orderBy.every((x) => allowedColumnNames.includes(x.column))) {
|
|
283
|
+
throw BadRequest(
|
|
284
|
+
`Invalid order value ${orderBy.map((x) => x.column).join(", ")}. Must be one of: ${allowedColumnNames.join(", ")}`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
const fieldsToSelect = [sql`${sql.identifier(table)}.*`];
|
|
288
|
+
const foreignTables = Object.fromEntries(
|
|
289
|
+
await Promise.all(
|
|
290
|
+
definition.columnNames.flatMap((column) => {
|
|
291
|
+
const metadata = definition.columnMetadata[column];
|
|
292
|
+
return metadata.foreignKeys.map((x) => x.table);
|
|
293
|
+
}).map(async (t) => [t, await getTable(t)])
|
|
294
|
+
)
|
|
295
|
+
);
|
|
296
|
+
const joins = [];
|
|
297
|
+
for (const column of definition.columnNames) {
|
|
298
|
+
const metadata = definition.columnMetadata[column];
|
|
299
|
+
for (const {
|
|
300
|
+
table: fkTable,
|
|
301
|
+
column: fkColumn
|
|
302
|
+
} of metadata.foreignKeys) {
|
|
303
|
+
const foreignColumns = foreignTables[fkTable].columnNames;
|
|
304
|
+
const prefix = column.replace(/_id$/, "");
|
|
305
|
+
for (const foreignColumn of foreignColumns) {
|
|
306
|
+
fieldsToSelect.push(
|
|
307
|
+
sql`${sql.identifier(fkTable)}.${sql.identifier(foreignColumn)} AS ${sql.identifier(`${prefix}__${foreignColumn}`)}`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
joins.push(
|
|
311
|
+
sql`LEFT JOIN ${sql.identifier(fkTable)} ON ${sql.identifier(fkTable)}.${sql.identifier(fkColumn)} = ${sql.identifier(table)}.${sql.identifier(column)}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const statement = sql`SELECT ${joinStatements(fieldsToSelect, ", ")} FROM ${sql.identifier(table)} ${joinStatements(joins, " ")}`;
|
|
316
|
+
if (search && search.length > 2) {
|
|
317
|
+
statement.append(" WHERE ");
|
|
318
|
+
statement.append(
|
|
319
|
+
joinStatements(
|
|
320
|
+
definition.columnNames.map(
|
|
321
|
+
(c) => sql`${sql.identifier(c)}::text ILIKE ${`%${search}%`}`
|
|
322
|
+
),
|
|
323
|
+
" OR "
|
|
324
|
+
)
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
if (orderBy.length > 0) {
|
|
328
|
+
statement.append(sql` ORDER BY `);
|
|
329
|
+
statement.append(
|
|
330
|
+
joinStatements(
|
|
331
|
+
orderBy.map(
|
|
332
|
+
(o) => sql`${sql.identifier(o.column)} ${sql.raw(o.direction)}`
|
|
333
|
+
),
|
|
334
|
+
", "
|
|
335
|
+
)
|
|
336
|
+
);
|
|
337
|
+
} else if (definition.primaryKey) {
|
|
338
|
+
statement.append(
|
|
339
|
+
sql` ORDER BY ${sql.identifier(definition.primaryKey)}`
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
statement.append(sql` LIMIT ${limit} OFFSET ${limit * (page - 1)}`);
|
|
343
|
+
return await postgres.all(statement);
|
|
344
|
+
})
|
|
345
|
+
);
|
|
346
|
+
app.get(
|
|
347
|
+
"/api/tables/:table/:pk",
|
|
348
|
+
route(async (req) => {
|
|
349
|
+
const table = stripIdentifier(req.params.table);
|
|
350
|
+
const definition = await getTable(table);
|
|
351
|
+
const primaryKey = definition.primaryKey;
|
|
352
|
+
if (!primaryKey) {
|
|
353
|
+
throw BadRequest(`Table ${table} has no primary key`);
|
|
354
|
+
}
|
|
355
|
+
const statement = sql`
|
|
356
|
+
SELECT *
|
|
357
|
+
FROM ${sql.identifier(table)}
|
|
358
|
+
WHERE ${sql.identifier(primaryKey)} = ${req.params.pk}
|
|
359
|
+
`;
|
|
360
|
+
return postgres.first(statement);
|
|
361
|
+
})
|
|
362
|
+
);
|
|
363
|
+
app.post(
|
|
364
|
+
"/api/tables/:table",
|
|
365
|
+
route(async (req) => {
|
|
366
|
+
const table = stripIdentifier(req.params.table);
|
|
367
|
+
const definition = await getTable(table);
|
|
368
|
+
const primaryKey = definition.primaryKey;
|
|
369
|
+
if (!primaryKey) {
|
|
370
|
+
throw BadRequest(`Table ${table} has no primary key`);
|
|
371
|
+
}
|
|
372
|
+
const { [primaryKey]: pk, ...data } = req.body;
|
|
373
|
+
const statement = sql`
|
|
374
|
+
INSERT INTO ${sql.identifier(table)}
|
|
375
|
+
${sql.spreadInsert(data)}
|
|
376
|
+
`;
|
|
377
|
+
return postgres.first(statement);
|
|
378
|
+
})
|
|
379
|
+
);
|
|
380
|
+
app.put(
|
|
381
|
+
"/api/tables/:table/:pk",
|
|
382
|
+
route(async (req) => {
|
|
383
|
+
const table = stripIdentifier(req.params.table);
|
|
384
|
+
const pk = req.params.pk;
|
|
385
|
+
const definition = await getTable(table);
|
|
386
|
+
const primaryKey = definition.primaryKey;
|
|
387
|
+
if (!primaryKey) {
|
|
388
|
+
throw BadRequest(`Table ${table} has no primary key`);
|
|
389
|
+
}
|
|
390
|
+
return await postgres.transaction(async (tx) => {
|
|
391
|
+
await postgres.get(sql`
|
|
392
|
+
SELECT * FROM ${sql.identifier(table)}
|
|
393
|
+
WHERE ${sql.identifier(primaryKey)} = ${req.params.pk}
|
|
394
|
+
FOR UPDATE
|
|
395
|
+
`);
|
|
396
|
+
return tx.get(sql`
|
|
397
|
+
UPDATE ${sql.identifier(table)}
|
|
398
|
+
SET ${sql.spreadUpdate(req.body)}
|
|
399
|
+
WHERE ${sql.identifier(primaryKey)} = ${pk}
|
|
400
|
+
RETURNING *
|
|
401
|
+
`);
|
|
402
|
+
});
|
|
403
|
+
})
|
|
404
|
+
);
|
|
405
|
+
app.delete(
|
|
406
|
+
"/api/tables/:table/:pk",
|
|
407
|
+
route(async (req) => {
|
|
408
|
+
const table = stripIdentifier(req.params.table);
|
|
409
|
+
const pk = parseInt(req.params.pk);
|
|
410
|
+
const definition = await getTable(table);
|
|
411
|
+
const primaryKey = definition.primaryKey;
|
|
412
|
+
if (!primaryKey) {
|
|
413
|
+
throw BadRequest(`Table ${table} has no primary key`);
|
|
414
|
+
}
|
|
415
|
+
const statement = sql`
|
|
416
|
+
DELETE FROM ${sql.identifier(table)}
|
|
417
|
+
WHERE ${sql.identifier(primaryKey)} = ${pk}
|
|
418
|
+
`;
|
|
419
|
+
await postgres.query(statement);
|
|
420
|
+
return 204;
|
|
421
|
+
})
|
|
422
|
+
);
|
|
423
|
+
app.use(
|
|
424
|
+
express.static(
|
|
425
|
+
path.resolve("node_modules/@devmoods/postgres-admin/dist/client")
|
|
426
|
+
)
|
|
427
|
+
);
|
|
428
|
+
app.get("*", (req, res) => {
|
|
429
|
+
res.sendFile(
|
|
430
|
+
path.resolve(
|
|
431
|
+
"node_modules/@devmoods/postgres-admin/dist/client/index.html"
|
|
432
|
+
)
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
return app;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/index.ts
|
|
439
|
+
var admin = createRouter;
|
|
440
|
+
export {
|
|
441
|
+
admin
|
|
442
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@devmoods/postgres-admin",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@devmoods/express-extras": "^0.42.0",
|
|
12
|
+
"pg-structure": "^7.15.3"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@bitiforms/adapter-devmoods-ui": "^0.11.0",
|
|
16
|
+
"@bitiforms/react": "^0.11.0",
|
|
17
|
+
"@devmoods/fetch": "^4.0.1",
|
|
18
|
+
"@devmoods/ui": "^2.3.0",
|
|
19
|
+
"@tanstack/react-query": "^5.62.2",
|
|
20
|
+
"@types/react": "^18.3.13",
|
|
21
|
+
"@types/react-dom": "^18.3.1",
|
|
22
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
23
|
+
"@vitest/coverage-v8": "2.1.8",
|
|
24
|
+
"date-fns": "^4.1.0",
|
|
25
|
+
"dotenv": "^16.4.7",
|
|
26
|
+
"express": "^4.21.1",
|
|
27
|
+
"react": "19.0.0-rc-7670501b-20241124",
|
|
28
|
+
"react-dom": "19.0.0-rc-7670501b-20241124",
|
|
29
|
+
"react-fast-compare": "^3.2.2",
|
|
30
|
+
"react-icons": "5.4.0",
|
|
31
|
+
"react-router": "^7.0.2",
|
|
32
|
+
"sass": "^1.82.0",
|
|
33
|
+
"sql-formatter": "^15.4.6",
|
|
34
|
+
"tsup": "^8.3.5",
|
|
35
|
+
"vite": "^6.0.2",
|
|
36
|
+
"vitest": "^2.1.8",
|
|
37
|
+
"zustand": "^5.0.2"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"clean": "rm -rf dist",
|
|
41
|
+
"build:server": "tsup-node src/index.ts --format esm --dts",
|
|
42
|
+
"build:client": "tsc --noEmit && vite build",
|
|
43
|
+
"build": "pnpm run build:server && pnpm run build:client",
|
|
44
|
+
"test": "vitest"
|
|
45
|
+
}
|
|
46
|
+
}
|