@gonvex/cli 0.4.1 → 0.5.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/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  The Gonvex CLI for app-local development with the Gonvex runtime.
4
4
 
5
- Gonvex gives you a Convex-style workflow with TypeScript backend functions,
6
- generated TypeScript bindings, React hooks, realtime subscriptions,
7
- and a local runtime backed by Postgres.
5
+ Gonvex provides TypeScript backend functions, generated TypeScript bindings,
6
+ React hooks, transaction-ordered realtime delivery, and a persistent Local
7
+ Replica backed by Postgres.
8
8
 
9
9
  ## Install
10
10
 
@@ -88,6 +88,21 @@ Run a one-shot sync for CI or Docker builds:
88
88
  npx gonvex dev --once
89
89
  ```
90
90
 
91
+ Emit or verify the deterministic interactive-function catalog used by agent
92
+ orchestration:
93
+
94
+ ```bash
95
+ npx gonvex functions emit --format ndjson --output agent-api.ndjson
96
+ npx gonvex functions emit --format typescript --output agent-api.d.ts
97
+ npx gonvex functions check
98
+ ```
99
+
100
+ Both files are derived from the compiled module artifact, sorted by public
101
+ function path, and contain the artifact hash. The NDJSON format stores one
102
+ complete function contract per line. The TypeScript declaration renders the
103
+ same schemas, classification, offline policy, optimistic transaction,
104
+ description, tags, and confirmation requirement as literal types.
105
+
91
106
  By default, `gonvex dev` streams only runtime warnings and errors to the
92
107
  terminal. To tail every Query, Reducer, and Action:
93
108
 
@@ -0,0 +1,19 @@
1
+ import type { JsonValue, ModuleArtifact, ModuleFunction, ModuleSchema } from "./manifest-types.js";
2
+ export type FunctionCatalogFormat = "ndjson" | "typescript";
3
+ export type FunctionCatalogEntry = {
4
+ path: string;
5
+ kind: ModuleFunction["kind"];
6
+ artifactHash: string;
7
+ args: ModuleSchema;
8
+ result: ModuleSchema;
9
+ classification: "interactive";
10
+ interactive: true;
11
+ offline: JsonValue | null;
12
+ optimistic: JsonValue | null;
13
+ description: string;
14
+ tags: string[];
15
+ confirmation: "none" | "required" | "destructive";
16
+ };
17
+ export declare function functionCatalogEntries(artifact: ModuleArtifact): FunctionCatalogEntry[];
18
+ export declare function renderFunctionCatalog(artifact: ModuleArtifact, format: FunctionCatalogFormat): string;
19
+ export declare function renderPortableSchema(schema: ModuleSchema, indent?: number): string;
@@ -0,0 +1,107 @@
1
+ const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
2
+ export function functionCatalogEntries(artifact) {
3
+ return Object.entries(artifact.functions)
4
+ .filter(([, definition]) => !definition.internal && (definition.classification === "interactive" || (definition.classification === undefined
5
+ && (definition.interactive === true || (definition.interactive === undefined && definition.kind !== "action")))))
6
+ .sort(([left], [right]) => compareText(left, right))
7
+ .map(([path, definition]) => ({
8
+ path,
9
+ kind: definition.kind,
10
+ artifactHash: artifact.hash,
11
+ args: definition.args ?? { kind: "object", fields: {} },
12
+ result: definition.result ?? { kind: "any" },
13
+ classification: "interactive",
14
+ interactive: true,
15
+ offline: definition.offline ?? null,
16
+ optimistic: definition.optimistic ?? null,
17
+ description: definition.description ?? "",
18
+ tags: [...(definition.agent?.tags ?? [])].sort(),
19
+ confirmation: definition.agent?.confirmation ?? "none",
20
+ }));
21
+ }
22
+ export function renderFunctionCatalog(artifact, format) {
23
+ const entries = functionCatalogEntries(artifact);
24
+ if (format === "ndjson") {
25
+ return entries.map((entry) => JSON.stringify(entry)).join("\n") + (entries.length > 0 ? "\n" : "");
26
+ }
27
+ const lines = [
28
+ "// Generated by Gonvex. Do not edit.",
29
+ `export declare const artifactHash: ${JSON.stringify(artifact.hash)};`,
30
+ "export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };",
31
+ "export type Id<Entity extends string> = string & { readonly __gonvexEntity: Entity };",
32
+ "export interface GonvexAgentAPI {",
33
+ ];
34
+ for (const entry of entries) {
35
+ lines.push(` ${JSON.stringify(entry.path)}: {`);
36
+ lines.push(` kind: ${JSON.stringify(entry.kind)};`);
37
+ lines.push(' classification: "interactive";');
38
+ lines.push(" interactive: true;");
39
+ lines.push(` args: ${renderPortableSchema(entry.args, 4)};`);
40
+ lines.push(` result: ${renderPortableSchema(entry.result, 4)};`);
41
+ lines.push(` offline: ${renderJsonLiteralType(entry.offline)};`);
42
+ lines.push(` optimistic: ${renderJsonLiteralType(entry.optimistic)};`);
43
+ lines.push(` description: ${JSON.stringify(entry.description)};`);
44
+ lines.push(` tags: readonly [${entry.tags.map((tag) => JSON.stringify(tag)).join(", ")}];`);
45
+ lines.push(` confirmation: ${JSON.stringify(entry.confirmation)};`);
46
+ lines.push(" };");
47
+ }
48
+ lines.push("}", "");
49
+ return lines.join("\n");
50
+ }
51
+ export function renderPortableSchema(schema, indent = 0) {
52
+ if (!schema || typeof schema !== "object" || Array.isArray(schema))
53
+ return "unknown";
54
+ const value = schema;
55
+ switch (value.kind) {
56
+ case "string": return "string";
57
+ case "number": return "number";
58
+ case "boolean": return "boolean";
59
+ case "null": return "null";
60
+ case "any": return "JsonValue";
61
+ case "id": return `Id<${JSON.stringify(String(value.entity ?? ""))}>`;
62
+ case "literal": return renderLiteral(value.value);
63
+ case "array": return `Array<${renderPortableSchema(value.items, indent)}>`;
64
+ case "record": return `Record<string, ${renderPortableSchema(value.values, indent)}>`;
65
+ case "optional": return `${renderPortableSchema(value.value, indent)} | undefined`;
66
+ case "object": {
67
+ const fields = value.fields && typeof value.fields === "object" && !Array.isArray(value.fields)
68
+ ? Object.entries(value.fields).sort(([left], [right]) => compareText(left, right))
69
+ : [];
70
+ if (fields.length === 0)
71
+ return "Record<string, never>";
72
+ const padding = " ".repeat(indent);
73
+ const childPadding = " ".repeat(indent + 2);
74
+ const rendered = fields.map(([name, field]) => {
75
+ const optional = field?.kind === "optional";
76
+ const actual = field.kind === "optional" ? field.value : field;
77
+ return `${childPadding}${renderProperty(name)}${optional ? "?" : ""}: ${renderPortableSchema(actual, indent + 2)};`;
78
+ });
79
+ if (value.allowUnknown === true)
80
+ rendered.push(`${childPadding}[key: string]: JsonValue;`);
81
+ return `{\n${rendered.join("\n")}\n${padding}}`;
82
+ }
83
+ default: return "unknown";
84
+ }
85
+ }
86
+ function renderProperty(value) {
87
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
88
+ }
89
+ function renderLiteral(value) {
90
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
91
+ return JSON.stringify(value);
92
+ }
93
+ return "JsonValue";
94
+ }
95
+ function renderJsonLiteralType(value) {
96
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
97
+ return JSON.stringify(value);
98
+ }
99
+ if (Array.isArray(value)) {
100
+ return `readonly [${value.map(renderJsonLiteralType).join(", ")}]`;
101
+ }
102
+ const fields = Object.entries(value)
103
+ .sort(([left], [right]) => compareText(left, right))
104
+ .map(([name, field]) => `readonly ${renderProperty(name)}: ${renderJsonLiteralType(field)};`);
105
+ return `{ ${fields.join(" ")} }`;
106
+ }
107
+ //# sourceMappingURL=function-catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-catalog.js","sourceRoot":"","sources":["../src/function-catalog.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  export type * from "./manifest-types.js";
3
3
  export type { ProjectLanguage } from "./module-artifact.js";
4
+ export * from "./function-catalog.js";
4
5
  export declare function main(argv?: string[]): Promise<void>;
5
6
  export declare function runCreate(argv: string[]): Promise<void>;
6
7
  export declare function runAuth(argv: string[]): Promise<void>;
package/dist/index.js CHANGED
@@ -9,6 +9,8 @@ import { dirname, join, relative, resolve } from "node:path";
9
9
  import { Writable } from "node:stream";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import { buildModuleArtifact, detectProjectLanguage, moduleManifestFunctions, moduleSourceFiles, isModuleSchema, } from "./module-artifact.js";
12
+ import { renderFunctionCatalog } from "./function-catalog.js";
13
+ export * from "./function-catalog.js";
12
14
  const defaultRuntimeURL = "http://localhost:8080";
13
15
  const runtimeSyncRetryMs = 5000;
14
16
  const runtimeStateCheckMs = 2500;
@@ -57,6 +59,10 @@ export async function main(argv = process.argv.slice(2)) {
57
59
  await runCodegen(argv.slice(1));
58
60
  return;
59
61
  }
62
+ if (command === "functions") {
63
+ await runFunctions(argv.slice(1));
64
+ return;
65
+ }
60
66
  if (command === "init") {
61
67
  await runInit(argv.slice(1));
62
68
  return;
@@ -96,6 +102,42 @@ export async function main(argv = process.argv.slice(2)) {
96
102
  printHelp();
97
103
  throw new Error(`unknown command ${command}`);
98
104
  }
105
+ async function runFunctions(argv) {
106
+ const action = argv[0];
107
+ if (!action || !["emit", "check"].includes(action)) {
108
+ throw new Error("usage: gonvex functions <emit|check> [--format ndjson|typescript] [--output FILE] [--project PATH]");
109
+ }
110
+ const root = resolve(valueFor(argv, "--project") ?? ".");
111
+ const sources = await collectBackendSources(root);
112
+ const settings = await loadSettings(root, {});
113
+ const manifest = await buildManifest(root, sources, settings.projectID);
114
+ const targets = action === "check"
115
+ ? [
116
+ { format: "ndjson", output: resolve(root, "agent-api.ndjson") },
117
+ { format: "typescript", output: resolve(root, "agent-api.d.ts") },
118
+ ]
119
+ : [{
120
+ format: normalizeCatalogFormat(valueFor(argv, "--format") ?? "ndjson"),
121
+ output: resolve(root, valueFor(argv, "--output") ?? (valueFor(argv, "--format") === "typescript" ? "agent-api.d.ts" : "agent-api.ndjson")),
122
+ }];
123
+ for (const target of targets) {
124
+ const expected = renderFunctionCatalog(manifest.module, target.format);
125
+ if (action === "check") {
126
+ const actual = await readFile(target.output, "utf8").catch(() => "");
127
+ if (actual !== expected)
128
+ throw new Error(`${relative(root, target.output)} is stale; run gonvex functions emit --format ${target.format} --output ${relative(root, target.output)}`);
129
+ continue;
130
+ }
131
+ await mkdir(dirname(target.output), { recursive: true });
132
+ await writeFile(target.output, expected);
133
+ console.log(`[gonvex] wrote ${relative(root, target.output)} for artifact ${manifest.module.hash}`);
134
+ }
135
+ }
136
+ function normalizeCatalogFormat(value) {
137
+ if (value === "ndjson" || value === "typescript")
138
+ return value;
139
+ throw new Error("--format must be ndjson or typescript");
140
+ }
99
141
  export async function runCreate(argv) {
100
142
  const optionNames = ["--template", "--runtime-url", "--runtime", "--database-mode", "--origin", "--callback-path", "--signup-mode", "--owner"];
101
143
  const target = positionalArgs(argv, optionNames)[0] ?? "my-gonvex-app";
@@ -1186,7 +1228,7 @@ function renderAPI(manifest) {
1186
1228
  const internalRoot = {};
1187
1229
  const optimisticTransactions = {};
1188
1230
  const functionTypes = [];
1189
- for (const [path, entry] of Object.entries(manifest.functions).sort(([a], [b]) => a.localeCompare(b))) {
1231
+ for (const [path, entry] of Object.entries(manifest.functions).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
1190
1232
  const parts = path.split(".").filter(Boolean);
1191
1233
  if (parts.length === 0)
1192
1234
  continue;
@@ -1417,7 +1459,7 @@ function renderSchemaObject(scope, tables, depth) {
1417
1459
  function renderObject(value, depth) {
1418
1460
  if (!value || typeof value !== "object" || Array.isArray(value))
1419
1461
  return JSON.stringify(value);
1420
- const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
1462
+ const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
1421
1463
  if (isFunctionRef(value)) {
1422
1464
  const visible = Object.fromEntries(entries.filter(([key]) => !key.startsWith("__")));
1423
1465
  return `${renderObject(visible, depth)} as unknown as FunctionReference<${JSON.stringify(value.kind)}, ${value.__argsType}, ${value.__resultType}>`;
@@ -2177,9 +2219,11 @@ function sleep(ms, signal) {
2177
2219
  });
2178
2220
  }
2179
2221
  function printHelp() {
2180
- console.log("Usage: gonvex <dev|codegen|init|create|env|auth|login|logout|whoami|project|token> [options]");
2222
+ console.log("Usage: gonvex <dev|codegen|functions|init|create|env|auth|login|logout|whoami|project|token> [options]");
2181
2223
  console.log(" gonvex dev [--project <path>] [--runtime-url <url>] [--project-id <id>] [--key <key>] [--once] [--verbose-logs] [-- <command>]");
2182
2224
  console.log(" gonvex codegen [--project <path>] [--project-id <id>]");
2225
+ console.log(" gonvex functions emit --format ndjson|typescript [--output FILE] [--project <path>]");
2226
+ console.log(" gonvex functions check [--project <path>]");
2183
2227
  console.log(" gonvex init [--template vite-react] [--project <id>] [--runtime <url>]");
2184
2228
  console.log(" gonvex create <app-name> [--runtime-url <url>] [--provision] [--database-mode single|multiTenant] [--google-auth] [--origin <url>]... [--signup-mode personal|inviteOnly] [--owner <email>]");
2185
2229
  console.log(" gonvex env <list|get|set|push|remove> [--project <path>] [--runtime-url <url>] [--project-id <id>] [--key <key>]");