@kelpie/server 0.13.0 → 0.14.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/boot.d.ts +9 -0
- package/dist/boot.d.ts.map +1 -1
- package/dist/boot.js +11 -2
- package/dist/boot.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/jobs.d.ts +110 -0
- package/dist/lib/jobs.d.ts.map +1 -0
- package/dist/lib/jobs.js +5 -0
- package/dist/lib/jobs.js.map +1 -0
- package/dist/runtime/jobs.d.ts +63 -0
- package/dist/runtime/jobs.d.ts.map +1 -0
- package/dist/runtime/jobs.js +231 -0
- package/dist/runtime/jobs.js.map +1 -0
- package/dist/runtime/module.d.ts +12 -0
- package/dist/runtime/module.d.ts.map +1 -1
- package/dist/runtime/registry.d.ts +8 -0
- package/dist/runtime/registry.d.ts.map +1 -1
- package/dist/runtime/registry.js +15 -0
- package/dist/runtime/registry.js.map +1 -1
- package/dist/runtime/transaction.d.ts +19 -0
- package/dist/runtime/transaction.d.ts.map +1 -1
- package/dist/runtime/transaction.js +7 -0
- package/dist/runtime/transaction.js.map +1 -1
- package/dist/testing/app.d.ts +7 -0
- package/dist/testing/app.d.ts.map +1 -1
- package/dist/testing/app.js +1 -0
- package/dist/testing/app.js.map +1 -1
- package/dist/testing/database.d.ts +7 -0
- package/dist/testing/database.d.ts.map +1 -1
- package/dist/testing/database.js +26 -2
- package/dist/testing/database.js.map +1 -1
- package/dist/testing/services.d.ts +7 -0
- package/dist/testing/services.d.ts.map +1 -1
- package/dist/testing/services.js +8 -1
- package/dist/testing/services.js.map +1 -1
- package/dist/worker.d.ts +15 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +19 -0
- package/dist/worker.js.map +1 -0
- package/package.json +3 -2
- package/src/boot.ts +20 -2
- package/src/index.ts +17 -0
- package/src/lib/jobs.ts +126 -0
- package/src/runtime/jobs.ts +358 -0
- package/src/runtime/module.ts +12 -0
- package/src/runtime/registry.ts +24 -0
- package/src/runtime/transaction.ts +43 -0
- package/src/testing/app.ts +8 -0
- package/src/testing/database.ts +35 -2
- package/src/testing/services.ts +15 -1
- package/src/worker.ts +20 -0
package/src/testing/app.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { DatabaseProbe } from '../lib/database.ts'
|
|
|
9
9
|
import { createCaptureTransport, createLogger } from '../lib/logger.ts'
|
|
10
10
|
import { rateLimitConfigFrom, rateLimitConfigSchema } from '../lib/rateLimit.ts'
|
|
11
11
|
import type { RateLimitConfig } from '../lib/rateLimit.ts'
|
|
12
|
+
import type { JobRegistry } from '../lib/jobs.ts'
|
|
12
13
|
import type { KelpieModule } from '../runtime/module.ts'
|
|
13
14
|
import type { ModuleContributions } from '../runtime/registry.ts'
|
|
14
15
|
import type { EntitlementRegistry } from '../runtime/entitlements.ts'
|
|
@@ -69,6 +70,12 @@ export interface TestAppOptions {
|
|
|
69
70
|
readonly signupsEnabled?: boolean
|
|
70
71
|
/** Reported through `GET /v1/public/config`. Defaults to `[]`. */
|
|
71
72
|
readonly regions?: readonly PublicRegion[]
|
|
73
|
+
/**
|
|
74
|
+
* Jobs registry to hand each module at register time. Optional: tests that
|
|
75
|
+
* never define a job (the common case) leave this unset, and a stub
|
|
76
|
+
* refuses `define` with a boot-time bug message that names the module.
|
|
77
|
+
*/
|
|
78
|
+
readonly jobs?: JobRegistry
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
export interface TestApp {
|
|
@@ -102,6 +109,7 @@ export async function createTestApp(options: TestAppOptions = {}): Promise<TestA
|
|
|
102
109
|
...(options.entitlements === undefined ? {} : { entitlements: options.entitlements }),
|
|
103
110
|
...(options.moduleConfig === undefined ? {} : { moduleConfig: options.moduleConfig }),
|
|
104
111
|
...(options.resolveActor === undefined ? {} : { resolveActor: options.resolveActor }),
|
|
112
|
+
...(options.jobs === undefined ? {} : { jobs: options.jobs }),
|
|
105
113
|
services: {
|
|
106
114
|
...services,
|
|
107
115
|
...(options.signupsEnabled === undefined ? {} : { signupsEnabled: options.signupsEnabled }),
|
package/src/testing/database.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { connectDatabase } from '../lib/database.ts'
|
|
|
6
6
|
import type { DatabaseConnection } from '../lib/database.ts'
|
|
7
7
|
import { createLogger } from '../lib/logger.ts'
|
|
8
8
|
import { coreModules } from '../modules/core.ts'
|
|
9
|
+
import { createJobsRuntime } from '../runtime/jobs.ts'
|
|
10
|
+
import type { JobsRuntime } from '../runtime/jobs.ts'
|
|
9
11
|
import { runMigrations } from '../runtime/migrate.ts'
|
|
10
12
|
import { registerModules } from '../runtime/registry.ts'
|
|
11
13
|
import { TEST_ENVIRONMENT } from './environment.ts'
|
|
@@ -30,6 +32,12 @@ const silentLogger = createLogger({ level: 'error', transports: [] })
|
|
|
30
32
|
export interface TestDatabase extends DatabaseConnection {
|
|
31
33
|
/** Empties every table. Cheaper than truncating or re-migrating between tests. */
|
|
32
34
|
readonly truncateAll: () => Promise<void>
|
|
35
|
+
/**
|
|
36
|
+
* The pg-boss-backed jobs runtime, migrated onto the same test database.
|
|
37
|
+
* Tests that need workers running call `jobs.startWorking()`; tests that
|
|
38
|
+
* only enqueue can leave it stopped, since `enqueueOnTx` needs no worker.
|
|
39
|
+
*/
|
|
40
|
+
readonly jobs: JobsRuntime
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
/**
|
|
@@ -90,18 +98,28 @@ export async function connectTestDatabase(connectionString: string): Promise<Tes
|
|
|
90
98
|
await ensureDatabaseExists(connectionString)
|
|
91
99
|
|
|
92
100
|
const connection = connectDatabase(connectionString, silentLogger)
|
|
93
|
-
const
|
|
101
|
+
const jobs = createJobsRuntime({
|
|
102
|
+
connectionString,
|
|
103
|
+
logger: silentLogger,
|
|
104
|
+
// A short poll floor so a test that enqueues and waits for the handler
|
|
105
|
+
// never blocks on the 30 s NOTIFY backstop when notify is unavailable
|
|
106
|
+
// (e.g. connection dropped between tests). Real workers stay at 30 s.
|
|
107
|
+
pollingIntervalSeconds: 0.5,
|
|
108
|
+
})
|
|
109
|
+
const services = createTestServices({ db: connection.db, enqueueOnTx: jobs.enqueueOnTx })
|
|
94
110
|
const contributions = await registerModules({
|
|
95
111
|
modules: coreModules,
|
|
96
112
|
// Enough for core modules to configure themselves; no test reads it further.
|
|
97
113
|
environment: TEST_ENVIRONMENT,
|
|
98
114
|
logger: silentLogger,
|
|
99
115
|
services,
|
|
116
|
+
jobs: jobs.registry,
|
|
100
117
|
email: { provider: TEST_EMAIL_PROVIDER, from: TEST_EMAIL_FROM },
|
|
101
118
|
additionalEmailProviders: new Map([[TEST_EMAIL_PROVIDER, services.emailSender]]),
|
|
102
119
|
})
|
|
103
120
|
|
|
104
121
|
await runMigrations(connection.db, contributions.schemas, silentLogger)
|
|
122
|
+
await jobs.migrate()
|
|
105
123
|
|
|
106
124
|
let cachedReset: string | undefined
|
|
107
125
|
|
|
@@ -189,7 +207,22 @@ export async function connectTestDatabase(connectionString: string): Promise<Tes
|
|
|
189
207
|
if (cachedReset !== undefined) {
|
|
190
208
|
await connection.db.execute(sql.raw(cachedReset))
|
|
191
209
|
}
|
|
210
|
+
|
|
211
|
+
// Clear pg-boss's job table too. Every test starts with an empty queue
|
|
212
|
+
// regardless of whether the previous test's worker consumed everything.
|
|
213
|
+
// A TRUNCATE would rebuild the partition heap on every call; delete stays
|
|
214
|
+
// fast against a mostly-empty table.
|
|
215
|
+
await connection.db.execute(sql`delete from pgboss.job`)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function close(): Promise<void> {
|
|
219
|
+
// Order matters: pg-boss owns its own connection pool, so stop it first
|
|
220
|
+
// so it can `unlisten` and drain workers. Then close the postgres.js pool
|
|
221
|
+
// the app uses. Idempotent for tests that never started the worker;
|
|
222
|
+
// `jobs.stop()` returns early when nothing is running.
|
|
223
|
+
await jobs.stop()
|
|
224
|
+
await connection.close()
|
|
192
225
|
}
|
|
193
226
|
|
|
194
|
-
return { ...connection, truncateAll }
|
|
227
|
+
return { ...connection, close, truncateAll, jobs }
|
|
195
228
|
}
|
package/src/testing/services.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createEventBus } from '../runtime/events.ts'
|
|
|
9
9
|
import type { EventBus } from '../runtime/events.ts'
|
|
10
10
|
import type { ModuleServices } from '../runtime/module.ts'
|
|
11
11
|
import { createTransactionScope } from '../runtime/transaction.ts'
|
|
12
|
+
import type { EnqueueOnTransaction } from '../runtime/transaction.ts'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The collaborators a module needs, wired for tests.
|
|
@@ -40,6 +41,12 @@ export interface TestServicesOptions {
|
|
|
40
41
|
readonly appBaseUrl?: string
|
|
41
42
|
/** Threaded through to `ModuleServices.secretEncryption`, so a test exercises the preferred path. */
|
|
42
43
|
readonly secretEncryption?: SecretEncryptionConfig
|
|
44
|
+
/**
|
|
45
|
+
* How `tx.jobs.enqueue(...)` reaches its backing runtime. Omitted, the
|
|
46
|
+
* transaction scope rejects the call with a boot-time bug message; tests
|
|
47
|
+
* that never enqueue leave this unset.
|
|
48
|
+
*/
|
|
49
|
+
readonly enqueueOnTx?: EnqueueOnTransaction
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
export interface TestServices extends ModuleServices {
|
|
@@ -81,7 +88,14 @@ export function createTestServices(options: TestServicesOptions = {}): TestServi
|
|
|
81
88
|
|
|
82
89
|
return {
|
|
83
90
|
db,
|
|
84
|
-
transaction: createTransactionScope({
|
|
91
|
+
transaction: createTransactionScope({
|
|
92
|
+
db,
|
|
93
|
+
bus: events,
|
|
94
|
+
logger,
|
|
95
|
+
createId,
|
|
96
|
+
now,
|
|
97
|
+
...(options.enqueueOnTx === undefined ? {} : { enqueueOnTx: options.enqueueOnTx }),
|
|
98
|
+
}),
|
|
85
99
|
createId,
|
|
86
100
|
now,
|
|
87
101
|
emailSender: sender,
|
package/src/worker.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AssemblyBoot } from './boot.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Runs after `bootAssembly` on the worker entry point.
|
|
5
|
+
*
|
|
6
|
+
* Applies pg-boss's schema (idempotent), opens the persistent pg-boss
|
|
7
|
+
* instance, and starts the work loop for every registered handle. Does not
|
|
8
|
+
* call `createApp`: workers process queues, they do not answer HTTP.
|
|
9
|
+
*
|
|
10
|
+
* The API entry point calls the same steps unless started with `--no-worker`,
|
|
11
|
+
* so an OSS assembly runs one container that both serves and works. Cloud
|
|
12
|
+
* runs a separate worker process group and passes `--no-worker` to the API,
|
|
13
|
+
* so the two never contend on the same handler.
|
|
14
|
+
*/
|
|
15
|
+
export async function startWorker(boot: AssemblyBoot): Promise<void> {
|
|
16
|
+
await boot.jobs.migrate()
|
|
17
|
+
await boot.jobs.start()
|
|
18
|
+
await boot.jobs.startWorking()
|
|
19
|
+
boot.logger.info('worker started', { runtimeMode: boot.config.runtimeMode })
|
|
20
|
+
}
|