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
|
@@ -4063,33 +4063,17 @@ var init_engine = __esm({
|
|
|
4063
4063
|
i++;
|
|
4064
4064
|
}
|
|
4065
4065
|
}
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
let depth2 = 0;
|
|
4078
|
-
while (i2 < tokens.length) {
|
|
4079
|
-
if (tokens[i2][0] === "BLOCK") {
|
|
4080
|
-
const [tagContent] = stripTag(tokens[i2][1]);
|
|
4081
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4082
|
-
if (tag === "cache") depth2++;
|
|
4083
|
-
else if (tag === "endcache") {
|
|
4084
|
-
if (depth2 === 0) return [htmlContent, i2 + 1];
|
|
4085
|
-
depth2--;
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
i2++;
|
|
4089
|
-
}
|
|
4090
|
-
return [htmlContent, i2];
|
|
4091
|
-
}
|
|
4092
|
-
}
|
|
4066
|
+
/**
|
|
4067
|
+
* Collect the body tokens of a {% <openTag> %}...{% end<openTag> %} block,
|
|
4068
|
+
* starting from the token after the opening tag (start + 1). Nested same-tag
|
|
4069
|
+
* blocks are kept in the body and balanced by depth; the matching closing tag is
|
|
4070
|
+
* consumed but NOT included. Returns [bodyTokens, indexAfterClosingTag].
|
|
4071
|
+
*
|
|
4072
|
+
* canNest guards the open-tag count: handleSetBlock passes it so the inline
|
|
4073
|
+
* {% set x = 1 %} form (which has no {% endset %}) never opens a nested block —
|
|
4074
|
+
* only the block form {% set x %} nests. Omitted, every openTag occurrence nests.
|
|
4075
|
+
*/
|
|
4076
|
+
collectBlockBody(tokens, start2, openTag, closeTag, canNest) {
|
|
4093
4077
|
const bodyTokens = [];
|
|
4094
4078
|
let i = start2 + 1;
|
|
4095
4079
|
let depth = 0;
|
|
@@ -4097,10 +4081,10 @@ var init_engine = __esm({
|
|
|
4097
4081
|
if (tokens[i][0] === "BLOCK") {
|
|
4098
4082
|
const [tagContent] = stripTag(tokens[i][1]);
|
|
4099
4083
|
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4100
|
-
if (tag ===
|
|
4084
|
+
if (tag === openTag && (canNest ? canNest(tagContent) : true)) {
|
|
4101
4085
|
depth++;
|
|
4102
4086
|
bodyTokens.push(tokens[i]);
|
|
4103
|
-
} else if (tag ===
|
|
4087
|
+
} else if (tag === closeTag) {
|
|
4104
4088
|
if (depth === 0) {
|
|
4105
4089
|
i++;
|
|
4106
4090
|
break;
|
|
@@ -4115,6 +4099,36 @@ var init_engine = __esm({
|
|
|
4115
4099
|
}
|
|
4116
4100
|
i++;
|
|
4117
4101
|
}
|
|
4102
|
+
return [bodyTokens, i];
|
|
4103
|
+
}
|
|
4104
|
+
handleCache(tokens, start2, context) {
|
|
4105
|
+
const [content] = stripTag(tokens[start2][1]);
|
|
4106
|
+
const m = content.match(/^cache\s+["'](.+?)["']\s*(\d+)?/);
|
|
4107
|
+
const cacheKey = m ? m[1] : "default";
|
|
4108
|
+
const ttl = m && m[2] ? parseInt(m[2], 10) : 60;
|
|
4109
|
+
sweepExpiredCache(this.fragmentCache);
|
|
4110
|
+
const cached = this.fragmentCache.get(cacheKey);
|
|
4111
|
+
if (cached) {
|
|
4112
|
+
const [htmlContent, expiresAt] = cached;
|
|
4113
|
+
if (Date.now() < expiresAt) {
|
|
4114
|
+
let i2 = start2 + 1;
|
|
4115
|
+
let depth = 0;
|
|
4116
|
+
while (i2 < tokens.length) {
|
|
4117
|
+
if (tokens[i2][0] === "BLOCK") {
|
|
4118
|
+
const [tagContent] = stripTag(tokens[i2][1]);
|
|
4119
|
+
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4120
|
+
if (tag === "cache") depth++;
|
|
4121
|
+
else if (tag === "endcache") {
|
|
4122
|
+
if (depth === 0) return [htmlContent, i2 + 1];
|
|
4123
|
+
depth--;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
i2++;
|
|
4127
|
+
}
|
|
4128
|
+
return [htmlContent, i2];
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "cache", "endcache");
|
|
4118
4132
|
const rendered = this.renderTokens([...bodyTokens], context);
|
|
4119
4133
|
capCache(this.fragmentCache, TEMPLATE_CACHE_MAX);
|
|
4120
4134
|
this.fragmentCache.set(cacheKey, [rendered, Date.now() + ttl * 1e3]);
|
|
@@ -4279,62 +4293,20 @@ var init_engine = __esm({
|
|
|
4279
4293
|
handleSetBlock(tokens, start2, context) {
|
|
4280
4294
|
const [content] = stripTag(tokens[start2][1]);
|
|
4281
4295
|
const name = (content.split(/\s+/)[1] || "").trim();
|
|
4282
|
-
const bodyTokens =
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
if (tag === "set" && !tagContent.includes("=")) {
|
|
4290
|
-
depth++;
|
|
4291
|
-
bodyTokens.push(tokens[i]);
|
|
4292
|
-
} else if (tag === "endset") {
|
|
4293
|
-
if (depth === 0) {
|
|
4294
|
-
i++;
|
|
4295
|
-
break;
|
|
4296
|
-
}
|
|
4297
|
-
depth--;
|
|
4298
|
-
bodyTokens.push(tokens[i]);
|
|
4299
|
-
} else {
|
|
4300
|
-
bodyTokens.push(tokens[i]);
|
|
4301
|
-
}
|
|
4302
|
-
} else {
|
|
4303
|
-
bodyTokens.push(tokens[i]);
|
|
4304
|
-
}
|
|
4305
|
-
i++;
|
|
4306
|
-
}
|
|
4296
|
+
const [bodyTokens, i] = this.collectBlockBody(
|
|
4297
|
+
tokens,
|
|
4298
|
+
start2,
|
|
4299
|
+
"set",
|
|
4300
|
+
"endset",
|
|
4301
|
+
(tagContent) => !tagContent.includes("=")
|
|
4302
|
+
);
|
|
4307
4303
|
if (name) {
|
|
4308
4304
|
context[name] = new SafeString(this.renderTokens([...bodyTokens], context));
|
|
4309
4305
|
}
|
|
4310
4306
|
return i;
|
|
4311
4307
|
}
|
|
4312
4308
|
handleSpaceless(tokens, start2, context) {
|
|
4313
|
-
const bodyTokens =
|
|
4314
|
-
let i = start2 + 1;
|
|
4315
|
-
let depth = 0;
|
|
4316
|
-
while (i < tokens.length) {
|
|
4317
|
-
if (tokens[i][0] === "BLOCK") {
|
|
4318
|
-
const [tagContent] = stripTag(tokens[i][1]);
|
|
4319
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4320
|
-
if (tag === "spaceless") {
|
|
4321
|
-
depth++;
|
|
4322
|
-
bodyTokens.push(tokens[i]);
|
|
4323
|
-
} else if (tag === "endspaceless") {
|
|
4324
|
-
if (depth === 0) {
|
|
4325
|
-
i++;
|
|
4326
|
-
break;
|
|
4327
|
-
}
|
|
4328
|
-
depth--;
|
|
4329
|
-
bodyTokens.push(tokens[i]);
|
|
4330
|
-
} else {
|
|
4331
|
-
bodyTokens.push(tokens[i]);
|
|
4332
|
-
}
|
|
4333
|
-
} else {
|
|
4334
|
-
bodyTokens.push(tokens[i]);
|
|
4335
|
-
}
|
|
4336
|
-
i++;
|
|
4337
|
-
}
|
|
4309
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "spaceless", "endspaceless");
|
|
4338
4310
|
let rendered = this.renderTokens([...bodyTokens], context);
|
|
4339
4311
|
rendered = rendered.replace(/>\s+</g, "><");
|
|
4340
4312
|
return [rendered, i];
|
|
@@ -4343,31 +4315,7 @@ var init_engine = __esm({
|
|
|
4343
4315
|
const [content] = stripTag(tokens[start2][1]);
|
|
4344
4316
|
const modeMatch = content.match(/^autoescape\s+(false|true)/);
|
|
4345
4317
|
const autoEscapeOn = !(modeMatch && modeMatch[1] === "false");
|
|
4346
|
-
const bodyTokens =
|
|
4347
|
-
let i = start2 + 1;
|
|
4348
|
-
let depth = 0;
|
|
4349
|
-
while (i < tokens.length) {
|
|
4350
|
-
if (tokens[i][0] === "BLOCK") {
|
|
4351
|
-
const [tagContent] = stripTag(tokens[i][1]);
|
|
4352
|
-
const tag = tagContent.split(/\s+/)[0] || "";
|
|
4353
|
-
if (tag === "autoescape") {
|
|
4354
|
-
depth++;
|
|
4355
|
-
bodyTokens.push(tokens[i]);
|
|
4356
|
-
} else if (tag === "endautoescape") {
|
|
4357
|
-
if (depth === 0) {
|
|
4358
|
-
i++;
|
|
4359
|
-
break;
|
|
4360
|
-
}
|
|
4361
|
-
depth--;
|
|
4362
|
-
bodyTokens.push(tokens[i]);
|
|
4363
|
-
} else {
|
|
4364
|
-
bodyTokens.push(tokens[i]);
|
|
4365
|
-
}
|
|
4366
|
-
} else {
|
|
4367
|
-
bodyTokens.push(tokens[i]);
|
|
4368
|
-
}
|
|
4369
|
-
i++;
|
|
4370
|
-
}
|
|
4318
|
+
const [bodyTokens, i] = this.collectBlockBody(tokens, start2, "autoescape", "endautoescape");
|
|
4371
4319
|
if (!autoEscapeOn) {
|
|
4372
4320
|
const oldAutoEscape = this._autoEscape;
|
|
4373
4321
|
this._autoEscape = false;
|
|
@@ -5721,6 +5669,99 @@ var init_databaseResult = __esm({
|
|
|
5721
5669
|
}
|
|
5722
5670
|
});
|
|
5723
5671
|
|
|
5672
|
+
// ../orm/src/modelCollection.ts
|
|
5673
|
+
var ModelCollection;
|
|
5674
|
+
var init_modelCollection = __esm({
|
|
5675
|
+
"../orm/src/modelCollection.ts"() {
|
|
5676
|
+
"use strict";
|
|
5677
|
+
ModelCollection = class extends Array {
|
|
5678
|
+
/** Total rows matching the query's filter (ignores limit/offset). */
|
|
5679
|
+
_total = 0;
|
|
5680
|
+
/** The SQL limit that produced this page. */
|
|
5681
|
+
_limit = 0;
|
|
5682
|
+
/** The SQL offset that produced this page. */
|
|
5683
|
+
_offset = 0;
|
|
5684
|
+
/**
|
|
5685
|
+
* Derived array operations (`map`, `filter`, `slice`, spread, …) build a plain
|
|
5686
|
+
* `Array`, never another `ModelCollection`. This is what defuses the Array-
|
|
5687
|
+
* subclass constructor/species trap: the engine never calls this constructor
|
|
5688
|
+
* with a length during those operations.
|
|
5689
|
+
*/
|
|
5690
|
+
static get [Symbol.species]() {
|
|
5691
|
+
return Array;
|
|
5692
|
+
}
|
|
5693
|
+
/**
|
|
5694
|
+
* @param items the page of hydrated model instances (or the length, when the
|
|
5695
|
+
* engine constructs a derived array — defended against here).
|
|
5696
|
+
* @param total total rows matching the query's filter (ignores limit/offset).
|
|
5697
|
+
* @param limit the SQL limit that produced this page.
|
|
5698
|
+
* @param offset the SQL offset that produced this page.
|
|
5699
|
+
*/
|
|
5700
|
+
constructor(items, total = 0, limit = 0, offset = 0) {
|
|
5701
|
+
super();
|
|
5702
|
+
if (typeof items === "number") {
|
|
5703
|
+
this.length = items;
|
|
5704
|
+
return;
|
|
5705
|
+
}
|
|
5706
|
+
if (items) {
|
|
5707
|
+
for (let i = 0; i < items.length; i++) {
|
|
5708
|
+
this[i] = items[i];
|
|
5709
|
+
}
|
|
5710
|
+
}
|
|
5711
|
+
this._total = Math.trunc(total) || 0;
|
|
5712
|
+
this._limit = Math.trunc(limit) || 0;
|
|
5713
|
+
this._offset = Math.trunc(offset) || 0;
|
|
5714
|
+
}
|
|
5715
|
+
/**
|
|
5716
|
+
* Total rows matching the query's filter, ignoring limit/offset.
|
|
5717
|
+
*
|
|
5718
|
+
* This is the whole point of the collection: the page slice you are iterating
|
|
5719
|
+
* is capped by `limit`, but this number is the full count of matching rows —
|
|
5720
|
+
* what a pager needs to render "page 3 of 13".
|
|
5721
|
+
*/
|
|
5722
|
+
getTotalRecords() {
|
|
5723
|
+
return this._total;
|
|
5724
|
+
}
|
|
5725
|
+
/**
|
|
5726
|
+
* The canonical pagination envelope — seven snake_case keys, identical to
|
|
5727
|
+
* `DatabaseResult.toPaginate()` (ADR-0043) and to the other three frameworks'
|
|
5728
|
+
* `toPaginate()` / `to_paginate()`.
|
|
5729
|
+
*
|
|
5730
|
+
* records the page's rows as plain objects (never re-sliced)
|
|
5731
|
+
* total getTotalRecords() — the true total for the filter
|
|
5732
|
+
* page floor(offset / per_page) + 1
|
|
5733
|
+
* per_page the query's limit
|
|
5734
|
+
* total_pages ceil(total / per_page)
|
|
5735
|
+
* limit the SQL limit actually applied
|
|
5736
|
+
* offset the SQL offset actually applied
|
|
5737
|
+
*
|
|
5738
|
+
* `records` are model dicts (via `toDict()`, the same serialisation the
|
|
5739
|
+
* framework applies to a model in a JSON response), so the JSON a client sees
|
|
5740
|
+
* matches `DatabaseResult` exactly — the result is uniform whether the route
|
|
5741
|
+
* returned a raw `db.fetch()` or an ORM query.
|
|
5742
|
+
*/
|
|
5743
|
+
toPaginate() {
|
|
5744
|
+
const perPage = this._limit > 0 ? this._limit : this.length;
|
|
5745
|
+
const page = perPage > 0 ? Math.floor(this._offset / perPage) + 1 : 1;
|
|
5746
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this._total / perPage)) : 1;
|
|
5747
|
+
const records = this.map((model) => {
|
|
5748
|
+
const m = model;
|
|
5749
|
+
return m && typeof m.toDict === "function" ? m.toDict() : model;
|
|
5750
|
+
});
|
|
5751
|
+
return {
|
|
5752
|
+
records,
|
|
5753
|
+
total: this._total,
|
|
5754
|
+
page,
|
|
5755
|
+
per_page: perPage,
|
|
5756
|
+
total_pages: totalPages,
|
|
5757
|
+
limit: perPage,
|
|
5758
|
+
offset: this._offset
|
|
5759
|
+
};
|
|
5760
|
+
}
|
|
5761
|
+
};
|
|
5762
|
+
}
|
|
5763
|
+
});
|
|
5764
|
+
|
|
5724
5765
|
// ../orm/src/databaseUrl.ts
|
|
5725
5766
|
import { inspect } from "node:util";
|
|
5726
5767
|
function stripOneSlash(path8) {
|
|
@@ -14069,6 +14110,7 @@ var init_baseModel = __esm({
|
|
|
14069
14110
|
"../orm/src/baseModel.ts"() {
|
|
14070
14111
|
"use strict";
|
|
14071
14112
|
init_database();
|
|
14113
|
+
init_modelCollection();
|
|
14072
14114
|
init_validation();
|
|
14073
14115
|
init_queryBuilder();
|
|
14074
14116
|
init_sqlite();
|
|
@@ -14304,6 +14346,30 @@ var init_baseModel = __esm({
|
|
|
14304
14346
|
static getPkColumn() {
|
|
14305
14347
|
return this.getDbColumn(this.getPkField());
|
|
14306
14348
|
}
|
|
14349
|
+
/**
|
|
14350
|
+
* Shared read tail for the collection-returning finders (where / all / select
|
|
14351
|
+
* / find filter-form / withTrashed). Runs the SAME two calls `db.fetch()`
|
|
14352
|
+
* makes — the page fetch AND the COUNT probe over the SAME base SQL — hydrates
|
|
14353
|
+
* the rows into model instances, and returns a ModelCollection carrying the
|
|
14354
|
+
* total (ADR-0064).
|
|
14355
|
+
*
|
|
14356
|
+
* The total is FREE: `probeTotal` is the exact `COUNT(*)` probe `db.fetch()`
|
|
14357
|
+
* already runs; the ORM used to discard it. ZERO extra queries beyond that one
|
|
14358
|
+
* probe. `sql` MUST NOT carry its own LIMIT/OFFSET — `adapterFetch` applies
|
|
14359
|
+
* limit/offset to the page, and the probe wraps the un-limited SQL so it counts
|
|
14360
|
+
* the WHOLE filtered set, not the page.
|
|
14361
|
+
*/
|
|
14362
|
+
static async _collect(sql, params, limit, offset, include) {
|
|
14363
|
+
const db = this.getDb();
|
|
14364
|
+
const rows = await adapterFetch(db, sql, params, limit, offset);
|
|
14365
|
+
const data = Array.isArray(rows) ? rows : rows?.data ?? [];
|
|
14366
|
+
const total = await probeTotal(db, sql, params, limit) ?? data.length;
|
|
14367
|
+
const instances = data.map((row) => new this(row));
|
|
14368
|
+
if (include) {
|
|
14369
|
+
await this._eagerLoad(instances, include);
|
|
14370
|
+
}
|
|
14371
|
+
return new ModelCollection(instances, total, limit, offset);
|
|
14372
|
+
}
|
|
14307
14373
|
/**
|
|
14308
14374
|
* Find a record by primary key.
|
|
14309
14375
|
* @param id Primary key value.
|
|
@@ -14356,7 +14422,6 @@ var init_baseModel = __esm({
|
|
|
14356
14422
|
);
|
|
14357
14423
|
}
|
|
14358
14424
|
const lim = typeof limit === "number" ? limit : 100;
|
|
14359
|
-
const db = ModelClass.getDb();
|
|
14360
14425
|
const conditions = [];
|
|
14361
14426
|
const params = [];
|
|
14362
14427
|
if (filter) {
|
|
@@ -14376,16 +14441,7 @@ var init_baseModel = __esm({
|
|
|
14376
14441
|
if (orderBy) {
|
|
14377
14442
|
sql += ` ORDER BY ${orderBy}`;
|
|
14378
14443
|
}
|
|
14379
|
-
|
|
14380
|
-
const data = rows?.data ?? rows;
|
|
14381
|
-
const instances = (Array.isArray(data) ? data : []).map((row) => {
|
|
14382
|
-
const inst = new this(row);
|
|
14383
|
-
return inst;
|
|
14384
|
-
});
|
|
14385
|
-
if (include) {
|
|
14386
|
-
await ModelClass._eagerLoad(instances, include);
|
|
14387
|
-
}
|
|
14388
|
-
return instances;
|
|
14444
|
+
return ModelClass._collect(sql, params, lim, offset, include);
|
|
14389
14445
|
}
|
|
14390
14446
|
/**
|
|
14391
14447
|
* Load a record into this instance via selectOne.
|
|
@@ -14453,7 +14509,6 @@ var init_baseModel = __esm({
|
|
|
14453
14509
|
*/
|
|
14454
14510
|
static async all(limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
14455
14511
|
const ModelClass = this;
|
|
14456
|
-
const db = ModelClass.getDb();
|
|
14457
14512
|
const conditions = [];
|
|
14458
14513
|
if (ModelClass.softDelete) {
|
|
14459
14514
|
conditions.push("is_deleted = 0");
|
|
@@ -14463,13 +14518,8 @@ var init_baseModel = __esm({
|
|
|
14463
14518
|
}
|
|
14464
14519
|
const whereClause = conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "";
|
|
14465
14520
|
const orderClause = orderBy ? ` ORDER BY ${orderBy}` : "";
|
|
14466
|
-
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}
|
|
14467
|
-
|
|
14468
|
-
const instances = rows.map((row) => new ModelClass(row));
|
|
14469
|
-
if (include) {
|
|
14470
|
-
await ModelClass._eagerLoad(instances, include);
|
|
14471
|
-
}
|
|
14472
|
-
return instances;
|
|
14521
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}"${whereClause}${orderClause}`;
|
|
14522
|
+
return ModelClass._collect(sql, [], limit, offset, include);
|
|
14473
14523
|
}
|
|
14474
14524
|
/**
|
|
14475
14525
|
* Query records with a WHERE clause.
|
|
@@ -14484,7 +14534,6 @@ var init_baseModel = __esm({
|
|
|
14484
14534
|
*/
|
|
14485
14535
|
static async where(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0, include, orderBy) {
|
|
14486
14536
|
const ModelClass = this;
|
|
14487
|
-
const db = ModelClass.getDb();
|
|
14488
14537
|
const parts = [];
|
|
14489
14538
|
if (ModelClass.softDelete) {
|
|
14490
14539
|
parts.push("is_deleted = 0");
|
|
@@ -14494,13 +14543,8 @@ var init_baseModel = __esm({
|
|
|
14494
14543
|
}
|
|
14495
14544
|
parts.push(`(${conditions})`);
|
|
14496
14545
|
const orderClause = orderBy ? ` ORDER BY ${orderBy}` : "";
|
|
14497
|
-
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}
|
|
14498
|
-
|
|
14499
|
-
const instances = rows.map((row) => new ModelClass(row));
|
|
14500
|
-
if (include) {
|
|
14501
|
-
await ModelClass._eagerLoad(instances, include);
|
|
14502
|
-
}
|
|
14503
|
-
return instances;
|
|
14546
|
+
const sql = `SELECT * FROM "${ModelClass.tableName}" WHERE ${parts.join(" AND ")}${orderClause}`;
|
|
14547
|
+
return ModelClass._collect(sql, params, limit, offset, include);
|
|
14504
14548
|
}
|
|
14505
14549
|
/**
|
|
14506
14550
|
* Save this instance (insert or update). Returns this on success (fluent
|
|
@@ -14980,10 +15024,7 @@ var init_baseModel = __esm({
|
|
|
14980
15024
|
*/
|
|
14981
15025
|
static async select(sql, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
14982
15026
|
const ModelClass = this;
|
|
14983
|
-
|
|
14984
|
-
const paged = SQLTranslator.appendLimit(sql, limit, offset);
|
|
14985
|
-
const rows = await adapterQuery(db, paged, params);
|
|
14986
|
-
return rows.map((row) => new ModelClass(row));
|
|
15027
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
14987
15028
|
}
|
|
14988
15029
|
static async selectOne(sql, params, include) {
|
|
14989
15030
|
const ModelClass = this;
|
|
@@ -15055,7 +15096,6 @@ var init_baseModel = __esm({
|
|
|
15055
15096
|
*/
|
|
15056
15097
|
static async withTrashed(conditions, params, limit = DEFAULT_ROW_CAP, offset = 0) {
|
|
15057
15098
|
const ModelClass = this;
|
|
15058
|
-
const db = ModelClass.getDb();
|
|
15059
15099
|
const parts = [];
|
|
15060
15100
|
if (ModelClass.tableFilter) {
|
|
15061
15101
|
parts.push(ModelClass.tableFilter);
|
|
@@ -15067,14 +15107,7 @@ var init_baseModel = __esm({
|
|
|
15067
15107
|
if (parts.length > 0) {
|
|
15068
15108
|
sql += ` WHERE ${parts.join(" AND ")}`;
|
|
15069
15109
|
}
|
|
15070
|
-
|
|
15071
|
-
sql += ` LIMIT ${limit}`;
|
|
15072
|
-
}
|
|
15073
|
-
if (offset !== void 0) {
|
|
15074
|
-
sql += ` OFFSET ${offset}`;
|
|
15075
|
-
}
|
|
15076
|
-
const rows = await adapterQuery(db, sql, params);
|
|
15077
|
-
return rows.map((row) => new ModelClass(row));
|
|
15110
|
+
return ModelClass._collect(sql, params, limit, offset);
|
|
15078
15111
|
}
|
|
15079
15112
|
/**
|
|
15080
15113
|
* Count records matching conditions (respects soft delete and table filter).
|
|
@@ -18413,6 +18446,7 @@ __export(src_exports, {
|
|
|
18413
18446
|
InvalidId: () => InvalidId,
|
|
18414
18447
|
LocalStorage: () => LocalStorage,
|
|
18415
18448
|
Migration: () => Migration,
|
|
18449
|
+
ModelCollection: () => ModelCollection,
|
|
18416
18450
|
MongodbAdapter: () => MongodbAdapter,
|
|
18417
18451
|
MssqlAdapter: () => MssqlAdapter,
|
|
18418
18452
|
MysqlAdapter: () => MysqlAdapter,
|
|
@@ -18517,6 +18551,7 @@ var init_src = __esm({
|
|
|
18517
18551
|
"use strict";
|
|
18518
18552
|
init_types();
|
|
18519
18553
|
init_databaseResult();
|
|
18554
|
+
init_modelCollection();
|
|
18520
18555
|
init_database();
|
|
18521
18556
|
init_database();
|
|
18522
18557
|
init_databaseUrl();
|
|
@@ -35243,7 +35278,7 @@ function toolbarJs() {
|
|
|
35243
35278
|
connect();
|
|
35244
35279
|
})();`;
|
|
35245
35280
|
}
|
|
35246
|
-
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,
|
|
35281
|
+
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;
|
|
35247
35282
|
var init_devAdmin = __esm({
|
|
35248
35283
|
"src/devAdmin.ts"() {
|
|
35249
35284
|
"use strict";
|
|
@@ -35581,9 +35616,6 @@ var init_devAdmin = __esm({
|
|
|
35581
35616
|
{ method: "POST", pattern: "/__dev/api/websockets/disconnect", handler: handleWebsocketsDisconnect },
|
|
35582
35617
|
// Tools
|
|
35583
35618
|
{ method: "POST", pattern: "/__dev/api/tool", handler: handleTool },
|
|
35584
|
-
// Chat — proxies to Rust agent /chat (SSE passthrough). Forwards
|
|
35585
|
-
// active_file and any other body keys verbatim. See proxyToSupervisor.
|
|
35586
|
-
{ method: "POST", pattern: "/__dev/api/chat", handler: handleChat },
|
|
35587
35619
|
// Threads — proxies to Rust agent /threads. Mirrors Python's
|
|
35588
35620
|
// _api_threads + _api_threads_sub.
|
|
35589
35621
|
{ method: "GET", pattern: "/__dev/api/threads", handler: handleThreads },
|
|
@@ -35662,8 +35694,6 @@ var init_devAdmin = __esm({
|
|
|
35662
35694
|
{ method: "GET", pattern: "/__dev/api/supervise/diff", handler: handleSuperviseStub },
|
|
35663
35695
|
{ method: "POST", pattern: "/__dev/api/supervise/commit", handler: handleSuperviseStub },
|
|
35664
35696
|
{ method: "POST", pattern: "/__dev/api/supervise/cancel", handler: handleSuperviseStub },
|
|
35665
|
-
// Execute — proxies to the framework_port+2000 Rust agent (SSE passthrough)
|
|
35666
|
-
{ method: "POST", pattern: "/__dev/api/execute", handler: handleExecute },
|
|
35667
35697
|
// Framework-grounding MCP token config — self-contained (.env upsert)
|
|
35668
35698
|
{ method: "GET", pattern: "/__dev/api/grounding/status", handler: handleGroundingStatus },
|
|
35669
35699
|
{ method: "POST", pattern: "/__dev/api/grounding/token", handler: handleGroundingToken },
|
|
@@ -36233,9 +36263,6 @@ var init_devAdmin = __esm({
|
|
|
36233
36263
|
}
|
|
36234
36264
|
res.json({ tool, status: "executed", message: `Tool '${tool}' executed (stub)`, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
|
|
36235
36265
|
};
|
|
36236
|
-
handleChat = async (req2, res) => {
|
|
36237
|
-
await proxyToSupervisor(req2, res, "/chat");
|
|
36238
|
-
};
|
|
36239
36266
|
DEFAULT_MCP_URL = "https://mcp.tina4.com";
|
|
36240
36267
|
handleGroundingStatus = async (_req, res) => {
|
|
36241
36268
|
const token = resolveDevEnvVar("TINA4_MCP_TOKEN");
|
|
@@ -36523,35 +36550,6 @@ var init_devAdmin = __esm({
|
|
|
36523
36550
|
501
|
|
36524
36551
|
);
|
|
36525
36552
|
};
|
|
36526
|
-
handleExecute = async (req2, res) => {
|
|
36527
|
-
const port = parseInt(process.env.TINA4_PORT ?? process.env.PORT ?? "7148", 10);
|
|
36528
|
-
const agentUrl = `http://127.0.0.1:${port + 2e3}/execute`;
|
|
36529
|
-
try {
|
|
36530
|
-
const upstream = await fetch(agentUrl, {
|
|
36531
|
-
method: "POST",
|
|
36532
|
-
headers: { "Content-Type": "application/json" },
|
|
36533
|
-
body: JSON.stringify(req2.body ?? {})
|
|
36534
|
-
});
|
|
36535
|
-
if (!upstream.body) {
|
|
36536
|
-
res.json({ error: "agent returned no body" }, 502);
|
|
36537
|
-
return;
|
|
36538
|
-
}
|
|
36539
|
-
res.raw.writeHead(upstream.status || 200, {
|
|
36540
|
-
"Content-Type": upstream.headers.get("content-type") || "text/event-stream",
|
|
36541
|
-
"Cache-Control": "no-cache",
|
|
36542
|
-
Connection: "keep-alive"
|
|
36543
|
-
});
|
|
36544
|
-
const reader = upstream.body.getReader();
|
|
36545
|
-
while (true) {
|
|
36546
|
-
const { done, value } = await reader.read();
|
|
36547
|
-
if (done) break;
|
|
36548
|
-
if (value) res.raw.write(Buffer.from(value));
|
|
36549
|
-
}
|
|
36550
|
-
res.raw.end();
|
|
36551
|
-
} catch (e) {
|
|
36552
|
-
res.json({ error: `agent unreachable at ${agentUrl}: ${e.message}` }, 502);
|
|
36553
|
-
}
|
|
36554
|
-
};
|
|
36555
36553
|
DEV_FILES_IGNORED = /* @__PURE__ */ new Set([
|
|
36556
36554
|
"__pycache__",
|
|
36557
36555
|
"node_modules",
|
|
@@ -37616,12 +37614,46 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
37616
37614
|
});
|
|
37617
37615
|
}
|
|
37618
37616
|
function generate2(routes, models = []) {
|
|
37617
|
+
const schemes = resolveSecuritySchemes();
|
|
37618
|
+
const spec = {
|
|
37619
|
+
openapi: resolveOpenApiVersion(),
|
|
37620
|
+
info: buildInfo(),
|
|
37621
|
+
servers: resolveServers(),
|
|
37622
|
+
paths: {},
|
|
37623
|
+
components: {
|
|
37624
|
+
schemas: {},
|
|
37625
|
+
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
37626
|
+
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
37627
|
+
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
37628
|
+
securitySchemes: schemes
|
|
37629
|
+
}
|
|
37630
|
+
};
|
|
37631
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
37632
|
+
buildComponentSchemas(models, spec, tableToSchema);
|
|
37633
|
+
const ctx = {
|
|
37634
|
+
models,
|
|
37635
|
+
tableToSchema,
|
|
37636
|
+
schemes,
|
|
37637
|
+
// Default scheme secured routes use when no explicit meta.security is set.
|
|
37638
|
+
defaultScheme: process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth",
|
|
37639
|
+
// Path filters (comma-separated raw-path prefixes).
|
|
37640
|
+
includePrefixes: csv(process.env.TINA4_SWAGGER_INCLUDE),
|
|
37641
|
+
excludePrefixes: csv(process.env.TINA4_SWAGGER_EXCLUDE),
|
|
37642
|
+
// Reusable custom schemas referenced by routes via meta.requestSchema/responseSchemas.
|
|
37643
|
+
refSchemas: /* @__PURE__ */ new Set(),
|
|
37644
|
+
usedTags: [],
|
|
37645
|
+
seenIds: /* @__PURE__ */ new Set()
|
|
37646
|
+
};
|
|
37647
|
+
for (const route of routes) {
|
|
37648
|
+
buildOperation(route, spec, ctx);
|
|
37649
|
+
}
|
|
37650
|
+
buildRefSchemas(spec, ctx.refSchemas);
|
|
37651
|
+
buildTags(spec, ctx.usedTags);
|
|
37652
|
+
return spec;
|
|
37653
|
+
}
|
|
37654
|
+
function buildInfo() {
|
|
37619
37655
|
const info = {
|
|
37620
37656
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
37621
|
-
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
37622
|
-
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
37623
|
-
// Both are the settled cross-framework defaults (parity with the Python
|
|
37624
|
-
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
37625
37657
|
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
37626
37658
|
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
37627
37659
|
};
|
|
@@ -37638,135 +37670,134 @@ function generate2(routes, models = []) {
|
|
|
37638
37670
|
const [name, url] = licenseRaw.split("|").map((s) => s.trim());
|
|
37639
37671
|
info.license = url ? { name, url } : { name };
|
|
37640
37672
|
}
|
|
37641
|
-
|
|
37642
|
-
|
|
37643
|
-
|
|
37644
|
-
info,
|
|
37645
|
-
servers: resolveServers(),
|
|
37646
|
-
paths: {},
|
|
37647
|
-
components: {
|
|
37648
|
-
schemas: {},
|
|
37649
|
-
// Configurable security schemes (v3.13.42): bearerFormat via env, optional
|
|
37650
|
-
// apiKey scheme, plus any programmatically-registered schemes (which may
|
|
37651
|
-
// override bearerAuth — e.g. an oauth2 scheme with scopes).
|
|
37652
|
-
securitySchemes: schemes
|
|
37653
|
-
}
|
|
37654
|
-
};
|
|
37655
|
-
const defaultScheme = process.env.TINA4_SWAGGER_DEFAULT_SCHEME ?? "bearerAuth";
|
|
37656
|
-
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
37657
|
-
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
37658
|
-
const refSchemas = /* @__PURE__ */ new Set();
|
|
37659
|
-
const tableToSchema = /* @__PURE__ */ new Map();
|
|
37673
|
+
return info;
|
|
37674
|
+
}
|
|
37675
|
+
function buildComponentSchemas(models, spec, tableToSchema) {
|
|
37660
37676
|
for (const model of models) {
|
|
37661
37677
|
const schemaKey = schemaNameForModel(model);
|
|
37662
37678
|
tableToSchema.set(model.tableName, schemaKey);
|
|
37663
37679
|
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
37664
37680
|
}
|
|
37665
|
-
|
|
37666
|
-
|
|
37667
|
-
|
|
37668
|
-
|
|
37669
|
-
|
|
37670
|
-
|
|
37671
|
-
|
|
37672
|
-
|
|
37673
|
-
|
|
37674
|
-
|
|
37675
|
-
|
|
37676
|
-
|
|
37677
|
-
|
|
37678
|
-
|
|
37679
|
-
|
|
37680
|
-
|
|
37681
|
-
|
|
37682
|
-
|
|
37683
|
-
"200": { description: "Successful response" }
|
|
37684
|
-
}
|
|
37685
|
-
};
|
|
37686
|
-
if (route.meta?.description) operation.description = route.meta.description;
|
|
37687
|
-
if (route.meta?.deprecated) operation.deprecated = true;
|
|
37688
|
-
const pathParams = extractPathParams(route.pattern);
|
|
37689
|
-
if (pathParams.length > 0) {
|
|
37690
|
-
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
37691
|
-
name,
|
|
37692
|
-
in: "path",
|
|
37693
|
-
required: true,
|
|
37694
|
-
schema
|
|
37695
|
-
}));
|
|
37681
|
+
}
|
|
37682
|
+
function buildOperation(route, spec, ctx) {
|
|
37683
|
+
if (!isIncludedPath(route.pattern, ctx.includePrefixes, ctx.excludePrefixes)) return;
|
|
37684
|
+
const openApiPath = patternToOpenAPI(route.pattern);
|
|
37685
|
+
const method = route.method.toLowerCase();
|
|
37686
|
+
if (!spec.paths[openApiPath]) {
|
|
37687
|
+
spec.paths[openApiPath] = {};
|
|
37688
|
+
}
|
|
37689
|
+
const tags = route.meta?.tags ?? inferTags(route.pattern);
|
|
37690
|
+
for (const t of tags) {
|
|
37691
|
+
if (!ctx.usedTags.includes(t)) ctx.usedTags.push(t);
|
|
37692
|
+
}
|
|
37693
|
+
const operation = {
|
|
37694
|
+
operationId: uniqueOperationId(method, openApiPath, ctx.seenIds),
|
|
37695
|
+
summary: route.meta?.summary ?? `${route.method} ${route.pattern}`,
|
|
37696
|
+
tags,
|
|
37697
|
+
responses: route.meta?.responses ?? {
|
|
37698
|
+
"200": { description: "Successful response" }
|
|
37696
37699
|
}
|
|
37697
|
-
|
|
37698
|
-
|
|
37699
|
-
|
|
37700
|
-
|
|
37701
|
-
|
|
37702
|
-
|
|
37703
|
-
|
|
37704
|
-
|
|
37705
|
-
|
|
37706
|
-
|
|
37700
|
+
};
|
|
37701
|
+
if (route.meta?.description) operation.description = route.meta.description;
|
|
37702
|
+
if (route.meta?.deprecated) operation.deprecated = true;
|
|
37703
|
+
const parameters = operationParameters(route, method, ctx.models);
|
|
37704
|
+
if (parameters.length > 0) operation.parameters = parameters;
|
|
37705
|
+
operationRequestBody(route, method, operation, ctx);
|
|
37706
|
+
operationResponseSchemas(route, operation, ctx.refSchemas);
|
|
37707
|
+
operationSecurity(route, method, operation, ctx.schemes, ctx.defaultScheme);
|
|
37708
|
+
spec.paths[openApiPath][method] = operation;
|
|
37709
|
+
}
|
|
37710
|
+
function operationParameters(route, method, models) {
|
|
37711
|
+
let parameters = [];
|
|
37712
|
+
const pathParams = extractPathParams(route.pattern);
|
|
37713
|
+
if (pathParams.length > 0) {
|
|
37714
|
+
parameters = pathParams.map(({ name, schema }) => ({
|
|
37715
|
+
name,
|
|
37716
|
+
in: "path",
|
|
37717
|
+
required: true,
|
|
37718
|
+
schema
|
|
37719
|
+
}));
|
|
37720
|
+
}
|
|
37721
|
+
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
37722
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
37723
|
+
if (modelName && models.some((m) => m.tableName === modelName)) {
|
|
37724
|
+
parameters = [
|
|
37725
|
+
...parameters,
|
|
37726
|
+
{ name: "page", in: "query", schema: { type: "integer", default: 1 } },
|
|
37727
|
+
{ name: "limit", in: "query", schema: { type: "integer", default: 20 } },
|
|
37728
|
+
{ name: "sort", in: "query", schema: { type: "string" }, description: "Sort fields (prefix with - for descending)" }
|
|
37729
|
+
];
|
|
37707
37730
|
}
|
|
37708
|
-
|
|
37709
|
-
|
|
37710
|
-
|
|
37711
|
-
|
|
37712
|
-
|
|
37713
|
-
|
|
37714
|
-
|
|
37731
|
+
}
|
|
37732
|
+
return parameters;
|
|
37733
|
+
}
|
|
37734
|
+
function mediaWithExample(schema, example) {
|
|
37735
|
+
const media = { schema };
|
|
37736
|
+
if (example !== void 0) media.example = example;
|
|
37737
|
+
return media;
|
|
37738
|
+
}
|
|
37739
|
+
function operationRequestBody(route, method, operation, ctx) {
|
|
37740
|
+
const reqSchemaRef = parseRequestSchema(route.meta?.requestSchema);
|
|
37741
|
+
if (reqSchemaRef && (method === "post" || method === "put" || method === "patch")) {
|
|
37742
|
+
ctx.refSchemas.add(reqSchemaRef.name);
|
|
37743
|
+
const media = mediaWithExample({ $ref: `#/components/schemas/${reqSchemaRef.name}` }, route.meta?.example);
|
|
37744
|
+
operation.requestBody = {
|
|
37745
|
+
content: { [reqSchemaRef.contentType]: media }
|
|
37746
|
+
};
|
|
37747
|
+
} else if (method === "post" || method === "put") {
|
|
37748
|
+
const modelName = inferModelFromPath(route.pattern);
|
|
37749
|
+
const schemaKey = modelName ? ctx.tableToSchema.get(modelName) : void 0;
|
|
37750
|
+
if (schemaKey) {
|
|
37751
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
37715
37752
|
operation.requestBody = {
|
|
37716
|
-
|
|
37753
|
+
required: true,
|
|
37754
|
+
content: { "application/json": mediaWithExample({ $ref: sref }, route.meta?.example) }
|
|
37717
37755
|
};
|
|
37718
|
-
|
|
37719
|
-
|
|
37720
|
-
|
|
37721
|
-
if (schemaKey) {
|
|
37722
|
-
const sref = `#/components/schemas/${schemaKey}`;
|
|
37723
|
-
const media = { schema: { $ref: sref } };
|
|
37724
|
-
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
37725
|
-
operation.requestBody = {
|
|
37726
|
-
required: true,
|
|
37727
|
-
content: { "application/json": media }
|
|
37728
|
-
};
|
|
37729
|
-
if (route.meta?.responses === void 0) {
|
|
37730
|
-
operation.responses = {
|
|
37731
|
-
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
37732
|
-
};
|
|
37733
|
-
}
|
|
37734
|
-
} else if (route.meta?.example !== void 0) {
|
|
37735
|
-
operation.requestBody = {
|
|
37736
|
-
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
37756
|
+
if (route.meta?.responses === void 0) {
|
|
37757
|
+
operation.responses = {
|
|
37758
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
37737
37759
|
};
|
|
37738
37760
|
}
|
|
37761
|
+
} else if (route.meta?.example !== void 0) {
|
|
37762
|
+
operation.requestBody = {
|
|
37763
|
+
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
37764
|
+
};
|
|
37739
37765
|
}
|
|
37740
|
-
|
|
37741
|
-
|
|
37742
|
-
|
|
37743
|
-
|
|
37744
|
-
|
|
37745
|
-
|
|
37746
|
-
|
|
37747
|
-
|
|
37748
|
-
|
|
37749
|
-
|
|
37750
|
-
|
|
37751
|
-
|
|
37766
|
+
}
|
|
37767
|
+
}
|
|
37768
|
+
function operationResponseSchemas(route, operation, refSchemas) {
|
|
37769
|
+
const respSchemas = parseResponseSchemas(route.meta?.responseSchemas);
|
|
37770
|
+
if (respSchemas.length > 0) {
|
|
37771
|
+
const responses = operation.responses;
|
|
37772
|
+
for (const { status: status2, name, isList } of respSchemas) {
|
|
37773
|
+
refSchemas.add(name);
|
|
37774
|
+
const sref = `#/components/schemas/${name}`;
|
|
37775
|
+
const schema = isList ? { type: "array", items: { $ref: sref } } : { $ref: sref };
|
|
37776
|
+
responses[status2] = {
|
|
37777
|
+
description: status2.startsWith("2") ? "Successful response" : "Response",
|
|
37778
|
+
content: { "application/json": { schema } }
|
|
37779
|
+
};
|
|
37752
37780
|
}
|
|
37753
|
-
|
|
37754
|
-
|
|
37755
|
-
|
|
37756
|
-
|
|
37757
|
-
|
|
37758
|
-
|
|
37759
|
-
|
|
37760
|
-
|
|
37761
|
-
} else if (routeRequiresAuth(route, method)) {
|
|
37762
|
-
const requirements = [{ [defaultScheme]: [] }];
|
|
37763
|
-
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
37764
|
-
operation.security = sanitizeSecurity(requirements, schemes);
|
|
37781
|
+
}
|
|
37782
|
+
}
|
|
37783
|
+
function operationSecurity(route, method, operation, schemes, defaultScheme) {
|
|
37784
|
+
const hasExplicitSecurity = route.meta?.security !== void 0 || route.meta?.scopes !== void 0 && route.meta.scopes.length > 0;
|
|
37785
|
+
if (hasExplicitSecurity) {
|
|
37786
|
+
const normalized = normalizeSecurity(route.meta?.security, route.meta?.scopes);
|
|
37787
|
+
operation.security = normalized.length > 0 ? sanitizeSecurity(normalized, schemes) : [];
|
|
37788
|
+
if (normalized.length > 0) {
|
|
37765
37789
|
const responses = operation.responses;
|
|
37766
37790
|
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
37767
37791
|
}
|
|
37768
|
-
|
|
37792
|
+
} else if (routeRequiresAuth(route, method)) {
|
|
37793
|
+
const requirements = [{ [defaultScheme]: [] }];
|
|
37794
|
+
if (defaultScheme === "bearerAuth" && schemes.ssoSession) requirements.push({ ssoSession: [] });
|
|
37795
|
+
operation.security = sanitizeSecurity(requirements, schemes);
|
|
37796
|
+
const responses = operation.responses;
|
|
37797
|
+
if (!responses["401"]) responses["401"] = { description: "Unauthorized" };
|
|
37769
37798
|
}
|
|
37799
|
+
}
|
|
37800
|
+
function buildRefSchemas(spec, refSchemas) {
|
|
37770
37801
|
if (refSchemas.size > 0) {
|
|
37771
37802
|
const schemas = spec.components.schemas;
|
|
37772
37803
|
for (const name of refSchemas) {
|
|
@@ -37775,10 +37806,11 @@ function generate2(routes, models = []) {
|
|
|
37775
37806
|
}
|
|
37776
37807
|
}
|
|
37777
37808
|
}
|
|
37809
|
+
}
|
|
37810
|
+
function buildTags(spec, usedTags) {
|
|
37778
37811
|
if (usedTags.length > 0) {
|
|
37779
37812
|
spec.tags = usedTags.map((name) => ({ name }));
|
|
37780
37813
|
}
|
|
37781
|
-
return spec;
|
|
37782
37814
|
}
|
|
37783
37815
|
function routeRequiresAuth(route, method) {
|
|
37784
37816
|
if (route.noAuth) return false;
|
|
@@ -39029,7 +39061,9 @@ function injectIntoHtml(ctx, devToolbar, html) {
|
|
|
39029
39061
|
// Suppress the live reloader on the AI/stable port (data-reload="0"); the
|
|
39030
39062
|
// toolbar JS early-returns when data-reload !== "1". Mirrors PHP's
|
|
39031
39063
|
// suppressReload flag.
|
|
39032
|
-
|
|
39064
|
+
// Also suppress on the dev-admin dashboard (any /__dev page): its SPA reloads
|
|
39065
|
+
// itself gently, so the toolbar's full-page reloader must not fire there.
|
|
39066
|
+
reload: !ctx.isAiPortRequest && !ctx.pathname.startsWith("/__dev")
|
|
39033
39067
|
};
|
|
39034
39068
|
return injectFeedbackWidget(ctx.req, injectDevToolbar(html, toolbarCtx));
|
|
39035
39069
|
}
|
|
@@ -39284,6 +39318,51 @@ async function runDispatch(ctx, rawReq, rawRes) {
|
|
|
39284
39318
|
if (!rawRes.headersSent) rawRes.setHeader("x-request-id", requestId);
|
|
39285
39319
|
return Log.runWithRequestId(requestId, () => dispatchInner(ctx, rawReq, rawRes, requestId));
|
|
39286
39320
|
}
|
|
39321
|
+
function loopbackBindHosts(host) {
|
|
39322
|
+
const normalized = host.trim().replace(/^\[|\]$/g, "").toLowerCase();
|
|
39323
|
+
switch (normalized) {
|
|
39324
|
+
case "localhost":
|
|
39325
|
+
return ["127.0.0.1", "::1"];
|
|
39326
|
+
case "127.0.0.1":
|
|
39327
|
+
case "0.0.0.0":
|
|
39328
|
+
return ["::1"];
|
|
39329
|
+
case "::1":
|
|
39330
|
+
case "::":
|
|
39331
|
+
return ["127.0.0.1"];
|
|
39332
|
+
default:
|
|
39333
|
+
return [];
|
|
39334
|
+
}
|
|
39335
|
+
}
|
|
39336
|
+
function startLoopbackSiblings(port, host, dispatch) {
|
|
39337
|
+
const siblingHosts = loopbackBindHosts(host);
|
|
39338
|
+
if (siblingHosts.length === 0) return Promise.resolve([]);
|
|
39339
|
+
return Promise.all(
|
|
39340
|
+
siblingHosts.map(
|
|
39341
|
+
(siblingHost) => new Promise((resolveSibling) => {
|
|
39342
|
+
const sibling = createServer2(dispatch);
|
|
39343
|
+
let settled = false;
|
|
39344
|
+
sibling.on("error", (err) => {
|
|
39345
|
+
const expected = err.code === "EADDRINUSE" || err.code === "EADDRNOTAVAIL" || err.code === "EAFNOSUPPORT";
|
|
39346
|
+
if (!expected) {
|
|
39347
|
+
Log.debug(
|
|
39348
|
+
`Loopback sibling ${siblingHost}:${port} not bound (${err.code ?? err.message}) \u2014 skipped, primary already serves`
|
|
39349
|
+
);
|
|
39350
|
+
}
|
|
39351
|
+
if (!settled) {
|
|
39352
|
+
settled = true;
|
|
39353
|
+
resolveSibling(null);
|
|
39354
|
+
}
|
|
39355
|
+
});
|
|
39356
|
+
sibling.listen(port, siblingHost, () => {
|
|
39357
|
+
settled = true;
|
|
39358
|
+
resolveSibling(sibling);
|
|
39359
|
+
});
|
|
39360
|
+
})
|
|
39361
|
+
)
|
|
39362
|
+
).then(
|
|
39363
|
+
(results) => results.filter((s) => s !== null)
|
|
39364
|
+
);
|
|
39365
|
+
}
|
|
39287
39366
|
async function startServer(config) {
|
|
39288
39367
|
loadEnv(".env.local");
|
|
39289
39368
|
loadEnv();
|
|
@@ -39552,8 +39631,9 @@ ${reset2}
|
|
|
39552
39631
|
}
|
|
39553
39632
|
});
|
|
39554
39633
|
return new Promise((resolvePromise) => {
|
|
39555
|
-
server.listen(port, host, () => {
|
|
39634
|
+
server.listen(port, host, async () => {
|
|
39556
39635
|
if (!cluster.isWorker) writePidfile(port);
|
|
39636
|
+
const siblingServers = await startLoopbackSiblings(port, host, dispatch);
|
|
39557
39637
|
const displayHost = host === "0.0.0.0" ? "localhost" : host;
|
|
39558
39638
|
const isDebug = isTruthy(process.env.TINA4_DEBUG);
|
|
39559
39639
|
const logLevel = (process.env.TINA4_LOG_LEVEL ?? "DEBUG").toUpperCase();
|
|
@@ -39616,14 +39696,22 @@ ${reset2}
|
|
|
39616
39696
|
}
|
|
39617
39697
|
let shuttingDown = false;
|
|
39618
39698
|
const closeListeners = () => new Promise((done) => {
|
|
39619
|
-
let pending = aiServer ?
|
|
39699
|
+
let pending = 1 + (aiServer ? 1 : 0) + siblingServers.length;
|
|
39620
39700
|
const one = () => {
|
|
39621
39701
|
if (--pending === 0) done();
|
|
39622
39702
|
};
|
|
39623
39703
|
server.close(one);
|
|
39624
39704
|
if (aiServer) aiServer.close(one);
|
|
39705
|
+
for (const sibling of siblingServers) {
|
|
39706
|
+
try {
|
|
39707
|
+
sibling.close(one);
|
|
39708
|
+
} catch {
|
|
39709
|
+
one();
|
|
39710
|
+
}
|
|
39711
|
+
}
|
|
39625
39712
|
server.closeIdleConnections();
|
|
39626
39713
|
aiServer?.closeIdleConnections();
|
|
39714
|
+
for (const sibling of siblingServers) sibling.closeIdleConnections();
|
|
39627
39715
|
});
|
|
39628
39716
|
const gracefulShutdown = async (signal) => {
|
|
39629
39717
|
if (shuttingDown) return;
|
|
@@ -39651,6 +39739,7 @@ ${reset2}
|
|
|
39651
39739
|
);
|
|
39652
39740
|
server.closeAllConnections();
|
|
39653
39741
|
aiServer?.closeAllConnections();
|
|
39742
|
+
for (const sibling of siblingServers) sibling.closeAllConnections();
|
|
39654
39743
|
}
|
|
39655
39744
|
try {
|
|
39656
39745
|
const orm = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
@@ -39678,6 +39767,12 @@ ${reset2}
|
|
|
39678
39767
|
stopAllBackgroundTasks();
|
|
39679
39768
|
if (aiServer) aiServer.close();
|
|
39680
39769
|
server.close();
|
|
39770
|
+
for (const sibling of siblingServers) {
|
|
39771
|
+
try {
|
|
39772
|
+
sibling.close();
|
|
39773
|
+
} catch {
|
|
39774
|
+
}
|
|
39775
|
+
}
|
|
39681
39776
|
Promise.resolve().then(() => (init_src(), src_exports)).then((orm) => orm.closeDatabase()).catch(() => {
|
|
39682
39777
|
});
|
|
39683
39778
|
},
|
|
@@ -46863,6 +46958,7 @@ __export(index_exports, {
|
|
|
46863
46958
|
isValidSessionId: () => isValidSessionId,
|
|
46864
46959
|
kafkaSecurityConfig: () => kafkaSecurityConfig,
|
|
46865
46960
|
loadEnv: () => loadEnv,
|
|
46961
|
+
loopbackBindHosts: () => loopbackBindHosts,
|
|
46866
46962
|
makeCaseInsensitiveHeaders: () => makeCaseInsensitiveHeaders,
|
|
46867
46963
|
matchCronField: () => matchCronField,
|
|
46868
46964
|
matchesCron: () => matchesCron,
|
|
@@ -47232,6 +47328,7 @@ export {
|
|
|
47232
47328
|
isValidSessionId,
|
|
47233
47329
|
kafkaSecurityConfig,
|
|
47234
47330
|
loadEnv,
|
|
47331
|
+
loopbackBindHosts,
|
|
47235
47332
|
makeCaseInsensitiveHeaders,
|
|
47236
47333
|
matchCronField,
|
|
47237
47334
|
matchesCron,
|