tina4-nodejs 3.13.131 → 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 +2 -2
- 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
package/packages/cli/dist/bin.js
CHANGED
|
@@ -4064,33 +4064,17 @@ var init_engine = __esm({
|
|
|
4064
4064
|
i++;
|
|
4065
4065
|
}
|
|
4066
4066
|
}
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
let depth2 = 0;
|
|
4079
|
-
while (i2 < tokens.length) {
|
|
4080
|
-
if (tokens[i2][0] === "BLOCK") {
|
|
4081
|
-
const [tagContent] = stripTag(tokens[i2][1]);
|
|
4082
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4083
|
-
if (tag === "cache") depth2++;
|
|
4084
|
-
else if (tag === "endcache") {
|
|
4085
|
-
if (depth2 === 0) return [htmlContent, i2 + 1];
|
|
4086
|
-
depth2--;
|
|
4087
|
-
}
|
|
4088
|
-
}
|
|
4089
|
-
i2++;
|
|
4090
|
-
}
|
|
4091
|
-
return [htmlContent, i2];
|
|
4092
|
-
}
|
|
4093
|
-
}
|
|
4067
|
+
/**
|
|
4068
|
+
* Collect the body tokens of a {% <openTag> %}...{% end<openTag> %} block,
|
|
4069
|
+
* starting from the token after the opening tag (start + 1). Nested same-tag
|
|
4070
|
+
* blocks are kept in the body and balanced by depth; the matching closing tag is
|
|
4071
|
+
* consumed but NOT included. Returns [bodyTokens, indexAfterClosingTag].
|
|
4072
|
+
*
|
|
4073
|
+
* canNest guards the open-tag count: handleSetBlock passes it so the inline
|
|
4074
|
+
* {% set x = 1 %} form (which has no {% endset %}) never opens a nested block —
|
|
4075
|
+
* only the block form {% set x %} nests. Omitted, every openTag occurrence nests.
|
|
4076
|
+
*/
|
|
4077
|
+
collectBlockBody(tokens, start2, openTag, closeTag, canNest) {
|
|
4094
4078
|
const bodyTokens = [];
|
|
4095
4079
|
let i = start2 + 1;
|
|
4096
4080
|
let depth = 0;
|
|
@@ -4098,10 +4082,10 @@ var init_engine = __esm({
|
|
|
4098
4082
|
if (tokens[i][0] === "BLOCK") {
|
|
4099
4083
|
const [tagContent] = stripTag(tokens[i][1]);
|
|
4100
4084
|
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4101
|
-
if (tag ===
|
|
4085
|
+
if (tag === openTag && (canNest ? canNest(tagContent) : true)) {
|
|
4102
4086
|
depth++;
|
|
4103
4087
|
bodyTokens.push(tokens[i]);
|
|
4104
|
-
} else if (tag ===
|
|
4088
|
+
} else if (tag === closeTag) {
|
|
4105
4089
|
if (depth === 0) {
|
|
4106
4090
|
i++;
|
|
4107
4091
|
break;
|
|
@@ -4116,6 +4100,36 @@ var init_engine = __esm({
|
|
|
4116
4100
|
}
|
|
4117
4101
|
i++;
|
|
4118
4102
|
}
|
|
4103
|
+
return [bodyTokens, i];
|
|
4104
|
+
}
|
|
4105
|
+
handleCache(tokens, start2, context) {
|
|
4106
|
+
const [content] = stripTag(tokens[start2][1]);
|
|
4107
|
+
const m = content.match(/^cache\s+["'](.+?)["']\s*(\d+)?/);
|
|
4108
|
+
const cacheKey = m ? m[1] : "default";
|
|
4109
|
+
const ttl = m && m[2] ? parseInt(m[2], 10) : 60;
|
|
4110
|
+
sweepExpiredCache(this.fragmentCache);
|
|
4111
|
+
const cached = this.fragmentCache.get(cacheKey);
|
|
4112
|
+
if (cached) {
|
|
4113
|
+
const [htmlContent, expiresAt] = cached;
|
|
4114
|
+
if (Date.now() < expiresAt) {
|
|
4115
|
+
let i2 = start2 + 1;
|
|
4116
|
+
let depth = 0;
|
|
4117
|
+
while (i2 < tokens.length) {
|
|
4118
|
+
if (tokens[i2][0] === "BLOCK") {
|
|
4119
|
+
const [tagContent] = stripTag(tokens[i2][1]);
|
|
4120
|
+
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4121
|
+
if (tag === "cache") depth++;
|
|
4122
|
+
else if (tag === "endcache") {
|
|
4123
|
+
if (depth === 0) return [htmlContent, i2 + 1];
|
|
4124
|
+
depth--;
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
i2++;
|
|
4128
|
+
}
|
|
4129
|
+
return [htmlContent, i2];
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "cache", "endcache");
|
|
4119
4133
|
const rendered = this.renderTokens([...bodyTokens], context);
|
|
4120
4134
|
capCache(this.fragmentCache, TEMPLATE_CACHE_MAX);
|
|
4121
4135
|
this.fragmentCache.set(cacheKey, [rendered, Date.now() + ttl * 1e3]);
|
|
@@ -4280,62 +4294,20 @@ var init_engine = __esm({
|
|
|
4280
4294
|
handleSetBlock(tokens, start2, context) {
|
|
4281
4295
|
const [content] = stripTag(tokens[start2][1]);
|
|
4282
4296
|
const name = (content.split(/\s+/)[1] || "").trim();
|
|
4283
|
-
const bodyTokens =
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
if (tag === "set" && !tagContent.includes("=")) {
|
|
4291
|
-
depth++;
|
|
4292
|
-
bodyTokens.push(tokens[i]);
|
|
4293
|
-
} else if (tag === "endset") {
|
|
4294
|
-
if (depth === 0) {
|
|
4295
|
-
i++;
|
|
4296
|
-
break;
|
|
4297
|
-
}
|
|
4298
|
-
depth--;
|
|
4299
|
-
bodyTokens.push(tokens[i]);
|
|
4300
|
-
} else {
|
|
4301
|
-
bodyTokens.push(tokens[i]);
|
|
4302
|
-
}
|
|
4303
|
-
} else {
|
|
4304
|
-
bodyTokens.push(tokens[i]);
|
|
4305
|
-
}
|
|
4306
|
-
i++;
|
|
4307
|
-
}
|
|
4297
|
+
const [bodyTokens, i] = this.collectBlockBody(
|
|
4298
|
+
tokens,
|
|
4299
|
+
start2,
|
|
4300
|
+
"set",
|
|
4301
|
+
"endset",
|
|
4302
|
+
(tagContent) => !tagContent.includes("=")
|
|
4303
|
+
);
|
|
4308
4304
|
if (name) {
|
|
4309
4305
|
context[name] = new SafeString(this.renderTokens([...bodyTokens], context));
|
|
4310
4306
|
}
|
|
4311
4307
|
return i;
|
|
4312
4308
|
}
|
|
4313
4309
|
handleSpaceless(tokens, start2, context) {
|
|
4314
|
-
const bodyTokens =
|
|
4315
|
-
let i = start2 + 1;
|
|
4316
|
-
let depth = 0;
|
|
4317
|
-
while (i < tokens.length) {
|
|
4318
|
-
if (tokens[i][0] === "BLOCK") {
|
|
4319
|
-
const [tagContent] = stripTag(tokens[i][1]);
|
|
4320
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4321
|
-
if (tag === "spaceless") {
|
|
4322
|
-
depth++;
|
|
4323
|
-
bodyTokens.push(tokens[i]);
|
|
4324
|
-
} else if (tag === "endspaceless") {
|
|
4325
|
-
if (depth === 0) {
|
|
4326
|
-
i++;
|
|
4327
|
-
break;
|
|
4328
|
-
}
|
|
4329
|
-
depth--;
|
|
4330
|
-
bodyTokens.push(tokens[i]);
|
|
4331
|
-
} else {
|
|
4332
|
-
bodyTokens.push(tokens[i]);
|
|
4333
|
-
}
|
|
4334
|
-
} else {
|
|
4335
|
-
bodyTokens.push(tokens[i]);
|
|
4336
|
-
}
|
|
4337
|
-
i++;
|
|
4338
|
-
}
|
|
4310
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "spaceless", "endspaceless");
|
|
4339
4311
|
let rendered = this.renderTokens([...bodyTokens], context);
|
|
4340
4312
|
rendered = rendered.replace(/>\s+</g, "><");
|
|
4341
4313
|
return [rendered, i];
|
|
@@ -4344,31 +4316,7 @@ var init_engine = __esm({
|
|
|
4344
4316
|
const [content] = stripTag(tokens[start2][1]);
|
|
4345
4317
|
const modeMatch = content.match(/^autoescape\s+(false|true)/);
|
|
4346
4318
|
const autoEscapeOn = !(modeMatch && modeMatch[1] === "false");
|
|
4347
|
-
const bodyTokens =
|
|
4348
|
-
let i = start2 + 1;
|
|
4349
|
-
let depth = 0;
|
|
4350
|
-
while (i < tokens.length) {
|
|
4351
|
-
if (tokens[i][0] === "BLOCK") {
|
|
4352
|
-
const [tagContent] = stripTag(tokens[i][1]);
|
|
4353
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4354
|
-
if (tag === "autoescape") {
|
|
4355
|
-
depth++;
|
|
4356
|
-
bodyTokens.push(tokens[i]);
|
|
4357
|
-
} else if (tag === "endautoescape") {
|
|
4358
|
-
if (depth === 0) {
|
|
4359
|
-
i++;
|
|
4360
|
-
break;
|
|
4361
|
-
}
|
|
4362
|
-
depth--;
|
|
4363
|
-
bodyTokens.push(tokens[i]);
|
|
4364
|
-
} else {
|
|
4365
|
-
bodyTokens.push(tokens[i]);
|
|
4366
|
-
}
|
|
4367
|
-
} else {
|
|
4368
|
-
bodyTokens.push(tokens[i]);
|
|
4369
|
-
}
|
|
4370
|
-
i++;
|
|
4371
|
-
}
|
|
4319
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "autoescape", "endautoescape");
|
|
4372
4320
|
if (!autoEscapeOn) {
|
|
4373
4321
|
const oldAutoEscape = this._autoEscape;
|
|
4374
4322
|
this._autoEscape = false;
|
|
@@ -5722,6 +5670,99 @@ var init_databaseResult = __esm({
|
|
|
5722
5670
|
}
|
|
5723
5671
|
});
|
|
5724
5672
|
|
|
5673
|
+
// ../orm/src/modelCollection.ts
|
|
5674
|
+
var ModelCollection;
|
|
5675
|
+
var init_modelCollection = __esm({
|
|
5676
|
+
"../orm/src/modelCollection.ts"() {
|
|
5677
|
+
"use strict";
|
|
5678
|
+
ModelCollection = class extends Array {
|
|
5679
|
+
/** Total rows matching the query's filter (ignores limit/offset). */
|
|
5680
|
+
_total = 0;
|
|
5681
|
+
/** The SQL limit that produced this page. */
|
|
5682
|
+
_limit = 0;
|
|
5683
|
+
/** The SQL offset that produced this page. */
|
|
5684
|
+
_offset = 0;
|
|
5685
|
+
/**
|
|
5686
|
+
* Derived array operations (`map`, `filter`, `slice`, spread, …) build a plain
|
|
5687
|
+
* `Array`, never another `ModelCollection`. This is what defuses the Array-
|
|
5688
|
+
* subclass constructor/species trap: the engine never calls this constructor
|
|
5689
|
+
* with a length during those operations.
|
|
5690
|
+
*/
|
|
5691
|
+
static get [Symbol.species]() {
|
|
5692
|
+
return Array;
|
|
5693
|
+
}
|
|
5694
|
+
/**
|
|
5695
|
+
* @param items the page of hydrated model instances (or the length, when the
|
|
5696
|
+
* engine constructs a derived array — defended against here).
|
|
5697
|
+
* @param total total rows matching the query's filter (ignores limit/offset).
|
|
5698
|
+
* @param limit the SQL limit that produced this page.
|
|
5699
|
+
* @param offset the SQL offset that produced this page.
|
|
5700
|
+
*/
|
|
5701
|
+
constructor(items, total = 0, limit = 0, offset = 0) {
|
|
5702
|
+
super();
|
|
5703
|
+
if (typeof items === "number") {
|
|
5704
|
+
this.length = items;
|
|
5705
|
+
return;
|
|
5706
|
+
}
|
|
5707
|
+
if (items) {
|
|
5708
|
+
for (let i = 0; i < items.length; i++) {
|
|
5709
|
+
this[i] = items[i];
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
this._total = Math.trunc(total) || 0;
|
|
5713
|
+
this._limit = Math.trunc(limit) || 0;
|
|
5714
|
+
this._offset = Math.trunc(offset) || 0;
|
|
5715
|
+
}
|
|
5716
|
+
/**
|
|
5717
|
+
* Total rows matching the query's filter, ignoring limit/offset.
|
|
5718
|
+
*
|
|
5719
|
+
* This is the whole point of the collection: the page slice you are iterating
|
|
5720
|
+
* is capped by `limit`, but this number is the full count of matching rows —
|
|
5721
|
+
* what a pager needs to render "page 3 of 13".
|
|
5722
|
+
*/
|
|
5723
|
+
getTotalRecords() {
|
|
5724
|
+
return this._total;
|
|
5725
|
+
}
|
|
5726
|
+
/**
|
|
5727
|
+
* The canonical pagination envelope — seven snake_case keys, identical to
|
|
5728
|
+
* `DatabaseResult.toPaginate()` (ADR-0043) and to the other three frameworks'
|
|
5729
|
+
* `toPaginate()` / `to_paginate()`.
|
|
5730
|
+
*
|
|
5731
|
+
* records the page's rows as plain objects (never re-sliced)
|
|
5732
|
+
* total getTotalRecords() — the true total for the filter
|
|
5733
|
+
* page floor(offset / per_page) + 1
|
|
5734
|
+
* per_page the query's limit
|
|
5735
|
+
* total_pages ceil(total / per_page)
|
|
5736
|
+
* limit the SQL limit actually applied
|
|
5737
|
+
* offset the SQL offset actually applied
|
|
5738
|
+
*
|
|
5739
|
+
* `records` are model dicts (via `toDict()`, the same serialisation the
|
|
5740
|
+
* framework applies to a model in a JSON response), so the JSON a client sees
|
|
5741
|
+
* matches `DatabaseResult` exactly — the result is uniform whether the route
|
|
5742
|
+
* returned a raw `db.fetch()` or an ORM query.
|
|
5743
|
+
*/
|
|
5744
|
+
toPaginate() {
|
|
5745
|
+
const perPage = this._limit > 0 ? this._limit : this.length;
|
|
5746
|
+
const page = perPage > 0 ? Math.floor(this._offset / perPage) + 1 : 1;
|
|
5747
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this._total / perPage)) : 1;
|
|
5748
|
+
const records = this.map((model) => {
|
|
5749
|
+
const m = model;
|
|
5750
|
+
return m && typeof m.toDict === "function" ? m.toDict() : model;
|
|
5751
|
+
});
|
|
5752
|
+
return {
|
|
5753
|
+
records,
|
|
5754
|
+
total: this._total,
|
|
5755
|
+
page,
|
|
5756
|
+
per_page: perPage,
|
|
5757
|
+
total_pages: totalPages,
|
|
5758
|
+
limit: perPage,
|
|
5759
|
+
offset: this._offset
|
|
5760
|
+
};
|
|
5761
|
+
}
|
|
5762
|
+
};
|
|
5763
|
+
}
|
|
5764
|
+
});
|
|
5765
|
+
|
|
5725
5766
|
// ../orm/src/databaseUrl.ts
|
|
5726
5767
|
import { inspect } from "node:util";
|
|
5727
5768
|
function stripOneSlash(path8) {
|
|
@@ -14070,6 +14111,7 @@ var init_baseModel = __esm({
|
|
|
14070
14111
|
"../orm/src/baseModel.ts"() {
|
|
14071
14112
|
"use strict";
|
|
14072
14113
|
init_database();
|
|
14114
|
+
init_modelCollection();
|
|
14073
14115
|
init_validation();
|
|
14074
14116
|
init_queryBuilder();
|
|
14075
14117
|
init_sqlite();
|
|
@@ -14305,6 +14347,30 @@ var init_baseModel = __esm({
|
|
|
14305
14347
|
static getPkColumn() {
|
|
14306
14348
|
return this.getDbColumn(this.getPkField());
|
|
14307
14349
|
}
|
|
14350
|
+
/**
|
|
14351
|
+
* Shared read tail for the collection-returning finders (where / all / select
|
|
14352
|
+
* / find filter-form / withTrashed). Runs the SAME two calls `db.fetch()`
|
|
14353
|
+
* makes — the page fetch AND the COUNT probe over the SAME base SQL — hydrates
|
|
14354
|
+
* the rows into model instances, and returns a ModelCollection carrying the
|
|
14355
|
+
* total (ADR-0064).
|
|
14356
|
+
*
|
|
14357
|
+
* The total is FREE: `probeTotal` is the exact `COUNT(*)` probe `db.fetch()`
|
|
14358
|
+
* already runs; the ORM used to discard it. ZERO extra queries beyond that one
|
|
14359
|
+
* probe. `sql` MUST NOT carry its own LIMIT/OFFSET — `adapterFetch` applies
|
|
14360
|
+
* limit/offset to the page, and the probe wraps the un-limited SQL so it counts
|
|
14361
|
+
* the WHOLE filtered set, not the page.
|
|
14362
|
+
*/
|
|
14363
|
+
static async _collect(sql, params, limit, offset, include) {
|
|
14364
|
+
const db = this.getDb();
|
|
14365
|
+
const rows = await adapterFetch(db, sql, params, limit, offset);
|
|
14366
|
+
const data = Array.isArray(rows) ? rows : rows?.data ?? [];
|
|
14367
|
+
const total = await probeTotal(db, sql, params, limit) ?? data.length;
|
|
14368
|
+
const instances = data.map((row) => new this(row));
|
|
14369
|
+
if (include) {
|
|
14370
|
+
await this._eagerLoad(instances, include);
|
|
14371
|
+
}
|
|
14372
|
+
return new ModelCollection(instances, total, limit, offset);
|
|
14373
|
+
}
|
|
14308
14374
|
/**
|
|
14309
14375
|
* Find a record by primary key.
|
|
14310
14376
|
* @param id Primary key value.
|
|
@@ -14357,7 +14423,6 @@ var init_baseModel = __esm({
|
|
|
14357
14423
|
);
|
|
14358
14424
|
}
|
|
14359
14425
|
const lim = typeof limit === "number" ? limit : 100;
|
|
14360
|
-
const db = ModelClass.getDb();
|
|
14361
14426
|
const conditions = [];
|
|
14362
14427
|
const params = [];
|
|
14363
14428
|
if (filter) {
|
|
@@ -14377,16 +14442,7 @@ var init_baseModel = __esm({
|
|
|
14377
14442
|
if (orderBy) {
|
|
14378
14443
|
sql += ` ORDER BY ${orderBy}`;
|
|
14379
14444
|
}
|
|
14380
|
-
|
|
14381
|
-
const data = rows?.data ?? rows;
|
|
14382
|
-
const instances = (Array.isArray(data) ? data : []).map((row) => {
|
|
14383
|
-
const inst = new this(row);
|
|
14384
|
-
return inst;
|
|
14385
|
-
});
|
|
14386
|
-
if (include) {
|
|
14387
|
-
await ModelClass._eagerLoad(instances, include);
|
|
14388
|
-
}
|
|
14389
|
-
return instances;
|
|
14445
|
+
return ModelClass._collect(sql, params, lim, offset, include);
|
|
14390
14446
|
}
|
|
14391
14447
|
/**
|
|
14392
14448
|
* Load a record into this instance via selectOne.
|
|
@@ -14454,7 +14510,6 @@ var init_baseModel = __esm({
|
|
|
14454
14510
|
*/
|
|
14455
14511
|
static async all(limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
14456
14512
|
const ModelClass = this;
|
|
14457
|
-
const db = ModelClass.getDb();
|
|
14458
14513
|
const conditions = [];
|
|
14459
14514
|
if (ModelClass.softDelete) {
|
|
14460
14515
|
conditions.push("is_deleted = 0");
|
|
@@ -14464,13 +14519,8 @@ var init_baseModel = __esm({
|
|
|
14464
14519
|
}
|
|
14465
14520
|
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
14466
14521
|
const orderClause = orderBy ? ` ORDER BY ${orderBy}` : "";
|
|
14467
|
-
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}
|
|
14468
|
-
|
|
14469
|
-
const instances = rows.map((row) => new ModelClass(row));
|
|
14470
|
-
if (include) {
|
|
14471
|
-
await ModelClass._eagerLoad(instances, include);
|
|
14472
|
-
}
|
|
14473
|
-
return instances;
|
|
14522
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}`;
|
|
14523
|
+
return ModelClass._collect(sql, [], limit, offset, include);
|
|
14474
14524
|
}
|
|
14475
14525
|
/**
|
|
14476
14526
|
* Query records with a WHERE clause.
|
|
@@ -14485,7 +14535,6 @@ var init_baseModel = __esm({
|
|
|
14485
14535
|
*/
|
|
14486
14536
|
static async where(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
14487
14537
|
const ModelClass = this;
|
|
14488
|
-
const db = ModelClass.getDb();
|
|
14489
14538
|
const parts = [];
|
|
14490
14539
|
if (ModelClass.softDelete) {
|
|
14491
14540
|
parts.push("is_deleted = 0");
|
|
@@ -14495,13 +14544,8 @@ var init_baseModel = __esm({
|
|
|
14495
14544
|
}
|
|
14496
14545
|
parts.push(`(${conditions})`);
|
|
14497
14546
|
const orderClause = orderBy ? ` ORDER BY ${orderBy}` : "";
|
|
14498
|
-
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}
|
|
14499
|
-
|
|
14500
|
-
const instances = rows.map((row) => new ModelClass(row));
|
|
14501
|
-
if (include) {
|
|
14502
|
-
await ModelClass._eagerLoad(instances, include);
|
|
14503
|
-
}
|
|
14504
|
-
return instances;
|
|
14547
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}`;
|
|
14548
|
+
return ModelClass._collect(sql, params, limit, offset, include);
|
|
14505
14549
|
}
|
|
14506
14550
|
/**
|
|
14507
14551
|
* Save this instance (insert or update). Returns this on success (fluent
|
|
@@ -14981,10 +15025,7 @@ var init_baseModel = __esm({
|
|
|
14981
15025
|
*/
|
|
14982
15026
|
static async select(sql, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
14983
15027
|
const ModelClass = this;
|
|
14984
|
-
|
|
14985
|
-
const paged = SQLTranslator.appendLimit(sql, limit, offset);
|
|
14986
|
-
const rows = await adapterQuery(db, paged, params);
|
|
14987
|
-
return rows.map((row) => new ModelClass(row));
|
|
15028
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
14988
15029
|
}
|
|
14989
15030
|
static async selectOne(sql, params, include) {
|
|
14990
15031
|
const ModelClass = this;
|
|
@@ -15056,7 +15097,6 @@ var init_baseModel = __esm({
|
|
|
15056
15097
|
*/
|
|
15057
15098
|
static async withTrashed(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
15058
15099
|
const ModelClass = this;
|
|
15059
|
-
const db = ModelClass.getDb();
|
|
15060
15100
|
const parts = [];
|
|
15061
15101
|
if (ModelClass.tableFilter) {
|
|
15062
15102
|
parts.push(ModelClass.tableFilter);
|
|
@@ -15068,14 +15108,7 @@ var init_baseModel = __esm({
|
|
|
15068
15108
|
if (parts.length > 0) {
|
|
15069
15109
|
sql += ` WHERE ${parts.join(" AND ")}`;
|
|
15070
15110
|
}
|
|
15071
|
-
|
|
15072
|
-
sql += ` LIMIT ${limit}`;
|
|
15073
|
-
}
|
|
15074
|
-
if (offset !== void 0) {
|
|
15075
|
-
sql += ` OFFSET ${offset}`;
|
|
15076
|
-
}
|
|
15077
|
-
const rows = await adapterQuery(db, sql, params);
|
|
15078
|
-
return rows.map((row) => new ModelClass(row));
|
|
15111
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
15079
15112
|
}
|
|
15080
15113
|
/**
|
|
15081
15114
|
* Count records matching conditions (respects soft delete and table filter).
|
|
@@ -18414,6 +18447,7 @@ __export(src_exports, {
|
|
|
18414
18447
|
InvalidId: () => InvalidId,
|
|
18415
18448
|
LocalStorage: () => LocalStorage,
|
|
18416
18449
|
Migration: () => Migration,
|
|
18450
|
+
ModelCollection: () => ModelCollection,
|
|
18417
18451
|
MongodbAdapter: () => MongodbAdapter,
|
|
18418
18452
|
MssqlAdapter: () => MssqlAdapter,
|
|
18419
18453
|
MysqlAdapter: () => MysqlAdapter,
|
|
@@ -18518,6 +18552,7 @@ var init_src = __esm({
|
|
|
18518
18552
|
"use strict";
|
|
18519
18553
|
init_types();
|
|
18520
18554
|
init_databaseResult();
|
|
18555
|
+
init_modelCollection();
|
|
18521
18556
|
init_database();
|
|
18522
18557
|
init_database();
|
|
18523
18558
|
init_databaseUrl();
|
|
@@ -35264,7 +35299,7 @@ function toolbarJs() {
|
|
|
35264
35299
|
connect();
|
|
35265
35300
|
})();`;
|
|
35266
35301
|
}
|
|
35267
|
-
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,
|
|
35302
|
+
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;
|
|
35268
35303
|
var init_devAdmin = __esm({
|
|
35269
35304
|
"../core/src/devAdmin.ts"() {
|
|
35270
35305
|
"use strict";
|
|
@@ -35602,9 +35637,6 @@ var init_devAdmin = __esm({
|
|
|
35602
35637
|
{ method: "POST", pattern: "/__dev/api/websockets/disconnect", handler: handleWebsocketsDisconnect },
|
|
35603
35638
|
// Tools
|
|
35604
35639
|
{ method: "POST", pattern: "/__dev/api/tool", handler: handleTool },
|
|
35605
|
-
// Chat — proxies to Rust agent /chat (SSE passthrough). Forwards
|
|
35606
|
-
// active_file and any other body keys verbatim. See proxyToSupervisor.
|
|
35607
|
-
{ method: "POST", pattern: "/__dev/api/chat", handler: handleChat },
|
|
35608
35640
|
// Threads — proxies to Rust agent /threads. Mirrors Python's
|
|
35609
35641
|
// _api_threads + _api_threads_sub.
|
|
35610
35642
|
{ method: "GET", pattern: "/__dev/api/threads", handler: handleThreads },
|
|
@@ -35683,8 +35715,6 @@ var init_devAdmin = __esm({
|
|
|
35683
35715
|
{ method: "GET", pattern: "/__dev/api/supervise/diff", handler: handleSuperviseStub },
|
|
35684
35716
|
{ method: "POST", pattern: "/__dev/api/supervise/commit", handler: handleSuperviseStub },
|
|
35685
35717
|
{ method: "POST", pattern: "/__dev/api/supervise/cancel", handler: handleSuperviseStub },
|
|
35686
|
-
// Execute — proxies to the framework_port+2000 Rust agent (SSE passthrough)
|
|
35687
|
-
{ method: "POST", pattern: "/__dev/api/execute", handler: handleExecute },
|
|
35688
35718
|
// Framework-grounding MCP token config — self-contained (.env upsert)
|
|
35689
35719
|
{ method: "GET", pattern: "/__dev/api/grounding/status", handler: handleGroundingStatus },
|
|
35690
35720
|
{ method: "POST", pattern: "/__dev/api/grounding/token", handler: handleGroundingToken },
|
|
@@ -36254,9 +36284,6 @@ var init_devAdmin = __esm({
|
|
|
36254
36284
|
}
|
|
36255
36285
|
res.json({ tool, status: "executed", message: `Tool '${tool}' executed (stub)`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
36256
36286
|
};
|
|
36257
|
-
handleChat = async (req2, res) => {
|
|
36258
|
-
await proxyToSupervisor(req2, res, "/chat");
|
|
36259
|
-
};
|
|
36260
36287
|
DEFAULT_MCP_URL = "https://mcp.tina4.com";
|
|
36261
36288
|
handleGroundingStatus = async (_req, res) => {
|
|
36262
36289
|
const token = resolveDevEnvVar("TINA4_MCP_TOKEN");
|
|
@@ -36544,35 +36571,6 @@ var init_devAdmin = __esm({
|
|
|
36544
36571
|
501
|
|
36545
36572
|
);
|
|
36546
36573
|
};
|
|
36547
|
-
handleExecute = async (req2, res) => {
|
|
36548
|
-
const port = parseInt(process.env.TINA4_PORT ?? process.env.PORT ?? "7148", 10);
|
|
36549
|
-
const agentUrl = `http://127.0.0.1:${port + 2e3}/execute`;
|
|
36550
|
-
try {
|
|
36551
|
-
const upstream = await fetch(agentUrl, {
|
|
36552
|
-
method: "POST",
|
|
36553
|
-
headers: { "Content-Type": "application/json" },
|
|
36554
|
-
body: JSON.stringify(req2.body ?? {})
|
|
36555
|
-
});
|
|
36556
|
-
if (!upstream.body) {
|
|
36557
|
-
res.json({ error: "agent returned no body" }, 502);
|
|
36558
|
-
return;
|
|
36559
|
-
}
|
|
36560
|
-
res.raw.writeHead(upstream.status || 200, {
|
|
36561
|
-
"Content-Type": upstream.headers.get("content-type") || "text/event-stream",
|
|
36562
|
-
"Cache-Control": "no-cache",
|
|
36563
|
-
Connection: "keep-alive"
|
|
36564
|
-
});
|
|
36565
|
-
const reader = upstream.body.getReader();
|
|
36566
|
-
while (true) {
|
|
36567
|
-
const { done, value } = await reader.read();
|
|
36568
|
-
if (done) break;
|
|
36569
|
-
if (value) res.raw.write(Buffer.from(value));
|
|
36570
|
-
}
|
|
36571
|
-
res.raw.end();
|
|
36572
|
-
} catch (e) {
|
|
36573
|
-
res.json({ error: `agent unreachable at ${agentUrl}: ${e.message}` }, 502);
|
|
36574
|
-
}
|
|
36575
|
-
};
|
|
36576
36574
|
DEV_FILES_IGNORED = /* @__PURE__ */ new Set([
|
|
36577
36575
|
"__pycache__",
|
|
36578
36576
|
"node_modules",
|
|
@@ -39050,7 +39048,9 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
39050
39048
|
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
39051
39049
|
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
39052
39050
|
// suppressReload flag.
|
|
39053
|
-
|
|
39051
|
+
// Also suppress on the dev-admin dashboard (any /__dev page): its SPA reloads
|
|
39052
|
+
// itself gently, so the toolbar's full-page reloader must not fire there.
|
|
39053
|
+
reload: !ctx.isAiPortRequest && !ctx.pathname.startsWith("/__dev")
|
|
39054
39054
|
};
|
|
39055
39055
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
39056
39056
|
}
|
|
@@ -39305,6 +39305,51 @@ async function runDispatch(ctx, rawReq, rawRes) {
|
|
|
39305
39305
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
39306
39306
|
return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
|
|
39307
39307
|
}
|
|
39308
|
+
function loopbackBindHosts(host) {
|
|
39309
|
+
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
39310
|
+
switch (normalized) {
|
|
39311
|
+
case "localhost":
|
|
39312
|
+
return ["127.0.0.1", "::1"];
|
|
39313
|
+
case "127.0.0.1":
|
|
39314
|
+
case "0.0.0.0":
|
|
39315
|
+
return ["::1"];
|
|
39316
|
+
case "::1":
|
|
39317
|
+
case "::":
|
|
39318
|
+
return ["127.0.0.1"];
|
|
39319
|
+
default:
|
|
39320
|
+
return [];
|
|
39321
|
+
}
|
|
39322
|
+
}
|
|
39323
|
+
function startLoopbackSiblings(port, host, dispatch) {
|
|
39324
|
+
const siblingHosts = loopbackBindHosts(host);
|
|
39325
|
+
if (siblingHosts.length === 0) return Promise.resolve([]);
|
|
39326
|
+
return Promise.all(
|
|
39327
|
+
siblingHosts.map(
|
|
39328
|
+
(siblingHost) => new Promise((resolveSibling) => {
|
|
39329
|
+
const sibling = createServer2(dispatch);
|
|
39330
|
+
let settled = false;
|
|
39331
|
+
sibling.on("error", (err) => {
|
|
39332
|
+
const expected = err.code === "EADDRINUSE" || err.code === "EADDRNOTAVAIL" || err.code === "EAFNOSUPPORT";
|
|
39333
|
+
if (!expected) {
|
|
39334
|
+
Log.debug(
|
|
39335
|
+
`Loopback sibling ${siblingHost}:${port} not bound (${err.code ?? err.message}) \u2014 skipped, primary already serves`
|
|
39336
|
+
);
|
|
39337
|
+
}
|
|
39338
|
+
if (!settled) {
|
|
39339
|
+
settled = true;
|
|
39340
|
+
resolveSibling(null);
|
|
39341
|
+
}
|
|
39342
|
+
});
|
|
39343
|
+
sibling.listen(port, siblingHost, () => {
|
|
39344
|
+
settled = true;
|
|
39345
|
+
resolveSibling(sibling);
|
|
39346
|
+
});
|
|
39347
|
+
})
|
|
39348
|
+
)
|
|
39349
|
+
).then(
|
|
39350
|
+
(results) => results.filter((s) => s !== null)
|
|
39351
|
+
);
|
|
39352
|
+
}
|
|
39308
39353
|
async function startServer(config) {
|
|
39309
39354
|
loadEnv(".env.local");
|
|
39310
39355
|
loadEnv();
|
|
@@ -39573,8 +39618,9 @@ ${reset2}
|
|
|
39573
39618
|
}
|
|
39574
39619
|
});
|
|
39575
39620
|
return new Promise((resolvePromise) => {
|
|
39576
|
-
server.listen(port, host, () => {
|
|
39621
|
+
server.listen(port, host, async () => {
|
|
39577
39622
|
if (!cluster.isWorker) writePidfile(port);
|
|
39623
|
+
const siblingServers = await startLoopbackSiblings(port, host, dispatch);
|
|
39578
39624
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
39579
39625
|
const isDebug = isTruthy(process.env.TINA4_DEBUG);
|
|
39580
39626
|
const logLevel = (process.env.TINA4_LOG_LEVEL ?? "DEBUG").toUpperCase();
|
|
@@ -39637,14 +39683,22 @@ ${reset2}
|
|
|
39637
39683
|
}
|
|
39638
39684
|
let shuttingDown = false;
|
|
39639
39685
|
const closeListeners = () => new Promise((done) => {
|
|
39640
|
-
let pending = aiServer ?
|
|
39686
|
+
let pending = 1 + (aiServer ? 1 : 0) + siblingServers.length;
|
|
39641
39687
|
const one = () => {
|
|
39642
39688
|
if (--pending === 0) done();
|
|
39643
39689
|
};
|
|
39644
39690
|
server.close(one);
|
|
39645
39691
|
if (aiServer) aiServer.close(one);
|
|
39692
|
+
for (const sibling of siblingServers) {
|
|
39693
|
+
try {
|
|
39694
|
+
sibling.close(one);
|
|
39695
|
+
} catch {
|
|
39696
|
+
one();
|
|
39697
|
+
}
|
|
39698
|
+
}
|
|
39646
39699
|
server.closeIdleConnections();
|
|
39647
39700
|
aiServer?.closeIdleConnections();
|
|
39701
|
+
for (const sibling of siblingServers) sibling.closeIdleConnections();
|
|
39648
39702
|
});
|
|
39649
39703
|
const gracefulShutdown = async (signal) => {
|
|
39650
39704
|
if (shuttingDown) return;
|
|
@@ -39672,6 +39726,7 @@ ${reset2}
|
|
|
39672
39726
|
);
|
|
39673
39727
|
server.closeAllConnections();
|
|
39674
39728
|
aiServer?.closeAllConnections();
|
|
39729
|
+
for (const sibling of siblingServers) sibling.closeAllConnections();
|
|
39675
39730
|
}
|
|
39676
39731
|
try {
|
|
39677
39732
|
const orm = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -39699,6 +39754,12 @@ ${reset2}
|
|
|
39699
39754
|
stopAllBackgroundTasks();
|
|
39700
39755
|
if (aiServer) aiServer.close();
|
|
39701
39756
|
server.close();
|
|
39757
|
+
for (const sibling of siblingServers) {
|
|
39758
|
+
try {
|
|
39759
|
+
sibling.close();
|
|
39760
|
+
} catch {
|
|
39761
|
+
}
|
|
39762
|
+
}
|
|
39702
39763
|
Promise.resolve().then(() => (init_src(), src_exports)).then((orm) => orm.closeDatabase()).catch(() => {
|
|
39703
39764
|
});
|
|
39704
39765
|
},
|
|
@@ -46902,6 +46963,7 @@ __export(src_exports3, {
|
|
|
46902
46963
|
isValidSessionId: () => isValidSessionId,
|
|
46903
46964
|
kafkaSecurityConfig: () => kafkaSecurityConfig,
|
|
46904
46965
|
loadEnv: () => loadEnv,
|
|
46966
|
+
loopbackBindHosts: () => loopbackBindHosts,
|
|
46905
46967
|
makeCaseInsensitiveHeaders: () => makeCaseInsensitiveHeaders,
|
|
46906
46968
|
matchCronField: () => matchCronField,
|
|
46907
46969
|
matchesCron: () => matchesCron,
|