tina4-nodejs 3.13.95 → 3.13.96
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 +1 -2
- package/package.json +2 -1
- package/packages/cli/dist/bin.js +681 -994
- package/packages/core/dist/index.js +561 -875
- package/packages/core/public/css/tina4.min.css +1 -1
- package/packages/core/src/index.ts +1 -3
- package/packages/core/src/messenger.ts +288 -96
- package/packages/core/src/request.ts +28 -7
- package/packages/core/src/server.ts +135 -7
- package/packages/orm/dist/index.js +612 -926
- package/packages/orm/src/autoCrud.ts +12 -10
- package/packages/orm/src/database.ts +62 -58
- package/packages/orm/src/databaseResult.ts +44 -73
- package/packages/orm/src/index.ts +0 -3
- package/packages/orm/src/migration.ts +26 -8
- package/packages/orm/src/model.ts +4 -0
- package/packages/orm/src/queryBuilder.ts +12 -5
- package/packages/orm/src/types.ts +7 -74
- package/packages/swagger/dist/index.js +78 -20
- package/packages/swagger/src/generator.ts +172 -29
- package/types/core/src/index.d.ts +1 -3
- package/types/core/src/messenger.d.ts +45 -4
- package/types/core/src/server.d.ts +0 -4
- package/types/orm/src/database.d.ts +34 -30
- package/types/orm/src/databaseResult.d.ts +26 -36
- package/types/orm/src/index.d.ts +1 -2
- package/types/orm/src/migration.d.ts +4 -3
- package/types/orm/src/types.d.ts +7 -34
- package/packages/core/src/scss.ts +0 -623
- package/types/core/src/scss.d.ts +0 -19
|
@@ -8,64 +8,6 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
// src/types.ts
|
|
12
|
-
var FetchResult;
|
|
13
|
-
var init_types = __esm({
|
|
14
|
-
"src/types.ts"() {
|
|
15
|
-
"use strict";
|
|
16
|
-
FetchResult = class {
|
|
17
|
-
records;
|
|
18
|
-
count;
|
|
19
|
-
sql;
|
|
20
|
-
constructor(records, sql = "") {
|
|
21
|
-
this.records = records;
|
|
22
|
-
this.count = records.length;
|
|
23
|
-
this.sql = sql;
|
|
24
|
-
}
|
|
25
|
-
/** Paginate the in-memory result set. */
|
|
26
|
-
toPaginate(page = 1, perPage = 20) {
|
|
27
|
-
const total = this.count;
|
|
28
|
-
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
29
|
-
const offset = (page - 1) * perPage;
|
|
30
|
-
const data = this.records.slice(offset, offset + perPage);
|
|
31
|
-
return {
|
|
32
|
-
data,
|
|
33
|
-
page,
|
|
34
|
-
perPage,
|
|
35
|
-
total,
|
|
36
|
-
totalPages,
|
|
37
|
-
hasNext: page < totalPages,
|
|
38
|
-
hasPrev: page > 1
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
/** Return the first record or null. */
|
|
42
|
-
first() {
|
|
43
|
-
return this.records[0] ?? null;
|
|
44
|
-
}
|
|
45
|
-
/** Return the last record or null. */
|
|
46
|
-
last() {
|
|
47
|
-
return this.records[this.records.length - 1] ?? null;
|
|
48
|
-
}
|
|
49
|
-
/** Check if result is empty. */
|
|
50
|
-
isEmpty() {
|
|
51
|
-
return this.records.length === 0;
|
|
52
|
-
}
|
|
53
|
-
/** Convert to plain array. */
|
|
54
|
-
toArray() {
|
|
55
|
-
return [...this.records];
|
|
56
|
-
}
|
|
57
|
-
/** Convert to JSON string. */
|
|
58
|
-
toJSON() {
|
|
59
|
-
return JSON.stringify(this.records);
|
|
60
|
-
}
|
|
61
|
-
/** Iterate over records. */
|
|
62
|
-
[Symbol.iterator]() {
|
|
63
|
-
return this.records[Symbol.iterator]();
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
11
|
// src/databaseResult.ts
|
|
70
12
|
var DatabaseResult;
|
|
71
13
|
var init_databaseResult = __esm({
|
|
@@ -129,75 +71,52 @@ var init_databaseResult = __esm({
|
|
|
129
71
|
toArray() {
|
|
130
72
|
return this.records;
|
|
131
73
|
}
|
|
132
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Describe the page this result IS — the canonical pagination envelope.
|
|
133
76
|
*
|
|
134
|
-
*
|
|
135
|
-
* (
|
|
136
|
-
*
|
|
137
|
-
*
|
|
77
|
+
* Takes NO arguments and derives every field from the query that produced this
|
|
78
|
+
* result (ADR-0043). Passing ANY argument RAISES: a DatabaseResult holds no
|
|
79
|
+
* connection, so an argument could only re-slice the rows already in memory and
|
|
80
|
+
* then report total_pages for pages it can never reach. To read page N, FETCH
|
|
81
|
+
* page N (limit + offset) and call this with no arguments.
|
|
138
82
|
*
|
|
139
|
-
*
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Describe the page this result actually IS. Takes no arguments.
|
|
83
|
+
* The envelope is EXACTLY seven snake_case keys, identical across all four
|
|
84
|
+
* frameworks: `records, total, page, per_page, total_pages, limit, offset`.
|
|
143
85
|
*
|
|
144
|
-
*
|
|
145
|
-
* (
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
86
|
+
* per_page = the query's limit
|
|
87
|
+
* page = floor(offset / limit) + 1
|
|
88
|
+
* total = the TRUE total for the filter — Database.fetch (and
|
|
89
|
+
* QueryBuilder.get) run a COUNT probe whenever a limit was
|
|
90
|
+
* applied — NEVER the number of rows returned
|
|
91
|
+
* total_pages = ceil(total / per_page)
|
|
92
|
+
* records = the rows the query returned, VERBATIM (never re-sliced)
|
|
93
|
+
* limit = the SQL limit actually applied
|
|
94
|
+
* offset = the SQL offset actually applied
|
|
151
95
|
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* (
|
|
155
|
-
*
|
|
156
|
-
* pages 1-5 of 20 were right and every page from 6 onward came back EMPTY
|
|
157
|
-
* while totalPages reported 5,000.
|
|
96
|
+
* The JSON payload is snake_case even though the method name is camelCase — a
|
|
97
|
+
* JSON key is data, not a language surface (ADR-0043). The old duplicate and
|
|
98
|
+
* camelCase keys (`data`, `count`, `perPage`, `totalPages`, `has_next`,
|
|
99
|
+
* `has_prev`) are removed: Node emitted 13 keys, the worst offender of the four.
|
|
158
100
|
*
|
|
159
|
-
*
|
|
160
|
-
* all four frameworks - Database.fetch runs a COUNT probe whenever it applied
|
|
161
|
-
* a limit. It used to be ROWS RETURNED here and in Ruby while Python and PHP
|
|
162
|
-
* probed, so one query answered 20 in two frameworks and 250 in the other
|
|
163
|
-
* two.
|
|
101
|
+
* @throws {TypeError} if called with any argument.
|
|
164
102
|
*/
|
|
165
|
-
toPaginate(
|
|
166
|
-
if (
|
|
103
|
+
toPaginate() {
|
|
104
|
+
if (arguments.length > 0) {
|
|
167
105
|
throw new TypeError(
|
|
168
|
-
|
|
106
|
+
"toPaginate() takes no arguments and derives the page from the query that ran (ADR-0043). A DatabaseResult holds no connection, so an argument could only re-slice the rows already in memory and report total_pages for pages it can never reach. To read a page, FETCH it: db.fetch(sql, params, perPage, (page - 1) * perPage), then call toPaginate() with no arguments."
|
|
169
107
|
);
|
|
170
108
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let rows;
|
|
175
|
-
if (page === void 0 && perPage === void 0) {
|
|
176
|
-
resolvedPerPage = this.limit > 0 ? this.limit : this.records.length;
|
|
177
|
-
resolvedPage = resolvedPerPage > 0 ? Math.floor(this.offset / resolvedPerPage) + 1 : 1;
|
|
178
|
-
offset = this.offset;
|
|
179
|
-
rows = this.records;
|
|
180
|
-
} else {
|
|
181
|
-
resolvedPage = page ?? 1;
|
|
182
|
-
resolvedPerPage = perPage ?? (this.limit > 0 ? this.limit : 10);
|
|
183
|
-
offset = (resolvedPage - 1) * resolvedPerPage;
|
|
184
|
-
rows = this.records.slice(offset, offset + resolvedPerPage);
|
|
185
|
-
}
|
|
186
|
-
const totalPages = resolvedPerPage > 0 ? Math.max(1, Math.ceil(this.count / resolvedPerPage)) : 1;
|
|
109
|
+
const perPage = this.limit > 0 ? this.limit : this.records.length;
|
|
110
|
+
const page = perPage > 0 ? Math.floor(this.offset / perPage) + 1 : 1;
|
|
111
|
+
const totalPages = perPage > 0 ? Math.max(1, Math.ceil(this.count / perPage)) : 1;
|
|
187
112
|
return {
|
|
188
|
-
records:
|
|
189
|
-
data: rows,
|
|
190
|
-
count: this.count,
|
|
113
|
+
records: this.records,
|
|
191
114
|
total: this.count,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
page: resolvedPage,
|
|
195
|
-
per_page: resolvedPerPage,
|
|
196
|
-
perPage: resolvedPerPage,
|
|
197
|
-
totalPages,
|
|
115
|
+
page,
|
|
116
|
+
per_page: perPage,
|
|
198
117
|
total_pages: totalPages,
|
|
199
|
-
|
|
200
|
-
|
|
118
|
+
limit: perPage,
|
|
119
|
+
offset: this.offset
|
|
201
120
|
};
|
|
202
121
|
}
|
|
203
122
|
/** Iterable — for (const row of result) */
|
|
@@ -3336,7 +3255,7 @@ ${s}\r
|
|
|
3336
3255
|
connect() {
|
|
3337
3256
|
if (this.connected) return Promise.resolve();
|
|
3338
3257
|
if (this.connecting) return this.connecting;
|
|
3339
|
-
this.connecting = new Promise((
|
|
3258
|
+
this.connecting = new Promise((resolve20, reject) => {
|
|
3340
3259
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
3341
3260
|
sock.setNoDelay(true);
|
|
3342
3261
|
const onError = (err) => {
|
|
@@ -3373,7 +3292,7 @@ ${s}\r
|
|
|
3373
3292
|
sock.on("error", (e) => {
|
|
3374
3293
|
this.brokenError = e;
|
|
3375
3294
|
});
|
|
3376
|
-
|
|
3295
|
+
resolve20();
|
|
3377
3296
|
} catch (e) {
|
|
3378
3297
|
onError(e);
|
|
3379
3298
|
}
|
|
@@ -3436,12 +3355,12 @@ ${s}\r
|
|
|
3436
3355
|
}
|
|
3437
3356
|
/** Send one command and await its reply (assumes socket is up). */
|
|
3438
3357
|
raw(args) {
|
|
3439
|
-
return new Promise((
|
|
3358
|
+
return new Promise((resolve20, reject) => {
|
|
3440
3359
|
if (!this.sock || this.sock.destroyed) {
|
|
3441
3360
|
reject(this.brokenError ?? new Error("redis socket not connected"));
|
|
3442
3361
|
return;
|
|
3443
3362
|
}
|
|
3444
|
-
this.waiters.push({ resolve:
|
|
3363
|
+
this.waiters.push({ resolve: resolve20, reject });
|
|
3445
3364
|
this.sock.write(_RespClient.encode(args));
|
|
3446
3365
|
});
|
|
3447
3366
|
}
|
|
@@ -3783,7 +3702,7 @@ ${s}\r
|
|
|
3783
3702
|
connect() {
|
|
3784
3703
|
if (this.connected) return Promise.resolve();
|
|
3785
3704
|
if (this.connecting) return this.connecting;
|
|
3786
|
-
this.connecting = new Promise((
|
|
3705
|
+
this.connecting = new Promise((resolve20, reject) => {
|
|
3787
3706
|
const sock = net.createConnection({ host: this.host, port: this.port });
|
|
3788
3707
|
sock.setNoDelay(true);
|
|
3789
3708
|
sock.once("error", (err) => {
|
|
@@ -3804,7 +3723,7 @@ ${s}\r
|
|
|
3804
3723
|
p.resolve(this.buffer.toString("utf-8"));
|
|
3805
3724
|
}
|
|
3806
3725
|
});
|
|
3807
|
-
|
|
3726
|
+
resolve20();
|
|
3808
3727
|
});
|
|
3809
3728
|
});
|
|
3810
3729
|
return this.connecting;
|
|
@@ -3837,13 +3756,13 @@ ${s}\r
|
|
|
3837
3756
|
async send(payload, terminator) {
|
|
3838
3757
|
await this.connect();
|
|
3839
3758
|
if (!this.sock || this.sock.destroyed) return "";
|
|
3840
|
-
return new Promise((
|
|
3759
|
+
return new Promise((resolve20) => {
|
|
3841
3760
|
this.buffer = Buffer.alloc(0);
|
|
3842
|
-
this.pending = { terminator, resolve:
|
|
3761
|
+
this.pending = { terminator, resolve: resolve20 };
|
|
3843
3762
|
const timer = setTimeout(() => {
|
|
3844
|
-
if (this.pending && this.pending.resolve ===
|
|
3763
|
+
if (this.pending && this.pending.resolve === resolve20) {
|
|
3845
3764
|
this.pending = null;
|
|
3846
|
-
|
|
3765
|
+
resolve20(this.buffer.toString("utf-8"));
|
|
3847
3766
|
}
|
|
3848
3767
|
}, 4e3);
|
|
3849
3768
|
if (timer.unref) timer.unref();
|
|
@@ -5369,16 +5288,27 @@ async function parseBody(req2) {
|
|
|
5369
5288
|
}
|
|
5370
5289
|
const contentType = req2.headers["content-type"] ?? "";
|
|
5371
5290
|
const chunks = [];
|
|
5372
|
-
await new Promise((
|
|
5373
|
-
|
|
5374
|
-
|
|
5291
|
+
await new Promise((resolve20, reject) => {
|
|
5292
|
+
let received = 0;
|
|
5293
|
+
let refused = false;
|
|
5294
|
+
req2.on("data", (chunk) => {
|
|
5295
|
+
if (refused) return;
|
|
5296
|
+
received += chunk.length;
|
|
5297
|
+
if (received > TINA4_MAX_UPLOAD_SIZE) {
|
|
5298
|
+
refused = true;
|
|
5299
|
+
chunks.length = 0;
|
|
5300
|
+
reject(new PayloadTooLargeError(received, TINA4_MAX_UPLOAD_SIZE));
|
|
5301
|
+
return;
|
|
5302
|
+
}
|
|
5303
|
+
chunks.push(chunk);
|
|
5304
|
+
});
|
|
5305
|
+
req2.on("end", () => {
|
|
5306
|
+
if (!refused) resolve20();
|
|
5307
|
+
});
|
|
5375
5308
|
req2.on("error", reject);
|
|
5376
5309
|
});
|
|
5377
5310
|
const raw = Buffer.concat(chunks);
|
|
5378
5311
|
if (raw.length === 0) return;
|
|
5379
|
-
if (raw.length > TINA4_MAX_UPLOAD_SIZE) {
|
|
5380
|
-
throw new PayloadTooLargeError(raw.length, TINA4_MAX_UPLOAD_SIZE);
|
|
5381
|
-
}
|
|
5382
5312
|
if (contentType.includes("multipart/form-data")) {
|
|
5383
5313
|
const boundary = extractBoundary(contentType);
|
|
5384
5314
|
if (boundary) {
|
|
@@ -13201,14 +13131,14 @@ data: ${channel.buffer.shift()}
|
|
|
13201
13131
|
`;
|
|
13202
13132
|
continue;
|
|
13203
13133
|
}
|
|
13204
|
-
const gotMessage = await new Promise((
|
|
13134
|
+
const gotMessage = await new Promise((resolve20) => {
|
|
13205
13135
|
const timer = setTimeout(() => {
|
|
13206
13136
|
channel.wake = null;
|
|
13207
|
-
|
|
13137
|
+
resolve20(false);
|
|
13208
13138
|
}, keepaliveMs);
|
|
13209
13139
|
channel.wake = () => {
|
|
13210
13140
|
clearTimeout(timer);
|
|
13211
|
-
|
|
13141
|
+
resolve20(true);
|
|
13212
13142
|
};
|
|
13213
13143
|
});
|
|
13214
13144
|
if (!gotMessage) yield `: keep-alive
|
|
@@ -15723,7 +15653,7 @@ var init_websocket = __esm({
|
|
|
15723
15653
|
* Start the WebSocket server.
|
|
15724
15654
|
*/
|
|
15725
15655
|
async start() {
|
|
15726
|
-
return new Promise((
|
|
15656
|
+
return new Promise((resolve20, reject) => {
|
|
15727
15657
|
this.server = createServer((req2, res) => {
|
|
15728
15658
|
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
15729
15659
|
res.end("Upgrade Required");
|
|
@@ -15733,7 +15663,7 @@ var init_websocket = __esm({
|
|
|
15733
15663
|
});
|
|
15734
15664
|
this.server.listen(this.port, () => {
|
|
15735
15665
|
this.startIdleReaper();
|
|
15736
|
-
|
|
15666
|
+
resolve20();
|
|
15737
15667
|
});
|
|
15738
15668
|
this.server.on("error", (err) => {
|
|
15739
15669
|
this.emit("error", err);
|
|
@@ -16297,7 +16227,7 @@ var init_websocket = __esm({
|
|
|
16297
16227
|
client.trackerId = this.onAdd(socket.remoteAddress ?? "unknown", "/__dev_reload");
|
|
16298
16228
|
}
|
|
16299
16229
|
this.clients.add(client);
|
|
16300
|
-
const
|
|
16230
|
+
const cleanup = () => {
|
|
16301
16231
|
if (!this.clients.has(client)) return;
|
|
16302
16232
|
this.clients.delete(client);
|
|
16303
16233
|
if (client.trackerId && this.onRemove) this.onRemove(client.trackerId);
|
|
@@ -16320,13 +16250,13 @@ var init_websocket = __esm({
|
|
|
16320
16250
|
socket.end();
|
|
16321
16251
|
} catch {
|
|
16322
16252
|
}
|
|
16323
|
-
|
|
16253
|
+
cleanup();
|
|
16324
16254
|
return;
|
|
16325
16255
|
}
|
|
16326
16256
|
}
|
|
16327
16257
|
});
|
|
16328
|
-
socket.on("close",
|
|
16329
|
-
socket.on("error",
|
|
16258
|
+
socket.on("close", cleanup);
|
|
16259
|
+
socket.on("error", cleanup);
|
|
16330
16260
|
return true;
|
|
16331
16261
|
}
|
|
16332
16262
|
/**
|
|
@@ -17844,7 +17774,7 @@ var init_queue = __esm({
|
|
|
17844
17774
|
const jobs = this.popBatch(resolvedBatchSize);
|
|
17845
17775
|
if (jobs.length === 0) {
|
|
17846
17776
|
if (resolvedPollInterval <= 0) break;
|
|
17847
|
-
await new Promise((
|
|
17777
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
17848
17778
|
continue;
|
|
17849
17779
|
}
|
|
17850
17780
|
yield jobs;
|
|
@@ -17854,7 +17784,7 @@ var init_queue = __esm({
|
|
|
17854
17784
|
const raw = this.pop();
|
|
17855
17785
|
if (raw === null) {
|
|
17856
17786
|
if (resolvedPollInterval <= 0) break;
|
|
17857
|
-
await new Promise((
|
|
17787
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
17858
17788
|
continue;
|
|
17859
17789
|
}
|
|
17860
17790
|
yield createJob(raw, this);
|
|
@@ -22374,23 +22304,23 @@ var init_devAdmin = __esm({
|
|
|
22374
22304
|
});
|
|
22375
22305
|
};
|
|
22376
22306
|
handleDevAdminJs = async (_req, res) => {
|
|
22377
|
-
const { readFileSync:
|
|
22378
|
-
const { dirname:
|
|
22307
|
+
const { readFileSync: readFileSync26, existsSync: existsSync26 } = await import("node:fs");
|
|
22308
|
+
const { dirname: dirname13, join: join29, resolve: resolve20 } = await import("node:path");
|
|
22379
22309
|
const { fileURLToPath: fileURLToPath7 } = await import("node:url");
|
|
22380
|
-
const dir =
|
|
22310
|
+
const dir = dirname13(fileURLToPath7(import.meta.url));
|
|
22381
22311
|
const candidates = [
|
|
22382
|
-
|
|
22312
|
+
join29(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
22383
22313
|
// src/../public/js/
|
|
22384
|
-
|
|
22314
|
+
join29(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
22385
22315
|
// deeper nesting
|
|
22386
|
-
|
|
22387
|
-
|
|
22316
|
+
resolve20(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
22317
|
+
resolve20(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
22388
22318
|
// project public/
|
|
22389
22319
|
];
|
|
22390
22320
|
for (const jsPath of candidates) {
|
|
22391
|
-
if (
|
|
22321
|
+
if (existsSync26(jsPath)) {
|
|
22392
22322
|
try {
|
|
22393
|
-
const content =
|
|
22323
|
+
const content = readFileSync26(jsPath, "utf-8");
|
|
22394
22324
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
22395
22325
|
res.raw.end(content);
|
|
22396
22326
|
return;
|
|
@@ -22851,8 +22781,12 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
22851
22781
|
function generate(routes, models = []) {
|
|
22852
22782
|
const info = {
|
|
22853
22783
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
22854
|
-
version
|
|
22855
|
-
|
|
22784
|
+
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
22785
|
+
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
22786
|
+
// Both are the settled cross-framework defaults (parity with the Python
|
|
22787
|
+
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
22788
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
22789
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
22856
22790
|
};
|
|
22857
22791
|
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
22858
22792
|
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
@@ -22885,9 +22819,11 @@ function generate(routes, models = []) {
|
|
|
22885
22819
|
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
22886
22820
|
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
22887
22821
|
const refSchemas = /* @__PURE__ */ new Set();
|
|
22822
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
22888
22823
|
for (const model of models) {
|
|
22889
|
-
const
|
|
22890
|
-
|
|
22824
|
+
const schemaKey = schemaNameForModel(model);
|
|
22825
|
+
tableToSchema.set(model.tableName, schemaKey);
|
|
22826
|
+
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
22891
22827
|
}
|
|
22892
22828
|
const usedTags = [];
|
|
22893
22829
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -22914,11 +22850,11 @@ function generate(routes, models = []) {
|
|
|
22914
22850
|
if (route.meta?.deprecated) operation.deprecated = true;
|
|
22915
22851
|
const pathParams = extractPathParams(route.pattern);
|
|
22916
22852
|
if (pathParams.length > 0) {
|
|
22917
|
-
operation.parameters = pathParams.map((name) => ({
|
|
22853
|
+
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
22918
22854
|
name,
|
|
22919
22855
|
in: "path",
|
|
22920
22856
|
required: true,
|
|
22921
|
-
schema
|
|
22857
|
+
schema
|
|
22922
22858
|
}));
|
|
22923
22859
|
}
|
|
22924
22860
|
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
@@ -22944,19 +22880,20 @@ function generate(routes, models = []) {
|
|
|
22944
22880
|
};
|
|
22945
22881
|
} else if (method === "post" || method === "put") {
|
|
22946
22882
|
const modelName = inferModelFromPath(route.pattern);
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
};
|
|
22883
|
+
const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
|
|
22884
|
+
if (schemaKey) {
|
|
22885
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
22886
|
+
const media = { schema: { $ref: sref } };
|
|
22951
22887
|
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
22952
22888
|
operation.requestBody = {
|
|
22953
22889
|
required: true,
|
|
22954
22890
|
content: { "application/json": media }
|
|
22955
22891
|
};
|
|
22956
|
-
|
|
22957
|
-
|
|
22958
|
-
|
|
22959
|
-
|
|
22892
|
+
if (route.meta?.responses === void 0) {
|
|
22893
|
+
operation.responses = {
|
|
22894
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
22895
|
+
};
|
|
22896
|
+
}
|
|
22960
22897
|
} else if (route.meta?.example !== void 0) {
|
|
22961
22898
|
operation.requestBody = {
|
|
22962
22899
|
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
@@ -23043,6 +22980,21 @@ function resolveServers() {
|
|
|
23043
22980
|
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
23044
22981
|
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
23045
22982
|
}
|
|
22983
|
+
function schemaNameForModel(model) {
|
|
22984
|
+
const explicit = model.className?.trim();
|
|
22985
|
+
if (explicit) return explicit;
|
|
22986
|
+
return deriveClassName(model.tableName);
|
|
22987
|
+
}
|
|
22988
|
+
function deriveClassName(tableName) {
|
|
22989
|
+
return singularize(tableName).split(/[_\s-]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || tableName;
|
|
22990
|
+
}
|
|
22991
|
+
function singularize(word) {
|
|
22992
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
22993
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) return word.slice(0, -2);
|
|
22994
|
+
if (/ss$/i.test(word)) return word;
|
|
22995
|
+
if (/s$/i.test(word) && word.length > 1) return word.slice(0, -1);
|
|
22996
|
+
return word;
|
|
22997
|
+
}
|
|
23046
22998
|
function modelToSchema(model) {
|
|
23047
22999
|
const properties = {};
|
|
23048
23000
|
const required = [];
|
|
@@ -23116,15 +23068,35 @@ function inferSchema(value) {
|
|
|
23116
23068
|
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
23117
23069
|
return { type: "string" };
|
|
23118
23070
|
}
|
|
23071
|
+
function segmentParam(segment) {
|
|
23072
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
23073
|
+
const inner = segment.slice(1, -1);
|
|
23074
|
+
if (inner.startsWith("...")) return { name: inner.slice(3), type: "string" };
|
|
23075
|
+
const colon = inner.indexOf(":");
|
|
23076
|
+
if (colon >= 0) return { name: inner.slice(0, colon), type: inner.slice(colon + 1) };
|
|
23077
|
+
return { name: inner, type: "string" };
|
|
23078
|
+
}
|
|
23079
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
23080
|
+
const inner = segment.slice(1, -1);
|
|
23081
|
+
return { name: inner.startsWith("...") ? inner.slice(3) : inner, type: "string" };
|
|
23082
|
+
}
|
|
23083
|
+
if (segment.startsWith(":") && segment.length > 1) {
|
|
23084
|
+
return { name: segment.slice(1), type: "string" };
|
|
23085
|
+
}
|
|
23086
|
+
return null;
|
|
23087
|
+
}
|
|
23119
23088
|
function patternToOpenAPI(pattern) {
|
|
23120
|
-
return pattern.
|
|
23089
|
+
return pattern.split("/").map((segment) => {
|
|
23090
|
+
const p = segmentParam(segment);
|
|
23091
|
+
return p ? `{${p.name}}` : segment;
|
|
23092
|
+
}).join("/");
|
|
23121
23093
|
}
|
|
23122
23094
|
function extractPathParams(pattern) {
|
|
23123
23095
|
const params = [];
|
|
23124
|
-
const
|
|
23125
|
-
|
|
23126
|
-
|
|
23127
|
-
params.push(
|
|
23096
|
+
for (const segment of pattern.split("/")) {
|
|
23097
|
+
const p = segmentParam(segment);
|
|
23098
|
+
if (!p) continue;
|
|
23099
|
+
params.push({ name: p.name, schema: { ...PARAM_TYPE_SCHEMA[p.type] ?? { type: "string" } } });
|
|
23128
23100
|
}
|
|
23129
23101
|
return params;
|
|
23130
23102
|
}
|
|
@@ -23146,8 +23118,12 @@ function inferModelFromPath(pattern) {
|
|
|
23146
23118
|
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
23147
23119
|
return null;
|
|
23148
23120
|
}
|
|
23121
|
+
function operationIdBase(method, openApiPath) {
|
|
23122
|
+
const clean = openApiPath.replace(/^\/+|\/+$/g, "").replace(/\//g, "_").replace(/\.\.\./g, "").replace(/[{}]/g, "").replace(/\*/g, "wildcard");
|
|
23123
|
+
return clean ? `${method}_${clean}` : method;
|
|
23124
|
+
}
|
|
23149
23125
|
function uniqueOperationId(method, openApiPath, seen) {
|
|
23150
|
-
const base = (method
|
|
23126
|
+
const base = operationIdBase(method, openApiPath);
|
|
23151
23127
|
let oid = base;
|
|
23152
23128
|
let n = 2;
|
|
23153
23129
|
while (seen.has(oid)) {
|
|
@@ -23157,13 +23133,25 @@ function uniqueOperationId(method, openApiPath, seen) {
|
|
|
23157
23133
|
seen.add(oid);
|
|
23158
23134
|
return oid;
|
|
23159
23135
|
}
|
|
23160
|
-
var WRITE_METHODS, registeredSchemes, registeredSchemas;
|
|
23136
|
+
var WRITE_METHODS, registeredSchemes, registeredSchemas, PARAM_TYPE_SCHEMA;
|
|
23161
23137
|
var init_generator = __esm({
|
|
23162
23138
|
"../swagger/src/generator.ts"() {
|
|
23163
23139
|
"use strict";
|
|
23164
23140
|
WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
23165
23141
|
registeredSchemes = {};
|
|
23166
23142
|
registeredSchemas = {};
|
|
23143
|
+
PARAM_TYPE_SCHEMA = {
|
|
23144
|
+
int: { type: "integer" },
|
|
23145
|
+
integer: { type: "integer" },
|
|
23146
|
+
float: { type: "number" },
|
|
23147
|
+
number: { type: "number" },
|
|
23148
|
+
uuid: { type: "string", format: "uuid" },
|
|
23149
|
+
slug: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
23150
|
+
alpha: { type: "string", pattern: "^[A-Za-z]+$" },
|
|
23151
|
+
alnum: { type: "string", pattern: "^[A-Za-z0-9]+$" },
|
|
23152
|
+
path: { type: "string" },
|
|
23153
|
+
string: { type: "string" }
|
|
23154
|
+
};
|
|
23167
23155
|
}
|
|
23168
23156
|
});
|
|
23169
23157
|
|
|
@@ -23483,10 +23471,29 @@ function openBrowser(url) {
|
|
|
23483
23471
|
}, 2e3);
|
|
23484
23472
|
}
|
|
23485
23473
|
function resolvePortAndHost(config) {
|
|
23486
|
-
const
|
|
23474
|
+
const tina4Port = process.env.TINA4_PORT;
|
|
23475
|
+
const legacyPort = process.env.PORT;
|
|
23476
|
+
let port;
|
|
23477
|
+
if (config?.port !== void 0) {
|
|
23478
|
+
port = config.port;
|
|
23479
|
+
} else if (tina4Port && /^\d+$/.test(tina4Port)) {
|
|
23480
|
+
port = parseInt(tina4Port, 10);
|
|
23481
|
+
} else if (legacyPort && /^\d+$/.test(legacyPort)) {
|
|
23482
|
+
port = parseInt(legacyPort, 10);
|
|
23483
|
+
warnDeprecatedPort(port);
|
|
23484
|
+
} else {
|
|
23485
|
+
port = 7148;
|
|
23486
|
+
}
|
|
23487
23487
|
const host = config?.host ?? process.env.TINA4_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
23488
23488
|
return { port, host };
|
|
23489
23489
|
}
|
|
23490
|
+
function warnDeprecatedPort(port) {
|
|
23491
|
+
if (portDeprecationWarned) return;
|
|
23492
|
+
portDeprecationWarned = true;
|
|
23493
|
+
Log.warning(
|
|
23494
|
+
`PORT is deprecated and will be removed in 3.14 - use TINA4_PORT instead (binding port ${port} from PORT)`
|
|
23495
|
+
);
|
|
23496
|
+
}
|
|
23490
23497
|
function isBannerSuppressed() {
|
|
23491
23498
|
return isTruthy(process.env.TINA4_SUPPRESS);
|
|
23492
23499
|
}
|
|
@@ -23758,6 +23765,29 @@ function deployGallery(name) {
|
|
|
23758
23765
|
</body>
|
|
23759
23766
|
</html>`;
|
|
23760
23767
|
}
|
|
23768
|
+
function startLoopWatchdog() {
|
|
23769
|
+
const raw = (process.env.TINA4_LOOP_LAG_WARN_MS ?? "").trim();
|
|
23770
|
+
const threshold = /^\d+$/.test(raw) ? parseInt(raw, 10) : 250;
|
|
23771
|
+
if (threshold <= 0) {
|
|
23772
|
+
return { stop: () => {
|
|
23773
|
+
} };
|
|
23774
|
+
}
|
|
23775
|
+
let last = Date.now();
|
|
23776
|
+
let warned = 0;
|
|
23777
|
+
const timer = setInterval(() => {
|
|
23778
|
+
const now = Date.now();
|
|
23779
|
+
const lag = now - last - LOOP_WATCHDOG_TICK_MS;
|
|
23780
|
+
last = now;
|
|
23781
|
+
if (lag < threshold) return;
|
|
23782
|
+
warned++;
|
|
23783
|
+
if (warned > 5 && warned % 20 !== 0) return;
|
|
23784
|
+
Log.warning(
|
|
23785
|
+
`Event loop blocked for ${lag}ms. Node serves every request on one loop, so a handler doing CPU-bound work or synchronous I/O stalls all the others for that long. Move the work to Tina4's queue, or await it. Set TINA4_LOOP_LAG_WARN_MS to change the ${threshold}ms threshold, or 0 to silence.`
|
|
23786
|
+
);
|
|
23787
|
+
}, LOOP_WATCHDOG_TICK_MS);
|
|
23788
|
+
timer.unref();
|
|
23789
|
+
return { stop: () => clearInterval(timer) };
|
|
23790
|
+
}
|
|
23761
23791
|
async function start(config) {
|
|
23762
23792
|
const isManaged = process.argv.includes("--managed");
|
|
23763
23793
|
if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== "true") {
|
|
@@ -24007,7 +24037,9 @@ async function startServer(config) {
|
|
|
24007
24037
|
const resolved = resolvePortAndHost(config);
|
|
24008
24038
|
const host = resolved.host;
|
|
24009
24039
|
let port = resolved.port;
|
|
24010
|
-
|
|
24040
|
+
if (!cluster.isWorker) {
|
|
24041
|
+
port = findAvailablePort(port);
|
|
24042
|
+
}
|
|
24011
24043
|
const isProduction = (process.env.TINA4_PRODUCTION ?? "").toLowerCase() === "true";
|
|
24012
24044
|
if (cluster.isPrimary && isProduction) {
|
|
24013
24045
|
const numCPUs = os2.cpus().length;
|
|
@@ -24223,7 +24255,20 @@ ${reset2}
|
|
|
24223
24255
|
await sessionAutoStart(rawReq, rawRes, req2);
|
|
24224
24256
|
await middleware.run(req2, res);
|
|
24225
24257
|
if (res.raw.writableEnded) return;
|
|
24226
|
-
|
|
24258
|
+
try {
|
|
24259
|
+
await req2.parseBody();
|
|
24260
|
+
} catch (err) {
|
|
24261
|
+
const status2 = err?.statusCode;
|
|
24262
|
+
if (typeof status2 === "number" && status2 >= 400 && status2 < 500) {
|
|
24263
|
+
if (!rawRes.writableEnded) {
|
|
24264
|
+
rawRes.statusCode = status2;
|
|
24265
|
+
rawRes.setHeader("content-type", "application/json");
|
|
24266
|
+
rawRes.end(JSON.stringify({ error: err.message }));
|
|
24267
|
+
}
|
|
24268
|
+
return;
|
|
24269
|
+
}
|
|
24270
|
+
throw err;
|
|
24271
|
+
}
|
|
24227
24272
|
const pathname = req2.path;
|
|
24228
24273
|
const reqStartTime = DevAdmin.isEnabled() ? Date.now() : 0;
|
|
24229
24274
|
const matchedPattern = { value: "" };
|
|
@@ -24422,8 +24467,10 @@ ${reset2}
|
|
|
24422
24467
|
};
|
|
24423
24468
|
process.on("SIGTERM", onSigterm);
|
|
24424
24469
|
process.on("SIGINT", onSigint);
|
|
24470
|
+
const loopWatchdog = startLoopWatchdog();
|
|
24425
24471
|
resolvePromise({
|
|
24426
24472
|
close: () => {
|
|
24473
|
+
loopWatchdog.stop();
|
|
24427
24474
|
process.off("SIGTERM", onSigterm);
|
|
24428
24475
|
process.off("SIGINT", onSigint);
|
|
24429
24476
|
stopAllBackgroundTasks();
|
|
@@ -24438,7 +24485,7 @@ ${reset2}
|
|
|
24438
24485
|
});
|
|
24439
24486
|
});
|
|
24440
24487
|
}
|
|
24441
|
-
var __filename, __dirname, BUILTIN_ERROR_TEMPLATES_DIR, BUILTIN_PUBLIC_DIR, swaggerAssetsEnabled, DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TINA4_VERSION2, frondCache, _LEGACY_ENV_VARS, TEMPLATE_PAGES_DIR, HTTP_REASON_PHRASES, templateCache, _dispatchFn, _serverHandle, FALLBACK_STAGES;
|
|
24488
|
+
var __filename, __dirname, BUILTIN_ERROR_TEMPLATES_DIR, BUILTIN_PUBLIC_DIR, swaggerAssetsEnabled, DEFAULT_SHUTDOWN_TIMEOUT_SECONDS, TINA4_VERSION2, frondCache, _LEGACY_ENV_VARS, portDeprecationWarned, TEMPLATE_PAGES_DIR, HTTP_REASON_PHRASES, templateCache, _dispatchFn, _serverHandle, LOOP_WATCHDOG_TICK_MS, FALLBACK_STAGES;
|
|
24442
24489
|
var init_server = __esm({
|
|
24443
24490
|
"../core/src/server.ts"() {
|
|
24444
24491
|
"use strict";
|
|
@@ -24491,6 +24538,7 @@ var init_server = __esm({
|
|
|
24491
24538
|
SWAGGER_VERSION: "TINA4_SWAGGER_VERSION",
|
|
24492
24539
|
ORM_PLURAL_TABLE_NAMES: "TINA4_ORM_PLURAL_TABLE_NAMES"
|
|
24493
24540
|
};
|
|
24541
|
+
portDeprecationWarned = false;
|
|
24494
24542
|
TEMPLATE_PAGES_DIR = "pages";
|
|
24495
24543
|
HTTP_REASON_PHRASES = {
|
|
24496
24544
|
100: "Continue",
|
|
@@ -24527,6 +24575,7 @@ var init_server = __esm({
|
|
|
24527
24575
|
templateCache = null;
|
|
24528
24576
|
_dispatchFn = null;
|
|
24529
24577
|
_serverHandle = null;
|
|
24578
|
+
LOOP_WATCHDOG_TICK_MS = 100;
|
|
24530
24579
|
FALLBACK_STAGES = [
|
|
24531
24580
|
serveTemplateFallback,
|
|
24532
24581
|
serveLandingPage,
|
|
@@ -25082,433 +25131,6 @@ var init_fakeData = __esm({
|
|
|
25082
25131
|
}
|
|
25083
25132
|
});
|
|
25084
25133
|
|
|
25085
|
-
// ../core/src/scss.ts
|
|
25086
|
-
import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, existsSync as existsSync23, mkdirSync as mkdirSync14, readdirSync as readdirSync16 } from "node:fs";
|
|
25087
|
-
import { join as join23, resolve as resolve16, dirname as dirname10 } from "node:path";
|
|
25088
|
-
function compileString(scss, importPaths, variables) {
|
|
25089
|
-
const imported = /* @__PURE__ */ new Set();
|
|
25090
|
-
scss = resolveImports(scss, importPaths, imported);
|
|
25091
|
-
scss = scss.replace(/(?<![:"'])\/\/[^\n]*/g, "");
|
|
25092
|
-
scss = extractVariables(scss, variables);
|
|
25093
|
-
const mixins = {};
|
|
25094
|
-
scss = extractMixins(scss, mixins);
|
|
25095
|
-
scss = resolveIncludes(scss, mixins);
|
|
25096
|
-
scss = resolveInterpolation(scss, variables);
|
|
25097
|
-
scss = substituteVariables(scss, variables);
|
|
25098
|
-
scss = evalMath(scss);
|
|
25099
|
-
scss = resolveColorFunctions(scss);
|
|
25100
|
-
const css = flattenNesting(scss);
|
|
25101
|
-
return cleanup(css);
|
|
25102
|
-
}
|
|
25103
|
-
function resolveImports(content, paths, imported) {
|
|
25104
|
-
return content.replace(/@import\s+["']?([^"';\n]+)["']?\s*;/g, (_match, name) => {
|
|
25105
|
-
name = name.trim();
|
|
25106
|
-
const candidates = [];
|
|
25107
|
-
for (const base of paths) {
|
|
25108
|
-
candidates.push(
|
|
25109
|
-
join23(base, `${name}.scss`),
|
|
25110
|
-
join23(base, `_${name}.scss`),
|
|
25111
|
-
join23(base, name)
|
|
25112
|
-
);
|
|
25113
|
-
}
|
|
25114
|
-
for (const candidate of candidates) {
|
|
25115
|
-
if (existsSync23(candidate) && !imported.has(candidate)) {
|
|
25116
|
-
imported.add(candidate);
|
|
25117
|
-
const fileContent = readFileSync21(candidate, "utf-8");
|
|
25118
|
-
return resolveImports(fileContent, [dirname10(candidate), ...paths], imported);
|
|
25119
|
-
}
|
|
25120
|
-
}
|
|
25121
|
-
return `/* IMPORT NOT FOUND: ${name} */`;
|
|
25122
|
-
});
|
|
25123
|
-
}
|
|
25124
|
-
function stripVariableFlags(value) {
|
|
25125
|
-
let declaresDefault = false;
|
|
25126
|
-
for (; ; ) {
|
|
25127
|
-
const match = VARIABLE_FLAG.exec(value);
|
|
25128
|
-
if (match === null) return [value.trim(), declaresDefault];
|
|
25129
|
-
if (match[1] === "default") declaresDefault = true;
|
|
25130
|
-
value = value.slice(0, match.index);
|
|
25131
|
-
}
|
|
25132
|
-
}
|
|
25133
|
-
function extractVariables(scss, variables) {
|
|
25134
|
-
return scss.replace(/\$([a-zA-Z_][\w-]*)\s*:\s*([^;]+);/g, (_m, name, value) => {
|
|
25135
|
-
const [stripped, declaresDefault] = stripVariableFlags(value.trim());
|
|
25136
|
-
if (declaresDefault && (variables[name] ?? "null") !== "null") {
|
|
25137
|
-
return "";
|
|
25138
|
-
}
|
|
25139
|
-
let resolved = stripped;
|
|
25140
|
-
for (const [vName, vVal] of Object.entries(variables)) {
|
|
25141
|
-
resolved = resolved.replaceAll(`$${vName}`, vVal);
|
|
25142
|
-
}
|
|
25143
|
-
variables[name] = resolved;
|
|
25144
|
-
return "";
|
|
25145
|
-
});
|
|
25146
|
-
}
|
|
25147
|
-
function substituteVariables(scss, variables) {
|
|
25148
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
25149
|
-
for (const name of sorted) {
|
|
25150
|
-
scss = scss.replaceAll(`$${name}`, variables[name]);
|
|
25151
|
-
}
|
|
25152
|
-
return scss;
|
|
25153
|
-
}
|
|
25154
|
-
function resolveInterpolation(scss, variables) {
|
|
25155
|
-
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
25156
|
-
return scss.replace(/#\{([^{}]*)\}/g, (_m, inner) => {
|
|
25157
|
-
let resolved = inner.trim();
|
|
25158
|
-
for (const name of sorted) {
|
|
25159
|
-
resolved = resolved.replaceAll(`$${name}`, variables[name]);
|
|
25160
|
-
}
|
|
25161
|
-
return resolved;
|
|
25162
|
-
});
|
|
25163
|
-
}
|
|
25164
|
-
function extractMixins(scss, mixins) {
|
|
25165
|
-
const pattern = /@mixin\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*\{/g;
|
|
25166
|
-
let match;
|
|
25167
|
-
const locations = [];
|
|
25168
|
-
while ((match = pattern.exec(scss)) !== null) {
|
|
25169
|
-
const name = match[1];
|
|
25170
|
-
const paramsStr = match[2] ?? "";
|
|
25171
|
-
const params = paramsStr.split(",").map((p) => p.trim().replace(/^\$/, "")).filter(Boolean);
|
|
25172
|
-
const bodyStart = match.index + match[0].length;
|
|
25173
|
-
const body = findBlock(scss, bodyStart);
|
|
25174
|
-
if (body !== null) {
|
|
25175
|
-
mixins[name] = { params, body };
|
|
25176
|
-
locations.push({
|
|
25177
|
-
start: match.index,
|
|
25178
|
-
end: bodyStart + body.length + 1,
|
|
25179
|
-
name
|
|
25180
|
-
});
|
|
25181
|
-
}
|
|
25182
|
-
}
|
|
25183
|
-
let result = scss;
|
|
25184
|
-
for (const loc of locations.reverse()) {
|
|
25185
|
-
result = result.slice(0, loc.start) + result.slice(loc.end);
|
|
25186
|
-
}
|
|
25187
|
-
return result;
|
|
25188
|
-
}
|
|
25189
|
-
function resolveIncludes(scss, mixins) {
|
|
25190
|
-
return scss.replace(
|
|
25191
|
-
/@include\s+([\w-]+)\s*(?:\(([^)]*)\))?\s*;/g,
|
|
25192
|
-
(_m, name, argsStr) => {
|
|
25193
|
-
if (!(name in mixins)) {
|
|
25194
|
-
return `/* MIXIN NOT FOUND: ${name} */`;
|
|
25195
|
-
}
|
|
25196
|
-
const mixin = mixins[name];
|
|
25197
|
-
const args = argsStr ? argsStr.split(",").map((a) => a.trim()).filter(Boolean) : [];
|
|
25198
|
-
let body = mixin.body;
|
|
25199
|
-
for (let i = 0; i < mixin.params.length; i++) {
|
|
25200
|
-
const paramName = mixin.params[i].split(":")[0].trim();
|
|
25201
|
-
const defaultVal = mixin.params[i].includes(":") ? mixin.params[i].split(":").slice(1).join(":").trim() : "";
|
|
25202
|
-
const value = i < args.length ? args[i] : defaultVal;
|
|
25203
|
-
body = body.replaceAll(`$${paramName}`, value);
|
|
25204
|
-
}
|
|
25205
|
-
return body;
|
|
25206
|
-
}
|
|
25207
|
-
);
|
|
25208
|
-
}
|
|
25209
|
-
function evalMath(scss) {
|
|
25210
|
-
const placeholders = [];
|
|
25211
|
-
const masked = scss.replace(/calc\([^()]*\)/g, (m) => {
|
|
25212
|
-
placeholders.push(m);
|
|
25213
|
-
return `\0CALC${placeholders.length - 1}\0`;
|
|
25214
|
-
});
|
|
25215
|
-
const folded = masked.replace(
|
|
25216
|
-
/([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)/g,
|
|
25217
|
-
(full, n1, u1, op, n2, u2) => {
|
|
25218
|
-
const num1 = parseFloat(n1);
|
|
25219
|
-
const num2 = parseFloat(n2);
|
|
25220
|
-
if (Number.isNaN(num1) || Number.isNaN(num2)) return full;
|
|
25221
|
-
const unit1 = u1 || "";
|
|
25222
|
-
const unit2 = u2 || "";
|
|
25223
|
-
let unit;
|
|
25224
|
-
if (unit1 === unit2) {
|
|
25225
|
-
unit = unit1;
|
|
25226
|
-
} else if ((op === "*" || op === "/") && unit1 === "") {
|
|
25227
|
-
unit = unit2;
|
|
25228
|
-
} else if ((op === "*" || op === "/") && unit2 === "") {
|
|
25229
|
-
unit = unit1;
|
|
25230
|
-
} else {
|
|
25231
|
-
return full;
|
|
25232
|
-
}
|
|
25233
|
-
let result;
|
|
25234
|
-
switch (op) {
|
|
25235
|
-
case "+":
|
|
25236
|
-
result = num1 + num2;
|
|
25237
|
-
break;
|
|
25238
|
-
case "-":
|
|
25239
|
-
result = num1 - num2;
|
|
25240
|
-
break;
|
|
25241
|
-
case "*":
|
|
25242
|
-
result = num1 * num2;
|
|
25243
|
-
break;
|
|
25244
|
-
case "/":
|
|
25245
|
-
if (num2 === 0) return full;
|
|
25246
|
-
result = num1 / num2;
|
|
25247
|
-
break;
|
|
25248
|
-
default:
|
|
25249
|
-
return full;
|
|
25250
|
-
}
|
|
25251
|
-
if (result === Math.floor(result)) {
|
|
25252
|
-
return `${Math.floor(result)}${unit}`;
|
|
25253
|
-
}
|
|
25254
|
-
return `${result.toFixed(2)}${unit}`;
|
|
25255
|
-
}
|
|
25256
|
-
);
|
|
25257
|
-
return folded.replace(/\x00CALC(\d+)\x00/g, (_m, idx) => {
|
|
25258
|
-
return placeholders[parseInt(idx, 10)];
|
|
25259
|
-
});
|
|
25260
|
-
}
|
|
25261
|
-
function resolveColorFunctions(scss) {
|
|
25262
|
-
scss = scss.replace(
|
|
25263
|
-
/lighten\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
25264
|
-
(_m, color, amt) => adjustLightness(color.trim(), parseFloat(amt.trim().replace(/%$/, "")) / 100)
|
|
25265
|
-
);
|
|
25266
|
-
scss = scss.replace(
|
|
25267
|
-
/darken\(\s*([^,]+)\s*,\s*([^)]+)\s*\)/g,
|
|
25268
|
-
(_m, color, amt) => adjustLightness(color.trim(), -(parseFloat(amt.trim().replace(/%$/, "")) / 100))
|
|
25269
|
-
);
|
|
25270
|
-
scss = scss.replace(/rgba\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*([\d.]+)\s*\)/g, (whole, hex, alpha) => {
|
|
25271
|
-
const rgb = hexToRgb(hex);
|
|
25272
|
-
return rgb === null ? whole : `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha.trim()})`;
|
|
25273
|
-
});
|
|
25274
|
-
scss = scss.replace(/rgb\(\s*(#[0-9a-fA-F]{3,8})\s*\)/g, (whole, hex) => {
|
|
25275
|
-
const rgb = hexToRgb(hex);
|
|
25276
|
-
return rgb === null ? whole : `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
|
|
25277
|
-
});
|
|
25278
|
-
scss = scss.replace(
|
|
25279
|
-
/mix\(\s*(#[0-9a-fA-F]{3,8})\s*,\s*(#[0-9a-fA-F]{3,8})\s*(?:,\s*([\d.]+%?)\s*)?\)/g,
|
|
25280
|
-
(whole, h1, h2, weight) => {
|
|
25281
|
-
const c1 = hexToRgb(h1);
|
|
25282
|
-
const c2 = hexToRgb(h2);
|
|
25283
|
-
if (c1 === null || c2 === null) return whole;
|
|
25284
|
-
const w = weight ? parseFloat(weight.replace(/%$/, "")) / 100 : 0.5;
|
|
25285
|
-
const mixed = [0, 1, 2].map((i) => Math.round(c1[i] * w + c2[i] * (1 - w)));
|
|
25286
|
-
return `#${mixed.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
25287
|
-
}
|
|
25288
|
-
);
|
|
25289
|
-
return scss;
|
|
25290
|
-
}
|
|
25291
|
-
function hexToRgb(color) {
|
|
25292
|
-
let c = color.trim().replace(/^#/, "");
|
|
25293
|
-
if (c.length === 3) {
|
|
25294
|
-
c = c.split("").map((ch) => ch + ch).join("");
|
|
25295
|
-
}
|
|
25296
|
-
if (!/^[0-9a-fA-F]{6}$/.test(c)) return null;
|
|
25297
|
-
return [parseInt(c.slice(0, 2), 16), parseInt(c.slice(2, 4), 16), parseInt(c.slice(4, 6), 16)];
|
|
25298
|
-
}
|
|
25299
|
-
function adjustLightness(color, amount) {
|
|
25300
|
-
const rgb = hexToRgb(color);
|
|
25301
|
-
if (rgb === null) return color;
|
|
25302
|
-
let [r, g, b] = rgb.map((v) => v / 255);
|
|
25303
|
-
const max = Math.max(r, g, b);
|
|
25304
|
-
const min = Math.min(r, g, b);
|
|
25305
|
-
let l = (max + min) / 2;
|
|
25306
|
-
const d = max - min;
|
|
25307
|
-
let h = 0;
|
|
25308
|
-
let s = 0;
|
|
25309
|
-
if (d !== 0) {
|
|
25310
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
25311
|
-
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
25312
|
-
else if (max === g) h = (b - r) / d + 2;
|
|
25313
|
-
else h = (r - g) / d + 4;
|
|
25314
|
-
h /= 6;
|
|
25315
|
-
}
|
|
25316
|
-
l = Math.max(0, Math.min(1, l + amount));
|
|
25317
|
-
if (s === 0) {
|
|
25318
|
-
r = g = b = l;
|
|
25319
|
-
} else {
|
|
25320
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
25321
|
-
const p = 2 * l - q;
|
|
25322
|
-
r = hueToRgb(p, q, h + 1 / 3);
|
|
25323
|
-
g = hueToRgb(p, q, h);
|
|
25324
|
-
b = hueToRgb(p, q, h - 1 / 3);
|
|
25325
|
-
}
|
|
25326
|
-
const hex = (v) => Math.trunc(v * 255).toString(16).padStart(2, "0");
|
|
25327
|
-
return `#${hex(r)}${hex(g)}${hex(b)}`;
|
|
25328
|
-
}
|
|
25329
|
-
function hueToRgb(p, q, t) {
|
|
25330
|
-
if (t < 0) t += 1;
|
|
25331
|
-
if (t > 1) t -= 1;
|
|
25332
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
25333
|
-
if (t < 1 / 2) return q;
|
|
25334
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
25335
|
-
return p;
|
|
25336
|
-
}
|
|
25337
|
-
function flattenNesting(scss) {
|
|
25338
|
-
const output = [];
|
|
25339
|
-
flattenBlock(scss, [], output);
|
|
25340
|
-
return output.join("\n");
|
|
25341
|
-
}
|
|
25342
|
-
function flattenBlock(content, parentSelectors, output) {
|
|
25343
|
-
let pos = 0;
|
|
25344
|
-
const properties = [];
|
|
25345
|
-
while (pos < content.length) {
|
|
25346
|
-
while (pos < content.length && /[\s]/.test(content[pos])) {
|
|
25347
|
-
pos++;
|
|
25348
|
-
}
|
|
25349
|
-
if (pos >= content.length) break;
|
|
25350
|
-
if (content[pos] === "/" && content[pos + 1] === "*") {
|
|
25351
|
-
const end = content.indexOf("*/", pos + 2);
|
|
25352
|
-
if (end === -1) break;
|
|
25353
|
-
output.push(content.slice(pos, end + 2));
|
|
25354
|
-
pos = end + 2;
|
|
25355
|
-
continue;
|
|
25356
|
-
}
|
|
25357
|
-
if (content.slice(pos, pos + 6) === "@media") {
|
|
25358
|
-
const brace = content.indexOf("{", pos);
|
|
25359
|
-
if (brace === -1) break;
|
|
25360
|
-
const mediaQuery = content.slice(pos, brace).trim();
|
|
25361
|
-
const body = findBlock(content, brace + 1);
|
|
25362
|
-
if (body === null) break;
|
|
25363
|
-
pos = brace + 1 + body.length + 1;
|
|
25364
|
-
const innerOutput = [];
|
|
25365
|
-
flattenBlock(body, parentSelectors, innerOutput);
|
|
25366
|
-
if (innerOutput.length > 0) {
|
|
25367
|
-
output.push(`${mediaQuery} {`);
|
|
25368
|
-
for (const line of innerOutput) {
|
|
25369
|
-
output.push(` ${line}`);
|
|
25370
|
-
}
|
|
25371
|
-
output.push("}");
|
|
25372
|
-
}
|
|
25373
|
-
continue;
|
|
25374
|
-
}
|
|
25375
|
-
const bracePos = content.indexOf("{", pos);
|
|
25376
|
-
const semiPos = content.indexOf(";", pos);
|
|
25377
|
-
if (semiPos !== -1 && (bracePos === -1 || semiPos < bracePos)) {
|
|
25378
|
-
const prop = content.slice(pos, semiPos).trim();
|
|
25379
|
-
if (prop && !prop.startsWith("@")) {
|
|
25380
|
-
properties.push(prop);
|
|
25381
|
-
}
|
|
25382
|
-
pos = semiPos + 1;
|
|
25383
|
-
continue;
|
|
25384
|
-
}
|
|
25385
|
-
if (bracePos !== -1) {
|
|
25386
|
-
const selectorText = content.slice(pos, bracePos).trim();
|
|
25387
|
-
const body = findBlock(content, bracePos + 1);
|
|
25388
|
-
if (body === null) break;
|
|
25389
|
-
pos = bracePos + 1 + body.length + 1;
|
|
25390
|
-
if (!selectorText) continue;
|
|
25391
|
-
const selectors = selectorText.split(",").map((s) => s.trim());
|
|
25392
|
-
const newSelectors = [];
|
|
25393
|
-
for (const sel of selectors) {
|
|
25394
|
-
if (parentSelectors.length > 0) {
|
|
25395
|
-
for (const parent of parentSelectors) {
|
|
25396
|
-
if (sel.includes("&")) {
|
|
25397
|
-
newSelectors.push(sel.replace(/&/g, parent));
|
|
25398
|
-
} else {
|
|
25399
|
-
newSelectors.push(`${parent} ${sel}`);
|
|
25400
|
-
}
|
|
25401
|
-
}
|
|
25402
|
-
} else {
|
|
25403
|
-
newSelectors.push(sel);
|
|
25404
|
-
}
|
|
25405
|
-
}
|
|
25406
|
-
flattenBlock(body, newSelectors, output);
|
|
25407
|
-
continue;
|
|
25408
|
-
}
|
|
25409
|
-
const remaining = content.slice(pos).trim();
|
|
25410
|
-
if (remaining) {
|
|
25411
|
-
properties.push(remaining);
|
|
25412
|
-
}
|
|
25413
|
-
break;
|
|
25414
|
-
}
|
|
25415
|
-
if (properties.length > 0 && parentSelectors.length > 0) {
|
|
25416
|
-
const selectorStr = parentSelectors.join(", ");
|
|
25417
|
-
output.push(`${selectorStr} {`);
|
|
25418
|
-
for (const prop of properties) {
|
|
25419
|
-
output.push(` ${prop};`);
|
|
25420
|
-
}
|
|
25421
|
-
output.push("}");
|
|
25422
|
-
}
|
|
25423
|
-
}
|
|
25424
|
-
function findBlock(content, start2) {
|
|
25425
|
-
let depth = 1;
|
|
25426
|
-
let pos = start2;
|
|
25427
|
-
while (pos < content.length && depth > 0) {
|
|
25428
|
-
if (content[pos] === "{") depth++;
|
|
25429
|
-
else if (content[pos] === "}") depth--;
|
|
25430
|
-
if (depth > 0) pos++;
|
|
25431
|
-
}
|
|
25432
|
-
return depth === 0 ? content.slice(start2, pos) : null;
|
|
25433
|
-
}
|
|
25434
|
-
function cleanup(css) {
|
|
25435
|
-
css = css.replace(/[^{}]+\{\s*\}/g, "");
|
|
25436
|
-
css = css.replace(/\n{3,}/g, "\n\n");
|
|
25437
|
-
css = css.split("\n").map((line) => line.trimEnd()).join("\n");
|
|
25438
|
-
return css.trim() + "\n";
|
|
25439
|
-
}
|
|
25440
|
-
var ScssCompiler, VARIABLE_FLAG;
|
|
25441
|
-
var init_scss = __esm({
|
|
25442
|
-
"../core/src/scss.ts"() {
|
|
25443
|
-
"use strict";
|
|
25444
|
-
ScssCompiler = class {
|
|
25445
|
-
_importPaths;
|
|
25446
|
-
_variables;
|
|
25447
|
-
constructor(config) {
|
|
25448
|
-
this._importPaths = config?.importPaths ? [...config.importPaths] : [];
|
|
25449
|
-
this._variables = config?.variables ? { ...config.variables } : {};
|
|
25450
|
-
}
|
|
25451
|
-
/** Compile an SCSS string to CSS. */
|
|
25452
|
-
compile(source) {
|
|
25453
|
-
return compileString(source, this._importPaths, { ...this._variables });
|
|
25454
|
-
}
|
|
25455
|
-
/** Compile an SCSS file to CSS. */
|
|
25456
|
-
compileFile(filePath) {
|
|
25457
|
-
const absPath = resolve16(filePath);
|
|
25458
|
-
const content = readFileSync21(absPath, "utf-8");
|
|
25459
|
-
const paths = [dirname10(absPath), ...this._importPaths];
|
|
25460
|
-
return compileString(content, paths, { ...this._variables });
|
|
25461
|
-
}
|
|
25462
|
-
/** Add a directory to the import resolution path. */
|
|
25463
|
-
addImportPath(path8) {
|
|
25464
|
-
this._importPaths.push(resolve16(path8));
|
|
25465
|
-
}
|
|
25466
|
-
/** Set or override an SCSS variable. */
|
|
25467
|
-
setVariable(name, value) {
|
|
25468
|
-
const key = name.startsWith("$") ? name.slice(1) : name;
|
|
25469
|
-
this._variables[key] = value;
|
|
25470
|
-
}
|
|
25471
|
-
/** Compile all .scss files in a directory into a single CSS output file. */
|
|
25472
|
-
compileScss(scssDir = "src/scss", output = "src/public/css/default.css", minify = false) {
|
|
25473
|
-
const absDir = resolve16(scssDir);
|
|
25474
|
-
if (!existsSync23(absDir)) return "";
|
|
25475
|
-
const files = readdirSync16(absDir).filter((f) => f.endsWith(".scss") && !f.startsWith("_")).sort().map((f) => join23(absDir, f));
|
|
25476
|
-
if (files.length === 0) return "";
|
|
25477
|
-
const paths = [absDir, ...this._importPaths];
|
|
25478
|
-
const imported = /* @__PURE__ */ new Set();
|
|
25479
|
-
let merged = "";
|
|
25480
|
-
for (const file of files) {
|
|
25481
|
-
const content = readFileSync21(file, "utf-8");
|
|
25482
|
-
imported.add(file);
|
|
25483
|
-
merged += resolveImports(content, paths, imported) + "\n";
|
|
25484
|
-
}
|
|
25485
|
-
let css = compileString(merged, paths, { ...this._variables });
|
|
25486
|
-
if (minify) {
|
|
25487
|
-
css = css.replace(/\/\*.*?\*\//gs, "");
|
|
25488
|
-
css = css.replace(/\s+/g, " ");
|
|
25489
|
-
css = css.replace(/\s*([{}:;,])\s*/g, "$1");
|
|
25490
|
-
css = css.replace(/;}/g, "}");
|
|
25491
|
-
css = css.trim();
|
|
25492
|
-
}
|
|
25493
|
-
const absOutput = resolve16(output);
|
|
25494
|
-
const outDir = dirname10(absOutput);
|
|
25495
|
-
if (!existsSync23(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
25496
|
-
let existing = null;
|
|
25497
|
-
try {
|
|
25498
|
-
existing = existsSync23(absOutput) ? readFileSync21(absOutput, "utf-8") : null;
|
|
25499
|
-
} catch {
|
|
25500
|
-
existing = null;
|
|
25501
|
-
}
|
|
25502
|
-
if (existing !== css) {
|
|
25503
|
-
writeFileSync14(absOutput, css, "utf-8");
|
|
25504
|
-
}
|
|
25505
|
-
return css;
|
|
25506
|
-
}
|
|
25507
|
-
};
|
|
25508
|
-
VARIABLE_FLAG = /\s*!(default|global)\s*$/;
|
|
25509
|
-
}
|
|
25510
|
-
});
|
|
25511
|
-
|
|
25512
25134
|
// ../core/src/mqttMessage.ts
|
|
25513
25135
|
var MqttMessage;
|
|
25514
25136
|
var init_mqttMessage = __esm({
|
|
@@ -25582,7 +25204,7 @@ var init_mqttMessage = __esm({
|
|
|
25582
25204
|
import net2 from "node:net";
|
|
25583
25205
|
import tls from "node:tls";
|
|
25584
25206
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
25585
|
-
import { existsSync as
|
|
25207
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "node:fs";
|
|
25586
25208
|
var MqttError, MqttTimeoutError, CONNECT, CONNACK, PUBLISH, PUBACK, SUBSCRIBE, SUBACK, PINGREQ, PINGRESP, DISCONNECT, PROTOCOL_LEVEL, DEFAULT_PORT, DEFAULT_TLS_PORT, DEFAULT_URL, DEFAULT_KEEPALIVE, SUBSCRIPTION_REFUSED, MAX_REMAINING_LENGTH, QOS2_REFUSED_MESSAGE, CONNACK_RETURN_CODES, Mqtt;
|
|
25587
25209
|
var init_mqtt = __esm({
|
|
25588
25210
|
"../core/src/mqtt.ts"() {
|
|
@@ -25785,7 +25407,7 @@ var init_mqtt = __esm({
|
|
|
25785
25407
|
*/
|
|
25786
25408
|
async connect() {
|
|
25787
25409
|
this.closeSocket();
|
|
25788
|
-
if (this.secure && this.tlsVerify && this.caFile && !
|
|
25410
|
+
if (this.secure && this.tlsVerify && this.caFile && !existsSync23(this.caFile)) {
|
|
25789
25411
|
throw new MqttError(
|
|
25790
25412
|
`MQTT CA file not found: ${this.caFile} -- TINA4_MQTT_CA_FILE (or caFile) must point at the broker's CA certificate in PEM form`
|
|
25791
25413
|
);
|
|
@@ -26022,7 +25644,7 @@ var init_mqtt = __esm({
|
|
|
26022
25644
|
* a later client.
|
|
26023
25645
|
*/
|
|
26024
25646
|
openSocket() {
|
|
26025
|
-
return new Promise((
|
|
25647
|
+
return new Promise((resolve20, reject) => {
|
|
26026
25648
|
let settled = false;
|
|
26027
25649
|
const settle = (fn) => {
|
|
26028
25650
|
if (settled) return;
|
|
@@ -26047,10 +25669,10 @@ var init_mqtt = __esm({
|
|
|
26047
25669
|
servername: this.host,
|
|
26048
25670
|
rejectUnauthorized: this.tlsVerify
|
|
26049
25671
|
};
|
|
26050
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
26051
|
-
sock = tls.connect(opts, () => settle(() =>
|
|
25672
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync21(this.caFile);
|
|
25673
|
+
sock = tls.connect(opts, () => settle(() => resolve20(sock)));
|
|
26052
25674
|
} else {
|
|
26053
|
-
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() =>
|
|
25675
|
+
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve20(sock)));
|
|
26054
25676
|
}
|
|
26055
25677
|
sock.once("error", (err) => {
|
|
26056
25678
|
settle(() => {
|
|
@@ -26089,13 +25711,13 @@ var init_mqtt = __esm({
|
|
|
26089
25711
|
writePacket(header, body) {
|
|
26090
25712
|
if (this.socket === null) return Promise.reject(new MqttError("not connected to an MQTT broker"));
|
|
26091
25713
|
const packet = Buffer.concat([Buffer.from([header]), _Mqtt.encodeRemainingLength(body.length), body]);
|
|
26092
|
-
return new Promise((
|
|
25714
|
+
return new Promise((resolve20, reject) => {
|
|
26093
25715
|
this.socket.write(packet, (err) => {
|
|
26094
25716
|
if (err) {
|
|
26095
25717
|
reject(new MqttError(`MQTT write failed: ${err.message}`));
|
|
26096
25718
|
} else {
|
|
26097
25719
|
this.lastWriteAt = Date.now();
|
|
26098
|
-
|
|
25720
|
+
resolve20();
|
|
26099
25721
|
}
|
|
26100
25722
|
});
|
|
26101
25723
|
});
|
|
@@ -26128,7 +25750,7 @@ var init_mqtt = __esm({
|
|
|
26128
25750
|
if (this.readBuffer.length >= need) return Promise.resolve(this.take(need));
|
|
26129
25751
|
if (this.socket === null) return Promise.reject(this.socketError ?? new MqttError("not connected to an MQTT broker"));
|
|
26130
25752
|
if (this.socketError !== null) return Promise.reject(this.socketError);
|
|
26131
|
-
return new Promise((
|
|
25753
|
+
return new Promise((resolve20, reject) => {
|
|
26132
25754
|
let timer = null;
|
|
26133
25755
|
if (deadline !== null) {
|
|
26134
25756
|
const remaining = deadline - Date.now();
|
|
@@ -26143,7 +25765,7 @@ var init_mqtt = __esm({
|
|
|
26143
25765
|
}
|
|
26144
25766
|
}, remaining);
|
|
26145
25767
|
}
|
|
26146
|
-
this.waiter = { need, resolve:
|
|
25768
|
+
this.waiter = { need, resolve: resolve20, reject, timer };
|
|
26147
25769
|
this.serviceWaiter();
|
|
26148
25770
|
});
|
|
26149
25771
|
}
|
|
@@ -26267,8 +25889,8 @@ var init_mqtt = __esm({
|
|
|
26267
25889
|
});
|
|
26268
25890
|
|
|
26269
25891
|
// ../core/src/service.ts
|
|
26270
|
-
import { readdirSync as
|
|
26271
|
-
import { join as
|
|
25892
|
+
import { readdirSync as readdirSync16, statSync as statSync16, watchFile, unwatchFile } from "node:fs";
|
|
25893
|
+
import { join as join23, extname as extname7 } from "node:path";
|
|
26272
25894
|
import { pathToFileURL } from "node:url";
|
|
26273
25895
|
function matchCronField(field, value) {
|
|
26274
25896
|
if (field === "*") return true;
|
|
@@ -26434,14 +26056,14 @@ var init_service = __esm({
|
|
|
26434
26056
|
const discovered = [];
|
|
26435
26057
|
let entries;
|
|
26436
26058
|
try {
|
|
26437
|
-
entries =
|
|
26059
|
+
entries = readdirSync16(dir);
|
|
26438
26060
|
} catch {
|
|
26439
26061
|
return discovered;
|
|
26440
26062
|
}
|
|
26441
26063
|
for (const entry of entries) {
|
|
26442
26064
|
const ext = extname7(entry);
|
|
26443
26065
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
26444
|
-
const fullPath =
|
|
26066
|
+
const fullPath = join23(dir, entry);
|
|
26445
26067
|
const stat = statSync16(fullPath);
|
|
26446
26068
|
if (!stat.isFile()) continue;
|
|
26447
26069
|
try {
|
|
@@ -26550,14 +26172,14 @@ var init_service = __esm({
|
|
|
26550
26172
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
26551
26173
|
let entries;
|
|
26552
26174
|
try {
|
|
26553
|
-
entries =
|
|
26175
|
+
entries = readdirSync16(dir);
|
|
26554
26176
|
} catch {
|
|
26555
26177
|
return;
|
|
26556
26178
|
}
|
|
26557
26179
|
for (const entry of entries) {
|
|
26558
26180
|
const ext = extname7(entry);
|
|
26559
26181
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
26560
|
-
const fullPath =
|
|
26182
|
+
const fullPath = join23(dir, entry);
|
|
26561
26183
|
if (watchedFiles.has(fullPath)) continue;
|
|
26562
26184
|
watchedFiles.add(fullPath);
|
|
26563
26185
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -26601,7 +26223,7 @@ import https from "node:https";
|
|
|
26601
26223
|
import { URL as URL2 } from "node:url";
|
|
26602
26224
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
26603
26225
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
26604
|
-
import { basename as
|
|
26226
|
+
import { basename as basename4 } from "node:path";
|
|
26605
26227
|
import { pipeline } from "node:stream/promises";
|
|
26606
26228
|
function sameOrigin(urlA, urlB) {
|
|
26607
26229
|
try {
|
|
@@ -26891,7 +26513,7 @@ var init_api = __esm({
|
|
|
26891
26513
|
error: err instanceof Error ? err.message : String(err)
|
|
26892
26514
|
};
|
|
26893
26515
|
}
|
|
26894
|
-
uploadName = filename ||
|
|
26516
|
+
uploadName = filename || basename4(filePath);
|
|
26895
26517
|
} else {
|
|
26896
26518
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
26897
26519
|
}
|
|
@@ -27106,12 +26728,12 @@ var init_api = __esm({
|
|
|
27106
26728
|
* authenticate to.
|
|
27107
26729
|
*/
|
|
27108
26730
|
performRequest(method, url, headers, data, redirectsLeft) {
|
|
27109
|
-
return new Promise((
|
|
26731
|
+
return new Promise((resolve20) => {
|
|
27110
26732
|
let parsed;
|
|
27111
26733
|
try {
|
|
27112
26734
|
parsed = new URL2(url);
|
|
27113
26735
|
} catch (err) {
|
|
27114
|
-
|
|
26736
|
+
resolve20({ kind: "error", error: err instanceof Error ? err.message : String(err) });
|
|
27115
26737
|
return;
|
|
27116
26738
|
}
|
|
27117
26739
|
const isHttps = parsed.protocol === "https:";
|
|
@@ -27136,7 +26758,7 @@ var init_api = __esm({
|
|
|
27136
26758
|
try {
|
|
27137
26759
|
nextUrl = new URL2(location, url).toString();
|
|
27138
26760
|
} catch {
|
|
27139
|
-
|
|
26761
|
+
resolve20({ kind: "response", res });
|
|
27140
26762
|
return;
|
|
27141
26763
|
}
|
|
27142
26764
|
const crossOrigin = !sameOrigin(url, nextUrl);
|
|
@@ -27154,17 +26776,17 @@ var init_api = __esm({
|
|
|
27154
26776
|
deleteHeaderCaseInsensitive(nextHeaders, name);
|
|
27155
26777
|
}
|
|
27156
26778
|
}
|
|
27157
|
-
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(
|
|
26779
|
+
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(resolve20);
|
|
27158
26780
|
return;
|
|
27159
26781
|
}
|
|
27160
|
-
|
|
26782
|
+
resolve20({ kind: "response", res });
|
|
27161
26783
|
});
|
|
27162
26784
|
req2.on("timeout", () => {
|
|
27163
26785
|
req2.destroy();
|
|
27164
|
-
|
|
26786
|
+
resolve20({ kind: "error", error: `Request timed out after ${this.timeout}s` });
|
|
27165
26787
|
});
|
|
27166
26788
|
req2.on("error", (err) => {
|
|
27167
|
-
|
|
26789
|
+
resolve20({ kind: "error", error: err.message });
|
|
27168
26790
|
});
|
|
27169
26791
|
if (data) {
|
|
27170
26792
|
req2.write(data);
|
|
@@ -27174,7 +26796,7 @@ var init_api = __esm({
|
|
|
27174
26796
|
}
|
|
27175
26797
|
/** Buffer a response body, parse JSON if possible, and store cookies. */
|
|
27176
26798
|
readResponse(res) {
|
|
27177
|
-
return new Promise((
|
|
26799
|
+
return new Promise((resolve20) => {
|
|
27178
26800
|
const chunks = [];
|
|
27179
26801
|
res.on("data", (chunk) => {
|
|
27180
26802
|
chunks.push(chunk);
|
|
@@ -27189,7 +26811,7 @@ var init_api = __esm({
|
|
|
27189
26811
|
} catch {
|
|
27190
26812
|
parsed = raw;
|
|
27191
26813
|
}
|
|
27192
|
-
|
|
26814
|
+
resolve20({
|
|
27193
26815
|
http_code: res.statusCode ?? null,
|
|
27194
26816
|
body: parsed,
|
|
27195
26817
|
headers: respHeaders,
|
|
@@ -27197,7 +26819,7 @@ var init_api = __esm({
|
|
|
27197
26819
|
});
|
|
27198
26820
|
});
|
|
27199
26821
|
res.on("error", (err) => {
|
|
27200
|
-
|
|
26822
|
+
resolve20({ http_code: null, body: null, headers: {}, error: err.message });
|
|
27201
26823
|
});
|
|
27202
26824
|
});
|
|
27203
26825
|
}
|
|
@@ -27250,14 +26872,14 @@ var init_api = __esm({
|
|
|
27250
26872
|
// ../core/src/messenger.ts
|
|
27251
26873
|
import net3 from "node:net";
|
|
27252
26874
|
import tls2 from "node:tls";
|
|
27253
|
-
import { readFileSync as
|
|
27254
|
-
import { basename as
|
|
26875
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
26876
|
+
import { basename as basename5 } from "node:path";
|
|
27255
26877
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
27256
26878
|
function tlsRejectUnauthorized() {
|
|
27257
26879
|
return !isTruthy(process.env.TINA4_MAIL_TLS_INSECURE);
|
|
27258
26880
|
}
|
|
27259
26881
|
function readResponse(socket) {
|
|
27260
|
-
return new Promise((
|
|
26882
|
+
return new Promise((resolve20, reject) => {
|
|
27261
26883
|
let buffer = "";
|
|
27262
26884
|
const onData = (chunk) => {
|
|
27263
26885
|
buffer += chunk.toString("utf-8");
|
|
@@ -27269,7 +26891,7 @@ function readResponse(socket) {
|
|
|
27269
26891
|
if (line.length >= 4 && line[3] === " ") {
|
|
27270
26892
|
socket.removeListener("data", onData);
|
|
27271
26893
|
socket.removeListener("error", onError);
|
|
27272
|
-
|
|
26894
|
+
resolve20({ code, text: buffer.trim() });
|
|
27273
26895
|
return;
|
|
27274
26896
|
}
|
|
27275
26897
|
}
|
|
@@ -27283,10 +26905,10 @@ function readResponse(socket) {
|
|
|
27283
26905
|
});
|
|
27284
26906
|
}
|
|
27285
26907
|
function sendCommand(socket, command) {
|
|
27286
|
-
return new Promise((
|
|
26908
|
+
return new Promise((resolve20, reject) => {
|
|
27287
26909
|
socket.write(command + "\r\n", "utf-8", (err) => {
|
|
27288
26910
|
if (err) return reject(err);
|
|
27289
|
-
readResponse(socket).then(
|
|
26911
|
+
readResponse(socket).then(resolve20, reject);
|
|
27290
26912
|
});
|
|
27291
26913
|
});
|
|
27292
26914
|
}
|
|
@@ -27342,8 +26964,8 @@ function buildMimeMessage(options) {
|
|
|
27342
26964
|
lines.push(options.body);
|
|
27343
26965
|
}
|
|
27344
26966
|
for (const filePath of options.attachments) {
|
|
27345
|
-
const fileName =
|
|
27346
|
-
const fileData =
|
|
26967
|
+
const fileName = basename5(filePath);
|
|
26968
|
+
const fileData = readFileSync22(filePath);
|
|
27347
26969
|
const base64Data = fileData.toString("base64");
|
|
27348
26970
|
lines.push("");
|
|
27349
26971
|
lines.push(`--${boundary}`);
|
|
@@ -27386,7 +27008,7 @@ function imapQuote(s) {
|
|
|
27386
27008
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
27387
27009
|
}
|
|
27388
27010
|
function imapReadLine(socket) {
|
|
27389
|
-
return new Promise((
|
|
27011
|
+
return new Promise((resolve20, reject) => {
|
|
27390
27012
|
let buffer = "";
|
|
27391
27013
|
const onData = (chunk) => {
|
|
27392
27014
|
buffer += chunk.toString("utf-8");
|
|
@@ -27394,7 +27016,7 @@ function imapReadLine(socket) {
|
|
|
27394
27016
|
if (nlIndex !== -1) {
|
|
27395
27017
|
socket.removeListener("data", onData);
|
|
27396
27018
|
socket.removeListener("error", onError);
|
|
27397
|
-
|
|
27019
|
+
resolve20(buffer);
|
|
27398
27020
|
}
|
|
27399
27021
|
};
|
|
27400
27022
|
const onError = (err) => {
|
|
@@ -27406,7 +27028,7 @@ function imapReadLine(socket) {
|
|
|
27406
27028
|
});
|
|
27407
27029
|
}
|
|
27408
27030
|
function imapCommand(socket, command) {
|
|
27409
|
-
return new Promise((
|
|
27031
|
+
return new Promise((resolve20, reject) => {
|
|
27410
27032
|
imapTagCounter++;
|
|
27411
27033
|
const tag = `T${imapTagCounter}`;
|
|
27412
27034
|
const fullCommand = `${tag} ${command}\r
|
|
@@ -27417,7 +27039,7 @@ function imapCommand(socket, command) {
|
|
|
27417
27039
|
if (buffer.includes(`${tag} OK`)) {
|
|
27418
27040
|
socket.removeListener("data", onData);
|
|
27419
27041
|
socket.removeListener("error", onError);
|
|
27420
|
-
|
|
27042
|
+
resolve20(buffer);
|
|
27421
27043
|
return;
|
|
27422
27044
|
}
|
|
27423
27045
|
if (buffer.includes(`${tag} NO`) || buffer.includes(`${tag} BAD`)) {
|
|
@@ -27446,91 +27068,149 @@ function parseSearchResponse(response) {
|
|
|
27446
27068
|
if (!match) return [];
|
|
27447
27069
|
return match[1].trim().split(/\s+/).filter((s) => /^\d+$/.test(s));
|
|
27448
27070
|
}
|
|
27449
|
-
function
|
|
27450
|
-
const
|
|
27451
|
-
|
|
27452
|
-
|
|
27453
|
-
|
|
27454
|
-
let currentKey = "";
|
|
27455
|
-
for (const line of lines) {
|
|
27456
|
-
if (/^\s/.test(line) && currentKey) {
|
|
27457
|
-
headers[currentKey] += " " + line.trim();
|
|
27458
|
-
} else {
|
|
27459
|
-
const colonIdx = line.indexOf(":");
|
|
27460
|
-
if (colonIdx > 0) {
|
|
27461
|
-
currentKey = line.substring(0, colonIdx).trim().toLowerCase();
|
|
27462
|
-
headers[currentKey] = line.substring(colonIdx + 1).trim();
|
|
27463
|
-
}
|
|
27464
|
-
}
|
|
27465
|
-
}
|
|
27466
|
-
}
|
|
27467
|
-
const seen = /\\Seen/i.test(response);
|
|
27468
|
-
return {
|
|
27469
|
-
uid,
|
|
27470
|
-
subject: headers["subject"] ?? "",
|
|
27471
|
-
from: headers["from"] ?? "",
|
|
27472
|
-
to: headers["to"] ?? "",
|
|
27473
|
-
date: headers["date"] ?? "",
|
|
27474
|
-
snippet: "",
|
|
27475
|
-
seen
|
|
27476
|
-
};
|
|
27071
|
+
function extractRawMessage(response) {
|
|
27072
|
+
const m = response.match(/\{(\d+)\}\r\n/);
|
|
27073
|
+
if (!m) return response;
|
|
27074
|
+
const start2 = (m.index ?? 0) + m[0].length;
|
|
27075
|
+
return response.slice(start2, start2 + parseInt(m[1], 10));
|
|
27477
27076
|
}
|
|
27478
|
-
function
|
|
27479
|
-
const bodyMatch = response.match(/\{(\d+)\}\r\n([\s\S]*)/);
|
|
27480
|
-
const rawMessage = bodyMatch ? bodyMatch[2] : response;
|
|
27481
|
-
const headerEnd = rawMessage.indexOf("\r\n\r\n");
|
|
27482
|
-
const headerSection = headerEnd > 0 ? rawMessage.substring(0, headerEnd) : rawMessage;
|
|
27483
|
-
const bodySection = headerEnd > 0 ? rawMessage.substring(headerEnd + 4) : "";
|
|
27077
|
+
function parseMimeHeaders(section) {
|
|
27484
27078
|
const headers = {};
|
|
27485
|
-
const headerLines = headerSection.split(/\r\n/);
|
|
27486
27079
|
let currentKey = "";
|
|
27487
|
-
for (const line of
|
|
27080
|
+
for (const line of section.split(/\r\n/)) {
|
|
27488
27081
|
if (/^\s/.test(line) && currentKey) {
|
|
27489
27082
|
headers[currentKey] += " " + line.trim();
|
|
27490
27083
|
} else {
|
|
27491
|
-
const
|
|
27492
|
-
if (
|
|
27493
|
-
currentKey = line.substring(0,
|
|
27494
|
-
headers[currentKey] = line.substring(
|
|
27084
|
+
const idx = line.indexOf(":");
|
|
27085
|
+
if (idx > 0) {
|
|
27086
|
+
currentKey = line.substring(0, idx).trim().toLowerCase();
|
|
27087
|
+
headers[currentKey] = line.substring(idx + 1).trim();
|
|
27088
|
+
}
|
|
27089
|
+
}
|
|
27090
|
+
}
|
|
27091
|
+
return headers;
|
|
27092
|
+
}
|
|
27093
|
+
function decodeTransfer(body, encoding) {
|
|
27094
|
+
const enc = encoding.toLowerCase().trim();
|
|
27095
|
+
if (enc === "base64") {
|
|
27096
|
+
try {
|
|
27097
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
27098
|
+
} catch {
|
|
27099
|
+
return body;
|
|
27100
|
+
}
|
|
27101
|
+
}
|
|
27102
|
+
if (enc === "quoted-printable") {
|
|
27103
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
27104
|
+
}
|
|
27105
|
+
return body;
|
|
27106
|
+
}
|
|
27107
|
+
function decodeAttachmentBytes(body, encoding) {
|
|
27108
|
+
const enc = encoding.toLowerCase().trim();
|
|
27109
|
+
if (enc === "base64") {
|
|
27110
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
27111
|
+
}
|
|
27112
|
+
const trimmed = body.replace(/\r\n$/, "");
|
|
27113
|
+
if (enc === "quoted-printable") {
|
|
27114
|
+
const collapsed = trimmed.replace(/=\r?\n/g, "");
|
|
27115
|
+
const bytes = [];
|
|
27116
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
27117
|
+
const hex = collapsed.substring(i + 1, i + 3);
|
|
27118
|
+
if (collapsed[i] === "=" && /^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
27119
|
+
bytes.push(parseInt(hex, 16));
|
|
27120
|
+
i += 2;
|
|
27121
|
+
} else {
|
|
27122
|
+
bytes.push(collapsed.charCodeAt(i) & 255);
|
|
27495
27123
|
}
|
|
27496
27124
|
}
|
|
27125
|
+
return Buffer.from(bytes);
|
|
27497
27126
|
}
|
|
27127
|
+
return Buffer.from(trimmed, "utf-8");
|
|
27128
|
+
}
|
|
27129
|
+
function attachmentFilename(disposition, contentType) {
|
|
27130
|
+
const d = disposition.match(/filename="?([^";\r\n]+)"?/i);
|
|
27131
|
+
if (d) return d[1].trim();
|
|
27132
|
+
const c = contentType.match(/name="?([^";\r\n]+)"?/i);
|
|
27133
|
+
if (c) return c[1].trim();
|
|
27134
|
+
return "attachment";
|
|
27135
|
+
}
|
|
27136
|
+
function makeSnippet(bodyText, bodyHtml) {
|
|
27137
|
+
return (bodyText || bodyHtml || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
27138
|
+
}
|
|
27139
|
+
function toIsoDate(raw) {
|
|
27140
|
+
if (!raw) return "";
|
|
27141
|
+
const d = new Date(raw);
|
|
27142
|
+
return Number.isNaN(d.getTime()) ? raw : d.toISOString();
|
|
27143
|
+
}
|
|
27144
|
+
function parseMessage(response) {
|
|
27145
|
+
const raw = extractRawMessage(response);
|
|
27146
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
27147
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
27148
|
+
const bodySection = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
27149
|
+
const headers = parseMimeHeaders(headerSection);
|
|
27498
27150
|
const contentType = headers["content-type"] ?? "text/plain";
|
|
27499
27151
|
let bodyText = "";
|
|
27500
27152
|
let bodyHtml = "";
|
|
27153
|
+
const attachments = [];
|
|
27501
27154
|
if (contentType.includes("multipart")) {
|
|
27502
27155
|
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
|
|
27503
27156
|
if (boundaryMatch) {
|
|
27504
|
-
const boundary = boundaryMatch[1];
|
|
27505
|
-
const
|
|
27506
|
-
|
|
27507
|
-
if (
|
|
27508
|
-
const
|
|
27509
|
-
|
|
27510
|
-
const
|
|
27511
|
-
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27157
|
+
const boundary = "--" + boundaryMatch[1];
|
|
27158
|
+
for (const part of bodySection.split(boundary)) {
|
|
27159
|
+
const trimmed = part.trim();
|
|
27160
|
+
if (trimmed === "" || trimmed === "--") continue;
|
|
27161
|
+
const pEnd = part.indexOf("\r\n\r\n");
|
|
27162
|
+
if (pEnd < 0) continue;
|
|
27163
|
+
const pHeaders = parseMimeHeaders(part.substring(0, pEnd));
|
|
27164
|
+
const pBody = part.substring(pEnd + 4);
|
|
27165
|
+
const cte = pHeaders["content-transfer-encoding"] ?? "";
|
|
27166
|
+
const pType = pHeaders["content-type"] ?? "text/plain";
|
|
27167
|
+
const disposition = pHeaders["content-disposition"] ?? "";
|
|
27168
|
+
if (/attachment/i.test(disposition)) {
|
|
27169
|
+
const content = decodeAttachmentBytes(pBody, cte);
|
|
27170
|
+
attachments.push({
|
|
27171
|
+
filename: attachmentFilename(disposition, pType),
|
|
27172
|
+
contentType: pType.split(";")[0].trim(),
|
|
27173
|
+
size: content.length,
|
|
27174
|
+
content
|
|
27175
|
+
});
|
|
27176
|
+
} else if (pType.includes("text/html")) {
|
|
27177
|
+
bodyHtml = decodeTransfer(pBody, cte).trim();
|
|
27178
|
+
} else if (pType.includes("text/plain")) {
|
|
27179
|
+
bodyText = decodeTransfer(pBody, cte).trim();
|
|
27515
27180
|
}
|
|
27516
27181
|
}
|
|
27517
27182
|
}
|
|
27518
27183
|
} else if (contentType.includes("text/html")) {
|
|
27519
|
-
bodyHtml = bodySection;
|
|
27184
|
+
bodyHtml = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
27520
27185
|
} else {
|
|
27521
|
-
bodyText = bodySection;
|
|
27186
|
+
bodyText = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
27522
27187
|
}
|
|
27523
|
-
|
|
27524
|
-
|
|
27188
|
+
return { headers, bodyText, bodyHtml, attachments };
|
|
27189
|
+
}
|
|
27190
|
+
function parseSummary(uid, response) {
|
|
27191
|
+
const { headers, bodyText, bodyHtml } = parseMessage(response);
|
|
27192
|
+
return {
|
|
27193
|
+
uid,
|
|
27194
|
+
subject: headers["subject"] ?? "",
|
|
27195
|
+
from: headers["from"] ?? "",
|
|
27196
|
+
to: headers["to"] ?? "",
|
|
27197
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
27198
|
+
snippet: makeSnippet(bodyText, bodyHtml),
|
|
27199
|
+
seen: /\\Seen/i.test(response)
|
|
27200
|
+
};
|
|
27201
|
+
}
|
|
27202
|
+
function parseFullMessage(uid, response) {
|
|
27203
|
+
const { headers, bodyText, bodyHtml, attachments } = parseMessage(response);
|
|
27525
27204
|
return {
|
|
27526
27205
|
uid,
|
|
27527
27206
|
subject: headers["subject"] ?? "",
|
|
27528
27207
|
from: headers["from"] ?? "",
|
|
27529
27208
|
to: headers["to"] ?? "",
|
|
27530
27209
|
cc: headers["cc"] ?? "",
|
|
27531
|
-
date: headers["date"] ?? "",
|
|
27210
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
27532
27211
|
bodyText,
|
|
27533
27212
|
bodyHtml,
|
|
27213
|
+
attachments,
|
|
27534
27214
|
headers
|
|
27535
27215
|
};
|
|
27536
27216
|
}
|
|
@@ -27646,89 +27326,89 @@ var init_messenger = __esm({
|
|
|
27646
27326
|
}
|
|
27647
27327
|
const messageId = `${randomUUID7()}@${this.host}`;
|
|
27648
27328
|
if (allRecipients.length === 0) {
|
|
27649
|
-
return { success: false, message: "No recipients specified" };
|
|
27329
|
+
return { success: false, message: "No recipients specified", id: null };
|
|
27650
27330
|
}
|
|
27651
27331
|
if (!this.fromAddress) {
|
|
27652
|
-
return { success: false, message: "No from address configured" };
|
|
27332
|
+
return { success: false, message: "No from address configured", id: null };
|
|
27653
27333
|
}
|
|
27654
27334
|
try {
|
|
27655
27335
|
let socket;
|
|
27656
27336
|
if (this.port === 465) {
|
|
27657
27337
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
27658
|
-
await new Promise((
|
|
27659
|
-
socket.once("secureConnect",
|
|
27338
|
+
await new Promise((resolve20, reject) => {
|
|
27339
|
+
socket.once("secureConnect", resolve20);
|
|
27660
27340
|
socket.once("error", reject);
|
|
27661
27341
|
});
|
|
27662
27342
|
} else {
|
|
27663
27343
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
27664
|
-
await new Promise((
|
|
27665
|
-
socket.once("connect",
|
|
27344
|
+
await new Promise((resolve20, reject) => {
|
|
27345
|
+
socket.once("connect", resolve20);
|
|
27666
27346
|
socket.once("error", reject);
|
|
27667
27347
|
});
|
|
27668
27348
|
}
|
|
27669
27349
|
const greeting = await readResponse(socket);
|
|
27670
27350
|
if (greeting.code !== 220) {
|
|
27671
27351
|
socket.destroy();
|
|
27672
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}
|
|
27352
|
+
return { success: false, message: `SMTP greeting failed: ${greeting.text}`, id: null };
|
|
27673
27353
|
}
|
|
27674
27354
|
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
27675
27355
|
if (ehlo.code !== 250) {
|
|
27676
27356
|
socket.destroy();
|
|
27677
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}
|
|
27357
|
+
return { success: false, message: `EHLO failed: ${ehlo.text}`, id: null };
|
|
27678
27358
|
}
|
|
27679
27359
|
if (this.useTls && this.port !== 465 && ehlo.text.includes("STARTTLS")) {
|
|
27680
27360
|
const starttls = await sendCommand(socket, "STARTTLS");
|
|
27681
27361
|
if (starttls.code !== 220) {
|
|
27682
27362
|
socket.destroy();
|
|
27683
|
-
return { success: false, message: `STARTTLS failed: ${starttls.text}
|
|
27363
|
+
return { success: false, message: `STARTTLS failed: ${starttls.text}`, id: null };
|
|
27684
27364
|
}
|
|
27685
27365
|
const plainSocket = socket;
|
|
27686
27366
|
socket = tls2.connect(
|
|
27687
27367
|
{ socket: plainSocket, host: this.host, rejectUnauthorized: tlsRejectUnauthorized() }
|
|
27688
27368
|
);
|
|
27689
|
-
await new Promise((
|
|
27690
|
-
socket.once("secureConnect",
|
|
27369
|
+
await new Promise((resolve20, reject) => {
|
|
27370
|
+
socket.once("secureConnect", resolve20);
|
|
27691
27371
|
socket.once("error", reject);
|
|
27692
27372
|
});
|
|
27693
27373
|
const ehlo2 = await sendCommand(socket, `EHLO ${this.host}`);
|
|
27694
27374
|
if (ehlo2.code !== 250) {
|
|
27695
27375
|
socket.destroy();
|
|
27696
|
-
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}
|
|
27376
|
+
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}`, id: null };
|
|
27697
27377
|
}
|
|
27698
27378
|
}
|
|
27699
27379
|
if (this.username && this.password) {
|
|
27700
27380
|
const auth = await sendCommand(socket, "AUTH LOGIN");
|
|
27701
27381
|
if (auth.code !== 334) {
|
|
27702
27382
|
socket.destroy();
|
|
27703
|
-
return { success: false, message: `AUTH LOGIN failed: ${auth.text}
|
|
27383
|
+
return { success: false, message: `AUTH LOGIN failed: ${auth.text}`, id: null };
|
|
27704
27384
|
}
|
|
27705
27385
|
const userResp = await sendCommand(socket, Buffer.from(this.username).toString("base64"));
|
|
27706
27386
|
if (userResp.code !== 334) {
|
|
27707
27387
|
socket.destroy();
|
|
27708
|
-
return { success: false, message: `AUTH username failed: ${userResp.text}
|
|
27388
|
+
return { success: false, message: `AUTH username failed: ${userResp.text}`, id: null };
|
|
27709
27389
|
}
|
|
27710
27390
|
const passResp = await sendCommand(socket, Buffer.from(this.password).toString("base64"));
|
|
27711
27391
|
if (passResp.code !== 235) {
|
|
27712
27392
|
socket.destroy();
|
|
27713
|
-
return { success: false, message: `AUTH password failed: ${passResp.text}
|
|
27393
|
+
return { success: false, message: `AUTH password failed: ${passResp.text}`, id: null };
|
|
27714
27394
|
}
|
|
27715
27395
|
}
|
|
27716
27396
|
const mailFrom = await sendCommand(socket, `MAIL FROM:<${this.fromAddress}>`);
|
|
27717
27397
|
if (mailFrom.code !== 250) {
|
|
27718
27398
|
socket.destroy();
|
|
27719
|
-
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}
|
|
27399
|
+
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}`, id: null };
|
|
27720
27400
|
}
|
|
27721
27401
|
for (const recipient of allRecipients) {
|
|
27722
27402
|
const rcpt = await sendCommand(socket, `RCPT TO:<${recipient}>`);
|
|
27723
27403
|
if (rcpt.code !== 250 && rcpt.code !== 251) {
|
|
27724
27404
|
socket.destroy();
|
|
27725
|
-
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}
|
|
27405
|
+
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}`, id: null };
|
|
27726
27406
|
}
|
|
27727
27407
|
}
|
|
27728
27408
|
const dataCmd = await sendCommand(socket, "DATA");
|
|
27729
27409
|
if (dataCmd.code !== 354) {
|
|
27730
27410
|
socket.destroy();
|
|
27731
|
-
return { success: false, message: `DATA failed: ${dataCmd.text}
|
|
27411
|
+
return { success: false, message: `DATA failed: ${dataCmd.text}`, id: null };
|
|
27732
27412
|
}
|
|
27733
27413
|
const mimeMessage = buildMimeMessage({
|
|
27734
27414
|
from: this.fromAddress,
|
|
@@ -27747,15 +27427,30 @@ var init_messenger = __esm({
|
|
|
27747
27427
|
const endData = await sendCommand(socket, mimeMessage + "\r\n.");
|
|
27748
27428
|
if (endData.code !== 250) {
|
|
27749
27429
|
socket.destroy();
|
|
27750
|
-
return { success: false, message: `Message delivery failed: ${endData.text}
|
|
27430
|
+
return { success: false, message: `Message delivery failed: ${endData.text}`, id: null };
|
|
27751
27431
|
}
|
|
27752
27432
|
await sendCommand(socket, "QUIT");
|
|
27753
27433
|
socket.destroy();
|
|
27754
27434
|
return { success: true, message: "Email sent successfully", id: messageId };
|
|
27755
27435
|
} catch (err) {
|
|
27756
27436
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
27757
|
-
return { success: false, message: `SMTP error: ${errMsg}
|
|
27437
|
+
return { success: false, message: `SMTP error: ${errMsg}`, id: null };
|
|
27438
|
+
}
|
|
27439
|
+
}
|
|
27440
|
+
/**
|
|
27441
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
27442
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
27443
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
27444
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
27445
|
+
*/
|
|
27446
|
+
async sendTemplate(to, subject, template, data = {}, cc, bcc, replyTo, attachments, headers) {
|
|
27447
|
+
let body = template;
|
|
27448
|
+
try {
|
|
27449
|
+
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
27450
|
+
body = new Frond2().renderString(template, data);
|
|
27451
|
+
} catch {
|
|
27758
27452
|
}
|
|
27453
|
+
return this.send(to, subject, body, true, void 0, cc, bcc, replyTo, attachments, headers);
|
|
27759
27454
|
}
|
|
27760
27455
|
/**
|
|
27761
27456
|
* Test the SMTP connection without sending an email.
|
|
@@ -27765,14 +27460,14 @@ var init_messenger = __esm({
|
|
|
27765
27460
|
let socket;
|
|
27766
27461
|
if (this.port === 465) {
|
|
27767
27462
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
27768
|
-
await new Promise((
|
|
27769
|
-
socket.once("secureConnect",
|
|
27463
|
+
await new Promise((resolve20, reject) => {
|
|
27464
|
+
socket.once("secureConnect", resolve20);
|
|
27770
27465
|
socket.once("error", reject);
|
|
27771
27466
|
});
|
|
27772
27467
|
} else {
|
|
27773
27468
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
27774
|
-
await new Promise((
|
|
27775
|
-
socket.once("connect",
|
|
27469
|
+
await new Promise((resolve20, reject) => {
|
|
27470
|
+
socket.once("connect", resolve20);
|
|
27776
27471
|
socket.once("error", reject);
|
|
27777
27472
|
});
|
|
27778
27473
|
}
|
|
@@ -27807,14 +27502,14 @@ var init_messenger = __esm({
|
|
|
27807
27502
|
const useTls = this.imapEncryption === "tls" || this.imapEncryption === "ssl" || this.imapEncryption === "" && this.imapPort === 993;
|
|
27808
27503
|
if (useTls) {
|
|
27809
27504
|
socket = tls2.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
27810
|
-
await new Promise((
|
|
27811
|
-
socket.once("secureConnect",
|
|
27505
|
+
await new Promise((resolve20, reject) => {
|
|
27506
|
+
socket.once("secureConnect", resolve20);
|
|
27812
27507
|
socket.once("error", reject);
|
|
27813
27508
|
});
|
|
27814
27509
|
} else {
|
|
27815
27510
|
socket = net3.createConnection({ host: this.imapHost, port: this.imapPort });
|
|
27816
|
-
await new Promise((
|
|
27817
|
-
socket.once("connect",
|
|
27511
|
+
await new Promise((resolve20, reject) => {
|
|
27512
|
+
socket.once("connect", resolve20);
|
|
27818
27513
|
socket.once("error", reject);
|
|
27819
27514
|
});
|
|
27820
27515
|
}
|
|
@@ -27851,7 +27546,7 @@ var init_messenger = __esm({
|
|
|
27851
27546
|
}
|
|
27852
27547
|
try {
|
|
27853
27548
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27854
|
-
const searchResp = await imapCommand(socket, "SEARCH ALL");
|
|
27549
|
+
const searchResp = await imapCommand(socket, "UID SEARCH ALL");
|
|
27855
27550
|
const uids = parseSearchResponse(searchResp);
|
|
27856
27551
|
if (uids.length === 0) return [];
|
|
27857
27552
|
uids.reverse();
|
|
@@ -27859,8 +27554,8 @@ var init_messenger = __esm({
|
|
|
27859
27554
|
if (selected.length === 0) return [];
|
|
27860
27555
|
const messages = [];
|
|
27861
27556
|
for (const uid of selected) {
|
|
27862
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
27863
|
-
messages.push(
|
|
27557
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
27558
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
27864
27559
|
}
|
|
27865
27560
|
return messages;
|
|
27866
27561
|
} catch (err) {
|
|
@@ -27870,7 +27565,7 @@ var init_messenger = __esm({
|
|
|
27870
27565
|
}
|
|
27871
27566
|
}
|
|
27872
27567
|
/**
|
|
27873
|
-
* Read a single message by
|
|
27568
|
+
* Read a single message by its IMAP UID.
|
|
27874
27569
|
*/
|
|
27875
27570
|
async read(uid, folder = "INBOX") {
|
|
27876
27571
|
let socket;
|
|
@@ -27881,11 +27576,11 @@ var init_messenger = __esm({
|
|
|
27881
27576
|
}
|
|
27882
27577
|
try {
|
|
27883
27578
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27884
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY[])`);
|
|
27579
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY[])`);
|
|
27885
27580
|
if (!/\{\d+\}/.test(fetchResp)) {
|
|
27886
27581
|
return null;
|
|
27887
27582
|
}
|
|
27888
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
27583
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
27889
27584
|
return parseFullMessage(uid, fetchResp);
|
|
27890
27585
|
} catch (err) {
|
|
27891
27586
|
throw imapFail("read", err);
|
|
@@ -27912,14 +27607,14 @@ var init_messenger = __esm({
|
|
|
27912
27607
|
}
|
|
27913
27608
|
try {
|
|
27914
27609
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27915
|
-
const searchResp = await imapCommand(socket, `SEARCH ${query}`);
|
|
27610
|
+
const searchResp = await imapCommand(socket, `UID SEARCH ${query}`);
|
|
27916
27611
|
const uids = parseSearchResponse(searchResp);
|
|
27917
27612
|
if (uids.length === 0) return [];
|
|
27918
27613
|
uids.reverse();
|
|
27919
27614
|
const messages = [];
|
|
27920
27615
|
for (const uid of uids.slice(0, limit)) {
|
|
27921
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
27922
|
-
messages.push(
|
|
27616
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
27617
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
27923
27618
|
}
|
|
27924
27619
|
return messages;
|
|
27925
27620
|
} catch (err) {
|
|
@@ -27929,26 +27624,46 @@ var init_messenger = __esm({
|
|
|
27929
27624
|
}
|
|
27930
27625
|
}
|
|
27931
27626
|
/**
|
|
27932
|
-
* Delete a message by UID.
|
|
27627
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
27628
|
+
*
|
|
27629
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
27630
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
27933
27631
|
*/
|
|
27934
|
-
async
|
|
27632
|
+
async delete(uid, folder = "INBOX") {
|
|
27935
27633
|
const socket = await this.imapConnect();
|
|
27936
27634
|
try {
|
|
27937
27635
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27938
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Deleted)`);
|
|
27636
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Deleted)`);
|
|
27939
27637
|
await imapCommand(socket, "EXPUNGE");
|
|
27940
27638
|
} finally {
|
|
27941
27639
|
await this.imapDisconnect(socket);
|
|
27942
27640
|
}
|
|
27943
27641
|
}
|
|
27642
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
27643
|
+
async deleteMessage(uid, folder = "INBOX") {
|
|
27644
|
+
return this.delete(uid, folder);
|
|
27645
|
+
}
|
|
27944
27646
|
/**
|
|
27945
|
-
* Mark a message as read.
|
|
27647
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
27946
27648
|
*/
|
|
27947
27649
|
async markRead(uid, folder = "INBOX") {
|
|
27948
27650
|
const socket = await this.imapConnect();
|
|
27949
27651
|
try {
|
|
27950
27652
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27951
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
27653
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
27654
|
+
} finally {
|
|
27655
|
+
await this.imapDisconnect(socket);
|
|
27656
|
+
}
|
|
27657
|
+
}
|
|
27658
|
+
/**
|
|
27659
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
27660
|
+
* parity with Python's mark_unread).
|
|
27661
|
+
*/
|
|
27662
|
+
async markUnread(uid, folder = "INBOX") {
|
|
27663
|
+
const socket = await this.imapConnect();
|
|
27664
|
+
try {
|
|
27665
|
+
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27666
|
+
await imapCommand(socket, `UID STORE ${uid} -FLAGS (\\Seen)`);
|
|
27952
27667
|
} finally {
|
|
27953
27668
|
await this.imapDisconnect(socket);
|
|
27954
27669
|
}
|
|
@@ -27965,7 +27680,7 @@ var init_messenger = __esm({
|
|
|
27965
27680
|
}
|
|
27966
27681
|
try {
|
|
27967
27682
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27968
|
-
const searchResp = await imapCommand(socket, "SEARCH UNSEEN");
|
|
27683
|
+
const searchResp = await imapCommand(socket, "UID SEARCH UNSEEN");
|
|
27969
27684
|
return parseSearchResponse(searchResp).length;
|
|
27970
27685
|
} catch (err) {
|
|
27971
27686
|
throw imapFail("unread", err);
|
|
@@ -28704,17 +28419,17 @@ var init_htmlElement = __esm({
|
|
|
28704
28419
|
});
|
|
28705
28420
|
|
|
28706
28421
|
// ../core/src/ai.ts
|
|
28707
|
-
import { existsSync as
|
|
28422
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync14, writeFileSync as writeFileSync14, readFileSync as readFileSync23 } from "node:fs";
|
|
28708
28423
|
import { homedir } from "node:os";
|
|
28709
|
-
import { join as
|
|
28424
|
+
import { join as join24, resolve as resolve16, relative as relative10, dirname as dirname10 } from "node:path";
|
|
28710
28425
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
28711
28426
|
import { execSync, execFileSync as execFileSync3 } from "node:child_process";
|
|
28712
28427
|
import { createInterface } from "node:readline";
|
|
28713
28428
|
function readVersion() {
|
|
28714
28429
|
try {
|
|
28715
|
-
const thisDir =
|
|
28716
|
-
const rootPkg =
|
|
28717
|
-
const pkg = JSON.parse(
|
|
28430
|
+
const thisDir = dirname10(fileURLToPath6(import.meta.url));
|
|
28431
|
+
const rootPkg = resolve16(thisDir, "..", "..", "..", "package.json");
|
|
28432
|
+
const pkg = JSON.parse(readFileSync23(rootPkg, "utf-8"));
|
|
28718
28433
|
return pkg.version ?? "0.0.0";
|
|
28719
28434
|
} catch {
|
|
28720
28435
|
return "0.0.0";
|
|
@@ -28763,8 +28478,8 @@ function downloadSkillsSync(jobs) {
|
|
|
28763
28478
|
function installSkills(root = ".", targets) {
|
|
28764
28479
|
const ref = skillsRef();
|
|
28765
28480
|
const dests = targets ?? [
|
|
28766
|
-
|
|
28767
|
-
|
|
28481
|
+
join24(resolve16(root), ".claude", "skills"),
|
|
28482
|
+
join24(homedir(), ".claude", "skills")
|
|
28768
28483
|
];
|
|
28769
28484
|
const jobs = [];
|
|
28770
28485
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -28782,9 +28497,9 @@ function installSkills(root = ".", targets) {
|
|
|
28782
28497
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
28783
28498
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
28784
28499
|
for (const dest of dests) {
|
|
28785
|
-
add(`${base}/SKILL.md`,
|
|
28500
|
+
add(`${base}/SKILL.md`, join24(dest, skill, "SKILL.md"));
|
|
28786
28501
|
for (const r of spec.references) {
|
|
28787
|
-
add(`${base}/references/${r}`,
|
|
28502
|
+
add(`${base}/references/${r}`, join24(dest, skill, "references", r));
|
|
28788
28503
|
}
|
|
28789
28504
|
}
|
|
28790
28505
|
}
|
|
@@ -28796,10 +28511,10 @@ function installSkills(root = ".", targets) {
|
|
|
28796
28511
|
return installed;
|
|
28797
28512
|
}
|
|
28798
28513
|
function isInstalled(root, tool) {
|
|
28799
|
-
return
|
|
28514
|
+
return existsSync24(join24(resolve16(root), tool.contextFile));
|
|
28800
28515
|
}
|
|
28801
28516
|
function showMenu(root = ".") {
|
|
28802
|
-
const r =
|
|
28517
|
+
const r = resolve16(root);
|
|
28803
28518
|
console.log("\n Tina4 AI Context Installer\n");
|
|
28804
28519
|
for (let i = 0; i < AI_TOOLS.length; i++) {
|
|
28805
28520
|
const tool = AI_TOOLS[i];
|
|
@@ -28817,16 +28532,16 @@ function showMenu(root = ".") {
|
|
|
28817
28532
|
const tina4AiMarker = tina4AiInstalled ? ` ${GREEN2}[installed]${RESET2}` : "";
|
|
28818
28533
|
console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
|
|
28819
28534
|
console.log();
|
|
28820
|
-
return new Promise((
|
|
28535
|
+
return new Promise((resolve20) => {
|
|
28821
28536
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
28822
28537
|
rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
|
|
28823
28538
|
rl.close();
|
|
28824
|
-
|
|
28539
|
+
resolve20(answer.trim());
|
|
28825
28540
|
});
|
|
28826
28541
|
});
|
|
28827
28542
|
}
|
|
28828
28543
|
function installSelected(root, selection) {
|
|
28829
|
-
const rootPath =
|
|
28544
|
+
const rootPath = resolve16(root);
|
|
28830
28545
|
const created = [];
|
|
28831
28546
|
let indices;
|
|
28832
28547
|
let doInstallTina4Ai = false;
|
|
@@ -28919,33 +28634,33 @@ function looksLikeOldFrameworkInstall(existing) {
|
|
|
28919
28634
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
28920
28635
|
const block = skillBlock(contextFile);
|
|
28921
28636
|
const [start2, end] = markersFor(contextFile);
|
|
28922
|
-
if (!
|
|
28923
|
-
|
|
28637
|
+
if (!existsSync24(contextPath)) {
|
|
28638
|
+
writeFileSync14(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
28924
28639
|
return "Installed";
|
|
28925
28640
|
}
|
|
28926
|
-
const existing =
|
|
28641
|
+
const existing = readFileSync23(contextPath, "utf-8");
|
|
28927
28642
|
if (hasMarkers(existing, start2, end)) {
|
|
28928
|
-
|
|
28643
|
+
writeFileSync14(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
28929
28644
|
return "Refreshed skill block in";
|
|
28930
28645
|
}
|
|
28931
28646
|
if (looksLikeOldFrameworkInstall(existing)) {
|
|
28932
28647
|
const head = existing.replace(/^\s+/, "");
|
|
28933
28648
|
const preamble = existing.slice(0, existing.length - head.length);
|
|
28934
28649
|
const newContent = (preamble.trim() ? preamble.replace(/\s+$/, "") + "\n\n" : "") + frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n";
|
|
28935
|
-
|
|
28650
|
+
writeFileSync14(contextPath, newContent, "utf-8");
|
|
28936
28651
|
return "Migrated (replaced old framework dump in)";
|
|
28937
28652
|
}
|
|
28938
|
-
|
|
28653
|
+
writeFileSync14(contextPath, existing.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
28939
28654
|
return "Appended skill block to";
|
|
28940
28655
|
}
|
|
28941
28656
|
function installForTool(root, tool, context) {
|
|
28942
28657
|
const created = [];
|
|
28943
|
-
const contextPath =
|
|
28658
|
+
const contextPath = join24(root, tool.contextFile);
|
|
28944
28659
|
if (tool.configDir) {
|
|
28945
|
-
|
|
28660
|
+
mkdirSync14(join24(root, tool.configDir), { recursive: true });
|
|
28946
28661
|
}
|
|
28947
|
-
const parentDir =
|
|
28948
|
-
|
|
28662
|
+
const parentDir = dirname10(contextPath);
|
|
28663
|
+
mkdirSync14(parentDir, { recursive: true });
|
|
28949
28664
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
28950
28665
|
const rel = relative10(root, contextPath);
|
|
28951
28666
|
created.push(rel);
|
|
@@ -28980,7 +28695,7 @@ function installTina4Ai() {
|
|
|
28980
28695
|
function installClaudeSkills(root) {
|
|
28981
28696
|
const created = [];
|
|
28982
28697
|
for (const skill of installSkills(root)) {
|
|
28983
|
-
created.push(
|
|
28698
|
+
created.push(join24(".claude", "skills", skill));
|
|
28984
28699
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
28985
28700
|
}
|
|
28986
28701
|
return created;
|
|
@@ -29321,11 +29036,11 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
29321
29036
|
}
|
|
29322
29037
|
function generateClaudeCodeContext() {
|
|
29323
29038
|
try {
|
|
29324
|
-
const thisDir =
|
|
29325
|
-
const repoRoot =
|
|
29326
|
-
const claudeMdPath =
|
|
29327
|
-
if (
|
|
29328
|
-
return
|
|
29039
|
+
const thisDir = dirname10(fileURLToPath6(import.meta.url));
|
|
29040
|
+
const repoRoot = resolve16(thisDir, "..", "..", "..");
|
|
29041
|
+
const claudeMdPath = join24(repoRoot, "CLAUDE.md");
|
|
29042
|
+
if (existsSync24(claudeMdPath)) {
|
|
29043
|
+
return readFileSync23(claudeMdPath, "utf-8");
|
|
29329
29044
|
}
|
|
29330
29045
|
} catch {
|
|
29331
29046
|
}
|
|
@@ -31317,7 +31032,6 @@ __export(src_exports2, {
|
|
|
31317
31032
|
RouteRef: () => RouteRef,
|
|
31318
31033
|
Router: () => Router,
|
|
31319
31034
|
SafeString: () => SafeString2,
|
|
31320
|
-
ScssCompiler: () => ScssCompiler,
|
|
31321
31035
|
SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
|
|
31322
31036
|
ServiceRunner: () => ServiceRunner,
|
|
31323
31037
|
Session: () => Session,
|
|
@@ -31514,7 +31228,6 @@ var init_src2 = __esm({
|
|
|
31514
31228
|
init_session();
|
|
31515
31229
|
init_i18n();
|
|
31516
31230
|
init_fakeData();
|
|
31517
|
-
init_scss();
|
|
31518
31231
|
init_queue();
|
|
31519
31232
|
init_job();
|
|
31520
31233
|
init_mqtt();
|
|
@@ -32073,8 +31786,8 @@ __export(sqlite_exports, {
|
|
|
32073
31786
|
SQLiteAdapter: () => SQLiteAdapter
|
|
32074
31787
|
});
|
|
32075
31788
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32076
|
-
import { mkdirSync as
|
|
32077
|
-
import { dirname as
|
|
31789
|
+
import { mkdirSync as mkdirSync15 } from "node:fs";
|
|
31790
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, join as join25, resolve as resolve17 } from "node:path";
|
|
32078
31791
|
function isIdentifier(str) {
|
|
32079
31792
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
32080
31793
|
}
|
|
@@ -32107,13 +31820,13 @@ function resolveSqlitePath(dbPath) {
|
|
|
32107
31820
|
if (dbPath === ":memory:") return dbPath;
|
|
32108
31821
|
let path8 = dbPath;
|
|
32109
31822
|
if (!isAbsolute4(path8)) {
|
|
32110
|
-
path8 =
|
|
32111
|
-
|
|
31823
|
+
path8 = join25(process.cwd(), path8);
|
|
31824
|
+
mkdirSync15(dirname11(path8), { recursive: true });
|
|
32112
31825
|
} else {
|
|
32113
|
-
const cwd =
|
|
32114
|
-
const abs =
|
|
31826
|
+
const cwd = resolve17(process.cwd());
|
|
31827
|
+
const abs = resolve17(path8);
|
|
32115
31828
|
if (abs.startsWith(cwd + "/") || abs === cwd) {
|
|
32116
|
-
|
|
31829
|
+
mkdirSync15(dirname11(abs), { recursive: true });
|
|
32117
31830
|
}
|
|
32118
31831
|
}
|
|
32119
31832
|
return path8;
|
|
@@ -32461,7 +32174,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
32461
32174
|
const elapsedMs = () => performance.now() - startedAt;
|
|
32462
32175
|
if (budgetMs === null) return attempt();
|
|
32463
32176
|
const started = attempt();
|
|
32464
|
-
return new Promise((
|
|
32177
|
+
return new Promise((resolve20, reject) => {
|
|
32465
32178
|
let expired = false;
|
|
32466
32179
|
const timer = setTimeout(() => {
|
|
32467
32180
|
expired = true;
|
|
@@ -32471,7 +32184,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
32471
32184
|
(arrived) => {
|
|
32472
32185
|
clearTimeout(timer);
|
|
32473
32186
|
if (expired) abandon?.(arrived);
|
|
32474
|
-
else
|
|
32187
|
+
else resolve20(arrived);
|
|
32475
32188
|
},
|
|
32476
32189
|
(failure) => {
|
|
32477
32190
|
clearTimeout(timer);
|
|
@@ -33028,10 +32741,10 @@ var init_mysql = __esm({
|
|
|
33028
32741
|
...timeoutOption
|
|
33029
32742
|
});
|
|
33030
32743
|
}
|
|
33031
|
-
return new Promise((
|
|
32744
|
+
return new Promise((resolve20, reject) => {
|
|
33032
32745
|
this.connection.connect((err) => {
|
|
33033
32746
|
if (err) reject(err);
|
|
33034
|
-
else
|
|
32747
|
+
else resolve20();
|
|
33035
32748
|
});
|
|
33036
32749
|
});
|
|
33037
32750
|
},
|
|
@@ -33053,10 +32766,10 @@ var init_mysql = __esm({
|
|
|
33053
32766
|
}
|
|
33054
32767
|
}
|
|
33055
32768
|
queryPromise(sql, params) {
|
|
33056
|
-
return new Promise((
|
|
32769
|
+
return new Promise((resolve20, reject) => {
|
|
33057
32770
|
this.connection.query(sql, params ?? [], (err, results) => {
|
|
33058
32771
|
if (err) reject(err);
|
|
33059
|
-
else
|
|
32772
|
+
else resolve20(results);
|
|
33060
32773
|
});
|
|
33061
32774
|
});
|
|
33062
32775
|
}
|
|
@@ -33426,11 +33139,11 @@ var init_mssql = __esm({
|
|
|
33426
33139
|
};
|
|
33427
33140
|
}
|
|
33428
33141
|
await withConnectTimeout(
|
|
33429
|
-
() => new Promise((
|
|
33142
|
+
() => new Promise((resolve20, reject) => {
|
|
33430
33143
|
this.connection = new Connection(tediousConfig);
|
|
33431
33144
|
this.connection.on("connect", (err) => {
|
|
33432
33145
|
if (err) reject(err);
|
|
33433
|
-
else
|
|
33146
|
+
else resolve20();
|
|
33434
33147
|
});
|
|
33435
33148
|
this.connection.connect();
|
|
33436
33149
|
}),
|
|
@@ -33475,11 +33188,11 @@ var init_mssql = __esm({
|
|
|
33475
33188
|
const tediousModule = requireTedious();
|
|
33476
33189
|
const Request = tediousModule.Request;
|
|
33477
33190
|
const TYPES = tediousModule.TYPES;
|
|
33478
|
-
return new Promise((
|
|
33191
|
+
return new Promise((resolve20, reject) => {
|
|
33479
33192
|
const rows = [];
|
|
33480
33193
|
const request = new Request(sql, (err, rowCount) => {
|
|
33481
33194
|
if (err) reject(err);
|
|
33482
|
-
else
|
|
33195
|
+
else resolve20({ rows, rowCount });
|
|
33483
33196
|
});
|
|
33484
33197
|
if (params) {
|
|
33485
33198
|
params.forEach((p, i) => {
|
|
@@ -33682,8 +33395,8 @@ var init_mssql = __esm({
|
|
|
33682
33395
|
throw new Error("Use startTransactionAsync() for MSSQL.");
|
|
33683
33396
|
}
|
|
33684
33397
|
async startTransactionAsync() {
|
|
33685
|
-
await new Promise((
|
|
33686
|
-
this.connection.beginTransaction((err) => err ? reject(err) :
|
|
33398
|
+
await new Promise((resolve20, reject) => {
|
|
33399
|
+
this.connection.beginTransaction((err) => err ? reject(err) : resolve20());
|
|
33687
33400
|
});
|
|
33688
33401
|
this._inTransaction = true;
|
|
33689
33402
|
}
|
|
@@ -33691,8 +33404,8 @@ var init_mssql = __esm({
|
|
|
33691
33404
|
throw new Error("Use commitAsync() for MSSQL.");
|
|
33692
33405
|
}
|
|
33693
33406
|
async commitAsync() {
|
|
33694
|
-
await new Promise((
|
|
33695
|
-
this.connection.commitTransaction((err) => err ? reject(err) :
|
|
33407
|
+
await new Promise((resolve20, reject) => {
|
|
33408
|
+
this.connection.commitTransaction((err) => err ? reject(err) : resolve20());
|
|
33696
33409
|
});
|
|
33697
33410
|
this._inTransaction = false;
|
|
33698
33411
|
}
|
|
@@ -33700,8 +33413,8 @@ var init_mssql = __esm({
|
|
|
33700
33413
|
throw new Error("Use rollbackAsync() for MSSQL.");
|
|
33701
33414
|
}
|
|
33702
33415
|
async rollbackAsync() {
|
|
33703
|
-
await new Promise((
|
|
33704
|
-
this.connection.rollbackTransaction((err) => err ? reject(err) :
|
|
33416
|
+
await new Promise((resolve20, reject) => {
|
|
33417
|
+
this.connection.rollbackTransaction((err) => err ? reject(err) : resolve20());
|
|
33705
33418
|
});
|
|
33706
33419
|
this._inTransaction = false;
|
|
33707
33420
|
}
|
|
@@ -33971,8 +33684,8 @@ var init_firebird = __esm({
|
|
|
33971
33684
|
fbConfig.database = normalizeFirebirdDbIdentifier(fbConfig.database);
|
|
33972
33685
|
}
|
|
33973
33686
|
this.db = await withConnectTimeout(
|
|
33974
|
-
() => new Promise((
|
|
33975
|
-
fb.attach(fbConfig, (err, db) => err ? reject(err) :
|
|
33687
|
+
() => new Promise((resolve20, reject) => {
|
|
33688
|
+
fb.attach(fbConfig, (err, db) => err ? reject(err) : resolve20(db));
|
|
33976
33689
|
}),
|
|
33977
33690
|
connectTimeoutMillis(),
|
|
33978
33691
|
fbConfig.host,
|
|
@@ -34039,20 +33752,20 @@ var init_firebird = __esm({
|
|
|
34039
33752
|
return this.transaction ?? this.db;
|
|
34040
33753
|
}
|
|
34041
33754
|
queryPromise(sql, params) {
|
|
34042
|
-
return new Promise((
|
|
33755
|
+
return new Promise((resolve20, reject) => {
|
|
34043
33756
|
const translated = this.translateSql(sql);
|
|
34044
33757
|
this.statementHandle().query(translated, params ?? [], (err, result) => {
|
|
34045
33758
|
if (err) reject(err);
|
|
34046
|
-
else
|
|
33759
|
+
else resolve20(result ?? []);
|
|
34047
33760
|
});
|
|
34048
33761
|
});
|
|
34049
33762
|
}
|
|
34050
33763
|
executePromise(sql, params) {
|
|
34051
|
-
return new Promise((
|
|
33764
|
+
return new Promise((resolve20, reject) => {
|
|
34052
33765
|
const translated = this.translateSql(sql);
|
|
34053
33766
|
this.statementHandle().execute(translated, params ?? [], (err) => {
|
|
34054
33767
|
if (err) reject(err);
|
|
34055
|
-
else
|
|
33768
|
+
else resolve20();
|
|
34056
33769
|
});
|
|
34057
33770
|
});
|
|
34058
33771
|
}
|
|
@@ -34168,12 +33881,12 @@ var init_firebird = __esm({
|
|
|
34168
33881
|
}
|
|
34169
33882
|
async startTransactionAsync() {
|
|
34170
33883
|
this.ensureConnected();
|
|
34171
|
-
await new Promise((
|
|
33884
|
+
await new Promise((resolve20, reject) => {
|
|
34172
33885
|
this.db.transaction(0, (err, transaction) => {
|
|
34173
33886
|
if (err) reject(err);
|
|
34174
33887
|
else {
|
|
34175
33888
|
this.transaction = transaction;
|
|
34176
|
-
|
|
33889
|
+
resolve20();
|
|
34177
33890
|
}
|
|
34178
33891
|
});
|
|
34179
33892
|
});
|
|
@@ -34183,12 +33896,12 @@ var init_firebird = __esm({
|
|
|
34183
33896
|
}
|
|
34184
33897
|
async commitAsync() {
|
|
34185
33898
|
if (!this.transaction) throw new Error("No active transaction to commit.");
|
|
34186
|
-
await new Promise((
|
|
33899
|
+
await new Promise((resolve20, reject) => {
|
|
34187
33900
|
this.transaction.commit((err) => {
|
|
34188
33901
|
if (err) reject(err);
|
|
34189
33902
|
else {
|
|
34190
33903
|
this.transaction = null;
|
|
34191
|
-
|
|
33904
|
+
resolve20();
|
|
34192
33905
|
}
|
|
34193
33906
|
});
|
|
34194
33907
|
});
|
|
@@ -34198,12 +33911,12 @@ var init_firebird = __esm({
|
|
|
34198
33911
|
}
|
|
34199
33912
|
async rollbackAsync() {
|
|
34200
33913
|
if (!this.transaction) throw new Error("No active transaction to rollback.");
|
|
34201
|
-
await new Promise((
|
|
33914
|
+
await new Promise((resolve20, reject) => {
|
|
34202
33915
|
this.transaction.rollback((err) => {
|
|
34203
33916
|
if (err) reject(err);
|
|
34204
33917
|
else {
|
|
34205
33918
|
this.transaction = null;
|
|
34206
|
-
|
|
33919
|
+
resolve20();
|
|
34207
33920
|
}
|
|
34208
33921
|
});
|
|
34209
33922
|
});
|
|
@@ -35264,6 +34977,7 @@ __export(database_exports, {
|
|
|
35264
34977
|
getNamedAdapter: () => getNamedAdapter,
|
|
35265
34978
|
initDatabase: () => initDatabase,
|
|
35266
34979
|
parseDatabaseUrl: () => parseDatabaseUrl,
|
|
34980
|
+
probeTotal: () => probeTotal,
|
|
35267
34981
|
resetRequestCaches: () => resetRequestCaches2,
|
|
35268
34982
|
resolveDbPool: () => resolveDbPool,
|
|
35269
34983
|
setAdapter: () => setAdapter,
|
|
@@ -35317,6 +35031,29 @@ async function adapterCreateTable(adapter, name, columns) {
|
|
|
35317
35031
|
if (adapter.createTableAsync) await adapter.createTableAsync(name, columns);
|
|
35318
35032
|
else adapter.createTable(name, columns);
|
|
35319
35033
|
}
|
|
35034
|
+
async function probeTotal(adapter, sql, params, limit) {
|
|
35035
|
+
if (limit === void 0 || limit <= 0) return void 0;
|
|
35036
|
+
try {
|
|
35037
|
+
const alias = adapter.countSubqueryAlias;
|
|
35038
|
+
const suffix = alias ? ` AS ${alias}` : "";
|
|
35039
|
+
const rows = await adapterFetch(
|
|
35040
|
+
adapter,
|
|
35041
|
+
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
35042
|
+
)${suffix}`,
|
|
35043
|
+
params,
|
|
35044
|
+
void 0,
|
|
35045
|
+
void 0,
|
|
35046
|
+
true
|
|
35047
|
+
);
|
|
35048
|
+
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
35049
|
+
if (!row) return void 0;
|
|
35050
|
+
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
35051
|
+
const n = Number(value);
|
|
35052
|
+
return Number.isFinite(n) ? n : void 0;
|
|
35053
|
+
} catch {
|
|
35054
|
+
return void 0;
|
|
35055
|
+
}
|
|
35056
|
+
}
|
|
35320
35057
|
function extractLastInsertId(result) {
|
|
35321
35058
|
if (result && typeof result === "object") {
|
|
35322
35059
|
const r = result;
|
|
@@ -35775,65 +35512,13 @@ var init_database = __esm({
|
|
|
35775
35512
|
try {
|
|
35776
35513
|
const rows = await adapterFetch(adapter, sql, params, limit, offset, opts?.noCache);
|
|
35777
35514
|
this.lastError = null;
|
|
35778
|
-
const total = await
|
|
35515
|
+
const total = await probeTotal(adapter, sql, params, limit);
|
|
35779
35516
|
return new DatabaseResult(rows, void 0, total, limit, offset, adapter, sql);
|
|
35780
35517
|
} catch (e) {
|
|
35781
35518
|
this.lastError = e?.message ?? String(e);
|
|
35782
35519
|
throw e;
|
|
35783
35520
|
}
|
|
35784
35521
|
}
|
|
35785
|
-
/**
|
|
35786
|
-
* The true row count for `sql`, ignoring the pagination we appended.
|
|
35787
|
-
*
|
|
35788
|
-
* `count` is the TRUE TOTAL for the filter, not the number of rows this page
|
|
35789
|
-
* returned. Node and Ruby used to populate it with `records.length` while
|
|
35790
|
-
* Python and PHP populated it from a probe, so `db.fetch(sql).count` answered
|
|
35791
|
-
* 20 here and 250 there for one query against one table, and every paginated
|
|
35792
|
-
* response built on it under-reported. MEASURED 2026-08-05 on a 250-row table
|
|
35793
|
-
* read with limit=20: Node reported total 20 over 2 pages against Python's
|
|
35794
|
-
* 250 over 13.
|
|
35795
|
-
*
|
|
35796
|
-
* Only probed when a limit was actually applied. With no limit the rows
|
|
35797
|
-
* returned ARE the whole answer for this SQL, so `records.length` is already
|
|
35798
|
-
* the true total and a second round-trip would buy nothing — which is also
|
|
35799
|
-
* what keeps `fetchAll()` at one query.
|
|
35800
|
-
*
|
|
35801
|
-
* BEST EFFORT, and it can never mask a real failure: it runs AFTER the main
|
|
35802
|
-
* query (which has already thrown on bad SQL) and returns undefined on any
|
|
35803
|
-
* error. `undefined` — not 0 — is the miss value, so DatabaseResult falls
|
|
35804
|
-
* back to records.length, a true lower bound. Reporting 0 next to 100 real
|
|
35805
|
-
* records would be the same "states a wrong number authoritatively" defect
|
|
35806
|
-
* this change exists to remove.
|
|
35807
|
-
*
|
|
35808
|
-
* The closing paren goes on its OWN LINE: appended inline, a trailing
|
|
35809
|
-
* `-- comment` in the caller's SQL comments it out and the probe dies with
|
|
35810
|
-
* "incomplete input". Postgres, MySQL and MSSQL additionally require a name
|
|
35811
|
-
* for the derived table; SQLite and Firebird do not, and Firebird rejects
|
|
35812
|
-
* `AS` there — so the alias comes from the adapter, not an assumption.
|
|
35813
|
-
*/
|
|
35814
|
-
async countProbe(adapter, sql, params, limit) {
|
|
35815
|
-
if (limit === void 0 || limit <= 0) return void 0;
|
|
35816
|
-
try {
|
|
35817
|
-
const alias = adapter.countSubqueryAlias;
|
|
35818
|
-
const suffix = alias ? ` AS ${alias}` : "";
|
|
35819
|
-
const rows = await adapterFetch(
|
|
35820
|
-
adapter,
|
|
35821
|
-
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
35822
|
-
)${suffix}`,
|
|
35823
|
-
params,
|
|
35824
|
-
void 0,
|
|
35825
|
-
void 0,
|
|
35826
|
-
true
|
|
35827
|
-
);
|
|
35828
|
-
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
35829
|
-
if (!row) return void 0;
|
|
35830
|
-
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
35831
|
-
const n = Number(value);
|
|
35832
|
-
return Number.isFinite(n) ? n : void 0;
|
|
35833
|
-
} catch {
|
|
35834
|
-
return void 0;
|
|
35835
|
-
}
|
|
35836
|
-
}
|
|
35837
35522
|
/**
|
|
35838
35523
|
* Fetch a single row or null.
|
|
35839
35524
|
*
|
|
@@ -36519,18 +36204,18 @@ var init_database = __esm({
|
|
|
36519
36204
|
});
|
|
36520
36205
|
|
|
36521
36206
|
// src/model.ts
|
|
36522
|
-
import { readdirSync as
|
|
36523
|
-
import { join as
|
|
36207
|
+
import { readdirSync as readdirSync17, statSync as statSync17 } from "node:fs";
|
|
36208
|
+
import { join as join26, extname as extname8 } from "node:path";
|
|
36524
36209
|
async function discoverModels(modelsDir) {
|
|
36525
36210
|
const models = [];
|
|
36526
36211
|
let files;
|
|
36527
36212
|
try {
|
|
36528
|
-
files =
|
|
36213
|
+
files = readdirSync17(modelsDir);
|
|
36529
36214
|
} catch {
|
|
36530
36215
|
return models;
|
|
36531
36216
|
}
|
|
36532
36217
|
for (const file of files) {
|
|
36533
|
-
const filePath =
|
|
36218
|
+
const filePath = join26(modelsDir, file);
|
|
36534
36219
|
const stat = statSync17(filePath);
|
|
36535
36220
|
if (!stat.isFile()) continue;
|
|
36536
36221
|
const ext = extname8(file);
|
|
@@ -36545,6 +36230,10 @@ async function discoverModels(modelsDir) {
|
|
|
36545
36230
|
}
|
|
36546
36231
|
const definition = {
|
|
36547
36232
|
tableName: ModelClass.tableName,
|
|
36233
|
+
// The class name is the type name a generated OpenAPI client wants
|
|
36234
|
+
// (`Item`, not `items`). Carry it so Swagger keys components.schemas by
|
|
36235
|
+
// it. A model exported as `default` keeps its declared class name here.
|
|
36236
|
+
className: typeof ModelClass.name === "string" && ModelClass.name ? ModelClass.name : void 0,
|
|
36548
36237
|
fields: ModelClass.fields,
|
|
36549
36238
|
fieldMapping: ModelClass.fieldMapping,
|
|
36550
36239
|
softDelete: ModelClass.softDelete ?? false,
|
|
@@ -36568,8 +36257,8 @@ var init_model = __esm({
|
|
|
36568
36257
|
});
|
|
36569
36258
|
|
|
36570
36259
|
// src/migration.ts
|
|
36571
|
-
import { existsSync as
|
|
36572
|
-
import { join as
|
|
36260
|
+
import { existsSync as existsSync25, readdirSync as readdirSync18, readFileSync as readFileSync24, mkdirSync as mkdirSync16, writeFileSync as writeFileSync15 } from "node:fs";
|
|
36261
|
+
import { join as join27, resolve as resolve18 } from "node:path";
|
|
36573
36262
|
function unwrapAdapter(db) {
|
|
36574
36263
|
let cur = db;
|
|
36575
36264
|
while (cur && cur.constructor?.name === "CachedDatabaseAdapter" && cur.adapter) {
|
|
@@ -36877,16 +36566,16 @@ async function rollback(migrationsDir, delimiter2) {
|
|
|
36877
36566
|
}
|
|
36878
36567
|
return rolledBack2;
|
|
36879
36568
|
}
|
|
36880
|
-
const dir =
|
|
36569
|
+
const dir = resolve18(migrationsDir ?? "migrations");
|
|
36881
36570
|
const delim = delimiter2 ?? ";";
|
|
36882
36571
|
const db = getAdapter();
|
|
36883
36572
|
const migrations = await getLastBatchMigrations();
|
|
36884
36573
|
const rolledBack = [];
|
|
36885
36574
|
for (const migration of migrations) {
|
|
36886
36575
|
const downFile = `${migration.migration_name}.down.sql`;
|
|
36887
|
-
const downPath =
|
|
36888
|
-
if (
|
|
36889
|
-
const sqlContent =
|
|
36576
|
+
const downPath = join27(dir, downFile);
|
|
36577
|
+
if (existsSync25(downPath)) {
|
|
36578
|
+
const sqlContent = readFileSync24(downPath, "utf-8").trim();
|
|
36890
36579
|
if (sqlContent) {
|
|
36891
36580
|
const statements = splitStatements(sqlContent, delim);
|
|
36892
36581
|
try {
|
|
@@ -37046,15 +36735,15 @@ function warnUnprefixedMigrations(files) {
|
|
|
37046
36735
|
}
|
|
37047
36736
|
async function migrate(adapter, options) {
|
|
37048
36737
|
const db = adapter ?? getAdapter();
|
|
37049
|
-
const dir =
|
|
36738
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
37050
36739
|
const delimiter2 = options?.delimiter ?? ";";
|
|
37051
36740
|
const result = { applied: [], skipped: [], failed: [] };
|
|
37052
|
-
if (!
|
|
36741
|
+
if (!existsSync25(dir)) {
|
|
37053
36742
|
return result;
|
|
37054
36743
|
}
|
|
37055
36744
|
await ensureMigrationTableOn(db);
|
|
37056
36745
|
const files = sortMigrationFiles(
|
|
37057
|
-
|
|
36746
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37058
36747
|
);
|
|
37059
36748
|
if (files.length === 0) return result;
|
|
37060
36749
|
warnUnprefixedMigrations(files);
|
|
@@ -37085,7 +36774,7 @@ async function migrate(adapter, options) {
|
|
|
37085
36774
|
result.skipped.push(file);
|
|
37086
36775
|
continue;
|
|
37087
36776
|
}
|
|
37088
|
-
const sqlContent =
|
|
36777
|
+
const sqlContent = readFileSync24(join27(dir, file), "utf-8").trim();
|
|
37089
36778
|
if (!sqlContent) {
|
|
37090
36779
|
result.skipped.push(file);
|
|
37091
36780
|
continue;
|
|
@@ -37120,21 +36809,21 @@ async function migrate(adapter, options) {
|
|
|
37120
36809
|
}
|
|
37121
36810
|
async function status(adapter, options) {
|
|
37122
36811
|
const db = adapter ?? getAdapter();
|
|
37123
|
-
const dir =
|
|
36812
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
37124
36813
|
const result = { completed: [], pending: [] };
|
|
37125
|
-
if (!
|
|
36814
|
+
if (!existsSync25(dir)) {
|
|
37126
36815
|
return result;
|
|
37127
36816
|
}
|
|
37128
36817
|
if (!await adapterTableExists(db, MIGRATION_TABLE)) {
|
|
37129
36818
|
const files2 = sortMigrationFiles(
|
|
37130
|
-
|
|
36819
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37131
36820
|
);
|
|
37132
36821
|
result.pending = files2;
|
|
37133
36822
|
return result;
|
|
37134
36823
|
}
|
|
37135
36824
|
await ensureMigrationTableOn(db);
|
|
37136
36825
|
const files = sortMigrationFiles(
|
|
37137
|
-
|
|
36826
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37138
36827
|
);
|
|
37139
36828
|
const appliedNames = /* @__PURE__ */ new Set();
|
|
37140
36829
|
try {
|
|
@@ -37159,12 +36848,18 @@ async function status(adapter, options) {
|
|
|
37159
36848
|
return result;
|
|
37160
36849
|
}
|
|
37161
36850
|
async function createMigration(description, options) {
|
|
37162
|
-
|
|
36851
|
+
const kind = (options?.kind ?? "sql").trim().toLowerCase();
|
|
36852
|
+
if (!["sql", "code", "class"].includes(kind)) {
|
|
36853
|
+
throw new Error(
|
|
36854
|
+
`Unknown migration kind "${kind}". Use "sql" (default) or "code" (alias: "class"). An unrecognised kind used to produce a .sql file silently, which is why this now throws.`
|
|
36855
|
+
);
|
|
36856
|
+
}
|
|
36857
|
+
if (kind === "code" || kind === "class") {
|
|
37163
36858
|
return createClassMigration(description, options);
|
|
37164
36859
|
}
|
|
37165
|
-
const dir =
|
|
37166
|
-
if (!
|
|
37167
|
-
|
|
36860
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
36861
|
+
if (!existsSync25(dir)) {
|
|
36862
|
+
mkdirSync16(dir, { recursive: true });
|
|
37168
36863
|
}
|
|
37169
36864
|
const safeName = description.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
37170
36865
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -37178,8 +36873,8 @@ async function createMigration(description, options) {
|
|
|
37178
36873
|
].join("");
|
|
37179
36874
|
const upFileName = `${timestamp}_${safeName}.sql`;
|
|
37180
36875
|
const downFileName = `${timestamp}_${safeName}.down.sql`;
|
|
37181
|
-
const upPath =
|
|
37182
|
-
const downPath =
|
|
36876
|
+
const upPath = join27(dir, upFileName);
|
|
36877
|
+
const downPath = join27(dir, downFileName);
|
|
37183
36878
|
const upTemplate = `-- Migration: ${description}
|
|
37184
36879
|
-- Created: ${now.toISOString()}
|
|
37185
36880
|
|
|
@@ -37188,14 +36883,14 @@ async function createMigration(description, options) {
|
|
|
37188
36883
|
-- Created: ${now.toISOString()}
|
|
37189
36884
|
|
|
37190
36885
|
`;
|
|
37191
|
-
|
|
37192
|
-
|
|
36886
|
+
writeFileSync15(upPath, upTemplate, "utf-8");
|
|
36887
|
+
writeFileSync15(downPath, downTemplate, "utf-8");
|
|
37193
36888
|
return { upPath, downPath };
|
|
37194
36889
|
}
|
|
37195
36890
|
async function createClassMigration(description, options) {
|
|
37196
|
-
const dir =
|
|
37197
|
-
if (!
|
|
37198
|
-
|
|
36891
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
36892
|
+
if (!existsSync25(dir)) {
|
|
36893
|
+
mkdirSync16(dir, { recursive: true });
|
|
37199
36894
|
}
|
|
37200
36895
|
const safeName = description.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
37201
36896
|
const className = description.replace(/[^a-zA-Z0-9 ]+/g, " ").trim().split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
@@ -37209,7 +36904,7 @@ async function createClassMigration(description, options) {
|
|
|
37209
36904
|
String(now.getSeconds()).padStart(2, "0")
|
|
37210
36905
|
].join("");
|
|
37211
36906
|
const fileName = `${timestamp}_${safeName}.ts`;
|
|
37212
|
-
const filePath =
|
|
36907
|
+
const filePath = join27(dir, fileName);
|
|
37213
36908
|
const content = `// Migration: ${description}
|
|
37214
36909
|
// Created: ${now.toISOString()}
|
|
37215
36910
|
|
|
@@ -37225,7 +36920,7 @@ export class ${className} {
|
|
|
37225
36920
|
}
|
|
37226
36921
|
}
|
|
37227
36922
|
`;
|
|
37228
|
-
|
|
36923
|
+
writeFileSync15(filePath, content, "utf-8");
|
|
37229
36924
|
return filePath;
|
|
37230
36925
|
}
|
|
37231
36926
|
var ALTER_ADD_RE, CREATE_TABLE_RE, MIGRATION_TABLE, SMART_QUOTES, SMART_QUOTE_RE, SET_TERM_RE, Migration;
|
|
@@ -37286,15 +36981,13 @@ var init_migration = __esm({
|
|
|
37286
36981
|
* Scaffold a new migration file.
|
|
37287
36982
|
*
|
|
37288
36983
|
* kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default)
|
|
37289
|
-
* kind="
|
|
36984
|
+
* kind="code" — creates {timestamp}_{description}.ts with a TypeScript class
|
|
36985
|
+
* template. "class" is accepted as a legacy alias.
|
|
37290
36986
|
*
|
|
37291
36987
|
* Returns the path to the created up file (or class file).
|
|
37292
36988
|
*/
|
|
37293
36989
|
async create(description, kind = "sql") {
|
|
37294
|
-
|
|
37295
|
-
return createClassMigration(description, { migrationsDir: this.dir });
|
|
37296
|
-
}
|
|
37297
|
-
return createMigration(description, { migrationsDir: this.dir });
|
|
36990
|
+
return createMigration(description, { migrationsDir: this.dir, kind });
|
|
37298
36991
|
}
|
|
37299
36992
|
/** Return list of completed (applied) migration filenames. */
|
|
37300
36993
|
async getApplied() {
|
|
@@ -37308,10 +37001,10 @@ var init_migration = __esm({
|
|
|
37308
37001
|
}
|
|
37309
37002
|
/** Return sorted list of all migration files on disk (excludes .down.sql). */
|
|
37310
37003
|
getFiles() {
|
|
37311
|
-
const dir =
|
|
37312
|
-
if (!
|
|
37004
|
+
const dir = resolve18(this.dir);
|
|
37005
|
+
if (!existsSync25(dir)) return [];
|
|
37313
37006
|
return sortMigrationFiles(
|
|
37314
|
-
|
|
37007
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37315
37008
|
);
|
|
37316
37009
|
}
|
|
37317
37010
|
};
|
|
@@ -37537,15 +37230,8 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
37537
37230
|
const total = Number(countRow[0]?.total ?? 0);
|
|
37538
37231
|
const limit = qp.limit ?? 100;
|
|
37539
37232
|
const page = qp.page ?? 1;
|
|
37540
|
-
|
|
37541
|
-
|
|
37542
|
-
meta: {
|
|
37543
|
-
total,
|
|
37544
|
-
page,
|
|
37545
|
-
limit,
|
|
37546
|
-
totalPages: Math.ceil(total / limit)
|
|
37547
|
-
}
|
|
37548
|
-
});
|
|
37233
|
+
const offset = (page - 1) * limit;
|
|
37234
|
+
res.json(new DatabaseResult(rows, void 0, total, limit, offset).toPaginate());
|
|
37549
37235
|
}
|
|
37550
37236
|
});
|
|
37551
37237
|
routes.push({
|
|
@@ -37699,6 +37385,7 @@ var init_autoCrud = __esm({
|
|
|
37699
37385
|
"src/autoCrud.ts"() {
|
|
37700
37386
|
"use strict";
|
|
37701
37387
|
init_database();
|
|
37388
|
+
init_databaseResult();
|
|
37702
37389
|
init_query();
|
|
37703
37390
|
init_validation();
|
|
37704
37391
|
AutoCrud = class _AutoCrud {
|
|
@@ -37953,17 +37640,19 @@ var init_queryBuilder = __esm({
|
|
|
37953
37640
|
this.ensureDb();
|
|
37954
37641
|
const sql = this.toSql();
|
|
37955
37642
|
const allParams = [...this.params, ...this.havingParams];
|
|
37643
|
+
const queryParams = allParams.length > 0 ? allParams : void 0;
|
|
37956
37644
|
const rows = await adapterFetch(
|
|
37957
37645
|
this.db,
|
|
37958
37646
|
sql,
|
|
37959
|
-
|
|
37647
|
+
queryParams,
|
|
37960
37648
|
this.limitVal,
|
|
37961
37649
|
this.offsetVal
|
|
37962
37650
|
);
|
|
37651
|
+
const total = await probeTotal(this.db, sql, queryParams, this.limitVal);
|
|
37963
37652
|
return new DatabaseResult(
|
|
37964
37653
|
rows,
|
|
37965
37654
|
void 0,
|
|
37966
|
-
|
|
37655
|
+
total,
|
|
37967
37656
|
this.limitVal,
|
|
37968
37657
|
this.offsetVal,
|
|
37969
37658
|
this.db,
|
|
@@ -39828,8 +39517,8 @@ var init_seeder = __esm({
|
|
|
39828
39517
|
// src/docstore.ts
|
|
39829
39518
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
39830
39519
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
39831
|
-
import { mkdirSync as
|
|
39832
|
-
import { dirname as
|
|
39520
|
+
import { mkdirSync as mkdirSync17 } from "node:fs";
|
|
39521
|
+
import { dirname as dirname12, isAbsolute as isAbsolute5, join as join28 } from "node:path";
|
|
39833
39522
|
function iso(d) {
|
|
39834
39523
|
return d.toISOString();
|
|
39835
39524
|
}
|
|
@@ -40094,8 +39783,8 @@ function resolveStorePath(dbPath) {
|
|
|
40094
39783
|
if (dbPath === ":memory:") return dbPath;
|
|
40095
39784
|
let path8 = dbPath;
|
|
40096
39785
|
if (!isAbsolute5(path8)) {
|
|
40097
|
-
path8 =
|
|
40098
|
-
|
|
39786
|
+
path8 = join28(process.cwd(), path8);
|
|
39787
|
+
mkdirSync17(dirname12(path8), { recursive: true });
|
|
40099
39788
|
}
|
|
40100
39789
|
return path8;
|
|
40101
39790
|
}
|
|
@@ -40609,8 +40298,8 @@ var init_attachment = __esm({
|
|
|
40609
40298
|
|
|
40610
40299
|
// src/realtime/storage.ts
|
|
40611
40300
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
40612
|
-
import { mkdirSync as
|
|
40613
|
-
import { resolve as
|
|
40301
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, writeFileSync as writeFileSync16, unlinkSync as unlinkSync7, statSync as statSync18 } from "node:fs";
|
|
40302
|
+
import { resolve as resolve19, sep as sep3 } from "node:path";
|
|
40614
40303
|
import { createRequire as createRequire8 } from "node:module";
|
|
40615
40304
|
function storageKey(filename = "") {
|
|
40616
40305
|
let ext = "";
|
|
@@ -40646,23 +40335,23 @@ var init_storage = __esm({
|
|
|
40646
40335
|
LocalStorage = class {
|
|
40647
40336
|
directory;
|
|
40648
40337
|
constructor(directory) {
|
|
40649
|
-
this.directory =
|
|
40650
|
-
|
|
40338
|
+
this.directory = resolve19(directory || process.env.TINA4_STORAGE_DIR || "data/rt_storage");
|
|
40339
|
+
mkdirSync18(this.directory, { recursive: true });
|
|
40651
40340
|
}
|
|
40652
40341
|
// Resolve inside the root and reject any traversal attempt.
|
|
40653
40342
|
pathFor(key) {
|
|
40654
|
-
const target =
|
|
40343
|
+
const target = resolve19(this.directory, key);
|
|
40655
40344
|
if (target !== this.directory && !target.startsWith(this.directory + sep3)) {
|
|
40656
40345
|
throw new Error(`unsafe storage key: ${JSON.stringify(key)}`);
|
|
40657
40346
|
}
|
|
40658
40347
|
return target;
|
|
40659
40348
|
}
|
|
40660
40349
|
put(key, data) {
|
|
40661
|
-
|
|
40350
|
+
writeFileSync16(this.pathFor(key), data);
|
|
40662
40351
|
}
|
|
40663
40352
|
get(key) {
|
|
40664
40353
|
try {
|
|
40665
|
-
return
|
|
40354
|
+
return readFileSync25(this.pathFor(key));
|
|
40666
40355
|
} catch {
|
|
40667
40356
|
return null;
|
|
40668
40357
|
}
|
|
@@ -41072,7 +40761,6 @@ __export(index_exports, {
|
|
|
41072
40761
|
DatabaseUrl: () => DatabaseUrl,
|
|
41073
40762
|
DocStoreDriverMissing: () => DocStoreDriverMissing,
|
|
41074
40763
|
FakeData: () => FakeData2,
|
|
41075
|
-
FetchResult: () => FetchResult,
|
|
41076
40764
|
FirebirdAdapter: () => FirebirdAdapter,
|
|
41077
40765
|
InvalidId: () => InvalidId,
|
|
41078
40766
|
LocalStorage: () => LocalStorage,
|
|
@@ -41171,7 +40859,6 @@ __export(index_exports, {
|
|
|
41171
40859
|
});
|
|
41172
40860
|
var init_index = __esm({
|
|
41173
40861
|
"src/index.ts"() {
|
|
41174
|
-
init_types();
|
|
41175
40862
|
init_databaseResult();
|
|
41176
40863
|
init_database();
|
|
41177
40864
|
init_database();
|
|
@@ -41212,7 +40899,6 @@ export {
|
|
|
41212
40899
|
DatabaseUrl,
|
|
41213
40900
|
DocStoreDriverMissing,
|
|
41214
40901
|
FakeData2 as FakeData,
|
|
41215
|
-
FetchResult,
|
|
41216
40902
|
FirebirdAdapter,
|
|
41217
40903
|
InvalidId,
|
|
41218
40904
|
LocalStorage,
|