@emmett-community/emmett-google-services-bootstrap 0.3.0 → 0.5.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/index.cjs CHANGED
@@ -1,954 +1,57 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
29
2
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- ApiE2ESpecification: () => import_emmett_expressjs_with_openapi2.ApiE2ESpecification,
34
- ApiSpecification: () => import_emmett_expressjs_with_openapi2.ApiSpecification,
35
- DependencyChecker: () => DependencyChecker,
36
- DependencyTimeoutError: () => DependencyTimeoutError,
37
- DependencyUnavailableError: () => DependencyUnavailableError,
38
- FirebaseInitializer: () => FirebaseInitializer,
39
- GracefulShutdown: () => GracefulShutdown,
40
- NoContent: () => import_emmett_expressjs_with_openapi2.NoContent,
41
- OK: () => import_emmett_expressjs_with_openapi2.OK,
42
- PubSubInitializer: () => PubSubInitializer,
43
- ServiceBootstrap: () => ServiceBootstrap,
44
- ShutdownTimeoutError: () => ShutdownTimeoutError,
45
- createEventStore: () => createEventStore,
46
- createFirebaseAuthSecurityHandlers: () => import_emmett_expressjs_with_openapi2.createFirebaseAuthSecurityHandlers,
47
- existingStream: () => import_emmett_expressjs_with_openapi2.existingStream,
48
- expectNewEvents: () => import_emmett_expressjs_with_openapi2.expectNewEvents,
49
- expectResponse: () => import_emmett_expressjs_with_openapi2.expectResponse,
50
- on: () => import_emmett_expressjs_with_openapi2.on,
51
- realtimeDBInlineProjection: () => import_emmett_google_realtime_db2.realtimeDBInlineProjection,
52
- registerSignalHandlers: () => registerSignalHandlers,
53
- startDependency: () => startDependency
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+ var _chunkUBZVIW2Mcjs = require('./chunk-UBZVIW2M.cjs');
14
+
15
+ // src/bootstrap/probes.ts
16
+ var _emmettexpressjswithopenapi = require('@emmett-community/emmett-expressjs-with-openapi');
17
+ var getHealth = _emmettexpressjswithopenapi.on.call(void 0, async () => _emmettexpressjswithopenapi.OK.call(void 0, { body: { status: "ok" } }));
18
+ var getReady = (ctx) => _emmettexpressjswithopenapi.on.call(void 0, async () => {
19
+ const summary = await ctx.checkReadiness();
20
+ const checks = summary.results.map((result) => ({
21
+ name: result.name,
22
+ ok: result.ok
23
+ }));
24
+ return summary.ok ? _emmettexpressjswithopenapi.OK.call(void 0, { body: { status: "ready", checks } }) : _emmettexpressjswithopenapi.HttpResponse.call(void 0, 503, { body: { status: "unavailable", checks } });
54
25
  });
55
- module.exports = __toCommonJS(index_exports);
56
-
57
- // src/bootstrap/ServiceBootstrap.ts
58
- var import_node_path = __toESM(require("path"), 1);
59
- var import_emmett_observability = require("@emmett-community/emmett-observability");
60
- var import_emmett_expressjs_with_openapi = require("@emmett-community/emmett-expressjs-with-openapi");
61
- var import_emmett_google_pubsub = require("@emmett-community/emmett-google-pubsub");
62
-
63
- // src/firebase/FirebaseInitializer.ts
64
- var import_firebase_admin = __toESM(require("firebase-admin"), 1);
65
- var normalizeContext = (data) => {
66
- if (data === void 0 || data === null) return {};
67
- if (typeof data === "object" && !Array.isArray(data)) {
68
- return { ...data };
69
- }
70
- return { data };
71
- };
72
- var normalizeErrorContext = (error) => {
73
- if (error === void 0 || error === null) return {};
74
- if (error instanceof Error) return { err: error };
75
- if (typeof error === "object" && !Array.isArray(error)) {
76
- return { ...error };
77
- }
78
- return { err: error };
79
- };
80
- var safeLog = {
81
- debug: (logger, msg, data) => {
82
- if (!logger) return;
83
- logger.debug(normalizeContext(data), msg);
84
- },
85
- info: (logger, msg, data) => {
86
- if (!logger) return;
87
- logger.info(normalizeContext(data), msg);
88
- },
89
- warn: (logger, msg, data) => {
90
- if (!logger) return;
91
- logger.warn(normalizeContext(data), msg);
92
- },
93
- error: (logger, msg, error) => {
94
- if (!logger) return;
95
- logger.error(normalizeErrorContext(error), msg);
96
- }
97
- };
98
- var FirebaseInitializer = class {
99
- options;
100
- constructor(options) {
101
- this.options = options;
102
- }
103
- /**
104
- * Returns true if emulator hosts are configured.
105
- */
106
- isEmulatorMode() {
107
- return Boolean(
108
- this.options.firestoreEmulatorHost || this.options.databaseEmulatorHost || process.env.FIRESTORE_EMULATOR_HOST || process.env.FIREBASE_DATABASE_EMULATOR_HOST
109
- );
110
- }
111
- /**
112
- * Initialize Firebase Admin and return service handles.
113
- */
114
- async initialize() {
115
- const {
116
- projectId,
117
- databaseURL,
118
- firestoreEmulatorHost,
119
- databaseEmulatorHost,
120
- logger
121
- } = this.options;
122
- if (firestoreEmulatorHost && !process.env.FIRESTORE_EMULATOR_HOST) {
123
- process.env.FIRESTORE_EMULATOR_HOST = firestoreEmulatorHost;
124
- }
125
- if (databaseEmulatorHost && !process.env.FIREBASE_DATABASE_EMULATOR_HOST) {
126
- process.env.FIREBASE_DATABASE_EMULATOR_HOST = databaseEmulatorHost;
127
- }
128
- const resolvedDatabaseURL = databaseURL ?? this.getDatabaseURL(projectId, databaseEmulatorHost);
129
- const firebaseAdmin = this.options._firebaseAdmin ?? import_firebase_admin.default;
130
- const app = this.getOrCreateApp(firebaseAdmin, projectId, resolvedDatabaseURL);
131
- safeLog.info(logger, "Firebase initialized", {
132
- projectId,
133
- databaseURL: resolvedDatabaseURL,
134
- emulatorMode: this.isEmulatorMode()
135
- });
136
- const firestore = app.firestore();
137
- const database = app.database();
138
- const auth = app.auth();
139
- if (firestoreEmulatorHost) {
140
- firestore.settings({
141
- host: firestoreEmulatorHost,
142
- ssl: false
143
- });
144
- safeLog.info(logger, "Firestore emulator configured", {
145
- host: firestoreEmulatorHost
146
- });
147
- }
148
- return { app, firestore, database, auth };
149
- }
150
- getOrCreateApp(firebaseAdmin, projectId, databaseURL) {
151
- const apps = (firebaseAdmin.apps ?? []).filter(
152
- (app) => app !== null
153
- );
154
- if (apps.length > 0 && firebaseAdmin.app) {
155
- safeLog.debug(this.options.logger, "Reusing existing Firebase app");
156
- return firebaseAdmin.app();
157
- }
158
- safeLog.debug(this.options.logger, "Creating Firebase app");
159
- return firebaseAdmin.initializeApp(
160
- {
161
- projectId,
162
- databaseURL
163
- }
164
- );
165
- }
166
- getDatabaseURL(projectId, emulatorHost) {
167
- if (emulatorHost) {
168
- return `http://${emulatorHost}?ns=${projectId}`;
169
- }
170
- return `https://${projectId}-default-rtdb.firebaseio.com`;
171
- }
172
- };
173
-
174
- // src/pubsub/PubSubInitializer.ts
175
- var import_pubsub = require("@google-cloud/pubsub");
176
- var normalizeContext2 = (data) => {
177
- if (data === void 0 || data === null) return {};
178
- if (typeof data === "object" && !Array.isArray(data)) {
179
- return { ...data };
180
- }
181
- return { data };
182
- };
183
- var normalizeErrorContext2 = (error) => {
184
- if (error === void 0 || error === null) return {};
185
- if (error instanceof Error) return { err: error };
186
- if (typeof error === "object" && !Array.isArray(error)) {
187
- return { ...error };
188
- }
189
- return { err: error };
190
- };
191
- var safeLog2 = {
192
- debug: (logger, msg, data) => {
193
- if (!logger) return;
194
- logger.debug(normalizeContext2(data), msg);
195
- },
196
- info: (logger, msg, data) => {
197
- if (!logger) return;
198
- logger.info(normalizeContext2(data), msg);
199
- },
200
- warn: (logger, msg, data) => {
201
- if (!logger) return;
202
- logger.warn(normalizeContext2(data), msg);
203
- },
204
- error: (logger, msg, error) => {
205
- if (!logger) return;
206
- logger.error(normalizeErrorContext2(error), msg);
207
- }
208
- };
209
- var PubSubInitializer = class {
210
- options;
211
- constructor(options) {
212
- this.options = options;
213
- }
214
- /**
215
- * Returns true if emulator host is configured.
216
- */
217
- isEmulatorMode() {
218
- return Boolean(
219
- this.options.emulatorHost || process.env.PUBSUB_EMULATOR_HOST
220
- );
221
- }
222
- /**
223
- * Initialize PubSub client or return injected instance.
224
- */
225
- async initialize() {
226
- const { projectId, emulatorHost, logger } = this.options;
227
- if (emulatorHost && !process.env.PUBSUB_EMULATOR_HOST) {
228
- process.env.PUBSUB_EMULATOR_HOST = emulatorHost;
229
- }
230
- if (this.options._pubsub) {
231
- safeLog2.info(logger, "Using provided PubSub client", {
232
- projectId,
233
- emulatorHost: emulatorHost ?? process.env.PUBSUB_EMULATOR_HOST
234
- });
235
- return this.options._pubsub;
236
- }
237
- const pubsub = new import_pubsub.PubSub({ projectId });
238
- safeLog2.info(logger, "PubSub initialized", {
239
- projectId,
240
- emulatorHost: emulatorHost ?? process.env.PUBSUB_EMULATOR_HOST,
241
- emulatorMode: this.isEmulatorMode()
242
- });
243
- return pubsub;
244
- }
245
- };
246
-
247
- // src/eventstore/EventStoreFactory.ts
248
- var import_emmett_google_firestore = require("@emmett-community/emmett-google-firestore");
249
- var import_emmett_google_realtime_db = require("@emmett-community/emmett-google-realtime-db");
250
- var createEventStore = (options) => {
251
- const { firestore, database, projections, logger, collections } = options;
252
- const observability = logger ? { logger } : void 0;
253
- const baseEventStore = (0, import_emmett_google_firestore.getFirestoreEventStore)(firestore, {
254
- observability,
255
- collections
256
- });
257
- if (projections && projections.length > 0) {
258
- return (0, import_emmett_google_realtime_db.wireRealtimeDBProjections)({
259
- eventStore: baseEventStore,
260
- database,
261
- projections,
262
- observability
263
- });
264
- }
265
- return baseEventStore;
266
- };
267
-
268
- // src/lifecycle/DependencyChecker.ts
269
- var DependencyTimeoutError = class extends Error {
270
- constructor(timeoutMs) {
271
- super(`Dependency check timed out after ${timeoutMs}ms`);
272
- this.name = "DependencyTimeoutError";
273
- }
274
- };
275
- var normalizeErrorContext3 = (error) => {
276
- if (error === void 0 || error === null) return {};
277
- if (error instanceof Error) return { err: error };
278
- if (typeof error === "object" && !Array.isArray(error)) {
279
- return { ...error };
280
- }
281
- return { err: error };
282
- };
283
- var withTimeout = async (promise, timeoutMs) => {
284
- if (!timeoutMs || timeoutMs <= 0) return promise;
285
- let timeoutId;
286
- const timeoutPromise = new Promise((_, reject) => {
287
- timeoutId = setTimeout(() => {
288
- reject(new DependencyTimeoutError(timeoutMs));
289
- }, timeoutMs);
290
- });
291
- try {
292
- return await Promise.race([promise, timeoutPromise]);
293
- } finally {
294
- if (timeoutId) {
295
- clearTimeout(timeoutId);
296
- }
297
- }
298
- };
299
- var startDependency = async (dependency, startFn, options) => {
300
- const { logger, successMessage, timeoutMs } = options ?? {};
301
- const startTime = Date.now();
302
- try {
303
- await withTimeout(startFn(), timeoutMs);
304
- logger?.info({ dependency, durationMs: Date.now() - startTime }, successMessage);
305
- return true;
306
- } catch (error) {
307
- logger?.warn(
308
- { dependency, durationMs: Date.now() - startTime, ...normalizeErrorContext3(error) },
309
- "Unable to reach dependency"
310
- );
311
- return false;
312
- }
313
- };
314
- var DependencyChecker = class {
315
- logger;
316
- defaultTimeoutMs;
317
- constructor(options) {
318
- this.logger = options?.logger;
319
- this.defaultTimeoutMs = options?.defaultTimeoutMs;
320
- }
321
- async check(dependency) {
322
- const start = Date.now();
323
- const timeoutMs = dependency.timeoutMs ?? this.defaultTimeoutMs;
324
- try {
325
- await withTimeout(dependency.check(), timeoutMs);
326
- if (dependency.successMessage) {
327
- this.logger?.info(
328
- { dependency: dependency.name, durationMs: Date.now() - start },
329
- dependency.successMessage
330
- );
331
- }
332
- return {
333
- name: dependency.name,
334
- ok: true,
335
- required: dependency.required ?? true,
336
- durationMs: Date.now() - start,
337
- timedOut: false
338
- };
339
- } catch (error) {
340
- const timedOut = error instanceof DependencyTimeoutError;
341
- this.logger?.warn(
342
- {
343
- dependency: dependency.name,
344
- durationMs: Date.now() - start,
345
- timedOut,
346
- ...normalizeErrorContext3(error)
347
- },
348
- "Dependency check failed"
349
- );
350
- return {
351
- name: dependency.name,
352
- ok: false,
353
- required: dependency.required ?? true,
354
- durationMs: Date.now() - start,
355
- timedOut,
356
- error
357
- };
358
- }
359
- }
360
- async checkAll(dependencies) {
361
- const results = [];
362
- for (const dependency of dependencies) {
363
- results.push(await this.check(dependency));
364
- }
365
- const ok = results.every((result) => result.ok || !result.required);
366
- return { ok, results };
367
- }
368
- };
369
-
370
- // src/lifecycle/GracefulShutdown.ts
371
- var ShutdownTimeoutError = class extends Error {
372
- constructor(timeoutMs) {
373
- super(`Shutdown task timed out after ${timeoutMs}ms`);
374
- this.name = "ShutdownTimeoutError";
375
- }
376
- };
377
- var normalizeContext3 = (data) => {
378
- if (data === void 0 || data === null) return {};
379
- if (typeof data === "object" && !Array.isArray(data)) {
380
- return { ...data };
381
- }
382
- return { data };
383
- };
384
- var normalizeErrorContext4 = (error) => {
385
- if (error === void 0 || error === null) return {};
386
- if (error instanceof Error) return { err: error };
387
- if (typeof error === "object" && !Array.isArray(error)) {
388
- return { ...error };
389
- }
390
- return { err: error };
391
- };
392
- var withTimeout2 = async (promise, timeoutMs) => {
393
- if (!timeoutMs || timeoutMs <= 0) return promise;
394
- let timeoutId;
395
- const timeoutPromise = new Promise((_, reject) => {
396
- timeoutId = setTimeout(() => {
397
- reject(new ShutdownTimeoutError(timeoutMs));
398
- }, timeoutMs);
399
- });
400
- try {
401
- return await Promise.race([promise, timeoutPromise]);
402
- } finally {
403
- if (timeoutId) {
404
- clearTimeout(timeoutId);
405
- }
406
- }
407
- };
408
- var GracefulShutdown = class {
409
- logger;
410
- timeoutMs;
411
- exitOnComplete;
412
- exitFn;
413
- tasks = [];
414
- shuttingDown = false;
415
- lastResult;
416
- constructor(options) {
417
- this.logger = options?.logger;
418
- this.timeoutMs = options?.timeoutMs;
419
- this.exitOnComplete = options?.exitOnComplete ?? false;
420
- this.exitFn = options?.exitFn ?? process.exit;
421
- }
422
- /**
423
- * Register a shutdown task.
424
- */
425
- register(task) {
426
- this.tasks.push(task);
427
- }
428
- /**
429
- * Execute all shutdown tasks and return the aggregated result.
430
- */
431
- async shutdown(reason, exitCode = 1) {
432
- if (this.shuttingDown && this.lastResult) {
433
- return this.lastResult;
434
- }
435
- this.shuttingDown = true;
436
- this.logger?.info({ reason }, "Shutting down gracefully");
437
- const results = [];
438
- for (const task of this.tasks) {
439
- const start = Date.now();
440
- const timeoutMs = task.timeoutMs ?? this.timeoutMs;
441
- try {
442
- await withTimeout2(task.close(), timeoutMs);
443
- results.push({
444
- name: task.name,
445
- ok: true,
446
- timedOut: false,
447
- durationMs: Date.now() - start
448
- });
449
- this.logger?.info(
450
- { task: task.name, durationMs: Date.now() - start },
451
- "Shutdown task completed"
452
- );
453
- } catch (error) {
454
- const timedOut = error instanceof ShutdownTimeoutError;
455
- results.push({
456
- name: task.name,
457
- ok: false,
458
- timedOut,
459
- durationMs: Date.now() - start,
460
- error
461
- });
462
- this.logger?.warn(
463
- {
464
- task: task.name,
465
- timedOut,
466
- durationMs: Date.now() - start,
467
- ...normalizeErrorContext4(error)
468
- },
469
- "Shutdown task failed"
470
- );
471
- }
472
- }
473
- const failed = results.some((result2) => !result2.ok || result2.timedOut);
474
- const finalExitCode = failed ? exitCode : 0;
475
- if (failed) {
476
- const failedTasks = results.filter((result2) => !result2.ok || result2.timedOut).map((result2) => result2.name);
477
- this.logger?.error(
478
- normalizeContext3({ failedTasks }),
479
- "Shutdown completed with failures"
480
- );
481
- } else {
482
- this.logger?.info({}, "Shutdown completed");
483
- }
484
- const result = { reason, exitCode: finalExitCode, tasks: results };
485
- this.lastResult = result;
486
- if (this.exitOnComplete) {
487
- this.exitFn(finalExitCode);
488
- }
489
- return result;
490
- }
491
- };
492
-
493
- // src/lifecycle/SignalHandlers.ts
494
- var registerSignalHandlers = (options) => {
495
- const signals = options.signals ?? ["SIGINT", "SIGTERM"];
496
- const handlers = /* @__PURE__ */ new Map();
497
- for (const signal of signals) {
498
- const handler = () => {
499
- options.logger?.info({ signal }, "Shutdown signal received");
500
- void options.shutdown(`Signal ${signal} received`, options.exitCode ?? 0);
501
- };
502
- handlers.set(signal, handler);
503
- process.on(signal, handler);
504
- }
505
- return () => {
506
- for (const [signal, handler] of handlers.entries()) {
507
- process.off(signal, handler);
508
- }
509
- };
510
- };
511
-
512
- // src/bootstrap/ServiceBootstrap.ts
513
- var DependencyUnavailableError = class extends Error {
514
- summary;
515
- constructor(summary) {
516
- const failed = summary.results.filter((result) => result.required && !result.ok).map((result) => result.name).join(", ");
517
- super(`Dependency check failed: ${failed}`);
518
- this.name = "DependencyUnavailableError";
519
- this.summary = summary;
520
- }
521
- };
522
- var normalizeContext4 = (data) => {
523
- if (data === void 0 || data === null) return {};
524
- if (typeof data === "object" && !Array.isArray(data)) {
525
- return { ...data };
526
- }
527
- return { data };
528
- };
529
- var normalizeErrorContext5 = (error) => {
530
- if (error === void 0 || error === null) return {};
531
- if (error instanceof Error) return { err: error };
532
- if (typeof error === "object" && !Array.isArray(error)) {
533
- return { ...error };
534
- }
535
- return { err: error };
536
- };
537
- var createPubSubLogger = (logger) => {
538
- if (!logger) return void 0;
539
- const canonicalLogger = logger;
540
- return {
541
- debug: (msg, data) => canonicalLogger.debug(normalizeContext4(data), msg),
542
- info: (msg, data) => canonicalLogger.info(normalizeContext4(data), msg),
543
- warn: (msg, data) => canonicalLogger.warn(normalizeContext4(data), msg),
544
- error: (msg, error) => canonicalLogger.error(normalizeErrorContext5(error), msg)
545
- };
546
- };
547
- var resolvePath = (inputPath) => {
548
- return import_node_path.default.isAbsolute(inputPath) ? inputPath : import_node_path.default.resolve(process.cwd(), inputPath);
549
- };
550
- var ServiceBootstrap = class {
551
- config;
552
- resolvedConfig;
553
- logger;
554
- context;
555
- dependencyChecker;
556
- shutdownManager;
557
- constructor(config) {
558
- this.config = config;
559
- }
560
- /**
561
- * Resolve configuration with defaults and environment variables.
562
- */
563
- getConfig() {
564
- return this.resolveConfig();
565
- }
566
- /**
567
- * Return the initialized context if available.
568
- */
569
- getContext() {
570
- return this.context;
571
- }
572
- /**
573
- * Register process signal handlers that trigger shutdown.
574
- */
575
- registerSignalHandlers(options) {
576
- return registerSignalHandlers({
577
- shutdown: async (reason, exitCode) => {
578
- await this.shutdown(reason, exitCode);
579
- },
580
- signals: options?.signals,
581
- logger: this.getLogger(),
582
- exitCode: options?.exitCode
583
- });
584
- }
585
- /**
586
- * Create an Express app using emmett-expressjs-with-openapi defaults.
587
- */
588
- async createApp(options) {
589
- const logger = this.getLogger();
590
- const openApiValidator = (0, import_emmett_expressjs_with_openapi.createOpenApiValidatorOptions)(
591
- resolvePath(options.openApiPath),
592
- {
593
- ...options.openApiOptions,
594
- operationHandlers: resolvePath(options.handlersPath),
595
- initializeHandlers: options.initializeHandlers
596
- }
597
- );
598
- return (0, import_emmett_expressjs_with_openapi.getApplication)({
599
- ...options.appOptions ?? {},
600
- openApiValidator,
601
- observability: logger ? { logger } : void 0
602
- });
603
- }
604
- /**
605
- * Initialize, run dependency checks, create the app, and start the HTTP server.
606
- */
607
- async startApi(options) {
608
- const {
609
- port,
610
- registerSignalHandlers: registerSignalHandlers2,
611
- beforeStart,
612
- initializeHandlers,
613
- ...createAppOptions
614
- } = options;
615
- const logger = this.getLogger();
616
- try {
617
- const ctx = await this.initialize();
618
- if (beforeStart) {
619
- await beforeStart(ctx);
620
- }
621
- if (registerSignalHandlers2 !== false) {
622
- this.registerSignalHandlers();
623
- }
624
- await this.start();
625
- const app = await this.createApp({
626
- ...createAppOptions,
627
- initializeHandlers: (handlers) => initializeHandlers(handlers, ctx)
628
- });
629
- const server = (0, import_emmett_expressjs_with_openapi.startAPI)(app, { port, logger });
630
- this.getShutdownManager().register({
631
- name: "httpServer",
632
- close: () => new Promise((resolve, reject) => {
633
- server.close((error) => {
634
- if (error) {
635
- reject(error);
636
- return;
637
- }
638
- resolve();
639
- });
640
- })
641
- });
642
- logger?.info({ port: port ?? 3e3 }, "API server started");
643
- return { app, server, context: ctx };
644
- } catch (error) {
645
- logger?.error({ err: error }, "Startup failed");
646
- await this.shutdown("Startup failed");
647
- throw error;
648
- }
649
- }
650
- /**
651
- * Initialize Firebase, PubSub, and event store wiring.
652
- * Does not execute dependency checks.
653
- */
654
- async initialize() {
655
- if (this.context) {
656
- return this.context;
657
- }
658
- const resolvedConfig = this.resolveConfig();
659
- const logger = this.getLogger();
660
- const firebaseInitializer = new FirebaseInitializer({
661
- projectId: resolvedConfig.firebase.projectId,
662
- databaseURL: resolvedConfig.firebase.databaseURL,
663
- firestoreEmulatorHost: resolvedConfig.firebase.firestoreEmulatorHost,
664
- databaseEmulatorHost: resolvedConfig.firebase.databaseEmulatorHost,
665
- logger,
666
- _firebaseAdmin: resolvedConfig._testOverrides?.firebaseAdmin
667
- });
668
- const firebase = await firebaseInitializer.initialize();
669
- const firestore = resolvedConfig._testOverrides?.firestore ?? firebase.firestore;
670
- const database = resolvedConfig._testOverrides?.database ?? firebase.database;
671
- const auth = resolvedConfig._testOverrides?.auth ?? firebase.auth;
672
- const pubsubInitializer = new PubSubInitializer({
673
- projectId: resolvedConfig.pubsub.projectId,
674
- emulatorHost: resolvedConfig.pubsub.emulatorHost,
675
- logger,
676
- _pubsub: resolvedConfig._testOverrides?.pubsub
677
- });
678
- const pubsub = await pubsubInitializer.initialize();
679
- const eventStore = resolvedConfig._testOverrides?.eventStore ?? createEventStore({
680
- firestore,
681
- database,
682
- projections: resolvedConfig.projections,
683
- collections: resolvedConfig.eventStore.collections,
684
- logger
685
- });
686
- const messageBus = resolvedConfig._testOverrides?.messageBus ?? (0, import_emmett_google_pubsub.getPubSubMessageBus)({
687
- pubsub,
688
- useEmulator: pubsubInitializer.isEmulatorMode(),
689
- topicPrefix: resolvedConfig.pubsub.topicPrefix,
690
- subscriptionOptions: resolvedConfig.pubsub.subscriptionOptions,
691
- autoCreateResources: resolvedConfig.pubsub.autoCreateResources,
692
- cleanupOnClose: resolvedConfig.pubsub.cleanupOnClose,
693
- closePubSubClient: resolvedConfig.pubsub.closePubSubClient,
694
- observability: { logger: createPubSubLogger(logger) }
695
- });
696
- this.shutdownManager = new GracefulShutdown({
697
- ...resolvedConfig.shutdown,
698
- logger
699
- });
700
- this.registerDefaultShutdownTasks({
701
- messageBus,
702
- firestore,
703
- firebaseApp: firebase.app
704
- });
705
- this.dependencyChecker = new DependencyChecker({ logger });
706
- this.context = {
707
- serviceName: resolvedConfig.serviceName,
708
- firebase: {
709
- app: firebase.app,
710
- firestore,
711
- database,
712
- auth
713
- },
714
- firestore,
715
- database,
716
- auth,
717
- pubsub,
718
- eventStore,
719
- messageBus,
720
- logger,
721
- getCurrentTime: resolvedConfig.getCurrentTime,
722
- config: resolvedConfig
723
- };
724
- return this.context;
725
- }
726
- /**
727
- * Initialize and run dependency checks before returning context.
728
- */
729
- async start() {
730
- const ctx = await this.initialize();
731
- const resolvedConfig = this.resolveConfig();
732
- const checks = [];
733
- if (resolvedConfig.includeDefaultDependencyChecks) {
734
- checks.push(...this.buildDefaultDependencyChecks(ctx));
735
- }
736
- if (resolvedConfig.dependencies.length > 0) {
737
- checks.push(...resolvedConfig.dependencies);
738
- }
739
- if (checks.length > 0) {
740
- const summary = await this.getDependencyChecker().checkAll(checks);
741
- if (!summary.ok) {
742
- const error = new DependencyUnavailableError(summary);
743
- this.logger?.error(
744
- {
745
- err: error,
746
- results: summary.results
747
- },
748
- "Dependency checks failed"
749
- );
750
- if (resolvedConfig.shutdownOnDependencyFailure) {
751
- await this.shutdown("Dependency check failed");
752
- }
753
- throw error;
754
- }
755
- }
756
- this.logger?.info(
757
- { serviceName: resolvedConfig.serviceName },
758
- "Service is running"
759
- );
760
- return ctx;
761
- }
762
- /**
763
- * Execute graceful shutdown tasks and return the shutdown result.
764
- */
765
- async shutdown(reason, exitCode = 1) {
766
- return this.getShutdownManager().shutdown(reason, exitCode);
767
- }
768
- getLogger() {
769
- if (this.logger !== void 0) {
770
- return this.logger;
771
- }
772
- const resolvedConfig = this.resolveConfig();
773
- const observability = resolvedConfig.observability;
774
- if (observability.logger) {
775
- this.logger = observability.logger;
776
- return this.logger;
777
- }
778
- if (observability.createLogger === false) {
779
- this.logger = void 0;
780
- return this.logger;
781
- }
782
- this.logger = (0, import_emmett_observability.createLogger)({
783
- serviceName: resolvedConfig.serviceName,
784
- environment: observability.environment,
785
- logLevel: observability.logLevel
786
- });
787
- return this.logger;
788
- }
789
- resolveConfig() {
790
- if (this.resolvedConfig) {
791
- return this.resolvedConfig;
792
- }
793
- const missing = [];
794
- const serviceName = this.config.serviceName?.trim();
795
- if (!serviceName) {
796
- missing.push("serviceName");
797
- }
798
- const firestoreEmulatorHost = this.config.firebase?.firestoreEmulatorHost ?? process.env.FIRESTORE_EMULATOR_HOST;
799
- const databaseEmulatorHost = this.config.firebase?.databaseEmulatorHost ?? process.env.FIREBASE_DATABASE_EMULATOR_HOST;
800
- const pubsubEmulatorHost = this.config.pubsub?.emulatorHost ?? process.env.PUBSUB_EMULATOR_HOST;
801
- const hasEmulator = Boolean(firestoreEmulatorHost) || Boolean(databaseEmulatorHost) || Boolean(pubsubEmulatorHost);
802
- const firebaseProjectId = this.config.firebase?.projectId ?? process.env.FIRESTORE_PROJECT_ID ?? process.env.GCLOUD_PROJECT ?? process.env.GOOGLE_CLOUD_PROJECT ?? (hasEmulator ? "demo-project" : void 0);
803
- if (!firebaseProjectId) {
804
- missing.push("firebase.projectId (or FIRESTORE_PROJECT_ID)");
805
- }
806
- const pubsubProjectId = this.config.pubsub?.projectId ?? process.env.PUBSUB_PROJECT_ID ?? firebaseProjectId ?? (hasEmulator ? "demo-project" : void 0);
807
- if (!pubsubProjectId) {
808
- missing.push("pubsub.projectId (or PUBSUB_PROJECT_ID)");
809
- }
810
- if (missing.length > 0) {
811
- throw new Error(`Missing required configuration: ${missing.join(", ")}`);
812
- }
813
- const resolvedServiceName = serviceName;
814
- const resolvedFirebaseProjectId = firebaseProjectId;
815
- const resolvedPubsubProjectId = pubsubProjectId;
816
- const databaseURL = this.config.firebase?.databaseURL ?? this.getDatabaseURL(resolvedFirebaseProjectId, databaseEmulatorHost);
817
- const observability = {
818
- ...this.config.observability,
819
- environment: this.config.observability?.environment ?? process.env.NODE_ENV,
820
- logLevel: this.config.observability?.logLevel ?? process.env.LOG_LEVEL,
821
- createLogger: this.config.observability?.createLogger ?? true
822
- };
823
- const getCurrentTime = this.config.getCurrentTime ?? (() => /* @__PURE__ */ new Date());
824
- this.resolvedConfig = {
825
- serviceName: resolvedServiceName,
826
- firebase: {
827
- projectId: resolvedFirebaseProjectId,
828
- databaseURL,
829
- firestoreEmulatorHost,
830
- databaseEmulatorHost
831
- },
832
- pubsub: {
833
- projectId: resolvedPubsubProjectId,
834
- emulatorHost: pubsubEmulatorHost,
835
- topicPrefix: this.config.pubsub?.topicPrefix ?? resolvedServiceName,
836
- subscriptionOptions: this.config.pubsub?.subscriptionOptions,
837
- autoCreateResources: this.config.pubsub?.autoCreateResources ?? true,
838
- cleanupOnClose: this.config.pubsub?.cleanupOnClose ?? false,
839
- closePubSubClient: this.config.pubsub?.closePubSubClient ?? true
840
- },
841
- observability,
842
- projections: this.config.projections ?? [],
843
- getCurrentTime,
844
- dependencies: this.config.dependencies ?? [],
845
- includeDefaultDependencyChecks: this.config.includeDefaultDependencyChecks ?? true,
846
- autoStartMessageBus: this.config.autoStartMessageBus ?? true,
847
- shutdownOnDependencyFailure: this.config.shutdownOnDependencyFailure ?? true,
848
- shutdown: {
849
- timeoutMs: this.config.shutdown?.timeoutMs ?? 3e4,
850
- exitOnComplete: this.config.shutdown?.exitOnComplete ?? false,
851
- exitFn: this.config.shutdown?.exitFn
852
- },
853
- eventStore: {
854
- collections: this.config.eventStore?.collections
855
- },
856
- _testOverrides: this.config._testOverrides
857
- };
858
- return this.resolvedConfig;
859
- }
860
- getDatabaseURL(projectId, emulatorHost) {
861
- if (emulatorHost) {
862
- return `http://${emulatorHost}?ns=${projectId}`;
863
- }
864
- return `https://${projectId}-default-rtdb.firebaseio.com`;
865
- }
866
- getDependencyChecker() {
867
- if (!this.dependencyChecker) {
868
- this.dependencyChecker = new DependencyChecker({ logger: this.getLogger() });
869
- }
870
- return this.dependencyChecker;
871
- }
872
- getShutdownManager() {
873
- if (!this.shutdownManager) {
874
- this.shutdownManager = new GracefulShutdown({
875
- ...this.resolveConfig().shutdown,
876
- logger: this.getLogger()
877
- });
878
- }
879
- return this.shutdownManager;
880
- }
881
- registerDefaultShutdownTasks(options) {
882
- const shutdownManager = this.getShutdownManager();
883
- if (options.messageBus?.close) {
884
- shutdownManager.register({
885
- name: "messageBus",
886
- close: () => options.messageBus.close()
887
- });
888
- }
889
- if (typeof options.firestore.terminate === "function") {
890
- shutdownManager.register({
891
- name: "firestore",
892
- close: () => options.firestore.terminate()
893
- });
894
- }
895
- if (typeof options.firebaseApp.delete === "function") {
896
- shutdownManager.register({
897
- name: "firebaseApp",
898
- close: () => options.firebaseApp.delete()
899
- });
900
- }
901
- }
902
- buildDefaultDependencyChecks(ctx) {
903
- const checks = [];
904
- if (this.resolveConfig().autoStartMessageBus) {
905
- checks.push({
906
- name: "PubSub",
907
- check: () => ctx.messageBus.start(),
908
- successMessage: "Message bus started"
909
- });
910
- }
911
- checks.push({
912
- name: "Firestore",
913
- check: async () => {
914
- await ctx.firestore.doc("_healthcheck/connection").get();
915
- }
916
- });
917
- checks.push({
918
- name: "Realtime Database",
919
- check: async () => {
920
- await ctx.database.ref("_healthcheck/connection").once("value");
921
- }
922
- });
923
- return checks;
924
- }
925
- };
926
26
 
927
27
  // src/index.ts
928
- var import_emmett_google_realtime_db2 = require("@emmett-community/emmett-google-realtime-db");
929
- var import_emmett_expressjs_with_openapi2 = require("@emmett-community/emmett-expressjs-with-openapi");
930
- // Annotate the CommonJS export names for ESM import in node:
931
- 0 && (module.exports = {
932
- ApiE2ESpecification,
933
- ApiSpecification,
934
- DependencyChecker,
935
- DependencyTimeoutError,
936
- DependencyUnavailableError,
937
- FirebaseInitializer,
938
- GracefulShutdown,
939
- NoContent,
940
- OK,
941
- PubSubInitializer,
942
- ServiceBootstrap,
943
- ShutdownTimeoutError,
944
- createEventStore,
945
- createFirebaseAuthSecurityHandlers,
946
- existingStream,
947
- expectNewEvents,
948
- expectResponse,
949
- on,
950
- realtimeDBInlineProjection,
951
- registerSignalHandlers,
952
- startDependency
953
- });
28
+ var _emmettgooglerealtimedb = require('@emmett-community/emmett-google-realtime-db');
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
53
+
54
+
55
+
56
+ exports.DependencyChecker = _chunkUBZVIW2Mcjs.DependencyChecker; exports.DependencyTimeoutError = _chunkUBZVIW2Mcjs.DependencyTimeoutError; exports.DependencyUnavailableError = _chunkUBZVIW2Mcjs.DependencyUnavailableError; exports.FirebaseInitializer = _chunkUBZVIW2Mcjs.FirebaseInitializer; exports.GracefulShutdown = _chunkUBZVIW2Mcjs.GracefulShutdown; exports.NoContent = _emmettexpressjswithopenapi.NoContent; exports.OK = _emmettexpressjswithopenapi.OK; exports.PubSubInitializer = _chunkUBZVIW2Mcjs.PubSubInitializer; exports.ServiceBootstrap = _chunkUBZVIW2Mcjs.ServiceBootstrap; exports.ShutdownTimeoutError = _chunkUBZVIW2Mcjs.ShutdownTimeoutError; exports.createEventStore = _chunkUBZVIW2Mcjs.createEventStore; exports.createFirebaseAuthSecurityHandlers = _emmettexpressjswithopenapi.createFirebaseAuthSecurityHandlers; exports.getHealth = getHealth; exports.getReady = getReady; exports.lazyApplication = _emmettexpressjswithopenapi.lazyApplication; exports.on = _emmettexpressjswithopenapi.on; exports.realtimeDBInlineProjection = _emmettgooglerealtimedb.realtimeDBInlineProjection; exports.registerSignalHandlers = _chunkUBZVIW2Mcjs.registerSignalHandlers; exports.startDependency = _chunkUBZVIW2Mcjs.startDependency;
954
57
  //# sourceMappingURL=index.cjs.map