tina4-nodejs 3.13.130 → 3.13.132
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 +3 -3
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +247 -185
- package/packages/core/dist/index.js +248 -185
- package/packages/core/public/js/tina4-dev-admin.min.js +148 -512
- package/packages/core/src/devAdmin.ts +0 -46
- package/packages/core/src/index.ts +1 -1
- package/packages/core/src/server.ts +129 -3
- package/packages/frond/dist/index.js +52 -104
- package/packages/frond/src/engine.ts +52 -100
- package/packages/orm/dist/index.js +248 -185
- package/packages/orm/src/baseModel.ts +64 -55
- package/packages/orm/src/index.ts +2 -0
- package/packages/orm/src/modelCollection.ts +149 -0
- package/types/core/src/index.d.ts +1 -1
- package/types/core/src/server.d.ts +15 -0
- package/types/frond/src/engine.d.ts +11 -0
- package/types/orm/src/baseModel.d.ts +20 -5
- package/types/orm/src/index.d.ts +2 -0
- package/types/orm/src/modelCollection.d.ts +101 -0
|
@@ -262,6 +262,99 @@ var init_databaseResult = __esm({
|
|
|
262
262
|
}
|
|
263
263
|
});
|
|
264
264
|
|
|
265
|
+
// src/modelCollection.ts
|
|
266
|
+
var ModelCollection;
|
|
267
|
+
var init_modelCollection = __esm({
|
|
268
|
+
"src/modelCollection.ts"() {
|
|
269
|
+
"use strict";
|
|
270
|
+
ModelCollection = class extends Array {
|
|
271
|
+
/** Total rows matching the query's filter (ignores limit/offset). */
|
|
272
|
+
_total = 0;
|
|
273
|
+
/** The SQL limit that produced this page. */
|
|
274
|
+
_limit = 0;
|
|
275
|
+
/** The SQL offset that produced this page. */
|
|
276
|
+
_offset = 0;
|
|
277
|
+
/**
|
|
278
|
+
* Derived array operations (`map`, `filter`, `slice`, spread, …) build a plain
|
|
279
|
+
* `Array`, never another `ModelCollection`. This is what defuses the Array-
|
|
280
|
+
* subclass constructor/species trap: the engine never calls this constructor
|
|
281
|
+
* with a length during those operations.
|
|
282
|
+
*/
|
|
283
|
+
static get [Symbol.species]() {
|
|
284
|
+
return Array;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* @param items the page of hydrated model instances (or the length, when the
|
|
288
|
+
* engine constructs a derived array — defended against here).
|
|
289
|
+
* @param total total rows matching the query's filter (ignores limit/offset).
|
|
290
|
+
* @param limit the SQL limit that produced this page.
|
|
291
|
+
* @param offset the SQL offset that produced this page.
|
|
292
|
+
*/
|
|
293
|
+
constructor(items, total = 0, limit = 0, offset = 0) {
|
|
294
|
+
super();
|
|
295
|
+
if (typeof items === "number") {
|
|
296
|
+
this.length = items;
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (items) {
|
|
300
|
+
for (let i = 0; i < items.length; i++) {
|
|
301
|
+
this[i] = items[i];
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
this._total = Math.trunc(total) || 0;
|
|
305
|
+
this._limit = Math.trunc(limit) || 0;
|
|
306
|
+
this._offset = Math.trunc(offset) || 0;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Total rows matching the query's filter, ignoring limit/offset.
|
|
310
|
+
*
|
|
311
|
+
* This is the whole point of the collection: the page slice you are iterating
|
|
312
|
+
* is capped by `limit`, but this number is the full count of matching rows —
|
|
313
|
+
* what a pager needs to render "page 3 of 13".
|
|
314
|
+
*/
|
|
315
|
+
getTotalRecords() {
|
|
316
|
+
return this._total;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The canonical pagination envelope — seven snake_case keys, identical to
|
|
320
|
+
* `DatabaseResult.toPaginate()` (ADR-0043) and to the other three frameworks'
|
|
321
|
+
* `toPaginate()` / `to_paginate()`.
|
|
322
|
+
*
|
|
323
|
+
* records the page's rows as plain objects (never re-sliced)
|
|
324
|
+
* total getTotalRecords() — the true total for the filter
|
|
325
|
+
* page floor(offset / per_page) + 1
|
|
326
|
+
* per_page the query's limit
|
|
327
|
+
* total_pages ceil(total / per_page)
|
|
328
|
+
* limit the SQL limit actually applied
|
|
329
|
+
* offset the SQL offset actually applied
|
|
330
|
+
*
|
|
331
|
+
* `records` are model dicts (via `toDict()`, the same serialisation the
|
|
332
|
+
* framework applies to a model in a JSON response), so the JSON a client sees
|
|
333
|
+
* matches `DatabaseResult` exactly — the result is uniform whether the route
|
|
334
|
+
* returned a raw `db.fetch()` or an ORM query.
|
|
335
|
+
*/
|
|
336
|
+
toPaginate() {
|
|
337
|
+
const perPage = this._limit > 0 ? this._limit : this.length;
|
|
338
|
+
const page = perPage > 0 ? Math.floor(this._offset / perPage) + 1 : 1;
|
|
339
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this._total / perPage)) : 1;
|
|
340
|
+
const records = this.map((model) => {
|
|
341
|
+
const m = model;
|
|
342
|
+
return m && typeof m.toDict === "function" ? m.toDict() : model;
|
|
343
|
+
});
|
|
344
|
+
return {
|
|
345
|
+
records,
|
|
346
|
+
total: this._total,
|
|
347
|
+
page,
|
|
348
|
+
per_page: perPage,
|
|
349
|
+
total_pages: totalPages,
|
|
350
|
+
limit: perPage,
|
|
351
|
+
offset: this._offset
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
265
358
|
// src/databaseUrl.ts
|
|
266
359
|
import { inspect } from "node:util";
|
|
267
360
|
function stripOneSlash(path8) {
|
|
@@ -5414,33 +5507,17 @@ var init_engine = __esm({
|
|
|
5414
5507
|
i++;
|
|
5415
5508
|
}
|
|
5416
5509
|
}
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
let depth2 = 0;
|
|
5429
|
-
while (i2 < tokens.length) {
|
|
5430
|
-
if (tokens[i2][0] === "BLOCK") {
|
|
5431
|
-
const [tagContent] = stripTag(tokens[i2][1]);
|
|
5432
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
5433
|
-
if (tag === "cache") depth2++;
|
|
5434
|
-
else if (tag === "endcache") {
|
|
5435
|
-
if (depth2 === 0) return [htmlContent, i2 + 1];
|
|
5436
|
-
depth2--;
|
|
5437
|
-
}
|
|
5438
|
-
}
|
|
5439
|
-
i2++;
|
|
5440
|
-
}
|
|
5441
|
-
return [htmlContent, i2];
|
|
5442
|
-
}
|
|
5443
|
-
}
|
|
5510
|
+
/**
|
|
5511
|
+
* Collect the body tokens of a {% <openTag> %}...{% end<openTag> %} block,
|
|
5512
|
+
* starting from the token after the opening tag (start + 1). Nested same-tag
|
|
5513
|
+
* blocks are kept in the body and balanced by depth; the matching closing tag is
|
|
5514
|
+
* consumed but NOT included. Returns [bodyTokens, indexAfterClosingTag].
|
|
5515
|
+
*
|
|
5516
|
+
* canNest guards the open-tag count: handleSetBlock passes it so the inline
|
|
5517
|
+
* {% set x = 1 %} form (which has no {% endset %}) never opens a nested block —
|
|
5518
|
+
* only the block form {% set x %} nests. Omitted, every openTag occurrence nests.
|
|
5519
|
+
*/
|
|
5520
|
+
collectBlockBody(tokens, start2, openTag, closeTag, canNest) {
|
|
5444
5521
|
const bodyTokens = [];
|
|
5445
5522
|
let i = start2 + 1;
|
|
5446
5523
|
let depth = 0;
|
|
@@ -5448,10 +5525,10 @@ var init_engine = __esm({
|
|
|
5448
5525
|
if (tokens[i][0] === "BLOCK") {
|
|
5449
5526
|
const [tagContent] = stripTag(tokens[i][1]);
|
|
5450
5527
|
const tag = tagContent.split(/\s+/)[0] || "";
|
|
5451
|
-
if (tag ===
|
|
5528
|
+
if (tag === openTag && (canNest ? canNest(tagContent) : true)) {
|
|
5452
5529
|
depth++;
|
|
5453
5530
|
bodyTokens.push(tokens[i]);
|
|
5454
|
-
} else if (tag ===
|
|
5531
|
+
} else if (tag === closeTag) {
|
|
5455
5532
|
if (depth === 0) {
|
|
5456
5533
|
i++;
|
|
5457
5534
|
break;
|
|
@@ -5466,6 +5543,36 @@ var init_engine = __esm({
|
|
|
5466
5543
|
}
|
|
5467
5544
|
i++;
|
|
5468
5545
|
}
|
|
5546
|
+
return [bodyTokens, i];
|
|
5547
|
+
}
|
|
5548
|
+
handleCache(tokens, start2, context) {
|
|
5549
|
+
const [content] = stripTag(tokens[start2][1]);
|
|
5550
|
+
const m = content.match(/^cache\s+["'](.+?)["']\s*(\d+)?/);
|
|
5551
|
+
const cacheKey = m ? m[1] : "default";
|
|
5552
|
+
const ttl = m && m[2] ? parseInt(m[2], 10) : 60;
|
|
5553
|
+
sweepExpiredCache(this.fragmentCache);
|
|
5554
|
+
const cached = this.fragmentCache.get(cacheKey);
|
|
5555
|
+
if (cached) {
|
|
5556
|
+
const [htmlContent, expiresAt] = cached;
|
|
5557
|
+
if (Date.now() < expiresAt) {
|
|
5558
|
+
let i2 = start2 + 1;
|
|
5559
|
+
let depth = 0;
|
|
5560
|
+
while (i2 < tokens.length) {
|
|
5561
|
+
if (tokens[i2][0] === "BLOCK") {
|
|
5562
|
+
const [tagContent] = stripTag(tokens[i2][1]);
|
|
5563
|
+
const tag = tagContent.split(/\s+/)[0] || "";
|
|
5564
|
+
if (tag === "cache") depth++;
|
|
5565
|
+
else if (tag === "endcache") {
|
|
5566
|
+
if (depth === 0) return [htmlContent, i2 + 1];
|
|
5567
|
+
depth--;
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
i2++;
|
|
5571
|
+
}
|
|
5572
|
+
return [htmlContent, i2];
|
|
5573
|
+
}
|
|
5574
|
+
}
|
|
5575
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "cache", "endcache");
|
|
5469
5576
|
const rendered = this.renderTokens([...bodyTokens], context);
|
|
5470
5577
|
capCache(this.fragmentCache, TEMPLATE_CACHE_MAX);
|
|
5471
5578
|
this.fragmentCache.set(cacheKey, [rendered, Date.now() + ttl * 1e3]);
|
|
@@ -5630,62 +5737,20 @@ var init_engine = __esm({
|
|
|
5630
5737
|
handleSetBlock(tokens, start2, context) {
|
|
5631
5738
|
const [content] = stripTag(tokens[start2][1]);
|
|
5632
5739
|
const name = (content.split(/\s+/)[1] || "").trim();
|
|
5633
|
-
const bodyTokens =
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
if (tag === "set" && !tagContent.includes("=")) {
|
|
5641
|
-
depth++;
|
|
5642
|
-
bodyTokens.push(tokens[i]);
|
|
5643
|
-
} else if (tag === "endset") {
|
|
5644
|
-
if (depth === 0) {
|
|
5645
|
-
i++;
|
|
5646
|
-
break;
|
|
5647
|
-
}
|
|
5648
|
-
depth--;
|
|
5649
|
-
bodyTokens.push(tokens[i]);
|
|
5650
|
-
} else {
|
|
5651
|
-
bodyTokens.push(tokens[i]);
|
|
5652
|
-
}
|
|
5653
|
-
} else {
|
|
5654
|
-
bodyTokens.push(tokens[i]);
|
|
5655
|
-
}
|
|
5656
|
-
i++;
|
|
5657
|
-
}
|
|
5740
|
+
const [bodyTokens, i] = this.collectBlockBody(
|
|
5741
|
+
tokens,
|
|
5742
|
+
start2,
|
|
5743
|
+
"set",
|
|
5744
|
+
"endset",
|
|
5745
|
+
(tagContent) => !tagContent.includes("=")
|
|
5746
|
+
);
|
|
5658
5747
|
if (name) {
|
|
5659
5748
|
context[name] = new SafeString(this.renderTokens([...bodyTokens], context));
|
|
5660
5749
|
}
|
|
5661
5750
|
return i;
|
|
5662
5751
|
}
|
|
5663
5752
|
handleSpaceless(tokens, start2, context) {
|
|
5664
|
-
const bodyTokens =
|
|
5665
|
-
let i = start2 + 1;
|
|
5666
|
-
let depth = 0;
|
|
5667
|
-
while (i < tokens.length) {
|
|
5668
|
-
if (tokens[i][0] === "BLOCK") {
|
|
5669
|
-
const [tagContent] = stripTag(tokens[i][1]);
|
|
5670
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
5671
|
-
if (tag === "spaceless") {
|
|
5672
|
-
depth++;
|
|
5673
|
-
bodyTokens.push(tokens[i]);
|
|
5674
|
-
} else if (tag === "endspaceless") {
|
|
5675
|
-
if (depth === 0) {
|
|
5676
|
-
i++;
|
|
5677
|
-
break;
|
|
5678
|
-
}
|
|
5679
|
-
depth--;
|
|
5680
|
-
bodyTokens.push(tokens[i]);
|
|
5681
|
-
} else {
|
|
5682
|
-
bodyTokens.push(tokens[i]);
|
|
5683
|
-
}
|
|
5684
|
-
} else {
|
|
5685
|
-
bodyTokens.push(tokens[i]);
|
|
5686
|
-
}
|
|
5687
|
-
i++;
|
|
5688
|
-
}
|
|
5753
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "spaceless", "endspaceless");
|
|
5689
5754
|
let rendered = this.renderTokens([...bodyTokens], context);
|
|
5690
5755
|
rendered = rendered.replace(/>\s+</g, "><");
|
|
5691
5756
|
return [rendered, i];
|
|
@@ -5694,31 +5759,7 @@ var init_engine = __esm({
|
|
|
5694
5759
|
const [content] = stripTag(tokens[start2][1]);
|
|
5695
5760
|
const modeMatch = content.match(/^autoescape\s+(false|true)/);
|
|
5696
5761
|
const autoEscapeOn = !(modeMatch && modeMatch[1] === "false");
|
|
5697
|
-
const bodyTokens =
|
|
5698
|
-
let i = start2 + 1;
|
|
5699
|
-
let depth = 0;
|
|
5700
|
-
while (i < tokens.length) {
|
|
5701
|
-
if (tokens[i][0] === "BLOCK") {
|
|
5702
|
-
const [tagContent] = stripTag(tokens[i][1]);
|
|
5703
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
5704
|
-
if (tag === "autoescape") {
|
|
5705
|
-
depth++;
|
|
5706
|
-
bodyTokens.push(tokens[i]);
|
|
5707
|
-
} else if (tag === "endautoescape") {
|
|
5708
|
-
if (depth === 0) {
|
|
5709
|
-
i++;
|
|
5710
|
-
break;
|
|
5711
|
-
}
|
|
5712
|
-
depth--;
|
|
5713
|
-
bodyTokens.push(tokens[i]);
|
|
5714
|
-
} else {
|
|
5715
|
-
bodyTokens.push(tokens[i]);
|
|
5716
|
-
}
|
|
5717
|
-
} else {
|
|
5718
|
-
bodyTokens.push(tokens[i]);
|
|
5719
|
-
}
|
|
5720
|
-
i++;
|
|
5721
|
-
}
|
|
5762
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "autoescape", "endautoescape");
|
|
5722
5763
|
if (!autoEscapeOn) {
|
|
5723
5764
|
const oldAutoEscape = this._autoEscape;
|
|
5724
5765
|
this._autoEscape = false;
|
|
@@ -23511,7 +23552,7 @@ function toolbarJs() {
|
|
|
23511
23552
|
connect();
|
|
23512
23553
|
})();`;
|
|
23513
23554
|
}
|
|
23514
|
-
var cpuCount, DEV_SAFE_METHODS, 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,
|
|
23555
|
+
var cpuCount, DEV_SAFE_METHODS, 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;
|
|
23515
23556
|
var init_devAdmin = __esm({
|
|
23516
23557
|
"../core/src/devAdmin.ts"() {
|
|
23517
23558
|
"use strict";
|
|
@@ -23849,9 +23890,6 @@ var init_devAdmin = __esm({
|
|
|
23849
23890
|
{ method: "POST", pattern: "/__dev/api/websockets/disconnect", handler: handleWebsocketsDisconnect },
|
|
23850
23891
|
// Tools
|
|
23851
23892
|
{ method: "POST", pattern: "/__dev/api/tool", handler: handleTool },
|
|
23852
|
-
// Chat — proxies to Rust agent /chat (SSE passthrough). Forwards
|
|
23853
|
-
// active_file and any other body keys verbatim. See proxyToSupervisor.
|
|
23854
|
-
{ method: "POST", pattern: "/__dev/api/chat", handler: handleChat },
|
|
23855
23893
|
// Threads — proxies to Rust agent /threads. Mirrors Python's
|
|
23856
23894
|
// _api_threads + _api_threads_sub.
|
|
23857
23895
|
{ method: "GET", pattern: "/__dev/api/threads", handler: handleThreads },
|
|
@@ -23930,8 +23968,6 @@ var init_devAdmin = __esm({
|
|
|
23930
23968
|
{ method: "GET", pattern: "/__dev/api/supervise/diff", handler: handleSuperviseStub },
|
|
23931
23969
|
{ method: "POST", pattern: "/__dev/api/supervise/commit", handler: handleSuperviseStub },
|
|
23932
23970
|
{ method: "POST", pattern: "/__dev/api/supervise/cancel", handler: handleSuperviseStub },
|
|
23933
|
-
// Execute — proxies to the framework_port+2000 Rust agent (SSE passthrough)
|
|
23934
|
-
{ method: "POST", pattern: "/__dev/api/execute", handler: handleExecute },
|
|
23935
23971
|
// Framework-grounding MCP token config — self-contained (.env upsert)
|
|
23936
23972
|
{ method: "GET", pattern: "/__dev/api/grounding/status", handler: handleGroundingStatus },
|
|
23937
23973
|
{ method: "POST", pattern: "/__dev/api/grounding/token", handler: handleGroundingToken },
|
|
@@ -24501,9 +24537,6 @@ var init_devAdmin = __esm({
|
|
|
24501
24537
|
}
|
|
24502
24538
|
res.json({ tool, status: "executed", message: `Tool '${tool}' executed (stub)`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
24503
24539
|
};
|
|
24504
|
-
handleChat = async (req2, res) => {
|
|
24505
|
-
await proxyToSupervisor(req2, res, "/chat");
|
|
24506
|
-
};
|
|
24507
24540
|
DEFAULT_MCP_URL = "https://mcp.tina4.com";
|
|
24508
24541
|
handleGroundingStatus = async (_req, res) => {
|
|
24509
24542
|
const token = resolveDevEnvVar("TINA4_MCP_TOKEN");
|
|
@@ -24791,35 +24824,6 @@ var init_devAdmin = __esm({
|
|
|
24791
24824
|
501
|
|
24792
24825
|
);
|
|
24793
24826
|
};
|
|
24794
|
-
handleExecute = async (req2, res) => {
|
|
24795
|
-
const port = parseInt(process.env.TINA4_PORT ?? process.env.PORT ?? "7148", 10);
|
|
24796
|
-
const agentUrl = `http://127.0.0.1:${port + 2e3}/execute`;
|
|
24797
|
-
try {
|
|
24798
|
-
const upstream = await fetch(agentUrl, {
|
|
24799
|
-
method: "POST",
|
|
24800
|
-
headers: { "Content-Type": "application/json" },
|
|
24801
|
-
body: JSON.stringify(req2.body ?? {})
|
|
24802
|
-
});
|
|
24803
|
-
if (!upstream.body) {
|
|
24804
|
-
res.json({ error: "agent returned no body" }, 502);
|
|
24805
|
-
return;
|
|
24806
|
-
}
|
|
24807
|
-
res.raw.writeHead(upstream.status || 200, {
|
|
24808
|
-
"Content-Type": upstream.headers.get("content-type") || "text/event-stream",
|
|
24809
|
-
"Cache-Control": "no-cache",
|
|
24810
|
-
Connection: "keep-alive"
|
|
24811
|
-
});
|
|
24812
|
-
const reader = upstream.body.getReader();
|
|
24813
|
-
while (true) {
|
|
24814
|
-
const { done, value } = await reader.read();
|
|
24815
|
-
if (done) break;
|
|
24816
|
-
if (value) res.raw.write(Buffer.from(value));
|
|
24817
|
-
}
|
|
24818
|
-
res.raw.end();
|
|
24819
|
-
} catch (e) {
|
|
24820
|
-
res.json({ error: `agent unreachable at ${agentUrl}: ${e.message}` }, 502);
|
|
24821
|
-
}
|
|
24822
|
-
};
|
|
24823
24827
|
DEV_FILES_IGNORED = /* @__PURE__ */ new Set([
|
|
24824
24828
|
"__pycache__",
|
|
24825
24829
|
"node_modules",
|
|
@@ -27297,7 +27301,9 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
27297
27301
|
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
27298
27302
|
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
27299
27303
|
// suppressReload flag.
|
|
27300
|
-
|
|
27304
|
+
// Also suppress on the dev-admin dashboard (any /__dev page): its SPA reloads
|
|
27305
|
+
// itself gently, so the toolbar's full-page reloader must not fire there.
|
|
27306
|
+
reload: !ctx.isAiPortRequest && !ctx.pathname.startsWith("/__dev")
|
|
27301
27307
|
};
|
|
27302
27308
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
27303
27309
|
}
|
|
@@ -27552,6 +27558,51 @@ async function runDispatch(ctx, rawReq, rawRes) {
|
|
|
27552
27558
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
27553
27559
|
return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
|
|
27554
27560
|
}
|
|
27561
|
+
function loopbackBindHosts(host) {
|
|
27562
|
+
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
27563
|
+
switch (normalized) {
|
|
27564
|
+
case "localhost":
|
|
27565
|
+
return ["127.0.0.1", "::1"];
|
|
27566
|
+
case "127.0.0.1":
|
|
27567
|
+
case "0.0.0.0":
|
|
27568
|
+
return ["::1"];
|
|
27569
|
+
case "::1":
|
|
27570
|
+
case "::":
|
|
27571
|
+
return ["127.0.0.1"];
|
|
27572
|
+
default:
|
|
27573
|
+
return [];
|
|
27574
|
+
}
|
|
27575
|
+
}
|
|
27576
|
+
function startLoopbackSiblings(port, host, dispatch) {
|
|
27577
|
+
const siblingHosts = loopbackBindHosts(host);
|
|
27578
|
+
if (siblingHosts.length === 0) return Promise.resolve([]);
|
|
27579
|
+
return Promise.all(
|
|
27580
|
+
siblingHosts.map(
|
|
27581
|
+
(siblingHost) => new Promise((resolveSibling) => {
|
|
27582
|
+
const sibling = createServer2(dispatch);
|
|
27583
|
+
let settled = false;
|
|
27584
|
+
sibling.on("error", (err) => {
|
|
27585
|
+
const expected = err.code === "EADDRINUSE" || err.code === "EADDRNOTAVAIL" || err.code === "EAFNOSUPPORT";
|
|
27586
|
+
if (!expected) {
|
|
27587
|
+
Log.debug(
|
|
27588
|
+
`Loopback sibling ${siblingHost}:${port} not bound (${err.code ?? err.message}) \u2014 skipped, primary already serves`
|
|
27589
|
+
);
|
|
27590
|
+
}
|
|
27591
|
+
if (!settled) {
|
|
27592
|
+
settled = true;
|
|
27593
|
+
resolveSibling(null);
|
|
27594
|
+
}
|
|
27595
|
+
});
|
|
27596
|
+
sibling.listen(port, siblingHost, () => {
|
|
27597
|
+
settled = true;
|
|
27598
|
+
resolveSibling(sibling);
|
|
27599
|
+
});
|
|
27600
|
+
})
|
|
27601
|
+
)
|
|
27602
|
+
).then(
|
|
27603
|
+
(results) => results.filter((s) => s !== null)
|
|
27604
|
+
);
|
|
27605
|
+
}
|
|
27555
27606
|
async function startServer(config) {
|
|
27556
27607
|
loadEnv(".env.local");
|
|
27557
27608
|
loadEnv();
|
|
@@ -27820,8 +27871,9 @@ ${reset2}
|
|
|
27820
27871
|
}
|
|
27821
27872
|
});
|
|
27822
27873
|
return new Promise((resolvePromise) => {
|
|
27823
|
-
server.listen(port, host, () => {
|
|
27874
|
+
server.listen(port, host, async () => {
|
|
27824
27875
|
if (!cluster.isWorker) writePidfile(port);
|
|
27876
|
+
const siblingServers = await startLoopbackSiblings(port, host, dispatch);
|
|
27825
27877
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
27826
27878
|
const isDebug = isTruthy(process.env.TINA4_DEBUG);
|
|
27827
27879
|
const logLevel = (process.env.TINA4_LOG_LEVEL ?? "DEBUG").toUpperCase();
|
|
@@ -27884,14 +27936,22 @@ ${reset2}
|
|
|
27884
27936
|
}
|
|
27885
27937
|
let shuttingDown = false;
|
|
27886
27938
|
const closeListeners = () => new Promise((done) => {
|
|
27887
|
-
let pending = aiServer ?
|
|
27939
|
+
let pending = 1 + (aiServer ? 1 : 0) + siblingServers.length;
|
|
27888
27940
|
const one = () => {
|
|
27889
27941
|
if (--pending === 0) done();
|
|
27890
27942
|
};
|
|
27891
27943
|
server.close(one);
|
|
27892
27944
|
if (aiServer) aiServer.close(one);
|
|
27945
|
+
for (const sibling of siblingServers) {
|
|
27946
|
+
try {
|
|
27947
|
+
sibling.close(one);
|
|
27948
|
+
} catch {
|
|
27949
|
+
one();
|
|
27950
|
+
}
|
|
27951
|
+
}
|
|
27893
27952
|
server.closeIdleConnections();
|
|
27894
27953
|
aiServer?.closeIdleConnections();
|
|
27954
|
+
for (const sibling of siblingServers) sibling.closeIdleConnections();
|
|
27895
27955
|
});
|
|
27896
27956
|
const gracefulShutdown = async (signal) => {
|
|
27897
27957
|
if (shuttingDown) return;
|
|
@@ -27919,6 +27979,7 @@ ${reset2}
|
|
|
27919
27979
|
);
|
|
27920
27980
|
server.closeAllConnections();
|
|
27921
27981
|
aiServer?.closeAllConnections();
|
|
27982
|
+
for (const sibling of siblingServers) sibling.closeAllConnections();
|
|
27922
27983
|
}
|
|
27923
27984
|
try {
|
|
27924
27985
|
const orm = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
@@ -27946,6 +28007,12 @@ ${reset2}
|
|
|
27946
28007
|
stopAllBackgroundTasks();
|
|
27947
28008
|
if (aiServer) aiServer.close();
|
|
27948
28009
|
server.close();
|
|
28010
|
+
for (const sibling of siblingServers) {
|
|
28011
|
+
try {
|
|
28012
|
+
sibling.close();
|
|
28013
|
+
} catch {
|
|
28014
|
+
}
|
|
28015
|
+
}
|
|
27949
28016
|
Promise.resolve().then(() => (init_index(), index_exports)).then((orm) => orm.closeDatabase()).catch(() => {
|
|
27950
28017
|
});
|
|
27951
28018
|
},
|
|
@@ -35662,6 +35729,7 @@ __export(src_exports2, {
|
|
|
35662
35729
|
isValidSessionId: () => isValidSessionId,
|
|
35663
35730
|
kafkaSecurityConfig: () => kafkaSecurityConfig,
|
|
35664
35731
|
loadEnv: () => loadEnv,
|
|
35732
|
+
loopbackBindHosts: () => loopbackBindHosts,
|
|
35665
35733
|
makeCaseInsensitiveHeaders: () => makeCaseInsensitiveHeaders,
|
|
35666
35734
|
matchCronField: () => matchCronField,
|
|
35667
35735
|
matchesCron: () => matchesCron,
|
|
@@ -43053,6 +43121,7 @@ var init_baseModel = __esm({
|
|
|
43053
43121
|
"src/baseModel.ts"() {
|
|
43054
43122
|
"use strict";
|
|
43055
43123
|
init_database();
|
|
43124
|
+
init_modelCollection();
|
|
43056
43125
|
init_validation();
|
|
43057
43126
|
init_queryBuilder();
|
|
43058
43127
|
init_sqlite();
|
|
@@ -43288,6 +43357,30 @@ var init_baseModel = __esm({
|
|
|
43288
43357
|
static getPkColumn() {
|
|
43289
43358
|
return this.getDbColumn(this.getPkField());
|
|
43290
43359
|
}
|
|
43360
|
+
/**
|
|
43361
|
+
* Shared read tail for the collection-returning finders (where / all / select
|
|
43362
|
+
* / find filter-form / withTrashed). Runs the SAME two calls `db.fetch()`
|
|
43363
|
+
* makes — the page fetch AND the COUNT probe over the SAME base SQL — hydrates
|
|
43364
|
+
* the rows into model instances, and returns a ModelCollection carrying the
|
|
43365
|
+
* total (ADR-0064).
|
|
43366
|
+
*
|
|
43367
|
+
* The total is FREE: `probeTotal` is the exact `COUNT(*)` probe `db.fetch()`
|
|
43368
|
+
* already runs; the ORM used to discard it. ZERO extra queries beyond that one
|
|
43369
|
+
* probe. `sql` MUST NOT carry its own LIMIT/OFFSET — `adapterFetch` applies
|
|
43370
|
+
* limit/offset to the page, and the probe wraps the un-limited SQL so it counts
|
|
43371
|
+
* the WHOLE filtered set, not the page.
|
|
43372
|
+
*/
|
|
43373
|
+
static async _collect(sql, params, limit, offset, include) {
|
|
43374
|
+
const db = this.getDb();
|
|
43375
|
+
const rows = await adapterFetch(db, sql, params, limit, offset);
|
|
43376
|
+
const data = Array.isArray(rows) ? rows : rows?.data ?? [];
|
|
43377
|
+
const total = await probeTotal(db, sql, params, limit) ?? data.length;
|
|
43378
|
+
const instances = data.map((row) => new this(row));
|
|
43379
|
+
if (include) {
|
|
43380
|
+
await this._eagerLoad(instances, include);
|
|
43381
|
+
}
|
|
43382
|
+
return new ModelCollection(instances, total, limit, offset);
|
|
43383
|
+
}
|
|
43291
43384
|
/**
|
|
43292
43385
|
* Find a record by primary key.
|
|
43293
43386
|
* @param id Primary key value.
|
|
@@ -43340,7 +43433,6 @@ var init_baseModel = __esm({
|
|
|
43340
43433
|
);
|
|
43341
43434
|
}
|
|
43342
43435
|
const lim = typeof limit === "number" ? limit : 100;
|
|
43343
|
-
const db = ModelClass.getDb();
|
|
43344
43436
|
const conditions = [];
|
|
43345
43437
|
const params = [];
|
|
43346
43438
|
if (filter) {
|
|
@@ -43360,16 +43452,7 @@ var init_baseModel = __esm({
|
|
|
43360
43452
|
if (orderBy) {
|
|
43361
43453
|
sql += ` ORDER BY ${orderBy}`;
|
|
43362
43454
|
}
|
|
43363
|
-
|
|
43364
|
-
const data = rows?.data ?? rows;
|
|
43365
|
-
const instances = (Array.isArray(data) ? data : []).map((row) => {
|
|
43366
|
-
const inst = new this(row);
|
|
43367
|
-
return inst;
|
|
43368
|
-
});
|
|
43369
|
-
if (include) {
|
|
43370
|
-
await ModelClass._eagerLoad(instances, include);
|
|
43371
|
-
}
|
|
43372
|
-
return instances;
|
|
43455
|
+
return ModelClass._collect(sql, params, lim, offset, include);
|
|
43373
43456
|
}
|
|
43374
43457
|
/**
|
|
43375
43458
|
* Load a record into this instance via selectOne.
|
|
@@ -43437,7 +43520,6 @@ var init_baseModel = __esm({
|
|
|
43437
43520
|
*/
|
|
43438
43521
|
static async all(limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
43439
43522
|
const ModelClass = this;
|
|
43440
|
-
const db = ModelClass.getDb();
|
|
43441
43523
|
const conditions = [];
|
|
43442
43524
|
if (ModelClass.softDelete) {
|
|
43443
43525
|
conditions.push("is_deleted = 0");
|
|
@@ -43447,13 +43529,8 @@ var init_baseModel = __esm({
|
|
|
43447
43529
|
}
|
|
43448
43530
|
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
43449
43531
|
const orderClause = orderBy ? ` ORDER BY ${orderBy}` : "";
|
|
43450
|
-
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}
|
|
43451
|
-
|
|
43452
|
-
const instances = rows.map((row) => new ModelClass(row));
|
|
43453
|
-
if (include) {
|
|
43454
|
-
await ModelClass._eagerLoad(instances, include);
|
|
43455
|
-
}
|
|
43456
|
-
return instances;
|
|
43532
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}`;
|
|
43533
|
+
return ModelClass._collect(sql, [], limit, offset, include);
|
|
43457
43534
|
}
|
|
43458
43535
|
/**
|
|
43459
43536
|
* Query records with a WHERE clause.
|
|
@@ -43468,7 +43545,6 @@ var init_baseModel = __esm({
|
|
|
43468
43545
|
*/
|
|
43469
43546
|
static async where(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
43470
43547
|
const ModelClass = this;
|
|
43471
|
-
const db = ModelClass.getDb();
|
|
43472
43548
|
const parts = [];
|
|
43473
43549
|
if (ModelClass.softDelete) {
|
|
43474
43550
|
parts.push("is_deleted = 0");
|
|
@@ -43478,13 +43554,8 @@ var init_baseModel = __esm({
|
|
|
43478
43554
|
}
|
|
43479
43555
|
parts.push(`(${conditions})`);
|
|
43480
43556
|
const orderClause = orderBy ? ` ORDER BY ${orderBy}` : "";
|
|
43481
|
-
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}
|
|
43482
|
-
|
|
43483
|
-
const instances = rows.map((row) => new ModelClass(row));
|
|
43484
|
-
if (include) {
|
|
43485
|
-
await ModelClass._eagerLoad(instances, include);
|
|
43486
|
-
}
|
|
43487
|
-
return instances;
|
|
43557
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}`;
|
|
43558
|
+
return ModelClass._collect(sql, params, limit, offset, include);
|
|
43488
43559
|
}
|
|
43489
43560
|
/**
|
|
43490
43561
|
* Save this instance (insert or update). Returns this on success (fluent
|
|
@@ -43964,10 +44035,7 @@ var init_baseModel = __esm({
|
|
|
43964
44035
|
*/
|
|
43965
44036
|
static async select(sql, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
43966
44037
|
const ModelClass = this;
|
|
43967
|
-
|
|
43968
|
-
const paged = SQLTranslator.appendLimit(sql, limit, offset);
|
|
43969
|
-
const rows = await adapterQuery(db, paged, params);
|
|
43970
|
-
return rows.map((row) => new ModelClass(row));
|
|
44038
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
43971
44039
|
}
|
|
43972
44040
|
static async selectOne(sql, params, include) {
|
|
43973
44041
|
const ModelClass = this;
|
|
@@ -44039,7 +44107,6 @@ var init_baseModel = __esm({
|
|
|
44039
44107
|
*/
|
|
44040
44108
|
static async withTrashed(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
44041
44109
|
const ModelClass = this;
|
|
44042
|
-
const db = ModelClass.getDb();
|
|
44043
44110
|
const parts = [];
|
|
44044
44111
|
if (ModelClass.tableFilter) {
|
|
44045
44112
|
parts.push(ModelClass.tableFilter);
|
|
@@ -44051,14 +44118,7 @@ var init_baseModel = __esm({
|
|
|
44051
44118
|
if (parts.length > 0) {
|
|
44052
44119
|
sql += ` WHERE ${parts.join(" AND ")}`;
|
|
44053
44120
|
}
|
|
44054
|
-
|
|
44055
|
-
sql += ` LIMIT ${limit}`;
|
|
44056
|
-
}
|
|
44057
|
-
if (offset !== void 0) {
|
|
44058
|
-
sql += ` OFFSET ${offset}`;
|
|
44059
|
-
}
|
|
44060
|
-
const rows = await adapterQuery(db, sql, params);
|
|
44061
|
-
return rows.map((row) => new ModelClass(row));
|
|
44121
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
44062
44122
|
}
|
|
44063
44123
|
/**
|
|
44064
44124
|
* Count records matching conditions (respects soft delete and table filter).
|
|
@@ -46866,6 +46926,7 @@ __export(index_exports, {
|
|
|
46866
46926
|
InvalidId: () => InvalidId,
|
|
46867
46927
|
LocalStorage: () => LocalStorage,
|
|
46868
46928
|
Migration: () => Migration,
|
|
46929
|
+
ModelCollection: () => ModelCollection,
|
|
46869
46930
|
MongodbAdapter: () => MongodbAdapter,
|
|
46870
46931
|
MssqlAdapter: () => MssqlAdapter,
|
|
46871
46932
|
MysqlAdapter: () => MysqlAdapter,
|
|
@@ -46969,6 +47030,7 @@ var init_index = __esm({
|
|
|
46969
47030
|
"src/index.ts"() {
|
|
46970
47031
|
init_types();
|
|
46971
47032
|
init_databaseResult();
|
|
47033
|
+
init_modelCollection();
|
|
46972
47034
|
init_database();
|
|
46973
47035
|
init_database();
|
|
46974
47036
|
init_databaseUrl();
|
|
@@ -47028,6 +47090,7 @@ export {
|
|
|
47028
47090
|
InvalidId,
|
|
47029
47091
|
LocalStorage,
|
|
47030
47092
|
Migration,
|
|
47093
|
+
ModelCollection,
|
|
47031
47094
|
MongodbAdapter,
|
|
47032
47095
|
MssqlAdapter,
|
|
47033
47096
|
MysqlAdapter,
|