tina4-nodejs 3.13.148 → 3.13.150
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/CLAUDE.md +4 -3
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +10898 -9844
- package/packages/cli/src/commands/generate.ts +184 -25
- package/packages/core/dist/index.js +10864 -9810
- package/packages/core/public/bundled-component-provenance.json +2 -2
- package/packages/core/public/js/tina4-dev-admin-split.html +202 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +147 -138
- package/packages/core/src/devAdmin.ts +105 -1
- package/packages/core/src/index.ts +3 -0
- package/packages/core/src/messenger.ts +57 -1
- package/packages/core/src/metrics.ts +5 -1
- package/packages/core/src/plan.ts +1 -1
- package/packages/core/src/router.ts +11 -1
- package/packages/core/src/server.ts +5 -2
- package/packages/core/templates/crud/form.twig +26 -0
- package/packages/core/templates/crud/modals.twig +31 -0
- package/packages/core/templates/crud/page.twig +401 -0
- package/packages/core/templates/crud/table.twig +69 -0
- package/packages/frond/dist/index.js +9 -1
- package/packages/frond/src/engine.ts +9 -0
- package/packages/frond/src/index.ts +1 -1
- package/packages/orm/dist/index.js +1283 -219
- package/packages/orm/src/adapters/mongodb.ts +209 -34
- package/packages/orm/src/autoCrud.ts +9 -0
- package/packages/orm/src/crud.ts +273 -0
- package/packages/orm/src/crudHelpers.ts +194 -0
- package/packages/orm/src/crudRender.ts +202 -0
- package/packages/orm/src/crudTable.ts +171 -0
- package/packages/orm/src/index.ts +7 -1
- package/packages/orm/src/migration.ts +167 -4
- package/packages/orm/src/query.ts +99 -40
- package/packages/orm/src/sqlTranslator.ts +8 -1
- package/packages/orm/src/types.ts +19 -0
- package/types/core/src/devAdmin.d.ts +23 -0
- package/types/core/src/messenger.d.ts +4 -0
- package/types/core/src/router.d.ts +0 -5
- package/types/frond/src/engine.d.ts +6 -0
- package/types/frond/src/index.d.ts +1 -1
- package/types/orm/src/crud.d.ts +82 -0
- package/types/orm/src/crudHelpers.d.ts +63 -0
- package/types/orm/src/crudRender.d.ts +38 -0
- package/types/orm/src/crudTable.d.ts +45 -0
- package/types/orm/src/index.d.ts +3 -1
- package/types/orm/src/migration.d.ts +0 -18
- package/types/orm/src/query.d.ts +0 -12
- package/types/orm/src/types.d.ts +19 -0
|
@@ -1467,7 +1467,10 @@ var init_sqlTranslator = __esm({
|
|
|
1467
1467
|
static appendLimit(sql, limit, offset) {
|
|
1468
1468
|
if (limit === void 0 || limit === null || limit <= 0) return sql;
|
|
1469
1469
|
if (_SQLTranslator.hasTrailingLimit(sql)) return sql;
|
|
1470
|
-
const
|
|
1470
|
+
const trailing = " \n\r\f\v;";
|
|
1471
|
+
let end = sql.length;
|
|
1472
|
+
while (end > 0 && trailing.includes(sql[end - 1])) end--;
|
|
1473
|
+
const trimmed = sql.slice(0, end);
|
|
1471
1474
|
const suffix = offset !== void 0 && offset > 0 ? `LIMIT ${limit} OFFSET ${offset}` : `LIMIT ${limit}`;
|
|
1472
1475
|
return `${trimmed}
|
|
1473
1476
|
${suffix}`;
|
|
@@ -2791,11 +2794,11 @@ function getToken(payload, secretOrExpiresIn, expiresIn = 60, algorithm) {
|
|
|
2791
2794
|
if (resolvedExpiresIn !== 0) {
|
|
2792
2795
|
claims.exp = now + resolvedExpiresIn * 60;
|
|
2793
2796
|
}
|
|
2794
|
-
const
|
|
2797
|
+
const h2 = base64urlEncode(Buffer.from(JSON.stringify(header)));
|
|
2795
2798
|
const p = base64urlEncode(Buffer.from(JSON.stringify(claims)));
|
|
2796
|
-
const signingInput = `${
|
|
2799
|
+
const signingInput = `${h2}.${p}`;
|
|
2797
2800
|
const signature = sign(signingInput, resolvedSecret, resolvedAlgorithm);
|
|
2798
|
-
return `${
|
|
2801
|
+
return `${h2}.${p}.${signature}`;
|
|
2799
2802
|
}
|
|
2800
2803
|
function validToken(token, secret, algorithm) {
|
|
2801
2804
|
const resolvedSecret = secret ?? process.env.TINA4_SECRET ?? "";
|
|
@@ -2806,8 +2809,8 @@ function validToken(token, secret, algorithm) {
|
|
|
2806
2809
|
try {
|
|
2807
2810
|
const parts = token.split(".");
|
|
2808
2811
|
if (parts.length !== 3) return null;
|
|
2809
|
-
const [
|
|
2810
|
-
const header = JSON.parse(base64urlDecode(
|
|
2812
|
+
const [h2, p, sig] = parts;
|
|
2813
|
+
const header = JSON.parse(base64urlDecode(h2).toString());
|
|
2811
2814
|
if (header.alg !== resolvedAlgorithm) return null;
|
|
2812
2815
|
if (HMAC_DIGESTS.has(resolvedAlgorithm)) {
|
|
2813
2816
|
const weak = insecureSecretMessage(resolvedSecret);
|
|
@@ -2816,7 +2819,7 @@ function validToken(token, secret, algorithm) {
|
|
|
2816
2819
|
return null;
|
|
2817
2820
|
}
|
|
2818
2821
|
}
|
|
2819
|
-
const signingInput = `${
|
|
2822
|
+
const signingInput = `${h2}.${p}`;
|
|
2820
2823
|
if (!verifySignature(signingInput, sig, resolvedSecret, resolvedAlgorithm)) {
|
|
2821
2824
|
return null;
|
|
2822
2825
|
}
|
|
@@ -4593,7 +4596,7 @@ function any(path10, handler, middlewares, meta) {
|
|
|
4593
4596
|
function websocket(path10, handler, options) {
|
|
4594
4597
|
return defaultRouter.websocket(path10, handler, options);
|
|
4595
4598
|
}
|
|
4596
|
-
var WsRouteRef, RouteRef, Router, RouteGroup, defaultRouter;
|
|
4599
|
+
var WsRouteRef, RouteRef, Router, RouteGroup, TINA4_DEFAULT_ROUTER, globalRouterHost, defaultRouter;
|
|
4597
4600
|
var init_router = __esm({
|
|
4598
4601
|
"../core/src/router.ts"() {
|
|
4599
4602
|
"use strict";
|
|
@@ -5230,7 +5233,9 @@ var init_router = __esm({
|
|
|
5230
5233
|
callback(nestedGroup);
|
|
5231
5234
|
}
|
|
5232
5235
|
};
|
|
5233
|
-
|
|
5236
|
+
TINA4_DEFAULT_ROUTER = /* @__PURE__ */ Symbol.for("tina4.defaultRouter");
|
|
5237
|
+
globalRouterHost = globalThis;
|
|
5238
|
+
defaultRouter = globalRouterHost[TINA4_DEFAULT_ROUTER] ?? (globalRouterHost[TINA4_DEFAULT_ROUTER] = new Router());
|
|
5234
5239
|
}
|
|
5235
5240
|
});
|
|
5236
5241
|
|
|
@@ -5480,6 +5485,7 @@ __export(engine_exports, {
|
|
|
5480
5485
|
Frond: () => Frond,
|
|
5481
5486
|
MEMO_CACHE_MAX: () => MEMO_CACHE_MAX,
|
|
5482
5487
|
TEMPLATE_CACHE_MAX: () => TEMPLATE_CACHE_MAX,
|
|
5488
|
+
escapeHtml: () => escapeHtml,
|
|
5483
5489
|
expressionFormCache: () => expressionFormCache,
|
|
5484
5490
|
filterChainCache: () => filterChainCache,
|
|
5485
5491
|
pathParseCache: () => pathParseCache,
|
|
@@ -6404,6 +6410,9 @@ function splitArgs(raw) {
|
|
|
6404
6410
|
function htmlEscape(str) {
|
|
6405
6411
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
6406
6412
|
}
|
|
6413
|
+
function escapeHtml(value) {
|
|
6414
|
+
return htmlEscape(String(value));
|
|
6415
|
+
}
|
|
6407
6416
|
function jsEscape(value) {
|
|
6408
6417
|
let out = "";
|
|
6409
6418
|
for (const ch of String(value)) {
|
|
@@ -6580,11 +6589,11 @@ function _buildFormTokenJwt(descriptor = "") {
|
|
|
6580
6589
|
if (_formTokenSessionId) {
|
|
6581
6590
|
payload.session_id = _formTokenSessionId;
|
|
6582
6591
|
}
|
|
6583
|
-
const
|
|
6592
|
+
const h2 = _b64url(Buffer.from(JSON.stringify(header)));
|
|
6584
6593
|
const p = _b64url(Buffer.from(JSON.stringify(payload)));
|
|
6585
|
-
const sigInput = `${
|
|
6594
|
+
const sigInput = `${h2}.${p}`;
|
|
6586
6595
|
const sig = _b64url(createHmac2("sha256", secret).update(sigInput).digest());
|
|
6587
|
-
return `${
|
|
6596
|
+
return `${h2}.${p}.${sig}`;
|
|
6588
6597
|
}
|
|
6589
6598
|
function _generateFormToken(descriptor = "") {
|
|
6590
6599
|
const token = _buildFormTokenJwt(descriptor);
|
|
@@ -13503,7 +13512,7 @@ function enginePath() {
|
|
|
13503
13512
|
function runEngine(target) {
|
|
13504
13513
|
const binary = enginePath();
|
|
13505
13514
|
if (!binary) throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
|
|
13506
|
-
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json"], {
|
|
13515
|
+
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json", "--no-history"], {
|
|
13507
13516
|
encoding: "utf8",
|
|
13508
13517
|
timeout: 6e4,
|
|
13509
13518
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -13827,8 +13836,9 @@ __export(generate_exports, {
|
|
|
13827
13836
|
toSnake: () => toSnake,
|
|
13828
13837
|
toTableName: () => toTableName
|
|
13829
13838
|
});
|
|
13830
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13831
|
-
import { join as join15, relative as relative2, resolve as resolve6, sep as sep3 } from "node:path";
|
|
13839
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7, readdirSync as readdirSync6, copyFileSync } from "node:fs";
|
|
13840
|
+
import { join as join15, relative as relative2, resolve as resolve6, sep as sep3, dirname as dirname7 } from "node:path";
|
|
13841
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13832
13842
|
function ensureDir(dir) {
|
|
13833
13843
|
if (__resolution.dryRun) return;
|
|
13834
13844
|
if (!existsSync13(dir)) {
|
|
@@ -13911,7 +13921,7 @@ function currentResolution() {
|
|
|
13911
13921
|
transformations: [...__resolution.body.transformations]
|
|
13912
13922
|
};
|
|
13913
13923
|
if (__resolution.body.edit_hints) {
|
|
13914
|
-
body.edit_hints = __resolution.body.edit_hints.map((
|
|
13924
|
+
body.edit_hints = __resolution.body.edit_hints.map((h2) => ({ ...h2 }));
|
|
13915
13925
|
}
|
|
13916
13926
|
if (__resolution.body.next) {
|
|
13917
13927
|
body.next = [...__resolution.body.next];
|
|
@@ -14437,23 +14447,144 @@ ${aiFill(`delete_${singular}`, {
|
|
|
14437
14447
|
}
|
|
14438
14448
|
function generateCrud(name, flags) {
|
|
14439
14449
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
14440
|
-
const routeName = pluralizeReserved(toSnake(name));
|
|
14441
14450
|
const isPublic = Boolean(flags.public);
|
|
14442
14451
|
if (!__resolution.jsonMode) console.log(`
|
|
14443
14452
|
Generating CRUD for ${name}...
|
|
14444
14453
|
`);
|
|
14445
14454
|
generateModel(name, flags, false);
|
|
14446
|
-
|
|
14447
|
-
|
|
14448
|
-
|
|
14449
|
-
generateTest(routeName, { model: name, "secure-writes": true, public: isPublic });
|
|
14455
|
+
generateCrudAdminRoute(name, table2, isPublic);
|
|
14456
|
+
if (!flags["no-templates"]) copyCrudTemplates();
|
|
14457
|
+
generateCrudGateTest(name, table2, isPublic);
|
|
14450
14458
|
if (!__resolution.jsonMode) {
|
|
14451
14459
|
console.log(`
|
|
14452
14460
|
CRUD generation complete for ${name}.`);
|
|
14453
14461
|
console.log(" Run: tina4nodejs migrate");
|
|
14454
|
-
console.log(
|
|
14462
|
+
console.log(` Visit: /admin/${table2} for the admin UI, or /swagger for the API docs`);
|
|
14455
14463
|
}
|
|
14456
14464
|
}
|
|
14465
|
+
function generateCrudAdminRoute(model, table2, isPublic) {
|
|
14466
|
+
const dir = resolve6("src/routes/admin", table2);
|
|
14467
|
+
ensureDir(dir);
|
|
14468
|
+
pushRoute(`/admin/${table2}`);
|
|
14469
|
+
if (__resolution.target === "crud") {
|
|
14470
|
+
setResolutionField("file_path", `src/routes/admin/${table2}/get.ts`);
|
|
14471
|
+
}
|
|
14472
|
+
const secureExport = isPublic ? "" : "export const secure = true;\n\n";
|
|
14473
|
+
const publicOpt = isPublic ? ", public: true" : "";
|
|
14474
|
+
const writeDoc = isPublic ? "--public: the admin page and the writes are OPEN (no token)." : "Secure by default: the admin page AND the writes require a valid Bearer token (pass --public to open them).";
|
|
14475
|
+
const pageDoc = isPublic ? "" : `
|
|
14476
|
+
// NOTE: the admin page is secured (export const secure = true). A browser needs a valid
|
|
14477
|
+
// token/session to open it; wire your login (Auth / Session) or run
|
|
14478
|
+
// \`generate crud ${model} --public\` for an open page. The AutoCrud READ API
|
|
14479
|
+
// (GET /api/${table2}, GET /api/${table2}/{id}) stays public by AutoCrud's default.`;
|
|
14480
|
+
const content = `import ${model} from "../../../models/${model}.js";
|
|
14481
|
+
import { Crud } from "tina4-nodejs/orm";
|
|
14482
|
+
import type { Tina4Request, Tina4Response } from "tina4-nodejs";
|
|
14483
|
+
|
|
14484
|
+
// ${model} admin \u2014 one server-rendered CRUD page (searchable, sortable,
|
|
14485
|
+
// paginated table + create/edit/delete modals). The REST backend (GET list,
|
|
14486
|
+
// GET/{id}, POST, PUT, DELETE) is AutoCrud; this file owns only the GET admin
|
|
14487
|
+
// page. ${writeDoc}${pageDoc}
|
|
14488
|
+
//
|
|
14489
|
+
// Restyle the UI by editing src/templates/crud/*.twig (copied into this app).
|
|
14490
|
+
// Register the AutoCrud backend for the model at load (idempotent).
|
|
14491
|
+
await Crud.registerBackend(${model}, { public: ${isPublic} });
|
|
14492
|
+
|
|
14493
|
+
${secureExport}export const meta = { summary: "${model} admin", tags: ["admin"] };
|
|
14494
|
+
|
|
14495
|
+
export default async function (req: Tina4Request, res: Tina4Response) {
|
|
14496
|
+
return res.html(await Crud.toCrud(req, { model: ${model}, title: "${model} Admin"${publicOpt} }));
|
|
14497
|
+
}
|
|
14498
|
+
`;
|
|
14499
|
+
writeFileSafe(join15(dir, "get.ts"), content);
|
|
14500
|
+
}
|
|
14501
|
+
function frameworkCrudTemplatesDir() {
|
|
14502
|
+
const here = dirname7(fileURLToPath4(import.meta.url));
|
|
14503
|
+
const candidates = [
|
|
14504
|
+
resolve6(here, "../../../core/templates/crud"),
|
|
14505
|
+
// monorepo: packages/cli/src/commands -> packages/core/templates/crud
|
|
14506
|
+
resolve6(here, "../../core/templates/crud"),
|
|
14507
|
+
// built layout fallback
|
|
14508
|
+
resolve6(here, "../templates/crud")
|
|
14509
|
+
// flattened publish fallback
|
|
14510
|
+
];
|
|
14511
|
+
return candidates.find((c) => existsSync13(c)) ?? null;
|
|
14512
|
+
}
|
|
14513
|
+
function copyCrudTemplates() {
|
|
14514
|
+
const sourceDir = frameworkCrudTemplatesDir();
|
|
14515
|
+
if (!sourceDir) {
|
|
14516
|
+
if (!__resolution.jsonMode) console.log(" Skipped crud templates (framework templates not found)");
|
|
14517
|
+
return;
|
|
14518
|
+
}
|
|
14519
|
+
const targetDir = resolve6("src/templates/crud");
|
|
14520
|
+
ensureDir(targetDir);
|
|
14521
|
+
for (const file of readdirSync6(sourceDir).filter((f) => f.endsWith(".twig")).sort()) {
|
|
14522
|
+
const dest = join15(targetDir, file);
|
|
14523
|
+
if (existsSync13(dest)) {
|
|
14524
|
+
if (!__resolution.jsonMode) console.log(` File already exists: ${dest}`);
|
|
14525
|
+
continue;
|
|
14526
|
+
}
|
|
14527
|
+
if (__resolution.dryRun) continue;
|
|
14528
|
+
copyFileSync(join15(sourceDir, file), dest);
|
|
14529
|
+
__resolution.actionsTaken.push(`wrote ${dest}`);
|
|
14530
|
+
if (!__resolution.jsonMode) console.log(` Created ${dest}`);
|
|
14531
|
+
}
|
|
14532
|
+
}
|
|
14533
|
+
function generateCrudGateTest(model, table2, isPublic) {
|
|
14534
|
+
const dir = resolve6("tests");
|
|
14535
|
+
ensureDir(dir);
|
|
14536
|
+
const posture = isPublic ? "open (--public)" : "gated";
|
|
14537
|
+
const writeCase = isPublic ? ` // --public opened the write: no 401 gate (the handler, not the gate, answers).
|
|
14538
|
+
assert("anonymous POST is NOT gated (--public)",
|
|
14539
|
+
(await client.post("/api/${table2}", { json: { name: "test" } })).status !== 401);` : ` // Secure by default: a tokenless POST is rejected by the gate with 401.
|
|
14540
|
+
assert("anonymous POST is gated -> 401",
|
|
14541
|
+
(await client.post("/api/${table2}", { json: { name: "test" } })).status === 401);
|
|
14542
|
+
// A valid Bearer token passes the gate (the gate no longer answers 401).
|
|
14543
|
+
const token = getToken({ userId: 1 });
|
|
14544
|
+
assert("authenticated POST passes the gate (not 401)",
|
|
14545
|
+
(await client.post("/api/${table2}", { json: { name: "test" }, headers: { authorization: \`Bearer \${token}\` } })).status !== 401);`;
|
|
14546
|
+
const pageCase = isPublic ? ` assert("admin page is NOT gated (--public)", (await client.get("/admin/${table2}")).status !== 401);` : ` assert("admin page is secured -> 401 without a token", (await client.get("/admin/${table2}")).status === 401);`;
|
|
14547
|
+
const content = `/**
|
|
14548
|
+
* ${model} CRUD admin (ADR-0094) \u2014 reads public, writes ${posture}, page ${isPublic ? "public" : "secured"}.
|
|
14549
|
+
*
|
|
14550
|
+
* Real end-to-end via TestClient: no mocks \u2014 real Router, real auth gate, real
|
|
14551
|
+
* JWT, real route discovery. The admin route registers the AutoCrud backend at
|
|
14552
|
+
* load; discoverRoutes imports it, so the /api/${table2} REST routes exist here.
|
|
14553
|
+
* This gate test proves the SECURE-BY-DEFAULT posture (what registers, what is
|
|
14554
|
+
* gated vs open) \u2014 adapter-independent, since the auth gate runs before the
|
|
14555
|
+
* handler. Run with: npx tsx tests/${table2}.test.ts
|
|
14556
|
+
*/
|
|
14557
|
+
import { dirname, resolve } from "node:path";
|
|
14558
|
+
import { fileURLToPath } from "node:url";
|
|
14559
|
+
import { defaultRouter, TestClient, getToken, discoverRoutes } from "tina4-nodejs";
|
|
14560
|
+
|
|
14561
|
+
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "tina4-test-secret-0123456789abcdef0123456789";
|
|
14562
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14563
|
+
|
|
14564
|
+
let pass = 0;
|
|
14565
|
+
let fail = 0;
|
|
14566
|
+
function assert(label: string, ok: boolean): void {
|
|
14567
|
+
if (ok) { pass++; console.log(\` PASS \${label}\`); }
|
|
14568
|
+
else { fail++; console.log(\` FAIL \${label}\`); }
|
|
14569
|
+
}
|
|
14570
|
+
|
|
14571
|
+
// Discovering the routes imports src/routes/admin/${table2}/get.ts, whose
|
|
14572
|
+
// top-level Crud.registerBackend() adds the AutoCrud routes to defaultRouter.
|
|
14573
|
+
for (const def of await discoverRoutes(resolve(here, "../src/routes"))) defaultRouter.addRoute(def);
|
|
14574
|
+
const patterns = defaultRouter.getRoutes().map((r) => \`\${r.method} \${r.pattern}\`);
|
|
14575
|
+
const client = new TestClient();
|
|
14576
|
+
|
|
14577
|
+
assert("AutoCrud GET list route registered", patterns.includes("GET /api/${table2}"));
|
|
14578
|
+
assert("AutoCrud POST route registered", patterns.includes("POST /api/${table2}"));
|
|
14579
|
+
assert("admin page route registered", patterns.includes("GET /admin/${table2}"));
|
|
14580
|
+
${pageCase}
|
|
14581
|
+
${writeCase}
|
|
14582
|
+
|
|
14583
|
+
console.log(\`\\nResults: \${pass} passed, \${fail} failed\`);
|
|
14584
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
14585
|
+
`;
|
|
14586
|
+
writeFileSafe(join15(dir, `${table2}.test.ts`), content);
|
|
14587
|
+
}
|
|
14457
14588
|
function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest = true) {
|
|
14458
14589
|
const ts = timestamp();
|
|
14459
14590
|
const dir = resolve6("migrations");
|
|
@@ -14595,7 +14726,7 @@ import { Router, TestClient, getToken, discoverRoutes } from "tina4-nodejs";
|
|
|
14595
14726
|
import { initDatabase } from "tina4-nodejs/orm";
|
|
14596
14727
|
import ${model} from "../src/models/${model}.js";
|
|
14597
14728
|
|
|
14598
|
-
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "test-secret";
|
|
14729
|
+
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "tina4-test-secret-0123456789abcdef0123456789";
|
|
14599
14730
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
14600
14731
|
|
|
14601
14732
|
let pass = 0;
|
|
@@ -15558,7 +15689,7 @@ import { Router, TestClient, discoverRoutes } from "tina4-nodejs";
|
|
|
15558
15689
|
import { initDatabase } from "tina4-nodejs/orm";
|
|
15559
15690
|
import User from "../src/models/User.js";
|
|
15560
15691
|
|
|
15561
|
-
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "test-secret";
|
|
15692
|
+
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "tina4-test-secret-0123456789abcdef0123456789";
|
|
15562
15693
|
delete process.env.TINA4_API_KEY;
|
|
15563
15694
|
const here = dirname(fileURLToPath(import.meta.url));`;
|
|
15564
15695
|
const body = `await initDatabase({ url: "sqlite:///test_auth.db" });
|
|
@@ -15745,7 +15876,7 @@ var init_generate = __esm({
|
|
|
15745
15876
|
GENERATORS = {
|
|
15746
15877
|
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"] [--table-name <name>]', summary: "ORM model + matching migration" },
|
|
15747
15878
|
route: { handler: generateRoute, usage: "<name> [--model Name] [--public]", summary: "CRUD route file, secure by default (--public opens writes)" },
|
|
15748
|
-
crud: { handler: generateCrud, usage: '<Name> [--fields "..."] [--public]', summary: "Model + migration +
|
|
15879
|
+
crud: { handler: generateCrud, usage: '<Name> [--fields "..."] [--public] [--no-templates]', summary: "Model + migration + AutoCrud-backed admin page (toCrud) + editable crud/ templates + gate test" },
|
|
15749
15880
|
migration: { handler: (n, f) => generateMigration(n, f, void 0, void 0, !f["no-test"]), usage: "<description>", summary: "Timestamped migration file (UP/DOWN)" },
|
|
15750
15881
|
middleware: { handler: generateMiddleware, usage: "<Name>", summary: "Middleware with before/after hooks" },
|
|
15751
15882
|
test: { handler: generateTest, usage: "<name> [--model Name]", summary: "Test file" },
|
|
@@ -15774,8 +15905,9 @@ var init_generate = __esm({
|
|
|
15774
15905
|
],
|
|
15775
15906
|
crud: ({ name, table: table2 }) => [
|
|
15776
15907
|
`Apply the migration: npx tina4nodejs migrate`,
|
|
15777
|
-
`Serve and try: npx tina4nodejs serve -> visit /swagger`,
|
|
15778
|
-
`Run the gate test: npx tsx tests/${
|
|
15908
|
+
`Serve and try: npx tina4nodejs serve -> visit /admin/${table2} (or /swagger)`,
|
|
15909
|
+
`Run the gate test: npx tsx tests/${table2}.test.ts`,
|
|
15910
|
+
`Restyle the UI: edit src/templates/crud/*.twig`,
|
|
15779
15911
|
`Change fields: edit src/models/${name}.ts then re-run generate crud`
|
|
15780
15912
|
],
|
|
15781
15913
|
migration: () => [
|
|
@@ -18724,8 +18856,8 @@ __export(context_exports, {
|
|
|
18724
18856
|
fts5Supported: () => fts5Supported
|
|
18725
18857
|
});
|
|
18726
18858
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
18727
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync13, readdirSync as
|
|
18728
|
-
import { basename as basename4, dirname as
|
|
18859
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync13, readdirSync as readdirSync8, realpathSync as realpathSync5 } from "node:fs";
|
|
18860
|
+
import { basename as basename4, dirname as dirname9, extname as extname4, isAbsolute as isAbsolute4, join as join17, relative as relative4, resolve as resolve8 } from "node:path";
|
|
18729
18861
|
function fts5Supported() {
|
|
18730
18862
|
try {
|
|
18731
18863
|
const conn = new DatabaseSync2(":memory:");
|
|
@@ -18748,7 +18880,7 @@ function realResolve(abs) {
|
|
|
18748
18880
|
} catch {
|
|
18749
18881
|
}
|
|
18750
18882
|
try {
|
|
18751
|
-
return join17(realpathSync5(
|
|
18883
|
+
return join17(realpathSync5(dirname9(abs)), basename4(abs));
|
|
18752
18884
|
} catch {
|
|
18753
18885
|
return abs;
|
|
18754
18886
|
}
|
|
@@ -18836,7 +18968,7 @@ var init_context = __esm({
|
|
|
18836
18968
|
const check = fts5Check ?? fts5Supported;
|
|
18837
18969
|
this.available = Boolean(check());
|
|
18838
18970
|
if (!this.available) return;
|
|
18839
|
-
const parent =
|
|
18971
|
+
const parent = dirname9(this.path);
|
|
18840
18972
|
if (parent !== "" && parent !== ".") {
|
|
18841
18973
|
mkdirSync10(parent, { recursive: true });
|
|
18842
18974
|
}
|
|
@@ -18914,7 +19046,7 @@ var init_context = __esm({
|
|
|
18914
19046
|
this.root = rootAbs;
|
|
18915
19047
|
let total = 0;
|
|
18916
19048
|
const walk2 = (dir) => {
|
|
18917
|
-
const entries =
|
|
19049
|
+
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
18918
19050
|
const files = [];
|
|
18919
19051
|
const subdirs = [];
|
|
18920
19052
|
for (const e of entries) {
|
|
@@ -20830,7 +20962,7 @@ var init_mongoBackend = __esm({
|
|
|
20830
20962
|
return null;
|
|
20831
20963
|
}
|
|
20832
20964
|
}
|
|
20833
|
-
push(queue, payload,
|
|
20965
|
+
push(queue, payload, delay2, priority) {
|
|
20834
20966
|
const id = randomUUID4();
|
|
20835
20967
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20836
20968
|
const job = {
|
|
@@ -20839,7 +20971,7 @@ var init_mongoBackend = __esm({
|
|
|
20839
20971
|
status: "pending",
|
|
20840
20972
|
createdAt: now,
|
|
20841
20973
|
attempts: 0,
|
|
20842
|
-
delayUntil:
|
|
20974
|
+
delayUntil: delay2 ? new Date(Date.now() + delay2 * 1e3).toISOString() : null,
|
|
20843
20975
|
// The pop sort has always been { priority: -1, createdAt: 1 }, but this
|
|
20844
20976
|
// field was never written, so every job scored undefined and the queue
|
|
20845
20977
|
// ran pure FIFO. Priority did not even reach here: the backend interface
|
|
@@ -21008,7 +21140,7 @@ var init_job = __esm({
|
|
|
21008
21140
|
});
|
|
21009
21141
|
|
|
21010
21142
|
// ../core/src/queueBackends/liteBackend.ts
|
|
21011
|
-
import { mkdirSync as mkdirSync11, readdirSync as
|
|
21143
|
+
import { mkdirSync as mkdirSync11, readdirSync as readdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9, unlinkSync as unlinkSync6, existsSync as existsSync16 } from "node:fs";
|
|
21012
21144
|
import { join as join18 } from "node:path";
|
|
21013
21145
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
21014
21146
|
var LiteBackend;
|
|
@@ -21070,7 +21202,7 @@ var init_liteBackend = __esm({
|
|
|
21070
21202
|
*/
|
|
21071
21203
|
close() {
|
|
21072
21204
|
}
|
|
21073
|
-
push(queue, payload,
|
|
21205
|
+
push(queue, payload, delay2, priority) {
|
|
21074
21206
|
const dir = this.ensureDir(queue);
|
|
21075
21207
|
const id = randomUUID5();
|
|
21076
21208
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -21080,7 +21212,7 @@ var init_liteBackend = __esm({
|
|
|
21080
21212
|
status: "pending",
|
|
21081
21213
|
createdAt: now,
|
|
21082
21214
|
attempts: 0,
|
|
21083
|
-
delayUntil:
|
|
21215
|
+
delayUntil: delay2 ? new Date(Date.now() + delay2 * 1e3).toISOString() : null,
|
|
21084
21216
|
priority: priority ?? 0,
|
|
21085
21217
|
topic: queue,
|
|
21086
21218
|
error: void 0
|
|
@@ -21099,7 +21231,7 @@ var init_liteBackend = __esm({
|
|
|
21099
21231
|
const dir = this.ensureDir(queue);
|
|
21100
21232
|
let filenames;
|
|
21101
21233
|
try {
|
|
21102
|
-
filenames =
|
|
21234
|
+
filenames = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21103
21235
|
} catch {
|
|
21104
21236
|
return [];
|
|
21105
21237
|
}
|
|
@@ -21164,7 +21296,7 @@ var init_liteBackend = __esm({
|
|
|
21164
21296
|
const reservedDir = this.ensureReservedDir(queue);
|
|
21165
21297
|
let filenames;
|
|
21166
21298
|
try {
|
|
21167
|
-
filenames =
|
|
21299
|
+
filenames = readdirSync9(reservedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21168
21300
|
} catch {
|
|
21169
21301
|
return;
|
|
21170
21302
|
}
|
|
@@ -21275,7 +21407,7 @@ var init_liteBackend = __esm({
|
|
|
21275
21407
|
const scanDir = isDead ? this.ensureFailedDir(queue) : isReserved ? this.ensureReservedDir(queue) : this.ensureDir(queue);
|
|
21276
21408
|
let files;
|
|
21277
21409
|
try {
|
|
21278
|
-
files =
|
|
21410
|
+
files = readdirSync9(scanDir).filter((f) => f.endsWith(".queue-data"));
|
|
21279
21411
|
} catch {
|
|
21280
21412
|
return 0;
|
|
21281
21413
|
}
|
|
@@ -21296,7 +21428,7 @@ var init_liteBackend = __esm({
|
|
|
21296
21428
|
const dir = this.ensureDir(queue);
|
|
21297
21429
|
let count = 0;
|
|
21298
21430
|
try {
|
|
21299
|
-
const files =
|
|
21431
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21300
21432
|
for (const file of files) {
|
|
21301
21433
|
unlinkSync6(join18(dir, file));
|
|
21302
21434
|
count++;
|
|
@@ -21306,7 +21438,7 @@ var init_liteBackend = __esm({
|
|
|
21306
21438
|
const failedDir = join18(dir, "failed");
|
|
21307
21439
|
try {
|
|
21308
21440
|
if (existsSync16(failedDir)) {
|
|
21309
|
-
const files =
|
|
21441
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21310
21442
|
for (const file of files) {
|
|
21311
21443
|
unlinkSync6(join18(failedDir, file));
|
|
21312
21444
|
count++;
|
|
@@ -21317,7 +21449,7 @@ var init_liteBackend = __esm({
|
|
|
21317
21449
|
const reservedDir = join18(dir, "reserved");
|
|
21318
21450
|
try {
|
|
21319
21451
|
if (existsSync16(reservedDir)) {
|
|
21320
|
-
const files =
|
|
21452
|
+
const files = readdirSync9(reservedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21321
21453
|
for (const file of files) {
|
|
21322
21454
|
unlinkSync6(join18(reservedDir, file));
|
|
21323
21455
|
count++;
|
|
@@ -21339,7 +21471,7 @@ var init_liteBackend = __esm({
|
|
|
21339
21471
|
const dir = this.ensureDir(queue);
|
|
21340
21472
|
const results = [];
|
|
21341
21473
|
try {
|
|
21342
|
-
const files =
|
|
21474
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
21343
21475
|
for (const file of files) {
|
|
21344
21476
|
try {
|
|
21345
21477
|
const job = JSON.parse(readFileSync14(join18(dir, file), "utf-8"));
|
|
@@ -21363,7 +21495,7 @@ var init_liteBackend = __esm({
|
|
|
21363
21495
|
*/
|
|
21364
21496
|
retry(queue, jobId, delaySeconds) {
|
|
21365
21497
|
try {
|
|
21366
|
-
const queues =
|
|
21498
|
+
const queues = readdirSync9(this.basePath);
|
|
21367
21499
|
for (const q of queues) {
|
|
21368
21500
|
const failedDir = join18(this.basePath, q, "failed");
|
|
21369
21501
|
const filePath = join18(failedDir, `${jobId}.queue-data`);
|
|
@@ -21389,7 +21521,7 @@ var init_liteBackend = __esm({
|
|
|
21389
21521
|
const failedDir = this.ensureFailedDir(queue);
|
|
21390
21522
|
const results = [];
|
|
21391
21523
|
try {
|
|
21392
|
-
const files =
|
|
21524
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
21393
21525
|
for (const file of files) {
|
|
21394
21526
|
try {
|
|
21395
21527
|
const job = JSON.parse(readFileSync14(join18(failedDir, file), "utf-8"));
|
|
@@ -21410,7 +21542,7 @@ var init_liteBackend = __esm({
|
|
|
21410
21542
|
if (isDead) {
|
|
21411
21543
|
const failedDir = this.ensureFailedDir(queue);
|
|
21412
21544
|
try {
|
|
21413
|
-
const files =
|
|
21545
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21414
21546
|
for (const file of files) {
|
|
21415
21547
|
try {
|
|
21416
21548
|
unlinkSync6(join18(failedDir, file));
|
|
@@ -21423,7 +21555,7 @@ var init_liteBackend = __esm({
|
|
|
21423
21555
|
} else {
|
|
21424
21556
|
const dir = this.ensureDir(queue);
|
|
21425
21557
|
try {
|
|
21426
|
-
const files =
|
|
21558
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21427
21559
|
for (const file of files) {
|
|
21428
21560
|
try {
|
|
21429
21561
|
const job = JSON.parse(readFileSync14(join18(dir, file), "utf-8"));
|
|
@@ -21449,7 +21581,7 @@ var init_liteBackend = __esm({
|
|
|
21449
21581
|
const queueDir = this.ensureDir(queue);
|
|
21450
21582
|
let count = 0;
|
|
21451
21583
|
try {
|
|
21452
|
-
const files =
|
|
21584
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21453
21585
|
for (const file of files) {
|
|
21454
21586
|
try {
|
|
21455
21587
|
const filePath = join18(failedDir, file);
|
|
@@ -21476,7 +21608,7 @@ var init_liteBackend = __esm({
|
|
|
21476
21608
|
const dir = this.ensureDir(queue);
|
|
21477
21609
|
let files;
|
|
21478
21610
|
try {
|
|
21479
|
-
files =
|
|
21611
|
+
files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21480
21612
|
} catch {
|
|
21481
21613
|
return null;
|
|
21482
21614
|
}
|
|
@@ -21717,11 +21849,11 @@ var init_queue = __esm({
|
|
|
21717
21849
|
*
|
|
21718
21850
|
* @param priority — Higher value = higher priority. Default 0.
|
|
21719
21851
|
*/
|
|
21720
|
-
push(payload,
|
|
21852
|
+
push(payload, delay2, priority = 0) {
|
|
21721
21853
|
if (this.externalBackend) {
|
|
21722
|
-
return this.externalBackend.push(this.topic, payload,
|
|
21854
|
+
return this.externalBackend.push(this.topic, payload, delay2, priority);
|
|
21723
21855
|
}
|
|
21724
|
-
return this.liteBackend.push(this.topic, payload,
|
|
21856
|
+
return this.liteBackend.push(this.topic, payload, delay2, priority);
|
|
21725
21857
|
}
|
|
21726
21858
|
/**
|
|
21727
21859
|
* Atomically claim the next available job from this queue's topic. Returns null if empty.
|
|
@@ -21952,11 +22084,11 @@ var init_queue = __esm({
|
|
|
21952
22084
|
* shares the same retarget path consume()/process() use — keeping produce and
|
|
21953
22085
|
* consume symmetric on the same topic argument.
|
|
21954
22086
|
*/
|
|
21955
|
-
produce(topic, payload, priority = 0,
|
|
22087
|
+
produce(topic, payload, priority = 0, delay2 = 0) {
|
|
21956
22088
|
const previous = this.topic;
|
|
21957
22089
|
this.retarget(topic);
|
|
21958
22090
|
try {
|
|
21959
|
-
return this.push(payload,
|
|
22091
|
+
return this.push(payload, delay2, priority);
|
|
21960
22092
|
} finally {
|
|
21961
22093
|
this.retarget(previous);
|
|
21962
22094
|
}
|
|
@@ -22558,7 +22690,7 @@ var init_plan = __esm({
|
|
|
22558
22690
|
const existing = (currentPlan.steps || []).map((s) => s.text || "");
|
|
22559
22691
|
const title = currentPlan.title || target;
|
|
22560
22692
|
const goal = currentPlan.goal || "";
|
|
22561
|
-
const aiUrl = process.env.TINA4_AI_URL || "http://localhost:
|
|
22693
|
+
const aiUrl = process.env.TINA4_AI_URL || "http://localhost:11435/api/chat";
|
|
22562
22694
|
const aiModel = process.env.TINA4_AI_MODEL || "qwen2.5-coder:14b";
|
|
22563
22695
|
const prompts = fleshPrompt(title, goal, existing, prompt);
|
|
22564
22696
|
const fetched = await fetchFleshReply(aiUrl, aiModel, prompts);
|
|
@@ -22957,7 +23089,7 @@ var init_projectIndex = __esm({
|
|
|
22957
23089
|
}
|
|
22958
23090
|
}
|
|
22959
23091
|
hits.sort((a, b) => b[0] - a[0]);
|
|
22960
|
-
return hits.slice(0, Math.max(1, limit)).map((
|
|
23092
|
+
return hits.slice(0, Math.max(1, limit)).map((h2) => h2[1]);
|
|
22961
23093
|
},
|
|
22962
23094
|
fileEntry(relPath) {
|
|
22963
23095
|
ProjectIndex.refresh();
|
|
@@ -23486,7 +23618,7 @@ __export(docs_exports, {
|
|
|
23486
23618
|
});
|
|
23487
23619
|
import * as fs8 from "node:fs";
|
|
23488
23620
|
import * as path8 from "node:path";
|
|
23489
|
-
import { fileURLToPath as
|
|
23621
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
23490
23622
|
function detectFrameworkRoots() {
|
|
23491
23623
|
const corePackageRoot = path8.resolve(__DIRNAME, "..");
|
|
23492
23624
|
const packagesRoot = path8.resolve(corePackageRoot, "..");
|
|
@@ -23893,7 +24025,7 @@ var init_docs = __esm({
|
|
|
23893
24025
|
"hash"
|
|
23894
24026
|
]);
|
|
23895
24027
|
_DOCS_CACHE = /* @__PURE__ */ new Map();
|
|
23896
|
-
__FILENAME =
|
|
24028
|
+
__FILENAME = fileURLToPath5(import.meta.url);
|
|
23897
24029
|
__DIRNAME = path8.dirname(__FILENAME);
|
|
23898
24030
|
USER_DIRS = ["src/orm", "src/routes", "src/app", "src/services", "src/models"];
|
|
23899
24031
|
CAMEL_SPLIT = /([a-z0-9])([A-Z])|([A-Z]+)([A-Z][a-z])/g;
|
|
@@ -24270,11 +24402,12 @@ ${end}
|
|
|
24270
24402
|
|
|
24271
24403
|
// ../core/src/devAdmin.ts
|
|
24272
24404
|
import { cpus as osCpus } from "node:os";
|
|
24273
|
-
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync20, readdirSync as
|
|
24274
|
-
import { join as join22, dirname as
|
|
24275
|
-
import { fileURLToPath as
|
|
24405
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync20, readdirSync as readdirSync13, mkdirSync as mkdirSync14, copyFileSync as copyFileSync2, statSync as statSync15, openSync as openSync4, closeSync as closeSync4, fstatSync as fstatSync2, fchmodSync as fchmodSync2, ftruncateSync, constants as constants3, realpathSync as realpathSync6, lstatSync } from "node:fs";
|
|
24406
|
+
import { join as join22, dirname as dirname11, resolve as resolve12, relative as relative9, sep as sep6, basename as basename5 } from "node:path";
|
|
24407
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
24276
24408
|
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
24277
|
-
|
|
24409
|
+
import net2 from "node:net";
|
|
24410
|
+
function escapeHtml2(value) {
|
|
24278
24411
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
24279
24412
|
}
|
|
24280
24413
|
function devSameOriginOk(req2) {
|
|
@@ -24312,6 +24445,42 @@ function devHostAllowed(hostHeader) {
|
|
|
24312
24445
|
if (configured) allowed.add(configured);
|
|
24313
24446
|
return allowed.has(name);
|
|
24314
24447
|
}
|
|
24448
|
+
function ipInCidr(ip, entry) {
|
|
24449
|
+
try {
|
|
24450
|
+
ip = (ip ?? "").trim();
|
|
24451
|
+
if (ip.toLowerCase().startsWith("::ffff:")) ip = ip.slice(7);
|
|
24452
|
+
const ipFam = net2.isIP(ip);
|
|
24453
|
+
if (!ipFam) return false;
|
|
24454
|
+
entry = (entry ?? "").trim();
|
|
24455
|
+
const bl = new net2.BlockList();
|
|
24456
|
+
if (entry.includes("/")) {
|
|
24457
|
+
const [addr, bitsStr] = entry.split("/");
|
|
24458
|
+
const bits = Number(bitsStr);
|
|
24459
|
+
const fam = net2.isIP(addr);
|
|
24460
|
+
if (!fam || !Number.isInteger(bits)) return false;
|
|
24461
|
+
bl.addSubnet(addr, bits, fam === 4 ? "ipv4" : "ipv6");
|
|
24462
|
+
} else {
|
|
24463
|
+
const fam = net2.isIP(entry);
|
|
24464
|
+
if (!fam) return false;
|
|
24465
|
+
bl.addAddress(entry, fam === 4 ? "ipv4" : "ipv6");
|
|
24466
|
+
}
|
|
24467
|
+
return bl.check(ip, ipFam === 4 ? "ipv4" : "ipv6");
|
|
24468
|
+
} catch {
|
|
24469
|
+
return false;
|
|
24470
|
+
}
|
|
24471
|
+
}
|
|
24472
|
+
function devPeerAllowed(remoteIp) {
|
|
24473
|
+
const ip = (remoteIp ?? "").trim();
|
|
24474
|
+
if (isLoopback(ip)) return true;
|
|
24475
|
+
const configured = (process.env.TINA4_DEV_ALLOWED_PEERS ?? "").trim();
|
|
24476
|
+
if (!configured) return false;
|
|
24477
|
+
return configured.split(",").some((entry) => entry.trim() !== "" && ipInCidr(ip, entry));
|
|
24478
|
+
}
|
|
24479
|
+
function devToolbarAllowed(req2) {
|
|
24480
|
+
if (!devHostAllowed(req2.header("host"))) return false;
|
|
24481
|
+
const peer = req2.socket?.remoteAddress ?? "";
|
|
24482
|
+
return devPeerAllowed(peer);
|
|
24483
|
+
}
|
|
24315
24484
|
function devRequestDenial(req2) {
|
|
24316
24485
|
const path10 = (req2.path ?? "").replace(/\/+/g, "/");
|
|
24317
24486
|
const isMcp = DEV_MCP_PREFIXES.some((prefix) => path10 === prefix || path10.startsWith(prefix + "/"));
|
|
@@ -24321,9 +24490,12 @@ function devRequestDenial(req2) {
|
|
|
24321
24490
|
if (!devSameOriginOk(req2)) {
|
|
24322
24491
|
return { status: 403, error: "dev-admin: refused (cross-origin request)" };
|
|
24323
24492
|
}
|
|
24493
|
+
if (DEV_PUBLIC_ASSETS.has(path10)) {
|
|
24494
|
+
return null;
|
|
24495
|
+
}
|
|
24324
24496
|
if (!isMcp) {
|
|
24325
24497
|
const peer = req2.socket?.remoteAddress ?? "";
|
|
24326
|
-
if (!(
|
|
24498
|
+
if (!(devPeerAllowed(peer) || mcpTokenOk(req2, true))) {
|
|
24327
24499
|
return { status: 403, error: "dev-admin: refused (non-loopback peer)" };
|
|
24328
24500
|
}
|
|
24329
24501
|
}
|
|
@@ -24412,7 +24584,7 @@ function mapQueueJob(job, topic, status2) {
|
|
|
24412
24584
|
function readQueueDir(dir, topic, status2) {
|
|
24413
24585
|
if (!existsSync20(dir)) return [];
|
|
24414
24586
|
const jobs = [];
|
|
24415
|
-
for (const filename of
|
|
24587
|
+
for (const filename of readdirSync13(dir).sort()) {
|
|
24416
24588
|
if (!filename.endsWith(".queue-data")) continue;
|
|
24417
24589
|
try {
|
|
24418
24590
|
jobs.push(mapQueueJob(JSON.parse(readFileSync18(join22(dir, filename), "utf-8")), topic, status2));
|
|
@@ -24554,12 +24726,12 @@ function upsertDevEnvVar(key, value) {
|
|
|
24554
24726
|
}
|
|
24555
24727
|
function formatUptime(seconds) {
|
|
24556
24728
|
const d = Math.floor(seconds / 86400);
|
|
24557
|
-
const
|
|
24729
|
+
const h2 = Math.floor(seconds % 86400 / 3600);
|
|
24558
24730
|
const m = Math.floor(seconds % 3600 / 60);
|
|
24559
24731
|
const s = Math.floor(seconds % 60);
|
|
24560
24732
|
const parts = [];
|
|
24561
24733
|
if (d > 0) parts.push(`${d}d`);
|
|
24562
|
-
if (
|
|
24734
|
+
if (h2 > 0) parts.push(`${h2}h`);
|
|
24563
24735
|
if (m > 0) parts.push(`${m}m`);
|
|
24564
24736
|
parts.push(`${s}s`);
|
|
24565
24737
|
return parts.join(" ");
|
|
@@ -24580,7 +24752,7 @@ function parseEnvFile() {
|
|
|
24580
24752
|
function walkDirRecursive(dir) {
|
|
24581
24753
|
const results = [];
|
|
24582
24754
|
if (!existsSync20(dir)) return results;
|
|
24583
|
-
for (const entry of
|
|
24755
|
+
for (const entry of readdirSync13(dir)) {
|
|
24584
24756
|
const full = join22(dir, entry);
|
|
24585
24757
|
if (statSync15(full).isDirectory()) {
|
|
24586
24758
|
results.push(...walkDirRecursive(full));
|
|
@@ -24610,8 +24782,8 @@ function handleGalleryDeploy(router) {
|
|
|
24610
24782
|
for (const srcFile of allFiles) {
|
|
24611
24783
|
const rel = relative9(gallerySrc, srcFile);
|
|
24612
24784
|
const dest = join22(projectSrc, rel);
|
|
24613
|
-
mkdirSync14(
|
|
24614
|
-
|
|
24785
|
+
mkdirSync14(dirname11(dest), { recursive: true });
|
|
24786
|
+
copyFileSync2(srcFile, dest);
|
|
24615
24787
|
copied.push(rel);
|
|
24616
24788
|
}
|
|
24617
24789
|
try {
|
|
@@ -24641,9 +24813,9 @@ function realPathOf(target) {
|
|
|
24641
24813
|
present = true;
|
|
24642
24814
|
} catch {
|
|
24643
24815
|
}
|
|
24644
|
-
if (present ||
|
|
24816
|
+
if (present || dirname11(existing) === existing) break;
|
|
24645
24817
|
tail.unshift(basename5(existing));
|
|
24646
|
-
existing =
|
|
24818
|
+
existing = dirname11(existing);
|
|
24647
24819
|
}
|
|
24648
24820
|
return join22(realpathSync6(existing), ...tail);
|
|
24649
24821
|
}
|
|
@@ -24767,9 +24939,9 @@ function applyMcpOutcome(res, outcome) {
|
|
|
24767
24939
|
}
|
|
24768
24940
|
function renderToolbarHtml(ctx) {
|
|
24769
24941
|
const nodeVersion = process.version;
|
|
24770
|
-
const method =
|
|
24771
|
-
const path10 =
|
|
24772
|
-
const matchedPattern =
|
|
24942
|
+
const method = escapeHtml2(ctx.method);
|
|
24943
|
+
const path10 = escapeHtml2(ctx.path);
|
|
24944
|
+
const matchedPattern = escapeHtml2(ctx.matchedPattern);
|
|
24773
24945
|
const reload = ctx.reload === false ? "0" : "1";
|
|
24774
24946
|
return `<link rel="stylesheet" href="/__dev/toolbar.css">
|
|
24775
24947
|
<div id="tina4-dev-toolbar" data-reload="${reload}">
|
|
@@ -24955,7 +25127,7 @@ function toolbarJs() {
|
|
|
24955
25127
|
connect();
|
|
24956
25128
|
})();`;
|
|
24957
25129
|
}
|
|
24958
|
-
var cpuCount, DEV_MCP_PREFIXES, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleToolbarCss, handleToolbarJs;
|
|
25130
|
+
var cpuCount, DEV_MCP_PREFIXES, DEV_PUBLIC_ASSETS, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleDevAdminSplit, handleToolbarCss, handleToolbarJs;
|
|
24959
25131
|
var init_devAdmin = __esm({
|
|
24960
25132
|
"../core/src/devAdmin.ts"() {
|
|
24961
25133
|
"use strict";
|
|
@@ -24968,6 +25140,7 @@ var init_devAdmin = __esm({
|
|
|
24968
25140
|
init_version();
|
|
24969
25141
|
cpuCount = osCpus().length;
|
|
24970
25142
|
DEV_MCP_PREFIXES = ["/__dev/api/mcp", "/__dev/mcp"];
|
|
25143
|
+
DEV_PUBLIC_ASSETS = /* @__PURE__ */ new Set(["/__dev/toolbar.css", "/__dev/toolbar.js"]);
|
|
24971
25144
|
DEV_SECRET_BASENAMES = /* @__PURE__ */ new Set([
|
|
24972
25145
|
".env",
|
|
24973
25146
|
".envrc",
|
|
@@ -25424,6 +25597,7 @@ var init_devAdmin = __esm({
|
|
|
25424
25597
|
{ method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
|
|
25425
25598
|
// JS asset
|
|
25426
25599
|
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
25600
|
+
{ method: "GET", pattern: "/__dev/split", handler: handleDevAdminSplit },
|
|
25427
25601
|
// Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
|
|
25428
25602
|
// CSP-clean (no inline style=, onclick=, or <script>) under a strict
|
|
25429
25603
|
// `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
|
|
@@ -25669,7 +25843,7 @@ var init_devAdmin = __esm({
|
|
|
25669
25843
|
const queueDir = queueBasePath2();
|
|
25670
25844
|
let topics = [];
|
|
25671
25845
|
if (existsSync20(queueDir)) {
|
|
25672
|
-
topics =
|
|
25846
|
+
topics = readdirSync13(queueDir).filter((d) => {
|
|
25673
25847
|
try {
|
|
25674
25848
|
return statSync15(join22(queueDir, d)).isDirectory();
|
|
25675
25849
|
} catch {
|
|
@@ -26173,13 +26347,13 @@ var init_devAdmin = __esm({
|
|
|
26173
26347
|
res.json({ success: false, error: msg });
|
|
26174
26348
|
}
|
|
26175
26349
|
};
|
|
26176
|
-
__devAdminFilename =
|
|
26177
|
-
__devAdminDirname =
|
|
26350
|
+
__devAdminFilename = fileURLToPath6(import.meta.url);
|
|
26351
|
+
__devAdminDirname = dirname11(__devAdminFilename);
|
|
26178
26352
|
handleGalleryList = (_req, res) => {
|
|
26179
26353
|
const galleryDir = resolve12(__devAdminDirname, "..", "gallery");
|
|
26180
26354
|
const items = [];
|
|
26181
26355
|
if (existsSync20(galleryDir)) {
|
|
26182
|
-
const entries =
|
|
26356
|
+
const entries = readdirSync13(galleryDir).sort();
|
|
26183
26357
|
for (const entry of entries) {
|
|
26184
26358
|
const entryPath = join22(galleryDir, entry);
|
|
26185
26359
|
const metaFile = join22(entryPath, "meta.json");
|
|
@@ -26283,7 +26457,7 @@ var init_devAdmin = __esm({
|
|
|
26283
26457
|
if (cwdInGit !== "") cwdInGit += "/";
|
|
26284
26458
|
}
|
|
26285
26459
|
const entries = [];
|
|
26286
|
-
for (const name of
|
|
26460
|
+
for (const name of readdirSync13(target).sort()) {
|
|
26287
26461
|
if (devFilesHidden(name)) continue;
|
|
26288
26462
|
const full = join22(target, name);
|
|
26289
26463
|
const entryRel = relative9(root, full).replace(/\\/g, "/");
|
|
@@ -26314,7 +26488,7 @@ var init_devAdmin = __esm({
|
|
|
26314
26488
|
if (isDir) {
|
|
26315
26489
|
hasChildren = false;
|
|
26316
26490
|
try {
|
|
26317
|
-
for (const c of
|
|
26491
|
+
for (const c of readdirSync13(full)) {
|
|
26318
26492
|
if (devFilesHidden(c)) continue;
|
|
26319
26493
|
hasChildren = true;
|
|
26320
26494
|
break;
|
|
@@ -26406,7 +26580,7 @@ var init_devAdmin = __esm({
|
|
|
26406
26580
|
return;
|
|
26407
26581
|
}
|
|
26408
26582
|
try {
|
|
26409
|
-
mkdirSync14(
|
|
26583
|
+
mkdirSync14(dirname11(target), { recursive: true });
|
|
26410
26584
|
const existed = existsSync20(target);
|
|
26411
26585
|
writeFileSync13(target, content, "utf-8");
|
|
26412
26586
|
try {
|
|
@@ -26478,7 +26652,7 @@ var init_devAdmin = __esm({
|
|
|
26478
26652
|
}
|
|
26479
26653
|
try {
|
|
26480
26654
|
const { renameSync: renameSync3 } = await import("node:fs");
|
|
26481
|
-
mkdirSync14(
|
|
26655
|
+
mkdirSync14(dirname11(dst), { recursive: true });
|
|
26482
26656
|
renameSync3(src, dst);
|
|
26483
26657
|
res.json({ ok: true, from: relative9(root, src), to: relative9(root, dst) });
|
|
26484
26658
|
} catch (e) {
|
|
@@ -26499,8 +26673,8 @@ var init_devAdmin = __esm({
|
|
|
26499
26673
|
return;
|
|
26500
26674
|
}
|
|
26501
26675
|
try {
|
|
26502
|
-
const { rmSync } = await import("node:fs");
|
|
26503
|
-
|
|
26676
|
+
const { rmSync: rmSync2 } = await import("node:fs");
|
|
26677
|
+
rmSync2(target, { recursive: true, force: true });
|
|
26504
26678
|
res.json({ ok: true, deleted: relative9(root, target) });
|
|
26505
26679
|
} catch (e) {
|
|
26506
26680
|
res.json({ error: e.message }, 500);
|
|
@@ -26835,10 +27009,10 @@ var init_devAdmin = __esm({
|
|
|
26835
27009
|
});
|
|
26836
27010
|
};
|
|
26837
27011
|
handleDevAdminJs = async (_req, res) => {
|
|
26838
|
-
const { readFileSync: readFileSync27, existsSync:
|
|
26839
|
-
const { dirname:
|
|
26840
|
-
const { fileURLToPath:
|
|
26841
|
-
const dir =
|
|
27012
|
+
const { readFileSync: readFileSync27, existsSync: existsSync29 } = await import("node:fs");
|
|
27013
|
+
const { dirname: dirname17, join: join33, resolve: resolve21 } = await import("node:path");
|
|
27014
|
+
const { fileURLToPath: fileURLToPath10 } = await import("node:url");
|
|
27015
|
+
const dir = dirname17(fileURLToPath10(import.meta.url));
|
|
26842
27016
|
const candidates = [
|
|
26843
27017
|
join33(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
26844
27018
|
// src/../public/js/
|
|
@@ -26849,7 +27023,7 @@ var init_devAdmin = __esm({
|
|
|
26849
27023
|
// project public/
|
|
26850
27024
|
];
|
|
26851
27025
|
for (const jsPath of candidates) {
|
|
26852
|
-
if (
|
|
27026
|
+
if (existsSync29(jsPath)) {
|
|
26853
27027
|
try {
|
|
26854
27028
|
const content = readFileSync27(jsPath, "utf-8");
|
|
26855
27029
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
@@ -26862,6 +27036,31 @@ var init_devAdmin = __esm({
|
|
|
26862
27036
|
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
26863
27037
|
res.raw.end("tina4-dev-admin.min.js not found");
|
|
26864
27038
|
};
|
|
27039
|
+
handleDevAdminSplit = async (_req, res) => {
|
|
27040
|
+
const { readFileSync: readFileSync27, existsSync: existsSync29 } = await import("node:fs");
|
|
27041
|
+
const { dirname: dirname17, join: join33, resolve: resolve21 } = await import("node:path");
|
|
27042
|
+
const { fileURLToPath: fileURLToPath10 } = await import("node:url");
|
|
27043
|
+
const dir = dirname17(fileURLToPath10(import.meta.url));
|
|
27044
|
+
const candidates = [
|
|
27045
|
+
join33(dir, "..", "public", "js", "tina4-dev-admin-split.html"),
|
|
27046
|
+
join33(dir, "..", "..", "public", "js", "tina4-dev-admin-split.html"),
|
|
27047
|
+
resolve21(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin-split.html"),
|
|
27048
|
+
resolve21(process.cwd(), "public", "js", "tina4-dev-admin-split.html")
|
|
27049
|
+
];
|
|
27050
|
+
for (const path10 of candidates) {
|
|
27051
|
+
if (existsSync29(path10)) {
|
|
27052
|
+
try {
|
|
27053
|
+
const content = readFileSync27(path10, "utf-8");
|
|
27054
|
+
res.raw.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
27055
|
+
res.raw.end(content);
|
|
27056
|
+
return;
|
|
27057
|
+
} catch {
|
|
27058
|
+
}
|
|
27059
|
+
}
|
|
27060
|
+
}
|
|
27061
|
+
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
27062
|
+
res.raw.end("tina4-dev-admin-split.html not found");
|
|
27063
|
+
};
|
|
26865
27064
|
handleToolbarCss = (_req, res) => {
|
|
26866
27065
|
res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
26867
27066
|
res.raw.end(toolbarCss());
|
|
@@ -26874,7 +27073,7 @@ var init_devAdmin = __esm({
|
|
|
26874
27073
|
});
|
|
26875
27074
|
|
|
26876
27075
|
// ../core/src/i18n.ts
|
|
26877
|
-
import { readFileSync as readFileSync19, readdirSync as
|
|
27076
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync14, existsSync as existsSync21 } from "node:fs";
|
|
26878
27077
|
import { join as join23, resolve as resolve13 } from "node:path";
|
|
26879
27078
|
var I18n;
|
|
26880
27079
|
var init_i18n = __esm({
|
|
@@ -26956,7 +27155,7 @@ var init_i18n = __esm({
|
|
|
26956
27155
|
return [this._defaultLocale];
|
|
26957
27156
|
}
|
|
26958
27157
|
try {
|
|
26959
|
-
const files =
|
|
27158
|
+
const files = readdirSync14(this._localeDir);
|
|
26960
27159
|
const locales = files.filter((f) => f.endsWith(".json") || f.endsWith(".yml") || f.endsWith(".yaml")).map((f) => f.replace(/\.(json|ya?ml)$/, "")).sort();
|
|
26961
27160
|
return locales.length > 0 ? locales : [this._defaultLocale];
|
|
26962
27161
|
} catch {
|
|
@@ -28212,10 +28411,10 @@ var init_docsAutoDiscovery = __esm({
|
|
|
28212
28411
|
|
|
28213
28412
|
// ../core/src/server.ts
|
|
28214
28413
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
28215
|
-
import { resolve as resolve15, dirname as
|
|
28216
|
-
import { existsSync as existsSync23, readdirSync as
|
|
28414
|
+
import { resolve as resolve15, dirname as dirname12, join as join25, relative as relative10 } from "node:path";
|
|
28415
|
+
import { existsSync as existsSync23, readdirSync as readdirSync15, readFileSync as readFileSync21, statSync as statSync16 } from "node:fs";
|
|
28217
28416
|
import { isatty } from "node:tty";
|
|
28218
|
-
import { fileURLToPath as
|
|
28417
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
28219
28418
|
import { execFileSync as execFileSync3, exec } from "node:child_process";
|
|
28220
28419
|
import cluster from "node:cluster";
|
|
28221
28420
|
import os2 from "node:os";
|
|
@@ -28255,7 +28454,7 @@ async function autoMigrateOnStartup(migrationDir = "migrations", base = process.
|
|
|
28255
28454
|
if (!existsSync23(dir)) return;
|
|
28256
28455
|
let hasSql = false;
|
|
28257
28456
|
try {
|
|
28258
|
-
hasSql =
|
|
28457
|
+
hasSql = readdirSync15(dir).some((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"));
|
|
28259
28458
|
} catch {
|
|
28260
28459
|
return;
|
|
28261
28460
|
}
|
|
@@ -28425,7 +28624,7 @@ function injectDevToolbar(html, ctx) {
|
|
|
28425
28624
|
function walkGalleryFiles(dir) {
|
|
28426
28625
|
const results = [];
|
|
28427
28626
|
if (!existsSync23(dir)) return results;
|
|
28428
|
-
for (const f of
|
|
28627
|
+
for (const f of readdirSync15(dir)) {
|
|
28429
28628
|
const full = join25(dir, f);
|
|
28430
28629
|
if (statSync16(full).isDirectory()) results.push(...walkGalleryFiles(full));
|
|
28431
28630
|
else results.push(full);
|
|
@@ -28437,7 +28636,7 @@ function getGalleryDeployedState() {
|
|
|
28437
28636
|
const state = {};
|
|
28438
28637
|
if (!existsSync23(galleryDir)) return state;
|
|
28439
28638
|
try {
|
|
28440
|
-
const entries =
|
|
28639
|
+
const entries = readdirSync15(galleryDir).sort();
|
|
28441
28640
|
for (const entry of entries) {
|
|
28442
28641
|
const entryPath = join25(galleryDir, entry);
|
|
28443
28642
|
const metaFile = join25(entryPath, "meta.json");
|
|
@@ -28488,7 +28687,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
28488
28687
|
const pagesDir = resolve15(templatesDir, TEMPLATE_PAGES_DIR);
|
|
28489
28688
|
if (existsSync23(pagesDir)) {
|
|
28490
28689
|
const scan = (dir, prefix) => {
|
|
28491
|
-
for (const entry of
|
|
28690
|
+
for (const entry of readdirSync15(dir, { withFileTypes: true })) {
|
|
28492
28691
|
if (entry.name.startsWith("_")) continue;
|
|
28493
28692
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
28494
28693
|
if (entry.isDirectory()) {
|
|
@@ -28807,7 +29006,7 @@ function isInjectableHtml(res) {
|
|
|
28807
29006
|
return isHtmlResponse(res) && !res.raw.getHeader("content-encoding");
|
|
28808
29007
|
}
|
|
28809
29008
|
function injectIntoHtml(ctx, devToolbar, html) {
|
|
28810
|
-
if (!devToolbar) return injectFeedbackWidget(ctx.req, html);
|
|
29009
|
+
if (!devToolbar || !devToolbarAllowed(ctx.req)) return injectFeedbackWidget(ctx.req, html);
|
|
28811
29010
|
const toolbarCtx = {
|
|
28812
29011
|
version: TINA4_VERSION,
|
|
28813
29012
|
method: ctx.req.method ?? "GET",
|
|
@@ -29175,7 +29374,7 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
29175
29374
|
const localeDir = resolve15(base, process.env.TINA4_LOCALE_DIR ?? "src/locales");
|
|
29176
29375
|
if (!existsSync23(localeDir)) return;
|
|
29177
29376
|
try {
|
|
29178
|
-
const localeFiles =
|
|
29377
|
+
const localeFiles = readdirSync15(localeDir).filter((file) => file.endsWith(".json"));
|
|
29179
29378
|
if (localeFiles.length === 0 || frondEngine.globals?.t) return;
|
|
29180
29379
|
const i18nInstance = new I18n(process.env.TINA4_LOCALE ?? "en", localeDir);
|
|
29181
29380
|
frondEngine.addGlobal("t", (key, params) => i18nInstance.t(key, params));
|
|
@@ -29590,8 +29789,8 @@ var init_server = __esm({
|
|
|
29590
29789
|
init_i18n();
|
|
29591
29790
|
init_background();
|
|
29592
29791
|
init_version();
|
|
29593
|
-
__filename =
|
|
29594
|
-
__dirname =
|
|
29792
|
+
__filename = fileURLToPath7(import.meta.url);
|
|
29793
|
+
__dirname = dirname12(__filename);
|
|
29595
29794
|
BUILTIN_ERROR_TEMPLATES_DIR = resolve15(__dirname, "..", "templates");
|
|
29596
29795
|
BUILTIN_PUBLIC_DIR = resolve15(__dirname, "..", "public");
|
|
29597
29796
|
swaggerAssetsEnabled = false;
|
|
@@ -29753,7 +29952,7 @@ var init_env = __esm({
|
|
|
29753
29952
|
|
|
29754
29953
|
// ../core/src/fakeData.ts
|
|
29755
29954
|
import { randomInt, randomUUID as randomUUID6 } from "node:crypto";
|
|
29756
|
-
import { existsSync as existsSync24, readdirSync as
|
|
29955
|
+
import { existsSync as existsSync24, readdirSync as readdirSync16 } from "node:fs";
|
|
29757
29956
|
import { resolve as resolve16, join as join26 } from "node:path";
|
|
29758
29957
|
function mulberry32(seed) {
|
|
29759
29958
|
let s = seed | 0;
|
|
@@ -30253,7 +30452,7 @@ var init_fakeData = __esm({
|
|
|
30253
30452
|
async seedDir(seedDir) {
|
|
30254
30453
|
const dir = resolve16(seedDir ?? "src/seeds");
|
|
30255
30454
|
if (!existsSync24(dir)) return [];
|
|
30256
|
-
const files =
|
|
30455
|
+
const files = readdirSync16(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
|
|
30257
30456
|
const executed = [];
|
|
30258
30457
|
for (const file of files) {
|
|
30259
30458
|
const fullPath = join26(dir, file);
|
|
@@ -30359,7 +30558,7 @@ var init_mqttMessage = __esm({
|
|
|
30359
30558
|
});
|
|
30360
30559
|
|
|
30361
30560
|
// ../core/src/mqtt.ts
|
|
30362
|
-
import
|
|
30561
|
+
import net3 from "node:net";
|
|
30363
30562
|
import tls from "node:tls";
|
|
30364
30563
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
30365
30564
|
import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
|
|
@@ -30830,7 +31029,7 @@ var init_mqtt = __esm({
|
|
|
30830
31029
|
if (this.tlsVerify && this.caFile) opts.ca = readFileSync22(this.caFile);
|
|
30831
31030
|
sock = tls.connect(opts, () => settle(() => resolve21(sock)));
|
|
30832
31031
|
} else {
|
|
30833
|
-
sock =
|
|
31032
|
+
sock = net3.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve21(sock)));
|
|
30834
31033
|
}
|
|
30835
31034
|
sock.once("error", (err) => {
|
|
30836
31035
|
settle(() => {
|
|
@@ -31047,7 +31246,7 @@ var init_mqtt = __esm({
|
|
|
31047
31246
|
});
|
|
31048
31247
|
|
|
31049
31248
|
// ../core/src/service.ts
|
|
31050
|
-
import { readdirSync as
|
|
31249
|
+
import { readdirSync as readdirSync17, statSync as statSync17, watchFile, unwatchFile } from "node:fs";
|
|
31051
31250
|
import { join as join27, extname as extname7 } from "node:path";
|
|
31052
31251
|
import { pathToFileURL } from "node:url";
|
|
31053
31252
|
function matchCronField(field, value) {
|
|
@@ -31215,7 +31414,7 @@ var init_service = __esm({
|
|
|
31215
31414
|
const discovered = [];
|
|
31216
31415
|
let entries;
|
|
31217
31416
|
try {
|
|
31218
|
-
entries =
|
|
31417
|
+
entries = readdirSync17(dir);
|
|
31219
31418
|
} catch {
|
|
31220
31419
|
return discovered;
|
|
31221
31420
|
}
|
|
@@ -31342,7 +31541,7 @@ var init_service = __esm({
|
|
|
31342
31541
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
31343
31542
|
let entries;
|
|
31344
31543
|
try {
|
|
31345
|
-
entries =
|
|
31544
|
+
entries = readdirSync17(dir);
|
|
31346
31545
|
} catch {
|
|
31347
31546
|
return;
|
|
31348
31547
|
}
|
|
@@ -31449,9 +31648,9 @@ function ipToBytes(ip) {
|
|
|
31449
31648
|
}
|
|
31450
31649
|
function parseCidr(cidr) {
|
|
31451
31650
|
const [netStr, bitsStr] = cidr.split("/");
|
|
31452
|
-
const
|
|
31453
|
-
if (!
|
|
31454
|
-
return { net:
|
|
31651
|
+
const net5 = ipToBytes(netStr);
|
|
31652
|
+
if (!net5) return null;
|
|
31653
|
+
return { net: net5, bits: Number(bitsStr) };
|
|
31455
31654
|
}
|
|
31456
31655
|
function inCidr(packed, cidr) {
|
|
31457
31656
|
if (packed.length !== cidr.net.length) return false;
|
|
@@ -32030,11 +32229,11 @@ var init_api = __esm({
|
|
|
32030
32229
|
path: null
|
|
32031
32230
|
};
|
|
32032
32231
|
}
|
|
32033
|
-
const
|
|
32034
|
-
if (
|
|
32035
|
-
return { http_code: null, headers: {}, error:
|
|
32232
|
+
const net5 = await this.performRequest("GET", url, headers, data, MAX_REDIRECTS);
|
|
32233
|
+
if (net5.kind === "error") {
|
|
32234
|
+
return { http_code: null, headers: {}, error: net5.error, path: null };
|
|
32036
32235
|
}
|
|
32037
|
-
const res =
|
|
32236
|
+
const res = net5.res;
|
|
32038
32237
|
const code = res.statusCode ?? null;
|
|
32039
32238
|
const respHeaders = flattenHeaders(res.headers);
|
|
32040
32239
|
if (code === null || code >= 400) {
|
|
@@ -32292,11 +32491,11 @@ var init_api = __esm({
|
|
|
32292
32491
|
if (this.transportFn) {
|
|
32293
32492
|
return this.callTransport(method, url, headers, data);
|
|
32294
32493
|
}
|
|
32295
|
-
const
|
|
32296
|
-
if (
|
|
32297
|
-
return { http_code: null, body: null, headers: {}, error:
|
|
32494
|
+
const net5 = await this.performRequest(method, url, headers, data, MAX_REDIRECTS);
|
|
32495
|
+
if (net5.kind === "error") {
|
|
32496
|
+
return { http_code: null, body: null, headers: {}, error: net5.error };
|
|
32298
32497
|
}
|
|
32299
|
-
return this.readResponse(
|
|
32498
|
+
return this.readResponse(net5.res);
|
|
32300
32499
|
}
|
|
32301
32500
|
/**
|
|
32302
32501
|
* Invoke a user-injected transport and normalize its result. The transport
|
|
@@ -32473,7 +32672,7 @@ var init_api = __esm({
|
|
|
32473
32672
|
});
|
|
32474
32673
|
|
|
32475
32674
|
// ../core/src/messenger.ts
|
|
32476
|
-
import
|
|
32675
|
+
import net4 from "node:net";
|
|
32477
32676
|
import tls2 from "node:tls";
|
|
32478
32677
|
import { readFileSync as readFileSync23 } from "node:fs";
|
|
32479
32678
|
import { basename as basename7 } from "node:path";
|
|
@@ -32485,6 +32684,32 @@ function normaliseEncryption(value, kind) {
|
|
|
32485
32684
|
}
|
|
32486
32685
|
return normalised;
|
|
32487
32686
|
}
|
|
32687
|
+
function resolveSendTimeoutMs(optionTimeout) {
|
|
32688
|
+
if (optionTimeout === void 0 || optionTimeout === null) {
|
|
32689
|
+
return resolveEnvTimeoutSeconds(process.env.TINA4_MAIL_TIMEOUT) * 1e3;
|
|
32690
|
+
}
|
|
32691
|
+
if (!Number.isInteger(optionTimeout) || optionTimeout < 1) {
|
|
32692
|
+
throw new Error(
|
|
32693
|
+
`Messenger timeout must be a whole number of seconds and at least 1, got ${optionTimeout}.`
|
|
32694
|
+
);
|
|
32695
|
+
}
|
|
32696
|
+
return optionTimeout * 1e3;
|
|
32697
|
+
}
|
|
32698
|
+
function resolveEnvTimeoutSeconds(raw) {
|
|
32699
|
+
if (!raw || raw.trim() === "") return DEFAULT_TIMEOUT_SECONDS;
|
|
32700
|
+
const trimmed = raw.trim();
|
|
32701
|
+
const seconds = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
|
|
32702
|
+
if (Number.isInteger(seconds) && seconds >= 1) return seconds;
|
|
32703
|
+
warnBadMailTimeout(raw);
|
|
32704
|
+
return DEFAULT_TIMEOUT_SECONDS;
|
|
32705
|
+
}
|
|
32706
|
+
function warnBadMailTimeout(raw) {
|
|
32707
|
+
if (_timeoutWarned.has(raw)) return;
|
|
32708
|
+
_timeoutWarned.add(raw);
|
|
32709
|
+
Log.warning(
|
|
32710
|
+
`TINA4_MAIL_TIMEOUT must be a whole number of seconds and at least 1, got ${JSON.stringify(raw)} - using the default of ${DEFAULT_TIMEOUT_SECONDS} seconds`
|
|
32711
|
+
);
|
|
32712
|
+
}
|
|
32488
32713
|
function failOnIdle(socket, timeoutMs, what) {
|
|
32489
32714
|
socket.setTimeout(timeoutMs, () => {
|
|
32490
32715
|
socket.destroy(new Error(`${what} timed out after ${timeoutMs / 1e3}s`));
|
|
@@ -32496,7 +32721,7 @@ function connectVerifiedTls(host, port, socket) {
|
|
|
32496
32721
|
host,
|
|
32497
32722
|
port,
|
|
32498
32723
|
socket,
|
|
32499
|
-
servername:
|
|
32724
|
+
servername: net4.isIP(host) ? void 0 : host,
|
|
32500
32725
|
rejectUnauthorized: true
|
|
32501
32726
|
});
|
|
32502
32727
|
return new Promise((resolve21, reject) => {
|
|
@@ -32508,7 +32733,7 @@ function connectVerifiedTls(host, port, socket) {
|
|
|
32508
32733
|
});
|
|
32509
32734
|
}
|
|
32510
32735
|
function connectPlain(host, port) {
|
|
32511
|
-
const socket =
|
|
32736
|
+
const socket = net4.createConnection({ host, port });
|
|
32512
32737
|
return new Promise((resolve21, reject) => {
|
|
32513
32738
|
socket.once("connect", () => {
|
|
32514
32739
|
socket.removeListener("error", reject);
|
|
@@ -32746,7 +32971,7 @@ function decodeTransfer(body, encoding) {
|
|
|
32746
32971
|
}
|
|
32747
32972
|
}
|
|
32748
32973
|
if (enc === "quoted-printable") {
|
|
32749
|
-
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m,
|
|
32974
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h2) => String.fromCharCode(parseInt(h2, 16)));
|
|
32750
32975
|
}
|
|
32751
32976
|
return body;
|
|
32752
32977
|
}
|
|
@@ -32863,7 +33088,7 @@ function parseFullMessage(uid, response) {
|
|
|
32863
33088
|
function createMessenger() {
|
|
32864
33089
|
return new Messenger();
|
|
32865
33090
|
}
|
|
32866
|
-
var MAIL_ENCRYPTION_VALUES, SMTP_SEND_TIMEOUT_MS, SMTP_CHECK_TIMEOUT_MS, MessengerConnectionError, SmtpCommandError, Messenger, imapTagCounter;
|
|
33091
|
+
var MAIL_ENCRYPTION_VALUES, SMTP_SEND_TIMEOUT_MS, SMTP_CHECK_TIMEOUT_MS, _timeoutWarned, DEFAULT_TIMEOUT_SECONDS, MessengerConnectionError, SmtpCommandError, Messenger, imapTagCounter;
|
|
32867
33092
|
var init_messenger = __esm({
|
|
32868
33093
|
"../core/src/messenger.ts"() {
|
|
32869
33094
|
"use strict";
|
|
@@ -32873,6 +33098,8 @@ var init_messenger = __esm({
|
|
|
32873
33098
|
MAIL_ENCRYPTION_VALUES = ["ssl", "tls", "starttls", "none"];
|
|
32874
33099
|
SMTP_SEND_TIMEOUT_MS = 3e4;
|
|
32875
33100
|
SMTP_CHECK_TIMEOUT_MS = 1e4;
|
|
33101
|
+
_timeoutWarned = /* @__PURE__ */ new Set();
|
|
33102
|
+
DEFAULT_TIMEOUT_SECONDS = 30;
|
|
32876
33103
|
MessengerConnectionError = class extends Error {
|
|
32877
33104
|
constructor(message) {
|
|
32878
33105
|
super(message);
|
|
@@ -32902,6 +33129,8 @@ var init_messenger = __esm({
|
|
|
32902
33129
|
imapUser;
|
|
32903
33130
|
imapPass;
|
|
32904
33131
|
imapEncryption;
|
|
33132
|
+
/** Resolved SMTP send timeout, in milliseconds (see the constructor). */
|
|
33133
|
+
timeoutMs;
|
|
32905
33134
|
constructor(options) {
|
|
32906
33135
|
this.smtpConfigured = Boolean(options?.host ?? process.env.TINA4_MAIL_HOST);
|
|
32907
33136
|
this.host = options?.host ?? process.env.TINA4_MAIL_HOST ?? "localhost";
|
|
@@ -32923,6 +33152,7 @@ var init_messenger = __esm({
|
|
|
32923
33152
|
this.imapUser = options?.imapUser ?? process.env.TINA4_MAIL_IMAP_USERNAME ?? this.username;
|
|
32924
33153
|
this.imapPass = options?.imapPass ?? process.env.TINA4_MAIL_IMAP_PASSWORD ?? this.password;
|
|
32925
33154
|
this.imapEncryption = normaliseEncryption(options?.imapEncryption ?? process.env.TINA4_MAIL_IMAP_ENCRYPTION ?? "tls", "IMAP");
|
|
33155
|
+
this.timeoutMs = resolveSendTimeoutMs(options?.timeout);
|
|
32926
33156
|
}
|
|
32927
33157
|
/**
|
|
32928
33158
|
* How the SMTP connection is secured (ADR-0071 section 1). Port 465 is the
|
|
@@ -33058,7 +33288,7 @@ var init_messenger = __esm({
|
|
|
33058
33288
|
async sendSmtp(options, recipients, messageId) {
|
|
33059
33289
|
let socket = null;
|
|
33060
33290
|
try {
|
|
33061
|
-
socket = await this.openSmtpSession();
|
|
33291
|
+
socket = await this.openSmtpSession(this.timeoutMs);
|
|
33062
33292
|
await this.authenticateSmtp(socket);
|
|
33063
33293
|
await this.sendSmtpEnvelope(socket, recipients.allRecipients);
|
|
33064
33294
|
await this.sendSmtpMessage(socket, options, recipients, messageId);
|
|
@@ -34107,13 +34337,13 @@ var init_htmlElement = __esm({
|
|
|
34107
34337
|
// ../core/src/ai.ts
|
|
34108
34338
|
import { existsSync as existsSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync16, readFileSync as readFileSync24 } from "node:fs";
|
|
34109
34339
|
import { homedir } from "node:os";
|
|
34110
|
-
import { join as join28, resolve as resolve17, relative as relative11, dirname as
|
|
34111
|
-
import { fileURLToPath as
|
|
34340
|
+
import { join as join28, resolve as resolve17, relative as relative11, dirname as dirname13 } from "node:path";
|
|
34341
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
34112
34342
|
import { execSync, execFileSync as execFileSync4 } from "node:child_process";
|
|
34113
34343
|
import { createInterface } from "node:readline";
|
|
34114
34344
|
function readVersion() {
|
|
34115
34345
|
try {
|
|
34116
|
-
const thisDir =
|
|
34346
|
+
const thisDir = dirname13(fileURLToPath8(import.meta.url));
|
|
34117
34347
|
const rootPkg = resolve17(thisDir, "..", "..", "..", "package.json");
|
|
34118
34348
|
const pkg = JSON.parse(readFileSync24(rootPkg, "utf-8"));
|
|
34119
34349
|
return pkg.version ?? "0.0.0";
|
|
@@ -34329,7 +34559,7 @@ function replaceMarkerBlock(existing, block, start2, end) {
|
|
|
34329
34559
|
}
|
|
34330
34560
|
function looksLikeOldFrameworkInstall(existing) {
|
|
34331
34561
|
const head = existing.replace(/^\s+/, "").slice(0, 400);
|
|
34332
|
-
return OLD_FRAMEWORK_HEADERS.some((
|
|
34562
|
+
return OLD_FRAMEWORK_HEADERS.some((h2) => head.startsWith(h2));
|
|
34333
34563
|
}
|
|
34334
34564
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
34335
34565
|
const block = skillBlock(contextFile);
|
|
@@ -34359,7 +34589,7 @@ function installForTool(root, tool, context) {
|
|
|
34359
34589
|
if (tool.configDir) {
|
|
34360
34590
|
mkdirSync17(join28(root, tool.configDir), { recursive: true });
|
|
34361
34591
|
}
|
|
34362
|
-
const parentDir =
|
|
34592
|
+
const parentDir = dirname13(contextPath);
|
|
34363
34593
|
mkdirSync17(parentDir, { recursive: true });
|
|
34364
34594
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
34365
34595
|
const rel = relative11(root, contextPath);
|
|
@@ -34736,7 +34966,7 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
34736
34966
|
}
|
|
34737
34967
|
function generateClaudeCodeContext() {
|
|
34738
34968
|
try {
|
|
34739
|
-
const thisDir =
|
|
34969
|
+
const thisDir = dirname13(fileURLToPath8(import.meta.url));
|
|
34740
34970
|
const repoRoot = resolve17(thisDir, "..", "..", "..");
|
|
34741
34971
|
const claudeMdPath = join28(repoRoot, "CLAUDE.md");
|
|
34742
34972
|
if (existsSync26(claudeMdPath)) {
|
|
@@ -35584,8 +35814,8 @@ var init_aiClient = __esm({
|
|
|
35584
35814
|
static retryDelay(headers, deadline) {
|
|
35585
35815
|
const value = Array.isArray(headers["retry-after"]) ? headers["retry-after"][0] : headers["retry-after"];
|
|
35586
35816
|
const requested = value !== void 0 && Number.isFinite(Number(value)) ? Math.max(0, Number(value) * 1e3) : 100;
|
|
35587
|
-
const
|
|
35588
|
-
return new Promise((resolve21) => setTimeout(resolve21,
|
|
35817
|
+
const delay2 = Math.min(requested, Math.max(0, deadline - performance.now()));
|
|
35818
|
+
return new Promise((resolve21) => setTimeout(resolve21, delay2));
|
|
35589
35819
|
}
|
|
35590
35820
|
static async requestJson(config, headers, body) {
|
|
35591
35821
|
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
@@ -38640,7 +38870,7 @@ __export(sqlite_exports, {
|
|
|
38640
38870
|
});
|
|
38641
38871
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
38642
38872
|
import { mkdirSync as mkdirSync18 } from "node:fs";
|
|
38643
|
-
import { dirname as
|
|
38873
|
+
import { dirname as dirname14, isAbsolute as isAbsolute5, join as join29, resolve as resolve18, sep as sep7 } from "node:path";
|
|
38644
38874
|
function isIdentifier(str) {
|
|
38645
38875
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
38646
38876
|
}
|
|
@@ -38677,7 +38907,7 @@ function resolveSqlitePath(dbPath) {
|
|
|
38677
38907
|
}
|
|
38678
38908
|
const cwd = resolve18(process.cwd());
|
|
38679
38909
|
const path10 = join29(cwd, dbPath);
|
|
38680
|
-
const parent =
|
|
38910
|
+
const parent = dirname14(path10);
|
|
38681
38911
|
const absParent = resolve18(parent);
|
|
38682
38912
|
if (absParent !== cwd && !absParent.startsWith(cwd + sep7)) {
|
|
38683
38913
|
throw new Error(
|
|
@@ -41106,9 +41336,75 @@ __export(mongodb_exports, {
|
|
|
41106
41336
|
function isMatchAllWhere(where) {
|
|
41107
41337
|
return where != null && MATCH_ALL_WHERE.test(where);
|
|
41108
41338
|
}
|
|
41339
|
+
function isSqlWs(ch) {
|
|
41340
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
41341
|
+
}
|
|
41342
|
+
function isWordChar(ch) {
|
|
41343
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_";
|
|
41344
|
+
}
|
|
41345
|
+
function onlyWs(sql, start2, end) {
|
|
41346
|
+
for (let i = start2; i < end; i++) if (!isSqlWs(sql[i])) return false;
|
|
41347
|
+
return true;
|
|
41348
|
+
}
|
|
41349
|
+
function keywordAt(upper, keyword, from) {
|
|
41350
|
+
let at = upper.indexOf(keyword, from);
|
|
41351
|
+
while (at >= 0) {
|
|
41352
|
+
const end = at + keyword.length;
|
|
41353
|
+
const okBefore = at === 0 || isSqlWs(upper[at - 1]);
|
|
41354
|
+
const okAfter = end >= upper.length || isSqlWs(upper[end]);
|
|
41355
|
+
if (okBefore && okAfter) return at;
|
|
41356
|
+
at = upper.indexOf(keyword, at + 1);
|
|
41357
|
+
}
|
|
41358
|
+
return -1;
|
|
41359
|
+
}
|
|
41360
|
+
function earliest(positions, fallback) {
|
|
41361
|
+
let min = fallback;
|
|
41362
|
+
for (const p of positions) if (p >= 0 && p < min) min = p;
|
|
41363
|
+
return min;
|
|
41364
|
+
}
|
|
41365
|
+
function readIdentifier(sql, from) {
|
|
41366
|
+
let i = from;
|
|
41367
|
+
while (i < sql.length && isSqlWs(sql[i])) i++;
|
|
41368
|
+
if (i < sql.length && QUOTE_CHARS.includes(sql[i])) i++;
|
|
41369
|
+
const start2 = i;
|
|
41370
|
+
while (i < sql.length && isWordChar(sql[i])) i++;
|
|
41371
|
+
if (i === start2) return null;
|
|
41372
|
+
const name = sql.slice(start2, i);
|
|
41373
|
+
if (i < sql.length && QUOTE_CHARS.includes(sql[i])) i++;
|
|
41374
|
+
return [name, i];
|
|
41375
|
+
}
|
|
41376
|
+
function keywordGapEnd(upper, sql, keyword, from) {
|
|
41377
|
+
const at = keywordAt(upper, keyword, from);
|
|
41378
|
+
if (at < 0 || !onlyWs(sql, from, at)) return -1;
|
|
41379
|
+
return at + keyword.length;
|
|
41380
|
+
}
|
|
41381
|
+
function stripQuotes(value) {
|
|
41382
|
+
let result = value;
|
|
41383
|
+
if (result.length > 0 && (result[0] === '"' || result[0] === "'")) result = result.slice(1);
|
|
41384
|
+
const last = result.length - 1;
|
|
41385
|
+
if (last >= 0 && (result[last] === '"' || result[last] === "'")) result = result.slice(0, last);
|
|
41386
|
+
return result;
|
|
41387
|
+
}
|
|
41388
|
+
function splitOnAnd(where) {
|
|
41389
|
+
const parts = [];
|
|
41390
|
+
const lower = where.toLowerCase();
|
|
41391
|
+
let segStart = 0;
|
|
41392
|
+
let i = 0;
|
|
41393
|
+
while (i < where.length) {
|
|
41394
|
+
if (lower.startsWith("and", i) && i > 0 && isSqlWs(where[i - 1]) && i + 3 < where.length && isSqlWs(where[i + 3])) {
|
|
41395
|
+
parts.push(where.slice(segStart, i));
|
|
41396
|
+
i += 3;
|
|
41397
|
+
segStart = i;
|
|
41398
|
+
continue;
|
|
41399
|
+
}
|
|
41400
|
+
i++;
|
|
41401
|
+
}
|
|
41402
|
+
parts.push(where.slice(segStart));
|
|
41403
|
+
return parts;
|
|
41404
|
+
}
|
|
41109
41405
|
function parseWhereClause(where, params, paramOffset = 0) {
|
|
41110
41406
|
const filter = {};
|
|
41111
|
-
const parts = where
|
|
41407
|
+
const parts = splitOnAnd(where);
|
|
41112
41408
|
let paramIndex = paramOffset;
|
|
41113
41409
|
for (const part of parts) {
|
|
41114
41410
|
const trimmed = part.trim();
|
|
@@ -41181,27 +41477,57 @@ function requireWriteFilter(filter, operation, table2) {
|
|
|
41181
41477
|
);
|
|
41182
41478
|
}
|
|
41183
41479
|
}
|
|
41184
|
-
function
|
|
41185
|
-
const match = sql.match(/^SELECT\s+(.*?)\s+FROM\s+["']?(\w+)["']?(?:\s+WHERE\s+(.*?))?(?:\s+ORDER\s+BY\s+(.*?))?(?:\s+LIMIT\s+(\d+))?(?:\s+OFFSET\s+(\d+))?$/is);
|
|
41186
|
-
if (!match) return null;
|
|
41187
|
-
const [, cols, collection, whereClause, orderBy, limitStr, skipStr] = match;
|
|
41480
|
+
function buildProjection(cols) {
|
|
41188
41481
|
const projection = {};
|
|
41189
|
-
if (cols
|
|
41190
|
-
|
|
41191
|
-
|
|
41192
|
-
|
|
41193
|
-
|
|
41194
|
-
|
|
41195
|
-
|
|
41482
|
+
if (!cols || cols.trim() === "*") return projection;
|
|
41483
|
+
for (const col of cols.split(",")) {
|
|
41484
|
+
const name = stripQuotes(col.trim());
|
|
41485
|
+
if (name && name !== "*") projection[name] = 1;
|
|
41486
|
+
}
|
|
41487
|
+
return projection;
|
|
41488
|
+
}
|
|
41489
|
+
function stripTrailingDirection(part) {
|
|
41490
|
+
const lower = part.toLowerCase();
|
|
41491
|
+
let suffix = 0;
|
|
41492
|
+
if (lower.endsWith("asc")) suffix = 3;
|
|
41493
|
+
else if (lower.endsWith("desc")) suffix = 4;
|
|
41494
|
+
else return part;
|
|
41495
|
+
const beforeIdx = part.length - suffix - 1;
|
|
41496
|
+
if (beforeIdx < 0 || !isSqlWs(part[beforeIdx])) return part;
|
|
41497
|
+
let i = beforeIdx;
|
|
41498
|
+
while (i >= 0 && isSqlWs(part[i])) i--;
|
|
41499
|
+
return part.slice(0, i + 1);
|
|
41500
|
+
}
|
|
41501
|
+
function parseOrderBy(orderBy) {
|
|
41196
41502
|
const sort = {};
|
|
41197
|
-
|
|
41198
|
-
|
|
41199
|
-
|
|
41200
|
-
|
|
41201
|
-
|
|
41202
|
-
sort[col] = desc ? -1 : 1;
|
|
41203
|
-
}
|
|
41503
|
+
for (const rawPart of orderBy.split(",")) {
|
|
41504
|
+
const trimmed = rawPart.trim();
|
|
41505
|
+
const desc = trimmed.toLowerCase().endsWith("desc");
|
|
41506
|
+
const col = stripQuotes(stripTrailingDirection(trimmed)).trim();
|
|
41507
|
+
sort[col] = desc ? -1 : 1;
|
|
41204
41508
|
}
|
|
41509
|
+
return sort;
|
|
41510
|
+
}
|
|
41511
|
+
function parseSelect(sql, params) {
|
|
41512
|
+
const upper = sql.toUpperCase();
|
|
41513
|
+
if (!(upper.startsWith("SELECT") && (sql.length === 6 || isSqlWs(sql[6])))) return null;
|
|
41514
|
+
const fromAt = keywordAt(upper, "FROM", 6);
|
|
41515
|
+
if (fromAt < 0) return null;
|
|
41516
|
+
const cols = sql.slice(6, fromAt).trim();
|
|
41517
|
+
const ident = readIdentifier(sql, fromAt + 4);
|
|
41518
|
+
if (!ident) return null;
|
|
41519
|
+
const [collection, afterColl] = ident;
|
|
41520
|
+
const whereAt = keywordAt(upper, "WHERE", afterColl);
|
|
41521
|
+
const orderAt = keywordAt(upper, "ORDER BY", afterColl);
|
|
41522
|
+
const limitAt = keywordAt(upper, "LIMIT", afterColl);
|
|
41523
|
+
const offsetAt = keywordAt(upper, "OFFSET", afterColl);
|
|
41524
|
+
const end = sql.length;
|
|
41525
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5, earliest([orderAt, limitAt, offsetAt], end)).trim();
|
|
41526
|
+
const orderBy = orderAt < 0 ? void 0 : sql.slice(orderAt + 8, earliest([limitAt, offsetAt], end)).trim();
|
|
41527
|
+
const limitStr = limitAt < 0 ? void 0 : sql.slice(limitAt + 5, earliest([offsetAt], end)).trim();
|
|
41528
|
+
const skipStr = offsetAt < 0 ? void 0 : sql.slice(offsetAt + 6).trim();
|
|
41529
|
+
const projection = buildProjection(cols);
|
|
41530
|
+
const filter = whereClause ? parseWhereClause(whereClause, params).filter : {};
|
|
41205
41531
|
return {
|
|
41206
41532
|
type: "find",
|
|
41207
41533
|
collection,
|
|
@@ -41209,7 +41535,7 @@ function parseSelect(sql, params) {
|
|
|
41209
41535
|
projection: Object.keys(projection).length > 0 ? projection : void 0,
|
|
41210
41536
|
limit: limitStr ? parseInt(limitStr, 10) : void 0,
|
|
41211
41537
|
skip: skipStr ? parseInt(skipStr, 10) : void 0,
|
|
41212
|
-
sort: orderBy ?
|
|
41538
|
+
sort: orderBy ? parseOrderBy(orderBy) : void 0
|
|
41213
41539
|
};
|
|
41214
41540
|
}
|
|
41215
41541
|
function parseInsert(sql, params) {
|
|
@@ -41245,20 +41571,33 @@ function parseSetClause(setClause, params) {
|
|
|
41245
41571
|
return { document, consumed };
|
|
41246
41572
|
}
|
|
41247
41573
|
function parseUpdate(sql, params) {
|
|
41248
|
-
const
|
|
41249
|
-
if (!
|
|
41250
|
-
const
|
|
41574
|
+
const upper = sql.toUpperCase();
|
|
41575
|
+
if (!(upper.startsWith("UPDATE") && sql.length > 6 && isSqlWs(sql[6]))) return null;
|
|
41576
|
+
const ident = readIdentifier(sql, 6);
|
|
41577
|
+
if (!ident) return null;
|
|
41578
|
+
const [collection, afterColl] = ident;
|
|
41579
|
+
const setAt = keywordAt(upper, "SET", afterColl);
|
|
41580
|
+
if (setAt < 0 || !onlyWs(sql, afterColl, setAt)) return null;
|
|
41581
|
+
const whereAt = keywordAt(upper, "WHERE", setAt + 3);
|
|
41582
|
+
const setClause = sql.slice(setAt + 3, whereAt < 0 ? sql.length : whereAt).trim();
|
|
41583
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5).trim();
|
|
41251
41584
|
const set = parseSetClause(setClause, params);
|
|
41252
|
-
const matchAll = isMatchAllWhere(whereClause
|
|
41253
|
-
const filter = whereClause ? parseWhereClause(whereClause
|
|
41585
|
+
const matchAll = isMatchAllWhere(whereClause);
|
|
41586
|
+
const filter = whereClause ? parseWhereClause(whereClause, params, set.consumed).filter : {};
|
|
41254
41587
|
return { type: "updateMany", collection, filter, update: { $set: set.document }, matchAll };
|
|
41255
41588
|
}
|
|
41256
41589
|
function parseDelete(sql, params) {
|
|
41257
|
-
const
|
|
41258
|
-
if (!
|
|
41259
|
-
const
|
|
41260
|
-
|
|
41261
|
-
const
|
|
41590
|
+
const upper = sql.toUpperCase();
|
|
41591
|
+
if (!upper.startsWith("DELETE")) return null;
|
|
41592
|
+
const fromAt = keywordAt(upper, "FROM", 6);
|
|
41593
|
+
if (fromAt < 0 || !onlyWs(sql, 6, fromAt)) return null;
|
|
41594
|
+
const ident = readIdentifier(sql, fromAt + 4);
|
|
41595
|
+
if (!ident) return null;
|
|
41596
|
+
const [collection, afterColl] = ident;
|
|
41597
|
+
const whereAt = keywordAt(upper, "WHERE", afterColl);
|
|
41598
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5).trim();
|
|
41599
|
+
const matchAll = isMatchAllWhere(whereClause);
|
|
41600
|
+
const filter = whereClause ? parseWhereClause(whereClause, params).filter : {};
|
|
41262
41601
|
return { type: "deleteMany", collection, filter, matchAll };
|
|
41263
41602
|
}
|
|
41264
41603
|
function parseCreate(sql) {
|
|
@@ -41266,10 +41605,23 @@ function parseCreate(sql) {
|
|
|
41266
41605
|
return match ? { type: "raw", collection: match[1] } : null;
|
|
41267
41606
|
}
|
|
41268
41607
|
function parseCount(sql, params) {
|
|
41269
|
-
const
|
|
41270
|
-
if (!
|
|
41271
|
-
const
|
|
41272
|
-
|
|
41608
|
+
const upper = sql.toUpperCase();
|
|
41609
|
+
if (!upper.startsWith("SELECT")) return null;
|
|
41610
|
+
const afterCount = keywordGapEnd(upper, sql, "COUNT(*)", 6);
|
|
41611
|
+
if (afterCount < 0) return null;
|
|
41612
|
+
const afterAs = keywordGapEnd(upper, sql, "AS", afterCount);
|
|
41613
|
+
if (afterAs < 0) return null;
|
|
41614
|
+
const aliasIdent = readIdentifier(sql, afterAs);
|
|
41615
|
+
if (!aliasIdent) return null;
|
|
41616
|
+
const [alias, afterAlias] = aliasIdent;
|
|
41617
|
+
const afterFrom = keywordGapEnd(upper, sql, "FROM", afterAlias);
|
|
41618
|
+
if (afterFrom < 0) return null;
|
|
41619
|
+
const coll = readIdentifier(sql, afterFrom);
|
|
41620
|
+
if (!coll) return null;
|
|
41621
|
+
const [collection, afterColl] = coll;
|
|
41622
|
+
const whereAt = keywordAt(upper, "WHERE", afterColl);
|
|
41623
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5).trim();
|
|
41624
|
+
const filter = whereClause ? parseWhereClause(whereClause, params).filter : {};
|
|
41273
41625
|
return { type: "aggregate", collection, pipeline: [{ $match: filter }, { $count: alias }] };
|
|
41274
41626
|
}
|
|
41275
41627
|
function parseSql(sql, params = []) {
|
|
@@ -41295,12 +41647,13 @@ function fieldTypeToJsonSchema(def) {
|
|
|
41295
41647
|
return { bsonType: "string" };
|
|
41296
41648
|
}
|
|
41297
41649
|
}
|
|
41298
|
-
var MATCH_ALL_WHERE, MongodbAdapter;
|
|
41650
|
+
var MATCH_ALL_WHERE, QUOTE_CHARS, MongodbAdapter;
|
|
41299
41651
|
var init_mongodb = __esm({
|
|
41300
41652
|
"src/adapters/mongodb.ts"() {
|
|
41301
41653
|
"use strict";
|
|
41302
41654
|
init_connectTimeout();
|
|
41303
41655
|
MATCH_ALL_WHERE = /^\s*1\s*=\s*1\s*$/;
|
|
41656
|
+
QUOTE_CHARS = `"'`;
|
|
41304
41657
|
MongodbAdapter = class {
|
|
41305
41658
|
constructor(config) {
|
|
41306
41659
|
this.config = config;
|
|
@@ -43624,13 +43977,13 @@ var init_database = __esm({
|
|
|
43624
43977
|
});
|
|
43625
43978
|
|
|
43626
43979
|
// src/model.ts
|
|
43627
|
-
import { readdirSync as
|
|
43980
|
+
import { readdirSync as readdirSync18, statSync as statSync18 } from "node:fs";
|
|
43628
43981
|
import { join as join30, extname as extname8 } from "node:path";
|
|
43629
43982
|
async function discoverModels(modelsDir) {
|
|
43630
43983
|
const models = [];
|
|
43631
43984
|
let files;
|
|
43632
43985
|
try {
|
|
43633
|
-
files =
|
|
43986
|
+
files = readdirSync18(modelsDir);
|
|
43634
43987
|
} catch {
|
|
43635
43988
|
return models;
|
|
43636
43989
|
}
|
|
@@ -43677,8 +44030,10 @@ var init_model = __esm({
|
|
|
43677
44030
|
});
|
|
43678
44031
|
|
|
43679
44032
|
// src/migration.ts
|
|
43680
|
-
import { existsSync as existsSync27, readdirSync as
|
|
44033
|
+
import { existsSync as existsSync27, readdirSync as readdirSync19, readFileSync as readFileSync25, mkdirSync as mkdirSync19, writeFileSync as writeFileSync17, rmSync } from "node:fs";
|
|
43681
44034
|
import { join as join31, resolve as resolve19 } from "node:path";
|
|
44035
|
+
import { tmpdir } from "node:os";
|
|
44036
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
43682
44037
|
function unwrapAdapter(db) {
|
|
43683
44038
|
let cur = db;
|
|
43684
44039
|
while (cur && cur.constructor?.name === "CachedDatabaseAdapter" && cur.adapter) {
|
|
@@ -44157,17 +44512,113 @@ function warnUnprefixedMigrations(files) {
|
|
|
44157
44512
|
);
|
|
44158
44513
|
}
|
|
44159
44514
|
}
|
|
44515
|
+
async function acquireMigrationLock(db, dir) {
|
|
44516
|
+
let engine = "";
|
|
44517
|
+
try {
|
|
44518
|
+
engine = (db.getDatabaseType() || "").toLowerCase();
|
|
44519
|
+
} catch {
|
|
44520
|
+
engine = "";
|
|
44521
|
+
}
|
|
44522
|
+
try {
|
|
44523
|
+
if (engine.startsWith("postgres")) {
|
|
44524
|
+
await adapterQuery(db, `SELECT pg_advisory_lock(${PG_ADVISORY_KEY}) AS locked`);
|
|
44525
|
+
return { kind: "postgres" };
|
|
44526
|
+
}
|
|
44527
|
+
if (engine.startsWith("mysql")) {
|
|
44528
|
+
await adapterQuery(db, `SELECT GET_LOCK('${MIGRATION_LOCK_NAME}', -1) AS locked`);
|
|
44529
|
+
return { kind: "mysql" };
|
|
44530
|
+
}
|
|
44531
|
+
if (engine === "mssql" || engine === "sqlserver") {
|
|
44532
|
+
await adapterExecute(
|
|
44533
|
+
db,
|
|
44534
|
+
`DECLARE @res INT; EXEC @res = sp_getapplock @Resource = '${MIGRATION_LOCK_NAME}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = -1`
|
|
44535
|
+
);
|
|
44536
|
+
return { kind: "mssql" };
|
|
44537
|
+
}
|
|
44538
|
+
} catch (err) {
|
|
44539
|
+
Log.debug(`DB migration lock unavailable (${engine}): ${err instanceof Error ? err.message : err}; using a file lock`);
|
|
44540
|
+
}
|
|
44541
|
+
return acquireFileLock(dir);
|
|
44542
|
+
}
|
|
44543
|
+
async function acquireFileLock(dir) {
|
|
44544
|
+
const lockFile = join31(tmpdir(), `tina4-migration-${createHash10("sha256").update(resolve19(dir)).digest("hex")}.lock`);
|
|
44545
|
+
const deadline = Date.now() + 6e4;
|
|
44546
|
+
for (; ; ) {
|
|
44547
|
+
try {
|
|
44548
|
+
writeFileSync17(lockFile, String(process.pid), { flag: "wx" });
|
|
44549
|
+
return { kind: "file", resource: lockFile };
|
|
44550
|
+
} catch (err) {
|
|
44551
|
+
if (err.code !== "EEXIST") {
|
|
44552
|
+
Log.debug(`file migration lock unavailable: ${err instanceof Error ? err.message : err}; running unlocked`);
|
|
44553
|
+
return { kind: "none" };
|
|
44554
|
+
}
|
|
44555
|
+
if (isStaleLock(lockFile)) {
|
|
44556
|
+
try {
|
|
44557
|
+
rmSync(lockFile, { force: true });
|
|
44558
|
+
} catch {
|
|
44559
|
+
}
|
|
44560
|
+
continue;
|
|
44561
|
+
}
|
|
44562
|
+
if (Date.now() > deadline) {
|
|
44563
|
+
Log.debug("file migration lock wait timed out; running unlocked");
|
|
44564
|
+
return { kind: "none" };
|
|
44565
|
+
}
|
|
44566
|
+
await delay(50);
|
|
44567
|
+
}
|
|
44568
|
+
}
|
|
44569
|
+
}
|
|
44570
|
+
function isStaleLock(lockFile) {
|
|
44571
|
+
let raw;
|
|
44572
|
+
try {
|
|
44573
|
+
raw = readFileSync25(lockFile, "utf-8").trim();
|
|
44574
|
+
} catch {
|
|
44575
|
+
return false;
|
|
44576
|
+
}
|
|
44577
|
+
const pid = parseInt(raw, 10);
|
|
44578
|
+
if (!raw || !Number.isFinite(pid) || pid <= 0) {
|
|
44579
|
+
return false;
|
|
44580
|
+
}
|
|
44581
|
+
try {
|
|
44582
|
+
process.kill(pid, 0);
|
|
44583
|
+
return false;
|
|
44584
|
+
} catch (err) {
|
|
44585
|
+
return err.code === "ESRCH";
|
|
44586
|
+
}
|
|
44587
|
+
}
|
|
44588
|
+
async function releaseMigrationLock(db, lock) {
|
|
44589
|
+
try {
|
|
44590
|
+
if (lock.kind === "postgres") {
|
|
44591
|
+
await adapterQuery(db, `SELECT pg_advisory_unlock(${PG_ADVISORY_KEY}) AS released`);
|
|
44592
|
+
} else if (lock.kind === "mysql") {
|
|
44593
|
+
await adapterQuery(db, `SELECT RELEASE_LOCK('${MIGRATION_LOCK_NAME}') AS released`);
|
|
44594
|
+
} else if (lock.kind === "mssql") {
|
|
44595
|
+
await adapterExecute(db, `EXEC sp_releaseapplock @Resource = '${MIGRATION_LOCK_NAME}', @LockOwner = 'Session'`);
|
|
44596
|
+
} else if (lock.kind === "file" && lock.resource) {
|
|
44597
|
+
rmSync(lock.resource, { recursive: true, force: true });
|
|
44598
|
+
}
|
|
44599
|
+
} catch (err) {
|
|
44600
|
+
Log.debug(`migration lock release failed (${lock.kind}): ${err instanceof Error ? err.message : err}`);
|
|
44601
|
+
}
|
|
44602
|
+
}
|
|
44160
44603
|
async function migrate(adapter, options) {
|
|
44161
44604
|
const db = adapter ?? getAdapter();
|
|
44162
44605
|
const dir = resolve19(options?.migrationsDir ?? "migrations");
|
|
44163
44606
|
const delimiter2 = options?.delimiter ?? ";";
|
|
44164
|
-
const result = { applied: [], skipped: [], failed: [] };
|
|
44165
44607
|
if (!existsSync27(dir)) {
|
|
44166
|
-
return
|
|
44608
|
+
return { applied: [], skipped: [], failed: [] };
|
|
44609
|
+
}
|
|
44610
|
+
const lock = await acquireMigrationLock(db, dir);
|
|
44611
|
+
try {
|
|
44612
|
+
return await runPendingMigrations(db, dir, delimiter2);
|
|
44613
|
+
} finally {
|
|
44614
|
+
await releaseMigrationLock(db, lock);
|
|
44167
44615
|
}
|
|
44616
|
+
}
|
|
44617
|
+
async function runPendingMigrations(db, dir, delimiter2) {
|
|
44618
|
+
const result = { applied: [], skipped: [], failed: [] };
|
|
44168
44619
|
await ensureMigrationTableOn(db);
|
|
44169
44620
|
const files = sortMigrationFiles(
|
|
44170
|
-
|
|
44621
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44171
44622
|
);
|
|
44172
44623
|
if (files.length === 0) return result;
|
|
44173
44624
|
warnUnprefixedMigrations(files);
|
|
@@ -44240,14 +44691,14 @@ async function status(adapter, options) {
|
|
|
44240
44691
|
}
|
|
44241
44692
|
if (!await adapterTableExists(db, MIGRATION_TABLE)) {
|
|
44242
44693
|
const files2 = sortMigrationFiles(
|
|
44243
|
-
|
|
44694
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44244
44695
|
);
|
|
44245
44696
|
result.pending = files2;
|
|
44246
44697
|
return result;
|
|
44247
44698
|
}
|
|
44248
44699
|
await ensureMigrationTableOn(db);
|
|
44249
44700
|
const files = sortMigrationFiles(
|
|
44250
|
-
|
|
44701
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44251
44702
|
);
|
|
44252
44703
|
const appliedNames = /* @__PURE__ */ new Set();
|
|
44253
44704
|
try {
|
|
@@ -44347,7 +44798,7 @@ export class ${className} {
|
|
|
44347
44798
|
writeFileSync17(filePath, content, "utf-8");
|
|
44348
44799
|
return filePath;
|
|
44349
44800
|
}
|
|
44350
|
-
var ALTER_ADD_RE, CREATE_TABLE_RE, MIGRATION_TABLE, SMART_QUOTES, SMART_QUOTE_RE, SET_TERM_RE, Migration;
|
|
44801
|
+
var ALTER_ADD_RE, CREATE_TABLE_RE, MIGRATION_TABLE, SMART_QUOTES, SMART_QUOTE_RE, SET_TERM_RE, MIGRATION_LOCK_NAME, PG_ADVISORY_KEY, delay, Migration;
|
|
44351
44802
|
var init_migration = __esm({
|
|
44352
44803
|
"src/migration.ts"() {
|
|
44353
44804
|
"use strict";
|
|
@@ -44372,6 +44823,12 @@ var init_migration = __esm({
|
|
|
44372
44823
|
};
|
|
44373
44824
|
SMART_QUOTE_RE = new RegExp(`[${Object.keys(SMART_QUOTES).join("")}]`, "g");
|
|
44374
44825
|
SET_TERM_RE = /^SET\s+TERM\s+(\S+)$/i;
|
|
44826
|
+
MIGRATION_LOCK_NAME = "tina4_migration_lock";
|
|
44827
|
+
PG_ADVISORY_KEY = BigInt.asIntN(
|
|
44828
|
+
64,
|
|
44829
|
+
BigInt("0x" + createHash10("sha256").update(MIGRATION_LOCK_NAME).digest("hex").slice(0, 16))
|
|
44830
|
+
).toString();
|
|
44831
|
+
delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
44375
44832
|
Migration = class {
|
|
44376
44833
|
db;
|
|
44377
44834
|
dir;
|
|
@@ -44428,7 +44885,7 @@ var init_migration = __esm({
|
|
|
44428
44885
|
const dir = resolve19(this.dir);
|
|
44429
44886
|
if (!existsSync27(dir)) return [];
|
|
44430
44887
|
return sortMigrationFiles(
|
|
44431
|
-
|
|
44888
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44432
44889
|
);
|
|
44433
44890
|
}
|
|
44434
44891
|
};
|
|
@@ -44436,17 +44893,40 @@ var init_migration = __esm({
|
|
|
44436
44893
|
});
|
|
44437
44894
|
|
|
44438
44895
|
// src/query.ts
|
|
44439
|
-
function resolveFieldColumn(fields,
|
|
44440
|
-
const field = resolveField(fields,
|
|
44441
|
-
return field === null ? null :
|
|
44896
|
+
function resolveFieldColumn(fields, columnOf2, key) {
|
|
44897
|
+
const field = resolveField(fields, columnOf2, key);
|
|
44898
|
+
return field === null ? null : columnOf2(field);
|
|
44442
44899
|
}
|
|
44443
|
-
function resolveField(fields,
|
|
44900
|
+
function resolveField(fields, columnOf2, key) {
|
|
44444
44901
|
if (Object.hasOwn(fields, key)) return key;
|
|
44445
44902
|
for (const field of Object.keys(fields)) {
|
|
44446
|
-
if (
|
|
44903
|
+
if (columnOf2(field) === key) return field;
|
|
44447
44904
|
}
|
|
44448
44905
|
return null;
|
|
44449
44906
|
}
|
|
44907
|
+
function applySearch(options, conditions, params, quote) {
|
|
44908
|
+
const searchTerm = (options.search ?? "").trim();
|
|
44909
|
+
if (searchTerm === "" || !options.searchColumns || options.searchColumns.length === 0) return;
|
|
44910
|
+
const likeParts = options.searchColumns.map((col) => `${quote(col)} LIKE ?`);
|
|
44911
|
+
conditions.push(`(${likeParts.join(" OR ")})`);
|
|
44912
|
+
for (const _ of options.searchColumns) params.push(`%${searchTerm}%`);
|
|
44913
|
+
}
|
|
44914
|
+
function orderPart(rawPart, bareDir, column2) {
|
|
44915
|
+
const trimmed = rawPart.trim();
|
|
44916
|
+
if (trimmed === "") return null;
|
|
44917
|
+
const descending = trimmed.startsWith("-");
|
|
44918
|
+
const col = column2("sort", descending ? trimmed.slice(1) : trimmed);
|
|
44919
|
+
return `${col} ${bareDir ?? (descending ? "DESC" : "ASC")}`;
|
|
44920
|
+
}
|
|
44921
|
+
function buildOrderClause(options, column2) {
|
|
44922
|
+
if (!options.sort) return "";
|
|
44923
|
+
const rawParts = options.sort.split(",");
|
|
44924
|
+
const first = rawParts[0].trim();
|
|
44925
|
+
const singleBare = rawParts.length === 1 && first !== "" && !first.startsWith("-");
|
|
44926
|
+
const bareDir = singleBare && String(options.sortDir).toLowerCase() === "desc" ? "DESC" : null;
|
|
44927
|
+
const parts = rawParts.map((part) => orderPart(part, bareDir, column2)).filter((term) => term !== null);
|
|
44928
|
+
return parts.length > 0 ? `ORDER BY ${parts.join(", ")}` : "";
|
|
44929
|
+
}
|
|
44450
44930
|
function buildQuery(tableName, options, extraConditions, quote = quoteIdentifierAnsi, resolveColumn = () => null) {
|
|
44451
44931
|
const conditions = [];
|
|
44452
44932
|
const params = [];
|
|
@@ -44474,19 +44954,9 @@ function buildQuery(tableName, options, extraConditions, quote = quoteIdentifier
|
|
|
44474
44954
|
}
|
|
44475
44955
|
}
|
|
44476
44956
|
}
|
|
44957
|
+
applySearch(options, conditions, params, quote);
|
|
44477
44958
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
44478
|
-
|
|
44479
|
-
if (options.sort) {
|
|
44480
|
-
const parts = [];
|
|
44481
|
-
for (const part of options.sort.split(",")) {
|
|
44482
|
-
const trimmed = part.trim();
|
|
44483
|
-
if (trimmed === "") continue;
|
|
44484
|
-
const descending = trimmed.startsWith("-");
|
|
44485
|
-
const col = column2("sort", descending ? trimmed.slice(1) : trimmed);
|
|
44486
|
-
parts.push(`${col} ${descending ? "DESC" : "ASC"}`);
|
|
44487
|
-
}
|
|
44488
|
-
if (parts.length > 0) orderClause = `ORDER BY ${parts.join(", ")}`;
|
|
44489
|
-
}
|
|
44959
|
+
const orderClause = buildOrderClause(options, column2);
|
|
44490
44960
|
const limit = Math.min(options.limit ?? DEFAULT_ROW_CAP, DEFAULT_ROW_CAP);
|
|
44491
44961
|
const page = Math.max(options.page ?? 1, 1);
|
|
44492
44962
|
const offset = (page - 1) * limit;
|
|
@@ -44504,32 +44974,41 @@ function buildQuery(tableName, options, extraConditions, quote = quoteIdentifier
|
|
|
44504
44974
|
filterParams: params
|
|
44505
44975
|
};
|
|
44506
44976
|
}
|
|
44507
|
-
function
|
|
44508
|
-
const options = {};
|
|
44977
|
+
function parseFilterParams(query) {
|
|
44509
44978
|
const filter = /* @__PURE__ */ Object.create(null);
|
|
44510
44979
|
for (const [key, value] of Object.entries(query)) {
|
|
44511
44980
|
if (key.startsWith("sort[")) {
|
|
44512
44981
|
throw new InvalidQueryParameterError("Query parameter 'sort' must be a single comma-separated string");
|
|
44513
44982
|
}
|
|
44514
44983
|
if (!key.startsWith("filter[")) continue;
|
|
44515
|
-
|
|
44516
|
-
|
|
44517
|
-
|
|
44518
|
-
|
|
44519
|
-
|
|
44520
|
-
|
|
44521
|
-
|
|
44522
|
-
|
|
44523
|
-
|
|
44524
|
-
|
|
44525
|
-
|
|
44526
|
-
|
|
44527
|
-
|
|
44984
|
+
addFilterKey(filter, key, value);
|
|
44985
|
+
}
|
|
44986
|
+
return filter;
|
|
44987
|
+
}
|
|
44988
|
+
function addFilterKey(filter, key, value) {
|
|
44989
|
+
const filterMatch = key.match(/^filter\[([^\]]*)\](.*)$/s);
|
|
44990
|
+
if (!filterMatch) throw new UnknownFieldError("filter", key.slice("filter[".length));
|
|
44991
|
+
const [, field, rest] = filterMatch;
|
|
44992
|
+
if (rest === "") {
|
|
44993
|
+
filter[field] = value;
|
|
44994
|
+
return;
|
|
44995
|
+
}
|
|
44996
|
+
const operator = rest.match(/^\[([^[\]]*)\]$/)?.[1];
|
|
44997
|
+
if (operator === void 0 || !Object.hasOwn(operatorMap, operator)) throw filterValueError(field);
|
|
44998
|
+
if (!filter[field] || typeof filter[field] !== "object") {
|
|
44999
|
+
filter[field] = {};
|
|
44528
45000
|
}
|
|
45001
|
+
filter[field][operator] = value;
|
|
45002
|
+
}
|
|
45003
|
+
function parseQueryString(query) {
|
|
45004
|
+
const options = {};
|
|
45005
|
+
const filter = parseFilterParams(query);
|
|
44529
45006
|
if (Object.keys(filter).length > 0) {
|
|
44530
45007
|
options.filter = filter;
|
|
44531
45008
|
}
|
|
44532
45009
|
if (query.sort) options.sort = query.sort;
|
|
45010
|
+
if (query.search !== void 0) options.search = query.search;
|
|
45011
|
+
if (query.sort_dir !== void 0) options.sortDir = query.sort_dir;
|
|
44533
45012
|
if (query.page) options.page = parseInt(query.page, 10);
|
|
44534
45013
|
if (query.limit) options.limit = parseInt(query.limit, 10);
|
|
44535
45014
|
if (query.offset !== void 0) {
|
|
@@ -44665,12 +45144,12 @@ var init_validation = __esm({
|
|
|
44665
45144
|
});
|
|
44666
45145
|
|
|
44667
45146
|
// src/autoCrud.ts
|
|
44668
|
-
function allowListedBody(fields,
|
|
45147
|
+
function allowListedBody(fields, columnOf2, pkField, body, isCreate) {
|
|
44669
45148
|
const pkDef = fields[pkField];
|
|
44670
45149
|
const stripPk = isCreate ? pkDef?.autoIncrement === true : true;
|
|
44671
45150
|
const allowed = {};
|
|
44672
45151
|
for (const [key, value] of Object.entries(body ?? {})) {
|
|
44673
|
-
const field = resolveField(fields,
|
|
45152
|
+
const field = resolveField(fields, columnOf2, key);
|
|
44674
45153
|
if (field === null) continue;
|
|
44675
45154
|
if (field === "is_deleted") continue;
|
|
44676
45155
|
if (stripPk && field === pkField) continue;
|
|
@@ -44714,6 +45193,9 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
44714
45193
|
let built;
|
|
44715
45194
|
try {
|
|
44716
45195
|
const qp = parseQueryString(req2.query ?? {});
|
|
45196
|
+
if ((qp.search ?? "").trim() !== "") {
|
|
45197
|
+
qp.searchColumns = Object.entries(fields).filter(([, def]) => def.type === "string" || def.type === "text").map(([name]) => getDbCol(name));
|
|
45198
|
+
}
|
|
44717
45199
|
built = buildQuery(tableName, qp, extraConditions, q, (key) => resolveFieldColumn(fields, getDbCol, key));
|
|
44718
45200
|
} catch (err) {
|
|
44719
45201
|
if (err instanceof UnknownFieldError) {
|
|
@@ -44961,6 +45443,567 @@ var init_autoCrud = __esm({
|
|
|
44961
45443
|
}
|
|
44962
45444
|
});
|
|
44963
45445
|
|
|
45446
|
+
// src/crudHelpers.ts
|
|
45447
|
+
function columnOf(model, prop) {
|
|
45448
|
+
return model.fieldMapping?.[prop] ?? prop;
|
|
45449
|
+
}
|
|
45450
|
+
function primaryKeyProp(model) {
|
|
45451
|
+
for (const [name, def] of Object.entries(model.fields)) {
|
|
45452
|
+
if (def.primaryKey) return name;
|
|
45453
|
+
}
|
|
45454
|
+
return "id";
|
|
45455
|
+
}
|
|
45456
|
+
function searchableProps(model) {
|
|
45457
|
+
return Object.entries(model.fields).filter(([, def]) => def.type === "string" || def.type === "text").map(([name]) => name);
|
|
45458
|
+
}
|
|
45459
|
+
function columnAlignment(model, col) {
|
|
45460
|
+
const type2 = model?.fields?.[col]?.type;
|
|
45461
|
+
return type2 === "integer" || type2 === "number" || type2 === "float" || type2 === "decimal" ? "text-end" : "text-start";
|
|
45462
|
+
}
|
|
45463
|
+
function prettyLabel(col) {
|
|
45464
|
+
return String(col).split("_").map((p) => p ? p.charAt(0).toUpperCase() + p.slice(1) : p).join(" ");
|
|
45465
|
+
}
|
|
45466
|
+
function crudSortProp(model, requested, pkProp) {
|
|
45467
|
+
const req2 = (requested ?? "").trim();
|
|
45468
|
+
if (req2 === "") return pkProp;
|
|
45469
|
+
if (Object.hasOwn(model.fields, req2)) return req2;
|
|
45470
|
+
for (const name of Object.keys(model.fields)) {
|
|
45471
|
+
if (columnOf(model, name) === req2) return name;
|
|
45472
|
+
}
|
|
45473
|
+
return pkProp;
|
|
45474
|
+
}
|
|
45475
|
+
function cellValue(record, col) {
|
|
45476
|
+
return record[col];
|
|
45477
|
+
}
|
|
45478
|
+
function buildCells(columns, record, editable, aligns) {
|
|
45479
|
+
return columns.map((col, index) => {
|
|
45480
|
+
const raw = cellValue(record, col);
|
|
45481
|
+
const value = h(raw === null || raw === void 0 ? "" : raw);
|
|
45482
|
+
const css = aligns[index];
|
|
45483
|
+
return editable ? `<td class="${css}" contenteditable="true" data-field="${h(col)}">${value}</td>` : `<td class="${css}">${value}</td>`;
|
|
45484
|
+
}).join("");
|
|
45485
|
+
}
|
|
45486
|
+
function stripOrderAndLimit(sql) {
|
|
45487
|
+
const cut = (line, keyword) => {
|
|
45488
|
+
const hasNewline = line.endsWith("\n");
|
|
45489
|
+
const content = hasNewline ? line.slice(0, -1) : line;
|
|
45490
|
+
const at = content.toUpperCase().indexOf(keyword);
|
|
45491
|
+
if (at < 0) return line;
|
|
45492
|
+
const after = at + keyword.length;
|
|
45493
|
+
if (content.length <= after) return line;
|
|
45494
|
+
return content.slice(0, at) + (hasNewline ? "\n" : "");
|
|
45495
|
+
};
|
|
45496
|
+
const src = String(sql);
|
|
45497
|
+
const lines = [];
|
|
45498
|
+
let start2 = 0;
|
|
45499
|
+
for (let i = 0; i < src.length; i++) {
|
|
45500
|
+
if (src[i] === "\n") {
|
|
45501
|
+
lines.push(src.slice(start2, i + 1));
|
|
45502
|
+
start2 = i + 1;
|
|
45503
|
+
}
|
|
45504
|
+
}
|
|
45505
|
+
lines.push(src.slice(start2));
|
|
45506
|
+
return lines.map((line) => cut(cut(line, "ORDER BY "), "LIMIT ")).join("").trim();
|
|
45507
|
+
}
|
|
45508
|
+
function extractColumns(sql) {
|
|
45509
|
+
const colsStr = selectProjection(sql);
|
|
45510
|
+
if (colsStr === null || colsStr === "*") return ["*"];
|
|
45511
|
+
return colsStr.split(",").map((c) => columnAlias(c.trim()));
|
|
45512
|
+
}
|
|
45513
|
+
function selectProjection(sql) {
|
|
45514
|
+
const upper = sql.toUpperCase();
|
|
45515
|
+
const selectAt = keywordIndex(upper, "SELECT", 0);
|
|
45516
|
+
if (selectAt < 0) return null;
|
|
45517
|
+
const colsStart = selectAt + "SELECT".length;
|
|
45518
|
+
const fromAt = keywordIndex(upper, "FROM", colsStart);
|
|
45519
|
+
if (fromAt < 0) return null;
|
|
45520
|
+
return sql.slice(colsStart, fromAt).trim();
|
|
45521
|
+
}
|
|
45522
|
+
function columnAlias(trimmed) {
|
|
45523
|
+
const asAt = keywordIndex(trimmed.toUpperCase(), "AS", 0);
|
|
45524
|
+
if (asAt >= 0) {
|
|
45525
|
+
const after = trimmed.slice(asAt + "AS".length).trim();
|
|
45526
|
+
let endIdx = 0;
|
|
45527
|
+
while (endIdx < after.length && !SQL_WS.includes(after[endIdx]) && after[endIdx] !== ",") endIdx++;
|
|
45528
|
+
const word = after.slice(0, endIdx);
|
|
45529
|
+
if (word) return word;
|
|
45530
|
+
}
|
|
45531
|
+
if (trimmed.includes(".")) return trimmed.split(".").pop().trim();
|
|
45532
|
+
return trimmed;
|
|
45533
|
+
}
|
|
45534
|
+
function keywordIndex(upperHaystack, keyword, from) {
|
|
45535
|
+
let at = upperHaystack.indexOf(keyword, from);
|
|
45536
|
+
while (at >= 0) {
|
|
45537
|
+
const afterIdx = at + keyword.length;
|
|
45538
|
+
const boundaryBefore = at === 0 || SQL_WS.includes(upperHaystack[at - 1]);
|
|
45539
|
+
const boundaryAfter = afterIdx >= upperHaystack.length || SQL_WS.includes(upperHaystack[afterIdx]);
|
|
45540
|
+
if (boundaryBefore && boundaryAfter) return at;
|
|
45541
|
+
at = upperHaystack.indexOf(keyword, at + 1);
|
|
45542
|
+
}
|
|
45543
|
+
return -1;
|
|
45544
|
+
}
|
|
45545
|
+
var h, SQL_WS;
|
|
45546
|
+
var init_crudHelpers = __esm({
|
|
45547
|
+
"src/crudHelpers.ts"() {
|
|
45548
|
+
"use strict";
|
|
45549
|
+
init_engine();
|
|
45550
|
+
h = escapeHtml;
|
|
45551
|
+
SQL_WS = " \n\r\f\v";
|
|
45552
|
+
}
|
|
45553
|
+
});
|
|
45554
|
+
|
|
45555
|
+
// src/crudTable.ts
|
|
45556
|
+
function sortIndicator(sortCol, col, sortDir) {
|
|
45557
|
+
if (String(sortCol) !== String(col)) return "";
|
|
45558
|
+
const arrow = sortDir === "asc" ? "▲" : "▼";
|
|
45559
|
+
return ` <span class="sort-indicator">${arrow}</span>`;
|
|
45560
|
+
}
|
|
45561
|
+
function sortUrl(requestPath, col, nextDir, page, search, limit) {
|
|
45562
|
+
const query = `sort=${encodeURIComponent(col)}&sort_dir=${nextDir}&page=${page}&search=${encodeURIComponent(search)}&limit=${limit}`;
|
|
45563
|
+
return h(`${requestPath}?${query}`);
|
|
45564
|
+
}
|
|
45565
|
+
function pageUrl(requestPath, p, search, sortCol, sortDir, limit) {
|
|
45566
|
+
const query = `page=${p}&search=${encodeURIComponent(search)}&sort=${encodeURIComponent(sortCol)}&sort_dir=${sortDir}&limit=${limit}`;
|
|
45567
|
+
return h(`${requestPath}?${query}`);
|
|
45568
|
+
}
|
|
45569
|
+
function pageControls(page, totalPages, requestPath, search, sortCol, sortDir, limit) {
|
|
45570
|
+
if (totalPages <= 1) return [];
|
|
45571
|
+
const controls = [];
|
|
45572
|
+
if (page > 1) {
|
|
45573
|
+
controls.push({
|
|
45574
|
+
label: "Prev",
|
|
45575
|
+
page: page - 1,
|
|
45576
|
+
active: false,
|
|
45577
|
+
inactive: true,
|
|
45578
|
+
url: pageUrl(requestPath, page - 1, search, sortCol, sortDir, limit)
|
|
45579
|
+
});
|
|
45580
|
+
}
|
|
45581
|
+
let startPage = Math.max(page - 3, 1);
|
|
45582
|
+
const endPage = Math.min(startPage + 6, totalPages);
|
|
45583
|
+
startPage = Math.max(endPage - 6, 1);
|
|
45584
|
+
for (let p = startPage; p <= endPage; p++) {
|
|
45585
|
+
controls.push({
|
|
45586
|
+
label: p,
|
|
45587
|
+
page: p,
|
|
45588
|
+
active: p === page,
|
|
45589
|
+
inactive: p !== page,
|
|
45590
|
+
url: pageUrl(requestPath, p, search, sortCol, sortDir, limit)
|
|
45591
|
+
});
|
|
45592
|
+
}
|
|
45593
|
+
if (page < totalPages) {
|
|
45594
|
+
controls.push({
|
|
45595
|
+
label: "Next",
|
|
45596
|
+
page: page + 1,
|
|
45597
|
+
active: false,
|
|
45598
|
+
inactive: true,
|
|
45599
|
+
url: pageUrl(requestPath, page + 1, search, sortCol, sortDir, limit)
|
|
45600
|
+
});
|
|
45601
|
+
}
|
|
45602
|
+
return controls;
|
|
45603
|
+
}
|
|
45604
|
+
function jsConfig(opts) {
|
|
45605
|
+
const aligns = {};
|
|
45606
|
+
const labels = {};
|
|
45607
|
+
for (const col of opts.columns) {
|
|
45608
|
+
aligns[col] = columnAlignment(opts.model, col);
|
|
45609
|
+
labels[col] = prettyLabel(col);
|
|
45610
|
+
}
|
|
45611
|
+
const config = {
|
|
45612
|
+
api: opts.apiPath,
|
|
45613
|
+
pk: opts.pk,
|
|
45614
|
+
columns: opts.columns,
|
|
45615
|
+
editable: opts.editable,
|
|
45616
|
+
aligns,
|
|
45617
|
+
labels,
|
|
45618
|
+
limit: opts.limit,
|
|
45619
|
+
search: opts.search,
|
|
45620
|
+
sort: opts.sortCol,
|
|
45621
|
+
sort_dir: opts.sortDir,
|
|
45622
|
+
page: opts.page
|
|
45623
|
+
};
|
|
45624
|
+
return JSON.stringify(config).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
45625
|
+
}
|
|
45626
|
+
function headerCell(opts, col, align) {
|
|
45627
|
+
const header = { label: h(prettyLabel(col)), align };
|
|
45628
|
+
if (!opts.sortable) {
|
|
45629
|
+
header.plain = true;
|
|
45630
|
+
return header;
|
|
45631
|
+
}
|
|
45632
|
+
const nextDir = String(opts.sortCol) === String(col) && opts.sortDir === "asc" ? "desc" : "asc";
|
|
45633
|
+
header.sortable = true;
|
|
45634
|
+
header.col = h(col);
|
|
45635
|
+
header.next_dir = nextDir;
|
|
45636
|
+
header.url = sortUrl(opts.requestPath, col, nextDir, opts.page, opts.search, opts.limit);
|
|
45637
|
+
header.indicator = sortIndicator(opts.sortCol, col, opts.sortDir);
|
|
45638
|
+
return header;
|
|
45639
|
+
}
|
|
45640
|
+
function tableData(opts) {
|
|
45641
|
+
const aligns = opts.columns.map((col) => columnAlignment(opts.model, col));
|
|
45642
|
+
const headers = opts.columns.map((col, index) => headerCell(opts, col, aligns[index]));
|
|
45643
|
+
const rows = opts.records.map((record) => ({
|
|
45644
|
+
id: h(cellValue(record, opts.pk) ?? ""),
|
|
45645
|
+
cells: buildCells(opts.columns, record, opts.editable, aligns)
|
|
45646
|
+
}));
|
|
45647
|
+
return {
|
|
45648
|
+
headers,
|
|
45649
|
+
rows,
|
|
45650
|
+
empty: opts.records.length === 0,
|
|
45651
|
+
colspan: opts.columns.length + 1,
|
|
45652
|
+
editable: opts.editable,
|
|
45653
|
+
readonly: !opts.editable,
|
|
45654
|
+
inline_script: opts.inlineScript,
|
|
45655
|
+
table_name: h(opts.tableName),
|
|
45656
|
+
table_id_attr: opts.tableId ? ` id="${h(opts.tableId)}"` : ""
|
|
45657
|
+
};
|
|
45658
|
+
}
|
|
45659
|
+
var init_crudTable = __esm({
|
|
45660
|
+
"src/crudTable.ts"() {
|
|
45661
|
+
"use strict";
|
|
45662
|
+
init_crudHelpers();
|
|
45663
|
+
}
|
|
45664
|
+
});
|
|
45665
|
+
|
|
45666
|
+
// src/crudRender.ts
|
|
45667
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
45668
|
+
import { resolve as pathResolve, join as pathJoin, dirname as dirname15 } from "node:path";
|
|
45669
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
45670
|
+
function frondFor(dir) {
|
|
45671
|
+
let engine = frondCache2.get(dir);
|
|
45672
|
+
if (!engine) {
|
|
45673
|
+
setCspNonceProvider(currentCspNonce);
|
|
45674
|
+
engine = new Frond(dir);
|
|
45675
|
+
frondCache2.set(dir, engine);
|
|
45676
|
+
}
|
|
45677
|
+
return engine;
|
|
45678
|
+
}
|
|
45679
|
+
function renderCrudTemplate(name, data) {
|
|
45680
|
+
const templateFile = `crud/${name}.twig`;
|
|
45681
|
+
const userDir = pathResolve(process.cwd(), "src", "templates");
|
|
45682
|
+
if (existsSync28(pathJoin(userDir, templateFile))) {
|
|
45683
|
+
return frondFor(userDir).render(templateFile, data);
|
|
45684
|
+
}
|
|
45685
|
+
return frondFor(FRAMEWORK_TEMPLATES_DIR).render(templateFile, data);
|
|
45686
|
+
}
|
|
45687
|
+
function renderModalForm(mode, columns, pk, edit) {
|
|
45688
|
+
const fields = columns.map((col) => {
|
|
45689
|
+
const label = prettyLabel(col);
|
|
45690
|
+
return {
|
|
45691
|
+
id: `${mode}-${col}`,
|
|
45692
|
+
name: h(col),
|
|
45693
|
+
label: h(label),
|
|
45694
|
+
value: "",
|
|
45695
|
+
placeholder: h(`Enter ${label.toLowerCase()}`),
|
|
45696
|
+
type: "text",
|
|
45697
|
+
required_attr: "",
|
|
45698
|
+
input: true
|
|
45699
|
+
};
|
|
45700
|
+
});
|
|
45701
|
+
return renderCrudTemplate("form", {
|
|
45702
|
+
wrap: true,
|
|
45703
|
+
form_id_attr: ` id="form-${mode}"`,
|
|
45704
|
+
action: "",
|
|
45705
|
+
form_method: "POST",
|
|
45706
|
+
method_override: null,
|
|
45707
|
+
edit,
|
|
45708
|
+
mode,
|
|
45709
|
+
pk: h(pk),
|
|
45710
|
+
modal_footer: true,
|
|
45711
|
+
submit_button: false,
|
|
45712
|
+
fields
|
|
45713
|
+
});
|
|
45714
|
+
}
|
|
45715
|
+
function renderModals(editableColumns, pk) {
|
|
45716
|
+
return renderCrudTemplate("modals", {
|
|
45717
|
+
create_form: renderModalForm("create", editableColumns, pk, false),
|
|
45718
|
+
edit_form: renderModalForm("edit", editableColumns, pk, true)
|
|
45719
|
+
});
|
|
45720
|
+
}
|
|
45721
|
+
function generateTable(records, options = {}) {
|
|
45722
|
+
const rows = records ?? [];
|
|
45723
|
+
const tableName = String(options.tableName ?? "data");
|
|
45724
|
+
const primaryKey = String(options.primaryKey ?? "id");
|
|
45725
|
+
const editable = options.editable ?? true;
|
|
45726
|
+
const columns = rows.length === 0 ? [] : Object.keys(rows[0]);
|
|
45727
|
+
return renderCrudTemplate(
|
|
45728
|
+
"table",
|
|
45729
|
+
tableData({
|
|
45730
|
+
columns,
|
|
45731
|
+
records: rows,
|
|
45732
|
+
pk: primaryKey,
|
|
45733
|
+
tableName,
|
|
45734
|
+
editable,
|
|
45735
|
+
sortable: false,
|
|
45736
|
+
inlineScript: editable,
|
|
45737
|
+
requestPath: "",
|
|
45738
|
+
search: "",
|
|
45739
|
+
sortCol: "",
|
|
45740
|
+
sortDir: "asc",
|
|
45741
|
+
page: 1,
|
|
45742
|
+
limit: 10,
|
|
45743
|
+
tableId: `crud-${tableName}`,
|
|
45744
|
+
model: null
|
|
45745
|
+
})
|
|
45746
|
+
);
|
|
45747
|
+
}
|
|
45748
|
+
function buildOptions(options, selectedValue) {
|
|
45749
|
+
return (options ?? []).map((opt) => {
|
|
45750
|
+
const selected = String(opt.value) === String(selectedValue) ? " selected" : "";
|
|
45751
|
+
return `<option value="${h(opt.value)}"${selected}>${h(opt.label)}</option>`;
|
|
45752
|
+
}).join("");
|
|
45753
|
+
}
|
|
45754
|
+
function fieldInputShape(field, base) {
|
|
45755
|
+
const value = field.value;
|
|
45756
|
+
switch (field.type ?? "string") {
|
|
45757
|
+
case "text":
|
|
45758
|
+
return { ...base, textarea: true };
|
|
45759
|
+
case "boolean":
|
|
45760
|
+
return { ...base, checkbox: true, checked_attr: value ? " checked" : "" };
|
|
45761
|
+
case "select":
|
|
45762
|
+
return { ...base, select: true, options_html: buildOptions(field.options, value) };
|
|
45763
|
+
case "date":
|
|
45764
|
+
return { ...base, input: true, type: "date" };
|
|
45765
|
+
case "integer":
|
|
45766
|
+
case "number":
|
|
45767
|
+
case "float":
|
|
45768
|
+
case "decimal":
|
|
45769
|
+
return { ...base, input: true, type: "number" };
|
|
45770
|
+
default:
|
|
45771
|
+
return { ...base, input: true, type: "text" };
|
|
45772
|
+
}
|
|
45773
|
+
}
|
|
45774
|
+
function buildCustomField(field) {
|
|
45775
|
+
const name = String(field.name ?? "");
|
|
45776
|
+
const label = field.label ?? name.charAt(0).toUpperCase() + name.slice(1);
|
|
45777
|
+
const base = {
|
|
45778
|
+
id: h(name),
|
|
45779
|
+
name: h(name),
|
|
45780
|
+
label: h(String(label)),
|
|
45781
|
+
value: h(field.value ?? ""),
|
|
45782
|
+
placeholder: "",
|
|
45783
|
+
required_attr: field.required ? " required" : ""
|
|
45784
|
+
};
|
|
45785
|
+
return fieldInputShape(field, base);
|
|
45786
|
+
}
|
|
45787
|
+
function generateForm2(fields, options = {}) {
|
|
45788
|
+
const verb = String(options.method ?? "POST").toUpperCase();
|
|
45789
|
+
return renderCrudTemplate("form", {
|
|
45790
|
+
wrap: true,
|
|
45791
|
+
form_id_attr: "",
|
|
45792
|
+
action: h(options.action ?? "/"),
|
|
45793
|
+
form_method: h(verb),
|
|
45794
|
+
method_override: ["PUT", "PATCH", "DELETE"].includes(verb) ? verb : null,
|
|
45795
|
+
edit: false,
|
|
45796
|
+
modal_footer: false,
|
|
45797
|
+
submit_button: true,
|
|
45798
|
+
fields: (fields ?? []).map((f) => buildCustomField(f))
|
|
45799
|
+
});
|
|
45800
|
+
}
|
|
45801
|
+
var FRAMEWORK_TEMPLATES_DIR, frondCache2;
|
|
45802
|
+
var init_crudRender = __esm({
|
|
45803
|
+
"src/crudRender.ts"() {
|
|
45804
|
+
"use strict";
|
|
45805
|
+
init_engine();
|
|
45806
|
+
init_csp();
|
|
45807
|
+
init_crudHelpers();
|
|
45808
|
+
init_crudTable();
|
|
45809
|
+
FRAMEWORK_TEMPLATES_DIR = pathResolve(
|
|
45810
|
+
dirname15(fileURLToPath9(import.meta.url)),
|
|
45811
|
+
"..",
|
|
45812
|
+
"..",
|
|
45813
|
+
"core",
|
|
45814
|
+
"templates"
|
|
45815
|
+
);
|
|
45816
|
+
frondCache2 = /* @__PURE__ */ new Map();
|
|
45817
|
+
}
|
|
45818
|
+
});
|
|
45819
|
+
|
|
45820
|
+
// src/crud.ts
|
|
45821
|
+
function _resetCrudRegistrations() {
|
|
45822
|
+
registeredTables.clear();
|
|
45823
|
+
}
|
|
45824
|
+
function discoveredFrom(model) {
|
|
45825
|
+
return {
|
|
45826
|
+
filePath: `src/models/${model.name ?? model.tableName}.ts`,
|
|
45827
|
+
modelClass: model,
|
|
45828
|
+
definition: {
|
|
45829
|
+
tableName: model.tableName,
|
|
45830
|
+
className: model.name,
|
|
45831
|
+
fields: model.fields,
|
|
45832
|
+
fieldMapping: model.fieldMapping,
|
|
45833
|
+
softDelete: model.softDelete ?? false,
|
|
45834
|
+
tableFilter: model.tableFilter,
|
|
45835
|
+
dbName: model._db
|
|
45836
|
+
}
|
|
45837
|
+
};
|
|
45838
|
+
}
|
|
45839
|
+
async function registerBackend(model, options = {}) {
|
|
45840
|
+
const prefix = options.prefix ?? "/api";
|
|
45841
|
+
const key = `${prefix}::${model.tableName}`;
|
|
45842
|
+
if (registeredTables.has(key)) return;
|
|
45843
|
+
const routes = generateCrudRoutes([discoveredFrom(model)], { public: options.public === true });
|
|
45844
|
+
for (const route of routes) defaultRouter.addRoute(route);
|
|
45845
|
+
registeredTables.add(key);
|
|
45846
|
+
}
|
|
45847
|
+
async function fetchModelData(model, opts) {
|
|
45848
|
+
const orderBy = `${opts.sort} ${opts.sortDir.toUpperCase()}`;
|
|
45849
|
+
const search = opts.search.trim();
|
|
45850
|
+
const searchable = search === "" ? [] : searchableProps(model);
|
|
45851
|
+
let rows;
|
|
45852
|
+
if (searchable.length === 0) {
|
|
45853
|
+
rows = await model.all(opts.limit, opts.offset, void 0, orderBy);
|
|
45854
|
+
} else {
|
|
45855
|
+
const whereClause = searchable.map((prop) => `${columnOf(model, prop)} LIKE ?`).join(" OR ");
|
|
45856
|
+
const params = searchable.map(() => `%${search}%`);
|
|
45857
|
+
rows = await model.where(whereClause, params, opts.limit, opts.offset, void 0, orderBy);
|
|
45858
|
+
}
|
|
45859
|
+
const total = typeof rows.getTotalRecords === "function" ? rows.getTotalRecords() : rows.length;
|
|
45860
|
+
const records = rows.map((r) => typeof r.toDict === "function" ? r.toDict() : r);
|
|
45861
|
+
return [records, total];
|
|
45862
|
+
}
|
|
45863
|
+
async function fetchSqlData(sql, opts) {
|
|
45864
|
+
let adapter;
|
|
45865
|
+
try {
|
|
45866
|
+
adapter = getAdapter();
|
|
45867
|
+
} catch {
|
|
45868
|
+
return [[], 0];
|
|
45869
|
+
}
|
|
45870
|
+
const base = stripOrderAndLimit(sql);
|
|
45871
|
+
const dir = opts.sortDir.toUpperCase();
|
|
45872
|
+
const search = opts.search.trim();
|
|
45873
|
+
if (search === "") {
|
|
45874
|
+
const countRow2 = await adapterQuery(adapter, `SELECT COUNT(*) as cnt FROM (${base}) AS _crud_cnt`, []);
|
|
45875
|
+
const total2 = Number(countRow2[0]?.cnt ?? 0);
|
|
45876
|
+
const rows2 = await adapterFetch(adapter, `${base} ORDER BY ${opts.sort} ${dir}`, [], opts.limit, opts.offset);
|
|
45877
|
+
return [rows2, total2];
|
|
45878
|
+
}
|
|
45879
|
+
const columns = extractColumns(sql);
|
|
45880
|
+
const searchParts = columns.map((col) => `CAST(${col} AS TEXT) LIKE ?`);
|
|
45881
|
+
const params = columns.map(() => `%${search}%`);
|
|
45882
|
+
const countRow = await adapterQuery(
|
|
45883
|
+
adapter,
|
|
45884
|
+
`SELECT COUNT(*) as cnt FROM (${base}) AS _crud_cnt WHERE ${searchParts.join(" OR ")}`,
|
|
45885
|
+
params
|
|
45886
|
+
);
|
|
45887
|
+
const total = Number(countRow[0]?.cnt ?? 0);
|
|
45888
|
+
const rows = await adapterFetch(
|
|
45889
|
+
adapter,
|
|
45890
|
+
`SELECT * FROM (${base}) AS _crud_sub WHERE ${searchParts.join(" OR ")} ORDER BY ${opts.sort} ${dir}`,
|
|
45891
|
+
params,
|
|
45892
|
+
opts.limit,
|
|
45893
|
+
opts.offset
|
|
45894
|
+
);
|
|
45895
|
+
return [rows, total];
|
|
45896
|
+
}
|
|
45897
|
+
function parseCrudRequest(request, model, pkProp, limit) {
|
|
45898
|
+
const query = request && request.query || {};
|
|
45899
|
+
const page = Math.max(parseInt(String(query.page ?? "1"), 10) || 1, 1);
|
|
45900
|
+
const search = String(query.search ?? "").trim();
|
|
45901
|
+
const sortProp = crudSortProp(model, query.sort, pkProp);
|
|
45902
|
+
const sortColumn = columnOf(model, sortProp);
|
|
45903
|
+
const sortDir = query.sort_dir === "desc" ? "desc" : "asc";
|
|
45904
|
+
const requestPath = request && typeof request.path === "string" ? request.path : "/";
|
|
45905
|
+
return { page, search, sortProp, sortColumn, sortDir, offset: (page - 1) * limit, requestPath };
|
|
45906
|
+
}
|
|
45907
|
+
function clampLimit(raw) {
|
|
45908
|
+
const limit = Number(raw ?? 10);
|
|
45909
|
+
return !Number.isFinite(limit) || limit <= 0 ? 10 : limit;
|
|
45910
|
+
}
|
|
45911
|
+
async function toCrud(request, options) {
|
|
45912
|
+
const model = options.model;
|
|
45913
|
+
if (!model || !model.tableName || !model.fields) {
|
|
45914
|
+
throw new Error("Crud.toCrud requires a model (an ORM class)");
|
|
45915
|
+
}
|
|
45916
|
+
const sql = options.sql;
|
|
45917
|
+
const title = String(options.title ?? "CRUD");
|
|
45918
|
+
const prefix = String(options.prefix ?? "/api");
|
|
45919
|
+
const limit = clampLimit(options.limit);
|
|
45920
|
+
const tableName = String(model.tableName);
|
|
45921
|
+
const pkProp = primaryKeyProp(model);
|
|
45922
|
+
const columns = Object.keys(model.fields);
|
|
45923
|
+
await registerBackend(model, { prefix, public: options.public });
|
|
45924
|
+
const req2 = parseCrudRequest(request, model, pkProp, limit);
|
|
45925
|
+
const fetchOpts = {
|
|
45926
|
+
search: req2.search,
|
|
45927
|
+
sort: req2.sortColumn,
|
|
45928
|
+
sortDir: req2.sortDir,
|
|
45929
|
+
limit,
|
|
45930
|
+
offset: req2.offset
|
|
45931
|
+
};
|
|
45932
|
+
const [records, total] = sql ? await fetchSqlData(sql, fetchOpts) : await fetchModelData(model, fetchOpts);
|
|
45933
|
+
const totalPages = total > 0 ? Math.ceil(total / limit) : 1;
|
|
45934
|
+
const apiPath = `${prefix}/${tableName}`;
|
|
45935
|
+
const editableColumns = columns.filter((c) => c !== pkProp);
|
|
45936
|
+
const tableHtml = renderCrudTemplate(
|
|
45937
|
+
"table",
|
|
45938
|
+
tableData({
|
|
45939
|
+
columns,
|
|
45940
|
+
records,
|
|
45941
|
+
pk: pkProp,
|
|
45942
|
+
tableName,
|
|
45943
|
+
editable: false,
|
|
45944
|
+
sortable: true,
|
|
45945
|
+
inlineScript: false,
|
|
45946
|
+
requestPath: req2.requestPath,
|
|
45947
|
+
search: req2.search,
|
|
45948
|
+
sortCol: req2.sortProp,
|
|
45949
|
+
sortDir: req2.sortDir,
|
|
45950
|
+
page: req2.page,
|
|
45951
|
+
limit,
|
|
45952
|
+
tableId: null,
|
|
45953
|
+
model
|
|
45954
|
+
})
|
|
45955
|
+
);
|
|
45956
|
+
return renderCrudTemplate("page", {
|
|
45957
|
+
title: h(title),
|
|
45958
|
+
search: h(req2.search),
|
|
45959
|
+
request_path: h(req2.requestPath),
|
|
45960
|
+
info_count: records.length,
|
|
45961
|
+
info_total: total,
|
|
45962
|
+
info_page: req2.page,
|
|
45963
|
+
info_total_pages: totalPages,
|
|
45964
|
+
table_html: tableHtml,
|
|
45965
|
+
modals_html: renderModals(editableColumns, pkProp),
|
|
45966
|
+
show_pagination: totalPages > 1,
|
|
45967
|
+
controls: pageControls(req2.page, totalPages, req2.requestPath, req2.search, req2.sortProp, req2.sortDir, limit),
|
|
45968
|
+
config_json: jsConfig({
|
|
45969
|
+
apiPath,
|
|
45970
|
+
pk: pkProp,
|
|
45971
|
+
columns,
|
|
45972
|
+
editable: editableColumns,
|
|
45973
|
+
model,
|
|
45974
|
+
limit,
|
|
45975
|
+
search: req2.search,
|
|
45976
|
+
sortCol: req2.sortProp,
|
|
45977
|
+
sortDir: req2.sortDir,
|
|
45978
|
+
page: req2.page
|
|
45979
|
+
})
|
|
45980
|
+
});
|
|
45981
|
+
}
|
|
45982
|
+
var registeredTables, Crud, CRUD;
|
|
45983
|
+
var init_crud = __esm({
|
|
45984
|
+
"src/crud.ts"() {
|
|
45985
|
+
"use strict";
|
|
45986
|
+
init_router();
|
|
45987
|
+
init_autoCrud();
|
|
45988
|
+
init_database();
|
|
45989
|
+
init_crudHelpers();
|
|
45990
|
+
init_crudTable();
|
|
45991
|
+
init_crudRender();
|
|
45992
|
+
init_crudRender();
|
|
45993
|
+
init_crudHelpers();
|
|
45994
|
+
registeredTables = /* @__PURE__ */ new Set();
|
|
45995
|
+
Crud = {
|
|
45996
|
+
toCrud,
|
|
45997
|
+
registerBackend,
|
|
45998
|
+
generateTable,
|
|
45999
|
+
generateForm: generateForm2,
|
|
46000
|
+
stripOrderAndLimit,
|
|
46001
|
+
_resetCrudRegistrations
|
|
46002
|
+
};
|
|
46003
|
+
CRUD = Crud;
|
|
46004
|
+
}
|
|
46005
|
+
});
|
|
46006
|
+
|
|
44964
46007
|
// src/queryBuilder.ts
|
|
44965
46008
|
var QueryBuilder;
|
|
44966
46009
|
var init_queryBuilder = __esm({
|
|
@@ -47313,7 +48356,7 @@ var init_seeder = __esm({
|
|
|
47313
48356
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
47314
48357
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
47315
48358
|
import { mkdirSync as mkdirSync20 } from "node:fs";
|
|
47316
|
-
import { dirname as
|
|
48359
|
+
import { dirname as dirname16, isAbsolute as isAbsolute6, join as join32 } from "node:path";
|
|
47317
48360
|
function iso(d) {
|
|
47318
48361
|
return d.toISOString();
|
|
47319
48362
|
}
|
|
@@ -47584,7 +48627,7 @@ function resolveStorePath(dbPath) {
|
|
|
47584
48627
|
let path10 = dbPath;
|
|
47585
48628
|
if (!isAbsolute6(path10)) {
|
|
47586
48629
|
path10 = join32(process.cwd(), path10);
|
|
47587
|
-
mkdirSync20(
|
|
48630
|
+
mkdirSync20(dirname16(path10), { recursive: true });
|
|
47588
48631
|
}
|
|
47589
48632
|
return path10;
|
|
47590
48633
|
}
|
|
@@ -49334,7 +50377,9 @@ __export(index_exports, {
|
|
|
49334
50377
|
AutoCrud: () => AutoCrud,
|
|
49335
50378
|
BaseModel: () => BaseModel,
|
|
49336
50379
|
CONNECT_TIMEOUT_TOLERANCE_MS: () => CONNECT_TIMEOUT_TOLERANCE_MS,
|
|
50380
|
+
CRUD: () => CRUD,
|
|
49337
50381
|
CachedDatabaseAdapter: () => CachedDatabaseAdapter,
|
|
50382
|
+
Crud: () => Crud,
|
|
49338
50383
|
Cursor: () => Cursor,
|
|
49339
50384
|
DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
|
|
49340
50385
|
DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
|
|
@@ -49381,6 +50426,7 @@ __export(index_exports, {
|
|
|
49381
50426
|
SqliteCollection: () => SqliteCollection,
|
|
49382
50427
|
SqliteDatabase: () => SqliteDatabase,
|
|
49383
50428
|
UnknownFieldError: () => UnknownFieldError,
|
|
50429
|
+
_resetCrudRegistrations: () => _resetCrudRegistrations,
|
|
49384
50430
|
adapterColumns: () => adapterColumns,
|
|
49385
50431
|
adapterCommit: () => adapterCommit,
|
|
49386
50432
|
adapterCreateTable: () => adapterCreateTable,
|
|
@@ -49413,6 +50459,8 @@ __export(index_exports, {
|
|
|
49413
50459
|
ensureMigrationTable: () => ensureMigrationTable,
|
|
49414
50460
|
extractLastInsertId: () => extractLastInsertId,
|
|
49415
50461
|
generateCrudRoutes: () => generateCrudRoutes,
|
|
50462
|
+
generateForm: () => generateForm2,
|
|
50463
|
+
generateTable: () => generateTable,
|
|
49416
50464
|
getAdapter: () => getAdapter,
|
|
49417
50465
|
getAppliedMigrations: () => getAppliedMigrations,
|
|
49418
50466
|
getCollection: () => getCollection,
|
|
@@ -49432,10 +50480,13 @@ __export(index_exports, {
|
|
|
49432
50480
|
realtime: () => realtime,
|
|
49433
50481
|
recordMigration: () => recordMigration,
|
|
49434
50482
|
redactCredentials: () => redactCredentials,
|
|
50483
|
+
registerBackend: () => registerBackend,
|
|
49435
50484
|
removeMigrationRecord: () => removeMigrationRecord,
|
|
49436
50485
|
resetDefaultStore: () => resetDefaultStore,
|
|
49437
50486
|
resetRequestCaches: () => resetRequestCaches2,
|
|
49438
50487
|
resolveDbPool: () => resolveDbPool,
|
|
50488
|
+
resolveField: () => resolveField,
|
|
50489
|
+
resolveFieldColumn: () => resolveFieldColumn,
|
|
49439
50490
|
resolveFirebirdCharset: () => resolveFirebirdCharset,
|
|
49440
50491
|
resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
|
|
49441
50492
|
resolveSqlitePath: () => resolveSqlitePath,
|
|
@@ -49453,8 +50504,10 @@ __export(index_exports, {
|
|
|
49453
50504
|
splitStatements: () => splitStatements,
|
|
49454
50505
|
status: () => status,
|
|
49455
50506
|
storageKey: () => storageKey,
|
|
50507
|
+
stripOrderAndLimit: () => stripOrderAndLimit,
|
|
49456
50508
|
stripTrailingSemicolons: () => stripTrailingSemicolons,
|
|
49457
50509
|
syncModels: () => syncModels,
|
|
50510
|
+
toCrud: () => toCrud,
|
|
49458
50511
|
validate: () => validate,
|
|
49459
50512
|
withConnectTimeout: () => withConnectTimeout,
|
|
49460
50513
|
wrapWithCache: () => wrapWithCache
|
|
@@ -49470,6 +50523,7 @@ var init_index = __esm({
|
|
|
49470
50523
|
init_model();
|
|
49471
50524
|
init_migration();
|
|
49472
50525
|
init_autoCrud();
|
|
50526
|
+
init_crud();
|
|
49473
50527
|
init_query();
|
|
49474
50528
|
init_validation();
|
|
49475
50529
|
init_baseModel();
|
|
@@ -49501,7 +50555,9 @@ export {
|
|
|
49501
50555
|
AutoCrud,
|
|
49502
50556
|
BaseModel,
|
|
49503
50557
|
CONNECT_TIMEOUT_TOLERANCE_MS,
|
|
50558
|
+
CRUD,
|
|
49504
50559
|
CachedDatabaseAdapter,
|
|
50560
|
+
Crud,
|
|
49505
50561
|
Cursor,
|
|
49506
50562
|
DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
|
|
49507
50563
|
DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
|
|
@@ -49548,6 +50604,7 @@ export {
|
|
|
49548
50604
|
SqliteCollection,
|
|
49549
50605
|
SqliteDatabase,
|
|
49550
50606
|
UnknownFieldError,
|
|
50607
|
+
_resetCrudRegistrations,
|
|
49551
50608
|
adapterColumns,
|
|
49552
50609
|
adapterCommit,
|
|
49553
50610
|
adapterCreateTable,
|
|
@@ -49580,6 +50637,8 @@ export {
|
|
|
49580
50637
|
ensureMigrationTable,
|
|
49581
50638
|
extractLastInsertId,
|
|
49582
50639
|
generateCrudRoutes,
|
|
50640
|
+
generateForm2 as generateForm,
|
|
50641
|
+
generateTable,
|
|
49583
50642
|
getAdapter,
|
|
49584
50643
|
getAppliedMigrations,
|
|
49585
50644
|
getCollection,
|
|
@@ -49599,10 +50658,13 @@ export {
|
|
|
49599
50658
|
realtime,
|
|
49600
50659
|
recordMigration,
|
|
49601
50660
|
redactCredentials,
|
|
50661
|
+
registerBackend,
|
|
49602
50662
|
removeMigrationRecord,
|
|
49603
50663
|
resetDefaultStore,
|
|
49604
50664
|
resetRequestCaches2 as resetRequestCaches,
|
|
49605
50665
|
resolveDbPool,
|
|
50666
|
+
resolveField,
|
|
50667
|
+
resolveFieldColumn,
|
|
49606
50668
|
resolveFirebirdCharset,
|
|
49607
50669
|
resolveGraphConnectTimeout,
|
|
49608
50670
|
resolveSqlitePath,
|
|
@@ -49620,8 +50682,10 @@ export {
|
|
|
49620
50682
|
splitStatements,
|
|
49621
50683
|
status,
|
|
49622
50684
|
storageKey,
|
|
50685
|
+
stripOrderAndLimit,
|
|
49623
50686
|
stripTrailingSemicolons,
|
|
49624
50687
|
syncModels,
|
|
50688
|
+
toCrud,
|
|
49625
50689
|
validate,
|
|
49626
50690
|
withConnectTimeout,
|
|
49627
50691
|
wrapWithCache
|