@emeryld/rrroutes-contract 2.7.0 → 2.7.2

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
@@ -299,3 +299,168 @@ pnpm --filter @emeryld/rrroutes-contract build # tsup + d.ts
299
299
  pnpm --filter @emeryld/rrroutes-contract typecheck
300
300
  pnpm --filter @emeryld/rrroutes-contract test # optional Jest suite
301
301
  ```
302
+
303
+ ## Finalized Leaves Export (JSON)
304
+
305
+ You can export finalized leaves (plus flattened schema paths) to strict JSON.
306
+
307
+ ### What `--module` means
308
+
309
+ `--module` is the path to a JS/TS module file that **exports** your leaves or registry.
310
+
311
+ - The file can export:
312
+ - a finalized registry (`finalize(leaves)`)
313
+ - or a leaf array/tuple (the output of `.done()`)
314
+ - Use `--export` to select which exported symbol to read from that module.
315
+
316
+ ### CLI usage
317
+
318
+ From the repo root:
319
+
320
+ ```sh
321
+ pnpm --filter @emeryld/rrroutes-contract export:finalized-leaves -- \
322
+ --module ./path/to/contract-module.ts \
323
+ --export registry \
324
+ --out ./finalized-leaves.export.json
325
+ ```
326
+
327
+ Arguments:
328
+
329
+ - `--module` required path to the module that exports your data.
330
+ - `--export` optional export name (default: `leaves`).
331
+ - `--out` optional output file path (default: `finalized-leaves.export.json`).
332
+
333
+ ### Example module shapes
334
+
335
+ Registry export:
336
+
337
+ ```ts
338
+ import { finalize, resource } from '@emeryld/rrroutes-contract'
339
+ import { z } from 'zod'
340
+
341
+ const leaves = resource('/v1')
342
+ .get({ outputSchema: z.object({ ok: z.literal(true) }) })
343
+ .done()
344
+
345
+ export const registry = finalize(leaves)
346
+ ```
347
+
348
+ Leaves export:
349
+
350
+ ```ts
351
+ import { resource } from '@emeryld/rrroutes-contract'
352
+ import { z } from 'zod'
353
+
354
+ export const leaves = resource('/v1')
355
+ .get({ outputSchema: z.object({ ok: z.literal(true) }) })
356
+ .done()
357
+ ```
358
+
359
+ ### Runtime API
360
+
361
+ If you want to run this in code instead of CLI:
362
+
363
+ ```ts
364
+ import { exportFinalizedLeaves } from '@emeryld/rrroutes-contract'
365
+
366
+ const payload = await exportFinalizedLeaves(registry, {
367
+ outFile: './finalized-leaves.export.json',
368
+ htmlFile: './finalized-leaves-viewer.baked.html',
369
+ openOnFinish: true,
370
+ })
371
+ ```
372
+
373
+ `payload` contains:
374
+
375
+ - `_meta`: export/documentation metadata
376
+ - `leaves`: contract-native serialized leaves
377
+ - `schemaFlatByLeaf`: flattened schema map per leaf
378
+
379
+ `htmlFile` writes a self-contained viewer HTML with the export payload baked in (no file picker needed).
380
+ `viewerTemplateFile` optionally points to a custom viewer HTML template instead of the default bundled viewer.
381
+ `openOnFinish` opens the generated `htmlFile` in your default browser after write completes.
382
+
383
+ ### Custom `viewerTemplateFile`
384
+
385
+ Use `viewerTemplateFile` when you want your own branded/layout HTML while still baking export data directly into the page.
386
+
387
+ Behavior:
388
+
389
+ - If omitted, RRRoutes uses `packages/contract/tools/finalized-leaves-viewer.html`.
390
+ - If provided, RRRoutes reads your template and injects a script like:
391
+ - `window.__FINALIZED_LEAVES_PAYLOAD = {...}`
392
+ - If your template contains this marker comment, payload is injected exactly there:
393
+ - `<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->`
394
+ - If marker is missing, payload script is inserted before `</body>` (or prepended if no `</body>` exists).
395
+
396
+ Minimal custom template example:
397
+
398
+ ```html
399
+ <!doctype html>
400
+ <html>
401
+ <head>
402
+ <meta charset="UTF-8" />
403
+ <title>My Leaves Viewer</title>
404
+ </head>
405
+ <body>
406
+ <h1>My API Routes</h1>
407
+ <div id="app"></div>
408
+
409
+ <!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->
410
+
411
+ <script>
412
+ const payload = window.__FINALIZED_LEAVES_PAYLOAD
413
+ document.getElementById('app').textContent = payload
414
+ ? `Loaded ${payload.leaves.length} leaves`
415
+ : 'No baked payload found'
416
+ </script>
417
+ </body>
418
+ </html>
419
+ ```
420
+
421
+ Runtime usage with custom template:
422
+
423
+ ```ts
424
+ await exportFinalizedLeaves(registry, {
425
+ htmlFile: './dist/leaves-viewer.html',
426
+ viewerTemplateFile: './tools/my-viewer-template.html',
427
+ openOnFinish: true,
428
+ })
429
+ ```
430
+
431
+ ### Viewer HTML (searchable UI)
432
+
433
+ A simple local viewer is included at:
434
+
435
+ - `packages/contract/tools/finalized-leaves-viewer.html`
436
+
437
+ How to use:
438
+
439
+ 1. Generate an export JSON with `export:finalized-leaves`.
440
+ 2. Open the HTML file in your browser.
441
+ 3. Load the JSON file using the file picker.
442
+ 4. Use the single search textbox + field checkboxes to filter routes.
443
+
444
+ Each result is rendered as a collapsible block with title `METHOD path`.
445
+
446
+ To access it from your project:
447
+
448
+ - Quick local use: open the HTML file directly.
449
+ - Team/shared use: serve it as a static file (Express example):
450
+
451
+ ```ts
452
+ import express from 'express'
453
+ import path from 'node:path'
454
+
455
+ const app = express()
456
+
457
+ app.use(
458
+ '/tools/finalized-leaves-viewer',
459
+ express.static(
460
+ path.resolve(process.cwd(), 'packages/contract/tools'),
461
+ ),
462
+ )
463
+
464
+ app.listen(3000)
465
+ // open http://localhost:3000/tools/finalized-leaves-viewer/finalized-leaves-viewer.html
466
+ ```
@@ -0,0 +1,12 @@
1
+ import { type ExportFinalizedLeavesInput } from './exportFinalizedLeaves';
2
+ export type FinalizedLeavesCliArgs = {
3
+ modulePath: string;
4
+ exportName: string;
5
+ outFile: string;
6
+ };
7
+ export declare function parseFinalizedLeavesCliArgs(argv: string[]): FinalizedLeavesCliArgs;
8
+ export declare function loadFinalizedLeavesInput({ modulePath, exportName, }: FinalizedLeavesCliArgs): Promise<ExportFinalizedLeavesInput>;
9
+ export declare function runExportFinalizedLeavesCli(argv: string[]): Promise<{
10
+ payload: import("./exportFinalizedLeaves").FinalizedLeavesExport;
11
+ outFile: string;
12
+ }>;
@@ -0,0 +1,42 @@
1
+ import type { AnyLeafLowProfile } from '../core/routesV3.core';
2
+ import type { FinalizedRegistry } from '../core/routesV3.finalize';
3
+ import { type FlatSchemaMap } from './flattenSchema';
4
+ import { type SerializeLeafContractOptions, type SerializedLeafContract } from './serializeLeafContract';
5
+ export type ExportFinalizedLeavesInput = readonly AnyLeafLowProfile[] | FinalizedRegistry<readonly AnyLeafLowProfile[]>;
6
+ export type ExportFinalizedLeavesMeta = {
7
+ generatedAt: string;
8
+ description: string;
9
+ fieldCatalog: {
10
+ leaf: string[];
11
+ cfg: string[];
12
+ schemaNode: string[];
13
+ flatSchemaEntry: string[];
14
+ };
15
+ flattening: {
16
+ notation: 'dot+[]';
17
+ unionBranchSuffix: '-N';
18
+ sections: readonly ['params', 'query', 'body', 'output'];
19
+ };
20
+ };
21
+ export type FinalizedLeavesExport = {
22
+ _meta: ExportFinalizedLeavesMeta;
23
+ leaves: SerializedLeafContract[];
24
+ schemaFlatByLeaf: Record<string, FlatSchemaMap>;
25
+ };
26
+ export type ExportFinalizedLeavesOptions = SerializeLeafContractOptions & {
27
+ outFile?: string;
28
+ htmlFile?: string;
29
+ viewerTemplateFile?: string;
30
+ openOnFinish?: boolean;
31
+ };
32
+ export type WriteFinalizedLeavesExportOptions = {
33
+ outFile?: string;
34
+ htmlFile?: string;
35
+ viewerTemplateFile?: string;
36
+ openOnFinish?: boolean;
37
+ };
38
+ export declare function writeFinalizedLeavesExport(payload: FinalizedLeavesExport, outFileOrOptions: string | WriteFinalizedLeavesExportOptions): Promise<{
39
+ outFile?: string;
40
+ htmlFile?: string;
41
+ }>;
42
+ export declare function exportFinalizedLeaves(input: ExportFinalizedLeavesInput, options?: ExportFinalizedLeavesOptions): Promise<FinalizedLeavesExport>;
@@ -0,0 +1,11 @@
1
+ import type { AnyLeafLowProfile } from '../core/routesV3.core';
2
+ import { type SerializedLeafContract } from './serializeLeafContract';
3
+ import type { SerializableSchema } from './schemaIntrospection';
4
+ export type FlatSchemaEntry = {
5
+ type: string;
6
+ nullable: boolean;
7
+ optional: boolean;
8
+ };
9
+ export type FlatSchemaMap = Record<string, FlatSchemaEntry>;
10
+ export declare function flattenSerializableSchema(schema: SerializableSchema | undefined, path: string): FlatSchemaMap;
11
+ export declare function flattenLeafSchemas(leaf: AnyLeafLowProfile | SerializedLeafContract): FlatSchemaMap;
@@ -0,0 +1,5 @@
1
+ export * from './schemaIntrospection';
2
+ export * from './serializeLeafContract';
3
+ export * from './flattenSchema';
4
+ export * from './exportFinalizedLeaves';
5
+ export * from './exportFinalizedLeaves.cli';
@@ -0,0 +1,47 @@
1
+ import * as z from 'zod';
2
+ export declare const serializableSchemaKinds: readonly ["object", "string", "number", "boolean", "bigint", "date", "array", "enum", "literal", "union", "record", "tuple", "unknown", "any"];
3
+ export type SerializableSchemaKind = (typeof serializableSchemaKinds)[number];
4
+ export type SerializableSchema = {
5
+ kind: SerializableSchemaKind;
6
+ optional?: boolean;
7
+ nullable?: boolean;
8
+ description?: string;
9
+ properties?: Record<string, SerializableSchema>;
10
+ element?: SerializableSchema;
11
+ union?: SerializableSchema[];
12
+ literal?: unknown;
13
+ enumValues?: string[];
14
+ };
15
+ type ZodAny = z.ZodTypeAny;
16
+ type InternalSchemaKind = SerializableSchemaKind | 'intersection';
17
+ type IntrospectionWalker = (schema: ZodAny | undefined) => SerializableSchema | undefined;
18
+ export type IntrospectionContext = {
19
+ zod: typeof z;
20
+ introspect: IntrospectionWalker;
21
+ getDef: (schema: unknown) => any | undefined;
22
+ unwrap: (schema: ZodAny) => {
23
+ base: ZodAny;
24
+ optional: boolean;
25
+ nullable: boolean;
26
+ };
27
+ getDescription: (schema: ZodAny) => string | undefined;
28
+ };
29
+ export type IntrospectionNode = {
30
+ schema: ZodAny;
31
+ base: ZodAny;
32
+ def: any;
33
+ kind: InternalSchemaKind;
34
+ optional: boolean;
35
+ nullable: boolean;
36
+ node: SerializableSchema;
37
+ };
38
+ export type SchemaIntrospectionHandler = (args: IntrospectionNode, ctx: IntrospectionContext) => SerializableSchema | undefined;
39
+ export type SchemaIntrospectionHandlerMap = Partial<Record<InternalSchemaKind, SchemaIntrospectionHandler>>;
40
+ export type IntrospectSchemaOptions = {
41
+ handlers?: SchemaIntrospectionHandlerMap;
42
+ };
43
+ export declare function registerSchemaIntrospectionHandler(kind: InternalSchemaKind, handler: SchemaIntrospectionHandler): void;
44
+ export declare function clearSchemaIntrospectionHandlers(): void;
45
+ export declare function createSchemaIntrospector(options?: IntrospectSchemaOptions): IntrospectionWalker;
46
+ export declare function introspectSchema(schema: ZodAny | undefined, options?: IntrospectSchemaOptions): SerializableSchema | undefined;
47
+ export {};
@@ -0,0 +1,31 @@
1
+ import { type AnyLeafLowProfile, type FileField } from '../core/routesV3.core';
2
+ import { type IntrospectSchemaOptions, type SerializableSchema } from './schemaIntrospection';
3
+ export type ContractLeafSchemas = {
4
+ body?: SerializableSchema;
5
+ query?: SerializableSchema;
6
+ params?: SerializableSchema;
7
+ output?: SerializableSchema;
8
+ outputMeta?: SerializableSchema;
9
+ queryExtension?: SerializableSchema;
10
+ };
11
+ export type SerializedLeafContract = {
12
+ key: string;
13
+ method: AnyLeafLowProfile['method'];
14
+ path: string;
15
+ cfg: {
16
+ description?: string;
17
+ summary?: string;
18
+ docsGroup?: string;
19
+ tags?: string[];
20
+ deprecated?: boolean;
21
+ stability?: 'experimental' | 'beta' | 'stable' | 'deprecated';
22
+ docsHidden?: boolean;
23
+ docsMeta?: Record<string, unknown>;
24
+ feed?: boolean;
25
+ bodyFiles?: FileField[];
26
+ schemas: ContractLeafSchemas;
27
+ };
28
+ };
29
+ export type SerializeLeafContractOptions = IntrospectSchemaOptions;
30
+ export declare function serializeLeafContract(leaf: AnyLeafLowProfile, options?: SerializeLeafContractOptions): SerializedLeafContract;
31
+ export declare function serializeLeavesContract(leaves: readonly AnyLeafLowProfile[], options?: SerializeLeafContractOptions): SerializedLeafContract[];