@wopr-network/platform-core 1.58.0 → 1.59.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 (47) hide show
  1. package/dist/billing/crypto/key-server-entry.js +1 -0
  2. package/dist/billing/crypto/watcher-service.d.ts +2 -0
  3. package/dist/billing/crypto/watcher-service.js +7 -3
  4. package/dist/db/schema/index.d.ts +2 -0
  5. package/dist/db/schema/index.js +2 -0
  6. package/dist/db/schema/product-config.d.ts +610 -0
  7. package/dist/db/schema/product-config.js +51 -0
  8. package/dist/db/schema/products.d.ts +565 -0
  9. package/dist/db/schema/products.js +43 -0
  10. package/dist/product-config/boot.d.ts +36 -0
  11. package/dist/product-config/boot.js +30 -0
  12. package/dist/product-config/drizzle-product-config-repository.d.ts +19 -0
  13. package/dist/product-config/drizzle-product-config-repository.js +200 -0
  14. package/dist/product-config/drizzle-product-config-repository.test.d.ts +1 -0
  15. package/dist/product-config/drizzle-product-config-repository.test.js +114 -0
  16. package/dist/product-config/index.d.ts +24 -0
  17. package/dist/product-config/index.js +37 -0
  18. package/dist/product-config/repository-types.d.ts +143 -0
  19. package/dist/product-config/repository-types.js +53 -0
  20. package/dist/product-config/service.d.ts +27 -0
  21. package/dist/product-config/service.js +74 -0
  22. package/dist/product-config/service.test.d.ts +1 -0
  23. package/dist/product-config/service.test.js +107 -0
  24. package/dist/trpc/index.d.ts +1 -0
  25. package/dist/trpc/index.js +1 -0
  26. package/dist/trpc/product-config-router.d.ts +117 -0
  27. package/dist/trpc/product-config-router.js +137 -0
  28. package/docs/specs/2026-03-23-product-config-db-migration-plan.md +2260 -0
  29. package/docs/specs/2026-03-23-product-config-db-migration.md +371 -0
  30. package/drizzle/migrations/0020_product_config_tables.sql +109 -0
  31. package/drizzle/migrations/meta/_journal.json +7 -0
  32. package/package.json +1 -1
  33. package/scripts/seed-products.ts +268 -0
  34. package/src/billing/crypto/key-server-entry.ts +1 -0
  35. package/src/billing/crypto/watcher-service.ts +8 -2
  36. package/src/db/schema/index.ts +2 -0
  37. package/src/db/schema/product-config.ts +56 -0
  38. package/src/db/schema/products.ts +58 -0
  39. package/src/product-config/boot.ts +57 -0
  40. package/src/product-config/drizzle-product-config-repository.test.ts +132 -0
  41. package/src/product-config/drizzle-product-config-repository.ts +229 -0
  42. package/src/product-config/index.ts +62 -0
  43. package/src/product-config/repository-types.ts +222 -0
  44. package/src/product-config/service.test.ts +127 -0
  45. package/src/product-config/service.ts +105 -0
  46. package/src/trpc/index.ts +1 -0
  47. package/src/trpc/product-config-router.ts +161 -0
@@ -0,0 +1,51 @@
1
+ import { boolean, integer, jsonb, numeric, pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
2
+ import { products } from "./products.js";
3
+ export const fleetLifecycleEnum = pgEnum("fleet_lifecycle", ["managed", "ephemeral"]);
4
+ export const fleetBillingModelEnum = pgEnum("fleet_billing_model", ["monthly", "per_use", "none"]);
5
+ export const productFeatures = pgTable("product_features", {
6
+ productId: uuid("product_id")
7
+ .primaryKey()
8
+ .references(() => products.id, { onDelete: "cascade" }),
9
+ chatEnabled: boolean("chat_enabled").notNull().default(true),
10
+ onboardingEnabled: boolean("onboarding_enabled").notNull().default(true),
11
+ onboardingDefaultModel: text("onboarding_default_model"),
12
+ onboardingSystemPrompt: text("onboarding_system_prompt"),
13
+ onboardingMaxCredits: integer("onboarding_max_credits").notNull().default(100),
14
+ onboardingWelcomeMsg: text("onboarding_welcome_msg"),
15
+ sharedModuleBilling: boolean("shared_module_billing").notNull().default(true),
16
+ sharedModuleMonitoring: boolean("shared_module_monitoring").notNull().default(true),
17
+ sharedModuleAnalytics: boolean("shared_module_analytics").notNull().default(true),
18
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
19
+ });
20
+ export const productFleetConfig = pgTable("product_fleet_config", {
21
+ productId: uuid("product_id")
22
+ .primaryKey()
23
+ .references(() => products.id, { onDelete: "cascade" }),
24
+ containerImage: text("container_image").notNull(),
25
+ containerPort: integer("container_port").notNull().default(3100),
26
+ lifecycle: fleetLifecycleEnum("lifecycle").notNull().default("managed"),
27
+ billingModel: fleetBillingModelEnum("billing_model").notNull().default("monthly"),
28
+ maxInstances: integer("max_instances").notNull().default(5),
29
+ imageAllowlist: text("image_allowlist").array(),
30
+ dockerNetwork: text("docker_network").notNull().default(""),
31
+ placementStrategy: text("placement_strategy").notNull().default("least-loaded"),
32
+ fleetDataDir: text("fleet_data_dir").notNull().default("/data/fleet"),
33
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
34
+ });
35
+ export const productBillingConfig = pgTable("product_billing_config", {
36
+ productId: uuid("product_id")
37
+ .primaryKey()
38
+ .references(() => products.id, { onDelete: "cascade" }),
39
+ stripePublishableKey: text("stripe_publishable_key"),
40
+ /** Encrypted via credential vault (CRYPTO_SERVICE_KEY) before storage. */
41
+ stripeSecretKey: text("stripe_secret_key"),
42
+ /** Encrypted via credential vault (CRYPTO_SERVICE_KEY) before storage. */
43
+ stripeWebhookSecret: text("stripe_webhook_secret"),
44
+ creditPrices: jsonb("credit_prices").notNull().default({}),
45
+ affiliateBaseUrl: text("affiliate_base_url"),
46
+ affiliateMatchRate: numeric("affiliate_match_rate").notNull().default("1.0"),
47
+ affiliateMaxCap: integer("affiliate_max_cap").notNull().default(20000),
48
+ dividendRate: numeric("dividend_rate").notNull().default("1.0"),
49
+ marginConfig: jsonb("margin_config"),
50
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
51
+ });
@@ -0,0 +1,565 @@
1
+ export declare const products: import("drizzle-orm/pg-core").PgTableWithColumns<{
2
+ name: "products";
3
+ schema: undefined;
4
+ columns: {
5
+ id: import("drizzle-orm/pg-core").PgColumn<{
6
+ name: "id";
7
+ tableName: "products";
8
+ dataType: "string";
9
+ columnType: "PgUUID";
10
+ data: string;
11
+ driverParam: string;
12
+ notNull: true;
13
+ hasDefault: true;
14
+ isPrimaryKey: true;
15
+ isAutoincrement: false;
16
+ hasRuntimeDefault: false;
17
+ enumValues: undefined;
18
+ baseColumn: never;
19
+ identity: undefined;
20
+ generated: undefined;
21
+ }, {}, {}>;
22
+ slug: import("drizzle-orm/pg-core").PgColumn<{
23
+ name: "slug";
24
+ tableName: "products";
25
+ dataType: "string";
26
+ columnType: "PgText";
27
+ data: string;
28
+ driverParam: string;
29
+ notNull: true;
30
+ hasDefault: false;
31
+ isPrimaryKey: false;
32
+ isAutoincrement: false;
33
+ hasRuntimeDefault: false;
34
+ enumValues: [string, ...string[]];
35
+ baseColumn: never;
36
+ identity: undefined;
37
+ generated: undefined;
38
+ }, {}, {}>;
39
+ brandName: import("drizzle-orm/pg-core").PgColumn<{
40
+ name: "brand_name";
41
+ tableName: "products";
42
+ dataType: "string";
43
+ columnType: "PgText";
44
+ data: string;
45
+ driverParam: string;
46
+ notNull: true;
47
+ hasDefault: false;
48
+ isPrimaryKey: false;
49
+ isAutoincrement: false;
50
+ hasRuntimeDefault: false;
51
+ enumValues: [string, ...string[]];
52
+ baseColumn: never;
53
+ identity: undefined;
54
+ generated: undefined;
55
+ }, {}, {}>;
56
+ productName: import("drizzle-orm/pg-core").PgColumn<{
57
+ name: "product_name";
58
+ tableName: "products";
59
+ dataType: "string";
60
+ columnType: "PgText";
61
+ data: string;
62
+ driverParam: string;
63
+ notNull: true;
64
+ hasDefault: false;
65
+ isPrimaryKey: false;
66
+ isAutoincrement: false;
67
+ hasRuntimeDefault: false;
68
+ enumValues: [string, ...string[]];
69
+ baseColumn: never;
70
+ identity: undefined;
71
+ generated: undefined;
72
+ }, {}, {}>;
73
+ tagline: import("drizzle-orm/pg-core").PgColumn<{
74
+ name: "tagline";
75
+ tableName: "products";
76
+ dataType: "string";
77
+ columnType: "PgText";
78
+ data: string;
79
+ driverParam: string;
80
+ notNull: true;
81
+ hasDefault: true;
82
+ isPrimaryKey: false;
83
+ isAutoincrement: false;
84
+ hasRuntimeDefault: false;
85
+ enumValues: [string, ...string[]];
86
+ baseColumn: never;
87
+ identity: undefined;
88
+ generated: undefined;
89
+ }, {}, {}>;
90
+ domain: import("drizzle-orm/pg-core").PgColumn<{
91
+ name: "domain";
92
+ tableName: "products";
93
+ dataType: "string";
94
+ columnType: "PgText";
95
+ data: string;
96
+ driverParam: string;
97
+ notNull: true;
98
+ hasDefault: false;
99
+ isPrimaryKey: false;
100
+ isAutoincrement: false;
101
+ hasRuntimeDefault: false;
102
+ enumValues: [string, ...string[]];
103
+ baseColumn: never;
104
+ identity: undefined;
105
+ generated: undefined;
106
+ }, {}, {}>;
107
+ appDomain: import("drizzle-orm/pg-core").PgColumn<{
108
+ name: "app_domain";
109
+ tableName: "products";
110
+ dataType: "string";
111
+ columnType: "PgText";
112
+ data: string;
113
+ driverParam: string;
114
+ notNull: true;
115
+ hasDefault: false;
116
+ isPrimaryKey: false;
117
+ isAutoincrement: false;
118
+ hasRuntimeDefault: false;
119
+ enumValues: [string, ...string[]];
120
+ baseColumn: never;
121
+ identity: undefined;
122
+ generated: undefined;
123
+ }, {}, {}>;
124
+ cookieDomain: import("drizzle-orm/pg-core").PgColumn<{
125
+ name: "cookie_domain";
126
+ tableName: "products";
127
+ dataType: "string";
128
+ columnType: "PgText";
129
+ data: string;
130
+ driverParam: string;
131
+ notNull: true;
132
+ hasDefault: false;
133
+ isPrimaryKey: false;
134
+ isAutoincrement: false;
135
+ hasRuntimeDefault: false;
136
+ enumValues: [string, ...string[]];
137
+ baseColumn: never;
138
+ identity: undefined;
139
+ generated: undefined;
140
+ }, {}, {}>;
141
+ companyLegal: import("drizzle-orm/pg-core").PgColumn<{
142
+ name: "company_legal";
143
+ tableName: "products";
144
+ dataType: "string";
145
+ columnType: "PgText";
146
+ data: string;
147
+ driverParam: string;
148
+ notNull: true;
149
+ hasDefault: true;
150
+ isPrimaryKey: false;
151
+ isAutoincrement: false;
152
+ hasRuntimeDefault: false;
153
+ enumValues: [string, ...string[]];
154
+ baseColumn: never;
155
+ identity: undefined;
156
+ generated: undefined;
157
+ }, {}, {}>;
158
+ priceLabel: import("drizzle-orm/pg-core").PgColumn<{
159
+ name: "price_label";
160
+ tableName: "products";
161
+ dataType: "string";
162
+ columnType: "PgText";
163
+ data: string;
164
+ driverParam: string;
165
+ notNull: true;
166
+ hasDefault: true;
167
+ isPrimaryKey: false;
168
+ isAutoincrement: false;
169
+ hasRuntimeDefault: false;
170
+ enumValues: [string, ...string[]];
171
+ baseColumn: never;
172
+ identity: undefined;
173
+ generated: undefined;
174
+ }, {}, {}>;
175
+ defaultImage: import("drizzle-orm/pg-core").PgColumn<{
176
+ name: "default_image";
177
+ tableName: "products";
178
+ dataType: "string";
179
+ columnType: "PgText";
180
+ data: string;
181
+ driverParam: string;
182
+ notNull: true;
183
+ hasDefault: true;
184
+ isPrimaryKey: false;
185
+ isAutoincrement: false;
186
+ hasRuntimeDefault: false;
187
+ enumValues: [string, ...string[]];
188
+ baseColumn: never;
189
+ identity: undefined;
190
+ generated: undefined;
191
+ }, {}, {}>;
192
+ emailSupport: import("drizzle-orm/pg-core").PgColumn<{
193
+ name: "email_support";
194
+ tableName: "products";
195
+ dataType: "string";
196
+ columnType: "PgText";
197
+ data: string;
198
+ driverParam: string;
199
+ notNull: true;
200
+ hasDefault: true;
201
+ isPrimaryKey: false;
202
+ isAutoincrement: false;
203
+ hasRuntimeDefault: false;
204
+ enumValues: [string, ...string[]];
205
+ baseColumn: never;
206
+ identity: undefined;
207
+ generated: undefined;
208
+ }, {}, {}>;
209
+ emailPrivacy: import("drizzle-orm/pg-core").PgColumn<{
210
+ name: "email_privacy";
211
+ tableName: "products";
212
+ dataType: "string";
213
+ columnType: "PgText";
214
+ data: string;
215
+ driverParam: string;
216
+ notNull: true;
217
+ hasDefault: true;
218
+ isPrimaryKey: false;
219
+ isAutoincrement: false;
220
+ hasRuntimeDefault: false;
221
+ enumValues: [string, ...string[]];
222
+ baseColumn: never;
223
+ identity: undefined;
224
+ generated: undefined;
225
+ }, {}, {}>;
226
+ emailLegal: import("drizzle-orm/pg-core").PgColumn<{
227
+ name: "email_legal";
228
+ tableName: "products";
229
+ dataType: "string";
230
+ columnType: "PgText";
231
+ data: string;
232
+ driverParam: string;
233
+ notNull: true;
234
+ hasDefault: true;
235
+ isPrimaryKey: false;
236
+ isAutoincrement: false;
237
+ hasRuntimeDefault: false;
238
+ enumValues: [string, ...string[]];
239
+ baseColumn: never;
240
+ identity: undefined;
241
+ generated: undefined;
242
+ }, {}, {}>;
243
+ fromEmail: import("drizzle-orm/pg-core").PgColumn<{
244
+ name: "from_email";
245
+ tableName: "products";
246
+ dataType: "string";
247
+ columnType: "PgText";
248
+ data: string;
249
+ driverParam: string;
250
+ notNull: true;
251
+ hasDefault: true;
252
+ isPrimaryKey: false;
253
+ isAutoincrement: false;
254
+ hasRuntimeDefault: false;
255
+ enumValues: [string, ...string[]];
256
+ baseColumn: never;
257
+ identity: undefined;
258
+ generated: undefined;
259
+ }, {}, {}>;
260
+ homePath: import("drizzle-orm/pg-core").PgColumn<{
261
+ name: "home_path";
262
+ tableName: "products";
263
+ dataType: "string";
264
+ columnType: "PgText";
265
+ data: string;
266
+ driverParam: string;
267
+ notNull: true;
268
+ hasDefault: true;
269
+ isPrimaryKey: false;
270
+ isAutoincrement: false;
271
+ hasRuntimeDefault: false;
272
+ enumValues: [string, ...string[]];
273
+ baseColumn: never;
274
+ identity: undefined;
275
+ generated: undefined;
276
+ }, {}, {}>;
277
+ storagePrefix: import("drizzle-orm/pg-core").PgColumn<{
278
+ name: "storage_prefix";
279
+ tableName: "products";
280
+ dataType: "string";
281
+ columnType: "PgText";
282
+ data: string;
283
+ driverParam: string;
284
+ notNull: true;
285
+ hasDefault: false;
286
+ isPrimaryKey: false;
287
+ isAutoincrement: false;
288
+ hasRuntimeDefault: false;
289
+ enumValues: [string, ...string[]];
290
+ baseColumn: never;
291
+ identity: undefined;
292
+ generated: undefined;
293
+ }, {}, {}>;
294
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
295
+ name: "created_at";
296
+ tableName: "products";
297
+ dataType: "date";
298
+ columnType: "PgTimestamp";
299
+ data: Date;
300
+ driverParam: string;
301
+ notNull: true;
302
+ hasDefault: true;
303
+ isPrimaryKey: false;
304
+ isAutoincrement: false;
305
+ hasRuntimeDefault: false;
306
+ enumValues: undefined;
307
+ baseColumn: never;
308
+ identity: undefined;
309
+ generated: undefined;
310
+ }, {}, {}>;
311
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
312
+ name: "updated_at";
313
+ tableName: "products";
314
+ dataType: "date";
315
+ columnType: "PgTimestamp";
316
+ data: Date;
317
+ driverParam: string;
318
+ notNull: true;
319
+ hasDefault: true;
320
+ isPrimaryKey: false;
321
+ isAutoincrement: false;
322
+ hasRuntimeDefault: false;
323
+ enumValues: undefined;
324
+ baseColumn: never;
325
+ identity: undefined;
326
+ generated: undefined;
327
+ }, {}, {}>;
328
+ };
329
+ dialect: "pg";
330
+ }>;
331
+ export declare const productNavItems: import("drizzle-orm/pg-core").PgTableWithColumns<{
332
+ name: "product_nav_items";
333
+ schema: undefined;
334
+ columns: {
335
+ id: import("drizzle-orm/pg-core").PgColumn<{
336
+ name: "id";
337
+ tableName: "product_nav_items";
338
+ dataType: "string";
339
+ columnType: "PgUUID";
340
+ data: string;
341
+ driverParam: string;
342
+ notNull: true;
343
+ hasDefault: true;
344
+ isPrimaryKey: true;
345
+ isAutoincrement: false;
346
+ hasRuntimeDefault: false;
347
+ enumValues: undefined;
348
+ baseColumn: never;
349
+ identity: undefined;
350
+ generated: undefined;
351
+ }, {}, {}>;
352
+ productId: import("drizzle-orm/pg-core").PgColumn<{
353
+ name: "product_id";
354
+ tableName: "product_nav_items";
355
+ dataType: "string";
356
+ columnType: "PgUUID";
357
+ data: string;
358
+ driverParam: string;
359
+ notNull: true;
360
+ hasDefault: false;
361
+ isPrimaryKey: false;
362
+ isAutoincrement: false;
363
+ hasRuntimeDefault: false;
364
+ enumValues: undefined;
365
+ baseColumn: never;
366
+ identity: undefined;
367
+ generated: undefined;
368
+ }, {}, {}>;
369
+ label: import("drizzle-orm/pg-core").PgColumn<{
370
+ name: "label";
371
+ tableName: "product_nav_items";
372
+ dataType: "string";
373
+ columnType: "PgText";
374
+ data: string;
375
+ driverParam: string;
376
+ notNull: true;
377
+ hasDefault: false;
378
+ isPrimaryKey: false;
379
+ isAutoincrement: false;
380
+ hasRuntimeDefault: false;
381
+ enumValues: [string, ...string[]];
382
+ baseColumn: never;
383
+ identity: undefined;
384
+ generated: undefined;
385
+ }, {}, {}>;
386
+ href: import("drizzle-orm/pg-core").PgColumn<{
387
+ name: "href";
388
+ tableName: "product_nav_items";
389
+ dataType: "string";
390
+ columnType: "PgText";
391
+ data: string;
392
+ driverParam: string;
393
+ notNull: true;
394
+ hasDefault: false;
395
+ isPrimaryKey: false;
396
+ isAutoincrement: false;
397
+ hasRuntimeDefault: false;
398
+ enumValues: [string, ...string[]];
399
+ baseColumn: never;
400
+ identity: undefined;
401
+ generated: undefined;
402
+ }, {}, {}>;
403
+ icon: import("drizzle-orm/pg-core").PgColumn<{
404
+ name: "icon";
405
+ tableName: "product_nav_items";
406
+ dataType: "string";
407
+ columnType: "PgText";
408
+ data: string;
409
+ driverParam: string;
410
+ notNull: false;
411
+ hasDefault: false;
412
+ isPrimaryKey: false;
413
+ isAutoincrement: false;
414
+ hasRuntimeDefault: false;
415
+ enumValues: [string, ...string[]];
416
+ baseColumn: never;
417
+ identity: undefined;
418
+ generated: undefined;
419
+ }, {}, {}>;
420
+ sortOrder: import("drizzle-orm/pg-core").PgColumn<{
421
+ name: "sort_order";
422
+ tableName: "product_nav_items";
423
+ dataType: "number";
424
+ columnType: "PgInteger";
425
+ data: number;
426
+ driverParam: string | number;
427
+ notNull: true;
428
+ hasDefault: false;
429
+ isPrimaryKey: false;
430
+ isAutoincrement: false;
431
+ hasRuntimeDefault: false;
432
+ enumValues: undefined;
433
+ baseColumn: never;
434
+ identity: undefined;
435
+ generated: undefined;
436
+ }, {}, {}>;
437
+ requiresRole: import("drizzle-orm/pg-core").PgColumn<{
438
+ name: "requires_role";
439
+ tableName: "product_nav_items";
440
+ dataType: "string";
441
+ columnType: "PgText";
442
+ data: string;
443
+ driverParam: string;
444
+ notNull: false;
445
+ hasDefault: false;
446
+ isPrimaryKey: false;
447
+ isAutoincrement: false;
448
+ hasRuntimeDefault: false;
449
+ enumValues: [string, ...string[]];
450
+ baseColumn: never;
451
+ identity: undefined;
452
+ generated: undefined;
453
+ }, {}, {}>;
454
+ enabled: import("drizzle-orm/pg-core").PgColumn<{
455
+ name: "enabled";
456
+ tableName: "product_nav_items";
457
+ dataType: "boolean";
458
+ columnType: "PgBoolean";
459
+ data: boolean;
460
+ driverParam: boolean;
461
+ notNull: true;
462
+ hasDefault: true;
463
+ isPrimaryKey: false;
464
+ isAutoincrement: false;
465
+ hasRuntimeDefault: false;
466
+ enumValues: undefined;
467
+ baseColumn: never;
468
+ identity: undefined;
469
+ generated: undefined;
470
+ }, {}, {}>;
471
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
472
+ name: "created_at";
473
+ tableName: "product_nav_items";
474
+ dataType: "date";
475
+ columnType: "PgTimestamp";
476
+ data: Date;
477
+ driverParam: string;
478
+ notNull: true;
479
+ hasDefault: true;
480
+ isPrimaryKey: false;
481
+ isAutoincrement: false;
482
+ hasRuntimeDefault: false;
483
+ enumValues: undefined;
484
+ baseColumn: never;
485
+ identity: undefined;
486
+ generated: undefined;
487
+ }, {}, {}>;
488
+ };
489
+ dialect: "pg";
490
+ }>;
491
+ export declare const productDomains: import("drizzle-orm/pg-core").PgTableWithColumns<{
492
+ name: "product_domains";
493
+ schema: undefined;
494
+ columns: {
495
+ id: import("drizzle-orm/pg-core").PgColumn<{
496
+ name: "id";
497
+ tableName: "product_domains";
498
+ dataType: "string";
499
+ columnType: "PgUUID";
500
+ data: string;
501
+ driverParam: string;
502
+ notNull: true;
503
+ hasDefault: true;
504
+ isPrimaryKey: true;
505
+ isAutoincrement: false;
506
+ hasRuntimeDefault: false;
507
+ enumValues: undefined;
508
+ baseColumn: never;
509
+ identity: undefined;
510
+ generated: undefined;
511
+ }, {}, {}>;
512
+ productId: import("drizzle-orm/pg-core").PgColumn<{
513
+ name: "product_id";
514
+ tableName: "product_domains";
515
+ dataType: "string";
516
+ columnType: "PgUUID";
517
+ data: string;
518
+ driverParam: string;
519
+ notNull: true;
520
+ hasDefault: false;
521
+ isPrimaryKey: false;
522
+ isAutoincrement: false;
523
+ hasRuntimeDefault: false;
524
+ enumValues: undefined;
525
+ baseColumn: never;
526
+ identity: undefined;
527
+ generated: undefined;
528
+ }, {}, {}>;
529
+ host: import("drizzle-orm/pg-core").PgColumn<{
530
+ name: "host";
531
+ tableName: "product_domains";
532
+ dataType: "string";
533
+ columnType: "PgText";
534
+ data: string;
535
+ driverParam: string;
536
+ notNull: true;
537
+ hasDefault: false;
538
+ isPrimaryKey: false;
539
+ isAutoincrement: false;
540
+ hasRuntimeDefault: false;
541
+ enumValues: [string, ...string[]];
542
+ baseColumn: never;
543
+ identity: undefined;
544
+ generated: undefined;
545
+ }, {}, {}>;
546
+ role: import("drizzle-orm/pg-core").PgColumn<{
547
+ name: "role";
548
+ tableName: "product_domains";
549
+ dataType: "string";
550
+ columnType: "PgText";
551
+ data: string;
552
+ driverParam: string;
553
+ notNull: true;
554
+ hasDefault: true;
555
+ isPrimaryKey: false;
556
+ isAutoincrement: false;
557
+ hasRuntimeDefault: false;
558
+ enumValues: [string, ...string[]];
559
+ baseColumn: never;
560
+ identity: undefined;
561
+ generated: undefined;
562
+ }, {}, {}>;
563
+ };
564
+ dialect: "pg";
565
+ }>;
@@ -0,0 +1,43 @@
1
+ import { boolean, index, integer, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
2
+ export const products = pgTable("products", {
3
+ id: uuid("id").primaryKey().defaultRandom(),
4
+ slug: text("slug").notNull(),
5
+ brandName: text("brand_name").notNull(),
6
+ productName: text("product_name").notNull(),
7
+ tagline: text("tagline").notNull().default(""),
8
+ domain: text("domain").notNull(),
9
+ appDomain: text("app_domain").notNull(),
10
+ cookieDomain: text("cookie_domain").notNull(),
11
+ companyLegal: text("company_legal").notNull().default(""),
12
+ priceLabel: text("price_label").notNull().default(""),
13
+ defaultImage: text("default_image").notNull().default(""),
14
+ emailSupport: text("email_support").notNull().default(""),
15
+ emailPrivacy: text("email_privacy").notNull().default(""),
16
+ emailLegal: text("email_legal").notNull().default(""),
17
+ fromEmail: text("from_email").notNull().default(""),
18
+ homePath: text("home_path").notNull().default("/marketplace"),
19
+ storagePrefix: text("storage_prefix").notNull(),
20
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
21
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
22
+ }, (t) => [uniqueIndex("products_slug_idx").on(t.slug)]);
23
+ export const productNavItems = pgTable("product_nav_items", {
24
+ id: uuid("id").primaryKey().defaultRandom(),
25
+ productId: uuid("product_id")
26
+ .notNull()
27
+ .references(() => products.id, { onDelete: "cascade" }),
28
+ label: text("label").notNull(),
29
+ href: text("href").notNull(),
30
+ icon: text("icon"),
31
+ sortOrder: integer("sort_order").notNull(),
32
+ requiresRole: text("requires_role"),
33
+ enabled: boolean("enabled").notNull().default(true),
34
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
35
+ }, (t) => [index("product_nav_items_product_sort_idx").on(t.productId, t.sortOrder)]);
36
+ export const productDomains = pgTable("product_domains", {
37
+ id: uuid("id").primaryKey().defaultRandom(),
38
+ productId: uuid("product_id")
39
+ .notNull()
40
+ .references(() => products.id, { onDelete: "cascade" }),
41
+ host: text("host").notNull(),
42
+ role: text("role").notNull().default("canonical"),
43
+ }, (t) => [uniqueIndex("product_domains_product_host_idx").on(t.productId, t.host)]);
@@ -0,0 +1,36 @@
1
+ import type { DrizzleDb } from "../db/index.js";
2
+ import type { ProductConfig } from "./repository-types.js";
3
+ import { ProductConfigService } from "./service.js";
4
+ export interface PlatformBootResult {
5
+ /** The product config service — single point of access for config reads/writes. */
6
+ service: ProductConfigService;
7
+ /** The resolved product config (cached). */
8
+ config: ProductConfig;
9
+ /** CORS origins derived from product domains + optional dev origins. */
10
+ corsOrigins: string[];
11
+ }
12
+ export interface PlatformBootOptions {
13
+ /** Product slug (e.g. "paperclip", "wopr", "holyship", "nemoclaw"). */
14
+ slug: string;
15
+ /** Drizzle database instance. */
16
+ db: DrizzleDb;
17
+ /** Additional CORS origins for local dev (from DEV_ORIGINS env var). */
18
+ devOrigins?: string[];
19
+ }
20
+ /**
21
+ * Bootstrap product configuration from DB.
22
+ *
23
+ * Call once at startup, after DB + migrations, before route registration.
24
+ * Returns the service (for tRPC router wiring) and the resolved config
25
+ * (for CORS, email, fleet, auth initialization).
26
+ *
27
+ * This replaces: BRAND_NAME, PLATFORM_DOMAIN, UI_ORIGIN, FROM_EMAIL,
28
+ * SUPPORT_EMAIL, COOKIE_DOMAIN, APP_BASE_URL, and all other product-
29
+ * specific env vars that platform-core modules previously read from
30
+ * process.env.
31
+ *
32
+ * Product backends still own their specific wiring (crypto watchers,
33
+ * fleet updaters, notification pipelines). platformBoot handles the
34
+ * config-driven parts that are identical across products.
35
+ */
36
+ export declare function platformBoot(opts: PlatformBootOptions): Promise<PlatformBootResult>;