orpc-fn 0.5.0 → 0.5.1

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
@@ -243,7 +243,7 @@ Sanitized tool-name collisions between distinct procedures throw; identical alia
243
243
 
244
244
  ## `orpc-fn/cli`
245
245
 
246
- Optional peer: `hanzio` ^3.1.0 (`hanzio/cli`).
246
+ Uses `hanzio/cli`, which orpc-fn depends on (`hanzio` ^3.1.0). Your `defineCli` must come from the same `hanzio` copy, so keep your own `hanzio` on a matching major.
247
247
 
248
248
  ```ts
249
249
  import { defineCli } from 'hanzio/cli'
package/dist/cli.js CHANGED
@@ -1,56 +1,45 @@
1
1
  #!/usr/bin/env node
2
- // biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console
3
2
  import { readFile, writeFile } from 'node:fs/promises';
4
3
  import { resolve } from 'node:path';
5
4
  import { pathToFileURL } from 'node:url';
6
- import { parseArgs } from 'node:util';
5
+ import { CliError, defineCli, defineCommand } from 'hanzio/cli';
6
+ import { z } from 'zod';
7
7
  import { createStreamManifestAsync, renderStreamManifest } from './router.js';
8
- const usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]
9
-
10
- Imports the router (export "default" unless #export is given) and writes the
11
- paths of its streaming routes for createRpcLink({ streamPaths }). The file is
12
- only written when its content changes. --check writes nothing and exits 1 when
13
- the file is missing or stale. Importing the router runs its module code; run
14
- TypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`;
15
- async function run(argv) {
16
- const { positionals, values } = parseArgs({
17
- args: [...argv],
18
- allowPositionals: true,
19
- options: {
20
- out: { type: 'string' },
21
- name: { type: 'string' },
22
- check: { type: 'boolean' },
23
- help: { type: 'boolean', short: 'h' }
24
- }
25
- });
26
- const [command, target] = positionals;
27
- if (values.help || command !== 'stream-manifest' || !target || !values.out) {
28
- console.error(usage);
29
- return values.help ? 0 : 2;
8
+ const streamManifest = defineCommand({
9
+ description: "Write the paths of a router's streaming routes for createRpcLink({ streamPaths }), only when they change. Importing the router runs its module code: load .ts routers with bun, tsx or node >= 22.18.",
10
+ input: z.object({
11
+ module: z
12
+ .string()
13
+ .describe('Router module, with #export for a named export')
14
+ .meta({ positional: true }),
15
+ out: z.string().describe('Manifest to write: file.ts or file.json'),
16
+ name: z
17
+ .string()
18
+ .optional()
19
+ .describe('Exported constant name in a .ts file'),
20
+ check: z.boolean().describe('Exit 1 when the file is missing or stale')
21
+ }),
22
+ run: async (input) => {
23
+ const [modulePath = '', exportName = 'default'] = input.module.split('#');
24
+ const loaded = await import(pathToFileURL(resolve(modulePath)).href);
25
+ const router = loaded[exportName];
26
+ if (!router || typeof router !== 'object')
27
+ throw new CliError(`orpc-fn: ${modulePath} has no router export "${exportName}"`, { exitCode: 2 });
28
+ const out = resolve(input.out);
29
+ const source = renderStreamManifest(await createStreamManifestAsync(router), { format: out.endsWith('.json') ? 'json' : 'ts', name: input.name });
30
+ const current = await readFile(out, 'utf8').catch(() => undefined);
31
+ if (current === source)
32
+ return;
33
+ if (input.check)
34
+ throw new CliError(`orpc-fn: ${input.out} is stale; rerun without --check to update it`);
35
+ await writeFile(out, source);
36
+ return `orpc-fn: wrote ${input.out}`;
30
37
  }
31
- const [modulePath = '', exportName = 'default'] = target.split('#');
32
- const loaded = await import(pathToFileURL(resolve(modulePath)).href);
33
- const router = loaded[exportName];
34
- if (!router || typeof router !== 'object') {
35
- console.error(`orpc-fn: ${modulePath} has no router export "${exportName}"`);
36
- return 2;
37
- }
38
- const out = resolve(values.out);
39
- const source = renderStreamManifest(await createStreamManifestAsync(router), { format: out.endsWith('.json') ? 'json' : 'ts', name: values.name });
40
- const current = await readFile(out, 'utf8').catch(() => undefined);
41
- if (current === source)
42
- return 0;
43
- if (values.check) {
44
- console.error(`orpc-fn: ${values.out} is stale; rerun without --check to update it`);
45
- return 1;
46
- }
47
- await writeFile(out, source);
48
- console.log(`orpc-fn: wrote ${values.out}`);
49
- return 0;
50
- }
51
- // Exit explicitly: the imported router may hold open database or broker handles.
52
- run(process.argv.slice(2)).then((code) => process.exit(code), (error) => {
53
- console.error(error);
54
- process.exit(1);
55
38
  });
39
+ const cli = defineCli({
40
+ name: 'orpc-fn',
41
+ commands: { 'stream-manifest': streamManifest }
42
+ });
43
+ // Exit explicitly: the imported router may hold open database or broker handles.
44
+ process.exit(await cli.run());
56
45
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,2EAA2E;AAC3E,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE7E,MAAM,KAAK,GAAG;;;;;;4EAM8D,CAAA;AAE5E,KAAK,UAAU,GAAG,CAAC,IAAuB;IACzC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QACf,gBAAgB,EAAE,IAAI;QACtB,OAAO,EAAE;YACR,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACvB,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACxB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;YAC1B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;SACrC;KACD,CAAC,CAAA;IACF,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,WAAW,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,IAAI,OAAO,KAAK,iBAAiB,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC5E,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;QACpB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3B,CAAC;IACD,MAAM,CAAC,UAAU,GAAG,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACnE,MAAM,MAAM,GAA4B,MAAM,MAAM,CACnD,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CACvC,CAAA;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;IACjC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,YAAY,UAAU,0BAA0B,UAAU,GAAG,CAAC,CAAA;QAC5E,OAAO,CAAC,CAAA;IACT,CAAC;IACD,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC/B,MAAM,MAAM,GAAG,oBAAoB,CAClC,MAAM,yBAAyB,CAAC,MAAmB,CAAC,EACpD,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CACpE,CAAA;IACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;IAClE,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,CAAC,CAAA;IAChC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CACZ,YAAY,MAAM,CAAC,GAAG,+CAA+C,CACrE,CAAA;QACD,OAAO,CAAC,CAAA;IACT,CAAC;IACD,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IAC5B,OAAO,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,GAAG,EAAE,CAAC,CAAA;IAC3C,OAAO,CAAC,CAAA;AACT,CAAC;AAED,iFAAiF;AACjF,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,KAAK,EAAE,EAAE;IACT,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC,CACD,CAAA","sourcesContent":["#!/usr/bin/env node\n// biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { parseArgs } from 'node:util'\nimport type { AnyRouter } from '@orpc/server'\nimport { createStreamManifestAsync, renderStreamManifest } from './router.js'\n\nconst usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]\n\nImports the router (export \"default\" unless #export is given) and writes the\npaths of its streaming routes for createRpcLink({ streamPaths }). The file is\nonly written when its content changes. --check writes nothing and exits 1 when\nthe file is missing or stale. Importing the router runs its module code; run\nTypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`\n\nasync function run(argv: readonly string[]): Promise<number> {\n\tconst { positionals, values } = parseArgs({\n\t\targs: [...argv],\n\t\tallowPositionals: true,\n\t\toptions: {\n\t\t\tout: { type: 'string' },\n\t\t\tname: { type: 'string' },\n\t\t\tcheck: { type: 'boolean' },\n\t\t\thelp: { type: 'boolean', short: 'h' }\n\t\t}\n\t})\n\tconst [command, target] = positionals\n\tif (values.help || command !== 'stream-manifest' || !target || !values.out) {\n\t\tconsole.error(usage)\n\t\treturn values.help ? 0 : 2\n\t}\n\tconst [modulePath = '', exportName = 'default'] = target.split('#')\n\tconst loaded: Record<string, unknown> = await import(\n\t\tpathToFileURL(resolve(modulePath)).href\n\t)\n\tconst router = loaded[exportName]\n\tif (!router || typeof router !== 'object') {\n\t\tconsole.error(`orpc-fn: ${modulePath} has no router export \"${exportName}\"`)\n\t\treturn 2\n\t}\n\tconst out = resolve(values.out)\n\tconst source = renderStreamManifest(\n\t\tawait createStreamManifestAsync(router as AnyRouter),\n\t\t{ format: out.endsWith('.json') ? 'json' : 'ts', name: values.name }\n\t)\n\tconst current = await readFile(out, 'utf8').catch(() => undefined)\n\tif (current === source) return 0\n\tif (values.check) {\n\t\tconsole.error(\n\t\t\t`orpc-fn: ${values.out} is stale; rerun without --check to update it`\n\t\t)\n\t\treturn 1\n\t}\n\tawait writeFile(out, source)\n\tconsole.log(`orpc-fn: wrote ${values.out}`)\n\treturn 0\n}\n\n// Exit explicitly: the imported router may hold open database or broker handles.\nrun(process.argv.slice(2)).then(\n\t(code) => process.exit(code),\n\t(error) => {\n\t\tconsole.error(error)\n\t\tprocess.exit(1)\n\t}\n)\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAE7E,MAAM,cAAc,GAAG,aAAa,CAAC;IACpC,WAAW,EACV,uMAAuM;IACxM,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,MAAM,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CAAC,gDAAgD,CAAC;aAC1D,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAC5B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;QACnE,IAAI,EAAE,CAAC;aACL,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CAAC,sCAAsC,CAAC;QAClD,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;KACvE,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,CAAC,UAAU,GAAG,EAAE,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACzE,MAAM,MAAM,GAA4B,MAAM,MAAM,CACnD,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CACvC,CAAA;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;QACjC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YACxC,MAAM,IAAI,QAAQ,CACjB,YAAY,UAAU,0BAA0B,UAAU,GAAG,EAC7D,EAAE,QAAQ,EAAE,CAAC,EAAE,CACf,CAAA;QACF,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC9B,MAAM,MAAM,GAAG,oBAAoB,CAClC,MAAM,yBAAyB,CAAC,MAAmB,CAAC,EACpD,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CACnE,CAAA;QACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAA;QAClE,IAAI,OAAO,KAAK,MAAM;YAAE,OAAM;QAC9B,IAAI,KAAK,CAAC,KAAK;YACd,MAAM,IAAI,QAAQ,CACjB,YAAY,KAAK,CAAC,GAAG,+CAA+C,CACpE,CAAA;QACF,MAAM,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC5B,OAAO,kBAAkB,KAAK,CAAC,GAAG,EAAE,CAAA;IACrC,CAAC;CACD,CAAC,CAAA;AAEF,MAAM,GAAG,GAAG,SAAS,CAAC;IACrB,IAAI,EAAE,SAAS;IACf,QAAQ,EAAE,EAAE,iBAAiB,EAAE,cAAc,EAAE;CAC/C,CAAC,CAAA;AAEF,iFAAiF;AACjF,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\nimport { readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport type { AnyRouter } from '@orpc/server'\nimport { CliError, defineCli, defineCommand } from 'hanzio/cli'\nimport { z } from 'zod'\nimport { createStreamManifestAsync, renderStreamManifest } from './router.js'\n\nconst streamManifest = defineCommand({\n\tdescription:\n\t\t\"Write the paths of a router's streaming routes for createRpcLink({ streamPaths }), only when they change. Importing the router runs its module code: load .ts routers with bun, tsx or node >= 22.18.\",\n\tinput: z.object({\n\t\tmodule: z\n\t\t\t.string()\n\t\t\t.describe('Router module, with #export for a named export')\n\t\t\t.meta({ positional: true }),\n\t\tout: z.string().describe('Manifest to write: file.ts or file.json'),\n\t\tname: z\n\t\t\t.string()\n\t\t\t.optional()\n\t\t\t.describe('Exported constant name in a .ts file'),\n\t\tcheck: z.boolean().describe('Exit 1 when the file is missing or stale')\n\t}),\n\trun: async (input) => {\n\t\tconst [modulePath = '', exportName = 'default'] = input.module.split('#')\n\t\tconst loaded: Record<string, unknown> = await import(\n\t\t\tpathToFileURL(resolve(modulePath)).href\n\t\t)\n\t\tconst router = loaded[exportName]\n\t\tif (!router || typeof router !== 'object')\n\t\t\tthrow new CliError(\n\t\t\t\t`orpc-fn: ${modulePath} has no router export \"${exportName}\"`,\n\t\t\t\t{ exitCode: 2 }\n\t\t\t)\n\t\tconst out = resolve(input.out)\n\t\tconst source = renderStreamManifest(\n\t\t\tawait createStreamManifestAsync(router as AnyRouter),\n\t\t\t{ format: out.endsWith('.json') ? 'json' : 'ts', name: input.name }\n\t\t)\n\t\tconst current = await readFile(out, 'utf8').catch(() => undefined)\n\t\tif (current === source) return\n\t\tif (input.check)\n\t\t\tthrow new CliError(\n\t\t\t\t`orpc-fn: ${input.out} is stale; rerun without --check to update it`\n\t\t\t)\n\t\tawait writeFile(out, source)\n\t\treturn `orpc-fn: wrote ${input.out}`\n\t}\n})\n\nconst cli = defineCli({\n\tname: 'orpc-fn',\n\tcommands: { 'stream-manifest': streamManifest }\n})\n\n// Exit explicitly: the imported router may hold open database or broker handles.\nprocess.exit(await cli.run())\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orpc-fn",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Define a backend function once: a typed oRPC procedure with tracing, logging, guards, injected handler extras, nested calls and live queries over Redis.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -127,6 +127,9 @@
127
127
  "test:types-performance": "bun scripts/check-types-performance.ts",
128
128
  "test:client-bundle": "bun scripts/check-client-bundle.ts"
129
129
  },
130
+ "dependencies": {
131
+ "hanzio": "^3.1.0"
132
+ },
130
133
  "peerDependencies": {
131
134
  "@mastra/core": "^1.51.0",
132
135
  "@opentelemetry/api": "^1.9.0",
@@ -136,7 +139,6 @@
136
139
  "@orpc/openapi": "^1.14.0",
137
140
  "@orpc/server": "^1.14.0",
138
141
  "@orpc/zod": "^1.14.0",
139
- "hanzio": "^3.1.0",
140
142
  "hono": "^4.0.0",
141
143
  "ioredis": "^5.0.0",
142
144
  "zod": "^4.0.0",
@@ -163,9 +165,6 @@
163
165
  "@orpc/zod": {
164
166
  "optional": true
165
167
  },
166
- "hanzio": {
167
- "optional": true
168
- },
169
168
  "hono": {
170
169
  "optional": true
171
170
  },
@@ -212,7 +211,6 @@
212
211
  "@types/node": "^22.0.0",
213
212
  "@types/react": "19",
214
213
  "@types/react-test-renderer": "19",
215
- "hanzio": "^3.1.0",
216
214
  "hono": "^4.12.31",
217
215
  "ioredis": "5.8.2",
218
216
  "publint": "^0.3.12",
package/src/cli.ts CHANGED
@@ -1,68 +1,58 @@
1
1
  #!/usr/bin/env node
2
- // biome-ignore-all lint/suspicious/noConsole: a CLI reports on the console
3
2
  import { readFile, writeFile } from 'node:fs/promises'
4
3
  import { resolve } from 'node:path'
5
4
  import { pathToFileURL } from 'node:url'
6
- import { parseArgs } from 'node:util'
7
5
  import type { AnyRouter } from '@orpc/server'
6
+ import { CliError, defineCli, defineCommand } from 'hanzio/cli'
7
+ import { z } from 'zod'
8
8
  import { createStreamManifestAsync, renderStreamManifest } from './router.js'
9
9
 
10
- const usage = `Usage: orpc-fn stream-manifest <module>[#export] --out <file.ts|file.json> [--name streamPaths] [--check]
11
-
12
- Imports the router (export "default" unless #export is given) and writes the
13
- paths of its streaming routes for createRpcLink({ streamPaths }). The file is
14
- only written when its content changes. --check writes nothing and exits 1 when
15
- the file is missing or stale. Importing the router runs its module code; run
16
- TypeScript routers with a runtime that loads .ts (bun, tsx, node >= 22.18).`
17
-
18
- async function run(argv: readonly string[]): Promise<number> {
19
- const { positionals, values } = parseArgs({
20
- args: [...argv],
21
- allowPositionals: true,
22
- options: {
23
- out: { type: 'string' },
24
- name: { type: 'string' },
25
- check: { type: 'boolean' },
26
- help: { type: 'boolean', short: 'h' }
27
- }
28
- })
29
- const [command, target] = positionals
30
- if (values.help || command !== 'stream-manifest' || !target || !values.out) {
31
- console.error(usage)
32
- return values.help ? 0 : 2
33
- }
34
- const [modulePath = '', exportName = 'default'] = target.split('#')
35
- const loaded: Record<string, unknown> = await import(
36
- pathToFileURL(resolve(modulePath)).href
37
- )
38
- const router = loaded[exportName]
39
- if (!router || typeof router !== 'object') {
40
- console.error(`orpc-fn: ${modulePath} has no router export "${exportName}"`)
41
- return 2
42
- }
43
- const out = resolve(values.out)
44
- const source = renderStreamManifest(
45
- await createStreamManifestAsync(router as AnyRouter),
46
- { format: out.endsWith('.json') ? 'json' : 'ts', name: values.name }
47
- )
48
- const current = await readFile(out, 'utf8').catch(() => undefined)
49
- if (current === source) return 0
50
- if (values.check) {
51
- console.error(
52
- `orpc-fn: ${values.out} is stale; rerun without --check to update it`
10
+ const streamManifest = defineCommand({
11
+ description:
12
+ "Write the paths of a router's streaming routes for createRpcLink({ streamPaths }), only when they change. Importing the router runs its module code: load .ts routers with bun, tsx or node >= 22.18.",
13
+ input: z.object({
14
+ module: z
15
+ .string()
16
+ .describe('Router module, with #export for a named export')
17
+ .meta({ positional: true }),
18
+ out: z.string().describe('Manifest to write: file.ts or file.json'),
19
+ name: z
20
+ .string()
21
+ .optional()
22
+ .describe('Exported constant name in a .ts file'),
23
+ check: z.boolean().describe('Exit 1 when the file is missing or stale')
24
+ }),
25
+ run: async (input) => {
26
+ const [modulePath = '', exportName = 'default'] = input.module.split('#')
27
+ const loaded: Record<string, unknown> = await import(
28
+ pathToFileURL(resolve(modulePath)).href
53
29
  )
54
- return 1
30
+ const router = loaded[exportName]
31
+ if (!router || typeof router !== 'object')
32
+ throw new CliError(
33
+ `orpc-fn: ${modulePath} has no router export "${exportName}"`,
34
+ { exitCode: 2 }
35
+ )
36
+ const out = resolve(input.out)
37
+ const source = renderStreamManifest(
38
+ await createStreamManifestAsync(router as AnyRouter),
39
+ { format: out.endsWith('.json') ? 'json' : 'ts', name: input.name }
40
+ )
41
+ const current = await readFile(out, 'utf8').catch(() => undefined)
42
+ if (current === source) return
43
+ if (input.check)
44
+ throw new CliError(
45
+ `orpc-fn: ${input.out} is stale; rerun without --check to update it`
46
+ )
47
+ await writeFile(out, source)
48
+ return `orpc-fn: wrote ${input.out}`
55
49
  }
56
- await writeFile(out, source)
57
- console.log(`orpc-fn: wrote ${values.out}`)
58
- return 0
59
- }
50
+ })
51
+
52
+ const cli = defineCli({
53
+ name: 'orpc-fn',
54
+ commands: { 'stream-manifest': streamManifest }
55
+ })
60
56
 
61
57
  // Exit explicitly: the imported router may hold open database or broker handles.
62
- run(process.argv.slice(2)).then(
63
- (code) => process.exit(code),
64
- (error) => {
65
- console.error(error)
66
- process.exit(1)
67
- }
68
- )
58
+ process.exit(await cli.run())