@owlmeans/viable-common 0.0.6 → 0.0.8
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 +1 -68
- package/build/areas/consts.d.ts +1 -1
- package/build/areas/consts.js +1 -1
- package/build/connect/consts.d.ts +6 -94
- package/build/connect/consts.d.ts.map +1 -1
- package/build/connect/consts.js +2 -91
- package/build/connect/consts.js.map +1 -1
- package/build/connect/entrypoints.d.ts +3 -3
- package/build/connect/entrypoints.d.ts.map +1 -1
- package/build/connect/entrypoints.js +54 -95
- package/build/connect/entrypoints.js.map +1 -1
- package/build/connect/ops.d.ts +2 -46
- package/build/connect/ops.d.ts.map +1 -1
- package/build/connect/references.d.ts +63 -117
- package/build/connect/references.d.ts.map +1 -1
- package/build/connect/references.js +0 -16
- package/build/connect/references.js.map +1 -1
- package/build/connect/schemas.d.ts +2 -17
- package/build/connect/schemas.d.ts.map +1 -1
- package/build/connect/schemas.js +7 -91
- package/build/connect/schemas.js.map +1 -1
- package/build/connect/types.d.ts +1 -125
- package/build/connect/types.d.ts.map +1 -1
- package/build/dev/types.d.ts +3 -2
- package/build/dev/types.d.ts.map +1 -1
- package/build/index.d.ts +0 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +0 -1
- package/build/index.js.map +1 -1
- package/build/skills/catalogue.d.ts.map +1 -1
- package/build/skills/catalogue.js +109 -103
- package/build/skills/catalogue.js.map +1 -1
- package/build/slot/consts.d.ts +2 -73
- package/build/slot/consts.d.ts.map +1 -1
- package/build/slot/consts.js +5 -105
- package/build/slot/consts.js.map +1 -1
- package/build/slot/schemas.d.ts +1 -9
- package/build/slot/schemas.d.ts.map +1 -1
- package/build/slot/schemas.js +0 -18
- package/build/slot/schemas.js.map +1 -1
- package/build/slot/types.d.ts +0 -71
- package/build/slot/types.d.ts.map +1 -1
- package/package.json +7 -15
- package/src/areas/consts.ts +1 -1
- package/src/connect/consts.ts +2 -94
- package/src/connect/entrypoints.ts +69 -139
- package/src/connect/ops.ts +2 -50
- package/src/connect/references.ts +56 -32
- package/src/connect/schemas.ts +13 -102
- package/src/connect/types.ts +2 -130
- package/src/dev/types.ts +3 -2
- package/src/index.ts +0 -1
- package/src/skills/catalogue.ts +109 -103
- package/src/slot/consts.ts +6 -112
- package/src/slot/schemas.ts +1 -20
- package/src/slot/types.ts +0 -80
- package/agent-meta/manifest.json +0 -16
- package/agent-meta/skills/viable-common/SKILL.md +0 -179
- package/build/convert/consts.d.ts +0 -539
- package/build/convert/consts.d.ts.map +0 -1
- package/build/convert/consts.js +0 -630
- package/build/convert/consts.js.map +0 -1
- package/build/convert/helpers.d.ts +0 -91
- package/build/convert/helpers.d.ts.map +0 -1
- package/build/convert/helpers.js +0 -301
- package/build/convert/helpers.js.map +0 -1
- package/build/convert/index.d.ts +0 -11
- package/build/convert/index.d.ts.map +0 -1
- package/build/convert/index.js +0 -4
- package/build/convert/index.js.map +0 -1
- package/build/convert/schemas.d.ts +0 -62
- package/build/convert/schemas.d.ts.map +0 -1
- package/build/convert/schemas.js +0 -626
- package/build/convert/schemas.js.map +0 -1
- package/build/convert/types.d.ts +0 -683
- package/build/convert/types.d.ts.map +0 -1
- package/build/convert/types.js +0 -2
- package/build/convert/types.js.map +0 -1
- package/src/convert/consts.ts +0 -672
- package/src/convert/helpers.ts +0 -340
- package/src/convert/index.ts +0 -10
- package/src/convert/schemas.ts +0 -663
- package/src/convert/types.ts +0 -741
- package/tests/connect-convert.spec.ts +0 -161
- package/tests/convert.spec.ts +0 -318
- /package/tests/{design.spec.ts → design.test.ts} +0 -0
package/src/skills/catalogue.ts
CHANGED
|
@@ -107,10 +107,10 @@ These files are the CONTRACT between the packages. Every screen, endpoint and jo
|
|
|
107
107
|
them, and each has a sentinel comment marking where a new line goes:
|
|
108
108
|
|
|
109
109
|
- \`sources/common/src/consts.ts\` — the \`app\` alias tree.
|
|
110
|
-
- \`sources/common/src/entrypoints.ts\` — the shared \`
|
|
111
|
-
- \`sources/api/src/entrypoints.ts\` — server
|
|
112
|
-
- \`sources/web/src/entrypoints.ts\` — client
|
|
113
|
-
- \`sources/worker/src/entrypoints.ts\` — job
|
|
110
|
+
- \`sources/common/src/entrypoints.ts\` — the shared immutable protocol tree (\`appEntrypoints\`).
|
|
111
|
+
- \`sources/api/src/entrypoints.ts\` — server bindings (\`bind\` + \`handlers<Context>()\`).
|
|
112
|
+
- \`sources/web/src/entrypoints.ts\` — client bindings (\`bindAll\` + \`bindScreen\`).
|
|
113
|
+
- \`sources/worker/src/entrypoints.ts\` — job bindings (\`bind\`).
|
|
114
114
|
- \`sources/web/src/nav.ts\` — the navigation registry: one line per screen, which
|
|
115
115
|
is the only thing that puts it in the menus.
|
|
116
116
|
|
|
@@ -272,13 +272,13 @@ in its own language, with no TypeScript syntax and no import statements it does
|
|
|
272
272
|
skill(ViableSkill.OwlMeansEntrypoints, 'Entrypoints — screens and endpoints', `
|
|
273
273
|
Everything this app addresses — a screen the browser renders, an endpoint the server answers
|
|
274
274
|
— is an OwlMeans **entrypoint**: declared with \`@owlmeans/entrypoint\` and \`@owlmeans/route\`
|
|
275
|
-
in the shared package,
|
|
276
|
-
\`@owlmeans/server-
|
|
275
|
+
in the shared package, bound with \`@owlmeans/client-entrypoint\` in the browser and
|
|
276
|
+
\`@owlmeans/server-entrypoint\` on the backend. \`@owlmeans/web-client\` builds the route table from the
|
|
277
277
|
entrypoints and renders it over the History API, so there is no router file, no \`<Routes>\` and
|
|
278
278
|
no \`app.get(...)\`. No third-party routing or HTTP-server package is installed — importing one
|
|
279
279
|
fails to resolve.
|
|
280
280
|
|
|
281
|
-
An entrypoint is DECLARED once in the shared package, then
|
|
281
|
+
An entrypoint protocol is DECLARED once in the shared package, then BOUND on each side that uses it.
|
|
282
282
|
|
|
283
283
|
**1. The alias.** Aliases live in ONE place, the \`app\` tree in
|
|
284
284
|
\`sources/common/src/consts.ts\`. NEVER write an alias string inline — always reference
|
|
@@ -286,27 +286,30 @@ An entrypoint is DECLARED once in the shared package, then ELEVATED on each side
|
|
|
286
286
|
|
|
287
287
|
${ALIAS_CONVENTION}
|
|
288
288
|
|
|
289
|
-
**2. The declaration** — \`sources/common/src/entrypoints.ts\`, one
|
|
290
|
-
|
|
289
|
+
**2. The declaration** — \`sources/common/src/entrypoints.ts\`, one property in the immutable
|
|
290
|
+
\`appEntrypoints\` protocol tree:
|
|
291
291
|
|
|
292
|
-
import {
|
|
292
|
+
import { openProtocol, protocol, contract, typed } from '@owlmeans/entrypoint'
|
|
293
293
|
import { route, frontend, RouteMethod } from '@owlmeans/route'
|
|
294
294
|
|
|
295
|
-
|
|
295
|
+
task: {
|
|
296
|
+
list: protocol(route(app.api.task.list, '/list', { parent: app.api.task }),
|
|
297
|
+
contract(typed<Task[]>()), { guards: DEFAULT_GUARD }),
|
|
298
|
+
}
|
|
296
299
|
|
|
297
300
|
- \`route(alias, path)\` is a BACKEND route by default. \`route(alias, path, frontend())\` makes
|
|
298
301
|
it a screen; \`frontend({ default: true, parent: app.web.base })\` makes it the index screen.
|
|
299
302
|
- Nest with \`{ parent: <group alias> }\` — the child path is APPENDED to the parent's, so the
|
|
300
303
|
child path is the tail only (\`'/list'\`, not \`'/tasks/list'\`).
|
|
301
304
|
- \`{ method: RouteMethod.POST }\` (from \`@owlmeans/route\`) for anything that is not a GET.
|
|
302
|
-
- \`
|
|
305
|
+
- \`contract.request({ body: typed<T>(TSchema) }, typed<Response>())\` whenever the body's type has a schema beside it in the shared
|
|
303
306
|
package — every generated type exports one, named after the type with a \`Schema\` suffix
|
|
304
307
|
(\`Task\` → \`TaskSchema\`). Import both from the shared package and use them; that is what makes
|
|
305
308
|
the framework reject a malformed request before your handler runs, so the handler never has to
|
|
306
309
|
check whether a field arrived. Still never INVENT a schema inline: if the type has none, leave
|
|
307
310
|
the endpoint unfiltered rather than writing a literal here that nothing else agrees with.
|
|
308
|
-
- Access is declarative: no \`
|
|
309
|
-
\`
|
|
311
|
+
- Access is declarative: no \`guards\` option = public, \`{ guards: DEFAULT_GUARD }\` = any signed-in user,
|
|
312
|
+
\`{ guards: DEFAULT_GUARD, gate: { alias: OIDC_GATE, params: ['<permission>'] } }\` = a permission is required.
|
|
310
313
|
\`<permission>\` is a SHAPE, never a value: build the real name from the domain being
|
|
311
314
|
implemented — the resource, TWO hyphens, the action, lowercase kebab-case, singular resource.
|
|
312
315
|
Do not write a bracketed word into an application, and do not copy a name out of this
|
|
@@ -316,30 +319,29 @@ array:
|
|
|
316
319
|
nothing and the gate then refuses every request. Guards and gates are INHERITED by children and
|
|
317
320
|
enforced by the framework — a handler or a screen never re-checks them.
|
|
318
321
|
|
|
319
|
-
**3. The
|
|
322
|
+
**3. The binding.** The protocol declaration alone renders and answers nothing.
|
|
320
323
|
|
|
321
324
|
- Server, \`sources/api/src/entrypoints.ts\`:
|
|
322
|
-
\`
|
|
325
|
+
\`bind(protocols.api.task.list, api.request(protocols.api.task.list, handleTaskList))\`
|
|
323
326
|
- Client screen, \`sources/web/src/entrypoints.ts\`:
|
|
324
|
-
\`
|
|
325
|
-
- Client CALL, same file:
|
|
326
|
-
|
|
327
|
-
browser. Passing a component to a backend alias is a hard error, not a warning.
|
|
327
|
+
\`bindScreen(protocols.web.taskList, handler(TaskListScreen))\`
|
|
328
|
+
- Client CALL, same file: \`bindAll(protocols.api.task)\`. This is what makes the protocol callable
|
|
329
|
+
from the browser. Passing a component to a backend protocol is a hard error, not a warning.
|
|
328
330
|
|
|
329
|
-
**A missing client
|
|
330
|
-
registered on the browser context, so \`ctx.entrypoint(
|
|
331
|
-
and returns it; the call site
|
|
332
|
-
|
|
331
|
+
**A missing client binding is invisible until the app runs.** The whole shared tree is
|
|
332
|
+
registered on the browser context, so \`ctx.entrypoint(protocol)\` finds the unbound declaration
|
|
333
|
+
and returns it; the call site assumes the protocol is bound, so the build can still be clean. The
|
|
334
|
+
only symptom is in the browser:
|
|
333
335
|
|
|
334
336
|
TypeError: entrypoint.call is not a function
|
|
335
337
|
|
|
336
|
-
That message means EXACTLY one thing — the
|
|
337
|
-
\`
|
|
338
|
-
|
|
338
|
+
That message means EXACTLY one thing — the protocol in that \`ctx.entrypoint(...)\` has no
|
|
339
|
+
\`bindAll(protocols.api)\` or specific \`bind(protocols.api.<name>)\` line in
|
|
340
|
+
\`sources/web/src/entrypoints.ts\`. Add the binding above the API binding sentinel. It is
|
|
339
341
|
never a broken component, never a bad import, and never a reason to rewrite the view model or to
|
|
340
342
|
replace the call with \`fetch\`. If the alias is not declared in
|
|
341
|
-
\`sources/common/src/entrypoints.ts\` either, declare it there FIRST:
|
|
342
|
-
shared
|
|
343
|
+
\`sources/common/src/entrypoints.ts\` either, declare it there FIRST: binding a protocol the
|
|
344
|
+
shared tree does not carry throws while the module is
|
|
343
345
|
still loading, which blanks the whole app instead of failing one call.
|
|
344
346
|
|
|
345
347
|
**4. An AREA is the PARENT entrypoint.** The chrome of the app — header, navigation, footer —
|
|
@@ -352,16 +354,16 @@ project, and an area contributes chrome AND a URL prefix:
|
|
|
352
354
|
- \`app.web.area.admin\` at \`/admin\` — the owner of the application.
|
|
353
355
|
- \`app.web.area.operator\` at \`/backoffice\` — staff running the business process; the back office.
|
|
354
356
|
|
|
355
|
-
Those four declarations and their
|
|
356
|
-
path, NEVER touch its \`
|
|
357
|
+
Those four declarations and their bindings are FIXED. NEVER add an area, NEVER change one's
|
|
358
|
+
path, NEVER touch its \`guards\`/\`gate\` options, NEVER re-declare one — put the screen in the area
|
|
357
359
|
whose access and audience it needs instead. A product's own roles are not areas: they are users
|
|
358
360
|
or operators holding different permissions.
|
|
359
361
|
|
|
360
362
|
// sources/common/src/entrypoints.ts — a screen: TAIL path, area as parent, no access
|
|
361
|
-
|
|
363
|
+
taskList: openProtocol(route(app.web.taskList, '/tasks', frontend({ parent: app.web.area.user }))),
|
|
362
364
|
|
|
363
365
|
// sources/web/src/entrypoints.ts
|
|
364
|
-
|
|
366
|
+
bindScreen(protocols.web.taskList, handler(TaskListScreen))
|
|
365
367
|
|
|
366
368
|
That screen answers at \`/frontoffice/tasks\`. The path you write is the TAIL ONLY — repeating the
|
|
367
369
|
area prefix (\`'/frontoffice/tasks'\`) publishes it at \`/frontoffice/frontoffice/tasks\`, which
|
|
@@ -372,7 +374,7 @@ The framework passes the matched child to the area as \`children\`, so an area l
|
|
|
372
374
|
renders ONLY its own content: importing a layout inside a screen renders the header, the menu
|
|
373
375
|
and the footer a second time, nested inside the first.
|
|
374
376
|
|
|
375
|
-
- \`app.web.base\` stays
|
|
377
|
+
- \`app.web.base\` stays BOUND with \`stab\` — \`bindScreen(protocols.web.base, stab)\`, no \`handler()\`. It is the
|
|
376
378
|
pass-through shell that hosts the four areas and contributes no chrome.
|
|
377
379
|
- Every area needs a child declared \`frontend({ default: true, parent: app.web.area.<area> })\`.
|
|
378
380
|
An entrypoint with children but no default child matches nothing and renders a BLANK PAGE.
|
|
@@ -381,20 +383,20 @@ and the footer a second time, nested inside the first.
|
|
|
381
383
|
**Access is INHERITED from the area.** Guards and gates cascade to children, so choosing the area
|
|
382
384
|
IS the access decision and a screen declares none of its own:
|
|
383
385
|
|
|
384
|
-
- guest area — no
|
|
385
|
-
- user area — \`
|
|
386
|
-
- admin area — \`
|
|
386
|
+
- guest area — no guards option; its screens add nothing.
|
|
387
|
+
- user area — \`{ guards: DEFAULT_GUARD }\`; its screens add nothing.
|
|
388
|
+
- admin area — \`{ guards: DEFAULT_GUARD, gate: { alias: OIDC_GATE, params: ['project--admin'] } }\`; its screens add
|
|
387
389
|
nothing, the marker is the whole rule.
|
|
388
|
-
- operator area — \`
|
|
389
|
-
\`gate
|
|
390
|
+
- operator area — \`{ guards: DEFAULT_GUARD, gate: { alias: OIDC_GATE, params: ['project--operator'] } }\`, and EVERY screen under it additionally declares
|
|
391
|
+
a \`gate\` option naming the permission THAT screen needs, built from the
|
|
390
392
|
screen's own resource and action (add \`@<routeParam>\` when the screen shows one specific
|
|
391
393
|
record). This is the ONLY screen that declares access.
|
|
392
394
|
|
|
393
|
-
An ENDPOINT has no area to inherit from, so it states its level itself: no \`
|
|
394
|
-
\`
|
|
395
|
-
\`
|
|
395
|
+
An ENDPOINT has no area to inherit from, so it states its level itself: no \`guards\` option for guest,
|
|
396
|
+
\`{ guards: DEFAULT_GUARD }\` for any signed-in user,
|
|
397
|
+
\`{ guards: DEFAULT_GUARD, gate: { alias: OIDC_GATE, params: ['<permission>'] } }\` when a permission is required — built
|
|
396
398
|
from ITS OWN resource and action — and
|
|
397
|
-
\`
|
|
399
|
+
\`{ guards: DEFAULT_GUARD, gate: { alias: OIDC_GATE, params: ['project--admin'] } }\` for owner-only.
|
|
398
400
|
|
|
399
401
|
\`project--admin\` is the OWNER's marker — holding it passes EVERY gate, so the owner is not a
|
|
400
402
|
role inside the app. It is never one of the permissions this app declares for itself, and it is
|
|
@@ -403,17 +405,18 @@ existing guard to make something reachable, and never model a "limited admin" wi
|
|
|
403
405
|
that is an ordinary user holding some of the app's own permissions.
|
|
404
406
|
|
|
405
407
|
Insert every new line ABOVE the matching \`// owlmeans: add new ... above this line\` sentinel,
|
|
406
|
-
one declaration or
|
|
407
|
-
|
|
408
|
-
|
|
408
|
+
one declaration or binding per line. Adding an endpoint touches five files (alias,
|
|
409
|
+
protocol, handler, server binding, client binding); a screen touches five too (alias,
|
|
410
|
+
protocol, screen component, client binding, navigation entry). Skipping one leaves it
|
|
409
411
|
unreachable.
|
|
410
412
|
|
|
411
|
-
**Calling an endpoint** from the frontend — \`call()\` resolves to the VALUE, and a non-2xx THROWS
|
|
413
|
+
**Calling an endpoint** from the frontend — \`call()\` resolves to the VALUE, and a non-2xx THROWS.
|
|
414
|
+
Use the shared protocol reference; request and response types come from its contract:
|
|
412
415
|
|
|
413
|
-
const tasks = await owlCtx.entrypoint
|
|
414
|
-
const task = await owlCtx.entrypoint
|
|
416
|
+
const tasks = await owlCtx.entrypoint(protocols.api.task.list).call()
|
|
417
|
+
const task = await owlCtx.entrypoint(protocols.api.task.create)
|
|
415
418
|
.call({ body: input })
|
|
416
|
-
const task = await owlCtx.entrypoint
|
|
419
|
+
const task = await owlCtx.entrypoint(protocols.api.task.get)
|
|
417
420
|
.call({ params: { taskId } })
|
|
418
421
|
|
|
419
422
|
An entrypoint carries three verbs and each answers a different question:
|
|
@@ -426,15 +429,9 @@ An entrypoint carries three verbs and each answers a different question:
|
|
|
426
429
|
for a fully qualified one. A SCREEN entrypoint answers only this verb — calling \`call()\` or
|
|
427
430
|
\`invoke()\` on one THROWS.
|
|
428
431
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
\`@owlmeans/client\` does NOT export it — importing it from there fails the build with
|
|
434
|
-
\`error TS2305: Module '"@owlmeans/client"' has no exported member 'ClientEntrypoint'\`.
|
|
435
|
-
And the type argument goes on \`ClientEntrypoint\`, never on \`entrypoint\` itself:
|
|
436
|
-
\`owlCtx.entrypoint<Task>(alias)\` fails with \`error TS2739: Type 'Task' is missing the
|
|
437
|
-
following properties from type 'BasicEntrypoint'\`.
|
|
432
|
+
Do not import or cast a legacy client-entrypoint type, and do not put a value type argument on
|
|
433
|
+
\`entrypoint\`. The protocol reference is the only source of request and response types; a call
|
|
434
|
+
site that needs a different shape must fix the shared contract instead of overriding it locally.
|
|
438
435
|
|
|
439
436
|
Inside a component or a hook — which is where nearly every call belongs — read the context with
|
|
440
437
|
\`useContext()\` from \`@owlmeans/web-client\`. \`owlCtx\` is for module-level code that has no
|
|
@@ -491,7 +488,7 @@ resolve.
|
|
|
491
488
|
a layout. The chrome comes from \`NavLayout\` (\`@owlmeans/web-panel\`), which the four area
|
|
492
489
|
layouts already render off this registry.
|
|
493
490
|
|
|
494
|
-
A screen with no line here compiles,
|
|
491
|
+
A screen with no line here compiles, binds and renders — it is simply reachable by direct URL
|
|
495
492
|
only, and nothing reports it.
|
|
496
493
|
`),
|
|
497
494
|
|
|
@@ -502,7 +499,7 @@ copied out of this document is a gate nobody can ever pass.
|
|
|
502
499
|
|
|
503
500
|
A permission is ONE string that has to line up in three places:
|
|
504
501
|
|
|
505
|
-
1. the GATE on the
|
|
502
|
+
1. the GATE on the protocol — \`gate: { alias: OIDC_GATE, params: ['enquiry--view@enquiryId'] }\`
|
|
506
503
|
2. the permission DEFINITION registered for the project — name \`enquiry--view\`,
|
|
507
504
|
resource \`enquiry\`, action \`view\`, resource-scoped \`true\`
|
|
508
505
|
3. the GRANT an administrator makes, which addresses that definition by name.
|
|
@@ -510,9 +507,10 @@ A permission is ONE string that has to line up in three places:
|
|
|
510
507
|
**The \`@\` suffix belongs to the GATE ONLY. It is never part of a permission's name.**
|
|
511
508
|
|
|
512
509
|
// right — the gate scopes the check; the definition and the grant use the bare name
|
|
513
|
-
|
|
510
|
+
protocol(
|
|
514
511
|
route(app.api.enquiry.get, '/:enquiryId', { parent: app.api.enquiry }),
|
|
515
|
-
|
|
512
|
+
contract(typed<Enquiry>()),
|
|
513
|
+
{ guards: DEFAULT_GUARD, gate: { alias: OIDC_GATE, params: ['enquiry--view@enquiryId'] } },
|
|
516
514
|
)
|
|
517
515
|
// definition: enquiry--view grant: enquiry--view
|
|
518
516
|
|
|
@@ -556,16 +554,16 @@ examples; yours is not, so read the right-hand column as a shape and build the n
|
|
|
556
554
|
domain.
|
|
557
555
|
|
|
558
556
|
// WRONG — a bracketed word from an instruction, written as if it were a name
|
|
559
|
-
gate
|
|
560
|
-
gate
|
|
557
|
+
gate: { alias: OIDC_GATE, params: ['<permission>'] }
|
|
558
|
+
gate: { alias: OIDC_GATE, params: ['<resource>--<action>'] }
|
|
561
559
|
// RIGHT — this application's own resource and action
|
|
562
|
-
gate
|
|
560
|
+
gate: { alias: OIDC_GATE, params: ['appointment--modify'] }
|
|
563
561
|
// Costs: nothing registers a bracketed name, so the gate refuses every request forever.
|
|
564
562
|
|
|
565
563
|
// WRONG — a name copied out of a skill, a comment or another app's example
|
|
566
|
-
gate
|
|
564
|
+
gate: { alias: OIDC_GATE, params: ['article--modify'] } // in an application that has no articles
|
|
567
565
|
// RIGHT — a name that exists in THIS domain
|
|
568
|
-
gate
|
|
566
|
+
gate: { alias: OIDC_GATE, params: ['appointment--modify'] }
|
|
569
567
|
// Costs: the permission is asserted but never declared, so no administrator can grant it.
|
|
570
568
|
|
|
571
569
|
// WRONG — the selector carried into the stored name
|
|
@@ -576,15 +574,15 @@ domain.
|
|
|
576
574
|
|
|
577
575
|
// WRONG — a selector naming a param this route does not declare
|
|
578
576
|
route(app.api.appointment.get, '/:appointmentId')
|
|
579
|
-
gate
|
|
577
|
+
gate: { alias: OIDC_GATE, params: ['appointment--view@id'] }
|
|
580
578
|
// RIGHT — the name after @ is a ":" segment of this very route
|
|
581
|
-
gate
|
|
579
|
+
gate: { alias: OIDC_GATE, params: ['appointment--view@appointmentId'] }
|
|
582
580
|
// Costs: the id resolves to nothing, so the endpoint refuses every request with nothing logged.
|
|
583
581
|
|
|
584
582
|
// WRONG — one hyphen
|
|
585
|
-
gate
|
|
583
|
+
gate: { alias: OIDC_GATE, params: ['appointment-modify'] }
|
|
586
584
|
// RIGHT — two
|
|
587
|
-
gate
|
|
585
|
+
gate: { alias: OIDC_GATE, params: ['appointment--modify'] }
|
|
588
586
|
// Costs: it registers as a resource with no action, and never lines up with the real one.
|
|
589
587
|
|
|
590
588
|
**Never spell one permission two ways.** Reuse the exact string an existing declaration already
|
|
@@ -1014,7 +1012,7 @@ So a null check around it is dead code that never runs:
|
|
|
1014
1012
|
const svc = ctx.service(ALIAS)
|
|
1015
1013
|
if (!svc) { throw { status: 503 } }
|
|
1016
1014
|
|
|
1017
|
-
\`ctx.resource(alias)\` and \`ctx.entrypoint(
|
|
1015
|
+
\`ctx.resource(alias)\` and \`ctx.entrypoint(protocol)\` throw the same way
|
|
1018
1016
|
(\`Resource X not found\`, \`Entrypoint X not found\`).
|
|
1019
1017
|
|
|
1020
1018
|
**Only when a real lifecycle-owning singleton is needed** — a client holding a connection, a
|
|
@@ -1103,28 +1101,37 @@ There is NO express here. \`express\`, \`cors\` and \`@types/express\` are not i
|
|
|
1103
1101
|
no \`app.get(...)\`, no \`req\`/\`res\` of a web server, no \`next\`, and no middleware. Anything
|
|
1104
1102
|
written for express fails to resolve.
|
|
1105
1103
|
|
|
1106
|
-
A handler is a plain async function that RETURNS its result,
|
|
1107
|
-
from \`@owlmeans/server-
|
|
1104
|
+
A handler is a plain async function that RETURNS its result, created from the protocol with a
|
|
1105
|
+
typed helper from \`@owlmeans/server-api\`. Pick by what the handler reads:
|
|
1108
1106
|
|
|
1109
|
-
import {
|
|
1107
|
+
import { handlers } from '@owlmeans/server-api'
|
|
1108
|
+
import { bind } from '@owlmeans/server-entrypoint'
|
|
1109
|
+
import { protocols } from 'project-common/entrypoints'
|
|
1110
|
+
const api = handlers<Context>()
|
|
1110
1111
|
|
|
1111
|
-
// the request BODY — the payload type
|
|
1112
|
-
|
|
1112
|
+
// the request BODY — the payload type comes from the protocol contract
|
|
1113
|
+
const createTask = api.body(protocols.api.task.create, async (payload, ctx) => {
|
|
1113
1114
|
const tasks = getTaskResource(ctx)
|
|
1114
1115
|
return await tasks.create(payload)
|
|
1115
1116
|
})
|
|
1116
1117
|
|
|
1117
1118
|
// the route PARAMS — names match the ':' segments of the declared path
|
|
1118
|
-
|
|
1119
|
+
const getTask = api.params(protocols.api.task.get, async ({ taskId }, ctx) => {
|
|
1119
1120
|
return await tasks(ctx).load(taskId)
|
|
1120
1121
|
})
|
|
1121
1122
|
|
|
1122
1123
|
// anything else (query, headers, nothing at all) — the whole request
|
|
1123
|
-
|
|
1124
|
+
const listTasks = api.request(protocols.api.task.list, async (req, ctx) => {
|
|
1124
1125
|
const query = req.query as { search?: string }
|
|
1125
1126
|
return await tasks(ctx).list(query.search != null ? { search: query.search } : {})
|
|
1126
1127
|
})
|
|
1127
1128
|
|
|
1129
|
+
export const entrypoints = [
|
|
1130
|
+
bind(protocols.api.task.create, createTask),
|
|
1131
|
+
bind(protocols.api.task.get, getTask),
|
|
1132
|
+
bind(protocols.api.task.list, listTasks),
|
|
1133
|
+
]
|
|
1134
|
+
|
|
1128
1135
|
- The SECOND argument is the OwlMeans context. Reach every resource through it — never
|
|
1129
1136
|
import a database connection and never write raw SQL in a handler.
|
|
1130
1137
|
- RETURN the value. \`res.json(...)\`, \`res.status(...)\`, \`res.send(...)\` do not exist; a
|
|
@@ -1133,9 +1140,9 @@ from \`@owlmeans/server-app\`. Pick by what the handler reads:
|
|
|
1133
1140
|
\`ResilientError\` subclass is mapped to its status by the framework; catching it produces a
|
|
1134
1141
|
200 carrying an error object instead.
|
|
1135
1142
|
- Never read a token, never check a role, never look at an \`Authorization\` header. Access is
|
|
1136
|
-
declared on the
|
|
1137
|
-
- A handler is inert until
|
|
1138
|
-
\`sources/api/src/entrypoints.ts\` binds it to its
|
|
1143
|
+
declared on the protocol (\`guards\` / \`gate\` options) and enforced before the handler runs.
|
|
1144
|
+
- A handler is inert until a \`bind(protocols.api.<name>, handler)\` line in
|
|
1145
|
+
\`sources/api/src/entrypoints.ts\` binds it to its protocol. Without that line the endpoint
|
|
1139
1146
|
answers 404 and nothing reports an error.
|
|
1140
1147
|
- Handlers are ENTITY-SCOPED: \`sources/api/src/app/<entity>/<action>.ts\`, named exports only.
|
|
1141
1148
|
The directory is what keeps two entities' \`list\` apart — the file name carries no marker.
|
|
@@ -1196,15 +1203,15 @@ says before rewriting anything.
|
|
|
1196
1203
|
\`@reduxjs/toolkit\`, \`react-redux\`, \`@/state/store\` or \`@/lib/fetch\` means the file was
|
|
1197
1204
|
written against the REMOVED stack. None of those packages are installed and none will be.
|
|
1198
1205
|
Rewrite the file against entrypoints: \`useNavigate\` from \`@owlmeans/client\` for navigation,
|
|
1199
|
-
\`ctx.entrypoint(
|
|
1200
|
-
\`
|
|
1206
|
+
\`ctx.entrypoint(protocol).call(...)\` for a backend call, a typed
|
|
1207
|
+
\`handlers<Context>()\` callback bound to its protocol for an endpoint, and the state hooks
|
|
1201
1208
|
(\`useStoreModel\`/\`useStoreList\` over a state resource) for client state. Do NOT install the
|
|
1202
1209
|
package and do NOT create the missing module.
|
|
1203
1210
|
- A missing export from a \`*.ts\` module — a selector, an action creator, a thunk, a
|
|
1204
1211
|
reducer — is the same removed stack in a different disguise. That module exports an ALIAS
|
|
1205
1212
|
constant and read hooks only. Replace a selector with the matching hook, a dispatched action
|
|
1206
1213
|
with \`resource.save(record)\` / \`model.update({ ... })\`, and a thunk with an
|
|
1207
|
-
\`entrypoint(
|
|
1214
|
+
\`entrypoint(protocol).call(...)\` in the VIEW MODEL followed by a \`save\`. Do not add the missing
|
|
1208
1215
|
export to the state module.
|
|
1209
1216
|
- A runtime \`Resource <alias> not found\` for a state alias means the state module is written but
|
|
1210
1217
|
nothing registered it. Add the import and ONE
|
|
@@ -1222,21 +1229,20 @@ says before rewriting anything.
|
|
|
1222
1229
|
that changes which records match, so the \`useValue(() => resource.list())\` that fed it a list
|
|
1223
1230
|
of ids goes away entirely rather than being repaired.
|
|
1224
1231
|
- A runtime \`TypeError: entrypoint.call is not a function\` (or \`<name>.call is not a function\`
|
|
1225
|
-
on the result of \`ctx.entrypoint(...)\`) is a MISSING CLIENT
|
|
1226
|
-
|
|
1227
|
-
one has \`call\`. Add
|
|
1228
|
-
|
|
1232
|
+
on the result of \`ctx.entrypoint(...)\`) is a MISSING CLIENT BINDING and nothing else. The
|
|
1233
|
+
protocol resolves — the browser context carries every shared declaration — but only a bound
|
|
1234
|
+
one has \`call\`. Add \`bindAll(protocols.api)\` (or a specific \`bind(protocols.api.<name>)\`) above the
|
|
1235
|
+
API binding sentinel in
|
|
1229
1236
|
\`sources/web/src/entrypoints.ts\`. Do NOT rewrite the view model, do NOT replace the
|
|
1230
|
-
call with \`fetch\`, and do NOT add a component to a backend alias. The
|
|
1231
|
-
|
|
1232
|
-
the elevation line is the fix.
|
|
1237
|
+
call with \`fetch\`, and do NOT add a component to a backend alias. The binding line is the fix;
|
|
1238
|
+
the declaration itself cannot provide \`call\` until a client binding materializes it.
|
|
1233
1239
|
- A screen that renders blank is the same class of fault on the other side: the screen alias has
|
|
1234
|
-
no \`
|
|
1240
|
+
no \`bindScreen(protocols.web.<name>, handler(Screen))\` line. Adding a protocol without its binding
|
|
1235
1241
|
is the usual cause of both.
|
|
1236
1242
|
- A header, menu or footer rendered TWICE is a screen importing its own layout. The AREA is the
|
|
1237
1243
|
screen's parent entrypoint and the framework already wraps it — delete the import and the
|
|
1238
1244
|
wrapper element from the screen, never the area from the entrypoint tree.
|
|
1239
|
-
- An error naming an AREA — \`app.web.area.<area>\`, its \`route(...)\`, its \`
|
|
1245
|
+
- An error naming an AREA — \`app.web.area.<area>\`, its \`route(...)\`, its \`guards\`/\`gate\` options,
|
|
1240
1246
|
or one of the four layouts in \`sources/web/src/layout/area.tsx\` — means generated code
|
|
1241
1247
|
EDITED something that ships with the project. RESTORE the shipped form: four areas under
|
|
1242
1248
|
\`app.web.base\` at \`/\`, \`/frontoffice\`, \`/admin\` and \`/backoffice\`, each with its own guard
|
|
@@ -1245,14 +1251,14 @@ says before rewriting anything.
|
|
|
1245
1251
|
access belongs in a different area.
|
|
1246
1252
|
- A user who WAS granted a permission and still gets 403 is almost always an \`@\` in the stored
|
|
1247
1253
|
permission NAME. The gate splits its parameter at the first \`@\`: \`'enquiry--view@enquiryId'\`
|
|
1248
|
-
in \`gate
|
|
1254
|
+
in a protocol's \`gate: { alias: OIDC_GATE, params: [...] }\` is CORRECT and means "look up \`enquiry--view\`, read the resource
|
|
1249
1255
|
id from the \`:enquiryId\` route param". The same string registered or granted as a permission
|
|
1250
1256
|
NAME is a key nothing ever looks up, so every grant against it is a silent no-op. Fix the
|
|
1251
1257
|
registration and the grant, never the gate line — and never delete the \`@\` from the gate to
|
|
1252
1258
|
"make the names match". A gate whose \`@name\` is not a \`:\` segment of that entrypoint's own
|
|
1253
1259
|
declared path is the other half of the same fault: it refuses every request.
|
|
1254
1260
|
- A PERMISSION that no definition backs is a gate nobody can pass, and there are two ways generated
|
|
1255
|
-
code gets one. A bracketed word — \`gate
|
|
1261
|
+
code gets one. A bracketed word — \`gate: { alias: OIDC_GATE, params: ['<permission>'] }\` — is an instruction's
|
|
1256
1262
|
placeholder written out as if it were a name. A foreign name — \`article--modify\` in an
|
|
1257
1263
|
application that has no articles — is an example copied from a comment or a skill. Both look
|
|
1258
1264
|
perfectly valid to the compiler and to the boot check, so nothing reports them; the app simply
|
|
@@ -1380,12 +1386,12 @@ export const queues: QueueDeclaration[] = [
|
|
|
1380
1386
|
|
|
1381
1387
|
## 2. The alias and the entrypoint — \`sources/common/src\`
|
|
1382
1388
|
The job's name IS its entrypoint alias. Declare \`app.job.<name>\` in \`consts.ts\` above the
|
|
1383
|
-
sentinel, and the
|
|
1389
|
+
sentinel, and the protocol in \`entrypoints.ts\` with \`job()\` from \`@owlmeans/route\`.
|
|
1384
1390
|
|
|
1385
1391
|
## 3. The processor — \`sources/worker/src/jobs/<name>.ts\`
|
|
1386
|
-
A plain async function
|
|
1387
|
-
|
|
1388
|
-
|
|
1392
|
+
A plain async function created with \`handlers<Context>()\` and bound to the job protocol, exactly
|
|
1393
|
+
like an endpoint handler. It RETURNS its result; throwing a \`ResilientError\` subclass is how a
|
|
1394
|
+
refusal is reported, and the class survives the broker.
|
|
1389
1395
|
|
|
1390
1396
|
Two rules with no equivalent on the HTTP side:
|
|
1391
1397
|
- **Call \`job.touch()\` inside every long loop.** The broker judges liveness by the lock, and
|
|
@@ -1394,8 +1400,8 @@ Two rules with no equivalent on the HTTP side:
|
|
|
1394
1400
|
- **A processor must be safe to run twice.** Skip what a previous attempt recorded, or delete
|
|
1395
1401
|
what it created, and say in a comment which of the two this one does.
|
|
1396
1402
|
|
|
1397
|
-
|
|
1398
|
-
with \`context.
|
|
1403
|
+
Bind it in \`sources/worker/src/entrypoints.ts\` above the sentinel. Enqueue from an endpoint
|
|
1404
|
+
with the same typed call used for HTTP: \`context.entrypoint(appEntrypoints.job.<name>).call({ body: data })\`.
|
|
1399
1405
|
`),
|
|
1400
1406
|
|
|
1401
1407
|
skill(ViableSkill.TargetAgents, 'LLM agents inside the application', `
|
package/src/slot/consts.ts
CHANGED
|
@@ -41,14 +41,6 @@ export enum SlotCommandType {
|
|
|
41
41
|
|
|
42
42
|
export enum SlotGitCommand {
|
|
43
43
|
Ensure = 'ensure',
|
|
44
|
-
/**
|
|
45
|
-
* Bring a remote repository's tree onto a volume that already exists.
|
|
46
|
-
*
|
|
47
|
-
* NOT `git clone`: a slot's directory is never empty — provisioning leaves its own metadata
|
|
48
|
-
* there — and `git clone` refuses a non-empty destination. The executor therefore initializes,
|
|
49
|
-
* adds the remote, fetches at a bounded depth and checks the branch out over the tree.
|
|
50
|
-
*/
|
|
51
|
-
Clone = 'clone',
|
|
52
44
|
Status = 'status',
|
|
53
45
|
Commit = 'commit',
|
|
54
46
|
Log = 'log',
|
|
@@ -74,34 +66,6 @@ export enum SlotFileCommand {
|
|
|
74
66
|
DeleteFile = 'deleteFile',
|
|
75
67
|
FindFilesWithEnvVars = 'findFilesWithEnvVars',
|
|
76
68
|
GetRootPath = 'getRootPath',
|
|
77
|
-
/**
|
|
78
|
-
* List a tree with a size and a binary flag per entry, bounded by a caller-given limit.
|
|
79
|
-
*
|
|
80
|
-
* The reason it exists rather than being composed out of {@link SlotFileCommand.GetSourceList}
|
|
81
|
-
* plus a read per file: a foreign repository can hold a hundred thousand files, and one round
|
|
82
|
-
* trip each — over a signed HTTP call to a pod, or over a connector on somebody's laptop — is
|
|
83
|
-
* not a slower version of the same thing, it is a walk that never finishes.
|
|
84
|
-
*/
|
|
85
|
-
StatTree = 'statTree',
|
|
86
|
-
/**
|
|
87
|
-
* Read the first N bytes of one file.
|
|
88
|
-
*
|
|
89
|
-
* A census classifies a file from its head; reading whole files to do it would hold a 1 MB
|
|
90
|
-
* export in memory to look at its first line. The answer is text, so a binary head comes back
|
|
91
|
-
* as whatever decoding produced — which is exactly the signal the entropy classification wants.
|
|
92
|
-
*/
|
|
93
|
-
ReadHead = 'readHead',
|
|
94
|
-
/**
|
|
95
|
-
* Move everything in the project root into a subdirectory, keeping a named few in place.
|
|
96
|
-
*
|
|
97
|
-
* One command rather than a listing plus a move per file, because it must be ATOMIC from the
|
|
98
|
-
* caller's point of view: a relocation interrupted half way leaves a tree that is neither the
|
|
99
|
-
* origin nor a target, and nothing downstream can tell which files already moved. Binary-safe —
|
|
100
|
-
* it moves paths and never reads contents.
|
|
101
|
-
*/
|
|
102
|
-
Relocate = 'relocate',
|
|
103
|
-
/** Delete a directory and everything under it. The purge of a relocated origin. */
|
|
104
|
-
RemoveTree = 'removeTree',
|
|
105
69
|
}
|
|
106
70
|
|
|
107
71
|
export enum SlotShellCommand {
|
|
@@ -264,37 +228,6 @@ export const COMMAND_DEADLINES: Record<string, number> = {
|
|
|
264
228
|
|
|
265
229
|
export const DEFAULT_COMMAND_DEADLINE = 60_000
|
|
266
230
|
|
|
267
|
-
/**
|
|
268
|
-
* EXECUTOR-side ceilings for the git commands whose real duration is not a git command's.
|
|
269
|
-
*
|
|
270
|
-
* Everything git does inside a slot is local and takes seconds — except a clone, which is a
|
|
271
|
-
* network fetch of somebody else's repository and is measured in minutes. Bounding it by the
|
|
272
|
-
* generic git deadline made a conversion's very first step fail on any repository large enough
|
|
273
|
-
* to be worth converting.
|
|
274
|
-
*/
|
|
275
|
-
export const GIT_COMMAND_DEADLINES: Partial<Record<SlotGitCommand, number>> = {
|
|
276
|
-
[SlotGitCommand.Clone]: 600_000,
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** What every other git command gets. The value the type-level branch used to hard-code. */
|
|
280
|
-
export const DEFAULT_GIT_COMMAND_DEADLINE = 60_000
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* EXECUTOR-side ceilings for the file commands that walk or move a whole tree.
|
|
284
|
-
*
|
|
285
|
-
* The other file commands are one path each and finish in milliseconds; these three are bounded
|
|
286
|
-
* by how big the tree is, which for an origin project is set by whoever wrote it.
|
|
287
|
-
*/
|
|
288
|
-
export const FILE_COMMAND_DEADLINES: Partial<Record<SlotFileCommand, number>> = {
|
|
289
|
-
[SlotFileCommand.StatTree]: 120_000,
|
|
290
|
-
[SlotFileCommand.Relocate]: 300_000,
|
|
291
|
-
[SlotFileCommand.RemoveTree]: 120_000,
|
|
292
|
-
[SlotFileCommand.ReadHead]: 30_000,
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/** What every other file command gets. The value the type-level branch used to hard-code. */
|
|
296
|
-
export const DEFAULT_FILE_COMMAND_DEADLINE = 30_000
|
|
297
|
-
|
|
298
231
|
/**
|
|
299
232
|
* CALLER-side ceilings per command type: how long the asker waits for an answer.
|
|
300
233
|
*
|
|
@@ -333,33 +266,6 @@ export const SHELL_COMMAND_TIMEOUTS: Record<string, number> = {
|
|
|
333
266
|
[SlotShellCommand.Integrity]: 25_000, // executor deadline (15s) + margin
|
|
334
267
|
}
|
|
335
268
|
|
|
336
|
-
/**
|
|
337
|
-
* Per-COMMAND caller ceilings for git and for files.
|
|
338
|
-
*
|
|
339
|
-
* Each is its executor deadline plus the same 40 s margin: the caller's bound must outlast the
|
|
340
|
-
* executor's, or a command that failed cleanly inside its own deadline reaches the caller as a
|
|
341
|
-
* timeout and the real reason — the one the executor took the trouble to produce — is lost.
|
|
342
|
-
*/
|
|
343
|
-
export const GIT_COMMAND_TIMEOUTS: Partial<Record<SlotGitCommand, number>> = {
|
|
344
|
-
[SlotGitCommand.Clone]: 640_000,
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export const FILE_COMMAND_TIMEOUTS: Partial<Record<SlotFileCommand, number>> = {
|
|
348
|
-
[SlotFileCommand.StatTree]: 160_000,
|
|
349
|
-
[SlotFileCommand.Relocate]: 340_000,
|
|
350
|
-
[SlotFileCommand.RemoveTree]: 160_000,
|
|
351
|
-
[SlotFileCommand.ReadHead]: 70_000,
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/** The per-command caller bound for one command, or nothing where its type's default is right. */
|
|
355
|
-
const perCommandTimeout = (type: SlotCommandType, command: string): number | undefined => {
|
|
356
|
-
switch (type) {
|
|
357
|
-
case SlotCommandType.Shell: return SHELL_COMMAND_TIMEOUTS[command]
|
|
358
|
-
case SlotCommandType.Git: return GIT_COMMAND_TIMEOUTS[command as SlotGitCommand]
|
|
359
|
-
default: return FILE_COMMAND_TIMEOUTS[command as SlotFileCommand]
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
269
|
/**
|
|
364
270
|
* Resolve the caller-side bound for one command.
|
|
365
271
|
*
|
|
@@ -370,24 +276,12 @@ const perCommandTimeout = (type: SlotCommandType, command: string): number | und
|
|
|
370
276
|
export const commandTimeout = (
|
|
371
277
|
type: SlotCommandType, command: string, override?: number
|
|
372
278
|
): number => override
|
|
373
|
-
??
|
|
279
|
+
?? (type === SlotCommandType.Shell ? SHELL_COMMAND_TIMEOUTS[command] : undefined)
|
|
374
280
|
?? COMMAND_TIMEOUTS[type]
|
|
375
281
|
?? DEFAULT_COMMAND_TIMEOUT
|
|
376
282
|
|
|
377
|
-
/**
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
* reason.
|
|
383
|
-
*/
|
|
384
|
-
export const commandDeadline = (type: SlotCommandType, command: string): number => {
|
|
385
|
-
switch (type) {
|
|
386
|
-
case SlotCommandType.Shell:
|
|
387
|
-
return COMMAND_DEADLINES[command] ?? DEFAULT_COMMAND_DEADLINE
|
|
388
|
-
case SlotCommandType.Git:
|
|
389
|
-
return GIT_COMMAND_DEADLINES[command as SlotGitCommand] ?? DEFAULT_GIT_COMMAND_DEADLINE
|
|
390
|
-
default:
|
|
391
|
-
return FILE_COMMAND_DEADLINES[command as SlotFileCommand] ?? DEFAULT_FILE_COMMAND_DEADLINE
|
|
392
|
-
}
|
|
393
|
-
}
|
|
283
|
+
/** Resolve the executor-side deadline for one command. */
|
|
284
|
+
export const commandDeadline = (type: SlotCommandType, command: string): number =>
|
|
285
|
+
type === SlotCommandType.Shell
|
|
286
|
+
? COMMAND_DEADLINES[command] ?? DEFAULT_COMMAND_DEADLINE
|
|
287
|
+
: type === SlotCommandType.Git ? 60_000 : 30_000
|
package/src/slot/schemas.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSONSchemaType } from 'ajv'
|
|
2
2
|
import { SlotCommandType, SlotFileCommand, SlotGitCommand, SlotShellCommand } from './consts.js'
|
|
3
|
-
import type { SlotCommandPayload
|
|
3
|
+
import type { SlotCommandPayload } from './types.js'
|
|
4
4
|
|
|
5
5
|
export const SlotCommandPayloadSchema = {
|
|
6
6
|
type: 'object',
|
|
@@ -25,22 +25,3 @@ export const SlotCommandPayloadSchema = {
|
|
|
25
25
|
required: ['type', 'command'],
|
|
26
26
|
additionalProperties: false
|
|
27
27
|
} as JSONSchemaType<SlotCommandPayload>
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The clone command's arguments, as a signed body carries them.
|
|
31
|
-
*
|
|
32
|
-
* `token` is declared because it is SENT — a body field the schema does not declare is stripped
|
|
33
|
-
* by validation and then verified against a signature the sender made over the whole thing, which
|
|
34
|
-
* the guard reports as a 401 rather than as the validation gap it is.
|
|
35
|
-
*/
|
|
36
|
-
export const SlotGitCloneArgsSchema = {
|
|
37
|
-
type: 'object',
|
|
38
|
-
properties: {
|
|
39
|
-
remoteUrl: { type: 'string', minLength: 1, maxLength: 2048 },
|
|
40
|
-
branch: { type: 'string', maxLength: 256, nullable: true },
|
|
41
|
-
depth: { type: 'number', minimum: 0, nullable: true },
|
|
42
|
-
token: { type: 'string', maxLength: 4096, nullable: true },
|
|
43
|
-
},
|
|
44
|
-
required: ['remoteUrl'],
|
|
45
|
-
additionalProperties: false,
|
|
46
|
-
} as unknown as JSONSchemaType<SlotGitCloneArgs>
|