@primebrick/sdk 0.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -0
  3. package/dist/config/config-loader.d.ts +50 -0
  4. package/dist/config/config-loader.d.ts.map +1 -0
  5. package/dist/config/config-loader.js +84 -0
  6. package/dist/config/config-loader.js.map +1 -0
  7. package/dist/config/iconfig-entity.d.ts +20 -0
  8. package/dist/config/iconfig-entity.d.ts.map +1 -0
  9. package/dist/config/iconfig-entity.js +2 -0
  10. package/dist/config/iconfig-entity.js.map +1 -0
  11. package/dist/env/env-validator.d.ts +25 -0
  12. package/dist/env/env-validator.d.ts.map +1 -0
  13. package/dist/env/env-validator.js +30 -0
  14. package/dist/env/env-validator.js.map +1 -0
  15. package/dist/http/health-check.d.ts +22 -0
  16. package/dist/http/health-check.d.ts.map +1 -0
  17. package/dist/http/health-check.js +43 -0
  18. package/dist/http/health-check.js.map +1 -0
  19. package/dist/http/http-server.d.ts +16 -0
  20. package/dist/http/http-server.d.ts.map +1 -0
  21. package/dist/http/http-server.js +38 -0
  22. package/dist/http/http-server.js.map +1 -0
  23. package/dist/index.d.ts +33 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +32 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lifecycle/graceful-shutdown.d.ts +24 -0
  28. package/dist/lifecycle/graceful-shutdown.d.ts.map +1 -0
  29. package/dist/lifecycle/graceful-shutdown.js +55 -0
  30. package/dist/lifecycle/graceful-shutdown.js.map +1 -0
  31. package/dist/migrations/apply-patches.d.ts +25 -0
  32. package/dist/migrations/apply-patches.d.ts.map +1 -0
  33. package/dist/migrations/apply-patches.js +75 -0
  34. package/dist/migrations/apply-patches.js.map +1 -0
  35. package/dist/migrations/patch-naming.d.ts +5 -0
  36. package/dist/migrations/patch-naming.d.ts.map +1 -0
  37. package/dist/migrations/patch-naming.js +21 -0
  38. package/dist/migrations/patch-naming.js.map +1 -0
  39. package/dist/migrations/patch-registry.d.ts +5 -0
  40. package/dist/migrations/patch-registry.d.ts.map +1 -0
  41. package/dist/migrations/patch-registry.js +18 -0
  42. package/dist/migrations/patch-registry.js.map +1 -0
  43. package/dist/nats/nats-client.d.ts +17 -0
  44. package/dist/nats/nats-client.d.ts.map +1 -0
  45. package/dist/nats/nats-client.js +37 -0
  46. package/dist/nats/nats-client.js.map +1 -0
  47. package/dist/ports/config-repository-port.d.ts +18 -0
  48. package/dist/ports/config-repository-port.d.ts.map +1 -0
  49. package/dist/ports/config-repository-port.js +2 -0
  50. package/dist/ports/config-repository-port.js.map +1 -0
  51. package/dist/ports/database-port.d.ts +20 -0
  52. package/dist/ports/database-port.d.ts.map +1 -0
  53. package/dist/ports/database-port.js +2 -0
  54. package/dist/ports/database-port.js.map +1 -0
  55. package/dist/ports/health-check-port.d.ts +12 -0
  56. package/dist/ports/health-check-port.d.ts.map +1 -0
  57. package/dist/ports/health-check-port.js +2 -0
  58. package/dist/ports/health-check-port.js.map +1 -0
  59. package/dist/ports/service-registry-port.d.ts +17 -0
  60. package/dist/ports/service-registry-port.d.ts.map +1 -0
  61. package/dist/ports/service-registry-port.js +2 -0
  62. package/dist/ports/service-registry-port.js.map +1 -0
  63. package/dist/service/service-registrar.d.ts +27 -0
  64. package/dist/service/service-registrar.d.ts.map +1 -0
  65. package/dist/service/service-registrar.js +61 -0
  66. package/dist/service/service-registrar.js.map +1 -0
  67. package/dist/service/service-registry.d.ts +17 -0
  68. package/dist/service/service-registry.d.ts.map +1 -0
  69. package/dist/service/service-registry.js +2 -0
  70. package/dist/service/service-registry.js.map +1 -0
  71. package/package.json +47 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Sogos
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,21 @@
1
+ # @primebrick/sdk
2
+
3
+ Shared microservice infrastructure for Primebrick v3 — config loading, migration runner, service registration, graceful shutdown, NATS client, health checks, env validation. DB-agnostic via port interfaces.
4
+
5
+ ## What the SDK provides
6
+
7
+ - **Port interfaces** — `ConfigRepositoryPort`, `DatabasePort`, `ServiceRegistryPort`, `HealthCheckPort`. Abstract contracts the consumer implements using their DAL.
8
+ - **ConfigLoader** — load config from a DB config table at startup, cache in memory, `get(key)` on hot path, `invalidate()` for refresh.
9
+ - **IConfigEntity** — self-contained interface for dictionary-style config rows.
10
+ - **Migration runner** — SHA256-based patch tracking, idempotent re-runs.
11
+ - **ServiceRegistrar** — register microservice in `service_registry`, maintain heartbeat.
12
+ - **IServiceRegistry** — self-contained interface for `service_registry` rows.
13
+ - **GracefulShutdown** — re-entrancy guard, `Promise.allSettled` for parallel resource cleanup, signal handlers.
14
+ - **NatsClient** — singleton NATS connection management.
15
+ - **HttpServer** — minimal HTTP server with health endpoint.
16
+ - **HealthCheck** — DB health check utility.
17
+ - **EnvValidator** — centralized env var validation.
18
+
19
+ ## License
20
+
21
+ MIT — Copyright (c) 2026 Michael Sogos
@@ -0,0 +1,50 @@
1
+ import type { ConfigRepositoryPort } from "../ports/config-repository-port.js";
2
+ /**
3
+ * Dictionary-style config loader backed by a config table.
4
+ * Mirrors BE's loadAuthConfig / getAuthConfig / invalidateAuthConfig pattern
5
+ * (config.ts:150-180), generalized so every microservice can reuse it.
6
+ *
7
+ * DB-agnostic: depends on ConfigRepositoryPort, NOT on any specific DAL.
8
+ * The consumer provides an adapter that implements ConfigRepositoryPort
9
+ * using their DAL (e.g. @primebrick/dal-pg, or raw SQL).
10
+ *
11
+ * Load once at startup → cache in memory → get(key) on hot path (zero DB hits).
12
+ * Call invalidate() to force a reload on next load().
13
+ */
14
+ export declare class ConfigLoader {
15
+ private readonly repo;
16
+ private cache;
17
+ constructor(repo: ConfigRepositoryPort);
18
+ /**
19
+ * Load all config rows from DB into in-memory cache.
20
+ * Call once at startup. Throws if DB is unreachable.
21
+ */
22
+ load(): Promise<Record<string, string | null>>;
23
+ /**
24
+ * Get a config value from cache. Returns null if key is missing or value is null.
25
+ * Throws if load() has not been called.
26
+ */
27
+ get(key: string): string | null;
28
+ /**
29
+ * Get a config value, throwing if it's missing or empty.
30
+ */
31
+ require(key: string): string;
32
+ /**
33
+ * Get a typed config value via a converter function.
34
+ * Returns null if the key is missing.
35
+ */
36
+ getTyped<T>(key: string, converter: (v: string) => T): T | null;
37
+ /**
38
+ * Get a typed config value, throwing if it's missing.
39
+ */
40
+ requireTyped<T>(key: string, converter: (v: string) => T): T;
41
+ /**
42
+ * Get all config as a plain object.
43
+ */
44
+ getAll(): Record<string, string | null>;
45
+ /**
46
+ * Invalidate the cache so the next load() re-reads from DB.
47
+ */
48
+ invalidate(): void;
49
+ }
50
+ //# sourceMappingURL=config-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE/E;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;IAGX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,KAAK,CAA2C;gBAE3B,IAAI,EAAE,oBAAoB;IAEvD;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IASpD;;;OAGG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAO/B;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAQ5B;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI;IAM/D;;OAEG;IACH,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC;IAK5D;;OAEG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAOvC;;OAEG;IACH,UAAU,IAAI,IAAI;CAGnB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Dictionary-style config loader backed by a config table.
3
+ * Mirrors BE's loadAuthConfig / getAuthConfig / invalidateAuthConfig pattern
4
+ * (config.ts:150-180), generalized so every microservice can reuse it.
5
+ *
6
+ * DB-agnostic: depends on ConfigRepositoryPort, NOT on any specific DAL.
7
+ * The consumer provides an adapter that implements ConfigRepositoryPort
8
+ * using their DAL (e.g. @primebrick/dal-pg, or raw SQL).
9
+ *
10
+ * Load once at startup → cache in memory → get(key) on hot path (zero DB hits).
11
+ * Call invalidate() to force a reload on next load().
12
+ */
13
+ export class ConfigLoader {
14
+ repo;
15
+ cache = null;
16
+ constructor(repo) {
17
+ this.repo = repo;
18
+ }
19
+ /**
20
+ * Load all config rows from DB into in-memory cache.
21
+ * Call once at startup. Throws if DB is unreachable.
22
+ */
23
+ async load() {
24
+ const rows = await this.repo.findAll();
25
+ this.cache = new Map();
26
+ for (const row of rows) {
27
+ this.cache.set(row.key, row.value ?? null);
28
+ }
29
+ return Object.fromEntries(this.cache);
30
+ }
31
+ /**
32
+ * Get a config value from cache. Returns null if key is missing or value is null.
33
+ * Throws if load() has not been called.
34
+ */
35
+ get(key) {
36
+ if (this.cache === null) {
37
+ throw new Error("ConfigLoader.load() must be called before get()");
38
+ }
39
+ return this.cache.get(key) ?? null;
40
+ }
41
+ /**
42
+ * Get a config value, throwing if it's missing or empty.
43
+ */
44
+ require(key) {
45
+ const value = this.get(key);
46
+ if (value === null || value === "") {
47
+ throw new Error(`Missing required config key: ${key}`);
48
+ }
49
+ return value;
50
+ }
51
+ /**
52
+ * Get a typed config value via a converter function.
53
+ * Returns null if the key is missing.
54
+ */
55
+ getTyped(key, converter) {
56
+ const value = this.get(key);
57
+ if (value === null)
58
+ return null;
59
+ return converter(value);
60
+ }
61
+ /**
62
+ * Get a typed config value, throwing if it's missing.
63
+ */
64
+ requireTyped(key, converter) {
65
+ const value = this.require(key);
66
+ return converter(value);
67
+ }
68
+ /**
69
+ * Get all config as a plain object.
70
+ */
71
+ getAll() {
72
+ if (this.cache === null) {
73
+ throw new Error("ConfigLoader.load() must be called before getAll()");
74
+ }
75
+ return Object.fromEntries(this.cache);
76
+ }
77
+ /**
78
+ * Invalidate the cache so the next load() re-reads from DB.
79
+ */
80
+ invalidate() {
81
+ this.cache = null;
82
+ }
83
+ }
84
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/config/config-loader.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,YAAY;IAGM;IAFrB,KAAK,GAAsC,IAAI,CAAC;IAExD,YAA6B,IAA0B;QAA1B,SAAI,GAAJ,IAAI,CAAsB;IAAG,CAAC;IAE3D;;;OAGG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,GAAW;QACb,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACrC,CAAC;IAED;;OAEG;IACH,OAAO,CAAC,GAAW;QACjB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,gCAAgC,GAAG,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;OAGG;IACH,QAAQ,CAAI,GAAW,EAAE,SAA2B;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAChC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,YAAY,CAAI,GAAW,EAAE,SAA2B;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED;;OAEG;IACH,MAAM;QACJ,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QACD,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shape of a dictionary-style config row. Every microservice config table
3
+ * mirrors this: one row per key, value stored as TEXT, type conversion
4
+ * performed at read time by ConfigLoader consumers.
5
+ *
6
+ * Self-contained — does NOT extend IAuditableEntity from @primebrick/dal-pg.
7
+ * The SDK is DB-agnostic; audit fields are a DAL-specific concern handled
8
+ * by the consumer's entity class and adapter.
9
+ */
10
+ export interface IConfigEntity {
11
+ /** Unique config key, e.g. "brevo_api_key". */
12
+ key: string;
13
+ /** Raw TEXT value. null means "not set yet". Type conversion at read time. */
14
+ value: string | null;
15
+ /** Optional i18n translation key for a short title (used by BE/FE for display). */
16
+ label_key?: string;
17
+ /** Optional i18n translation key for a longer description (used by BE/FE for display). */
18
+ description_key?: string;
19
+ }
20
+ //# sourceMappingURL=iconfig-entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iconfig-entity.d.ts","sourceRoot":"","sources":["../../src/config/iconfig-entity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0FAA0F;IAC1F,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=iconfig-entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"iconfig-entity.js","sourceRoot":"","sources":["../../src/config/iconfig-entity.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ export interface EnvSchema {
2
+ [key: string]: {
3
+ required: boolean;
4
+ default?: string;
5
+ description?: string;
6
+ };
7
+ }
8
+ export interface EnvValidationResult {
9
+ valid: boolean;
10
+ errors: string[];
11
+ env: Record<string, string | undefined>;
12
+ }
13
+ /**
14
+ * Centralized env var validation. Replaces scattered inline checks
15
+ * (emailsender: dal.ts:18-20, http-server.ts:5-9, webhook-service.ts:9-14,
16
+ * email-service.ts:12-17; BE: src/db/pool.ts).
17
+ *
18
+ * Pure process.env — no DB dependency.
19
+ */
20
+ export declare function validateEnv(schema: EnvSchema): EnvValidationResult;
21
+ /**
22
+ * Validate env vars and throw if any required ones are missing.
23
+ */
24
+ export declare function requireEnv(schema: EnvSchema): Record<string, string | undefined>;
25
+ //# sourceMappingURL=env-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-validator.d.ts","sourceRoot":"","sources":["../../src/env/env-validator.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;CACzC;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,mBAAmB,CAalE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAMhF"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Centralized env var validation. Replaces scattered inline checks
3
+ * (emailsender: dal.ts:18-20, http-server.ts:5-9, webhook-service.ts:9-14,
4
+ * email-service.ts:12-17; BE: src/db/pool.ts).
5
+ *
6
+ * Pure process.env — no DB dependency.
7
+ */
8
+ export function validateEnv(schema) {
9
+ const errors = [];
10
+ const env = {};
11
+ for (const [key, spec] of Object.entries(schema)) {
12
+ const value = process.env[key] ?? spec.default;
13
+ env[key] = value;
14
+ if (spec.required && (value === undefined || value === "")) {
15
+ errors.push(`${key} is required${spec.description ? ` (${spec.description})` : ""}`);
16
+ }
17
+ }
18
+ return { valid: errors.length === 0, errors, env };
19
+ }
20
+ /**
21
+ * Validate env vars and throw if any required ones are missing.
22
+ */
23
+ export function requireEnv(schema) {
24
+ const result = validateEnv(schema);
25
+ if (!result.valid) {
26
+ throw new Error(`Environment validation failed:\n - ${result.errors.join("\n - ")}`);
27
+ }
28
+ return result.env;
29
+ }
30
+ //# sourceMappingURL=env-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-validator.js","sourceRoot":"","sources":["../../src/env/env-validator.ts"],"names":[],"mappings":"AAcA;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,MAAiB;IAC3C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAuC,EAAE,CAAC;IAEnD,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC;QAC/C,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACjB,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,EAAE,CAAC;YAC3D,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,eAAe,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;AACrD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,uCAAuC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,MAAM,CAAC,GAAG,CAAC;AACpB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { HealthCheckPort } from "../ports/health-check-port.js";
2
+ export interface HealthCheckResult {
3
+ ok: boolean;
4
+ [key: string]: unknown;
5
+ }
6
+ /**
7
+ * Health check utility. Extracted from BE's index.ts:126-148 pattern.
8
+ * Checks DB connectivity (via HealthCheckPort) and optional custom checks.
9
+ *
10
+ * DB-agnostic: depends on HealthCheckPort, NOT on pg.Pool.
11
+ * The consumer provides an adapter that runs whatever their DB uses
12
+ * (e.g. `SELECT 1` for PG).
13
+ */
14
+ export declare class HealthCheck {
15
+ private readonly dbPing;
16
+ private readonly customChecks;
17
+ constructor(dbPing: HealthCheckPort, customChecks?: Record<string, () => Promise<HealthCheckResult>>);
18
+ checkDb(): Promise<HealthCheckResult>;
19
+ runAll(): Promise<Record<string, HealthCheckResult>>;
20
+ isHealthy(results: Record<string, HealthCheckResult>): boolean;
21
+ }
22
+ //# sourceMappingURL=health-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check.d.ts","sourceRoot":"","sources":["../../src/http/health-check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;gBADZ,MAAM,EAAE,eAAe,EACvB,YAAY,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAM;IAGhF,OAAO,IAAI,OAAO,CAAC,iBAAiB,CAAC;IASrC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAc1D,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,OAAO;CAG/D"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Health check utility. Extracted from BE's index.ts:126-148 pattern.
3
+ * Checks DB connectivity (via HealthCheckPort) and optional custom checks.
4
+ *
5
+ * DB-agnostic: depends on HealthCheckPort, NOT on pg.Pool.
6
+ * The consumer provides an adapter that runs whatever their DB uses
7
+ * (e.g. `SELECT 1` for PG).
8
+ */
9
+ export class HealthCheck {
10
+ dbPing;
11
+ customChecks;
12
+ constructor(dbPing, customChecks = {}) {
13
+ this.dbPing = dbPing;
14
+ this.customChecks = customChecks;
15
+ }
16
+ async checkDb() {
17
+ try {
18
+ const ok = await this.dbPing.ping();
19
+ return { ok };
20
+ }
21
+ catch {
22
+ return { ok: false };
23
+ }
24
+ }
25
+ async runAll() {
26
+ const results = {
27
+ db: await this.checkDb(),
28
+ };
29
+ for (const [name, check] of Object.entries(this.customChecks)) {
30
+ try {
31
+ results[name] = await check();
32
+ }
33
+ catch (e) {
34
+ results[name] = { ok: false, error: e instanceof Error ? e.message : "Unknown error" };
35
+ }
36
+ }
37
+ return results;
38
+ }
39
+ isHealthy(results) {
40
+ return Object.values(results).every((r) => r.ok);
41
+ }
42
+ }
43
+ //# sourceMappingURL=health-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check.js","sourceRoot":"","sources":["../../src/http/health-check.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,MAAM,OAAO,WAAW;IAEH;IACA;IAFnB,YACmB,MAAuB,EACvB,eAAiE,EAAE;QADnE,WAAM,GAAN,MAAM,CAAiB;QACvB,iBAAY,GAAZ,YAAY,CAAuD;IACnF,CAAC;IAEJ,KAAK,CAAC,OAAO;QACX,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACpC,OAAO,EAAE,EAAE,EAAE,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,OAAO,GAAsC;YACjD,EAAE,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE;SACzB,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9D,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,KAAK,EAAE,CAAC;YAChC,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC;YACzF,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,SAAS,CAAC,OAA0C;QAClD,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnD,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import { type IncomingMessage, type ServerResponse, type Server } from "http";
2
+ import type { HealthCheck } from "./health-check.js";
3
+ export interface HttpServerOptions {
4
+ port: number;
5
+ healthCheck?: HealthCheck;
6
+ serviceName?: string;
7
+ /** Custom route handler — receives req/res, returns true if handled. */
8
+ routeHandler?: (req: IncomingMessage, res: ServerResponse, url: URL) => Promise<boolean>;
9
+ }
10
+ /**
11
+ * Minimal HTTP server with health endpoint. Extracted from emailsender's
12
+ * server/http-server.ts:1-64. Uses native http module (no Express).
13
+ * No DB dependency.
14
+ */
15
+ export declare function createHttpServer(options: HttpServerOptions): Promise<Server>;
16
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,MAAM,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1F;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAiClF"}
@@ -0,0 +1,38 @@
1
+ import { createServer } from "http";
2
+ /**
3
+ * Minimal HTTP server with health endpoint. Extracted from emailsender's
4
+ * server/http-server.ts:1-64. Uses native http module (no Express).
5
+ * No DB dependency.
6
+ */
7
+ export async function createHttpServer(options) {
8
+ const server = createServer(async (req, res) => {
9
+ const url = new URL(req.url || "", `http://${req.headers.host}`);
10
+ // Health check endpoint
11
+ if (url.pathname === "/health" && req.method === "GET") {
12
+ if (options.healthCheck) {
13
+ const results = await options.healthCheck.runAll();
14
+ const healthy = options.healthCheck.isHealthy(results);
15
+ res.writeHead(healthy ? 200 : 503, { "Content-Type": "application/json" });
16
+ res.end(JSON.stringify({ status: healthy ? "healthy" : "degraded", checks: results }));
17
+ }
18
+ else {
19
+ res.writeHead(200, { "Content-Type": "application/json" });
20
+ res.end(JSON.stringify({ status: "healthy" }));
21
+ }
22
+ return;
23
+ }
24
+ // Custom routes
25
+ if (options.routeHandler) {
26
+ const handled = await options.routeHandler(req, res, url);
27
+ if (handled)
28
+ return;
29
+ }
30
+ res.writeHead(404, { "Content-Type": "text/plain" });
31
+ res.end("Not Found");
32
+ });
33
+ server.listen(options.port, () => {
34
+ console.log(`HTTP server listening on port ${options.port}`);
35
+ });
36
+ return server;
37
+ }
38
+ //# sourceMappingURL=http-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.js","sourceRoot":"","sources":["../../src/http/http-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA0D,MAAM,MAAM,CAAC;AAW5F;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAA0B;IAC/D,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAE,EAAE;QAC9E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;QAEjE,wBAAwB;QACxB,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YACvD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;gBACxB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;gBACnD,MAAM,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;gBACvD,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3E,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;YACzF,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC3D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YACjD,CAAC;YACD,OAAO;QACT,CAAC;QAED,gBAAgB;QAChB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC1D,IAAI,OAAO;gBAAE,OAAO;QACtB,CAAC;QAED,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;QACrD,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE;QAC/B,OAAO,CAAC,GAAG,CAAC,iCAAiC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @primebrick/sdk — Shared microservice infrastructure for Primebrick v3.
3
+ *
4
+ * DB-agnostic via port interfaces (dependency inversion).
5
+ * ZERO dependencies on any Primebrick library or DB driver.
6
+ *
7
+ * Modules:
8
+ * - ports: ConfigRepositoryPort, DatabasePort, ServiceRegistryPort, HealthCheckPort
9
+ * - config: ConfigLoader, IConfigEntity
10
+ * - migrations: applyPatches, patch-registry, patch-naming
11
+ * - service: ServiceRegistrar, IServiceRegistry
12
+ * - lifecycle: GracefulShutdown
13
+ * - nats: NatsClient (requires `nats` peer dependency)
14
+ * - http: createHttpServer, HealthCheck
15
+ * - env: validateEnv, requireEnv
16
+ */
17
+ export { type ConfigRepositoryPort } from "./ports/config-repository-port.js";
18
+ export { type DatabasePort } from "./ports/database-port.js";
19
+ export { type ServiceRegistryPort } from "./ports/service-registry-port.js";
20
+ export { type HealthCheckPort } from "./ports/health-check-port.js";
21
+ export { type IConfigEntity } from "./config/iconfig-entity.js";
22
+ export { ConfigLoader } from "./config/config-loader.js";
23
+ export { PATCH_REGISTRY_DDL, PATCH_REGISTRY_FQNAME, isPatchBodyAlreadyRecorded, } from "./migrations/patch-registry.js";
24
+ export { utcTimestampForFilename, slugifyPatchSegment, patchIdFromFilename, sha256Hex, } from "./migrations/patch-naming.js";
25
+ export { applyPatches, type ApplyPatchesResult } from "./migrations/apply-patches.js";
26
+ export { type IServiceRegistry } from "./service/service-registry.js";
27
+ export { ServiceRegistrar, type ServiceRegistrarConfig } from "./service/service-registrar.js";
28
+ export { GracefulShutdown, type CleanupFn } from "./lifecycle/graceful-shutdown.js";
29
+ export { NatsClient } from "./nats/nats-client.js";
30
+ export { createHttpServer, type HttpServerOptions } from "./http/http-server.js";
31
+ export { HealthCheck, type HealthCheckResult } from "./http/health-check.js";
32
+ export { validateEnv, requireEnv, type EnvSchema, type EnvValidationResult } from "./env/env-validator.js";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,KAAK,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,SAAS,GACV,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGtF,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,KAAK,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG/F,OAAO,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAGpF,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGnD,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG7E,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @primebrick/sdk — Shared microservice infrastructure for Primebrick v3.
3
+ *
4
+ * DB-agnostic via port interfaces (dependency inversion).
5
+ * ZERO dependencies on any Primebrick library or DB driver.
6
+ *
7
+ * Modules:
8
+ * - ports: ConfigRepositoryPort, DatabasePort, ServiceRegistryPort, HealthCheckPort
9
+ * - config: ConfigLoader, IConfigEntity
10
+ * - migrations: applyPatches, patch-registry, patch-naming
11
+ * - service: ServiceRegistrar, IServiceRegistry
12
+ * - lifecycle: GracefulShutdown
13
+ * - nats: NatsClient (requires `nats` peer dependency)
14
+ * - http: createHttpServer, HealthCheck
15
+ * - env: validateEnv, requireEnv
16
+ */
17
+ export { ConfigLoader } from "./config/config-loader.js";
18
+ // Migrations
19
+ export { PATCH_REGISTRY_DDL, PATCH_REGISTRY_FQNAME, isPatchBodyAlreadyRecorded, } from "./migrations/patch-registry.js";
20
+ export { utcTimestampForFilename, slugifyPatchSegment, patchIdFromFilename, sha256Hex, } from "./migrations/patch-naming.js";
21
+ export { applyPatches } from "./migrations/apply-patches.js";
22
+ export { ServiceRegistrar } from "./service/service-registrar.js";
23
+ // Lifecycle
24
+ export { GracefulShutdown } from "./lifecycle/graceful-shutdown.js";
25
+ // NATS (optional — requires `nats` peer dependency)
26
+ export { NatsClient } from "./nats/nats-client.js";
27
+ // HTTP
28
+ export { createHttpServer } from "./http/http-server.js";
29
+ export { HealthCheck } from "./http/health-check.js";
30
+ // Env validation
31
+ export { validateEnv, requireEnv } from "./env/env-validator.js";
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAUH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,aAAa;AACb,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,SAAS,GACV,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,YAAY,EAA2B,MAAM,+BAA+B,CAAC;AAItF,OAAO,EAAE,gBAAgB,EAA+B,MAAM,gCAAgC,CAAC;AAE/F,YAAY;AACZ,OAAO,EAAE,gBAAgB,EAAkB,MAAM,kCAAkC,CAAC;AAEpF,oDAAoD;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO;AACP,OAAO,EAAE,gBAAgB,EAA0B,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,WAAW,EAA0B,MAAM,wBAAwB,CAAC;AAE7E,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAAE,UAAU,EAA4C,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,24 @@
1
+ export type CleanupFn = () => Promise<void>;
2
+ /**
3
+ * Graceful shutdown manager. Extracted from emailsender's index.ts:55-95.
4
+ *
5
+ * - Re-entrancy guard: second signal is a no-op.
6
+ * - Runs all cleanup functions in parallel (Promise.allSettled).
7
+ * - Always calls process.exit() explicitly.
8
+ * - Installs SIGTERM, SIGINT, SIGHUP + uncaughtException + unhandledRejection handlers.
9
+ *
10
+ * Pure Node.js — no DB dependency. The consumer registers cleanup functions
11
+ * (e.g. getDal().close(), NatsClient.close()) via addCleanup().
12
+ */
13
+ export declare class GracefulShutdown {
14
+ private shuttingDown;
15
+ private readonly cleanups;
16
+ private readonly serviceName;
17
+ constructor(serviceName: string);
18
+ /** Register a cleanup function to run on shutdown. */
19
+ addCleanup(fn: CleanupFn): void;
20
+ /** Install signal + crash handlers. */
21
+ install(): void;
22
+ shutdown(reason: string, code: number): Promise<void>;
23
+ }
24
+ //# sourceMappingURL=graceful-shutdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graceful-shutdown.d.ts","sourceRoot":"","sources":["../../src/lifecycle/graceful-shutdown.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,WAAW,EAAE,MAAM;IAI/B,sDAAsD;IACtD,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAI/B,uCAAuC;IACvC,OAAO,IAAI,IAAI;IAkBT,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAU5D"}
@@ -0,0 +1,55 @@
1
+ import os from "node:os";
2
+ /**
3
+ * Graceful shutdown manager. Extracted from emailsender's index.ts:55-95.
4
+ *
5
+ * - Re-entrancy guard: second signal is a no-op.
6
+ * - Runs all cleanup functions in parallel (Promise.allSettled).
7
+ * - Always calls process.exit() explicitly.
8
+ * - Installs SIGTERM, SIGINT, SIGHUP + uncaughtException + unhandledRejection handlers.
9
+ *
10
+ * Pure Node.js — no DB dependency. The consumer registers cleanup functions
11
+ * (e.g. getDal().close(), NatsClient.close()) via addCleanup().
12
+ */
13
+ export class GracefulShutdown {
14
+ shuttingDown = false;
15
+ cleanups = [];
16
+ serviceName;
17
+ constructor(serviceName) {
18
+ this.serviceName = serviceName;
19
+ }
20
+ /** Register a cleanup function to run on shutdown. */
21
+ addCleanup(fn) {
22
+ this.cleanups.push(fn);
23
+ }
24
+ /** Install signal + crash handlers. */
25
+ install() {
26
+ const signals = ["SIGTERM", "SIGINT", "SIGHUP"];
27
+ for (const sig of signals) {
28
+ process.on(sig, () => {
29
+ const code = 128 + (os.constants.signals[sig] ?? 0);
30
+ void this.shutdown(sig, code);
31
+ });
32
+ }
33
+ process.on("uncaughtException", (err) => {
34
+ console.error(`[${this.serviceName}] uncaughtException`, err);
35
+ void this.shutdown("uncaughtException", 1);
36
+ });
37
+ process.on("unhandledRejection", (reason) => {
38
+ console.error(`[${this.serviceName}] unhandledRejection`, reason);
39
+ void this.shutdown("unhandledRejection", 1);
40
+ });
41
+ }
42
+ async shutdown(reason, code) {
43
+ if (this.shuttingDown)
44
+ return;
45
+ this.shuttingDown = true;
46
+ console.log(`[${this.serviceName}] shutting down (${reason})`);
47
+ try {
48
+ await Promise.allSettled(this.cleanups.map((fn) => fn()));
49
+ }
50
+ finally {
51
+ process.exit(code);
52
+ }
53
+ }
54
+ }
55
+ //# sourceMappingURL=graceful-shutdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graceful-shutdown.js","sourceRoot":"","sources":["../../src/lifecycle/graceful-shutdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAIzB;;;;;;;;;;GAUG;AACH,MAAM,OAAO,gBAAgB;IACnB,YAAY,GAAG,KAAK,CAAC;IACZ,QAAQ,GAAgB,EAAE,CAAC;IAC3B,WAAW,CAAS;IAErC,YAAY,WAAmB;QAC7B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,sDAAsD;IACtD,UAAU,CAAC,EAAa;QACtB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IAED,uCAAuC;IACvC,OAAO;QACL,MAAM,OAAO,GAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAClE,KAAK,MAAM,GAAG,IAAI,OAAO,EAAE,CAAC;YAC1B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE;gBACnB,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,GAAwC,CAAC,IAAI,CAAC,CAAC,CAAC;gBACzF,KAAK,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE;YACtC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,qBAAqB,EAAE,GAAG,CAAC,CAAC;YAC9D,KAAK,IAAI,CAAC,QAAQ,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;YAC1C,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,WAAW,sBAAsB,EAAE,MAAM,CAAC,CAAC;YAClE,KAAK,IAAI,CAAC,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,IAAY;QACzC,IAAI,IAAI,CAAC,YAAY;YAAE,OAAO;QAC9B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,WAAW,oBAAoB,MAAM,GAAG,CAAC,CAAC;QAC/D,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;gBAAS,CAAC;YACT,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import type { DatabasePort } from "../ports/database-port.js";
2
+ export interface ApplyPatchesResult {
3
+ appliedOrRegistered: number;
4
+ skipped: number;
5
+ }
6
+ /**
7
+ * Apply database SQL patches from a directory.
8
+ *
9
+ * Strategy (adapted from BE's scripts/database-patch-apply.ts:1-148):
10
+ * - Read .sql files from patchesDir sorted by filename.
11
+ * - For each file, consult public.primebrick_database_patches (patch_id + content_sha256):
12
+ * - Same patch_id + same SHA → skip (already applied).
13
+ * - Same patch_id + different SHA → fail (immutable patch changed).
14
+ * - Missing patch_id but same SHA exists → register without re-executing.
15
+ * - Otherwise → BEGIN; apply SQL; INSERT registry row; COMMIT.
16
+ *
17
+ * DB-agnostic: depends on DatabasePort, NOT on pg.Pool.
18
+ * The consumer provides an adapter that wraps their DB driver.
19
+ *
20
+ * @param patchesDir Absolute path to the directory containing .sql patch files.
21
+ * @param db DatabasePort adapter (wraps the consumer's DB driver).
22
+ * @returns Result with count of applied/registered and skipped patches.
23
+ */
24
+ export declare function applyPatches(patchesDir: string, db: DatabasePort): Promise<ApplyPatchesResult>;
25
+ //# sourceMappingURL=apply-patches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-patches.d.ts","sourceRoot":"","sources":["../../src/migrations/apply-patches.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAI9D,MAAM,WAAW,kBAAkB;IACjC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAwEpG"}
@@ -0,0 +1,75 @@
1
+ import { readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { PATCH_REGISTRY_DDL, PATCH_REGISTRY_FQNAME } from "./patch-registry.js";
4
+ import { patchIdFromFilename, sha256Hex } from "./patch-naming.js";
5
+ /**
6
+ * Apply database SQL patches from a directory.
7
+ *
8
+ * Strategy (adapted from BE's scripts/database-patch-apply.ts:1-148):
9
+ * - Read .sql files from patchesDir sorted by filename.
10
+ * - For each file, consult public.primebrick_database_patches (patch_id + content_sha256):
11
+ * - Same patch_id + same SHA → skip (already applied).
12
+ * - Same patch_id + different SHA → fail (immutable patch changed).
13
+ * - Missing patch_id but same SHA exists → register without re-executing.
14
+ * - Otherwise → BEGIN; apply SQL; INSERT registry row; COMMIT.
15
+ *
16
+ * DB-agnostic: depends on DatabasePort, NOT on pg.Pool.
17
+ * The consumer provides an adapter that wraps their DB driver.
18
+ *
19
+ * @param patchesDir Absolute path to the directory containing .sql patch files.
20
+ * @param db DatabasePort adapter (wraps the consumer's DB driver).
21
+ * @returns Result with count of applied/registered and skipped patches.
22
+ */
23
+ export async function applyPatches(patchesDir, db) {
24
+ await db.query(PATCH_REGISTRY_DDL);
25
+ let files = [];
26
+ try {
27
+ files = readdirSync(patchesDir).filter((f) => f.endsWith(".sql")).sort();
28
+ }
29
+ catch {
30
+ return { appliedOrRegistered: 0, skipped: 0 };
31
+ }
32
+ if (files.length === 0) {
33
+ return { appliedOrRegistered: 0, skipped: 0 };
34
+ }
35
+ let appliedOrRegistered = 0;
36
+ let skipped = 0;
37
+ for (const filename of files) {
38
+ const patchPath = join(patchesDir, filename);
39
+ const raw = readFileSync(patchPath, "utf8");
40
+ const sha = sha256Hex(raw);
41
+ const patchId = patchIdFromFilename(filename);
42
+ const byId = await db.query(`SELECT content_sha256 FROM ${PATCH_REGISTRY_FQNAME} WHERE patch_id = $1`, [patchId]);
43
+ if (byId.rows.length > 0) {
44
+ const recorded = byId.rows[0].content_sha256;
45
+ if (recorded === sha) {
46
+ console.log(`Skipping already applied patch: ${filename}`);
47
+ skipped++;
48
+ continue;
49
+ }
50
+ throw new Error(`Patch ${filename} (${patchId}) exists in registry with a different content_sha256 — refusing to run.`);
51
+ }
52
+ const bySha = await db.query(`SELECT patch_id FROM ${PATCH_REGISTRY_FQNAME} WHERE content_sha256 = $1 LIMIT 1`, [sha]);
53
+ if (bySha.rows.length > 0) {
54
+ const other = bySha.rows[0].patch_id;
55
+ await db.query(`INSERT INTO ${PATCH_REGISTRY_FQNAME} (patch_id, content_sha256) VALUES ($1, $2)`, [patchId, sha]);
56
+ console.log(`Registered ${filename} (same body as ${other}) — no SQL re-execution.`);
57
+ appliedOrRegistered++;
58
+ continue;
59
+ }
60
+ console.log(`Applying patch: ${filename}`);
61
+ try {
62
+ await db.query("BEGIN");
63
+ await db.query(raw);
64
+ await db.query(`INSERT INTO ${PATCH_REGISTRY_FQNAME} (patch_id, content_sha256) VALUES ($1, $2)`, [patchId, sha]);
65
+ await db.query("COMMIT");
66
+ appliedOrRegistered++;
67
+ }
68
+ catch (e) {
69
+ await db.query("ROLLBACK");
70
+ throw new Error(`Failed to apply patch ${filename}: ${e}`);
71
+ }
72
+ }
73
+ return { appliedOrRegistered, skipped };
74
+ }
75
+ //# sourceMappingURL=apply-patches.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply-patches.js","sourceRoot":"","sources":["../../src/migrations/apply-patches.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAOnE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,UAAkB,EAAE,EAAgB;IACrE,MAAM,EAAE,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAEnC,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,mBAAmB,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAChD,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,mBAAmB,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAChD,CAAC;IAED,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAC5B,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,KAAK,MAAM,QAAQ,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC3B,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAE9C,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,KAAK,CACzB,8BAA8B,qBAAqB,sBAAsB,EACzE,CAAC,OAAO,CAAC,CACV,CAAC;QAEF,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAgC,CAAC,cAAc,CAAC;YAC7E,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;gBACrB,OAAO,CAAC,GAAG,CAAC,mCAAmC,QAAQ,EAAE,CAAC,CAAC;gBAC3D,OAAO,EAAE,CAAC;gBACV,SAAS;YACX,CAAC;YACD,MAAM,IAAI,KAAK,CACb,SAAS,QAAQ,KAAK,OAAO,yEAAyE,CACvG,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,EAAE,CAAC,KAAK,CAC1B,wBAAwB,qBAAqB,oCAAoC,EACjF,CAAC,GAAG,CAAC,CACN,CAAC;QACF,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAA0B,CAAC,QAAQ,CAAC;YAC/D,MAAM,EAAE,CAAC,KAAK,CACZ,eAAe,qBAAqB,6CAA6C,EACjF,CAAC,OAAO,EAAE,GAAG,CAAC,CACf,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,cAAc,QAAQ,kBAAkB,KAAK,0BAA0B,CAAC,CAAC;YACrF,mBAAmB,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACxB,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpB,MAAM,EAAE,CAAC,KAAK,CACZ,eAAe,qBAAqB,6CAA6C,EACjF,CAAC,OAAO,EAAE,GAAG,CAAC,CACf,CAAC;YACF,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;YACzB,mBAAmB,EAAE,CAAC;QACxB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare function utcTimestampForFilename(d?: Date): string;
2
+ export declare function slugifyPatchSegment(s: string): string;
3
+ export declare function patchIdFromFilename(filename: string): string;
4
+ export declare function sha256Hex(body: string): string;
5
+ //# sourceMappingURL=patch-naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-naming.d.ts","sourceRoot":"","sources":["../../src/migrations/patch-naming.ts"],"names":[],"mappings":"AAEA,wBAAgB,uBAAuB,CAAC,CAAC,OAAa,GAAG,MAAM,CAM9D;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAOrD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C"}
@@ -0,0 +1,21 @@
1
+ import { createHash } from "node:crypto";
2
+ export function utcTimestampForFilename(d = new Date()) {
3
+ const p = (n) => String(n).padStart(2, "0");
4
+ return (`${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}` +
5
+ `${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}`);
6
+ }
7
+ export function slugifyPatchSegment(s) {
8
+ return s
9
+ .replace(/[^a-zA-Z0-9]+/g, "_")
10
+ .replace(/^_+|_+$/g, "")
11
+ .replace(/_+/g, "_")
12
+ .toLowerCase()
13
+ .slice(0, 72) || "patch";
14
+ }
15
+ export function patchIdFromFilename(filename) {
16
+ return filename.replace(/\.sql$/i, "");
17
+ }
18
+ export function sha256Hex(body) {
19
+ return createHash("sha256").update(body, "utf-8").digest("hex");
20
+ }
21
+ //# sourceMappingURL=patch-naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-naming.js","sourceRoot":"","sources":["../../src/migrations/patch-naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,uBAAuB,CAAC,CAAC,GAAG,IAAI,IAAI,EAAE;IACpD,MAAM,CAAC,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,CACL,GAAG,CAAC,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE;QACpE,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,EAAE,CACtE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,CAAS;IAC3C,OAAO,CAAC;SACL,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC;SAC9B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;SACvB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,WAAW,EAAE;SACb,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,OAAO,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,OAAO,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { DatabasePort } from "../ports/database-port.js";
2
+ export declare const PATCH_REGISTRY_FQNAME = "public.primebrick_database_patches";
3
+ export declare const PATCH_REGISTRY_DDL = "CREATE TABLE IF NOT EXISTS public.primebrick_database_patches (\n patch_id text PRIMARY KEY,\n content_sha256 text NOT NULL,\n applied_at timestamptz NOT NULL DEFAULT now()\n);\nCREATE INDEX IF NOT EXISTS primebrick_database_patches_sha_idx\n ON public.primebrick_database_patches (content_sha256);\n";
4
+ export declare function isPatchBodyAlreadyRecorded(db: DatabasePort, contentSha256: string): Promise<boolean>;
5
+ //# sourceMappingURL=patch-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-registry.d.ts","sourceRoot":"","sources":["../../src/migrations/patch-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,eAAO,MAAM,qBAAqB,uCAAuC,CAAC;AAE1E,eAAO,MAAM,kBAAkB,qTAO9B,CAAC;AAEF,wBAAsB,0BAA0B,CAAC,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW1G"}
@@ -0,0 +1,18 @@
1
+ export const PATCH_REGISTRY_FQNAME = "public.primebrick_database_patches";
2
+ export const PATCH_REGISTRY_DDL = `CREATE TABLE IF NOT EXISTS public.primebrick_database_patches (
3
+ patch_id text PRIMARY KEY,
4
+ content_sha256 text NOT NULL,
5
+ applied_at timestamptz NOT NULL DEFAULT now()
6
+ );
7
+ CREATE INDEX IF NOT EXISTS primebrick_database_patches_sha_idx
8
+ ON public.primebrick_database_patches (content_sha256);
9
+ `;
10
+ export async function isPatchBodyAlreadyRecorded(db, contentSha256) {
11
+ const reg = await db.query(`SELECT to_regclass('${PATCH_REGISTRY_FQNAME}')::text AS oid`);
12
+ const oid = reg.rows[0]?.oid;
13
+ if (!oid || oid === "")
14
+ return false;
15
+ const hit = await db.query(`SELECT 1 FROM ${PATCH_REGISTRY_FQNAME} WHERE content_sha256 = $1 LIMIT 1`, [contentSha256]);
16
+ return hit.rows.length > 0;
17
+ }
18
+ //# sourceMappingURL=patch-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-registry.js","sourceRoot":"","sources":["../../src/migrations/patch-registry.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,qBAAqB,GAAG,oCAAoC,CAAC;AAE1E,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;CAOjC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,EAAgB,EAAE,aAAqB;IACtF,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,KAAK,CACxB,uBAAuB,qBAAqB,iBAAiB,CAC9D,CAAC;IACF,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;IAC7B,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,KAAK,CACxB,iBAAiB,qBAAqB,oCAAoC,EAC1E,CAAC,aAAa,CAAC,CAChB,CAAC;IACF,OAAO,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { type NatsConnection, type JetStreamClient } from "nats";
2
+ /**
3
+ * Singleton NATS connection manager. Extracted from emailsender's
4
+ * nats/client.ts:1-31.
5
+ *
6
+ * Requires `nats` as a peer dependency — consumers that don't need NATS
7
+ * can skip installing it and won't import this module.
8
+ * No DB dependency.
9
+ */
10
+ export declare class NatsClient {
11
+ private static nc;
12
+ private static js;
13
+ static getConnection(): Promise<NatsConnection>;
14
+ static getJetStream(): JetStreamClient;
15
+ static close(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=nats-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nats-client.d.ts","sourceRoot":"","sources":["../../src/nats/nats-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,MAAM,CAAC;AAE1E;;;;;;;GAOG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAC,EAAE,CAA+B;IAChD,OAAO,CAAC,MAAM,CAAC,EAAE,CAAgC;WAEpC,aAAa,IAAI,OAAO,CAAC,cAAc,CAAC;IASrD,MAAM,CAAC,YAAY,IAAI,eAAe;WAOzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQpC"}
@@ -0,0 +1,37 @@
1
+ import { connect } from "nats";
2
+ /**
3
+ * Singleton NATS connection manager. Extracted from emailsender's
4
+ * nats/client.ts:1-31.
5
+ *
6
+ * Requires `nats` as a peer dependency — consumers that don't need NATS
7
+ * can skip installing it and won't import this module.
8
+ * No DB dependency.
9
+ */
10
+ export class NatsClient {
11
+ static nc = null;
12
+ static js = null;
13
+ static async getConnection() {
14
+ if (NatsClient.nc)
15
+ return NatsClient.nc;
16
+ const natsUrl = process.env.NATS_URL || "nats://127.0.0.1:4222";
17
+ NatsClient.nc = await connect({ servers: natsUrl });
18
+ NatsClient.js = NatsClient.nc.jetstream();
19
+ console.log(`Connected to NATS at ${natsUrl}`);
20
+ return NatsClient.nc;
21
+ }
22
+ static getJetStream() {
23
+ if (!NatsClient.js) {
24
+ throw new Error("NATS JetStream not initialized. Call NatsClient.getConnection() first.");
25
+ }
26
+ return NatsClient.js;
27
+ }
28
+ static async close() {
29
+ if (NatsClient.nc) {
30
+ await NatsClient.nc.close();
31
+ NatsClient.nc = null;
32
+ NatsClient.js = null;
33
+ console.log("NATS connection closed");
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=nats-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nats-client.js","sourceRoot":"","sources":["../../src/nats/nats-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA6C,MAAM,MAAM,CAAC;AAE1E;;;;;;;GAOG;AACH,MAAM,OAAO,UAAU;IACb,MAAM,CAAC,EAAE,GAA0B,IAAI,CAAC;IACxC,MAAM,CAAC,EAAE,GAA2B,IAAI,CAAC;IAEjD,MAAM,CAAC,KAAK,CAAC,aAAa;QACxB,IAAI,UAAU,CAAC,EAAE;YAAE,OAAO,UAAU,CAAC,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAC;QAChE,UAAU,CAAC,EAAE,GAAG,MAAM,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACpD,UAAU,CAAC,EAAE,GAAG,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;QAC/C,OAAO,UAAU,CAAC,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,YAAY;QACjB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,UAAU,CAAC,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,KAAK;QAChB,IAAI,UAAU,CAAC,EAAE,EAAE,CAAC;YAClB,MAAM,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAC5B,UAAU,CAAC,EAAE,GAAG,IAAI,CAAC;YACrB,UAAU,CAAC,EAAE,GAAG,IAAI,CAAC;YACrB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACxC,CAAC;IACH,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Port interface for reading config rows from a DB config table.
3
+ *
4
+ * The SDK's ConfigLoader depends on this port, NOT on any specific DAL.
5
+ * The consumer provides an adapter implementation using their DAL
6
+ * (e.g. @primebrick/dal-pg's dal.findAll, or a raw SQL query).
7
+ */
8
+ export interface ConfigRepositoryPort {
9
+ /**
10
+ * Return all config rows as { key, value } pairs.
11
+ * value is null when the config key exists but has no value set.
12
+ */
13
+ findAll(): Promise<Array<{
14
+ key: string;
15
+ value: string | null;
16
+ }>>;
17
+ }
18
+ //# sourceMappingURL=config-repository-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-repository-port.d.ts","sourceRoot":"","sources":["../../src/ports/config-repository-port.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC,CAAC;CAClE"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config-repository-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-repository-port.js","sourceRoot":"","sources":["../../src/ports/config-repository-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Port interface for executing parameterized SQL queries.
3
+ *
4
+ * The SDK's migration runner (applyPatches) depends on this port,
5
+ * NOT on pg.Pool. The consumer provides an adapter that wraps their
6
+ * DB driver (pg.Pool, mssql.ConnectionPool, mariadb.Pool, etc.).
7
+ *
8
+ * The contract mirrors the minimal `query(text, params?)` shape that
9
+ * every SQL DB driver exposes.
10
+ */
11
+ export interface DatabasePort {
12
+ /**
13
+ * Execute a SQL statement. Returns rows (empty for non-SELECT statements).
14
+ * Used by the migration runner for BEGIN/COMMIT/ROLLBACK and patch SQL.
15
+ */
16
+ query<T = unknown>(text: string, params?: unknown[]): Promise<{
17
+ rows: T[];
18
+ }>;
19
+ }
20
+ //# sourceMappingURL=database-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-port.d.ts","sourceRoot":"","sources":["../../src/ports/database-port.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;CAC9E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=database-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-port.js","sourceRoot":"","sources":["../../src/ports/database-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Port interface for a DB health check (connectivity ping).
3
+ *
4
+ * The SDK's HealthCheck depends on this port, NOT on pg.Pool.
5
+ * The consumer provides an adapter that runs whatever their DB uses
6
+ * (e.g. `SELECT 1` for PG, `SELECT 1` for MSSQL, etc.).
7
+ */
8
+ export interface HealthCheckPort {
9
+ /** Returns true if the DB is reachable and responsive. */
10
+ ping(): Promise<boolean>;
11
+ }
12
+ //# sourceMappingURL=health-check-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check-port.d.ts","sourceRoot":"","sources":["../../src/ports/health-check-port.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=health-check-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health-check-port.js","sourceRoot":"","sources":["../../src/ports/health-check-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,17 @@
1
+ import type { IServiceRegistry } from "../service/service-registry.js";
2
+ /**
3
+ * Port interface for CRUD operations on the service_registry table.
4
+ *
5
+ * The SDK's ServiceRegistrar depends on this port, NOT on any specific DAL.
6
+ * The consumer provides an adapter implementation using their DAL
7
+ * (e.g. @primebrick/dal-pg's dal.find/dal.add/dal.update).
8
+ */
9
+ export interface ServiceRegistryPort<T = IServiceRegistry> {
10
+ /** Find a service registry row by its code. Returns null if not found. */
11
+ findByCode(code: string): Promise<T | null>;
12
+ /** Insert a new service registry row. */
13
+ insert(row: T): Promise<void>;
14
+ /** Update an existing service registry row by code. */
15
+ updateByCode(code: string, row: Partial<T>): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=service-registry-port.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registry-port.d.ts","sourceRoot":"","sources":["../../src/ports/service-registry-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,gBAAgB;IACvD,0EAA0E;IAC1E,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAE5C,yCAAyC;IACzC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B,uDAAuD;IACvD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service-registry-port.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registry-port.js","sourceRoot":"","sources":["../../src/ports/service-registry-port.ts"],"names":[],"mappings":""}
@@ -0,0 +1,27 @@
1
+ import type { ServiceRegistryPort } from "../ports/service-registry-port.js";
2
+ export interface ServiceRegistrarConfig {
3
+ serviceCode: string;
4
+ baseUrl: string;
5
+ endpoints: Record<string, unknown>;
6
+ heartbeatIntervalMs?: number;
7
+ }
8
+ /**
9
+ * Registers a microservice in `service_registry` and maintains
10
+ * a heartbeat. Extracted from emailsender's ServiceRegistration
11
+ * (service-registration.ts:1-101).
12
+ *
13
+ * DB-agnostic: depends on ServiceRegistryPort, NOT on getDal() or
14
+ * @primebrick/dal-pg. The consumer provides an adapter that implements
15
+ * ServiceRegistryPort using their DAL.
16
+ */
17
+ export declare class ServiceRegistrar {
18
+ private readonly repo;
19
+ private readonly config;
20
+ private heartbeatTimer;
21
+ constructor(repo: ServiceRegistryPort, config: ServiceRegistrarConfig);
22
+ register(): Promise<void>;
23
+ updateHeartbeat(): Promise<void>;
24
+ startHeartbeat(): ReturnType<typeof setInterval>;
25
+ stopHeartbeat(): void;
26
+ }
27
+ //# sourceMappingURL=service-registrar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registrar.d.ts","sourceRoot":"","sources":["../../src/service/service-registrar.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAG7E,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAKzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,cAAc,CAA+C;gBAGlD,IAAI,EAAE,mBAAmB,EAC1C,MAAM,EAAE,sBAAsB;IAQ1B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBzB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtC,cAAc,IAAI,UAAU,CAAC,OAAO,WAAW,CAAC;IAKhD,aAAa,IAAI,IAAI;CAMtB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Registers a microservice in `service_registry` and maintains
3
+ * a heartbeat. Extracted from emailsender's ServiceRegistration
4
+ * (service-registration.ts:1-101).
5
+ *
6
+ * DB-agnostic: depends on ServiceRegistryPort, NOT on getDal() or
7
+ * @primebrick/dal-pg. The consumer provides an adapter that implements
8
+ * ServiceRegistryPort using their DAL.
9
+ */
10
+ export class ServiceRegistrar {
11
+ repo;
12
+ config;
13
+ heartbeatTimer = null;
14
+ constructor(repo, config) {
15
+ this.repo = repo;
16
+ this.config = {
17
+ heartbeatIntervalMs: 60000,
18
+ ...config,
19
+ };
20
+ }
21
+ async register() {
22
+ const existing = await this.repo.findByCode(this.config.serviceCode);
23
+ if (existing) {
24
+ await this.repo.updateByCode(this.config.serviceCode, {
25
+ code: this.config.serviceCode,
26
+ base_url: this.config.baseUrl,
27
+ endpoints: this.config.endpoints,
28
+ });
29
+ console.log(`Updated service registration: ${this.config.serviceCode}`);
30
+ }
31
+ else {
32
+ await this.repo.insert({
33
+ code: this.config.serviceCode,
34
+ base_url: this.config.baseUrl,
35
+ endpoints: this.config.endpoints,
36
+ });
37
+ console.log(`Registered new service: ${this.config.serviceCode}`);
38
+ }
39
+ }
40
+ async updateHeartbeat() {
41
+ try {
42
+ await this.repo.updateByCode(this.config.serviceCode, {
43
+ base_url: this.config.baseUrl,
44
+ });
45
+ }
46
+ catch (error) {
47
+ console.error("Error updating heartbeat:", error);
48
+ }
49
+ }
50
+ startHeartbeat() {
51
+ this.heartbeatTimer = setInterval(() => this.updateHeartbeat(), this.config.heartbeatIntervalMs);
52
+ return this.heartbeatTimer;
53
+ }
54
+ stopHeartbeat() {
55
+ if (this.heartbeatTimer) {
56
+ clearInterval(this.heartbeatTimer);
57
+ this.heartbeatTimer = null;
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=service-registrar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registrar.js","sourceRoot":"","sources":["../../src/service/service-registrar.ts"],"names":[],"mappings":"AAUA;;;;;;;;GAQG;AACH,MAAM,OAAO,gBAAgB;IAKR;IAJF,MAAM,CAAyB;IACxC,cAAc,GAA0C,IAAI,CAAC;IAErE,YACmB,IAAyB,EAC1C,MAA8B;QADb,SAAI,GAAJ,IAAI,CAAqB;QAG1C,IAAI,CAAC,MAAM,GAAG;YACZ,mBAAmB,EAAE,KAAK;YAC1B,GAAG,MAAM;SACV,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAErE,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;gBACpD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;gBAC7B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;gBAC7B,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;aACjC,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,iCAAiC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QAC1E,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;gBACrB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;gBAC7B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;gBAC7B,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,SAAS;aACjC,CAAC,CAAC;YACH,OAAO,CAAC,GAAG,CAAC,2BAA2B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;gBACpD,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;aAC9B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,KAAK,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,cAAc;QACZ,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACjG,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;IAED,aAAa;QACX,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shape of a row in the `service_registry` table.
3
+ *
4
+ * Self-contained interface — NO decorators, NO IAuditableEntity.
5
+ * The SDK is DB-agnostic; the consumer keeps their own decorated
6
+ * entity class (e.g. ServiceRegistryEntity with @Entity/@Column from
7
+ * @primebrick/dal-pg) and maps it to/from this interface in their adapter.
8
+ *
9
+ * Previously duplicated in emailsender (service_registry_entity.ts:1-54)
10
+ * and BE (service_registry_entity.ts:1-42). Now the shared shape lives here.
11
+ */
12
+ export interface IServiceRegistry {
13
+ code: string;
14
+ base_url: string;
15
+ endpoints: Record<string, unknown>;
16
+ }
17
+ //# sourceMappingURL=service-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registry.d.ts","sourceRoot":"","sources":["../../src/service/service-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=service-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-registry.js","sourceRoot":"","sources":["../../src/service/service-registry.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@primebrick/sdk",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Shared microservice infrastructure for Primebrick v3 — config loading, migration runner, service registration, graceful shutdown, NATS client, health checks, env validation. DB-agnostic via port interfaces.",
7
+ "license": "MIT",
8
+ "author": "Michael Sogos",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/michaelsogos/primebrick-v3-sdk"
12
+ },
13
+ "homepage": "https://github.com/michaelsogos/primebrick-v3-sdk#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/michaelsogos/primebrick-v3-sdk/issues"
16
+ },
17
+ "main": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
21
+ },
22
+ "files": ["dist/"],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "prepare": "tsc",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public",
31
+ "registry": "https://registry.npmjs.org"
32
+ },
33
+ "dependencies": {},
34
+ "peerDependencies": {
35
+ "nats": "^2.28.1"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "nats": { "optional": true }
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.12.4",
42
+ "nats": "^2.28.1",
43
+ "typescript": "^5.7.2",
44
+ "vitest": "^2.1.0"
45
+ },
46
+ "packageManager": "pnpm@9.15.9"
47
+ }