tina4-nodejs 3.13.148 → 3.13.149
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 +10875 -9847
- package/packages/cli/src/commands/generate.ts +184 -25
- package/packages/core/dist/index.js +10848 -9820
- package/packages/core/src/devAdmin.ts +72 -1
- package/packages/core/src/index.ts +3 -0
- package/packages/core/src/messenger.ts +57 -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 +1255 -217
- 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);
|
|
@@ -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`);
|
|
14463
|
+
}
|
|
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}`);
|
|
14455
14531
|
}
|
|
14456
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
|
}
|
|
@@ -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, 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",
|
|
@@ -25669,7 +25842,7 @@ var init_devAdmin = __esm({
|
|
|
25669
25842
|
const queueDir = queueBasePath2();
|
|
25670
25843
|
let topics = [];
|
|
25671
25844
|
if (existsSync20(queueDir)) {
|
|
25672
|
-
topics =
|
|
25845
|
+
topics = readdirSync13(queueDir).filter((d) => {
|
|
25673
25846
|
try {
|
|
25674
25847
|
return statSync15(join22(queueDir, d)).isDirectory();
|
|
25675
25848
|
} catch {
|
|
@@ -26173,13 +26346,13 @@ var init_devAdmin = __esm({
|
|
|
26173
26346
|
res.json({ success: false, error: msg });
|
|
26174
26347
|
}
|
|
26175
26348
|
};
|
|
26176
|
-
__devAdminFilename =
|
|
26177
|
-
__devAdminDirname =
|
|
26349
|
+
__devAdminFilename = fileURLToPath6(import.meta.url);
|
|
26350
|
+
__devAdminDirname = dirname11(__devAdminFilename);
|
|
26178
26351
|
handleGalleryList = (_req, res) => {
|
|
26179
26352
|
const galleryDir = resolve12(__devAdminDirname, "..", "gallery");
|
|
26180
26353
|
const items = [];
|
|
26181
26354
|
if (existsSync20(galleryDir)) {
|
|
26182
|
-
const entries =
|
|
26355
|
+
const entries = readdirSync13(galleryDir).sort();
|
|
26183
26356
|
for (const entry of entries) {
|
|
26184
26357
|
const entryPath = join22(galleryDir, entry);
|
|
26185
26358
|
const metaFile = join22(entryPath, "meta.json");
|
|
@@ -26283,7 +26456,7 @@ var init_devAdmin = __esm({
|
|
|
26283
26456
|
if (cwdInGit !== "") cwdInGit += "/";
|
|
26284
26457
|
}
|
|
26285
26458
|
const entries = [];
|
|
26286
|
-
for (const name of
|
|
26459
|
+
for (const name of readdirSync13(target).sort()) {
|
|
26287
26460
|
if (devFilesHidden(name)) continue;
|
|
26288
26461
|
const full = join22(target, name);
|
|
26289
26462
|
const entryRel = relative9(root, full).replace(/\\/g, "/");
|
|
@@ -26314,7 +26487,7 @@ var init_devAdmin = __esm({
|
|
|
26314
26487
|
if (isDir) {
|
|
26315
26488
|
hasChildren = false;
|
|
26316
26489
|
try {
|
|
26317
|
-
for (const c of
|
|
26490
|
+
for (const c of readdirSync13(full)) {
|
|
26318
26491
|
if (devFilesHidden(c)) continue;
|
|
26319
26492
|
hasChildren = true;
|
|
26320
26493
|
break;
|
|
@@ -26406,7 +26579,7 @@ var init_devAdmin = __esm({
|
|
|
26406
26579
|
return;
|
|
26407
26580
|
}
|
|
26408
26581
|
try {
|
|
26409
|
-
mkdirSync14(
|
|
26582
|
+
mkdirSync14(dirname11(target), { recursive: true });
|
|
26410
26583
|
const existed = existsSync20(target);
|
|
26411
26584
|
writeFileSync13(target, content, "utf-8");
|
|
26412
26585
|
try {
|
|
@@ -26478,7 +26651,7 @@ var init_devAdmin = __esm({
|
|
|
26478
26651
|
}
|
|
26479
26652
|
try {
|
|
26480
26653
|
const { renameSync: renameSync3 } = await import("node:fs");
|
|
26481
|
-
mkdirSync14(
|
|
26654
|
+
mkdirSync14(dirname11(dst), { recursive: true });
|
|
26482
26655
|
renameSync3(src, dst);
|
|
26483
26656
|
res.json({ ok: true, from: relative9(root, src), to: relative9(root, dst) });
|
|
26484
26657
|
} catch (e) {
|
|
@@ -26499,8 +26672,8 @@ var init_devAdmin = __esm({
|
|
|
26499
26672
|
return;
|
|
26500
26673
|
}
|
|
26501
26674
|
try {
|
|
26502
|
-
const { rmSync } = await import("node:fs");
|
|
26503
|
-
|
|
26675
|
+
const { rmSync: rmSync2 } = await import("node:fs");
|
|
26676
|
+
rmSync2(target, { recursive: true, force: true });
|
|
26504
26677
|
res.json({ ok: true, deleted: relative9(root, target) });
|
|
26505
26678
|
} catch (e) {
|
|
26506
26679
|
res.json({ error: e.message }, 500);
|
|
@@ -26835,10 +27008,10 @@ var init_devAdmin = __esm({
|
|
|
26835
27008
|
});
|
|
26836
27009
|
};
|
|
26837
27010
|
handleDevAdminJs = async (_req, res) => {
|
|
26838
|
-
const { readFileSync: readFileSync27, existsSync:
|
|
26839
|
-
const { dirname:
|
|
26840
|
-
const { fileURLToPath:
|
|
26841
|
-
const dir =
|
|
27011
|
+
const { readFileSync: readFileSync27, existsSync: existsSync29 } = await import("node:fs");
|
|
27012
|
+
const { dirname: dirname17, join: join33, resolve: resolve21 } = await import("node:path");
|
|
27013
|
+
const { fileURLToPath: fileURLToPath10 } = await import("node:url");
|
|
27014
|
+
const dir = dirname17(fileURLToPath10(import.meta.url));
|
|
26842
27015
|
const candidates = [
|
|
26843
27016
|
join33(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
26844
27017
|
// src/../public/js/
|
|
@@ -26849,7 +27022,7 @@ var init_devAdmin = __esm({
|
|
|
26849
27022
|
// project public/
|
|
26850
27023
|
];
|
|
26851
27024
|
for (const jsPath of candidates) {
|
|
26852
|
-
if (
|
|
27025
|
+
if (existsSync29(jsPath)) {
|
|
26853
27026
|
try {
|
|
26854
27027
|
const content = readFileSync27(jsPath, "utf-8");
|
|
26855
27028
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
@@ -26874,7 +27047,7 @@ var init_devAdmin = __esm({
|
|
|
26874
27047
|
});
|
|
26875
27048
|
|
|
26876
27049
|
// ../core/src/i18n.ts
|
|
26877
|
-
import { readFileSync as readFileSync19, readdirSync as
|
|
27050
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync14, existsSync as existsSync21 } from "node:fs";
|
|
26878
27051
|
import { join as join23, resolve as resolve13 } from "node:path";
|
|
26879
27052
|
var I18n;
|
|
26880
27053
|
var init_i18n = __esm({
|
|
@@ -26956,7 +27129,7 @@ var init_i18n = __esm({
|
|
|
26956
27129
|
return [this._defaultLocale];
|
|
26957
27130
|
}
|
|
26958
27131
|
try {
|
|
26959
|
-
const files =
|
|
27132
|
+
const files = readdirSync14(this._localeDir);
|
|
26960
27133
|
const locales = files.filter((f) => f.endsWith(".json") || f.endsWith(".yml") || f.endsWith(".yaml")).map((f) => f.replace(/\.(json|ya?ml)$/, "")).sort();
|
|
26961
27134
|
return locales.length > 0 ? locales : [this._defaultLocale];
|
|
26962
27135
|
} catch {
|
|
@@ -28212,10 +28385,10 @@ var init_docsAutoDiscovery = __esm({
|
|
|
28212
28385
|
|
|
28213
28386
|
// ../core/src/server.ts
|
|
28214
28387
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
28215
|
-
import { resolve as resolve15, dirname as
|
|
28216
|
-
import { existsSync as existsSync23, readdirSync as
|
|
28388
|
+
import { resolve as resolve15, dirname as dirname12, join as join25, relative as relative10 } from "node:path";
|
|
28389
|
+
import { existsSync as existsSync23, readdirSync as readdirSync15, readFileSync as readFileSync21, statSync as statSync16 } from "node:fs";
|
|
28217
28390
|
import { isatty } from "node:tty";
|
|
28218
|
-
import { fileURLToPath as
|
|
28391
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
28219
28392
|
import { execFileSync as execFileSync3, exec } from "node:child_process";
|
|
28220
28393
|
import cluster from "node:cluster";
|
|
28221
28394
|
import os2 from "node:os";
|
|
@@ -28255,7 +28428,7 @@ async function autoMigrateOnStartup(migrationDir = "migrations", base = process.
|
|
|
28255
28428
|
if (!existsSync23(dir)) return;
|
|
28256
28429
|
let hasSql = false;
|
|
28257
28430
|
try {
|
|
28258
|
-
hasSql =
|
|
28431
|
+
hasSql = readdirSync15(dir).some((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"));
|
|
28259
28432
|
} catch {
|
|
28260
28433
|
return;
|
|
28261
28434
|
}
|
|
@@ -28425,7 +28598,7 @@ function injectDevToolbar(html, ctx) {
|
|
|
28425
28598
|
function walkGalleryFiles(dir) {
|
|
28426
28599
|
const results = [];
|
|
28427
28600
|
if (!existsSync23(dir)) return results;
|
|
28428
|
-
for (const f of
|
|
28601
|
+
for (const f of readdirSync15(dir)) {
|
|
28429
28602
|
const full = join25(dir, f);
|
|
28430
28603
|
if (statSync16(full).isDirectory()) results.push(...walkGalleryFiles(full));
|
|
28431
28604
|
else results.push(full);
|
|
@@ -28437,7 +28610,7 @@ function getGalleryDeployedState() {
|
|
|
28437
28610
|
const state = {};
|
|
28438
28611
|
if (!existsSync23(galleryDir)) return state;
|
|
28439
28612
|
try {
|
|
28440
|
-
const entries =
|
|
28613
|
+
const entries = readdirSync15(galleryDir).sort();
|
|
28441
28614
|
for (const entry of entries) {
|
|
28442
28615
|
const entryPath = join25(galleryDir, entry);
|
|
28443
28616
|
const metaFile = join25(entryPath, "meta.json");
|
|
@@ -28488,7 +28661,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
28488
28661
|
const pagesDir = resolve15(templatesDir, TEMPLATE_PAGES_DIR);
|
|
28489
28662
|
if (existsSync23(pagesDir)) {
|
|
28490
28663
|
const scan = (dir, prefix) => {
|
|
28491
|
-
for (const entry of
|
|
28664
|
+
for (const entry of readdirSync15(dir, { withFileTypes: true })) {
|
|
28492
28665
|
if (entry.name.startsWith("_")) continue;
|
|
28493
28666
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
28494
28667
|
if (entry.isDirectory()) {
|
|
@@ -28807,7 +28980,7 @@ function isInjectableHtml(res) {
|
|
|
28807
28980
|
return isHtmlResponse(res) && !res.raw.getHeader("content-encoding");
|
|
28808
28981
|
}
|
|
28809
28982
|
function injectIntoHtml(ctx, devToolbar, html) {
|
|
28810
|
-
if (!devToolbar) return injectFeedbackWidget(ctx.req, html);
|
|
28983
|
+
if (!devToolbar || !devToolbarAllowed(ctx.req)) return injectFeedbackWidget(ctx.req, html);
|
|
28811
28984
|
const toolbarCtx = {
|
|
28812
28985
|
version: TINA4_VERSION,
|
|
28813
28986
|
method: ctx.req.method ?? "GET",
|
|
@@ -29175,7 +29348,7 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
29175
29348
|
const localeDir = resolve15(base, process.env.TINA4_LOCALE_DIR ?? "src/locales");
|
|
29176
29349
|
if (!existsSync23(localeDir)) return;
|
|
29177
29350
|
try {
|
|
29178
|
-
const localeFiles =
|
|
29351
|
+
const localeFiles = readdirSync15(localeDir).filter((file) => file.endsWith(".json"));
|
|
29179
29352
|
if (localeFiles.length === 0 || frondEngine.globals?.t) return;
|
|
29180
29353
|
const i18nInstance = new I18n(process.env.TINA4_LOCALE ?? "en", localeDir);
|
|
29181
29354
|
frondEngine.addGlobal("t", (key, params) => i18nInstance.t(key, params));
|
|
@@ -29590,8 +29763,8 @@ var init_server = __esm({
|
|
|
29590
29763
|
init_i18n();
|
|
29591
29764
|
init_background();
|
|
29592
29765
|
init_version();
|
|
29593
|
-
__filename =
|
|
29594
|
-
__dirname =
|
|
29766
|
+
__filename = fileURLToPath7(import.meta.url);
|
|
29767
|
+
__dirname = dirname12(__filename);
|
|
29595
29768
|
BUILTIN_ERROR_TEMPLATES_DIR = resolve15(__dirname, "..", "templates");
|
|
29596
29769
|
BUILTIN_PUBLIC_DIR = resolve15(__dirname, "..", "public");
|
|
29597
29770
|
swaggerAssetsEnabled = false;
|
|
@@ -29753,7 +29926,7 @@ var init_env = __esm({
|
|
|
29753
29926
|
|
|
29754
29927
|
// ../core/src/fakeData.ts
|
|
29755
29928
|
import { randomInt, randomUUID as randomUUID6 } from "node:crypto";
|
|
29756
|
-
import { existsSync as existsSync24, readdirSync as
|
|
29929
|
+
import { existsSync as existsSync24, readdirSync as readdirSync16 } from "node:fs";
|
|
29757
29930
|
import { resolve as resolve16, join as join26 } from "node:path";
|
|
29758
29931
|
function mulberry32(seed) {
|
|
29759
29932
|
let s = seed | 0;
|
|
@@ -30253,7 +30426,7 @@ var init_fakeData = __esm({
|
|
|
30253
30426
|
async seedDir(seedDir) {
|
|
30254
30427
|
const dir = resolve16(seedDir ?? "src/seeds");
|
|
30255
30428
|
if (!existsSync24(dir)) return [];
|
|
30256
|
-
const files =
|
|
30429
|
+
const files = readdirSync16(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
|
|
30257
30430
|
const executed = [];
|
|
30258
30431
|
for (const file of files) {
|
|
30259
30432
|
const fullPath = join26(dir, file);
|
|
@@ -30359,7 +30532,7 @@ var init_mqttMessage = __esm({
|
|
|
30359
30532
|
});
|
|
30360
30533
|
|
|
30361
30534
|
// ../core/src/mqtt.ts
|
|
30362
|
-
import
|
|
30535
|
+
import net3 from "node:net";
|
|
30363
30536
|
import tls from "node:tls";
|
|
30364
30537
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
30365
30538
|
import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
|
|
@@ -30830,7 +31003,7 @@ var init_mqtt = __esm({
|
|
|
30830
31003
|
if (this.tlsVerify && this.caFile) opts.ca = readFileSync22(this.caFile);
|
|
30831
31004
|
sock = tls.connect(opts, () => settle(() => resolve21(sock)));
|
|
30832
31005
|
} else {
|
|
30833
|
-
sock =
|
|
31006
|
+
sock = net3.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve21(sock)));
|
|
30834
31007
|
}
|
|
30835
31008
|
sock.once("error", (err) => {
|
|
30836
31009
|
settle(() => {
|
|
@@ -31047,7 +31220,7 @@ var init_mqtt = __esm({
|
|
|
31047
31220
|
});
|
|
31048
31221
|
|
|
31049
31222
|
// ../core/src/service.ts
|
|
31050
|
-
import { readdirSync as
|
|
31223
|
+
import { readdirSync as readdirSync17, statSync as statSync17, watchFile, unwatchFile } from "node:fs";
|
|
31051
31224
|
import { join as join27, extname as extname7 } from "node:path";
|
|
31052
31225
|
import { pathToFileURL } from "node:url";
|
|
31053
31226
|
function matchCronField(field, value) {
|
|
@@ -31215,7 +31388,7 @@ var init_service = __esm({
|
|
|
31215
31388
|
const discovered = [];
|
|
31216
31389
|
let entries;
|
|
31217
31390
|
try {
|
|
31218
|
-
entries =
|
|
31391
|
+
entries = readdirSync17(dir);
|
|
31219
31392
|
} catch {
|
|
31220
31393
|
return discovered;
|
|
31221
31394
|
}
|
|
@@ -31342,7 +31515,7 @@ var init_service = __esm({
|
|
|
31342
31515
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
31343
31516
|
let entries;
|
|
31344
31517
|
try {
|
|
31345
|
-
entries =
|
|
31518
|
+
entries = readdirSync17(dir);
|
|
31346
31519
|
} catch {
|
|
31347
31520
|
return;
|
|
31348
31521
|
}
|
|
@@ -31449,9 +31622,9 @@ function ipToBytes(ip) {
|
|
|
31449
31622
|
}
|
|
31450
31623
|
function parseCidr(cidr) {
|
|
31451
31624
|
const [netStr, bitsStr] = cidr.split("/");
|
|
31452
|
-
const
|
|
31453
|
-
if (!
|
|
31454
|
-
return { net:
|
|
31625
|
+
const net5 = ipToBytes(netStr);
|
|
31626
|
+
if (!net5) return null;
|
|
31627
|
+
return { net: net5, bits: Number(bitsStr) };
|
|
31455
31628
|
}
|
|
31456
31629
|
function inCidr(packed, cidr) {
|
|
31457
31630
|
if (packed.length !== cidr.net.length) return false;
|
|
@@ -32030,11 +32203,11 @@ var init_api = __esm({
|
|
|
32030
32203
|
path: null
|
|
32031
32204
|
};
|
|
32032
32205
|
}
|
|
32033
|
-
const
|
|
32034
|
-
if (
|
|
32035
|
-
return { http_code: null, headers: {}, error:
|
|
32206
|
+
const net5 = await this.performRequest("GET", url, headers, data, MAX_REDIRECTS);
|
|
32207
|
+
if (net5.kind === "error") {
|
|
32208
|
+
return { http_code: null, headers: {}, error: net5.error, path: null };
|
|
32036
32209
|
}
|
|
32037
|
-
const res =
|
|
32210
|
+
const res = net5.res;
|
|
32038
32211
|
const code = res.statusCode ?? null;
|
|
32039
32212
|
const respHeaders = flattenHeaders(res.headers);
|
|
32040
32213
|
if (code === null || code >= 400) {
|
|
@@ -32292,11 +32465,11 @@ var init_api = __esm({
|
|
|
32292
32465
|
if (this.transportFn) {
|
|
32293
32466
|
return this.callTransport(method, url, headers, data);
|
|
32294
32467
|
}
|
|
32295
|
-
const
|
|
32296
|
-
if (
|
|
32297
|
-
return { http_code: null, body: null, headers: {}, error:
|
|
32468
|
+
const net5 = await this.performRequest(method, url, headers, data, MAX_REDIRECTS);
|
|
32469
|
+
if (net5.kind === "error") {
|
|
32470
|
+
return { http_code: null, body: null, headers: {}, error: net5.error };
|
|
32298
32471
|
}
|
|
32299
|
-
return this.readResponse(
|
|
32472
|
+
return this.readResponse(net5.res);
|
|
32300
32473
|
}
|
|
32301
32474
|
/**
|
|
32302
32475
|
* Invoke a user-injected transport and normalize its result. The transport
|
|
@@ -32473,7 +32646,7 @@ var init_api = __esm({
|
|
|
32473
32646
|
});
|
|
32474
32647
|
|
|
32475
32648
|
// ../core/src/messenger.ts
|
|
32476
|
-
import
|
|
32649
|
+
import net4 from "node:net";
|
|
32477
32650
|
import tls2 from "node:tls";
|
|
32478
32651
|
import { readFileSync as readFileSync23 } from "node:fs";
|
|
32479
32652
|
import { basename as basename7 } from "node:path";
|
|
@@ -32485,6 +32658,32 @@ function normaliseEncryption(value, kind) {
|
|
|
32485
32658
|
}
|
|
32486
32659
|
return normalised;
|
|
32487
32660
|
}
|
|
32661
|
+
function resolveSendTimeoutMs(optionTimeout) {
|
|
32662
|
+
if (optionTimeout === void 0 || optionTimeout === null) {
|
|
32663
|
+
return resolveEnvTimeoutSeconds(process.env.TINA4_MAIL_TIMEOUT) * 1e3;
|
|
32664
|
+
}
|
|
32665
|
+
if (!Number.isInteger(optionTimeout) || optionTimeout < 1) {
|
|
32666
|
+
throw new Error(
|
|
32667
|
+
`Messenger timeout must be a whole number of seconds and at least 1, got ${optionTimeout}.`
|
|
32668
|
+
);
|
|
32669
|
+
}
|
|
32670
|
+
return optionTimeout * 1e3;
|
|
32671
|
+
}
|
|
32672
|
+
function resolveEnvTimeoutSeconds(raw) {
|
|
32673
|
+
if (!raw || raw.trim() === "") return DEFAULT_TIMEOUT_SECONDS;
|
|
32674
|
+
const trimmed = raw.trim();
|
|
32675
|
+
const seconds = /^\d+$/.test(trimmed) ? Number(trimmed) : NaN;
|
|
32676
|
+
if (Number.isInteger(seconds) && seconds >= 1) return seconds;
|
|
32677
|
+
warnBadMailTimeout(raw);
|
|
32678
|
+
return DEFAULT_TIMEOUT_SECONDS;
|
|
32679
|
+
}
|
|
32680
|
+
function warnBadMailTimeout(raw) {
|
|
32681
|
+
if (_timeoutWarned.has(raw)) return;
|
|
32682
|
+
_timeoutWarned.add(raw);
|
|
32683
|
+
Log.warning(
|
|
32684
|
+
`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`
|
|
32685
|
+
);
|
|
32686
|
+
}
|
|
32488
32687
|
function failOnIdle(socket, timeoutMs, what) {
|
|
32489
32688
|
socket.setTimeout(timeoutMs, () => {
|
|
32490
32689
|
socket.destroy(new Error(`${what} timed out after ${timeoutMs / 1e3}s`));
|
|
@@ -32496,7 +32695,7 @@ function connectVerifiedTls(host, port, socket) {
|
|
|
32496
32695
|
host,
|
|
32497
32696
|
port,
|
|
32498
32697
|
socket,
|
|
32499
|
-
servername:
|
|
32698
|
+
servername: net4.isIP(host) ? void 0 : host,
|
|
32500
32699
|
rejectUnauthorized: true
|
|
32501
32700
|
});
|
|
32502
32701
|
return new Promise((resolve21, reject) => {
|
|
@@ -32508,7 +32707,7 @@ function connectVerifiedTls(host, port, socket) {
|
|
|
32508
32707
|
});
|
|
32509
32708
|
}
|
|
32510
32709
|
function connectPlain(host, port) {
|
|
32511
|
-
const socket =
|
|
32710
|
+
const socket = net4.createConnection({ host, port });
|
|
32512
32711
|
return new Promise((resolve21, reject) => {
|
|
32513
32712
|
socket.once("connect", () => {
|
|
32514
32713
|
socket.removeListener("error", reject);
|
|
@@ -32746,7 +32945,7 @@ function decodeTransfer(body, encoding) {
|
|
|
32746
32945
|
}
|
|
32747
32946
|
}
|
|
32748
32947
|
if (enc === "quoted-printable") {
|
|
32749
|
-
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m,
|
|
32948
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h2) => String.fromCharCode(parseInt(h2, 16)));
|
|
32750
32949
|
}
|
|
32751
32950
|
return body;
|
|
32752
32951
|
}
|
|
@@ -32863,7 +33062,7 @@ function parseFullMessage(uid, response) {
|
|
|
32863
33062
|
function createMessenger() {
|
|
32864
33063
|
return new Messenger();
|
|
32865
33064
|
}
|
|
32866
|
-
var MAIL_ENCRYPTION_VALUES, SMTP_SEND_TIMEOUT_MS, SMTP_CHECK_TIMEOUT_MS, MessengerConnectionError, SmtpCommandError, Messenger, imapTagCounter;
|
|
33065
|
+
var MAIL_ENCRYPTION_VALUES, SMTP_SEND_TIMEOUT_MS, SMTP_CHECK_TIMEOUT_MS, _timeoutWarned, DEFAULT_TIMEOUT_SECONDS, MessengerConnectionError, SmtpCommandError, Messenger, imapTagCounter;
|
|
32867
33066
|
var init_messenger = __esm({
|
|
32868
33067
|
"../core/src/messenger.ts"() {
|
|
32869
33068
|
"use strict";
|
|
@@ -32873,6 +33072,8 @@ var init_messenger = __esm({
|
|
|
32873
33072
|
MAIL_ENCRYPTION_VALUES = ["ssl", "tls", "starttls", "none"];
|
|
32874
33073
|
SMTP_SEND_TIMEOUT_MS = 3e4;
|
|
32875
33074
|
SMTP_CHECK_TIMEOUT_MS = 1e4;
|
|
33075
|
+
_timeoutWarned = /* @__PURE__ */ new Set();
|
|
33076
|
+
DEFAULT_TIMEOUT_SECONDS = 30;
|
|
32876
33077
|
MessengerConnectionError = class extends Error {
|
|
32877
33078
|
constructor(message) {
|
|
32878
33079
|
super(message);
|
|
@@ -32902,6 +33103,8 @@ var init_messenger = __esm({
|
|
|
32902
33103
|
imapUser;
|
|
32903
33104
|
imapPass;
|
|
32904
33105
|
imapEncryption;
|
|
33106
|
+
/** Resolved SMTP send timeout, in milliseconds (see the constructor). */
|
|
33107
|
+
timeoutMs;
|
|
32905
33108
|
constructor(options) {
|
|
32906
33109
|
this.smtpConfigured = Boolean(options?.host ?? process.env.TINA4_MAIL_HOST);
|
|
32907
33110
|
this.host = options?.host ?? process.env.TINA4_MAIL_HOST ?? "localhost";
|
|
@@ -32923,6 +33126,7 @@ var init_messenger = __esm({
|
|
|
32923
33126
|
this.imapUser = options?.imapUser ?? process.env.TINA4_MAIL_IMAP_USERNAME ?? this.username;
|
|
32924
33127
|
this.imapPass = options?.imapPass ?? process.env.TINA4_MAIL_IMAP_PASSWORD ?? this.password;
|
|
32925
33128
|
this.imapEncryption = normaliseEncryption(options?.imapEncryption ?? process.env.TINA4_MAIL_IMAP_ENCRYPTION ?? "tls", "IMAP");
|
|
33129
|
+
this.timeoutMs = resolveSendTimeoutMs(options?.timeout);
|
|
32926
33130
|
}
|
|
32927
33131
|
/**
|
|
32928
33132
|
* How the SMTP connection is secured (ADR-0071 section 1). Port 465 is the
|
|
@@ -33058,7 +33262,7 @@ var init_messenger = __esm({
|
|
|
33058
33262
|
async sendSmtp(options, recipients, messageId) {
|
|
33059
33263
|
let socket = null;
|
|
33060
33264
|
try {
|
|
33061
|
-
socket = await this.openSmtpSession();
|
|
33265
|
+
socket = await this.openSmtpSession(this.timeoutMs);
|
|
33062
33266
|
await this.authenticateSmtp(socket);
|
|
33063
33267
|
await this.sendSmtpEnvelope(socket, recipients.allRecipients);
|
|
33064
33268
|
await this.sendSmtpMessage(socket, options, recipients, messageId);
|
|
@@ -34107,13 +34311,13 @@ var init_htmlElement = __esm({
|
|
|
34107
34311
|
// ../core/src/ai.ts
|
|
34108
34312
|
import { existsSync as existsSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync16, readFileSync as readFileSync24 } from "node:fs";
|
|
34109
34313
|
import { homedir } from "node:os";
|
|
34110
|
-
import { join as join28, resolve as resolve17, relative as relative11, dirname as
|
|
34111
|
-
import { fileURLToPath as
|
|
34314
|
+
import { join as join28, resolve as resolve17, relative as relative11, dirname as dirname13 } from "node:path";
|
|
34315
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
34112
34316
|
import { execSync, execFileSync as execFileSync4 } from "node:child_process";
|
|
34113
34317
|
import { createInterface } from "node:readline";
|
|
34114
34318
|
function readVersion() {
|
|
34115
34319
|
try {
|
|
34116
|
-
const thisDir =
|
|
34320
|
+
const thisDir = dirname13(fileURLToPath8(import.meta.url));
|
|
34117
34321
|
const rootPkg = resolve17(thisDir, "..", "..", "..", "package.json");
|
|
34118
34322
|
const pkg = JSON.parse(readFileSync24(rootPkg, "utf-8"));
|
|
34119
34323
|
return pkg.version ?? "0.0.0";
|
|
@@ -34329,7 +34533,7 @@ function replaceMarkerBlock(existing, block, start2, end) {
|
|
|
34329
34533
|
}
|
|
34330
34534
|
function looksLikeOldFrameworkInstall(existing) {
|
|
34331
34535
|
const head = existing.replace(/^\s+/, "").slice(0, 400);
|
|
34332
|
-
return OLD_FRAMEWORK_HEADERS.some((
|
|
34536
|
+
return OLD_FRAMEWORK_HEADERS.some((h2) => head.startsWith(h2));
|
|
34333
34537
|
}
|
|
34334
34538
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
34335
34539
|
const block = skillBlock(contextFile);
|
|
@@ -34359,7 +34563,7 @@ function installForTool(root, tool, context) {
|
|
|
34359
34563
|
if (tool.configDir) {
|
|
34360
34564
|
mkdirSync17(join28(root, tool.configDir), { recursive: true });
|
|
34361
34565
|
}
|
|
34362
|
-
const parentDir =
|
|
34566
|
+
const parentDir = dirname13(contextPath);
|
|
34363
34567
|
mkdirSync17(parentDir, { recursive: true });
|
|
34364
34568
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
34365
34569
|
const rel = relative11(root, contextPath);
|
|
@@ -34736,7 +34940,7 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
34736
34940
|
}
|
|
34737
34941
|
function generateClaudeCodeContext() {
|
|
34738
34942
|
try {
|
|
34739
|
-
const thisDir =
|
|
34943
|
+
const thisDir = dirname13(fileURLToPath8(import.meta.url));
|
|
34740
34944
|
const repoRoot = resolve17(thisDir, "..", "..", "..");
|
|
34741
34945
|
const claudeMdPath = join28(repoRoot, "CLAUDE.md");
|
|
34742
34946
|
if (existsSync26(claudeMdPath)) {
|
|
@@ -35584,8 +35788,8 @@ var init_aiClient = __esm({
|
|
|
35584
35788
|
static retryDelay(headers, deadline) {
|
|
35585
35789
|
const value = Array.isArray(headers["retry-after"]) ? headers["retry-after"][0] : headers["retry-after"];
|
|
35586
35790
|
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,
|
|
35791
|
+
const delay2 = Math.min(requested, Math.max(0, deadline - performance.now()));
|
|
35792
|
+
return new Promise((resolve21) => setTimeout(resolve21, delay2));
|
|
35589
35793
|
}
|
|
35590
35794
|
static async requestJson(config, headers, body) {
|
|
35591
35795
|
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
@@ -38640,7 +38844,7 @@ __export(sqlite_exports, {
|
|
|
38640
38844
|
});
|
|
38641
38845
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
38642
38846
|
import { mkdirSync as mkdirSync18 } from "node:fs";
|
|
38643
|
-
import { dirname as
|
|
38847
|
+
import { dirname as dirname14, isAbsolute as isAbsolute5, join as join29, resolve as resolve18, sep as sep7 } from "node:path";
|
|
38644
38848
|
function isIdentifier(str) {
|
|
38645
38849
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
38646
38850
|
}
|
|
@@ -38677,7 +38881,7 @@ function resolveSqlitePath(dbPath) {
|
|
|
38677
38881
|
}
|
|
38678
38882
|
const cwd = resolve18(process.cwd());
|
|
38679
38883
|
const path10 = join29(cwd, dbPath);
|
|
38680
|
-
const parent =
|
|
38884
|
+
const parent = dirname14(path10);
|
|
38681
38885
|
const absParent = resolve18(parent);
|
|
38682
38886
|
if (absParent !== cwd && !absParent.startsWith(cwd + sep7)) {
|
|
38683
38887
|
throw new Error(
|
|
@@ -41106,9 +41310,75 @@ __export(mongodb_exports, {
|
|
|
41106
41310
|
function isMatchAllWhere(where) {
|
|
41107
41311
|
return where != null && MATCH_ALL_WHERE.test(where);
|
|
41108
41312
|
}
|
|
41313
|
+
function isSqlWs(ch) {
|
|
41314
|
+
return ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "\f" || ch === "\v";
|
|
41315
|
+
}
|
|
41316
|
+
function isWordChar(ch) {
|
|
41317
|
+
return ch >= "a" && ch <= "z" || ch >= "A" && ch <= "Z" || ch >= "0" && ch <= "9" || ch === "_";
|
|
41318
|
+
}
|
|
41319
|
+
function onlyWs(sql, start2, end) {
|
|
41320
|
+
for (let i = start2; i < end; i++) if (!isSqlWs(sql[i])) return false;
|
|
41321
|
+
return true;
|
|
41322
|
+
}
|
|
41323
|
+
function keywordAt(upper, keyword, from) {
|
|
41324
|
+
let at = upper.indexOf(keyword, from);
|
|
41325
|
+
while (at >= 0) {
|
|
41326
|
+
const end = at + keyword.length;
|
|
41327
|
+
const okBefore = at === 0 || isSqlWs(upper[at - 1]);
|
|
41328
|
+
const okAfter = end >= upper.length || isSqlWs(upper[end]);
|
|
41329
|
+
if (okBefore && okAfter) return at;
|
|
41330
|
+
at = upper.indexOf(keyword, at + 1);
|
|
41331
|
+
}
|
|
41332
|
+
return -1;
|
|
41333
|
+
}
|
|
41334
|
+
function earliest(positions, fallback) {
|
|
41335
|
+
let min = fallback;
|
|
41336
|
+
for (const p of positions) if (p >= 0 && p < min) min = p;
|
|
41337
|
+
return min;
|
|
41338
|
+
}
|
|
41339
|
+
function readIdentifier(sql, from) {
|
|
41340
|
+
let i = from;
|
|
41341
|
+
while (i < sql.length && isSqlWs(sql[i])) i++;
|
|
41342
|
+
if (i < sql.length && QUOTE_CHARS.includes(sql[i])) i++;
|
|
41343
|
+
const start2 = i;
|
|
41344
|
+
while (i < sql.length && isWordChar(sql[i])) i++;
|
|
41345
|
+
if (i === start2) return null;
|
|
41346
|
+
const name = sql.slice(start2, i);
|
|
41347
|
+
if (i < sql.length && QUOTE_CHARS.includes(sql[i])) i++;
|
|
41348
|
+
return [name, i];
|
|
41349
|
+
}
|
|
41350
|
+
function keywordGapEnd(upper, sql, keyword, from) {
|
|
41351
|
+
const at = keywordAt(upper, keyword, from);
|
|
41352
|
+
if (at < 0 || !onlyWs(sql, from, at)) return -1;
|
|
41353
|
+
return at + keyword.length;
|
|
41354
|
+
}
|
|
41355
|
+
function stripQuotes(value) {
|
|
41356
|
+
let result = value;
|
|
41357
|
+
if (result.length > 0 && (result[0] === '"' || result[0] === "'")) result = result.slice(1);
|
|
41358
|
+
const last = result.length - 1;
|
|
41359
|
+
if (last >= 0 && (result[last] === '"' || result[last] === "'")) result = result.slice(0, last);
|
|
41360
|
+
return result;
|
|
41361
|
+
}
|
|
41362
|
+
function splitOnAnd(where) {
|
|
41363
|
+
const parts = [];
|
|
41364
|
+
const lower = where.toLowerCase();
|
|
41365
|
+
let segStart = 0;
|
|
41366
|
+
let i = 0;
|
|
41367
|
+
while (i < where.length) {
|
|
41368
|
+
if (lower.startsWith("and", i) && i > 0 && isSqlWs(where[i - 1]) && i + 3 < where.length && isSqlWs(where[i + 3])) {
|
|
41369
|
+
parts.push(where.slice(segStart, i));
|
|
41370
|
+
i += 3;
|
|
41371
|
+
segStart = i;
|
|
41372
|
+
continue;
|
|
41373
|
+
}
|
|
41374
|
+
i++;
|
|
41375
|
+
}
|
|
41376
|
+
parts.push(where.slice(segStart));
|
|
41377
|
+
return parts;
|
|
41378
|
+
}
|
|
41109
41379
|
function parseWhereClause(where, params, paramOffset = 0) {
|
|
41110
41380
|
const filter = {};
|
|
41111
|
-
const parts = where
|
|
41381
|
+
const parts = splitOnAnd(where);
|
|
41112
41382
|
let paramIndex = paramOffset;
|
|
41113
41383
|
for (const part of parts) {
|
|
41114
41384
|
const trimmed = part.trim();
|
|
@@ -41181,27 +41451,57 @@ function requireWriteFilter(filter, operation, table2) {
|
|
|
41181
41451
|
);
|
|
41182
41452
|
}
|
|
41183
41453
|
}
|
|
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;
|
|
41454
|
+
function buildProjection(cols) {
|
|
41188
41455
|
const projection = {};
|
|
41189
|
-
if (cols
|
|
41190
|
-
|
|
41191
|
-
|
|
41192
|
-
|
|
41193
|
-
|
|
41194
|
-
|
|
41195
|
-
|
|
41456
|
+
if (!cols || cols.trim() === "*") return projection;
|
|
41457
|
+
for (const col of cols.split(",")) {
|
|
41458
|
+
const name = stripQuotes(col.trim());
|
|
41459
|
+
if (name && name !== "*") projection[name] = 1;
|
|
41460
|
+
}
|
|
41461
|
+
return projection;
|
|
41462
|
+
}
|
|
41463
|
+
function stripTrailingDirection(part) {
|
|
41464
|
+
const lower = part.toLowerCase();
|
|
41465
|
+
let suffix = 0;
|
|
41466
|
+
if (lower.endsWith("asc")) suffix = 3;
|
|
41467
|
+
else if (lower.endsWith("desc")) suffix = 4;
|
|
41468
|
+
else return part;
|
|
41469
|
+
const beforeIdx = part.length - suffix - 1;
|
|
41470
|
+
if (beforeIdx < 0 || !isSqlWs(part[beforeIdx])) return part;
|
|
41471
|
+
let i = beforeIdx;
|
|
41472
|
+
while (i >= 0 && isSqlWs(part[i])) i--;
|
|
41473
|
+
return part.slice(0, i + 1);
|
|
41474
|
+
}
|
|
41475
|
+
function parseOrderBy(orderBy) {
|
|
41196
41476
|
const sort = {};
|
|
41197
|
-
|
|
41198
|
-
|
|
41199
|
-
|
|
41200
|
-
|
|
41201
|
-
|
|
41202
|
-
sort[col] = desc ? -1 : 1;
|
|
41203
|
-
}
|
|
41477
|
+
for (const rawPart of orderBy.split(",")) {
|
|
41478
|
+
const trimmed = rawPart.trim();
|
|
41479
|
+
const desc = trimmed.toLowerCase().endsWith("desc");
|
|
41480
|
+
const col = stripQuotes(stripTrailingDirection(trimmed)).trim();
|
|
41481
|
+
sort[col] = desc ? -1 : 1;
|
|
41204
41482
|
}
|
|
41483
|
+
return sort;
|
|
41484
|
+
}
|
|
41485
|
+
function parseSelect(sql, params) {
|
|
41486
|
+
const upper = sql.toUpperCase();
|
|
41487
|
+
if (!(upper.startsWith("SELECT") && (sql.length === 6 || isSqlWs(sql[6])))) return null;
|
|
41488
|
+
const fromAt = keywordAt(upper, "FROM", 6);
|
|
41489
|
+
if (fromAt < 0) return null;
|
|
41490
|
+
const cols = sql.slice(6, fromAt).trim();
|
|
41491
|
+
const ident = readIdentifier(sql, fromAt + 4);
|
|
41492
|
+
if (!ident) return null;
|
|
41493
|
+
const [collection, afterColl] = ident;
|
|
41494
|
+
const whereAt = keywordAt(upper, "WHERE", afterColl);
|
|
41495
|
+
const orderAt = keywordAt(upper, "ORDER BY", afterColl);
|
|
41496
|
+
const limitAt = keywordAt(upper, "LIMIT", afterColl);
|
|
41497
|
+
const offsetAt = keywordAt(upper, "OFFSET", afterColl);
|
|
41498
|
+
const end = sql.length;
|
|
41499
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5, earliest([orderAt, limitAt, offsetAt], end)).trim();
|
|
41500
|
+
const orderBy = orderAt < 0 ? void 0 : sql.slice(orderAt + 8, earliest([limitAt, offsetAt], end)).trim();
|
|
41501
|
+
const limitStr = limitAt < 0 ? void 0 : sql.slice(limitAt + 5, earliest([offsetAt], end)).trim();
|
|
41502
|
+
const skipStr = offsetAt < 0 ? void 0 : sql.slice(offsetAt + 6).trim();
|
|
41503
|
+
const projection = buildProjection(cols);
|
|
41504
|
+
const filter = whereClause ? parseWhereClause(whereClause, params).filter : {};
|
|
41205
41505
|
return {
|
|
41206
41506
|
type: "find",
|
|
41207
41507
|
collection,
|
|
@@ -41209,7 +41509,7 @@ function parseSelect(sql, params) {
|
|
|
41209
41509
|
projection: Object.keys(projection).length > 0 ? projection : void 0,
|
|
41210
41510
|
limit: limitStr ? parseInt(limitStr, 10) : void 0,
|
|
41211
41511
|
skip: skipStr ? parseInt(skipStr, 10) : void 0,
|
|
41212
|
-
sort: orderBy ?
|
|
41512
|
+
sort: orderBy ? parseOrderBy(orderBy) : void 0
|
|
41213
41513
|
};
|
|
41214
41514
|
}
|
|
41215
41515
|
function parseInsert(sql, params) {
|
|
@@ -41245,20 +41545,33 @@ function parseSetClause(setClause, params) {
|
|
|
41245
41545
|
return { document, consumed };
|
|
41246
41546
|
}
|
|
41247
41547
|
function parseUpdate(sql, params) {
|
|
41248
|
-
const
|
|
41249
|
-
if (!
|
|
41250
|
-
const
|
|
41548
|
+
const upper = sql.toUpperCase();
|
|
41549
|
+
if (!(upper.startsWith("UPDATE") && sql.length > 6 && isSqlWs(sql[6]))) return null;
|
|
41550
|
+
const ident = readIdentifier(sql, 6);
|
|
41551
|
+
if (!ident) return null;
|
|
41552
|
+
const [collection, afterColl] = ident;
|
|
41553
|
+
const setAt = keywordAt(upper, "SET", afterColl);
|
|
41554
|
+
if (setAt < 0 || !onlyWs(sql, afterColl, setAt)) return null;
|
|
41555
|
+
const whereAt = keywordAt(upper, "WHERE", setAt + 3);
|
|
41556
|
+
const setClause = sql.slice(setAt + 3, whereAt < 0 ? sql.length : whereAt).trim();
|
|
41557
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5).trim();
|
|
41251
41558
|
const set = parseSetClause(setClause, params);
|
|
41252
|
-
const matchAll = isMatchAllWhere(whereClause
|
|
41253
|
-
const filter = whereClause ? parseWhereClause(whereClause
|
|
41559
|
+
const matchAll = isMatchAllWhere(whereClause);
|
|
41560
|
+
const filter = whereClause ? parseWhereClause(whereClause, params, set.consumed).filter : {};
|
|
41254
41561
|
return { type: "updateMany", collection, filter, update: { $set: set.document }, matchAll };
|
|
41255
41562
|
}
|
|
41256
41563
|
function parseDelete(sql, params) {
|
|
41257
|
-
const
|
|
41258
|
-
if (!
|
|
41259
|
-
const
|
|
41260
|
-
|
|
41261
|
-
const
|
|
41564
|
+
const upper = sql.toUpperCase();
|
|
41565
|
+
if (!upper.startsWith("DELETE")) return null;
|
|
41566
|
+
const fromAt = keywordAt(upper, "FROM", 6);
|
|
41567
|
+
if (fromAt < 0 || !onlyWs(sql, 6, fromAt)) return null;
|
|
41568
|
+
const ident = readIdentifier(sql, fromAt + 4);
|
|
41569
|
+
if (!ident) return null;
|
|
41570
|
+
const [collection, afterColl] = ident;
|
|
41571
|
+
const whereAt = keywordAt(upper, "WHERE", afterColl);
|
|
41572
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5).trim();
|
|
41573
|
+
const matchAll = isMatchAllWhere(whereClause);
|
|
41574
|
+
const filter = whereClause ? parseWhereClause(whereClause, params).filter : {};
|
|
41262
41575
|
return { type: "deleteMany", collection, filter, matchAll };
|
|
41263
41576
|
}
|
|
41264
41577
|
function parseCreate(sql) {
|
|
@@ -41266,10 +41579,23 @@ function parseCreate(sql) {
|
|
|
41266
41579
|
return match ? { type: "raw", collection: match[1] } : null;
|
|
41267
41580
|
}
|
|
41268
41581
|
function parseCount(sql, params) {
|
|
41269
|
-
const
|
|
41270
|
-
if (!
|
|
41271
|
-
const
|
|
41272
|
-
|
|
41582
|
+
const upper = sql.toUpperCase();
|
|
41583
|
+
if (!upper.startsWith("SELECT")) return null;
|
|
41584
|
+
const afterCount = keywordGapEnd(upper, sql, "COUNT(*)", 6);
|
|
41585
|
+
if (afterCount < 0) return null;
|
|
41586
|
+
const afterAs = keywordGapEnd(upper, sql, "AS", afterCount);
|
|
41587
|
+
if (afterAs < 0) return null;
|
|
41588
|
+
const aliasIdent = readIdentifier(sql, afterAs);
|
|
41589
|
+
if (!aliasIdent) return null;
|
|
41590
|
+
const [alias, afterAlias] = aliasIdent;
|
|
41591
|
+
const afterFrom = keywordGapEnd(upper, sql, "FROM", afterAlias);
|
|
41592
|
+
if (afterFrom < 0) return null;
|
|
41593
|
+
const coll = readIdentifier(sql, afterFrom);
|
|
41594
|
+
if (!coll) return null;
|
|
41595
|
+
const [collection, afterColl] = coll;
|
|
41596
|
+
const whereAt = keywordAt(upper, "WHERE", afterColl);
|
|
41597
|
+
const whereClause = whereAt < 0 ? void 0 : sql.slice(whereAt + 5).trim();
|
|
41598
|
+
const filter = whereClause ? parseWhereClause(whereClause, params).filter : {};
|
|
41273
41599
|
return { type: "aggregate", collection, pipeline: [{ $match: filter }, { $count: alias }] };
|
|
41274
41600
|
}
|
|
41275
41601
|
function parseSql(sql, params = []) {
|
|
@@ -41295,12 +41621,13 @@ function fieldTypeToJsonSchema(def) {
|
|
|
41295
41621
|
return { bsonType: "string" };
|
|
41296
41622
|
}
|
|
41297
41623
|
}
|
|
41298
|
-
var MATCH_ALL_WHERE, MongodbAdapter;
|
|
41624
|
+
var MATCH_ALL_WHERE, QUOTE_CHARS, MongodbAdapter;
|
|
41299
41625
|
var init_mongodb = __esm({
|
|
41300
41626
|
"src/adapters/mongodb.ts"() {
|
|
41301
41627
|
"use strict";
|
|
41302
41628
|
init_connectTimeout();
|
|
41303
41629
|
MATCH_ALL_WHERE = /^\s*1\s*=\s*1\s*$/;
|
|
41630
|
+
QUOTE_CHARS = `"'`;
|
|
41304
41631
|
MongodbAdapter = class {
|
|
41305
41632
|
constructor(config) {
|
|
41306
41633
|
this.config = config;
|
|
@@ -43624,13 +43951,13 @@ var init_database = __esm({
|
|
|
43624
43951
|
});
|
|
43625
43952
|
|
|
43626
43953
|
// src/model.ts
|
|
43627
|
-
import { readdirSync as
|
|
43954
|
+
import { readdirSync as readdirSync18, statSync as statSync18 } from "node:fs";
|
|
43628
43955
|
import { join as join30, extname as extname8 } from "node:path";
|
|
43629
43956
|
async function discoverModels(modelsDir) {
|
|
43630
43957
|
const models = [];
|
|
43631
43958
|
let files;
|
|
43632
43959
|
try {
|
|
43633
|
-
files =
|
|
43960
|
+
files = readdirSync18(modelsDir);
|
|
43634
43961
|
} catch {
|
|
43635
43962
|
return models;
|
|
43636
43963
|
}
|
|
@@ -43677,8 +44004,10 @@ var init_model = __esm({
|
|
|
43677
44004
|
});
|
|
43678
44005
|
|
|
43679
44006
|
// src/migration.ts
|
|
43680
|
-
import { existsSync as existsSync27, readdirSync as
|
|
44007
|
+
import { existsSync as existsSync27, readdirSync as readdirSync19, readFileSync as readFileSync25, mkdirSync as mkdirSync19, writeFileSync as writeFileSync17, rmSync } from "node:fs";
|
|
43681
44008
|
import { join as join31, resolve as resolve19 } from "node:path";
|
|
44009
|
+
import { tmpdir } from "node:os";
|
|
44010
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
43682
44011
|
function unwrapAdapter(db) {
|
|
43683
44012
|
let cur = db;
|
|
43684
44013
|
while (cur && cur.constructor?.name === "CachedDatabaseAdapter" && cur.adapter) {
|
|
@@ -44157,17 +44486,113 @@ function warnUnprefixedMigrations(files) {
|
|
|
44157
44486
|
);
|
|
44158
44487
|
}
|
|
44159
44488
|
}
|
|
44489
|
+
async function acquireMigrationLock(db, dir) {
|
|
44490
|
+
let engine = "";
|
|
44491
|
+
try {
|
|
44492
|
+
engine = (db.getDatabaseType() || "").toLowerCase();
|
|
44493
|
+
} catch {
|
|
44494
|
+
engine = "";
|
|
44495
|
+
}
|
|
44496
|
+
try {
|
|
44497
|
+
if (engine.startsWith("postgres")) {
|
|
44498
|
+
await adapterQuery(db, `SELECT pg_advisory_lock(${PG_ADVISORY_KEY}) AS locked`);
|
|
44499
|
+
return { kind: "postgres" };
|
|
44500
|
+
}
|
|
44501
|
+
if (engine.startsWith("mysql")) {
|
|
44502
|
+
await adapterQuery(db, `SELECT GET_LOCK('${MIGRATION_LOCK_NAME}', -1) AS locked`);
|
|
44503
|
+
return { kind: "mysql" };
|
|
44504
|
+
}
|
|
44505
|
+
if (engine === "mssql" || engine === "sqlserver") {
|
|
44506
|
+
await adapterExecute(
|
|
44507
|
+
db,
|
|
44508
|
+
`DECLARE @res INT; EXEC @res = sp_getapplock @Resource = '${MIGRATION_LOCK_NAME}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = -1`
|
|
44509
|
+
);
|
|
44510
|
+
return { kind: "mssql" };
|
|
44511
|
+
}
|
|
44512
|
+
} catch (err) {
|
|
44513
|
+
Log.debug(`DB migration lock unavailable (${engine}): ${err instanceof Error ? err.message : err}; using a file lock`);
|
|
44514
|
+
}
|
|
44515
|
+
return acquireFileLock(dir);
|
|
44516
|
+
}
|
|
44517
|
+
async function acquireFileLock(dir) {
|
|
44518
|
+
const lockFile = join31(tmpdir(), `tina4-migration-${createHash10("sha256").update(resolve19(dir)).digest("hex")}.lock`);
|
|
44519
|
+
const deadline = Date.now() + 6e4;
|
|
44520
|
+
for (; ; ) {
|
|
44521
|
+
try {
|
|
44522
|
+
writeFileSync17(lockFile, String(process.pid), { flag: "wx" });
|
|
44523
|
+
return { kind: "file", resource: lockFile };
|
|
44524
|
+
} catch (err) {
|
|
44525
|
+
if (err.code !== "EEXIST") {
|
|
44526
|
+
Log.debug(`file migration lock unavailable: ${err instanceof Error ? err.message : err}; running unlocked`);
|
|
44527
|
+
return { kind: "none" };
|
|
44528
|
+
}
|
|
44529
|
+
if (isStaleLock(lockFile)) {
|
|
44530
|
+
try {
|
|
44531
|
+
rmSync(lockFile, { force: true });
|
|
44532
|
+
} catch {
|
|
44533
|
+
}
|
|
44534
|
+
continue;
|
|
44535
|
+
}
|
|
44536
|
+
if (Date.now() > deadline) {
|
|
44537
|
+
Log.debug("file migration lock wait timed out; running unlocked");
|
|
44538
|
+
return { kind: "none" };
|
|
44539
|
+
}
|
|
44540
|
+
await delay(50);
|
|
44541
|
+
}
|
|
44542
|
+
}
|
|
44543
|
+
}
|
|
44544
|
+
function isStaleLock(lockFile) {
|
|
44545
|
+
let raw;
|
|
44546
|
+
try {
|
|
44547
|
+
raw = readFileSync25(lockFile, "utf-8").trim();
|
|
44548
|
+
} catch {
|
|
44549
|
+
return false;
|
|
44550
|
+
}
|
|
44551
|
+
const pid = parseInt(raw, 10);
|
|
44552
|
+
if (!raw || !Number.isFinite(pid) || pid <= 0) {
|
|
44553
|
+
return false;
|
|
44554
|
+
}
|
|
44555
|
+
try {
|
|
44556
|
+
process.kill(pid, 0);
|
|
44557
|
+
return false;
|
|
44558
|
+
} catch (err) {
|
|
44559
|
+
return err.code === "ESRCH";
|
|
44560
|
+
}
|
|
44561
|
+
}
|
|
44562
|
+
async function releaseMigrationLock(db, lock) {
|
|
44563
|
+
try {
|
|
44564
|
+
if (lock.kind === "postgres") {
|
|
44565
|
+
await adapterQuery(db, `SELECT pg_advisory_unlock(${PG_ADVISORY_KEY}) AS released`);
|
|
44566
|
+
} else if (lock.kind === "mysql") {
|
|
44567
|
+
await adapterQuery(db, `SELECT RELEASE_LOCK('${MIGRATION_LOCK_NAME}') AS released`);
|
|
44568
|
+
} else if (lock.kind === "mssql") {
|
|
44569
|
+
await adapterExecute(db, `EXEC sp_releaseapplock @Resource = '${MIGRATION_LOCK_NAME}', @LockOwner = 'Session'`);
|
|
44570
|
+
} else if (lock.kind === "file" && lock.resource) {
|
|
44571
|
+
rmSync(lock.resource, { recursive: true, force: true });
|
|
44572
|
+
}
|
|
44573
|
+
} catch (err) {
|
|
44574
|
+
Log.debug(`migration lock release failed (${lock.kind}): ${err instanceof Error ? err.message : err}`);
|
|
44575
|
+
}
|
|
44576
|
+
}
|
|
44160
44577
|
async function migrate(adapter, options) {
|
|
44161
44578
|
const db = adapter ?? getAdapter();
|
|
44162
44579
|
const dir = resolve19(options?.migrationsDir ?? "migrations");
|
|
44163
44580
|
const delimiter2 = options?.delimiter ?? ";";
|
|
44164
|
-
const result = { applied: [], skipped: [], failed: [] };
|
|
44165
44581
|
if (!existsSync27(dir)) {
|
|
44166
|
-
return
|
|
44582
|
+
return { applied: [], skipped: [], failed: [] };
|
|
44583
|
+
}
|
|
44584
|
+
const lock = await acquireMigrationLock(db, dir);
|
|
44585
|
+
try {
|
|
44586
|
+
return await runPendingMigrations(db, dir, delimiter2);
|
|
44587
|
+
} finally {
|
|
44588
|
+
await releaseMigrationLock(db, lock);
|
|
44167
44589
|
}
|
|
44590
|
+
}
|
|
44591
|
+
async function runPendingMigrations(db, dir, delimiter2) {
|
|
44592
|
+
const result = { applied: [], skipped: [], failed: [] };
|
|
44168
44593
|
await ensureMigrationTableOn(db);
|
|
44169
44594
|
const files = sortMigrationFiles(
|
|
44170
|
-
|
|
44595
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44171
44596
|
);
|
|
44172
44597
|
if (files.length === 0) return result;
|
|
44173
44598
|
warnUnprefixedMigrations(files);
|
|
@@ -44240,14 +44665,14 @@ async function status(adapter, options) {
|
|
|
44240
44665
|
}
|
|
44241
44666
|
if (!await adapterTableExists(db, MIGRATION_TABLE)) {
|
|
44242
44667
|
const files2 = sortMigrationFiles(
|
|
44243
|
-
|
|
44668
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44244
44669
|
);
|
|
44245
44670
|
result.pending = files2;
|
|
44246
44671
|
return result;
|
|
44247
44672
|
}
|
|
44248
44673
|
await ensureMigrationTableOn(db);
|
|
44249
44674
|
const files = sortMigrationFiles(
|
|
44250
|
-
|
|
44675
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44251
44676
|
);
|
|
44252
44677
|
const appliedNames = /* @__PURE__ */ new Set();
|
|
44253
44678
|
try {
|
|
@@ -44347,7 +44772,7 @@ export class ${className} {
|
|
|
44347
44772
|
writeFileSync17(filePath, content, "utf-8");
|
|
44348
44773
|
return filePath;
|
|
44349
44774
|
}
|
|
44350
|
-
var ALTER_ADD_RE, CREATE_TABLE_RE, MIGRATION_TABLE, SMART_QUOTES, SMART_QUOTE_RE, SET_TERM_RE, Migration;
|
|
44775
|
+
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
44776
|
var init_migration = __esm({
|
|
44352
44777
|
"src/migration.ts"() {
|
|
44353
44778
|
"use strict";
|
|
@@ -44372,6 +44797,12 @@ var init_migration = __esm({
|
|
|
44372
44797
|
};
|
|
44373
44798
|
SMART_QUOTE_RE = new RegExp(`[${Object.keys(SMART_QUOTES).join("")}]`, "g");
|
|
44374
44799
|
SET_TERM_RE = /^SET\s+TERM\s+(\S+)$/i;
|
|
44800
|
+
MIGRATION_LOCK_NAME = "tina4_migration_lock";
|
|
44801
|
+
PG_ADVISORY_KEY = BigInt.asIntN(
|
|
44802
|
+
64,
|
|
44803
|
+
BigInt("0x" + createHash10("sha256").update(MIGRATION_LOCK_NAME).digest("hex").slice(0, 16))
|
|
44804
|
+
).toString();
|
|
44805
|
+
delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
44375
44806
|
Migration = class {
|
|
44376
44807
|
db;
|
|
44377
44808
|
dir;
|
|
@@ -44428,7 +44859,7 @@ var init_migration = __esm({
|
|
|
44428
44859
|
const dir = resolve19(this.dir);
|
|
44429
44860
|
if (!existsSync27(dir)) return [];
|
|
44430
44861
|
return sortMigrationFiles(
|
|
44431
|
-
|
|
44862
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44432
44863
|
);
|
|
44433
44864
|
}
|
|
44434
44865
|
};
|
|
@@ -44436,17 +44867,40 @@ var init_migration = __esm({
|
|
|
44436
44867
|
});
|
|
44437
44868
|
|
|
44438
44869
|
// src/query.ts
|
|
44439
|
-
function resolveFieldColumn(fields,
|
|
44440
|
-
const field = resolveField(fields,
|
|
44441
|
-
return field === null ? null :
|
|
44870
|
+
function resolveFieldColumn(fields, columnOf2, key) {
|
|
44871
|
+
const field = resolveField(fields, columnOf2, key);
|
|
44872
|
+
return field === null ? null : columnOf2(field);
|
|
44442
44873
|
}
|
|
44443
|
-
function resolveField(fields,
|
|
44874
|
+
function resolveField(fields, columnOf2, key) {
|
|
44444
44875
|
if (Object.hasOwn(fields, key)) return key;
|
|
44445
44876
|
for (const field of Object.keys(fields)) {
|
|
44446
|
-
if (
|
|
44877
|
+
if (columnOf2(field) === key) return field;
|
|
44447
44878
|
}
|
|
44448
44879
|
return null;
|
|
44449
44880
|
}
|
|
44881
|
+
function applySearch(options, conditions, params, quote) {
|
|
44882
|
+
const searchTerm = (options.search ?? "").trim();
|
|
44883
|
+
if (searchTerm === "" || !options.searchColumns || options.searchColumns.length === 0) return;
|
|
44884
|
+
const likeParts = options.searchColumns.map((col) => `${quote(col)} LIKE ?`);
|
|
44885
|
+
conditions.push(`(${likeParts.join(" OR ")})`);
|
|
44886
|
+
for (const _ of options.searchColumns) params.push(`%${searchTerm}%`);
|
|
44887
|
+
}
|
|
44888
|
+
function orderPart(rawPart, bareDir, column2) {
|
|
44889
|
+
const trimmed = rawPart.trim();
|
|
44890
|
+
if (trimmed === "") return null;
|
|
44891
|
+
const descending = trimmed.startsWith("-");
|
|
44892
|
+
const col = column2("sort", descending ? trimmed.slice(1) : trimmed);
|
|
44893
|
+
return `${col} ${bareDir ?? (descending ? "DESC" : "ASC")}`;
|
|
44894
|
+
}
|
|
44895
|
+
function buildOrderClause(options, column2) {
|
|
44896
|
+
if (!options.sort) return "";
|
|
44897
|
+
const rawParts = options.sort.split(",");
|
|
44898
|
+
const first = rawParts[0].trim();
|
|
44899
|
+
const singleBare = rawParts.length === 1 && first !== "" && !first.startsWith("-");
|
|
44900
|
+
const bareDir = singleBare && String(options.sortDir).toLowerCase() === "desc" ? "DESC" : null;
|
|
44901
|
+
const parts = rawParts.map((part) => orderPart(part, bareDir, column2)).filter((term) => term !== null);
|
|
44902
|
+
return parts.length > 0 ? `ORDER BY ${parts.join(", ")}` : "";
|
|
44903
|
+
}
|
|
44450
44904
|
function buildQuery(tableName, options, extraConditions, quote = quoteIdentifierAnsi, resolveColumn = () => null) {
|
|
44451
44905
|
const conditions = [];
|
|
44452
44906
|
const params = [];
|
|
@@ -44474,19 +44928,9 @@ function buildQuery(tableName, options, extraConditions, quote = quoteIdentifier
|
|
|
44474
44928
|
}
|
|
44475
44929
|
}
|
|
44476
44930
|
}
|
|
44931
|
+
applySearch(options, conditions, params, quote);
|
|
44477
44932
|
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
|
-
}
|
|
44933
|
+
const orderClause = buildOrderClause(options, column2);
|
|
44490
44934
|
const limit = Math.min(options.limit ?? DEFAULT_ROW_CAP, DEFAULT_ROW_CAP);
|
|
44491
44935
|
const page = Math.max(options.page ?? 1, 1);
|
|
44492
44936
|
const offset = (page - 1) * limit;
|
|
@@ -44504,32 +44948,41 @@ function buildQuery(tableName, options, extraConditions, quote = quoteIdentifier
|
|
|
44504
44948
|
filterParams: params
|
|
44505
44949
|
};
|
|
44506
44950
|
}
|
|
44507
|
-
function
|
|
44508
|
-
const options = {};
|
|
44951
|
+
function parseFilterParams(query) {
|
|
44509
44952
|
const filter = /* @__PURE__ */ Object.create(null);
|
|
44510
44953
|
for (const [key, value] of Object.entries(query)) {
|
|
44511
44954
|
if (key.startsWith("sort[")) {
|
|
44512
44955
|
throw new InvalidQueryParameterError("Query parameter 'sort' must be a single comma-separated string");
|
|
44513
44956
|
}
|
|
44514
44957
|
if (!key.startsWith("filter[")) continue;
|
|
44515
|
-
|
|
44516
|
-
|
|
44517
|
-
|
|
44518
|
-
|
|
44519
|
-
|
|
44520
|
-
|
|
44521
|
-
|
|
44522
|
-
|
|
44523
|
-
|
|
44524
|
-
|
|
44525
|
-
|
|
44526
|
-
}
|
|
44527
|
-
filter[field][operator] = value;
|
|
44958
|
+
addFilterKey(filter, key, value);
|
|
44959
|
+
}
|
|
44960
|
+
return filter;
|
|
44961
|
+
}
|
|
44962
|
+
function addFilterKey(filter, key, value) {
|
|
44963
|
+
const filterMatch = key.match(/^filter\[([^\]]*)\](.*)$/s);
|
|
44964
|
+
if (!filterMatch) throw new UnknownFieldError("filter", key.slice("filter[".length));
|
|
44965
|
+
const [, field, rest] = filterMatch;
|
|
44966
|
+
if (rest === "") {
|
|
44967
|
+
filter[field] = value;
|
|
44968
|
+
return;
|
|
44528
44969
|
}
|
|
44970
|
+
const operator = rest.match(/^\[([^[\]]*)\]$/)?.[1];
|
|
44971
|
+
if (operator === void 0 || !Object.hasOwn(operatorMap, operator)) throw filterValueError(field);
|
|
44972
|
+
if (!filter[field] || typeof filter[field] !== "object") {
|
|
44973
|
+
filter[field] = {};
|
|
44974
|
+
}
|
|
44975
|
+
filter[field][operator] = value;
|
|
44976
|
+
}
|
|
44977
|
+
function parseQueryString(query) {
|
|
44978
|
+
const options = {};
|
|
44979
|
+
const filter = parseFilterParams(query);
|
|
44529
44980
|
if (Object.keys(filter).length > 0) {
|
|
44530
44981
|
options.filter = filter;
|
|
44531
44982
|
}
|
|
44532
44983
|
if (query.sort) options.sort = query.sort;
|
|
44984
|
+
if (query.search !== void 0) options.search = query.search;
|
|
44985
|
+
if (query.sort_dir !== void 0) options.sortDir = query.sort_dir;
|
|
44533
44986
|
if (query.page) options.page = parseInt(query.page, 10);
|
|
44534
44987
|
if (query.limit) options.limit = parseInt(query.limit, 10);
|
|
44535
44988
|
if (query.offset !== void 0) {
|
|
@@ -44665,12 +45118,12 @@ var init_validation = __esm({
|
|
|
44665
45118
|
});
|
|
44666
45119
|
|
|
44667
45120
|
// src/autoCrud.ts
|
|
44668
|
-
function allowListedBody(fields,
|
|
45121
|
+
function allowListedBody(fields, columnOf2, pkField, body, isCreate) {
|
|
44669
45122
|
const pkDef = fields[pkField];
|
|
44670
45123
|
const stripPk = isCreate ? pkDef?.autoIncrement === true : true;
|
|
44671
45124
|
const allowed = {};
|
|
44672
45125
|
for (const [key, value] of Object.entries(body ?? {})) {
|
|
44673
|
-
const field = resolveField(fields,
|
|
45126
|
+
const field = resolveField(fields, columnOf2, key);
|
|
44674
45127
|
if (field === null) continue;
|
|
44675
45128
|
if (field === "is_deleted") continue;
|
|
44676
45129
|
if (stripPk && field === pkField) continue;
|
|
@@ -44714,6 +45167,9 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
44714
45167
|
let built;
|
|
44715
45168
|
try {
|
|
44716
45169
|
const qp = parseQueryString(req2.query ?? {});
|
|
45170
|
+
if ((qp.search ?? "").trim() !== "") {
|
|
45171
|
+
qp.searchColumns = Object.entries(fields).filter(([, def]) => def.type === "string" || def.type === "text").map(([name]) => getDbCol(name));
|
|
45172
|
+
}
|
|
44717
45173
|
built = buildQuery(tableName, qp, extraConditions, q, (key) => resolveFieldColumn(fields, getDbCol, key));
|
|
44718
45174
|
} catch (err) {
|
|
44719
45175
|
if (err instanceof UnknownFieldError) {
|
|
@@ -44961,6 +45417,567 @@ var init_autoCrud = __esm({
|
|
|
44961
45417
|
}
|
|
44962
45418
|
});
|
|
44963
45419
|
|
|
45420
|
+
// src/crudHelpers.ts
|
|
45421
|
+
function columnOf(model, prop) {
|
|
45422
|
+
return model.fieldMapping?.[prop] ?? prop;
|
|
45423
|
+
}
|
|
45424
|
+
function primaryKeyProp(model) {
|
|
45425
|
+
for (const [name, def] of Object.entries(model.fields)) {
|
|
45426
|
+
if (def.primaryKey) return name;
|
|
45427
|
+
}
|
|
45428
|
+
return "id";
|
|
45429
|
+
}
|
|
45430
|
+
function searchableProps(model) {
|
|
45431
|
+
return Object.entries(model.fields).filter(([, def]) => def.type === "string" || def.type === "text").map(([name]) => name);
|
|
45432
|
+
}
|
|
45433
|
+
function columnAlignment(model, col) {
|
|
45434
|
+
const type2 = model?.fields?.[col]?.type;
|
|
45435
|
+
return type2 === "integer" || type2 === "number" || type2 === "float" || type2 === "decimal" ? "text-end" : "text-start";
|
|
45436
|
+
}
|
|
45437
|
+
function prettyLabel(col) {
|
|
45438
|
+
return String(col).split("_").map((p) => p ? p.charAt(0).toUpperCase() + p.slice(1) : p).join(" ");
|
|
45439
|
+
}
|
|
45440
|
+
function crudSortProp(model, requested, pkProp) {
|
|
45441
|
+
const req2 = (requested ?? "").trim();
|
|
45442
|
+
if (req2 === "") return pkProp;
|
|
45443
|
+
if (Object.hasOwn(model.fields, req2)) return req2;
|
|
45444
|
+
for (const name of Object.keys(model.fields)) {
|
|
45445
|
+
if (columnOf(model, name) === req2) return name;
|
|
45446
|
+
}
|
|
45447
|
+
return pkProp;
|
|
45448
|
+
}
|
|
45449
|
+
function cellValue(record, col) {
|
|
45450
|
+
return record[col];
|
|
45451
|
+
}
|
|
45452
|
+
function buildCells(columns, record, editable, aligns) {
|
|
45453
|
+
return columns.map((col, index) => {
|
|
45454
|
+
const raw = cellValue(record, col);
|
|
45455
|
+
const value = h(raw === null || raw === void 0 ? "" : raw);
|
|
45456
|
+
const css = aligns[index];
|
|
45457
|
+
return editable ? `<td class="${css}" contenteditable="true" data-field="${h(col)}">${value}</td>` : `<td class="${css}">${value}</td>`;
|
|
45458
|
+
}).join("");
|
|
45459
|
+
}
|
|
45460
|
+
function stripOrderAndLimit(sql) {
|
|
45461
|
+
const cut = (line, keyword) => {
|
|
45462
|
+
const hasNewline = line.endsWith("\n");
|
|
45463
|
+
const content = hasNewline ? line.slice(0, -1) : line;
|
|
45464
|
+
const at = content.toUpperCase().indexOf(keyword);
|
|
45465
|
+
if (at < 0) return line;
|
|
45466
|
+
const after = at + keyword.length;
|
|
45467
|
+
if (content.length <= after) return line;
|
|
45468
|
+
return content.slice(0, at) + (hasNewline ? "\n" : "");
|
|
45469
|
+
};
|
|
45470
|
+
const src = String(sql);
|
|
45471
|
+
const lines = [];
|
|
45472
|
+
let start2 = 0;
|
|
45473
|
+
for (let i = 0; i < src.length; i++) {
|
|
45474
|
+
if (src[i] === "\n") {
|
|
45475
|
+
lines.push(src.slice(start2, i + 1));
|
|
45476
|
+
start2 = i + 1;
|
|
45477
|
+
}
|
|
45478
|
+
}
|
|
45479
|
+
lines.push(src.slice(start2));
|
|
45480
|
+
return lines.map((line) => cut(cut(line, "ORDER BY "), "LIMIT ")).join("").trim();
|
|
45481
|
+
}
|
|
45482
|
+
function extractColumns(sql) {
|
|
45483
|
+
const colsStr = selectProjection(sql);
|
|
45484
|
+
if (colsStr === null || colsStr === "*") return ["*"];
|
|
45485
|
+
return colsStr.split(",").map((c) => columnAlias(c.trim()));
|
|
45486
|
+
}
|
|
45487
|
+
function selectProjection(sql) {
|
|
45488
|
+
const upper = sql.toUpperCase();
|
|
45489
|
+
const selectAt = keywordIndex(upper, "SELECT", 0);
|
|
45490
|
+
if (selectAt < 0) return null;
|
|
45491
|
+
const colsStart = selectAt + "SELECT".length;
|
|
45492
|
+
const fromAt = keywordIndex(upper, "FROM", colsStart);
|
|
45493
|
+
if (fromAt < 0) return null;
|
|
45494
|
+
return sql.slice(colsStart, fromAt).trim();
|
|
45495
|
+
}
|
|
45496
|
+
function columnAlias(trimmed) {
|
|
45497
|
+
const asAt = keywordIndex(trimmed.toUpperCase(), "AS", 0);
|
|
45498
|
+
if (asAt >= 0) {
|
|
45499
|
+
const after = trimmed.slice(asAt + "AS".length).trim();
|
|
45500
|
+
let endIdx = 0;
|
|
45501
|
+
while (endIdx < after.length && !SQL_WS.includes(after[endIdx]) && after[endIdx] !== ",") endIdx++;
|
|
45502
|
+
const word = after.slice(0, endIdx);
|
|
45503
|
+
if (word) return word;
|
|
45504
|
+
}
|
|
45505
|
+
if (trimmed.includes(".")) return trimmed.split(".").pop().trim();
|
|
45506
|
+
return trimmed;
|
|
45507
|
+
}
|
|
45508
|
+
function keywordIndex(upperHaystack, keyword, from) {
|
|
45509
|
+
let at = upperHaystack.indexOf(keyword, from);
|
|
45510
|
+
while (at >= 0) {
|
|
45511
|
+
const afterIdx = at + keyword.length;
|
|
45512
|
+
const boundaryBefore = at === 0 || SQL_WS.includes(upperHaystack[at - 1]);
|
|
45513
|
+
const boundaryAfter = afterIdx >= upperHaystack.length || SQL_WS.includes(upperHaystack[afterIdx]);
|
|
45514
|
+
if (boundaryBefore && boundaryAfter) return at;
|
|
45515
|
+
at = upperHaystack.indexOf(keyword, at + 1);
|
|
45516
|
+
}
|
|
45517
|
+
return -1;
|
|
45518
|
+
}
|
|
45519
|
+
var h, SQL_WS;
|
|
45520
|
+
var init_crudHelpers = __esm({
|
|
45521
|
+
"src/crudHelpers.ts"() {
|
|
45522
|
+
"use strict";
|
|
45523
|
+
init_engine();
|
|
45524
|
+
h = escapeHtml;
|
|
45525
|
+
SQL_WS = " \n\r\f\v";
|
|
45526
|
+
}
|
|
45527
|
+
});
|
|
45528
|
+
|
|
45529
|
+
// src/crudTable.ts
|
|
45530
|
+
function sortIndicator(sortCol, col, sortDir) {
|
|
45531
|
+
if (String(sortCol) !== String(col)) return "";
|
|
45532
|
+
const arrow = sortDir === "asc" ? "▲" : "▼";
|
|
45533
|
+
return ` <span class="sort-indicator">${arrow}</span>`;
|
|
45534
|
+
}
|
|
45535
|
+
function sortUrl(requestPath, col, nextDir, page, search, limit) {
|
|
45536
|
+
const query = `sort=${encodeURIComponent(col)}&sort_dir=${nextDir}&page=${page}&search=${encodeURIComponent(search)}&limit=${limit}`;
|
|
45537
|
+
return h(`${requestPath}?${query}`);
|
|
45538
|
+
}
|
|
45539
|
+
function pageUrl(requestPath, p, search, sortCol, sortDir, limit) {
|
|
45540
|
+
const query = `page=${p}&search=${encodeURIComponent(search)}&sort=${encodeURIComponent(sortCol)}&sort_dir=${sortDir}&limit=${limit}`;
|
|
45541
|
+
return h(`${requestPath}?${query}`);
|
|
45542
|
+
}
|
|
45543
|
+
function pageControls(page, totalPages, requestPath, search, sortCol, sortDir, limit) {
|
|
45544
|
+
if (totalPages <= 1) return [];
|
|
45545
|
+
const controls = [];
|
|
45546
|
+
if (page > 1) {
|
|
45547
|
+
controls.push({
|
|
45548
|
+
label: "Prev",
|
|
45549
|
+
page: page - 1,
|
|
45550
|
+
active: false,
|
|
45551
|
+
inactive: true,
|
|
45552
|
+
url: pageUrl(requestPath, page - 1, search, sortCol, sortDir, limit)
|
|
45553
|
+
});
|
|
45554
|
+
}
|
|
45555
|
+
let startPage = Math.max(page - 3, 1);
|
|
45556
|
+
const endPage = Math.min(startPage + 6, totalPages);
|
|
45557
|
+
startPage = Math.max(endPage - 6, 1);
|
|
45558
|
+
for (let p = startPage; p <= endPage; p++) {
|
|
45559
|
+
controls.push({
|
|
45560
|
+
label: p,
|
|
45561
|
+
page: p,
|
|
45562
|
+
active: p === page,
|
|
45563
|
+
inactive: p !== page,
|
|
45564
|
+
url: pageUrl(requestPath, p, search, sortCol, sortDir, limit)
|
|
45565
|
+
});
|
|
45566
|
+
}
|
|
45567
|
+
if (page < totalPages) {
|
|
45568
|
+
controls.push({
|
|
45569
|
+
label: "Next",
|
|
45570
|
+
page: page + 1,
|
|
45571
|
+
active: false,
|
|
45572
|
+
inactive: true,
|
|
45573
|
+
url: pageUrl(requestPath, page + 1, search, sortCol, sortDir, limit)
|
|
45574
|
+
});
|
|
45575
|
+
}
|
|
45576
|
+
return controls;
|
|
45577
|
+
}
|
|
45578
|
+
function jsConfig(opts) {
|
|
45579
|
+
const aligns = {};
|
|
45580
|
+
const labels = {};
|
|
45581
|
+
for (const col of opts.columns) {
|
|
45582
|
+
aligns[col] = columnAlignment(opts.model, col);
|
|
45583
|
+
labels[col] = prettyLabel(col);
|
|
45584
|
+
}
|
|
45585
|
+
const config = {
|
|
45586
|
+
api: opts.apiPath,
|
|
45587
|
+
pk: opts.pk,
|
|
45588
|
+
columns: opts.columns,
|
|
45589
|
+
editable: opts.editable,
|
|
45590
|
+
aligns,
|
|
45591
|
+
labels,
|
|
45592
|
+
limit: opts.limit,
|
|
45593
|
+
search: opts.search,
|
|
45594
|
+
sort: opts.sortCol,
|
|
45595
|
+
sort_dir: opts.sortDir,
|
|
45596
|
+
page: opts.page
|
|
45597
|
+
};
|
|
45598
|
+
return JSON.stringify(config).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
45599
|
+
}
|
|
45600
|
+
function headerCell(opts, col, align) {
|
|
45601
|
+
const header = { label: h(prettyLabel(col)), align };
|
|
45602
|
+
if (!opts.sortable) {
|
|
45603
|
+
header.plain = true;
|
|
45604
|
+
return header;
|
|
45605
|
+
}
|
|
45606
|
+
const nextDir = String(opts.sortCol) === String(col) && opts.sortDir === "asc" ? "desc" : "asc";
|
|
45607
|
+
header.sortable = true;
|
|
45608
|
+
header.col = h(col);
|
|
45609
|
+
header.next_dir = nextDir;
|
|
45610
|
+
header.url = sortUrl(opts.requestPath, col, nextDir, opts.page, opts.search, opts.limit);
|
|
45611
|
+
header.indicator = sortIndicator(opts.sortCol, col, opts.sortDir);
|
|
45612
|
+
return header;
|
|
45613
|
+
}
|
|
45614
|
+
function tableData(opts) {
|
|
45615
|
+
const aligns = opts.columns.map((col) => columnAlignment(opts.model, col));
|
|
45616
|
+
const headers = opts.columns.map((col, index) => headerCell(opts, col, aligns[index]));
|
|
45617
|
+
const rows = opts.records.map((record) => ({
|
|
45618
|
+
id: h(cellValue(record, opts.pk) ?? ""),
|
|
45619
|
+
cells: buildCells(opts.columns, record, opts.editable, aligns)
|
|
45620
|
+
}));
|
|
45621
|
+
return {
|
|
45622
|
+
headers,
|
|
45623
|
+
rows,
|
|
45624
|
+
empty: opts.records.length === 0,
|
|
45625
|
+
colspan: opts.columns.length + 1,
|
|
45626
|
+
editable: opts.editable,
|
|
45627
|
+
readonly: !opts.editable,
|
|
45628
|
+
inline_script: opts.inlineScript,
|
|
45629
|
+
table_name: h(opts.tableName),
|
|
45630
|
+
table_id_attr: opts.tableId ? ` id="${h(opts.tableId)}"` : ""
|
|
45631
|
+
};
|
|
45632
|
+
}
|
|
45633
|
+
var init_crudTable = __esm({
|
|
45634
|
+
"src/crudTable.ts"() {
|
|
45635
|
+
"use strict";
|
|
45636
|
+
init_crudHelpers();
|
|
45637
|
+
}
|
|
45638
|
+
});
|
|
45639
|
+
|
|
45640
|
+
// src/crudRender.ts
|
|
45641
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
45642
|
+
import { resolve as pathResolve, join as pathJoin, dirname as dirname15 } from "node:path";
|
|
45643
|
+
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
45644
|
+
function frondFor(dir) {
|
|
45645
|
+
let engine = frondCache2.get(dir);
|
|
45646
|
+
if (!engine) {
|
|
45647
|
+
setCspNonceProvider(currentCspNonce);
|
|
45648
|
+
engine = new Frond(dir);
|
|
45649
|
+
frondCache2.set(dir, engine);
|
|
45650
|
+
}
|
|
45651
|
+
return engine;
|
|
45652
|
+
}
|
|
45653
|
+
function renderCrudTemplate(name, data) {
|
|
45654
|
+
const templateFile = `crud/${name}.twig`;
|
|
45655
|
+
const userDir = pathResolve(process.cwd(), "src", "templates");
|
|
45656
|
+
if (existsSync28(pathJoin(userDir, templateFile))) {
|
|
45657
|
+
return frondFor(userDir).render(templateFile, data);
|
|
45658
|
+
}
|
|
45659
|
+
return frondFor(FRAMEWORK_TEMPLATES_DIR).render(templateFile, data);
|
|
45660
|
+
}
|
|
45661
|
+
function renderModalForm(mode, columns, pk, edit) {
|
|
45662
|
+
const fields = columns.map((col) => {
|
|
45663
|
+
const label = prettyLabel(col);
|
|
45664
|
+
return {
|
|
45665
|
+
id: `${mode}-${col}`,
|
|
45666
|
+
name: h(col),
|
|
45667
|
+
label: h(label),
|
|
45668
|
+
value: "",
|
|
45669
|
+
placeholder: h(`Enter ${label.toLowerCase()}`),
|
|
45670
|
+
type: "text",
|
|
45671
|
+
required_attr: "",
|
|
45672
|
+
input: true
|
|
45673
|
+
};
|
|
45674
|
+
});
|
|
45675
|
+
return renderCrudTemplate("form", {
|
|
45676
|
+
wrap: true,
|
|
45677
|
+
form_id_attr: ` id="form-${mode}"`,
|
|
45678
|
+
action: "",
|
|
45679
|
+
form_method: "POST",
|
|
45680
|
+
method_override: null,
|
|
45681
|
+
edit,
|
|
45682
|
+
mode,
|
|
45683
|
+
pk: h(pk),
|
|
45684
|
+
modal_footer: true,
|
|
45685
|
+
submit_button: false,
|
|
45686
|
+
fields
|
|
45687
|
+
});
|
|
45688
|
+
}
|
|
45689
|
+
function renderModals(editableColumns, pk) {
|
|
45690
|
+
return renderCrudTemplate("modals", {
|
|
45691
|
+
create_form: renderModalForm("create", editableColumns, pk, false),
|
|
45692
|
+
edit_form: renderModalForm("edit", editableColumns, pk, true)
|
|
45693
|
+
});
|
|
45694
|
+
}
|
|
45695
|
+
function generateTable(records, options = {}) {
|
|
45696
|
+
const rows = records ?? [];
|
|
45697
|
+
const tableName = String(options.tableName ?? "data");
|
|
45698
|
+
const primaryKey = String(options.primaryKey ?? "id");
|
|
45699
|
+
const editable = options.editable ?? true;
|
|
45700
|
+
const columns = rows.length === 0 ? [] : Object.keys(rows[0]);
|
|
45701
|
+
return renderCrudTemplate(
|
|
45702
|
+
"table",
|
|
45703
|
+
tableData({
|
|
45704
|
+
columns,
|
|
45705
|
+
records: rows,
|
|
45706
|
+
pk: primaryKey,
|
|
45707
|
+
tableName,
|
|
45708
|
+
editable,
|
|
45709
|
+
sortable: false,
|
|
45710
|
+
inlineScript: editable,
|
|
45711
|
+
requestPath: "",
|
|
45712
|
+
search: "",
|
|
45713
|
+
sortCol: "",
|
|
45714
|
+
sortDir: "asc",
|
|
45715
|
+
page: 1,
|
|
45716
|
+
limit: 10,
|
|
45717
|
+
tableId: `crud-${tableName}`,
|
|
45718
|
+
model: null
|
|
45719
|
+
})
|
|
45720
|
+
);
|
|
45721
|
+
}
|
|
45722
|
+
function buildOptions(options, selectedValue) {
|
|
45723
|
+
return (options ?? []).map((opt) => {
|
|
45724
|
+
const selected = String(opt.value) === String(selectedValue) ? " selected" : "";
|
|
45725
|
+
return `<option value="${h(opt.value)}"${selected}>${h(opt.label)}</option>`;
|
|
45726
|
+
}).join("");
|
|
45727
|
+
}
|
|
45728
|
+
function fieldInputShape(field, base) {
|
|
45729
|
+
const value = field.value;
|
|
45730
|
+
switch (field.type ?? "string") {
|
|
45731
|
+
case "text":
|
|
45732
|
+
return { ...base, textarea: true };
|
|
45733
|
+
case "boolean":
|
|
45734
|
+
return { ...base, checkbox: true, checked_attr: value ? " checked" : "" };
|
|
45735
|
+
case "select":
|
|
45736
|
+
return { ...base, select: true, options_html: buildOptions(field.options, value) };
|
|
45737
|
+
case "date":
|
|
45738
|
+
return { ...base, input: true, type: "date" };
|
|
45739
|
+
case "integer":
|
|
45740
|
+
case "number":
|
|
45741
|
+
case "float":
|
|
45742
|
+
case "decimal":
|
|
45743
|
+
return { ...base, input: true, type: "number" };
|
|
45744
|
+
default:
|
|
45745
|
+
return { ...base, input: true, type: "text" };
|
|
45746
|
+
}
|
|
45747
|
+
}
|
|
45748
|
+
function buildCustomField(field) {
|
|
45749
|
+
const name = String(field.name ?? "");
|
|
45750
|
+
const label = field.label ?? name.charAt(0).toUpperCase() + name.slice(1);
|
|
45751
|
+
const base = {
|
|
45752
|
+
id: h(name),
|
|
45753
|
+
name: h(name),
|
|
45754
|
+
label: h(String(label)),
|
|
45755
|
+
value: h(field.value ?? ""),
|
|
45756
|
+
placeholder: "",
|
|
45757
|
+
required_attr: field.required ? " required" : ""
|
|
45758
|
+
};
|
|
45759
|
+
return fieldInputShape(field, base);
|
|
45760
|
+
}
|
|
45761
|
+
function generateForm2(fields, options = {}) {
|
|
45762
|
+
const verb = String(options.method ?? "POST").toUpperCase();
|
|
45763
|
+
return renderCrudTemplate("form", {
|
|
45764
|
+
wrap: true,
|
|
45765
|
+
form_id_attr: "",
|
|
45766
|
+
action: h(options.action ?? "/"),
|
|
45767
|
+
form_method: h(verb),
|
|
45768
|
+
method_override: ["PUT", "PATCH", "DELETE"].includes(verb) ? verb : null,
|
|
45769
|
+
edit: false,
|
|
45770
|
+
modal_footer: false,
|
|
45771
|
+
submit_button: true,
|
|
45772
|
+
fields: (fields ?? []).map((f) => buildCustomField(f))
|
|
45773
|
+
});
|
|
45774
|
+
}
|
|
45775
|
+
var FRAMEWORK_TEMPLATES_DIR, frondCache2;
|
|
45776
|
+
var init_crudRender = __esm({
|
|
45777
|
+
"src/crudRender.ts"() {
|
|
45778
|
+
"use strict";
|
|
45779
|
+
init_engine();
|
|
45780
|
+
init_csp();
|
|
45781
|
+
init_crudHelpers();
|
|
45782
|
+
init_crudTable();
|
|
45783
|
+
FRAMEWORK_TEMPLATES_DIR = pathResolve(
|
|
45784
|
+
dirname15(fileURLToPath9(import.meta.url)),
|
|
45785
|
+
"..",
|
|
45786
|
+
"..",
|
|
45787
|
+
"core",
|
|
45788
|
+
"templates"
|
|
45789
|
+
);
|
|
45790
|
+
frondCache2 = /* @__PURE__ */ new Map();
|
|
45791
|
+
}
|
|
45792
|
+
});
|
|
45793
|
+
|
|
45794
|
+
// src/crud.ts
|
|
45795
|
+
function _resetCrudRegistrations() {
|
|
45796
|
+
registeredTables.clear();
|
|
45797
|
+
}
|
|
45798
|
+
function discoveredFrom(model) {
|
|
45799
|
+
return {
|
|
45800
|
+
filePath: `src/models/${model.name ?? model.tableName}.ts`,
|
|
45801
|
+
modelClass: model,
|
|
45802
|
+
definition: {
|
|
45803
|
+
tableName: model.tableName,
|
|
45804
|
+
className: model.name,
|
|
45805
|
+
fields: model.fields,
|
|
45806
|
+
fieldMapping: model.fieldMapping,
|
|
45807
|
+
softDelete: model.softDelete ?? false,
|
|
45808
|
+
tableFilter: model.tableFilter,
|
|
45809
|
+
dbName: model._db
|
|
45810
|
+
}
|
|
45811
|
+
};
|
|
45812
|
+
}
|
|
45813
|
+
async function registerBackend(model, options = {}) {
|
|
45814
|
+
const prefix = options.prefix ?? "/api";
|
|
45815
|
+
const key = `${prefix}::${model.tableName}`;
|
|
45816
|
+
if (registeredTables.has(key)) return;
|
|
45817
|
+
const routes = generateCrudRoutes([discoveredFrom(model)], { public: options.public === true });
|
|
45818
|
+
for (const route of routes) defaultRouter.addRoute(route);
|
|
45819
|
+
registeredTables.add(key);
|
|
45820
|
+
}
|
|
45821
|
+
async function fetchModelData(model, opts) {
|
|
45822
|
+
const orderBy = `${opts.sort} ${opts.sortDir.toUpperCase()}`;
|
|
45823
|
+
const search = opts.search.trim();
|
|
45824
|
+
const searchable = search === "" ? [] : searchableProps(model);
|
|
45825
|
+
let rows;
|
|
45826
|
+
if (searchable.length === 0) {
|
|
45827
|
+
rows = await model.all(opts.limit, opts.offset, void 0, orderBy);
|
|
45828
|
+
} else {
|
|
45829
|
+
const whereClause = searchable.map((prop) => `${columnOf(model, prop)} LIKE ?`).join(" OR ");
|
|
45830
|
+
const params = searchable.map(() => `%${search}%`);
|
|
45831
|
+
rows = await model.where(whereClause, params, opts.limit, opts.offset, void 0, orderBy);
|
|
45832
|
+
}
|
|
45833
|
+
const total = typeof rows.getTotalRecords === "function" ? rows.getTotalRecords() : rows.length;
|
|
45834
|
+
const records = rows.map((r) => typeof r.toDict === "function" ? r.toDict() : r);
|
|
45835
|
+
return [records, total];
|
|
45836
|
+
}
|
|
45837
|
+
async function fetchSqlData(sql, opts) {
|
|
45838
|
+
let adapter;
|
|
45839
|
+
try {
|
|
45840
|
+
adapter = getAdapter();
|
|
45841
|
+
} catch {
|
|
45842
|
+
return [[], 0];
|
|
45843
|
+
}
|
|
45844
|
+
const base = stripOrderAndLimit(sql);
|
|
45845
|
+
const dir = opts.sortDir.toUpperCase();
|
|
45846
|
+
const search = opts.search.trim();
|
|
45847
|
+
if (search === "") {
|
|
45848
|
+
const countRow2 = await adapterQuery(adapter, `SELECT COUNT(*) as cnt FROM (${base}) AS _crud_cnt`, []);
|
|
45849
|
+
const total2 = Number(countRow2[0]?.cnt ?? 0);
|
|
45850
|
+
const rows2 = await adapterFetch(adapter, `${base} ORDER BY ${opts.sort} ${dir}`, [], opts.limit, opts.offset);
|
|
45851
|
+
return [rows2, total2];
|
|
45852
|
+
}
|
|
45853
|
+
const columns = extractColumns(sql);
|
|
45854
|
+
const searchParts = columns.map((col) => `CAST(${col} AS TEXT) LIKE ?`);
|
|
45855
|
+
const params = columns.map(() => `%${search}%`);
|
|
45856
|
+
const countRow = await adapterQuery(
|
|
45857
|
+
adapter,
|
|
45858
|
+
`SELECT COUNT(*) as cnt FROM (${base}) AS _crud_cnt WHERE ${searchParts.join(" OR ")}`,
|
|
45859
|
+
params
|
|
45860
|
+
);
|
|
45861
|
+
const total = Number(countRow[0]?.cnt ?? 0);
|
|
45862
|
+
const rows = await adapterFetch(
|
|
45863
|
+
adapter,
|
|
45864
|
+
`SELECT * FROM (${base}) AS _crud_sub WHERE ${searchParts.join(" OR ")} ORDER BY ${opts.sort} ${dir}`,
|
|
45865
|
+
params,
|
|
45866
|
+
opts.limit,
|
|
45867
|
+
opts.offset
|
|
45868
|
+
);
|
|
45869
|
+
return [rows, total];
|
|
45870
|
+
}
|
|
45871
|
+
function parseCrudRequest(request, model, pkProp, limit) {
|
|
45872
|
+
const query = request && request.query || {};
|
|
45873
|
+
const page = Math.max(parseInt(String(query.page ?? "1"), 10) || 1, 1);
|
|
45874
|
+
const search = String(query.search ?? "").trim();
|
|
45875
|
+
const sortProp = crudSortProp(model, query.sort, pkProp);
|
|
45876
|
+
const sortColumn = columnOf(model, sortProp);
|
|
45877
|
+
const sortDir = query.sort_dir === "desc" ? "desc" : "asc";
|
|
45878
|
+
const requestPath = request && typeof request.path === "string" ? request.path : "/";
|
|
45879
|
+
return { page, search, sortProp, sortColumn, sortDir, offset: (page - 1) * limit, requestPath };
|
|
45880
|
+
}
|
|
45881
|
+
function clampLimit(raw) {
|
|
45882
|
+
const limit = Number(raw ?? 10);
|
|
45883
|
+
return !Number.isFinite(limit) || limit <= 0 ? 10 : limit;
|
|
45884
|
+
}
|
|
45885
|
+
async function toCrud(request, options) {
|
|
45886
|
+
const model = options.model;
|
|
45887
|
+
if (!model || !model.tableName || !model.fields) {
|
|
45888
|
+
throw new Error("Crud.toCrud requires a model (an ORM class)");
|
|
45889
|
+
}
|
|
45890
|
+
const sql = options.sql;
|
|
45891
|
+
const title = String(options.title ?? "CRUD");
|
|
45892
|
+
const prefix = String(options.prefix ?? "/api");
|
|
45893
|
+
const limit = clampLimit(options.limit);
|
|
45894
|
+
const tableName = String(model.tableName);
|
|
45895
|
+
const pkProp = primaryKeyProp(model);
|
|
45896
|
+
const columns = Object.keys(model.fields);
|
|
45897
|
+
await registerBackend(model, { prefix, public: options.public });
|
|
45898
|
+
const req2 = parseCrudRequest(request, model, pkProp, limit);
|
|
45899
|
+
const fetchOpts = {
|
|
45900
|
+
search: req2.search,
|
|
45901
|
+
sort: req2.sortColumn,
|
|
45902
|
+
sortDir: req2.sortDir,
|
|
45903
|
+
limit,
|
|
45904
|
+
offset: req2.offset
|
|
45905
|
+
};
|
|
45906
|
+
const [records, total] = sql ? await fetchSqlData(sql, fetchOpts) : await fetchModelData(model, fetchOpts);
|
|
45907
|
+
const totalPages = total > 0 ? Math.ceil(total / limit) : 1;
|
|
45908
|
+
const apiPath = `${prefix}/${tableName}`;
|
|
45909
|
+
const editableColumns = columns.filter((c) => c !== pkProp);
|
|
45910
|
+
const tableHtml = renderCrudTemplate(
|
|
45911
|
+
"table",
|
|
45912
|
+
tableData({
|
|
45913
|
+
columns,
|
|
45914
|
+
records,
|
|
45915
|
+
pk: pkProp,
|
|
45916
|
+
tableName,
|
|
45917
|
+
editable: false,
|
|
45918
|
+
sortable: true,
|
|
45919
|
+
inlineScript: false,
|
|
45920
|
+
requestPath: req2.requestPath,
|
|
45921
|
+
search: req2.search,
|
|
45922
|
+
sortCol: req2.sortProp,
|
|
45923
|
+
sortDir: req2.sortDir,
|
|
45924
|
+
page: req2.page,
|
|
45925
|
+
limit,
|
|
45926
|
+
tableId: null,
|
|
45927
|
+
model
|
|
45928
|
+
})
|
|
45929
|
+
);
|
|
45930
|
+
return renderCrudTemplate("page", {
|
|
45931
|
+
title: h(title),
|
|
45932
|
+
search: h(req2.search),
|
|
45933
|
+
request_path: h(req2.requestPath),
|
|
45934
|
+
info_count: records.length,
|
|
45935
|
+
info_total: total,
|
|
45936
|
+
info_page: req2.page,
|
|
45937
|
+
info_total_pages: totalPages,
|
|
45938
|
+
table_html: tableHtml,
|
|
45939
|
+
modals_html: renderModals(editableColumns, pkProp),
|
|
45940
|
+
show_pagination: totalPages > 1,
|
|
45941
|
+
controls: pageControls(req2.page, totalPages, req2.requestPath, req2.search, req2.sortProp, req2.sortDir, limit),
|
|
45942
|
+
config_json: jsConfig({
|
|
45943
|
+
apiPath,
|
|
45944
|
+
pk: pkProp,
|
|
45945
|
+
columns,
|
|
45946
|
+
editable: editableColumns,
|
|
45947
|
+
model,
|
|
45948
|
+
limit,
|
|
45949
|
+
search: req2.search,
|
|
45950
|
+
sortCol: req2.sortProp,
|
|
45951
|
+
sortDir: req2.sortDir,
|
|
45952
|
+
page: req2.page
|
|
45953
|
+
})
|
|
45954
|
+
});
|
|
45955
|
+
}
|
|
45956
|
+
var registeredTables, Crud, CRUD;
|
|
45957
|
+
var init_crud = __esm({
|
|
45958
|
+
"src/crud.ts"() {
|
|
45959
|
+
"use strict";
|
|
45960
|
+
init_router();
|
|
45961
|
+
init_autoCrud();
|
|
45962
|
+
init_database();
|
|
45963
|
+
init_crudHelpers();
|
|
45964
|
+
init_crudTable();
|
|
45965
|
+
init_crudRender();
|
|
45966
|
+
init_crudRender();
|
|
45967
|
+
init_crudHelpers();
|
|
45968
|
+
registeredTables = /* @__PURE__ */ new Set();
|
|
45969
|
+
Crud = {
|
|
45970
|
+
toCrud,
|
|
45971
|
+
registerBackend,
|
|
45972
|
+
generateTable,
|
|
45973
|
+
generateForm: generateForm2,
|
|
45974
|
+
stripOrderAndLimit,
|
|
45975
|
+
_resetCrudRegistrations
|
|
45976
|
+
};
|
|
45977
|
+
CRUD = Crud;
|
|
45978
|
+
}
|
|
45979
|
+
});
|
|
45980
|
+
|
|
44964
45981
|
// src/queryBuilder.ts
|
|
44965
45982
|
var QueryBuilder;
|
|
44966
45983
|
var init_queryBuilder = __esm({
|
|
@@ -47313,7 +48330,7 @@ var init_seeder = __esm({
|
|
|
47313
48330
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
47314
48331
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
47315
48332
|
import { mkdirSync as mkdirSync20 } from "node:fs";
|
|
47316
|
-
import { dirname as
|
|
48333
|
+
import { dirname as dirname16, isAbsolute as isAbsolute6, join as join32 } from "node:path";
|
|
47317
48334
|
function iso(d) {
|
|
47318
48335
|
return d.toISOString();
|
|
47319
48336
|
}
|
|
@@ -47584,7 +48601,7 @@ function resolveStorePath(dbPath) {
|
|
|
47584
48601
|
let path10 = dbPath;
|
|
47585
48602
|
if (!isAbsolute6(path10)) {
|
|
47586
48603
|
path10 = join32(process.cwd(), path10);
|
|
47587
|
-
mkdirSync20(
|
|
48604
|
+
mkdirSync20(dirname16(path10), { recursive: true });
|
|
47588
48605
|
}
|
|
47589
48606
|
return path10;
|
|
47590
48607
|
}
|
|
@@ -49334,7 +50351,9 @@ __export(index_exports, {
|
|
|
49334
50351
|
AutoCrud: () => AutoCrud,
|
|
49335
50352
|
BaseModel: () => BaseModel,
|
|
49336
50353
|
CONNECT_TIMEOUT_TOLERANCE_MS: () => CONNECT_TIMEOUT_TOLERANCE_MS,
|
|
50354
|
+
CRUD: () => CRUD,
|
|
49337
50355
|
CachedDatabaseAdapter: () => CachedDatabaseAdapter,
|
|
50356
|
+
Crud: () => Crud,
|
|
49338
50357
|
Cursor: () => Cursor,
|
|
49339
50358
|
DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
|
|
49340
50359
|
DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
|
|
@@ -49381,6 +50400,7 @@ __export(index_exports, {
|
|
|
49381
50400
|
SqliteCollection: () => SqliteCollection,
|
|
49382
50401
|
SqliteDatabase: () => SqliteDatabase,
|
|
49383
50402
|
UnknownFieldError: () => UnknownFieldError,
|
|
50403
|
+
_resetCrudRegistrations: () => _resetCrudRegistrations,
|
|
49384
50404
|
adapterColumns: () => adapterColumns,
|
|
49385
50405
|
adapterCommit: () => adapterCommit,
|
|
49386
50406
|
adapterCreateTable: () => adapterCreateTable,
|
|
@@ -49413,6 +50433,8 @@ __export(index_exports, {
|
|
|
49413
50433
|
ensureMigrationTable: () => ensureMigrationTable,
|
|
49414
50434
|
extractLastInsertId: () => extractLastInsertId,
|
|
49415
50435
|
generateCrudRoutes: () => generateCrudRoutes,
|
|
50436
|
+
generateForm: () => generateForm2,
|
|
50437
|
+
generateTable: () => generateTable,
|
|
49416
50438
|
getAdapter: () => getAdapter,
|
|
49417
50439
|
getAppliedMigrations: () => getAppliedMigrations,
|
|
49418
50440
|
getCollection: () => getCollection,
|
|
@@ -49432,10 +50454,13 @@ __export(index_exports, {
|
|
|
49432
50454
|
realtime: () => realtime,
|
|
49433
50455
|
recordMigration: () => recordMigration,
|
|
49434
50456
|
redactCredentials: () => redactCredentials,
|
|
50457
|
+
registerBackend: () => registerBackend,
|
|
49435
50458
|
removeMigrationRecord: () => removeMigrationRecord,
|
|
49436
50459
|
resetDefaultStore: () => resetDefaultStore,
|
|
49437
50460
|
resetRequestCaches: () => resetRequestCaches2,
|
|
49438
50461
|
resolveDbPool: () => resolveDbPool,
|
|
50462
|
+
resolveField: () => resolveField,
|
|
50463
|
+
resolveFieldColumn: () => resolveFieldColumn,
|
|
49439
50464
|
resolveFirebirdCharset: () => resolveFirebirdCharset,
|
|
49440
50465
|
resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
|
|
49441
50466
|
resolveSqlitePath: () => resolveSqlitePath,
|
|
@@ -49453,8 +50478,10 @@ __export(index_exports, {
|
|
|
49453
50478
|
splitStatements: () => splitStatements,
|
|
49454
50479
|
status: () => status,
|
|
49455
50480
|
storageKey: () => storageKey,
|
|
50481
|
+
stripOrderAndLimit: () => stripOrderAndLimit,
|
|
49456
50482
|
stripTrailingSemicolons: () => stripTrailingSemicolons,
|
|
49457
50483
|
syncModels: () => syncModels,
|
|
50484
|
+
toCrud: () => toCrud,
|
|
49458
50485
|
validate: () => validate,
|
|
49459
50486
|
withConnectTimeout: () => withConnectTimeout,
|
|
49460
50487
|
wrapWithCache: () => wrapWithCache
|
|
@@ -49470,6 +50497,7 @@ var init_index = __esm({
|
|
|
49470
50497
|
init_model();
|
|
49471
50498
|
init_migration();
|
|
49472
50499
|
init_autoCrud();
|
|
50500
|
+
init_crud();
|
|
49473
50501
|
init_query();
|
|
49474
50502
|
init_validation();
|
|
49475
50503
|
init_baseModel();
|
|
@@ -49501,7 +50529,9 @@ export {
|
|
|
49501
50529
|
AutoCrud,
|
|
49502
50530
|
BaseModel,
|
|
49503
50531
|
CONNECT_TIMEOUT_TOLERANCE_MS,
|
|
50532
|
+
CRUD,
|
|
49504
50533
|
CachedDatabaseAdapter,
|
|
50534
|
+
Crud,
|
|
49505
50535
|
Cursor,
|
|
49506
50536
|
DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
|
|
49507
50537
|
DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
|
|
@@ -49548,6 +50578,7 @@ export {
|
|
|
49548
50578
|
SqliteCollection,
|
|
49549
50579
|
SqliteDatabase,
|
|
49550
50580
|
UnknownFieldError,
|
|
50581
|
+
_resetCrudRegistrations,
|
|
49551
50582
|
adapterColumns,
|
|
49552
50583
|
adapterCommit,
|
|
49553
50584
|
adapterCreateTable,
|
|
@@ -49580,6 +50611,8 @@ export {
|
|
|
49580
50611
|
ensureMigrationTable,
|
|
49581
50612
|
extractLastInsertId,
|
|
49582
50613
|
generateCrudRoutes,
|
|
50614
|
+
generateForm2 as generateForm,
|
|
50615
|
+
generateTable,
|
|
49583
50616
|
getAdapter,
|
|
49584
50617
|
getAppliedMigrations,
|
|
49585
50618
|
getCollection,
|
|
@@ -49599,10 +50632,13 @@ export {
|
|
|
49599
50632
|
realtime,
|
|
49600
50633
|
recordMigration,
|
|
49601
50634
|
redactCredentials,
|
|
50635
|
+
registerBackend,
|
|
49602
50636
|
removeMigrationRecord,
|
|
49603
50637
|
resetDefaultStore,
|
|
49604
50638
|
resetRequestCaches2 as resetRequestCaches,
|
|
49605
50639
|
resolveDbPool,
|
|
50640
|
+
resolveField,
|
|
50641
|
+
resolveFieldColumn,
|
|
49606
50642
|
resolveFirebirdCharset,
|
|
49607
50643
|
resolveGraphConnectTimeout,
|
|
49608
50644
|
resolveSqlitePath,
|
|
@@ -49620,8 +50656,10 @@ export {
|
|
|
49620
50656
|
splitStatements,
|
|
49621
50657
|
status,
|
|
49622
50658
|
storageKey,
|
|
50659
|
+
stripOrderAndLimit,
|
|
49623
50660
|
stripTrailingSemicolons,
|
|
49624
50661
|
syncModels,
|
|
50662
|
+
toCrud,
|
|
49625
50663
|
validate,
|
|
49626
50664
|
withConnectTimeout,
|
|
49627
50665
|
wrapWithCache
|