tina4-nodejs 3.13.95 → 3.13.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -4
- package/package.json +2 -1
- package/packages/cli/dist/bin.js +708 -1012
- package/packages/core/dist/index.js +588 -893
- 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/queueBackends/kafkaBackend.ts +23 -2
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +29 -17
- package/packages/core/src/request.ts +28 -7
- package/packages/core/src/server.ts +135 -7
- package/packages/core/src/session.ts +8 -1
- package/packages/orm/dist/index.js +639 -944
- 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/queueBackends/kafkaBackend.d.ts +1 -0
- package/types/core/src/queueBackends/rabbitmqBackend.d.ts +2 -1
- package/types/core/src/server.d.ts +0 -4
- package/types/core/src/session.d.ts +7 -0
- 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) {
|
|
@@ -7327,10 +7257,17 @@ var init_session = __esm({
|
|
|
7327
7257
|
*
|
|
7328
7258
|
* session.flash("message", "Saved!") // set
|
|
7329
7259
|
* session.flash("message") // get + auto-remove → "Saved!"
|
|
7260
|
+
* session.flash("message", null) // get + auto-remove (null is a GET sentinel)
|
|
7261
|
+
*
|
|
7262
|
+
* `null` — NOT just `undefined` — is the GET sentinel, so `flash(key, null)`
|
|
7263
|
+
* READS and clears rather than STORING null. This matches the Python master
|
|
7264
|
+
* (`if value is not None`), PHP (`if ($value !== null)`) and Ruby
|
|
7265
|
+
* (`if value.nil?`): passing the language's "no value" literal means GET. A
|
|
7266
|
+
* caller wanting to persist an explicit null should store it with `set()`.
|
|
7330
7267
|
*/
|
|
7331
7268
|
flash(key, value) {
|
|
7332
7269
|
const flashKey = `${FLASH_PREFIX}${key}`;
|
|
7333
|
-
if (value !== void 0) {
|
|
7270
|
+
if (value !== void 0 && value !== null) {
|
|
7334
7271
|
this.set(flashKey, value);
|
|
7335
7272
|
return void 0;
|
|
7336
7273
|
}
|
|
@@ -13201,14 +13138,14 @@ data: ${channel.buffer.shift()}
|
|
|
13201
13138
|
`;
|
|
13202
13139
|
continue;
|
|
13203
13140
|
}
|
|
13204
|
-
const gotMessage = await new Promise((
|
|
13141
|
+
const gotMessage = await new Promise((resolve20) => {
|
|
13205
13142
|
const timer = setTimeout(() => {
|
|
13206
13143
|
channel.wake = null;
|
|
13207
|
-
|
|
13144
|
+
resolve20(false);
|
|
13208
13145
|
}, keepaliveMs);
|
|
13209
13146
|
channel.wake = () => {
|
|
13210
13147
|
clearTimeout(timer);
|
|
13211
|
-
|
|
13148
|
+
resolve20(true);
|
|
13212
13149
|
};
|
|
13213
13150
|
});
|
|
13214
13151
|
if (!gotMessage) yield `: keep-alive
|
|
@@ -15723,7 +15660,7 @@ var init_websocket = __esm({
|
|
|
15723
15660
|
* Start the WebSocket server.
|
|
15724
15661
|
*/
|
|
15725
15662
|
async start() {
|
|
15726
|
-
return new Promise((
|
|
15663
|
+
return new Promise((resolve20, reject) => {
|
|
15727
15664
|
this.server = createServer((req2, res) => {
|
|
15728
15665
|
res.writeHead(426, { "Content-Type": "text/plain" });
|
|
15729
15666
|
res.end("Upgrade Required");
|
|
@@ -15733,7 +15670,7 @@ var init_websocket = __esm({
|
|
|
15733
15670
|
});
|
|
15734
15671
|
this.server.listen(this.port, () => {
|
|
15735
15672
|
this.startIdleReaper();
|
|
15736
|
-
|
|
15673
|
+
resolve20();
|
|
15737
15674
|
});
|
|
15738
15675
|
this.server.on("error", (err) => {
|
|
15739
15676
|
this.emit("error", err);
|
|
@@ -16297,7 +16234,7 @@ var init_websocket = __esm({
|
|
|
16297
16234
|
client.trackerId = this.onAdd(socket.remoteAddress ?? "unknown", "/__dev_reload");
|
|
16298
16235
|
}
|
|
16299
16236
|
this.clients.add(client);
|
|
16300
|
-
const
|
|
16237
|
+
const cleanup = () => {
|
|
16301
16238
|
if (!this.clients.has(client)) return;
|
|
16302
16239
|
this.clients.delete(client);
|
|
16303
16240
|
if (client.trackerId && this.onRemove) this.onRemove(client.trackerId);
|
|
@@ -16320,13 +16257,13 @@ var init_websocket = __esm({
|
|
|
16320
16257
|
socket.end();
|
|
16321
16258
|
} catch {
|
|
16322
16259
|
}
|
|
16323
|
-
|
|
16260
|
+
cleanup();
|
|
16324
16261
|
return;
|
|
16325
16262
|
}
|
|
16326
16263
|
}
|
|
16327
16264
|
});
|
|
16328
|
-
socket.on("close",
|
|
16329
|
-
socket.on("error",
|
|
16265
|
+
socket.on("close", cleanup);
|
|
16266
|
+
socket.on("error", cleanup);
|
|
16330
16267
|
return true;
|
|
16331
16268
|
}
|
|
16332
16269
|
/**
|
|
@@ -17844,7 +17781,7 @@ var init_queue = __esm({
|
|
|
17844
17781
|
const jobs = this.popBatch(resolvedBatchSize);
|
|
17845
17782
|
if (jobs.length === 0) {
|
|
17846
17783
|
if (resolvedPollInterval <= 0) break;
|
|
17847
|
-
await new Promise((
|
|
17784
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
17848
17785
|
continue;
|
|
17849
17786
|
}
|
|
17850
17787
|
yield jobs;
|
|
@@ -17854,7 +17791,7 @@ var init_queue = __esm({
|
|
|
17854
17791
|
const raw = this.pop();
|
|
17855
17792
|
if (raw === null) {
|
|
17856
17793
|
if (resolvedPollInterval <= 0) break;
|
|
17857
|
-
await new Promise((
|
|
17794
|
+
await new Promise((resolve20) => setTimeout(resolve20, resolvedPollInterval));
|
|
17858
17795
|
continue;
|
|
17859
17796
|
}
|
|
17860
17797
|
yield createJob(raw, this);
|
|
@@ -22374,23 +22311,23 @@ var init_devAdmin = __esm({
|
|
|
22374
22311
|
});
|
|
22375
22312
|
};
|
|
22376
22313
|
handleDevAdminJs = async (_req, res) => {
|
|
22377
|
-
const { readFileSync:
|
|
22378
|
-
const { dirname:
|
|
22314
|
+
const { readFileSync: readFileSync26, existsSync: existsSync26 } = await import("node:fs");
|
|
22315
|
+
const { dirname: dirname13, join: join29, resolve: resolve20 } = await import("node:path");
|
|
22379
22316
|
const { fileURLToPath: fileURLToPath7 } = await import("node:url");
|
|
22380
|
-
const dir =
|
|
22317
|
+
const dir = dirname13(fileURLToPath7(import.meta.url));
|
|
22381
22318
|
const candidates = [
|
|
22382
|
-
|
|
22319
|
+
join29(dir, "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
22383
22320
|
// src/../public/js/
|
|
22384
|
-
|
|
22321
|
+
join29(dir, "..", "..", "public", "js", "tina4-dev-admin.min.js"),
|
|
22385
22322
|
// deeper nesting
|
|
22386
|
-
|
|
22387
|
-
|
|
22323
|
+
resolve20(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin.min.js"),
|
|
22324
|
+
resolve20(process.cwd(), "public", "js", "tina4-dev-admin.min.js")
|
|
22388
22325
|
// project public/
|
|
22389
22326
|
];
|
|
22390
22327
|
for (const jsPath of candidates) {
|
|
22391
|
-
if (
|
|
22328
|
+
if (existsSync26(jsPath)) {
|
|
22392
22329
|
try {
|
|
22393
|
-
const content =
|
|
22330
|
+
const content = readFileSync26(jsPath, "utf-8");
|
|
22394
22331
|
res.raw.writeHead(200, { "Content-Type": "application/javascript; charset=utf-8", "Cache-Control": "no-cache" });
|
|
22395
22332
|
res.raw.end(content);
|
|
22396
22333
|
return;
|
|
@@ -22851,8 +22788,12 @@ function sanitizeSecurity(reqs, schemes) {
|
|
|
22851
22788
|
function generate(routes, models = []) {
|
|
22852
22789
|
const info = {
|
|
22853
22790
|
title: process.env.TINA4_SWAGGER_TITLE ?? "Tina4 API",
|
|
22854
|
-
version
|
|
22855
|
-
|
|
22791
|
+
// The app's version, defaulting to 1.0.0 — NOT the framework's (Node shipped
|
|
22792
|
+
// 0.0.1). description defaults to the empty string, not a canned sentence.
|
|
22793
|
+
// Both are the settled cross-framework defaults (parity with the Python
|
|
22794
|
+
// master); TINA4_SWAGGER_VERSION / _DESCRIPTION still override.
|
|
22795
|
+
version: process.env.TINA4_SWAGGER_VERSION ?? "1.0.0",
|
|
22796
|
+
description: process.env.TINA4_SWAGGER_DESCRIPTION ?? ""
|
|
22856
22797
|
};
|
|
22857
22798
|
const contactEmail = (process.env.TINA4_SWAGGER_CONTACT_EMAIL ?? "").trim();
|
|
22858
22799
|
const contactName = (process.env.TINA4_SWAGGER_CONTACT_TEAM ?? "").trim();
|
|
@@ -22885,9 +22826,11 @@ function generate(routes, models = []) {
|
|
|
22885
22826
|
const includePrefixes = csv(process.env.TINA4_SWAGGER_INCLUDE);
|
|
22886
22827
|
const excludePrefixes = csv(process.env.TINA4_SWAGGER_EXCLUDE);
|
|
22887
22828
|
const refSchemas = /* @__PURE__ */ new Set();
|
|
22829
|
+
const tableToSchema = /* @__PURE__ */ new Map();
|
|
22888
22830
|
for (const model of models) {
|
|
22889
|
-
const
|
|
22890
|
-
|
|
22831
|
+
const schemaKey = schemaNameForModel(model);
|
|
22832
|
+
tableToSchema.set(model.tableName, schemaKey);
|
|
22833
|
+
spec.components.schemas[schemaKey] = modelToSchema(model);
|
|
22891
22834
|
}
|
|
22892
22835
|
const usedTags = [];
|
|
22893
22836
|
const seenIds = /* @__PURE__ */ new Set();
|
|
@@ -22914,11 +22857,11 @@ function generate(routes, models = []) {
|
|
|
22914
22857
|
if (route.meta?.deprecated) operation.deprecated = true;
|
|
22915
22858
|
const pathParams = extractPathParams(route.pattern);
|
|
22916
22859
|
if (pathParams.length > 0) {
|
|
22917
|
-
operation.parameters = pathParams.map((name) => ({
|
|
22860
|
+
operation.parameters = pathParams.map(({ name, schema }) => ({
|
|
22918
22861
|
name,
|
|
22919
22862
|
in: "path",
|
|
22920
22863
|
required: true,
|
|
22921
|
-
schema
|
|
22864
|
+
schema
|
|
22922
22865
|
}));
|
|
22923
22866
|
}
|
|
22924
22867
|
if (method === "get" && !route.pattern.includes("[id]") && !route.pattern.includes("[...")) {
|
|
@@ -22944,19 +22887,20 @@ function generate(routes, models = []) {
|
|
|
22944
22887
|
};
|
|
22945
22888
|
} else if (method === "post" || method === "put") {
|
|
22946
22889
|
const modelName = inferModelFromPath(route.pattern);
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
};
|
|
22890
|
+
const schemaKey = modelName ? tableToSchema.get(modelName) : void 0;
|
|
22891
|
+
if (schemaKey) {
|
|
22892
|
+
const sref = `#/components/schemas/${schemaKey}`;
|
|
22893
|
+
const media = { schema: { $ref: sref } };
|
|
22951
22894
|
if (route.meta?.example !== void 0) media.example = route.meta.example;
|
|
22952
22895
|
operation.requestBody = {
|
|
22953
22896
|
required: true,
|
|
22954
22897
|
content: { "application/json": media }
|
|
22955
22898
|
};
|
|
22956
|
-
|
|
22957
|
-
|
|
22958
|
-
|
|
22959
|
-
|
|
22899
|
+
if (route.meta?.responses === void 0) {
|
|
22900
|
+
operation.responses = {
|
|
22901
|
+
"200": { description: "Successful response", content: { "application/json": { schema: { $ref: sref } } } }
|
|
22902
|
+
};
|
|
22903
|
+
}
|
|
22960
22904
|
} else if (route.meta?.example !== void 0) {
|
|
22961
22905
|
operation.requestBody = {
|
|
22962
22906
|
content: { "application/json": { schema: inferSchema(route.meta.example), example: route.meta.example } }
|
|
@@ -23043,6 +22987,21 @@ function resolveServers() {
|
|
|
23043
22987
|
const dev = (process.env.SWAGGER_DEV_URL ?? "").trim();
|
|
23044
22988
|
return dev.length > 0 ? [{ url: dev }] : [{ url: "/" }];
|
|
23045
22989
|
}
|
|
22990
|
+
function schemaNameForModel(model) {
|
|
22991
|
+
const explicit = model.className?.trim();
|
|
22992
|
+
if (explicit) return explicit;
|
|
22993
|
+
return deriveClassName(model.tableName);
|
|
22994
|
+
}
|
|
22995
|
+
function deriveClassName(tableName) {
|
|
22996
|
+
return singularize(tableName).split(/[_\s-]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") || tableName;
|
|
22997
|
+
}
|
|
22998
|
+
function singularize(word) {
|
|
22999
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
23000
|
+
if (/(ses|xes|zes|ches|shes)$/i.test(word)) return word.slice(0, -2);
|
|
23001
|
+
if (/ss$/i.test(word)) return word;
|
|
23002
|
+
if (/s$/i.test(word) && word.length > 1) return word.slice(0, -1);
|
|
23003
|
+
return word;
|
|
23004
|
+
}
|
|
23046
23005
|
function modelToSchema(model) {
|
|
23047
23006
|
const properties = {};
|
|
23048
23007
|
const required = [];
|
|
@@ -23116,15 +23075,35 @@ function inferSchema(value) {
|
|
|
23116
23075
|
if (typeof value === "number") return { type: Number.isInteger(value) ? "integer" : "number" };
|
|
23117
23076
|
return { type: "string" };
|
|
23118
23077
|
}
|
|
23078
|
+
function segmentParam(segment) {
|
|
23079
|
+
if (segment.startsWith("{") && segment.endsWith("}")) {
|
|
23080
|
+
const inner = segment.slice(1, -1);
|
|
23081
|
+
if (inner.startsWith("...")) return { name: inner.slice(3), type: "string" };
|
|
23082
|
+
const colon = inner.indexOf(":");
|
|
23083
|
+
if (colon >= 0) return { name: inner.slice(0, colon), type: inner.slice(colon + 1) };
|
|
23084
|
+
return { name: inner, type: "string" };
|
|
23085
|
+
}
|
|
23086
|
+
if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
23087
|
+
const inner = segment.slice(1, -1);
|
|
23088
|
+
return { name: inner.startsWith("...") ? inner.slice(3) : inner, type: "string" };
|
|
23089
|
+
}
|
|
23090
|
+
if (segment.startsWith(":") && segment.length > 1) {
|
|
23091
|
+
return { name: segment.slice(1), type: "string" };
|
|
23092
|
+
}
|
|
23093
|
+
return null;
|
|
23094
|
+
}
|
|
23119
23095
|
function patternToOpenAPI(pattern) {
|
|
23120
|
-
return pattern.
|
|
23096
|
+
return pattern.split("/").map((segment) => {
|
|
23097
|
+
const p = segmentParam(segment);
|
|
23098
|
+
return p ? `{${p.name}}` : segment;
|
|
23099
|
+
}).join("/");
|
|
23121
23100
|
}
|
|
23122
23101
|
function extractPathParams(pattern) {
|
|
23123
23102
|
const params = [];
|
|
23124
|
-
const
|
|
23125
|
-
|
|
23126
|
-
|
|
23127
|
-
params.push(
|
|
23103
|
+
for (const segment of pattern.split("/")) {
|
|
23104
|
+
const p = segmentParam(segment);
|
|
23105
|
+
if (!p) continue;
|
|
23106
|
+
params.push({ name: p.name, schema: { ...PARAM_TYPE_SCHEMA[p.type] ?? { type: "string" } } });
|
|
23128
23107
|
}
|
|
23129
23108
|
return params;
|
|
23130
23109
|
}
|
|
@@ -23146,8 +23125,12 @@ function inferModelFromPath(pattern) {
|
|
|
23146
23125
|
if (rest.length === 1 && /^[[{]\.{0,3}\w+[\]}]$/.test(rest[0])) return candidate;
|
|
23147
23126
|
return null;
|
|
23148
23127
|
}
|
|
23128
|
+
function operationIdBase(method, openApiPath) {
|
|
23129
|
+
const clean = openApiPath.replace(/^\/+|\/+$/g, "").replace(/\//g, "_").replace(/\.\.\./g, "").replace(/[{}]/g, "").replace(/\*/g, "wildcard");
|
|
23130
|
+
return clean ? `${method}_${clean}` : method;
|
|
23131
|
+
}
|
|
23149
23132
|
function uniqueOperationId(method, openApiPath, seen) {
|
|
23150
|
-
const base = (method
|
|
23133
|
+
const base = operationIdBase(method, openApiPath);
|
|
23151
23134
|
let oid = base;
|
|
23152
23135
|
let n = 2;
|
|
23153
23136
|
while (seen.has(oid)) {
|
|
@@ -23157,13 +23140,25 @@ function uniqueOperationId(method, openApiPath, seen) {
|
|
|
23157
23140
|
seen.add(oid);
|
|
23158
23141
|
return oid;
|
|
23159
23142
|
}
|
|
23160
|
-
var WRITE_METHODS, registeredSchemes, registeredSchemas;
|
|
23143
|
+
var WRITE_METHODS, registeredSchemes, registeredSchemas, PARAM_TYPE_SCHEMA;
|
|
23161
23144
|
var init_generator = __esm({
|
|
23162
23145
|
"../swagger/src/generator.ts"() {
|
|
23163
23146
|
"use strict";
|
|
23164
23147
|
WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
23165
23148
|
registeredSchemes = {};
|
|
23166
23149
|
registeredSchemas = {};
|
|
23150
|
+
PARAM_TYPE_SCHEMA = {
|
|
23151
|
+
int: { type: "integer" },
|
|
23152
|
+
integer: { type: "integer" },
|
|
23153
|
+
float: { type: "number" },
|
|
23154
|
+
number: { type: "number" },
|
|
23155
|
+
uuid: { type: "string", format: "uuid" },
|
|
23156
|
+
slug: { type: "string", pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
23157
|
+
alpha: { type: "string", pattern: "^[A-Za-z]+$" },
|
|
23158
|
+
alnum: { type: "string", pattern: "^[A-Za-z0-9]+$" },
|
|
23159
|
+
path: { type: "string" },
|
|
23160
|
+
string: { type: "string" }
|
|
23161
|
+
};
|
|
23167
23162
|
}
|
|
23168
23163
|
});
|
|
23169
23164
|
|
|
@@ -23483,10 +23478,29 @@ function openBrowser(url) {
|
|
|
23483
23478
|
}, 2e3);
|
|
23484
23479
|
}
|
|
23485
23480
|
function resolvePortAndHost(config) {
|
|
23486
|
-
const
|
|
23481
|
+
const tina4Port = process.env.TINA4_PORT;
|
|
23482
|
+
const legacyPort = process.env.PORT;
|
|
23483
|
+
let port;
|
|
23484
|
+
if (config?.port !== void 0) {
|
|
23485
|
+
port = config.port;
|
|
23486
|
+
} else if (tina4Port && /^\d+$/.test(tina4Port)) {
|
|
23487
|
+
port = parseInt(tina4Port, 10);
|
|
23488
|
+
} else if (legacyPort && /^\d+$/.test(legacyPort)) {
|
|
23489
|
+
port = parseInt(legacyPort, 10);
|
|
23490
|
+
warnDeprecatedPort(port);
|
|
23491
|
+
} else {
|
|
23492
|
+
port = 7148;
|
|
23493
|
+
}
|
|
23487
23494
|
const host = config?.host ?? process.env.TINA4_HOST ?? process.env.HOST ?? "0.0.0.0";
|
|
23488
23495
|
return { port, host };
|
|
23489
23496
|
}
|
|
23497
|
+
function warnDeprecatedPort(port) {
|
|
23498
|
+
if (portDeprecationWarned) return;
|
|
23499
|
+
portDeprecationWarned = true;
|
|
23500
|
+
Log.warning(
|
|
23501
|
+
`PORT is deprecated and will be removed in 3.14 - use TINA4_PORT instead (binding port ${port} from PORT)`
|
|
23502
|
+
);
|
|
23503
|
+
}
|
|
23490
23504
|
function isBannerSuppressed() {
|
|
23491
23505
|
return isTruthy(process.env.TINA4_SUPPRESS);
|
|
23492
23506
|
}
|
|
@@ -23758,6 +23772,29 @@ function deployGallery(name) {
|
|
|
23758
23772
|
</body>
|
|
23759
23773
|
</html>`;
|
|
23760
23774
|
}
|
|
23775
|
+
function startLoopWatchdog() {
|
|
23776
|
+
const raw = (process.env.TINA4_LOOP_LAG_WARN_MS ?? "").trim();
|
|
23777
|
+
const threshold = /^\d+$/.test(raw) ? parseInt(raw, 10) : 250;
|
|
23778
|
+
if (threshold <= 0) {
|
|
23779
|
+
return { stop: () => {
|
|
23780
|
+
} };
|
|
23781
|
+
}
|
|
23782
|
+
let last = Date.now();
|
|
23783
|
+
let warned = 0;
|
|
23784
|
+
const timer = setInterval(() => {
|
|
23785
|
+
const now = Date.now();
|
|
23786
|
+
const lag = now - last - LOOP_WATCHDOG_TICK_MS;
|
|
23787
|
+
last = now;
|
|
23788
|
+
if (lag < threshold) return;
|
|
23789
|
+
warned++;
|
|
23790
|
+
if (warned > 5 && warned % 20 !== 0) return;
|
|
23791
|
+
Log.warning(
|
|
23792
|
+
`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.`
|
|
23793
|
+
);
|
|
23794
|
+
}, LOOP_WATCHDOG_TICK_MS);
|
|
23795
|
+
timer.unref();
|
|
23796
|
+
return { stop: () => clearInterval(timer) };
|
|
23797
|
+
}
|
|
23761
23798
|
async function start(config) {
|
|
23762
23799
|
const isManaged = process.argv.includes("--managed");
|
|
23763
23800
|
if (!isManaged && process.env.TINA4_OVERRIDE_CLIENT !== "true") {
|
|
@@ -24007,7 +24044,9 @@ async function startServer(config) {
|
|
|
24007
24044
|
const resolved = resolvePortAndHost(config);
|
|
24008
24045
|
const host = resolved.host;
|
|
24009
24046
|
let port = resolved.port;
|
|
24010
|
-
|
|
24047
|
+
if (!cluster.isWorker) {
|
|
24048
|
+
port = findAvailablePort(port);
|
|
24049
|
+
}
|
|
24011
24050
|
const isProduction = (process.env.TINA4_PRODUCTION ?? "").toLowerCase() === "true";
|
|
24012
24051
|
if (cluster.isPrimary && isProduction) {
|
|
24013
24052
|
const numCPUs = os2.cpus().length;
|
|
@@ -24223,7 +24262,20 @@ ${reset2}
|
|
|
24223
24262
|
await sessionAutoStart(rawReq, rawRes, req2);
|
|
24224
24263
|
await middleware.run(req2, res);
|
|
24225
24264
|
if (res.raw.writableEnded) return;
|
|
24226
|
-
|
|
24265
|
+
try {
|
|
24266
|
+
await req2.parseBody();
|
|
24267
|
+
} catch (err) {
|
|
24268
|
+
const status2 = err?.statusCode;
|
|
24269
|
+
if (typeof status2 === "number" && status2 >= 400 && status2 < 500) {
|
|
24270
|
+
if (!rawRes.writableEnded) {
|
|
24271
|
+
rawRes.statusCode = status2;
|
|
24272
|
+
rawRes.setHeader("content-type", "application/json");
|
|
24273
|
+
rawRes.end(JSON.stringify({ error: err.message }));
|
|
24274
|
+
}
|
|
24275
|
+
return;
|
|
24276
|
+
}
|
|
24277
|
+
throw err;
|
|
24278
|
+
}
|
|
24227
24279
|
const pathname = req2.path;
|
|
24228
24280
|
const reqStartTime = DevAdmin.isEnabled() ? Date.now() : 0;
|
|
24229
24281
|
const matchedPattern = { value: "" };
|
|
@@ -24422,8 +24474,10 @@ ${reset2}
|
|
|
24422
24474
|
};
|
|
24423
24475
|
process.on("SIGTERM", onSigterm);
|
|
24424
24476
|
process.on("SIGINT", onSigint);
|
|
24477
|
+
const loopWatchdog = startLoopWatchdog();
|
|
24425
24478
|
resolvePromise({
|
|
24426
24479
|
close: () => {
|
|
24480
|
+
loopWatchdog.stop();
|
|
24427
24481
|
process.off("SIGTERM", onSigterm);
|
|
24428
24482
|
process.off("SIGINT", onSigint);
|
|
24429
24483
|
stopAllBackgroundTasks();
|
|
@@ -24438,7 +24492,7 @@ ${reset2}
|
|
|
24438
24492
|
});
|
|
24439
24493
|
});
|
|
24440
24494
|
}
|
|
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;
|
|
24495
|
+
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
24496
|
var init_server = __esm({
|
|
24443
24497
|
"../core/src/server.ts"() {
|
|
24444
24498
|
"use strict";
|
|
@@ -24491,6 +24545,7 @@ var init_server = __esm({
|
|
|
24491
24545
|
SWAGGER_VERSION: "TINA4_SWAGGER_VERSION",
|
|
24492
24546
|
ORM_PLURAL_TABLE_NAMES: "TINA4_ORM_PLURAL_TABLE_NAMES"
|
|
24493
24547
|
};
|
|
24548
|
+
portDeprecationWarned = false;
|
|
24494
24549
|
TEMPLATE_PAGES_DIR = "pages";
|
|
24495
24550
|
HTTP_REASON_PHRASES = {
|
|
24496
24551
|
100: "Continue",
|
|
@@ -24527,6 +24582,7 @@ var init_server = __esm({
|
|
|
24527
24582
|
templateCache = null;
|
|
24528
24583
|
_dispatchFn = null;
|
|
24529
24584
|
_serverHandle = null;
|
|
24585
|
+
LOOP_WATCHDOG_TICK_MS = 100;
|
|
24530
24586
|
FALLBACK_STAGES = [
|
|
24531
24587
|
serveTemplateFallback,
|
|
24532
24588
|
serveLandingPage,
|
|
@@ -25082,433 +25138,6 @@ var init_fakeData = __esm({
|
|
|
25082
25138
|
}
|
|
25083
25139
|
});
|
|
25084
25140
|
|
|
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
25141
|
// ../core/src/mqttMessage.ts
|
|
25513
25142
|
var MqttMessage;
|
|
25514
25143
|
var init_mqttMessage = __esm({
|
|
@@ -25582,7 +25211,7 @@ var init_mqttMessage = __esm({
|
|
|
25582
25211
|
import net2 from "node:net";
|
|
25583
25212
|
import tls from "node:tls";
|
|
25584
25213
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
25585
|
-
import { existsSync as
|
|
25214
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "node:fs";
|
|
25586
25215
|
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
25216
|
var init_mqtt = __esm({
|
|
25588
25217
|
"../core/src/mqtt.ts"() {
|
|
@@ -25785,7 +25414,7 @@ var init_mqtt = __esm({
|
|
|
25785
25414
|
*/
|
|
25786
25415
|
async connect() {
|
|
25787
25416
|
this.closeSocket();
|
|
25788
|
-
if (this.secure && this.tlsVerify && this.caFile && !
|
|
25417
|
+
if (this.secure && this.tlsVerify && this.caFile && !existsSync23(this.caFile)) {
|
|
25789
25418
|
throw new MqttError(
|
|
25790
25419
|
`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
25420
|
);
|
|
@@ -26022,7 +25651,7 @@ var init_mqtt = __esm({
|
|
|
26022
25651
|
* a later client.
|
|
26023
25652
|
*/
|
|
26024
25653
|
openSocket() {
|
|
26025
|
-
return new Promise((
|
|
25654
|
+
return new Promise((resolve20, reject) => {
|
|
26026
25655
|
let settled = false;
|
|
26027
25656
|
const settle = (fn) => {
|
|
26028
25657
|
if (settled) return;
|
|
@@ -26047,10 +25676,10 @@ var init_mqtt = __esm({
|
|
|
26047
25676
|
servername: this.host,
|
|
26048
25677
|
rejectUnauthorized: this.tlsVerify
|
|
26049
25678
|
};
|
|
26050
|
-
if (this.tlsVerify && this.caFile) opts.ca =
|
|
26051
|
-
sock = tls.connect(opts, () => settle(() =>
|
|
25679
|
+
if (this.tlsVerify && this.caFile) opts.ca = readFileSync21(this.caFile);
|
|
25680
|
+
sock = tls.connect(opts, () => settle(() => resolve20(sock)));
|
|
26052
25681
|
} else {
|
|
26053
|
-
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() =>
|
|
25682
|
+
sock = net2.createConnection({ host: this.host, port: this.port }, () => settle(() => resolve20(sock)));
|
|
26054
25683
|
}
|
|
26055
25684
|
sock.once("error", (err) => {
|
|
26056
25685
|
settle(() => {
|
|
@@ -26089,13 +25718,13 @@ var init_mqtt = __esm({
|
|
|
26089
25718
|
writePacket(header, body) {
|
|
26090
25719
|
if (this.socket === null) return Promise.reject(new MqttError("not connected to an MQTT broker"));
|
|
26091
25720
|
const packet = Buffer.concat([Buffer.from([header]), _Mqtt.encodeRemainingLength(body.length), body]);
|
|
26092
|
-
return new Promise((
|
|
25721
|
+
return new Promise((resolve20, reject) => {
|
|
26093
25722
|
this.socket.write(packet, (err) => {
|
|
26094
25723
|
if (err) {
|
|
26095
25724
|
reject(new MqttError(`MQTT write failed: ${err.message}`));
|
|
26096
25725
|
} else {
|
|
26097
25726
|
this.lastWriteAt = Date.now();
|
|
26098
|
-
|
|
25727
|
+
resolve20();
|
|
26099
25728
|
}
|
|
26100
25729
|
});
|
|
26101
25730
|
});
|
|
@@ -26128,7 +25757,7 @@ var init_mqtt = __esm({
|
|
|
26128
25757
|
if (this.readBuffer.length >= need) return Promise.resolve(this.take(need));
|
|
26129
25758
|
if (this.socket === null) return Promise.reject(this.socketError ?? new MqttError("not connected to an MQTT broker"));
|
|
26130
25759
|
if (this.socketError !== null) return Promise.reject(this.socketError);
|
|
26131
|
-
return new Promise((
|
|
25760
|
+
return new Promise((resolve20, reject) => {
|
|
26132
25761
|
let timer = null;
|
|
26133
25762
|
if (deadline !== null) {
|
|
26134
25763
|
const remaining = deadline - Date.now();
|
|
@@ -26143,7 +25772,7 @@ var init_mqtt = __esm({
|
|
|
26143
25772
|
}
|
|
26144
25773
|
}, remaining);
|
|
26145
25774
|
}
|
|
26146
|
-
this.waiter = { need, resolve:
|
|
25775
|
+
this.waiter = { need, resolve: resolve20, reject, timer };
|
|
26147
25776
|
this.serviceWaiter();
|
|
26148
25777
|
});
|
|
26149
25778
|
}
|
|
@@ -26267,8 +25896,8 @@ var init_mqtt = __esm({
|
|
|
26267
25896
|
});
|
|
26268
25897
|
|
|
26269
25898
|
// ../core/src/service.ts
|
|
26270
|
-
import { readdirSync as
|
|
26271
|
-
import { join as
|
|
25899
|
+
import { readdirSync as readdirSync16, statSync as statSync16, watchFile, unwatchFile } from "node:fs";
|
|
25900
|
+
import { join as join23, extname as extname7 } from "node:path";
|
|
26272
25901
|
import { pathToFileURL } from "node:url";
|
|
26273
25902
|
function matchCronField(field, value) {
|
|
26274
25903
|
if (field === "*") return true;
|
|
@@ -26434,14 +26063,14 @@ var init_service = __esm({
|
|
|
26434
26063
|
const discovered = [];
|
|
26435
26064
|
let entries;
|
|
26436
26065
|
try {
|
|
26437
|
-
entries =
|
|
26066
|
+
entries = readdirSync16(dir);
|
|
26438
26067
|
} catch {
|
|
26439
26068
|
return discovered;
|
|
26440
26069
|
}
|
|
26441
26070
|
for (const entry of entries) {
|
|
26442
26071
|
const ext = extname7(entry);
|
|
26443
26072
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
26444
|
-
const fullPath =
|
|
26073
|
+
const fullPath = join23(dir, entry);
|
|
26445
26074
|
const stat = statSync16(fullPath);
|
|
26446
26075
|
if (!stat.isFile()) continue;
|
|
26447
26076
|
try {
|
|
@@ -26550,14 +26179,14 @@ var init_service = __esm({
|
|
|
26550
26179
|
const dir = serviceDir ?? process.env.TINA4_SERVICE_DIR ?? "src/services";
|
|
26551
26180
|
let entries;
|
|
26552
26181
|
try {
|
|
26553
|
-
entries =
|
|
26182
|
+
entries = readdirSync16(dir);
|
|
26554
26183
|
} catch {
|
|
26555
26184
|
return;
|
|
26556
26185
|
}
|
|
26557
26186
|
for (const entry of entries) {
|
|
26558
26187
|
const ext = extname7(entry);
|
|
26559
26188
|
if (ext !== ".ts" && ext !== ".js") continue;
|
|
26560
|
-
const fullPath =
|
|
26189
|
+
const fullPath = join23(dir, entry);
|
|
26561
26190
|
if (watchedFiles.has(fullPath)) continue;
|
|
26562
26191
|
watchedFiles.add(fullPath);
|
|
26563
26192
|
watchFile(fullPath, { interval: 1e3 }, async () => {
|
|
@@ -26601,7 +26230,7 @@ import https from "node:https";
|
|
|
26601
26230
|
import { URL as URL2 } from "node:url";
|
|
26602
26231
|
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
26603
26232
|
import { promises as fsp, createWriteStream } from "node:fs";
|
|
26604
|
-
import { basename as
|
|
26233
|
+
import { basename as basename4 } from "node:path";
|
|
26605
26234
|
import { pipeline } from "node:stream/promises";
|
|
26606
26235
|
function sameOrigin(urlA, urlB) {
|
|
26607
26236
|
try {
|
|
@@ -26891,7 +26520,7 @@ var init_api = __esm({
|
|
|
26891
26520
|
error: err instanceof Error ? err.message : String(err)
|
|
26892
26521
|
};
|
|
26893
26522
|
}
|
|
26894
|
-
uploadName = filename ||
|
|
26523
|
+
uploadName = filename || basename4(filePath);
|
|
26895
26524
|
} else {
|
|
26896
26525
|
return { http_code: null, body: null, headers: {}, error: "upload requires filePath or fileBytes" };
|
|
26897
26526
|
}
|
|
@@ -27106,12 +26735,12 @@ var init_api = __esm({
|
|
|
27106
26735
|
* authenticate to.
|
|
27107
26736
|
*/
|
|
27108
26737
|
performRequest(method, url, headers, data, redirectsLeft) {
|
|
27109
|
-
return new Promise((
|
|
26738
|
+
return new Promise((resolve20) => {
|
|
27110
26739
|
let parsed;
|
|
27111
26740
|
try {
|
|
27112
26741
|
parsed = new URL2(url);
|
|
27113
26742
|
} catch (err) {
|
|
27114
|
-
|
|
26743
|
+
resolve20({ kind: "error", error: err instanceof Error ? err.message : String(err) });
|
|
27115
26744
|
return;
|
|
27116
26745
|
}
|
|
27117
26746
|
const isHttps = parsed.protocol === "https:";
|
|
@@ -27136,7 +26765,7 @@ var init_api = __esm({
|
|
|
27136
26765
|
try {
|
|
27137
26766
|
nextUrl = new URL2(location, url).toString();
|
|
27138
26767
|
} catch {
|
|
27139
|
-
|
|
26768
|
+
resolve20({ kind: "response", res });
|
|
27140
26769
|
return;
|
|
27141
26770
|
}
|
|
27142
26771
|
const crossOrigin = !sameOrigin(url, nextUrl);
|
|
@@ -27154,17 +26783,17 @@ var init_api = __esm({
|
|
|
27154
26783
|
deleteHeaderCaseInsensitive(nextHeaders, name);
|
|
27155
26784
|
}
|
|
27156
26785
|
}
|
|
27157
|
-
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(
|
|
26786
|
+
this.performRequest(nextMethod, nextUrl, nextHeaders, nextData, redirectsLeft - 1).then(resolve20);
|
|
27158
26787
|
return;
|
|
27159
26788
|
}
|
|
27160
|
-
|
|
26789
|
+
resolve20({ kind: "response", res });
|
|
27161
26790
|
});
|
|
27162
26791
|
req2.on("timeout", () => {
|
|
27163
26792
|
req2.destroy();
|
|
27164
|
-
|
|
26793
|
+
resolve20({ kind: "error", error: `Request timed out after ${this.timeout}s` });
|
|
27165
26794
|
});
|
|
27166
26795
|
req2.on("error", (err) => {
|
|
27167
|
-
|
|
26796
|
+
resolve20({ kind: "error", error: err.message });
|
|
27168
26797
|
});
|
|
27169
26798
|
if (data) {
|
|
27170
26799
|
req2.write(data);
|
|
@@ -27174,7 +26803,7 @@ var init_api = __esm({
|
|
|
27174
26803
|
}
|
|
27175
26804
|
/** Buffer a response body, parse JSON if possible, and store cookies. */
|
|
27176
26805
|
readResponse(res) {
|
|
27177
|
-
return new Promise((
|
|
26806
|
+
return new Promise((resolve20) => {
|
|
27178
26807
|
const chunks = [];
|
|
27179
26808
|
res.on("data", (chunk) => {
|
|
27180
26809
|
chunks.push(chunk);
|
|
@@ -27189,7 +26818,7 @@ var init_api = __esm({
|
|
|
27189
26818
|
} catch {
|
|
27190
26819
|
parsed = raw;
|
|
27191
26820
|
}
|
|
27192
|
-
|
|
26821
|
+
resolve20({
|
|
27193
26822
|
http_code: res.statusCode ?? null,
|
|
27194
26823
|
body: parsed,
|
|
27195
26824
|
headers: respHeaders,
|
|
@@ -27197,7 +26826,7 @@ var init_api = __esm({
|
|
|
27197
26826
|
});
|
|
27198
26827
|
});
|
|
27199
26828
|
res.on("error", (err) => {
|
|
27200
|
-
|
|
26829
|
+
resolve20({ http_code: null, body: null, headers: {}, error: err.message });
|
|
27201
26830
|
});
|
|
27202
26831
|
});
|
|
27203
26832
|
}
|
|
@@ -27250,14 +26879,14 @@ var init_api = __esm({
|
|
|
27250
26879
|
// ../core/src/messenger.ts
|
|
27251
26880
|
import net3 from "node:net";
|
|
27252
26881
|
import tls2 from "node:tls";
|
|
27253
|
-
import { readFileSync as
|
|
27254
|
-
import { basename as
|
|
26882
|
+
import { readFileSync as readFileSync22 } from "node:fs";
|
|
26883
|
+
import { basename as basename5 } from "node:path";
|
|
27255
26884
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
27256
26885
|
function tlsRejectUnauthorized() {
|
|
27257
26886
|
return !isTruthy(process.env.TINA4_MAIL_TLS_INSECURE);
|
|
27258
26887
|
}
|
|
27259
26888
|
function readResponse(socket) {
|
|
27260
|
-
return new Promise((
|
|
26889
|
+
return new Promise((resolve20, reject) => {
|
|
27261
26890
|
let buffer = "";
|
|
27262
26891
|
const onData = (chunk) => {
|
|
27263
26892
|
buffer += chunk.toString("utf-8");
|
|
@@ -27269,7 +26898,7 @@ function readResponse(socket) {
|
|
|
27269
26898
|
if (line.length >= 4 && line[3] === " ") {
|
|
27270
26899
|
socket.removeListener("data", onData);
|
|
27271
26900
|
socket.removeListener("error", onError);
|
|
27272
|
-
|
|
26901
|
+
resolve20({ code, text: buffer.trim() });
|
|
27273
26902
|
return;
|
|
27274
26903
|
}
|
|
27275
26904
|
}
|
|
@@ -27283,10 +26912,10 @@ function readResponse(socket) {
|
|
|
27283
26912
|
});
|
|
27284
26913
|
}
|
|
27285
26914
|
function sendCommand(socket, command) {
|
|
27286
|
-
return new Promise((
|
|
26915
|
+
return new Promise((resolve20, reject) => {
|
|
27287
26916
|
socket.write(command + "\r\n", "utf-8", (err) => {
|
|
27288
26917
|
if (err) return reject(err);
|
|
27289
|
-
readResponse(socket).then(
|
|
26918
|
+
readResponse(socket).then(resolve20, reject);
|
|
27290
26919
|
});
|
|
27291
26920
|
});
|
|
27292
26921
|
}
|
|
@@ -27342,8 +26971,8 @@ function buildMimeMessage(options) {
|
|
|
27342
26971
|
lines.push(options.body);
|
|
27343
26972
|
}
|
|
27344
26973
|
for (const filePath of options.attachments) {
|
|
27345
|
-
const fileName =
|
|
27346
|
-
const fileData =
|
|
26974
|
+
const fileName = basename5(filePath);
|
|
26975
|
+
const fileData = readFileSync22(filePath);
|
|
27347
26976
|
const base64Data = fileData.toString("base64");
|
|
27348
26977
|
lines.push("");
|
|
27349
26978
|
lines.push(`--${boundary}`);
|
|
@@ -27386,7 +27015,7 @@ function imapQuote(s) {
|
|
|
27386
27015
|
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
27387
27016
|
}
|
|
27388
27017
|
function imapReadLine(socket) {
|
|
27389
|
-
return new Promise((
|
|
27018
|
+
return new Promise((resolve20, reject) => {
|
|
27390
27019
|
let buffer = "";
|
|
27391
27020
|
const onData = (chunk) => {
|
|
27392
27021
|
buffer += chunk.toString("utf-8");
|
|
@@ -27394,7 +27023,7 @@ function imapReadLine(socket) {
|
|
|
27394
27023
|
if (nlIndex !== -1) {
|
|
27395
27024
|
socket.removeListener("data", onData);
|
|
27396
27025
|
socket.removeListener("error", onError);
|
|
27397
|
-
|
|
27026
|
+
resolve20(buffer);
|
|
27398
27027
|
}
|
|
27399
27028
|
};
|
|
27400
27029
|
const onError = (err) => {
|
|
@@ -27406,7 +27035,7 @@ function imapReadLine(socket) {
|
|
|
27406
27035
|
});
|
|
27407
27036
|
}
|
|
27408
27037
|
function imapCommand(socket, command) {
|
|
27409
|
-
return new Promise((
|
|
27038
|
+
return new Promise((resolve20, reject) => {
|
|
27410
27039
|
imapTagCounter++;
|
|
27411
27040
|
const tag = `T${imapTagCounter}`;
|
|
27412
27041
|
const fullCommand = `${tag} ${command}\r
|
|
@@ -27417,7 +27046,7 @@ function imapCommand(socket, command) {
|
|
|
27417
27046
|
if (buffer.includes(`${tag} OK`)) {
|
|
27418
27047
|
socket.removeListener("data", onData);
|
|
27419
27048
|
socket.removeListener("error", onError);
|
|
27420
|
-
|
|
27049
|
+
resolve20(buffer);
|
|
27421
27050
|
return;
|
|
27422
27051
|
}
|
|
27423
27052
|
if (buffer.includes(`${tag} NO`) || buffer.includes(`${tag} BAD`)) {
|
|
@@ -27446,91 +27075,149 @@ function parseSearchResponse(response) {
|
|
|
27446
27075
|
if (!match) return [];
|
|
27447
27076
|
return match[1].trim().split(/\s+/).filter((s) => /^\d+$/.test(s));
|
|
27448
27077
|
}
|
|
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
|
-
};
|
|
27078
|
+
function extractRawMessage(response) {
|
|
27079
|
+
const m = response.match(/\{(\d+)\}\r\n/);
|
|
27080
|
+
if (!m) return response;
|
|
27081
|
+
const start2 = (m.index ?? 0) + m[0].length;
|
|
27082
|
+
return response.slice(start2, start2 + parseInt(m[1], 10));
|
|
27477
27083
|
}
|
|
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) : "";
|
|
27084
|
+
function parseMimeHeaders(section) {
|
|
27484
27085
|
const headers = {};
|
|
27485
|
-
const headerLines = headerSection.split(/\r\n/);
|
|
27486
27086
|
let currentKey = "";
|
|
27487
|
-
for (const line of
|
|
27087
|
+
for (const line of section.split(/\r\n/)) {
|
|
27488
27088
|
if (/^\s/.test(line) && currentKey) {
|
|
27489
27089
|
headers[currentKey] += " " + line.trim();
|
|
27490
27090
|
} else {
|
|
27491
|
-
const
|
|
27492
|
-
if (
|
|
27493
|
-
currentKey = line.substring(0,
|
|
27494
|
-
headers[currentKey] = line.substring(
|
|
27091
|
+
const idx = line.indexOf(":");
|
|
27092
|
+
if (idx > 0) {
|
|
27093
|
+
currentKey = line.substring(0, idx).trim().toLowerCase();
|
|
27094
|
+
headers[currentKey] = line.substring(idx + 1).trim();
|
|
27095
|
+
}
|
|
27096
|
+
}
|
|
27097
|
+
}
|
|
27098
|
+
return headers;
|
|
27099
|
+
}
|
|
27100
|
+
function decodeTransfer(body, encoding) {
|
|
27101
|
+
const enc = encoding.toLowerCase().trim();
|
|
27102
|
+
if (enc === "base64") {
|
|
27103
|
+
try {
|
|
27104
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
27105
|
+
} catch {
|
|
27106
|
+
return body;
|
|
27107
|
+
}
|
|
27108
|
+
}
|
|
27109
|
+
if (enc === "quoted-printable") {
|
|
27110
|
+
return body.replace(/=\r?\n/g, "").replace(/=([0-9A-Fa-f]{2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)));
|
|
27111
|
+
}
|
|
27112
|
+
return body;
|
|
27113
|
+
}
|
|
27114
|
+
function decodeAttachmentBytes(body, encoding) {
|
|
27115
|
+
const enc = encoding.toLowerCase().trim();
|
|
27116
|
+
if (enc === "base64") {
|
|
27117
|
+
return Buffer.from(body.replace(/\s+/g, ""), "base64");
|
|
27118
|
+
}
|
|
27119
|
+
const trimmed = body.replace(/\r\n$/, "");
|
|
27120
|
+
if (enc === "quoted-printable") {
|
|
27121
|
+
const collapsed = trimmed.replace(/=\r?\n/g, "");
|
|
27122
|
+
const bytes = [];
|
|
27123
|
+
for (let i = 0; i < collapsed.length; i++) {
|
|
27124
|
+
const hex = collapsed.substring(i + 1, i + 3);
|
|
27125
|
+
if (collapsed[i] === "=" && /^[0-9A-Fa-f]{2}$/.test(hex)) {
|
|
27126
|
+
bytes.push(parseInt(hex, 16));
|
|
27127
|
+
i += 2;
|
|
27128
|
+
} else {
|
|
27129
|
+
bytes.push(collapsed.charCodeAt(i) & 255);
|
|
27495
27130
|
}
|
|
27496
27131
|
}
|
|
27132
|
+
return Buffer.from(bytes);
|
|
27497
27133
|
}
|
|
27134
|
+
return Buffer.from(trimmed, "utf-8");
|
|
27135
|
+
}
|
|
27136
|
+
function attachmentFilename(disposition, contentType) {
|
|
27137
|
+
const d = disposition.match(/filename="?([^";\r\n]+)"?/i);
|
|
27138
|
+
if (d) return d[1].trim();
|
|
27139
|
+
const c = contentType.match(/name="?([^";\r\n]+)"?/i);
|
|
27140
|
+
if (c) return c[1].trim();
|
|
27141
|
+
return "attachment";
|
|
27142
|
+
}
|
|
27143
|
+
function makeSnippet(bodyText, bodyHtml) {
|
|
27144
|
+
return (bodyText || bodyHtml || "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 200);
|
|
27145
|
+
}
|
|
27146
|
+
function toIsoDate(raw) {
|
|
27147
|
+
if (!raw) return "";
|
|
27148
|
+
const d = new Date(raw);
|
|
27149
|
+
return Number.isNaN(d.getTime()) ? raw : d.toISOString();
|
|
27150
|
+
}
|
|
27151
|
+
function parseMessage(response) {
|
|
27152
|
+
const raw = extractRawMessage(response);
|
|
27153
|
+
const headerEnd = raw.indexOf("\r\n\r\n");
|
|
27154
|
+
const headerSection = headerEnd >= 0 ? raw.substring(0, headerEnd) : raw;
|
|
27155
|
+
const bodySection = headerEnd >= 0 ? raw.substring(headerEnd + 4) : "";
|
|
27156
|
+
const headers = parseMimeHeaders(headerSection);
|
|
27498
27157
|
const contentType = headers["content-type"] ?? "text/plain";
|
|
27499
27158
|
let bodyText = "";
|
|
27500
27159
|
let bodyHtml = "";
|
|
27160
|
+
const attachments = [];
|
|
27501
27161
|
if (contentType.includes("multipart")) {
|
|
27502
27162
|
const boundaryMatch = contentType.match(/boundary="?([^";\s]+)"?/);
|
|
27503
27163
|
if (boundaryMatch) {
|
|
27504
|
-
const boundary = boundaryMatch[1];
|
|
27505
|
-
const
|
|
27506
|
-
|
|
27507
|
-
if (
|
|
27508
|
-
const
|
|
27509
|
-
|
|
27510
|
-
const
|
|
27511
|
-
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27164
|
+
const boundary = "--" + boundaryMatch[1];
|
|
27165
|
+
for (const part of bodySection.split(boundary)) {
|
|
27166
|
+
const trimmed = part.trim();
|
|
27167
|
+
if (trimmed === "" || trimmed === "--") continue;
|
|
27168
|
+
const pEnd = part.indexOf("\r\n\r\n");
|
|
27169
|
+
if (pEnd < 0) continue;
|
|
27170
|
+
const pHeaders = parseMimeHeaders(part.substring(0, pEnd));
|
|
27171
|
+
const pBody = part.substring(pEnd + 4);
|
|
27172
|
+
const cte = pHeaders["content-transfer-encoding"] ?? "";
|
|
27173
|
+
const pType = pHeaders["content-type"] ?? "text/plain";
|
|
27174
|
+
const disposition = pHeaders["content-disposition"] ?? "";
|
|
27175
|
+
if (/attachment/i.test(disposition)) {
|
|
27176
|
+
const content = decodeAttachmentBytes(pBody, cte);
|
|
27177
|
+
attachments.push({
|
|
27178
|
+
filename: attachmentFilename(disposition, pType),
|
|
27179
|
+
contentType: pType.split(";")[0].trim(),
|
|
27180
|
+
size: content.length,
|
|
27181
|
+
content
|
|
27182
|
+
});
|
|
27183
|
+
} else if (pType.includes("text/html")) {
|
|
27184
|
+
bodyHtml = decodeTransfer(pBody, cte).trim();
|
|
27185
|
+
} else if (pType.includes("text/plain")) {
|
|
27186
|
+
bodyText = decodeTransfer(pBody, cte).trim();
|
|
27515
27187
|
}
|
|
27516
27188
|
}
|
|
27517
27189
|
}
|
|
27518
27190
|
} else if (contentType.includes("text/html")) {
|
|
27519
|
-
bodyHtml = bodySection;
|
|
27191
|
+
bodyHtml = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
27520
27192
|
} else {
|
|
27521
|
-
bodyText = bodySection;
|
|
27193
|
+
bodyText = decodeTransfer(bodySection, headers["content-transfer-encoding"] ?? "").trim();
|
|
27522
27194
|
}
|
|
27523
|
-
|
|
27524
|
-
|
|
27195
|
+
return { headers, bodyText, bodyHtml, attachments };
|
|
27196
|
+
}
|
|
27197
|
+
function parseSummary(uid, response) {
|
|
27198
|
+
const { headers, bodyText, bodyHtml } = parseMessage(response);
|
|
27199
|
+
return {
|
|
27200
|
+
uid,
|
|
27201
|
+
subject: headers["subject"] ?? "",
|
|
27202
|
+
from: headers["from"] ?? "",
|
|
27203
|
+
to: headers["to"] ?? "",
|
|
27204
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
27205
|
+
snippet: makeSnippet(bodyText, bodyHtml),
|
|
27206
|
+
seen: /\\Seen/i.test(response)
|
|
27207
|
+
};
|
|
27208
|
+
}
|
|
27209
|
+
function parseFullMessage(uid, response) {
|
|
27210
|
+
const { headers, bodyText, bodyHtml, attachments } = parseMessage(response);
|
|
27525
27211
|
return {
|
|
27526
27212
|
uid,
|
|
27527
27213
|
subject: headers["subject"] ?? "",
|
|
27528
27214
|
from: headers["from"] ?? "",
|
|
27529
27215
|
to: headers["to"] ?? "",
|
|
27530
27216
|
cc: headers["cc"] ?? "",
|
|
27531
|
-
date: headers["date"] ?? "",
|
|
27217
|
+
date: toIsoDate(headers["date"] ?? ""),
|
|
27532
27218
|
bodyText,
|
|
27533
27219
|
bodyHtml,
|
|
27220
|
+
attachments,
|
|
27534
27221
|
headers
|
|
27535
27222
|
};
|
|
27536
27223
|
}
|
|
@@ -27646,89 +27333,89 @@ var init_messenger = __esm({
|
|
|
27646
27333
|
}
|
|
27647
27334
|
const messageId = `${randomUUID7()}@${this.host}`;
|
|
27648
27335
|
if (allRecipients.length === 0) {
|
|
27649
|
-
return { success: false, message: "No recipients specified" };
|
|
27336
|
+
return { success: false, message: "No recipients specified", id: null };
|
|
27650
27337
|
}
|
|
27651
27338
|
if (!this.fromAddress) {
|
|
27652
|
-
return { success: false, message: "No from address configured" };
|
|
27339
|
+
return { success: false, message: "No from address configured", id: null };
|
|
27653
27340
|
}
|
|
27654
27341
|
try {
|
|
27655
27342
|
let socket;
|
|
27656
27343
|
if (this.port === 465) {
|
|
27657
27344
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
27658
|
-
await new Promise((
|
|
27659
|
-
socket.once("secureConnect",
|
|
27345
|
+
await new Promise((resolve20, reject) => {
|
|
27346
|
+
socket.once("secureConnect", resolve20);
|
|
27660
27347
|
socket.once("error", reject);
|
|
27661
27348
|
});
|
|
27662
27349
|
} else {
|
|
27663
27350
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
27664
|
-
await new Promise((
|
|
27665
|
-
socket.once("connect",
|
|
27351
|
+
await new Promise((resolve20, reject) => {
|
|
27352
|
+
socket.once("connect", resolve20);
|
|
27666
27353
|
socket.once("error", reject);
|
|
27667
27354
|
});
|
|
27668
27355
|
}
|
|
27669
27356
|
const greeting = await readResponse(socket);
|
|
27670
27357
|
if (greeting.code !== 220) {
|
|
27671
27358
|
socket.destroy();
|
|
27672
|
-
return { success: false, message: `SMTP greeting failed: ${greeting.text}
|
|
27359
|
+
return { success: false, message: `SMTP greeting failed: ${greeting.text}`, id: null };
|
|
27673
27360
|
}
|
|
27674
27361
|
const ehlo = await sendCommand(socket, `EHLO ${this.host}`);
|
|
27675
27362
|
if (ehlo.code !== 250) {
|
|
27676
27363
|
socket.destroy();
|
|
27677
|
-
return { success: false, message: `EHLO failed: ${ehlo.text}
|
|
27364
|
+
return { success: false, message: `EHLO failed: ${ehlo.text}`, id: null };
|
|
27678
27365
|
}
|
|
27679
27366
|
if (this.useTls && this.port !== 465 && ehlo.text.includes("STARTTLS")) {
|
|
27680
27367
|
const starttls = await sendCommand(socket, "STARTTLS");
|
|
27681
27368
|
if (starttls.code !== 220) {
|
|
27682
27369
|
socket.destroy();
|
|
27683
|
-
return { success: false, message: `STARTTLS failed: ${starttls.text}
|
|
27370
|
+
return { success: false, message: `STARTTLS failed: ${starttls.text}`, id: null };
|
|
27684
27371
|
}
|
|
27685
27372
|
const plainSocket = socket;
|
|
27686
27373
|
socket = tls2.connect(
|
|
27687
27374
|
{ socket: plainSocket, host: this.host, rejectUnauthorized: tlsRejectUnauthorized() }
|
|
27688
27375
|
);
|
|
27689
|
-
await new Promise((
|
|
27690
|
-
socket.once("secureConnect",
|
|
27376
|
+
await new Promise((resolve20, reject) => {
|
|
27377
|
+
socket.once("secureConnect", resolve20);
|
|
27691
27378
|
socket.once("error", reject);
|
|
27692
27379
|
});
|
|
27693
27380
|
const ehlo2 = await sendCommand(socket, `EHLO ${this.host}`);
|
|
27694
27381
|
if (ehlo2.code !== 250) {
|
|
27695
27382
|
socket.destroy();
|
|
27696
|
-
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}
|
|
27383
|
+
return { success: false, message: `EHLO after STARTTLS failed: ${ehlo2.text}`, id: null };
|
|
27697
27384
|
}
|
|
27698
27385
|
}
|
|
27699
27386
|
if (this.username && this.password) {
|
|
27700
27387
|
const auth = await sendCommand(socket, "AUTH LOGIN");
|
|
27701
27388
|
if (auth.code !== 334) {
|
|
27702
27389
|
socket.destroy();
|
|
27703
|
-
return { success: false, message: `AUTH LOGIN failed: ${auth.text}
|
|
27390
|
+
return { success: false, message: `AUTH LOGIN failed: ${auth.text}`, id: null };
|
|
27704
27391
|
}
|
|
27705
27392
|
const userResp = await sendCommand(socket, Buffer.from(this.username).toString("base64"));
|
|
27706
27393
|
if (userResp.code !== 334) {
|
|
27707
27394
|
socket.destroy();
|
|
27708
|
-
return { success: false, message: `AUTH username failed: ${userResp.text}
|
|
27395
|
+
return { success: false, message: `AUTH username failed: ${userResp.text}`, id: null };
|
|
27709
27396
|
}
|
|
27710
27397
|
const passResp = await sendCommand(socket, Buffer.from(this.password).toString("base64"));
|
|
27711
27398
|
if (passResp.code !== 235) {
|
|
27712
27399
|
socket.destroy();
|
|
27713
|
-
return { success: false, message: `AUTH password failed: ${passResp.text}
|
|
27400
|
+
return { success: false, message: `AUTH password failed: ${passResp.text}`, id: null };
|
|
27714
27401
|
}
|
|
27715
27402
|
}
|
|
27716
27403
|
const mailFrom = await sendCommand(socket, `MAIL FROM:<${this.fromAddress}>`);
|
|
27717
27404
|
if (mailFrom.code !== 250) {
|
|
27718
27405
|
socket.destroy();
|
|
27719
|
-
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}
|
|
27406
|
+
return { success: false, message: `MAIL FROM failed: ${mailFrom.text}`, id: null };
|
|
27720
27407
|
}
|
|
27721
27408
|
for (const recipient of allRecipients) {
|
|
27722
27409
|
const rcpt = await sendCommand(socket, `RCPT TO:<${recipient}>`);
|
|
27723
27410
|
if (rcpt.code !== 250 && rcpt.code !== 251) {
|
|
27724
27411
|
socket.destroy();
|
|
27725
|
-
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}
|
|
27412
|
+
return { success: false, message: `RCPT TO <${recipient}> failed: ${rcpt.text}`, id: null };
|
|
27726
27413
|
}
|
|
27727
27414
|
}
|
|
27728
27415
|
const dataCmd = await sendCommand(socket, "DATA");
|
|
27729
27416
|
if (dataCmd.code !== 354) {
|
|
27730
27417
|
socket.destroy();
|
|
27731
|
-
return { success: false, message: `DATA failed: ${dataCmd.text}
|
|
27418
|
+
return { success: false, message: `DATA failed: ${dataCmd.text}`, id: null };
|
|
27732
27419
|
}
|
|
27733
27420
|
const mimeMessage = buildMimeMessage({
|
|
27734
27421
|
from: this.fromAddress,
|
|
@@ -27747,16 +27434,31 @@ var init_messenger = __esm({
|
|
|
27747
27434
|
const endData = await sendCommand(socket, mimeMessage + "\r\n.");
|
|
27748
27435
|
if (endData.code !== 250) {
|
|
27749
27436
|
socket.destroy();
|
|
27750
|
-
return { success: false, message: `Message delivery failed: ${endData.text}
|
|
27437
|
+
return { success: false, message: `Message delivery failed: ${endData.text}`, id: null };
|
|
27751
27438
|
}
|
|
27752
27439
|
await sendCommand(socket, "QUIT");
|
|
27753
27440
|
socket.destroy();
|
|
27754
27441
|
return { success: true, message: "Email sent successfully", id: messageId };
|
|
27755
27442
|
} catch (err) {
|
|
27756
27443
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
27757
|
-
return { success: false, message: `SMTP error: ${errMsg}
|
|
27444
|
+
return { success: false, message: `SMTP error: ${errMsg}`, id: null };
|
|
27758
27445
|
}
|
|
27759
27446
|
}
|
|
27447
|
+
/**
|
|
27448
|
+
* Render a Frond template STRING and send it as an HTML email (G7, parity with
|
|
27449
|
+
* Python's send_template). Extra send() options (cc, bcc, replyTo, attachments,
|
|
27450
|
+
* headers) pass through. If the Frond package cannot be loaded the raw template
|
|
27451
|
+
* is sent verbatim (matches Python's ImportError fallback) rather than failing.
|
|
27452
|
+
*/
|
|
27453
|
+
async sendTemplate(to, subject, template, data = {}, cc, bcc, replyTo, attachments, headers) {
|
|
27454
|
+
let body = template;
|
|
27455
|
+
try {
|
|
27456
|
+
const { Frond: Frond2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
27457
|
+
body = new Frond2().renderString(template, data);
|
|
27458
|
+
} catch {
|
|
27459
|
+
}
|
|
27460
|
+
return this.send(to, subject, body, true, void 0, cc, bcc, replyTo, attachments, headers);
|
|
27461
|
+
}
|
|
27760
27462
|
/**
|
|
27761
27463
|
* Test the SMTP connection without sending an email.
|
|
27762
27464
|
*/
|
|
@@ -27765,14 +27467,14 @@ var init_messenger = __esm({
|
|
|
27765
27467
|
let socket;
|
|
27766
27468
|
if (this.port === 465) {
|
|
27767
27469
|
socket = tls2.connect({ host: this.host, port: this.port, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
27768
|
-
await new Promise((
|
|
27769
|
-
socket.once("secureConnect",
|
|
27470
|
+
await new Promise((resolve20, reject) => {
|
|
27471
|
+
socket.once("secureConnect", resolve20);
|
|
27770
27472
|
socket.once("error", reject);
|
|
27771
27473
|
});
|
|
27772
27474
|
} else {
|
|
27773
27475
|
socket = net3.createConnection({ host: this.host, port: this.port });
|
|
27774
|
-
await new Promise((
|
|
27775
|
-
socket.once("connect",
|
|
27476
|
+
await new Promise((resolve20, reject) => {
|
|
27477
|
+
socket.once("connect", resolve20);
|
|
27776
27478
|
socket.once("error", reject);
|
|
27777
27479
|
});
|
|
27778
27480
|
}
|
|
@@ -27807,14 +27509,14 @@ var init_messenger = __esm({
|
|
|
27807
27509
|
const useTls = this.imapEncryption === "tls" || this.imapEncryption === "ssl" || this.imapEncryption === "" && this.imapPort === 993;
|
|
27808
27510
|
if (useTls) {
|
|
27809
27511
|
socket = tls2.connect({ host: this.imapHost, port: this.imapPort, rejectUnauthorized: tlsRejectUnauthorized() });
|
|
27810
|
-
await new Promise((
|
|
27811
|
-
socket.once("secureConnect",
|
|
27512
|
+
await new Promise((resolve20, reject) => {
|
|
27513
|
+
socket.once("secureConnect", resolve20);
|
|
27812
27514
|
socket.once("error", reject);
|
|
27813
27515
|
});
|
|
27814
27516
|
} else {
|
|
27815
27517
|
socket = net3.createConnection({ host: this.imapHost, port: this.imapPort });
|
|
27816
|
-
await new Promise((
|
|
27817
|
-
socket.once("connect",
|
|
27518
|
+
await new Promise((resolve20, reject) => {
|
|
27519
|
+
socket.once("connect", resolve20);
|
|
27818
27520
|
socket.once("error", reject);
|
|
27819
27521
|
});
|
|
27820
27522
|
}
|
|
@@ -27851,7 +27553,7 @@ var init_messenger = __esm({
|
|
|
27851
27553
|
}
|
|
27852
27554
|
try {
|
|
27853
27555
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27854
|
-
const searchResp = await imapCommand(socket, "SEARCH ALL");
|
|
27556
|
+
const searchResp = await imapCommand(socket, "UID SEARCH ALL");
|
|
27855
27557
|
const uids = parseSearchResponse(searchResp);
|
|
27856
27558
|
if (uids.length === 0) return [];
|
|
27857
27559
|
uids.reverse();
|
|
@@ -27859,8 +27561,8 @@ var init_messenger = __esm({
|
|
|
27859
27561
|
if (selected.length === 0) return [];
|
|
27860
27562
|
const messages = [];
|
|
27861
27563
|
for (const uid of selected) {
|
|
27862
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
27863
|
-
messages.push(
|
|
27564
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
27565
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
27864
27566
|
}
|
|
27865
27567
|
return messages;
|
|
27866
27568
|
} catch (err) {
|
|
@@ -27870,7 +27572,7 @@ var init_messenger = __esm({
|
|
|
27870
27572
|
}
|
|
27871
27573
|
}
|
|
27872
27574
|
/**
|
|
27873
|
-
* Read a single message by
|
|
27575
|
+
* Read a single message by its IMAP UID.
|
|
27874
27576
|
*/
|
|
27875
27577
|
async read(uid, folder = "INBOX") {
|
|
27876
27578
|
let socket;
|
|
@@ -27881,11 +27583,11 @@ var init_messenger = __esm({
|
|
|
27881
27583
|
}
|
|
27882
27584
|
try {
|
|
27883
27585
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27884
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY[])`);
|
|
27586
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY[])`);
|
|
27885
27587
|
if (!/\{\d+\}/.test(fetchResp)) {
|
|
27886
27588
|
return null;
|
|
27887
27589
|
}
|
|
27888
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
27590
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
27889
27591
|
return parseFullMessage(uid, fetchResp);
|
|
27890
27592
|
} catch (err) {
|
|
27891
27593
|
throw imapFail("read", err);
|
|
@@ -27912,14 +27614,14 @@ var init_messenger = __esm({
|
|
|
27912
27614
|
}
|
|
27913
27615
|
try {
|
|
27914
27616
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27915
|
-
const searchResp = await imapCommand(socket, `SEARCH ${query}`);
|
|
27617
|
+
const searchResp = await imapCommand(socket, `UID SEARCH ${query}`);
|
|
27916
27618
|
const uids = parseSearchResponse(searchResp);
|
|
27917
27619
|
if (uids.length === 0) return [];
|
|
27918
27620
|
uids.reverse();
|
|
27919
27621
|
const messages = [];
|
|
27920
27622
|
for (const uid of uids.slice(0, limit)) {
|
|
27921
|
-
const fetchResp = await imapCommand(socket, `FETCH ${uid} (FLAGS BODY.PEEK[
|
|
27922
|
-
messages.push(
|
|
27623
|
+
const fetchResp = await imapCommand(socket, `UID FETCH ${uid} (FLAGS BODY.PEEK[])`);
|
|
27624
|
+
messages.push(parseSummary(uid, fetchResp));
|
|
27923
27625
|
}
|
|
27924
27626
|
return messages;
|
|
27925
27627
|
} catch (err) {
|
|
@@ -27929,26 +27631,46 @@ var init_messenger = __esm({
|
|
|
27929
27631
|
}
|
|
27930
27632
|
}
|
|
27931
27633
|
/**
|
|
27932
|
-
* Delete a message by UID.
|
|
27634
|
+
* Delete a message by UID (mark \Deleted, then EXPUNGE).
|
|
27635
|
+
*
|
|
27636
|
+
* `delete` is the one cross-framework name (python/php/ruby/node all spell it
|
|
27637
|
+
* `delete`). `deleteMessage` remains as a DEPRECATED alias for one release.
|
|
27933
27638
|
*/
|
|
27934
|
-
async
|
|
27639
|
+
async delete(uid, folder = "INBOX") {
|
|
27935
27640
|
const socket = await this.imapConnect();
|
|
27936
27641
|
try {
|
|
27937
27642
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27938
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Deleted)`);
|
|
27643
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Deleted)`);
|
|
27939
27644
|
await imapCommand(socket, "EXPUNGE");
|
|
27940
27645
|
} finally {
|
|
27941
27646
|
await this.imapDisconnect(socket);
|
|
27942
27647
|
}
|
|
27943
27648
|
}
|
|
27649
|
+
/** @deprecated Use {@link delete} — kept as an alias for one release (G7). */
|
|
27650
|
+
async deleteMessage(uid, folder = "INBOX") {
|
|
27651
|
+
return this.delete(uid, folder);
|
|
27652
|
+
}
|
|
27944
27653
|
/**
|
|
27945
|
-
* Mark a message as read.
|
|
27654
|
+
* Mark a message as read (+FLAGS \Seen).
|
|
27946
27655
|
*/
|
|
27947
27656
|
async markRead(uid, folder = "INBOX") {
|
|
27948
27657
|
const socket = await this.imapConnect();
|
|
27949
27658
|
try {
|
|
27950
27659
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27951
|
-
await imapCommand(socket, `STORE ${uid} +FLAGS (\\Seen)`);
|
|
27660
|
+
await imapCommand(socket, `UID STORE ${uid} +FLAGS (\\Seen)`);
|
|
27661
|
+
} finally {
|
|
27662
|
+
await this.imapDisconnect(socket);
|
|
27663
|
+
}
|
|
27664
|
+
}
|
|
27665
|
+
/**
|
|
27666
|
+
* Mark a message as unread (-FLAGS \Seen) — the inverse of markRead (G7,
|
|
27667
|
+
* parity with Python's mark_unread).
|
|
27668
|
+
*/
|
|
27669
|
+
async markUnread(uid, folder = "INBOX") {
|
|
27670
|
+
const socket = await this.imapConnect();
|
|
27671
|
+
try {
|
|
27672
|
+
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27673
|
+
await imapCommand(socket, `UID STORE ${uid} -FLAGS (\\Seen)`);
|
|
27952
27674
|
} finally {
|
|
27953
27675
|
await this.imapDisconnect(socket);
|
|
27954
27676
|
}
|
|
@@ -27965,7 +27687,7 @@ var init_messenger = __esm({
|
|
|
27965
27687
|
}
|
|
27966
27688
|
try {
|
|
27967
27689
|
await imapCommand(socket, `SELECT ${imapQuote(folder)}`);
|
|
27968
|
-
const searchResp = await imapCommand(socket, "SEARCH UNSEEN");
|
|
27690
|
+
const searchResp = await imapCommand(socket, "UID SEARCH UNSEEN");
|
|
27969
27691
|
return parseSearchResponse(searchResp).length;
|
|
27970
27692
|
} catch (err) {
|
|
27971
27693
|
throw imapFail("unread", err);
|
|
@@ -28704,17 +28426,17 @@ var init_htmlElement = __esm({
|
|
|
28704
28426
|
});
|
|
28705
28427
|
|
|
28706
28428
|
// ../core/src/ai.ts
|
|
28707
|
-
import { existsSync as
|
|
28429
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync14, writeFileSync as writeFileSync14, readFileSync as readFileSync23 } from "node:fs";
|
|
28708
28430
|
import { homedir } from "node:os";
|
|
28709
|
-
import { join as
|
|
28431
|
+
import { join as join24, resolve as resolve16, relative as relative10, dirname as dirname10 } from "node:path";
|
|
28710
28432
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
28711
28433
|
import { execSync, execFileSync as execFileSync3 } from "node:child_process";
|
|
28712
28434
|
import { createInterface } from "node:readline";
|
|
28713
28435
|
function readVersion() {
|
|
28714
28436
|
try {
|
|
28715
|
-
const thisDir =
|
|
28716
|
-
const rootPkg =
|
|
28717
|
-
const pkg = JSON.parse(
|
|
28437
|
+
const thisDir = dirname10(fileURLToPath6(import.meta.url));
|
|
28438
|
+
const rootPkg = resolve16(thisDir, "..", "..", "..", "package.json");
|
|
28439
|
+
const pkg = JSON.parse(readFileSync23(rootPkg, "utf-8"));
|
|
28718
28440
|
return pkg.version ?? "0.0.0";
|
|
28719
28441
|
} catch {
|
|
28720
28442
|
return "0.0.0";
|
|
@@ -28763,8 +28485,8 @@ function downloadSkillsSync(jobs) {
|
|
|
28763
28485
|
function installSkills(root = ".", targets) {
|
|
28764
28486
|
const ref = skillsRef();
|
|
28765
28487
|
const dests = targets ?? [
|
|
28766
|
-
|
|
28767
|
-
|
|
28488
|
+
join24(resolve16(root), ".claude", "skills"),
|
|
28489
|
+
join24(homedir(), ".claude", "skills")
|
|
28768
28490
|
];
|
|
28769
28491
|
const jobs = [];
|
|
28770
28492
|
const index = /* @__PURE__ */ new Map();
|
|
@@ -28782,9 +28504,9 @@ function installSkills(root = ".", targets) {
|
|
|
28782
28504
|
const base = `https://raw.githubusercontent.com/tina4stack/${spec.repo}/${ref}/.claude/skills/${skill}`;
|
|
28783
28505
|
skillMdUrl[skill] = `${base}/SKILL.md`;
|
|
28784
28506
|
for (const dest of dests) {
|
|
28785
|
-
add(`${base}/SKILL.md`,
|
|
28507
|
+
add(`${base}/SKILL.md`, join24(dest, skill, "SKILL.md"));
|
|
28786
28508
|
for (const r of spec.references) {
|
|
28787
|
-
add(`${base}/references/${r}`,
|
|
28509
|
+
add(`${base}/references/${r}`, join24(dest, skill, "references", r));
|
|
28788
28510
|
}
|
|
28789
28511
|
}
|
|
28790
28512
|
}
|
|
@@ -28796,10 +28518,10 @@ function installSkills(root = ".", targets) {
|
|
|
28796
28518
|
return installed;
|
|
28797
28519
|
}
|
|
28798
28520
|
function isInstalled(root, tool) {
|
|
28799
|
-
return
|
|
28521
|
+
return existsSync24(join24(resolve16(root), tool.contextFile));
|
|
28800
28522
|
}
|
|
28801
28523
|
function showMenu(root = ".") {
|
|
28802
|
-
const r =
|
|
28524
|
+
const r = resolve16(root);
|
|
28803
28525
|
console.log("\n Tina4 AI Context Installer\n");
|
|
28804
28526
|
for (let i = 0; i < AI_TOOLS.length; i++) {
|
|
28805
28527
|
const tool = AI_TOOLS[i];
|
|
@@ -28817,16 +28539,16 @@ function showMenu(root = ".") {
|
|
|
28817
28539
|
const tina4AiMarker = tina4AiInstalled ? ` ${GREEN2}[installed]${RESET2}` : "";
|
|
28818
28540
|
console.log(` 8. Install tina4-ai tools (requires Python)${tina4AiMarker}`);
|
|
28819
28541
|
console.log();
|
|
28820
|
-
return new Promise((
|
|
28542
|
+
return new Promise((resolve20) => {
|
|
28821
28543
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
28822
28544
|
rl.question(" Select (comma-separated, or 'all'): ", (answer) => {
|
|
28823
28545
|
rl.close();
|
|
28824
|
-
|
|
28546
|
+
resolve20(answer.trim());
|
|
28825
28547
|
});
|
|
28826
28548
|
});
|
|
28827
28549
|
}
|
|
28828
28550
|
function installSelected(root, selection) {
|
|
28829
|
-
const rootPath =
|
|
28551
|
+
const rootPath = resolve16(root);
|
|
28830
28552
|
const created = [];
|
|
28831
28553
|
let indices;
|
|
28832
28554
|
let doInstallTina4Ai = false;
|
|
@@ -28919,33 +28641,33 @@ function looksLikeOldFrameworkInstall(existing) {
|
|
|
28919
28641
|
function writeOrMerge(contextPath, contextFile, frameworkGuide) {
|
|
28920
28642
|
const block = skillBlock(contextFile);
|
|
28921
28643
|
const [start2, end] = markersFor(contextFile);
|
|
28922
|
-
if (!
|
|
28923
|
-
|
|
28644
|
+
if (!existsSync24(contextPath)) {
|
|
28645
|
+
writeFileSync14(contextPath, frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
28924
28646
|
return "Installed";
|
|
28925
28647
|
}
|
|
28926
|
-
const existing =
|
|
28648
|
+
const existing = readFileSync23(contextPath, "utf-8");
|
|
28927
28649
|
if (hasMarkers(existing, start2, end)) {
|
|
28928
|
-
|
|
28650
|
+
writeFileSync14(contextPath, replaceMarkerBlock(existing, block, start2, end), "utf-8");
|
|
28929
28651
|
return "Refreshed skill block in";
|
|
28930
28652
|
}
|
|
28931
28653
|
if (looksLikeOldFrameworkInstall(existing)) {
|
|
28932
28654
|
const head = existing.replace(/^\s+/, "");
|
|
28933
28655
|
const preamble = existing.slice(0, existing.length - head.length);
|
|
28934
28656
|
const newContent = (preamble.trim() ? preamble.replace(/\s+$/, "") + "\n\n" : "") + frameworkGuide.replace(/\s+$/, "") + "\n\n" + block + "\n";
|
|
28935
|
-
|
|
28657
|
+
writeFileSync14(contextPath, newContent, "utf-8");
|
|
28936
28658
|
return "Migrated (replaced old framework dump in)";
|
|
28937
28659
|
}
|
|
28938
|
-
|
|
28660
|
+
writeFileSync14(contextPath, existing.replace(/\s+$/, "") + "\n\n" + block + "\n", "utf-8");
|
|
28939
28661
|
return "Appended skill block to";
|
|
28940
28662
|
}
|
|
28941
28663
|
function installForTool(root, tool, context) {
|
|
28942
28664
|
const created = [];
|
|
28943
|
-
const contextPath =
|
|
28665
|
+
const contextPath = join24(root, tool.contextFile);
|
|
28944
28666
|
if (tool.configDir) {
|
|
28945
|
-
|
|
28667
|
+
mkdirSync14(join24(root, tool.configDir), { recursive: true });
|
|
28946
28668
|
}
|
|
28947
|
-
const parentDir =
|
|
28948
|
-
|
|
28669
|
+
const parentDir = dirname10(contextPath);
|
|
28670
|
+
mkdirSync14(parentDir, { recursive: true });
|
|
28949
28671
|
const action = writeOrMerge(contextPath, tool.contextFile, context);
|
|
28950
28672
|
const rel = relative10(root, contextPath);
|
|
28951
28673
|
created.push(rel);
|
|
@@ -28980,7 +28702,7 @@ function installTina4Ai() {
|
|
|
28980
28702
|
function installClaudeSkills(root) {
|
|
28981
28703
|
const created = [];
|
|
28982
28704
|
for (const skill of installSkills(root)) {
|
|
28983
|
-
created.push(
|
|
28705
|
+
created.push(join24(".claude", "skills", skill));
|
|
28984
28706
|
console.log(` ${GREEN2}\u2713${RESET2} Installed .claude/skills/${skill} (project + global)`);
|
|
28985
28707
|
}
|
|
28986
28708
|
return created;
|
|
@@ -29321,11 +29043,11 @@ import { tests, assertEqual, runAll } from "tina4-nodejs";
|
|
|
29321
29043
|
}
|
|
29322
29044
|
function generateClaudeCodeContext() {
|
|
29323
29045
|
try {
|
|
29324
|
-
const thisDir =
|
|
29325
|
-
const repoRoot =
|
|
29326
|
-
const claudeMdPath =
|
|
29327
|
-
if (
|
|
29328
|
-
return
|
|
29046
|
+
const thisDir = dirname10(fileURLToPath6(import.meta.url));
|
|
29047
|
+
const repoRoot = resolve16(thisDir, "..", "..", "..");
|
|
29048
|
+
const claudeMdPath = join24(repoRoot, "CLAUDE.md");
|
|
29049
|
+
if (existsSync24(claudeMdPath)) {
|
|
29050
|
+
return readFileSync23(claudeMdPath, "utf-8");
|
|
29329
29051
|
}
|
|
29330
29052
|
} catch {
|
|
29331
29053
|
}
|
|
@@ -29841,16 +29563,8 @@ var init_rabbitmqBackend = __esm({
|
|
|
29841
29563
|
process.stdout.write(String(msgCount));
|
|
29842
29564
|
closeConnection();
|
|
29843
29565
|
}
|
|
29844
|
-
|
|
29845
|
-
|
|
29846
|
-
const qBuf = Buffer.from(queueName, "utf-8");
|
|
29847
|
-
const purgePayload = Buffer.alloc(4 + qBuf.length);
|
|
29848
|
-
purgePayload.writeUInt16BE(0, 0);
|
|
29849
|
-
purgePayload.writeUInt8(qBuf.length, 2);
|
|
29850
|
-
qBuf.copy(purgePayload, 3);
|
|
29851
|
-
purgePayload.writeUInt8(0, 3 + qBuf.length); // no-wait=false
|
|
29852
|
-
sendMethod(1, 50, 30, purgePayload);
|
|
29853
|
-
}
|
|
29566
|
+
// No "purge" operation: clear()/purge() refuse by name (ADR-0022),
|
|
29567
|
+
// so nothing ever sends Queue.Purge and the drain path is gone.
|
|
29854
29568
|
}
|
|
29855
29569
|
else if (classId === 60 && methodId === 71) {
|
|
29856
29570
|
// Basic.Get-Ok \u2014 message body will follow in content frames
|
|
@@ -29861,11 +29575,6 @@ var init_rabbitmqBackend = __esm({
|
|
|
29861
29575
|
process.stdout.write("__EMPTY__");
|
|
29862
29576
|
closeConnection();
|
|
29863
29577
|
}
|
|
29864
|
-
else if (classId === 50 && methodId === 31) {
|
|
29865
|
-
// Queue.Purge-Ok
|
|
29866
|
-
process.stdout.write("__PURGED__");
|
|
29867
|
-
closeConnection();
|
|
29868
|
-
}
|
|
29869
29578
|
else if (classId === 10 && methodId === 50) {
|
|
29870
29579
|
// Connection.Close (server-initiated, e.g. a channel/protocol error)
|
|
29871
29580
|
// \u2192 send Connection.Close-Ok and exit non-zero so the caller sees the
|
|
@@ -29995,8 +29704,15 @@ var init_rabbitmqBackend = __esm({
|
|
|
29995
29704
|
const num = parseInt(result, 10);
|
|
29996
29705
|
return isNaN(num) ? 0 : num;
|
|
29997
29706
|
}
|
|
29998
|
-
clear(
|
|
29999
|
-
|
|
29707
|
+
clear(_queue) {
|
|
29708
|
+
throw new Error(
|
|
29709
|
+
"The rabbitmq queue backend cannot perform clear(): RabbitMQ cannot address messages by status (basic.get pops the head of the queue), so a status-addressed clear would have to drain the entire live queue and destroy pending work. Use the file or mongodb backend."
|
|
29710
|
+
);
|
|
29711
|
+
}
|
|
29712
|
+
purge(_queue, _status) {
|
|
29713
|
+
throw new Error(
|
|
29714
|
+
"The rabbitmq queue backend cannot perform purge(): RabbitMQ cannot address messages by status (basic.get pops the head of the queue), so a status-addressed purge would have to drain the entire live queue and destroy pending work. Use the file or mongodb backend."
|
|
29715
|
+
);
|
|
30000
29716
|
}
|
|
30001
29717
|
};
|
|
30002
29718
|
}
|
|
@@ -30558,6 +30274,14 @@ var init_kafkaBackend = __esm({
|
|
|
30558
30274
|
return 0;
|
|
30559
30275
|
}
|
|
30560
30276
|
clear(_queue) {
|
|
30277
|
+
throw new Error(
|
|
30278
|
+
"The kafka queue backend cannot perform clear(): Kafka has no notion of job status and cannot delete records on demand. A log is read in offset order and records leave only by retention. Use the file or mongodb backend."
|
|
30279
|
+
);
|
|
30280
|
+
}
|
|
30281
|
+
purge(_queue, _status) {
|
|
30282
|
+
throw new Error(
|
|
30283
|
+
"The kafka queue backend cannot perform purge(): Kafka has no notion of job status to purge by. A log is read in offset order and records leave only by retention. Use the file or mongodb backend."
|
|
30284
|
+
);
|
|
30561
30285
|
}
|
|
30562
30286
|
};
|
|
30563
30287
|
}
|
|
@@ -31317,7 +31041,6 @@ __export(src_exports2, {
|
|
|
31317
31041
|
RouteRef: () => RouteRef,
|
|
31318
31042
|
Router: () => Router,
|
|
31319
31043
|
SafeString: () => SafeString2,
|
|
31320
|
-
ScssCompiler: () => ScssCompiler,
|
|
31321
31044
|
SecurityHeadersMiddleware: () => SecurityHeadersMiddleware,
|
|
31322
31045
|
ServiceRunner: () => ServiceRunner,
|
|
31323
31046
|
Session: () => Session,
|
|
@@ -31514,7 +31237,6 @@ var init_src2 = __esm({
|
|
|
31514
31237
|
init_session();
|
|
31515
31238
|
init_i18n();
|
|
31516
31239
|
init_fakeData();
|
|
31517
|
-
init_scss();
|
|
31518
31240
|
init_queue();
|
|
31519
31241
|
init_job();
|
|
31520
31242
|
init_mqtt();
|
|
@@ -32073,8 +31795,8 @@ __export(sqlite_exports, {
|
|
|
32073
31795
|
SQLiteAdapter: () => SQLiteAdapter
|
|
32074
31796
|
});
|
|
32075
31797
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32076
|
-
import { mkdirSync as
|
|
32077
|
-
import { dirname as
|
|
31798
|
+
import { mkdirSync as mkdirSync15 } from "node:fs";
|
|
31799
|
+
import { dirname as dirname11, isAbsolute as isAbsolute4, join as join25, resolve as resolve17 } from "node:path";
|
|
32078
31800
|
function isIdentifier(str) {
|
|
32079
31801
|
return /^[A-Za-z_][A-Za-z0-9_]*$/.test(str);
|
|
32080
31802
|
}
|
|
@@ -32107,13 +31829,13 @@ function resolveSqlitePath(dbPath) {
|
|
|
32107
31829
|
if (dbPath === ":memory:") return dbPath;
|
|
32108
31830
|
let path8 = dbPath;
|
|
32109
31831
|
if (!isAbsolute4(path8)) {
|
|
32110
|
-
path8 =
|
|
32111
|
-
|
|
31832
|
+
path8 = join25(process.cwd(), path8);
|
|
31833
|
+
mkdirSync15(dirname11(path8), { recursive: true });
|
|
32112
31834
|
} else {
|
|
32113
|
-
const cwd =
|
|
32114
|
-
const abs =
|
|
31835
|
+
const cwd = resolve17(process.cwd());
|
|
31836
|
+
const abs = resolve17(path8);
|
|
32115
31837
|
if (abs.startsWith(cwd + "/") || abs === cwd) {
|
|
32116
|
-
|
|
31838
|
+
mkdirSync15(dirname11(abs), { recursive: true });
|
|
32117
31839
|
}
|
|
32118
31840
|
}
|
|
32119
31841
|
return path8;
|
|
@@ -32461,7 +32183,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
32461
32183
|
const elapsedMs = () => performance.now() - startedAt;
|
|
32462
32184
|
if (budgetMs === null) return attempt();
|
|
32463
32185
|
const started = attempt();
|
|
32464
|
-
return new Promise((
|
|
32186
|
+
return new Promise((resolve20, reject) => {
|
|
32465
32187
|
let expired = false;
|
|
32466
32188
|
const timer = setTimeout(() => {
|
|
32467
32189
|
expired = true;
|
|
@@ -32471,7 +32193,7 @@ function withConnectTimeout(attempt, budgetMs, host, port, abandon) {
|
|
|
32471
32193
|
(arrived) => {
|
|
32472
32194
|
clearTimeout(timer);
|
|
32473
32195
|
if (expired) abandon?.(arrived);
|
|
32474
|
-
else
|
|
32196
|
+
else resolve20(arrived);
|
|
32475
32197
|
},
|
|
32476
32198
|
(failure) => {
|
|
32477
32199
|
clearTimeout(timer);
|
|
@@ -33028,10 +32750,10 @@ var init_mysql = __esm({
|
|
|
33028
32750
|
...timeoutOption
|
|
33029
32751
|
});
|
|
33030
32752
|
}
|
|
33031
|
-
return new Promise((
|
|
32753
|
+
return new Promise((resolve20, reject) => {
|
|
33032
32754
|
this.connection.connect((err) => {
|
|
33033
32755
|
if (err) reject(err);
|
|
33034
|
-
else
|
|
32756
|
+
else resolve20();
|
|
33035
32757
|
});
|
|
33036
32758
|
});
|
|
33037
32759
|
},
|
|
@@ -33053,10 +32775,10 @@ var init_mysql = __esm({
|
|
|
33053
32775
|
}
|
|
33054
32776
|
}
|
|
33055
32777
|
queryPromise(sql, params) {
|
|
33056
|
-
return new Promise((
|
|
32778
|
+
return new Promise((resolve20, reject) => {
|
|
33057
32779
|
this.connection.query(sql, params ?? [], (err, results) => {
|
|
33058
32780
|
if (err) reject(err);
|
|
33059
|
-
else
|
|
32781
|
+
else resolve20(results);
|
|
33060
32782
|
});
|
|
33061
32783
|
});
|
|
33062
32784
|
}
|
|
@@ -33426,11 +33148,11 @@ var init_mssql = __esm({
|
|
|
33426
33148
|
};
|
|
33427
33149
|
}
|
|
33428
33150
|
await withConnectTimeout(
|
|
33429
|
-
() => new Promise((
|
|
33151
|
+
() => new Promise((resolve20, reject) => {
|
|
33430
33152
|
this.connection = new Connection(tediousConfig);
|
|
33431
33153
|
this.connection.on("connect", (err) => {
|
|
33432
33154
|
if (err) reject(err);
|
|
33433
|
-
else
|
|
33155
|
+
else resolve20();
|
|
33434
33156
|
});
|
|
33435
33157
|
this.connection.connect();
|
|
33436
33158
|
}),
|
|
@@ -33475,11 +33197,11 @@ var init_mssql = __esm({
|
|
|
33475
33197
|
const tediousModule = requireTedious();
|
|
33476
33198
|
const Request = tediousModule.Request;
|
|
33477
33199
|
const TYPES = tediousModule.TYPES;
|
|
33478
|
-
return new Promise((
|
|
33200
|
+
return new Promise((resolve20, reject) => {
|
|
33479
33201
|
const rows = [];
|
|
33480
33202
|
const request = new Request(sql, (err, rowCount) => {
|
|
33481
33203
|
if (err) reject(err);
|
|
33482
|
-
else
|
|
33204
|
+
else resolve20({ rows, rowCount });
|
|
33483
33205
|
});
|
|
33484
33206
|
if (params) {
|
|
33485
33207
|
params.forEach((p, i) => {
|
|
@@ -33682,8 +33404,8 @@ var init_mssql = __esm({
|
|
|
33682
33404
|
throw new Error("Use startTransactionAsync() for MSSQL.");
|
|
33683
33405
|
}
|
|
33684
33406
|
async startTransactionAsync() {
|
|
33685
|
-
await new Promise((
|
|
33686
|
-
this.connection.beginTransaction((err) => err ? reject(err) :
|
|
33407
|
+
await new Promise((resolve20, reject) => {
|
|
33408
|
+
this.connection.beginTransaction((err) => err ? reject(err) : resolve20());
|
|
33687
33409
|
});
|
|
33688
33410
|
this._inTransaction = true;
|
|
33689
33411
|
}
|
|
@@ -33691,8 +33413,8 @@ var init_mssql = __esm({
|
|
|
33691
33413
|
throw new Error("Use commitAsync() for MSSQL.");
|
|
33692
33414
|
}
|
|
33693
33415
|
async commitAsync() {
|
|
33694
|
-
await new Promise((
|
|
33695
|
-
this.connection.commitTransaction((err) => err ? reject(err) :
|
|
33416
|
+
await new Promise((resolve20, reject) => {
|
|
33417
|
+
this.connection.commitTransaction((err) => err ? reject(err) : resolve20());
|
|
33696
33418
|
});
|
|
33697
33419
|
this._inTransaction = false;
|
|
33698
33420
|
}
|
|
@@ -33700,8 +33422,8 @@ var init_mssql = __esm({
|
|
|
33700
33422
|
throw new Error("Use rollbackAsync() for MSSQL.");
|
|
33701
33423
|
}
|
|
33702
33424
|
async rollbackAsync() {
|
|
33703
|
-
await new Promise((
|
|
33704
|
-
this.connection.rollbackTransaction((err) => err ? reject(err) :
|
|
33425
|
+
await new Promise((resolve20, reject) => {
|
|
33426
|
+
this.connection.rollbackTransaction((err) => err ? reject(err) : resolve20());
|
|
33705
33427
|
});
|
|
33706
33428
|
this._inTransaction = false;
|
|
33707
33429
|
}
|
|
@@ -33971,8 +33693,8 @@ var init_firebird = __esm({
|
|
|
33971
33693
|
fbConfig.database = normalizeFirebirdDbIdentifier(fbConfig.database);
|
|
33972
33694
|
}
|
|
33973
33695
|
this.db = await withConnectTimeout(
|
|
33974
|
-
() => new Promise((
|
|
33975
|
-
fb.attach(fbConfig, (err, db) => err ? reject(err) :
|
|
33696
|
+
() => new Promise((resolve20, reject) => {
|
|
33697
|
+
fb.attach(fbConfig, (err, db) => err ? reject(err) : resolve20(db));
|
|
33976
33698
|
}),
|
|
33977
33699
|
connectTimeoutMillis(),
|
|
33978
33700
|
fbConfig.host,
|
|
@@ -34039,20 +33761,20 @@ var init_firebird = __esm({
|
|
|
34039
33761
|
return this.transaction ?? this.db;
|
|
34040
33762
|
}
|
|
34041
33763
|
queryPromise(sql, params) {
|
|
34042
|
-
return new Promise((
|
|
33764
|
+
return new Promise((resolve20, reject) => {
|
|
34043
33765
|
const translated = this.translateSql(sql);
|
|
34044
33766
|
this.statementHandle().query(translated, params ?? [], (err, result) => {
|
|
34045
33767
|
if (err) reject(err);
|
|
34046
|
-
else
|
|
33768
|
+
else resolve20(result ?? []);
|
|
34047
33769
|
});
|
|
34048
33770
|
});
|
|
34049
33771
|
}
|
|
34050
33772
|
executePromise(sql, params) {
|
|
34051
|
-
return new Promise((
|
|
33773
|
+
return new Promise((resolve20, reject) => {
|
|
34052
33774
|
const translated = this.translateSql(sql);
|
|
34053
33775
|
this.statementHandle().execute(translated, params ?? [], (err) => {
|
|
34054
33776
|
if (err) reject(err);
|
|
34055
|
-
else
|
|
33777
|
+
else resolve20();
|
|
34056
33778
|
});
|
|
34057
33779
|
});
|
|
34058
33780
|
}
|
|
@@ -34168,12 +33890,12 @@ var init_firebird = __esm({
|
|
|
34168
33890
|
}
|
|
34169
33891
|
async startTransactionAsync() {
|
|
34170
33892
|
this.ensureConnected();
|
|
34171
|
-
await new Promise((
|
|
33893
|
+
await new Promise((resolve20, reject) => {
|
|
34172
33894
|
this.db.transaction(0, (err, transaction) => {
|
|
34173
33895
|
if (err) reject(err);
|
|
34174
33896
|
else {
|
|
34175
33897
|
this.transaction = transaction;
|
|
34176
|
-
|
|
33898
|
+
resolve20();
|
|
34177
33899
|
}
|
|
34178
33900
|
});
|
|
34179
33901
|
});
|
|
@@ -34183,12 +33905,12 @@ var init_firebird = __esm({
|
|
|
34183
33905
|
}
|
|
34184
33906
|
async commitAsync() {
|
|
34185
33907
|
if (!this.transaction) throw new Error("No active transaction to commit.");
|
|
34186
|
-
await new Promise((
|
|
33908
|
+
await new Promise((resolve20, reject) => {
|
|
34187
33909
|
this.transaction.commit((err) => {
|
|
34188
33910
|
if (err) reject(err);
|
|
34189
33911
|
else {
|
|
34190
33912
|
this.transaction = null;
|
|
34191
|
-
|
|
33913
|
+
resolve20();
|
|
34192
33914
|
}
|
|
34193
33915
|
});
|
|
34194
33916
|
});
|
|
@@ -34198,12 +33920,12 @@ var init_firebird = __esm({
|
|
|
34198
33920
|
}
|
|
34199
33921
|
async rollbackAsync() {
|
|
34200
33922
|
if (!this.transaction) throw new Error("No active transaction to rollback.");
|
|
34201
|
-
await new Promise((
|
|
33923
|
+
await new Promise((resolve20, reject) => {
|
|
34202
33924
|
this.transaction.rollback((err) => {
|
|
34203
33925
|
if (err) reject(err);
|
|
34204
33926
|
else {
|
|
34205
33927
|
this.transaction = null;
|
|
34206
|
-
|
|
33928
|
+
resolve20();
|
|
34207
33929
|
}
|
|
34208
33930
|
});
|
|
34209
33931
|
});
|
|
@@ -35264,6 +34986,7 @@ __export(database_exports, {
|
|
|
35264
34986
|
getNamedAdapter: () => getNamedAdapter,
|
|
35265
34987
|
initDatabase: () => initDatabase,
|
|
35266
34988
|
parseDatabaseUrl: () => parseDatabaseUrl,
|
|
34989
|
+
probeTotal: () => probeTotal,
|
|
35267
34990
|
resetRequestCaches: () => resetRequestCaches2,
|
|
35268
34991
|
resolveDbPool: () => resolveDbPool,
|
|
35269
34992
|
setAdapter: () => setAdapter,
|
|
@@ -35317,6 +35040,29 @@ async function adapterCreateTable(adapter, name, columns) {
|
|
|
35317
35040
|
if (adapter.createTableAsync) await adapter.createTableAsync(name, columns);
|
|
35318
35041
|
else adapter.createTable(name, columns);
|
|
35319
35042
|
}
|
|
35043
|
+
async function probeTotal(adapter, sql, params, limit) {
|
|
35044
|
+
if (limit === void 0 || limit <= 0) return void 0;
|
|
35045
|
+
try {
|
|
35046
|
+
const alias = adapter.countSubqueryAlias;
|
|
35047
|
+
const suffix = alias ? ` AS ${alias}` : "";
|
|
35048
|
+
const rows = await adapterFetch(
|
|
35049
|
+
adapter,
|
|
35050
|
+
`SELECT COUNT(*) AS tina4_total FROM (${sql}
|
|
35051
|
+
)${suffix}`,
|
|
35052
|
+
params,
|
|
35053
|
+
void 0,
|
|
35054
|
+
void 0,
|
|
35055
|
+
true
|
|
35056
|
+
);
|
|
35057
|
+
const row = Array.isArray(rows) ? rows[0] : void 0;
|
|
35058
|
+
if (!row) return void 0;
|
|
35059
|
+
const value = row["tina4_total"] ?? row["TINA4_TOTAL"] ?? Object.values(row)[0];
|
|
35060
|
+
const n = Number(value);
|
|
35061
|
+
return Number.isFinite(n) ? n : void 0;
|
|
35062
|
+
} catch {
|
|
35063
|
+
return void 0;
|
|
35064
|
+
}
|
|
35065
|
+
}
|
|
35320
35066
|
function extractLastInsertId(result) {
|
|
35321
35067
|
if (result && typeof result === "object") {
|
|
35322
35068
|
const r = result;
|
|
@@ -35775,65 +35521,13 @@ var init_database = __esm({
|
|
|
35775
35521
|
try {
|
|
35776
35522
|
const rows = await adapterFetch(adapter, sql, params, limit, offset, opts?.noCache);
|
|
35777
35523
|
this.lastError = null;
|
|
35778
|
-
const total = await
|
|
35524
|
+
const total = await probeTotal(adapter, sql, params, limit);
|
|
35779
35525
|
return new DatabaseResult(rows, void 0, total, limit, offset, adapter, sql);
|
|
35780
35526
|
} catch (e) {
|
|
35781
35527
|
this.lastError = e?.message ?? String(e);
|
|
35782
35528
|
throw e;
|
|
35783
35529
|
}
|
|
35784
35530
|
}
|
|
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
35531
|
/**
|
|
35838
35532
|
* Fetch a single row or null.
|
|
35839
35533
|
*
|
|
@@ -36519,18 +36213,18 @@ var init_database = __esm({
|
|
|
36519
36213
|
});
|
|
36520
36214
|
|
|
36521
36215
|
// src/model.ts
|
|
36522
|
-
import { readdirSync as
|
|
36523
|
-
import { join as
|
|
36216
|
+
import { readdirSync as readdirSync17, statSync as statSync17 } from "node:fs";
|
|
36217
|
+
import { join as join26, extname as extname8 } from "node:path";
|
|
36524
36218
|
async function discoverModels(modelsDir) {
|
|
36525
36219
|
const models = [];
|
|
36526
36220
|
let files;
|
|
36527
36221
|
try {
|
|
36528
|
-
files =
|
|
36222
|
+
files = readdirSync17(modelsDir);
|
|
36529
36223
|
} catch {
|
|
36530
36224
|
return models;
|
|
36531
36225
|
}
|
|
36532
36226
|
for (const file of files) {
|
|
36533
|
-
const filePath =
|
|
36227
|
+
const filePath = join26(modelsDir, file);
|
|
36534
36228
|
const stat = statSync17(filePath);
|
|
36535
36229
|
if (!stat.isFile()) continue;
|
|
36536
36230
|
const ext = extname8(file);
|
|
@@ -36545,6 +36239,10 @@ async function discoverModels(modelsDir) {
|
|
|
36545
36239
|
}
|
|
36546
36240
|
const definition = {
|
|
36547
36241
|
tableName: ModelClass.tableName,
|
|
36242
|
+
// The class name is the type name a generated OpenAPI client wants
|
|
36243
|
+
// (`Item`, not `items`). Carry it so Swagger keys components.schemas by
|
|
36244
|
+
// it. A model exported as `default` keeps its declared class name here.
|
|
36245
|
+
className: typeof ModelClass.name === "string" && ModelClass.name ? ModelClass.name : void 0,
|
|
36548
36246
|
fields: ModelClass.fields,
|
|
36549
36247
|
fieldMapping: ModelClass.fieldMapping,
|
|
36550
36248
|
softDelete: ModelClass.softDelete ?? false,
|
|
@@ -36568,8 +36266,8 @@ var init_model = __esm({
|
|
|
36568
36266
|
});
|
|
36569
36267
|
|
|
36570
36268
|
// src/migration.ts
|
|
36571
|
-
import { existsSync as
|
|
36572
|
-
import { join as
|
|
36269
|
+
import { existsSync as existsSync25, readdirSync as readdirSync18, readFileSync as readFileSync24, mkdirSync as mkdirSync16, writeFileSync as writeFileSync15 } from "node:fs";
|
|
36270
|
+
import { join as join27, resolve as resolve18 } from "node:path";
|
|
36573
36271
|
function unwrapAdapter(db) {
|
|
36574
36272
|
let cur = db;
|
|
36575
36273
|
while (cur && cur.constructor?.name === "CachedDatabaseAdapter" && cur.adapter) {
|
|
@@ -36877,16 +36575,16 @@ async function rollback(migrationsDir, delimiter2) {
|
|
|
36877
36575
|
}
|
|
36878
36576
|
return rolledBack2;
|
|
36879
36577
|
}
|
|
36880
|
-
const dir =
|
|
36578
|
+
const dir = resolve18(migrationsDir ?? "migrations");
|
|
36881
36579
|
const delim = delimiter2 ?? ";";
|
|
36882
36580
|
const db = getAdapter();
|
|
36883
36581
|
const migrations = await getLastBatchMigrations();
|
|
36884
36582
|
const rolledBack = [];
|
|
36885
36583
|
for (const migration of migrations) {
|
|
36886
36584
|
const downFile = `${migration.migration_name}.down.sql`;
|
|
36887
|
-
const downPath =
|
|
36888
|
-
if (
|
|
36889
|
-
const sqlContent =
|
|
36585
|
+
const downPath = join27(dir, downFile);
|
|
36586
|
+
if (existsSync25(downPath)) {
|
|
36587
|
+
const sqlContent = readFileSync24(downPath, "utf-8").trim();
|
|
36890
36588
|
if (sqlContent) {
|
|
36891
36589
|
const statements = splitStatements(sqlContent, delim);
|
|
36892
36590
|
try {
|
|
@@ -37046,15 +36744,15 @@ function warnUnprefixedMigrations(files) {
|
|
|
37046
36744
|
}
|
|
37047
36745
|
async function migrate(adapter, options) {
|
|
37048
36746
|
const db = adapter ?? getAdapter();
|
|
37049
|
-
const dir =
|
|
36747
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
37050
36748
|
const delimiter2 = options?.delimiter ?? ";";
|
|
37051
36749
|
const result = { applied: [], skipped: [], failed: [] };
|
|
37052
|
-
if (!
|
|
36750
|
+
if (!existsSync25(dir)) {
|
|
37053
36751
|
return result;
|
|
37054
36752
|
}
|
|
37055
36753
|
await ensureMigrationTableOn(db);
|
|
37056
36754
|
const files = sortMigrationFiles(
|
|
37057
|
-
|
|
36755
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37058
36756
|
);
|
|
37059
36757
|
if (files.length === 0) return result;
|
|
37060
36758
|
warnUnprefixedMigrations(files);
|
|
@@ -37085,7 +36783,7 @@ async function migrate(adapter, options) {
|
|
|
37085
36783
|
result.skipped.push(file);
|
|
37086
36784
|
continue;
|
|
37087
36785
|
}
|
|
37088
|
-
const sqlContent =
|
|
36786
|
+
const sqlContent = readFileSync24(join27(dir, file), "utf-8").trim();
|
|
37089
36787
|
if (!sqlContent) {
|
|
37090
36788
|
result.skipped.push(file);
|
|
37091
36789
|
continue;
|
|
@@ -37120,21 +36818,21 @@ async function migrate(adapter, options) {
|
|
|
37120
36818
|
}
|
|
37121
36819
|
async function status(adapter, options) {
|
|
37122
36820
|
const db = adapter ?? getAdapter();
|
|
37123
|
-
const dir =
|
|
36821
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
37124
36822
|
const result = { completed: [], pending: [] };
|
|
37125
|
-
if (!
|
|
36823
|
+
if (!existsSync25(dir)) {
|
|
37126
36824
|
return result;
|
|
37127
36825
|
}
|
|
37128
36826
|
if (!await adapterTableExists(db, MIGRATION_TABLE)) {
|
|
37129
36827
|
const files2 = sortMigrationFiles(
|
|
37130
|
-
|
|
36828
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37131
36829
|
);
|
|
37132
36830
|
result.pending = files2;
|
|
37133
36831
|
return result;
|
|
37134
36832
|
}
|
|
37135
36833
|
await ensureMigrationTableOn(db);
|
|
37136
36834
|
const files = sortMigrationFiles(
|
|
37137
|
-
|
|
36835
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37138
36836
|
);
|
|
37139
36837
|
const appliedNames = /* @__PURE__ */ new Set();
|
|
37140
36838
|
try {
|
|
@@ -37159,12 +36857,18 @@ async function status(adapter, options) {
|
|
|
37159
36857
|
return result;
|
|
37160
36858
|
}
|
|
37161
36859
|
async function createMigration(description, options) {
|
|
37162
|
-
|
|
36860
|
+
const kind = (options?.kind ?? "sql").trim().toLowerCase();
|
|
36861
|
+
if (!["sql", "code", "class"].includes(kind)) {
|
|
36862
|
+
throw new Error(
|
|
36863
|
+
`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.`
|
|
36864
|
+
);
|
|
36865
|
+
}
|
|
36866
|
+
if (kind === "code" || kind === "class") {
|
|
37163
36867
|
return createClassMigration(description, options);
|
|
37164
36868
|
}
|
|
37165
|
-
const dir =
|
|
37166
|
-
if (!
|
|
37167
|
-
|
|
36869
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
36870
|
+
if (!existsSync25(dir)) {
|
|
36871
|
+
mkdirSync16(dir, { recursive: true });
|
|
37168
36872
|
}
|
|
37169
36873
|
const safeName = description.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
37170
36874
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -37178,8 +36882,8 @@ async function createMigration(description, options) {
|
|
|
37178
36882
|
].join("");
|
|
37179
36883
|
const upFileName = `${timestamp}_${safeName}.sql`;
|
|
37180
36884
|
const downFileName = `${timestamp}_${safeName}.down.sql`;
|
|
37181
|
-
const upPath =
|
|
37182
|
-
const downPath =
|
|
36885
|
+
const upPath = join27(dir, upFileName);
|
|
36886
|
+
const downPath = join27(dir, downFileName);
|
|
37183
36887
|
const upTemplate = `-- Migration: ${description}
|
|
37184
36888
|
-- Created: ${now.toISOString()}
|
|
37185
36889
|
|
|
@@ -37188,14 +36892,14 @@ async function createMigration(description, options) {
|
|
|
37188
36892
|
-- Created: ${now.toISOString()}
|
|
37189
36893
|
|
|
37190
36894
|
`;
|
|
37191
|
-
|
|
37192
|
-
|
|
36895
|
+
writeFileSync15(upPath, upTemplate, "utf-8");
|
|
36896
|
+
writeFileSync15(downPath, downTemplate, "utf-8");
|
|
37193
36897
|
return { upPath, downPath };
|
|
37194
36898
|
}
|
|
37195
36899
|
async function createClassMigration(description, options) {
|
|
37196
|
-
const dir =
|
|
37197
|
-
if (!
|
|
37198
|
-
|
|
36900
|
+
const dir = resolve18(options?.migrationsDir ?? "migrations");
|
|
36901
|
+
if (!existsSync25(dir)) {
|
|
36902
|
+
mkdirSync16(dir, { recursive: true });
|
|
37199
36903
|
}
|
|
37200
36904
|
const safeName = description.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "");
|
|
37201
36905
|
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 +36913,7 @@ async function createClassMigration(description, options) {
|
|
|
37209
36913
|
String(now.getSeconds()).padStart(2, "0")
|
|
37210
36914
|
].join("");
|
|
37211
36915
|
const fileName = `${timestamp}_${safeName}.ts`;
|
|
37212
|
-
const filePath =
|
|
36916
|
+
const filePath = join27(dir, fileName);
|
|
37213
36917
|
const content = `// Migration: ${description}
|
|
37214
36918
|
// Created: ${now.toISOString()}
|
|
37215
36919
|
|
|
@@ -37225,7 +36929,7 @@ export class ${className} {
|
|
|
37225
36929
|
}
|
|
37226
36930
|
}
|
|
37227
36931
|
`;
|
|
37228
|
-
|
|
36932
|
+
writeFileSync15(filePath, content, "utf-8");
|
|
37229
36933
|
return filePath;
|
|
37230
36934
|
}
|
|
37231
36935
|
var ALTER_ADD_RE, CREATE_TABLE_RE, MIGRATION_TABLE, SMART_QUOTES, SMART_QUOTE_RE, SET_TERM_RE, Migration;
|
|
@@ -37286,15 +36990,13 @@ var init_migration = __esm({
|
|
|
37286
36990
|
* Scaffold a new migration file.
|
|
37287
36991
|
*
|
|
37288
36992
|
* kind="sql" — creates {timestamp}_{description}.sql + .down.sql (default)
|
|
37289
|
-
* kind="
|
|
36993
|
+
* kind="code" — creates {timestamp}_{description}.ts with a TypeScript class
|
|
36994
|
+
* template. "class" is accepted as a legacy alias.
|
|
37290
36995
|
*
|
|
37291
36996
|
* Returns the path to the created up file (or class file).
|
|
37292
36997
|
*/
|
|
37293
36998
|
async create(description, kind = "sql") {
|
|
37294
|
-
|
|
37295
|
-
return createClassMigration(description, { migrationsDir: this.dir });
|
|
37296
|
-
}
|
|
37297
|
-
return createMigration(description, { migrationsDir: this.dir });
|
|
36999
|
+
return createMigration(description, { migrationsDir: this.dir, kind });
|
|
37298
37000
|
}
|
|
37299
37001
|
/** Return list of completed (applied) migration filenames. */
|
|
37300
37002
|
async getApplied() {
|
|
@@ -37308,10 +37010,10 @@ var init_migration = __esm({
|
|
|
37308
37010
|
}
|
|
37309
37011
|
/** Return sorted list of all migration files on disk (excludes .down.sql). */
|
|
37310
37012
|
getFiles() {
|
|
37311
|
-
const dir =
|
|
37312
|
-
if (!
|
|
37013
|
+
const dir = resolve18(this.dir);
|
|
37014
|
+
if (!existsSync25(dir)) return [];
|
|
37313
37015
|
return sortMigrationFiles(
|
|
37314
|
-
|
|
37016
|
+
readdirSync18(dir).filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
37315
37017
|
);
|
|
37316
37018
|
}
|
|
37317
37019
|
};
|
|
@@ -37537,15 +37239,8 @@ function generateCrudRoutes(models, options = {}) {
|
|
|
37537
37239
|
const total = Number(countRow[0]?.total ?? 0);
|
|
37538
37240
|
const limit = qp.limit ?? 100;
|
|
37539
37241
|
const page = qp.page ?? 1;
|
|
37540
|
-
|
|
37541
|
-
|
|
37542
|
-
meta: {
|
|
37543
|
-
total,
|
|
37544
|
-
page,
|
|
37545
|
-
limit,
|
|
37546
|
-
totalPages: Math.ceil(total / limit)
|
|
37547
|
-
}
|
|
37548
|
-
});
|
|
37242
|
+
const offset = (page - 1) * limit;
|
|
37243
|
+
res.json(new DatabaseResult(rows, void 0, total, limit, offset).toPaginate());
|
|
37549
37244
|
}
|
|
37550
37245
|
});
|
|
37551
37246
|
routes.push({
|
|
@@ -37699,6 +37394,7 @@ var init_autoCrud = __esm({
|
|
|
37699
37394
|
"src/autoCrud.ts"() {
|
|
37700
37395
|
"use strict";
|
|
37701
37396
|
init_database();
|
|
37397
|
+
init_databaseResult();
|
|
37702
37398
|
init_query();
|
|
37703
37399
|
init_validation();
|
|
37704
37400
|
AutoCrud = class _AutoCrud {
|
|
@@ -37953,17 +37649,19 @@ var init_queryBuilder = __esm({
|
|
|
37953
37649
|
this.ensureDb();
|
|
37954
37650
|
const sql = this.toSql();
|
|
37955
37651
|
const allParams = [...this.params, ...this.havingParams];
|
|
37652
|
+
const queryParams = allParams.length > 0 ? allParams : void 0;
|
|
37956
37653
|
const rows = await adapterFetch(
|
|
37957
37654
|
this.db,
|
|
37958
37655
|
sql,
|
|
37959
|
-
|
|
37656
|
+
queryParams,
|
|
37960
37657
|
this.limitVal,
|
|
37961
37658
|
this.offsetVal
|
|
37962
37659
|
);
|
|
37660
|
+
const total = await probeTotal(this.db, sql, queryParams, this.limitVal);
|
|
37963
37661
|
return new DatabaseResult(
|
|
37964
37662
|
rows,
|
|
37965
37663
|
void 0,
|
|
37966
|
-
|
|
37664
|
+
total,
|
|
37967
37665
|
this.limitVal,
|
|
37968
37666
|
this.offsetVal,
|
|
37969
37667
|
this.db,
|
|
@@ -39828,8 +39526,8 @@ var init_seeder = __esm({
|
|
|
39828
39526
|
// src/docstore.ts
|
|
39829
39527
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
39830
39528
|
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
39831
|
-
import { mkdirSync as
|
|
39832
|
-
import { dirname as
|
|
39529
|
+
import { mkdirSync as mkdirSync17 } from "node:fs";
|
|
39530
|
+
import { dirname as dirname12, isAbsolute as isAbsolute5, join as join28 } from "node:path";
|
|
39833
39531
|
function iso(d) {
|
|
39834
39532
|
return d.toISOString();
|
|
39835
39533
|
}
|
|
@@ -40094,8 +39792,8 @@ function resolveStorePath(dbPath) {
|
|
|
40094
39792
|
if (dbPath === ":memory:") return dbPath;
|
|
40095
39793
|
let path8 = dbPath;
|
|
40096
39794
|
if (!isAbsolute5(path8)) {
|
|
40097
|
-
path8 =
|
|
40098
|
-
|
|
39795
|
+
path8 = join28(process.cwd(), path8);
|
|
39796
|
+
mkdirSync17(dirname12(path8), { recursive: true });
|
|
40099
39797
|
}
|
|
40100
39798
|
return path8;
|
|
40101
39799
|
}
|
|
@@ -40609,8 +40307,8 @@ var init_attachment = __esm({
|
|
|
40609
40307
|
|
|
40610
40308
|
// src/realtime/storage.ts
|
|
40611
40309
|
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
40612
|
-
import { mkdirSync as
|
|
40613
|
-
import { resolve as
|
|
40310
|
+
import { mkdirSync as mkdirSync18, readFileSync as readFileSync25, writeFileSync as writeFileSync16, unlinkSync as unlinkSync7, statSync as statSync18 } from "node:fs";
|
|
40311
|
+
import { resolve as resolve19, sep as sep3 } from "node:path";
|
|
40614
40312
|
import { createRequire as createRequire8 } from "node:module";
|
|
40615
40313
|
function storageKey(filename = "") {
|
|
40616
40314
|
let ext = "";
|
|
@@ -40646,23 +40344,23 @@ var init_storage = __esm({
|
|
|
40646
40344
|
LocalStorage = class {
|
|
40647
40345
|
directory;
|
|
40648
40346
|
constructor(directory) {
|
|
40649
|
-
this.directory =
|
|
40650
|
-
|
|
40347
|
+
this.directory = resolve19(directory || process.env.TINA4_STORAGE_DIR || "data/rt_storage");
|
|
40348
|
+
mkdirSync18(this.directory, { recursive: true });
|
|
40651
40349
|
}
|
|
40652
40350
|
// Resolve inside the root and reject any traversal attempt.
|
|
40653
40351
|
pathFor(key) {
|
|
40654
|
-
const target =
|
|
40352
|
+
const target = resolve19(this.directory, key);
|
|
40655
40353
|
if (target !== this.directory && !target.startsWith(this.directory + sep3)) {
|
|
40656
40354
|
throw new Error(`unsafe storage key: ${JSON.stringify(key)}`);
|
|
40657
40355
|
}
|
|
40658
40356
|
return target;
|
|
40659
40357
|
}
|
|
40660
40358
|
put(key, data) {
|
|
40661
|
-
|
|
40359
|
+
writeFileSync16(this.pathFor(key), data);
|
|
40662
40360
|
}
|
|
40663
40361
|
get(key) {
|
|
40664
40362
|
try {
|
|
40665
|
-
return
|
|
40363
|
+
return readFileSync25(this.pathFor(key));
|
|
40666
40364
|
} catch {
|
|
40667
40365
|
return null;
|
|
40668
40366
|
}
|
|
@@ -41072,7 +40770,6 @@ __export(index_exports, {
|
|
|
41072
40770
|
DatabaseUrl: () => DatabaseUrl,
|
|
41073
40771
|
DocStoreDriverMissing: () => DocStoreDriverMissing,
|
|
41074
40772
|
FakeData: () => FakeData2,
|
|
41075
|
-
FetchResult: () => FetchResult,
|
|
41076
40773
|
FirebirdAdapter: () => FirebirdAdapter,
|
|
41077
40774
|
InvalidId: () => InvalidId,
|
|
41078
40775
|
LocalStorage: () => LocalStorage,
|
|
@@ -41171,7 +40868,6 @@ __export(index_exports, {
|
|
|
41171
40868
|
});
|
|
41172
40869
|
var init_index = __esm({
|
|
41173
40870
|
"src/index.ts"() {
|
|
41174
|
-
init_types();
|
|
41175
40871
|
init_databaseResult();
|
|
41176
40872
|
init_database();
|
|
41177
40873
|
init_database();
|
|
@@ -41212,7 +40908,6 @@ export {
|
|
|
41212
40908
|
DatabaseUrl,
|
|
41213
40909
|
DocStoreDriverMissing,
|
|
41214
40910
|
FakeData2 as FakeData,
|
|
41215
|
-
FetchResult,
|
|
41216
40911
|
FirebirdAdapter,
|
|
41217
40912
|
InvalidId,
|
|
41218
40913
|
LocalStorage,
|