Write one manifest; the generator plans the shadcn-registry adds, generates the wired Hono entry, merges each module's
provision fragment, and emits the whole scaffold (package.json / tsconfig / wrangler.toml / .env.example / …). The
higher-level surface over the Suluk registry + @suluk/provision.
There are two authoring surfaces. The legacy one (C051) is a single object and is supported forever. The C053 one splits a platform into a reusable system and a swappable brand, adds typed opts, and lets services compose.
defineService<SO, BO>({ id, mount, provision?, deps?, env?, serviceOpts?, brandOpts?, reads?, compose? }) — the shape a
community shadcn registry extends. The 19 core services are exported as typed consts (authService, creditsService, …).
import { defineSystem, defineBrand, definePlatform, authService, creditsService, emailService } from "@suluk/platform";
import { analyticsService } from "@acme/suluk-analytics"; // a community service
export const system = defineSystem({
registry: "MahmoodKhalil57/suluk",
services: [authService, creditsService, emailService, analyticsService],
globalServiceOpts: { ENVIRONMENT: "production", TRUSTED_ORIGINS: "https://app.example" },
serviceOpts: { auth: { mcp: { loginPage: "…", consentPage: "…", resource: "…", scopes: ["credits:read"] } } }, // typed by id
wire: [{ id: "signup-grant", from: "auth.onUserCreated", to: "credits.grantOnSignup", with: { amount: 100 } }],
});
export const brand = defineBrand({
name: "app",
globalBrandOpts: { BRAND_NAME: "App", BASE_URL: "https://app.example", EMAIL_FROM: "hi@app.example" },
});
export default definePlatform({ system, brand });
A system (services + serviceOpts + globalServiceOpts + wiring) is the reusable, publishable template. A brand
(brandOpts + globalBrandOpts) is thin and swappable — two businesses run the same system with different brands; the generated
entry code is identical, only wrangler.toml [vars] differ.
| per-service | global | |
|---|---|---|
| service axis (how it works) | serviceOpts → the entry (mount opts) |
globalServiceOpts (a service reads the keys it needs) |
| brand axis (identity) | brandOpts → [vars] |
globalBrandOpts → [vars] |
serviceOpts is typed per service id off the imported service objects (or CoreServiceOptsMap for a string id) — a wrong
opt is a compile error.
wire)An edge binds a producer port to a consumer capability. It renders into the producer's existing mount-opt field
(e.g. auth.onUserCreated), not a separate statement — so it reuses a real seam and the hook closure gets a real env.
resolveWiring validates presence, port/capability existence, JSON-safe params, safe identifiers, and acyclicity; fan-out
(several wires on one port) composes in declaration order. A community service participates by offering a capability (fills a
core port) or exposing its own.
suluk-platform # generate from ./platform.config.ts (legacy OR { system, brand })
suluk-platform --config <path>
suluk-platform migrate # print the { system, brand } split of a legacy config (a starting point)
migrate is byte-faithful: liftLegacy → the same generated app as the legacy manifest.
{ system, brand } with no wire — regenerates the exact bytes the C051
generator produced (pinned by a golden test over the real 18-service reference app).@suluk logic.