@spfn/core 0.3.0-beta.1 → 0.3.0-beta.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 +38 -15
- package/dist/contract/index.d.ts +3 -3
- package/dist/ops/index.d.ts +53 -8
- package/dist/ops/index.js +37 -13
- package/dist/ops/index.js.map +1 -1
- package/dist/route/index.d.ts +2 -2
- package/dist/{router-ukNdAZcN.d.ts → router-Qbssr11H.d.ts} +1 -1
- package/dist/{types-Bvvig_tT.d.ts → types-D1c57Ko-.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -369,11 +369,10 @@ path: `spfn build && spfn start`, or the generated Docker files.
|
|
|
369
369
|
Put the contract in a file and let the agent read it, instead of describing the
|
|
370
370
|
architecture again in every prompt.
|
|
371
371
|
|
|
372
|
-
|
|
372
|
+
The SPFN repository states it in `CONTRIBUTING.md`: what the repo is, the commands, the
|
|
373
373
|
vertical-slice pattern, and the rules that are not negotiable — never hand-edit generated
|
|
374
|
-
files, migrations come from the schema.
|
|
375
|
-
|
|
376
|
-
apart. Projects created by `spfn create` get the same arrangement.
|
|
374
|
+
files, migrations come from the schema. One file answers to people and agents alike, so
|
|
375
|
+
there is no second copy to drift apart from the first.
|
|
377
376
|
|
|
378
377
|
Each module README under `src/` is written for the same reader. When an agent is working
|
|
379
378
|
on database code, `src/db/README.md` is the page to give it.
|
|
@@ -389,12 +388,11 @@ vertical slice whose path lives under `/_ops/`.
|
|
|
389
388
|
```typescript
|
|
390
389
|
// src/server/ops.ts
|
|
391
390
|
import { Type } from '@sinclair/typebox';
|
|
392
|
-
import {
|
|
393
|
-
import { createOpsRouter } from '@spfn/core/ops';
|
|
391
|
+
import { createOpsRouter, opsRoute } from '@spfn/core/ops';
|
|
394
392
|
import { opsTokenAuth, requireOpsScope } from '@spfn/auth/server';
|
|
395
393
|
|
|
396
394
|
export const opsRouter = createOpsRouter({
|
|
397
|
-
listSignups:
|
|
395
|
+
listSignups: opsRoute.get('/signups') // GET /_ops/signups
|
|
398
396
|
.use([requireOpsScope('waitlist:read')])
|
|
399
397
|
.input({ query: Type.Object({ limit: Type.Optional(Type.Number()) }) })
|
|
400
398
|
.handler(async (c) => signupsRepository.list((await c.data()).query.limit)),
|
|
@@ -404,16 +402,41 @@ export const opsRouter = createOpsRouter({
|
|
|
404
402
|
export const appRouter = defineRouter({ ... }).packages([opsRouter]);
|
|
405
403
|
```
|
|
406
404
|
|
|
407
|
-
`
|
|
408
|
-
|
|
409
|
-
|
|
405
|
+
`opsRoute` is `route` with the `/_ops` namespace applied, so a definition carries only the
|
|
406
|
+
path this app owns — what that path looks like, how it nests, which segments are
|
|
407
|
+
parameters are the app's decisions. It exists from `@spfn/core` **0.3.0-beta.2**.
|
|
408
|
+
`createOpsRouter` injects the auth middleware into every route (there is no
|
|
409
|
+
unauthenticated variant) and serves `GET /_ops/_manifest` — the self-description the CLI
|
|
410
|
+
reads, with each command's TypeBox schemas as JSON Schema.
|
|
411
|
+
|
|
412
|
+
The manifest is registered ahead of the ops routes, so none of them can take its URL even
|
|
413
|
+
when one is a pattern like `/_ops/:name`. That ordering does not reach outside the ops
|
|
414
|
+
router: routes an app declares in its own router are registered before any package router,
|
|
415
|
+
so a pattern there that covers `/_ops/_manifest` — a `/*` catch-all, say — shadows the
|
|
416
|
+
manifest, exactly as it shadows every other package route.
|
|
410
417
|
|
|
411
418
|
Routes may be grouped in nested `defineRouter`s, and a group's own `.use()` middlewares
|
|
412
|
-
apply to its routes
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
419
|
+
apply to its routes — always after the injected auth, so a group-wide guard reads a
|
|
420
|
+
request that has already been authenticated. A group-level middleware must be a named one;
|
|
421
|
+
wrap a factory to give it a name:
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
const requireAdmin = defineMiddleware('opsAdminScope', requireOpsScope('admin:read'));
|
|
425
|
+
|
|
426
|
+
export const opsRouter = createOpsRouter({
|
|
427
|
+
admin: defineRouter({ getStats, reindex }).use([requireAdmin]),
|
|
428
|
+
}, { auth: opsTokenAuth });
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Note that a group's `.use()` middleware carries its `skips` into every route in the group,
|
|
432
|
+
suppressing that server-level middleware there.
|
|
433
|
+
|
|
434
|
+
Three things are refused when the surface is defined, rather than discovered in
|
|
435
|
+
production: a route built with `route` instead of `opsRoute` (it would carry no
|
|
436
|
+
namespace), two routes sharing a command name (the manifest flattens nested groups into
|
|
437
|
+
one list, so the CLI could not tell them apart), and a group mounting `.packages()` (those
|
|
438
|
+
routes register with neither the namespace nor the auth injection). The command name
|
|
439
|
+
`getOpsManifest` and the path `/_ops/_manifest` are reserved.
|
|
417
440
|
|
|
418
441
|
```bash
|
|
419
442
|
spfn ops list --app https://api.example.com # discover commands
|
package/dist/contract/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { R as Router } from '../router-
|
|
2
|
-
import { C as ContractDocument, a as ContractViolation, b as ContractOperation, c as ContractSnapshot } from '../types-
|
|
3
|
-
export { d as CompatibilityPolicy, e as ContractRequest, f as ContractViolationKind, J as JsonSchema } from '../types-
|
|
1
|
+
import { R as Router } from '../router-Qbssr11H.js';
|
|
2
|
+
import { C as ContractDocument, a as ContractViolation, b as ContractOperation, c as ContractSnapshot } from '../types-D1c57Ko-.js';
|
|
3
|
+
export { d as CompatibilityPolicy, e as ContractRequest, f as ContractViolationKind, J as JsonSchema } from '../types-D1c57Ko-.js';
|
|
4
4
|
import '../define-middleware-DfDP39Nq.js';
|
|
5
5
|
import 'hono';
|
|
6
6
|
import '@sinclair/typebox';
|
package/dist/ops/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as NamedMiddleware } from '../define-middleware-DfDP39Nq.js';
|
|
2
|
-
import { a as RouteDef, R as Router } from '../router-
|
|
3
|
-
import { J as JsonSchema } from '../types-
|
|
2
|
+
import { a as RouteDef, R as Router, b as RouteBuilder } from '../router-Qbssr11H.js';
|
|
3
|
+
import { J as JsonSchema } from '../types-D1c57Ko-.js';
|
|
4
4
|
import { HttpMethod } from '../route/types.js';
|
|
5
5
|
import 'hono';
|
|
6
6
|
import '@sinclair/typebox';
|
|
@@ -13,13 +13,17 @@ import 'hono/utils/http-status';
|
|
|
13
13
|
* its own ops as ordinary routes — domain operations only that app can name —
|
|
14
14
|
* and this factory turns them into a mountable package router that:
|
|
15
15
|
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
16
|
+
* - requires every route to come from `opsRoute`, which applies the `/_ops`
|
|
17
|
+
* namespace, so the surface is recognizable and an ops route can never
|
|
18
|
+
* shadow an app route;
|
|
18
19
|
* - injects the given auth middleware into every route, the manifest
|
|
19
20
|
* included, so an unauthenticated ops surface cannot be created by
|
|
20
21
|
* accident — there is no opt-out;
|
|
21
22
|
* - serves `GET /_ops/_manifest`, the self-description the `spfn ops` CLI
|
|
22
|
-
* discovers commands from.
|
|
23
|
+
* discovers commands from, registered first so no app route takes its URL.
|
|
24
|
+
*
|
|
25
|
+
* What the path looks like after the namespace is the app's business, decided
|
|
26
|
+
* when the ops route is written — this factory does not audit its shape.
|
|
23
27
|
*
|
|
24
28
|
* The auth middleware itself lives with the app's auth stack (`@spfn/auth`
|
|
25
29
|
* ships `opsTokenAuth`); core owns only the structure, so the ops surface has
|
|
@@ -27,11 +31,11 @@ import 'hono/utils/http-status';
|
|
|
27
31
|
*
|
|
28
32
|
* @example
|
|
29
33
|
* ```ts
|
|
30
|
-
* import { createOpsRouter } from '@spfn/core/ops';
|
|
34
|
+
* import { createOpsRouter, opsRoute } from '@spfn/core/ops';
|
|
31
35
|
* import { opsTokenAuth, requireOpsScope } from '@spfn/auth/server';
|
|
32
36
|
*
|
|
33
37
|
* export const opsRouter = createOpsRouter({
|
|
34
|
-
* listSignups:
|
|
38
|
+
* listSignups: opsRoute.get('/signups') // GET /_ops/signups
|
|
35
39
|
* .use([requireOpsScope('waitlist:read')])
|
|
36
40
|
* .handler(async () => signupsRepository.list()),
|
|
37
41
|
* }, { auth: opsTokenAuth });
|
|
@@ -62,6 +66,47 @@ interface OpsRouterOptions {
|
|
|
62
66
|
*/
|
|
63
67
|
declare function createOpsRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(routes: TRoutes, options: OpsRouterOptions): Router<any>;
|
|
64
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Ops Route Builder
|
|
71
|
+
*
|
|
72
|
+
* `route` with the ops namespace already applied. An ops route lives under
|
|
73
|
+
* `/_ops/` without exception, so the prefix is the helper's business rather
|
|
74
|
+
* than something every definition retypes and the factory then checks:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* const countExamples = opsRoute.get('/examples/count') // GET /_ops/examples/count
|
|
78
|
+
* .handler(async () => ({ count: await repo.countAll() }));
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Everything after the prefix belongs to the app. What the paths look like,
|
|
82
|
+
* how they nest, which segments are parameters — those are the app author's
|
|
83
|
+
* decisions, made when the ops route is written.
|
|
84
|
+
*
|
|
85
|
+
* The builder returned is an ordinary `RouteBuilder`, so `.use()`, `.input()`
|
|
86
|
+
* and `.handler()` work exactly as they do elsewhere.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/** The ops namespace, without the trailing slash. */
|
|
90
|
+
declare const OPS_PATH_ROOT = "/_ops";
|
|
91
|
+
/**
|
|
92
|
+
* Ops route builder entry point — `route`, namespaced under `/_ops`.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const listRecent = opsRoute.get('/examples')
|
|
97
|
+
* .use([requireOpsScope('example:read')])
|
|
98
|
+
* .input({ query: Type.Object({ limit: Type.Optional(Type.Number()) }) })
|
|
99
|
+
* .handler(async (c) => ({ items: await repo.findAll((await c.data()).query.limit ?? 10, 0) }));
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare const opsRoute: {
|
|
103
|
+
get: (path: string) => RouteBuilder;
|
|
104
|
+
post: (path: string) => RouteBuilder;
|
|
105
|
+
put: (path: string) => RouteBuilder;
|
|
106
|
+
patch: (path: string) => RouteBuilder;
|
|
107
|
+
delete: (path: string) => RouteBuilder;
|
|
108
|
+
};
|
|
109
|
+
|
|
65
110
|
/**
|
|
66
111
|
* Ops Manifest
|
|
67
112
|
*
|
|
@@ -104,4 +149,4 @@ declare class OpsRouterError extends Error {
|
|
|
104
149
|
*/
|
|
105
150
|
declare function collectOpsCommands(routes: Record<string, RouteDef<any> | Router<any>>): OpsCommand[];
|
|
106
151
|
|
|
107
|
-
export { OPS_MANIFEST_PATH, OPS_PATH_PREFIX, type OpsCommand, type OpsManifest, OpsRouterError, type OpsRouterOptions, collectOpsCommands, createOpsRouter };
|
|
152
|
+
export { OPS_MANIFEST_PATH, OPS_PATH_PREFIX, OPS_PATH_ROOT, type OpsCommand, type OpsManifest, OpsRouterError, type OpsRouterOptions, collectOpsCommands, createOpsRouter, opsRoute };
|
package/dist/ops/index.js
CHANGED
|
@@ -401,12 +401,12 @@ function assertOpsRoute(name, def) {
|
|
|
401
401
|
}
|
|
402
402
|
if (!def.path.startsWith(OPS_PATH_PREFIX)) {
|
|
403
403
|
throw new OpsRouterError(
|
|
404
|
-
`Ops route "${name}" is at "${def.path}", outside "${OPS_PATH_PREFIX}".
|
|
404
|
+
`Ops route "${name}" is at "${def.path}", outside "${OPS_PATH_PREFIX}". Build ops routes with \`opsRoute\` rather than \`route\` \u2014 it applies the namespace, so the path a definition carries is only the part the app owns.`
|
|
405
405
|
);
|
|
406
406
|
}
|
|
407
407
|
if (def.path === OPS_MANIFEST_PATH) {
|
|
408
408
|
throw new OpsRouterError(
|
|
409
|
-
`Ops route "${name}" claims "${OPS_MANIFEST_PATH}", which is reserved for the manifest.`
|
|
409
|
+
`Ops route "${name}" claims "${OPS_MANIFEST_PATH}", which is reserved for the manifest. The manifest is registered first, so this route would never answer.`
|
|
410
410
|
);
|
|
411
411
|
}
|
|
412
412
|
}
|
|
@@ -417,29 +417,27 @@ function assertOpsName(name) {
|
|
|
417
417
|
);
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
|
-
function rebuildNestedRouter(name, router, auth) {
|
|
420
|
+
function rebuildNestedRouter(name, router, auth, inherited) {
|
|
421
421
|
if (router._packageRouters?.length > 0) {
|
|
422
422
|
throw new OpsRouterError(
|
|
423
423
|
`Ops router "${name}" mounts package routers with .packages(). Their routes bypass the prefix check and the auth injection, so an ops surface cannot carry them.`
|
|
424
424
|
);
|
|
425
425
|
}
|
|
426
|
+
const handedDown = [...inherited, ...router._globalMiddlewares ?? []];
|
|
426
427
|
let rebuilt = defineRouter(
|
|
427
|
-
secureRoutes(router.routes, auth)
|
|
428
|
+
secureRoutes(router.routes, auth, handedDown)
|
|
428
429
|
);
|
|
429
|
-
if (router._globalMiddlewares?.length > 0) {
|
|
430
|
-
rebuilt = rebuilt.use(router._globalMiddlewares);
|
|
431
|
-
}
|
|
432
430
|
if (router._contractVersion) {
|
|
433
431
|
rebuilt = rebuilt.contractVersion(router._contractVersion);
|
|
434
432
|
}
|
|
435
433
|
return rebuilt;
|
|
436
434
|
}
|
|
437
|
-
function secureRoutes(routes, auth) {
|
|
435
|
+
function secureRoutes(routes, auth, inherited = []) {
|
|
438
436
|
const secured = {};
|
|
439
437
|
for (const [name, entry] of Object.entries(routes)) {
|
|
440
438
|
assertOpsName(name);
|
|
441
439
|
if (isRouter2(entry)) {
|
|
442
|
-
secured[name] = rebuildNestedRouter(name, entry, auth);
|
|
440
|
+
secured[name] = rebuildNestedRouter(name, entry, auth, inherited);
|
|
443
441
|
continue;
|
|
444
442
|
}
|
|
445
443
|
if (!isRouteDef2(entry)) {
|
|
@@ -448,7 +446,7 @@ function secureRoutes(routes, auth) {
|
|
|
448
446
|
assertOpsRoute(name, entry);
|
|
449
447
|
secured[name] = {
|
|
450
448
|
...entry,
|
|
451
|
-
middlewares: [auth, ...entry.middlewares ?? []]
|
|
449
|
+
middlewares: [auth, ...inherited, ...entry.middlewares ?? []]
|
|
452
450
|
};
|
|
453
451
|
}
|
|
454
452
|
return secured;
|
|
@@ -466,11 +464,37 @@ function createOpsRouter(routes, options) {
|
|
|
466
464
|
const secured = secureRoutes(routes, options.auth);
|
|
467
465
|
const manifestRoute = route.get(OPS_MANIFEST_PATH).use([options.auth]).handler(async () => manifest);
|
|
468
466
|
return defineRouter({
|
|
469
|
-
|
|
470
|
-
|
|
467
|
+
[OPS_MANIFEST_NAME]: manifestRoute,
|
|
468
|
+
...secured
|
|
471
469
|
});
|
|
472
470
|
}
|
|
473
471
|
|
|
474
|
-
|
|
472
|
+
// src/ops/ops-route.ts
|
|
473
|
+
var OPS_PATH_ROOT = "/_ops";
|
|
474
|
+
function toOpsPath(path) {
|
|
475
|
+
if (!path.startsWith("/")) {
|
|
476
|
+
throw new OpsRouterError(
|
|
477
|
+
`Ops route path "${path}" must start with "/". It is appended to "${OPS_PATH_ROOT}", so "${path}" would read as "${OPS_PATH_ROOT}${path}".`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
if (path === "/") {
|
|
481
|
+
throw new OpsRouterError(
|
|
482
|
+
`Ops route path "/" names no command \u2014 "${OPS_PATH_ROOT}" itself is not a command.`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
return OPS_PATH_ROOT + path;
|
|
486
|
+
}
|
|
487
|
+
function opsMethod(method) {
|
|
488
|
+
return (path) => route[method](toOpsPath(path));
|
|
489
|
+
}
|
|
490
|
+
var opsRoute = {
|
|
491
|
+
get: opsMethod("get"),
|
|
492
|
+
post: opsMethod("post"),
|
|
493
|
+
put: opsMethod("put"),
|
|
494
|
+
patch: opsMethod("patch"),
|
|
495
|
+
delete: opsMethod("delete")
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export { OPS_MANIFEST_PATH, OPS_PATH_PREFIX, OPS_PATH_ROOT, OpsRouterError, collectOpsCommands, createOpsRouter, opsRoute };
|
|
475
499
|
//# sourceMappingURL=index.js.map
|
|
476
500
|
//# sourceMappingURL=index.js.map
|
package/dist/ops/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/route/route-builder.ts","../../src/route/router.ts","../../src/ops/manifest.ts","../../src/ops/create-ops-router.ts"],"names":["isRouter","isRouteDef"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Route Builder\n *\n * Provides tRPC-style chainable API for route definition\n */\n\nimport type { MiddlewareHandler } from 'hono';\nimport type { NamedMiddleware } from './define-middleware';\nimport type { RouteInput } from './route-input';\nimport type { RouteBuilderContext } from './context';\nimport type { RouteContract } from './contract';\nimport type { HttpMethod } from './types';\n\n/**\n * Route handler function\n */\nexport type RouteHandlerFn<\n TInput extends RouteInput = RouteInput,\n TInterceptor extends RouteInput = {},\n TResponse = unknown,\n> = (c: RouteBuilderContext<TInput, TInterceptor>) => Response | Promise<Response> | TResponse | Promise<TResponse>;\n\n/**\n * Route definition result\n *\n * Contains all information needed for type inference and registration\n */\nexport type RouteDef<\n TInput extends RouteInput = RouteInput,\n TInterceptor extends RouteInput = {},\n TResponse = unknown,\n> = {\n method?: HttpMethod;\n path?: string;\n input?: TInput;\n interceptor?: TInterceptor;\n middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];\n skipMiddlewares?: string[] | '*';\n\n /**\n * Public promise this route makes to separately deployed clients.\n *\n * Present as a runtime value, unlike `_response`: the contract generator and\n * the compatibility gate read it.\n */\n contract?: RouteContract;\n\n handler: RouteHandlerFn<TInput, TInterceptor, TResponse>;\n\n // Type inference helpers\n _input: TInput;\n _interceptor: TInterceptor;\n _response: TResponse;\n};\n\n/**\n * Route builder with chainable API (tRPC-style)\n */\nexport class RouteBuilder<\n TInput extends RouteInput = {},\n TInterceptor extends RouteInput = {},\n TResponse = never,\n>\n{\n public _method?: HttpMethod;\n public _path?: string;\n public _input?: TInput;\n public _interceptor?: TInterceptor;\n public _middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];\n public _skipMiddlewares?: string[] | '*';\n public _contract?: RouteContract;\n\n /**\n * Create a new RouteBuilder with copied properties and optional overrides\n */\n private clone<\n TNewInput extends RouteInput = TInput,\n TNewInterceptor extends RouteInput = TInterceptor,\n >(\n overrides?: Partial<{\n input: TNewInput;\n interceptor: TNewInterceptor;\n middlewares: (MiddlewareHandler | NamedMiddleware<string>)[];\n skipMiddlewares: string[] | '*';\n contract: RouteContract;\n }>,\n ): RouteBuilder<TNewInput, TNewInterceptor, TResponse>\n {\n const builder = new RouteBuilder<TNewInput, TNewInterceptor, TResponse>();\n builder._method = this._method;\n builder._path = this._path;\n builder._input = (overrides?.input ?? this._input) as TNewInput | undefined;\n builder._interceptor = (overrides?.interceptor ?? this._interceptor) as TNewInterceptor | undefined;\n builder._middlewares = overrides?.middlewares ?? this._middlewares;\n builder._skipMiddlewares = overrides?.skipMiddlewares ?? this._skipMiddlewares;\n builder._contract = overrides?.contract ?? this._contract;\n\n return builder;\n }\n\n /**\n * Define input schemas\n *\n * @example\n * ```ts\n * route.get('/users/:id')\n * .input({\n * params: Type.Object({ id: Type.String() }),\n * query: Type.Object({ page: Type.Number() }),\n * headers: Type.Object({ authorization: Type.String() })\n * })\n * .handler(async (c) => {\n * const { params, query, headers } = await c.data();\n * // params = { id: string }\n * // query = { page: number }\n * // headers = { authorization: string }\n * })\n * ```\n */\n input<TNewInput extends RouteInput>(input: TNewInput): RouteBuilder<TNewInput, TInterceptor, TResponse>\n {\n return this.clone({ input });\n }\n\n /**\n * Define fields injected by interceptors\n *\n * These fields are:\n * - Available in the handler (merged with input)\n * - Excluded from client types (codegen uses only input)\n * - Not validated by route input schema (injected by middleware)\n *\n * Use this when middleware/interceptors add fields to the request\n * before it reaches the handler.\n *\n * @example\n * ```ts\n * // Auth interceptor injects crypto key fields\n * route.post('/_auth/login')\n * .input({\n * body: Type.Object({\n * email: Type.String(),\n * password: Type.String()\n * })\n * })\n * .interceptor({\n * body: Type.Object({\n * publicKey: Type.String(),\n * keyId: Type.String(),\n * fingerprint: Type.String()\n * })\n * })\n * .handler(async (c) => {\n * const { body } = await c.data();\n * // body type: { email, password, publicKey, keyId, fingerprint }\n * // Client only sees: { email, password }\n * return loginService(body);\n * });\n * ```\n */\n interceptor<TNewInterceptor extends RouteInput>(\n interceptor: TNewInterceptor,\n ): RouteBuilder<TInput, TNewInterceptor, TResponse>\n {\n return this.clone({ interceptor });\n }\n\n /**\n * Add middlewares to the route\n *\n * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).\n * Named middlewares that are already registered globally will be automatically\n * deduplicated to prevent double execution.\n *\n * @example\n * ```ts\n * import { authenticate } from '@spfn/auth/server/middleware';\n *\n * // With NamedMiddleware (auto-deduped if registered globally)\n * route.get('/users')\n * .use([authenticate, RateLimitMiddleware()])\n *\n * // With regular middleware handlers\n * route.get('/users')\n * .use([AuthMiddleware(), RateLimitMiddleware()])\n * ```\n */\n middleware(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.clone({ middlewares });\n }\n\n /**\n * Add middlewares to the route (alias for `.middleware()`)\n *\n * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).\n * Named middlewares that are already registered globally will be automatically\n * deduplicated to prevent double execution.\n *\n * @example\n * ```ts\n * import { authenticate } from '@spfn/auth/server/middleware';\n *\n * // With NamedMiddleware (auto-deduped if registered globally)\n * route.get('/users')\n * .use([authenticate, RateLimitMiddleware()])\n *\n * // With regular middleware handlers\n * route.get('/users')\n * .use([AuthMiddleware(), RateLimitMiddleware()])\n * ```\n */\n use(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.middleware(middlewares);\n }\n\n /**\n * Skip server-level named middlewares\n *\n * Useful for public endpoints that should bypass auth or rate limiting\n *\n * @param middlewareNames - Array of middleware names to skip, or '*' to skip all\n *\n * @example\n * ```ts\n * // Skip specific middlewares\n * route.get('/health')\n * .skip(['auth', 'rateLimit'])\n * .handler(async (c) => c.json({ status: 'ok' }));\n *\n * // Skip only auth (still apply rate limiting)\n * route.get('/public-data')\n * .skip(['auth'])\n * .handler(async (c) => { ... });\n *\n * // Skip all middlewares\n * route.get('/public-health')\n * .skip('*')\n * .handler(async (c) => c.json({ status: 'ok' }));\n * ```\n */\n skip(middlewareNames: string[] | '*'): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.clone({ skipMiddlewares: middlewareNames });\n }\n\n /**\n * Publish this route as a versioned contract operation\n *\n * Marks the route as a promise to clients that are compiled and deployed\n * separately from the server — a mobile app, an external API consumer.\n * The `@spfn/core:contract` generator writes every contracted route into\n * `contracts/current.json`, and the build refuses a change that would break\n * an already-released client.\n *\n * Routes without `.contract()` are unaffected: they simply do not appear in\n * the contract. A web client needs nothing here — it derives its types from\n * the router in the same build.\n *\n * @example\n * ```ts\n * export const getUser = route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .contract({\n * since: '1.2.0',\n * auth: 'clientProofV1',\n * requiresSession: true,\n * response: Type.Object({\n * id: Type.String(),\n * name: Type.String(),\n * email: Type.Optional(Type.String()),\n * }),\n * })\n * .handler(async (c) => { ... });\n * ```\n */\n contract(contract: RouteContract): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.clone({ contract });\n }\n\n /**\n * Define handler function\n *\n * Response type is automatically inferred from the return value.\n * Use helper methods like `c.created()`, `c.paginated()` for proper type inference.\n *\n * @example\n * ```ts\n * // Direct return - type inferred from data\n * route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => {\n * const { params } = await c.data();\n * return await getUser(params.id); // Type: User\n * })\n *\n * // Using c.created() - returns data with 201 status, type preserved\n * route.post('/users')\n * .input({ body: Type.Object({ name: Type.String() }) })\n * .handler(async (c) => {\n * const { body } = await c.data();\n * return c.created(await createUser(body)); // Type: User\n * })\n *\n * // Using c.paginated() - returns PaginatedResult<T>\n * route.get('/users')\n * .handler(async (c) => {\n * const users = await getUsers();\n * return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>\n * })\n *\n * // Using c.noContent() - returns void\n * route.delete('/users/:id')\n * .handler(async (c) => {\n * await deleteUser(params.id);\n * return c.noContent(); // Type: void\n * })\n *\n * // Using c.json() - returns Response (type inference lost)\n * // Use only when you need custom status codes not covered by helpers\n * route.get('/custom')\n * .handler(async (c) => {\n * return c.json({ data }, 418); // Type: Response\n * })\n * ```\n */\n handler<THandlerResponse>(\n fn: RouteHandlerFn<TInput, TInterceptor, THandlerResponse>,\n ): RouteDef<TInput, TInterceptor, THandlerResponse>\n {\n return {\n method: this._method,\n path: this._path,\n input: this._input,\n interceptor: this._interceptor,\n middlewares: this._middlewares,\n skipMiddlewares: this._skipMiddlewares,\n contract: this._contract,\n handler: fn,\n _input: {} as TInput,\n _interceptor: {} as TInterceptor,\n _response: {} as THandlerResponse,\n };\n }\n}\n\n/**\n * Create a route definition with HTTP method shortcuts\n */\nfunction createMethodRoute(method: HttpMethod): (path: string) => RouteBuilder\n{\n return (path: string) =>\n {\n const builder = new RouteBuilder();\n builder._method = method;\n builder._path = path;\n\n return builder;\n };\n}\n\n/**\n * Route builder entry point\n *\n * @example\n * ```ts\n * // GET request\n * export const getUser = route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => {\n * const { params } = await c.data();\n * return await db.user.findUnique({ where: { id: params.id } });\n * });\n *\n * // POST request\n * export const createUser = route.post('/users')\n * .input({ body: Type.Object({ name: Type.String(), email: Type.String() }) })\n * .handler(async (c) => {\n * const { body } = await c.data();\n * return c.created(await db.user.create({ data: body }));\n * });\n * ```\n */\nexport const route = {\n get: createMethodRoute('GET'),\n post: createMethodRoute('POST'),\n put: createMethodRoute('PUT'),\n patch: createMethodRoute('PATCH'),\n delete: createMethodRoute('DELETE'),\n};\n","/**\n * Router Definition\n *\n * Provides router composition and middleware management\n */\n\nimport type { NamedMiddleware } from './define-middleware';\nimport type { RouteDef } from './route-builder';\n\n/**\n * Router definition - holds all routes\n */\nexport interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>> {\n routes: TRoutes;\n _routes: TRoutes;\n _packageRouters: Router<any>[];\n _globalMiddlewares: NamedMiddleware<string>[];\n\n /** The contract version these routes publish, or null when uncontracted. */\n _contractVersion: string | null;\n\n /**\n * Register package routers (type-hidden)\n *\n * Package routes are:\n * - Recognized by RPC proxy and backend\n * - NOT exposed in client types (use package's own API like authApi, cmsApi)\n *\n * @example\n * ```ts\n * import { authRouter } from '@spfn/auth/server';\n * import { cmsAppRouter } from '@spfn/cms/server';\n *\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter, cmsAppRouter]);\n *\n * // Client usage:\n * // api.getRoot.call({}) - app routes\n * // authApi.login.call({}) - package API\n * ```\n */\n packages(routers: Router<any>[]): Router<TRoutes>;\n\n /**\n * Register global middlewares\n *\n * Applied to all routes unless explicitly skipped via .skip()\n *\n * @example\n * ```ts\n * import { authMiddleware, loggingMiddleware } from './middlewares';\n *\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter])\n * .use([authMiddleware, loggingMiddleware]);\n * ```\n */\n use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>;\n\n /**\n * Declare the contract version these routes publish.\n *\n * A client compiled against this server — a mobile app in a store — is\n * generated from one version of the contract and cannot be updated when the\n * server changes. The server announces this version on every response so\n * that client can tell whether the two ends still agree.\n *\n * This is the version's source. A released snapshot is written to\n * `contracts/released/<version>.json` from what is declared here, so the\n * filename follows the code rather than the code having to be told what the\n * filename said.\n *\n * Only a server with contracted routes needs it. Without it the contract\n * generator still writes `current.json` and still runs the compatibility\n * gate; what it cannot do is cut a release or announce a version.\n *\n * @example\n * ```ts\n * export const appRouter = defineRouter({ getRoot, listItems })\n * .contractVersion('1.2.0')\n * .packages([authRouter]);\n * ```\n */\n contractVersion(version: string): Router<TRoutes>;\n}\n\n/**\n * Create a Router instance with chainable methods\n */\nfunction createRouterInstance<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(\n routes: TRoutes,\n packageRouters: Router<any>[] = [],\n globalMiddlewares: NamedMiddleware<string>[] = [],\n contractVersion: string | null = null,\n): Router<TRoutes>\n{\n return {\n routes,\n _routes: routes,\n _packageRouters: packageRouters,\n _globalMiddlewares: globalMiddlewares,\n _contractVersion: contractVersion,\n\n packages(routers: Router<any>[]): Router<TRoutes>\n {\n const newPackageRouters = [...this._packageRouters, ...routers];\n\n // Also include nested package routers if any\n for (const pkgRouter of routers)\n {\n if (pkgRouter._packageRouters?.length > 0)\n {\n newPackageRouters.push(...pkgRouter._packageRouters);\n }\n }\n\n return createRouterInstance(\n this.routes,\n newPackageRouters,\n this._globalMiddlewares,\n this._contractVersion,\n );\n },\n\n use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>\n {\n return createRouterInstance(\n this.routes,\n this._packageRouters,\n [...this._globalMiddlewares, ...middlewares],\n this._contractVersion,\n );\n },\n\n contractVersion(version: string): Router<TRoutes>\n {\n assertContractVersion(version);\n\n return createRouterInstance(\n this.routes,\n this._packageRouters,\n this._globalMiddlewares,\n version,\n );\n },\n };\n}\n\n/**\n * A version that cannot be ordered cannot gate a release.\n *\n * Checked when it is declared rather than when a snapshot is cut: the failure\n * belongs next to the typo, not in a build step that runs much later.\n */\nfunction assertContractVersion(version: string): void\n{\n if (!/^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)*$/.test(version))\n {\n throw new Error(\n `contractVersion(\"${version}\") is not a version of the form major.minor.patch. `\n + 'The released snapshot is named from this value and releases are compared by it.',\n );\n }\n}\n\n/**\n * Define a router with multiple routes (tRPC-style)\n *\n * Supports chainable API for packages and middlewares:\n *\n * @example\n * ```ts\n * // Basic usage\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * listExamples,\n * });\n *\n * // With package routers (type-hidden)\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter, cmsAppRouter]);\n *\n * // With global middlewares\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter])\n * .use([authMiddleware, loggingMiddleware]);\n *\n * export type AppRouter = typeof appRouter;\n * ```\n *\n * Package routes:\n * - Recognized by RPC proxy and backend for routing\n * - NOT included in AppRouter type (use authApi, cmsApi instead)\n * - Prevents confusion between app API and package APIs\n */\nexport function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(\n routes: TRoutes,\n): Router<TRoutes>\n{\n return createRouterInstance(routes);\n}\n","/**\n * Ops Manifest\n *\n * The server's self-description of its ops surface: every command an app\n * exposes under `/_ops`, with each command's input schemas as plain JSON\n * Schema. The ops CLI fetches this from the running server, so command\n * discovery needs neither the app's source nor a generated artifact on the\n * operator's machine.\n *\n * The router is loaded and walked (the contract collector's approach) rather\n * than parsed from source: route schemas built from imported values resolve,\n * and TypeBox schemas serialize to JSON Schema by construction.\n */\n\nimport type { JsonSchema } from '../contract/types';\nimport type { RouteDef } from '../route/route-builder';\nimport type { RouteInput } from '../route/route-input';\nimport type { Router } from '../route/router';\nimport type { HttpMethod } from '../route/types';\n\n/** One invokable ops command, as the CLI sees it. */\nexport interface OpsCommand\n{\n name: string;\n method: HttpMethod;\n path: string;\n\n /** Input sections the route declares, each as JSON Schema. */\n input: {\n params?: JsonSchema;\n query?: JsonSchema;\n body?: JsonSchema;\n };\n}\n\n/** What `GET /_ops/_manifest` answers. */\nexport interface OpsManifest\n{\n manifestVersion: 1;\n commands: OpsCommand[];\n}\n\n/** Thrown when a route cannot be part of an ops surface. */\nexport class OpsRouterError extends Error\n{\n constructor(message: string)\n {\n super(message);\n this.name = 'OpsRouterError';\n }\n}\n\nfunction isRouter(value: unknown): value is Router<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'routes' in value\n && '_routes' in value;\n}\n\nfunction isRouteDef(value: unknown): value is RouteDef<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'handler' in value;\n}\n\n/**\n * Strip TypeBox's symbol-keyed metadata and hand back plain JSON — the same\n * round trip the contract collector uses.\n */\nfunction toJsonSchema(schema: unknown): JsonSchema\n{\n return JSON.parse(JSON.stringify(schema)) as JsonSchema;\n}\n\nconst INPUT_SECTIONS = ['params', 'query', 'body'] as const;\n\nfunction toCommandInput(input: RouteInput | undefined): OpsCommand['input']\n{\n const sections: OpsCommand['input'] = {};\n\n if (!input)\n {\n return sections;\n }\n\n for (const section of INPUT_SECTIONS)\n {\n const schema = input[section];\n if (schema)\n {\n sections[section] = toJsonSchema(schema);\n }\n }\n\n return sections;\n}\n\n/**\n * Walk a routes record (nested routers included) and collect every RouteDef\n * as an ops command. Validation of paths and names happens in\n * `createOpsRouter` before this runs.\n */\nexport function collectOpsCommands(\n routes: Record<string, RouteDef<any> | Router<any>>,\n): OpsCommand[]\n{\n const commands: OpsCommand[] = [];\n visit(routes, commands, new Map<string, string>());\n commands.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n\n return commands;\n}\n\nfunction visit(\n routes: Record<string, RouteDef<any> | Router<any>>,\n commands: OpsCommand[],\n claimed: Map<string, string>,\n): void\n{\n for (const [name, entry] of Object.entries(routes))\n {\n if (isRouter(entry))\n {\n visit(entry.routes, commands, claimed);\n continue;\n }\n\n if (!isRouteDef(entry) || !entry.method || !entry.path)\n {\n continue;\n }\n\n assertUnclaimedName(name, entry.path, claimed);\n\n commands.push({\n name,\n method: entry.method,\n path: entry.path,\n input: toCommandInput(entry.input as RouteInput | undefined),\n });\n }\n}\n\n/**\n * Nested routers flatten into one command list, so two routes keyed alike in\n * different routers would both be announced under the same command name. The\n * CLI resolves a name to the first match, which means an operator asking for\n * one command silently invokes the other — refuse it at definition time.\n */\nfunction assertUnclaimedName(name: string, path: string, claimed: Map<string, string>): void\n{\n const existing = claimed.get(name);\n if (existing !== undefined)\n {\n throw new OpsRouterError(\n `Two ops routes are named \"${name}\" (\"${existing}\" and \"${path}\"). `\n + 'Command names are flattened across nested routers, so each must be unique '\n + 'for the CLI to resolve the one an operator asked for.',\n );\n }\n\n claimed.set(name, path);\n}\n","/**\n * Ops Router\n *\n * The structure half of SPFN's CLI-first operations surface. An app develops\n * its own ops as ordinary routes — domain operations only that app can name —\n * and this factory turns them into a mountable package router that:\n *\n * - enforces the `/_ops/` path prefix, so the surface is recognizable and an\n * ops route can never shadow an app route;\n * - injects the given auth middleware into every route, the manifest\n * included, so an unauthenticated ops surface cannot be created by\n * accident — there is no opt-out;\n * - serves `GET /_ops/_manifest`, the self-description the `spfn ops` CLI\n * discovers commands from.\n *\n * The auth middleware itself lives with the app's auth stack (`@spfn/auth`\n * ships `opsTokenAuth`); core owns only the structure, so the ops surface has\n * no opinion about how a token is stored or verified.\n *\n * @example\n * ```ts\n * import { createOpsRouter } from '@spfn/core/ops';\n * import { opsTokenAuth, requireOpsScope } from '@spfn/auth/server';\n *\n * export const opsRouter = createOpsRouter({\n * listSignups: route.get('/_ops/signups')\n * .use([requireOpsScope('waitlist:read')])\n * .handler(async () => signupsRepository.list()),\n * }, { auth: opsTokenAuth });\n *\n * // mounted like any package router:\n * export const appRouter = defineRouter({ ... }).packages([opsRouter]);\n * ```\n */\n\nimport type { NamedMiddleware } from '../route/define-middleware';\nimport { route, type RouteDef } from '../route/route-builder';\nimport { defineRouter, type Router } from '../route/router';\nimport { collectOpsCommands, OpsRouterError, type OpsManifest } from './manifest';\n\n/** Every ops route lives under this prefix. */\nexport const OPS_PATH_PREFIX = '/_ops/';\n\n/** Where the manifest is served. Reserved — an app route cannot claim it. */\nexport const OPS_MANIFEST_PATH = '/_ops/_manifest';\n\n/** Reserved route name for the injected manifest route. */\nconst OPS_MANIFEST_NAME = 'getOpsManifest';\n\nexport interface OpsRouterOptions\n{\n /**\n * The middleware that authenticates every ops request. Required — an ops\n * surface without authentication is refused at definition time, not\n * discovered in production.\n */\n auth: NamedMiddleware<string>;\n}\n\nfunction isRouter(value: unknown): value is Router<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'routes' in value\n && '_routes' in value;\n}\n\nfunction isRouteDef(value: unknown): value is RouteDef<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'handler' in value;\n}\n\nfunction assertOpsRoute(name: string, def: RouteDef<any>): void\n{\n if (!def.method || !def.path)\n {\n throw new OpsRouterError(\n `Ops route \"${name}\" has no method or path. `\n + 'An ops command is invoked on the wire, so both are required.',\n );\n }\n\n if (!def.path.startsWith(OPS_PATH_PREFIX))\n {\n throw new OpsRouterError(\n `Ops route \"${name}\" is at \"${def.path}\", outside \"${OPS_PATH_PREFIX}\". `\n + 'Every ops route lives under the prefix so the surface stays recognizable '\n + 'and can never shadow an app route.',\n );\n }\n\n if (def.path === OPS_MANIFEST_PATH)\n {\n throw new OpsRouterError(\n `Ops route \"${name}\" claims \"${OPS_MANIFEST_PATH}\", which is reserved for the manifest.`,\n );\n }\n}\n\n/**\n * The reserved name is checked for every entry, route and nested router\n * alike: the manifest route is merged in last, so an entry under this name\n * would be overwritten rather than refused — its routes would still be\n * announced by the manifest and answer 404 when invoked.\n */\nfunction assertOpsName(name: string): void\n{\n if (name === OPS_MANIFEST_NAME)\n {\n throw new OpsRouterError(\n `Ops route name \"${OPS_MANIFEST_NAME}\" is reserved for the manifest route.`,\n );\n }\n}\n\n/**\n * Rebuild a nested router with the auth middleware injected into its routes,\n * carrying over what the original declared. A plain `defineRouter` of the\n * secured routes would silently drop the router's own `.use()` middlewares —\n * a `requireOpsScope` guard among them — leaving those routes reachable by\n * any valid ops token.\n *\n * `.packages()` is refused rather than carried: package routes are registered\n * without passing through this factory, so they would join the ops surface\n * with neither the prefix check nor the auth injection.\n */\nfunction rebuildNestedRouter(name: string, router: Router<any>, auth: NamedMiddleware<string>): Router<any>\n{\n if (router._packageRouters?.length > 0)\n {\n throw new OpsRouterError(\n `Ops router \"${name}\" mounts package routers with .packages(). `\n + 'Their routes bypass the prefix check and the auth injection, so an ops surface cannot carry them.',\n );\n }\n\n let rebuilt = defineRouter(\n secureRoutes(router.routes, auth) as Record<string, RouteDef<any>>,\n );\n\n if (router._globalMiddlewares?.length > 0)\n {\n rebuilt = rebuilt.use(router._globalMiddlewares);\n }\n\n if (router._contractVersion)\n {\n rebuilt = rebuilt.contractVersion(router._contractVersion);\n }\n\n return rebuilt;\n}\n\n/**\n * Validate every route and hand back a copy with the auth middleware\n * prepended. Route-level injection (rather than router-level `.use`) makes\n * the middleware's `skips` declaration effective, so `opsTokenAuth` can\n * auto-skip a server-level `auth` middleware exactly as `oneTimeTokenAuth`\n * does.\n */\nfunction secureRoutes(\n routes: Record<string, RouteDef<any> | Router<any>>,\n auth: NamedMiddleware<string>,\n): Record<string, RouteDef<any> | Router<any>>\n{\n const secured: Record<string, RouteDef<any> | Router<any>> = {};\n\n for (const [name, entry] of Object.entries(routes))\n {\n assertOpsName(name);\n\n if (isRouter(entry))\n {\n secured[name] = rebuildNestedRouter(name, entry, auth);\n continue;\n }\n\n if (!isRouteDef(entry))\n {\n throw new OpsRouterError(`Ops router entry \"${name}\" is neither a route nor a router.`);\n }\n\n assertOpsRoute(name, entry);\n secured[name] = {\n ...entry,\n middlewares: [auth, ...(entry.middlewares ?? [])],\n };\n }\n\n return secured;\n}\n\n/**\n * Build the app's ops surface from its ops routes.\n *\n * Returns an ordinary `Router` meant to be mounted with `.packages()`, so ops\n * routes stay out of the app's client types exactly like other package\n * routes.\n */\nexport function createOpsRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(\n routes: TRoutes,\n options: OpsRouterOptions,\n): Router<any>\n{\n if (!options?.auth)\n {\n throw new OpsRouterError(\n 'createOpsRouter requires an auth middleware ({ auth: ... }). '\n + 'An ops surface reachable without authentication cannot be created.',\n );\n }\n\n const manifest: OpsManifest = {\n manifestVersion: 1,\n commands: collectOpsCommands(routes),\n };\n\n const secured = secureRoutes(routes, options.auth);\n\n const manifestRoute = route.get(OPS_MANIFEST_PATH)\n .use([options.auth])\n .handler(async () => manifest);\n\n return defineRouter({\n ...secured,\n [OPS_MANIFEST_NAME]: manifestRoute,\n } as Record<string, RouteDef<any>>);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/route/route-builder.ts","../../src/route/router.ts","../../src/ops/manifest.ts","../../src/ops/create-ops-router.ts","../../src/ops/ops-route.ts"],"names":["isRouter","isRouteDef"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Route Builder\n *\n * Provides tRPC-style chainable API for route definition\n */\n\nimport type { MiddlewareHandler } from 'hono';\nimport type { NamedMiddleware } from './define-middleware';\nimport type { RouteInput } from './route-input';\nimport type { RouteBuilderContext } from './context';\nimport type { RouteContract } from './contract';\nimport type { HttpMethod } from './types';\n\n/**\n * Route handler function\n */\nexport type RouteHandlerFn<\n TInput extends RouteInput = RouteInput,\n TInterceptor extends RouteInput = {},\n TResponse = unknown,\n> = (c: RouteBuilderContext<TInput, TInterceptor>) => Response | Promise<Response> | TResponse | Promise<TResponse>;\n\n/**\n * Route definition result\n *\n * Contains all information needed for type inference and registration\n */\nexport type RouteDef<\n TInput extends RouteInput = RouteInput,\n TInterceptor extends RouteInput = {},\n TResponse = unknown,\n> = {\n method?: HttpMethod;\n path?: string;\n input?: TInput;\n interceptor?: TInterceptor;\n middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];\n skipMiddlewares?: string[] | '*';\n\n /**\n * Public promise this route makes to separately deployed clients.\n *\n * Present as a runtime value, unlike `_response`: the contract generator and\n * the compatibility gate read it.\n */\n contract?: RouteContract;\n\n handler: RouteHandlerFn<TInput, TInterceptor, TResponse>;\n\n // Type inference helpers\n _input: TInput;\n _interceptor: TInterceptor;\n _response: TResponse;\n};\n\n/**\n * Route builder with chainable API (tRPC-style)\n */\nexport class RouteBuilder<\n TInput extends RouteInput = {},\n TInterceptor extends RouteInput = {},\n TResponse = never,\n>\n{\n public _method?: HttpMethod;\n public _path?: string;\n public _input?: TInput;\n public _interceptor?: TInterceptor;\n public _middlewares?: (MiddlewareHandler | NamedMiddleware<string>)[];\n public _skipMiddlewares?: string[] | '*';\n public _contract?: RouteContract;\n\n /**\n * Create a new RouteBuilder with copied properties and optional overrides\n */\n private clone<\n TNewInput extends RouteInput = TInput,\n TNewInterceptor extends RouteInput = TInterceptor,\n >(\n overrides?: Partial<{\n input: TNewInput;\n interceptor: TNewInterceptor;\n middlewares: (MiddlewareHandler | NamedMiddleware<string>)[];\n skipMiddlewares: string[] | '*';\n contract: RouteContract;\n }>,\n ): RouteBuilder<TNewInput, TNewInterceptor, TResponse>\n {\n const builder = new RouteBuilder<TNewInput, TNewInterceptor, TResponse>();\n builder._method = this._method;\n builder._path = this._path;\n builder._input = (overrides?.input ?? this._input) as TNewInput | undefined;\n builder._interceptor = (overrides?.interceptor ?? this._interceptor) as TNewInterceptor | undefined;\n builder._middlewares = overrides?.middlewares ?? this._middlewares;\n builder._skipMiddlewares = overrides?.skipMiddlewares ?? this._skipMiddlewares;\n builder._contract = overrides?.contract ?? this._contract;\n\n return builder;\n }\n\n /**\n * Define input schemas\n *\n * @example\n * ```ts\n * route.get('/users/:id')\n * .input({\n * params: Type.Object({ id: Type.String() }),\n * query: Type.Object({ page: Type.Number() }),\n * headers: Type.Object({ authorization: Type.String() })\n * })\n * .handler(async (c) => {\n * const { params, query, headers } = await c.data();\n * // params = { id: string }\n * // query = { page: number }\n * // headers = { authorization: string }\n * })\n * ```\n */\n input<TNewInput extends RouteInput>(input: TNewInput): RouteBuilder<TNewInput, TInterceptor, TResponse>\n {\n return this.clone({ input });\n }\n\n /**\n * Define fields injected by interceptors\n *\n * These fields are:\n * - Available in the handler (merged with input)\n * - Excluded from client types (codegen uses only input)\n * - Not validated by route input schema (injected by middleware)\n *\n * Use this when middleware/interceptors add fields to the request\n * before it reaches the handler.\n *\n * @example\n * ```ts\n * // Auth interceptor injects crypto key fields\n * route.post('/_auth/login')\n * .input({\n * body: Type.Object({\n * email: Type.String(),\n * password: Type.String()\n * })\n * })\n * .interceptor({\n * body: Type.Object({\n * publicKey: Type.String(),\n * keyId: Type.String(),\n * fingerprint: Type.String()\n * })\n * })\n * .handler(async (c) => {\n * const { body } = await c.data();\n * // body type: { email, password, publicKey, keyId, fingerprint }\n * // Client only sees: { email, password }\n * return loginService(body);\n * });\n * ```\n */\n interceptor<TNewInterceptor extends RouteInput>(\n interceptor: TNewInterceptor,\n ): RouteBuilder<TInput, TNewInterceptor, TResponse>\n {\n return this.clone({ interceptor });\n }\n\n /**\n * Add middlewares to the route\n *\n * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).\n * Named middlewares that are already registered globally will be automatically\n * deduplicated to prevent double execution.\n *\n * @example\n * ```ts\n * import { authenticate } from '@spfn/auth/server/middleware';\n *\n * // With NamedMiddleware (auto-deduped if registered globally)\n * route.get('/users')\n * .use([authenticate, RateLimitMiddleware()])\n *\n * // With regular middleware handlers\n * route.get('/users')\n * .use([AuthMiddleware(), RateLimitMiddleware()])\n * ```\n */\n middleware(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.clone({ middlewares });\n }\n\n /**\n * Add middlewares to the route (alias for `.middleware()`)\n *\n * Accepts both regular middleware handlers and named middlewares (NamedMiddleware).\n * Named middlewares that are already registered globally will be automatically\n * deduplicated to prevent double execution.\n *\n * @example\n * ```ts\n * import { authenticate } from '@spfn/auth/server/middleware';\n *\n * // With NamedMiddleware (auto-deduped if registered globally)\n * route.get('/users')\n * .use([authenticate, RateLimitMiddleware()])\n *\n * // With regular middleware handlers\n * route.get('/users')\n * .use([AuthMiddleware(), RateLimitMiddleware()])\n * ```\n */\n use(middlewares: (MiddlewareHandler | NamedMiddleware<string>)[]): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.middleware(middlewares);\n }\n\n /**\n * Skip server-level named middlewares\n *\n * Useful for public endpoints that should bypass auth or rate limiting\n *\n * @param middlewareNames - Array of middleware names to skip, or '*' to skip all\n *\n * @example\n * ```ts\n * // Skip specific middlewares\n * route.get('/health')\n * .skip(['auth', 'rateLimit'])\n * .handler(async (c) => c.json({ status: 'ok' }));\n *\n * // Skip only auth (still apply rate limiting)\n * route.get('/public-data')\n * .skip(['auth'])\n * .handler(async (c) => { ... });\n *\n * // Skip all middlewares\n * route.get('/public-health')\n * .skip('*')\n * .handler(async (c) => c.json({ status: 'ok' }));\n * ```\n */\n skip(middlewareNames: string[] | '*'): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.clone({ skipMiddlewares: middlewareNames });\n }\n\n /**\n * Publish this route as a versioned contract operation\n *\n * Marks the route as a promise to clients that are compiled and deployed\n * separately from the server — a mobile app, an external API consumer.\n * The `@spfn/core:contract` generator writes every contracted route into\n * `contracts/current.json`, and the build refuses a change that would break\n * an already-released client.\n *\n * Routes without `.contract()` are unaffected: they simply do not appear in\n * the contract. A web client needs nothing here — it derives its types from\n * the router in the same build.\n *\n * @example\n * ```ts\n * export const getUser = route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .contract({\n * since: '1.2.0',\n * auth: 'clientProofV1',\n * requiresSession: true,\n * response: Type.Object({\n * id: Type.String(),\n * name: Type.String(),\n * email: Type.Optional(Type.String()),\n * }),\n * })\n * .handler(async (c) => { ... });\n * ```\n */\n contract(contract: RouteContract): RouteBuilder<TInput, TInterceptor, TResponse>\n {\n return this.clone({ contract });\n }\n\n /**\n * Define handler function\n *\n * Response type is automatically inferred from the return value.\n * Use helper methods like `c.created()`, `c.paginated()` for proper type inference.\n *\n * @example\n * ```ts\n * // Direct return - type inferred from data\n * route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => {\n * const { params } = await c.data();\n * return await getUser(params.id); // Type: User\n * })\n *\n * // Using c.created() - returns data with 201 status, type preserved\n * route.post('/users')\n * .input({ body: Type.Object({ name: Type.String() }) })\n * .handler(async (c) => {\n * const { body } = await c.data();\n * return c.created(await createUser(body)); // Type: User\n * })\n *\n * // Using c.paginated() - returns PaginatedResult<T>\n * route.get('/users')\n * .handler(async (c) => {\n * const users = await getUsers();\n * return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>\n * })\n *\n * // Using c.noContent() - returns void\n * route.delete('/users/:id')\n * .handler(async (c) => {\n * await deleteUser(params.id);\n * return c.noContent(); // Type: void\n * })\n *\n * // Using c.json() - returns Response (type inference lost)\n * // Use only when you need custom status codes not covered by helpers\n * route.get('/custom')\n * .handler(async (c) => {\n * return c.json({ data }, 418); // Type: Response\n * })\n * ```\n */\n handler<THandlerResponse>(\n fn: RouteHandlerFn<TInput, TInterceptor, THandlerResponse>,\n ): RouteDef<TInput, TInterceptor, THandlerResponse>\n {\n return {\n method: this._method,\n path: this._path,\n input: this._input,\n interceptor: this._interceptor,\n middlewares: this._middlewares,\n skipMiddlewares: this._skipMiddlewares,\n contract: this._contract,\n handler: fn,\n _input: {} as TInput,\n _interceptor: {} as TInterceptor,\n _response: {} as THandlerResponse,\n };\n }\n}\n\n/**\n * Create a route definition with HTTP method shortcuts\n */\nfunction createMethodRoute(method: HttpMethod): (path: string) => RouteBuilder\n{\n return (path: string) =>\n {\n const builder = new RouteBuilder();\n builder._method = method;\n builder._path = path;\n\n return builder;\n };\n}\n\n/**\n * Route builder entry point\n *\n * @example\n * ```ts\n * // GET request\n * export const getUser = route.get('/users/:id')\n * .input({ params: Type.Object({ id: Type.String() }) })\n * .handler(async (c) => {\n * const { params } = await c.data();\n * return await db.user.findUnique({ where: { id: params.id } });\n * });\n *\n * // POST request\n * export const createUser = route.post('/users')\n * .input({ body: Type.Object({ name: Type.String(), email: Type.String() }) })\n * .handler(async (c) => {\n * const { body } = await c.data();\n * return c.created(await db.user.create({ data: body }));\n * });\n * ```\n */\nexport const route = {\n get: createMethodRoute('GET'),\n post: createMethodRoute('POST'),\n put: createMethodRoute('PUT'),\n patch: createMethodRoute('PATCH'),\n delete: createMethodRoute('DELETE'),\n};\n","/**\n * Router Definition\n *\n * Provides router composition and middleware management\n */\n\nimport type { NamedMiddleware } from './define-middleware';\nimport type { RouteDef } from './route-builder';\n\n/**\n * Router definition - holds all routes\n */\nexport interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>> {\n routes: TRoutes;\n _routes: TRoutes;\n _packageRouters: Router<any>[];\n _globalMiddlewares: NamedMiddleware<string>[];\n\n /** The contract version these routes publish, or null when uncontracted. */\n _contractVersion: string | null;\n\n /**\n * Register package routers (type-hidden)\n *\n * Package routes are:\n * - Recognized by RPC proxy and backend\n * - NOT exposed in client types (use package's own API like authApi, cmsApi)\n *\n * @example\n * ```ts\n * import { authRouter } from '@spfn/auth/server';\n * import { cmsAppRouter } from '@spfn/cms/server';\n *\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter, cmsAppRouter]);\n *\n * // Client usage:\n * // api.getRoot.call({}) - app routes\n * // authApi.login.call({}) - package API\n * ```\n */\n packages(routers: Router<any>[]): Router<TRoutes>;\n\n /**\n * Register global middlewares\n *\n * Applied to all routes unless explicitly skipped via .skip()\n *\n * @example\n * ```ts\n * import { authMiddleware, loggingMiddleware } from './middlewares';\n *\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter])\n * .use([authMiddleware, loggingMiddleware]);\n * ```\n */\n use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>;\n\n /**\n * Declare the contract version these routes publish.\n *\n * A client compiled against this server — a mobile app in a store — is\n * generated from one version of the contract and cannot be updated when the\n * server changes. The server announces this version on every response so\n * that client can tell whether the two ends still agree.\n *\n * This is the version's source. A released snapshot is written to\n * `contracts/released/<version>.json` from what is declared here, so the\n * filename follows the code rather than the code having to be told what the\n * filename said.\n *\n * Only a server with contracted routes needs it. Without it the contract\n * generator still writes `current.json` and still runs the compatibility\n * gate; what it cannot do is cut a release or announce a version.\n *\n * @example\n * ```ts\n * export const appRouter = defineRouter({ getRoot, listItems })\n * .contractVersion('1.2.0')\n * .packages([authRouter]);\n * ```\n */\n contractVersion(version: string): Router<TRoutes>;\n}\n\n/**\n * Create a Router instance with chainable methods\n */\nfunction createRouterInstance<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(\n routes: TRoutes,\n packageRouters: Router<any>[] = [],\n globalMiddlewares: NamedMiddleware<string>[] = [],\n contractVersion: string | null = null,\n): Router<TRoutes>\n{\n return {\n routes,\n _routes: routes,\n _packageRouters: packageRouters,\n _globalMiddlewares: globalMiddlewares,\n _contractVersion: contractVersion,\n\n packages(routers: Router<any>[]): Router<TRoutes>\n {\n const newPackageRouters = [...this._packageRouters, ...routers];\n\n // Also include nested package routers if any\n for (const pkgRouter of routers)\n {\n if (pkgRouter._packageRouters?.length > 0)\n {\n newPackageRouters.push(...pkgRouter._packageRouters);\n }\n }\n\n return createRouterInstance(\n this.routes,\n newPackageRouters,\n this._globalMiddlewares,\n this._contractVersion,\n );\n },\n\n use(middlewares: NamedMiddleware<string>[]): Router<TRoutes>\n {\n return createRouterInstance(\n this.routes,\n this._packageRouters,\n [...this._globalMiddlewares, ...middlewares],\n this._contractVersion,\n );\n },\n\n contractVersion(version: string): Router<TRoutes>\n {\n assertContractVersion(version);\n\n return createRouterInstance(\n this.routes,\n this._packageRouters,\n this._globalMiddlewares,\n version,\n );\n },\n };\n}\n\n/**\n * A version that cannot be ordered cannot gate a release.\n *\n * Checked when it is declared rather than when a snapshot is cut: the failure\n * belongs next to the typo, not in a build step that runs much later.\n */\nfunction assertContractVersion(version: string): void\n{\n if (!/^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)*$/.test(version))\n {\n throw new Error(\n `contractVersion(\"${version}\") is not a version of the form major.minor.patch. `\n + 'The released snapshot is named from this value and releases are compared by it.',\n );\n }\n}\n\n/**\n * Define a router with multiple routes (tRPC-style)\n *\n * Supports chainable API for packages and middlewares:\n *\n * @example\n * ```ts\n * // Basic usage\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * listExamples,\n * });\n *\n * // With package routers (type-hidden)\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter, cmsAppRouter]);\n *\n * // With global middlewares\n * export const appRouter = defineRouter({\n * getRoot,\n * getHealth,\n * })\n * .packages([authRouter])\n * .use([authMiddleware, loggingMiddleware]);\n *\n * export type AppRouter = typeof appRouter;\n * ```\n *\n * Package routes:\n * - Recognized by RPC proxy and backend for routing\n * - NOT included in AppRouter type (use authApi, cmsApi instead)\n * - Prevents confusion between app API and package APIs\n */\nexport function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(\n routes: TRoutes,\n): Router<TRoutes>\n{\n return createRouterInstance(routes);\n}\n","/**\n * Ops Manifest\n *\n * The server's self-description of its ops surface: every command an app\n * exposes under `/_ops`, with each command's input schemas as plain JSON\n * Schema. The ops CLI fetches this from the running server, so command\n * discovery needs neither the app's source nor a generated artifact on the\n * operator's machine.\n *\n * The router is loaded and walked (the contract collector's approach) rather\n * than parsed from source: route schemas built from imported values resolve,\n * and TypeBox schemas serialize to JSON Schema by construction.\n */\n\nimport type { JsonSchema } from '../contract/types';\nimport type { RouteDef } from '../route/route-builder';\nimport type { RouteInput } from '../route/route-input';\nimport type { Router } from '../route/router';\nimport type { HttpMethod } from '../route/types';\n\n/** One invokable ops command, as the CLI sees it. */\nexport interface OpsCommand\n{\n name: string;\n method: HttpMethod;\n path: string;\n\n /** Input sections the route declares, each as JSON Schema. */\n input: {\n params?: JsonSchema;\n query?: JsonSchema;\n body?: JsonSchema;\n };\n}\n\n/** What `GET /_ops/_manifest` answers. */\nexport interface OpsManifest\n{\n manifestVersion: 1;\n commands: OpsCommand[];\n}\n\n/** Thrown when a route cannot be part of an ops surface. */\nexport class OpsRouterError extends Error\n{\n constructor(message: string)\n {\n super(message);\n this.name = 'OpsRouterError';\n }\n}\n\nfunction isRouter(value: unknown): value is Router<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'routes' in value\n && '_routes' in value;\n}\n\nfunction isRouteDef(value: unknown): value is RouteDef<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'handler' in value;\n}\n\n/**\n * Strip TypeBox's symbol-keyed metadata and hand back plain JSON — the same\n * round trip the contract collector uses.\n */\nfunction toJsonSchema(schema: unknown): JsonSchema\n{\n return JSON.parse(JSON.stringify(schema)) as JsonSchema;\n}\n\nconst INPUT_SECTIONS = ['params', 'query', 'body'] as const;\n\nfunction toCommandInput(input: RouteInput | undefined): OpsCommand['input']\n{\n const sections: OpsCommand['input'] = {};\n\n if (!input)\n {\n return sections;\n }\n\n for (const section of INPUT_SECTIONS)\n {\n const schema = input[section];\n if (schema)\n {\n sections[section] = toJsonSchema(schema);\n }\n }\n\n return sections;\n}\n\n/**\n * Walk a routes record (nested routers included) and collect every RouteDef\n * as an ops command. Validation of paths and names happens in\n * `createOpsRouter` before this runs.\n */\nexport function collectOpsCommands(\n routes: Record<string, RouteDef<any> | Router<any>>,\n): OpsCommand[]\n{\n const commands: OpsCommand[] = [];\n visit(routes, commands, new Map<string, string>());\n commands.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n\n return commands;\n}\n\nfunction visit(\n routes: Record<string, RouteDef<any> | Router<any>>,\n commands: OpsCommand[],\n claimed: Map<string, string>,\n): void\n{\n for (const [name, entry] of Object.entries(routes))\n {\n if (isRouter(entry))\n {\n visit(entry.routes, commands, claimed);\n continue;\n }\n\n if (!isRouteDef(entry) || !entry.method || !entry.path)\n {\n continue;\n }\n\n assertUnclaimedName(name, entry.path, claimed);\n\n commands.push({\n name,\n method: entry.method,\n path: entry.path,\n input: toCommandInput(entry.input as RouteInput | undefined),\n });\n }\n}\n\n/**\n * Nested routers flatten into one command list, so two routes keyed alike in\n * different routers would both be announced under the same command name. The\n * CLI resolves a name to the first match, which means an operator asking for\n * one command silently invokes the other — refuse it at definition time.\n */\nfunction assertUnclaimedName(name: string, path: string, claimed: Map<string, string>): void\n{\n const existing = claimed.get(name);\n if (existing !== undefined)\n {\n throw new OpsRouterError(\n `Two ops routes are named \"${name}\" (\"${existing}\" and \"${path}\"). `\n + 'Command names are flattened across nested routers, so each must be unique '\n + 'for the CLI to resolve the one an operator asked for.',\n );\n }\n\n claimed.set(name, path);\n}\n","/**\n * Ops Router\n *\n * The structure half of SPFN's CLI-first operations surface. An app develops\n * its own ops as ordinary routes — domain operations only that app can name —\n * and this factory turns them into a mountable package router that:\n *\n * - requires every route to come from `opsRoute`, which applies the `/_ops`\n * namespace, so the surface is recognizable and an ops route can never\n * shadow an app route;\n * - injects the given auth middleware into every route, the manifest\n * included, so an unauthenticated ops surface cannot be created by\n * accident — there is no opt-out;\n * - serves `GET /_ops/_manifest`, the self-description the `spfn ops` CLI\n * discovers commands from, registered first so no app route takes its URL.\n *\n * What the path looks like after the namespace is the app's business, decided\n * when the ops route is written — this factory does not audit its shape.\n *\n * The auth middleware itself lives with the app's auth stack (`@spfn/auth`\n * ships `opsTokenAuth`); core owns only the structure, so the ops surface has\n * no opinion about how a token is stored or verified.\n *\n * @example\n * ```ts\n * import { createOpsRouter, opsRoute } from '@spfn/core/ops';\n * import { opsTokenAuth, requireOpsScope } from '@spfn/auth/server';\n *\n * export const opsRouter = createOpsRouter({\n * listSignups: opsRoute.get('/signups') // GET /_ops/signups\n * .use([requireOpsScope('waitlist:read')])\n * .handler(async () => signupsRepository.list()),\n * }, { auth: opsTokenAuth });\n *\n * // mounted like any package router:\n * export const appRouter = defineRouter({ ... }).packages([opsRouter]);\n * ```\n */\n\nimport type { NamedMiddleware } from '../route/define-middleware';\nimport { route, type RouteDef } from '../route/route-builder';\nimport { defineRouter, type Router } from '../route/router';\nimport { collectOpsCommands, OpsRouterError, type OpsManifest } from './manifest';\n\n/** Every ops route lives under this prefix. */\nexport const OPS_PATH_PREFIX = '/_ops/';\n\n/** Where the manifest is served. Reserved — an app route cannot claim it. */\nexport const OPS_MANIFEST_PATH = '/_ops/_manifest';\n\n/** Reserved route name for the injected manifest route. */\nconst OPS_MANIFEST_NAME = 'getOpsManifest';\n\nexport interface OpsRouterOptions\n{\n /**\n * The middleware that authenticates every ops request. Required — an ops\n * surface without authentication is refused at definition time, not\n * discovered in production.\n */\n auth: NamedMiddleware<string>;\n}\n\nfunction isRouter(value: unknown): value is Router<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'routes' in value\n && '_routes' in value;\n}\n\nfunction isRouteDef(value: unknown): value is RouteDef<any>\n{\n return value !== null\n && typeof value === 'object'\n && 'handler' in value;\n}\n\nfunction assertOpsRoute(name: string, def: RouteDef<any>): void\n{\n if (!def.method || !def.path)\n {\n throw new OpsRouterError(\n `Ops route \"${name}\" has no method or path. `\n + 'An ops command is invoked on the wire, so both are required.',\n );\n }\n\n if (!def.path.startsWith(OPS_PATH_PREFIX))\n {\n throw new OpsRouterError(\n `Ops route \"${name}\" is at \"${def.path}\", outside \"${OPS_PATH_PREFIX}\". `\n + 'Build ops routes with `opsRoute` rather than `route` — it applies the namespace, '\n + 'so the path a definition carries is only the part the app owns.',\n );\n }\n\n if (def.path === OPS_MANIFEST_PATH)\n {\n throw new OpsRouterError(\n `Ops route \"${name}\" claims \"${OPS_MANIFEST_PATH}\", which is reserved for the manifest. `\n + 'The manifest is registered first, so this route would never answer.',\n );\n }\n}\n\n/**\n * The reserved name is checked for every entry, route and nested router\n * alike, because the merge cannot refuse a duplicate key on its own. The\n * manifest is merged in first and the app's entries spread over it, so an\n * entry under this name would replace the manifest — the ops surface would\n * then announce nothing and the CLI would discover no commands at all.\n */\nfunction assertOpsName(name: string): void\n{\n if (name === OPS_MANIFEST_NAME)\n {\n throw new OpsRouterError(\n `Ops route name \"${OPS_MANIFEST_NAME}\" is reserved for the manifest route.`,\n );\n }\n}\n\n/**\n * Rebuild a nested router with the auth middleware injected into its routes,\n * carrying over what the original declared. A plain `defineRouter` of the\n * secured routes would silently drop the router's own `.use()` middlewares —\n * a `requireOpsScope` guard among them — leaving those routes reachable by\n * any valid ops token.\n *\n * Those middlewares are handed down to the routes rather than left on the\n * rebuilt router. Router-level middlewares are registered ahead of every\n * route-level one, so a guard left in place would run before the auth that\n * was injected per route — reading a request no one had authenticated yet.\n *\n * `.packages()` is refused rather than carried: package routes are registered\n * without passing through this factory, so they would join the ops surface\n * with neither the prefix check nor the auth injection.\n */\nfunction rebuildNestedRouter(\n name: string,\n router: Router<any>,\n auth: NamedMiddleware<string>,\n inherited: ReadonlyArray<NamedMiddleware<string>>,\n): Router<any>\n{\n if (router._packageRouters?.length > 0)\n {\n throw new OpsRouterError(\n `Ops router \"${name}\" mounts package routers with .packages(). `\n + 'Their routes bypass the prefix check and the auth injection, so an ops surface cannot carry them.',\n );\n }\n\n const handedDown = [...inherited, ...(router._globalMiddlewares ?? [])];\n\n let rebuilt = defineRouter(\n secureRoutes(router.routes, auth, handedDown) as Record<string, RouteDef<any>>,\n );\n\n if (router._contractVersion)\n {\n rebuilt = rebuilt.contractVersion(router._contractVersion);\n }\n\n return rebuilt;\n}\n\n/**\n * Validate every route and hand back a copy carrying, in order, the auth\n * middleware, the middlewares its enclosing routers declared with `.use()`,\n * and its own. Route-level injection (rather than router-level `.use`) makes\n * the middleware's `skips` declaration effective, so `opsTokenAuth` can\n * auto-skip a server-level `auth` middleware exactly as `oneTimeTokenAuth`\n * does — and it is what puts auth ahead of every group guard.\n */\nfunction secureRoutes(\n routes: Record<string, RouteDef<any> | Router<any>>,\n auth: NamedMiddleware<string>,\n inherited: ReadonlyArray<NamedMiddleware<string>> = [],\n): Record<string, RouteDef<any> | Router<any>>\n{\n const secured: Record<string, RouteDef<any> | Router<any>> = {};\n\n for (const [name, entry] of Object.entries(routes))\n {\n assertOpsName(name);\n\n if (isRouter(entry))\n {\n secured[name] = rebuildNestedRouter(name, entry, auth, inherited);\n continue;\n }\n\n if (!isRouteDef(entry))\n {\n throw new OpsRouterError(`Ops router entry \"${name}\" is neither a route nor a router.`);\n }\n\n assertOpsRoute(name, entry);\n secured[name] = {\n ...entry,\n middlewares: [auth, ...inherited, ...(entry.middlewares ?? [])],\n };\n }\n\n return secured;\n}\n\n/**\n * Build the app's ops surface from its ops routes.\n *\n * Returns an ordinary `Router` meant to be mounted with `.packages()`, so ops\n * routes stay out of the app's client types exactly like other package\n * routes.\n */\nexport function createOpsRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(\n routes: TRoutes,\n options: OpsRouterOptions,\n): Router<any>\n{\n if (!options?.auth)\n {\n throw new OpsRouterError(\n 'createOpsRouter requires an auth middleware ({ auth: ... }). '\n + 'An ops surface reachable without authentication cannot be created.',\n );\n }\n\n const manifest: OpsManifest = {\n manifestVersion: 1,\n commands: collectOpsCommands(routes),\n };\n\n const secured = secureRoutes(routes, options.auth);\n\n const manifestRoute = route.get(OPS_MANIFEST_PATH)\n .use([options.auth])\n .handler(async () => manifest);\n\n // The manifest goes first, so no route in this object can answer its path.\n // A route pattern that happens to cover `/_ops/_manifest` — `/_ops/:name`,\n // say — is then only a route the app never reaches through that one URL,\n // not a surface-wide outage where the CLI cannot discover any command.\n //\n // The ordering reaches no further than this object. `registerRoutes`\n // registers a router's own routes before its package routers, so a pattern\n // the app declares in its own router still shadows the manifest — as it\n // shadows every other package route, which is a property of where the app\n // put that pattern rather than of this factory.\n return defineRouter({\n [OPS_MANIFEST_NAME]: manifestRoute,\n ...secured,\n } as Record<string, RouteDef<any>>);\n}\n","/**\n * Ops Route Builder\n *\n * `route` with the ops namespace already applied. An ops route lives under\n * `/_ops/` without exception, so the prefix is the helper's business rather\n * than something every definition retypes and the factory then checks:\n *\n * ```ts\n * const countExamples = opsRoute.get('/examples/count') // GET /_ops/examples/count\n * .handler(async () => ({ count: await repo.countAll() }));\n * ```\n *\n * Everything after the prefix belongs to the app. What the paths look like,\n * how they nest, which segments are parameters — those are the app author's\n * decisions, made when the ops route is written.\n *\n * The builder returned is an ordinary `RouteBuilder`, so `.use()`, `.input()`\n * and `.handler()` work exactly as they do elsewhere.\n */\n\nimport { route, type RouteBuilder } from '../route/route-builder';\nimport { OpsRouterError } from './manifest';\n\n/** The ops namespace, without the trailing slash. */\nexport const OPS_PATH_ROOT = '/_ops';\n\nfunction toOpsPath(path: string): string\n{\n if (!path.startsWith('/'))\n {\n throw new OpsRouterError(\n `Ops route path \"${path}\" must start with \"/\". It is appended to \"${OPS_PATH_ROOT}\", `\n + `so \"${path}\" would read as \"${OPS_PATH_ROOT}${path}\".`,\n );\n }\n\n if (path === '/')\n {\n throw new OpsRouterError(\n `Ops route path \"/\" names no command — \"${OPS_PATH_ROOT}\" itself is not a command.`,\n );\n }\n\n return OPS_PATH_ROOT + path;\n}\n\nfunction opsMethod(method: keyof typeof route): (path: string) => RouteBuilder\n{\n return (path: string) => route[method](toOpsPath(path));\n}\n\n/**\n * Ops route builder entry point — `route`, namespaced under `/_ops`.\n *\n * @example\n * ```ts\n * const listRecent = opsRoute.get('/examples')\n * .use([requireOpsScope('example:read')])\n * .input({ query: Type.Object({ limit: Type.Optional(Type.Number()) }) })\n * .handler(async (c) => ({ items: await repo.findAll((await c.data()).query.limit ?? 10, 0) }));\n * ```\n */\nexport const opsRoute = {\n get: opsMethod('get'),\n post: opsMethod('post'),\n put: opsMethod('put'),\n patch: opsMethod('patch'),\n delete: opsMethod('delete'),\n};\n"]}
|
package/dist/route/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as RouteDef, R as Router } from '../router-
|
|
2
|
-
export { M as MergedInput, P as PaginatedResult,
|
|
1
|
+
import { a as RouteDef, R as Router } from '../router-Qbssr11H.js';
|
|
2
|
+
export { M as MergedInput, P as PaginatedResult, c as RouteAuthProfile, d as RouteBuilderContext, e as RouteContract, f as RouteHandlerFn, g as RouteInput, h as defineRouter, r as route } from '../router-Qbssr11H.js';
|
|
3
3
|
import { Hono, MiddlewareHandler } from 'hono';
|
|
4
4
|
import { HttpMethod } from './types.js';
|
|
5
5
|
export { E as ExtractMiddlewareNames, a as NamedMiddleware, N as NamedMiddlewareFactory, d as defineMiddleware, b as defineMiddlewareFactory } from '../define-middleware-DfDP39Nq.js';
|
|
@@ -673,4 +673,4 @@ interface Router<TRoutes extends Record<string, RouteDef<any, any, any> | Router
|
|
|
673
673
|
*/
|
|
674
674
|
declare function defineRouter<TRoutes extends Record<string, RouteDef<any, any, any> | Router<any>>>(routes: TRoutes): Router<TRoutes>;
|
|
675
675
|
|
|
676
|
-
export { type MergedInput as M, type PaginatedResult as P, type Router as R, type RouteDef as a, type RouteAuthProfile as
|
|
676
|
+
export { type MergedInput as M, type PaginatedResult as P, type Router as R, type RouteDef as a, RouteBuilder as b, type RouteAuthProfile as c, type RouteBuilderContext as d, type RouteContract as e, type RouteHandlerFn as f, type RouteInput as g, defineRouter as h, route as r };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spfn/core",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.2",
|
|
4
4
|
"description": "Full-stack TypeScript backend for Next.js: file-based typed routes, Drizzle entities and repositories, PostgreSQL transactions and a generated end-to-end client, in one fixed vertical slice per feature",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|