tina4-nodejs 3.13.147 → 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 +10901 -9855
- package/packages/cli/src/commands/generate.ts +184 -25
- package/packages/core/dist/index.js +10874 -9828
- package/packages/core/src/devAdmin.ts +72 -1
- package/packages/core/src/dispatchPipeline.ts +4 -12
- 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/src/session.ts +45 -3
- package/packages/core/templates/crud/form.twig +26 -0
- package/packages/core/templates/crud/modals.twig +31 -0
- package/packages/core/templates/crud/page.twig +401 -0
- package/packages/core/templates/crud/table.twig +69 -0
- package/packages/frond/dist/index.js +9 -1
- package/packages/frond/src/engine.ts +9 -0
- package/packages/frond/src/index.ts +1 -1
- package/packages/orm/dist/index.js +1283 -227
- 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/core/src/session.d.ts +25 -0
- 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);
|
|
@@ -11833,6 +11842,7 @@ __export(session_exports, {
|
|
|
11833
11842
|
isSecureScheme: () => isSecureScheme,
|
|
11834
11843
|
isValidSessionId: () => isValidSessionId,
|
|
11835
11844
|
sessionCookieName: () => sessionCookieName,
|
|
11845
|
+
sessionCookieToSend: () => sessionCookieToSend,
|
|
11836
11846
|
sessionStrictMode: () => sessionStrictMode
|
|
11837
11847
|
});
|
|
11838
11848
|
import { createHash as createHash5, randomBytes as randomBytes4 } from "node:crypto";
|
|
@@ -11893,6 +11903,15 @@ function buildSessionCookie(sessionId, ttl, cookieName, forwardedProto, socketEn
|
|
|
11893
11903
|
parts.push(`Max-Age=${ttl}`);
|
|
11894
11904
|
return parts.join("; ");
|
|
11895
11905
|
}
|
|
11906
|
+
function sessionCookieToSend(sess, existingSid, rawReq, rawRes) {
|
|
11907
|
+
const newSid = sess.sessionId ?? sess.getSessionId?.();
|
|
11908
|
+
if (!newSid || newSid === existingSid || sess.isFresh() || rawRes.headersSent) return null;
|
|
11909
|
+
const ttl = parseInt(process.env.TINA4_SESSION_TTL ?? "3600", 10);
|
|
11910
|
+
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
11911
|
+
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
11912
|
+
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
11913
|
+
return buildSessionCookie(newSid, ttl, void 0, forwardedProto, socketEncrypted);
|
|
11914
|
+
}
|
|
11896
11915
|
var SESSION_ID_PATTERN, VALID_SESSION_BACKENDS, CANONICAL_SESSION_BACKENDS, RETIRED_SESSION_BACKENDS, FileSessionHandler, RedisSessionHandler, FLASH_PREFIX, Session;
|
|
11897
11916
|
var init_session = __esm({
|
|
11898
11917
|
"../core/src/session.ts"() {
|
|
@@ -12255,7 +12274,6 @@ var init_session = __esm({
|
|
|
12255
12274
|
const now = Math.floor(Date.now() / 1e3);
|
|
12256
12275
|
this.data = { _created: now, _accessed: now };
|
|
12257
12276
|
this.dirty = false;
|
|
12258
|
-
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
12259
12277
|
this.stored = false;
|
|
12260
12278
|
this.loaded = /* @__PURE__ */ new Map();
|
|
12261
12279
|
this.cleared = false;
|
|
@@ -12406,6 +12424,19 @@ var init_session = __esm({
|
|
|
12406
12424
|
const result = this.flash(key);
|
|
12407
12425
|
return result !== void 0 ? result : defaultValue;
|
|
12408
12426
|
}
|
|
12427
|
+
/**
|
|
12428
|
+
* Whether this session is only an id: nothing has stored it and it holds no
|
|
12429
|
+
* data of its own (its `_created` / `_accessed` bookkeeping does not count).
|
|
12430
|
+
* Such a session has no record and needs no cookie, so save() writes nothing
|
|
12431
|
+
* for it and the server sends no cookie for it. It stops being fresh when
|
|
12432
|
+
* something is stored in it. Calls that mark a session changed without leaving
|
|
12433
|
+
* anything in it (deleting a key it never had, clear(), regenerate() on an
|
|
12434
|
+
* empty session) leave it fresh: a record with no data is not a session.
|
|
12435
|
+
*/
|
|
12436
|
+
isFresh() {
|
|
12437
|
+
if (this.stored) return false;
|
|
12438
|
+
return !this.data || Object.keys(this.data).every((k) => k === "_created" || k === "_accessed");
|
|
12439
|
+
}
|
|
12409
12440
|
/**
|
|
12410
12441
|
* Get the current session ID.
|
|
12411
12442
|
*/
|
|
@@ -12468,7 +12499,7 @@ var init_session = __esm({
|
|
|
12468
12499
|
*/
|
|
12469
12500
|
save() {
|
|
12470
12501
|
if (!this.sessionId || !this.data) return true;
|
|
12471
|
-
if (
|
|
12502
|
+
if (this.isFresh()) return true;
|
|
12472
12503
|
let record = this.data;
|
|
12473
12504
|
if (this.stored) {
|
|
12474
12505
|
const current = this.safeRead(this.sessionId);
|
|
@@ -12655,7 +12686,7 @@ function errorLabel(err) {
|
|
|
12655
12686
|
return err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
12656
12687
|
}
|
|
12657
12688
|
async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
12658
|
-
const { Session: Session2,
|
|
12689
|
+
const { Session: Session2, sessionCookieToSend: sessionCookieToSend2, sessionCookieName: sessionCookieName2, sessionStrictMode: sessionStrictMode2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
12659
12690
|
const cookieHeader = rawReq.headers.cookie ?? "";
|
|
12660
12691
|
const cookiePrefix = sessionCookieName2() + "=";
|
|
12661
12692
|
let existingSid;
|
|
@@ -12694,13 +12725,9 @@ async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
|
12694
12725
|
} catch {
|
|
12695
12726
|
}
|
|
12696
12727
|
}
|
|
12697
|
-
const
|
|
12698
|
-
if (
|
|
12699
|
-
|
|
12700
|
-
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
12701
|
-
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
12702
|
-
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
12703
|
-
rawRes.appendHeader("Set-Cookie", buildSessionCookie2(newSid, ttl, void 0, forwardedProto, socketEncrypted));
|
|
12728
|
+
const sessionCookie = sessionCookieToSend2(sess, existingSid, rawReq, rawRes);
|
|
12729
|
+
if (sessionCookie !== null) {
|
|
12730
|
+
rawRes.appendHeader("Set-Cookie", sessionCookie);
|
|
12704
12731
|
}
|
|
12705
12732
|
return origEnd(...args);
|
|
12706
12733
|
};
|
|
@@ -13809,8 +13836,9 @@ __export(generate_exports, {
|
|
|
13809
13836
|
toSnake: () => toSnake,
|
|
13810
13837
|
toTableName: () => toTableName
|
|
13811
13838
|
});
|
|
13812
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13813
|
-
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";
|
|
13814
13842
|
function ensureDir(dir) {
|
|
13815
13843
|
if (__resolution.dryRun) return;
|
|
13816
13844
|
if (!existsSync13(dir)) {
|
|
@@ -13893,7 +13921,7 @@ function currentResolution() {
|
|
|
13893
13921
|
transformations: [...__resolution.body.transformations]
|
|
13894
13922
|
};
|
|
13895
13923
|
if (__resolution.body.edit_hints) {
|
|
13896
|
-
body.edit_hints = __resolution.body.edit_hints.map((
|
|
13924
|
+
body.edit_hints = __resolution.body.edit_hints.map((h2) => ({ ...h2 }));
|
|
13897
13925
|
}
|
|
13898
13926
|
if (__resolution.body.next) {
|
|
13899
13927
|
body.next = [...__resolution.body.next];
|
|
@@ -14419,23 +14447,144 @@ ${aiFill(`delete_${singular}`, {
|
|
|
14419
14447
|
}
|
|
14420
14448
|
function generateCrud(name, flags) {
|
|
14421
14449
|
const table2 = resolveTable(name, flags, { announce: false });
|
|
14422
|
-
const routeName = pluralizeReserved(toSnake(name));
|
|
14423
14450
|
const isPublic = Boolean(flags.public);
|
|
14424
14451
|
if (!__resolution.jsonMode) console.log(`
|
|
14425
14452
|
Generating CRUD for ${name}...
|
|
14426
14453
|
`);
|
|
14427
14454
|
generateModel(name, flags, false);
|
|
14428
|
-
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
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);
|
|
14432
14458
|
if (!__resolution.jsonMode) {
|
|
14433
14459
|
console.log(`
|
|
14434
14460
|
CRUD generation complete for ${name}.`);
|
|
14435
14461
|
console.log(" Run: tina4nodejs migrate");
|
|
14436
|
-
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}`);
|
|
14437
14531
|
}
|
|
14438
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
|
+
}
|
|
14439
14588
|
function generateMigration(name, flags, fieldsOverride, tableOverride, emitTest = true) {
|
|
14440
14589
|
const ts = timestamp();
|
|
14441
14590
|
const dir = resolve6("migrations");
|
|
@@ -14577,7 +14726,7 @@ import { Router, TestClient, getToken, discoverRoutes } from "tina4-nodejs";
|
|
|
14577
14726
|
import { initDatabase } from "tina4-nodejs/orm";
|
|
14578
14727
|
import ${model} from "../src/models/${model}.js";
|
|
14579
14728
|
|
|
14580
|
-
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "test-secret";
|
|
14729
|
+
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "tina4-test-secret-0123456789abcdef0123456789";
|
|
14581
14730
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
14582
14731
|
|
|
14583
14732
|
let pass = 0;
|
|
@@ -15540,7 +15689,7 @@ import { Router, TestClient, discoverRoutes } from "tina4-nodejs";
|
|
|
15540
15689
|
import { initDatabase } from "tina4-nodejs/orm";
|
|
15541
15690
|
import User from "../src/models/User.js";
|
|
15542
15691
|
|
|
15543
|
-
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "test-secret";
|
|
15692
|
+
process.env.TINA4_SECRET = process.env.TINA4_SECRET ?? "tina4-test-secret-0123456789abcdef0123456789";
|
|
15544
15693
|
delete process.env.TINA4_API_KEY;
|
|
15545
15694
|
const here = dirname(fileURLToPath(import.meta.url));`;
|
|
15546
15695
|
const body = `await initDatabase({ url: "sqlite:///test_auth.db" });
|
|
@@ -15727,7 +15876,7 @@ var init_generate = __esm({
|
|
|
15727
15876
|
GENERATORS = {
|
|
15728
15877
|
model: { handler: generateModel, usage: '<Name> [--fields "name:string,price:float"] [--table-name <name>]', summary: "ORM model + matching migration" },
|
|
15729
15878
|
route: { handler: generateRoute, usage: "<name> [--model Name] [--public]", summary: "CRUD route file, secure by default (--public opens writes)" },
|
|
15730
|
-
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" },
|
|
15731
15880
|
migration: { handler: (n, f) => generateMigration(n, f, void 0, void 0, !f["no-test"]), usage: "<description>", summary: "Timestamped migration file (UP/DOWN)" },
|
|
15732
15881
|
middleware: { handler: generateMiddleware, usage: "<Name>", summary: "Middleware with before/after hooks" },
|
|
15733
15882
|
test: { handler: generateTest, usage: "<name> [--model Name]", summary: "Test file" },
|
|
@@ -15756,8 +15905,9 @@ var init_generate = __esm({
|
|
|
15756
15905
|
],
|
|
15757
15906
|
crud: ({ name, table: table2 }) => [
|
|
15758
15907
|
`Apply the migration: npx tina4nodejs migrate`,
|
|
15759
|
-
`Serve and try: npx tina4nodejs serve -> visit /swagger`,
|
|
15760
|
-
`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`,
|
|
15761
15911
|
`Change fields: edit src/models/${name}.ts then re-run generate crud`
|
|
15762
15912
|
],
|
|
15763
15913
|
migration: () => [
|
|
@@ -18706,8 +18856,8 @@ __export(context_exports, {
|
|
|
18706
18856
|
fts5Supported: () => fts5Supported
|
|
18707
18857
|
});
|
|
18708
18858
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
18709
|
-
import { existsSync as existsSync15, mkdirSync as mkdirSync10, readFileSync as readFileSync13, readdirSync as
|
|
18710
|
-
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";
|
|
18711
18861
|
function fts5Supported() {
|
|
18712
18862
|
try {
|
|
18713
18863
|
const conn = new DatabaseSync2(":memory:");
|
|
@@ -18730,7 +18880,7 @@ function realResolve(abs) {
|
|
|
18730
18880
|
} catch {
|
|
18731
18881
|
}
|
|
18732
18882
|
try {
|
|
18733
|
-
return join17(realpathSync5(
|
|
18883
|
+
return join17(realpathSync5(dirname9(abs)), basename4(abs));
|
|
18734
18884
|
} catch {
|
|
18735
18885
|
return abs;
|
|
18736
18886
|
}
|
|
@@ -18818,7 +18968,7 @@ var init_context = __esm({
|
|
|
18818
18968
|
const check = fts5Check ?? fts5Supported;
|
|
18819
18969
|
this.available = Boolean(check());
|
|
18820
18970
|
if (!this.available) return;
|
|
18821
|
-
const parent =
|
|
18971
|
+
const parent = dirname9(this.path);
|
|
18822
18972
|
if (parent !== "" && parent !== ".") {
|
|
18823
18973
|
mkdirSync10(parent, { recursive: true });
|
|
18824
18974
|
}
|
|
@@ -18896,7 +19046,7 @@ var init_context = __esm({
|
|
|
18896
19046
|
this.root = rootAbs;
|
|
18897
19047
|
let total = 0;
|
|
18898
19048
|
const walk2 = (dir) => {
|
|
18899
|
-
const entries =
|
|
19049
|
+
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
18900
19050
|
const files = [];
|
|
18901
19051
|
const subdirs = [];
|
|
18902
19052
|
for (const e of entries) {
|
|
@@ -20812,7 +20962,7 @@ var init_mongoBackend = __esm({
|
|
|
20812
20962
|
return null;
|
|
20813
20963
|
}
|
|
20814
20964
|
}
|
|
20815
|
-
push(queue, payload,
|
|
20965
|
+
push(queue, payload, delay2, priority) {
|
|
20816
20966
|
const id = randomUUID4();
|
|
20817
20967
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20818
20968
|
const job = {
|
|
@@ -20821,7 +20971,7 @@ var init_mongoBackend = __esm({
|
|
|
20821
20971
|
status: "pending",
|
|
20822
20972
|
createdAt: now,
|
|
20823
20973
|
attempts: 0,
|
|
20824
|
-
delayUntil:
|
|
20974
|
+
delayUntil: delay2 ? new Date(Date.now() + delay2 * 1e3).toISOString() : null,
|
|
20825
20975
|
// The pop sort has always been { priority: -1, createdAt: 1 }, but this
|
|
20826
20976
|
// field was never written, so every job scored undefined and the queue
|
|
20827
20977
|
// ran pure FIFO. Priority did not even reach here: the backend interface
|
|
@@ -20990,7 +21140,7 @@ var init_job = __esm({
|
|
|
20990
21140
|
});
|
|
20991
21141
|
|
|
20992
21142
|
// ../core/src/queueBackends/liteBackend.ts
|
|
20993
|
-
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";
|
|
20994
21144
|
import { join as join18 } from "node:path";
|
|
20995
21145
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
20996
21146
|
var LiteBackend;
|
|
@@ -21052,7 +21202,7 @@ var init_liteBackend = __esm({
|
|
|
21052
21202
|
*/
|
|
21053
21203
|
close() {
|
|
21054
21204
|
}
|
|
21055
|
-
push(queue, payload,
|
|
21205
|
+
push(queue, payload, delay2, priority) {
|
|
21056
21206
|
const dir = this.ensureDir(queue);
|
|
21057
21207
|
const id = randomUUID5();
|
|
21058
21208
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -21062,7 +21212,7 @@ var init_liteBackend = __esm({
|
|
|
21062
21212
|
status: "pending",
|
|
21063
21213
|
createdAt: now,
|
|
21064
21214
|
attempts: 0,
|
|
21065
|
-
delayUntil:
|
|
21215
|
+
delayUntil: delay2 ? new Date(Date.now() + delay2 * 1e3).toISOString() : null,
|
|
21066
21216
|
priority: priority ?? 0,
|
|
21067
21217
|
topic: queue,
|
|
21068
21218
|
error: void 0
|
|
@@ -21081,7 +21231,7 @@ var init_liteBackend = __esm({
|
|
|
21081
21231
|
const dir = this.ensureDir(queue);
|
|
21082
21232
|
let filenames;
|
|
21083
21233
|
try {
|
|
21084
|
-
filenames =
|
|
21234
|
+
filenames = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21085
21235
|
} catch {
|
|
21086
21236
|
return [];
|
|
21087
21237
|
}
|
|
@@ -21146,7 +21296,7 @@ var init_liteBackend = __esm({
|
|
|
21146
21296
|
const reservedDir = this.ensureReservedDir(queue);
|
|
21147
21297
|
let filenames;
|
|
21148
21298
|
try {
|
|
21149
|
-
filenames =
|
|
21299
|
+
filenames = readdirSync9(reservedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21150
21300
|
} catch {
|
|
21151
21301
|
return;
|
|
21152
21302
|
}
|
|
@@ -21257,7 +21407,7 @@ var init_liteBackend = __esm({
|
|
|
21257
21407
|
const scanDir = isDead ? this.ensureFailedDir(queue) : isReserved ? this.ensureReservedDir(queue) : this.ensureDir(queue);
|
|
21258
21408
|
let files;
|
|
21259
21409
|
try {
|
|
21260
|
-
files =
|
|
21410
|
+
files = readdirSync9(scanDir).filter((f) => f.endsWith(".queue-data"));
|
|
21261
21411
|
} catch {
|
|
21262
21412
|
return 0;
|
|
21263
21413
|
}
|
|
@@ -21278,7 +21428,7 @@ var init_liteBackend = __esm({
|
|
|
21278
21428
|
const dir = this.ensureDir(queue);
|
|
21279
21429
|
let count = 0;
|
|
21280
21430
|
try {
|
|
21281
|
-
const files =
|
|
21431
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21282
21432
|
for (const file of files) {
|
|
21283
21433
|
unlinkSync6(join18(dir, file));
|
|
21284
21434
|
count++;
|
|
@@ -21288,7 +21438,7 @@ var init_liteBackend = __esm({
|
|
|
21288
21438
|
const failedDir = join18(dir, "failed");
|
|
21289
21439
|
try {
|
|
21290
21440
|
if (existsSync16(failedDir)) {
|
|
21291
|
-
const files =
|
|
21441
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21292
21442
|
for (const file of files) {
|
|
21293
21443
|
unlinkSync6(join18(failedDir, file));
|
|
21294
21444
|
count++;
|
|
@@ -21299,7 +21449,7 @@ var init_liteBackend = __esm({
|
|
|
21299
21449
|
const reservedDir = join18(dir, "reserved");
|
|
21300
21450
|
try {
|
|
21301
21451
|
if (existsSync16(reservedDir)) {
|
|
21302
|
-
const files =
|
|
21452
|
+
const files = readdirSync9(reservedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21303
21453
|
for (const file of files) {
|
|
21304
21454
|
unlinkSync6(join18(reservedDir, file));
|
|
21305
21455
|
count++;
|
|
@@ -21321,7 +21471,7 @@ var init_liteBackend = __esm({
|
|
|
21321
21471
|
const dir = this.ensureDir(queue);
|
|
21322
21472
|
const results = [];
|
|
21323
21473
|
try {
|
|
21324
|
-
const files =
|
|
21474
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
21325
21475
|
for (const file of files) {
|
|
21326
21476
|
try {
|
|
21327
21477
|
const job = JSON.parse(readFileSync14(join18(dir, file), "utf-8"));
|
|
@@ -21345,7 +21495,7 @@ var init_liteBackend = __esm({
|
|
|
21345
21495
|
*/
|
|
21346
21496
|
retry(queue, jobId, delaySeconds) {
|
|
21347
21497
|
try {
|
|
21348
|
-
const queues =
|
|
21498
|
+
const queues = readdirSync9(this.basePath);
|
|
21349
21499
|
for (const q of queues) {
|
|
21350
21500
|
const failedDir = join18(this.basePath, q, "failed");
|
|
21351
21501
|
const filePath = join18(failedDir, `${jobId}.queue-data`);
|
|
@@ -21371,7 +21521,7 @@ var init_liteBackend = __esm({
|
|
|
21371
21521
|
const failedDir = this.ensureFailedDir(queue);
|
|
21372
21522
|
const results = [];
|
|
21373
21523
|
try {
|
|
21374
|
-
const files =
|
|
21524
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data")).sort();
|
|
21375
21525
|
for (const file of files) {
|
|
21376
21526
|
try {
|
|
21377
21527
|
const job = JSON.parse(readFileSync14(join18(failedDir, file), "utf-8"));
|
|
@@ -21392,7 +21542,7 @@ var init_liteBackend = __esm({
|
|
|
21392
21542
|
if (isDead) {
|
|
21393
21543
|
const failedDir = this.ensureFailedDir(queue);
|
|
21394
21544
|
try {
|
|
21395
|
-
const files =
|
|
21545
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21396
21546
|
for (const file of files) {
|
|
21397
21547
|
try {
|
|
21398
21548
|
unlinkSync6(join18(failedDir, file));
|
|
@@ -21405,7 +21555,7 @@ var init_liteBackend = __esm({
|
|
|
21405
21555
|
} else {
|
|
21406
21556
|
const dir = this.ensureDir(queue);
|
|
21407
21557
|
try {
|
|
21408
|
-
const files =
|
|
21558
|
+
const files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21409
21559
|
for (const file of files) {
|
|
21410
21560
|
try {
|
|
21411
21561
|
const job = JSON.parse(readFileSync14(join18(dir, file), "utf-8"));
|
|
@@ -21431,7 +21581,7 @@ var init_liteBackend = __esm({
|
|
|
21431
21581
|
const queueDir = this.ensureDir(queue);
|
|
21432
21582
|
let count = 0;
|
|
21433
21583
|
try {
|
|
21434
|
-
const files =
|
|
21584
|
+
const files = readdirSync9(failedDir).filter((f) => f.endsWith(".queue-data"));
|
|
21435
21585
|
for (const file of files) {
|
|
21436
21586
|
try {
|
|
21437
21587
|
const filePath = join18(failedDir, file);
|
|
@@ -21458,7 +21608,7 @@ var init_liteBackend = __esm({
|
|
|
21458
21608
|
const dir = this.ensureDir(queue);
|
|
21459
21609
|
let files;
|
|
21460
21610
|
try {
|
|
21461
|
-
files =
|
|
21611
|
+
files = readdirSync9(dir).filter((f) => f.endsWith(".queue-data"));
|
|
21462
21612
|
} catch {
|
|
21463
21613
|
return null;
|
|
21464
21614
|
}
|
|
@@ -21699,11 +21849,11 @@ var init_queue = __esm({
|
|
|
21699
21849
|
*
|
|
21700
21850
|
* @param priority — Higher value = higher priority. Default 0.
|
|
21701
21851
|
*/
|
|
21702
|
-
push(payload,
|
|
21852
|
+
push(payload, delay2, priority = 0) {
|
|
21703
21853
|
if (this.externalBackend) {
|
|
21704
|
-
return this.externalBackend.push(this.topic, payload,
|
|
21854
|
+
return this.externalBackend.push(this.topic, payload, delay2, priority);
|
|
21705
21855
|
}
|
|
21706
|
-
return this.liteBackend.push(this.topic, payload,
|
|
21856
|
+
return this.liteBackend.push(this.topic, payload, delay2, priority);
|
|
21707
21857
|
}
|
|
21708
21858
|
/**
|
|
21709
21859
|
* Atomically claim the next available job from this queue's topic. Returns null if empty.
|
|
@@ -21934,11 +22084,11 @@ var init_queue = __esm({
|
|
|
21934
22084
|
* shares the same retarget path consume()/process() use — keeping produce and
|
|
21935
22085
|
* consume symmetric on the same topic argument.
|
|
21936
22086
|
*/
|
|
21937
|
-
produce(topic, payload, priority = 0,
|
|
22087
|
+
produce(topic, payload, priority = 0, delay2 = 0) {
|
|
21938
22088
|
const previous = this.topic;
|
|
21939
22089
|
this.retarget(topic);
|
|
21940
22090
|
try {
|
|
21941
|
-
return this.push(payload,
|
|
22091
|
+
return this.push(payload, delay2, priority);
|
|
21942
22092
|
} finally {
|
|
21943
22093
|
this.retarget(previous);
|
|
21944
22094
|
}
|
|
@@ -22939,7 +23089,7 @@ var init_projectIndex = __esm({
|
|
|
22939
23089
|
}
|
|
22940
23090
|
}
|
|
22941
23091
|
hits.sort((a, b) => b[0] - a[0]);
|
|
22942
|
-
return hits.slice(0, Math.max(1, limit)).map((
|
|
23092
|
+
return hits.slice(0, Math.max(1, limit)).map((h2) => h2[1]);
|
|
22943
23093
|
},
|
|
22944
23094
|
fileEntry(relPath) {
|
|
22945
23095
|
ProjectIndex.refresh();
|
|
@@ -23468,7 +23618,7 @@ __export(docs_exports, {
|
|
|
23468
23618
|
});
|
|
23469
23619
|
import * as fs8 from "node:fs";
|
|
23470
23620
|
import * as path8 from "node:path";
|
|
23471
|
-
import { fileURLToPath as
|
|
23621
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
23472
23622
|
function detectFrameworkRoots() {
|
|
23473
23623
|
const corePackageRoot = path8.resolve(__DIRNAME, "..");
|
|
23474
23624
|
const packagesRoot = path8.resolve(corePackageRoot, "..");
|
|
@@ -23875,7 +24025,7 @@ var init_docs = __esm({
|
|
|
23875
24025
|
"hash"
|
|
23876
24026
|
]);
|
|
23877
24027
|
_DOCS_CACHE = /* @__PURE__ */ new Map();
|
|
23878
|
-
__FILENAME =
|
|
24028
|
+
__FILENAME = fileURLToPath5(import.meta.url);
|
|
23879
24029
|
__DIRNAME = path8.dirname(__FILENAME);
|
|
23880
24030
|
USER_DIRS = ["src/orm", "src/routes", "src/app", "src/services", "src/models"];
|
|
23881
24031
|
CAMEL_SPLIT = /([a-z0-9])([A-Z])|([A-Z]+)([A-Z][a-z])/g;
|
|
@@ -24252,11 +24402,12 @@ ${end}
|
|
|
24252
24402
|
|
|
24253
24403
|
// ../core/src/devAdmin.ts
|
|
24254
24404
|
import { cpus as osCpus } from "node:os";
|
|
24255
|
-
import { readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync20, readdirSync as
|
|
24256
|
-
import { join as join22, dirname as
|
|
24257
|
-
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";
|
|
24258
24408
|
import { timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
24259
|
-
|
|
24409
|
+
import net2 from "node:net";
|
|
24410
|
+
function escapeHtml2(value) {
|
|
24260
24411
|
return String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
24261
24412
|
}
|
|
24262
24413
|
function devSameOriginOk(req2) {
|
|
@@ -24294,6 +24445,42 @@ function devHostAllowed(hostHeader) {
|
|
|
24294
24445
|
if (configured) allowed.add(configured);
|
|
24295
24446
|
return allowed.has(name);
|
|
24296
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
|
+
}
|
|
24297
24484
|
function devRequestDenial(req2) {
|
|
24298
24485
|
const path10 = (req2.path ?? "").replace(/\/+/g, "/");
|
|
24299
24486
|
const isMcp = DEV_MCP_PREFIXES.some((prefix) => path10 === prefix || path10.startsWith(prefix + "/"));
|
|
@@ -24303,9 +24490,12 @@ function devRequestDenial(req2) {
|
|
|
24303
24490
|
if (!devSameOriginOk(req2)) {
|
|
24304
24491
|
return { status: 403, error: "dev-admin: refused (cross-origin request)" };
|
|
24305
24492
|
}
|
|
24493
|
+
if (DEV_PUBLIC_ASSETS.has(path10)) {
|
|
24494
|
+
return null;
|
|
24495
|
+
}
|
|
24306
24496
|
if (!isMcp) {
|
|
24307
24497
|
const peer = req2.socket?.remoteAddress ?? "";
|
|
24308
|
-
if (!(
|
|
24498
|
+
if (!(devPeerAllowed(peer) || mcpTokenOk(req2, true))) {
|
|
24309
24499
|
return { status: 403, error: "dev-admin: refused (non-loopback peer)" };
|
|
24310
24500
|
}
|
|
24311
24501
|
}
|
|
@@ -24394,7 +24584,7 @@ function mapQueueJob(job, topic, status2) {
|
|
|
24394
24584
|
function readQueueDir(dir, topic, status2) {
|
|
24395
24585
|
if (!existsSync20(dir)) return [];
|
|
24396
24586
|
const jobs = [];
|
|
24397
|
-
for (const filename of
|
|
24587
|
+
for (const filename of readdirSync13(dir).sort()) {
|
|
24398
24588
|
if (!filename.endsWith(".queue-data")) continue;
|
|
24399
24589
|
try {
|
|
24400
24590
|
jobs.push(mapQueueJob(JSON.parse(readFileSync18(join22(dir, filename), "utf-8")), topic, status2));
|
|
@@ -24536,12 +24726,12 @@ function upsertDevEnvVar(key, value) {
|
|
|
24536
24726
|
}
|
|
24537
24727
|
function formatUptime(seconds) {
|
|
24538
24728
|
const d = Math.floor(seconds / 86400);
|
|
24539
|
-
const
|
|
24729
|
+
const h2 = Math.floor(seconds % 86400 / 3600);
|
|
24540
24730
|
const m = Math.floor(seconds % 3600 / 60);
|
|
24541
24731
|
const s = Math.floor(seconds % 60);
|
|
24542
24732
|
const parts = [];
|
|
24543
24733
|
if (d > 0) parts.push(`${d}d`);
|
|
24544
|
-
if (
|
|
24734
|
+
if (h2 > 0) parts.push(`${h2}h`);
|
|
24545
24735
|
if (m > 0) parts.push(`${m}m`);
|
|
24546
24736
|
parts.push(`${s}s`);
|
|
24547
24737
|
return parts.join(" ");
|
|
@@ -24562,7 +24752,7 @@ function parseEnvFile() {
|
|
|
24562
24752
|
function walkDirRecursive(dir) {
|
|
24563
24753
|
const results = [];
|
|
24564
24754
|
if (!existsSync20(dir)) return results;
|
|
24565
|
-
for (const entry of
|
|
24755
|
+
for (const entry of readdirSync13(dir)) {
|
|
24566
24756
|
const full = join22(dir, entry);
|
|
24567
24757
|
if (statSync15(full).isDirectory()) {
|
|
24568
24758
|
results.push(...walkDirRecursive(full));
|
|
@@ -24592,8 +24782,8 @@ function handleGalleryDeploy(router) {
|
|
|
24592
24782
|
for (const srcFile of allFiles) {
|
|
24593
24783
|
const rel = relative9(gallerySrc, srcFile);
|
|
24594
24784
|
const dest = join22(projectSrc, rel);
|
|
24595
|
-
mkdirSync14(
|
|
24596
|
-
|
|
24785
|
+
mkdirSync14(dirname11(dest), { recursive: true });
|
|
24786
|
+
copyFileSync2(srcFile, dest);
|
|
24597
24787
|
copied.push(rel);
|
|
24598
24788
|
}
|
|
24599
24789
|
try {
|
|
@@ -24623,9 +24813,9 @@ function realPathOf(target) {
|
|
|
24623
24813
|
present = true;
|
|
24624
24814
|
} catch {
|
|
24625
24815
|
}
|
|
24626
|
-
if (present ||
|
|
24816
|
+
if (present || dirname11(existing) === existing) break;
|
|
24627
24817
|
tail.unshift(basename5(existing));
|
|
24628
|
-
existing =
|
|
24818
|
+
existing = dirname11(existing);
|
|
24629
24819
|
}
|
|
24630
24820
|
return join22(realpathSync6(existing), ...tail);
|
|
24631
24821
|
}
|
|
@@ -24749,9 +24939,9 @@ function applyMcpOutcome(res, outcome) {
|
|
|
24749
24939
|
}
|
|
24750
24940
|
function renderToolbarHtml(ctx) {
|
|
24751
24941
|
const nodeVersion = process.version;
|
|
24752
|
-
const method =
|
|
24753
|
-
const path10 =
|
|
24754
|
-
const matchedPattern =
|
|
24942
|
+
const method = escapeHtml2(ctx.method);
|
|
24943
|
+
const path10 = escapeHtml2(ctx.path);
|
|
24944
|
+
const matchedPattern = escapeHtml2(ctx.matchedPattern);
|
|
24755
24945
|
const reload = ctx.reload === false ? "0" : "1";
|
|
24756
24946
|
return `<link rel="stylesheet" href="/__dev/toolbar.css">
|
|
24757
24947
|
<div id="tina4-dev-toolbar" data-reload="${reload}">
|
|
@@ -24937,7 +25127,7 @@ function toolbarJs() {
|
|
|
24937
25127
|
connect();
|
|
24938
25128
|
})();`;
|
|
24939
25129
|
}
|
|
24940
|
-
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;
|
|
24941
25131
|
var init_devAdmin = __esm({
|
|
24942
25132
|
"../core/src/devAdmin.ts"() {
|
|
24943
25133
|
"use strict";
|
|
@@ -24950,6 +25140,7 @@ var init_devAdmin = __esm({
|
|
|
24950
25140
|
init_version();
|
|
24951
25141
|
cpuCount = osCpus().length;
|
|
24952
25142
|
DEV_MCP_PREFIXES = ["/__dev/api/mcp", "/__dev/mcp"];
|
|
25143
|
+
DEV_PUBLIC_ASSETS = /* @__PURE__ */ new Set(["/__dev/toolbar.css", "/__dev/toolbar.js"]);
|
|
24953
25144
|
DEV_SECRET_BASENAMES = /* @__PURE__ */ new Set([
|
|
24954
25145
|
".env",
|
|
24955
25146
|
".envrc",
|
|
@@ -25651,7 +25842,7 @@ var init_devAdmin = __esm({
|
|
|
25651
25842
|
const queueDir = queueBasePath2();
|
|
25652
25843
|
let topics = [];
|
|
25653
25844
|
if (existsSync20(queueDir)) {
|
|
25654
|
-
topics =
|
|
25845
|
+
topics = readdirSync13(queueDir).filter((d) => {
|
|
25655
25846
|
try {
|
|
25656
25847
|
return statSync15(join22(queueDir, d)).isDirectory();
|
|
25657
25848
|
} catch {
|
|
@@ -26155,13 +26346,13 @@ var init_devAdmin = __esm({
|
|
|
26155
26346
|
res.json({ success: false, error: msg });
|
|
26156
26347
|
}
|
|
26157
26348
|
};
|
|
26158
|
-
__devAdminFilename =
|
|
26159
|
-
__devAdminDirname =
|
|
26349
|
+
__devAdminFilename = fileURLToPath6(import.meta.url);
|
|
26350
|
+
__devAdminDirname = dirname11(__devAdminFilename);
|
|
26160
26351
|
handleGalleryList = (_req, res) => {
|
|
26161
26352
|
const galleryDir = resolve12(__devAdminDirname, "..", "gallery");
|
|
26162
26353
|
const items = [];
|
|
26163
26354
|
if (existsSync20(galleryDir)) {
|
|
26164
|
-
const entries =
|
|
26355
|
+
const entries = readdirSync13(galleryDir).sort();
|
|
26165
26356
|
for (const entry of entries) {
|
|
26166
26357
|
const entryPath = join22(galleryDir, entry);
|
|
26167
26358
|
const metaFile = join22(entryPath, "meta.json");
|
|
@@ -26265,7 +26456,7 @@ var init_devAdmin = __esm({
|
|
|
26265
26456
|
if (cwdInGit !== "") cwdInGit += "/";
|
|
26266
26457
|
}
|
|
26267
26458
|
const entries = [];
|
|
26268
|
-
for (const name of
|
|
26459
|
+
for (const name of readdirSync13(target).sort()) {
|
|
26269
26460
|
if (devFilesHidden(name)) continue;
|
|
26270
26461
|
const full = join22(target, name);
|
|
26271
26462
|
const entryRel = relative9(root, full).replace(/\\/g, "/");
|
|
@@ -26296,7 +26487,7 @@ var init_devAdmin = __esm({
|
|
|
26296
26487
|
if (isDir) {
|
|
26297
26488
|
hasChildren = false;
|
|
26298
26489
|
try {
|
|
26299
|
-
for (const c of
|
|
26490
|
+
for (const c of readdirSync13(full)) {
|
|
26300
26491
|
if (devFilesHidden(c)) continue;
|
|
26301
26492
|
hasChildren = true;
|
|
26302
26493
|
break;
|
|
@@ -26388,7 +26579,7 @@ var init_devAdmin = __esm({
|
|
|
26388
26579
|
return;
|
|
26389
26580
|
}
|
|
26390
26581
|
try {
|
|
26391
|
-
mkdirSync14(
|
|
26582
|
+
mkdirSync14(dirname11(target), { recursive: true });
|
|
26392
26583
|
const existed = existsSync20(target);
|
|
26393
26584
|
writeFileSync13(target, content, "utf-8");
|
|
26394
26585
|
try {
|
|
@@ -26460,7 +26651,7 @@ var init_devAdmin = __esm({
|
|
|
26460
26651
|
}
|
|
26461
26652
|
try {
|
|
26462
26653
|
const { renameSync: renameSync3 } = await import("node:fs");
|
|
26463
|
-
mkdirSync14(
|
|
26654
|
+
mkdirSync14(dirname11(dst), { recursive: true });
|
|
26464
26655
|
renameSync3(src, dst);
|
|
26465
26656
|
res.json({ ok: true, from: relative9(root, src), to: relative9(root, dst) });
|
|
26466
26657
|
} catch (e) {
|
|
@@ -26481,8 +26672,8 @@ var init_devAdmin = __esm({
|
|
|
26481
26672
|
return;
|
|
26482
26673
|
}
|
|
26483
26674
|
try {
|
|
26484
|
-
const { rmSync } = await import("node:fs");
|
|
26485
|
-
|
|
26675
|
+
const { rmSync: rmSync2 } = await import("node:fs");
|
|
26676
|
+
rmSync2(target, { recursive: true, force: true });
|
|
26486
26677
|
res.json({ ok: true, deleted: relative9(root, target) });
|
|
26487
26678
|
} catch (e) {
|
|
26488
26679
|
res.json({ error: e.message }, 500);
|
|
@@ -26817,10 +27008,10 @@ var init_devAdmin = __esm({
|
|
|
26817
27008
|
});
|
|
26818
27009
|
};
|
|
26819
27010
|
handleDevAdminJs = async (_req, res) => {
|
|
26820
|
-
const { readFileSync: readFileSync27, existsSync:
|
|
26821
|
-
const { dirname:
|
|
26822
|
-
const { fileURLToPath:
|
|
26823
|
-
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));
|
|
26824
27015
|
const candidates = [
|
|
26825
27016
|
join33(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
26826
27017
|
// src/../public/js/
|
|
@@ -26831,7 +27022,7 @@ var init_devAdmin = __esm({
|
|
|
26831
27022
|
// project public/
|
|
26832
27023
|
];
|
|
26833
27024
|
for (const jsPath of candidates) {
|
|
26834
|
-
if (
|
|
27025
|
+
if (existsSync29(jsPath)) {
|
|
26835
27026
|
try {
|
|
26836
27027
|
const content = readFileSync27(jsPath, "utf-8");
|
|
26837
27028
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
@@ -26856,7 +27047,7 @@ var init_devAdmin = __esm({
|
|
|
26856
27047
|
});
|
|
26857
27048
|
|
|
26858
27049
|
// ../core/src/i18n.ts
|
|
26859
|
-
import { readFileSync as readFileSync19, readdirSync as
|
|
27050
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync14, existsSync as existsSync21 } from "node:fs";
|
|
26860
27051
|
import { join as join23, resolve as resolve13 } from "node:path";
|
|
26861
27052
|
var I18n;
|
|
26862
27053
|
var init_i18n = __esm({
|
|
@@ -26938,7 +27129,7 @@ var init_i18n = __esm({
|
|
|
26938
27129
|
return [this._defaultLocale];
|
|
26939
27130
|
}
|
|
26940
27131
|
try {
|
|
26941
|
-
const files =
|
|
27132
|
+
const files = readdirSync14(this._localeDir);
|
|
26942
27133
|
const locales = files.filter((f) => f.endsWith(".json") || f.endsWith(".yml") || f.endsWith(".yaml")).map((f) => f.replace(/\.(json|ya?ml)$/, "")).sort();
|
|
26943
27134
|
return locales.length > 0 ? locales : [this._defaultLocale];
|
|
26944
27135
|
} catch {
|
|
@@ -28194,10 +28385,10 @@ var init_docsAutoDiscovery = __esm({
|
|
|
28194
28385
|
|
|
28195
28386
|
// ../core/src/server.ts
|
|
28196
28387
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
28197
|
-
import { resolve as resolve15, dirname as
|
|
28198
|
-
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";
|
|
28199
28390
|
import { isatty } from "node:tty";
|
|
28200
|
-
import { fileURLToPath as
|
|
28391
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
28201
28392
|
import { execFileSync as execFileSync3, exec } from "node:child_process";
|
|
28202
28393
|
import cluster from "node:cluster";
|
|
28203
28394
|
import os2 from "node:os";
|
|
@@ -28237,7 +28428,7 @@ async function autoMigrateOnStartup(migrationDir = "migrations", base = process.
|
|
|
28237
28428
|
if (!existsSync23(dir)) return;
|
|
28238
28429
|
let hasSql = false;
|
|
28239
28430
|
try {
|
|
28240
|
-
hasSql =
|
|
28431
|
+
hasSql = readdirSync15(dir).some((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"));
|
|
28241
28432
|
} catch {
|
|
28242
28433
|
return;
|
|
28243
28434
|
}
|
|
@@ -28407,7 +28598,7 @@ function injectDevToolbar(html, ctx) {
|
|
|
28407
28598
|
function walkGalleryFiles(dir) {
|
|
28408
28599
|
const results = [];
|
|
28409
28600
|
if (!existsSync23(dir)) return results;
|
|
28410
|
-
for (const f of
|
|
28601
|
+
for (const f of readdirSync15(dir)) {
|
|
28411
28602
|
const full = join25(dir, f);
|
|
28412
28603
|
if (statSync16(full).isDirectory()) results.push(...walkGalleryFiles(full));
|
|
28413
28604
|
else results.push(full);
|
|
@@ -28419,7 +28610,7 @@ function getGalleryDeployedState() {
|
|
|
28419
28610
|
const state = {};
|
|
28420
28611
|
if (!existsSync23(galleryDir)) return state;
|
|
28421
28612
|
try {
|
|
28422
|
-
const entries =
|
|
28613
|
+
const entries = readdirSync15(galleryDir).sort();
|
|
28423
28614
|
for (const entry of entries) {
|
|
28424
28615
|
const entryPath = join25(galleryDir, entry);
|
|
28425
28616
|
const metaFile = join25(entryPath, "meta.json");
|
|
@@ -28470,7 +28661,7 @@ function resolveTemplate(pathname, templatesDir) {
|
|
|
28470
28661
|
const pagesDir = resolve15(templatesDir, TEMPLATE_PAGES_DIR);
|
|
28471
28662
|
if (existsSync23(pagesDir)) {
|
|
28472
28663
|
const scan = (dir, prefix) => {
|
|
28473
|
-
for (const entry of
|
|
28664
|
+
for (const entry of readdirSync15(dir, { withFileTypes: true })) {
|
|
28474
28665
|
if (entry.name.startsWith("_")) continue;
|
|
28475
28666
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
28476
28667
|
if (entry.isDirectory()) {
|
|
@@ -28789,7 +28980,7 @@ function isInjectableHtml(res) {
|
|
|
28789
28980
|
return isHtmlResponse(res) && !res.raw.getHeader("content-encoding");
|
|
28790
28981
|
}
|
|
28791
28982
|
function injectIntoHtml(ctx, devToolbar, html) {
|
|
28792
|
-
if (!devToolbar) return injectFeedbackWidget(ctx.req, html);
|
|
28983
|
+
if (!devToolbar || !devToolbarAllowed(ctx.req)) return injectFeedbackWidget(ctx.req, html);
|
|
28793
28984
|
const toolbarCtx = {
|
|
28794
28985
|
version: TINA4_VERSION,
|
|
28795
28986
|
method: ctx.req.method ?? "GET",
|
|
@@ -29157,7 +29348,7 @@ function wireLocaleGlobal(frondEngine, base) {
|
|
|
29157
29348
|
const localeDir = resolve15(base, process.env.TINA4_LOCALE_DIR ?? "src/locales");
|
|
29158
29349
|
if (!existsSync23(localeDir)) return;
|
|
29159
29350
|
try {
|
|
29160
|
-
const localeFiles =
|
|
29351
|
+
const localeFiles = readdirSync15(localeDir).filter((file) => file.endsWith(".json"));
|
|
29161
29352
|
if (localeFiles.length === 0 || frondEngine.globals?.t) return;
|
|
29162
29353
|
const i18nInstance = new I18n(process.env.TINA4_LOCALE ?? "en", localeDir);
|
|
29163
29354
|
frondEngine.addGlobal("t", (key, params) => i18nInstance.t(key, params));
|
|
@@ -29572,8 +29763,8 @@ var init_server = __esm({
|
|
|
29572
29763
|
init_i18n();
|
|
29573
29764
|
init_background();
|
|
29574
29765
|
init_version();
|
|
29575
|
-
__filename =
|
|
29576
|
-
__dirname =
|
|
29766
|
+
__filename = fileURLToPath7(import.meta.url);
|
|
29767
|
+
__dirname = dirname12(__filename);
|
|
29577
29768
|
BUILTIN_ERROR_TEMPLATES_DIR = resolve15(__dirname, "..", "templates");
|
|
29578
29769
|
BUILTIN_PUBLIC_DIR = resolve15(__dirname, "..", "public");
|
|
29579
29770
|
swaggerAssetsEnabled = false;
|
|
@@ -29735,7 +29926,7 @@ var init_env = __esm({
|
|
|
29735
29926
|
|
|
29736
29927
|
// ../core/src/fakeData.ts
|
|
29737
29928
|
import { randomInt, randomUUID as randomUUID6 } from "node:crypto";
|
|
29738
|
-
import { existsSync as existsSync24, readdirSync as
|
|
29929
|
+
import { existsSync as existsSync24, readdirSync as readdirSync16 } from "node:fs";
|
|
29739
29930
|
import { resolve as resolve16, join as join26 } from "node:path";
|
|
29740
29931
|
function mulberry32(seed) {
|
|
29741
29932
|
let s = seed | 0;
|
|
@@ -30235,7 +30426,7 @@ var init_fakeData = __esm({
|
|
|
30235
30426
|
async seedDir(seedDir) {
|
|
30236
30427
|
const dir = resolve16(seedDir ?? "src/seeds");
|
|
30237
30428
|
if (!existsSync24(dir)) return [];
|
|
30238
|
-
const files =
|
|
30429
|
+
const files = readdirSync16(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".js")).sort();
|
|
30239
30430
|
const executed = [];
|
|
30240
30431
|
for (const file of files) {
|
|
30241
30432
|
const fullPath = join26(dir, file);
|
|
@@ -30341,7 +30532,7 @@ var init_mqttMessage = __esm({
|
|
|
30341
30532
|
});
|
|
30342
30533
|
|
|
30343
30534
|
// ../core/src/mqtt.ts
|
|
30344
|
-
import
|
|
30535
|
+
import net3 from "node:net";
|
|
30345
30536
|
import tls from "node:tls";
|
|
30346
30537
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
30347
30538
|
import { existsSync as existsSync25, readFileSync as readFileSync22 } from "node:fs";
|
|
@@ -30812,7 +31003,7 @@ var init_mqtt = __esm({
|
|
|
30812
31003
|
if (this.tlsVerify && this.caFile) opts.ca = readFileSync22(this.caFile);
|
|
30813
31004
|
sock = tls.connect(opts, () => settle(() => resolve21(sock)));
|
|
30814
31005
|
} else {
|
|
30815
|
-
sock =
|
|
31006
|
+
sock = net3.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve21(sock)));
|
|
30816
31007
|
}
|
|
30817
31008
|
sock.once("error", (err) => {
|
|
30818
31009
|
settle(() => {
|
|
@@ -31029,7 +31220,7 @@ var init_mqtt = __esm({
|
|
|
31029
31220
|
});
|
|
31030
31221
|
|
|
31031
31222
|
// ../core/src/service.ts
|
|
31032
|
-
import { readdirSync as
|
|
31223
|
+
import { readdirSync as readdirSync17, statSync as statSync17, watchFile, unwatchFile } from "node:fs";
|
|
31033
31224
|
import { join as join27, extname as extname7 } from "node:path";
|
|
31034
31225
|
import { pathToFileURL } from "node:url";
|
|
31035
31226
|
function matchCronField(field, value) {
|
|
@@ -31197,7 +31388,7 @@ var init_service = __esm({
|
|
|
31197
31388
|
const discovered = [];
|
|
31198
31389
|
let entries;
|
|
31199
31390
|
try {
|
|
31200
|
-
entries =
|
|
31391
|
+
entries = readdirSync17(dir);
|
|
31201
31392
|
} catch {
|
|
31202
31393
|
return discovered;
|
|
31203
31394
|
}
|
|
@@ -31324,7 +31515,7 @@ var init_service = __esm({
|
|
|
31324
31515
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
31325
31516
|
let entries;
|
|
31326
31517
|
try {
|
|
31327
|
-
entries =
|
|
31518
|
+
entries = readdirSync17(dir);
|
|
31328
31519
|
} catch {
|
|
31329
31520
|
return;
|
|
31330
31521
|
}
|
|
@@ -31431,9 +31622,9 @@ function ipToBytes(ip) {
|
|
|
31431
31622
|
}
|
|
31432
31623
|
function parseCidr(cidr) {
|
|
31433
31624
|
const [netStr, bitsStr] = cidr.split("/");
|
|
31434
|
-
const
|
|
31435
|
-
if (!
|
|
31436
|
-
return { net:
|
|
31625
|
+
const net5 = ipToBytes(netStr);
|
|
31626
|
+
if (!net5) return null;
|
|
31627
|
+
return { net: net5, bits: Number(bitsStr) };
|
|
31437
31628
|
}
|
|
31438
31629
|
function inCidr(packed, cidr) {
|
|
31439
31630
|
if (packed.length !== cidr.net.length) return false;
|
|
@@ -32012,11 +32203,11 @@ var init_api = __esm({
|
|
|
32012
32203
|
path: null
|
|
32013
32204
|
};
|
|
32014
32205
|
}
|
|
32015
|
-
const
|
|
32016
|
-
if (
|
|
32017
|
-
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 };
|
|
32018
32209
|
}
|
|
32019
|
-
const res =
|
|
32210
|
+
const res = net5.res;
|
|
32020
32211
|
const code = res.statusCode ?? null;
|
|
32021
32212
|
const respHeaders = flattenHeaders(res.headers);
|
|
32022
32213
|
if (code === null || code >= 400) {
|
|
@@ -32274,11 +32465,11 @@ var init_api = __esm({
|
|
|
32274
32465
|
if (this.transportFn) {
|
|
32275
32466
|
return this.callTransport(method, url, headers, data);
|
|
32276
32467
|
}
|
|
32277
|
-
const
|
|
32278
|
-
if (
|
|
32279
|
-
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 };
|
|
32280
32471
|
}
|
|
32281
|
-
return this.readResponse(
|
|
32472
|
+
return this.readResponse(net5.res);
|
|
32282
32473
|
}
|
|
32283
32474
|
/**
|
|
32284
32475
|
* Invoke a user-injected transport and normalize its result. The transport
|
|
@@ -32455,7 +32646,7 @@ var init_api = __esm({
|
|
|
32455
32646
|
});
|
|
32456
32647
|
|
|
32457
32648
|
// ../core/src/messenger.ts
|
|
32458
|
-
import
|
|
32649
|
+
import net4 from "node:net";
|
|
32459
32650
|
import tls2 from "node:tls";
|
|
32460
32651
|
import { readFileSync as readFileSync23 } from "node:fs";
|
|
32461
32652
|
import { basename as basename7 } from "node:path";
|
|
@@ -32467,6 +32658,32 @@ function normaliseEncryption(value, kind) {
|
|
|
32467
32658
|
}
|
|
32468
32659
|
return normalised;
|
|
32469
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
|
+
}
|
|
32470
32687
|
function failOnIdle(socket, timeoutMs, what) {
|
|
32471
32688
|
socket.setTimeout(timeoutMs, () => {
|
|
32472
32689
|
socket.destroy(new Error(`${what} timed out after ${timeoutMs / 1e3}s`));
|
|
@@ -32478,7 +32695,7 @@ function connectVerifiedTls(host, port, socket) {
|
|
|
32478
32695
|
host,
|
|
32479
32696
|
port,
|
|
32480
32697
|
socket,
|
|
32481
|
-
servername:
|
|
32698
|
+
servername: net4.isIP(host) ? void 0 : host,
|
|
32482
32699
|
rejectUnauthorized: true
|
|
32483
32700
|
});
|
|
32484
32701
|
return new Promise((resolve21, reject) => {
|
|
@@ -32490,7 +32707,7 @@ function connectVerifiedTls(host, port, socket) {
|
|
|
32490
32707
|
});
|
|
32491
32708
|
}
|
|
32492
32709
|
function connectPlain(host, port) {
|
|
32493
|
-
const socket =
|
|
32710
|
+
const socket = net4.createConnection({ host, port });
|
|
32494
32711
|
return new Promise((resolve21, reject) => {
|
|
32495
32712
|
socket.once("connect", () => {
|
|
32496
32713
|
socket.removeListener("error", reject);
|
|
@@ -32728,7 +32945,7 @@ function decodeTransfer(body, encoding) {
|
|
|
32728
32945
|
}
|
|
32729
32946
|
}
|
|
32730
32947
|
if (enc === "quoted-printable") {
|
|
32731
|
-
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)));
|
|
32732
32949
|
}
|
|
32733
32950
|
return body;
|
|
32734
32951
|
}
|
|
@@ -32845,7 +33062,7 @@ function parseFullMessage(uid, response) {
|
|
|
32845
33062
|
function createMessenger() {
|
|
32846
33063
|
return new Messenger();
|
|
32847
33064
|
}
|
|
32848
|
-
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;
|
|
32849
33066
|
var init_messenger = __esm({
|
|
32850
33067
|
"../core/src/messenger.ts"() {
|
|
32851
33068
|
"use strict";
|
|
@@ -32855,6 +33072,8 @@ var init_messenger = __esm({
|
|
|
32855
33072
|
MAIL_ENCRYPTION_VALUES = ["ssl", "tls", "starttls", "none"];
|
|
32856
33073
|
SMTP_SEND_TIMEOUT_MS = 3e4;
|
|
32857
33074
|
SMTP_CHECK_TIMEOUT_MS = 1e4;
|
|
33075
|
+
_timeoutWarned = /* @__PURE__ */ new Set();
|
|
33076
|
+
DEFAULT_TIMEOUT_SECONDS = 30;
|
|
32858
33077
|
MessengerConnectionError = class extends Error {
|
|
32859
33078
|
constructor(message) {
|
|
32860
33079
|
super(message);
|
|
@@ -32884,6 +33103,8 @@ var init_messenger = __esm({
|
|
|
32884
33103
|
imapUser;
|
|
32885
33104
|
imapPass;
|
|
32886
33105
|
imapEncryption;
|
|
33106
|
+
/** Resolved SMTP send timeout, in milliseconds (see the constructor). */
|
|
33107
|
+
timeoutMs;
|
|
32887
33108
|
constructor(options) {
|
|
32888
33109
|
this.smtpConfigured = Boolean(options?.host ?? process.env.TINA4_MAIL_HOST);
|
|
32889
33110
|
this.host = options?.host ?? process.env.TINA4_MAIL_HOST ?? "localhost";
|
|
@@ -32905,6 +33126,7 @@ var init_messenger = __esm({
|
|
|
32905
33126
|
this.imapUser = options?.imapUser ?? process.env.TINA4_MAIL_IMAP_USERNAME ?? this.username;
|
|
32906
33127
|
this.imapPass = options?.imapPass ?? process.env.TINA4_MAIL_IMAP_PASSWORD ?? this.password;
|
|
32907
33128
|
this.imapEncryption = normaliseEncryption(options?.imapEncryption ?? process.env.TINA4_MAIL_IMAP_ENCRYPTION ?? "tls", "IMAP");
|
|
33129
|
+
this.timeoutMs = resolveSendTimeoutMs(options?.timeout);
|
|
32908
33130
|
}
|
|
32909
33131
|
/**
|
|
32910
33132
|
* How the SMTP connection is secured (ADR-0071 section 1). Port 465 is the
|
|
@@ -33040,7 +33262,7 @@ var init_messenger = __esm({
|
|
|
33040
33262
|
async sendSmtp(options, recipients, messageId) {
|
|
33041
33263
|
let socket = null;
|
|
33042
33264
|
try {
|
|
33043
|
-
socket = await this.openSmtpSession();
|
|
33265
|
+
socket = await this.openSmtpSession(this.timeoutMs);
|
|
33044
33266
|
await this.authenticateSmtp(socket);
|
|
33045
33267
|
await this.sendSmtpEnvelope(socket, recipients.allRecipients);
|
|
33046
33268
|
await this.sendSmtpMessage(socket, options, recipients, messageId);
|
|
@@ -34089,13 +34311,13 @@ var init_htmlElement = __esm({
|
|
|
34089
34311
|
// ../core/src/ai.ts
|
|
34090
34312
|
import { existsSync as existsSync26, mkdirSync as mkdirSync17, writeFileSync as writeFileSync16, readFileSync as readFileSync24 } from "node:fs";
|
|
34091
34313
|
import { homedir } from "node:os";
|
|
34092
|
-
import { join as join28, resolve as resolve17, relative as relative11, dirname as
|
|
34093
|
-
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";
|
|
34094
34316
|
import { execSync, execFileSync as execFileSync4 } from "node:child_process";
|
|
34095
34317
|
import { createInterface } from "node:readline";
|
|
34096
34318
|
function readVersion() {
|
|
34097
34319
|
try {
|
|
34098
|
-
const thisDir =
|
|
34320
|
+
const thisDir = dirname13(fileURLToPath8(import.meta.url));
|
|
34099
34321
|
const rootPkg = resolve17(thisDir, "..", "..", "..", "package.json");
|
|
34100
34322
|
const pkg = JSON.parse(readFileSync24(rootPkg, "utf-8"));
|
|
34101
34323
|
return pkg.version ?? "0.0.0";
|
|
@@ -34311,7 +34533,7 @@ function replaceMarkerBlock(existing, block, start2, end) {
|
|
|
34311
34533
|
}
|
|
34312
34534
|
function looksLikeOldFrameworkInstall(existing) {
|
|
34313
34535
|
const head = existing.replace(/^\s+/, "").slice(0, 400);
|
|
34314
|
-
return OLD_FRAMEWORK_HEADERS.some((
|
|
34536
|
+
return OLD_FRAMEWORK_HEADERS.some((h2) => head.startsWith(h2));
|
|
34315
34537
|
}
|
|
34316
34538
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
34317
34539
|
const block = skillBlock(contextFile);
|
|
@@ -34341,7 +34563,7 @@ function installForTool(root, tool, context) {
|
|
|
34341
34563
|
if (tool.configDir) {
|
|
34342
34564
|
mkdirSync17(join28(root, tool.configDir), { recursive: true });
|
|
34343
34565
|
}
|
|
34344
|
-
const parentDir =
|
|
34566
|
+
const parentDir = dirname13(contextPath);
|
|
34345
34567
|
mkdirSync17(parentDir, { recursive: true });
|
|
34346
34568
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
34347
34569
|
const rel = relative11(root, contextPath);
|
|
@@ -34718,7 +34940,7 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
34718
34940
|
}
|
|
34719
34941
|
function generateClaudeCodeContext() {
|
|
34720
34942
|
try {
|
|
34721
|
-
const thisDir =
|
|
34943
|
+
const thisDir = dirname13(fileURLToPath8(import.meta.url));
|
|
34722
34944
|
const repoRoot = resolve17(thisDir, "..", "..", "..");
|
|
34723
34945
|
const claudeMdPath = join28(repoRoot, "CLAUDE.md");
|
|
34724
34946
|
if (existsSync26(claudeMdPath)) {
|
|
@@ -35566,8 +35788,8 @@ var init_aiClient = __esm({
|
|
|
35566
35788
|
static retryDelay(headers, deadline) {
|
|
35567
35789
|
const value = Array.isArray(headers["retry-after"]) ? headers["retry-after"][0] : headers["retry-after"];
|
|
35568
35790
|
const requested = value !== void 0 && Number.isFinite(Number(value)) ? Math.max(0, Number(value) * 1e3) : 100;
|
|
35569
|
-
const
|
|
35570
|
-
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));
|
|
35571
35793
|
}
|
|
35572
35794
|
static async requestJson(config, headers, body) {
|
|
35573
35795
|
const deadline = performance.now() + config.totalTimeout * 1e3;
|
|
@@ -38622,7 +38844,7 @@ __export(sqlite_exports, {
|
|
|
38622
38844
|
});
|
|
38623
38845
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
38624
38846
|
import { mkdirSync as mkdirSync18 } from "node:fs";
|
|
38625
|
-
import { dirname as
|
|
38847
|
+
import { dirname as dirname14, isAbsolute as isAbsolute5, join as join29, resolve as resolve18, sep as sep7 } from "node:path";
|
|
38626
38848
|
function isIdentifier(str) {
|
|
38627
38849
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
38628
38850
|
}
|
|
@@ -38659,7 +38881,7 @@ function resolveSqlitePath(dbPath) {
|
|
|
38659
38881
|
}
|
|
38660
38882
|
const cwd = resolve18(process.cwd());
|
|
38661
38883
|
const path10 = join29(cwd, dbPath);
|
|
38662
|
-
const parent =
|
|
38884
|
+
const parent = dirname14(path10);
|
|
38663
38885
|
const absParent = resolve18(parent);
|
|
38664
38886
|
if (absParent !== cwd && !absParent.startsWith(cwd + sep7)) {
|
|
38665
38887
|
throw new Error(
|
|
@@ -41088,9 +41310,75 @@ __export(mongodb_exports, {
|
|
|
41088
41310
|
function isMatchAllWhere(where) {
|
|
41089
41311
|
return where != null && MATCH_ALL_WHERE.test(where);
|
|
41090
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
|
+
}
|
|
41091
41379
|
function parseWhereClause(where, params, paramOffset = 0) {
|
|
41092
41380
|
const filter = {};
|
|
41093
|
-
const parts = where
|
|
41381
|
+
const parts = splitOnAnd(where);
|
|
41094
41382
|
let paramIndex = paramOffset;
|
|
41095
41383
|
for (const part of parts) {
|
|
41096
41384
|
const trimmed = part.trim();
|
|
@@ -41163,27 +41451,57 @@ function requireWriteFilter(filter, operation, table2) {
|
|
|
41163
41451
|
);
|
|
41164
41452
|
}
|
|
41165
41453
|
}
|
|
41166
|
-
function
|
|
41167
|
-
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);
|
|
41168
|
-
if (!match) return null;
|
|
41169
|
-
const [, cols, collection, whereClause, orderBy, limitStr, skipStr] = match;
|
|
41454
|
+
function buildProjection(cols) {
|
|
41170
41455
|
const projection = {};
|
|
41171
|
-
if (cols
|
|
41172
|
-
|
|
41173
|
-
|
|
41174
|
-
|
|
41175
|
-
|
|
41176
|
-
|
|
41177
|
-
|
|
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) {
|
|
41178
41476
|
const sort = {};
|
|
41179
|
-
|
|
41180
|
-
|
|
41181
|
-
|
|
41182
|
-
|
|
41183
|
-
|
|
41184
|
-
sort[col] = desc ? -1 : 1;
|
|
41185
|
-
}
|
|
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;
|
|
41186
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 : {};
|
|
41187
41505
|
return {
|
|
41188
41506
|
type: "find",
|
|
41189
41507
|
collection,
|
|
@@ -41191,7 +41509,7 @@ function parseSelect(sql, params) {
|
|
|
41191
41509
|
projection: Object.keys(projection).length > 0 ? projection : void 0,
|
|
41192
41510
|
limit: limitStr ? parseInt(limitStr, 10) : void 0,
|
|
41193
41511
|
skip: skipStr ? parseInt(skipStr, 10) : void 0,
|
|
41194
|
-
sort: orderBy ?
|
|
41512
|
+
sort: orderBy ? parseOrderBy(orderBy) : void 0
|
|
41195
41513
|
};
|
|
41196
41514
|
}
|
|
41197
41515
|
function parseInsert(sql, params) {
|
|
@@ -41227,20 +41545,33 @@ function parseSetClause(setClause, params) {
|
|
|
41227
41545
|
return { document, consumed };
|
|
41228
41546
|
}
|
|
41229
41547
|
function parseUpdate(sql, params) {
|
|
41230
|
-
const
|
|
41231
|
-
if (!
|
|
41232
|
-
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();
|
|
41233
41558
|
const set = parseSetClause(setClause, params);
|
|
41234
|
-
const matchAll = isMatchAllWhere(whereClause
|
|
41235
|
-
const filter = whereClause ? parseWhereClause(whereClause
|
|
41559
|
+
const matchAll = isMatchAllWhere(whereClause);
|
|
41560
|
+
const filter = whereClause ? parseWhereClause(whereClause, params, set.consumed).filter : {};
|
|
41236
41561
|
return { type: "updateMany", collection, filter, update: { $set: set.document }, matchAll };
|
|
41237
41562
|
}
|
|
41238
41563
|
function parseDelete(sql, params) {
|
|
41239
|
-
const
|
|
41240
|
-
if (!
|
|
41241
|
-
const
|
|
41242
|
-
|
|
41243
|
-
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 : {};
|
|
41244
41575
|
return { type: "deleteMany", collection, filter, matchAll };
|
|
41245
41576
|
}
|
|
41246
41577
|
function parseCreate(sql) {
|
|
@@ -41248,10 +41579,23 @@ function parseCreate(sql) {
|
|
|
41248
41579
|
return match ? { type: "raw", collection: match[1] } : null;
|
|
41249
41580
|
}
|
|
41250
41581
|
function parseCount(sql, params) {
|
|
41251
|
-
const
|
|
41252
|
-
if (!
|
|
41253
|
-
const
|
|
41254
|
-
|
|
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 : {};
|
|
41255
41599
|
return { type: "aggregate", collection, pipeline: [{ $match: filter }, { $count: alias }] };
|
|
41256
41600
|
}
|
|
41257
41601
|
function parseSql(sql, params = []) {
|
|
@@ -41277,12 +41621,13 @@ function fieldTypeToJsonSchema(def) {
|
|
|
41277
41621
|
return { bsonType: "string" };
|
|
41278
41622
|
}
|
|
41279
41623
|
}
|
|
41280
|
-
var MATCH_ALL_WHERE, MongodbAdapter;
|
|
41624
|
+
var MATCH_ALL_WHERE, QUOTE_CHARS, MongodbAdapter;
|
|
41281
41625
|
var init_mongodb = __esm({
|
|
41282
41626
|
"src/adapters/mongodb.ts"() {
|
|
41283
41627
|
"use strict";
|
|
41284
41628
|
init_connectTimeout();
|
|
41285
41629
|
MATCH_ALL_WHERE = /^\s*1\s*=\s*1\s*$/;
|
|
41630
|
+
QUOTE_CHARS = `"'`;
|
|
41286
41631
|
MongodbAdapter = class {
|
|
41287
41632
|
constructor(config) {
|
|
41288
41633
|
this.config = config;
|
|
@@ -43606,13 +43951,13 @@ var init_database = __esm({
|
|
|
43606
43951
|
});
|
|
43607
43952
|
|
|
43608
43953
|
// src/model.ts
|
|
43609
|
-
import { readdirSync as
|
|
43954
|
+
import { readdirSync as readdirSync18, statSync as statSync18 } from "node:fs";
|
|
43610
43955
|
import { join as join30, extname as extname8 } from "node:path";
|
|
43611
43956
|
async function discoverModels(modelsDir) {
|
|
43612
43957
|
const models = [];
|
|
43613
43958
|
let files;
|
|
43614
43959
|
try {
|
|
43615
|
-
files =
|
|
43960
|
+
files = readdirSync18(modelsDir);
|
|
43616
43961
|
} catch {
|
|
43617
43962
|
return models;
|
|
43618
43963
|
}
|
|
@@ -43659,8 +44004,10 @@ var init_model = __esm({
|
|
|
43659
44004
|
});
|
|
43660
44005
|
|
|
43661
44006
|
// src/migration.ts
|
|
43662
|
-
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";
|
|
43663
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";
|
|
43664
44011
|
function unwrapAdapter(db) {
|
|
43665
44012
|
let cur = db;
|
|
43666
44013
|
while (cur && cur.constructor?.name === "CachedDatabaseAdapter" && cur.adapter) {
|
|
@@ -44139,17 +44486,113 @@ function warnUnprefixedMigrations(files) {
|
|
|
44139
44486
|
);
|
|
44140
44487
|
}
|
|
44141
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
|
+
}
|
|
44142
44577
|
async function migrate(adapter, options) {
|
|
44143
44578
|
const db = adapter ?? getAdapter();
|
|
44144
44579
|
const dir = resolve19(options?.migrationsDir ?? "migrations");
|
|
44145
44580
|
const delimiter2 = options?.delimiter ?? ";";
|
|
44146
|
-
const result = { applied: [], skipped: [], failed: [] };
|
|
44147
44581
|
if (!existsSync27(dir)) {
|
|
44148
|
-
return
|
|
44582
|
+
return { applied: [], skipped: [], failed: [] };
|
|
44149
44583
|
}
|
|
44584
|
+
const lock = await acquireMigrationLock(db, dir);
|
|
44585
|
+
try {
|
|
44586
|
+
return await runPendingMigrations(db, dir, delimiter2);
|
|
44587
|
+
} finally {
|
|
44588
|
+
await releaseMigrationLock(db, lock);
|
|
44589
|
+
}
|
|
44590
|
+
}
|
|
44591
|
+
async function runPendingMigrations(db, dir, delimiter2) {
|
|
44592
|
+
const result = { applied: [], skipped: [], failed: [] };
|
|
44150
44593
|
await ensureMigrationTableOn(db);
|
|
44151
44594
|
const files = sortMigrationFiles(
|
|
44152
|
-
|
|
44595
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44153
44596
|
);
|
|
44154
44597
|
if (files.length === 0) return result;
|
|
44155
44598
|
warnUnprefixedMigrations(files);
|
|
@@ -44222,14 +44665,14 @@ async function status(adapter, options) {
|
|
|
44222
44665
|
}
|
|
44223
44666
|
if (!await adapterTableExists(db, MIGRATION_TABLE)) {
|
|
44224
44667
|
const files2 = sortMigrationFiles(
|
|
44225
|
-
|
|
44668
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44226
44669
|
);
|
|
44227
44670
|
result.pending = files2;
|
|
44228
44671
|
return result;
|
|
44229
44672
|
}
|
|
44230
44673
|
await ensureMigrationTableOn(db);
|
|
44231
44674
|
const files = sortMigrationFiles(
|
|
44232
|
-
|
|
44675
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44233
44676
|
);
|
|
44234
44677
|
const appliedNames = /* @__PURE__ */ new Set();
|
|
44235
44678
|
try {
|
|
@@ -44329,7 +44772,7 @@ export class ${className} {
|
|
|
44329
44772
|
writeFileSync17(filePath, content, "utf-8");
|
|
44330
44773
|
return filePath;
|
|
44331
44774
|
}
|
|
44332
|
-
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;
|
|
44333
44776
|
var init_migration = __esm({
|
|
44334
44777
|
"src/migration.ts"() {
|
|
44335
44778
|
"use strict";
|
|
@@ -44354,6 +44797,12 @@ var init_migration = __esm({
|
|
|
44354
44797
|
};
|
|
44355
44798
|
SMART_QUOTE_RE = new RegExp(`[${Object.keys(SMART_QUOTES).join("")}]`, "g");
|
|
44356
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));
|
|
44357
44806
|
Migration = class {
|
|
44358
44807
|
db;
|
|
44359
44808
|
dir;
|
|
@@ -44410,7 +44859,7 @@ var init_migration = __esm({
|
|
|
44410
44859
|
const dir = resolve19(this.dir);
|
|
44411
44860
|
if (!existsSync27(dir)) return [];
|
|
44412
44861
|
return sortMigrationFiles(
|
|
44413
|
-
|
|
44862
|
+
readdirSync19(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
44414
44863
|
);
|
|
44415
44864
|
}
|
|
44416
44865
|
};
|
|
@@ -44418,17 +44867,40 @@ var init_migration = __esm({
|
|
|
44418
44867
|
});
|
|
44419
44868
|
|
|
44420
44869
|
// src/query.ts
|
|
44421
|
-
function resolveFieldColumn(fields,
|
|
44422
|
-
const field = resolveField(fields,
|
|
44423
|
-
return field === null ? null :
|
|
44870
|
+
function resolveFieldColumn(fields, columnOf2, key) {
|
|
44871
|
+
const field = resolveField(fields, columnOf2, key);
|
|
44872
|
+
return field === null ? null : columnOf2(field);
|
|
44424
44873
|
}
|
|
44425
|
-
function resolveField(fields,
|
|
44874
|
+
function resolveField(fields, columnOf2, key) {
|
|
44426
44875
|
if (Object.hasOwn(fields, key)) return key;
|
|
44427
44876
|
for (const field of Object.keys(fields)) {
|
|
44428
|
-
if (
|
|
44877
|
+
if (columnOf2(field) === key) return field;
|
|
44429
44878
|
}
|
|
44430
44879
|
return null;
|
|
44431
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
|
+
}
|
|
44432
44904
|
function buildQuery(tableName, options, extraConditions, quote = quoteIdentifierAnsi, resolveColumn = () => null) {
|
|
44433
44905
|
const conditions = [];
|
|
44434
44906
|
const params = [];
|
|
@@ -44456,19 +44928,9 @@ function buildQuery(tableName, options, extraConditions, quote = quoteIdentifier
|
|
|
44456
44928
|
}
|
|
44457
44929
|
}
|
|
44458
44930
|
}
|
|
44931
|
+
applySearch(options, conditions, params, quote);
|
|
44459
44932
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
44460
|
-
|
|
44461
|
-
if (options.sort) {
|
|
44462
|
-
const parts = [];
|
|
44463
|
-
for (const part of options.sort.split(",")) {
|
|
44464
|
-
const trimmed = part.trim();
|
|
44465
|
-
if (trimmed === "") continue;
|
|
44466
|
-
const descending = trimmed.startsWith("-");
|
|
44467
|
-
const col = column2("sort", descending ? trimmed.slice(1) : trimmed);
|
|
44468
|
-
parts.push(`${col} ${descending ? "DESC" : "ASC"}`);
|
|
44469
|
-
}
|
|
44470
|
-
if (parts.length > 0) orderClause = `ORDER BY ${parts.join(", ")}`;
|
|
44471
|
-
}
|
|
44933
|
+
const orderClause = buildOrderClause(options, column2);
|
|
44472
44934
|
const limit = Math.min(options.limit ?? DEFAULT_ROW_CAP, DEFAULT_ROW_CAP);
|
|
44473
44935
|
const page = Math.max(options.page ?? 1, 1);
|
|
44474
44936
|
const offset = (page - 1) * limit;
|
|
@@ -44486,32 +44948,41 @@ function buildQuery(tableName, options, extraConditions, quote = quoteIdentifier
|
|
|
44486
44948
|
filterParams: params
|
|
44487
44949
|
};
|
|
44488
44950
|
}
|
|
44489
|
-
function
|
|
44490
|
-
const options = {};
|
|
44951
|
+
function parseFilterParams(query) {
|
|
44491
44952
|
const filter = /* @__PURE__ */ Object.create(null);
|
|
44492
44953
|
for (const [key, value] of Object.entries(query)) {
|
|
44493
44954
|
if (key.startsWith("sort[")) {
|
|
44494
44955
|
throw new InvalidQueryParameterError("Query parameter 'sort' must be a single comma-separated string");
|
|
44495
44956
|
}
|
|
44496
44957
|
if (!key.startsWith("filter[")) continue;
|
|
44497
|
-
|
|
44498
|
-
|
|
44499
|
-
|
|
44500
|
-
|
|
44501
|
-
|
|
44502
|
-
|
|
44503
|
-
|
|
44504
|
-
|
|
44505
|
-
|
|
44506
|
-
|
|
44507
|
-
|
|
44508
|
-
|
|
44509
|
-
|
|
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;
|
|
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] = {};
|
|
44510
44974
|
}
|
|
44975
|
+
filter[field][operator] = value;
|
|
44976
|
+
}
|
|
44977
|
+
function parseQueryString(query) {
|
|
44978
|
+
const options = {};
|
|
44979
|
+
const filter = parseFilterParams(query);
|
|
44511
44980
|
if (Object.keys(filter).length > 0) {
|
|
44512
44981
|
options.filter = filter;
|
|
44513
44982
|
}
|
|
44514
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;
|
|
44515
44986
|
if (query.page) options.page = parseInt(query.page, 10);
|
|
44516
44987
|
if (query.limit) options.limit = parseInt(query.limit, 10);
|
|
44517
44988
|
if (query.offset !== void 0) {
|
|
@@ -44647,12 +45118,12 @@ var init_validation = __esm({
|
|
|
44647
45118
|
});
|
|
44648
45119
|
|
|
44649
45120
|
// src/autoCrud.ts
|
|
44650
|
-
function allowListedBody(fields,
|
|
45121
|
+
function allowListedBody(fields, columnOf2, pkField, body, isCreate) {
|
|
44651
45122
|
const pkDef = fields[pkField];
|
|
44652
45123
|
const stripPk = isCreate ? pkDef?.autoIncrement === true : true;
|
|
44653
45124
|
const allowed = {};
|
|
44654
45125
|
for (const [key, value] of Object.entries(body ?? {})) {
|
|
44655
|
-
const field = resolveField(fields,
|
|
45126
|
+
const field = resolveField(fields, columnOf2, key);
|
|
44656
45127
|
if (field === null) continue;
|
|
44657
45128
|
if (field === "is_deleted") continue;
|
|
44658
45129
|
if (stripPk && field === pkField) continue;
|
|
@@ -44696,6 +45167,9 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
44696
45167
|
let built;
|
|
44697
45168
|
try {
|
|
44698
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
|
+
}
|
|
44699
45173
|
built = buildQuery(tableName, qp, extraConditions, q, (key) => resolveFieldColumn(fields, getDbCol, key));
|
|
44700
45174
|
} catch (err) {
|
|
44701
45175
|
if (err instanceof UnknownFieldError) {
|
|
@@ -44943,6 +45417,567 @@ var init_autoCrud = __esm({
|
|
|
44943
45417
|
}
|
|
44944
45418
|
});
|
|
44945
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
|
+
|
|
44946
45981
|
// src/queryBuilder.ts
|
|
44947
45982
|
var QueryBuilder;
|
|
44948
45983
|
var init_queryBuilder = __esm({
|
|
@@ -47295,7 +48330,7 @@ var init_seeder = __esm({
|
|
|
47295
48330
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
47296
48331
|
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
47297
48332
|
import { mkdirSync as mkdirSync20 } from "node:fs";
|
|
47298
|
-
import { dirname as
|
|
48333
|
+
import { dirname as dirname16, isAbsolute as isAbsolute6, join as join32 } from "node:path";
|
|
47299
48334
|
function iso(d) {
|
|
47300
48335
|
return d.toISOString();
|
|
47301
48336
|
}
|
|
@@ -47566,7 +48601,7 @@ function resolveStorePath(dbPath) {
|
|
|
47566
48601
|
let path10 = dbPath;
|
|
47567
48602
|
if (!isAbsolute6(path10)) {
|
|
47568
48603
|
path10 = join32(process.cwd(), path10);
|
|
47569
|
-
mkdirSync20(
|
|
48604
|
+
mkdirSync20(dirname16(path10), { recursive: true });
|
|
47570
48605
|
}
|
|
47571
48606
|
return path10;
|
|
47572
48607
|
}
|
|
@@ -49316,7 +50351,9 @@ __export(index_exports, {
|
|
|
49316
50351
|
AutoCrud: () => AutoCrud,
|
|
49317
50352
|
BaseModel: () => BaseModel,
|
|
49318
50353
|
CONNECT_TIMEOUT_TOLERANCE_MS: () => CONNECT_TIMEOUT_TOLERANCE_MS,
|
|
50354
|
+
CRUD: () => CRUD,
|
|
49319
50355
|
CachedDatabaseAdapter: () => CachedDatabaseAdapter,
|
|
50356
|
+
Crud: () => Crud,
|
|
49320
50357
|
Cursor: () => Cursor,
|
|
49321
50358
|
DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
|
|
49322
50359
|
DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS: () => DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
|
|
@@ -49363,6 +50400,7 @@ __export(index_exports, {
|
|
|
49363
50400
|
SqliteCollection: () => SqliteCollection,
|
|
49364
50401
|
SqliteDatabase: () => SqliteDatabase,
|
|
49365
50402
|
UnknownFieldError: () => UnknownFieldError,
|
|
50403
|
+
_resetCrudRegistrations: () => _resetCrudRegistrations,
|
|
49366
50404
|
adapterColumns: () => adapterColumns,
|
|
49367
50405
|
adapterCommit: () => adapterCommit,
|
|
49368
50406
|
adapterCreateTable: () => adapterCreateTable,
|
|
@@ -49395,6 +50433,8 @@ __export(index_exports, {
|
|
|
49395
50433
|
ensureMigrationTable: () => ensureMigrationTable,
|
|
49396
50434
|
extractLastInsertId: () => extractLastInsertId,
|
|
49397
50435
|
generateCrudRoutes: () => generateCrudRoutes,
|
|
50436
|
+
generateForm: () => generateForm2,
|
|
50437
|
+
generateTable: () => generateTable,
|
|
49398
50438
|
getAdapter: () => getAdapter,
|
|
49399
50439
|
getAppliedMigrations: () => getAppliedMigrations,
|
|
49400
50440
|
getCollection: () => getCollection,
|
|
@@ -49414,10 +50454,13 @@ __export(index_exports, {
|
|
|
49414
50454
|
realtime: () => realtime,
|
|
49415
50455
|
recordMigration: () => recordMigration,
|
|
49416
50456
|
redactCredentials: () => redactCredentials,
|
|
50457
|
+
registerBackend: () => registerBackend,
|
|
49417
50458
|
removeMigrationRecord: () => removeMigrationRecord,
|
|
49418
50459
|
resetDefaultStore: () => resetDefaultStore,
|
|
49419
50460
|
resetRequestCaches: () => resetRequestCaches2,
|
|
49420
50461
|
resolveDbPool: () => resolveDbPool,
|
|
50462
|
+
resolveField: () => resolveField,
|
|
50463
|
+
resolveFieldColumn: () => resolveFieldColumn,
|
|
49421
50464
|
resolveFirebirdCharset: () => resolveFirebirdCharset,
|
|
49422
50465
|
resolveGraphConnectTimeout: () => resolveGraphConnectTimeout,
|
|
49423
50466
|
resolveSqlitePath: () => resolveSqlitePath,
|
|
@@ -49435,8 +50478,10 @@ __export(index_exports, {
|
|
|
49435
50478
|
splitStatements: () => splitStatements,
|
|
49436
50479
|
status: () => status,
|
|
49437
50480
|
storageKey: () => storageKey,
|
|
50481
|
+
stripOrderAndLimit: () => stripOrderAndLimit,
|
|
49438
50482
|
stripTrailingSemicolons: () => stripTrailingSemicolons,
|
|
49439
50483
|
syncModels: () => syncModels,
|
|
50484
|
+
toCrud: () => toCrud,
|
|
49440
50485
|
validate: () => validate,
|
|
49441
50486
|
withConnectTimeout: () => withConnectTimeout,
|
|
49442
50487
|
wrapWithCache: () => wrapWithCache
|
|
@@ -49452,6 +50497,7 @@ var init_index = __esm({
|
|
|
49452
50497
|
init_model();
|
|
49453
50498
|
init_migration();
|
|
49454
50499
|
init_autoCrud();
|
|
50500
|
+
init_crud();
|
|
49455
50501
|
init_query();
|
|
49456
50502
|
init_validation();
|
|
49457
50503
|
init_baseModel();
|
|
@@ -49483,7 +50529,9 @@ export {
|
|
|
49483
50529
|
AutoCrud,
|
|
49484
50530
|
BaseModel,
|
|
49485
50531
|
CONNECT_TIMEOUT_TOLERANCE_MS,
|
|
50532
|
+
CRUD,
|
|
49486
50533
|
CachedDatabaseAdapter,
|
|
50534
|
+
Crud,
|
|
49487
50535
|
Cursor,
|
|
49488
50536
|
DEFAULT_DATABASE_CONNECT_TIMEOUT_SECONDS,
|
|
49489
50537
|
DEFAULT_GRAPH_CONNECT_TIMEOUT_SECONDS,
|
|
@@ -49530,6 +50578,7 @@ export {
|
|
|
49530
50578
|
SqliteCollection,
|
|
49531
50579
|
SqliteDatabase,
|
|
49532
50580
|
UnknownFieldError,
|
|
50581
|
+
_resetCrudRegistrations,
|
|
49533
50582
|
adapterColumns,
|
|
49534
50583
|
adapterCommit,
|
|
49535
50584
|
adapterCreateTable,
|
|
@@ -49562,6 +50611,8 @@ export {
|
|
|
49562
50611
|
ensureMigrationTable,
|
|
49563
50612
|
extractLastInsertId,
|
|
49564
50613
|
generateCrudRoutes,
|
|
50614
|
+
generateForm2 as generateForm,
|
|
50615
|
+
generateTable,
|
|
49565
50616
|
getAdapter,
|
|
49566
50617
|
getAppliedMigrations,
|
|
49567
50618
|
getCollection,
|
|
@@ -49581,10 +50632,13 @@ export {
|
|
|
49581
50632
|
realtime,
|
|
49582
50633
|
recordMigration,
|
|
49583
50634
|
redactCredentials,
|
|
50635
|
+
registerBackend,
|
|
49584
50636
|
removeMigrationRecord,
|
|
49585
50637
|
resetDefaultStore,
|
|
49586
50638
|
resetRequestCaches2 as resetRequestCaches,
|
|
49587
50639
|
resolveDbPool,
|
|
50640
|
+
resolveField,
|
|
50641
|
+
resolveFieldColumn,
|
|
49588
50642
|
resolveFirebirdCharset,
|
|
49589
50643
|
resolveGraphConnectTimeout,
|
|
49590
50644
|
resolveSqlitePath,
|
|
@@ -49602,8 +50656,10 @@ export {
|
|
|
49602
50656
|
splitStatements,
|
|
49603
50657
|
status,
|
|
49604
50658
|
storageKey,
|
|
50659
|
+
stripOrderAndLimit,
|
|
49605
50660
|
stripTrailingSemicolons,
|
|
49606
50661
|
syncModels,
|
|
50662
|
+
toCrud,
|
|
49607
50663
|
validate,
|
|
49608
50664
|
withConnectTimeout,
|
|
49609
50665
|
wrapWithCache
|