@heavybit/shared-lib 0.0.0-stage → 1.1.0
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/dist/cjs/access/index.js +118 -0
- package/dist/cjs/access/index.js.map +1 -0
- package/dist/cjs/alerting/index.js +56 -0
- package/dist/cjs/alerting/index.js.map +1 -0
- package/dist/cjs/cache/index.js +167 -0
- package/dist/cjs/cache/index.js.map +1 -0
- package/dist/cjs/common/async.js +40 -0
- package/dist/cjs/common/async.js.map +1 -0
- package/dist/cjs/common/audit.js +21 -0
- package/dist/cjs/common/audit.js.map +1 -0
- package/dist/cjs/common/crypto.js +75 -0
- package/dist/cjs/common/crypto.js.map +1 -0
- package/dist/cjs/common/errors.js +90 -0
- package/dist/cjs/common/errors.js.map +1 -0
- package/dist/cjs/common/id.js +40 -0
- package/dist/cjs/common/id.js.map +1 -0
- package/dist/cjs/common/index.js +28 -0
- package/dist/cjs/common/index.js.map +1 -0
- package/dist/cjs/common/jwt.js +59 -0
- package/dist/cjs/common/jwt.js.map +1 -0
- package/dist/cjs/common/password.js +38 -0
- package/dist/cjs/common/password.js.map +1 -0
- package/dist/cjs/common/phone.js +42 -0
- package/dist/cjs/common/phone.js.map +1 -0
- package/dist/cjs/common/response.js +32 -0
- package/dist/cjs/common/response.js.map +1 -0
- package/dist/cjs/common/text.js +46 -0
- package/dist/cjs/common/text.js.map +1 -0
- package/dist/cjs/common/time.js +109 -0
- package/dist/cjs/common/time.js.map +1 -0
- package/dist/cjs/config/index.js +96 -0
- package/dist/cjs/config/index.js.map +1 -0
- package/dist/cjs/contracts/editorial-draft.js +111 -0
- package/dist/cjs/contracts/editorial-draft.js.map +1 -0
- package/dist/cjs/contracts/index.js +20 -0
- package/dist/cjs/contracts/index.js.map +1 -0
- package/dist/cjs/contracts/notification-dispatch.js +92 -0
- package/dist/cjs/contracts/notification-dispatch.js.map +1 -0
- package/dist/cjs/contracts/upload-import.js +54 -0
- package/dist/cjs/contracts/upload-import.js.map +1 -0
- package/dist/cjs/database/index.js +57 -0
- package/dist/cjs/database/index.js.map +1 -0
- package/dist/cjs/express/auth.js +72 -0
- package/dist/cjs/express/auth.js.map +1 -0
- package/dist/cjs/express/health.js +31 -0
- package/dist/cjs/express/health.js.map +1 -0
- package/dist/cjs/express/index.js +20 -0
- package/dist/cjs/express/index.js.map +1 -0
- package/dist/cjs/express/middleware.js +95 -0
- package/dist/cjs/express/middleware.js.map +1 -0
- package/dist/cjs/http/index.js +90 -0
- package/dist/cjs/http/index.js.map +1 -0
- package/dist/cjs/http/safe-fetch.js +199 -0
- package/dist/cjs/http/safe-fetch.js.map +1 -0
- package/dist/cjs/identity/index.js +117 -0
- package/dist/cjs/identity/index.js.map +1 -0
- package/dist/cjs/index.js +29 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/knex/index.js +76 -0
- package/dist/cjs/knex/index.js.map +1 -0
- package/dist/cjs/logging/index.js +75 -0
- package/dist/cjs/logging/index.js.map +1 -0
- package/dist/cjs/messaging/connection.js +115 -0
- package/dist/cjs/messaging/connection.js.map +1 -0
- package/dist/cjs/messaging/consumer.js +134 -0
- package/dist/cjs/messaging/consumer.js.map +1 -0
- package/dist/cjs/messaging/envelope.js +23 -0
- package/dist/cjs/messaging/envelope.js.map +1 -0
- package/dist/cjs/messaging/idempotency.js +57 -0
- package/dist/cjs/messaging/idempotency.js.map +1 -0
- package/dist/cjs/messaging/index.js +62 -0
- package/dist/cjs/messaging/index.js.map +1 -0
- package/dist/cjs/messaging/monitor.js +48 -0
- package/dist/cjs/messaging/monitor.js.map +1 -0
- package/dist/cjs/messaging/outbox.js +135 -0
- package/dist/cjs/messaging/outbox.js.map +1 -0
- package/dist/cjs/messaging/publisher.js +149 -0
- package/dist/cjs/messaging/publisher.js.map +1 -0
- package/dist/cjs/messaging/rpc.js +125 -0
- package/dist/cjs/messaging/rpc.js.map +1 -0
- package/dist/cjs/messaging/staff-notifier.js +78 -0
- package/dist/cjs/messaging/staff-notifier.js.map +1 -0
- package/dist/cjs/messaging/topology.js +22 -0
- package/dist/cjs/messaging/topology.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/rate-limit/index.js +61 -0
- package/dist/cjs/rate-limit/index.js.map +1 -0
- package/dist/cjs/scheduling/index.js +88 -0
- package/dist/cjs/scheduling/index.js.map +1 -0
- package/dist/cjs/security/index.js +39 -0
- package/dist/cjs/security/index.js.map +1 -0
- package/dist/cjs/socket/index.js +53 -0
- package/dist/cjs/socket/index.js.map +1 -0
- package/dist/cjs/testing/index.js +73 -0
- package/dist/cjs/testing/index.js.map +1 -0
- package/dist/cjs/types/index.js +3 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/validation/index.js +41 -0
- package/dist/cjs/validation/index.js.map +1 -0
- package/dist/esm/access/index.js +104 -0
- package/dist/esm/access/index.js.map +1 -0
- package/dist/esm/alerting/index.js +52 -0
- package/dist/esm/alerting/index.js.map +1 -0
- package/dist/esm/cache/index.js +163 -0
- package/dist/esm/cache/index.js.map +1 -0
- package/dist/esm/common/async.js +33 -0
- package/dist/esm/common/async.js.map +1 -0
- package/dist/esm/common/audit.js +18 -0
- package/dist/esm/common/audit.js.map +1 -0
- package/dist/esm/common/crypto.js +64 -0
- package/dist/esm/common/crypto.js.map +1 -0
- package/dist/esm/common/errors.js +75 -0
- package/dist/esm/common/errors.js.map +1 -0
- package/dist/esm/common/id.js +33 -0
- package/dist/esm/common/id.js.map +1 -0
- package/dist/esm/common/index.js +12 -0
- package/dist/esm/common/index.js.map +1 -0
- package/dist/esm/common/jwt.js +49 -0
- package/dist/esm/common/jwt.js.map +1 -0
- package/dist/esm/common/password.js +30 -0
- package/dist/esm/common/password.js.map +1 -0
- package/dist/esm/common/phone.js +33 -0
- package/dist/esm/common/phone.js.map +1 -0
- package/dist/esm/common/response.js +24 -0
- package/dist/esm/common/response.js.map +1 -0
- package/dist/esm/common/text.js +39 -0
- package/dist/esm/common/text.js.map +1 -0
- package/dist/esm/common/time.js +96 -0
- package/dist/esm/common/time.js.map +1 -0
- package/dist/esm/config/index.js +88 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/contracts/editorial-draft.js +108 -0
- package/dist/esm/contracts/editorial-draft.js.map +1 -0
- package/dist/esm/contracts/index.js +4 -0
- package/dist/esm/contracts/index.js.map +1 -0
- package/dist/esm/contracts/notification-dispatch.js +89 -0
- package/dist/esm/contracts/notification-dispatch.js.map +1 -0
- package/dist/esm/contracts/upload-import.js +51 -0
- package/dist/esm/contracts/upload-import.js.map +1 -0
- package/dist/esm/database/index.js +48 -0
- package/dist/esm/database/index.js.map +1 -0
- package/dist/esm/express/auth.js +63 -0
- package/dist/esm/express/auth.js.map +1 -0
- package/dist/esm/express/health.js +27 -0
- package/dist/esm/express/health.js.map +1 -0
- package/dist/esm/express/index.js +4 -0
- package/dist/esm/express/index.js.map +1 -0
- package/dist/esm/express/middleware.js +84 -0
- package/dist/esm/express/middleware.js.map +1 -0
- package/dist/esm/http/index.js +73 -0
- package/dist/esm/http/index.js.map +1 -0
- package/dist/esm/http/safe-fetch.js +193 -0
- package/dist/esm/http/safe-fetch.js.map +1 -0
- package/dist/esm/identity/index.js +111 -0
- package/dist/esm/identity/index.js.map +1 -0
- package/dist/esm/index.js +13 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/knex/index.js +66 -0
- package/dist/esm/knex/index.js.map +1 -0
- package/dist/esm/logging/index.js +67 -0
- package/dist/esm/logging/index.js.map +1 -0
- package/dist/esm/messaging/connection.js +108 -0
- package/dist/esm/messaging/connection.js.map +1 -0
- package/dist/esm/messaging/consumer.js +130 -0
- package/dist/esm/messaging/consumer.js.map +1 -0
- package/dist/esm/messaging/envelope.js +19 -0
- package/dist/esm/messaging/envelope.js.map +1 -0
- package/dist/esm/messaging/idempotency.js +52 -0
- package/dist/esm/messaging/idempotency.js.map +1 -0
- package/dist/esm/messaging/index.js +45 -0
- package/dist/esm/messaging/index.js.map +1 -0
- package/dist/esm/messaging/monitor.js +44 -0
- package/dist/esm/messaging/monitor.js.map +1 -0
- package/dist/esm/messaging/outbox.js +129 -0
- package/dist/esm/messaging/outbox.js.map +1 -0
- package/dist/esm/messaging/publisher.js +142 -0
- package/dist/esm/messaging/publisher.js.map +1 -0
- package/dist/esm/messaging/rpc.js +120 -0
- package/dist/esm/messaging/rpc.js.map +1 -0
- package/dist/esm/messaging/staff-notifier.js +73 -0
- package/dist/esm/messaging/staff-notifier.js.map +1 -0
- package/dist/esm/messaging/topology.js +16 -0
- package/dist/esm/messaging/topology.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/rate-limit/index.js +56 -0
- package/dist/esm/rate-limit/index.js.map +1 -0
- package/dist/esm/scheduling/index.js +83 -0
- package/dist/esm/scheduling/index.js.map +1 -0
- package/dist/esm/security/index.js +36 -0
- package/dist/esm/security/index.js.map +1 -0
- package/dist/esm/socket/index.js +47 -0
- package/dist/esm/socket/index.js.map +1 -0
- package/dist/esm/testing/index.js +66 -0
- package/dist/esm/testing/index.js.map +1 -0
- package/dist/esm/types/index.js +2 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/validation/index.js +36 -0
- package/dist/esm/validation/index.js.map +1 -0
- package/dist/types/access/index.d.ts +50 -0
- package/dist/types/access/index.d.ts.map +1 -0
- package/dist/types/alerting/index.d.ts +43 -0
- package/dist/types/alerting/index.d.ts.map +1 -0
- package/dist/types/cache/index.d.ts +45 -0
- package/dist/types/cache/index.d.ts.map +1 -0
- package/dist/types/common/async.d.ts +14 -0
- package/dist/types/common/async.d.ts.map +1 -0
- package/dist/types/common/audit.d.ts +17 -0
- package/dist/types/common/audit.d.ts.map +1 -0
- package/dist/types/common/crypto.d.ts +18 -0
- package/dist/types/common/crypto.d.ts.map +1 -0
- package/dist/types/common/errors.d.ts +71 -0
- package/dist/types/common/errors.d.ts.map +1 -0
- package/dist/types/common/id.d.ts +14 -0
- package/dist/types/common/id.d.ts.map +1 -0
- package/dist/types/common/index.d.ts +12 -0
- package/dist/types/common/index.d.ts.map +1 -0
- package/dist/types/common/jwt.d.ts +28 -0
- package/dist/types/common/jwt.d.ts.map +1 -0
- package/dist/types/common/password.d.ts +4 -0
- package/dist/types/common/password.d.ts.map +1 -0
- package/dist/types/common/phone.d.ts +10 -0
- package/dist/types/common/phone.d.ts.map +1 -0
- package/dist/types/common/response.d.ts +12 -0
- package/dist/types/common/response.d.ts.map +1 -0
- package/dist/types/common/text.d.ts +10 -0
- package/dist/types/common/text.d.ts.map +1 -0
- package/dist/types/common/time.d.ts +40 -0
- package/dist/types/common/time.d.ts.map +1 -0
- package/dist/types/config/index.d.ts +115 -0
- package/dist/types/config/index.d.ts.map +1 -0
- package/dist/types/contracts/editorial-draft.d.ts +577 -0
- package/dist/types/contracts/editorial-draft.d.ts.map +1 -0
- package/dist/types/contracts/index.d.ts +4 -0
- package/dist/types/contracts/index.d.ts.map +1 -0
- package/dist/types/contracts/notification-dispatch.d.ts +175 -0
- package/dist/types/contracts/notification-dispatch.d.ts.map +1 -0
- package/dist/types/contracts/upload-import.d.ts +96 -0
- package/dist/types/contracts/upload-import.d.ts.map +1 -0
- package/dist/types/database/index.d.ts +25 -0
- package/dist/types/database/index.d.ts.map +1 -0
- package/dist/types/express/auth.d.ts +27 -0
- package/dist/types/express/auth.d.ts.map +1 -0
- package/dist/types/express/health.d.ts +14 -0
- package/dist/types/express/health.d.ts.map +1 -0
- package/dist/types/express/index.d.ts +4 -0
- package/dist/types/express/index.d.ts.map +1 -0
- package/dist/types/express/middleware.d.ts +20 -0
- package/dist/types/express/middleware.d.ts.map +1 -0
- package/dist/types/http/index.d.ts +34 -0
- package/dist/types/http/index.d.ts.map +1 -0
- package/dist/types/http/safe-fetch.d.ts +58 -0
- package/dist/types/http/safe-fetch.d.ts.map +1 -0
- package/dist/types/identity/index.d.ts +51 -0
- package/dist/types/identity/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/knex/index.d.ts +25 -0
- package/dist/types/knex/index.d.ts.map +1 -0
- package/dist/types/logging/index.d.ts +22 -0
- package/dist/types/logging/index.d.ts.map +1 -0
- package/dist/types/messaging/connection.d.ts +48 -0
- package/dist/types/messaging/connection.d.ts.map +1 -0
- package/dist/types/messaging/consumer.d.ts +56 -0
- package/dist/types/messaging/consumer.d.ts.map +1 -0
- package/dist/types/messaging/envelope.d.ts +16 -0
- package/dist/types/messaging/envelope.d.ts.map +1 -0
- package/dist/types/messaging/idempotency.d.ts +29 -0
- package/dist/types/messaging/idempotency.d.ts.map +1 -0
- package/dist/types/messaging/index.d.ts +39 -0
- package/dist/types/messaging/index.d.ts.map +1 -0
- package/dist/types/messaging/monitor.d.ts +23 -0
- package/dist/types/messaging/monitor.d.ts.map +1 -0
- package/dist/types/messaging/outbox.d.ts +51 -0
- package/dist/types/messaging/outbox.d.ts.map +1 -0
- package/dist/types/messaging/publisher.d.ts +84 -0
- package/dist/types/messaging/publisher.d.ts.map +1 -0
- package/dist/types/messaging/rpc.d.ts +46 -0
- package/dist/types/messaging/rpc.d.ts.map +1 -0
- package/dist/types/messaging/staff-notifier.d.ts +56 -0
- package/dist/types/messaging/staff-notifier.d.ts.map +1 -0
- package/dist/types/messaging/topology.d.ts +23 -0
- package/dist/types/messaging/topology.d.ts.map +1 -0
- package/dist/types/rate-limit/index.d.ts +28 -0
- package/dist/types/rate-limit/index.d.ts.map +1 -0
- package/dist/types/scheduling/index.d.ts +33 -0
- package/dist/types/scheduling/index.d.ts.map +1 -0
- package/dist/types/security/index.d.ts +20 -0
- package/dist/types/security/index.d.ts.map +1 -0
- package/dist/types/socket/index.d.ts +30 -0
- package/dist/types/socket/index.d.ts.map +1 -0
- package/dist/types/testing/index.d.ts +41 -0
- package/dist/types/testing/index.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +65 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/validation/index.d.ts +34 -0
- package/dist/types/validation/index.d.ts.map +1 -0
- package/package.json +189 -4
- package/README.md +0 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.userRoom = void 0;
|
|
4
|
+
exports.createSocketServer = createSocketServer;
|
|
5
|
+
exports.gatewaySocketAuth = gatewaySocketAuth;
|
|
6
|
+
const socket_io_1 = require("socket.io");
|
|
7
|
+
const index_js_1 = require("../identity/index.js");
|
|
8
|
+
const index_js_2 = require("../logging/index.js");
|
|
9
|
+
const userRoom = (userId) => `user:${userId}`;
|
|
10
|
+
exports.userRoom = userRoom;
|
|
11
|
+
function createSocketServer(httpServer, options) {
|
|
12
|
+
const logger = options.logger ?? index_js_2.silentLogger;
|
|
13
|
+
const io = new socket_io_1.Server(httpServer, {
|
|
14
|
+
path: options.path,
|
|
15
|
+
cors: { origin: options.corsOrigins?.length ? options.corsOrigins : false, credentials: true },
|
|
16
|
+
...(options.adapter ? { adapter: options.adapter } : {}),
|
|
17
|
+
...options.serverOptions,
|
|
18
|
+
});
|
|
19
|
+
io.use(async (socket, next) => {
|
|
20
|
+
try {
|
|
21
|
+
const user = await options.authenticate(socket);
|
|
22
|
+
if (!user)
|
|
23
|
+
return next(new Error('Authentication failed'));
|
|
24
|
+
socket.data.user = user;
|
|
25
|
+
next();
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
logger.warn('Socket authentication rejected', { error: error.message });
|
|
29
|
+
next(new Error('Authentication failed'));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
io.on('connection', (socket) => {
|
|
33
|
+
const user = socket.data.user;
|
|
34
|
+
const rooms = options.rooms ? options.rooms(user) : [(0, exports.userRoom)(user.id)];
|
|
35
|
+
void socket.join(rooms);
|
|
36
|
+
logger.debug('Socket connected', { userId: user.id, rooms });
|
|
37
|
+
socket.on('disconnect', (reason) => logger.debug('Socket disconnected', { userId: user.id, reason }));
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
io,
|
|
41
|
+
emitToUser: (userId, event, payload) => { io.to((0, exports.userRoom)(userId)).emit(event, payload); },
|
|
42
|
+
emitToRoom: (room, event, payload) => { io.to(room).emit(event, payload); },
|
|
43
|
+
close: () => new Promise((resolve) => { io.close(() => resolve()); }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/** Trusts the gateway-signed identity headers forwarded on the WebSocket upgrade request. */
|
|
47
|
+
function gatewaySocketAuth(secret) {
|
|
48
|
+
return (socket) => {
|
|
49
|
+
const result = (0, index_js_1.verifyIdentityHeaders)(socket.handshake.headers, secret);
|
|
50
|
+
return result.status === 'authenticated' ? result.user : null;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/socket/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RecordingBus = exports.RecordingLogger = void 0;
|
|
4
|
+
exports.makeAuthUser = makeAuthUser;
|
|
5
|
+
exports.identityHeaders = identityHeaders;
|
|
6
|
+
const id_js_1 = require("../common/id.js");
|
|
7
|
+
const index_js_1 = require("../identity/index.js");
|
|
8
|
+
const envelope_js_1 = require("../messaging/envelope.js");
|
|
9
|
+
/** A logger spy: records every line (level, message, meta) so tests can assert on log context. */
|
|
10
|
+
class RecordingLogger {
|
|
11
|
+
lines = [];
|
|
12
|
+
debug(message, meta = {}) { this.lines.push({ level: 'debug', message, meta }); }
|
|
13
|
+
info(message, meta = {}) { this.lines.push({ level: 'info', message, meta }); }
|
|
14
|
+
warn(message, meta = {}) { this.lines.push({ level: 'warn', message, meta }); }
|
|
15
|
+
error(message, meta = {}) { this.lines.push({ level: 'error', message, meta }); }
|
|
16
|
+
}
|
|
17
|
+
exports.RecordingLogger = RecordingLogger;
|
|
18
|
+
/** An AuthUser for tests. */
|
|
19
|
+
function makeAuthUser(overrides = {}) {
|
|
20
|
+
return { id: (0, id_js_1.newId)(), email: 'user@example.com', roles: [], permissions: [], scopes: {}, attrs: {}, ...overrides };
|
|
21
|
+
}
|
|
22
|
+
/** Gateway-signed identity headers for route tests (use with supertest `.set()`). */
|
|
23
|
+
function identityHeaders(user, secret) {
|
|
24
|
+
return (0, index_js_1.signIdentityHeaders)(user, secret);
|
|
25
|
+
}
|
|
26
|
+
/** Records everything published/sent so tests can assert on events, commands and direct queue sends. */
|
|
27
|
+
class RecordingBus {
|
|
28
|
+
source;
|
|
29
|
+
events = [];
|
|
30
|
+
commands = [];
|
|
31
|
+
queued = [];
|
|
32
|
+
failWith = null;
|
|
33
|
+
constructor(source = 'test') {
|
|
34
|
+
this.source = source;
|
|
35
|
+
}
|
|
36
|
+
async publish(type, data, meta) {
|
|
37
|
+
if (this.failWith)
|
|
38
|
+
throw this.failWith;
|
|
39
|
+
const envelope = (0, envelope_js_1.buildEnvelope)(type, data, this.source, meta);
|
|
40
|
+
this.events.push(envelope);
|
|
41
|
+
return envelope;
|
|
42
|
+
}
|
|
43
|
+
async publishEnvelope(envelope) {
|
|
44
|
+
if (this.failWith)
|
|
45
|
+
throw this.failWith;
|
|
46
|
+
this.events.push(envelope);
|
|
47
|
+
}
|
|
48
|
+
async send(command, data, meta) {
|
|
49
|
+
if (this.failWith)
|
|
50
|
+
throw this.failWith;
|
|
51
|
+
const envelope = (0, envelope_js_1.buildEnvelope)(command, data, this.source, meta);
|
|
52
|
+
this.commands.push(envelope);
|
|
53
|
+
return envelope;
|
|
54
|
+
}
|
|
55
|
+
async sendEnvelope(envelope) {
|
|
56
|
+
if (this.failWith)
|
|
57
|
+
throw this.failWith;
|
|
58
|
+
this.commands.push(envelope);
|
|
59
|
+
}
|
|
60
|
+
async sendToQueue(queue, payload, options = {}) {
|
|
61
|
+
if (this.failWith)
|
|
62
|
+
throw this.failWith;
|
|
63
|
+
this.queued.push({ queue, payload, options });
|
|
64
|
+
}
|
|
65
|
+
eventsOfType(type) {
|
|
66
|
+
return this.events.filter((e) => e.type === type);
|
|
67
|
+
}
|
|
68
|
+
commandsOfType(command) {
|
|
69
|
+
return this.commands.filter((e) => e.type === command);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.RecordingBus = RecordingBus;
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/testing/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.z = exports.csvList = exports.booleanish = exports.passwordSchema = exports.slugSchema = exports.emailSchema = exports.sortOrderSchema = exports.paginationSchema = exports.idParamSchema = exports.idSchema = void 0;
|
|
4
|
+
exports.zodIssuesToErrors = zodIssuesToErrors;
|
|
5
|
+
exports.parseOrThrow = parseOrThrow;
|
|
6
|
+
const zod_1 = require("zod");
|
|
7
|
+
Object.defineProperty(exports, "z", { enumerable: true, get: function () { return zod_1.z; } });
|
|
8
|
+
const id_js_1 = require("../common/id.js");
|
|
9
|
+
const errors_js_1 = require("../common/errors.js");
|
|
10
|
+
exports.idSchema = zod_1.z.string().regex(id_js_1.ID_PATTERN, 'Invalid id');
|
|
11
|
+
exports.idParamSchema = zod_1.z.object({ id: exports.idSchema });
|
|
12
|
+
exports.paginationSchema = zod_1.z.object({
|
|
13
|
+
page: zod_1.z.coerce.number().int().min(1).default(1),
|
|
14
|
+
limit: zod_1.z.coerce.number().int().min(1).max(100).default(20),
|
|
15
|
+
});
|
|
16
|
+
exports.sortOrderSchema = zod_1.z.enum(['asc', 'desc']).default('desc');
|
|
17
|
+
exports.emailSchema = zod_1.z.string().trim().toLowerCase().email('Invalid email address').max(191);
|
|
18
|
+
exports.slugSchema = zod_1.z.string().trim().min(1).max(120).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, 'Invalid slug');
|
|
19
|
+
/** Length-first password rule (NIST 800-63B): 8–128 chars, no composition rules. Tighten per app if needed. */
|
|
20
|
+
exports.passwordSchema = zod_1.z.string().min(8, 'Password must be at least 8 characters').max(128, 'Password is too long');
|
|
21
|
+
/** Accepts `true/false/1/0/yes/no` from query strings. */
|
|
22
|
+
exports.booleanish = zod_1.z.union([zod_1.z.boolean(), zod_1.z.enum(['true', 'false', '1', '0', 'yes', 'no'])]).transform((v) => v === true || v === 'true' || v === '1' || v === 'yes');
|
|
23
|
+
/** Comma list or repeated query param → string[]. */
|
|
24
|
+
exports.csvList = zod_1.z.union([zod_1.z.string(), zod_1.z.array(zod_1.z.string())]).transform((v) => (Array.isArray(v) ? v : v.split(',')).map((s) => s.trim()).filter(Boolean));
|
|
25
|
+
/** Converts Zod issues into the `{ field: [messages] }` shape used by the error envelope. */
|
|
26
|
+
function zodIssuesToErrors(error) {
|
|
27
|
+
const errors = {};
|
|
28
|
+
for (const issue of error.issues) {
|
|
29
|
+
const key = issue.path.join('.') || '_';
|
|
30
|
+
(errors[key] ??= []).push(issue.message);
|
|
31
|
+
}
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
/** Parses `input` or throws a ValidationError (422). */
|
|
35
|
+
function parseOrThrow(schema, input) {
|
|
36
|
+
const result = schema.safeParse(input);
|
|
37
|
+
if (!result.success)
|
|
38
|
+
throw new errors_js_1.ValidationError(zodIssuesToErrors(result.error));
|
|
39
|
+
return result.data;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/validation/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { ForbiddenError, UnauthorizedError } from '../common/errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Permission and ownership checks. Keep every authorization decision in these helpers (not
|
|
4
|
+
* inline in controllers) so the rules are identical across endpoints and services.
|
|
5
|
+
*
|
|
6
|
+
* Permission strings are dotted (`users.manage`, `media.manage.any`). Grants may use
|
|
7
|
+
* wildcards: `*` grants everything; `media.*` grants any permission starting with `media.`.
|
|
8
|
+
*/
|
|
9
|
+
export const WILDCARD_PERMISSION = '*';
|
|
10
|
+
function grants(granted, required) {
|
|
11
|
+
if (granted === WILDCARD_PERMISSION || granted === required)
|
|
12
|
+
return true;
|
|
13
|
+
return granted.endsWith('.*') && required.startsWith(granted.slice(0, -1));
|
|
14
|
+
}
|
|
15
|
+
/** True when the user holds at least one of `required`. */
|
|
16
|
+
export function hasPermission(user, ...required) {
|
|
17
|
+
if (!user)
|
|
18
|
+
return false;
|
|
19
|
+
return required.some((perm) => user.permissions.some((g) => grants(g, perm)));
|
|
20
|
+
}
|
|
21
|
+
export function hasAllPermissions(user, ...required) {
|
|
22
|
+
if (!user)
|
|
23
|
+
return false;
|
|
24
|
+
return required.every((perm) => user.permissions.some((g) => grants(g, perm)));
|
|
25
|
+
}
|
|
26
|
+
export function hasRole(user, ...roles) {
|
|
27
|
+
return !!user && roles.some((role) => user.roles.includes(role));
|
|
28
|
+
}
|
|
29
|
+
export function requireUser(user) {
|
|
30
|
+
if (!user)
|
|
31
|
+
throw new UnauthorizedError();
|
|
32
|
+
return user;
|
|
33
|
+
}
|
|
34
|
+
export function assertPermission(user, ...required) {
|
|
35
|
+
const u = requireUser(user);
|
|
36
|
+
if (!hasPermission(u, ...required))
|
|
37
|
+
throw new ForbiddenError();
|
|
38
|
+
return u;
|
|
39
|
+
}
|
|
40
|
+
/** IDs the user may act on within a scope (e.g. profiles they are a member of). */
|
|
41
|
+
export function scopeIds(user, scope) {
|
|
42
|
+
return user?.scopes?.[scope] ?? [];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Self-owned data: allow when the caller *is* `ownerUserId`, or holds `bypassPermission`.
|
|
46
|
+
* Throws ForbiddenError otherwise.
|
|
47
|
+
*/
|
|
48
|
+
export function assertSelfOrPermission(user, ownerUserId, bypassPermission) {
|
|
49
|
+
const u = requireUser(user);
|
|
50
|
+
if (u.id === ownerUserId)
|
|
51
|
+
return u;
|
|
52
|
+
if (bypassPermission && hasPermission(u, bypassPermission))
|
|
53
|
+
return u;
|
|
54
|
+
throw new ForbiddenError();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Scoped data: allow when `resourceScopeId` is one of the caller's scope IDs, or the caller holds
|
|
58
|
+
* `bypassPermission`.
|
|
59
|
+
*/
|
|
60
|
+
export function assertInScope(user, scope, resourceScopeId, bypassPermission) {
|
|
61
|
+
const u = requireUser(user);
|
|
62
|
+
if (scopeIds(u, scope).includes(resourceScopeId))
|
|
63
|
+
return u;
|
|
64
|
+
if (bypassPermission && hasPermission(u, bypassPermission))
|
|
65
|
+
return u;
|
|
66
|
+
throw new ForbiddenError();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolves the single scope ID a request acts on. With one membership it is implicit; with
|
|
70
|
+
* several the client names one (e.g. `x-active-profile-id`), which must be a member ID.
|
|
71
|
+
* IDs from request bodies are never trusted — only the gateway-signed scopes.
|
|
72
|
+
*/
|
|
73
|
+
export function resolveActiveScopeId(user, scope, requested) {
|
|
74
|
+
const ids = scopeIds(requireUser(user), scope);
|
|
75
|
+
if (requested) {
|
|
76
|
+
if (!ids.includes(requested))
|
|
77
|
+
throw new ForbiddenError(`Not a member of this ${scope}`);
|
|
78
|
+
return requested;
|
|
79
|
+
}
|
|
80
|
+
if (ids.length === 1)
|
|
81
|
+
return ids[0];
|
|
82
|
+
if (ids.length === 0)
|
|
83
|
+
throw new ForbiddenError(`No ${scope} is linked to this account`);
|
|
84
|
+
throw new ForbiddenError(`Select a ${scope} to act on`, { code: 'SCOPE_SELECTION_REQUIRED' });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Like `resolveActiveScopeId`, for screens that work without a selector: with several memberships
|
|
88
|
+
* and none named, the first (primary) one is used. A named ID must still be a member ID.
|
|
89
|
+
*/
|
|
90
|
+
export function resolvePrimaryScopeId(user, scope, requested) {
|
|
91
|
+
const ids = scopeIds(requireUser(user), scope);
|
|
92
|
+
return resolveActiveScopeId(user, scope, requested || ids[0]);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Repository filter for list queries: `{ all: true }` for callers holding `bypassPermission`,
|
|
96
|
+
* otherwise the caller's own scope IDs (possibly empty → return nothing).
|
|
97
|
+
*/
|
|
98
|
+
export function ownerFilter(user, scope, bypassPermission) {
|
|
99
|
+
const u = requireUser(user);
|
|
100
|
+
if (bypassPermission && hasPermission(u, bypassPermission))
|
|
101
|
+
return { all: true };
|
|
102
|
+
return { all: false, ids: scopeIds(u, scope) };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/access/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { silentLogger } from '../logging/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Posts alerts to a Slack notification webhook as `{ messages, alert_type, app_name, environment, consolidate }`.
|
|
4
|
+
* An unset URL is a valid "alerts off" configuration: the first skipped alert is warned about once, then silently.
|
|
5
|
+
*/
|
|
6
|
+
export class SlackAlerter {
|
|
7
|
+
options;
|
|
8
|
+
warnedUnconfigured = false;
|
|
9
|
+
logger;
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.logger = options.logger ?? silentLogger;
|
|
13
|
+
}
|
|
14
|
+
get configured() {
|
|
15
|
+
return Boolean(this.options.url);
|
|
16
|
+
}
|
|
17
|
+
async send(messages, options = {}) {
|
|
18
|
+
if (!this.options.url) {
|
|
19
|
+
if (!this.warnedUnconfigured)
|
|
20
|
+
this.logger.warn('Slack alert URL is not configured; alerts are logged only');
|
|
21
|
+
this.warnedUnconfigured = true;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const payload = {
|
|
25
|
+
messages,
|
|
26
|
+
alert_type: options.alertType ?? 'app-alert',
|
|
27
|
+
app_name: this.options.app,
|
|
28
|
+
environment: this.options.environment,
|
|
29
|
+
consolidate: true,
|
|
30
|
+
};
|
|
31
|
+
const post = this.options.fetch ?? fetch;
|
|
32
|
+
try {
|
|
33
|
+
const res = await post(this.options.url, {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify(payload),
|
|
37
|
+
signal: AbortSignal.timeout(this.options.timeoutMs ?? 10_000),
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
const body = await res.text().catch(() => '');
|
|
41
|
+
this.logger.error('Slack alert rejected', { status: res.status, body: body.slice(0, 200) });
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
this.logger.error('Slack alert failed', { error: error.message });
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/alerting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA+BnD;;;GAGG;AACH,MAAM,OAAO,YAAY;IAIM;IAHrB,kBAAkB,GAAG,KAAK,CAAC;IAClB,MAAM,CAAS;IAEhC,YAA6B,OAA4B;QAA5B,YAAO,GAAP,OAAO,CAAqB;QACvD,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC;IAC/C,CAAC;IAED,IAAI,UAAU;QACZ,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAkB,EAAE,UAAkC,EAAE;QACjE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,kBAAkB;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;YAC5G,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;YAC/B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,OAAO,GAAsB;YACjC,QAAQ;YACR,UAAU,EAAE,OAAO,CAAC,SAAS,IAAI,WAAW;YAC5C,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;YAC1B,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;YACrC,WAAW,EAAE,IAAI;SAClB,CAAC;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;QACzC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE;gBACvC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;gBAC7B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;aAC9D,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC9C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC5F,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7E,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Redis } from 'ioredis';
|
|
2
|
+
import { silentLogger } from '../logging/index.js';
|
|
3
|
+
const NULL_SENTINEL = '__null__';
|
|
4
|
+
class MemoryStore {
|
|
5
|
+
max;
|
|
6
|
+
map = new Map();
|
|
7
|
+
constructor(max) {
|
|
8
|
+
this.max = max;
|
|
9
|
+
}
|
|
10
|
+
get(key) {
|
|
11
|
+
const entry = this.map.get(key);
|
|
12
|
+
if (!entry)
|
|
13
|
+
return undefined;
|
|
14
|
+
if (entry.expiresAt < Date.now()) {
|
|
15
|
+
this.map.delete(key);
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
// Refresh recency for LRU eviction.
|
|
19
|
+
this.map.delete(key);
|
|
20
|
+
this.map.set(key, entry);
|
|
21
|
+
return entry.value;
|
|
22
|
+
}
|
|
23
|
+
set(key, value, ttlSec) {
|
|
24
|
+
if (this.map.size >= this.max)
|
|
25
|
+
this.map.delete(this.map.keys().next().value);
|
|
26
|
+
this.map.set(key, { value, expiresAt: Date.now() + ttlSec * 1000 });
|
|
27
|
+
}
|
|
28
|
+
del(key) {
|
|
29
|
+
this.map.delete(key);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Redis-backed cache with tag invalidation. If Redis errors, reads fall through to the loader
|
|
34
|
+
* and writes go to a bounded in-memory store, so a cache outage degrades latency rather than
|
|
35
|
+
* availability.
|
|
36
|
+
*/
|
|
37
|
+
export function createCache(options) {
|
|
38
|
+
const logger = options.logger ?? silentLogger;
|
|
39
|
+
const defaultTtl = options.defaultTtlSec ?? 60;
|
|
40
|
+
const memory = new MemoryStore(options.memoryMaxEntries ?? 5000);
|
|
41
|
+
const inflight = new Map();
|
|
42
|
+
const k = (key) => `${options.namespace}:${key}`;
|
|
43
|
+
const tagKey = (tag) => `${options.namespace}:tag:${tag}`;
|
|
44
|
+
let warned = false;
|
|
45
|
+
const redisFailed = (error) => {
|
|
46
|
+
if (!warned)
|
|
47
|
+
logger.warn('Cache backend unavailable; using in-memory fallback', { error: error.message });
|
|
48
|
+
warned = true;
|
|
49
|
+
};
|
|
50
|
+
const readRaw = async (key) => {
|
|
51
|
+
if (options.redis) {
|
|
52
|
+
try {
|
|
53
|
+
const value = await options.redis.get(k(key));
|
|
54
|
+
warned = false;
|
|
55
|
+
return value ?? undefined;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
redisFailed(error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return memory.get(k(key));
|
|
62
|
+
};
|
|
63
|
+
const writeRaw = async (key, raw, opts = {}) => {
|
|
64
|
+
const ttl = opts.ttlSec ?? defaultTtl;
|
|
65
|
+
if (options.redis) {
|
|
66
|
+
try {
|
|
67
|
+
const pipeline = options.redis.multi().set(k(key), raw, 'EX', ttl);
|
|
68
|
+
for (const tag of opts.tags ?? [])
|
|
69
|
+
pipeline.sadd(tagKey(tag), k(key)).expire(tagKey(tag), Math.max(ttl, 86_400));
|
|
70
|
+
await pipeline.exec();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
redisFailed(error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
memory.set(k(key), raw, ttl);
|
|
78
|
+
};
|
|
79
|
+
const cache = {
|
|
80
|
+
async get(key) {
|
|
81
|
+
const raw = await readRaw(key);
|
|
82
|
+
if (raw === undefined || raw === NULL_SENTINEL)
|
|
83
|
+
return undefined;
|
|
84
|
+
return JSON.parse(raw);
|
|
85
|
+
},
|
|
86
|
+
async set(key, value, opts) {
|
|
87
|
+
await writeRaw(key, value === null ? NULL_SENTINEL : JSON.stringify(value), opts);
|
|
88
|
+
},
|
|
89
|
+
async del(...keys) {
|
|
90
|
+
for (const key of keys)
|
|
91
|
+
memory.del(k(key));
|
|
92
|
+
if (options.redis && keys.length)
|
|
93
|
+
await options.redis.del(...keys.map(k)).catch(redisFailed);
|
|
94
|
+
},
|
|
95
|
+
async wrap(key, loader, opts = {}) {
|
|
96
|
+
const raw = await readRaw(key);
|
|
97
|
+
if (raw !== undefined) {
|
|
98
|
+
if (raw === NULL_SENTINEL)
|
|
99
|
+
return null;
|
|
100
|
+
return JSON.parse(raw);
|
|
101
|
+
}
|
|
102
|
+
const existing = inflight.get(key);
|
|
103
|
+
if (existing)
|
|
104
|
+
return existing;
|
|
105
|
+
const load = (async () => {
|
|
106
|
+
try {
|
|
107
|
+
const value = await loader();
|
|
108
|
+
if (value !== null && value !== undefined)
|
|
109
|
+
await writeRaw(key, JSON.stringify(value), opts);
|
|
110
|
+
else if (opts.cacheNull)
|
|
111
|
+
await writeRaw(key, NULL_SENTINEL, { ...opts, ttlSec: Math.min(opts.ttlSec ?? defaultTtl, 30) });
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
inflight.delete(key);
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
inflight.set(key, load);
|
|
119
|
+
return load;
|
|
120
|
+
},
|
|
121
|
+
async invalidateTags(...tags) {
|
|
122
|
+
if (!options.redis)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
for (const tag of tags) {
|
|
126
|
+
const members = await options.redis.smembers(tagKey(tag));
|
|
127
|
+
for (const member of members)
|
|
128
|
+
memory.del(member);
|
|
129
|
+
if (members.length)
|
|
130
|
+
await options.redis.del(...members);
|
|
131
|
+
await options.redis.del(tagKey(tag));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
redisFailed(error);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
return cache;
|
|
140
|
+
}
|
|
141
|
+
/** ioredis client that never crashes the process on connection errors and retries forever. */
|
|
142
|
+
export function createRedisClient(opts) {
|
|
143
|
+
const logger = opts.logger ?? silentLogger;
|
|
144
|
+
const base = {
|
|
145
|
+
db: opts.db ?? 0,
|
|
146
|
+
keyPrefix: opts.keyPrefix || undefined,
|
|
147
|
+
lazyConnect: false,
|
|
148
|
+
enableOfflineQueue: false,
|
|
149
|
+
maxRetriesPerRequest: 1,
|
|
150
|
+
retryStrategy: (times) => Math.min(times * 500, 10_000),
|
|
151
|
+
...opts.options,
|
|
152
|
+
};
|
|
153
|
+
const client = opts.url ? new Redis(opts.url, base) : new Redis(base);
|
|
154
|
+
let lastError = '';
|
|
155
|
+
client.on('error', (error) => {
|
|
156
|
+
if (error.message !== lastError)
|
|
157
|
+
logger.warn('Redis error', { error: error.message });
|
|
158
|
+
lastError = error.message;
|
|
159
|
+
});
|
|
160
|
+
client.on('ready', () => { lastError = ''; logger.info('Redis connected', { db: base.db }); });
|
|
161
|
+
return client;
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cache/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2
|
+
/** Runs `fn`, retrying failures with exponential back-off. */
|
|
3
|
+
export async function retry(fn, options) {
|
|
4
|
+
const delay = options.delayMs ?? ((n) => Math.min(5000, 200 * 2 ** (n - 1)));
|
|
5
|
+
let attempt = 0;
|
|
6
|
+
for (;;) {
|
|
7
|
+
try {
|
|
8
|
+
return await fn(attempt);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
attempt += 1;
|
|
12
|
+
if (attempt > options.retries || (options.shouldRetry && !options.shouldRetry(error)))
|
|
13
|
+
throw error;
|
|
14
|
+
await sleep(delay(attempt));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Rejects with `onTimeout()` if `promise` does not settle within `ms`. */
|
|
19
|
+
export function withTimeout(promise, ms, onTimeout) {
|
|
20
|
+
let timer;
|
|
21
|
+
return Promise.race([
|
|
22
|
+
promise.finally(() => clearTimeout(timer)),
|
|
23
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(onTimeout()), ms); }),
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
26
|
+
/** Splits `items` into arrays of at most `size`. */
|
|
27
|
+
export function chunk(items, size) {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 0; i < items.length; i += size)
|
|
30
|
+
out.push(items.slice(i, i + size));
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=async.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async.js","sourceRoot":"","sources":["../../../src/common/async.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAStG,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,KAAK,CAAI,EAAmC,EAAE,OAAqB;IACvF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,CAAC;YACb,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAAE,MAAM,KAAK,CAAC;YACnG,MAAM,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAC9B,CAAC;IACH,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAI,OAAmB,EAAE,EAAU,EAAE,SAAsB;IACpF,IAAI,KAAoC,CAAC;IACzC,OAAO,OAAO,CAAC,IAAI,CAAC;QAClB,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;KAC1F,CAAC,CAAC;AACL,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,KAAK,CAAI,KAAmB,EAAE,IAAY;IACxD,MAAM,GAAG,GAAU,EAAE,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAChF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { newId } from './id.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the row for a `*_audits` sibling table: a copy of the parent row plus `action`,
|
|
4
|
+
* `parentId`, and a fresh `id`. The parent's timestamps are dropped so the audit row records
|
|
5
|
+
* when the change happened. The actor lands in `createdBy`/`updatedBy` exactly as on the parent
|
|
6
|
+
* (null for system operations). Strip secrets (e.g. password hashes) before calling.
|
|
7
|
+
*/
|
|
8
|
+
export function buildAuditRow(row, action, actorId = null) {
|
|
9
|
+
const { id: parentId, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = row;
|
|
10
|
+
return {
|
|
11
|
+
...rest,
|
|
12
|
+
id: newId(),
|
|
13
|
+
parentId,
|
|
14
|
+
action,
|
|
15
|
+
...(action === 'create' ? { createdBy: actorId, updatedBy: actorId } : { updatedBy: actorId }),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/common/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAMhC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAA2B,GAAM,EAAE,MAAmB,EAAE,UAAyB,IAAI;IAChH,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,GAAuD,CAAC;IACxI,OAAO;QACL,GAAI,IAAkD;QACtD,EAAE,EAAE,KAAK,EAAE;QACX,QAAQ;QACR,MAAM;QACN,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;KAC/F,CAAC;AACJ,CAAC"}
|