@gonvex/cli 0.5.2-staging.2 → 0.5.2-staging.21

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/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { lazyReferenceRuntime, renderLazyReferences } from "./lazy-reference-bindings.js";
2
3
  import { createHash } from "node:crypto";
3
4
  import { spawn } from "node:child_process";
4
5
  import { existsSync, readFileSync, realpathSync } from "node:fs";
@@ -8,8 +9,10 @@ import { createInterface } from "node:readline/promises";
8
9
  import { dirname, join, relative, resolve } from "node:path";
9
10
  import { Writable } from "node:stream";
10
11
  import { fileURLToPath } from "node:url";
11
- import { buildModuleArtifact, detectProjectLanguage, moduleManifestFunctions, moduleSourceFiles, isModuleSchema, } from "./module-artifact.js";
12
+ import { buildModuleArtifact, detectProjectLanguage, moduleManifestFunctions, moduleSourceFiles, } from "./module-artifact.js";
12
13
  import { renderFunctionCatalog } from "./function-catalog.js";
14
+ import { localBindings, projectLocalSchema } from "./local-bindings.js";
15
+ import { analyzeLocalReducerDependencies, formatLocalDependencyViolations, localDependencyTables, localDependencyViolations, } from "./local-dependencies.js";
13
16
  export * from "./function-catalog.js";
14
17
  const defaultRuntimeURL = "http://localhost:8080";
15
18
  const runtimeSyncRetryMs = 5000;
@@ -1102,15 +1105,50 @@ async function buildManifest(root, sources, projectID) {
1102
1105
  entrypoint: sources.config.module?.entrypoint,
1103
1106
  bundle: sources.config.module?.bundle,
1104
1107
  });
1108
+ const functions = moduleManifestFunctions(module);
1109
+ await applyLocalDependencies(root, sources, functions);
1105
1110
  return {
1106
1111
  project: projectID,
1107
1112
  generatedAt: new Date().toISOString(),
1108
- functions: moduleManifestFunctions(module),
1113
+ functions,
1109
1114
  schema: emptySchemaDefinition(),
1110
1115
  module,
1111
1116
  ...(Object.keys(module.visibility).length > 0 ? { visibility: module.visibility } : {}),
1112
1117
  };
1113
1118
  }
1119
+ /**
1120
+ * Record each locally executing Reducer's table dependencies in the manifest
1121
+ * and reject dependencies that no generated local collection can satisfy.
1122
+ * See local-dependencies.ts for the analysis and its precision limits.
1123
+ */
1124
+ async function applyLocalDependencies(root, sources, functions) {
1125
+ const reducers = Object.entries(functions)
1126
+ .filter(([, entry]) => entry.kind === "reducer" && entry.localExecution === 1 && !entry.internal)
1127
+ .map(([path, entry]) => ({ path, file: entry.file, exportName: entry.handler }));
1128
+ if (!reducers.length)
1129
+ return;
1130
+ const mode = sources.config.module?.localDependencyCheck ?? "error";
1131
+ if (mode !== "error" && mode !== "warn")
1132
+ throw new Error('gonvex.json module.localDependencyCheck must be "error" or "warn"');
1133
+ const analyses = await analyzeLocalReducerDependencies({
1134
+ root, backendDir: join(root, "gonvex"), files: sources.moduleFiles, reducers, tsconfig: sources.config.module?.tsconfig,
1135
+ });
1136
+ const schema = await projectLocalSchema(root);
1137
+ const tableKeys = new Map(Object.entries(schema).map(([table, shape]) => [table, shape.key]));
1138
+ const localTables = new Set(Object.entries(functions)
1139
+ .filter(([path, entry]) => path.startsWith("__local.") && entry.replica)
1140
+ .map(([, entry]) => entry.replica.table));
1141
+ for (const analysis of analyses)
1142
+ functions[analysis.path].localDependencies = localDependencyTables(analysis, tableKeys);
1143
+ const violations = localDependencyViolations(analyses, localTables, tableKeys);
1144
+ if (!violations.length)
1145
+ return;
1146
+ const report = formatLocalDependencyViolations(violations);
1147
+ if (mode === "error") {
1148
+ throw new Error(`Local Reducer dependency check failed. ${report}\nSet gonvex.json module.localDependencyCheck to "warn" only while migrating.`);
1149
+ }
1150
+ console.warn(color.yellow(`[gonvex] warning: ${report}`));
1151
+ }
1114
1152
  async function writeBindings(root, manifest) {
1115
1153
  const dir = join(root, "gonvex", "_generated");
1116
1154
  await mkdir(dir, { recursive: true });
@@ -1118,9 +1156,15 @@ async function writeBindings(root, manifest) {
1118
1156
  let changedFiles = 0;
1119
1157
  if (await writeManifestIfChanged(join(dir, "manifest.json"), manifest))
1120
1158
  changedFiles += 1;
1159
+ const local = await localBindings(root, manifest);
1160
+ if (local["local-reducers.js"])
1161
+ await rm(join(dir, "local-reducers.ts"), { force: true });
1121
1162
  const outputs = {
1163
+ ...local,
1122
1164
  "api.ts": renderAPI(manifest),
1123
- "client.ts": '// Generated by gonvex dev. Do not edit.\nexport { GonvexClient, createFirebaseAuthAdapter } from "@gonvex/client";\nexport type { GonvexExternalAuthAdapter, GonvexExternalIdentityHint, GonvexFirebaseAuthAdapterOptions } from "@gonvex/client";\n',
1165
+ "client.ts": local["local-runtime.ts"]
1166
+ ? '// Generated by Gonvex. Do not edit.\nimport { GonvexClient as BaseClient, type GonvexClientOptions } from "@gonvex/client";\nimport { localRuntime } from "./local-runtime.js";\nexport class GonvexClient extends BaseClient { constructor(url: string, options: GonvexClientOptions = {}) { super(url, { ...options, localRuntime: options.localRuntime ?? localRuntime }); } }\nexport { createFirebaseAuthAdapter } from "@gonvex/client";\n'
1167
+ : '// Generated by gonvex dev. Do not edit.\nexport { GonvexClient, createFirebaseAuthAdapter } from "@gonvex/client";\nexport type { GonvexExternalAuthAdapter, GonvexExternalIdentityHint, GonvexFirebaseAuthAdapterOptions } from "@gonvex/client";\n',
1124
1168
  "react.ts": '// Generated by gonvex dev. Do not edit.\nexport { createFirebaseAuthAdapter, createGonvexAuth, GonvexAuthProvider, GonvexGoogleAuthButton, GonvexProvider, GonvexProviderWithAuth, useAction, useControlQuery, useCurrentTenantProfile, useEntity, useGonvexAuth, useGonvexAuthState, useGonvexClient, useGonvexConnectionState, useInvitationList, useLiveQuery, useLiveQueryState, useQuery, useReducer, useReplicaCollection, useReplicaCollectionState, useReplicaEntities, useReplicaSelector, useRetainedLiveQuery } from "@gonvex/react";\nexport type { GonvexAuthAccount, GonvexAuthConfig, GonvexAuthProviderName, GonvexAuthTenant, GonvexAuthValue, GonvexDeveloperModeState, GonvexExternalAuthAdapter, GonvexExternalIdentityHint, GonvexFirebaseAuthAdapterOptions } from "@gonvex/react";\n',
1125
1169
  "types.ts": "// Generated by gonvex dev. Do not edit.\nexport type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };\n",
1126
1170
  "schema.ts": renderSchemaIndex(manifest),
@@ -1136,6 +1180,22 @@ async function writeBindings(root, manifest) {
1136
1180
  if (await writeFileIfChanged(join(dir, name), contents))
1137
1181
  changedFiles += 1;
1138
1182
  }
1183
+ // Agent catalogs are checked into some applications and imported by their
1184
+ // browser bundle. Once a project opts in by creating either conventional
1185
+ // output, keep it on the exact artifact built by `gonvex dev`/`codegen`.
1186
+ // Otherwise a backend hot reload can activate a new artifact while the
1187
+ // browser continues sending the old hash, making every delegated call fail
1188
+ // with STALE_AGENT_CATALOG until somebody manually re-emits the files.
1189
+ for (const [name, format] of [
1190
+ ["agent-api.ndjson", "ndjson"],
1191
+ ["agent-api.d.ts", "typescript"],
1192
+ ]) {
1193
+ const output = join(root, name);
1194
+ if (existsSync(output) && await writeFileIfChanged(output, renderFunctionCatalog(manifest.module, format))) {
1195
+ changedFiles += 1;
1196
+ console.log(`[gonvex] refreshed ${name} for artifact ${manifest.module.hash}`);
1197
+ }
1198
+ }
1139
1199
  return { changedFiles };
1140
1200
  }
1141
1201
  async function writeManifestIfChanged(path, manifest) {
@@ -1244,8 +1304,9 @@ function renderAPI(manifest) {
1244
1304
  ...(entry.delivery ? { delivery: entry.delivery } : {}),
1245
1305
  ...(entry.delivery === "replica" && entry.replica ? { replica: entry.replica } : {}),
1246
1306
  ...(entry.offline !== undefined ? { offline: entry.offline } : {}),
1247
- ...(isModuleSchema(entry.args) ? { args: entry.args } : {}),
1248
- ...(isModuleSchema(entry.result) ? { result: entry.result } : {}),
1307
+ ...(entry.localExecution !== undefined ? { localExecution: entry.localExecution } : {}),
1308
+ // Args/Result are phantom types below. Validation schemas remain in the
1309
+ // module manifest and agent catalog; the browser never consumes them.
1249
1310
  };
1250
1311
  if (entry.delivery === "live" && entry.dependencies?.liveQueryPlan) {
1251
1312
  const plan = entry.dependencies.liveQueryPlan;
@@ -1284,6 +1345,7 @@ function renderAPI(manifest) {
1284
1345
  " readonly delivery?: \"oneShot\" | \"live\" | \"replica\";",
1285
1346
  " readonly replica?: { readonly table: string; readonly key: string; readonly columns?: readonly string[] };",
1286
1347
  " readonly offline?: { readonly mode: \"forbidden\" | \"allowed\" | \"onlineOnly\"; readonly conflict?: \"reject\" | \"expectedVersion\" | \"merge\"; readonly reason?: string };",
1348
+ " readonly localExecution?: 1;",
1287
1349
  " readonly args?: Args;",
1288
1350
  " readonly result?: Result;",
1289
1351
  " readonly live?: { readonly entity: string; readonly key: string; readonly resultPath?: readonly string[]; readonly plan: LiveQueryPlan };",
@@ -1295,11 +1357,13 @@ function renderAPI(manifest) {
1295
1357
  `export type ${result} = ${renderSchemaType(manifest.functions[path]?.result)};`,
1296
1358
  ]),
1297
1359
  "",
1298
- `export const api = ${renderObject(publicRoot, 0)} as const;`,
1360
+ lazyReferenceRuntime,
1361
+ "",
1362
+ `export const api = ${renderLazyReferences(publicRoot, 0, isFunctionRef, renderObject)};`,
1299
1363
  "",
1300
1364
  "export const control = gonvexControl;",
1301
1365
  "",
1302
- `export const internal = ${renderObject(internalRoot, 0)} as const;`,
1366
+ `export const internal = ${renderLazyReferences(internalRoot, 0, isFunctionRef, renderObject)};`,
1303
1367
  "export type Api = typeof api;",
1304
1368
  "",
1305
1369
  `export const optimisticTransactions: Record<string, OptimisticTransactionDefinition> = ${renderObject(optimisticTransactions, 0)};`,