@spfn/core 0.3.0-beta.19 → 0.3.0-beta.20
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 +61 -0
- package/dist/contract/index.d.ts +4 -4
- package/dist/error-BcdBVfRA.d.ts +150 -0
- package/dist/ops/index.d.ts +31 -71
- package/dist/ops/index.js +333 -182
- package/dist/ops/index.js.map +1 -1
- package/dist/route/index.d.ts +4 -4
- package/dist/route/index.js +16 -0
- package/dist/route/index.js.map +1 -1
- package/dist/{route-builder-2ani2jEI.d.ts → route-builder-BHKgGkwI.d.ts} +1 -1
- package/dist/{router-BqyEARoY.d.ts → router-DaDrprJd.d.ts} +8 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +16 -0
- package/dist/server/index.js.map +1 -1
- package/dist/surface/index.d.ts +109 -0
- package/dist/surface/index.js +908 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/{types-B4DuGCyM.d.ts → types-D52SokSM.d.ts} +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -356,6 +356,7 @@ Each module has its own README with the API detail.
|
|
|
356
356
|
| `@spfn/core/codegen` | The codegen orchestrator and the built-in generators: `@spfn/core:route-map` for the proxy's route map, `@spfn/core:contract` for the client contract. | [src/codegen](./src/codegen/README.md) |
|
|
357
357
|
| `@spfn/core/contract` | Route contracts for clients that ship separately: collect, snapshot, and the build gate that refuses a breaking change. | [src/contract](./src/contract/README.md) |
|
|
358
358
|
| `@spfn/core/ops` | The operations surface `spfn ops` drives: `opsRoute`, `createOpsRouter`, `defineOpsModule`, and the manifest the CLI discovers commands from. Structure only — the router is always authenticated, and token verification lives in `@spfn/auth`. | [How do I operate the app from the terminal?](#how-do-i-operate-the-app-from-the-terminal) |
|
|
359
|
+
| `@spfn/core/surface` | `defineSurface`: a command surface on any namespace — the same authenticated router, manifest and modules `@spfn/core/ops` is on `/_ops`, for a caller other than an operator. | [Can a second surface sit beside `/_ops`?](#can-a-second-surface-sit-beside-_ops) |
|
|
359
360
|
|
|
360
361
|
`db/manager`, `db/schema` and `db/transaction` are **not** package subpaths of their own.
|
|
361
362
|
They are internal modules re-exported by `@spfn/core/db` — import their symbols from
|
|
@@ -703,6 +704,66 @@ CLI keeps the token in the keychain (`spfn ops token store`), and resolution ord
|
|
|
703
704
|
`--token` → `SPFN_OPS_TOKEN` → keychain. The `--app` URL must be https, since every command
|
|
704
705
|
carries a secret; `http` is accepted only against a loopback host.
|
|
705
706
|
|
|
707
|
+
### Can a second surface sit beside `/_ops`?
|
|
708
|
+
|
|
709
|
+
Yes. `/_ops` is one command surface; `defineSurface` from `@spfn/core/surface` builds
|
|
710
|
+
another on a namespace of your choosing, for a caller that is not an operator — a service
|
|
711
|
+
holding a token some issuer signed, say. It is the same structure: a `route` with the
|
|
712
|
+
namespace applied, auth injected into every command, a manifest at `<namespace>/_manifest`
|
|
713
|
+
registered first, and modules mounted only when you name them. `createOpsRouter` is
|
|
714
|
+
`defineSurface({ namespace: '/_ops', ... }).router(...)`.
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
// src/server/app-surface.ts
|
|
718
|
+
import { Type } from '@sinclair/typebox';
|
|
719
|
+
import { defineSurface } from '@spfn/core/surface';
|
|
720
|
+
import { signedTokenAuth } from '@spfn/auth/server';
|
|
721
|
+
|
|
722
|
+
export const app = defineSurface({
|
|
723
|
+
namespace: '/_app', // required
|
|
724
|
+
auth: signedTokenAuth({ // required
|
|
725
|
+
keysUrl: 'https://issuer.example/.well-known/keys',
|
|
726
|
+
iss: 'https://issuer.example',
|
|
727
|
+
aud: 'my-app',
|
|
728
|
+
}),
|
|
729
|
+
manifest: 'public', // 'authenticated' (default) | 'public'
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
export const pageAdd = app.route.post('/pages') // POST /_app/pages
|
|
733
|
+
.input({ body: Type.Object({ title: Type.String() }) })
|
|
734
|
+
.handler(async (c) => pagesRepository.add((await c.data()).body.title));
|
|
735
|
+
|
|
736
|
+
// src/server/router.ts — both surfaces, side by side
|
|
737
|
+
export const appRouter = defineRouter({ ... }).packages([app.router({ pageAdd }), opsRouter]);
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
- **Namespace.** An absolute path of `[A-Za-z0-9_-]` segments with no trailing slash —
|
|
741
|
+
`/_app`, `/machine/v1`. Anything else (`app`, `/_app/`, `/`, a `%`, a `:param`) is refused
|
|
742
|
+
at `defineSurface`.
|
|
743
|
+
- **Manifest.** Served at `<namespace>/_manifest` under the reserved route name
|
|
744
|
+
`get<Namespace>Manifest` — `getAppManifest` for `/_app`, `getOpsManifest` for `/_ops`,
|
|
745
|
+
`getappManifest` for `/app`. The namespace is encoded one-to-one (`_x` → `X`; any other
|
|
746
|
+
`_`, `-`, `/` or uppercase letter is escaped with `_`), so two surfaces never register one
|
|
747
|
+
name. Two surfaces on one namespace are refused by `.packages()`. `manifest: 'public'`
|
|
748
|
+
drops `auth` from that one route and skips a server-level `auth` middleware on it, so a
|
|
749
|
+
caller can discover commands before it holds a token; every command stays behind `auth`
|
|
750
|
+
regardless.
|
|
751
|
+
- **`authorize`.** Optional, `(command) => middleware | undefined`, called once per command
|
|
752
|
+
(module commands included) when the router is built. The middleware runs after `auth` and
|
|
753
|
+
before the command's own: `[auth, authorize?, ...group, ...own]`. Required as soon as a
|
|
754
|
+
module is mounted. A guard named like `auth` or like one of the command's group or own
|
|
755
|
+
middlewares is refused: registration keeps one middleware per name, so the other one would
|
|
756
|
+
never run.
|
|
757
|
+
- **Modules.** A module is validated against the namespace of the surface that mounts it —
|
|
758
|
+
its commands must live under `<namespace>/<id>/`. Write one for a surface with
|
|
759
|
+
`defineSurfaceModule(module, '/_app')`; a module written for `/_ops` mounted on `/_app` is
|
|
760
|
+
refused, and the message names the namespace.
|
|
761
|
+
- **Errors.** Every refusal is a `SurfaceError`. `@spfn/core/ops` exports the same class as
|
|
762
|
+
`OpsRouterError`, so `instanceof` holds under either name.
|
|
763
|
+
|
|
764
|
+
The client for any surface is the CLI's library export,
|
|
765
|
+
[`spfn/surface`](../cli/README.md#can-i-call-a-surface-from-my-own-code).
|
|
766
|
+
|
|
706
767
|
---
|
|
707
768
|
|
|
708
769
|
## How is this different from NestJS or tRPC?
|
package/dist/contract/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { E as EventRouterDef } from '../router-CysBmgv3.js';
|
|
2
|
-
import { R as Router } from '../router-
|
|
3
|
-
import { C as ContractDocument, a as ContractViolation, b as ContractOperation, c as ContractSnapshot } from '../types-
|
|
4
|
-
export { d as CompatibilityPolicy, e as ContractEvent, f as ContractEvents, g as ContractRequest, h as ContractViolationKind, J as JsonSchema } from '../types-
|
|
2
|
+
import { R as Router } from '../router-DaDrprJd.js';
|
|
3
|
+
import { C as ContractDocument, a as ContractViolation, b as ContractOperation, c as ContractSnapshot } from '../types-D52SokSM.js';
|
|
4
|
+
export { d as CompatibilityPolicy, e as ContractEvent, f as ContractEvents, g as ContractRequest, h as ContractViolationKind, J as JsonSchema } from '../types-D52SokSM.js';
|
|
5
5
|
import '@sinclair/typebox';
|
|
6
6
|
import '../define-middleware-CVKgqo8S.js';
|
|
7
7
|
import 'hono';
|
|
8
|
-
import '../route-builder-
|
|
8
|
+
import '../route-builder-BHKgGkwI.js';
|
|
9
9
|
import 'hono/utils/http-status';
|
|
10
10
|
import '../route/types.js';
|
|
11
11
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { R as RouteDef, f as RouteBuilder } from './route-builder-BHKgGkwI.js';
|
|
2
|
+
import { J as JsonSchema } from './types-D52SokSM.js';
|
|
3
|
+
import { R as Router } from './router-DaDrprJd.js';
|
|
4
|
+
import { HttpMethod } from './route/types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* How refusals name the surface they come from.
|
|
8
|
+
*
|
|
9
|
+
* A command surface is generic, but the ops surface predates it and its
|
|
10
|
+
* refusals already say "ops route" and point at `opsRoute`. The ops layer
|
|
11
|
+
* passes its own wording so an operator reading an ops refusal sees the same
|
|
12
|
+
* text as before; every other surface reads the neutral one.
|
|
13
|
+
*/
|
|
14
|
+
interface SurfaceWording {
|
|
15
|
+
/** What a route on this surface is called: "ops route", "surface route". */
|
|
16
|
+
noun: string;
|
|
17
|
+
/** The builder that applies the namespace, as named in a refusal. */
|
|
18
|
+
builder: string;
|
|
19
|
+
/** The function that builds the surface, as named in a refusal. */
|
|
20
|
+
factory: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Capability module contract
|
|
25
|
+
*
|
|
26
|
+
* A package describes commands here, but an adopter application decides
|
|
27
|
+
* whether to mount the resulting module, and on which surface. Validation
|
|
28
|
+
* keeps an installed package from becoming a command surface merely by
|
|
29
|
+
* existing, and gives the surface enough metadata to enforce scopes and
|
|
30
|
+
* describe the module to a client from one declaration.
|
|
31
|
+
*
|
|
32
|
+
* A module's paths are written for one namespace — `/_ops/<id>/...` for an
|
|
33
|
+
* ops module — so it is validated against the namespace of the surface that
|
|
34
|
+
* mounts it. A module written for `/_ops` mounted on `/_app` is refused rather
|
|
35
|
+
* than registered at paths outside the surface that is supposed to guard them.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
type SurfaceEffect = 'read' | 'write' | 'destructive';
|
|
39
|
+
interface SurfaceModuleCommand<TRoute extends RouteDef<any> = RouteDef<any>> {
|
|
40
|
+
summary: string;
|
|
41
|
+
effect: SurfaceEffect;
|
|
42
|
+
scopes: readonly string[];
|
|
43
|
+
route: TRoute;
|
|
44
|
+
}
|
|
45
|
+
interface SurfaceModule<TCommands extends Record<string, SurfaceModuleCommand<any>> = Record<string, SurfaceModuleCommand<any>>> {
|
|
46
|
+
id: string;
|
|
47
|
+
source: string;
|
|
48
|
+
contractVersion: string;
|
|
49
|
+
summary: string;
|
|
50
|
+
commands: TCommands;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Validate and return a capability module written for a surface on
|
|
54
|
+
* `namespace` — every command under `<namespace>/<id>/`. The surface checks
|
|
55
|
+
* it again against its own namespace when it mounts it, so a module defined
|
|
56
|
+
* for one namespace and mounted on another is still refused.
|
|
57
|
+
*/
|
|
58
|
+
declare function defineSurfaceModule<TCommands extends Record<string, SurfaceModuleCommand<any>>>(module: SurfaceModule<TCommands>, namespace: string): SurfaceModule<TCommands>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Surface namespace and route builder
|
|
62
|
+
*
|
|
63
|
+
* A namespace is the path every command on a surface lives under — `/_ops`
|
|
64
|
+
* for operators, `/_app` or anything else an app chooses for another caller.
|
|
65
|
+
* It is refused at definition time unless it is a plain absolute path of
|
|
66
|
+
* URL-safe segments: no trailing slash, no dots, no percent signs, no
|
|
67
|
+
* parameters. Every check downstream — the prefix a route must carry, the
|
|
68
|
+
* module paths, the manifest URL, the CLI's own path checks — is a string
|
|
69
|
+
* comparison against it, and only a literal path keeps those comparisons
|
|
70
|
+
* honest.
|
|
71
|
+
*
|
|
72
|
+
* The route builder is `route` with the namespace applied, so a definition
|
|
73
|
+
* carries only the part of the path the app owns:
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* const pageAdd = app.route.post('/pages') // POST /_app/pages
|
|
77
|
+
* .handler(async () => ({ ok: true }));
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
|
|
81
|
+
/** `route`, with a surface's namespace applied. */
|
|
82
|
+
interface SurfaceRouteBuilder {
|
|
83
|
+
get: (path: string) => RouteBuilder;
|
|
84
|
+
post: (path: string) => RouteBuilder;
|
|
85
|
+
put: (path: string) => RouteBuilder;
|
|
86
|
+
patch: (path: string) => RouteBuilder;
|
|
87
|
+
delete: (path: string) => RouteBuilder;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Surface Manifest
|
|
92
|
+
*
|
|
93
|
+
* The server's self-description of a command surface: every command an app
|
|
94
|
+
* exposes under the surface's namespace, with each command's input schemas as
|
|
95
|
+
* plain JSON Schema. A client fetches this from the running server, so command
|
|
96
|
+
* discovery needs neither the app's source nor a generated artifact on the
|
|
97
|
+
* caller's machine.
|
|
98
|
+
*
|
|
99
|
+
* The router is loaded and walked (the contract collector's approach) rather
|
|
100
|
+
* than parsed from source: route schemas built from imported values resolve,
|
|
101
|
+
* and TypeBox schemas serialize to JSON Schema by construction.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
/** One explicitly mounted capability module, as a client sees it. */
|
|
105
|
+
interface SurfaceModuleDescriptor {
|
|
106
|
+
id: string;
|
|
107
|
+
source: string;
|
|
108
|
+
contractVersion: string;
|
|
109
|
+
summary: string;
|
|
110
|
+
}
|
|
111
|
+
/** One invokable command, as a client sees it. */
|
|
112
|
+
interface SurfaceCommand {
|
|
113
|
+
name: string;
|
|
114
|
+
method: HttpMethod;
|
|
115
|
+
path: string;
|
|
116
|
+
/** Present for commands contributed by an explicitly mounted module. */
|
|
117
|
+
module?: string;
|
|
118
|
+
summary?: string;
|
|
119
|
+
effect?: SurfaceEffect;
|
|
120
|
+
scopes?: string[];
|
|
121
|
+
/** Input sections the route declares, each as JSON Schema. */
|
|
122
|
+
input: {
|
|
123
|
+
params?: JsonSchema;
|
|
124
|
+
query?: JsonSchema;
|
|
125
|
+
body?: JsonSchema;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** What `GET <namespace>/_manifest` answers. */
|
|
129
|
+
interface SurfaceManifest {
|
|
130
|
+
manifestVersion: 1;
|
|
131
|
+
/** Additive module metadata. Omitted for an app-only v1 surface. */
|
|
132
|
+
modules?: SurfaceModuleDescriptor[];
|
|
133
|
+
commands: SurfaceCommand[];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Walk a routes record (nested routers included) and collect every RouteDef
|
|
137
|
+
* as a command. Validation of paths happens in the surface's `router()`, so
|
|
138
|
+
* nothing here is checked but the uniqueness of names.
|
|
139
|
+
*/
|
|
140
|
+
declare function collectSurfaceCommands(routes: Record<string, RouteDef<any> | Router<any>>, wording?: SurfaceWording): SurfaceCommand[];
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Thrown when a namespace, route, or module cannot be part of a command
|
|
144
|
+
* surface. `@spfn/core/ops` exports the same class as `OpsRouterError`.
|
|
145
|
+
*/
|
|
146
|
+
declare class SurfaceError extends Error {
|
|
147
|
+
constructor(message: string);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { type SurfaceEffect as S, type SurfaceModuleCommand as a, type SurfaceModule as b, type SurfaceRouteBuilder as c, type SurfaceCommand as d, type SurfaceManifest as e, type SurfaceModuleDescriptor as f, SurfaceError as g, collectSurfaceCommands as h, defineSurfaceModule as i };
|
package/dist/ops/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { MiddlewareHandler } from 'hono';
|
|
2
2
|
import { a as NamedMiddleware } from '../define-middleware-CVKgqo8S.js';
|
|
3
|
-
import { R as RouteDef
|
|
4
|
-
import { R as Router } from '../router-
|
|
5
|
-
import {
|
|
6
|
-
|
|
3
|
+
import { R as RouteDef } from '../route-builder-BHKgGkwI.js';
|
|
4
|
+
import { R as Router } from '../router-DaDrprJd.js';
|
|
5
|
+
import { S as SurfaceEffect, a as SurfaceModuleCommand, b as SurfaceModule, c as SurfaceRouteBuilder, d as SurfaceCommand, e as SurfaceManifest, f as SurfaceModuleDescriptor } from '../error-BcdBVfRA.js';
|
|
6
|
+
export { g as OpsRouterError } from '../error-BcdBVfRA.js';
|
|
7
7
|
import '@sinclair/typebox';
|
|
8
8
|
import 'hono/utils/http-status';
|
|
9
|
+
import '../route/types.js';
|
|
10
|
+
import '../types-D52SokSM.js';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Capability ops module contract
|
|
@@ -15,24 +17,17 @@ import 'hono/utils/http-status';
|
|
|
15
17
|
* installed package from becoming an ops surface merely by existing, and
|
|
16
18
|
* gives `createOpsRouter` enough metadata to enforce scopes and describe the
|
|
17
19
|
* module to the CLI from one declaration.
|
|
20
|
+
*
|
|
21
|
+
* The contract is the command surface's (`@spfn/core/surface`);
|
|
22
|
+
* `defineOpsModule` is its validator fixed to the `/_ops` namespace.
|
|
18
23
|
*/
|
|
19
24
|
|
|
20
|
-
type OpsEffect =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
effect: OpsEffect;
|
|
24
|
-
scopes: readonly string[];
|
|
25
|
-
route: TRoute;
|
|
26
|
-
}
|
|
27
|
-
interface OpsModule<TCommands extends Record<string, OpsModuleCommand<any>> = Record<string, OpsModuleCommand<any>>> {
|
|
28
|
-
id: string;
|
|
29
|
-
source: string;
|
|
30
|
-
contractVersion: string;
|
|
31
|
-
summary: string;
|
|
32
|
-
commands: TCommands;
|
|
33
|
-
}
|
|
25
|
+
type OpsEffect = SurfaceEffect;
|
|
26
|
+
type OpsModuleCommand<TRoute extends RouteDef<any> = RouteDef<any>> = SurfaceModuleCommand<TRoute>;
|
|
27
|
+
type OpsModule<TCommands extends Record<string, OpsModuleCommand<any>> = Record<string, OpsModuleCommand<any>>> = SurfaceModule<TCommands>;
|
|
34
28
|
/**
|
|
35
|
-
* Validate and return a capability ops module
|
|
29
|
+
* Validate and return a capability ops module — every command under
|
|
30
|
+
* `/_ops/<id>/`.
|
|
36
31
|
*
|
|
37
32
|
* This function intentionally does not register anything. Only an adopter's
|
|
38
33
|
* explicit `createOpsRouter({ modules: [...] })` composition exposes it.
|
|
@@ -62,6 +57,11 @@ declare function defineOpsModule<TCommands extends Record<string, OpsModuleComma
|
|
|
62
57
|
* ships `opsTokenAuth`); core owns only the structure, so the ops surface has
|
|
63
58
|
* no opinion about how a token is stored or verified.
|
|
64
59
|
*
|
|
60
|
+
* It is the command surface (`@spfn/core/surface`) fixed to `/_ops`, with the
|
|
61
|
+
* scope-factory `authorize` this factory has always taken adapted to the
|
|
62
|
+
* surface's per-command one: applied to module commands only, so every
|
|
63
|
+
* middleware chain is what it was before surfaces existed.
|
|
64
|
+
*
|
|
65
65
|
* @example
|
|
66
66
|
* ```ts
|
|
67
67
|
* import { createOpsRouter, opsRoute } from '@spfn/core/ops';
|
|
@@ -124,9 +124,9 @@ declare function createOpsRouter<TRoutes extends Record<string, RouteDef<any, an
|
|
|
124
124
|
* decisions, made when the ops route is written.
|
|
125
125
|
*
|
|
126
126
|
* The builder returned is an ordinary `RouteBuilder`, so `.use()`, `.input()`
|
|
127
|
-
* and `.handler()` work exactly as they do elsewhere.
|
|
127
|
+
* and `.handler()` work exactly as they do elsewhere. It is the `/_ops`
|
|
128
|
+
* surface's `route` (`@spfn/core/surface`).
|
|
128
129
|
*/
|
|
129
|
-
|
|
130
130
|
/** The ops namespace, without the trailing slash. */
|
|
131
131
|
declare const OPS_PATH_ROOT = "/_ops";
|
|
132
132
|
/**
|
|
@@ -140,64 +140,24 @@ declare const OPS_PATH_ROOT = "/_ops";
|
|
|
140
140
|
* .handler(async (c) => ({ items: await repo.findAll((await c.data()).query.limit ?? 10, 0) }));
|
|
141
141
|
* ```
|
|
142
142
|
*/
|
|
143
|
-
declare const opsRoute:
|
|
144
|
-
get: (path: string) => RouteBuilder;
|
|
145
|
-
post: (path: string) => RouteBuilder;
|
|
146
|
-
put: (path: string) => RouteBuilder;
|
|
147
|
-
patch: (path: string) => RouteBuilder;
|
|
148
|
-
delete: (path: string) => RouteBuilder;
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
/** Thrown when a route or module cannot be part of an ops surface. */
|
|
152
|
-
declare class OpsRouterError extends Error {
|
|
153
|
-
constructor(message: string);
|
|
154
|
-
}
|
|
143
|
+
declare const opsRoute: SurfaceRouteBuilder;
|
|
155
144
|
|
|
156
145
|
/**
|
|
157
146
|
* Ops Manifest
|
|
158
147
|
*
|
|
159
|
-
* The
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* The router is loaded and walked (the contract collector's approach) rather
|
|
166
|
-
* than parsed from source: route schemas built from imported values resolve,
|
|
167
|
-
* and TypeBox schemas serialize to JSON Schema by construction.
|
|
148
|
+
* The ops surface's self-description: every command an app exposes under
|
|
149
|
+
* `/_ops`, with each command's input schemas as plain JSON Schema. The
|
|
150
|
+
* `spfn ops` CLI fetches it from the running server. The collector and the
|
|
151
|
+
* shapes are the command surface's (`@spfn/core/surface`); these are their
|
|
152
|
+
* ops names, kept so existing imports read as they always did.
|
|
168
153
|
*/
|
|
169
154
|
|
|
170
155
|
/** One explicitly mounted capability module, as the CLI sees it. */
|
|
171
|
-
|
|
172
|
-
id: string;
|
|
173
|
-
source: string;
|
|
174
|
-
contractVersion: string;
|
|
175
|
-
summary: string;
|
|
176
|
-
}
|
|
156
|
+
type OpsModuleDescriptor = SurfaceModuleDescriptor;
|
|
177
157
|
/** One invokable ops command, as the CLI sees it. */
|
|
178
|
-
|
|
179
|
-
name: string;
|
|
180
|
-
method: HttpMethod;
|
|
181
|
-
path: string;
|
|
182
|
-
/** Present for commands contributed by an explicitly mounted ops module. */
|
|
183
|
-
module?: string;
|
|
184
|
-
summary?: string;
|
|
185
|
-
effect?: OpsEffect;
|
|
186
|
-
scopes?: string[];
|
|
187
|
-
/** Input sections the route declares, each as JSON Schema. */
|
|
188
|
-
input: {
|
|
189
|
-
params?: JsonSchema;
|
|
190
|
-
query?: JsonSchema;
|
|
191
|
-
body?: JsonSchema;
|
|
192
|
-
};
|
|
193
|
-
}
|
|
158
|
+
type OpsCommand = SurfaceCommand;
|
|
194
159
|
/** What `GET /_ops/_manifest` answers. */
|
|
195
|
-
|
|
196
|
-
manifestVersion: 1;
|
|
197
|
-
/** Additive module metadata. Omitted for an app-only v1 surface. */
|
|
198
|
-
modules?: OpsModuleDescriptor[];
|
|
199
|
-
commands: OpsCommand[];
|
|
200
|
-
}
|
|
160
|
+
type OpsManifest = SurfaceManifest;
|
|
201
161
|
/**
|
|
202
162
|
* Walk a routes record (nested routers included) and collect every RouteDef
|
|
203
163
|
* as an ops command. Validation of paths and names happens in
|
|
@@ -205,4 +165,4 @@ interface OpsManifest {
|
|
|
205
165
|
*/
|
|
206
166
|
declare function collectOpsCommands(routes: Record<string, RouteDef<any> | Router<any>>): OpsCommand[];
|
|
207
167
|
|
|
208
|
-
export { OPS_MANIFEST_PATH, OPS_PATH_PREFIX, OPS_PATH_ROOT, type OpsCommand, type OpsEffect, type OpsManifest, type OpsModule, type OpsModuleCommand, type OpsModuleDescriptor,
|
|
168
|
+
export { OPS_MANIFEST_PATH, OPS_PATH_PREFIX, OPS_PATH_ROOT, type OpsCommand, type OpsEffect, type OpsManifest, type OpsModule, type OpsModuleCommand, type OpsModuleDescriptor, type OpsRouterOptions, collectOpsCommands, createOpsRouter, defineOpsModule, opsRoute };
|