@spfn/core 0.2.0-beta.9 → 0.3.0-beta.10
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/LICENSE +1 -1
- package/README.md +693 -279
- package/dist/app-config/index.d.ts +96 -0
- package/dist/app-config/index.js +58 -0
- package/dist/app-config/index.js.map +1 -0
- package/dist/authz/index.d.ts +34 -0
- package/dist/authz/index.js +430 -0
- package/dist/authz/index.js.map +1 -0
- package/dist/boss-BiDCbbAQ.d.ts +337 -0
- package/dist/cache/index.js +42 -30
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +121 -13
- package/dist/codegen/index.js +212 -15
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +576 -30
- package/dist/config/index.js +136 -17
- package/dist/config/index.js.map +1 -1
- package/dist/contract/index.d.ts +221 -0
- package/dist/contract/index.js +558 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/db/index.d.ts +704 -94
- package/dist/db/index.js +1105 -260
- package/dist/db/index.js.map +1 -1
- package/dist/define-middleware-CVKgqo8S.d.ts +167 -0
- package/dist/env/index.d.ts +26 -2
- package/dist/env/index.js +15 -5
- package/dist/env/index.js.map +1 -1
- package/dist/env/loader.d.ts +26 -19
- package/dist/env/loader.js +55 -25
- package/dist/env/loader.js.map +1 -1
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.js +40 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +33 -3
- package/dist/event/index.js +24 -3
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +55 -3
- package/dist/event/sse/client.js +164 -45
- package/dist/event/sse/client.js.map +1 -1
- package/dist/event/sse/index.d.ts +12 -5
- package/dist/event/sse/index.js +280 -32
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +59 -0
- package/dist/event/ws/client.js +273 -0
- package/dist/event/ws/client.js.map +1 -0
- package/dist/event/ws/index.d.ts +94 -0
- package/dist/event/ws/index.js +272 -0
- package/dist/event/ws/index.js.map +1 -0
- package/dist/job/index.d.ts +48 -3
- package/dist/job/index.js +265 -74
- package/dist/job/index.js.map +1 -1
- package/dist/logger/index.d.ts +5 -0
- package/dist/logger/index.js +14 -12
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +352 -10
- package/dist/middleware/index.js +1079 -13
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +39 -3
- package/dist/nextjs/index.js +82 -29
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/server.d.ts +69 -52
- package/dist/nextjs/server.js +140 -60
- package/dist/nextjs/server.js.map +1 -1
- package/dist/ops/index.d.ts +208 -0
- package/dist/ops/index.js +800 -0
- package/dist/ops/index.js.map +1 -0
- package/dist/route/index.d.ts +10 -694
- package/dist/route/index.js +112 -23
- package/dist/route/index.js.map +1 -1
- package/dist/route-builder-2ani2jEI.d.ts +553 -0
- package/dist/router-DJdpwuB6.d.ts +127 -0
- package/dist/security/index.d.ts +83 -0
- package/dist/security/index.js +173 -0
- package/dist/security/index.js.map +1 -0
- package/dist/server/index.d.ts +619 -31
- package/dist/server/index.js +2583 -360
- package/dist/server/index.js.map +1 -1
- package/dist/token-manager-BT5EnUAR.d.ts +278 -0
- package/dist/types-2AbaW4Ie.d.ts +205 -0
- package/dist/{types-BOPTApC2.d.ts → types-9oszaJqp.d.ts} +7 -2
- package/dist/types-ClQVomgV.d.ts +115 -0
- package/dist/types-ZQODsBft.d.ts +282 -0
- package/docs/file-upload.md +195 -333
- package/package.json +250 -208
- package/src/cache/README.md +330 -0
- package/src/codegen/README.md +516 -0
- package/src/config/README.md +326 -0
- package/src/contract/README.md +326 -0
- package/src/db/README.md +589 -0
- package/src/db/manager/README.md +500 -0
- package/src/db/schema/README.md +354 -0
- package/src/db/transaction/README.md +822 -0
- package/src/env/README.md +651 -0
- package/src/errors/README.md +429 -0
- package/src/event/README.md +741 -0
- package/src/job/README.md +613 -0
- package/src/logger/README.md +321 -0
- package/src/middleware/README.md +634 -0
- package/src/nextjs/README.md +608 -0
- package/src/route/README.md +738 -0
- package/src/security/README.md +100 -0
- package/src/server/README.md +704 -0
- package/dist/boss-DI1r4kTS.d.ts +0 -244
- package/dist/router-Di7ENoah.d.ts +0 -151
- package/dist/types-B-e_f2dQ.d.ts +0 -121
- package/docs/cache.md +0 -133
- package/docs/codegen.md +0 -74
- package/docs/database.md +0 -346
- package/docs/entity.md +0 -539
- package/docs/env.md +0 -477
- package/docs/errors.md +0 -319
- package/docs/event.md +0 -116
- package/docs/job.md +0 -131
- package/docs/logger.md +0 -108
- package/docs/middleware.md +0 -337
- package/docs/nextjs.md +0 -241
- package/docs/repository.md +0 -496
- package/docs/route.md +0 -497
- package/docs/server.md +0 -307
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
// src/route/route-builder.ts
|
|
2
|
+
var RouteBuilder = class _RouteBuilder {
|
|
3
|
+
_method;
|
|
4
|
+
_path;
|
|
5
|
+
_input;
|
|
6
|
+
_interceptor;
|
|
7
|
+
_middlewares;
|
|
8
|
+
_skipMiddlewares;
|
|
9
|
+
_contract;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new RouteBuilder with copied properties and optional overrides
|
|
12
|
+
*/
|
|
13
|
+
clone(overrides) {
|
|
14
|
+
const builder = new _RouteBuilder();
|
|
15
|
+
builder._method = this._method;
|
|
16
|
+
builder._path = this._path;
|
|
17
|
+
builder._input = overrides?.input ?? this._input;
|
|
18
|
+
builder._interceptor = overrides?.interceptor ?? this._interceptor;
|
|
19
|
+
builder._middlewares = overrides?.middlewares ?? this._middlewares;
|
|
20
|
+
builder._skipMiddlewares = overrides?.skipMiddlewares ?? this._skipMiddlewares;
|
|
21
|
+
builder._contract = overrides?.contract ?? this._contract;
|
|
22
|
+
return builder;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Define input schemas
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* route.get('/users/:id')
|
|
30
|
+
* .input({
|
|
31
|
+
* params: Type.Object({ id: Type.String() }),
|
|
32
|
+
* query: Type.Object({ page: Type.Number() }),
|
|
33
|
+
* headers: Type.Object({ authorization: Type.String() })
|
|
34
|
+
* })
|
|
35
|
+
* .handler(async (c) => {
|
|
36
|
+
* const { params, query, headers } = await c.data();
|
|
37
|
+
* // params = { id: string }
|
|
38
|
+
* // query = { page: number }
|
|
39
|
+
* // headers = { authorization: string }
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
input(input) {
|
|
44
|
+
return this.clone({ input });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Define fields injected by interceptors
|
|
48
|
+
*
|
|
49
|
+
* These fields are:
|
|
50
|
+
* - Available in the handler (merged with input)
|
|
51
|
+
* - Excluded from client types (codegen uses only input)
|
|
52
|
+
* - Not validated by route input schema (injected by middleware)
|
|
53
|
+
*
|
|
54
|
+
* Use this when middleware/interceptors add fields to the request
|
|
55
|
+
* before it reaches the handler.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* // Auth interceptor injects crypto key fields
|
|
60
|
+
* route.post('/_auth/login')
|
|
61
|
+
* .input({
|
|
62
|
+
* body: Type.Object({
|
|
63
|
+
* email: Type.String(),
|
|
64
|
+
* password: Type.String()
|
|
65
|
+
* })
|
|
66
|
+
* })
|
|
67
|
+
* .interceptor({
|
|
68
|
+
* body: Type.Object({
|
|
69
|
+
* publicKey: Type.String(),
|
|
70
|
+
* keyId: Type.String(),
|
|
71
|
+
* fingerprint: Type.String()
|
|
72
|
+
* })
|
|
73
|
+
* })
|
|
74
|
+
* .handler(async (c) => {
|
|
75
|
+
* const { body } = await c.data();
|
|
76
|
+
* // body type: { email, password, publicKey, keyId, fingerprint }
|
|
77
|
+
* // Client only sees: { email, password }
|
|
78
|
+
* return loginService(body);
|
|
79
|
+
* });
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
interceptor(interceptor) {
|
|
83
|
+
return this.clone({ interceptor });
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Add middlewares to the route
|
|
87
|
+
*
|
|
88
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
89
|
+
* Named middlewares that are already registered globally will be automatically
|
|
90
|
+
* deduplicated to prevent double execution.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
95
|
+
*
|
|
96
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
97
|
+
* route.get('/users')
|
|
98
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
99
|
+
*
|
|
100
|
+
* // With regular middleware handlers
|
|
101
|
+
* route.get('/users')
|
|
102
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
middleware(middlewares) {
|
|
106
|
+
return this.clone({ middlewares });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Add middlewares to the route (alias for `.middleware()`)
|
|
110
|
+
*
|
|
111
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
112
|
+
* Named middlewares that are already registered globally will be automatically
|
|
113
|
+
* deduplicated to prevent double execution.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
118
|
+
*
|
|
119
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
120
|
+
* route.get('/users')
|
|
121
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
122
|
+
*
|
|
123
|
+
* // With regular middleware handlers
|
|
124
|
+
* route.get('/users')
|
|
125
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
use(middlewares) {
|
|
129
|
+
return this.middleware(middlewares);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Skip server-level named middlewares
|
|
133
|
+
*
|
|
134
|
+
* Useful for public endpoints that should bypass auth or rate limiting
|
|
135
|
+
*
|
|
136
|
+
* @param middlewareNames - Array of middleware names to skip, or '*' to skip all
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* // Skip specific middlewares
|
|
141
|
+
* route.get('/status')
|
|
142
|
+
* .skip(['auth', 'rateLimit'])
|
|
143
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
144
|
+
*
|
|
145
|
+
* // Skip only auth (still apply rate limiting)
|
|
146
|
+
* route.get('/public-data')
|
|
147
|
+
* .skip(['auth'])
|
|
148
|
+
* .handler(async (c) => { ... });
|
|
149
|
+
*
|
|
150
|
+
* // Skip all middlewares
|
|
151
|
+
* route.get('/public-health')
|
|
152
|
+
* .skip('*')
|
|
153
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
skip(middlewareNames) {
|
|
157
|
+
return this.clone({ skipMiddlewares: middlewareNames });
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Publish this route as a versioned contract operation
|
|
161
|
+
*
|
|
162
|
+
* Marks the route as a promise to clients that are compiled and deployed
|
|
163
|
+
* separately from the server — a mobile app, an external API consumer.
|
|
164
|
+
* The `@spfn/core:contract` generator writes every contracted route into
|
|
165
|
+
* `contracts/current.json`, and the build refuses a change that would break
|
|
166
|
+
* an already-released client.
|
|
167
|
+
*
|
|
168
|
+
* Routes without `.contract()` are unaffected: they simply do not appear in
|
|
169
|
+
* the contract. A web client needs nothing here — it derives its types from
|
|
170
|
+
* the router in the same build.
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* export const getUser = route.get('/users/:id')
|
|
175
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
176
|
+
* .contract({
|
|
177
|
+
* since: '1.2.0',
|
|
178
|
+
* auth: 'clientProofV1',
|
|
179
|
+
* requiresSession: true,
|
|
180
|
+
* response: Type.Object({
|
|
181
|
+
* id: Type.String(),
|
|
182
|
+
* name: Type.String(),
|
|
183
|
+
* email: Type.Optional(Type.String()),
|
|
184
|
+
* }),
|
|
185
|
+
* })
|
|
186
|
+
* .handler(async (c) => { ... });
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
contract(contract) {
|
|
190
|
+
return this.clone({ contract });
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Define handler function
|
|
194
|
+
*
|
|
195
|
+
* Response type is automatically inferred from the return value.
|
|
196
|
+
* Use helper methods like `c.created()`, `c.paginated()` for proper type inference.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* // Direct return - type inferred from data
|
|
201
|
+
* route.get('/users/:id')
|
|
202
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
203
|
+
* .handler(async (c) => {
|
|
204
|
+
* const { params } = await c.data();
|
|
205
|
+
* return await getUser(params.id); // Type: User
|
|
206
|
+
* })
|
|
207
|
+
*
|
|
208
|
+
* // Using c.created() - returns data with 201 status, type preserved
|
|
209
|
+
* route.post('/users')
|
|
210
|
+
* .input({ body: Type.Object({ name: Type.String() }) })
|
|
211
|
+
* .handler(async (c) => {
|
|
212
|
+
* const { body } = await c.data();
|
|
213
|
+
* return c.created(await createUser(body)); // Type: User
|
|
214
|
+
* })
|
|
215
|
+
*
|
|
216
|
+
* // Using c.paginated() - returns PaginatedResult<T>
|
|
217
|
+
* route.get('/users')
|
|
218
|
+
* .handler(async (c) => {
|
|
219
|
+
* const users = await getUsers();
|
|
220
|
+
* return c.paginated(users, 1, 20, 100); // Type: PaginatedResult<User>
|
|
221
|
+
* })
|
|
222
|
+
*
|
|
223
|
+
* // Using c.noContent() - returns void
|
|
224
|
+
* route.delete('/users/:id')
|
|
225
|
+
* .handler(async (c) => {
|
|
226
|
+
* await deleteUser(params.id);
|
|
227
|
+
* return c.noContent(); // Type: void
|
|
228
|
+
* })
|
|
229
|
+
*
|
|
230
|
+
* // Using c.json() - returns Response (type inference lost)
|
|
231
|
+
* // Use only when you need custom status codes not covered by helpers
|
|
232
|
+
* route.get('/custom')
|
|
233
|
+
* .handler(async (c) => {
|
|
234
|
+
* return c.json({ data }, 418); // Type: Response
|
|
235
|
+
* })
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
handler(fn) {
|
|
239
|
+
return {
|
|
240
|
+
method: this._method,
|
|
241
|
+
path: this._path,
|
|
242
|
+
input: this._input,
|
|
243
|
+
interceptor: this._interceptor,
|
|
244
|
+
middlewares: this._middlewares,
|
|
245
|
+
skipMiddlewares: this._skipMiddlewares,
|
|
246
|
+
contract: this._contract,
|
|
247
|
+
handler: fn,
|
|
248
|
+
_input: {},
|
|
249
|
+
_interceptor: {},
|
|
250
|
+
_response: {}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
function createMethodRoute(method) {
|
|
255
|
+
return (path) => {
|
|
256
|
+
const builder = new RouteBuilder();
|
|
257
|
+
builder._method = method;
|
|
258
|
+
builder._path = path;
|
|
259
|
+
return builder;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
var route = {
|
|
263
|
+
get: createMethodRoute("GET"),
|
|
264
|
+
post: createMethodRoute("POST"),
|
|
265
|
+
put: createMethodRoute("PUT"),
|
|
266
|
+
patch: createMethodRoute("PATCH"),
|
|
267
|
+
delete: createMethodRoute("DELETE")
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/route/router.ts
|
|
271
|
+
function createRouterInstance(routes, packageRouters = [], globalMiddlewares = [], contractVersion = null) {
|
|
272
|
+
return {
|
|
273
|
+
routes,
|
|
274
|
+
_routes: routes,
|
|
275
|
+
_packageRouters: packageRouters,
|
|
276
|
+
_globalMiddlewares: globalMiddlewares,
|
|
277
|
+
_contractVersion: contractVersion,
|
|
278
|
+
packages(routers) {
|
|
279
|
+
const newPackageRouters = [...this._packageRouters, ...routers];
|
|
280
|
+
for (const pkgRouter of routers) {
|
|
281
|
+
if (pkgRouter._packageRouters?.length > 0) {
|
|
282
|
+
newPackageRouters.push(...pkgRouter._packageRouters);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return createRouterInstance(
|
|
286
|
+
this.routes,
|
|
287
|
+
newPackageRouters,
|
|
288
|
+
this._globalMiddlewares,
|
|
289
|
+
this._contractVersion
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
use(middlewares) {
|
|
293
|
+
return createRouterInstance(
|
|
294
|
+
this.routes,
|
|
295
|
+
this._packageRouters,
|
|
296
|
+
[...this._globalMiddlewares, ...middlewares],
|
|
297
|
+
this._contractVersion
|
|
298
|
+
);
|
|
299
|
+
},
|
|
300
|
+
contractVersion(version) {
|
|
301
|
+
assertContractVersion(version);
|
|
302
|
+
return createRouterInstance(
|
|
303
|
+
this.routes,
|
|
304
|
+
this._packageRouters,
|
|
305
|
+
this._globalMiddlewares,
|
|
306
|
+
version
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function assertContractVersion(version) {
|
|
312
|
+
if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)*$/.test(version)) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`contractVersion("${version}") is not a version of the form major.minor.patch. The released snapshot is named from this value and releases are compared by it.`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function defineRouter(routes) {
|
|
319
|
+
return createRouterInstance(routes);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/ops/error.ts
|
|
323
|
+
var OpsRouterError = class extends Error {
|
|
324
|
+
constructor(message) {
|
|
325
|
+
super(message);
|
|
326
|
+
this.name = "OpsRouterError";
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/ops/manifest.ts
|
|
331
|
+
function isRouter(value) {
|
|
332
|
+
return value !== null && typeof value === "object" && "routes" in value && "_routes" in value;
|
|
333
|
+
}
|
|
334
|
+
function isRouteDef(value) {
|
|
335
|
+
return value !== null && typeof value === "object" && "handler" in value;
|
|
336
|
+
}
|
|
337
|
+
function toJsonSchema(schema) {
|
|
338
|
+
return JSON.parse(JSON.stringify(schema));
|
|
339
|
+
}
|
|
340
|
+
var INPUT_SECTIONS = ["params", "query", "body"];
|
|
341
|
+
function toCommandInput(input) {
|
|
342
|
+
const sections = {};
|
|
343
|
+
if (!input) {
|
|
344
|
+
return sections;
|
|
345
|
+
}
|
|
346
|
+
for (const section of INPUT_SECTIONS) {
|
|
347
|
+
const schema = input[section];
|
|
348
|
+
if (schema) {
|
|
349
|
+
sections[section] = toJsonSchema(schema);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return sections;
|
|
353
|
+
}
|
|
354
|
+
function collectOpsCommands(routes) {
|
|
355
|
+
const commands = [];
|
|
356
|
+
visit(routes, commands, /* @__PURE__ */ new Map());
|
|
357
|
+
commands.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
358
|
+
return commands;
|
|
359
|
+
}
|
|
360
|
+
function visit(routes, commands, claimed) {
|
|
361
|
+
for (const [name, entry] of Object.entries(routes)) {
|
|
362
|
+
if (isRouter(entry)) {
|
|
363
|
+
visit(entry.routes, commands, claimed);
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
if (!isRouteDef(entry) || !entry.method || !entry.path) {
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
assertUnclaimedName(name, entry.path, claimed);
|
|
370
|
+
commands.push({
|
|
371
|
+
name,
|
|
372
|
+
method: entry.method,
|
|
373
|
+
path: entry.path,
|
|
374
|
+
input: toCommandInput(entry.input)
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
function assertUnclaimedName(name, path, claimed) {
|
|
379
|
+
const existing = claimed.get(name);
|
|
380
|
+
if (existing !== void 0) {
|
|
381
|
+
throw new OpsRouterError(
|
|
382
|
+
`Two ops routes are named "${name}" ("${existing}" and "${path}"). Command names are flattened across nested routers, so each must be unique for the CLI to resolve the one an operator asked for.`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
claimed.set(name, path);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/ops/ops-route.ts
|
|
389
|
+
var OPS_PATH_ROOT = "/_ops";
|
|
390
|
+
function toOpsPath(path) {
|
|
391
|
+
if (!path.startsWith("/")) {
|
|
392
|
+
throw new OpsRouterError(
|
|
393
|
+
`Ops route path "${path}" must start with "/". It is appended to "${OPS_PATH_ROOT}", so "${path}" would read as "${OPS_PATH_ROOT}${path}".`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
if (path === "/") {
|
|
397
|
+
throw new OpsRouterError(
|
|
398
|
+
`Ops route path "/" names no command \u2014 "${OPS_PATH_ROOT}" itself is not a command.`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return OPS_PATH_ROOT + path;
|
|
402
|
+
}
|
|
403
|
+
function opsMethod(method) {
|
|
404
|
+
return (path) => route[method](toOpsPath(path));
|
|
405
|
+
}
|
|
406
|
+
var opsRoute = {
|
|
407
|
+
get: opsMethod("get"),
|
|
408
|
+
post: opsMethod("post"),
|
|
409
|
+
put: opsMethod("put"),
|
|
410
|
+
patch: opsMethod("patch"),
|
|
411
|
+
delete: opsMethod("delete")
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// src/ops/route-overlap.ts
|
|
415
|
+
function routeSegments(path) {
|
|
416
|
+
return path.split("/").slice(1).map((raw) => {
|
|
417
|
+
if (raw === "*" || raw.endsWith("*")) {
|
|
418
|
+
return { kind: "wildcard", value: raw, optional: true };
|
|
419
|
+
}
|
|
420
|
+
if (raw.startsWith(":")) {
|
|
421
|
+
return {
|
|
422
|
+
kind: "parameter",
|
|
423
|
+
value: raw.slice(1).replace(/\?$/, ""),
|
|
424
|
+
optional: raw.endsWith("?")
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return { kind: "static", value: raw, optional: false };
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
function customPattern(parameter) {
|
|
431
|
+
const openingBrace = parameter.indexOf("{");
|
|
432
|
+
return openingBrace >= 0 && parameter.endsWith("}") ? parameter.slice(openingBrace + 1, -1) : null;
|
|
433
|
+
}
|
|
434
|
+
function parameterAccepts(parameter, value) {
|
|
435
|
+
const pattern = customPattern(parameter);
|
|
436
|
+
if (pattern === null) {
|
|
437
|
+
return value.length > 0 && !value.includes("/");
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
return new RegExp(`^(?:${pattern})$`).test(value);
|
|
441
|
+
} catch {
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
function remainingStaticPath(segments, from) {
|
|
446
|
+
const remaining = segments.slice(from);
|
|
447
|
+
return remaining.every((segment) => segment.kind === "static") ? remaining.map((segment) => segment.value).join("/") : null;
|
|
448
|
+
}
|
|
449
|
+
function opsRoutePatternsOverlap(firstPath, secondPath) {
|
|
450
|
+
const first = routeSegments(firstPath);
|
|
451
|
+
const second = routeSegments(secondPath);
|
|
452
|
+
const length = Math.max(first.length, second.length);
|
|
453
|
+
for (let index = 0; index < length; index++) {
|
|
454
|
+
const left = first[index];
|
|
455
|
+
const right = second[index];
|
|
456
|
+
if (!left || !right) {
|
|
457
|
+
const remaining = left ? first.slice(index) : second.slice(index);
|
|
458
|
+
return remaining.every((segment) => segment.optional || segment.kind === "wildcard");
|
|
459
|
+
}
|
|
460
|
+
if (left.kind === "wildcard" || right.kind === "wildcard") {
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
if (left.kind === "static" && right.kind === "static") {
|
|
464
|
+
if (left.value !== right.value) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (left.kind === "parameter" && right.kind === "static") {
|
|
470
|
+
const remaining = remainingStaticPath(second, index);
|
|
471
|
+
if (customPattern(left.value) !== null && remaining !== null && parameterAccepts(left.value, remaining)) {
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
if (!parameterAccepts(left.value, right.value)) {
|
|
475
|
+
return customPattern(left.value) !== null && remaining === null;
|
|
476
|
+
}
|
|
477
|
+
if (customPattern(left.value) !== null && first.length !== second.length) {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (left.kind === "static" && right.kind === "parameter") {
|
|
483
|
+
const remaining = remainingStaticPath(first, index);
|
|
484
|
+
if (customPattern(right.value) !== null && remaining !== null && parameterAccepts(right.value, remaining)) {
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
if (!parameterAccepts(right.value, left.value)) {
|
|
488
|
+
return customPattern(right.value) !== null && remaining === null;
|
|
489
|
+
}
|
|
490
|
+
if (customPattern(right.value) !== null && first.length !== second.length) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
return true;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// src/ops/module.ts
|
|
501
|
+
var MODULE_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
502
|
+
var COMMAND_NAME = /^[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z][A-Za-z0-9]*)*$/;
|
|
503
|
+
var CONTRACT_VERSION = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)*$/;
|
|
504
|
+
var EFFECTS = /* @__PURE__ */ new Set(["read", "write", "destructive"]);
|
|
505
|
+
var OPS_PATH_BASE = "https://spfn.invalid";
|
|
506
|
+
function assertText(label, value) {
|
|
507
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
508
|
+
throw new OpsRouterError(`${label} must be a non-empty string.`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
function assertStableModulePath(moduleId, commandName, path) {
|
|
512
|
+
const label = `Ops command "${moduleId}.${commandName}" path "${path}"`;
|
|
513
|
+
const transportPath = path.replace(/[{}?#]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
514
|
+
let url;
|
|
515
|
+
try {
|
|
516
|
+
url = new URL(transportPath, OPS_PATH_BASE);
|
|
517
|
+
} catch {
|
|
518
|
+
throw new OpsRouterError(`${label} is not a valid URL path.`);
|
|
519
|
+
}
|
|
520
|
+
if (url.origin !== OPS_PATH_BASE || url.search !== "" || url.hash !== "" || url.pathname !== transportPath) {
|
|
521
|
+
throw new OpsRouterError(`${label} is not a stable plain absolute path.`);
|
|
522
|
+
}
|
|
523
|
+
const slashCount = (path.match(/\//g) ?? []).length;
|
|
524
|
+
let decoded = path;
|
|
525
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
526
|
+
let next;
|
|
527
|
+
try {
|
|
528
|
+
next = decodeURIComponent(decoded);
|
|
529
|
+
} catch {
|
|
530
|
+
throw new OpsRouterError(`${label} contains malformed percent encoding.`);
|
|
531
|
+
}
|
|
532
|
+
if (next.includes("\\") || (next.match(/\//g) ?? []).length !== slashCount || next.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
533
|
+
throw new OpsRouterError(`${label} contains encoded path separators or dot segments.`);
|
|
534
|
+
}
|
|
535
|
+
if (next === decoded) {
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
decoded = next;
|
|
539
|
+
}
|
|
540
|
+
throw new OpsRouterError(`${label} uses too many percent-encoding layers.`);
|
|
541
|
+
}
|
|
542
|
+
function assertCliCallableModulePath(moduleId, commandName, path) {
|
|
543
|
+
const unsupported = path.split("/").slice(1).find((segment) => segment === "*" || segment.includes("*") || (segment.startsWith(":") ? !/^:[A-Za-z0-9_]+$/.test(segment) : !/^[A-Za-z0-9._~-]+$/.test(segment)));
|
|
544
|
+
if (unsupported !== void 0) {
|
|
545
|
+
throw new OpsRouterError(
|
|
546
|
+
`Ops command "${moduleId}.${commandName}" path segment "${unsupported}" is not CLI-callable. Module routes support URL-safe static segments and simple :name parameters only; optional, wildcard, and custom-regex parameters are not supported.`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function assertCommand(moduleId, name, command) {
|
|
551
|
+
if (!COMMAND_NAME.test(name)) {
|
|
552
|
+
throw new OpsRouterError(
|
|
553
|
+
`Ops module "${moduleId}" has invalid command name "${name}". Use dot-separated alphanumeric names.`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
assertText(`Ops command "${moduleId}.${name}" summary`, command?.summary);
|
|
557
|
+
if (!EFFECTS.has(command?.effect)) {
|
|
558
|
+
throw new OpsRouterError(
|
|
559
|
+
`Ops command "${moduleId}.${name}" has invalid effect ${JSON.stringify(command?.effect)}.`
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
if (!Array.isArray(command?.scopes) || command.scopes.length === 0 || command.scopes.some((scope) => typeof scope !== "string" || scope.trim().length === 0)) {
|
|
563
|
+
throw new OpsRouterError(`Ops command "${moduleId}.${name}" must declare at least one non-empty scope.`);
|
|
564
|
+
}
|
|
565
|
+
const route2 = command?.route;
|
|
566
|
+
if (!route2 || typeof route2.handler !== "function" || !route2.method || !route2.path) {
|
|
567
|
+
throw new OpsRouterError(`Ops command "${moduleId}.${name}" must carry a complete ops route.`);
|
|
568
|
+
}
|
|
569
|
+
assertStableModulePath(moduleId, name, route2.path);
|
|
570
|
+
assertCliCallableModulePath(moduleId, name, route2.path);
|
|
571
|
+
const modulePath = `${OPS_PATH_ROOT}/${moduleId}/`;
|
|
572
|
+
if (!route2.path.startsWith(modulePath)) {
|
|
573
|
+
throw new OpsRouterError(
|
|
574
|
+
`Ops command "${moduleId}.${name}" is at "${route2.path}", outside "${modulePath}".`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
if (route2.path.split("/").includes("..")) {
|
|
578
|
+
throw new OpsRouterError(`Ops command "${moduleId}.${name}" path climbs out of its module namespace.`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function defineOpsModule(module) {
|
|
582
|
+
if (!MODULE_ID.test(module?.id ?? "")) {
|
|
583
|
+
throw new OpsRouterError(
|
|
584
|
+
`Ops module id ${JSON.stringify(module?.id)} is invalid. Use lower-kebab-case.`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
assertText(`Ops module "${module.id}" source`, module.source);
|
|
588
|
+
assertText(`Ops module "${module.id}" summary`, module.summary);
|
|
589
|
+
if (!CONTRACT_VERSION.test(module.contractVersion)) {
|
|
590
|
+
throw new OpsRouterError(
|
|
591
|
+
`Ops module "${module.id}" contractVersion must be a semantic version.`
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
if (!module.commands || typeof module.commands !== "object" || Array.isArray(module.commands)) {
|
|
595
|
+
throw new OpsRouterError(`Ops module "${module.id}" commands must be an object.`);
|
|
596
|
+
}
|
|
597
|
+
const signatures = /* @__PURE__ */ new Map();
|
|
598
|
+
const claimedRoutes = [];
|
|
599
|
+
for (const [name, command] of Object.entries(module.commands)) {
|
|
600
|
+
assertCommand(module.id, name, command);
|
|
601
|
+
const signature = `${command.route.method} ${command.route.path}`;
|
|
602
|
+
const existing = signatures.get(signature);
|
|
603
|
+
if (existing) {
|
|
604
|
+
throw new OpsRouterError(
|
|
605
|
+
`Ops module "${module.id}" commands "${existing}" and "${name}" both use ${signature}.`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
signatures.set(signature, name);
|
|
609
|
+
const overlapping = claimedRoutes.find((claim) => claim.method === command.route.method && opsRoutePatternsOverlap(claim.path, command.route.path));
|
|
610
|
+
if (overlapping) {
|
|
611
|
+
throw new OpsRouterError(
|
|
612
|
+
`Ops module "${module.id}" commands "${overlapping.name}" and "${name}" have overlapping ${command.route.method} routes ("${overlapping.path}" and "${command.route.path}").`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
claimedRoutes.push({
|
|
616
|
+
name,
|
|
617
|
+
method: command.route.method,
|
|
618
|
+
path: command.route.path
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return module;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/ops/create-ops-router.ts
|
|
625
|
+
var OPS_PATH_PREFIX = "/_ops/";
|
|
626
|
+
var OPS_MANIFEST_PATH = "/_ops/_manifest";
|
|
627
|
+
var OPS_MANIFEST_NAME = "getOpsManifest";
|
|
628
|
+
function isRouter2(value) {
|
|
629
|
+
return value !== null && typeof value === "object" && "routes" in value && "_routes" in value;
|
|
630
|
+
}
|
|
631
|
+
function isRouteDef2(value) {
|
|
632
|
+
return value !== null && typeof value === "object" && "handler" in value;
|
|
633
|
+
}
|
|
634
|
+
function assertOpsRoute(name, def) {
|
|
635
|
+
if (!def.method || !def.path) {
|
|
636
|
+
throw new OpsRouterError(
|
|
637
|
+
`Ops route "${name}" has no method or path. An ops command is invoked on the wire, so both are required.`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (!def.path.startsWith(OPS_PATH_PREFIX)) {
|
|
641
|
+
throw new OpsRouterError(
|
|
642
|
+
`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.`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (def.path === OPS_MANIFEST_PATH) {
|
|
646
|
+
throw new OpsRouterError(
|
|
647
|
+
`Ops route "${name}" claims "${OPS_MANIFEST_PATH}", which is reserved for the manifest. The manifest is registered first, so this route would never answer.`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
function assertOpsName(name) {
|
|
652
|
+
if (name === OPS_MANIFEST_NAME) {
|
|
653
|
+
throw new OpsRouterError(
|
|
654
|
+
`Ops route name "${OPS_MANIFEST_NAME}" is reserved for the manifest route.`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function rebuildNestedRouter(name, router, auth, inherited) {
|
|
659
|
+
if (router._packageRouters?.length > 0) {
|
|
660
|
+
throw new OpsRouterError(
|
|
661
|
+
`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.`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
const handedDown = [...inherited, ...router._globalMiddlewares ?? []];
|
|
665
|
+
let rebuilt = defineRouter(
|
|
666
|
+
secureRoutes(router.routes, auth, handedDown)
|
|
667
|
+
);
|
|
668
|
+
if (router._contractVersion) {
|
|
669
|
+
rebuilt = rebuilt.contractVersion(router._contractVersion);
|
|
670
|
+
}
|
|
671
|
+
return rebuilt;
|
|
672
|
+
}
|
|
673
|
+
function secureRoutes(routes, auth, inherited = []) {
|
|
674
|
+
const secured = {};
|
|
675
|
+
for (const [name, entry] of Object.entries(routes)) {
|
|
676
|
+
assertOpsName(name);
|
|
677
|
+
if (isRouter2(entry)) {
|
|
678
|
+
secured[name] = rebuildNestedRouter(name, entry, auth, inherited);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (!isRouteDef2(entry)) {
|
|
682
|
+
throw new OpsRouterError(`Ops router entry "${name}" is neither a route nor a router.`);
|
|
683
|
+
}
|
|
684
|
+
assertOpsRoute(name, entry);
|
|
685
|
+
secured[name] = {
|
|
686
|
+
...entry,
|
|
687
|
+
middlewares: [auth, ...inherited, ...entry.middlewares ?? []]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
return secured;
|
|
691
|
+
}
|
|
692
|
+
function compileModules(modules, appCommands) {
|
|
693
|
+
const descriptors = [];
|
|
694
|
+
const commands = [];
|
|
695
|
+
const routes = {};
|
|
696
|
+
const moduleIds = /* @__PURE__ */ new Set();
|
|
697
|
+
const commandNames = new Set(appCommands.map((command) => command.name));
|
|
698
|
+
const routeSignatures = new Map(
|
|
699
|
+
appCommands.map((command) => [`${command.method} ${command.path}`, command.name])
|
|
700
|
+
);
|
|
701
|
+
for (const rawModule of modules) {
|
|
702
|
+
const module = defineOpsModule(rawModule);
|
|
703
|
+
if (moduleIds.has(module.id)) {
|
|
704
|
+
throw new OpsRouterError(`Two ops modules use id "${module.id}".`);
|
|
705
|
+
}
|
|
706
|
+
moduleIds.add(module.id);
|
|
707
|
+
descriptors.push({
|
|
708
|
+
id: module.id,
|
|
709
|
+
source: module.source,
|
|
710
|
+
contractVersion: module.contractVersion,
|
|
711
|
+
summary: module.summary
|
|
712
|
+
});
|
|
713
|
+
for (const [localName, definition] of Object.entries(module.commands)) {
|
|
714
|
+
const name = `${module.id}.${localName}`;
|
|
715
|
+
if (commandNames.has(name)) {
|
|
716
|
+
throw new OpsRouterError(`Two ops commands are named "${name}".`);
|
|
717
|
+
}
|
|
718
|
+
commandNames.add(name);
|
|
719
|
+
const method = definition.route.method;
|
|
720
|
+
const path = definition.route.path;
|
|
721
|
+
const signature = `${method} ${path}`;
|
|
722
|
+
const existing = routeSignatures.get(signature);
|
|
723
|
+
if (existing) {
|
|
724
|
+
throw new OpsRouterError(
|
|
725
|
+
`Ops commands "${existing}" and "${name}" both use ${signature}.`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
const overlappingAppCommand = appCommands.find((command) => command.method === method && opsRoutePatternsOverlap(command.path, path));
|
|
729
|
+
if (overlappingAppCommand) {
|
|
730
|
+
throw new OpsRouterError(
|
|
731
|
+
`App ops command "${overlappingAppCommand.name}" at ${method} ${overlappingAppCommand.path} overlaps module command "${name}" at ${method} ${path}. Which one answers cannot depend on route registration order.`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
routeSignatures.set(signature, name);
|
|
735
|
+
commands.push({
|
|
736
|
+
name,
|
|
737
|
+
module: module.id,
|
|
738
|
+
summary: definition.summary,
|
|
739
|
+
effect: definition.effect,
|
|
740
|
+
scopes: [...definition.scopes],
|
|
741
|
+
method,
|
|
742
|
+
path,
|
|
743
|
+
input: collectOpsCommands({ [localName]: definition.route })[0].input
|
|
744
|
+
});
|
|
745
|
+
routes[name] = { route: definition.route, scopes: definition.scopes };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
descriptors.sort((a, b) => a.id.localeCompare(b.id));
|
|
749
|
+
commands.sort((a, b) => a.name.localeCompare(b.name));
|
|
750
|
+
return { descriptors, commands, routes };
|
|
751
|
+
}
|
|
752
|
+
function secureModuleRoutes(routes, auth, authorize) {
|
|
753
|
+
const secured = {};
|
|
754
|
+
for (const [name, definition] of Object.entries(routes)) {
|
|
755
|
+
assertOpsName(name);
|
|
756
|
+
assertOpsRoute(name, definition.route);
|
|
757
|
+
secured[name] = {
|
|
758
|
+
...definition.route,
|
|
759
|
+
middlewares: [
|
|
760
|
+
auth,
|
|
761
|
+
authorize(...definition.scopes),
|
|
762
|
+
...definition.route.middlewares ?? []
|
|
763
|
+
]
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
return secured;
|
|
767
|
+
}
|
|
768
|
+
function createOpsRouter(routes, options) {
|
|
769
|
+
if (!options?.auth) {
|
|
770
|
+
throw new OpsRouterError(
|
|
771
|
+
"createOpsRouter requires an auth middleware ({ auth: ... }). An ops surface reachable without authentication cannot be created."
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
const modules = options.modules ?? [];
|
|
775
|
+
if (modules.length > 0 && !options.authorize) {
|
|
776
|
+
throw new OpsRouterError(
|
|
777
|
+
"createOpsRouter requires an authorize scope factory when modules are mounted."
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
const appCommands = collectOpsCommands(routes);
|
|
781
|
+
const moduleSurface = compileModules(modules, appCommands);
|
|
782
|
+
const commands = [...appCommands, ...moduleSurface.commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
783
|
+
const manifest = {
|
|
784
|
+
manifestVersion: 1,
|
|
785
|
+
...moduleSurface.descriptors.length > 0 ? { modules: moduleSurface.descriptors } : {},
|
|
786
|
+
commands
|
|
787
|
+
};
|
|
788
|
+
const secured = secureRoutes(routes, options.auth);
|
|
789
|
+
const securedModules = moduleSurface.descriptors.length > 0 ? secureModuleRoutes(moduleSurface.routes, options.auth, options.authorize) : {};
|
|
790
|
+
const manifestRoute = route.get(OPS_MANIFEST_PATH).use([options.auth]).handler(async () => manifest);
|
|
791
|
+
return defineRouter({
|
|
792
|
+
[OPS_MANIFEST_NAME]: manifestRoute,
|
|
793
|
+
...secured,
|
|
794
|
+
...securedModules
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export { OPS_MANIFEST_PATH, OPS_PATH_PREFIX, OPS_PATH_ROOT, OpsRouterError, collectOpsCommands, createOpsRouter, defineOpsModule, opsRoute };
|
|
799
|
+
//# sourceMappingURL=index.js.map
|
|
800
|
+
//# sourceMappingURL=index.js.map
|