@wopr-network/platform-core 1.67.1 → 1.69.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 (81) hide show
  1. package/dist/auth/better-auth.js +7 -0
  2. package/dist/backup/types.d.ts +1 -1
  3. package/dist/email/client.js +16 -0
  4. package/dist/server/__tests__/build-container.test.d.ts +1 -0
  5. package/dist/server/__tests__/build-container.test.js +339 -0
  6. package/dist/server/__tests__/container.test.d.ts +1 -0
  7. package/dist/server/__tests__/container.test.js +170 -0
  8. package/dist/server/__tests__/lifecycle.test.d.ts +1 -0
  9. package/dist/server/__tests__/lifecycle.test.js +90 -0
  10. package/dist/server/__tests__/mount-routes.test.d.ts +1 -0
  11. package/dist/server/__tests__/mount-routes.test.js +151 -0
  12. package/dist/server/boot-config.d.ts +51 -0
  13. package/dist/server/boot-config.js +7 -0
  14. package/dist/server/container.d.ts +81 -0
  15. package/dist/server/container.js +134 -0
  16. package/dist/server/index.d.ts +33 -0
  17. package/dist/server/index.js +66 -0
  18. package/dist/server/lifecycle.d.ts +25 -0
  19. package/dist/server/lifecycle.js +46 -0
  20. package/dist/server/middleware/__tests__/admin-auth.test.d.ts +1 -0
  21. package/dist/server/middleware/__tests__/admin-auth.test.js +59 -0
  22. package/dist/server/middleware/__tests__/tenant-proxy.test.d.ts +1 -0
  23. package/dist/server/middleware/__tests__/tenant-proxy.test.js +268 -0
  24. package/dist/server/middleware/admin-auth.d.ts +18 -0
  25. package/dist/server/middleware/admin-auth.js +38 -0
  26. package/dist/server/middleware/tenant-proxy.d.ts +56 -0
  27. package/dist/server/middleware/tenant-proxy.js +162 -0
  28. package/dist/server/mount-routes.d.ts +30 -0
  29. package/dist/server/mount-routes.js +74 -0
  30. package/dist/server/routes/__tests__/admin.test.d.ts +1 -0
  31. package/dist/server/routes/__tests__/admin.test.js +267 -0
  32. package/dist/server/routes/__tests__/crypto-webhook.test.d.ts +1 -0
  33. package/dist/server/routes/__tests__/crypto-webhook.test.js +137 -0
  34. package/dist/server/routes/__tests__/provision-webhook.test.d.ts +1 -0
  35. package/dist/server/routes/__tests__/provision-webhook.test.js +212 -0
  36. package/dist/server/routes/__tests__/stripe-webhook.test.d.ts +1 -0
  37. package/dist/server/routes/__tests__/stripe-webhook.test.js +65 -0
  38. package/dist/server/routes/admin.d.ts +111 -0
  39. package/dist/server/routes/admin.js +273 -0
  40. package/dist/server/routes/crypto-webhook.d.ts +23 -0
  41. package/dist/server/routes/crypto-webhook.js +82 -0
  42. package/dist/server/routes/provision-webhook.d.ts +38 -0
  43. package/dist/server/routes/provision-webhook.js +160 -0
  44. package/dist/server/routes/stripe-webhook.d.ts +10 -0
  45. package/dist/server/routes/stripe-webhook.js +29 -0
  46. package/dist/server/test-container.d.ts +15 -0
  47. package/dist/server/test-container.js +103 -0
  48. package/dist/trpc/auth-helpers.d.ts +17 -0
  49. package/dist/trpc/auth-helpers.js +26 -0
  50. package/dist/trpc/container-factories.d.ts +300 -0
  51. package/dist/trpc/container-factories.js +80 -0
  52. package/dist/trpc/index.d.ts +2 -0
  53. package/dist/trpc/index.js +2 -0
  54. package/package.json +8 -3
  55. package/src/auth/better-auth.ts +8 -0
  56. package/src/email/client.ts +18 -0
  57. package/src/server/__tests__/build-container.test.ts +402 -0
  58. package/src/server/__tests__/container.test.ts +204 -0
  59. package/src/server/__tests__/lifecycle.test.ts +106 -0
  60. package/src/server/__tests__/mount-routes.test.ts +169 -0
  61. package/src/server/boot-config.ts +84 -0
  62. package/src/server/container.ts +237 -0
  63. package/src/server/index.ts +92 -0
  64. package/src/server/lifecycle.ts +62 -0
  65. package/src/server/middleware/__tests__/admin-auth.test.ts +67 -0
  66. package/src/server/middleware/__tests__/tenant-proxy.test.ts +308 -0
  67. package/src/server/middleware/admin-auth.ts +51 -0
  68. package/src/server/middleware/tenant-proxy.ts +192 -0
  69. package/src/server/mount-routes.ts +113 -0
  70. package/src/server/routes/__tests__/admin.test.ts +320 -0
  71. package/src/server/routes/__tests__/crypto-webhook.test.ts +167 -0
  72. package/src/server/routes/__tests__/provision-webhook.test.ts +323 -0
  73. package/src/server/routes/__tests__/stripe-webhook.test.ts +73 -0
  74. package/src/server/routes/admin.ts +334 -0
  75. package/src/server/routes/crypto-webhook.ts +110 -0
  76. package/src/server/routes/provision-webhook.ts +212 -0
  77. package/src/server/routes/stripe-webhook.ts +36 -0
  78. package/src/server/test-container.ts +120 -0
  79. package/src/trpc/auth-helpers.ts +28 -0
  80. package/src/trpc/container-factories.ts +114 -0
  81. package/src/trpc/index.ts +9 -0
@@ -0,0 +1,204 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import type { BootConfig, FeatureFlags } from "../boot-config.js";
3
+ import type { CryptoServices, FleetServices, GatewayServices, HotPoolServices, StripeServices } from "../container.js";
4
+ import { createTestContainer } from "../test-container.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // PlatformContainer interface
8
+ // ---------------------------------------------------------------------------
9
+
10
+ describe("PlatformContainer", () => {
11
+ it("allows null feature services", () => {
12
+ const c = createTestContainer();
13
+ expect(c.fleet).toBeNull();
14
+ expect(c.crypto).toBeNull();
15
+ expect(c.stripe).toBeNull();
16
+ expect(c.gateway).toBeNull();
17
+ expect(c.hotPool).toBeNull();
18
+ });
19
+
20
+ it("requires core services to be present", () => {
21
+ const c = createTestContainer();
22
+ expect(c.db).toBeDefined();
23
+ expect(c.pool).toBeDefined();
24
+ expect(c.productConfig).toBeDefined();
25
+ expect(c.creditLedger).toBeDefined();
26
+ expect(c.orgMemberRepo).toBeDefined();
27
+ expect(c.orgService).toBeDefined();
28
+ expect(c.userRoleRepo).toBeDefined();
29
+ });
30
+ });
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // BootConfig shape
34
+ // ---------------------------------------------------------------------------
35
+
36
+ describe("BootConfig", () => {
37
+ it("requires slug and provisionSecret", () => {
38
+ const config: BootConfig = {
39
+ slug: "test-product",
40
+ databaseUrl: "postgres://localhost/test",
41
+ provisionSecret: "s3cret",
42
+ features: {
43
+ fleet: false,
44
+ crypto: false,
45
+ stripe: false,
46
+ gateway: false,
47
+ hotPool: false,
48
+ },
49
+ };
50
+
51
+ expect(config.slug).toBe("test-product");
52
+ expect(config.provisionSecret).toBe("s3cret");
53
+ });
54
+
55
+ it("accepts optional fields", () => {
56
+ const config: BootConfig = {
57
+ slug: "full",
58
+ databaseUrl: "postgres://localhost/full",
59
+ provisionSecret: "secret",
60
+ host: "127.0.0.1",
61
+ port: 4000,
62
+ features: {
63
+ fleet: true,
64
+ crypto: true,
65
+ stripe: true,
66
+ gateway: true,
67
+ hotPool: true,
68
+ },
69
+ stripeSecretKey: "sk_test_xxx",
70
+ stripeWebhookSecret: "whsec_xxx",
71
+ cryptoServiceKey: "csk_xxx",
72
+ routes: [],
73
+ };
74
+
75
+ expect(config.host).toBe("127.0.0.1");
76
+ expect(config.port).toBe(4000);
77
+ expect(config.features.fleet).toBe(true);
78
+ });
79
+
80
+ it("FeatureFlags has all five toggles", () => {
81
+ const flags: FeatureFlags = {
82
+ fleet: true,
83
+ crypto: false,
84
+ stripe: true,
85
+ gateway: false,
86
+ hotPool: true,
87
+ };
88
+
89
+ expect(Object.keys(flags)).toHaveLength(5);
90
+ });
91
+ });
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // createTestContainer
95
+ // ---------------------------------------------------------------------------
96
+
97
+ describe("createTestContainer", () => {
98
+ it("returns valid defaults", () => {
99
+ const c = createTestContainer();
100
+
101
+ expect(c.fleet).toBeNull();
102
+ expect(c.crypto).toBeNull();
103
+ expect(c.stripe).toBeNull();
104
+ expect(c.gateway).toBeNull();
105
+ expect(c.hotPool).toBeNull();
106
+ expect(c.productConfig.product).toBeDefined();
107
+ });
108
+
109
+ it("allows overrides for core services", () => {
110
+ const customConfig = {
111
+ product: { slug: "custom", name: "Custom" } as never,
112
+ navItems: [],
113
+ domains: [],
114
+ features: null,
115
+ fleet: null,
116
+ billing: null,
117
+ };
118
+
119
+ const c = createTestContainer({ productConfig: customConfig });
120
+ expect(c.productConfig.product).toEqual({ slug: "custom", name: "Custom" });
121
+ });
122
+
123
+ it("stub ledger methods return sensible defaults", async () => {
124
+ const c = createTestContainer();
125
+
126
+ expect(await c.creditLedger.balance("t1")).toBe(0);
127
+ expect(await c.creditLedger.hasReferenceId("ref")).toBe(false);
128
+ expect(await c.creditLedger.history("t1")).toEqual([]);
129
+ expect(await c.creditLedger.tenantsWithBalance()).toEqual([]);
130
+ expect(await c.creditLedger.lifetimeSpendBatch([])).toEqual(new Map());
131
+ });
132
+
133
+ it("stub orgMemberRepo methods return sensible defaults", async () => {
134
+ const c = createTestContainer();
135
+
136
+ expect(await c.orgMemberRepo.findMember("org1", "u1")).toBeNull();
137
+ expect(await c.orgMemberRepo.listMembers("org1")).toEqual([]);
138
+ expect(await c.orgMemberRepo.countAdminsAndOwners("org1")).toBe(0);
139
+ });
140
+
141
+ it("stub userRoleRepo methods return sensible defaults", async () => {
142
+ const c = createTestContainer();
143
+
144
+ expect(await c.userRoleRepo.getTenantIdByUserId("u1")).toBeNull();
145
+ expect(await c.userRoleRepo.isPlatformAdmin("u1")).toBe(false);
146
+ expect(await c.userRoleRepo.listRolesByUser("u1")).toEqual([]);
147
+ });
148
+
149
+ it("allows enabling feature services via overrides", () => {
150
+ const fleet: FleetServices = {
151
+ manager: {} as never,
152
+ docker: {} as never,
153
+ proxy: {} as never,
154
+ profileStore: {} as never,
155
+ serviceKeyRepo: {} as never,
156
+ };
157
+
158
+ const crypto: CryptoServices = {
159
+ chargeRepo: {} as never,
160
+ webhookSeenRepo: {} as never,
161
+ };
162
+
163
+ const stripe: StripeServices = {
164
+ stripe: {} as never,
165
+ webhookSecret: "whsec_test",
166
+ customerRepo: {} as never,
167
+ processor: {} as never,
168
+ };
169
+
170
+ const gateway: GatewayServices = {
171
+ serviceKeyRepo: {} as never,
172
+ };
173
+
174
+ const hotPool: HotPoolServices = {
175
+ poolManager: {},
176
+ };
177
+
178
+ const c = createTestContainer({ fleet, crypto, stripe, gateway, hotPool });
179
+
180
+ expect(c.fleet).not.toBeNull();
181
+ expect(c.crypto).not.toBeNull();
182
+ expect(c.stripe).not.toBeNull();
183
+ expect(c.stripe?.webhookSecret).toBe("whsec_test");
184
+ expect(c.gateway).not.toBeNull();
185
+ expect(c.hotPool).not.toBeNull();
186
+ });
187
+
188
+ it("overrides merge without affecting other defaults", () => {
189
+ const c = createTestContainer({ gateway: { serviceKeyRepo: {} as never } });
190
+
191
+ // Overridden field
192
+ expect(c.gateway).not.toBeNull();
193
+
194
+ // Other feature services remain null
195
+ expect(c.fleet).toBeNull();
196
+ expect(c.crypto).toBeNull();
197
+ expect(c.stripe).toBeNull();
198
+ expect(c.hotPool).toBeNull();
199
+
200
+ // Core services still present
201
+ expect(c.creditLedger).toBeDefined();
202
+ expect(c.orgMemberRepo).toBeDefined();
203
+ });
204
+ });
@@ -0,0 +1,106 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { gracefulShutdown, startBackgroundServices } from "../lifecycle.js";
3
+ import { createTestContainer } from "../test-container.js";
4
+
5
+ describe("startBackgroundServices", () => {
6
+ it("returns a BackgroundHandles object", async () => {
7
+ const container = createTestContainer();
8
+ const handles = await startBackgroundServices(container);
9
+ expect(handles).toHaveProperty("intervals");
10
+ expect(handles).toHaveProperty("unsubscribes");
11
+ expect(Array.isArray(handles.intervals)).toBe(true);
12
+ expect(Array.isArray(handles.unsubscribes)).toBe(true);
13
+ });
14
+
15
+ it("calls proxy.start when fleet is enabled", async () => {
16
+ const startFn = vi.fn().mockResolvedValue(undefined);
17
+ const container = createTestContainer({
18
+ fleet: {
19
+ manager: {} as never,
20
+ docker: {} as never,
21
+ proxy: {
22
+ start: startFn,
23
+ addRoute: async () => {},
24
+ removeRoute: () => {},
25
+ getRoutes: () => [],
26
+ } as never,
27
+ profileStore: { list: async () => [] } as never,
28
+ serviceKeyRepo: {} as never,
29
+ },
30
+ });
31
+ await startBackgroundServices(container);
32
+ expect(startFn).toHaveBeenCalledOnce();
33
+ });
34
+
35
+ it("does not throw when proxy.start fails", async () => {
36
+ const container = createTestContainer({
37
+ fleet: {
38
+ manager: {} as never,
39
+ docker: {} as never,
40
+ proxy: {
41
+ start: async () => {
42
+ throw new Error("proxy start failed");
43
+ },
44
+ addRoute: async () => {},
45
+ removeRoute: () => {},
46
+ getRoutes: () => [],
47
+ } as never,
48
+ profileStore: { list: async () => [] } as never,
49
+ serviceKeyRepo: {} as never,
50
+ },
51
+ });
52
+ // Should not throw
53
+ const handles = await startBackgroundServices(container);
54
+ expect(handles).toBeDefined();
55
+ });
56
+ });
57
+
58
+ describe("gracefulShutdown", () => {
59
+ it("clears all intervals", async () => {
60
+ const container = createTestContainer();
61
+ const clearSpy = vi.spyOn(global, "clearInterval");
62
+
63
+ const interval1 = setInterval(() => {}, 10000);
64
+ const interval2 = setInterval(() => {}, 10000);
65
+
66
+ const handles = {
67
+ intervals: [interval1, interval2],
68
+ unsubscribes: [],
69
+ };
70
+
71
+ await gracefulShutdown(container, handles);
72
+
73
+ expect(clearSpy).toHaveBeenCalledWith(interval1);
74
+ expect(clearSpy).toHaveBeenCalledWith(interval2);
75
+ clearSpy.mockRestore();
76
+ });
77
+
78
+ it("calls all unsubscribe functions", async () => {
79
+ const container = createTestContainer();
80
+ const unsub1 = vi.fn();
81
+ const unsub2 = vi.fn();
82
+
83
+ const handles = {
84
+ intervals: [],
85
+ unsubscribes: [unsub1, unsub2],
86
+ };
87
+
88
+ await gracefulShutdown(container, handles);
89
+
90
+ expect(unsub1).toHaveBeenCalledOnce();
91
+ expect(unsub2).toHaveBeenCalledOnce();
92
+ });
93
+
94
+ it("calls pool.end()", async () => {
95
+ const endFn = vi.fn().mockResolvedValue(undefined);
96
+ const container = createTestContainer({
97
+ pool: { end: endFn } as never,
98
+ });
99
+
100
+ const handles = { intervals: [], unsubscribes: [] };
101
+
102
+ await gracefulShutdown(container, handles);
103
+
104
+ expect(endFn).toHaveBeenCalledOnce();
105
+ });
106
+ });
@@ -0,0 +1,169 @@
1
+ import { Hono } from "hono";
2
+ import { describe, expect, it } from "vitest";
3
+ import type { PlatformContainer } from "../container.js";
4
+ import { mountRoutes } from "../mount-routes.js";
5
+ import { createTestContainer } from "../test-container.js";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Helpers
9
+ // ---------------------------------------------------------------------------
10
+
11
+ function defaultMountConfig() {
12
+ return {
13
+ provisionSecret: "test-secret",
14
+ cryptoServiceKey: "test-crypto-key",
15
+ platformDomain: "example.com",
16
+ };
17
+ }
18
+
19
+ function makeApp(container: PlatformContainer) {
20
+ const app = new Hono();
21
+ mountRoutes(app, container, defaultMountConfig());
22
+ return app;
23
+ }
24
+
25
+ async function req(app: Hono, method: string, path: string, opts?: RequestInit) {
26
+ const request = new Request(`http://localhost${path}`, { method, ...opts });
27
+ return app.request(request);
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Minimal stubs for feature sub-containers
32
+ // ---------------------------------------------------------------------------
33
+
34
+ function stubCrypto(): PlatformContainer["crypto"] {
35
+ return {
36
+ chargeRepo: {} as never,
37
+ webhookSeenRepo: {} as never,
38
+ };
39
+ }
40
+
41
+ function stubStripe(): PlatformContainer["stripe"] {
42
+ return {
43
+ stripe: {} as never,
44
+ webhookSecret: "whsec_test",
45
+ customerRepo: {} as never,
46
+ processor: {
47
+ handleWebhook: async () => ({ ok: true }),
48
+ },
49
+ };
50
+ }
51
+
52
+ function stubFleet(): PlatformContainer["fleet"] {
53
+ return {
54
+ manager: {} as never,
55
+ docker: {} as never,
56
+ proxy: {
57
+ start: async () => {},
58
+ addRoute: async () => {},
59
+ removeRoute: () => {},
60
+ getRoutes: () => [],
61
+ } as never,
62
+ profileStore: { list: async () => [] } as never,
63
+ serviceKeyRepo: {} as never,
64
+ };
65
+ }
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Tests
69
+ // ---------------------------------------------------------------------------
70
+
71
+ describe("mountRoutes", () => {
72
+ // 1. Health endpoint always available
73
+ it("mounts /health endpoint", async () => {
74
+ const app = makeApp(createTestContainer());
75
+ const res = await req(app, "GET", "/health");
76
+ expect(res.status).toBe(200);
77
+ const body = await res.json();
78
+ expect(body).toEqual({ ok: true });
79
+ });
80
+
81
+ // 2. Crypto webhook mounted when crypto enabled
82
+ it("mounts crypto webhook when crypto enabled", async () => {
83
+ const container = createTestContainer({ crypto: stubCrypto() });
84
+ const app = makeApp(container);
85
+ const res = await req(app, "POST", "/api/webhooks/crypto", {
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify({}),
88
+ });
89
+ // Should return 401 (no auth), NOT 404 (not found)
90
+ expect(res.status).toBe(401);
91
+ });
92
+
93
+ // 3. Crypto webhook NOT mounted when crypto disabled
94
+ it("does not mount crypto webhook when crypto disabled", async () => {
95
+ const container = createTestContainer({ crypto: null });
96
+ const app = makeApp(container);
97
+ const res = await req(app, "POST", "/api/webhooks/crypto", {
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify({}),
100
+ });
101
+ expect(res.status).toBe(404);
102
+ });
103
+
104
+ // 4. Stripe webhook mounted when stripe enabled
105
+ it("mounts stripe webhook when stripe enabled", async () => {
106
+ const container = createTestContainer({ stripe: stubStripe() });
107
+ const app = makeApp(container);
108
+ const res = await req(app, "POST", "/api/webhooks/stripe", {
109
+ headers: { "Content-Type": "application/json" },
110
+ body: JSON.stringify({}),
111
+ });
112
+ // Should return 400 (missing stripe-signature), NOT 404
113
+ expect(res.status).toBe(400);
114
+ });
115
+
116
+ // 5. Stripe webhook NOT mounted when stripe disabled
117
+ it("does not mount stripe webhook when stripe disabled", async () => {
118
+ const container = createTestContainer({ stripe: null });
119
+ const app = makeApp(container);
120
+ const res = await req(app, "POST", "/api/webhooks/stripe", {
121
+ headers: { "Content-Type": "application/json" },
122
+ body: JSON.stringify({}),
123
+ });
124
+ expect(res.status).toBe(404);
125
+ });
126
+
127
+ // 6. Provision webhook mounted when fleet enabled
128
+ it("mounts provision webhook when fleet enabled", async () => {
129
+ const container = createTestContainer({ fleet: stubFleet() });
130
+ const app = makeApp(container);
131
+ const res = await req(app, "POST", "/api/provision/create", {
132
+ headers: { "Content-Type": "application/json" },
133
+ body: JSON.stringify({}),
134
+ });
135
+ // Should return 401 (no auth), NOT 404
136
+ expect(res.status).toBe(401);
137
+ });
138
+
139
+ // 7. Provision webhook NOT mounted when fleet disabled
140
+ it("does not mount provision webhook when fleet disabled", async () => {
141
+ const container = createTestContainer({ fleet: null });
142
+ const app = makeApp(container);
143
+ const res = await req(app, "POST", "/api/provision/create", {
144
+ headers: { "Content-Type": "application/json" },
145
+ body: JSON.stringify({}),
146
+ });
147
+ expect(res.status).toBe(404);
148
+ });
149
+
150
+ // 8. Product-specific route plugins
151
+ it("mounts product-specific route plugins", async () => {
152
+ const container = createTestContainer();
153
+ const app = new Hono();
154
+ mountRoutes(app, container, defaultMountConfig(), [
155
+ {
156
+ path: "/api/custom",
157
+ handler: () => {
158
+ const sub = new Hono();
159
+ sub.get("/ping", (c) => c.json({ pong: true }));
160
+ return sub;
161
+ },
162
+ },
163
+ ]);
164
+ const res = await req(app, "GET", "/api/custom/ping");
165
+ expect(res.status).toBe(200);
166
+ const body = await res.json();
167
+ expect(body).toEqual({ pong: true });
168
+ });
169
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * BootConfig — declarative configuration for platformBoot().
3
+ *
4
+ * Products pass a BootConfig describing which features to enable and
5
+ * receive back a fully-wired Hono app + PlatformContainer.
6
+ */
7
+
8
+ import type { Hono } from "hono";
9
+ import type { PlatformContainer } from "./container.js";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // Feature flags
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export interface FeatureFlags {
16
+ fleet: boolean;
17
+ crypto: boolean;
18
+ stripe: boolean;
19
+ gateway: boolean;
20
+ hotPool: boolean;
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Route plugins
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export interface RoutePlugin {
28
+ path: string;
29
+ handler: (container: PlatformContainer) => Hono;
30
+ }
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Boot config
34
+ // ---------------------------------------------------------------------------
35
+
36
+ export interface BootConfig {
37
+ /** Short product identifier (e.g. "paperclip", "wopr", "holyship"). */
38
+ slug: string;
39
+
40
+ /** PostgreSQL connection string. */
41
+ databaseUrl: string;
42
+
43
+ /** Bind host (default "0.0.0.0"). */
44
+ host?: string;
45
+
46
+ /** Bind port (default 3001). */
47
+ port?: number;
48
+
49
+ /** Which optional feature slices to wire up. */
50
+ features: FeatureFlags;
51
+
52
+ /** Additional Hono sub-apps mounted after core routes. */
53
+ routes?: RoutePlugin[];
54
+
55
+ /** Required when features.stripe is true. */
56
+ stripeSecretKey?: string;
57
+
58
+ /** Required when features.stripe is true. */
59
+ stripeWebhookSecret?: string;
60
+
61
+ /** Service key for the crypto chain server webhook endpoint. */
62
+ cryptoServiceKey?: string;
63
+
64
+ /** Shared secret used to authenticate provision requests. */
65
+ provisionSecret: string;
66
+ }
67
+
68
+ // ---------------------------------------------------------------------------
69
+ // Boot result
70
+ // ---------------------------------------------------------------------------
71
+
72
+ export interface BootResult {
73
+ /** The fully-wired Hono application. */
74
+ app: Hono;
75
+
76
+ /** The assembled DI container — useful for tests and ad-hoc access. */
77
+ container: PlatformContainer;
78
+
79
+ /** Start listening. Uses BootConfig.port unless overridden. */
80
+ start: (port?: number) => Promise<void>;
81
+
82
+ /** Graceful shutdown: drain connections, close pool. */
83
+ stop: () => Promise<void>;
84
+ }