velocious 1.0.708 → 1.0.710
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/README.md +1 -1
- package/build/configuration-types.js +19 -3
- package/build/configuration.js +85 -3
- package/build/database/advisory-lock-errors.js +59 -0
- package/build/database/advisory-lock-lock-server-backend.js +557 -0
- package/build/database/advisory-lock-runner.js +175 -2
- package/build/database/migrator.js +29 -1
- package/build/database/record/index.js +16 -2
- package/build/src/configuration-types.d.ts +76 -4
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +19 -4
- package/build/src/configuration.d.ts +42 -2
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +82 -4
- package/build/src/database/advisory-lock-errors.d.ts +54 -0
- package/build/src/database/advisory-lock-errors.d.ts.map +1 -0
- package/build/src/database/advisory-lock-errors.js +52 -0
- package/build/src/database/advisory-lock-lock-server-backend.d.ts +279 -0
- package/build/src/database/advisory-lock-lock-server-backend.d.ts.map +1 -0
- package/build/src/database/advisory-lock-lock-server-backend.js +474 -0
- package/build/src/database/advisory-lock-runner.d.ts +69 -1
- package/build/src/database/advisory-lock-runner.d.ts.map +1 -1
- package/build/src/database/advisory-lock-runner.js +150 -3
- package/build/src/database/migrator.d.ts +13 -0
- package/build/src/database/migrator.d.ts.map +1 -1
- package/build/src/database/migrator.js +27 -2
- package/build/src/database/record/index.d.ts +2 -2
- package/build/src/database/record/index.d.ts.map +1 -1
- package/build/src/database/record/index.js +3 -3
- package/package.json +1 -1
- package/src/configuration-types.js +19 -3
- package/src/configuration.js +85 -3
- package/src/database/advisory-lock-errors.js +59 -0
- package/src/database/advisory-lock-lock-server-backend.js +557 -0
- package/src/database/advisory-lock-runner.js +175 -2
- package/src/database/migrator.js +29 -1
- package/src/database/record/index.js +16 -2
package/package.json
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Resolved lock-service configuration after environment and Beacon defaults.
|
|
5
|
+
* @typedef {object} LockServiceResolvedConfigType
|
|
6
|
+
* @property {string} [authToken] - Shared secret enforced by the host, when configured.
|
|
7
|
+
* @property {number} connectBackoffMs - Base backoff between connection retries.
|
|
8
|
+
* @property {number} connectRetries - Bounded connection retries before giving up.
|
|
9
|
+
* @property {number} connectTimeoutMs - Handshake timeout.
|
|
10
|
+
* @property {"database" | "none"} fallback - What to do when the service is unreachable.
|
|
11
|
+
* @property {string} host - Lock-service host.
|
|
12
|
+
* @property {number} leaseMs - Lease duration advertised to clients.
|
|
13
|
+
* @property {"beacon" | "in-process" | "off"} mode - Where the authority lives.
|
|
14
|
+
* @property {string} namespace - Prefix applied to every lock name.
|
|
15
|
+
* @property {number} port - Lock-service port.
|
|
16
|
+
* @property {string | null} statePath - Durable mirror for epochs and holders.
|
|
17
|
+
*/
|
|
18
|
+
|
|
3
19
|
/**
|
|
4
20
|
* @module types
|
|
5
21
|
*/
|
|
6
22
|
|
|
7
23
|
/**
|
|
8
|
-
* Immutable identity shared by application initializers for one process lifecycle.
|
|
24
|
+
* Immutable identity shared by application hooks and initializers for one process lifecycle.
|
|
9
25
|
* @typedef {object} ApplicationProcessContext
|
|
10
26
|
* @property {string} type - Generic process type supplied to `configuration.initialize()`.
|
|
11
27
|
* @property {string} instanceId - Opaque identity unique to this lifecycle.
|
|
@@ -866,7 +882,7 @@
|
|
|
866
882
|
* behavior; the file backend is disabled by default and only selectable by
|
|
867
883
|
* explicit configuration.
|
|
868
884
|
* @typedef {object} AdvisoryLocksConfiguration
|
|
869
|
-
* @property {{file?: {directory?: string, enabled?: boolean, leaseMs?: number, renewIntervalMs?: number}}} [backends] - Backend enablement. `file.enabled` defaults to false; enabling it is the deployment's explicit single-host declaration.
|
|
885
|
+
* @property {{file?: {directory?: string, enabled?: boolean, leaseMs?: number, renewIntervalMs?: number}, lockServer?: {authToken?: string, connectTimeoutMs?: number, enabled?: boolean, host?: string, leaseMs?: number, port?: number, reprobeCooldownMs?: number, url?: string}}} [backends] - Backend enablement. `file.enabled` defaults to false; enabling it is the deployment's explicit single-host declaration. `lockServer` defaults to enabled when a host/port/url or the `VELOCIOUS_LOCK_SERVER_HOST`/`_PORT` env pair is configured.
|
|
870
886
|
* @property {import("./database/advisory-lock-backend-policy.js").AdvisoryLockBackendPolicyEntry[]} [policy] - Per-lock-name minimum/allowed/preferred tier rules (design §5).
|
|
871
887
|
*/
|
|
872
888
|
|
|
@@ -899,7 +915,7 @@
|
|
|
899
915
|
* @property {BeaconConfiguration} [beacon] - Beacon broadcast bus configuration.
|
|
900
916
|
* @property {ScheduledBackgroundJobsConfiguration | ScheduledBackgroundJobsLoaderType} [scheduledBackgroundJobs] - Scheduled background jobs configuration.
|
|
901
917
|
* @property {MailerBackend} [mailerBackend] - Mail delivery backend.
|
|
902
|
-
* @property {(args: {configuration: import("./configuration.js").default, type: string}) => void} initializeModels - Hook to register models for a given initialization type.
|
|
918
|
+
* @property {(args: {configuration: import("./configuration.js").default, processContext: ApplicationProcessContext | undefined, type: string}) => void} initializeModels - Hook to register models for a given initialization type.
|
|
903
919
|
* @property {InitializersType} [initializers] - Initializer loader for environment bootstrapping.
|
|
904
920
|
* @property {string | (() => string)} locale - Default locale or locale resolver.
|
|
905
921
|
* @property {string[]} locales - Supported locales.
|
package/src/configuration.js
CHANGED
|
@@ -261,6 +261,12 @@ export default class VelociousConfiguration {
|
|
|
261
261
|
* (browser/Expo, no `fs`) cannot create one at all.
|
|
262
262
|
* @type {import("./database/advisory-lock-file-backend.js").default | undefined} */
|
|
263
263
|
_advisoryLockFileBackend = undefined
|
|
264
|
+
/**
|
|
265
|
+
* Lazily created lock-server (tier (b)) backend; undefined when the tier is
|
|
266
|
+
* not configured.
|
|
267
|
+
* @type {import("./database/advisory-lock-lock-server-backend.js").default | undefined}
|
|
268
|
+
*/
|
|
269
|
+
_advisoryLockLockServerBackend = undefined
|
|
264
270
|
|
|
265
271
|
/**
|
|
266
272
|
* Most recent advisory-lock acquisition per lock name — the "which backend
|
|
@@ -2641,6 +2647,16 @@ export default class VelociousConfiguration {
|
|
|
2641
2647
|
*/
|
|
2642
2648
|
isInitialized() { return this._isInitialized }
|
|
2643
2649
|
|
|
2650
|
+
/**
|
|
2651
|
+
* Returns the framework-owned process context for this configuration's
|
|
2652
|
+
* lifecycle. It is undefined until `initialize()` starts, and it carries the
|
|
2653
|
+
* process `type` (`server`, `worker-handler`, background-jobs types, or an
|
|
2654
|
+
* application-defined string) so hooks and framework services can tell which
|
|
2655
|
+
* process they are running in.
|
|
2656
|
+
* @returns {import("./configuration-types.js").ApplicationProcessContext | undefined} - Process context, when initialization has started.
|
|
2657
|
+
*/
|
|
2658
|
+
getApplicationProcessContext() { return this._applicationProcessContext }
|
|
2659
|
+
|
|
2644
2660
|
/**
|
|
2645
2661
|
* Runs initialize models.
|
|
2646
2662
|
* @param {object} args - Options object.
|
|
@@ -2674,7 +2690,7 @@ export default class VelociousConfiguration {
|
|
|
2674
2690
|
|
|
2675
2691
|
if (!shouldSkipDummyModelInitialization) {
|
|
2676
2692
|
if (this._initializeModels) {
|
|
2677
|
-
await this._initializeModels({configuration: this, type: args.type})
|
|
2693
|
+
await this._initializeModels({configuration: this, processContext: this.getApplicationProcessContext(), type: args.type})
|
|
2678
2694
|
}
|
|
2679
2695
|
|
|
2680
2696
|
await this.getEnvironmentHandler().initializePackageModels(this)
|
|
@@ -4195,11 +4211,21 @@ export default class VelociousConfiguration {
|
|
|
4195
4211
|
* Full backward compatibility: an app that configures nothing gets today's
|
|
4196
4212
|
* driver-native-only behavior — the file backend is **disabled by default**
|
|
4197
4213
|
* and only ever selectable by explicit configuration.
|
|
4198
|
-
* @returns {{backends: {file: {directory?: string, enabled: boolean, leaseMs: number, renewIntervalMs: number}}, policy: import("./database/advisory-lock-backend-policy.js").AdvisoryLockBackendPolicyEntry[]}} - Advisory-lock configuration with defaults applied.
|
|
4214
|
+
* @returns {{backends: {file: {directory?: string, enabled: boolean, leaseMs: number, renewIntervalMs: number}, lockServer: {authToken: string, connectTimeoutMs: number, enabled: boolean, leaseMs: number, reprobeCooldownMs: number, url: string}}, policy: import("./database/advisory-lock-backend-policy.js").AdvisoryLockBackendPolicyEntry[]}} - Advisory-lock configuration with defaults applied.
|
|
4199
4215
|
*/
|
|
4200
4216
|
getAdvisoryLockConfig() {
|
|
4201
4217
|
const configured = this._advisoryLocks || {}
|
|
4202
4218
|
const fileConfigured = configured.backends?.file || {}
|
|
4219
|
+
const lockServerConfigured = configured.backends?.lockServer || {}
|
|
4220
|
+
const envHost = process.env.VELOCIOUS_LOCK_SERVER_HOST
|
|
4221
|
+
const envPortRaw = process.env.VELOCIOUS_LOCK_SERVER_PORT
|
|
4222
|
+
const envPort = envPortRaw ? Number(envPortRaw) : undefined
|
|
4223
|
+
const lockServerHost = lockServerConfigured.host || envHost
|
|
4224
|
+
const lockServerPort = typeof lockServerConfigured.port === "number"
|
|
4225
|
+
? lockServerConfigured.port
|
|
4226
|
+
: (typeof envPort === "number" && Number.isFinite(envPort) ? envPort : 3006)
|
|
4227
|
+
const lockServerUrl = lockServerConfigured.url
|
|
4228
|
+
|| `ws://${lockServerHost || "127.0.0.1"}:${lockServerPort}/websocket`
|
|
4203
4229
|
|
|
4204
4230
|
return {
|
|
4205
4231
|
backends: {
|
|
@@ -4208,6 +4234,18 @@ export default class VelociousConfiguration {
|
|
|
4208
4234
|
enabled: fileConfigured.enabled === true,
|
|
4209
4235
|
leaseMs: typeof fileConfigured.leaseMs === "number" ? fileConfigured.leaseMs : 30_000,
|
|
4210
4236
|
renewIntervalMs: typeof fileConfigured.renewIntervalMs === "number" ? fileConfigured.renewIntervalMs : 10_000
|
|
4237
|
+
},
|
|
4238
|
+
lockServer: {
|
|
4239
|
+
// Enabled only by explicit configuration or an explicit env pair, so
|
|
4240
|
+
// an app that configures nothing keeps today's driver-native behavior.
|
|
4241
|
+
authToken: typeof lockServerConfigured.authToken === "string" ? lockServerConfigured.authToken : "",
|
|
4242
|
+
connectTimeoutMs: typeof lockServerConfigured.connectTimeoutMs === "number" ? lockServerConfigured.connectTimeoutMs : 3000,
|
|
4243
|
+
enabled: typeof lockServerConfigured.enabled === "boolean"
|
|
4244
|
+
? lockServerConfigured.enabled
|
|
4245
|
+
: Boolean(lockServerConfigured.url || lockServerConfigured.host || envHost || typeof lockServerConfigured.port === "number" || envPortRaw),
|
|
4246
|
+
leaseMs: typeof lockServerConfigured.leaseMs === "number" ? lockServerConfigured.leaseMs : 30_000,
|
|
4247
|
+
reprobeCooldownMs: typeof lockServerConfigured.reprobeCooldownMs === "number" ? lockServerConfigured.reprobeCooldownMs : 5000,
|
|
4248
|
+
url: lockServerUrl
|
|
4211
4249
|
}
|
|
4212
4250
|
},
|
|
4213
4251
|
policy: Array.isArray(configured.policy) ? configured.policy : []
|
|
@@ -4257,6 +4295,39 @@ export default class VelociousConfiguration {
|
|
|
4257
4295
|
return this._advisoryLockFileBackend
|
|
4258
4296
|
}
|
|
4259
4297
|
|
|
4298
|
+
/**
|
|
4299
|
+
* Returns the lock-server advisory-lock backend (tier (b)) when this
|
|
4300
|
+
* deployment configured one, creating it on first use. Returns `undefined`
|
|
4301
|
+
* when the tier is not configured — callers must never invent one.
|
|
4302
|
+
*
|
|
4303
|
+
* The backend keeps a single socket per process; it holds no database
|
|
4304
|
+
* connection while waiting for or holding a lease.
|
|
4305
|
+
* @returns {Promise<import("./database/advisory-lock-lock-server-backend.js").default | undefined>} - Resolves with the configured backend, or undefined.
|
|
4306
|
+
*/
|
|
4307
|
+
async getAdvisoryLockLockServerBackend() {
|
|
4308
|
+
const config = this.getAdvisoryLockConfig()
|
|
4309
|
+
|
|
4310
|
+
if (!config.backends.lockServer.enabled) return undefined
|
|
4311
|
+
if (this._advisoryLockLockServerBackend) return this._advisoryLockLockServerBackend
|
|
4312
|
+
|
|
4313
|
+
const {default: AdvisoryLockLockServerBackend} = await import("./database/advisory-lock-lock-server-backend.js")
|
|
4314
|
+
const backend = new AdvisoryLockLockServerBackend({
|
|
4315
|
+
authToken: config.backends.lockServer.authToken,
|
|
4316
|
+
connectTimeoutMs: config.backends.lockServer.connectTimeoutMs,
|
|
4317
|
+
leaseMs: config.backends.lockServer.leaseMs,
|
|
4318
|
+
onError: (error) => {
|
|
4319
|
+
this.getErrorEvents().emit("framework-error", {context: "advisory-lock-lock-server", error})
|
|
4320
|
+
this.getErrorEvents().emit("all-error", {context: "advisory-lock-lock-server", error, errorType: "framework-error"})
|
|
4321
|
+
},
|
|
4322
|
+
reprobeCooldownMs: config.backends.lockServer.reprobeCooldownMs,
|
|
4323
|
+
url: config.backends.lockServer.url
|
|
4324
|
+
})
|
|
4325
|
+
|
|
4326
|
+
this._advisoryLockLockServerBackend = backend
|
|
4327
|
+
|
|
4328
|
+
return backend
|
|
4329
|
+
}
|
|
4330
|
+
|
|
4260
4331
|
/**
|
|
4261
4332
|
* Records which backend tier served one advisory-lock acquisition so
|
|
4262
4333
|
* `getAdvisoryLockBackendStatus()` can answer "which backend served this
|
|
@@ -4277,11 +4348,12 @@ export default class VelociousConfiguration {
|
|
|
4277
4348
|
* carries fencing tokens, and the resolved policy (minimum/allowed/preferred
|
|
4278
4349
|
* tiers) for the name.
|
|
4279
4350
|
* @param {string} lockName - Lock name.
|
|
4280
|
-
* @returns {{acquisition: {backend: import("./database/advisory-lock-backend-policy.js").AdvisoryLockTier, fencingSupported: boolean, servedAt: string} | null, policy: import("./database/advisory-lock-backend-policy.js").ResolvedAdvisoryLockBackendPolicy}} - Backend status for the name.
|
|
4351
|
+
* @returns {{acquisition: {backend: import("./database/advisory-lock-backend-policy.js").AdvisoryLockTier, fencingSupported: boolean, servedAt: string} | null, lockServer: {connected: boolean, lastError: string | null, lastProbeAt: string | null, leasedNames: string[]} | null, policy: import("./database/advisory-lock-backend-policy.js").ResolvedAdvisoryLockBackendPolicy}} - Backend status for the name.
|
|
4281
4352
|
*/
|
|
4282
4353
|
getAdvisoryLockBackendStatus(lockName) {
|
|
4283
4354
|
return {
|
|
4284
4355
|
acquisition: this._advisoryLockAcquisitions.get(lockName) ?? null,
|
|
4356
|
+
lockServer: this._advisoryLockLockServerBackend?.getStatus() ?? null,
|
|
4285
4357
|
policy: this.resolveAdvisoryLockPolicy(lockName)
|
|
4286
4358
|
}
|
|
4287
4359
|
}
|
|
@@ -4374,6 +4446,16 @@ export default class VelociousConfiguration {
|
|
|
4374
4446
|
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
4375
4447
|
}
|
|
4376
4448
|
|
|
4449
|
+
try {
|
|
4450
|
+
// Release lock-server leases explicitly first, then drop the socket:
|
|
4451
|
+
// the server reclaims a closed connection anyway, but an explicit
|
|
4452
|
+
// release hands the name to the next waiter without waiting for the
|
|
4453
|
+
// transport teardown.
|
|
4454
|
+
await this._advisoryLockLockServerBackend?.close()
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
closeErrors.push(error instanceof Error ? error : new Error(String(error)))
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4377
4459
|
for (const pool of Object.values(this.databasePools)) {
|
|
4378
4460
|
if (!pool) continue
|
|
4379
4461
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {AdvisoryLockBackendUnavailableError} from "./advisory-lock-backend-policy.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Thrown when a lock-server acquisition could not be attempted because the
|
|
7
|
+
* lock server is unreachable. Distinct from `AdvisoryLockTimeoutError` ("the
|
|
8
|
+
* server was reachable, the holder never released") so a caller can react to
|
|
9
|
+
* an infrastructure outage without guessing.
|
|
10
|
+
*/
|
|
11
|
+
export class AdvisoryLockServiceUnavailableError extends Error {
|
|
12
|
+
/**
|
|
13
|
+
* Runs constructor.
|
|
14
|
+
* @param {string} message - Error message.
|
|
15
|
+
* @param {{lockName: string, reason: string}} args - Lock name and failure reason.
|
|
16
|
+
*/
|
|
17
|
+
constructor(message, {lockName, reason}) {
|
|
18
|
+
super(message)
|
|
19
|
+
this.name = "AdvisoryLockServiceUnavailableError"
|
|
20
|
+
this.lockName = lockName
|
|
21
|
+
this.reason = reason
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Thrown after a critical section ran on the lock-server tier while its lease
|
|
27
|
+
* was fenced or lost (design §7/§8): the section may have overlapped another
|
|
28
|
+
* holder, so its result must not be trusted as exclusive.
|
|
29
|
+
*/
|
|
30
|
+
export class AdvisoryLockLostError extends Error {
|
|
31
|
+
/**
|
|
32
|
+
* Runs constructor.
|
|
33
|
+
* @param {string} message - Error message.
|
|
34
|
+
* @param {{cause?: Error, currentEpoch: number | null, epoch: number | null, lockName: string}} args - Fencing details.
|
|
35
|
+
*/
|
|
36
|
+
constructor(message, {cause, currentEpoch, epoch, lockName}) {
|
|
37
|
+
super(message)
|
|
38
|
+
this.name = "AdvisoryLockLostError"
|
|
39
|
+
this.lockName = lockName
|
|
40
|
+
this.epoch = epoch
|
|
41
|
+
this.currentEpoch = currentEpoch
|
|
42
|
+
this.cause = cause
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Builds the error thrown when a lock name's minimum tier cannot be served and
|
|
48
|
+
* no eligible tier is available (design §9, fail-closed).
|
|
49
|
+
* @param {{lockName: string, minTier: import("./advisory-lock-backend-policy.js").AdvisoryLockTier, reasons: string[]}} args - Unavailability details.
|
|
50
|
+
* @returns {AdvisoryLockBackendUnavailableError} - Error to throw.
|
|
51
|
+
*/
|
|
52
|
+
export function advisoryLockBackendUnavailable(args) {
|
|
53
|
+
const {lockName, minTier, reasons} = args
|
|
54
|
+
|
|
55
|
+
return new AdvisoryLockBackendUnavailableError(
|
|
56
|
+
`No eligible advisory-lock backend is available for ${JSON.stringify(lockName)} (minimum tier ${JSON.stringify(minTier)}): ${reasons.join("; ")}`,
|
|
57
|
+
{lockName, minTier, reasons}
|
|
58
|
+
)
|
|
59
|
+
}
|