tina4-nodejs 3.13.131 → 3.13.133
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 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +398 -302
- package/packages/core/dist/index.js +399 -302
- 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 +399 -302
- 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/packages/swagger/dist/index.js +150 -116
- package/packages/swagger/src/generator.ts +261 -154
- 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",
|
|
@@ -25884,12 +25888,46 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
25884
25888
|
});
|
|
25885
25889
|
}
|
|
25886
25890
|
function generate2(routes, models = []) {
|
|
25891
|
+
const schemes = resolveSecuritySchemes();
|
|
25892
|
+
const spec = {
|
|
25893
|
+
openapi: resolveOpenApiVersion(),
|
|
25894
|
+
info: buildInfo(),
|
|
25895
|
+
servers: resolveServers(),
|
|
25896
|
+
paths: {},
|
|
25897
|
+
components: {
|
|
25898
|
+
schemas: {},
|
|
25899
|
+
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
25900
|
+
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
25901
|
+
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
25902
|
+
securitySchemes: schemes
|
|
25903
|
+
}
|
|
25904
|
+
};
|
|
25905
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
25906
|
+
buildComponentSchemas(models, spec, tableToSchema);
|
|
25907
|
+
const ctx = {
|
|
25908
|
+
models,
|
|
25909
|
+
tableToSchema,
|
|
25910
|
+
schemes,
|
|
25911
|
+
// Default scheme secured routes use when no explicit meta.security is set.
|
|
25912
|
+
defaultScheme: process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth",
|
|
25913
|
+
// Path filters (comma-separated raw-path prefixes).
|
|
25914
|
+
includePrefixes: csv(process.env.TINA4_SWAGGER_INCLUDE),
|
|
25915
|
+
excludePrefixes: csv(process.env.TINA4_SWAGGER_EXCLUDE),
|
|
25916
|
+
// Reusable custom schemas referenced by routes via meta.requestSchema/responseSchemas.
|
|
25917
|
+
refSchemas: /* @__PURE__ */ new Set(),
|
|
25918
|
+
usedTags: [],
|
|
25919
|
+
seenIds: /* @__PURE__ */ new Set()
|
|
25920
|
+
};
|
|
25921
|
+
for (const route of routes) {
|
|
25922
|
+
buildOperation(route, spec, ctx);
|
|
25923
|
+
}
|
|
25924
|
+
buildRefSchemas(spec, ctx.refSchemas);
|
|
25925
|
+
buildTags(spec, ctx.usedTags);
|
|
25926
|
+
return spec;
|
|
25927
|
+
}
|
|
25928
|
+
function buildInfo() {
|
|
25887
25929
|
const info = {
|
|
25888
25930
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
25889
|
-
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
25890
|
-
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
25891
|
-
// Both are the settled cross-framework defaults (parity with the Python
|
|
25892
|
-
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
25893
25931
|
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
25894
25932
|
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
25895
25933
|
};
|
|
@@ -25906,135 +25944,134 @@ function generate2(routes, models = []) {
|
|
|
25906
25944
|
const [name, url] = licenseRaw.split("|").map((s) => s.trim());
|
|
25907
25945
|
info.license = url ? { name, url } : { name };
|
|
25908
25946
|
}
|
|
25909
|
-
|
|
25910
|
-
|
|
25911
|
-
|
|
25912
|
-
info,
|
|
25913
|
-
servers: resolveServers(),
|
|
25914
|
-
paths: {},
|
|
25915
|
-
components: {
|
|
25916
|
-
schemas: {},
|
|
25917
|
-
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
25918
|
-
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
25919
|
-
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
25920
|
-
securitySchemes: schemes
|
|
25921
|
-
}
|
|
25922
|
-
};
|
|
25923
|
-
const defaultScheme = process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth";
|
|
25924
|
-
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
25925
|
-
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
25926
|
-
const refSchemas = /* @__PURE__ */ new Set();
|
|
25927
|
-
const tableToSchema = /* @__PURE__ */ new Map();
|
|
25947
|
+
return info;
|
|
25948
|
+
}
|
|
25949
|
+
function buildComponentSchemas(models, spec, tableToSchema) {
|
|
25928
25950
|
for (const model of models) {
|
|
25929
25951
|
const schemaKey = schemaNameForModel(model);
|
|
25930
25952
|
tableToSchema.set(model.tableName, schemaKey);
|
|
25931
25953
|
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
25932
25954
|
}
|
|
25933
|
-
|
|
25934
|
-
|
|
25935
|
-
|
|
25936
|
-
|
|
25937
|
-
|
|
25938
|
-
|
|
25939
|
-
|
|
25940
|
-
|
|
25941
|
-
|
|
25942
|
-
|
|
25943
|
-
|
|
25944
|
-
|
|
25945
|
-
|
|
25946
|
-
|
|
25947
|
-
|
|
25948
|
-
|
|
25949
|
-
|
|
25950
|
-
|
|
25951
|
-
"200": { description: "Successful response" }
|
|
25952
|
-
}
|
|
25953
|
-
};
|
|
25954
|
-
if (route.meta?.description) operation.description = route.meta.description;
|
|
25955
|
-
if (route.meta?.deprecated) operation.deprecated = true;
|
|
25956
|
-
const pathParams = extractPathParams(route.pattern);
|
|
25957
|
-
if (pathParams.length > 0) {
|
|
25958
|
-
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
25959
|
-
name,
|
|
25960
|
-
in: "path",
|
|
25961
|
-
required: true,
|
|
25962
|
-
schema
|
|
25963
|
-
}));
|
|
25955
|
+
}
|
|
25956
|
+
function buildOperation(route, spec, ctx) {
|
|
25957
|
+
if (!isIncludedPath(route.pattern, ctx.includePrefixes, ctx.excludePrefixes)) return;
|
|
25958
|
+
const openApiPath = patternToOpenAPI(route.pattern);
|
|
25959
|
+
const method = route.method.toLowerCase();
|
|
25960
|
+
if (!spec.paths[openApiPath]) {
|
|
25961
|
+
spec.paths[openApiPath] = {};
|
|
25962
|
+
}
|
|
25963
|
+
const tags = route.meta?.tags ?? inferTags(route.pattern);
|
|
25964
|
+
for (const t of tags) {
|
|
25965
|
+
if (!ctx.usedTags.includes(t)) ctx.usedTags.push(t);
|
|
25966
|
+
}
|
|
25967
|
+
const operation = {
|
|
25968
|
+
operationId: uniqueOperationId(method, openApiPath, ctx.seenIds),
|
|
25969
|
+
summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
|
|
25970
|
+
tags,
|
|
25971
|
+
responses: route.meta?.responses ?? {
|
|
25972
|
+
"200": { description: "Successful response" }
|
|
25964
25973
|
}
|
|
25965
|
-
|
|
25966
|
-
|
|
25967
|
-
|
|
25968
|
-
|
|
25969
|
-
|
|
25970
|
-
|
|
25971
|
-
|
|
25972
|
-
|
|
25973
|
-
|
|
25974
|
-
|
|
25974
|
+
};
|
|
25975
|
+
if (route.meta?.description) operation.description = route.meta.description;
|
|
25976
|
+
if (route.meta?.deprecated) operation.deprecated = true;
|
|
25977
|
+
const parameters = operationParameters(route, method, ctx.models);
|
|
25978
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
25979
|
+
operationRequestBody(route, method, operation, ctx);
|
|
25980
|
+
operationResponseSchemas(route, operation, ctx.refSchemas);
|
|
25981
|
+
operationSecurity(route, method, operation, ctx.schemes, ctx.defaultScheme);
|
|
25982
|
+
spec.paths[openApiPath][method] = operation;
|
|
25983
|
+
}
|
|
25984
|
+
function operationParameters(route, method, models) {
|
|
25985
|
+
let parameters = [];
|
|
25986
|
+
const pathParams = extractPathParams(route.pattern);
|
|
25987
|
+
if (pathParams.length > 0) {
|
|
25988
|
+
parameters = pathParams.map(({ name, schema }) => ({
|
|
25989
|
+
name,
|
|
25990
|
+
in: "path",
|
|
25991
|
+
required: true,
|
|
25992
|
+
schema
|
|
25993
|
+
}));
|
|
25994
|
+
}
|
|
25995
|
+
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
25996
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
25997
|
+
if (modelName && models.some((m) => m.tableName === modelName)) {
|
|
25998
|
+
parameters = [
|
|
25999
|
+
...parameters,
|
|
26000
|
+
{ name: "page", in: "query", schema: { type: "integer", default: 1 } },
|
|
26001
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
|
|
26002
|
+
{ name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" }
|
|
26003
|
+
];
|
|
25975
26004
|
}
|
|
25976
|
-
|
|
25977
|
-
|
|
25978
|
-
|
|
25979
|
-
|
|
25980
|
-
|
|
25981
|
-
|
|
25982
|
-
|
|
26005
|
+
}
|
|
26006
|
+
return parameters;
|
|
26007
|
+
}
|
|
26008
|
+
function mediaWithExample(schema, example) {
|
|
26009
|
+
const media = { schema };
|
|
26010
|
+
if (example !== void 0) media.example = example;
|
|
26011
|
+
return media;
|
|
26012
|
+
}
|
|
26013
|
+
function operationRequestBody(route, method, operation, ctx) {
|
|
26014
|
+
const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
|
|
26015
|
+
if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
|
|
26016
|
+
ctx.refSchemas.add(reqSchemaRef.name);
|
|
26017
|
+
const media = mediaWithExample({ $ref: `#/components/schemas/${reqSchemaRef.name}` }, route.meta?.example);
|
|
26018
|
+
operation.requestBody = {
|
|
26019
|
+
content: { [reqSchemaRef.contentType]: media }
|
|
26020
|
+
};
|
|
26021
|
+
} else if (method === "post" || method === "put") {
|
|
26022
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
26023
|
+
const schemaKey = modelName ? ctx.tableToSchema.get(modelName) : void 0;
|
|
26024
|
+
if (schemaKey) {
|
|
26025
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
25983
26026
|
operation.requestBody = {
|
|
25984
|
-
|
|
26027
|
+
required: true,
|
|
26028
|
+
content: { "application/json": mediaWithExample({ $ref: sref }, route.meta?.example) }
|
|
25985
26029
|
};
|
|
25986
|
-
|
|
25987
|
-
|
|
25988
|
-
|
|
25989
|
-
if (schemaKey) {
|
|
25990
|
-
const sref = `#/components/schemas/${schemaKey}`;
|
|
25991
|
-
const media = { schema: { $ref: sref } };
|
|
25992
|
-
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
25993
|
-
operation.requestBody = {
|
|
25994
|
-
required: true,
|
|
25995
|
-
content: { "application/json": media }
|
|
25996
|
-
};
|
|
25997
|
-
if (route.meta?.responses === void 0) {
|
|
25998
|
-
operation.responses = {
|
|
25999
|
-
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
26000
|
-
};
|
|
26001
|
-
}
|
|
26002
|
-
} else if (route.meta?.example !== void 0) {
|
|
26003
|
-
operation.requestBody = {
|
|
26004
|
-
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
26030
|
+
if (route.meta?.responses === void 0) {
|
|
26031
|
+
operation.responses = {
|
|
26032
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
26005
26033
|
};
|
|
26006
26034
|
}
|
|
26035
|
+
} else if (route.meta?.example !== void 0) {
|
|
26036
|
+
operation.requestBody = {
|
|
26037
|
+
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
26038
|
+
};
|
|
26007
26039
|
}
|
|
26008
|
-
|
|
26009
|
-
|
|
26010
|
-
|
|
26011
|
-
|
|
26012
|
-
|
|
26013
|
-
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
|
|
26017
|
-
|
|
26018
|
-
|
|
26019
|
-
|
|
26040
|
+
}
|
|
26041
|
+
}
|
|
26042
|
+
function operationResponseSchemas(route, operation, refSchemas) {
|
|
26043
|
+
const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
|
|
26044
|
+
if (respSchemas.length > 0) {
|
|
26045
|
+
const responses = operation.responses;
|
|
26046
|
+
for (const { status: status2, name, isList } of respSchemas) {
|
|
26047
|
+
refSchemas.add(name);
|
|
26048
|
+
const sref = `#/components/schemas/${name}`;
|
|
26049
|
+
const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
|
|
26050
|
+
responses[status2] = {
|
|
26051
|
+
description: status2.startsWith("2") ? "Successful response" : "Response",
|
|
26052
|
+
content: { "application/json": { schema } }
|
|
26053
|
+
};
|
|
26020
26054
|
}
|
|
26021
|
-
|
|
26022
|
-
|
|
26023
|
-
|
|
26024
|
-
|
|
26025
|
-
|
|
26026
|
-
|
|
26027
|
-
|
|
26028
|
-
|
|
26029
|
-
} else if (routeRequiresAuth(route, method)) {
|
|
26030
|
-
const requirements = [{ [defaultScheme]: [] }];
|
|
26031
|
-
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
26032
|
-
operation.security = sanitizeSecurity(requirements, schemes);
|
|
26055
|
+
}
|
|
26056
|
+
}
|
|
26057
|
+
function operationSecurity(route, method, operation, schemes, defaultScheme) {
|
|
26058
|
+
const hasExplicitSecurity = route.meta?.security !== void 0 || route.meta?.scopes !== void 0 && route.meta.scopes.length > 0;
|
|
26059
|
+
if (hasExplicitSecurity) {
|
|
26060
|
+
const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
|
|
26061
|
+
operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
|
|
26062
|
+
if (normalized.length > 0) {
|
|
26033
26063
|
const responses = operation.responses;
|
|
26034
26064
|
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
26035
26065
|
}
|
|
26036
|
-
|
|
26066
|
+
} else if (routeRequiresAuth(route, method)) {
|
|
26067
|
+
const requirements = [{ [defaultScheme]: [] }];
|
|
26068
|
+
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
26069
|
+
operation.security = sanitizeSecurity(requirements, schemes);
|
|
26070
|
+
const responses = operation.responses;
|
|
26071
|
+
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
26037
26072
|
}
|
|
26073
|
+
}
|
|
26074
|
+
function buildRefSchemas(spec, refSchemas) {
|
|
26038
26075
|
if (refSchemas.size > 0) {
|
|
26039
26076
|
const schemas = spec.components.schemas;
|
|
26040
26077
|
for (const name of refSchemas) {
|
|
@@ -26043,10 +26080,11 @@ function generate2(routes, models = []) {
|
|
|
26043
26080
|
}
|
|
26044
26081
|
}
|
|
26045
26082
|
}
|
|
26083
|
+
}
|
|
26084
|
+
function buildTags(spec, usedTags) {
|
|
26046
26085
|
if (usedTags.length > 0) {
|
|
26047
26086
|
spec.tags = usedTags.map((name) => ({ name }));
|
|
26048
26087
|
}
|
|
26049
|
-
return spec;
|
|
26050
26088
|
}
|
|
26051
26089
|
function routeRequiresAuth(route, method) {
|
|
26052
26090
|
if (route.noAuth) return false;
|
|
@@ -27297,7 +27335,9 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
27297
27335
|
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
27298
27336
|
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
27299
27337
|
// suppressReload flag.
|
|
27300
|
-
|
|
27338
|
+
// Also suppress on the dev-admin dashboard (any /__dev page): its SPA reloads
|
|
27339
|
+
// itself gently, so the toolbar's full-page reloader must not fire there.
|
|
27340
|
+
reload: !ctx.isAiPortRequest && !ctx.pathname.startsWith("/__dev")
|
|
27301
27341
|
};
|
|
27302
27342
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
27303
27343
|
}
|
|
@@ -27552,6 +27592,51 @@ async function runDispatch(ctx, rawReq, rawRes) {
|
|
|
27552
27592
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
27553
27593
|
return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
|
|
27554
27594
|
}
|
|
27595
|
+
function loopbackBindHosts(host) {
|
|
27596
|
+
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
27597
|
+
switch (normalized) {
|
|
27598
|
+
case "localhost":
|
|
27599
|
+
return ["127.0.0.1", "::1"];
|
|
27600
|
+
case "127.0.0.1":
|
|
27601
|
+
case "0.0.0.0":
|
|
27602
|
+
return ["::1"];
|
|
27603
|
+
case "::1":
|
|
27604
|
+
case "::":
|
|
27605
|
+
return ["127.0.0.1"];
|
|
27606
|
+
default:
|
|
27607
|
+
return [];
|
|
27608
|
+
}
|
|
27609
|
+
}
|
|
27610
|
+
function startLoopbackSiblings(port, host, dispatch) {
|
|
27611
|
+
const siblingHosts = loopbackBindHosts(host);
|
|
27612
|
+
if (siblingHosts.length === 0) return Promise.resolve([]);
|
|
27613
|
+
return Promise.all(
|
|
27614
|
+
siblingHosts.map(
|
|
27615
|
+
(siblingHost) => new Promise((resolveSibling) => {
|
|
27616
|
+
const sibling = createServer2(dispatch);
|
|
27617
|
+
let settled = false;
|
|
27618
|
+
sibling.on("error", (err) => {
|
|
27619
|
+
const expected = err.code === "EADDRINUSE" || err.code === "EADDRNOTAVAIL" || err.code === "EAFNOSUPPORT";
|
|
27620
|
+
if (!expected) {
|
|
27621
|
+
Log.debug(
|
|
27622
|
+
`Loopback sibling ${siblingHost}:${port} not bound (${err.code ?? err.message}) \u2014 skipped, primary already serves`
|
|
27623
|
+
);
|
|
27624
|
+
}
|
|
27625
|
+
if (!settled) {
|
|
27626
|
+
settled = true;
|
|
27627
|
+
resolveSibling(null);
|
|
27628
|
+
}
|
|
27629
|
+
});
|
|
27630
|
+
sibling.listen(port, siblingHost, () => {
|
|
27631
|
+
settled = true;
|
|
27632
|
+
resolveSibling(sibling);
|
|
27633
|
+
});
|
|
27634
|
+
})
|
|
27635
|
+
)
|
|
27636
|
+
).then(
|
|
27637
|
+
(results) => results.filter((s) => s !== null)
|
|
27638
|
+
);
|
|
27639
|
+
}
|
|
27555
27640
|
async function startServer(config) {
|
|
27556
27641
|
loadEnv(".env.local");
|
|
27557
27642
|
loadEnv();
|
|
@@ -27820,8 +27905,9 @@ ${reset2}
|
|
|
27820
27905
|
}
|
|
27821
27906
|
});
|
|
27822
27907
|
return new Promise((resolvePromise) => {
|
|
27823
|
-
server.listen(port, host, () => {
|
|
27908
|
+
server.listen(port, host, async () => {
|
|
27824
27909
|
if (!cluster.isWorker) writePidfile(port);
|
|
27910
|
+
const siblingServers = await startLoopbackSiblings(port, host, dispatch);
|
|
27825
27911
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
27826
27912
|
const isDebug = isTruthy(process.env.TINA4_DEBUG);
|
|
27827
27913
|
const logLevel = (process.env.TINA4_LOG_LEVEL ?? "DEBUG").toUpperCase();
|
|
@@ -27884,14 +27970,22 @@ ${reset2}
|
|
|
27884
27970
|
}
|
|
27885
27971
|
let shuttingDown = false;
|
|
27886
27972
|
const closeListeners = () => new Promise((done) => {
|
|
27887
|
-
let pending = aiServer ?
|
|
27973
|
+
let pending = 1 + (aiServer ? 1 : 0) + siblingServers.length;
|
|
27888
27974
|
const one = () => {
|
|
27889
27975
|
if (--pending === 0) done();
|
|
27890
27976
|
};
|
|
27891
27977
|
server.close(one);
|
|
27892
27978
|
if (aiServer) aiServer.close(one);
|
|
27979
|
+
for (const sibling of siblingServers) {
|
|
27980
|
+
try {
|
|
27981
|
+
sibling.close(one);
|
|
27982
|
+
} catch {
|
|
27983
|
+
one();
|
|
27984
|
+
}
|
|
27985
|
+
}
|
|
27893
27986
|
server.closeIdleConnections();
|
|
27894
27987
|
aiServer?.closeIdleConnections();
|
|
27988
|
+
for (const sibling of siblingServers) sibling.closeIdleConnections();
|
|
27895
27989
|
});
|
|
27896
27990
|
const gracefulShutdown = async (signal) => {
|
|
27897
27991
|
if (shuttingDown) return;
|
|
@@ -27919,6 +28013,7 @@ ${reset2}
|
|
|
27919
28013
|
);
|
|
27920
28014
|
server.closeAllConnections();
|
|
27921
28015
|
aiServer?.closeAllConnections();
|
|
28016
|
+
for (const sibling of siblingServers) sibling.closeAllConnections();
|
|
27922
28017
|
}
|
|
27923
28018
|
try {
|
|
27924
28019
|
const orm = await Promise.resolve().then(() => (init_index(), index_exports));
|
|
@@ -27946,6 +28041,12 @@ ${reset2}
|
|
|
27946
28041
|
stopAllBackgroundTasks();
|
|
27947
28042
|
if (aiServer) aiServer.close();
|
|
27948
28043
|
server.close();
|
|
28044
|
+
for (const sibling of siblingServers) {
|
|
28045
|
+
try {
|
|
28046
|
+
sibling.close();
|
|
28047
|
+
} catch {
|
|
28048
|
+
}
|
|
28049
|
+
}
|
|
27949
28050
|
Promise.resolve().then(() => (init_index(), index_exports)).then((orm) => orm.closeDatabase()).catch(() => {
|
|
27950
28051
|
});
|
|
27951
28052
|
},
|
|
@@ -35662,6 +35763,7 @@ __export(src_exports2, {
|
|
|
35662
35763
|
isValidSessionId: () => isValidSessionId,
|
|
35663
35764
|
kafkaSecurityConfig: () => kafkaSecurityConfig,
|
|
35664
35765
|
loadEnv: () => loadEnv,
|
|
35766
|
+
loopbackBindHosts: () => loopbackBindHosts,
|
|
35665
35767
|
makeCaseInsensitiveHeaders: () => makeCaseInsensitiveHeaders,
|
|
35666
35768
|
matchCronField: () => matchCronField,
|
|
35667
35769
|
matchesCron: () => matchesCron,
|
|
@@ -43053,6 +43155,7 @@ var init_baseModel = __esm({
|
|
|
43053
43155
|
"src/baseModel.ts"() {
|
|
43054
43156
|
"use strict";
|
|
43055
43157
|
init_database();
|
|
43158
|
+
init_modelCollection();
|
|
43056
43159
|
init_validation();
|
|
43057
43160
|
init_queryBuilder();
|
|
43058
43161
|
init_sqlite();
|
|
@@ -43288,6 +43391,30 @@ var init_baseModel = __esm({
|
|
|
43288
43391
|
static getPkColumn() {
|
|
43289
43392
|
return this.getDbColumn(this.getPkField());
|
|
43290
43393
|
}
|
|
43394
|
+
/**
|
|
43395
|
+
* Shared read tail for the collection-returning finders (where / all / select
|
|
43396
|
+
* / find filter-form / withTrashed). Runs the SAME two calls `db.fetch()`
|
|
43397
|
+
* makes — the page fetch AND the COUNT probe over the SAME base SQL — hydrates
|
|
43398
|
+
* the rows into model instances, and returns a ModelCollection carrying the
|
|
43399
|
+
* total (ADR-0064).
|
|
43400
|
+
*
|
|
43401
|
+
* The total is FREE: `probeTotal` is the exact `COUNT(*)` probe `db.fetch()`
|
|
43402
|
+
* already runs; the ORM used to discard it. ZERO extra queries beyond that one
|
|
43403
|
+
* probe. `sql` MUST NOT carry its own LIMIT/OFFSET — `adapterFetch` applies
|
|
43404
|
+
* limit/offset to the page, and the probe wraps the un-limited SQL so it counts
|
|
43405
|
+
* the WHOLE filtered set, not the page.
|
|
43406
|
+
*/
|
|
43407
|
+
static async _collect(sql, params, limit, offset, include) {
|
|
43408
|
+
const db = this.getDb();
|
|
43409
|
+
const rows = await adapterFetch(db, sql, params, limit, offset);
|
|
43410
|
+
const data = Array.isArray(rows) ? rows : rows?.data ?? [];
|
|
43411
|
+
const total = await probeTotal(db, sql, params, limit) ?? data.length;
|
|
43412
|
+
const instances = data.map((row) => new this(row));
|
|
43413
|
+
if (include) {
|
|
43414
|
+
await this._eagerLoad(instances, include);
|
|
43415
|
+
}
|
|
43416
|
+
return new ModelCollection(instances, total, limit, offset);
|
|
43417
|
+
}
|
|
43291
43418
|
/**
|
|
43292
43419
|
* Find a record by primary key.
|
|
43293
43420
|
* @param id Primary key value.
|
|
@@ -43340,7 +43467,6 @@ var init_baseModel = __esm({
|
|
|
43340
43467
|
);
|
|
43341
43468
|
}
|
|
43342
43469
|
const lim = typeof limit === "number" ? limit : 100;
|
|
43343
|
-
const db = ModelClass.getDb();
|
|
43344
43470
|
const conditions = [];
|
|
43345
43471
|
const params = [];
|
|
43346
43472
|
if (filter) {
|
|
@@ -43360,16 +43486,7 @@ var init_baseModel = __esm({
|
|
|
43360
43486
|
if (orderBy) {
|
|
43361
43487
|
sql += ` ORDER BY ${orderBy}`;
|
|
43362
43488
|
}
|
|
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;
|
|
43489
|
+
return ModelClass._collect(sql, params, lim, offset, include);
|
|
43373
43490
|
}
|
|
43374
43491
|
/**
|
|
43375
43492
|
* Load a record into this instance via selectOne.
|
|
@@ -43437,7 +43554,6 @@ var init_baseModel = __esm({
|
|
|
43437
43554
|
*/
|
|
43438
43555
|
static async all(limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
43439
43556
|
const ModelClass = this;
|
|
43440
|
-
const db = ModelClass.getDb();
|
|
43441
43557
|
const conditions = [];
|
|
43442
43558
|
if (ModelClass.softDelete) {
|
|
43443
43559
|
conditions.push("is_deleted = 0");
|
|
@@ -43447,13 +43563,8 @@ var init_baseModel = __esm({
|
|
|
43447
43563
|
}
|
|
43448
43564
|
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
43449
43565
|
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;
|
|
43566
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}`;
|
|
43567
|
+
return ModelClass._collect(sql, [], limit, offset, include);
|
|
43457
43568
|
}
|
|
43458
43569
|
/**
|
|
43459
43570
|
* Query records with a WHERE clause.
|
|
@@ -43468,7 +43579,6 @@ var init_baseModel = __esm({
|
|
|
43468
43579
|
*/
|
|
43469
43580
|
static async where(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
43470
43581
|
const ModelClass = this;
|
|
43471
|
-
const db = ModelClass.getDb();
|
|
43472
43582
|
const parts = [];
|
|
43473
43583
|
if (ModelClass.softDelete) {
|
|
43474
43584
|
parts.push("is_deleted = 0");
|
|
@@ -43478,13 +43588,8 @@ var init_baseModel = __esm({
|
|
|
43478
43588
|
}
|
|
43479
43589
|
parts.push(`(${conditions})`);
|
|
43480
43590
|
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;
|
|
43591
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}`;
|
|
43592
|
+
return ModelClass._collect(sql, params, limit, offset, include);
|
|
43488
43593
|
}
|
|
43489
43594
|
/**
|
|
43490
43595
|
* Save this instance (insert or update). Returns this on success (fluent
|
|
@@ -43964,10 +44069,7 @@ var init_baseModel = __esm({
|
|
|
43964
44069
|
*/
|
|
43965
44070
|
static async select(sql, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
43966
44071
|
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));
|
|
44072
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
43971
44073
|
}
|
|
43972
44074
|
static async selectOne(sql, params, include) {
|
|
43973
44075
|
const ModelClass = this;
|
|
@@ -44039,7 +44141,6 @@ var init_baseModel = __esm({
|
|
|
44039
44141
|
*/
|
|
44040
44142
|
static async withTrashed(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
44041
44143
|
const ModelClass = this;
|
|
44042
|
-
const db = ModelClass.getDb();
|
|
44043
44144
|
const parts = [];
|
|
44044
44145
|
if (ModelClass.tableFilter) {
|
|
44045
44146
|
parts.push(ModelClass.tableFilter);
|
|
@@ -44051,14 +44152,7 @@ var init_baseModel = __esm({
|
|
|
44051
44152
|
if (parts.length > 0) {
|
|
44052
44153
|
sql += ` WHERE ${parts.join(" AND ")}`;
|
|
44053
44154
|
}
|
|
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));
|
|
44155
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
44062
44156
|
}
|
|
44063
44157
|
/**
|
|
44064
44158
|
* Count records matching conditions (respects soft delete and table filter).
|
|
@@ -46866,6 +46960,7 @@ __export(index_exports, {
|
|
|
46866
46960
|
InvalidId: () => InvalidId,
|
|
46867
46961
|
LocalStorage: () => LocalStorage,
|
|
46868
46962
|
Migration: () => Migration,
|
|
46963
|
+
ModelCollection: () => ModelCollection,
|
|
46869
46964
|
MongodbAdapter: () => MongodbAdapter,
|
|
46870
46965
|
MssqlAdapter: () => MssqlAdapter,
|
|
46871
46966
|
MysqlAdapter: () => MysqlAdapter,
|
|
@@ -46969,6 +47064,7 @@ var init_index = __esm({
|
|
|
46969
47064
|
"src/index.ts"() {
|
|
46970
47065
|
init_types();
|
|
46971
47066
|
init_databaseResult();
|
|
47067
|
+
init_modelCollection();
|
|
46972
47068
|
init_database();
|
|
46973
47069
|
init_database();
|
|
46974
47070
|
init_databaseUrl();
|
|
@@ -47028,6 +47124,7 @@ export {
|
|
|
47028
47124
|
InvalidId,
|
|
47029
47125
|
LocalStorage,
|
|
47030
47126
|
Migration,
|
|
47127
|
+
ModelCollection,
|
|
47031
47128
|
MongodbAdapter,
|
|
47032
47129
|
MssqlAdapter,
|
|
47033
47130
|
MysqlAdapter,
|