@rvoh/psychic-workers 2.4.1 → 2.7.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 (36) hide show
  1. package/dist/cjs/src/background/BaseBackgroundedModel.js +112 -19
  2. package/dist/cjs/src/background/BaseBackgroundedService.js +73 -9
  3. package/dist/cjs/src/background/BaseScheduledService.js +59 -1
  4. package/dist/cjs/src/background/index.js +418 -105
  5. package/dist/cjs/src/error/background/DebouncedJobOutpacesRateLimit.js +64 -0
  6. package/dist/cjs/src/error/background/DebouncedJobRequiresMinimumDelay.js +52 -0
  7. package/dist/cjs/src/error/background/NamedWorkstreamRateLimitMissingMaxOrDuration.js +31 -0
  8. package/dist/cjs/src/error/background/RateLimitedPsychicJob.js +82 -0
  9. package/dist/cjs/src/error/background/RateLimitedPsychicJobThrownFromWorkerWithoutLimiter.js +51 -0
  10. package/dist/cjs/src/package-exports/errors.js +7 -0
  11. package/dist/cjs/src/test-utils/WorkerTestUtils.js +79 -14
  12. package/dist/esm/src/background/BaseBackgroundedModel.js +112 -19
  13. package/dist/esm/src/background/BaseBackgroundedService.js +73 -9
  14. package/dist/esm/src/background/BaseScheduledService.js +59 -1
  15. package/dist/esm/src/background/index.js +418 -105
  16. package/dist/esm/src/error/background/DebouncedJobOutpacesRateLimit.js +64 -0
  17. package/dist/esm/src/error/background/DebouncedJobRequiresMinimumDelay.js +52 -0
  18. package/dist/esm/src/error/background/NamedWorkstreamRateLimitMissingMaxOrDuration.js +31 -0
  19. package/dist/esm/src/error/background/RateLimitedPsychicJob.js +82 -0
  20. package/dist/esm/src/error/background/RateLimitedPsychicJobThrownFromWorkerWithoutLimiter.js +51 -0
  21. package/dist/esm/src/package-exports/errors.js +7 -0
  22. package/dist/esm/src/test-utils/WorkerTestUtils.js +79 -14
  23. package/dist/types/src/background/BaseBackgroundedModel.d.ts +97 -20
  24. package/dist/types/src/background/BaseBackgroundedService.d.ts +61 -10
  25. package/dist/types/src/background/BaseScheduledService.d.ts +54 -1
  26. package/dist/types/src/background/index.d.ts +137 -5
  27. package/dist/types/src/error/background/DebouncedJobOutpacesRateLimit.d.ts +31 -0
  28. package/dist/types/src/error/background/DebouncedJobRequiresMinimumDelay.d.ts +23 -0
  29. package/dist/types/src/error/background/NamedWorkstreamRateLimitMissingMaxOrDuration.d.ts +16 -0
  30. package/dist/types/src/error/background/RateLimitedPsychicJob.d.ts +73 -0
  31. package/dist/types/src/error/background/RateLimitedPsychicJobThrownFromWorkerWithoutLimiter.d.ts +16 -0
  32. package/dist/types/src/package-exports/errors.d.ts +1 -0
  33. package/dist/types/src/psychic-app-workers/index.d.ts +102 -53
  34. package/dist/types/src/test-utils/WorkerTestUtils.d.ts +32 -1
  35. package/dist/types/src/types/background.d.ts +100 -4
  36. package/package.json +9 -8
@@ -64,7 +64,25 @@ export interface BullMQNativeWorkerOptions extends WorkerOptions {
64
64
  concurrency?: number;
65
65
  priority?: number;
66
66
  };
67
+ /**
68
+ * How many jobs each worker built from this configuration runs at once
69
+ * (https://docs.bullmq.io/guide/workers/concurrency).
70
+ *
71
+ * Native BullMQ mode writes none, so BullMQ's default of **1** applies unless
72
+ * this or `defaultBullMQWorkerOptions.concurrency` sets it. See
73
+ * `PsychicBackgroundWorkstreamOptions.concurrency`.
74
+ */
67
75
  concurrency?: number;
76
+ /**
77
+ * the number of workers to create with this configuration, in this process.
78
+ *
79
+ * Read **only** for `nativeBullMQ.namedQueueWorkers`, where it defaults to 1
80
+ * (a named queue with no entry in `namedQueueWorkers` gets no workers at
81
+ * all). It is *not* read for `nativeBullMQ.defaultWorkerOptions`: the default
82
+ * queue's worker count comes from `nativeBullMQ.defaultWorkerCount`. See
83
+ * `PsychicBackgroundWorkstreamOptions.workerCount` for the
84
+ * `workerCount × concurrency` product.
85
+ */
68
86
  workerCount?: number;
69
87
  }
70
88
  export interface PsychicBackgroundNativeBullMQOptions extends PsychicBackgroundSharedOptions {
@@ -82,26 +100,24 @@ export interface PsychicBackgroundNativeBullMQOptions extends PsychicBackgroundS
82
100
  */
83
101
  namedQueueOptions?: Record<string, QueueOptionsWithConnectionInstance>;
84
102
  /**
85
- * Native BullMQ options to provide to configure the default workers
86
- * for psychic. By default, Psychic leverages a single-queue system, with
87
- * many workers running off the queue. Each worker receives the
88
- * same worker configuration, so this configuration is really
89
- * only used to supply the number of default workers that you want.
103
+ * Native BullMQ options for the default workers, spread over
104
+ * `defaultBullMQWorkerOptions` — where `concurrency`, `limiter` and every
105
+ * other BullMQ worker option for them comes from. A `workerCount` set here
106
+ * is never read; the count comes from `defaultWorkerCount` below.
90
107
  */
91
108
  defaultWorkerOptions?: BullMQNativeWorkerOptions;
92
109
  /**
93
- * The number of default workers to run against the default Psychic
94
- * background queues.
95
- *
96
- * By default, Psychic leverages a single-queue system, with
97
- * many workers running off a single queue. This number determines
98
- * the number of those default workers to provide.
110
+ * The number of default workers to run in this process, and the only source
111
+ * of that count in native mode. Defaults to 1, and takes effect only in a
112
+ * process that activates workers. See
113
+ * `PsychicBackgroundWorkstreamOptions.workerCount`.
99
114
  */
100
115
  defaultWorkerCount?: number;
101
116
  /**
102
- * namedQueueWorkers are necessary to work off namedQueues
103
- * With BullMQ Pro, namedQueueWorkers can be rate limited (useful
104
- * for interacting with external APIs)
117
+ * namedQueueWorkers are necessary to work off namedQueues.
118
+ * A named queue's workers can be rate limited with BullMQ's `limiter`
119
+ * option (https://docs.bullmq.io/guide/rate-limiting; useful for
120
+ * interacting with external APIs) on open-source BullMQ and BullMQ Pro alike
105
121
  */
106
122
  namedQueueWorkers?: Record<string, BullMQNativeWorkerOptions>;
107
123
  };
@@ -138,24 +154,34 @@ export interface PsychicBackgroundSimpleOptions extends PsychicBackgroundSharedO
138
154
  */
139
155
  defaultWorkerConnection: RedisOrRedisClusterConnection | undefined;
140
156
  /**
141
- * Every Psychic application that leverages simple background jobs will have a default
142
- * workstream. Set workerCount to set the number of workers that will work through the
143
- * default queue
157
+ * The default workstream, whose queue carries every job not backgrounded to a
158
+ * named workstream. Both fields mean exactly what they mean on a named
159
+ * workstream — see `PsychicBackgroundWorkstreamOptions.workerCount` and
160
+ * `.concurrency`.
144
161
  */
145
162
  defaultWorkstream?: {
163
+ /** The number of workers working through the default queue, in this process. Defaults to 1. */
146
164
  workerCount?: number;
165
+ /**
166
+ * How many jobs each default worker runs at once
167
+ * (https://docs.bullmq.io/guide/workers/concurrency). Left unset, psychic
168
+ * falls back to 10, as everywhere in simple mode. See
169
+ * `PsychicBackgroundWorkstreamOptions.concurrency`.
170
+ */
147
171
  concurrency?: number;
148
172
  };
149
173
  /**
150
174
  * When running background jobs on BullMQ, each named workstream corresponds
151
- * to a specific queue and workers created for a named workstream are given
152
- * a groupId corresponding to the workstream name
153
- *
154
- * named workstreams are useful for dispersing queues among nodes in a Redis cluster
155
- * and for running queues on different Redis instances
175
+ * to a specific queue, and jobs backgrounded to a named workstream are added
176
+ * with a group id equal to the workstream name (a BullMQ Pro concept, ignored
177
+ * by open-source BullMQ)
156
178
  *
157
- * With BullMQ Pro, named workstreams can be rate limited (useful
158
- * for interacting with external APIs)
179
+ * named workstreams are useful for dispersing queues among nodes in a Redis cluster,
180
+ * for running queues on different Redis instances, and for rate limiting, on
181
+ * open-source BullMQ and BullMQ Pro alike (see
182
+ * `PsychicBackgroundWorkstreamOptions.rateLimit`). A rate limit targets one external
183
+ * limit, so give each rate-limited service its own named workstream — and where a
184
+ * service meters its endpoints separately, one workstream per limit
159
185
  */
160
186
  namedWorkstreams?: PsychicBackgroundWorkstreamOptions[];
161
187
  /**
@@ -171,43 +197,66 @@ export interface PsychicBackgroundWorkstreamOptions {
171
197
  */
172
198
  name: string;
173
199
  /**
174
- * The number of workers you want to run on this configuration
200
+ * The number of workers you want to run on this configuration, in **this
201
+ * process**. Defaults to 1. These are BullMQ `Worker` objects sharing the
202
+ * activating process's event loop, not forked processes, so raising this buys
203
+ * concurrent waiting rather than concurrent CPU.
204
+ *
205
+ * `workerCount × concurrency` is the ceiling on this workstream's jobs in
206
+ * flight in one process, and that product is what to size against a
207
+ * downstream limit such as a connection pool or an external API's quota.
175
208
  */
176
209
  workerCount?: number;
177
- concurrency?: number;
178
- /**
179
- * See https://docs.bullmq.io/bullmq-pro/groups/rate-limiting for documentation
180
- * on rate limiting in BullMQ Pro (requires paid BullMQ Pro license)
181
- */
182
- rateLimit?: {
183
- max?: number;
184
- duration?: number;
185
- };
186
- /**
187
- * Optional redis connection. If not provided, the default background redis connection will be used.
188
- * See https://docs.bullmq.io/guide/going-to-production for the different settings to use between
189
- * queue and worker connections.
190
- */
191
- queueConnection?: RedisOrRedisClusterConnection;
192
- workerConnection?: RedisOrRedisClusterConnection;
193
- }
194
- export interface PsychicBackgroundWorkstreamOptions {
195
- /**
196
- * This will be the name of the queue (and the group if using BullMQ Pro)
197
- */
198
- name: string;
199
210
  /**
200
- * The number of workers you want to run on this configuration
211
+ * How many jobs each of this workstream's workers runs at once
212
+ * (https://docs.bullmq.io/guide/workers/concurrency).
213
+ *
214
+ * **In simple mode this defaults to 10.** Psychic always writes a
215
+ * `concurrency` onto a simple-mode worker, falling back to 10 when the
216
+ * workstream does not set one — which also means it overrides any
217
+ * `concurrency` placed in `defaultBullMQWorkerOptions`. Native BullMQ mode
218
+ * writes none, so BullMQ's own default of 1 applies there. Moving a queue
219
+ * between the two modes therefore changes how many jobs it runs at once by a
220
+ * factor of ten, with nothing in either configuration saying so.
221
+ *
222
+ * See `workerCount` above for the `workerCount × concurrency` product.
201
223
  */
202
- workerCount?: number;
203
224
  concurrency?: number;
204
225
  /**
205
- * See https://docs.bullmq.io/bullmq-pro/groups/rate-limiting for documentation
206
- * on rate limiting in BullMQ Pro (requires paid BullMQ Pro license)
226
+ * Rate-limit this workstream: at most `max` jobs start in any `duration`
227
+ * milliseconds. Works on open-source BullMQ and on BullMQ Pro.
228
+ *
229
+ * The limit is per queue, not per worker: every one of this workstream's
230
+ * workers, in every process running it, shares one counter kept in Redis, so
231
+ * `{ max: 1, duration: 1000 }` means one job per second for the workstream as
232
+ * a whole. `concurrency` caps how many jobs each worker runs at once;
233
+ * `rateLimit` caps how many may start per window across the whole workstream.
234
+ * For this workstream's workers it takes precedence over a global
235
+ * `defaultBullMQWorkerOptions.limiter`.
236
+ *
237
+ * A job on this workstream that is told to slow down (an HTTP 429, say) can
238
+ * throw `RateLimitedPsychicJob` from `@rvoh/psychic-workers/errors` to pause
239
+ * the whole workstream without burning a retry attempt; see that class for
240
+ * the pause semantics and for the `maxStartedAttempts` bound.
241
+ *
242
+ * A debounced job on this workstream must carry a delay long enough that it
243
+ * cannot enqueue faster than this limit starts jobs, or it is refused at
244
+ * enqueue: see `DelayedJobOpts.jobId`.
245
+ *
246
+ * `max` and `duration` are both required positive integers (`duration` in
247
+ * milliseconds): open-source BullMQ floors a fractional `duration` to 0ms
248
+ * (rate limiting nothing) and fails every job fetch on one past Redis's
249
+ * integer range. A `rateLimit` that reaches `connect()` without both fails
250
+ * `connect()` naming the workstream and the field, before any queue or worker
251
+ * is built.
252
+ *
253
+ * Implemented as BullMQ's worker `limiter` option, and on BullMQ Pro also as
254
+ * the workstream group's rate limit (unverified against an installed Pro
255
+ * build).
207
256
  */
208
257
  rateLimit?: {
209
- max?: number;
210
- duration?: number;
258
+ max: number;
259
+ duration: number;
211
260
  };
212
261
  /**
213
262
  * Optional redis connection. If not provided, the default background redis connection will be used.
@@ -1,10 +1,41 @@
1
+ import { Queue } from 'bullmq';
2
+ /**
3
+ * @internal
4
+ *
5
+ * resolves the queue's expected Redis name through `nameToRedisQueueName`,
6
+ * rather than hand-reconstructing a cluster hash tag or parallel-test suffix.
7
+ * `queue.opts.connection` is the same `Redis | Cluster` instance the queue was
8
+ * built with, which is why the cast is safe for every queue this package builds.
9
+ */
10
+ export declare function queueNamesMatch(queue: Queue, compareQueueName: string): boolean;
1
11
  export default class WorkerTestUtils {
2
12
  static parallelTestSafeQueueName(queueName: string): string;
3
13
  static work(opts?: TestWorkerWorkOffOpts): Promise<void>;
4
14
  static workScheduled(opts?: TestWorkerScheduledWorkOffOpts): Promise<void>;
15
+ /**
16
+ * runs a delayed job in place. A `RateLimitedPsychicJob` thrown from a job on
17
+ * a queue whose workers carry no BullMQ `limiter` surfaces as the same
18
+ * misconfiguration error production would fail the job with; on a queue whose
19
+ * workers carry one, it propagates untranslated so the spec can assert on it.
20
+ */
21
+ private static doScheduledWork;
5
22
  static clean(): Promise<void>;
6
23
  private static workOne;
7
- private static queueNamesMatch;
24
+ /**
25
+ * runs one fetched job to completion or failure, mirroring what a real worker
26
+ * would do with a `RateLimitedPsychicJob`, then surfacing it so the spec goes
27
+ * red rather than looping:
28
+ *
29
+ * - on a queue whose workers carry no BullMQ `limiter`, the job is failed with
30
+ * the misconfiguration error production would fail it with (so, with the
31
+ * queue's `attempts` > 1, it lands in `delayed` for its retry, out of
32
+ * `active`, where `clean()` can reach it), and that error is rethrown
33
+ * - on a queue whose workers carry one, the job is moved back to the queue
34
+ * with no attempt counted (BullMQ's own rate-limit path), and the
35
+ * `RateLimitedPsychicJob` is rethrown
36
+ *
37
+ * Every other error only fails the job, as before.
38
+ */
8
39
  private static processJob;
9
40
  }
10
41
  interface TestWorkerWorkOffOpts {
@@ -29,14 +29,73 @@ export interface BackgroundJobData {
29
29
  */
30
30
  globalName?: string;
31
31
  }
32
- export type DelayedJobOpts = DelayedJobDuration & {
32
+ /**
33
+ * the delay to hold a job for and, when a `jobId` is provided, the debounce
34
+ * that delay drives.
35
+ *
36
+ * Calling a delayed background method repeatedly with the same `jobId`
37
+ * collapses those calls into a **single execution**. Each call slides the
38
+ * pending job's fire time further out, and the job runs once, after the last
39
+ * call, when the delay has finally elapsed without another call arriving.
40
+ *
41
+ * ```ts
42
+ * // ten calls in quick succession; the report is generated once,
43
+ * // ten seconds after the last of them
44
+ * await Report.backgroundWith({ delay: { seconds: 10, jobId: `report-${user.id}` } }, 'generate', user.id)
45
+ * ```
46
+ *
47
+ * A delay object must carry at least one of `seconds`, `minutes`, `hours` or
48
+ * `days`. `{}` and `{ jobId: 'my-job' }` are compile errors, since a delay with
49
+ * no duration delays nothing and a `jobId` with no duration debounces
50
+ * nothing.
51
+ */
52
+ export type DelayedJobOpts = AtLeastOneDelayedJobDuration & {
33
53
  /**
34
- * a unique identifier for your job. this identifier will be
35
- * used to debounce, leveraging the internal throttling mechanisms
36
- * provided by BullMQ
54
+ * a **deduplication key**, which debounces repeated calls: calls carrying the
55
+ * same `jobId` collapse into a single execution, which runs once the delay
56
+ * has elapsed without another call arriving. The window runs from the last
57
+ * call, not the first, so a burst is collapsed however long it lasts.
58
+ *
59
+ * Despite the name, this is not the BullMQ job id of the enqueued job, and
60
+ * `queue.getJob(jobId)` will not resolve the debounced job through it. BullMQ
61
+ * stores it as a separate Redis key; the matching read is
62
+ * `queue.getDeduplicationJobId(jobId)`.
63
+ *
64
+ * A delay carrying a `jobId` must be **at least three seconds**, and a
65
+ * shorter one is refused at enqueue.
66
+ *
67
+ * On a rate-limited workstream the delay must also clear the limiter's
68
+ * spacing: `delay - 1s >= duration / max`, so a workstream limited to one job
69
+ * a minute needs a 61-second delay. A delay that does not is refused at
70
+ * enqueue, naming the queue, both limiter numbers and a delay that would
71
+ * pass.
37
72
  */
38
73
  jobId?: string;
39
74
  };
75
+ /**
76
+ * `DelayedJobDuration`, but at least one field has to be there. Every field is
77
+ * optional on its own, so `{}` would otherwise compile and delay nothing.
78
+ * TypeScript has no "at least one of these" operator, so this spells out the
79
+ * four ways to satisfy it and joins them with `|`:
80
+ *
81
+ * ```ts
82
+ * | { seconds: number; minutes?: number; hours?: number; days?: number }
83
+ * | { minutes: number; seconds?: number; hours?: number; days?: number }
84
+ * | { hours: number; seconds?: number; minutes?: number; days?: number }
85
+ * | { days: number; seconds?: number; minutes?: number; hours?: number }
86
+ * ```
87
+ *
88
+ * A value has to match one of those, which means carrying that line's required
89
+ * field. `{ seconds: 30 }` matches the first. `{}` matches none, and neither
90
+ * does `{ jobId: 'my-job' }`.
91
+ *
92
+ * Not exported: it exists only to narrow `DelayedJobOpts`. `DelayedJobDuration`
93
+ * itself stays all-optional because it is also the parameter type of the shared
94
+ * `durationToSeconds` helper.
95
+ */
96
+ type AtLeastOneDelayedJobDuration = {
97
+ [Field in keyof DelayedJobDuration]-?: Required<Pick<DelayedJobDuration, Field>> & Partial<Omit<DelayedJobDuration, Field>>;
98
+ }[keyof DelayedJobDuration];
40
99
  export interface DelayedJobDuration {
41
100
  seconds?: number;
42
101
  minutes?: number;
@@ -45,6 +104,27 @@ export interface DelayedJobDuration {
45
104
  }
46
105
  export type JobTypes = 'BackgroundJobQueueFunctionJob' | 'BackgroundJobQueueStaticJob' | 'BackgroundJobQueueModelInstanceJob';
47
106
  export type BackgroundQueuePriority = 'default' | 'urgent' | 'not_urgent' | 'last';
107
+ export interface BackgroundWithOpts {
108
+ /**
109
+ * an optional delay to hold off the job for a certain amount of
110
+ * time after it is entered into the queue. Accepts the same options
111
+ * as `backgroundWithDelay`, and must carry at least one of `seconds`,
112
+ * `minutes`, `hours` or `days`.
113
+ *
114
+ * Adding a `jobId` turns the delay into a **debounce**: repeated calls
115
+ * carrying the same `jobId` collapse into a single execution, which runs
116
+ * once the delay has elapsed without another call arriving. `jobId` is a
117
+ * deduplication key rather than a BullMQ job id.
118
+ *
119
+ * See {@link DelayedJobOpts.jobId}.
120
+ */
121
+ delay?: DelayedJobOpts;
122
+ /**
123
+ * an optional priority. When provided, this overrides the priority
124
+ * set on the `backgroundJobConfig` of the service or model.
125
+ */
126
+ priority?: BackgroundQueuePriority;
127
+ }
48
128
  interface BaseBackgroundJobConfig {
49
129
  priority?: BackgroundQueuePriority;
50
130
  }
@@ -57,4 +137,20 @@ export interface QueueBackgroundJobConfig<T extends BaseScheduledService | BaseB
57
137
  }
58
138
  export type BackgroundJobConfig<T extends BaseScheduledService | BaseBackgroundedService> = Either<WorkstreamBackgroundJobConfig<T>, QueueBackgroundJobConfig<T>>;
59
139
  export type PsychicBackgroundOptions = (PsychicBackgroundSimpleOptions & Partial<Record<Exclude<keyof PsychicBackgroundNativeBullMQOptions, keyof PsychicBackgroundSimpleOptions>, never>>) | (PsychicBackgroundNativeBullMQOptions & Partial<Record<Exclude<keyof PsychicBackgroundSimpleOptions, keyof PsychicBackgroundNativeBullMQOptions>, never>>);
140
+ /**
141
+ * @internal
142
+ *
143
+ * how a queue was configured, as far as the misconfiguration message needs to
144
+ * know in order to name the fix that applies to the worker that threw
145
+ */
146
+ export interface WorkerQueueDescription {
147
+ /** simple (workstream) configuration or native BullMQ configuration */
148
+ mode: 'simple' | 'native';
149
+ /** the default workstream/queue, as opposed to a named one */
150
+ isDefaultQueue: boolean;
151
+ /** the workstream or queue name as configured (not the formatted Redis queue name) */
152
+ configuredName: string;
153
+ /** a workstream declared under `transitionalWorkstreams` */
154
+ transitional: boolean;
155
+ }
60
156
  export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "@rvoh/psychic-workers",
4
4
  "description": "Background job system for Psychic applications",
5
- "version": "2.4.1",
5
+ "version": "2.7.0",
6
6
  "author": "RVO Health",
7
7
  "publishConfig": {
8
8
  "access": "public"
@@ -61,7 +61,7 @@
61
61
  "ioredis": "^5.8.2"
62
62
  },
63
63
  "devDependencies": {
64
- "@eslint/js": "^9.0.0",
64
+ "@eslint/js": "^10.0.1",
65
65
  "@koa/cors": "^5.0.0",
66
66
  "@koa/etag": "^5.0.2",
67
67
  "@koa/router": "^15.4.0",
@@ -78,15 +78,15 @@
78
78
  "@types/node": "^22.12.0",
79
79
  "@types/pg": "^8",
80
80
  "@types/supertest": "^6.0.3",
81
- "@typescript-eslint/parser": "^8.48.1",
81
+ "@typescript-eslint/parser": "^8.67.0",
82
82
  "bullmq": "^5.64.0",
83
- "eslint": "^9.39.1",
83
+ "eslint": "^10.0.1",
84
84
  "ioredis": "^5.8.2",
85
85
  "koa": "^3.1.2",
86
86
  "koa-bodyparser": "^4.4.1",
87
87
  "koa-conditional-get": "^3.0.0",
88
- "kysely": "^0.28.13",
89
- "kysely-codegen": "^0.19.0",
88
+ "kysely": "^0.28.17",
89
+ "kysely-codegen": "~0.20.0",
90
90
  "luxon-jest-matchers": "^0.1.14",
91
91
  "openapi-typescript": "^7.9.1",
92
92
  "pg": "^8.13.1",
@@ -96,8 +96,9 @@
96
96
  "tsx": "^4.20.6",
97
97
  "typedoc": "^0.28.17",
98
98
  "typescript": "^5.8.2",
99
- "typescript-eslint": "^8.48.1",
100
- "vitest": "^4.1.0"
99
+ "typescript-eslint": "^8.67.0",
100
+ "vite": "^7.3.6",
101
+ "vitest": "^4.1.11"
101
102
  },
102
103
  "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
103
104
  "dependencies": {