@voltro/cli 0.27.0 → 0.29.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/CHANGELOG.md +513 -0
- package/THIRD-PARTY-NOTICES.md +88 -2
- package/dist/{apiBuild-D22_EpoR.js → apiBuild-BESZbTjA.js} +4 -4
- package/dist/apiBuild-C8d74T6g.js +2 -0
- package/dist/bin.js +3 -3
- package/dist/checkCommand-BPAtyWs8.js +2420 -0
- package/dist/checkCommand-DBvZvCh1.js +2 -0
- package/dist/cliRuntime-Oh517vCV.js +96 -0
- package/dist/{commands-jBX8no1I.js → commands-82BDEktg.js} +6476 -7672
- package/dist/dbCommand-CaIVTp-a.js +2 -0
- package/dist/{dbCommand-uuNCrFAb.js → dbCommand-bCBbk6tz.js} +3 -3
- package/dist/{dev-DcbIJrWg.js → dev-Cg4BKLmi.js} +3034 -2619
- package/dist/dev-DeEJj5T4.js +3 -0
- package/dist/fileConventions-Cof68_BL.js +33 -0
- package/dist/{frameworkTableAssembly-BwHU9Euq.js → frameworkTableAssembly-4Db40V23.js} +2 -2
- package/dist/frameworkTableAssembly-BTNirAAk.js +2 -0
- package/dist/index.js +1 -1
- package/dist/{inspect-CUCCzw2I.js → inspect-BMvQpwqy.js} +211 -190
- package/dist/inspect-DHeaMZak.js +2 -0
- package/dist/{inspectMetrics-BU90mvJN.js → inspectMetrics-DHYygE4s.js} +999 -961
- package/dist/{manifestBuild-BnzAxp2O.js → manifestBuild-C4R1EdHn.js} +1 -1
- package/dist/manifestBuild-Ck_90gZy.js +2 -0
- package/dist/sdkgen-CW5NbYF0.js +582 -0
- package/dist/{seedRunner-D6eu-u5U.js → seedRunner-Bqxgp7HZ.js} +60 -59
- package/dist/serveCommand-BXl8mhZD.js +1489 -0
- package/dist/serveEntry.js +3 -3
- package/dist/{start-BGXIf6zT.js → start-CsCIaW4-.js} +282 -269
- package/dist/startEntry.js +2 -2
- package/package.json +17 -17
- package/templates/AGENTS.md +2 -1
- package/templates/agent-docs/_index.md +2 -1
- package/templates/agent-docs/_manifest.json +10 -1
- package/templates/agent-docs/ai.md +135 -0
- package/templates/agent-docs/cli.md +124 -6
- package/templates/agent-docs/configuration.md +32 -0
- package/templates/agent-docs/data.md +282 -0
- package/templates/agent-docs/database/migrations.md +47 -0
- package/templates/agent-docs/database/misc.md +57 -0
- package/templates/agent-docs/database/schema.md +3 -1
- package/templates/agent-docs/deployment.md +56 -0
- package/templates/agent-docs/internationalization.md +161 -1
- package/templates/agent-docs/local-first-mobile.md +414 -0
- package/templates/agent-docs/routing.md +93 -2
- package/templates/agent-docs/schema-driven-ui.md +12 -0
- package/templates/agent-docs/templates/apibackends.md +1 -1
- package/templates/agent-docs/testing.md +42 -0
- package/templates/agent-docs/whats-new.md +165 -131
- package/templates/agent-docs/workflows.md +11 -0
- package/templates/apps/api-ai/package.json +7 -7
- package/templates/apps/api-auth/package.json +8 -8
- package/templates/apps/api-backend/package.json +7 -7
- package/templates/apps/api-backend-deactivation/package.json +7 -7
- package/templates/apps/api-backend-mail/package.json +8 -8
- package/templates/apps/api-backend-mariadb/package.json +9 -9
- package/templates/apps/api-backend-sqlite/.env.example +19 -0
- package/templates/apps/api-backend-sqlite/README.md +38 -0
- package/templates/apps/api-backend-sqlite/app.config.ts +35 -0
- package/templates/apps/api-backend-sqlite/database/schema.ts +54 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-sqlite/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-sqlite/package.json +30 -0
- package/templates/apps/api-backend-sqlite/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-sqlite/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-sqlite/template.json +6 -0
- package/templates/apps/api-backend-sqlite/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-sqlite/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/package.json +8 -8
- package/templates/apps/api-cms/README.md +104 -0
- package/templates/apps/api-cms/actions/content.get.action.server.ts +27 -0
- package/templates/apps/api-cms/actions/content.get.action.ts +19 -0
- package/templates/apps/api-cms/actions/content.types.action.server.ts +26 -0
- package/templates/apps/api-cms/actions/content.types.action.ts +40 -0
- package/templates/apps/api-cms/actions/me.action.server.ts +18 -0
- package/templates/apps/api-cms/actions/me.action.ts +16 -0
- package/templates/apps/api-cms/app.config.ts +61 -0
- package/templates/apps/api-cms/content/blogPost.contentType.ts +39 -0
- package/templates/apps/api-cms/content/index.ts +18 -0
- package/templates/apps/api-cms/content/page.contentType.ts +24 -0
- package/templates/apps/api-cms/database/schema.ts +64 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.server.ts +19 -0
- package/templates/apps/api-cms/mutations/content.publish.mutation.ts +15 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.server.ts +36 -0
- package/templates/apps/api-cms/mutations/content.saveDraft.mutation.ts +32 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.server.ts +19 -0
- package/templates/apps/api-cms/mutations/content.unpublish.mutation.ts +11 -0
- package/templates/apps/api-cms/package.json +32 -0
- package/templates/apps/api-cms/queries/content.list.query.server.ts +0 -0
- package/templates/apps/api-cms/queries/content.list.query.ts +27 -0
- package/templates/apps/api-cms/template.json +6 -0
- package/templates/apps/api-cms/tests/content.descriptors.test.ts +64 -0
- package/templates/apps/api-cms/tests/content.write.test.ts +85 -0
- package/templates/apps/api-cms/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/package.json +8 -8
- package/templates/apps/api-durable/package.json +8 -8
- package/templates/apps/api-feature-flags/package.json +9 -9
- package/templates/apps/api-governance/package.json +8 -8
- package/templates/apps/api-kv/package.json +8 -8
- package/templates/apps/api-moderation/package.json +8 -8
- package/templates/apps/api-observability/package.json +8 -8
- package/templates/apps/api-ratelimit/package.json +8 -8
- package/templates/apps/api-rbac/package.json +8 -8
- package/templates/apps/api-rest/package.json +7 -7
- package/templates/apps/api-saas/package.json +11 -11
- package/templates/apps/api-saas-starter/README.md +103 -0
- package/templates/apps/api-saas-starter/actions/me.action.server.ts +18 -0
- package/templates/apps/api-saas-starter/actions/me.action.ts +20 -0
- package/templates/apps/api-saas-starter/app.config.ts +87 -0
- package/templates/apps/api-saas-starter/database/schema.ts +57 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.server.ts +26 -0
- package/templates/apps/api-saas-starter/mutations/invites.create.mutation.ts +18 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.server.ts +29 -0
- package/templates/apps/api-saas-starter/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas-starter/package.json +32 -0
- package/templates/apps/api-saas-starter/queries/invites.list.query.server.ts +14 -0
- package/templates/apps/api-saas-starter/queries/invites.list.query.ts +17 -0
- package/templates/apps/api-saas-starter/queries/projects.list.query.server.ts +14 -0
- package/templates/apps/api-saas-starter/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas-starter/template.json +6 -0
- package/templates/apps/api-saas-starter/tests/projects.create.test.ts +62 -0
- package/templates/apps/api-saas-starter/tests/session.test.ts +33 -0
- package/templates/apps/api-saas-starter/tsconfig.json +5 -0
- package/templates/apps/api-search/package.json +8 -8
- package/templates/apps/api-status/README.md +88 -0
- package/templates/apps/api-status/app.config.ts +36 -0
- package/templates/apps/api-status/authz.ts +33 -0
- package/templates/apps/api-status/database/schema.ts +70 -0
- package/templates/apps/api-status/mutations/components.create.mutation.server.ts +19 -0
- package/templates/apps/api-status/mutations/components.create.mutation.ts +16 -0
- package/templates/apps/api-status/mutations/incidents.create.mutation.server.ts +25 -0
- package/templates/apps/api-status/mutations/incidents.create.mutation.ts +22 -0
- package/templates/apps/api-status/mutations/incidents.resolve.mutation.server.ts +24 -0
- package/templates/apps/api-status/mutations/incidents.resolve.mutation.ts +19 -0
- package/templates/apps/api-status/mutations/incidents.update.mutation.server.ts +26 -0
- package/templates/apps/api-status/mutations/incidents.update.mutation.ts +21 -0
- package/templates/apps/api-status/package.json +30 -0
- package/templates/apps/api-status/queries/components.list.query.server.ts +14 -0
- package/templates/apps/api-status/queries/components.list.query.ts +13 -0
- package/templates/apps/api-status/queries/incidents.live.query.server.ts +14 -0
- package/templates/apps/api-status/queries/incidents.live.query.ts +20 -0
- package/templates/apps/api-status/queries/updates.list.query.server.ts +14 -0
- package/templates/apps/api-status/queries/updates.list.query.ts +17 -0
- package/templates/apps/api-status/template.json +6 -0
- package/templates/apps/api-status/tests/status.test.ts +70 -0
- package/templates/apps/api-status/tsconfig.json +5 -0
- package/templates/apps/api-versioning/package.json +8 -8
- package/templates/apps/api-webhooks/package.json +9 -9
- package/templates/apps/changelog/package.json +6 -6
- package/templates/apps/edge-functions/package.json +2 -2
- package/templates/apps/frontend-admin/package.json +8 -8
- package/templates/apps/frontend-app/package.json +8 -8
- package/templates/apps/frontend-auth/README.md +78 -0
- package/templates/apps/frontend-auth/app.config.ts +34 -0
- package/templates/apps/frontend-auth/package.json +32 -0
- package/templates/apps/frontend-auth/src/components/AuthShell.tsx +35 -0
- package/templates/apps/frontend-auth/src/components/PasswordStrength.tsx +33 -0
- package/templates/apps/frontend-auth/src/config.ts +11 -0
- package/templates/apps/frontend-auth/src/globals.css +105 -0
- package/templates/apps/frontend-auth/src/globals.d.ts +6 -0
- package/templates/apps/frontend-auth/src/lib/auth.ts +34 -0
- package/templates/apps/frontend-auth/src/lib/redirect.test.ts +24 -0
- package/templates/apps/frontend-auth/src/lib/redirect.ts +29 -0
- package/templates/apps/frontend-auth/src/locales/de.ts +66 -0
- package/templates/apps/frontend-auth/src/locales/en.ts +76 -0
- package/templates/apps/frontend-auth/src/locales/index.ts +14 -0
- package/templates/apps/frontend-auth/src/pages/forgot/page.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-auth/src/pages/login/page.test.tsx +53 -0
- package/templates/apps/frontend-auth/src/pages/login/page.tsx +67 -0
- package/templates/apps/frontend-auth/src/pages/logout/page.tsx +28 -0
- package/templates/apps/frontend-auth/src/pages/magic/page.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/page.tsx +26 -0
- package/templates/apps/frontend-auth/src/pages/reset/page.test.tsx +51 -0
- package/templates/apps/frontend-auth/src/pages/reset/page.tsx +63 -0
- package/templates/apps/frontend-auth/src/pages/signup/page.tsx +60 -0
- package/templates/apps/frontend-auth/src/pages/verify/page.tsx +50 -0
- package/templates/apps/frontend-auth/template.json +6 -0
- package/templates/apps/frontend-auth/tsconfig.json +5 -0
- package/templates/apps/frontend-blank/package.json +7 -7
- package/templates/apps/frontend-cms/README.md +47 -0
- package/templates/apps/frontend-cms/app.config.ts +37 -0
- package/templates/apps/frontend-cms/package.json +33 -0
- package/templates/apps/frontend-cms/src/config.ts +8 -0
- package/templates/apps/frontend-cms/src/globals.css +105 -0
- package/templates/apps/frontend-cms/src/globals.d.ts +6 -0
- package/templates/apps/frontend-cms/src/lib/api.ts +58 -0
- package/templates/apps/frontend-cms/src/locales/de.ts +50 -0
- package/templates/apps/frontend-cms/src/locales/en.ts +55 -0
- package/templates/apps/frontend-cms/src/locales/index.ts +14 -0
- package/templates/apps/frontend-cms/src/pages/(app)/error.tsx +18 -0
- package/templates/apps/frontend-cms/src/pages/(app)/layout.test.tsx +31 -0
- package/templates/apps/frontend-cms/src/pages/(app)/layout.tsx +62 -0
- package/templates/apps/frontend-cms/src/pages/(app)/not-found.tsx +14 -0
- package/templates/apps/frontend-cms/src/pages/(app)/page.test.tsx +110 -0
- package/templates/apps/frontend-cms/src/pages/(app)/page.tsx +159 -0
- package/templates/apps/frontend-cms/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-cms/src/pages/login/page.test.tsx +58 -0
- package/templates/apps/frontend-cms/src/pages/login/page.tsx +93 -0
- package/templates/apps/frontend-cms/template.json +6 -0
- package/templates/apps/frontend-cms/tsconfig.json +5 -0
- package/templates/apps/frontend-contact/package.json +7 -7
- package/templates/apps/frontend-dashboard/package.json +7 -7
- package/templates/apps/frontend-docs/package.json +7 -7
- package/templates/apps/frontend-i18n/package.json +6 -6
- package/templates/apps/frontend-landing/package.json +7 -7
- package/templates/apps/frontend-portal/README.md +71 -0
- package/templates/apps/frontend-portal/app.config.ts +37 -0
- package/templates/apps/frontend-portal/package.json +32 -0
- package/templates/apps/frontend-portal/src/config.ts +8 -0
- package/templates/apps/frontend-portal/src/globals.css +93 -0
- package/templates/apps/frontend-portal/src/globals.d.ts +6 -0
- package/templates/apps/frontend-portal/src/lib/api.ts +62 -0
- package/templates/apps/frontend-portal/src/locales/de.ts +86 -0
- package/templates/apps/frontend-portal/src/locales/en.ts +93 -0
- package/templates/apps/frontend-portal/src/locales/index.ts +14 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/api-keys/page.test.tsx +71 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/api-keys/page.tsx +124 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/billing/page.test.tsx +86 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/billing/page.tsx +97 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/error.tsx +19 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/invoices/page.tsx +45 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.test.tsx +31 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/layout.tsx +63 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/not-found.tsx +15 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/page.tsx +39 -0
- package/templates/apps/frontend-portal/src/pages/(portal)/profile/page.tsx +79 -0
- package/templates/apps/frontend-portal/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-portal/src/pages/login/page.test.tsx +58 -0
- package/templates/apps/frontend-portal/src/pages/login/page.tsx +96 -0
- package/templates/apps/frontend-portal/template.json +6 -0
- package/templates/apps/frontend-portal/tsconfig.json +5 -0
- package/templates/apps/frontend-saas/README.md +73 -0
- package/templates/apps/frontend-saas/app.config.ts +46 -0
- package/templates/apps/frontend-saas/package.json +32 -0
- package/templates/apps/frontend-saas/src/config.ts +8 -0
- package/templates/apps/frontend-saas/src/globals.css +85 -0
- package/templates/apps/frontend-saas/src/globals.d.ts +6 -0
- package/templates/apps/frontend-saas/src/lib/api.ts +47 -0
- package/templates/apps/frontend-saas/src/locales/de.ts +72 -0
- package/templates/apps/frontend-saas/src/locales/en.ts +81 -0
- package/templates/apps/frontend-saas/src/locales/index.ts +14 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/layout.tsx +30 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/login/page.test.tsx +58 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/login/page.tsx +96 -0
- package/templates/apps/frontend-saas/src/pages/(marketing)/page.tsx +27 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/billing/page.tsx +72 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/error.tsx +20 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.test.tsx +32 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/not-found.tsx +17 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/page.test.tsx +113 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/page.tsx +76 -0
- package/templates/apps/frontend-saas/src/pages/dashboard/team/page.tsx +73 -0
- package/templates/apps/frontend-saas/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-saas/template.json +6 -0
- package/templates/apps/frontend-saas/tsconfig.json +5 -0
- package/templates/apps/frontend-spa/package.json +7 -7
- package/templates/apps/frontend-ssr/package.json +7 -7
- package/templates/apps/frontend-ssr-api/package.json +8 -8
- package/templates/apps/frontend-static-blog/package.json +6 -6
- package/templates/apps/frontend-status/README.md +51 -0
- package/templates/apps/frontend-status/app.config.ts +39 -0
- package/templates/apps/frontend-status/package.json +32 -0
- package/templates/apps/frontend-status/src/config.ts +7 -0
- package/templates/apps/frontend-status/src/globals.css +70 -0
- package/templates/apps/frontend-status/src/globals.d.ts +6 -0
- package/templates/apps/frontend-status/src/lib/status.ts +81 -0
- package/templates/apps/frontend-status/src/locales/de.ts +41 -0
- package/templates/apps/frontend-status/src/locales/en.ts +45 -0
- package/templates/apps/frontend-status/src/locales/index.ts +13 -0
- package/templates/apps/frontend-status/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-status/src/pages/page.test.tsx +123 -0
- package/templates/apps/frontend-status/src/pages/page.tsx +136 -0
- package/templates/apps/frontend-status/template.json +6 -0
- package/templates/apps/frontend-status/tsconfig.json +11 -0
- package/templates/baselines/compose/docker-compose.prod.yml +15 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +15 -0
- package/dist/apiBuild-BrjrVJJh.js +0 -2
- package/dist/dbCommand-DrzXimKf.js +0 -2
- package/dist/dev-DNkso403.js +0 -3
- package/dist/fileConventions-3bffWssN.js +0 -30
- package/dist/frameworkTableAssembly-lrjZtk0G.js +0 -2
- package/dist/inspect-gt8bq-Tz.js +0 -2
- package/dist/manifestBuild-ifczArzr.js +0 -2
- package/dist/serveCommand-DfkisVWP.js +0 -1310
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { t as e } from "./cliRuntime-Oh517vCV.js";
|
|
2
|
+
import { dirname as t, isAbsolute as n, join as r } from "node:path";
|
|
3
|
+
import { Effect as i } from "effect";
|
|
4
|
+
import { createLogger as a } from "@voltro/logger";
|
|
5
|
+
import { promises as o } from "node:fs";
|
|
6
|
+
//#region src/sdkgen/schemaIr.ts
|
|
7
|
+
var s = () => ({
|
|
8
|
+
models: /* @__PURE__ */ new Map(),
|
|
9
|
+
enums: /* @__PURE__ */ new Map(),
|
|
10
|
+
inProgress: /* @__PURE__ */ new Set()
|
|
11
|
+
}), c = (e) => e.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter((e) => e.length > 0), l = (e) => {
|
|
12
|
+
let t = c(e);
|
|
13
|
+
if (t.length === 0) return "Value";
|
|
14
|
+
let n = t.map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join("");
|
|
15
|
+
return /^[0-9]/.test(n) ? `T${n}` : n;
|
|
16
|
+
}, u = (e) => {
|
|
17
|
+
let t = l(e);
|
|
18
|
+
return t.charAt(0).toLowerCase() + t.slice(1);
|
|
19
|
+
}, d = (e) => {
|
|
20
|
+
let t = c(e);
|
|
21
|
+
if (t.length === 0) return "VALUE";
|
|
22
|
+
let n = t.map((e) => e.toUpperCase()).join("_");
|
|
23
|
+
return /^[0-9]/.test(n) ? `V_${n}` : n;
|
|
24
|
+
}, f = (e) => typeof e == "object" && !!e && e.type === "null", p = (e) => l(e.split("/").pop() ?? e), m = (e) => e.every((e) => typeof e == "string"), h = (e, t, n) => (e.enums.has(t) || e.enums.set(t, {
|
|
25
|
+
name: t,
|
|
26
|
+
values: n
|
|
27
|
+
}), {
|
|
28
|
+
kind: "ref",
|
|
29
|
+
name: t
|
|
30
|
+
}), g = (e, t, n, r) => {
|
|
31
|
+
let i = e.$ref;
|
|
32
|
+
if (typeof i == "string") {
|
|
33
|
+
let e = p(i);
|
|
34
|
+
if (r.models.has(e) || r.enums.has(e) || r.inProgress.has(e)) return {
|
|
35
|
+
kind: "ref",
|
|
36
|
+
name: e
|
|
37
|
+
};
|
|
38
|
+
let t = n[i.split("/").pop() ?? ""];
|
|
39
|
+
if (t === void 0) return { kind: "unknown" };
|
|
40
|
+
r.inProgress.add(e);
|
|
41
|
+
let a = g(t, e, n, r);
|
|
42
|
+
return r.inProgress.delete(e), a;
|
|
43
|
+
}
|
|
44
|
+
let a = e.type;
|
|
45
|
+
if (Array.isArray(a)) {
|
|
46
|
+
let i = a.filter((e) => e !== "null"), o = a.includes("null"), s = g({
|
|
47
|
+
...e,
|
|
48
|
+
...i.length === 1 ? { type: i[0] } : { type: void 0 }
|
|
49
|
+
}, t, n, r);
|
|
50
|
+
return o ? {
|
|
51
|
+
kind: "optional",
|
|
52
|
+
inner: s
|
|
53
|
+
} : s;
|
|
54
|
+
}
|
|
55
|
+
let o = e.anyOf ?? e.oneOf;
|
|
56
|
+
if (Array.isArray(o)) {
|
|
57
|
+
let e = o.filter(f), i = o.filter((e) => !f(e));
|
|
58
|
+
if (i.length === 1 && i[0] !== void 0) {
|
|
59
|
+
let a = g(i[0], t, n, r);
|
|
60
|
+
return e.length > 0 ? {
|
|
61
|
+
kind: "optional",
|
|
62
|
+
inner: a
|
|
63
|
+
} : a;
|
|
64
|
+
}
|
|
65
|
+
return { kind: "unknown" };
|
|
66
|
+
}
|
|
67
|
+
let s = e.enum;
|
|
68
|
+
if (Array.isArray(s) && s.length > 0 && m(s)) return h(r, t, [...s]);
|
|
69
|
+
switch (a) {
|
|
70
|
+
case "string": return { kind: "string" };
|
|
71
|
+
case "integer": return { kind: "integer" };
|
|
72
|
+
case "number": return { kind: "number" };
|
|
73
|
+
case "boolean": return { kind: "boolean" };
|
|
74
|
+
case "array": {
|
|
75
|
+
let i = e.items;
|
|
76
|
+
return {
|
|
77
|
+
kind: "array",
|
|
78
|
+
element: i !== void 0 && typeof i == "object" && !Array.isArray(i) ? g(i, `${t}Item`, n, r) : { kind: "unknown" }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
case "object": {
|
|
82
|
+
let i = e.properties;
|
|
83
|
+
if (i === void 0 || Object.keys(i).length === 0) return { kind: "unknown" };
|
|
84
|
+
let a = new Set(Array.isArray(e.required) ? e.required : []);
|
|
85
|
+
r.inProgress.add(t);
|
|
86
|
+
let o = Object.entries(i).map(([e, i]) => {
|
|
87
|
+
let o = g(i, `${t}${l(e)}`, n, r);
|
|
88
|
+
return {
|
|
89
|
+
jsonName: e,
|
|
90
|
+
type: !a.has(e) && o.kind !== "optional" ? {
|
|
91
|
+
kind: "optional",
|
|
92
|
+
inner: o
|
|
93
|
+
} : o
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
return r.inProgress.delete(t), r.models.set(t, {
|
|
97
|
+
name: t,
|
|
98
|
+
fields: o
|
|
99
|
+
}), {
|
|
100
|
+
kind: "ref",
|
|
101
|
+
name: t
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
default: return { kind: "unknown" };
|
|
105
|
+
}
|
|
106
|
+
}, _ = (e, t, n) => e === void 0 ? void 0 : g(e, t, e.$defs ?? e.definitions ?? {}, n), v = (e, t) => e.name.localeCompare(t.name), y = (e) => {
|
|
107
|
+
let t = s();
|
|
108
|
+
return {
|
|
109
|
+
procedures: [...e.procedures].sort((e, t) => e.tag.localeCompare(t.tag)).map((e) => {
|
|
110
|
+
let n = l(e.tag);
|
|
111
|
+
return {
|
|
112
|
+
tag: e.tag,
|
|
113
|
+
kind: e.kind,
|
|
114
|
+
methodName: u(e.tag),
|
|
115
|
+
input: _(e.input, `${n}Input`, t),
|
|
116
|
+
output: _(e.output, `${n}Output`, t)
|
|
117
|
+
};
|
|
118
|
+
}),
|
|
119
|
+
models: [...t.models.values()].sort(v),
|
|
120
|
+
enums: [...t.enums.values()].sort(v)
|
|
121
|
+
};
|
|
122
|
+
}, b = /^[A-Za-z_][A-Za-z0-9_]*$/, x = /* @__PURE__ */ new Set(/* @__PURE__ */ "associatedtype.class.deinit.enum.extension.fileprivate.func.import.init.inout.internal.let.open.operator.private.protocol.public.rethrows.static.struct.subscript.typealias.var.break.case.continue.default.defer.do.else.fallthrough.for.guard.if.in.repeat.return.switch.where.while.as.catch.false.is.nil.super.self.Self.throw.throws.true.try.Type.Protocol".split(".")), S = /* @__PURE__ */ new Set(/* @__PURE__ */ "as.break.class.continue.do.else.false.for.fun.if.in.interface.is.null.object.package.return.super.this.throw.true.try.typealias.typeof.val.var.when.while".split(".")), C = (e, t) => {
|
|
123
|
+
if (b.test(e)) return t.has(e) ? {
|
|
124
|
+
ident: `\`${e}\``,
|
|
125
|
+
needsAlias: !1
|
|
126
|
+
} : {
|
|
127
|
+
ident: e,
|
|
128
|
+
needsAlias: !1
|
|
129
|
+
};
|
|
130
|
+
let n = u(e);
|
|
131
|
+
return {
|
|
132
|
+
ident: b.test(n) ? n : `field_${n.replace(/[^A-Za-z0-9_]/g, "")}`,
|
|
133
|
+
needsAlias: !0
|
|
134
|
+
};
|
|
135
|
+
}, w = (e) => C(e, x), ee = (e) => C(e, S), T = "// Generated by `voltro build api --target kotlin`. Do not edit by hand.", E = (e) => {
|
|
136
|
+
switch (e.kind) {
|
|
137
|
+
case "string": return "String";
|
|
138
|
+
case "integer": return "Int";
|
|
139
|
+
case "number": return "Double";
|
|
140
|
+
case "boolean": return "Boolean";
|
|
141
|
+
case "unknown": return "JsonElement";
|
|
142
|
+
case "array": return `List<${E(e.element)}>`;
|
|
143
|
+
case "optional": return `${E(e.inner)}?`;
|
|
144
|
+
case "ref": return e.name;
|
|
145
|
+
}
|
|
146
|
+
}, D = (e) => e.kind === "optional", O = (e) => {
|
|
147
|
+
if (e.fields.length === 0) return `@Serializable\npublic class ${e.name}`;
|
|
148
|
+
let t = ["@Serializable", `public data class ${e.name}(`];
|
|
149
|
+
for (let n of e.fields) {
|
|
150
|
+
let e = ee(n.jsonName), r = e.needsAlias ? `@SerialName(${JSON.stringify(n.jsonName)}) ` : "", i = D(n.type) ? " = null" : "";
|
|
151
|
+
t.push(` ${r}val ${e.ident}: ${E(n.type)}${i},`);
|
|
152
|
+
}
|
|
153
|
+
return t.push(")"), t.join("\n");
|
|
154
|
+
}, k = (e) => {
|
|
155
|
+
let t = ["@Serializable", `public enum class ${e.name} {`];
|
|
156
|
+
for (let n of e.values) t.push(` @SerialName(${JSON.stringify(n)}) ${d(n)},`);
|
|
157
|
+
return t.push("}"), t.join("\n");
|
|
158
|
+
}, A = (e) => e.output ? E(e.output) : "JsonElement", j = (e, t) => {
|
|
159
|
+
let n = t.filter((e) => e.kind !== "stream"), r = [
|
|
160
|
+
"/** One-shot RPC client (query / mutation / action) over HTTP. */",
|
|
161
|
+
`public class ${e}(`,
|
|
162
|
+
" private val baseUrl: String,",
|
|
163
|
+
" public var auth: AuthContext = AuthContext(),",
|
|
164
|
+
" private val transport: RpcTransport = RpcTransport(),",
|
|
165
|
+
") {"
|
|
166
|
+
];
|
|
167
|
+
for (let e of n) {
|
|
168
|
+
let t = A(e);
|
|
169
|
+
e.input === void 0 ? r.push("", ` /** \`${e.tag}\` (${e.kind}) */`, ` public suspend fun ${e.methodName}(): ${t} =`, ` transport.call(baseUrl, auth, ${JSON.stringify(e.tag)}, EmptyPayload)`) : r.push("", ` /** \`${e.tag}\` (${e.kind}) */`, ` public suspend fun ${e.methodName}(input: ${E(e.input)}): ${t} =`, ` transport.call(baseUrl, auth, ${JSON.stringify(e.tag)}, input)`);
|
|
170
|
+
}
|
|
171
|
+
return r.push("}"), r.join("\n");
|
|
172
|
+
}, M = (e, t) => {
|
|
173
|
+
let n = t.filter((e) => e.kind === "stream"), r = [
|
|
174
|
+
"/** Reactive subscription client over WebSocket — one Flow per server-push procedure. */",
|
|
175
|
+
`public class ${e}(`,
|
|
176
|
+
" private val socketUrl: String,",
|
|
177
|
+
" public var auth: AuthContext = AuthContext(),",
|
|
178
|
+
" private val transport: SocketTransport = SocketTransport(),",
|
|
179
|
+
") {"
|
|
180
|
+
];
|
|
181
|
+
for (let e of n) {
|
|
182
|
+
let t = A(e);
|
|
183
|
+
e.input === void 0 ? r.push("", ` /** \`${e.tag}\` (stream) */`, ` public fun ${e.methodName}(): Flow<${t}> =`, ` transport.subscribe(socketUrl, auth, ${JSON.stringify(e.tag)}, EmptyPayload)`) : r.push("", ` /** \`${e.tag}\` (stream) */`, ` public fun ${e.methodName}(input: ${E(e.input)}): Flow<${t}> =`, ` transport.subscribe(socketUrl, auth, ${JSON.stringify(e.tag)}, input)`);
|
|
184
|
+
}
|
|
185
|
+
return r.push("}"), r.join("\n");
|
|
186
|
+
}, N = (e) => `${T}
|
|
187
|
+
package ${e}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Authentication + tenant context carried on every request. The bearer token
|
|
191
|
+
* and tenant id are injected as request headers by the transport.
|
|
192
|
+
*/
|
|
193
|
+
public data class AuthContext(
|
|
194
|
+
public val token: String? = null,
|
|
195
|
+
public val tenant: String? = null,
|
|
196
|
+
) {
|
|
197
|
+
/** The header pairs this context contributes. Empty when unauthenticated. */
|
|
198
|
+
public fun headers(): List<Pair<String, String>> = buildList {
|
|
199
|
+
token?.let { add("authorization" to "Bearer $it") }
|
|
200
|
+
tenant?.let { add("x-tenant" to it) }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
`, P = (e) => `${T}
|
|
204
|
+
package ${e}
|
|
205
|
+
|
|
206
|
+
import kotlinx.serialization.SerialName
|
|
207
|
+
import kotlinx.serialization.Serializable
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Device registration for push notifications.
|
|
211
|
+
*
|
|
212
|
+
* STUB — this registers the device TOKEN with the app's registerDevice
|
|
213
|
+
* procedure (the storage side). The SENDER (APNs / FCM) is deliberately NOT part
|
|
214
|
+
* of the generated SDK: it needs per-tenant Apple / Firebase credentials managed
|
|
215
|
+
* by provider provisioning, which is server infrastructure, not client code.
|
|
216
|
+
*/
|
|
217
|
+
@Serializable
|
|
218
|
+
public data class PushRegistration(
|
|
219
|
+
@SerialName("deviceToken") val deviceToken: String,
|
|
220
|
+
val platform: String = "android",
|
|
221
|
+
val locale: String? = null,
|
|
222
|
+
val timezone: String? = null,
|
|
223
|
+
)
|
|
224
|
+
`, F = (e) => `${T}
|
|
225
|
+
package ${e}
|
|
226
|
+
|
|
227
|
+
import io.ktor.client.HttpClient
|
|
228
|
+
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
|
229
|
+
import io.ktor.client.plugins.websocket.WebSockets
|
|
230
|
+
import io.ktor.client.plugins.websocket.webSocketSession
|
|
231
|
+
import io.ktor.client.request.header
|
|
232
|
+
import io.ktor.client.request.post
|
|
233
|
+
import io.ktor.client.request.setBody
|
|
234
|
+
import io.ktor.client.statement.HttpResponse
|
|
235
|
+
import io.ktor.client.statement.bodyAsText
|
|
236
|
+
import io.ktor.http.ContentType
|
|
237
|
+
import io.ktor.http.contentType
|
|
238
|
+
import io.ktor.serialization.kotlinx.json.json
|
|
239
|
+
import io.ktor.websocket.Frame
|
|
240
|
+
import io.ktor.websocket.readText
|
|
241
|
+
import io.ktor.websocket.send
|
|
242
|
+
import kotlinx.coroutines.flow.Flow
|
|
243
|
+
import kotlinx.coroutines.flow.flow
|
|
244
|
+
import kotlinx.serialization.KSerializer
|
|
245
|
+
import kotlinx.serialization.Serializable
|
|
246
|
+
import kotlinx.serialization.json.Json
|
|
247
|
+
import kotlinx.serialization.json.JsonElement
|
|
248
|
+
import kotlinx.serialization.serializer
|
|
249
|
+
|
|
250
|
+
/** Empty payload for no-argument procedures. */
|
|
251
|
+
@Serializable
|
|
252
|
+
public object EmptyPayload
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The RPC request envelope sent to the api. Mirrors the wire shape
|
|
256
|
+
* \`{ _tag: "Request", id, tag, payload, headers }\`.
|
|
257
|
+
*/
|
|
258
|
+
@Serializable
|
|
259
|
+
internal data class RpcRequest<Payload>(
|
|
260
|
+
val _tag: String = "Request",
|
|
261
|
+
val id: String,
|
|
262
|
+
val tag: String,
|
|
263
|
+
val payload: Payload,
|
|
264
|
+
val headers: Map<String, String> = emptyMap(),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
internal val sdkJson: Json = Json { ignoreUnknownKeys = true; encodeDefaults = true }
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* HTTP transport for one-shot calls.
|
|
271
|
+
*
|
|
272
|
+
* NOTE — the exact @effect/rpc response FRAMING is the one seam to validate
|
|
273
|
+
* against a live server; this decodes the success value from the response body.
|
|
274
|
+
* Correct type mapping + one method per procedure is the generator's guarantee;
|
|
275
|
+
* the wire framing is the cross-language integration step.
|
|
276
|
+
*/
|
|
277
|
+
public class RpcTransport(private val client: HttpClient = defaultClient()) {
|
|
278
|
+
public suspend inline fun <reified Payload, reified Result> call(
|
|
279
|
+
baseUrl: String, auth: AuthContext, tag: String, payload: Payload,
|
|
280
|
+
): Result {
|
|
281
|
+
val response: HttpResponse = client.post("$baseUrl/rpc") {
|
|
282
|
+
contentType(ContentType.Application.Json)
|
|
283
|
+
for ((k, v) in auth.headers()) header(k, v)
|
|
284
|
+
setBody(RpcRequest(id = randomId(), tag = tag, payload = payload))
|
|
285
|
+
}
|
|
286
|
+
return sdkJson.decodeFromString(serializer(), response.bodyAsText())
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
public companion object {
|
|
290
|
+
public fun defaultClient(): HttpClient = HttpClient {
|
|
291
|
+
install(ContentNegotiation) { json(sdkJson) }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** WebSocket transport for reactive subscriptions. */
|
|
297
|
+
public class SocketTransport(private val client: HttpClient = defaultClient()) {
|
|
298
|
+
public inline fun <reified Payload, reified Value> subscribe(
|
|
299
|
+
socketUrl: String, auth: AuthContext, tag: String, payload: Payload,
|
|
300
|
+
): Flow<Value> = flow {
|
|
301
|
+
val session = client.webSocketSession(socketUrl)
|
|
302
|
+
val serializer: KSerializer<Value> = serializer()
|
|
303
|
+
session.send(sdkJson.encodeToString(RpcRequest(id = randomId(), tag = tag, payload = payload)))
|
|
304
|
+
for (frame in session.incoming) {
|
|
305
|
+
if (frame is Frame.Text) {
|
|
306
|
+
emit(sdkJson.decodeFromString(serializer, frame.readText()))
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
public companion object {
|
|
312
|
+
public fun defaultClient(): HttpClient = HttpClient {
|
|
313
|
+
install(WebSockets)
|
|
314
|
+
install(ContentNegotiation) { json(sdkJson) }
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
internal expect fun randomId(): String
|
|
320
|
+
|
|
321
|
+
/** A raw JSON value, for schemas with no faithful static type. */
|
|
322
|
+
public typealias AnyJson = JsonElement
|
|
323
|
+
`, I = (e) => `// ${T.replace("// ", "")}
|
|
324
|
+
plugins {
|
|
325
|
+
kotlin("multiplatform") version "2.0.20"
|
|
326
|
+
kotlin("plugin.serialization") version "2.0.20"
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
group = "com.voltro"
|
|
330
|
+
version = "0.1.0"
|
|
331
|
+
|
|
332
|
+
kotlin {
|
|
333
|
+
jvm()
|
|
334
|
+
iosArm64()
|
|
335
|
+
iosSimulatorArm64()
|
|
336
|
+
|
|
337
|
+
sourceSets {
|
|
338
|
+
val commonMain by getting {
|
|
339
|
+
dependencies {
|
|
340
|
+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1")
|
|
341
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
|
|
342
|
+
implementation("io.ktor:ktor-client-core:2.3.12")
|
|
343
|
+
implementation("io.ktor:ktor-client-content-negotiation:2.3.12")
|
|
344
|
+
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
|
|
345
|
+
implementation("io.ktor:ktor-client-websockets:2.3.12")
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Generated Kotlin Multiplatform module: ${e}
|
|
352
|
+
`, L = (e, t, n) => {
|
|
353
|
+
let r = `src/commonMain/kotlin/${n.replace(/\./g, "/")}`, i = [...e.enums.map(k), ...e.models.map(O)].join("\n\n");
|
|
354
|
+
return [
|
|
355
|
+
{
|
|
356
|
+
path: "build.gradle.kts",
|
|
357
|
+
content: I(t)
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
path: `${r}/Models.kt`,
|
|
361
|
+
content: `${T}\npackage ${n}\n\nimport kotlinx.serialization.SerialName
|
|
362
|
+
import kotlinx.serialization.Serializable
|
|
363
|
+
import kotlinx.serialization.json.JsonElement\n\n${i}${i ? "\n" : ""}`
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
path: `${r}/Client.kt`,
|
|
367
|
+
content: `${T}\npackage ${n}\n\nimport kotlinx.serialization.json.JsonElement\n\n${j(`${t}Client`, e.procedures)}\n`
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
path: `${r}/Subscriptions.kt`,
|
|
371
|
+
content: `${T}\npackage ${n}\n\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.serialization.json.JsonElement\n\n${M(`${t}Subscriptions`, e.procedures)}\n`
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
path: `${r}/Auth.kt`,
|
|
375
|
+
content: N(n)
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
path: `${r}/Push.kt`,
|
|
379
|
+
content: P(n)
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
path: `${r}/RpcTransport.kt`,
|
|
383
|
+
content: F(n)
|
|
384
|
+
}
|
|
385
|
+
];
|
|
386
|
+
}, R = (e) => {
|
|
387
|
+
switch (e.kind) {
|
|
388
|
+
case "string": return "String";
|
|
389
|
+
case "integer": return "Int";
|
|
390
|
+
case "number": return "Double";
|
|
391
|
+
case "boolean": return "Bool";
|
|
392
|
+
case "unknown": return "AnyCodable";
|
|
393
|
+
case "array": return `[${R(e.element)}]`;
|
|
394
|
+
case "optional": return `${R(e.inner)}?`;
|
|
395
|
+
case "ref": return e.name;
|
|
396
|
+
}
|
|
397
|
+
}, z = (e) => e.kind === "optional", B = (e) => {
|
|
398
|
+
let t = [`public struct ${e.name}: Codable, Equatable, Sendable {`], n = e.fields.map((e) => ({
|
|
399
|
+
field: e,
|
|
400
|
+
id: w(e.jsonName)
|
|
401
|
+
}));
|
|
402
|
+
for (let { field: e, id: r } of n) t.push(` public var ${r.ident}: ${R(e.type)}`);
|
|
403
|
+
if (n.some(({ id: e }) => e.needsAlias)) {
|
|
404
|
+
t.push("", " enum CodingKeys: String, CodingKey {");
|
|
405
|
+
for (let { field: e, id: r } of n) t.push(r.needsAlias ? ` case ${r.ident} = ${JSON.stringify(e.jsonName)}` : ` case ${r.ident}`);
|
|
406
|
+
t.push(" }");
|
|
407
|
+
}
|
|
408
|
+
let r = n.map(({ field: e, id: t }) => z(e.type) ? `${t.ident}: ${R(e.type)} = nil` : `${t.ident}: ${R(e.type)}`);
|
|
409
|
+
t.push("", ` public init(${r.join(", ")}) {`);
|
|
410
|
+
for (let { id: e } of n) t.push(` self.${e.ident} = ${e.ident}`);
|
|
411
|
+
return t.push(" }", "}"), t.join("\n");
|
|
412
|
+
}, te = (e) => {
|
|
413
|
+
let t = [`public enum ${e.name}: String, Codable, Sendable {`];
|
|
414
|
+
for (let n of e.values) {
|
|
415
|
+
let e = w(n).ident;
|
|
416
|
+
t.push(e === n ? ` case ${e}` : ` case ${e} = ${JSON.stringify(n)}`);
|
|
417
|
+
}
|
|
418
|
+
return t.push("}"), t.join("\n");
|
|
419
|
+
}, V = (e) => {
|
|
420
|
+
let t = e.output ? R(e.output) : "AnyCodable";
|
|
421
|
+
return e.input === void 0 ? {
|
|
422
|
+
signature: "()",
|
|
423
|
+
payload: "EmptyPayload()",
|
|
424
|
+
returns: t
|
|
425
|
+
} : {
|
|
426
|
+
signature: `(_ input: ${R(e.input)})`,
|
|
427
|
+
payload: "input",
|
|
428
|
+
returns: t
|
|
429
|
+
};
|
|
430
|
+
}, H = (e, t) => {
|
|
431
|
+
let n = t.filter((e) => e.kind !== "stream"), r = [
|
|
432
|
+
"/// One-shot RPC client (query / mutation / action) over HTTP.",
|
|
433
|
+
`public struct ${e}: Sendable {`,
|
|
434
|
+
" public let baseURL: URL",
|
|
435
|
+
" public var auth: AuthContext",
|
|
436
|
+
" private let transport: RpcTransport",
|
|
437
|
+
"",
|
|
438
|
+
" public init(baseURL: URL, auth: AuthContext = AuthContext(), session: URLSession = .shared) {",
|
|
439
|
+
" self.baseURL = baseURL",
|
|
440
|
+
" self.auth = auth",
|
|
441
|
+
" self.transport = RpcTransport(session: session)",
|
|
442
|
+
" }"
|
|
443
|
+
];
|
|
444
|
+
for (let e of n) {
|
|
445
|
+
let { signature: t, payload: n, returns: i } = V(e);
|
|
446
|
+
r.push("", ` /// \`${e.tag}\` (${e.kind})`, ` public func ${e.methodName}${t} async throws -> ${i} {`, ` try await transport.call(baseURL: baseURL, auth: auth, tag: ${JSON.stringify(e.tag)}, payload: ${n})`, " }");
|
|
447
|
+
}
|
|
448
|
+
return r.push("}"), r.join("\n");
|
|
449
|
+
}, U = (e, t) => {
|
|
450
|
+
let n = t.filter((e) => e.kind === "stream"), r = [
|
|
451
|
+
"/// Reactive subscription client over WebSocket. Each method opens a stream",
|
|
452
|
+
"/// for one server-push procedure and yields decoded values as they arrive.",
|
|
453
|
+
`public final class ${e}: @unchecked Sendable {`,
|
|
454
|
+
" public let socketURL: URL",
|
|
455
|
+
" public var auth: AuthContext",
|
|
456
|
+
" private let transport: SocketTransport",
|
|
457
|
+
"",
|
|
458
|
+
" public init(socketURL: URL, auth: AuthContext = AuthContext(), session: URLSession = .shared) {",
|
|
459
|
+
" self.socketURL = socketURL",
|
|
460
|
+
" self.auth = auth",
|
|
461
|
+
" self.transport = SocketTransport(session: session)",
|
|
462
|
+
" }"
|
|
463
|
+
];
|
|
464
|
+
for (let e of n) {
|
|
465
|
+
let t = e.output ? R(e.output) : "AnyCodable", { signature: n, payload: i } = V(e);
|
|
466
|
+
r.push("", ` /// \`${e.tag}\` (stream)`, ` public func ${e.methodName}${n} -> AsyncThrowingStream<${t}, Error> {`, ` transport.subscribe(socketURL: socketURL, auth: auth, tag: ${JSON.stringify(e.tag)}, payload: ${i})`, " }");
|
|
467
|
+
}
|
|
468
|
+
return r.push("}"), r.join("\n");
|
|
469
|
+
}, W = "import Foundation\n\n/// Authentication + tenant context carried on every request. The bearer token\n/// and tenant id are injected as request headers by the transport.\npublic struct AuthContext: Sendable {\n public var token: String?\n public var tenant: String?\n\n public init(token: String? = nil, tenant: String? = nil) {\n self.token = token\n self.tenant = tenant\n }\n\n /// The header pairs this context contributes. Empty when unauthenticated.\n public func headers() -> [(String, String)] {\n var out: [(String, String)] = []\n if let token = token { out.append((\"authorization\", \"Bearer \\(token)\")) }\n if let tenant = tenant { out.append((\"x-tenant\", tenant)) }\n return out\n }\n}\n", G = "import Foundation\n\n/// Device registration for push notifications.\n///\n/// STUB — this registers the device TOKEN with the app's `registerDevice`\n/// procedure (the storage side). The SENDER (APNs / FCM) is deliberately NOT\n/// part of the generated SDK: it needs per-tenant Apple Developer / Firebase\n/// credentials managed by provider provisioning, which is server infrastructure,\n/// not client code. Wire this to your app's registerDevice mutation.\npublic struct PushRegistration: Codable, Equatable, Sendable {\n public var deviceToken: String\n public var platform: String\n public var locale: String?\n public var timezone: String?\n\n public init(deviceToken: String, platform: String = \"ios\", locale: String? = nil, timezone: String? = nil) {\n self.deviceToken = deviceToken\n self.platform = platform\n self.locale = locale\n self.timezone = timezone\n }\n}\n\npublic extension AuthContext {\n /// Convenience: build a registration payload after the OS issues a token.\n static func pushRegistration(deviceToken: String, platform: String = \"ios\") -> PushRegistration {\n PushRegistration(deviceToken: deviceToken, platform: platform)\n }\n}\n", K = "import Foundation\n\n/// The RPC request envelope sent to the api's `/rpc` endpoint. Mirrors the\n/// wire shape `{ _tag: \"Request\", id, tag, payload, headers }`.\nstruct RpcRequest<Payload: Encodable>: Encodable {\n let _tag = \"Request\"\n let id: String\n let tag: String\n let payload: Payload\n let headers: [String: String]\n}\n\n/// An empty payload for no-argument procedures.\nstruct EmptyPayload: Encodable {}\n\n/// A dynamic JSON value for schemas with no faithful static type.\npublic struct AnyCodable: Codable, Equatable, Sendable {\n public let value: Data\n public init(from decoder: Decoder) throws {\n let container = try decoder.singleValueContainer()\n if let raw = try? container.decode(AnyCodableRaw.self) {\n self.value = raw.data\n } else {\n self.value = Data()\n }\n }\n public func encode(to encoder: Encoder) throws {\n var container = encoder.singleValueContainer()\n try container.encode(AnyCodableRaw(data: value))\n }\n}\n\nstruct AnyCodableRaw: Codable {\n let data: Data\n init(data: Data) { self.data = data }\n init(from decoder: Decoder) throws {\n let container = try decoder.singleValueContainer()\n let json = try container.decode(JSONValue.self)\n self.data = try JSONEncoder().encode(json)\n }\n func encode(to encoder: Encoder) throws {\n var container = encoder.singleValueContainer()\n try container.encode(try JSONDecoder().decode(JSONValue.self, from: data))\n }\n}\n\nindirect enum JSONValue: Codable {\n case null\n case bool(Bool)\n case number(Double)\n case string(String)\n case array([JSONValue])\n case object([String: JSONValue])\n init(from decoder: Decoder) throws {\n let c = try decoder.singleValueContainer()\n if c.decodeNil() { self = .null }\n else if let b = try? c.decode(Bool.self) { self = .bool(b) }\n else if let n = try? c.decode(Double.self) { self = .number(n) }\n else if let s = try? c.decode(String.self) { self = .string(s) }\n else if let a = try? c.decode([JSONValue].self) { self = .array(a) }\n else if let o = try? c.decode([String: JSONValue].self) { self = .object(o) }\n else { self = .null }\n }\n func encode(to encoder: Encoder) throws {\n var c = encoder.singleValueContainer()\n switch self {\n case .null: try c.encodeNil()\n case .bool(let b): try c.encode(b)\n case .number(let n): try c.encode(n)\n case .string(let s): try c.encode(s)\n case .array(let a): try c.encode(a)\n case .object(let o): try c.encode(o)\n }\n }\n}\n\npublic enum RpcError: Error {\n case http(status: Int, body: Data)\n case decoding(Error)\n case transport(Error)\n}\n\n/// HTTP transport for one-shot calls.\n///\n/// NOTE — the exact @effect/rpc response FRAMING (success/failure envelope over\n/// HTTP) is the one seam that must be validated against a live server; this\n/// decodes the success value from the response body and surfaces non-2xx as\n/// `RpcError.http`. Correct type mapping + one method per procedure is what the\n/// generator guarantees; the wire framing is the cross-language integration step.\nstruct RpcTransport: Sendable {\n let session: URLSession\n\n func call<Payload: Encodable, Result: Decodable>(\n baseURL: URL, auth: AuthContext, tag: String, payload: Payload\n ) async throws -> Result {\n var request = URLRequest(url: baseURL.appendingPathComponent(\"rpc\"))\n request.httpMethod = \"POST\"\n request.setValue(\"application/json\", forHTTPHeaderField: \"content-type\")\n for (k, v) in auth.headers() { request.setValue(v, forHTTPHeaderField: k) }\n let envelope = RpcRequest(id: UUID().uuidString, tag: tag, payload: payload, headers: [:])\n request.httpBody = try JSONEncoder().encode(envelope)\n let (data, response): (Data, URLResponse)\n do { (data, response) = try await session.data(for: request) }\n catch { throw RpcError.transport(error) }\n if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {\n throw RpcError.http(status: http.statusCode, body: data)\n }\n do { return try JSONDecoder().decode(Result.self, from: data) }\n catch { throw RpcError.decoding(error) }\n }\n}\n\n/// WebSocket transport for reactive subscriptions.\nstruct SocketTransport: Sendable {\n let session: URLSession\n\n func subscribe<Payload: Encodable, Value: Decodable>(\n socketURL: URL, auth: AuthContext, tag: String, payload: Payload\n ) -> AsyncThrowingStream<Value, Error> {\n AsyncThrowingStream { continuation in\n let task = session.webSocketTask(with: socketURL)\n task.resume()\n let envelope = RpcRequest(id: UUID().uuidString, tag: tag, payload: payload, headers: [:])\n if let frame = try? JSONEncoder().encode(envelope), let text = String(data: frame, encoding: .utf8) {\n task.send(.string(text)) { _ in }\n }\n func receive() {\n task.receive { result in\n switch result {\n case .failure(let error):\n continuation.finish(throwing: error)\n case .success(let message):\n let data: Data?\n switch message {\n case .string(let s): data = s.data(using: .utf8)\n case .data(let d): data = d\n @unknown default: data = nil\n }\n if let data = data, let value = try? JSONDecoder().decode(Value.self, from: data) {\n continuation.yield(value)\n }\n receive()\n }\n }\n }\n receive()\n continuation.onTermination = { _ in task.cancel(with: .goingAway, reason: nil) }\n }\n }\n}\n", q = (e) => `// swift-tools-version:5.9
|
|
470
|
+
import PackageDescription
|
|
471
|
+
|
|
472
|
+
let package = Package(
|
|
473
|
+
name: ${JSON.stringify(e)},
|
|
474
|
+
platforms: [.iOS(.v15), .macOS(.v12)],
|
|
475
|
+
products: [
|
|
476
|
+
.library(name: ${JSON.stringify(e)}, targets: [${JSON.stringify(e)}]),
|
|
477
|
+
],
|
|
478
|
+
targets: [
|
|
479
|
+
.target(name: ${JSON.stringify(e)}, path: "Sources/${e}"),
|
|
480
|
+
]
|
|
481
|
+
)
|
|
482
|
+
`, J = "// Generated by `voltro build api --target swift`. Do not edit by hand.", Y = (e, t) => {
|
|
483
|
+
let n = `Sources/${t}`, r = [...e.enums.map(te), ...e.models.map(B)].join("\n\n");
|
|
484
|
+
return [
|
|
485
|
+
{
|
|
486
|
+
path: "Package.swift",
|
|
487
|
+
content: q(t)
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
path: `${n}/Models.swift`,
|
|
491
|
+
content: `${J}\nimport Foundation\n\n${r}${r ? "\n" : ""}`
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
path: `${n}/Client.swift`,
|
|
495
|
+
content: `${J}\nimport Foundation\n\n${H(`${t}Client`, e.procedures)}\n`
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
path: `${n}/Subscriptions.swift`,
|
|
499
|
+
content: `${J}\nimport Foundation\n\n${U(`${t}Subscriptions`, e.procedures)}\n`
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
path: `${n}/Auth.swift`,
|
|
503
|
+
content: `${J}\n${W}`
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
path: `${n}/Push.swift`,
|
|
507
|
+
content: `${J}\n${G}`
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
path: `${n}/RpcTransport.swift`,
|
|
511
|
+
content: `${J}\n${K}`
|
|
512
|
+
}
|
|
513
|
+
];
|
|
514
|
+
}, X = a({ scope: "voltro:sdk" }), Z = "VoltroClient", Q = "com.voltro.client", ne = (e, t) => {
|
|
515
|
+
let n = y(e), r = t.packageName ?? Z;
|
|
516
|
+
return t.target === "swift" ? Y(n, r) : L(n, r, t.kotlinPackage ?? Q);
|
|
517
|
+
}, re = async (e, n) => {
|
|
518
|
+
for (let i of e) {
|
|
519
|
+
let e = r(n, i.path);
|
|
520
|
+
await o.mkdir(t(e), { recursive: !0 }), await o.writeFile(e, i.content, "utf8");
|
|
521
|
+
}
|
|
522
|
+
}, ie = async (e) => {
|
|
523
|
+
let { offlineManifest: t } = await import("./checkCommand-DBvZvCh1.js"), { manifest: n } = await t(e);
|
|
524
|
+
return n;
|
|
525
|
+
}, ae = async (e) => {
|
|
526
|
+
let t = ne(await (e.manifestProvider ?? ie)(e.root), {
|
|
527
|
+
target: e.target,
|
|
528
|
+
...e.packageName === void 0 ? {} : { packageName: e.packageName },
|
|
529
|
+
...e.kotlinPackage === void 0 ? {} : { kotlinPackage: e.kotlinPackage }
|
|
530
|
+
});
|
|
531
|
+
return await re(t, e.outDir), {
|
|
532
|
+
files: t,
|
|
533
|
+
outDir: e.outDir
|
|
534
|
+
};
|
|
535
|
+
}, $ = (e, t) => {
|
|
536
|
+
let n = e.indexOf(t);
|
|
537
|
+
return n !== -1 && n + 1 < e.length ? e[n + 1] : void 0;
|
|
538
|
+
}, oe = (e, t) => {
|
|
539
|
+
let i = $(e, "--target");
|
|
540
|
+
if (i !== "swift" && i !== "kotlin") return { error: `--target must be "swift" or "kotlin" (got ${i === void 0 ? "<none>" : JSON.stringify(i)})` };
|
|
541
|
+
let a = $(e, "--out"), o = $(e, "--name"), s = $(e, "--kotlin-package"), c = new Set([
|
|
542
|
+
i,
|
|
543
|
+
a,
|
|
544
|
+
o,
|
|
545
|
+
s
|
|
546
|
+
].filter((e) => e !== void 0)), l = /* @__PURE__ */ new Set([
|
|
547
|
+
"--target",
|
|
548
|
+
"--out",
|
|
549
|
+
"--name",
|
|
550
|
+
"--kotlin-package"
|
|
551
|
+
]), u = e.find((e) => !e.startsWith("-") && e !== "api" && !l.has(e) && !c.has(e));
|
|
552
|
+
return {
|
|
553
|
+
target: i,
|
|
554
|
+
root: u ? n(u) ? u : r(t, u) : t,
|
|
555
|
+
...a === void 0 ? {} : { outDir: a },
|
|
556
|
+
...o === void 0 ? {} : { packageName: o },
|
|
557
|
+
...s === void 0 ? {} : { kotlinPackage: s }
|
|
558
|
+
};
|
|
559
|
+
}, se = (e, t) => r(e, "sdk", t), ce = async (t, n = process.cwd()) => {
|
|
560
|
+
let r = oe(t, n);
|
|
561
|
+
if ("error" in r) return console.error(`voltro build --target: ${r.error}`), 1;
|
|
562
|
+
let a = r.outDir ?? se(r.root, r.target);
|
|
563
|
+
return e(i.gen(function* () {
|
|
564
|
+
let e = yield* i.tryPromise({
|
|
565
|
+
try: () => ae({
|
|
566
|
+
root: r.root,
|
|
567
|
+
target: r.target,
|
|
568
|
+
outDir: a,
|
|
569
|
+
...r.packageName === void 0 ? {} : { packageName: r.packageName },
|
|
570
|
+
...r.kotlinPackage === void 0 ? {} : { kotlinPackage: r.kotlinPackage }
|
|
571
|
+
}),
|
|
572
|
+
catch: (e) => e
|
|
573
|
+
}).pipe(i.orDie);
|
|
574
|
+
return yield* i.sync(() => X.info("SDK generated", {
|
|
575
|
+
target: r.target,
|
|
576
|
+
files: e.files.length,
|
|
577
|
+
out: a
|
|
578
|
+
})), 0;
|
|
579
|
+
}), { scope: "voltro:sdk" });
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
export { ce as runSdkBuildCommand };
|