@molecule/api-instance-leadership 0.0.1-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/README.md +287 -4
- package/dist/browser-guard.js +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,294 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
3
|
+
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
|
+
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
|
+
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
+
Generated: 2026-10-19T22:02:10.338Z
|
|
7
|
+
-->
|
|
8
|
+
|
|
1
9
|
# @molecule/api-instance-leadership
|
|
2
10
|
|
|
3
|
-
Instance-leadership core interface for molecule.dev: elect
|
|
11
|
+
Instance-leadership core interface for molecule.dev: elect ONE API instance
|
|
12
|
+
to run platform-wide singleton work, and hand leadership over within seconds
|
|
13
|
+
when the holder's connection is lost.
|
|
14
|
+
|
|
15
|
+
Bond a provider — `@molecule/api-instance-leadership-postgresql` — at
|
|
16
|
+
startup, then start leader-only work from `acquireLeadership()`:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { acquireLeadership, isLeader } from '@molecule/api-instance-leadership'
|
|
20
|
+
import { provider } from '@molecule/api-instance-leadership-postgresql'
|
|
21
|
+
import { setProvider } from '@molecule/api-instance-leadership'
|
|
22
|
+
|
|
23
|
+
setProvider(provider)
|
|
24
|
+
|
|
25
|
+
acquireLeadership({
|
|
26
|
+
onAcquired: () => startPoolManager(),
|
|
27
|
+
onLost: () => stopPoolManager(),
|
|
28
|
+
})
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Only the code you run inside `onAcquired` is leader-only.** Every instance
|
|
32
|
+
still serves HTTP traffic, processes queue messages, and answers readiness
|
|
33
|
+
probes. Reach for this package when a job must run once across the whole
|
|
34
|
+
platform — pre-warm pool managers, retention sweeps, status recorders,
|
|
35
|
+
periodic billing — not for anything request-driven.
|
|
36
|
+
|
|
37
|
+
Use `isLeader()` for facts that must be decided at call time (e.g. skipping a
|
|
38
|
+
duplicate periodic tick); use `onAcquired`/`onLost` to start and stop the
|
|
39
|
+
loops themselves.
|
|
40
|
+
|
|
41
|
+
> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
|
|
42
|
+
> It is written to be read by coding agents as much as by people, and is generated from this
|
|
43
|
+
> package's source — edit `src/index.ts` JSDoc, not this file.
|
|
44
|
+
|
|
45
|
+
## Type
|
|
46
|
+
|
|
47
|
+
`core`
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install @molecule/api-instance-leadership @molecule/api-bond
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### Interfaces
|
|
58
|
+
|
|
59
|
+
#### `LeaderLockSession`
|
|
60
|
+
|
|
61
|
+
One dedicated lock session, as a provider bond sees it.
|
|
62
|
+
|
|
63
|
+
A session must be backed by a connection the provider owns exclusively: the
|
|
64
|
+
lock lives and dies with that connection, so an operator never has to tune a
|
|
65
|
+
TTL and a crashed instance never leaves a stale lock row behind.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
interface LeaderLockSession {
|
|
69
|
+
/**
|
|
70
|
+
* Ask the shared store for the lock WITHOUT blocking.
|
|
71
|
+
*
|
|
72
|
+
* @returns `true` when this session now holds it.
|
|
73
|
+
*/
|
|
74
|
+
tryLock(): Promise<boolean>
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Release the lock (a no-op when this session does not hold it) and close
|
|
78
|
+
* the connection.
|
|
79
|
+
*/
|
|
80
|
+
release(): Promise<void>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register the "this connection is gone" handler. Called at most once, when
|
|
84
|
+
* the connection errors or the store ends the session.
|
|
85
|
+
*
|
|
86
|
+
* @param handler - Invoked on connection loss.
|
|
87
|
+
*/
|
|
88
|
+
onLost(handler: () => void): void
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### `AcquireLeadershipOptions`
|
|
93
|
+
|
|
94
|
+
What {@link InstanceLeadershipProvider.acquire} needs from the application.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
interface AcquireLeadershipOptions {
|
|
98
|
+
/**
|
|
99
|
+
* Start the singleton subsystems. Invoked once per acquisition — including a
|
|
100
|
+
* RE-acquisition after a connection loss, so it must be safe to call again
|
|
101
|
+
* after {@link AcquireLeadershipOptions.onLost} stopped everything.
|
|
102
|
+
*/
|
|
103
|
+
onAcquired: () => void | Promise<void>
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stop the singleton subsystems. Invoked when a leader loses its lock
|
|
107
|
+
* connection, BEFORE the provider tries to re-acquire: the store has (or is
|
|
108
|
+
* about to) release the lock, another instance may already be taking over,
|
|
109
|
+
* and two live leaders is exactly what leadership exists to prevent.
|
|
110
|
+
*/
|
|
111
|
+
onLost?: () => void | Promise<void>
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### `InstanceLeadershipProvider`
|
|
116
|
+
|
|
117
|
+
The contract every instance-leadership provider implements.
|
|
118
|
+
|
|
119
|
+
Application code reaches the bonded provider through the accessors in
|
|
120
|
+
`@molecule/api-instance-leadership` (`setProvider`, `getProvider`,
|
|
121
|
+
`isLeader`, `acquireLeadership`, `releaseLeadership`) rather than importing
|
|
122
|
+
a bond.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
interface InstanceLeadershipProvider {
|
|
126
|
+
/**
|
|
127
|
+
* Whether THIS instance currently holds the leadership lock.
|
|
128
|
+
*
|
|
129
|
+
* @returns `true` from the moment the lock is acquired until it is lost or
|
|
130
|
+
* released.
|
|
131
|
+
*/
|
|
132
|
+
isLeader(): boolean
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The current leadership phase.
|
|
136
|
+
*
|
|
137
|
+
* @returns The {@link LeadershipState}.
|
|
138
|
+
*/
|
|
139
|
+
state(): LeadershipState
|
|
4
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Start contending for the lock, retrying forever in the background. Never
|
|
143
|
+
* rejects and never blocks — the caller starts its singleton subsystems from
|
|
144
|
+
* {@link AcquireLeadershipOptions.onAcquired}.
|
|
145
|
+
*
|
|
146
|
+
* Calling `acquire()` again while already contending or leading is a no-op.
|
|
147
|
+
*
|
|
148
|
+
* @param options - What to start on acquisition and stop on loss.
|
|
149
|
+
*/
|
|
150
|
+
acquire(options: AcquireLeadershipOptions): void
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Stop contending and release the lock, so another instance can take over
|
|
154
|
+
* immediately instead of waiting for this process to exit.
|
|
155
|
+
*
|
|
156
|
+
* @returns Resolves once the lock release and the connection close settle.
|
|
157
|
+
*/
|
|
158
|
+
release(): Promise<void>
|
|
159
|
+
}
|
|
5
160
|
```
|
|
6
|
-
|
|
161
|
+
|
|
162
|
+
### Types
|
|
163
|
+
|
|
164
|
+
#### `LeadershipState`
|
|
165
|
+
|
|
166
|
+
The leadership phase of one instance.
|
|
167
|
+
|
|
168
|
+
- `follower` — the instance is contending for the lock but does not hold it.
|
|
169
|
+
- `leader` — the instance holds the lock and runs the singleton work.
|
|
170
|
+
- `stopped` — `release()` was called; the instance no longer contends.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
type LeadershipState = 'follower' | 'leader' | 'stopped'
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `LeaderLockFactory`
|
|
177
|
+
|
|
178
|
+
Creates a fresh lock session.
|
|
179
|
+
|
|
180
|
+
Injected rather than imported so a host application can reuse its own
|
|
181
|
+
database connection settings, and so tests can drive several simulated
|
|
182
|
+
instances through one fake store.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
type LeaderLockFactory = () => Promise<LeaderLockSession>
|
|
7
186
|
```
|
|
8
187
|
|
|
9
|
-
|
|
188
|
+
### Functions
|
|
189
|
+
|
|
190
|
+
#### `setProvider(provider)`
|
|
191
|
+
|
|
192
|
+
Registers an instance-leadership provider as the active singleton. Called by
|
|
193
|
+
a bond package during application startup.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
function setProvider(provider: InstanceLeadershipProvider): void
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- `provider` — The provider implementation to bond.
|
|
200
|
+
|
|
201
|
+
#### `getProvider()`
|
|
202
|
+
|
|
203
|
+
Retrieves the bonded instance-leadership provider, throwing if none is wired.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
function getProvider(): InstanceLeadershipProvider
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Returns:** The bonded instance-leadership provider.
|
|
210
|
+
|
|
211
|
+
#### `hasProvider()`
|
|
212
|
+
|
|
213
|
+
Checks whether an instance-leadership provider is currently bonded.
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
function hasProvider(): boolean
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Returns:** `true` if a provider is bonded.
|
|
220
|
+
|
|
221
|
+
#### `isLeader()`
|
|
222
|
+
|
|
223
|
+
Whether THIS instance currently holds platform leadership.
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
function isLeader(): boolean
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Returns:** `false` when no provider is bonded or this instance is a follower.
|
|
230
|
+
|
|
231
|
+
#### `leadershipState()`
|
|
232
|
+
|
|
233
|
+
The current leadership phase of this instance.
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
function leadershipState(): LeadershipState
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Returns:** The bonded provider's state, or `'follower'` when none is bonded.
|
|
240
|
+
|
|
241
|
+
#### `acquireLeadership(options)`
|
|
242
|
+
|
|
243
|
+
Starts contending for platform leadership through the bonded provider.
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
function acquireLeadership(options: AcquireLeadershipOptions): void
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- `options` — What to start on acquisition and stop on loss.
|
|
250
|
+
|
|
251
|
+
#### `releaseLeadership()`
|
|
252
|
+
|
|
253
|
+
Releases platform leadership through the bonded provider so another
|
|
254
|
+
instance can take over immediately.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
function releaseLeadership(): Promise<void>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Returns:** Resolves once the release settles; a no-op when no provider is bonded.
|
|
261
|
+
|
|
262
|
+
## Available Providers
|
|
263
|
+
|
|
264
|
+
| Provider | Package |
|
|
265
|
+
| ---------- | ---------------------------------------------- |
|
|
266
|
+
| PostgreSQL | `@molecule/api-instance-leadership-postgresql` |
|
|
267
|
+
|
|
268
|
+
## Injection Notes
|
|
269
|
+
|
|
270
|
+
### Requirements
|
|
271
|
+
|
|
272
|
+
Peer dependencies:
|
|
273
|
+
|
|
274
|
+
- `@molecule/api-bond` ^1.0.1
|
|
275
|
+
|
|
276
|
+
### Runtime Dependencies
|
|
277
|
+
|
|
278
|
+
- `@molecule/api-bond`
|
|
10
279
|
|
|
11
|
-
|
|
280
|
+
- **The lock must live in a store every instance shares.** A host-local lock
|
|
281
|
+
(a loopback port, a file) elects one leader PER HOST, so every singleton
|
|
282
|
+
runs twice — the failure this package exists to prevent.
|
|
283
|
+
- **`onLost` is not optional in practice for a real leader.** A provider may
|
|
284
|
+
lose the lock while the process keeps running (a database connection that
|
|
285
|
+
errors); the instance demotes itself and another may already be leading.
|
|
286
|
+
Stop the loops in `onLost`, or two copies run.
|
|
287
|
+
- **Re-acquisition is possible.** `onAcquired` runs again after a loss, so
|
|
288
|
+
it must be safe to call after `onLost` stopped everything.
|
|
289
|
+
- **Followers retry forever in the background.** `acquireLeadership()` never
|
|
290
|
+
rejects and never blocks; a database that is down at boot just means this
|
|
291
|
+
instance stays a follower until it is reachable.
|
|
292
|
+
- **No provider bonded ⇒ `isLeader()` is `false`.** A single-instance app can
|
|
293
|
+
use this package unchanged (it never leads until a bond exists) or omit it
|
|
294
|
+
and run its singleton work directly.
|
package/dist/browser-guard.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Browser guard — `@molecule/api-instance-leadership` is SERVER-ONLY.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* node runtime.
|
|
4
|
+
* Generated by scripts/gen-browser-guards.mjs (workspace root) — edit THAT, not this.
|
|
5
|
+
* Evaluating a server package in a browser bundle is always an import-graph mistake
|
|
6
|
+
* (node APIs, secrets); without this guard it surfaces as a cryptic downstream crash
|
|
7
|
+
* ("Buffer is not defined") far from the culprit. Throwing here names the package and
|
|
8
|
+
* the fix at the exact moment the client bundle evaluates it. jsdom tests and SSR are
|
|
9
|
+
* unaffected: the throw requires browser globals AND the absence of a node runtime.
|
|
10
10
|
*/
|
|
11
11
|
const g = globalThis;
|
|
12
12
|
if (g.window !== undefined && g.document !== undefined && !g.process?.versions?.node) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-instance-leadership",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Instance-leadership core interface for molecule.dev: elect one API instance to run platform-wide singleton work, with demotion and takeover when the holder's connection is lost.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|