@suluk/platform 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generate.ts +6 -0
- package/src/plan.ts +23 -0
- package/src/service.ts +17 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suluk/platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "The platform generator (C051): write one `definePlatform` manifest → it plans the shadcn-registry adds, generates the wired Hono entry, and merges each module's provision fragment into a single provision.config. The manifest compiles to a shadcn-add list + a C047 provision.config; the generator runs the adds + `@suluk/provision`. Turns the Suluk backend registry into a one-command platform. CANDIDATE tooling.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
package/src/generate.ts
CHANGED
|
@@ -85,6 +85,12 @@ export async function generatePlatform(input: PlatformManifest | Platform, opts:
|
|
|
85
85
|
log("▸ writing provision.config.ts");
|
|
86
86
|
await opts.write("provision.config.ts", plan.provisionConfig);
|
|
87
87
|
written.push("src/index.ts", "provision.config.ts");
|
|
88
|
+
// the composed contract surface (one fragment per module) — only when the `contract` service is installed.
|
|
89
|
+
if (plan.contractOps) {
|
|
90
|
+
log("▸ writing src/contract.ops.ts");
|
|
91
|
+
await opts.write("src/contract.ops.ts", plan.contractOps);
|
|
92
|
+
written.push("src/contract.ops.ts");
|
|
93
|
+
}
|
|
88
94
|
// the bun MOCK-PROVIDER dev server + the state-purge helper — only when the manifest sets `local: true`.
|
|
89
95
|
if (plan.devEntry) {
|
|
90
96
|
log("▸ writing src/dev.ts");
|
package/src/plan.ts
CHANGED
|
@@ -16,6 +16,9 @@ export interface PlatformPlan {
|
|
|
16
16
|
entry: string;
|
|
17
17
|
/** the generated `provision.config.ts` content. */
|
|
18
18
|
provisionConfig: string;
|
|
19
|
+
/** the generated `src/contract.ops.ts` — the COMPOSED contract surface (one `RouteContract[]` fragment per module). Present
|
|
20
|
+
* ONLY when the `contract` service is installed; the base `src/contract.ts` consumes its `ALL_OPS`. */
|
|
21
|
+
contractOps?: string;
|
|
19
22
|
/** the generated `package.json` content (the FRAMEWORK baseline — `generate` merges it with any existing so app-added
|
|
20
23
|
* deps/scripts survive). @suluk/* on "latest" so fixes flow via `bun update`; ecosystem deps on pinned ranges. */
|
|
21
24
|
packageJson: string;
|
|
@@ -88,6 +91,7 @@ export function planPlatform(input: PlatformManifest | Platform): PlatformPlan {
|
|
|
88
91
|
adds: services.map((s) => `${catalog[s].registry ?? manifest.registry}/${s}`),
|
|
89
92
|
entry: buildEntry(services, manifest.opts, wiring, catalog, local),
|
|
90
93
|
provisionConfig: buildProvisionConfig(services, catalog),
|
|
94
|
+
...(services.includes("contract") ? { contractOps: buildContractOps(services, catalog) } : {}),
|
|
91
95
|
packageJson: buildPackageJson(manifest.name, services, catalog, local),
|
|
92
96
|
tsconfig: buildTsconfig(local),
|
|
93
97
|
componentsJson: buildComponentsJson(),
|
|
@@ -846,6 +850,25 @@ if (token && account && d1Id) {
|
|
|
846
850
|
`;
|
|
847
851
|
}
|
|
848
852
|
|
|
853
|
+
/**
|
|
854
|
+
* `src/contract.ops.ts` — the COMPOSED contract surface: one `RouteContract[]` fragment per installed module (each module
|
|
855
|
+
* OWNS its ops next to its routes), spread into `ALL_OPS`. Mirrors {@link buildProvisionConfig}. The base `src/contract.ts`
|
|
856
|
+
* consumes `ALL_OPS` (adding the system op + the derivations), so adding/changing a module's routes only touches THAT
|
|
857
|
+
* module's fragment — the central contract can never drift from the routes again.
|
|
858
|
+
*/
|
|
859
|
+
function buildContractOps(services: string[], catalog: Record<string, Service> = CORE_SERVICES): string {
|
|
860
|
+
const frags = services.map((s) => catalog[s].contract).filter((c): c is NonNullable<typeof c> => !!c);
|
|
861
|
+
const imports = frags.map((f) => `import { ${f.symbol} } from "${f.from}";`);
|
|
862
|
+
return [
|
|
863
|
+
"// AUTO-GENERATED by @suluk/platform — the composed contract surface (one fragment per installed module).",
|
|
864
|
+
'import type { DocumentedRoute } from "@suluk/hono";',
|
|
865
|
+
...imports,
|
|
866
|
+
"",
|
|
867
|
+
`export const ALL_OPS: readonly DocumentedRoute[] = [${frags.map((f) => `...${f.symbol}`).join(", ")}];`,
|
|
868
|
+
"",
|
|
869
|
+
].join("\n");
|
|
870
|
+
}
|
|
871
|
+
|
|
849
872
|
function buildProvisionConfig(services: string[], catalog: Record<string, Service> = CORE_SERVICES): string {
|
|
850
873
|
const frags = services.map((s) => catalog[s].provision).filter((p): p is NonNullable<typeof p> => !!p);
|
|
851
874
|
const imports = frags.map((f) => `import { ${f.symbol} } from "${f.from}";`);
|
package/src/service.ts
CHANGED
|
@@ -50,6 +50,8 @@ export interface EnvVar {
|
|
|
50
50
|
export interface CatalogEntry {
|
|
51
51
|
mount: Mount;
|
|
52
52
|
provision?: { symbol: string; from: string };
|
|
53
|
+
/** the module's CONTRACT fragment — its `RouteContract[]` (ops), composed into `src/contract.ops.ts` (mirrors `provision`). */
|
|
54
|
+
contract?: { symbol: string; from: string };
|
|
53
55
|
deps?: string[];
|
|
54
56
|
env?: EnvVar[];
|
|
55
57
|
}
|
|
@@ -109,6 +111,8 @@ export interface Service<SO = {}, BO = {}> {
|
|
|
109
111
|
readonly registry?: string; // owning registry (multi-registry, Phase 4); default = the manifest's core alias
|
|
110
112
|
readonly mount: Mount;
|
|
111
113
|
readonly provision?: { symbol: string; from: string };
|
|
114
|
+
/** the module's CONTRACT fragment — its `RouteContract[]` (ops), composed into `src/contract.ops.ts` (mirrors `provision`). */
|
|
115
|
+
readonly contract?: { symbol: string; from: string };
|
|
112
116
|
readonly deps?: string[];
|
|
113
117
|
readonly env?: EnvVar[];
|
|
114
118
|
readonly serviceOpts?: Schema<SO>; // how THIS service works → the ENTRY (mount 2nd arg) [Phase 2]
|
|
@@ -163,7 +167,7 @@ export interface CoreServiceOptsMap {
|
|
|
163
167
|
/** Project a Service onto the legacy {@link CatalogEntry} shape the C051 generator reads. Field-for-field — so a derived
|
|
164
168
|
* CATALOG is behaviourally identical to the old hardcoded one (proven by the Phase-0 golden lock). */
|
|
165
169
|
export function toCatalogEntry(s: Service): CatalogEntry {
|
|
166
|
-
return { mount: s.mount, provision: s.provision, deps: s.deps, env: s.env };
|
|
170
|
+
return { mount: s.mount, provision: s.provision, contract: s.contract, deps: s.deps, env: s.env };
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
/**
|
|
@@ -214,12 +218,13 @@ export const authService = defineService({
|
|
|
214
218
|
});
|
|
215
219
|
|
|
216
220
|
export const contractService = defineService({ id: "contract", mount: { kind: "middleware", symbol: "mountContract", from: "./routes/contract" }, deps: ["@suluk/hono", "zod"] });
|
|
217
|
-
export const mcpService = defineService({ id: "mcp", mount: { kind: "middleware", symbol: "mountMcp", from: "./routes/mcp" }, provision: { symbol: "mcpProvision", from: "./src/provision/mcp" }, deps: ["@suluk/mcp", "better-auth"] });
|
|
221
|
+
export const mcpService = defineService({ id: "mcp", mount: { kind: "middleware", symbol: "mountMcp", from: "./routes/mcp" }, provision: { symbol: "mcpProvision", from: "./src/provision/mcp" }, contract: { symbol: "mcpOps", from: "./contract/mcp" }, deps: ["@suluk/mcp", "better-auth"] });
|
|
218
222
|
|
|
219
223
|
export const creditsService = defineService({
|
|
220
224
|
id: "credits",
|
|
221
225
|
mount: { kind: "route", path: "/api/credits", symbol: "creditsRoutes", from: "./routes/credits" },
|
|
222
226
|
provision: { symbol: "creditsProvision", from: "./src/provision/credits" },
|
|
227
|
+
contract: { symbol: "creditsOps", from: "./contract/credits" },
|
|
223
228
|
deps: ["@suluk/credits"],
|
|
224
229
|
compose: {
|
|
225
230
|
offers: {
|
|
@@ -246,12 +251,13 @@ export const creditsService = defineService({
|
|
|
246
251
|
},
|
|
247
252
|
});
|
|
248
253
|
|
|
249
|
-
export const keysService = defineService({ id: "keys", mount: { kind: "route", path: "/api/keys", symbol: "keysRoutes", from: "./routes/keys" }, provision: { symbol: "keysProvision", from: "./src/provision/keys" }, deps: ["@suluk/keys"] });
|
|
254
|
+
export const keysService = defineService({ id: "keys", mount: { kind: "route", path: "/api/keys", symbol: "keysRoutes", from: "./routes/keys" }, provision: { symbol: "keysProvision", from: "./src/provision/keys" }, contract: { symbol: "keysOps", from: "./contract/keys" }, deps: ["@suluk/keys"] });
|
|
250
255
|
|
|
251
256
|
export const billingService = defineService({
|
|
252
257
|
id: "billing",
|
|
253
258
|
mount: { kind: "route", path: "/api/billing", symbol: "billingRoutes", from: "./routes/billing" },
|
|
254
259
|
provision: { symbol: "billingProvision", from: "./src/provision/billing" },
|
|
260
|
+
contract: { symbol: "billingOps", from: "./contract/billing" },
|
|
255
261
|
deps: ["@suluk/billing", "@suluk/payments", "@suluk/credits"],
|
|
256
262
|
env: [
|
|
257
263
|
{ name: "STRIPE_SECRET_KEY", required: true, secret: true, hint: "your Stripe secret key" },
|
|
@@ -259,12 +265,13 @@ export const billingService = defineService({
|
|
|
259
265
|
],
|
|
260
266
|
});
|
|
261
267
|
|
|
262
|
-
export const costService = defineService({ id: "cost", mount: { kind: "route", path: "/api/cost", symbol: "costRoutes", from: "./routes/cost" }, provision: { symbol: "costProvision", from: "./src/provision/cost" }, deps: ["@suluk/cost"] });
|
|
263
|
-
export const erasureService = defineService({ id: "erasure", mount: { kind: "route", path: "/api/erasure", symbol: "erasureRoutes", from: "./routes/erasure" }, provision: { symbol: "erasureProvision", from: "./src/provision/erasure" }, deps: ["@suluk/better-auth"] });
|
|
268
|
+
export const costService = defineService({ id: "cost", mount: { kind: "route", path: "/api/cost", symbol: "costRoutes", from: "./routes/cost" }, provision: { symbol: "costProvision", from: "./src/provision/cost" }, contract: { symbol: "costOps", from: "./contract/cost" }, deps: ["@suluk/cost"] });
|
|
269
|
+
export const erasureService = defineService({ id: "erasure", mount: { kind: "route", path: "/api/erasure", symbol: "erasureRoutes", from: "./routes/erasure" }, provision: { symbol: "erasureProvision", from: "./src/provision/erasure" }, contract: { symbol: "erasureOps", from: "./contract/erasure" }, deps: ["@suluk/better-auth"] });
|
|
264
270
|
|
|
265
271
|
export const emailService = defineService({
|
|
266
272
|
id: "email",
|
|
267
|
-
mount: { kind: "route", path: "/api/email", symbol: "emailRoutes", from: "./routes/email" }, // stateless binding — no provision fragment (C052)
|
|
273
|
+
mount: { kind: "route", path: "/api/email", symbol: "emailRoutes", from: "./routes/email" }, // stateless binding — no provision fragment (C052) // stateless binding — no provision fragment (C052)
|
|
274
|
+
contract: { symbol: "emailOps", from: "./contract/email" },
|
|
268
275
|
deps: ["@suluk/email"],
|
|
269
276
|
env: [
|
|
270
277
|
{ name: "RESEND_API_KEY", secret: true, hint: "omit → the console provider (dev)" },
|
|
@@ -279,6 +286,7 @@ export const webhooksService = defineService({
|
|
|
279
286
|
id: "webhooks",
|
|
280
287
|
mount: { kind: "route", path: "/api/webhooks", symbol: "webhooksRoutes", from: "./routes/webhooks" },
|
|
281
288
|
provision: { symbol: "webhooksProvision", from: "./src/provision/webhooks" },
|
|
289
|
+
contract: { symbol: "webhooksOps", from: "./contract/webhooks" },
|
|
282
290
|
deps: ["@suluk/payments"],
|
|
283
291
|
env: [{ name: "STRIPE_WEBHOOK_SECRET", required: true, secret: true, hint: "verifies inbound Stripe events (POST /api/webhooks/stripe)" }],
|
|
284
292
|
});
|
|
@@ -286,9 +294,9 @@ export const webhooksService = defineService({
|
|
|
286
294
|
export const rateLimitService = defineService({ id: "rate-limit", mount: { kind: "middleware", symbol: "mountRateLimit", from: "./services/rate-limit" }, deps: ["@suluk/hono"] });
|
|
287
295
|
export const rateCreditService = defineService({ id: "rate-credit", mount: { kind: "middleware", symbol: "mountRateCredit", from: "./services/rate-credit" } }); // credit-backed free-tier bucket (KV binding)
|
|
288
296
|
export const i18nService = defineService({ id: "i18n", mount: { kind: "middleware", symbol: "mountI18n", from: "./services/i18n" }, deps: ["@suluk/i18n"] });
|
|
289
|
-
export const referenceService = defineService({ id: "reference", mount: { kind: "route", path: "/api/reference", symbol: "referenceRoutes", from: "./routes/reference" }, deps: ["@suluk/reference"] }); // derived — no provision
|
|
290
|
-
export const adminService = defineService({ id: "admin", mount: { kind: "route", path: "/api/admin", symbol: "adminRoutes", from: "./routes/admin" }, deps: ["@suluk/credits"], env: [{ name: "SUPERADMIN_EMAILS", secret: true, hint: "comma/space-separated admin emails → the admin scope (secret-surfaced so they stay out of git plaintext)" }] }); // reads existing tables — no provision
|
|
291
|
-
export const logsService = defineService({ id: "logs", mount: { kind: "route", path: "/api/logs", symbol: "logsRoutes", from: "./routes/logs" }, provision: { symbol: "logsProvision", from: "./src/provision/logs" } });
|
|
297
|
+
export const referenceService = defineService({ id: "reference", mount: { kind: "route", path: "/api/reference", symbol: "referenceRoutes", from: "./routes/reference" }, contract: { symbol: "referenceOps", from: "./contract/reference" }, deps: ["@suluk/reference"] }); // derived — no provision
|
|
298
|
+
export const adminService = defineService({ id: "admin", mount: { kind: "route", path: "/api/admin", symbol: "adminRoutes", from: "./routes/admin" }, contract: { symbol: "adminOps", from: "./contract/admin" }, deps: ["@suluk/credits"], env: [{ name: "SUPERADMIN_EMAILS", secret: true, hint: "comma/space-separated admin emails → the admin scope (secret-surfaced so they stay out of git plaintext)" }] }); // reads existing tables — no provision
|
|
299
|
+
export const logsService = defineService({ id: "logs", mount: { kind: "route", path: "/api/logs", symbol: "logsRoutes", from: "./routes/logs" }, provision: { symbol: "logsProvision", from: "./src/provision/logs" }, contract: { symbol: "logsOps", from: "./contract/logs" } });
|
|
292
300
|
export const journeysService = defineService({ id: "journeys", mount: { kind: "dev" }, deps: ["@suluk/journeys"] });
|
|
293
301
|
export const auditService = defineService({ id: "audit", mount: { kind: "dev" }, deps: ["@suluk/cockpit", "@suluk/harden"] });
|
|
294
302
|
|