@suluk/platform 0.1.4 → 0.1.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suluk/platform",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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/catalog.ts CHANGED
@@ -21,8 +21,10 @@ export interface CatalogEntry {
21
21
  export const CATALOG: Record<string, CatalogEntry> = {
22
22
  app: { mount: { kind: "base" } },
23
23
  auth: { mount: { kind: "middleware", symbol: "mountAuthRoutes", from: "./auth" }, provision: { symbol: "authProvision", from: "./src/provision/auth" } },
24
+ // the contract is a MIDDLEWARE mount: it installs the scope gate (enforceApiKeyScope) + GET /api/openapi.json. Place it
25
+ // after `auth` in the manifest so the gate runs after identity/apiKeyAuth set keyId/scopes. Derived + stateless.
26
+ contract: { mount: { kind: "middleware", symbol: "mountContract", from: "./routes/contract" } },
24
27
  // feature routes mount under /api/* — where the caller-resolution + cors + rate-limit middleware live (toolfactory parity).
25
- contract: { mount: { kind: "route", path: "/api", symbol: "contractRoutes", from: "./routes/contract" } }, // serves GET /api/openapi.json (derived); stateless
26
28
  credits: { mount: { kind: "route", path: "/api/credits", symbol: "creditsRoutes", from: "./routes/credits" }, provision: { symbol: "creditsProvision", from: "./src/provision/credits" } },
27
29
  keys: { mount: { kind: "route", path: "/api/keys", symbol: "keysRoutes", from: "./routes/keys" }, provision: { symbol: "keysProvision", from: "./src/provision/keys" } },
28
30
  billing: { mount: { kind: "route", path: "/api/billing", symbol: "billingRoutes", from: "./routes/billing" }, provision: { symbol: "billingProvision", from: "./src/provision/billing" } },
@@ -33,6 +35,8 @@ export const CATALOG: Record<string, CatalogEntry> = {
33
35
  // cross-cutting MIDDLEWARE (apply globally via app.use, emitted before any route) — not routed resources.
34
36
  "rate-limit": { mount: { kind: "middleware", symbol: "mountRateLimit", from: "./services/rate-limit" } },
35
37
  i18n: { mount: { kind: "middleware", symbol: "mountI18n", from: "./services/i18n" } },
38
+ reference: { mount: { kind: "route", path: "/api/reference", symbol: "referenceRoutes", from: "./routes/reference" } }, // derived doc render — no provision
39
+ admin: { mount: { kind: "route", path: "/api/admin", symbol: "adminRoutes", from: "./routes/admin" } }, // reads existing tables — no provision
36
40
  logs: { mount: { kind: "route", path: "/api/logs", symbol: "logsRoutes", from: "./routes/logs" }, provision: { symbol: "logsProvision", from: "./src/provision/logs" } },
37
41
  // dev/CI tooling — pulled in as files, no runtime mount, no provision fragment.
38
42
  journeys: { mount: { kind: "dev" } },
package/test/plan.test.ts CHANGED
@@ -83,14 +83,24 @@ describe("cost (route + provision) + dev modules (journeys/audit — files only)
83
83
  expect(lastMw).toBeLessThan(p.entry.indexOf('app.route("/api/credits"'));
84
84
  });
85
85
 
86
- test("contract mounts at /api (serving /api/openapi.json) with NO provision; feature routes are under /api/*", () => {
86
+ test("contract is a MIDDLEWARE mount (scope gate + /api/openapi.json), emitted before routes; feature routes under /api/*", () => {
87
87
  const p = planPlatform(definePlatform({ name: "c", registry: "acme/reg", services: ["auth", "contract", "credits"] }));
88
- expect(p.entry).toContain('import { contractRoutes } from "./routes/contract";');
89
- expect(p.entry).toContain('app.route("/api", contractRoutes());');
88
+ expect(p.entry).toContain('import { mountContract } from "./routes/contract";');
89
+ expect(p.entry).toContain("mountContract(app);");
90
90
  expect(p.entry).toContain('app.route("/api/credits", creditsRoutes());'); // toolfactory-parity /api/* prefix
91
+ // the gate (middleware) is emitted before any route.
92
+ expect(p.entry.indexOf("mountContract(app);")).toBeLessThan(p.entry.indexOf('app.route("/api/credits"'));
91
93
  expect(p.provisionConfig).not.toContain("contractProvision");
92
94
  });
93
95
 
96
+ test("reference + admin mount /api routes with NO provision (derived / reads existing tables)", () => {
97
+ const p = planPlatform(definePlatform({ name: "ra", registry: "acme/reg", services: ["auth", "contract", "reference", "admin", "credits"] }));
98
+ expect(p.entry).toContain('app.route("/api/reference", referenceRoutes());');
99
+ expect(p.entry).toContain('app.route("/api/admin", adminRoutes());');
100
+ expect(p.provisionConfig).not.toContain("referenceProvision");
101
+ expect(p.provisionConfig).not.toContain("adminProvision");
102
+ });
103
+
94
104
  test("dev modules add shadcn refs but NO entry mount and NO provision fragment", () => {
95
105
  expect(plan.adds).toContain("acme/reg/journeys");
96
106
  expect(plan.adds).toContain("acme/reg/audit");