tina4-nodejs 3.13.146 → 3.13.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +117 -16
- package/packages/core/dist/index.js +117 -16
- package/packages/core/src/dispatchPipeline.ts +4 -12
- package/packages/core/src/docs.ts +56 -3
- package/packages/core/src/mcp.ts +44 -1
- package/packages/core/src/router.ts +4 -0
- package/packages/core/src/session.ts +45 -3
- package/packages/orm/dist/index.js +117 -16
- package/types/core/src/docs.d.ts +6 -1
- package/types/core/src/mcp.d.ts +10 -0
- package/types/core/src/router.d.ts +2 -0
- package/types/core/src/session.d.ts +25 -0
package/CLAUDE.md
CHANGED
|
@@ -13,13 +13,13 @@ Even if the skill text is not currently loaded, these are non-negotiable:
|
|
|
13
13
|
|
|
14
14
|
The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this block is the always-on floor.
|
|
15
15
|
|
|
16
|
-
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.
|
|
16
|
+
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.148)
|
|
17
17
|
|
|
18
18
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
19
19
|
|
|
20
20
|
## What This Project Is
|
|
21
21
|
|
|
22
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
22
|
+
Tina4 for Node.js/TypeScript v3.13.148 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
23
23
|
|
|
24
24
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
25
25
|
|
package/package.json
CHANGED
package/packages/cli/dist/bin.js
CHANGED
|
@@ -16940,6 +16940,7 @@ var init_router = __esm({
|
|
|
16940
16940
|
path: route.pattern,
|
|
16941
16941
|
handler: route.filePath ?? (route.handler.name || "(anonymous)"),
|
|
16942
16942
|
middlewareCount: route.middlewares?.length ?? 0,
|
|
16943
|
+
middleware: (route.middlewares ?? []).map((attached) => typeof attached === "string" ? attached : attached?.name || attached?.constructor?.name || "(anonymous)"),
|
|
16943
16944
|
cached: route.cached ?? false,
|
|
16944
16945
|
secure: route.secure ?? false
|
|
16945
16946
|
});
|
|
@@ -23877,6 +23878,7 @@ __export(session_exports, {
|
|
|
23877
23878
|
isSecureScheme: () => isSecureScheme,
|
|
23878
23879
|
isValidSessionId: () => isValidSessionId,
|
|
23879
23880
|
sessionCookieName: () => sessionCookieName,
|
|
23881
|
+
sessionCookieToSend: () => sessionCookieToSend,
|
|
23880
23882
|
sessionStrictMode: () => sessionStrictMode
|
|
23881
23883
|
});
|
|
23882
23884
|
import { createHash as createHash5, randomBytes as randomBytes6 } from "node:crypto";
|
|
@@ -23937,6 +23939,15 @@ function buildSessionCookie(sessionId, ttl, cookieName, forwardedProto, socketEn
|
|
|
23937
23939
|
parts.push(`Max-Age=${ttl}`);
|
|
23938
23940
|
return parts.join("; ");
|
|
23939
23941
|
}
|
|
23942
|
+
function sessionCookieToSend(sess, existingSid, rawReq, rawRes) {
|
|
23943
|
+
const newSid = sess.sessionId ?? sess.getSessionId?.();
|
|
23944
|
+
if (!newSid || newSid === existingSid || sess.isFresh() || rawRes.headersSent) return null;
|
|
23945
|
+
const ttl = parseInt(process.env.TINA4_SESSION_TTL ?? "3600", 10);
|
|
23946
|
+
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
23947
|
+
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
23948
|
+
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
23949
|
+
return buildSessionCookie(newSid, ttl, void 0, forwardedProto, socketEncrypted);
|
|
23950
|
+
}
|
|
23940
23951
|
var SESSION_ID_PATTERN, VALID_SESSION_BACKENDS, CANONICAL_SESSION_BACKENDS, RETIRED_SESSION_BACKENDS, FileSessionHandler, RedisSessionHandler, FLASH_PREFIX, Session;
|
|
23941
23952
|
var init_session = __esm({
|
|
23942
23953
|
"../core/src/session.ts"() {
|
|
@@ -24299,7 +24310,6 @@ var init_session = __esm({
|
|
|
24299
24310
|
const now = Math.floor(Date.now() / 1e3);
|
|
24300
24311
|
this.data = { _created: now, _accessed: now };
|
|
24301
24312
|
this.dirty = false;
|
|
24302
|
-
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
24303
24313
|
this.stored = false;
|
|
24304
24314
|
this.loaded = /* @__PURE__ */ new Map();
|
|
24305
24315
|
this.cleared = false;
|
|
@@ -24450,6 +24460,19 @@ var init_session = __esm({
|
|
|
24450
24460
|
const result = this.flash(key);
|
|
24451
24461
|
return result !== void 0 ? result : defaultValue;
|
|
24452
24462
|
}
|
|
24463
|
+
/**
|
|
24464
|
+
* Whether this session is only an id: nothing has stored it and it holds no
|
|
24465
|
+
* data of its own (its `_created` / `_accessed` bookkeeping does not count).
|
|
24466
|
+
* Such a session has no record and needs no cookie, so save() writes nothing
|
|
24467
|
+
* for it and the server sends no cookie for it. It stops being fresh when
|
|
24468
|
+
* something is stored in it. Calls that mark a session changed without leaving
|
|
24469
|
+
* anything in it (deleting a key it never had, clear(), regenerate() on an
|
|
24470
|
+
* empty session) leave it fresh: a record with no data is not a session.
|
|
24471
|
+
*/
|
|
24472
|
+
isFresh() {
|
|
24473
|
+
if (this.stored) return false;
|
|
24474
|
+
return !this.data || Object.keys(this.data).every((k) => k === "_created" || k === "_accessed");
|
|
24475
|
+
}
|
|
24453
24476
|
/**
|
|
24454
24477
|
* Get the current session ID.
|
|
24455
24478
|
*/
|
|
@@ -24512,7 +24535,7 @@ var init_session = __esm({
|
|
|
24512
24535
|
*/
|
|
24513
24536
|
save() {
|
|
24514
24537
|
if (!this.sessionId || !this.data) return true;
|
|
24515
|
-
if (
|
|
24538
|
+
if (this.isFresh()) return true;
|
|
24516
24539
|
let record = this.data;
|
|
24517
24540
|
if (this.stored) {
|
|
24518
24541
|
const current = this.safeRead(this.sessionId);
|
|
@@ -24699,7 +24722,7 @@ function errorLabel(err) {
|
|
|
24699
24722
|
return err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
24700
24723
|
}
|
|
24701
24724
|
async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
24702
|
-
const { Session: Session2,
|
|
24725
|
+
const { Session: Session2, sessionCookieToSend: sessionCookieToSend2, sessionCookieName: sessionCookieName2, sessionStrictMode: sessionStrictMode2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
24703
24726
|
const cookieHeader = rawReq.headers.cookie ?? "";
|
|
24704
24727
|
const cookiePrefix = sessionCookieName2() + "=";
|
|
24705
24728
|
let existingSid;
|
|
@@ -24738,13 +24761,9 @@ async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
|
24738
24761
|
} catch {
|
|
24739
24762
|
}
|
|
24740
24763
|
}
|
|
24741
|
-
const
|
|
24742
|
-
if (
|
|
24743
|
-
|
|
24744
|
-
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
24745
|
-
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
24746
|
-
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
24747
|
-
rawRes.appendHeader("Set-Cookie", buildSessionCookie2(newSid, ttl, void 0, forwardedProto, socketEncrypted));
|
|
24764
|
+
const sessionCookie = sessionCookieToSend2(sess, existingSid, rawReq, rawRes);
|
|
24765
|
+
if (sessionCookie !== null) {
|
|
24766
|
+
rawRes.appendHeader("Set-Cookie", sessionCookie);
|
|
24748
24767
|
}
|
|
24749
24768
|
return origEnd(...args);
|
|
24750
24769
|
};
|
|
@@ -27924,7 +27943,8 @@ __export(mcp_exports, {
|
|
|
27924
27943
|
mcpTool: () => mcpTool,
|
|
27925
27944
|
register: () => register,
|
|
27926
27945
|
registerDevTools: () => registerDevTools,
|
|
27927
|
-
schemaFromParams: () => schemaFromParams
|
|
27946
|
+
schemaFromParams: () => schemaFromParams,
|
|
27947
|
+
validateToolArguments: () => validateToolArguments
|
|
27928
27948
|
});
|
|
27929
27949
|
import * as fs4 from "node:fs";
|
|
27930
27950
|
import * as path3 from "node:path";
|
|
@@ -27996,6 +28016,27 @@ function schemaFromParams(params) {
|
|
|
27996
28016
|
}
|
|
27997
28017
|
return schema;
|
|
27998
28018
|
}
|
|
28019
|
+
function validateToolArguments(toolName, schema, args) {
|
|
28020
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
28021
|
+
const takes = properties.length > 0 ? properties.join(", ") : "no arguments";
|
|
28022
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
28023
|
+
return `arguments must be an object (${toolName} takes ${takes})`;
|
|
28024
|
+
}
|
|
28025
|
+
const supplied = args;
|
|
28026
|
+
for (const requiredName of schema.required ?? []) {
|
|
28027
|
+
if (!(requiredName in supplied)) {
|
|
28028
|
+
return `missing required argument '${requiredName}' (${toolName} takes ${takes})`;
|
|
28029
|
+
}
|
|
28030
|
+
}
|
|
28031
|
+
if (!schema.additionalProperties) {
|
|
28032
|
+
for (const suppliedName of Object.keys(supplied)) {
|
|
28033
|
+
if (!properties.includes(suppliedName)) {
|
|
28034
|
+
return `unknown argument '${suppliedName}' (${toolName} takes ${takes})`;
|
|
28035
|
+
}
|
|
28036
|
+
}
|
|
28037
|
+
}
|
|
28038
|
+
return null;
|
|
28039
|
+
}
|
|
27999
28040
|
function isLocalhost() {
|
|
28000
28041
|
const hostEnv = process.env.TINA4_HOST_NAME || "localhost:7148";
|
|
28001
28042
|
const host = hostEnv.split(":")[0];
|
|
@@ -28294,7 +28335,12 @@ function registerDevTools(server) {
|
|
|
28294
28335
|
if (!/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/.test(table2)) {
|
|
28295
28336
|
return { error: "Invalid table name" };
|
|
28296
28337
|
}
|
|
28297
|
-
|
|
28338
|
+
const columns = await db.getColumns?.(table2) ?? [];
|
|
28339
|
+
if (columns.length === 0) {
|
|
28340
|
+
const tables = await db.getTables?.() ?? [];
|
|
28341
|
+
if (!tables.includes(table2)) return { error: `table not found: ${table2}` };
|
|
28342
|
+
}
|
|
28343
|
+
return columns;
|
|
28298
28344
|
} catch (e) {
|
|
28299
28345
|
return { error: e.message };
|
|
28300
28346
|
}
|
|
@@ -28313,7 +28359,8 @@ function registerDevTools(server) {
|
|
|
28313
28359
|
return routes.map((r) => ({
|
|
28314
28360
|
method: r.method || "",
|
|
28315
28361
|
path: r.pattern || r.path || "",
|
|
28316
|
-
auth_required: r.secure ?? false
|
|
28362
|
+
auth_required: r.secure ?? false,
|
|
28363
|
+
middleware: r.middleware ?? []
|
|
28317
28364
|
}));
|
|
28318
28365
|
} catch (e) {
|
|
28319
28366
|
return { error: e.message };
|
|
@@ -29459,6 +29506,10 @@ data: ${channel.buffer.shift()}
|
|
|
29459
29506
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
29460
29507
|
}
|
|
29461
29508
|
const args = params.arguments || {};
|
|
29509
|
+
const invalid = validateToolArguments(toolName, tool.inputSchema, args);
|
|
29510
|
+
if (invalid) {
|
|
29511
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: invalid }, null, 2) }] };
|
|
29512
|
+
}
|
|
29462
29513
|
const result = await tool.handler(args);
|
|
29463
29514
|
let content;
|
|
29464
29515
|
if (typeof result === "string") {
|
|
@@ -35497,7 +35548,9 @@ var init_docsParser = __esm({
|
|
|
35497
35548
|
// ../core/src/docs.ts
|
|
35498
35549
|
var docs_exports = {};
|
|
35499
35550
|
__export(docs_exports, {
|
|
35500
|
-
Docs: () => Docs
|
|
35551
|
+
Docs: () => Docs,
|
|
35552
|
+
paramsFromSignature: () => paramsFromSignature,
|
|
35553
|
+
returnFromSignature: () => returnFromSignature
|
|
35501
35554
|
});
|
|
35502
35555
|
import * as fs8 from "node:fs";
|
|
35503
35556
|
import * as path8 from "node:path";
|
|
@@ -35724,6 +35777,54 @@ function scoreFqn(entry, tokens) {
|
|
|
35724
35777
|
}
|
|
35725
35778
|
return score;
|
|
35726
35779
|
}
|
|
35780
|
+
function splitTopLevel(text) {
|
|
35781
|
+
const parts = [];
|
|
35782
|
+
let depth = 0;
|
|
35783
|
+
let current = "";
|
|
35784
|
+
for (const ch of text) {
|
|
35785
|
+
if ("<([{".includes(ch)) depth++;
|
|
35786
|
+
else if (">)]}".includes(ch)) depth--;
|
|
35787
|
+
if (ch === "," && depth === 0) {
|
|
35788
|
+
parts.push(current);
|
|
35789
|
+
current = "";
|
|
35790
|
+
} else current += ch;
|
|
35791
|
+
}
|
|
35792
|
+
if (current.trim()) parts.push(current);
|
|
35793
|
+
return parts;
|
|
35794
|
+
}
|
|
35795
|
+
function closingParen(signature, open) {
|
|
35796
|
+
let depth = 0;
|
|
35797
|
+
for (let i = open; i < signature.length; i++) {
|
|
35798
|
+
if (signature[i] === "(") depth++;
|
|
35799
|
+
else if (signature[i] === ")" && --depth === 0) return i;
|
|
35800
|
+
}
|
|
35801
|
+
return -1;
|
|
35802
|
+
}
|
|
35803
|
+
function paramsFromSignature(signature) {
|
|
35804
|
+
const open = signature.indexOf("(");
|
|
35805
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
35806
|
+
if (close < 0) return [];
|
|
35807
|
+
return splitTopLevel(signature.slice(open + 1, close)).map((raw) => {
|
|
35808
|
+
const [declaration, ...defaultParts] = raw.split(/=(?!>)/);
|
|
35809
|
+
const defaultText = defaultParts.length > 0 ? defaultParts.join("=").trim() : null;
|
|
35810
|
+
const colon = declaration.indexOf(":");
|
|
35811
|
+
const head = (colon < 0 ? declaration : declaration.slice(0, colon)).trim();
|
|
35812
|
+
const optional = head.endsWith("?");
|
|
35813
|
+
return {
|
|
35814
|
+
name: head.replace(/\?$/, ""),
|
|
35815
|
+
type: colon < 0 ? "" : declaration.slice(colon + 1).trim(),
|
|
35816
|
+
required: !optional && defaultText === null && !head.startsWith("..."),
|
|
35817
|
+
default: defaultText
|
|
35818
|
+
};
|
|
35819
|
+
});
|
|
35820
|
+
}
|
|
35821
|
+
function returnFromSignature(signature) {
|
|
35822
|
+
const open = signature.indexOf("(");
|
|
35823
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
35824
|
+
if (close < 0) return "";
|
|
35825
|
+
const rest = signature.slice(close + 1).trim();
|
|
35826
|
+
return rest.startsWith(":") ? rest.slice(1).trim() : "";
|
|
35827
|
+
}
|
|
35727
35828
|
var STDLIB_ALLOWLIST, _DOCS_CACHE, __FILENAME, __DIRNAME, USER_DIRS, CAMEL_SPLIT, FENCE_RE, CALL_RE, sharedFrameworkIndex, Docs;
|
|
35728
35829
|
var init_docs = __esm({
|
|
35729
35830
|
"../core/src/docs.ts"() {
|
|
@@ -36029,8 +36130,8 @@ var init_docs = __esm({
|
|
|
36029
36130
|
static: entry.static ?? false,
|
|
36030
36131
|
source: entry.source,
|
|
36031
36132
|
version: entry.version,
|
|
36032
|
-
params:
|
|
36033
|
-
return:
|
|
36133
|
+
params: paramsFromSignature(entry.signature),
|
|
36134
|
+
return: returnFromSignature(entry.signature)
|
|
36034
36135
|
};
|
|
36035
36136
|
}
|
|
36036
36137
|
/**
|
|
@@ -16939,6 +16939,7 @@ var init_router = __esm({
|
|
|
16939
16939
|
path: route.pattern,
|
|
16940
16940
|
handler: route.filePath ?? (route.handler.name || "(anonymous)"),
|
|
16941
16941
|
middlewareCount: route.middlewares?.length ?? 0,
|
|
16942
|
+
middleware: (route.middlewares ?? []).map((attached) => typeof attached === "string" ? attached : attached?.name || attached?.constructor?.name || "(anonymous)"),
|
|
16942
16943
|
cached: route.cached ?? false,
|
|
16943
16944
|
secure: route.secure ?? false
|
|
16944
16945
|
});
|
|
@@ -23856,6 +23857,7 @@ __export(session_exports, {
|
|
|
23856
23857
|
isSecureScheme: () => isSecureScheme,
|
|
23857
23858
|
isValidSessionId: () => isValidSessionId,
|
|
23858
23859
|
sessionCookieName: () => sessionCookieName,
|
|
23860
|
+
sessionCookieToSend: () => sessionCookieToSend,
|
|
23859
23861
|
sessionStrictMode: () => sessionStrictMode
|
|
23860
23862
|
});
|
|
23861
23863
|
import { createHash as createHash5, randomBytes as randomBytes6 } from "node:crypto";
|
|
@@ -23916,6 +23918,15 @@ function buildSessionCookie(sessionId, ttl, cookieName, forwardedProto, socketEn
|
|
|
23916
23918
|
parts.push(`Max-Age=${ttl}`);
|
|
23917
23919
|
return parts.join("; ");
|
|
23918
23920
|
}
|
|
23921
|
+
function sessionCookieToSend(sess, existingSid, rawReq, rawRes) {
|
|
23922
|
+
const newSid = sess.sessionId ?? sess.getSessionId?.();
|
|
23923
|
+
if (!newSid || newSid === existingSid || sess.isFresh() || rawRes.headersSent) return null;
|
|
23924
|
+
const ttl = parseInt(process.env.TINA4_SESSION_TTL ?? "3600", 10);
|
|
23925
|
+
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
23926
|
+
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
23927
|
+
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
23928
|
+
return buildSessionCookie(newSid, ttl, void 0, forwardedProto, socketEncrypted);
|
|
23929
|
+
}
|
|
23919
23930
|
var SESSION_ID_PATTERN, VALID_SESSION_BACKENDS, CANONICAL_SESSION_BACKENDS, RETIRED_SESSION_BACKENDS, FileSessionHandler, RedisSessionHandler, FLASH_PREFIX, Session;
|
|
23920
23931
|
var init_session = __esm({
|
|
23921
23932
|
"src/session.ts"() {
|
|
@@ -24278,7 +24289,6 @@ var init_session = __esm({
|
|
|
24278
24289
|
const now = Math.floor(Date.now() / 1e3);
|
|
24279
24290
|
this.data = { _created: now, _accessed: now };
|
|
24280
24291
|
this.dirty = false;
|
|
24281
|
-
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
24282
24292
|
this.stored = false;
|
|
24283
24293
|
this.loaded = /* @__PURE__ */ new Map();
|
|
24284
24294
|
this.cleared = false;
|
|
@@ -24429,6 +24439,19 @@ var init_session = __esm({
|
|
|
24429
24439
|
const result = this.flash(key);
|
|
24430
24440
|
return result !== void 0 ? result : defaultValue;
|
|
24431
24441
|
}
|
|
24442
|
+
/**
|
|
24443
|
+
* Whether this session is only an id: nothing has stored it and it holds no
|
|
24444
|
+
* data of its own (its `_created` / `_accessed` bookkeeping does not count).
|
|
24445
|
+
* Such a session has no record and needs no cookie, so save() writes nothing
|
|
24446
|
+
* for it and the server sends no cookie for it. It stops being fresh when
|
|
24447
|
+
* something is stored in it. Calls that mark a session changed without leaving
|
|
24448
|
+
* anything in it (deleting a key it never had, clear(), regenerate() on an
|
|
24449
|
+
* empty session) leave it fresh: a record with no data is not a session.
|
|
24450
|
+
*/
|
|
24451
|
+
isFresh() {
|
|
24452
|
+
if (this.stored) return false;
|
|
24453
|
+
return !this.data || Object.keys(this.data).every((k) => k === "_created" || k === "_accessed");
|
|
24454
|
+
}
|
|
24432
24455
|
/**
|
|
24433
24456
|
* Get the current session ID.
|
|
24434
24457
|
*/
|
|
@@ -24491,7 +24514,7 @@ var init_session = __esm({
|
|
|
24491
24514
|
*/
|
|
24492
24515
|
save() {
|
|
24493
24516
|
if (!this.sessionId || !this.data) return true;
|
|
24494
|
-
if (
|
|
24517
|
+
if (this.isFresh()) return true;
|
|
24495
24518
|
let record = this.data;
|
|
24496
24519
|
if (this.stored) {
|
|
24497
24520
|
const current = this.safeRead(this.sessionId);
|
|
@@ -24678,7 +24701,7 @@ function errorLabel(err) {
|
|
|
24678
24701
|
return err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
24679
24702
|
}
|
|
24680
24703
|
async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
24681
|
-
const { Session: Session2,
|
|
24704
|
+
const { Session: Session2, sessionCookieToSend: sessionCookieToSend2, sessionCookieName: sessionCookieName2, sessionStrictMode: sessionStrictMode2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
24682
24705
|
const cookieHeader = rawReq.headers.cookie ?? "";
|
|
24683
24706
|
const cookiePrefix = sessionCookieName2() + "=";
|
|
24684
24707
|
let existingSid;
|
|
@@ -24717,13 +24740,9 @@ async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
|
24717
24740
|
} catch {
|
|
24718
24741
|
}
|
|
24719
24742
|
}
|
|
24720
|
-
const
|
|
24721
|
-
if (
|
|
24722
|
-
|
|
24723
|
-
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
24724
|
-
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
24725
|
-
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
24726
|
-
rawRes.appendHeader("Set-Cookie", buildSessionCookie2(newSid, ttl, void 0, forwardedProto, socketEncrypted));
|
|
24743
|
+
const sessionCookie = sessionCookieToSend2(sess, existingSid, rawReq, rawRes);
|
|
24744
|
+
if (sessionCookie !== null) {
|
|
24745
|
+
rawRes.appendHeader("Set-Cookie", sessionCookie);
|
|
24727
24746
|
}
|
|
24728
24747
|
return origEnd(...args);
|
|
24729
24748
|
};
|
|
@@ -27880,7 +27899,8 @@ __export(mcp_exports, {
|
|
|
27880
27899
|
mcpTool: () => mcpTool,
|
|
27881
27900
|
register: () => register,
|
|
27882
27901
|
registerDevTools: () => registerDevTools,
|
|
27883
|
-
schemaFromParams: () => schemaFromParams
|
|
27902
|
+
schemaFromParams: () => schemaFromParams,
|
|
27903
|
+
validateToolArguments: () => validateToolArguments
|
|
27884
27904
|
});
|
|
27885
27905
|
import * as fs4 from "node:fs";
|
|
27886
27906
|
import * as path3 from "node:path";
|
|
@@ -27952,6 +27972,27 @@ function schemaFromParams(params) {
|
|
|
27952
27972
|
}
|
|
27953
27973
|
return schema;
|
|
27954
27974
|
}
|
|
27975
|
+
function validateToolArguments(toolName, schema, args) {
|
|
27976
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
27977
|
+
const takes = properties.length > 0 ? properties.join(", ") : "no arguments";
|
|
27978
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
27979
|
+
return `arguments must be an object (${toolName} takes ${takes})`;
|
|
27980
|
+
}
|
|
27981
|
+
const supplied = args;
|
|
27982
|
+
for (const requiredName of schema.required ?? []) {
|
|
27983
|
+
if (!(requiredName in supplied)) {
|
|
27984
|
+
return `missing required argument '${requiredName}' (${toolName} takes ${takes})`;
|
|
27985
|
+
}
|
|
27986
|
+
}
|
|
27987
|
+
if (!schema.additionalProperties) {
|
|
27988
|
+
for (const suppliedName of Object.keys(supplied)) {
|
|
27989
|
+
if (!properties.includes(suppliedName)) {
|
|
27990
|
+
return `unknown argument '${suppliedName}' (${toolName} takes ${takes})`;
|
|
27991
|
+
}
|
|
27992
|
+
}
|
|
27993
|
+
}
|
|
27994
|
+
return null;
|
|
27995
|
+
}
|
|
27955
27996
|
function isLocalhost() {
|
|
27956
27997
|
const hostEnv = process.env.TINA4_HOST_NAME || "localhost:7148";
|
|
27957
27998
|
const host = hostEnv.split(":")[0];
|
|
@@ -28250,7 +28291,12 @@ function registerDevTools(server) {
|
|
|
28250
28291
|
if (!/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/.test(table2)) {
|
|
28251
28292
|
return { error: "Invalid table name" };
|
|
28252
28293
|
}
|
|
28253
|
-
|
|
28294
|
+
const columns = await db.getColumns?.(table2) ?? [];
|
|
28295
|
+
if (columns.length === 0) {
|
|
28296
|
+
const tables = await db.getTables?.() ?? [];
|
|
28297
|
+
if (!tables.includes(table2)) return { error: `table not found: ${table2}` };
|
|
28298
|
+
}
|
|
28299
|
+
return columns;
|
|
28254
28300
|
} catch (e) {
|
|
28255
28301
|
return { error: e.message };
|
|
28256
28302
|
}
|
|
@@ -28269,7 +28315,8 @@ function registerDevTools(server) {
|
|
|
28269
28315
|
return routes.map((r) => ({
|
|
28270
28316
|
method: r.method || "",
|
|
28271
28317
|
path: r.pattern || r.path || "",
|
|
28272
|
-
auth_required: r.secure ?? false
|
|
28318
|
+
auth_required: r.secure ?? false,
|
|
28319
|
+
middleware: r.middleware ?? []
|
|
28273
28320
|
}));
|
|
28274
28321
|
} catch (e) {
|
|
28275
28322
|
return { error: e.message };
|
|
@@ -29415,6 +29462,10 @@ data: ${channel.buffer.shift()}
|
|
|
29415
29462
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
29416
29463
|
}
|
|
29417
29464
|
const args = params.arguments || {};
|
|
29465
|
+
const invalid = validateToolArguments(toolName, tool.inputSchema, args);
|
|
29466
|
+
if (invalid) {
|
|
29467
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: invalid }, null, 2) }] };
|
|
29468
|
+
}
|
|
29418
29469
|
const result = await tool.handler(args);
|
|
29419
29470
|
let content;
|
|
29420
29471
|
if (typeof result === "string") {
|
|
@@ -35453,7 +35504,9 @@ var init_docsParser = __esm({
|
|
|
35453
35504
|
// src/docs.ts
|
|
35454
35505
|
var docs_exports = {};
|
|
35455
35506
|
__export(docs_exports, {
|
|
35456
|
-
Docs: () => Docs
|
|
35507
|
+
Docs: () => Docs,
|
|
35508
|
+
paramsFromSignature: () => paramsFromSignature,
|
|
35509
|
+
returnFromSignature: () => returnFromSignature
|
|
35457
35510
|
});
|
|
35458
35511
|
import * as fs8 from "node:fs";
|
|
35459
35512
|
import * as path8 from "node:path";
|
|
@@ -35680,6 +35733,54 @@ function scoreFqn(entry, tokens) {
|
|
|
35680
35733
|
}
|
|
35681
35734
|
return score;
|
|
35682
35735
|
}
|
|
35736
|
+
function splitTopLevel(text) {
|
|
35737
|
+
const parts = [];
|
|
35738
|
+
let depth = 0;
|
|
35739
|
+
let current = "";
|
|
35740
|
+
for (const ch of text) {
|
|
35741
|
+
if ("<([{".includes(ch)) depth++;
|
|
35742
|
+
else if (">)]}".includes(ch)) depth--;
|
|
35743
|
+
if (ch === "," && depth === 0) {
|
|
35744
|
+
parts.push(current);
|
|
35745
|
+
current = "";
|
|
35746
|
+
} else current += ch;
|
|
35747
|
+
}
|
|
35748
|
+
if (current.trim()) parts.push(current);
|
|
35749
|
+
return parts;
|
|
35750
|
+
}
|
|
35751
|
+
function closingParen(signature, open) {
|
|
35752
|
+
let depth = 0;
|
|
35753
|
+
for (let i = open; i < signature.length; i++) {
|
|
35754
|
+
if (signature[i] === "(") depth++;
|
|
35755
|
+
else if (signature[i] === ")" && --depth === 0) return i;
|
|
35756
|
+
}
|
|
35757
|
+
return -1;
|
|
35758
|
+
}
|
|
35759
|
+
function paramsFromSignature(signature) {
|
|
35760
|
+
const open = signature.indexOf("(");
|
|
35761
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
35762
|
+
if (close < 0) return [];
|
|
35763
|
+
return splitTopLevel(signature.slice(open + 1, close)).map((raw) => {
|
|
35764
|
+
const [declaration, ...defaultParts] = raw.split(/=(?!>)/);
|
|
35765
|
+
const defaultText = defaultParts.length > 0 ? defaultParts.join("=").trim() : null;
|
|
35766
|
+
const colon = declaration.indexOf(":");
|
|
35767
|
+
const head = (colon < 0 ? declaration : declaration.slice(0, colon)).trim();
|
|
35768
|
+
const optional = head.endsWith("?");
|
|
35769
|
+
return {
|
|
35770
|
+
name: head.replace(/\?$/, ""),
|
|
35771
|
+
type: colon < 0 ? "" : declaration.slice(colon + 1).trim(),
|
|
35772
|
+
required: !optional && defaultText === null && !head.startsWith("..."),
|
|
35773
|
+
default: defaultText
|
|
35774
|
+
};
|
|
35775
|
+
});
|
|
35776
|
+
}
|
|
35777
|
+
function returnFromSignature(signature) {
|
|
35778
|
+
const open = signature.indexOf("(");
|
|
35779
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
35780
|
+
if (close < 0) return "";
|
|
35781
|
+
const rest = signature.slice(close + 1).trim();
|
|
35782
|
+
return rest.startsWith(":") ? rest.slice(1).trim() : "";
|
|
35783
|
+
}
|
|
35683
35784
|
var STDLIB_ALLOWLIST, _DOCS_CACHE, __FILENAME, __DIRNAME, USER_DIRS, CAMEL_SPLIT, FENCE_RE, CALL_RE, sharedFrameworkIndex, Docs;
|
|
35684
35785
|
var init_docs = __esm({
|
|
35685
35786
|
"src/docs.ts"() {
|
|
@@ -35985,8 +36086,8 @@ var init_docs = __esm({
|
|
|
35985
36086
|
static: entry.static ?? false,
|
|
35986
36087
|
source: entry.source,
|
|
35987
36088
|
version: entry.version,
|
|
35988
|
-
params:
|
|
35989
|
-
return:
|
|
36089
|
+
params: paramsFromSignature(entry.signature),
|
|
36090
|
+
return: returnFromSignature(entry.signature)
|
|
35990
36091
|
};
|
|
35991
36092
|
}
|
|
35992
36093
|
/**
|
|
@@ -367,7 +367,7 @@ export async function sessionAutoStart(
|
|
|
367
367
|
rawRes: ServerResponse,
|
|
368
368
|
req: Tina4Request,
|
|
369
369
|
): Promise<void> {
|
|
370
|
-
const { Session,
|
|
370
|
+
const { Session, sessionCookieToSend, sessionCookieName, sessionStrictMode } =
|
|
371
371
|
await import("./session.js");
|
|
372
372
|
const cookieHeader = rawReq.headers.cookie ?? "";
|
|
373
373
|
|
|
@@ -433,16 +433,8 @@ export async function sessionAutoStart(
|
|
|
433
433
|
try { sess.gc(); } catch { /* GC failure is non-critical */ }
|
|
434
434
|
}
|
|
435
435
|
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
438
|
-
const ttl = parseInt(process.env.TINA4_SESSION_TTL ?? "3600", 10);
|
|
439
|
-
// Thread the client's real scheme in so an HTTPS deploy behind a
|
|
440
|
-
// TLS-terminating proxy ships the session cookie with `Secure`
|
|
441
|
-
// (nodejs#34). `x-forwarded-proto` is the same header request.ts trusts
|
|
442
|
-
// for URL construction; native socket TLS is the fallback.
|
|
443
|
-
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
444
|
-
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
445
|
-
const socketEncrypted = (rawReq.socket as { encrypted?: boolean })?.encrypted === true;
|
|
436
|
+
const sessionCookie = sessionCookieToSend(sess, existingSid, rawReq, rawRes);
|
|
437
|
+
if (sessionCookie !== null) {
|
|
446
438
|
// appendHeader, not setHeader (feature 131 fix, found while proving
|
|
447
439
|
// TC-DEC-02): setHeader REPLACES any existing Set-Cookie value wholesale,
|
|
448
440
|
// so a route that had already called response.cookie() of its own — on
|
|
@@ -452,7 +444,7 @@ export async function sessionAutoStart(
|
|
|
452
444
|
// appendHeader adds to whatever is already there (promoting a scalar to
|
|
453
445
|
// an array, extending an existing array) and behaves exactly like
|
|
454
446
|
// setHeader when nothing is set yet, so the common case is unchanged.
|
|
455
|
-
rawRes.appendHeader("Set-Cookie",
|
|
447
|
+
rawRes.appendHeader("Set-Cookie", sessionCookie);
|
|
456
448
|
}
|
|
457
449
|
return origEnd(...args);
|
|
458
450
|
} as typeof rawRes.end;
|
|
@@ -61,7 +61,7 @@ export interface MethodSpec {
|
|
|
61
61
|
static: boolean;
|
|
62
62
|
source: "framework" | "user" | "vendor";
|
|
63
63
|
version: string;
|
|
64
|
-
params: Array<{ name: string; type: string; default
|
|
64
|
+
params: Array<{ name: string; type: string; required: boolean; default: string | null }>;
|
|
65
65
|
return: string;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -448,6 +448,59 @@ function scoreFqn(entry: InternalEntry, tokens: string[]): number {
|
|
|
448
448
|
*/
|
|
449
449
|
const sharedFrameworkIndex = new Map<string, { entries: Map<string, InternalEntry>; mtime: number }>();
|
|
450
450
|
|
|
451
|
+
/** Split `text` on top-level commas, ignoring commas nested in <>, (), [] or {}. */
|
|
452
|
+
function splitTopLevel(text: string): string[] {
|
|
453
|
+
const parts: string[] = [];
|
|
454
|
+
let depth = 0;
|
|
455
|
+
let current = "";
|
|
456
|
+
for (const ch of text) {
|
|
457
|
+
if ("<([{".includes(ch)) depth++;
|
|
458
|
+
else if (">)]}".includes(ch)) depth--;
|
|
459
|
+
if (ch === "," && depth === 0) { parts.push(current); current = ""; } else current += ch;
|
|
460
|
+
}
|
|
461
|
+
if (current.trim()) parts.push(current);
|
|
462
|
+
return parts;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Index of the `)` closing the parameter list that opens at `open`, or -1. */
|
|
466
|
+
function closingParen(signature: string, open: number): number {
|
|
467
|
+
let depth = 0;
|
|
468
|
+
for (let i = open; i < signature.length; i++) {
|
|
469
|
+
if (signature[i] === "(") depth++;
|
|
470
|
+
else if (signature[i] === ")" && --depth === 0) return i;
|
|
471
|
+
}
|
|
472
|
+
return -1;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** Structured params `[{name, type, required, default}]` from `name(a: T, b?: U = 1) : R`. */
|
|
476
|
+
export function paramsFromSignature(signature: string): MethodSpec["params"] {
|
|
477
|
+
const open = signature.indexOf("(");
|
|
478
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
479
|
+
if (close < 0) return [];
|
|
480
|
+
return splitTopLevel(signature.slice(open + 1, close)).map((raw) => {
|
|
481
|
+
const [declaration, ...defaultParts] = raw.split(/=(?!>)/);
|
|
482
|
+
const defaultText = defaultParts.length > 0 ? defaultParts.join("=").trim() : null;
|
|
483
|
+
const colon = declaration.indexOf(":");
|
|
484
|
+
const head = (colon < 0 ? declaration : declaration.slice(0, colon)).trim();
|
|
485
|
+
const optional = head.endsWith("?");
|
|
486
|
+
return {
|
|
487
|
+
name: head.replace(/\?$/, ""),
|
|
488
|
+
type: colon < 0 ? "" : declaration.slice(colon + 1).trim(),
|
|
489
|
+
required: !optional && defaultText === null && !head.startsWith("..."),
|
|
490
|
+
default: defaultText,
|
|
491
|
+
};
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Return type text from `name(...) : R` (or `): R`), "" when absent. */
|
|
496
|
+
export function returnFromSignature(signature: string): string {
|
|
497
|
+
const open = signature.indexOf("(");
|
|
498
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
499
|
+
if (close < 0) return "";
|
|
500
|
+
const rest = signature.slice(close + 1).trim();
|
|
501
|
+
return rest.startsWith(":") ? rest.slice(1).trim() : "";
|
|
502
|
+
}
|
|
503
|
+
|
|
451
504
|
export class Docs {
|
|
452
505
|
private projectRoot: string;
|
|
453
506
|
private frameworkRoots: string[];
|
|
@@ -622,8 +675,8 @@ export class Docs {
|
|
|
622
675
|
static: entry.static ?? false,
|
|
623
676
|
source: entry.source,
|
|
624
677
|
version: entry.version,
|
|
625
|
-
params:
|
|
626
|
-
return:
|
|
678
|
+
params: paramsFromSignature(entry.signature),
|
|
679
|
+
return: returnFromSignature(entry.signature),
|
|
627
680
|
};
|
|
628
681
|
}
|
|
629
682
|
|
package/packages/core/src/mcp.ts
CHANGED
|
@@ -91,6 +91,7 @@ export interface JsonSchema {
|
|
|
91
91
|
type: string;
|
|
92
92
|
properties: Record<string, { type: string; default?: unknown }>;
|
|
93
93
|
required?: string[];
|
|
94
|
+
additionalProperties?: boolean;
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
export interface McpToolParam {
|
|
@@ -203,6 +204,36 @@ export function schemaFromParams(params: McpToolParam[]): JsonSchema {
|
|
|
203
204
|
return schema;
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Check call arguments against a tool's input schema BEFORE the handler runs.
|
|
209
|
+
* Returns null when valid, else the actionable message
|
|
210
|
+
* `missing required argument '<p>' (<tool> takes <p1>, <p2>)` (or
|
|
211
|
+
* `unknown argument '<k>' (...)`). Byte-identical to tina4-python's
|
|
212
|
+
* `_validate_tool_arguments` (tina4-php#271) so a wrong key never surfaces as a
|
|
213
|
+
* raw TypeError / 500.
|
|
214
|
+
*/
|
|
215
|
+
export function validateToolArguments(toolName: string, schema: JsonSchema, args: unknown): string | null {
|
|
216
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
217
|
+
const takes = properties.length > 0 ? properties.join(", ") : "no arguments";
|
|
218
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
219
|
+
return `arguments must be an object (${toolName} takes ${takes})`;
|
|
220
|
+
}
|
|
221
|
+
const supplied = args as Record<string, unknown>;
|
|
222
|
+
for (const requiredName of schema.required ?? []) {
|
|
223
|
+
if (!(requiredName in supplied)) {
|
|
224
|
+
return `missing required argument '${requiredName}' (${toolName} takes ${takes})`;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!schema.additionalProperties) {
|
|
228
|
+
for (const suppliedName of Object.keys(supplied)) {
|
|
229
|
+
if (!properties.includes(suppliedName)) {
|
|
230
|
+
return `unknown argument '${suppliedName}' (${toolName} takes ${takes})`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
206
237
|
// ── Localhost detection ──────────────────────────────────────
|
|
207
238
|
|
|
208
239
|
/**
|
|
@@ -574,6 +605,10 @@ export class McpServer {
|
|
|
574
605
|
}
|
|
575
606
|
|
|
576
607
|
const args = (params.arguments as Record<string, unknown>) || {};
|
|
608
|
+
const invalid = validateToolArguments(toolName, tool.inputSchema, args);
|
|
609
|
+
if (invalid) {
|
|
610
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: invalid }, null, 2) }] };
|
|
611
|
+
}
|
|
577
612
|
// Tool handlers may be async (the DB tools await the Database wrapper); a
|
|
578
613
|
// sync handler's plain return value passes through `await` unchanged.
|
|
579
614
|
const result = await tool.handler(args);
|
|
@@ -1139,7 +1174,14 @@ export function registerDevTools(server: McpServer): void {
|
|
|
1139
1174
|
if (!/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/.test(table)) {
|
|
1140
1175
|
return { error: "Invalid table name" };
|
|
1141
1176
|
}
|
|
1142
|
-
|
|
1177
|
+
// Schema metadata (PRAGMA / information_schema via the adapter), never the
|
|
1178
|
+
// first row, so an EMPTY table still reports its columns (tina4-php#271).
|
|
1179
|
+
const columns = (await db.getColumns?.(table)) ?? [];
|
|
1180
|
+
if (columns.length === 0) {
|
|
1181
|
+
const tables = ((await db.getTables?.()) ?? []) as string[];
|
|
1182
|
+
if (!tables.includes(table)) return { error: `table not found: ${table}` };
|
|
1183
|
+
}
|
|
1184
|
+
return columns;
|
|
1143
1185
|
} catch (e) {
|
|
1144
1186
|
return { error: (e as Error).message };
|
|
1145
1187
|
}
|
|
@@ -1168,6 +1210,7 @@ export function registerDevTools(server: McpServer): void {
|
|
|
1168
1210
|
method: r.method || "",
|
|
1169
1211
|
path: r.pattern || r.path || "",
|
|
1170
1212
|
auth_required: r.secure ?? false,
|
|
1213
|
+
middleware: r.middleware ?? [],
|
|
1171
1214
|
}));
|
|
1172
1215
|
} catch (e) {
|
|
1173
1216
|
return { error: (e as Error).message };
|
|
@@ -187,6 +187,8 @@ export interface RouteInfo {
|
|
|
187
187
|
path: string;
|
|
188
188
|
handler: string;
|
|
189
189
|
middlewareCount: number;
|
|
190
|
+
/** Names of the middleware attached to the route (functions / classes / string specs). */
|
|
191
|
+
middleware: string[];
|
|
190
192
|
cached: boolean;
|
|
191
193
|
secure: boolean;
|
|
192
194
|
}
|
|
@@ -486,6 +488,8 @@ export class Router {
|
|
|
486
488
|
path: route.pattern,
|
|
487
489
|
handler: route.filePath ?? (route.handler.name || "(anonymous)"),
|
|
488
490
|
middlewareCount: route.middlewares?.length ?? 0,
|
|
491
|
+
middleware: (route.middlewares ?? []).map((attached: any) =>
|
|
492
|
+
typeof attached === "string" ? attached : (attached?.name || attached?.constructor?.name || "(anonymous)")),
|
|
489
493
|
cached: route.cached ?? false,
|
|
490
494
|
secure: route.secure ?? false,
|
|
491
495
|
});
|
|
@@ -610,12 +610,14 @@ export class Session {
|
|
|
610
610
|
}
|
|
611
611
|
}
|
|
612
612
|
|
|
613
|
-
// Generate new session
|
|
613
|
+
// Generate new session. Nothing is written for it yet: a session nobody
|
|
614
|
+
// writes to has nothing to keep, and writing it here stored one record for
|
|
615
|
+
// every request that arrived without a cookie (static files, 404s and
|
|
616
|
+
// /health included). Its first save() after a change writes it whole.
|
|
614
617
|
this.sessionId = randomBytes(16).toString("hex");
|
|
615
618
|
const now = Math.floor(Date.now() / 1000);
|
|
616
619
|
this.data = { _created: now, _accessed: now };
|
|
617
620
|
this.dirty = false;
|
|
618
|
-
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
619
621
|
this.stored = false;
|
|
620
622
|
this.loaded = new Map();
|
|
621
623
|
this.cleared = false;
|
|
@@ -785,6 +787,20 @@ export class Session {
|
|
|
785
787
|
return result !== undefined ? result : defaultValue;
|
|
786
788
|
}
|
|
787
789
|
|
|
790
|
+
/**
|
|
791
|
+
* Whether this session is only an id: nothing has stored it and it holds no
|
|
792
|
+
* data of its own (its `_created` / `_accessed` bookkeeping does not count).
|
|
793
|
+
* Such a session has no record and needs no cookie, so save() writes nothing
|
|
794
|
+
* for it and the server sends no cookie for it. It stops being fresh when
|
|
795
|
+
* something is stored in it. Calls that mark a session changed without leaving
|
|
796
|
+
* anything in it (deleting a key it never had, clear(), regenerate() on an
|
|
797
|
+
* empty session) leave it fresh: a record with no data is not a session.
|
|
798
|
+
*/
|
|
799
|
+
isFresh(): boolean {
|
|
800
|
+
if (this.stored) return false;
|
|
801
|
+
return !this.data || Object.keys(this.data).every((k) => k === "_created" || k === "_accessed");
|
|
802
|
+
}
|
|
803
|
+
|
|
788
804
|
/**
|
|
789
805
|
* Get the current session ID.
|
|
790
806
|
*/
|
|
@@ -850,7 +866,7 @@ export class Session {
|
|
|
850
866
|
*/
|
|
851
867
|
save(): boolean {
|
|
852
868
|
if (!this.sessionId || !this.data) return true;
|
|
853
|
-
if (
|
|
869
|
+
if (this.isFresh()) return true;
|
|
854
870
|
let record: SessionData = this.data;
|
|
855
871
|
if (this.stored) {
|
|
856
872
|
const current = this.safeRead(this.sessionId);
|
|
@@ -1018,3 +1034,29 @@ export function buildSessionCookie(
|
|
|
1018
1034
|
parts.push(`Max-Age=${ttl}`);
|
|
1019
1035
|
return parts.join("; ");
|
|
1020
1036
|
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* The Set-Cookie value that hands the client this request's session, or null
|
|
1040
|
+
* when none is owed: the client already holds the id, headers have gone out, or
|
|
1041
|
+
* the session is fresh. A fresh session (only an id: minted for this request
|
|
1042
|
+
* and holding no data) was not stored, so a cookie for it would name nothing:
|
|
1043
|
+
* the next request could not resume it and would be handed another one.
|
|
1044
|
+
*/
|
|
1045
|
+
export function sessionCookieToSend(
|
|
1046
|
+
sess: { isFresh(): boolean },
|
|
1047
|
+
existingSid: string | undefined,
|
|
1048
|
+
rawReq: { headers: Record<string, string | string[] | undefined>; socket?: unknown },
|
|
1049
|
+
rawRes: { headersSent: boolean },
|
|
1050
|
+
): string | null {
|
|
1051
|
+
const newSid = (sess as any).sessionId ?? (sess as any).getSessionId?.();
|
|
1052
|
+
if (!newSid || newSid === existingSid || sess.isFresh() || rawRes.headersSent) return null;
|
|
1053
|
+
const ttl = parseInt(process.env.TINA4_SESSION_TTL ?? "3600", 10);
|
|
1054
|
+
// Thread the client's real scheme in so an HTTPS deploy behind a
|
|
1055
|
+
// TLS-terminating proxy ships the session cookie with `Secure`
|
|
1056
|
+
// (nodejs#34). `x-forwarded-proto` is the same header request.ts trusts
|
|
1057
|
+
// for URL construction; native socket TLS is the fallback.
|
|
1058
|
+
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
1059
|
+
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
1060
|
+
const socketEncrypted = (rawReq.socket as { encrypted?: boolean } | undefined)?.encrypted === true;
|
|
1061
|
+
return buildSessionCookie(newSid, ttl, undefined, forwardedProto, socketEncrypted);
|
|
1062
|
+
}
|
|
@@ -4915,6 +4915,7 @@ var init_router = __esm({
|
|
|
4915
4915
|
path: route.pattern,
|
|
4916
4916
|
handler: route.filePath ?? (route.handler.name || "(anonymous)"),
|
|
4917
4917
|
middlewareCount: route.middlewares?.length ?? 0,
|
|
4918
|
+
middleware: (route.middlewares ?? []).map((attached) => typeof attached === "string" ? attached : attached?.name || attached?.constructor?.name || "(anonymous)"),
|
|
4918
4919
|
cached: route.cached ?? false,
|
|
4919
4920
|
secure: route.secure ?? false
|
|
4920
4921
|
});
|
|
@@ -11832,6 +11833,7 @@ __export(session_exports, {
|
|
|
11832
11833
|
isSecureScheme: () => isSecureScheme,
|
|
11833
11834
|
isValidSessionId: () => isValidSessionId,
|
|
11834
11835
|
sessionCookieName: () => sessionCookieName,
|
|
11836
|
+
sessionCookieToSend: () => sessionCookieToSend,
|
|
11835
11837
|
sessionStrictMode: () => sessionStrictMode
|
|
11836
11838
|
});
|
|
11837
11839
|
import { createHash as createHash5, randomBytes as randomBytes4 } from "node:crypto";
|
|
@@ -11892,6 +11894,15 @@ function buildSessionCookie(sessionId, ttl, cookieName, forwardedProto, socketEn
|
|
|
11892
11894
|
parts.push(`Max-Age=${ttl}`);
|
|
11893
11895
|
return parts.join("; ");
|
|
11894
11896
|
}
|
|
11897
|
+
function sessionCookieToSend(sess, existingSid, rawReq, rawRes) {
|
|
11898
|
+
const newSid = sess.sessionId ?? sess.getSessionId?.();
|
|
11899
|
+
if (!newSid || newSid === existingSid || sess.isFresh() || rawRes.headersSent) return null;
|
|
11900
|
+
const ttl = parseInt(process.env.TINA4_SESSION_TTL ?? "3600", 10);
|
|
11901
|
+
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
11902
|
+
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
11903
|
+
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
11904
|
+
return buildSessionCookie(newSid, ttl, void 0, forwardedProto, socketEncrypted);
|
|
11905
|
+
}
|
|
11895
11906
|
var SESSION_ID_PATTERN, VALID_SESSION_BACKENDS, CANONICAL_SESSION_BACKENDS, RETIRED_SESSION_BACKENDS, FileSessionHandler, RedisSessionHandler, FLASH_PREFIX, Session;
|
|
11896
11907
|
var init_session = __esm({
|
|
11897
11908
|
"../core/src/session.ts"() {
|
|
@@ -12254,7 +12265,6 @@ var init_session = __esm({
|
|
|
12254
12265
|
const now = Math.floor(Date.now() / 1e3);
|
|
12255
12266
|
this.data = { _created: now, _accessed: now };
|
|
12256
12267
|
this.dirty = false;
|
|
12257
|
-
this.safeWrite(this.sessionId, this.data, this.ttl);
|
|
12258
12268
|
this.stored = false;
|
|
12259
12269
|
this.loaded = /* @__PURE__ */ new Map();
|
|
12260
12270
|
this.cleared = false;
|
|
@@ -12405,6 +12415,19 @@ var init_session = __esm({
|
|
|
12405
12415
|
const result = this.flash(key);
|
|
12406
12416
|
return result !== void 0 ? result : defaultValue;
|
|
12407
12417
|
}
|
|
12418
|
+
/**
|
|
12419
|
+
* Whether this session is only an id: nothing has stored it and it holds no
|
|
12420
|
+
* data of its own (its `_created` / `_accessed` bookkeeping does not count).
|
|
12421
|
+
* Such a session has no record and needs no cookie, so save() writes nothing
|
|
12422
|
+
* for it and the server sends no cookie for it. It stops being fresh when
|
|
12423
|
+
* something is stored in it. Calls that mark a session changed without leaving
|
|
12424
|
+
* anything in it (deleting a key it never had, clear(), regenerate() on an
|
|
12425
|
+
* empty session) leave it fresh: a record with no data is not a session.
|
|
12426
|
+
*/
|
|
12427
|
+
isFresh() {
|
|
12428
|
+
if (this.stored) return false;
|
|
12429
|
+
return !this.data || Object.keys(this.data).every((k) => k === "_created" || k === "_accessed");
|
|
12430
|
+
}
|
|
12408
12431
|
/**
|
|
12409
12432
|
* Get the current session ID.
|
|
12410
12433
|
*/
|
|
@@ -12467,7 +12490,7 @@ var init_session = __esm({
|
|
|
12467
12490
|
*/
|
|
12468
12491
|
save() {
|
|
12469
12492
|
if (!this.sessionId || !this.data) return true;
|
|
12470
|
-
if (
|
|
12493
|
+
if (this.isFresh()) return true;
|
|
12471
12494
|
let record = this.data;
|
|
12472
12495
|
if (this.stored) {
|
|
12473
12496
|
const current = this.safeRead(this.sessionId);
|
|
@@ -12654,7 +12677,7 @@ function errorLabel(err) {
|
|
|
12654
12677
|
return err instanceof Error ? `${err.name}: ${err.message}` : String(err);
|
|
12655
12678
|
}
|
|
12656
12679
|
async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
12657
|
-
const { Session: Session2,
|
|
12680
|
+
const { Session: Session2, sessionCookieToSend: sessionCookieToSend2, sessionCookieName: sessionCookieName2, sessionStrictMode: sessionStrictMode2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
12658
12681
|
const cookieHeader = rawReq.headers.cookie ?? "";
|
|
12659
12682
|
const cookiePrefix = sessionCookieName2() + "=";
|
|
12660
12683
|
let existingSid;
|
|
@@ -12693,13 +12716,9 @@ async function sessionAutoStart(rawReq, rawRes, req2) {
|
|
|
12693
12716
|
} catch {
|
|
12694
12717
|
}
|
|
12695
12718
|
}
|
|
12696
|
-
const
|
|
12697
|
-
if (
|
|
12698
|
-
|
|
12699
|
-
const xfProto = rawReq.headers["x-forwarded-proto"];
|
|
12700
|
-
const forwardedProto = Array.isArray(xfProto) ? xfProto[0] : xfProto;
|
|
12701
|
-
const socketEncrypted = rawReq.socket?.encrypted === true;
|
|
12702
|
-
rawRes.appendHeader("Set-Cookie", buildSessionCookie2(newSid, ttl, void 0, forwardedProto, socketEncrypted));
|
|
12719
|
+
const sessionCookie = sessionCookieToSend2(sess, existingSid, rawReq, rawRes);
|
|
12720
|
+
if (sessionCookie !== null) {
|
|
12721
|
+
rawRes.appendHeader("Set-Cookie", sessionCookie);
|
|
12703
12722
|
}
|
|
12704
12723
|
return origEnd(...args);
|
|
12705
12724
|
};
|
|
@@ -15856,7 +15875,8 @@ __export(mcp_exports, {
|
|
|
15856
15875
|
mcpTool: () => mcpTool,
|
|
15857
15876
|
register: () => register,
|
|
15858
15877
|
registerDevTools: () => registerDevTools,
|
|
15859
|
-
schemaFromParams: () => schemaFromParams
|
|
15878
|
+
schemaFromParams: () => schemaFromParams,
|
|
15879
|
+
validateToolArguments: () => validateToolArguments
|
|
15860
15880
|
});
|
|
15861
15881
|
import * as fs4 from "node:fs";
|
|
15862
15882
|
import * as path3 from "node:path";
|
|
@@ -15928,6 +15948,27 @@ function schemaFromParams(params) {
|
|
|
15928
15948
|
}
|
|
15929
15949
|
return schema;
|
|
15930
15950
|
}
|
|
15951
|
+
function validateToolArguments(toolName, schema, args) {
|
|
15952
|
+
const properties = Object.keys(schema.properties ?? {});
|
|
15953
|
+
const takes = properties.length > 0 ? properties.join(", ") : "no arguments";
|
|
15954
|
+
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
15955
|
+
return `arguments must be an object (${toolName} takes ${takes})`;
|
|
15956
|
+
}
|
|
15957
|
+
const supplied = args;
|
|
15958
|
+
for (const requiredName of schema.required ?? []) {
|
|
15959
|
+
if (!(requiredName in supplied)) {
|
|
15960
|
+
return `missing required argument '${requiredName}' (${toolName} takes ${takes})`;
|
|
15961
|
+
}
|
|
15962
|
+
}
|
|
15963
|
+
if (!schema.additionalProperties) {
|
|
15964
|
+
for (const suppliedName of Object.keys(supplied)) {
|
|
15965
|
+
if (!properties.includes(suppliedName)) {
|
|
15966
|
+
return `unknown argument '${suppliedName}' (${toolName} takes ${takes})`;
|
|
15967
|
+
}
|
|
15968
|
+
}
|
|
15969
|
+
}
|
|
15970
|
+
return null;
|
|
15971
|
+
}
|
|
15931
15972
|
function isLocalhost() {
|
|
15932
15973
|
const hostEnv = process.env.TINA4_HOST_NAME || "localhost:7148";
|
|
15933
15974
|
const host = hostEnv.split(":")[0];
|
|
@@ -16226,7 +16267,12 @@ function registerDevTools(server) {
|
|
|
16226
16267
|
if (!/^[A-Za-z_][A-Za-z0-9_$]*(\.[A-Za-z_][A-Za-z0-9_$]*)?$/.test(table2)) {
|
|
16227
16268
|
return { error: "Invalid table name" };
|
|
16228
16269
|
}
|
|
16229
|
-
|
|
16270
|
+
const columns = await db.getColumns?.(table2) ?? [];
|
|
16271
|
+
if (columns.length === 0) {
|
|
16272
|
+
const tables = await db.getTables?.() ?? [];
|
|
16273
|
+
if (!tables.includes(table2)) return { error: `table not found: ${table2}` };
|
|
16274
|
+
}
|
|
16275
|
+
return columns;
|
|
16230
16276
|
} catch (e) {
|
|
16231
16277
|
return { error: e.message };
|
|
16232
16278
|
}
|
|
@@ -16245,7 +16291,8 @@ function registerDevTools(server) {
|
|
|
16245
16291
|
return routes.map((r) => ({
|
|
16246
16292
|
method: r.method || "",
|
|
16247
16293
|
path: r.pattern || r.path || "",
|
|
16248
|
-
auth_required: r.secure ?? false
|
|
16294
|
+
auth_required: r.secure ?? false,
|
|
16295
|
+
middleware: r.middleware ?? []
|
|
16249
16296
|
}));
|
|
16250
16297
|
} catch (e) {
|
|
16251
16298
|
return { error: e.message };
|
|
@@ -17391,6 +17438,10 @@ data: ${channel.buffer.shift()}
|
|
|
17391
17438
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
17392
17439
|
}
|
|
17393
17440
|
const args = params.arguments || {};
|
|
17441
|
+
const invalid = validateToolArguments(toolName, tool.inputSchema, args);
|
|
17442
|
+
if (invalid) {
|
|
17443
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: invalid }, null, 2) }] };
|
|
17444
|
+
}
|
|
17394
17445
|
const result = await tool.handler(args);
|
|
17395
17446
|
let content;
|
|
17396
17447
|
if (typeof result === "string") {
|
|
@@ -23429,7 +23480,9 @@ var init_docsParser = __esm({
|
|
|
23429
23480
|
// ../core/src/docs.ts
|
|
23430
23481
|
var docs_exports = {};
|
|
23431
23482
|
__export(docs_exports, {
|
|
23432
|
-
Docs: () => Docs
|
|
23483
|
+
Docs: () => Docs,
|
|
23484
|
+
paramsFromSignature: () => paramsFromSignature,
|
|
23485
|
+
returnFromSignature: () => returnFromSignature
|
|
23433
23486
|
});
|
|
23434
23487
|
import * as fs8 from "node:fs";
|
|
23435
23488
|
import * as path8 from "node:path";
|
|
@@ -23656,6 +23709,54 @@ function scoreFqn(entry, tokens) {
|
|
|
23656
23709
|
}
|
|
23657
23710
|
return score;
|
|
23658
23711
|
}
|
|
23712
|
+
function splitTopLevel(text) {
|
|
23713
|
+
const parts = [];
|
|
23714
|
+
let depth = 0;
|
|
23715
|
+
let current = "";
|
|
23716
|
+
for (const ch of text) {
|
|
23717
|
+
if ("<([{".includes(ch)) depth++;
|
|
23718
|
+
else if (">)]}".includes(ch)) depth--;
|
|
23719
|
+
if (ch === "," && depth === 0) {
|
|
23720
|
+
parts.push(current);
|
|
23721
|
+
current = "";
|
|
23722
|
+
} else current += ch;
|
|
23723
|
+
}
|
|
23724
|
+
if (current.trim()) parts.push(current);
|
|
23725
|
+
return parts;
|
|
23726
|
+
}
|
|
23727
|
+
function closingParen(signature, open) {
|
|
23728
|
+
let depth = 0;
|
|
23729
|
+
for (let i = open; i < signature.length; i++) {
|
|
23730
|
+
if (signature[i] === "(") depth++;
|
|
23731
|
+
else if (signature[i] === ")" && --depth === 0) return i;
|
|
23732
|
+
}
|
|
23733
|
+
return -1;
|
|
23734
|
+
}
|
|
23735
|
+
function paramsFromSignature(signature) {
|
|
23736
|
+
const open = signature.indexOf("(");
|
|
23737
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
23738
|
+
if (close < 0) return [];
|
|
23739
|
+
return splitTopLevel(signature.slice(open + 1, close)).map((raw) => {
|
|
23740
|
+
const [declaration, ...defaultParts] = raw.split(/=(?!>)/);
|
|
23741
|
+
const defaultText = defaultParts.length > 0 ? defaultParts.join("=").trim() : null;
|
|
23742
|
+
const colon = declaration.indexOf(":");
|
|
23743
|
+
const head = (colon < 0 ? declaration : declaration.slice(0, colon)).trim();
|
|
23744
|
+
const optional = head.endsWith("?");
|
|
23745
|
+
return {
|
|
23746
|
+
name: head.replace(/\?$/, ""),
|
|
23747
|
+
type: colon < 0 ? "" : declaration.slice(colon + 1).trim(),
|
|
23748
|
+
required: !optional && defaultText === null && !head.startsWith("..."),
|
|
23749
|
+
default: defaultText
|
|
23750
|
+
};
|
|
23751
|
+
});
|
|
23752
|
+
}
|
|
23753
|
+
function returnFromSignature(signature) {
|
|
23754
|
+
const open = signature.indexOf("(");
|
|
23755
|
+
const close = open < 0 ? -1 : closingParen(signature, open);
|
|
23756
|
+
if (close < 0) return "";
|
|
23757
|
+
const rest = signature.slice(close + 1).trim();
|
|
23758
|
+
return rest.startsWith(":") ? rest.slice(1).trim() : "";
|
|
23759
|
+
}
|
|
23659
23760
|
var STDLIB_ALLOWLIST, _DOCS_CACHE, __FILENAME, __DIRNAME, USER_DIRS, CAMEL_SPLIT, FENCE_RE, CALL_RE, sharedFrameworkIndex, Docs;
|
|
23660
23761
|
var init_docs = __esm({
|
|
23661
23762
|
"../core/src/docs.ts"() {
|
|
@@ -23961,8 +24062,8 @@ var init_docs = __esm({
|
|
|
23961
24062
|
static: entry.static ?? false,
|
|
23962
24063
|
source: entry.source,
|
|
23963
24064
|
version: entry.version,
|
|
23964
|
-
params:
|
|
23965
|
-
return:
|
|
24065
|
+
params: paramsFromSignature(entry.signature),
|
|
24066
|
+
return: returnFromSignature(entry.signature)
|
|
23966
24067
|
};
|
|
23967
24068
|
}
|
|
23968
24069
|
/**
|
package/types/core/src/docs.d.ts
CHANGED
|
@@ -30,7 +30,8 @@ export interface MethodSpec {
|
|
|
30
30
|
params: Array<{
|
|
31
31
|
name: string;
|
|
32
32
|
type: string;
|
|
33
|
-
|
|
33
|
+
required: boolean;
|
|
34
|
+
default: string | null;
|
|
34
35
|
}>;
|
|
35
36
|
return: string;
|
|
36
37
|
}
|
|
@@ -69,6 +70,10 @@ export interface DriftHit {
|
|
|
69
70
|
line: number;
|
|
70
71
|
block: string;
|
|
71
72
|
}
|
|
73
|
+
/** Structured params `[{name, type, required, default}]` from `name(a: T, b?: U = 1) : R`. */
|
|
74
|
+
export declare function paramsFromSignature(signature: string): MethodSpec["params"];
|
|
75
|
+
/** Return type text from `name(...) : R` (or `): R`), "" when absent. */
|
|
76
|
+
export declare function returnFromSignature(signature: string): string;
|
|
72
77
|
export declare class Docs {
|
|
73
78
|
private projectRoot;
|
|
74
79
|
private frameworkRoots;
|
package/types/core/src/mcp.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface JsonSchema {
|
|
|
30
30
|
default?: unknown;
|
|
31
31
|
}>;
|
|
32
32
|
required?: string[];
|
|
33
|
+
additionalProperties?: boolean;
|
|
33
34
|
}
|
|
34
35
|
export interface McpToolParam {
|
|
35
36
|
name: string;
|
|
@@ -57,6 +58,15 @@ export declare function decodeRequest(data: string | Record<string, unknown>): {
|
|
|
57
58
|
* Since TypeScript erases types at runtime, we use explicit metadata.
|
|
58
59
|
*/
|
|
59
60
|
export declare function schemaFromParams(params: McpToolParam[]): JsonSchema;
|
|
61
|
+
/**
|
|
62
|
+
* Check call arguments against a tool's input schema BEFORE the handler runs.
|
|
63
|
+
* Returns null when valid, else the actionable message
|
|
64
|
+
* `missing required argument '<p>' (<tool> takes <p1>, <p2>)` (or
|
|
65
|
+
* `unknown argument '<k>' (...)`). Byte-identical to tina4-python's
|
|
66
|
+
* `_validate_tool_arguments` (tina4-php#271) so a wrong key never surfaces as a
|
|
67
|
+
* raw TypeError / 500.
|
|
68
|
+
*/
|
|
69
|
+
export declare function validateToolArguments(toolName: string, schema: JsonSchema, args: unknown): string | null;
|
|
60
70
|
/**
|
|
61
71
|
* Informational only — whether the CONFIGURED host looks local.
|
|
62
72
|
*
|
|
@@ -258,6 +258,16 @@ export declare class Session {
|
|
|
258
258
|
* Get flash data by key (alias for flash(key) without value).
|
|
259
259
|
*/
|
|
260
260
|
getFlash(key: string, defaultValue?: unknown): unknown;
|
|
261
|
+
/**
|
|
262
|
+
* Whether this session is only an id: nothing has stored it and it holds no
|
|
263
|
+
* data of its own (its `_created` / `_accessed` bookkeeping does not count).
|
|
264
|
+
* Such a session has no record and needs no cookie, so save() writes nothing
|
|
265
|
+
* for it and the server sends no cookie for it. It stops being fresh when
|
|
266
|
+
* something is stored in it. Calls that mark a session changed without leaving
|
|
267
|
+
* anything in it (deleting a key it never had, clear(), regenerate() on an
|
|
268
|
+
* empty session) leave it fresh: a record with no data is not a session.
|
|
269
|
+
*/
|
|
270
|
+
isFresh(): boolean;
|
|
261
271
|
/**
|
|
262
272
|
* Get the current session ID.
|
|
263
273
|
*/
|
|
@@ -388,4 +398,19 @@ export declare function sessionStrictMode(): boolean;
|
|
|
388
398
|
* http://localhost dev cookies undeliverable.
|
|
389
399
|
*/
|
|
390
400
|
export declare function buildSessionCookie(sessionId: string | null, ttl: number, cookieName?: string, forwardedProto?: string, socketEncrypted?: boolean): string;
|
|
401
|
+
/**
|
|
402
|
+
* The Set-Cookie value that hands the client this request's session, or null
|
|
403
|
+
* when none is owed: the client already holds the id, headers have gone out, or
|
|
404
|
+
* the session is fresh. A fresh session (only an id: minted for this request
|
|
405
|
+
* and holding no data) was not stored, so a cookie for it would name nothing:
|
|
406
|
+
* the next request could not resume it and would be handed another one.
|
|
407
|
+
*/
|
|
408
|
+
export declare function sessionCookieToSend(sess: {
|
|
409
|
+
isFresh(): boolean;
|
|
410
|
+
}, existingSid: string | undefined, rawReq: {
|
|
411
|
+
headers: Record<string, string | string[] | undefined>;
|
|
412
|
+
socket?: unknown;
|
|
413
|
+
}, rawRes: {
|
|
414
|
+
headersSent: boolean;
|
|
415
|
+
}): string | null;
|
|
391
416
|
export {};
|