@getstrata/core 2.0.2 → 2.0.4
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/CHANGELOG.md +8 -0
- package/dist/core/database/baseRepository.d.ts +2 -0
- package/dist/core/database/dialect.d.ts +4 -0
- package/dist/core/database/mysqlConnection.d.ts +12 -2
- package/dist/core/database/sqliteConnection.d.ts +2 -1
- package/dist/core/database/transaction.d.ts +2 -2
- package/dist/core/database/transactionControl.d.ts +2 -1
- package/dist/core/events/eventBus.d.ts +4 -0
- package/dist/core/events/outbox/index.d.ts +55 -0
- package/dist/core/events/outbox/migration.d.ts +7 -0
- package/dist/core/http/requestMetaContext.d.ts +2 -0
- package/dist/core/http/response.d.ts +2 -1
- package/dist/core/openapi/registeredRoute.d.ts +40 -1
- package/dist/core/security/safeFetch.d.ts +1 -0
- package/dist/entries/audit/exportAuditLogs.js +137 -17
- package/dist/entries/auth/basicAuthGuard.js +18 -0
- package/dist/entries/auth/oauth/oidcProvider.js +100 -17
- package/dist/entries/auth/oauth/providers.js +100 -17
- package/dist/entries/auth/oauth/samlProvider.js +18 -0
- package/dist/entries/auth/oneTimeToken.js +18 -0
- package/dist/entries/auth/passwordLogin.js +18 -0
- package/dist/entries/auth/saml/samlServiceProvider.js +18 -0
- package/dist/entries/auth/scimAuthMiddleware.js +37 -0
- package/dist/entries/database/migrations.js +18 -0
- package/dist/entries/database/sqliteConnection.js +101 -7
- package/dist/entries/events/outbox.js +1 -0
- package/dist/entries/http/response.js +39 -5
- package/dist/entries/jobs/exportAuditLogsJob.js +137 -17
- package/dist/entries/openapi/generator.js +42 -12
- package/dist/entries/openapi/validate.js +10 -0
- package/dist/entries/security/safeFetch.js +100 -17
- package/dist/entries/tenant/databaseTenantContext.js +37 -0
- package/dist/entries/tenant/tenantDatabaseScope.js +37 -0
- package/dist/entries/tracing/tracingMiddleware.js +100 -17
- package/dist/framework/public-api.d.ts +3 -1
- package/dist/index.js +649 -60
- package/package.json +6 -1
|
@@ -325,50 +325,133 @@ async function resolveSafeOutboundTarget(rawUrl, options = {}) {
|
|
|
325
325
|
|
|
326
326
|
// ../../src/core/security/safeFetch.ts
|
|
327
327
|
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
328
|
+
function untilAborted(work, signal) {
|
|
329
|
+
signal.throwIfAborted();
|
|
330
|
+
return new Promise((resolve, reject) => {
|
|
331
|
+
const onAbort = () => reject(signal.reason);
|
|
332
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
333
|
+
work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
function preserveResponseMetadata(response, original) {
|
|
337
|
+
Object.defineProperties(response, {
|
|
338
|
+
url: { value: original.url },
|
|
339
|
+
redirected: { value: original.redirected },
|
|
340
|
+
type: { value: original.type }
|
|
341
|
+
});
|
|
342
|
+
const clone = response.clone.bind(response);
|
|
343
|
+
response.clone = () => preserveResponseMetadata(clone(), original);
|
|
344
|
+
return response;
|
|
345
|
+
}
|
|
346
|
+
function withBodyDeadline(response, signal, release) {
|
|
347
|
+
if (!response.body) {
|
|
348
|
+
release();
|
|
349
|
+
return response;
|
|
350
|
+
}
|
|
351
|
+
const reader = response.body.getReader();
|
|
352
|
+
let stopped = false;
|
|
353
|
+
let bodyController;
|
|
354
|
+
const stop = (reason, errorBody) => {
|
|
355
|
+
if (stopped)
|
|
356
|
+
return;
|
|
357
|
+
stopped = true;
|
|
358
|
+
release();
|
|
359
|
+
signal.removeEventListener("abort", onAbort);
|
|
360
|
+
if (errorBody)
|
|
361
|
+
bodyController.error(reason);
|
|
362
|
+
reader.cancel(reason).catch(() => {}).finally(() => reader.releaseLock());
|
|
363
|
+
};
|
|
364
|
+
const onAbort = () => stop(signal.reason, true);
|
|
365
|
+
const body = new ReadableStream({
|
|
366
|
+
start(controller) {
|
|
367
|
+
bodyController = controller;
|
|
368
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
369
|
+
if (signal.aborted)
|
|
370
|
+
onAbort();
|
|
371
|
+
},
|
|
372
|
+
async pull(controller) {
|
|
373
|
+
try {
|
|
374
|
+
const result = await untilAborted(reader.read(), signal);
|
|
375
|
+
if (stopped)
|
|
376
|
+
return;
|
|
377
|
+
if (result.done) {
|
|
378
|
+
stopped = true;
|
|
379
|
+
release();
|
|
380
|
+
signal.removeEventListener("abort", onAbort);
|
|
381
|
+
reader.releaseLock();
|
|
382
|
+
controller.close();
|
|
383
|
+
} else {
|
|
384
|
+
controller.enqueue(result.value);
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
stop(error, true);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
cancel(reason) {
|
|
391
|
+
stop(reason, false);
|
|
392
|
+
}
|
|
393
|
+
}, { highWaterMark: 0 });
|
|
394
|
+
return preserveResponseMetadata(new Response(body, {
|
|
395
|
+
status: response.status,
|
|
396
|
+
statusText: response.statusText,
|
|
397
|
+
headers: response.headers
|
|
398
|
+
}), response);
|
|
399
|
+
}
|
|
328
400
|
async function safeFetch(input, init = {}, options = {}) {
|
|
401
|
+
init.signal?.throwIfAborted();
|
|
329
402
|
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
403
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
404
|
+
throw new RangeError("safeFetch timeoutMs must be a positive finite number.");
|
|
405
|
+
}
|
|
330
406
|
const maxRedirects = options.maxRedirects ?? 0;
|
|
331
|
-
const resolveDns = options.resolveDns ?? true;
|
|
332
407
|
const urlOptions = {
|
|
333
408
|
allowHttp: options.allowHttp,
|
|
334
|
-
resolveDns,
|
|
409
|
+
resolveDns: options.resolveDns ?? true,
|
|
335
410
|
allowPrivate: options.allowPrivate
|
|
336
411
|
};
|
|
337
412
|
const controller = new AbortController;
|
|
338
|
-
const
|
|
413
|
+
const forwardAbort = () => controller.abort(init.signal?.reason);
|
|
414
|
+
init.signal?.addEventListener("abort", forwardAbort, { once: true });
|
|
415
|
+
const timeout = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
|
|
416
|
+
const release = () => {
|
|
417
|
+
clearTimeout(timeout);
|
|
418
|
+
init.signal?.removeEventListener("abort", forwardAbort);
|
|
419
|
+
};
|
|
339
420
|
try {
|
|
340
|
-
let current = await resolveSafeOutboundTarget(input, urlOptions);
|
|
421
|
+
let current = await untilAborted(resolveSafeOutboundTarget(input, urlOptions), controller.signal);
|
|
341
422
|
let redirectCount = 0;
|
|
342
423
|
while (true) {
|
|
424
|
+
controller.signal.throwIfAborted();
|
|
343
425
|
const address = current.addresses[0];
|
|
344
426
|
const fetchUrl = address ? pinUrlToAddress(current.url, address).toString() : current.url.toString();
|
|
345
427
|
const headers = new Headers(init.headers);
|
|
346
|
-
if (address && !headers.has("host"))
|
|
428
|
+
if (address && !headers.has("host"))
|
|
347
429
|
headers.set("Host", current.url.host);
|
|
348
|
-
}
|
|
349
430
|
const fetchInit = {
|
|
350
431
|
...init,
|
|
351
432
|
headers,
|
|
352
433
|
signal: controller.signal,
|
|
353
434
|
redirect: "manual"
|
|
354
435
|
};
|
|
355
|
-
if (address)
|
|
436
|
+
if (address)
|
|
356
437
|
fetchInit.tls = { serverName: current.url.hostname };
|
|
357
|
-
|
|
358
|
-
const response = await fetch(fetchUrl, fetchInit);
|
|
438
|
+
const response = await untilAborted(fetch(fetchUrl, fetchInit), controller.signal);
|
|
359
439
|
if (response.status >= 300 && response.status < 400) {
|
|
360
440
|
const location = response.headers.get("location");
|
|
361
|
-
if (
|
|
362
|
-
|
|
441
|
+
if (location && redirectCount < maxRedirects) {
|
|
442
|
+
if (response.body)
|
|
443
|
+
await untilAborted(response.body.cancel(), controller.signal);
|
|
444
|
+
current = await untilAborted(resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions), controller.signal);
|
|
445
|
+
redirectCount++;
|
|
446
|
+
continue;
|
|
363
447
|
}
|
|
364
|
-
current = await resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions);
|
|
365
|
-
redirectCount += 1;
|
|
366
|
-
continue;
|
|
367
448
|
}
|
|
368
|
-
return response;
|
|
449
|
+
return withBodyDeadline(response, controller.signal, release);
|
|
369
450
|
}
|
|
370
|
-
}
|
|
371
|
-
|
|
451
|
+
} catch (error) {
|
|
452
|
+
controller.abort(error);
|
|
453
|
+
release();
|
|
454
|
+
throw error;
|
|
372
455
|
}
|
|
373
456
|
}
|
|
374
457
|
|
|
@@ -171,6 +171,12 @@ var init_dialect = __esm(() => {
|
|
|
171
171
|
nowExpression() {
|
|
172
172
|
return "NOW()";
|
|
173
173
|
},
|
|
174
|
+
epochMillisecondsExpression() {
|
|
175
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
176
|
+
},
|
|
177
|
+
rowLockClause(skipLocked = false) {
|
|
178
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
179
|
+
},
|
|
174
180
|
timestampValue(value) {
|
|
175
181
|
return value.toISOString();
|
|
176
182
|
},
|
|
@@ -209,6 +215,12 @@ var init_dialect = __esm(() => {
|
|
|
209
215
|
nowExpression() {
|
|
210
216
|
return "CURRENT_TIMESTAMP";
|
|
211
217
|
},
|
|
218
|
+
epochMillisecondsExpression() {
|
|
219
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
220
|
+
},
|
|
221
|
+
rowLockClause(skipLocked = false) {
|
|
222
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
223
|
+
},
|
|
212
224
|
timestampValue(value) {
|
|
213
225
|
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
214
226
|
},
|
|
@@ -247,6 +259,12 @@ var init_dialect = __esm(() => {
|
|
|
247
259
|
nowExpression() {
|
|
248
260
|
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
249
261
|
},
|
|
262
|
+
epochMillisecondsExpression() {
|
|
263
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
264
|
+
},
|
|
265
|
+
rowLockClause() {
|
|
266
|
+
return "";
|
|
267
|
+
},
|
|
250
268
|
timestampValue(value) {
|
|
251
269
|
return value.toISOString();
|
|
252
270
|
},
|
|
@@ -98,6 +98,12 @@ var init_dialect = __esm(() => {
|
|
|
98
98
|
nowExpression() {
|
|
99
99
|
return "NOW()";
|
|
100
100
|
},
|
|
101
|
+
epochMillisecondsExpression() {
|
|
102
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
103
|
+
},
|
|
104
|
+
rowLockClause(skipLocked = false) {
|
|
105
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
106
|
+
},
|
|
101
107
|
timestampValue(value) {
|
|
102
108
|
return value.toISOString();
|
|
103
109
|
},
|
|
@@ -136,6 +142,12 @@ var init_dialect = __esm(() => {
|
|
|
136
142
|
nowExpression() {
|
|
137
143
|
return "CURRENT_TIMESTAMP";
|
|
138
144
|
},
|
|
145
|
+
epochMillisecondsExpression() {
|
|
146
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
147
|
+
},
|
|
148
|
+
rowLockClause(skipLocked = false) {
|
|
149
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
150
|
+
},
|
|
139
151
|
timestampValue(value) {
|
|
140
152
|
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
141
153
|
},
|
|
@@ -174,6 +186,12 @@ var init_dialect = __esm(() => {
|
|
|
174
186
|
nowExpression() {
|
|
175
187
|
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
176
188
|
},
|
|
189
|
+
epochMillisecondsExpression() {
|
|
190
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
191
|
+
},
|
|
192
|
+
rowLockClause() {
|
|
193
|
+
return "";
|
|
194
|
+
},
|
|
177
195
|
timestampValue(value) {
|
|
178
196
|
return value.toISOString();
|
|
179
197
|
},
|
|
@@ -98,6 +98,12 @@ var init_dialect = __esm(() => {
|
|
|
98
98
|
nowExpression() {
|
|
99
99
|
return "NOW()";
|
|
100
100
|
},
|
|
101
|
+
epochMillisecondsExpression() {
|
|
102
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
103
|
+
},
|
|
104
|
+
rowLockClause(skipLocked = false) {
|
|
105
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
106
|
+
},
|
|
101
107
|
timestampValue(value) {
|
|
102
108
|
return value.toISOString();
|
|
103
109
|
},
|
|
@@ -136,6 +142,12 @@ var init_dialect = __esm(() => {
|
|
|
136
142
|
nowExpression() {
|
|
137
143
|
return "CURRENT_TIMESTAMP";
|
|
138
144
|
},
|
|
145
|
+
epochMillisecondsExpression() {
|
|
146
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
147
|
+
},
|
|
148
|
+
rowLockClause(skipLocked = false) {
|
|
149
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
150
|
+
},
|
|
139
151
|
timestampValue(value) {
|
|
140
152
|
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
141
153
|
},
|
|
@@ -174,6 +186,12 @@ var init_dialect = __esm(() => {
|
|
|
174
186
|
nowExpression() {
|
|
175
187
|
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
176
188
|
},
|
|
189
|
+
epochMillisecondsExpression() {
|
|
190
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
191
|
+
},
|
|
192
|
+
rowLockClause() {
|
|
193
|
+
return "";
|
|
194
|
+
},
|
|
177
195
|
timestampValue(value) {
|
|
178
196
|
return value.toISOString();
|
|
179
197
|
},
|
|
@@ -171,6 +171,12 @@ var init_dialect = __esm(() => {
|
|
|
171
171
|
nowExpression() {
|
|
172
172
|
return "NOW()";
|
|
173
173
|
},
|
|
174
|
+
epochMillisecondsExpression() {
|
|
175
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
176
|
+
},
|
|
177
|
+
rowLockClause(skipLocked = false) {
|
|
178
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
179
|
+
},
|
|
174
180
|
timestampValue(value) {
|
|
175
181
|
return value.toISOString();
|
|
176
182
|
},
|
|
@@ -209,6 +215,12 @@ var init_dialect = __esm(() => {
|
|
|
209
215
|
nowExpression() {
|
|
210
216
|
return "CURRENT_TIMESTAMP";
|
|
211
217
|
},
|
|
218
|
+
epochMillisecondsExpression() {
|
|
219
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
220
|
+
},
|
|
221
|
+
rowLockClause(skipLocked = false) {
|
|
222
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
223
|
+
},
|
|
212
224
|
timestampValue(value) {
|
|
213
225
|
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
214
226
|
},
|
|
@@ -247,6 +259,12 @@ var init_dialect = __esm(() => {
|
|
|
247
259
|
nowExpression() {
|
|
248
260
|
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
249
261
|
},
|
|
262
|
+
epochMillisecondsExpression() {
|
|
263
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
264
|
+
},
|
|
265
|
+
rowLockClause() {
|
|
266
|
+
return "";
|
|
267
|
+
},
|
|
250
268
|
timestampValue(value) {
|
|
251
269
|
return value.toISOString();
|
|
252
270
|
},
|
|
@@ -127,6 +127,7 @@ async function runWithTransactionScope(callback) {
|
|
|
127
127
|
var TransactionRollback, transactionScopes;
|
|
128
128
|
var init_transactionControl = __esm(() => {
|
|
129
129
|
init_asyncContextStore();
|
|
130
|
+
init_connectionContext();
|
|
130
131
|
TransactionRollback = class TransactionRollback extends Error {
|
|
131
132
|
result;
|
|
132
133
|
constructor(result) {
|
|
@@ -154,6 +155,24 @@ var init_tenantContext = __esm(() => {
|
|
|
154
155
|
// ../../src/core/events/eventBus.ts
|
|
155
156
|
class EventBus {
|
|
156
157
|
constructor() {}
|
|
158
|
+
transactionalListeners = new Map;
|
|
159
|
+
listenTransactional(event, listener) {
|
|
160
|
+
const handlers = this.transactionalListeners.get(event) ?? new Set;
|
|
161
|
+
handlers.add(listener);
|
|
162
|
+
this.transactionalListeners.set(event, handlers);
|
|
163
|
+
return () => {
|
|
164
|
+
handlers.delete(listener);
|
|
165
|
+
if (!handlers.size)
|
|
166
|
+
this.transactionalListeners.delete(event);
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
hasTransactionalListeners(event) {
|
|
170
|
+
return (this.transactionalListeners.get(event)?.size ?? 0) > 0;
|
|
171
|
+
}
|
|
172
|
+
async dispatchTransactional(event, payload) {
|
|
173
|
+
for (const listener of this.transactionalListeners.get(event) ?? [])
|
|
174
|
+
await listener(payload);
|
|
175
|
+
}
|
|
157
176
|
listeners = new Map;
|
|
158
177
|
on(event, listener) {
|
|
159
178
|
return this.listen(event, listener);
|
|
@@ -408,6 +427,12 @@ var init_dialect = __esm(() => {
|
|
|
408
427
|
nowExpression() {
|
|
409
428
|
return "NOW()";
|
|
410
429
|
},
|
|
430
|
+
epochMillisecondsExpression() {
|
|
431
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
432
|
+
},
|
|
433
|
+
rowLockClause(skipLocked = false) {
|
|
434
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
435
|
+
},
|
|
411
436
|
timestampValue(value) {
|
|
412
437
|
return value.toISOString();
|
|
413
438
|
},
|
|
@@ -446,6 +471,12 @@ var init_dialect = __esm(() => {
|
|
|
446
471
|
nowExpression() {
|
|
447
472
|
return "CURRENT_TIMESTAMP";
|
|
448
473
|
},
|
|
474
|
+
epochMillisecondsExpression() {
|
|
475
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
476
|
+
},
|
|
477
|
+
rowLockClause(skipLocked = false) {
|
|
478
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
479
|
+
},
|
|
449
480
|
timestampValue(value) {
|
|
450
481
|
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
451
482
|
},
|
|
@@ -484,6 +515,12 @@ var init_dialect = __esm(() => {
|
|
|
484
515
|
nowExpression() {
|
|
485
516
|
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
486
517
|
},
|
|
518
|
+
epochMillisecondsExpression() {
|
|
519
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
520
|
+
},
|
|
521
|
+
rowLockClause() {
|
|
522
|
+
return "";
|
|
523
|
+
},
|
|
487
524
|
timestampValue(value) {
|
|
488
525
|
return value.toISOString();
|
|
489
526
|
},
|
|
@@ -98,6 +98,12 @@ var init_dialect = __esm(() => {
|
|
|
98
98
|
nowExpression() {
|
|
99
99
|
return "NOW()";
|
|
100
100
|
},
|
|
101
|
+
epochMillisecondsExpression() {
|
|
102
|
+
return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
|
|
103
|
+
},
|
|
104
|
+
rowLockClause(skipLocked = false) {
|
|
105
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
106
|
+
},
|
|
101
107
|
timestampValue(value) {
|
|
102
108
|
return value.toISOString();
|
|
103
109
|
},
|
|
@@ -136,6 +142,12 @@ var init_dialect = __esm(() => {
|
|
|
136
142
|
nowExpression() {
|
|
137
143
|
return "CURRENT_TIMESTAMP";
|
|
138
144
|
},
|
|
145
|
+
epochMillisecondsExpression() {
|
|
146
|
+
return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
|
|
147
|
+
},
|
|
148
|
+
rowLockClause(skipLocked = false) {
|
|
149
|
+
return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
|
|
150
|
+
},
|
|
139
151
|
timestampValue(value) {
|
|
140
152
|
return value.toISOString().slice(0, 19).replace("T", " ");
|
|
141
153
|
},
|
|
@@ -174,6 +186,12 @@ var init_dialect = __esm(() => {
|
|
|
174
186
|
nowExpression() {
|
|
175
187
|
return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
|
|
176
188
|
},
|
|
189
|
+
epochMillisecondsExpression() {
|
|
190
|
+
return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
|
|
191
|
+
},
|
|
192
|
+
rowLockClause() {
|
|
193
|
+
return "";
|
|
194
|
+
},
|
|
177
195
|
timestampValue(value) {
|
|
178
196
|
return value.toISOString();
|
|
179
197
|
},
|
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
var __esm = (fn, res, err) => () => {
|
|
3
|
+
if (fn)
|
|
4
|
+
try {
|
|
5
|
+
res = fn(fn = 0);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
err = [e];
|
|
8
|
+
}
|
|
9
|
+
if (err)
|
|
10
|
+
throw err[0];
|
|
11
|
+
return res;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ../../src/core/runtime/asyncContextStore.ts
|
|
15
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
16
|
+
function createAsyncContextStore(key) {
|
|
17
|
+
const symbol = Symbol.for(key);
|
|
18
|
+
const globalRecord = globalThis;
|
|
19
|
+
const existing = globalRecord[symbol];
|
|
20
|
+
if (existing) {
|
|
21
|
+
return existing;
|
|
22
|
+
}
|
|
23
|
+
const store = new AsyncLocalStorage;
|
|
24
|
+
globalRecord[symbol] = store;
|
|
25
|
+
return store;
|
|
26
|
+
}
|
|
27
|
+
var init_asyncContextStore = () => {};
|
|
28
|
+
|
|
29
|
+
// ../../src/core/database/connectionContext.ts
|
|
30
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
31
|
+
return activeConnection.run(connection, callback);
|
|
32
|
+
}
|
|
33
|
+
function getActiveDatabaseConnection(fallback) {
|
|
34
|
+
return activeConnection.getStore() ?? fallback;
|
|
35
|
+
}
|
|
36
|
+
function hasActiveDatabaseConnection() {
|
|
37
|
+
return activeConnection.getStore() !== undefined;
|
|
38
|
+
}
|
|
39
|
+
var activeConnection;
|
|
40
|
+
var init_connectionContext = __esm(() => {
|
|
41
|
+
init_asyncContextStore();
|
|
42
|
+
activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
43
|
+
});
|
|
44
|
+
|
|
2
45
|
// ../../src/core/database/sqliteConnection.ts
|
|
46
|
+
init_connectionContext();
|
|
3
47
|
import { Database } from "bun:sqlite";
|
|
4
48
|
function isRowReturning(sql) {
|
|
5
49
|
const upper = sql.replace(/\s+/g, " ").trim().toUpperCase();
|
|
@@ -19,20 +63,70 @@ function createSqliteConnection(filename) {
|
|
|
19
63
|
db.exec("PRAGMA journal_mode = WAL");
|
|
20
64
|
db.exec("PRAGMA synchronous = NORMAL");
|
|
21
65
|
}
|
|
22
|
-
|
|
66
|
+
let tail = Promise.resolve();
|
|
67
|
+
let pending = 0;
|
|
68
|
+
let closed = false;
|
|
69
|
+
const transactions = new WeakSet;
|
|
70
|
+
function execute(query, params) {
|
|
71
|
+
const statement = db.query(query);
|
|
72
|
+
const args = [...params];
|
|
73
|
+
if (isRowReturning(query))
|
|
74
|
+
return statement.all(...args);
|
|
75
|
+
statement.run(...args);
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
function enqueue(operation) {
|
|
79
|
+
if (closed)
|
|
80
|
+
return Promise.reject(new Error("SQLite connection is closed."));
|
|
81
|
+
pending++;
|
|
82
|
+
const result = tail.then(operation).finally(() => {
|
|
83
|
+
pending--;
|
|
84
|
+
});
|
|
85
|
+
tail = result.then(() => {}, () => {});
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
const connection = {
|
|
23
89
|
async unsafe(query, params = []) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
90
|
+
const active = getActiveDatabaseConnection(connection);
|
|
91
|
+
if (transactions.has(active))
|
|
92
|
+
return active.unsafe(query, params);
|
|
93
|
+
return enqueue(() => execute(query, params));
|
|
94
|
+
},
|
|
95
|
+
async begin(callback) {
|
|
96
|
+
if (transactions.has(getActiveDatabaseConnection(connection))) {
|
|
97
|
+
throw new Error("Use runInTransaction for nested SQLite transactions.");
|
|
28
98
|
}
|
|
29
|
-
|
|
30
|
-
|
|
99
|
+
return enqueue(async () => {
|
|
100
|
+
db.exec("BEGIN IMMEDIATE");
|
|
101
|
+
let open = true;
|
|
102
|
+
const transaction = {
|
|
103
|
+
async unsafe(query, params = []) {
|
|
104
|
+
if (!open)
|
|
105
|
+
throw new Error("SQLite transaction is no longer active.");
|
|
106
|
+
return execute(query, params);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
transactions.add(transaction);
|
|
110
|
+
try {
|
|
111
|
+
const result = await runWithDatabaseConnection(transaction, () => callback(transaction));
|
|
112
|
+
db.exec("COMMIT");
|
|
113
|
+
return result;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
db.exec("ROLLBACK");
|
|
116
|
+
throw error;
|
|
117
|
+
} finally {
|
|
118
|
+
open = false;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
31
121
|
},
|
|
32
122
|
close() {
|
|
123
|
+
if (pending > 0)
|
|
124
|
+
throw new Error("Drain SQLite operations before closing the connection.");
|
|
125
|
+
closed = true;
|
|
33
126
|
db.close();
|
|
34
127
|
}
|
|
35
128
|
};
|
|
129
|
+
return connection;
|
|
36
130
|
}
|
|
37
131
|
export {
|
|
38
132
|
createSqliteConnection
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../../index.js";
|
|
@@ -26,6 +26,22 @@ function createAsyncContextStore(key) {
|
|
|
26
26
|
}
|
|
27
27
|
var init_asyncContextStore = () => {};
|
|
28
28
|
|
|
29
|
+
// ../../src/core/database/connectionContext.ts
|
|
30
|
+
function runWithDatabaseConnection(connection, callback) {
|
|
31
|
+
return activeConnection.run(connection, callback);
|
|
32
|
+
}
|
|
33
|
+
function getActiveDatabaseConnection(fallback) {
|
|
34
|
+
return activeConnection.getStore() ?? fallback;
|
|
35
|
+
}
|
|
36
|
+
function hasActiveDatabaseConnection() {
|
|
37
|
+
return activeConnection.getStore() !== undefined;
|
|
38
|
+
}
|
|
39
|
+
var activeConnection;
|
|
40
|
+
var init_connectionContext = __esm(() => {
|
|
41
|
+
init_asyncContextStore();
|
|
42
|
+
activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
43
|
+
});
|
|
44
|
+
|
|
29
45
|
// ../../src/core/database/transactionControl.ts
|
|
30
46
|
function unwrapTransactionRollback(error, depth = 0) {
|
|
31
47
|
if (error instanceof TransactionRollback) {
|
|
@@ -89,6 +105,7 @@ async function runWithTransactionScope(callback) {
|
|
|
89
105
|
var TransactionRollback, transactionScopes;
|
|
90
106
|
var init_transactionControl = __esm(() => {
|
|
91
107
|
init_asyncContextStore();
|
|
108
|
+
init_connectionContext();
|
|
92
109
|
TransactionRollback = class TransactionRollback extends Error {
|
|
93
110
|
result;
|
|
94
111
|
constructor(result) {
|
|
@@ -225,6 +242,19 @@ function isUniqueConstraintError(error) {
|
|
|
225
242
|
// ../../src/core/http/response.ts
|
|
226
243
|
init_transactionControl();
|
|
227
244
|
|
|
245
|
+
// ../../src/core/http/requestMetaContext.ts
|
|
246
|
+
init_asyncContextStore();
|
|
247
|
+
var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
|
|
248
|
+
function runWithRequestMeta(meta, callback) {
|
|
249
|
+
return requestMetaContext.run(meta, callback);
|
|
250
|
+
}
|
|
251
|
+
function currentRequestMeta() {
|
|
252
|
+
return requestMetaContext.getStore() ?? {
|
|
253
|
+
ipAddress: null,
|
|
254
|
+
userAgent: null
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
228
258
|
// ../../src/core/http/webErrorResponse.ts
|
|
229
259
|
import { toHttpError as toHttpError2, ValidationError } from "@getstrata/core/errors/http";
|
|
230
260
|
import { appLogger } from "@getstrata/core/logging/logger";
|
|
@@ -289,7 +319,7 @@ function readSpaPrefix() {
|
|
|
289
319
|
}
|
|
290
320
|
|
|
291
321
|
// ../../src/core/view/webErrorView.ts
|
|
292
|
-
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
322
|
+
import { currentRequestMeta as currentRequestMeta2 } from "@getstrata/core/http/requestMetaContext";
|
|
293
323
|
|
|
294
324
|
// ../../src/core/http/safeInternalPath.ts
|
|
295
325
|
var NESTED_REDIRECT_KEYS = new Set(["redirect", "next", "return", "returnTo", "return_to"]);
|
|
@@ -408,7 +438,7 @@ async function renderWebErrorHtml(input) {
|
|
|
408
438
|
try {
|
|
409
439
|
return await render({
|
|
410
440
|
...input,
|
|
411
|
-
request: input.request ??
|
|
441
|
+
request: input.request ?? currentRequestMeta2().request
|
|
412
442
|
});
|
|
413
443
|
} catch {
|
|
414
444
|
return renderKernelErrorChrome(input);
|
|
@@ -490,7 +520,7 @@ function logServerError(error, mappedError) {
|
|
|
490
520
|
});
|
|
491
521
|
}
|
|
492
522
|
async function webErrorResponse(error, request) {
|
|
493
|
-
if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
|
|
523
|
+
if (currentRequestMeta().errorFormat === "json" || !request || !isViewsEnabled() || requestPrefersJson(request)) {
|
|
494
524
|
return null;
|
|
495
525
|
}
|
|
496
526
|
const mappedError = toHttpError2(error) ?? mapDatabaseError(error);
|
|
@@ -555,17 +585,21 @@ function withJsonErrorHandling(handler) {
|
|
|
555
585
|
};
|
|
556
586
|
}
|
|
557
587
|
function createJsonErrorMiddleware() {
|
|
558
|
-
return async (_request, next) => {
|
|
588
|
+
return async (_request, next) => await runWithRequestMeta({ ...currentRequestMeta(), errorFormat: "json" }, async () => {
|
|
559
589
|
try {
|
|
560
590
|
return await next();
|
|
561
591
|
} catch (error) {
|
|
562
592
|
requestTransactionRollback();
|
|
563
593
|
return errorResponse(error);
|
|
564
594
|
}
|
|
565
|
-
};
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function createWebErrorMiddleware() {
|
|
598
|
+
return async (request, next) => await withErrorHandling(async (_request) => next())(request);
|
|
566
599
|
}
|
|
567
600
|
export {
|
|
568
601
|
createJsonErrorMiddleware,
|
|
602
|
+
createWebErrorMiddleware,
|
|
569
603
|
createdResponse,
|
|
570
604
|
errorResponse,
|
|
571
605
|
jsonResponse,
|