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
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",
|
|
@@ -37637,12 +37635,46 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
37637
37635
|
});
|
|
37638
37636
|
}
|
|
37639
37637
|
function generate2(routes, models = []) {
|
|
37638
|
+
const schemes = resolveSecuritySchemes();
|
|
37639
|
+
const spec = {
|
|
37640
|
+
openapi: resolveOpenApiVersion(),
|
|
37641
|
+
info: buildInfo(),
|
|
37642
|
+
servers: resolveServers(),
|
|
37643
|
+
paths: {},
|
|
37644
|
+
components: {
|
|
37645
|
+
schemas: {},
|
|
37646
|
+
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
37647
|
+
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
37648
|
+
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
37649
|
+
securitySchemes: schemes
|
|
37650
|
+
}
|
|
37651
|
+
};
|
|
37652
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
37653
|
+
buildComponentSchemas(models, spec, tableToSchema);
|
|
37654
|
+
const ctx = {
|
|
37655
|
+
models,
|
|
37656
|
+
tableToSchema,
|
|
37657
|
+
schemes,
|
|
37658
|
+
// Default scheme secured routes use when no explicit meta.security is set.
|
|
37659
|
+
defaultScheme: process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth",
|
|
37660
|
+
// Path filters (comma-separated raw-path prefixes).
|
|
37661
|
+
includePrefixes: csv(process.env.TINA4_SWAGGER_INCLUDE),
|
|
37662
|
+
excludePrefixes: csv(process.env.TINA4_SWAGGER_EXCLUDE),
|
|
37663
|
+
// Reusable custom schemas referenced by routes via meta.requestSchema/responseSchemas.
|
|
37664
|
+
refSchemas: /* @__PURE__ */ new Set(),
|
|
37665
|
+
usedTags: [],
|
|
37666
|
+
seenIds: /* @__PURE__ */ new Set()
|
|
37667
|
+
};
|
|
37668
|
+
for (const route of routes) {
|
|
37669
|
+
buildOperation(route, spec, ctx);
|
|
37670
|
+
}
|
|
37671
|
+
buildRefSchemas(spec, ctx.refSchemas);
|
|
37672
|
+
buildTags(spec, ctx.usedTags);
|
|
37673
|
+
return spec;
|
|
37674
|
+
}
|
|
37675
|
+
function buildInfo() {
|
|
37640
37676
|
const info = {
|
|
37641
37677
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
37642
|
-
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
37643
|
-
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
37644
|
-
// Both are the settled cross-framework defaults (parity with the Python
|
|
37645
|
-
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
37646
37678
|
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
37647
37679
|
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
37648
37680
|
};
|
|
@@ -37659,135 +37691,134 @@ function generate2(routes, models = []) {
|
|
|
37659
37691
|
const [name, url] = licenseRaw.split("|").map((s) => s.trim());
|
|
37660
37692
|
info.license = url ? { name, url } : { name };
|
|
37661
37693
|
}
|
|
37662
|
-
|
|
37663
|
-
|
|
37664
|
-
|
|
37665
|
-
info,
|
|
37666
|
-
servers: resolveServers(),
|
|
37667
|
-
paths: {},
|
|
37668
|
-
components: {
|
|
37669
|
-
schemas: {},
|
|
37670
|
-
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
37671
|
-
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
37672
|
-
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
37673
|
-
securitySchemes: schemes
|
|
37674
|
-
}
|
|
37675
|
-
};
|
|
37676
|
-
const defaultScheme = process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth";
|
|
37677
|
-
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
37678
|
-
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
37679
|
-
const refSchemas = /* @__PURE__ */ new Set();
|
|
37680
|
-
const tableToSchema = /* @__PURE__ */ new Map();
|
|
37694
|
+
return info;
|
|
37695
|
+
}
|
|
37696
|
+
function buildComponentSchemas(models, spec, tableToSchema) {
|
|
37681
37697
|
for (const model of models) {
|
|
37682
37698
|
const schemaKey = schemaNameForModel(model);
|
|
37683
37699
|
tableToSchema.set(model.tableName, schemaKey);
|
|
37684
37700
|
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
37685
37701
|
}
|
|
37686
|
-
|
|
37687
|
-
|
|
37688
|
-
|
|
37689
|
-
|
|
37690
|
-
|
|
37691
|
-
|
|
37692
|
-
|
|
37693
|
-
|
|
37694
|
-
|
|
37695
|
-
|
|
37696
|
-
|
|
37697
|
-
|
|
37698
|
-
|
|
37699
|
-
|
|
37700
|
-
|
|
37701
|
-
|
|
37702
|
-
|
|
37703
|
-
|
|
37704
|
-
"200": { description: "Successful response" }
|
|
37705
|
-
}
|
|
37706
|
-
};
|
|
37707
|
-
if (route.meta?.description) operation.description = route.meta.description;
|
|
37708
|
-
if (route.meta?.deprecated) operation.deprecated = true;
|
|
37709
|
-
const pathParams = extractPathParams(route.pattern);
|
|
37710
|
-
if (pathParams.length > 0) {
|
|
37711
|
-
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
37712
|
-
name,
|
|
37713
|
-
in: "path",
|
|
37714
|
-
required: true,
|
|
37715
|
-
schema
|
|
37716
|
-
}));
|
|
37702
|
+
}
|
|
37703
|
+
function buildOperation(route, spec, ctx) {
|
|
37704
|
+
if (!isIncludedPath(route.pattern, ctx.includePrefixes, ctx.excludePrefixes)) return;
|
|
37705
|
+
const openApiPath = patternToOpenAPI(route.pattern);
|
|
37706
|
+
const method = route.method.toLowerCase();
|
|
37707
|
+
if (!spec.paths[openApiPath]) {
|
|
37708
|
+
spec.paths[openApiPath] = {};
|
|
37709
|
+
}
|
|
37710
|
+
const tags = route.meta?.tags ?? inferTags(route.pattern);
|
|
37711
|
+
for (const t of tags) {
|
|
37712
|
+
if (!ctx.usedTags.includes(t)) ctx.usedTags.push(t);
|
|
37713
|
+
}
|
|
37714
|
+
const operation = {
|
|
37715
|
+
operationId: uniqueOperationId(method, openApiPath, ctx.seenIds),
|
|
37716
|
+
summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
|
|
37717
|
+
tags,
|
|
37718
|
+
responses: route.meta?.responses ?? {
|
|
37719
|
+
"200": { description: "Successful response" }
|
|
37717
37720
|
}
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
|
|
37722
|
-
|
|
37723
|
-
|
|
37724
|
-
|
|
37725
|
-
|
|
37726
|
-
|
|
37727
|
-
|
|
37721
|
+
};
|
|
37722
|
+
if (route.meta?.description) operation.description = route.meta.description;
|
|
37723
|
+
if (route.meta?.deprecated) operation.deprecated = true;
|
|
37724
|
+
const parameters = operationParameters(route, method, ctx.models);
|
|
37725
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
37726
|
+
operationRequestBody(route, method, operation, ctx);
|
|
37727
|
+
operationResponseSchemas(route, operation, ctx.refSchemas);
|
|
37728
|
+
operationSecurity(route, method, operation, ctx.schemes, ctx.defaultScheme);
|
|
37729
|
+
spec.paths[openApiPath][method] = operation;
|
|
37730
|
+
}
|
|
37731
|
+
function operationParameters(route, method, models) {
|
|
37732
|
+
let parameters = [];
|
|
37733
|
+
const pathParams = extractPathParams(route.pattern);
|
|
37734
|
+
if (pathParams.length > 0) {
|
|
37735
|
+
parameters = pathParams.map(({ name, schema }) => ({
|
|
37736
|
+
name,
|
|
37737
|
+
in: "path",
|
|
37738
|
+
required: true,
|
|
37739
|
+
schema
|
|
37740
|
+
}));
|
|
37741
|
+
}
|
|
37742
|
+
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
37743
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
37744
|
+
if (modelName && models.some((m) => m.tableName === modelName)) {
|
|
37745
|
+
parameters = [
|
|
37746
|
+
...parameters,
|
|
37747
|
+
{ name: "page", in: "query", schema: { type: "integer", default: 1 } },
|
|
37748
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
|
|
37749
|
+
{ name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" }
|
|
37750
|
+
];
|
|
37728
37751
|
}
|
|
37729
|
-
|
|
37730
|
-
|
|
37731
|
-
|
|
37732
|
-
|
|
37733
|
-
|
|
37734
|
-
|
|
37735
|
-
|
|
37752
|
+
}
|
|
37753
|
+
return parameters;
|
|
37754
|
+
}
|
|
37755
|
+
function mediaWithExample(schema, example) {
|
|
37756
|
+
const media = { schema };
|
|
37757
|
+
if (example !== void 0) media.example = example;
|
|
37758
|
+
return media;
|
|
37759
|
+
}
|
|
37760
|
+
function operationRequestBody(route, method, operation, ctx) {
|
|
37761
|
+
const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
|
|
37762
|
+
if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
|
|
37763
|
+
ctx.refSchemas.add(reqSchemaRef.name);
|
|
37764
|
+
const media = mediaWithExample({ $ref: `#/components/schemas/${reqSchemaRef.name}` }, route.meta?.example);
|
|
37765
|
+
operation.requestBody = {
|
|
37766
|
+
content: { [reqSchemaRef.contentType]: media }
|
|
37767
|
+
};
|
|
37768
|
+
} else if (method === "post" || method === "put") {
|
|
37769
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
37770
|
+
const schemaKey = modelName ? ctx.tableToSchema.get(modelName) : void 0;
|
|
37771
|
+
if (schemaKey) {
|
|
37772
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
37736
37773
|
operation.requestBody = {
|
|
37737
|
-
|
|
37774
|
+
required: true,
|
|
37775
|
+
content: { "application/json": mediaWithExample({ $ref: sref }, route.meta?.example) }
|
|
37738
37776
|
};
|
|
37739
|
-
|
|
37740
|
-
|
|
37741
|
-
|
|
37742
|
-
if (schemaKey) {
|
|
37743
|
-
const sref = `#/components/schemas/${schemaKey}`;
|
|
37744
|
-
const media = { schema: { $ref: sref } };
|
|
37745
|
-
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
37746
|
-
operation.requestBody = {
|
|
37747
|
-
required: true,
|
|
37748
|
-
content: { "application/json": media }
|
|
37749
|
-
};
|
|
37750
|
-
if (route.meta?.responses === void 0) {
|
|
37751
|
-
operation.responses = {
|
|
37752
|
-
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
37753
|
-
};
|
|
37754
|
-
}
|
|
37755
|
-
} else if (route.meta?.example !== void 0) {
|
|
37756
|
-
operation.requestBody = {
|
|
37757
|
-
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
37777
|
+
if (route.meta?.responses === void 0) {
|
|
37778
|
+
operation.responses = {
|
|
37779
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
37758
37780
|
};
|
|
37759
37781
|
}
|
|
37782
|
+
} else if (route.meta?.example !== void 0) {
|
|
37783
|
+
operation.requestBody = {
|
|
37784
|
+
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
37785
|
+
};
|
|
37760
37786
|
}
|
|
37761
|
-
|
|
37762
|
-
|
|
37763
|
-
|
|
37764
|
-
|
|
37765
|
-
|
|
37766
|
-
|
|
37767
|
-
|
|
37768
|
-
|
|
37769
|
-
|
|
37770
|
-
|
|
37771
|
-
|
|
37772
|
-
|
|
37787
|
+
}
|
|
37788
|
+
}
|
|
37789
|
+
function operationResponseSchemas(route, operation, refSchemas) {
|
|
37790
|
+
const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
|
|
37791
|
+
if (respSchemas.length > 0) {
|
|
37792
|
+
const responses = operation.responses;
|
|
37793
|
+
for (const { status: status2, name, isList } of respSchemas) {
|
|
37794
|
+
refSchemas.add(name);
|
|
37795
|
+
const sref = `#/components/schemas/${name}`;
|
|
37796
|
+
const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
|
|
37797
|
+
responses[status2] = {
|
|
37798
|
+
description: status2.startsWith("2") ? "Successful response" : "Response",
|
|
37799
|
+
content: { "application/json": { schema } }
|
|
37800
|
+
};
|
|
37773
37801
|
}
|
|
37774
|
-
|
|
37775
|
-
|
|
37776
|
-
|
|
37777
|
-
|
|
37778
|
-
|
|
37779
|
-
|
|
37780
|
-
|
|
37781
|
-
|
|
37782
|
-
} else if (routeRequiresAuth(route, method)) {
|
|
37783
|
-
const requirements = [{ [defaultScheme]: [] }];
|
|
37784
|
-
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
37785
|
-
operation.security = sanitizeSecurity(requirements, schemes);
|
|
37802
|
+
}
|
|
37803
|
+
}
|
|
37804
|
+
function operationSecurity(route, method, operation, schemes, defaultScheme) {
|
|
37805
|
+
const hasExplicitSecurity = route.meta?.security !== void 0 || route.meta?.scopes !== void 0 && route.meta.scopes.length > 0;
|
|
37806
|
+
if (hasExplicitSecurity) {
|
|
37807
|
+
const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
|
|
37808
|
+
operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
|
|
37809
|
+
if (normalized.length > 0) {
|
|
37786
37810
|
const responses = operation.responses;
|
|
37787
37811
|
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
37788
37812
|
}
|
|
37789
|
-
|
|
37813
|
+
} else if (routeRequiresAuth(route, method)) {
|
|
37814
|
+
const requirements = [{ [defaultScheme]: [] }];
|
|
37815
|
+
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
37816
|
+
operation.security = sanitizeSecurity(requirements, schemes);
|
|
37817
|
+
const responses = operation.responses;
|
|
37818
|
+
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
37790
37819
|
}
|
|
37820
|
+
}
|
|
37821
|
+
function buildRefSchemas(spec, refSchemas) {
|
|
37791
37822
|
if (refSchemas.size > 0) {
|
|
37792
37823
|
const schemas = spec.components.schemas;
|
|
37793
37824
|
for (const name of refSchemas) {
|
|
@@ -37796,10 +37827,11 @@ function generate2(routes, models = []) {
|
|
|
37796
37827
|
}
|
|
37797
37828
|
}
|
|
37798
37829
|
}
|
|
37830
|
+
}
|
|
37831
|
+
function buildTags(spec, usedTags) {
|
|
37799
37832
|
if (usedTags.length > 0) {
|
|
37800
37833
|
spec.tags = usedTags.map((name) => ({ name }));
|
|
37801
37834
|
}
|
|
37802
|
-
return spec;
|
|
37803
37835
|
}
|
|
37804
37836
|
function routeRequiresAuth(route, method) {
|
|
37805
37837
|
if (route.noAuth) return false;
|
|
@@ -39050,7 +39082,9 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
39050
39082
|
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
39051
39083
|
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
39052
39084
|
// suppressReload flag.
|
|
39053
|
-
|
|
39085
|
+
// Also suppress on the dev-admin dashboard (any /__dev page): its SPA reloads
|
|
39086
|
+
// itself gently, so the toolbar's full-page reloader must not fire there.
|
|
39087
|
+
reload: !ctx.isAiPortRequest && !ctx.pathname.startsWith("/__dev")
|
|
39054
39088
|
};
|
|
39055
39089
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
39056
39090
|
}
|
|
@@ -39305,6 +39339,51 @@ async function runDispatch(ctx, rawReq, rawRes) {
|
|
|
39305
39339
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
39306
39340
|
return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
|
|
39307
39341
|
}
|
|
39342
|
+
function loopbackBindHosts(host) {
|
|
39343
|
+
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
39344
|
+
switch (normalized) {
|
|
39345
|
+
case "localhost":
|
|
39346
|
+
return ["127.0.0.1", "::1"];
|
|
39347
|
+
case "127.0.0.1":
|
|
39348
|
+
case "0.0.0.0":
|
|
39349
|
+
return ["::1"];
|
|
39350
|
+
case "::1":
|
|
39351
|
+
case "::":
|
|
39352
|
+
return ["127.0.0.1"];
|
|
39353
|
+
default:
|
|
39354
|
+
return [];
|
|
39355
|
+
}
|
|
39356
|
+
}
|
|
39357
|
+
function startLoopbackSiblings(port, host, dispatch) {
|
|
39358
|
+
const siblingHosts = loopbackBindHosts(host);
|
|
39359
|
+
if (siblingHosts.length === 0) return Promise.resolve([]);
|
|
39360
|
+
return Promise.all(
|
|
39361
|
+
siblingHosts.map(
|
|
39362
|
+
(siblingHost) => new Promise((resolveSibling) => {
|
|
39363
|
+
const sibling = createServer2(dispatch);
|
|
39364
|
+
let settled = false;
|
|
39365
|
+
sibling.on("error", (err) => {
|
|
39366
|
+
const expected = err.code === "EADDRINUSE" || err.code === "EADDRNOTAVAIL" || err.code === "EAFNOSUPPORT";
|
|
39367
|
+
if (!expected) {
|
|
39368
|
+
Log.debug(
|
|
39369
|
+
`Loopback sibling ${siblingHost}:${port} not bound (${err.code ?? err.message}) \u2014 skipped, primary already serves`
|
|
39370
|
+
);
|
|
39371
|
+
}
|
|
39372
|
+
if (!settled) {
|
|
39373
|
+
settled = true;
|
|
39374
|
+
resolveSibling(null);
|
|
39375
|
+
}
|
|
39376
|
+
});
|
|
39377
|
+
sibling.listen(port, siblingHost, () => {
|
|
39378
|
+
settled = true;
|
|
39379
|
+
resolveSibling(sibling);
|
|
39380
|
+
});
|
|
39381
|
+
})
|
|
39382
|
+
)
|
|
39383
|
+
).then(
|
|
39384
|
+
(results) => results.filter((s) => s !== null)
|
|
39385
|
+
);
|
|
39386
|
+
}
|
|
39308
39387
|
async function startServer(config) {
|
|
39309
39388
|
loadEnv(".env.local");
|
|
39310
39389
|
loadEnv();
|
|
@@ -39573,8 +39652,9 @@ ${reset2}
|
|
|
39573
39652
|
}
|
|
39574
39653
|
});
|
|
39575
39654
|
return new Promise((resolvePromise) => {
|
|
39576
|
-
server.listen(port, host, () => {
|
|
39655
|
+
server.listen(port, host, async () => {
|
|
39577
39656
|
if (!cluster.isWorker) writePidfile(port);
|
|
39657
|
+
const siblingServers = await startLoopbackSiblings(port, host, dispatch);
|
|
39578
39658
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
39579
39659
|
const isDebug = isTruthy(process.env.TINA4_DEBUG);
|
|
39580
39660
|
const logLevel = (process.env.TINA4_LOG_LEVEL ?? "DEBUG").toUpperCase();
|
|
@@ -39637,14 +39717,22 @@ ${reset2}
|
|
|
39637
39717
|
}
|
|
39638
39718
|
let shuttingDown = false;
|
|
39639
39719
|
const closeListeners = () => new Promise((done) => {
|
|
39640
|
-
let pending = aiServer ?
|
|
39720
|
+
let pending = 1 + (aiServer ? 1 : 0) + siblingServers.length;
|
|
39641
39721
|
const one = () => {
|
|
39642
39722
|
if (--pending === 0) done();
|
|
39643
39723
|
};
|
|
39644
39724
|
server.close(one);
|
|
39645
39725
|
if (aiServer) aiServer.close(one);
|
|
39726
|
+
for (const sibling of siblingServers) {
|
|
39727
|
+
try {
|
|
39728
|
+
sibling.close(one);
|
|
39729
|
+
} catch {
|
|
39730
|
+
one();
|
|
39731
|
+
}
|
|
39732
|
+
}
|
|
39646
39733
|
server.closeIdleConnections();
|
|
39647
39734
|
aiServer?.closeIdleConnections();
|
|
39735
|
+
for (const sibling of siblingServers) sibling.closeIdleConnections();
|
|
39648
39736
|
});
|
|
39649
39737
|
const gracefulShutdown = async (signal) => {
|
|
39650
39738
|
if (shuttingDown) return;
|
|
@@ -39672,6 +39760,7 @@ ${reset2}
|
|
|
39672
39760
|
);
|
|
39673
39761
|
server.closeAllConnections();
|
|
39674
39762
|
aiServer?.closeAllConnections();
|
|
39763
|
+
for (const sibling of siblingServers) sibling.closeAllConnections();
|
|
39675
39764
|
}
|
|
39676
39765
|
try {
|
|
39677
39766
|
const orm = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -39699,6 +39788,12 @@ ${reset2}
|
|
|
39699
39788
|
stopAllBackgroundTasks();
|
|
39700
39789
|
if (aiServer) aiServer.close();
|
|
39701
39790
|
server.close();
|
|
39791
|
+
for (const sibling of siblingServers) {
|
|
39792
|
+
try {
|
|
39793
|
+
sibling.close();
|
|
39794
|
+
} catch {
|
|
39795
|
+
}
|
|
39796
|
+
}
|
|
39702
39797
|
Promise.resolve().then(() => (init_src(), src_exports)).then((orm) => orm.closeDatabase()).catch(() => {
|
|
39703
39798
|
});
|
|
39704
39799
|
},
|
|
@@ -46902,6 +46997,7 @@ __export(src_exports3, {
|
|
|
46902
46997
|
isValidSessionId: () => isValidSessionId,
|
|
46903
46998
|
kafkaSecurityConfig: () => kafkaSecurityConfig,
|
|
46904
46999
|
loadEnv: () => loadEnv,
|
|
47000
|
+
loopbackBindHosts: () => loopbackBindHosts,
|
|
46905
47001
|
makeCaseInsensitiveHeaders: () => makeCaseInsensitiveHeaders,
|
|
46906
47002
|
matchCronField: () => matchCronField,
|
|
46907
47003
|
matchesCron: () => matchesCron,
|