@getstrata/core 0.4.0 → 0.5.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.
Files changed (114) hide show
  1. package/dist/bootstrap/httpKernel.d.ts +2 -0
  2. package/dist/config/queue.d.ts +8 -0
  3. package/dist/config/rateLimit.d.ts +2 -1
  4. package/dist/core/database/baseRepository.d.ts +16 -4
  5. package/dist/core/database/index.d.ts +10 -4
  6. package/dist/core/database/migrations/advisoryLock.d.ts +6 -0
  7. package/dist/core/database/migrations/runner.d.ts +8 -2
  8. package/dist/core/database/migrations/types.d.ts +1 -0
  9. package/dist/core/database/model.d.ts +46 -17
  10. package/dist/core/database/query.d.ts +25 -14
  11. package/dist/core/database/relationships.d.ts +32 -2
  12. package/dist/core/database/repositoryQuery.d.ts +16 -1
  13. package/dist/core/database/schema/blueprint.d.ts +47 -0
  14. package/dist/core/database/schema/columnDefinition.d.ts +36 -0
  15. package/dist/core/database/schema/driver.d.ts +8 -0
  16. package/dist/core/database/schema/errors.d.ts +4 -0
  17. package/dist/core/database/schema/grammars/compileStatements.d.ts +8 -0
  18. package/dist/core/database/schema/grammars/createGrammar.d.ts +4 -0
  19. package/dist/core/database/schema/grammars/grammar.d.ts +10 -0
  20. package/dist/core/database/schema/grammars/index.d.ts +7 -0
  21. package/dist/core/database/schema/grammars/mysqlGrammar.d.ts +2 -0
  22. package/dist/core/database/schema/grammars/postgresGrammar.d.ts +2 -0
  23. package/dist/core/database/schema/grammars/sqliteGrammar.d.ts +2 -0
  24. package/dist/core/database/schema/index.d.ts +12 -0
  25. package/dist/core/database/schema/schema.d.ts +21 -0
  26. package/dist/core/database/seeders/runner.d.ts +6 -0
  27. package/dist/core/database/seeders/types.d.ts +8 -0
  28. package/dist/core/database/types.d.ts +39 -2
  29. package/dist/core/database/whereBuilder.d.ts +17 -0
  30. package/dist/core/http/index.d.ts +1 -1
  31. package/dist/core/http/securedRouteModelBinding.d.ts +2 -1
  32. package/dist/core/lifecycle/gracefulShutdown.d.ts +6 -0
  33. package/dist/core/pagination/index.d.ts +11 -1
  34. package/dist/core/queue/failedJobRepository.d.ts +6 -0
  35. package/dist/core/queue/failedJobService.d.ts +15 -0
  36. package/dist/core/queue/failedJobTable.d.ts +3 -0
  37. package/dist/core/queue/jobRegistry.d.ts +14 -0
  38. package/dist/core/queue/jobRunner.d.ts +9 -0
  39. package/dist/core/queue/publicQueue.d.ts +15 -0
  40. package/dist/core/queue/redisQueue.d.ts +29 -0
  41. package/dist/core/queue/resilientQueue.d.ts +9 -0
  42. package/dist/core/queue/types.d.ts +8 -0
  43. package/dist/core/scheduler/schedule.d.ts +15 -0
  44. package/dist/entries/auth/accessControl.js +113 -0
  45. package/dist/entries/auth/authContext.js +15 -0
  46. package/dist/entries/auth/guard.js +2870 -0
  47. package/dist/entries/auth/membershipContext.js +276 -0
  48. package/dist/entries/auth/membershipScope.js +390 -0
  49. package/dist/entries/auth/membershipService.js +477 -0
  50. package/dist/entries/auth/oauth/oidcProvider.js +49 -0
  51. package/dist/entries/auth/oauth/providers.js +71 -0
  52. package/dist/entries/auth/oauth/samlProvider.js +26 -0
  53. package/dist/entries/auth/oauth/types.js +1 -0
  54. package/dist/entries/auth/password.js +15 -0
  55. package/dist/entries/auth/policy.js +134 -0
  56. package/dist/entries/auth/sessionCookie.js +75 -0
  57. package/dist/entries/auth/tokenHash.js +17 -0
  58. package/dist/entries/cache/tags.js +13 -0
  59. package/dist/entries/crypto/fieldEncryption.js +93 -0
  60. package/dist/entries/crypto/mfaSecret.js +105 -0
  61. package/dist/entries/database/factory.js +17 -0
  62. package/dist/entries/database/seeders.js +30 -0
  63. package/dist/entries/database/types.js +1 -0
  64. package/dist/entries/database.js +2218 -0
  65. package/dist/entries/errors/http.js +78 -0
  66. package/dist/entries/http/contentNegotiation.js +26 -0
  67. package/dist/entries/http/csrfToken.js +147 -0
  68. package/dist/entries/http/etag.js +170 -0
  69. package/dist/entries/http/flashSession.js +106 -0
  70. package/dist/entries/http/middleware.js +68 -0
  71. package/dist/entries/http/parseFormBody.js +88 -0
  72. package/dist/entries/http/requestMetaContext.js +18 -0
  73. package/dist/entries/http/resources.js +19 -0
  74. package/dist/entries/http/webErrorResponse.js +3143 -0
  75. package/dist/entries/http/webFormRequest.js +293 -0
  76. package/dist/entries/http.js +3924 -0
  77. package/dist/entries/jobs/dispatchWebhookJob.js +342 -0
  78. package/dist/entries/lifecycle/gracefulShutdown.js +50 -0
  79. package/dist/entries/metrics/prometheus.js +70 -0
  80. package/dist/entries/pagination.js +14 -0
  81. package/dist/entries/queue/createAppQueue.js +2848 -0
  82. package/dist/entries/queue/failedJobService.js +38 -0
  83. package/dist/entries/queue/jobRegistry.js +32 -0
  84. package/dist/entries/queue/jobRunner.js +75 -0
  85. package/dist/entries/queue/publicQueue.js +2476 -0
  86. package/dist/entries/queue/queueMetrics.js +2898 -0
  87. package/dist/entries/queue/types.js +1 -0
  88. package/dist/entries/security/oauthState.js +75 -0
  89. package/dist/entries/security/publicReads.js +33 -0
  90. package/dist/entries/security/safeUrl.js +143 -0
  91. package/dist/entries/security/securityEvents.js +41 -0
  92. package/dist/entries/security/stripeWebhook.js +115 -0
  93. package/dist/entries/security/tokenExpiry.js +16 -0
  94. package/dist/entries/security/totp.js +51 -0
  95. package/dist/entries/storage/storage.js +123 -0
  96. package/dist/entries/tenant/tenantContext.js +30 -0
  97. package/dist/entries/tenant/tenantMiddleware.js +312 -0
  98. package/dist/entries/tracing/traceContext.js +15 -0
  99. package/dist/entries/validation/rules.js +232 -0
  100. package/dist/entries/view.js +3072 -0
  101. package/dist/framework/public-api.d.ts +65 -38
  102. package/dist/index.js +3198 -308
  103. package/dist/modules/user/apiTokenRepository.d.ts +1 -1
  104. package/dist/modules/user/apiTokenTable.d.ts +1 -1
  105. package/dist/modules/user/authService.d.ts +1 -1
  106. package/dist/modules/user/notificationRepository.d.ts +1 -1
  107. package/dist/modules/user/notificationService.d.ts +1 -1
  108. package/dist/modules/user/notificationTable.d.ts +1 -1
  109. package/dist/modules/user/oauthIdentityRepository.d.ts +2 -2
  110. package/dist/modules/user/provider.d.ts +1 -1
  111. package/dist/modules/user/repository.d.ts +1 -1
  112. package/dist/modules/user/table.d.ts +1 -1
  113. package/dist/modules/user/tokenService.d.ts +1 -1
  114. package/package.json +289 -3
@@ -0,0 +1,342 @@
1
+ // @bun
2
+ // ../../src/core/jobs/dispatchWebhookJob.ts
3
+ import { createHmac } from "crypto";
4
+
5
+ // ../../src/config/app.ts
6
+ var appConfig = {
7
+ name: "WorkHub",
8
+ env: process.env.APP_ENV ?? "local",
9
+ debug: (process.env.APP_DEBUG ?? "true") !== "false",
10
+ url: process.env.APP_URL ?? "http://localhost:3000",
11
+ apiPrefix: process.env.API_PREFIX ?? "/api/v1"
12
+ };
13
+
14
+ // ../../src/config/database.ts
15
+ function readInteger(name, fallback) {
16
+ const parsed = Number.parseInt(process.env[name] ?? String(fallback), 10);
17
+ return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback;
18
+ }
19
+ var databaseConfig = {
20
+ url: process.env.DATABASE_URL ?? "",
21
+ poolMax: readInteger("DB_POOL_MAX", 10),
22
+ idleTimeoutSeconds: readInteger("DB_POOL_IDLE_TIMEOUT", 30),
23
+ maxLifetimeSeconds: readInteger("DB_POOL_MAX_LIFETIME", 3600),
24
+ connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
25
+ };
26
+
27
+ // ../../src/core/database/connectionContext.ts
28
+ import { AsyncLocalStorage } from "async_hooks";
29
+ var activeConnection = new AsyncLocalStorage;
30
+ function runWithDatabaseConnection(connection, callback) {
31
+ return activeConnection.run(connection, callback);
32
+ }
33
+ function getActiveDatabaseConnection(fallback) {
34
+ return activeConnection.getStore() ?? fallback;
35
+ }
36
+
37
+ // ../../src/db/connection/createConnection.ts
38
+ var {SQL } = globalThis.Bun;
39
+ function createDatabaseConnection(config) {
40
+ if (!config.url) {
41
+ throw new Error("DATABASE_URL is not configured. Set DATABASE_URL before starting the app or running integration tests.");
42
+ }
43
+ return new SQL({
44
+ url: config.url,
45
+ max: config.poolMax,
46
+ idleTimeout: config.idleTimeoutSeconds,
47
+ maxLifetime: config.maxLifetimeSeconds,
48
+ connectionTimeout: config.connectionTimeoutSeconds
49
+ });
50
+ }
51
+
52
+ // ../../src/db/connection/index.ts
53
+ var connectionHolder = {
54
+ connection: null
55
+ };
56
+ function getDatabase() {
57
+ if (!connectionHolder.connection) {
58
+ connectionHolder.connection = createDatabaseConnection(databaseConfig);
59
+ }
60
+ return connectionHolder.connection;
61
+ }
62
+ var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
63
+ function resolveDatabase() {
64
+ return getActiveDatabaseConnection(getDatabase());
65
+ }
66
+ function resolveDatabaseForProperty(property) {
67
+ if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
68
+ return getDatabase();
69
+ }
70
+ return resolveDatabase();
71
+ }
72
+ var db = new Proxy(function database() {}, {
73
+ apply(_target, _thisArg, args) {
74
+ return resolveDatabase()(...args);
75
+ },
76
+ get(_target, property) {
77
+ const connection = resolveDatabaseForProperty(property);
78
+ const value = connection[property];
79
+ return typeof value === "function" ? value.bind(connection) : value;
80
+ }
81
+ });
82
+ var connection_default = db;
83
+
84
+ // ../../src/core/queue/index.ts
85
+ class Job {
86
+ maxAttempts;
87
+ backoffMs;
88
+ priority;
89
+ }
90
+
91
+ class SyncQueue {
92
+ async dispatch(job, payload) {
93
+ await job.handle(payload);
94
+ }
95
+ }
96
+
97
+ class AsyncQueue {
98
+ async dispatch(job, payload) {
99
+ setTimeout(() => {
100
+ job.handle(payload).catch((error) => {
101
+ console.error("[AsyncQueue] Job failed:", error);
102
+ });
103
+ }, 0);
104
+ }
105
+ }
106
+ function createQueue(driver) {
107
+ return driver === "async" ? new AsyncQueue : new SyncQueue;
108
+ }
109
+
110
+ // ../../src/core/security/safeFetch.ts
111
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
112
+ async function safeFetch(input, init = {}, options = {}) {
113
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
114
+ const maxRedirects = options.maxRedirects ?? 0;
115
+ const controller = new AbortController;
116
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
117
+ try {
118
+ let currentUrl = input;
119
+ let redirectCount = 0;
120
+ while (true) {
121
+ const response = await fetch(currentUrl, {
122
+ ...init,
123
+ signal: controller.signal,
124
+ redirect: "manual"
125
+ });
126
+ if (response.status >= 300 && response.status < 400) {
127
+ const location = response.headers.get("location");
128
+ if (!location || redirectCount >= maxRedirects) {
129
+ return response;
130
+ }
131
+ currentUrl = new URL(location, currentUrl).toString();
132
+ redirectCount += 1;
133
+ continue;
134
+ }
135
+ return response;
136
+ }
137
+ } finally {
138
+ clearTimeout(timeout);
139
+ }
140
+ }
141
+
142
+ // ../../src/core/errors/http.ts
143
+ class HttpError extends Error {
144
+ status;
145
+ details;
146
+ constructor(status, message, details) {
147
+ super(message);
148
+ this.name = new.target.name;
149
+ this.status = status;
150
+ this.details = details;
151
+ }
152
+ }
153
+
154
+ class BadRequestError extends HttpError {
155
+ constructor(message = "Bad Request", details) {
156
+ super(400, message, details);
157
+ }
158
+ }
159
+
160
+ class NotFoundError extends HttpError {
161
+ constructor(message = "Not Found", details) {
162
+ super(404, message, details);
163
+ }
164
+ }
165
+
166
+ class ConflictError extends HttpError {
167
+ constructor(message = "Conflict", details) {
168
+ super(409, message, details);
169
+ }
170
+ }
171
+
172
+ class UnprocessableEntityError extends HttpError {
173
+ constructor(message = "Unprocessable Entity", details) {
174
+ super(422, message, details);
175
+ }
176
+ }
177
+
178
+ class ValidationError extends HttpError {
179
+ constructor(message = "Validation failed", details) {
180
+ super(422, message, details);
181
+ }
182
+ }
183
+
184
+ class ForbiddenError extends HttpError {
185
+ constructor(message = "Forbidden", details) {
186
+ super(403, message, details);
187
+ }
188
+ }
189
+
190
+ class UnauthorizedError extends HttpError {
191
+ constructor(message = "Unauthorized", details) {
192
+ super(401, message, details);
193
+ }
194
+ }
195
+
196
+ class PayloadTooLargeError extends HttpError {
197
+ constructor(message = "Payload Too Large", details) {
198
+ super(413, message, details);
199
+ }
200
+ }
201
+
202
+ class PreconditionFailedError extends HttpError {
203
+ constructor(message = "Precondition Failed", details) {
204
+ super(412, message, details);
205
+ }
206
+ }
207
+
208
+ // ../../src/core/security/safeUrl.ts
209
+ var BLOCKED_HOSTNAMES = new Set([
210
+ "localhost",
211
+ "127.0.0.1",
212
+ "0.0.0.0",
213
+ "::1",
214
+ "metadata.google.internal"
215
+ ]);
216
+ function isPrivateIpv4(hostname) {
217
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
218
+ if (!match) {
219
+ return false;
220
+ }
221
+ const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
222
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
223
+ return true;
224
+ }
225
+ const [a = 0, b = 0] = octets;
226
+ if (a === 10) {
227
+ return true;
228
+ }
229
+ if (a === 127) {
230
+ return true;
231
+ }
232
+ if (a === 0) {
233
+ return true;
234
+ }
235
+ if (a === 169 && b === 254) {
236
+ return true;
237
+ }
238
+ if (a === 172 && b >= 16 && b <= 31) {
239
+ return true;
240
+ }
241
+ if (a === 192 && b === 168) {
242
+ return true;
243
+ }
244
+ return false;
245
+ }
246
+ function isBlockedHostname(hostname) {
247
+ const normalized = hostname.trim().toLowerCase();
248
+ if (normalized.length === 0) {
249
+ return true;
250
+ }
251
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
252
+ return true;
253
+ }
254
+ if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
255
+ return true;
256
+ }
257
+ if (normalized.includes(":")) {
258
+ return true;
259
+ }
260
+ return isPrivateIpv4(normalized);
261
+ }
262
+ function assertSafeOutboundUrl(rawUrl, options = {}) {
263
+ let parsed;
264
+ try {
265
+ parsed = new URL(rawUrl);
266
+ } catch {
267
+ throw new BadRequestError("Webhook URL is invalid.");
268
+ }
269
+ if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
270
+ throw new BadRequestError("Webhook URL must use HTTPS.");
271
+ }
272
+ if (parsed.username || parsed.password) {
273
+ throw new BadRequestError("Webhook URL must not include credentials.");
274
+ }
275
+ if (isBlockedHostname(parsed.hostname)) {
276
+ throw new BadRequestError("Webhook URL targets a blocked host.");
277
+ }
278
+ return parsed;
279
+ }
280
+
281
+ // ../../src/core/jobs/dispatchWebhookJob.ts
282
+ class DispatchWebhookJob extends Job {
283
+ constructor() {
284
+ super();
285
+ }
286
+ maxAttempts = 3;
287
+ backoffMs = 2000;
288
+ async handle(payload) {
289
+ const rows = await connection_default`
290
+ SELECT id, url, secret
291
+ FROM webhook
292
+ WHERE id = ${payload.webhookId} AND active = TRUE
293
+ LIMIT 1
294
+ `;
295
+ const webhook = rows[0];
296
+ if (!webhook) {
297
+ return;
298
+ }
299
+ const body = JSON.stringify({ event: payload.event, payload: payload.payload });
300
+ const signature = createHmac("sha256", webhook.secret).update(body).digest("hex");
301
+ assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
302
+ let responseStatus = null;
303
+ let errorMessage = null;
304
+ try {
305
+ const response = await safeFetch(webhook.url, {
306
+ method: "POST",
307
+ headers: {
308
+ "content-type": "application/json",
309
+ "x-workhub-signature": signature
310
+ },
311
+ body
312
+ });
313
+ responseStatus = response.status;
314
+ if (!response.ok) {
315
+ throw new Error(`Webhook delivery failed with status ${response.status}.`);
316
+ }
317
+ } catch (error) {
318
+ errorMessage = error instanceof Error ? error.message : String(error);
319
+ await connection_default`
320
+ INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
321
+ VALUES (
322
+ ${webhook.id},
323
+ ${payload.event},
324
+ ${JSON.stringify(payload.payload)}::jsonb,
325
+ ${responseStatus},
326
+ ${errorMessage}
327
+ )
328
+ `;
329
+ throw error instanceof Error ? error : new Error(errorMessage);
330
+ }
331
+ await connection_default`
332
+ INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
333
+ VALUES (
334
+ ${webhook.id},
335
+ ${payload.event},
336
+ ${JSON.stringify(payload.payload)}::jsonb,
337
+ ${responseStatus}
338
+ )
339
+ `;
340
+ }
341
+ }
342
+ var dispatchWebhookJob_default = DispatchWebhookJob;
@@ -0,0 +1,50 @@
1
+ // @bun
2
+ // ../../src/core/lifecycle/gracefulShutdown.ts
3
+ var shutdownHandlers = new Map;
4
+ var shutdownInstalled = false;
5
+ var shuttingDown = false;
6
+ function registerShutdownHandler(name, handler) {
7
+ shutdownHandlers.set(name, handler);
8
+ return () => {
9
+ shutdownHandlers.delete(name);
10
+ };
11
+ }
12
+ async function runGracefulShutdown(signal) {
13
+ if (shuttingDown) {
14
+ return;
15
+ }
16
+ shuttingDown = true;
17
+ console.log(`[shutdown] Received ${signal}, draining ${shutdownHandlers.size} handler(s)...`);
18
+ for (const [name, handler] of shutdownHandlers) {
19
+ try {
20
+ await handler();
21
+ console.log(`[shutdown] Completed ${name}`);
22
+ } catch (error) {
23
+ console.error(`[shutdown] Failed ${name}:`, error);
24
+ }
25
+ }
26
+ }
27
+ function installGracefulShutdownSignals(signals = ["SIGINT", "SIGTERM"]) {
28
+ if (shutdownInstalled) {
29
+ return;
30
+ }
31
+ shutdownInstalled = true;
32
+ for (const signal of signals) {
33
+ process.on(signal, () => {
34
+ runGracefulShutdown(signal).finally(() => {
35
+ process.exit(0);
36
+ });
37
+ });
38
+ }
39
+ }
40
+ function resetGracefulShutdownForTests() {
41
+ shutdownHandlers.clear();
42
+ shutdownInstalled = false;
43
+ shuttingDown = false;
44
+ }
45
+ export {
46
+ runGracefulShutdown,
47
+ resetGracefulShutdownForTests,
48
+ registerShutdownHandler,
49
+ installGracefulShutdownSignals
50
+ };
@@ -0,0 +1,70 @@
1
+ // @bun
2
+ // ../../src/core/metrics/prometheus.ts
3
+ class PrometheusRegistry {
4
+ httpRequestsTotal = new Map;
5
+ httpRequestDurationMs = new Map;
6
+ incrementHttpRequest(labels) {
7
+ const key = this.metricKey(labels);
8
+ this.httpRequestsTotal.set(key, (this.httpRequestsTotal.get(key) ?? 0) + 1);
9
+ }
10
+ observeHttpDuration(labels, durationMs) {
11
+ const key = this.metricKey(labels);
12
+ const samples = this.httpRequestDurationMs.get(key) ?? [];
13
+ samples.push(durationMs);
14
+ this.httpRequestDurationMs.set(key, samples);
15
+ }
16
+ renderMetrics() {
17
+ const lines = [
18
+ "# HELP http_requests_total Total HTTP requests processed.",
19
+ "# TYPE http_requests_total counter"
20
+ ];
21
+ for (const [key, value] of this.httpRequestsTotal) {
22
+ lines.push(`http_requests_total{${key}} ${value}`);
23
+ }
24
+ lines.push("# HELP http_request_duration_ms_sum Sum of HTTP request durations in milliseconds.", "# TYPE http_request_duration_ms_sum counter");
25
+ for (const [key, samples] of this.httpRequestDurationMs) {
26
+ const sum = samples.reduce((total, sample) => total + sample, 0);
27
+ lines.push(`http_request_duration_ms_sum{${key}} ${sum}`);
28
+ }
29
+ return `${lines.join(`
30
+ `)}
31
+ `;
32
+ }
33
+ resetForTests() {
34
+ this.httpRequestsTotal.clear();
35
+ this.httpRequestDurationMs.clear();
36
+ }
37
+ getHttpRequestSummary() {
38
+ const byStatus = {};
39
+ const pathCounts = new Map;
40
+ let totalRequests = 0;
41
+ for (const [key, count] of this.httpRequestsTotal) {
42
+ totalRequests += count;
43
+ const method = key.match(/method="([^"]+)"/)?.[1] ?? "GET";
44
+ const path = key.match(/path="([^"]+)"/)?.[1] ?? "/";
45
+ const status = key.match(/status="([^"]+)"/)?.[1] ?? "200";
46
+ byStatus[status] = (byStatus[status] ?? 0) + count;
47
+ const pathKey = `${method} ${path}`;
48
+ const existing = pathCounts.get(pathKey);
49
+ if (existing) {
50
+ existing.count += count;
51
+ } else {
52
+ pathCounts.set(pathKey, { method, path, count });
53
+ }
54
+ }
55
+ const topPaths = Array.from(pathCounts.values()).sort((left, right) => right.count - left.count).slice(0, 10);
56
+ return {
57
+ totalRequests,
58
+ byStatus,
59
+ topPaths
60
+ };
61
+ }
62
+ metricKey(labels) {
63
+ return `method="${labels.method}",path="${labels.path}",status="${labels.status}"`;
64
+ }
65
+ }
66
+ var prometheusRegistry = new PrometheusRegistry;
67
+ export {
68
+ prometheusRegistry,
69
+ PrometheusRegistry
70
+ };
@@ -0,0 +1,14 @@
1
+ // @bun
2
+ // ../../src/core/pagination/index.ts
3
+ function buildPaginationMeta(input) {
4
+ const lastPage = Math.max(1, Math.ceil(input.total / input.perPage));
5
+ return {
6
+ page: input.page,
7
+ per_page: input.perPage,
8
+ total: input.total,
9
+ last_page: lastPage
10
+ };
11
+ }
12
+ export {
13
+ buildPaginationMeta
14
+ };