@c9up/eon 0.2.9 → 0.2.10
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/EonProvider.d.ts.map +1 -1
- package/dist/EonProvider.js +154 -25
- package/dist/EonProvider.js.map +1 -1
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/src/EonProvider.ts +178 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EonProvider.d.ts","sourceRoot":"","sources":["../src/EonProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAa,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EACN,KAAK,iBAAiB,EAEtB,KAAK,UAAU,EACf,MAAM,mBAAmB,CAAC;AAE3B;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE;QACV,SAAS,CACR,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,EAC5D,OAAO,EAAE,MAAM,OAAO,GACpB,IAAI,CAAC;QACR;;;;WAIG;QACH,OAAO,CAAC,CACP,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,GAC1D,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC9B,CAAC;IACF,MAAM,EAAE;QAAE,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD;;;;;;;;OAQG;IACH,OAAO,CAAC,IAAI,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,iBAAiB,CAAC;CAC/D;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,MAAM,EAAE,mBAAmB,KACvB,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,qBAAa,WAAW;;
|
|
1
|
+
{"version":3,"file":"EonProvider.d.ts","sourceRoot":"","sources":["../src/EonProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAa,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAGnE,OAAO,EACN,KAAK,iBAAiB,EAEtB,KAAK,UAAU,EACf,MAAM,mBAAmB,CAAC;AAE3B;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC7B,SAAS,EAAE;QACV,SAAS,CACR,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,EAC5D,OAAO,EAAE,MAAM,OAAO,GACpB,IAAI,CAAC;QACR;;;;WAIG;QACH,OAAO,CAAC,CACP,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,GAC1D,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAC9B,CAAC;IACF,MAAM,EAAE;QAAE,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAA;KAAE,CAAC;IACzD;;;;;;;;OAQG;IACH,OAAO,CAAC,IAAI,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,iBAAiB,CAAC;CAC/D;AAED;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAC1B,MAAM,EAAE,mBAAmB,KACvB,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,qBAAa,WAAW;;gBAUX,GAAG,EAAE,aAAa,EAAE,OAAO,GAAE,YAA2B;IAKpE,QAAQ,IAAI,IAAI;IAgFV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAuNrB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAkF/B;AAID,eAAe,WAAW,CAAC"}
|
package/dist/EonProvider.js
CHANGED
|
@@ -22,11 +22,72 @@ export class EonProvider {
|
|
|
22
22
|
#connections = new Map();
|
|
23
23
|
/** Set once a full boot opened connections — guards against a leaking re-boot. */
|
|
24
24
|
#booted = false;
|
|
25
|
+
/** What the `eon` / `eon.connection` tokens resolve to, once boot opened it. */
|
|
26
|
+
#defaultConnection;
|
|
25
27
|
constructor(app, connect = connectWsEon) {
|
|
26
28
|
this.#app = app;
|
|
27
29
|
this.#connect = connect;
|
|
28
30
|
}
|
|
29
|
-
register() {
|
|
31
|
+
register() {
|
|
32
|
+
// Bindings belong here, not in boot(). Upstream registers in `register`
|
|
33
|
+
// precisely so another provider can count on them during its own boot;
|
|
34
|
+
// bound in boot, the container had a different surface depending on
|
|
35
|
+
// which provider ran first, and `eon.compiler` was simply unavailable to
|
|
36
|
+
// anything booting earlier.
|
|
37
|
+
//
|
|
38
|
+
// It is also pure — a compile is a call into the native compiler, no
|
|
39
|
+
// socket, no database — so there is nothing here an inspection must
|
|
40
|
+
// avoid.
|
|
41
|
+
const service = {
|
|
42
|
+
compile: (spec, dialect) => compileStatementNative(spec, dialect),
|
|
43
|
+
};
|
|
44
|
+
this.#app.container.singleton("eon.compiler", () => service);
|
|
45
|
+
// The connection tokens are registered here too, so the surface does not
|
|
46
|
+
// change between warmup and run. What they RESOLVE to still depends on
|
|
47
|
+
// boot having opened something — a lazy factory cannot await — so
|
|
48
|
+
// reading one too early says what to do instead of answering undefined.
|
|
49
|
+
this.#app.container.singleton("eon", () => this.#requireDefault("eon"));
|
|
50
|
+
this.#app.container.singleton("eon.connection", () => this.#requireDefault("eon.connection"));
|
|
51
|
+
// The NAMED tokens too. Their names come from the config, which is
|
|
52
|
+
// readable here — nothing about `eon:primary` needs a socket to exist.
|
|
53
|
+
// Bound in boot they appeared only on the run path, so the container had
|
|
54
|
+
// a different surface in an inspection, and a boot that failed late left
|
|
55
|
+
// factories behind pointing at connections it had just closed.
|
|
56
|
+
const config = this.#app.config.get("timeseries") ??
|
|
57
|
+
this.#app.config.get("eon");
|
|
58
|
+
if (!config)
|
|
59
|
+
return;
|
|
60
|
+
for (const name of Object.keys(this.#resolveConnections(config).connections)) {
|
|
61
|
+
this.#app.container.singleton(`eon:${name}`, () => this.#requireNamed(name));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A named connection, or a message saying what is missing.
|
|
66
|
+
*
|
|
67
|
+
* Same reasoning as {@link #requireDefault}: resolving to `undefined` sent
|
|
68
|
+
* the caller into a TypeError several frames from the cause.
|
|
69
|
+
*/
|
|
70
|
+
#requireNamed(name) {
|
|
71
|
+
const connection = this.#connections.get(name);
|
|
72
|
+
if (connection === undefined) {
|
|
73
|
+
throw new Error(`EonProvider: connection '${name}' was resolved before it was opened. Connections open in boot(); check that config.timeseries.connections defines '${name}' and that EonProvider is listed before whatever resolved this.`);
|
|
74
|
+
}
|
|
75
|
+
return connection;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The default connection, or a message naming what is missing.
|
|
79
|
+
*
|
|
80
|
+
* Resolving to `undefined` sent the caller into a `TypeError` several
|
|
81
|
+
* frames away from the cause — a provider that resolved eon during its own
|
|
82
|
+
* boot, or an application with no `config/timeseries.ts` at all.
|
|
83
|
+
*/
|
|
84
|
+
#requireDefault(token) {
|
|
85
|
+
const connection = this.#defaultConnection;
|
|
86
|
+
if (connection === undefined) {
|
|
87
|
+
throw new Error(`EonProvider: '${token}' was resolved before any connection was opened. Connections open in boot(); check that config.timeseries defines one and that EonProvider is listed before whatever resolved this.`);
|
|
88
|
+
}
|
|
89
|
+
return connection;
|
|
90
|
+
}
|
|
30
91
|
/** True when the application was assembled to be inspected rather than run. */
|
|
31
92
|
#isInspecting() {
|
|
32
93
|
return this.#app.getMode?.() === "warmup";
|
|
@@ -39,12 +100,7 @@ export class EonProvider {
|
|
|
39
100
|
if (this.#booted) {
|
|
40
101
|
throw new Error("EonProvider: boot() has already opened connections; a second boot would overwrite and leak them. Call shutdown() first, or construct a new provider.");
|
|
41
102
|
}
|
|
42
|
-
//
|
|
43
|
-
const service = {
|
|
44
|
-
compile: (spec, dialect) => compileStatementNative(spec, dialect),
|
|
45
|
-
};
|
|
46
|
-
this.#app.container.singleton("eon.compiler", () => service);
|
|
47
|
-
// Everything past here opens a socket or creates a database, so an
|
|
103
|
+
// Everything here opens a socket or creates a database, so an
|
|
48
104
|
// inspection stops at the compiler. `warmUp()` runs boot, so a route
|
|
49
105
|
// listing reached TDengine — and could CREATE a database there — while
|
|
50
106
|
// `shutdown()` never fires on that path, leaving the sockets open.
|
|
@@ -97,22 +153,63 @@ export class EonProvider {
|
|
|
97
153
|
this.#connections.clear();
|
|
98
154
|
throw new Error(`EonProvider: default connection '${defaultName}' is not defined in config.timeseries.connections`);
|
|
99
155
|
}
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
156
|
+
// Everything past this point runs with the sockets ALREADY OPEN, and any
|
|
157
|
+
// of it can fail: importing the services module, registering the
|
|
158
|
+
// migration source. Without this the failure left the connections open,
|
|
159
|
+
// possibly the module singleton published, and `#booted` false — so the
|
|
160
|
+
// next attempt opened another set on top of the ones nobody could reach.
|
|
161
|
+
try {
|
|
162
|
+
// The tokens were all bound in `register()`; this is what they
|
|
163
|
+
// resolve to. Filling the map is what makes them answer.
|
|
164
|
+
this.#defaultConnection = defaultConn;
|
|
165
|
+
// Populate the `@c9up/eon/services/connection` singleton so apps can
|
|
166
|
+
// `import connection from '@c9up/eon/services/connection'` anywhere.
|
|
167
|
+
// Lazy import so a type-only discovery scan does not pull the module
|
|
168
|
+
// at construction time.
|
|
169
|
+
const { setConnection } = await import("./services/connection.js");
|
|
170
|
+
setConnection(defaultConn);
|
|
171
|
+
await this.#registerMigrationSource(defaultConn, config);
|
|
172
|
+
this.#booted = true;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
await this.#rollbackBoot(defaultConn);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Undo a boot that opened its connections and then failed.
|
|
181
|
+
*
|
|
182
|
+
* Everything this releases was created by THIS attempt, so a retry starts
|
|
183
|
+
* from nothing rather than from a half-assembled state. The module
|
|
184
|
+
* singleton is released only while it is still the one we published — two
|
|
185
|
+
* applications can share a process, and the survivor's connection must
|
|
186
|
+
* stay.
|
|
187
|
+
*/
|
|
188
|
+
async #rollbackBoot(published) {
|
|
189
|
+
await this.#releaseMigrationSource();
|
|
190
|
+
// The connector keeps PROCESS-GLOBAL handles, so closing the individual
|
|
191
|
+
// connections is not enough to let Node exit — shutdown() says so and
|
|
192
|
+
// destroys them; a failed boot left them behind and the process hung.
|
|
193
|
+
const hadWebsocket = [...this.#connections.values()].some((conn) => conn.transport === "websocket");
|
|
194
|
+
try {
|
|
195
|
+
const { clearConnection } = await import("./services/connection.js");
|
|
196
|
+
clearConnection(published);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// The module could not be loaded, so nothing was published from it.
|
|
200
|
+
}
|
|
201
|
+
this.#defaultConnection = undefined;
|
|
202
|
+
await Promise.allSettled([...this.#connections.values()].map((conn) => conn.close()));
|
|
203
|
+
this.#connections.clear();
|
|
204
|
+
if (hadWebsocket) {
|
|
205
|
+
try {
|
|
206
|
+
const { destroyEonConnector } = await import("./connection/websocket.js");
|
|
207
|
+
await destroyEonConnector();
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// A fake-only boot never pulled the connector in.
|
|
211
|
+
}
|
|
212
|
+
}
|
|
116
213
|
}
|
|
117
214
|
/**
|
|
118
215
|
* Hand eon's migration runner to the framework's `migrations` registry, so
|
|
@@ -142,20 +239,52 @@ export class EonProvider {
|
|
|
142
239
|
}
|
|
143
240
|
const { EonMigrationRunner } = await import("./schema/EonMigrationRunner.js");
|
|
144
241
|
const migrationsDir = config.migrationsDir ?? "database/eon-migrations";
|
|
145
|
-
|
|
242
|
+
const source = {
|
|
146
243
|
name: "eon",
|
|
147
244
|
directory: migrationsDir,
|
|
148
245
|
runner: new EonMigrationRunner(conn, { migrationsDir }),
|
|
149
|
-
}
|
|
246
|
+
};
|
|
247
|
+
registry.register(source);
|
|
248
|
+
// Kept so shutdown can hand the name back. Registering refuses a
|
|
249
|
+
// duplicate, so a provider that stops without releasing its name leaves
|
|
250
|
+
// a second boot in the same process failing on "already registered" —
|
|
251
|
+
// and the CLI driving a runner that holds a closed connection.
|
|
252
|
+
this.#migrationSource = source;
|
|
253
|
+
}
|
|
254
|
+
/** What this provider registered with the host's migration registry. */
|
|
255
|
+
#migrationSource;
|
|
256
|
+
/** Give the migration name back, while it is still ours. */
|
|
257
|
+
async #releaseMigrationSource() {
|
|
258
|
+
const source = this.#migrationSource;
|
|
259
|
+
if (source === undefined)
|
|
260
|
+
return;
|
|
261
|
+
this.#migrationSource = undefined;
|
|
262
|
+
const resolve = this.#app.container.resolve;
|
|
263
|
+
if (typeof resolve !== "function")
|
|
264
|
+
return;
|
|
265
|
+
try {
|
|
266
|
+
const registry = await resolve.call(this.#app.container, "migrations");
|
|
267
|
+
if (typeof registry === "object" &&
|
|
268
|
+
registry !== null &&
|
|
269
|
+
typeof Reflect.get(registry, "unregister") === "function") {
|
|
270
|
+
registry.unregister("eon", source);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// No registry, or an older host without `unregister`: nothing to give
|
|
275
|
+
// back, and a shutdown must not fail over it.
|
|
276
|
+
}
|
|
150
277
|
}
|
|
151
278
|
async shutdown() {
|
|
152
279
|
// Close every connection in parallel. `allSettled` (fail-open) so a single
|
|
153
280
|
// stuck close doesn't block the rest, but failures are aggregated and
|
|
154
281
|
// rethrown so supervisors see a non-zero shutdown signal. The map + module
|
|
155
282
|
// singleton are cleared unconditionally (never hand out closed handles).
|
|
283
|
+
await this.#releaseMigrationSource();
|
|
156
284
|
const named = [...this.#connections.entries()];
|
|
157
285
|
const results = await Promise.allSettled(named.map(([, c]) => c.close()));
|
|
158
286
|
this.#connections.clear();
|
|
287
|
+
this.#defaultConnection = undefined;
|
|
159
288
|
this.#booted = false; // allow a fresh boot() after a clean shutdown
|
|
160
289
|
const { clearConnection } = await import("./services/connection.js");
|
|
161
290
|
for (const [, conn] of named)
|
package/dist/EonProvider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EonProvider.js","sourceRoot":"","sources":["../src/EonProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,oBAAoB,CAAC;AAG5B,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAEN,sBAAsB,GAEtB,MAAM,mBAAmB,CAAC;AAoD3B,MAAM,OAAO,WAAW;IACd,IAAI,CAAgB;IACpB,QAAQ,CAAe;IAChC,4DAA4D;IACnD,YAAY,GAAG,IAAI,GAAG,EAAyB,CAAC;IACzD,kFAAkF;IAClF,OAAO,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"EonProvider.js","sourceRoot":"","sources":["../src/EonProvider.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
package/package.json
CHANGED
package/src/EonProvider.ts
CHANGED
|
@@ -80,13 +80,88 @@ export class EonProvider {
|
|
|
80
80
|
readonly #connections = new Map<string, EonConnection>();
|
|
81
81
|
/** Set once a full boot opened connections — guards against a leaking re-boot. */
|
|
82
82
|
#booted = false;
|
|
83
|
+
/** What the `eon` / `eon.connection` tokens resolve to, once boot opened it. */
|
|
84
|
+
#defaultConnection?: EonConnection;
|
|
83
85
|
|
|
84
86
|
constructor(app: EonAppContext, connect: EonConnector = connectWsEon) {
|
|
85
87
|
this.#app = app;
|
|
86
88
|
this.#connect = connect;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
register(): void {
|
|
91
|
+
register(): void {
|
|
92
|
+
// Bindings belong here, not in boot(). Upstream registers in `register`
|
|
93
|
+
// precisely so another provider can count on them during its own boot;
|
|
94
|
+
// bound in boot, the container had a different surface depending on
|
|
95
|
+
// which provider ran first, and `eon.compiler` was simply unavailable to
|
|
96
|
+
// anything booting earlier.
|
|
97
|
+
//
|
|
98
|
+
// It is also pure — a compile is a call into the native compiler, no
|
|
99
|
+
// socket, no database — so there is nothing here an inspection must
|
|
100
|
+
// avoid.
|
|
101
|
+
const service: EonService = {
|
|
102
|
+
compile: (spec, dialect) => compileStatementNative(spec, dialect),
|
|
103
|
+
};
|
|
104
|
+
this.#app.container.singleton("eon.compiler", () => service);
|
|
105
|
+
|
|
106
|
+
// The connection tokens are registered here too, so the surface does not
|
|
107
|
+
// change between warmup and run. What they RESOLVE to still depends on
|
|
108
|
+
// boot having opened something — a lazy factory cannot await — so
|
|
109
|
+
// reading one too early says what to do instead of answering undefined.
|
|
110
|
+
this.#app.container.singleton("eon", () => this.#requireDefault("eon"));
|
|
111
|
+
this.#app.container.singleton("eon.connection", () =>
|
|
112
|
+
this.#requireDefault("eon.connection"),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// The NAMED tokens too. Their names come from the config, which is
|
|
116
|
+
// readable here — nothing about `eon:primary` needs a socket to exist.
|
|
117
|
+
// Bound in boot they appeared only on the run path, so the container had
|
|
118
|
+
// a different surface in an inspection, and a boot that failed late left
|
|
119
|
+
// factories behind pointing at connections it had just closed.
|
|
120
|
+
const config =
|
|
121
|
+
this.#app.config.get<EonConfig>("timeseries") ??
|
|
122
|
+
this.#app.config.get<EonConfig>("eon");
|
|
123
|
+
if (!config) return;
|
|
124
|
+
for (const name of Object.keys(
|
|
125
|
+
this.#resolveConnections(config).connections,
|
|
126
|
+
)) {
|
|
127
|
+
this.#app.container.singleton(`eon:${name}`, () =>
|
|
128
|
+
this.#requireNamed(name),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A named connection, or a message saying what is missing.
|
|
135
|
+
*
|
|
136
|
+
* Same reasoning as {@link #requireDefault}: resolving to `undefined` sent
|
|
137
|
+
* the caller into a TypeError several frames from the cause.
|
|
138
|
+
*/
|
|
139
|
+
#requireNamed(name: string): EonConnection {
|
|
140
|
+
const connection = this.#connections.get(name);
|
|
141
|
+
if (connection === undefined) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`EonProvider: connection '${name}' was resolved before it was opened. Connections open in boot(); check that config.timeseries.connections defines '${name}' and that EonProvider is listed before whatever resolved this.`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return connection;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The default connection, or a message naming what is missing.
|
|
151
|
+
*
|
|
152
|
+
* Resolving to `undefined` sent the caller into a `TypeError` several
|
|
153
|
+
* frames away from the cause — a provider that resolved eon during its own
|
|
154
|
+
* boot, or an application with no `config/timeseries.ts` at all.
|
|
155
|
+
*/
|
|
156
|
+
#requireDefault(token: string): EonConnection {
|
|
157
|
+
const connection = this.#defaultConnection;
|
|
158
|
+
if (connection === undefined) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`EonProvider: '${token}' was resolved before any connection was opened. Connections open in boot(); check that config.timeseries defines one and that EonProvider is listed before whatever resolved this.`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return connection;
|
|
164
|
+
}
|
|
90
165
|
|
|
91
166
|
/** True when the application was assembled to be inspected rather than run. */
|
|
92
167
|
#isInspecting(): boolean {
|
|
@@ -103,13 +178,7 @@ export class EonProvider {
|
|
|
103
178
|
"EonProvider: boot() has already opened connections; a second boot would overwrite and leak them. Call shutdown() first, or construct a new provider.",
|
|
104
179
|
);
|
|
105
180
|
}
|
|
106
|
-
//
|
|
107
|
-
const service: EonService = {
|
|
108
|
-
compile: (spec, dialect) => compileStatementNative(spec, dialect),
|
|
109
|
-
};
|
|
110
|
-
this.#app.container.singleton("eon.compiler", () => service);
|
|
111
|
-
|
|
112
|
-
// Everything past here opens a socket or creates a database, so an
|
|
181
|
+
// Everything here opens a socket or creates a database, so an
|
|
113
182
|
// inspection stops at the compiler. `warmUp()` runs boot, so a route
|
|
114
183
|
// listing reached TDengine — and could CREATE a database there — while
|
|
115
184
|
// `shutdown()` never fires on that path, leaving the sockets open.
|
|
@@ -172,24 +241,69 @@ export class EonProvider {
|
|
|
172
241
|
`EonProvider: default connection '${defaultName}' is not defined in config.timeseries.connections`,
|
|
173
242
|
);
|
|
174
243
|
}
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
244
|
+
// Everything past this point runs with the sockets ALREADY OPEN, and any
|
|
245
|
+
// of it can fail: importing the services module, registering the
|
|
246
|
+
// migration source. Without this the failure left the connections open,
|
|
247
|
+
// possibly the module singleton published, and `#booted` false — so the
|
|
248
|
+
// next attempt opened another set on top of the ones nobody could reach.
|
|
249
|
+
try {
|
|
250
|
+
// The tokens were all bound in `register()`; this is what they
|
|
251
|
+
// resolve to. Filling the map is what makes them answer.
|
|
252
|
+
this.#defaultConnection = defaultConn;
|
|
253
|
+
|
|
254
|
+
// Populate the `@c9up/eon/services/connection` singleton so apps can
|
|
255
|
+
// `import connection from '@c9up/eon/services/connection'` anywhere.
|
|
256
|
+
// Lazy import so a type-only discovery scan does not pull the module
|
|
257
|
+
// at construction time.
|
|
258
|
+
const { setConnection } = await import("./services/connection.js");
|
|
259
|
+
setConnection(defaultConn);
|
|
260
|
+
|
|
261
|
+
await this.#registerMigrationSource(defaultConn, config);
|
|
262
|
+
this.#booted = true;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
await this.#rollbackBoot(defaultConn);
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Undo a boot that opened its connections and then failed.
|
|
271
|
+
*
|
|
272
|
+
* Everything this releases was created by THIS attempt, so a retry starts
|
|
273
|
+
* from nothing rather than from a half-assembled state. The module
|
|
274
|
+
* singleton is released only while it is still the one we published — two
|
|
275
|
+
* applications can share a process, and the survivor's connection must
|
|
276
|
+
* stay.
|
|
277
|
+
*/
|
|
278
|
+
async #rollbackBoot(published: EonConnection): Promise<void> {
|
|
279
|
+
await this.#releaseMigrationSource();
|
|
280
|
+
// The connector keeps PROCESS-GLOBAL handles, so closing the individual
|
|
281
|
+
// connections is not enough to let Node exit — shutdown() says so and
|
|
282
|
+
// destroys them; a failed boot left them behind and the process hung.
|
|
283
|
+
const hadWebsocket = [...this.#connections.values()].some(
|
|
284
|
+
(conn) => conn.transport === "websocket",
|
|
285
|
+
);
|
|
286
|
+
try {
|
|
287
|
+
const { clearConnection } = await import("./services/connection.js");
|
|
288
|
+
clearConnection(published);
|
|
289
|
+
} catch {
|
|
290
|
+
// The module could not be loaded, so nothing was published from it.
|
|
291
|
+
}
|
|
292
|
+
this.#defaultConnection = undefined;
|
|
293
|
+
await Promise.allSettled(
|
|
294
|
+
[...this.#connections.values()].map((conn) => conn.close()),
|
|
295
|
+
);
|
|
296
|
+
this.#connections.clear();
|
|
297
|
+
if (hadWebsocket) {
|
|
298
|
+
try {
|
|
299
|
+
const { destroyEonConnector } = await import(
|
|
300
|
+
"./connection/websocket.js"
|
|
301
|
+
);
|
|
302
|
+
await destroyEonConnector();
|
|
303
|
+
} catch {
|
|
304
|
+
// A fake-only boot never pulled the connector in.
|
|
305
|
+
}
|
|
180
306
|
}
|
|
181
|
-
this.#app.container.singleton("eon", () => defaultConn);
|
|
182
|
-
this.#app.container.singleton("eon.connection", () => defaultConn);
|
|
183
|
-
|
|
184
|
-
// Populate the `@c9up/eon/services/connection` singleton so apps can
|
|
185
|
-
// `import connection from '@c9up/eon/services/connection'` anywhere. Lazy
|
|
186
|
-
// import so a type-only discovery scan does not pull the module at
|
|
187
|
-
// construction time.
|
|
188
|
-
const { setConnection } = await import("./services/connection.js");
|
|
189
|
-
setConnection(defaultConn);
|
|
190
|
-
|
|
191
|
-
await this.#registerMigrationSource(defaultConn, config);
|
|
192
|
-
this.#booted = true;
|
|
193
307
|
}
|
|
194
308
|
|
|
195
309
|
/**
|
|
@@ -227,11 +341,46 @@ export class EonProvider {
|
|
|
227
341
|
"./schema/EonMigrationRunner.js"
|
|
228
342
|
);
|
|
229
343
|
const migrationsDir = config.migrationsDir ?? "database/eon-migrations";
|
|
230
|
-
|
|
344
|
+
const source = {
|
|
231
345
|
name: "eon",
|
|
232
346
|
directory: migrationsDir,
|
|
233
347
|
runner: new EonMigrationRunner(conn, { migrationsDir }),
|
|
234
|
-
}
|
|
348
|
+
};
|
|
349
|
+
(registry.register as (source: unknown) => unknown)(source);
|
|
350
|
+
// Kept so shutdown can hand the name back. Registering refuses a
|
|
351
|
+
// duplicate, so a provider that stops without releasing its name leaves
|
|
352
|
+
// a second boot in the same process failing on "already registered" —
|
|
353
|
+
// and the CLI driving a runner that holds a closed connection.
|
|
354
|
+
this.#migrationSource = source;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** What this provider registered with the host's migration registry. */
|
|
358
|
+
#migrationSource?: object;
|
|
359
|
+
|
|
360
|
+
/** Give the migration name back, while it is still ours. */
|
|
361
|
+
async #releaseMigrationSource(): Promise<void> {
|
|
362
|
+
const source = this.#migrationSource;
|
|
363
|
+
if (source === undefined) return;
|
|
364
|
+
this.#migrationSource = undefined;
|
|
365
|
+
const resolve = this.#app.container.resolve;
|
|
366
|
+
if (typeof resolve !== "function") return;
|
|
367
|
+
try {
|
|
368
|
+
const registry = await resolve.call(this.#app.container, "migrations");
|
|
369
|
+
if (
|
|
370
|
+
typeof registry === "object" &&
|
|
371
|
+
registry !== null &&
|
|
372
|
+
typeof Reflect.get(registry, "unregister") === "function"
|
|
373
|
+
) {
|
|
374
|
+
(
|
|
375
|
+
registry as {
|
|
376
|
+
unregister: (name: string, source?: unknown) => unknown;
|
|
377
|
+
}
|
|
378
|
+
).unregister("eon", source);
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
// No registry, or an older host without `unregister`: nothing to give
|
|
382
|
+
// back, and a shutdown must not fail over it.
|
|
383
|
+
}
|
|
235
384
|
}
|
|
236
385
|
|
|
237
386
|
async shutdown(): Promise<void> {
|
|
@@ -239,9 +388,11 @@ export class EonProvider {
|
|
|
239
388
|
// stuck close doesn't block the rest, but failures are aggregated and
|
|
240
389
|
// rethrown so supervisors see a non-zero shutdown signal. The map + module
|
|
241
390
|
// singleton are cleared unconditionally (never hand out closed handles).
|
|
391
|
+
await this.#releaseMigrationSource();
|
|
242
392
|
const named = [...this.#connections.entries()];
|
|
243
393
|
const results = await Promise.allSettled(named.map(([, c]) => c.close()));
|
|
244
394
|
this.#connections.clear();
|
|
395
|
+
this.#defaultConnection = undefined;
|
|
245
396
|
this.#booted = false; // allow a fresh boot() after a clean shutdown
|
|
246
397
|
|
|
247
398
|
const { clearConnection } = await import("./services/connection.js");
|