@plitzi/sdk-shared 0.38.14 → 0.38.15
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/CHANGELOG.md +102 -0
- package/dist/authoring/declare.d.ts +2 -1
- package/dist/authoring/index.mjs +2 -2
- package/dist/authoring/spaceCallbacks.d.ts +29 -0
- package/dist/authoring/spaceCallbacks.mjs +41 -20
- package/dist/realtime/topics.mjs +3 -3
- package/dist/types/ActionTypes.d.ts +38 -0
- package/dist/types/RealtimeTypes.d.ts +12 -0
- package/dist/types/ServerTypes.d.ts +22 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# @plitzi/sdk-shared
|
|
2
2
|
|
|
3
|
+
## 0.38.15
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6cdd5cf: ## Security
|
|
8
|
+
|
|
9
|
+
- **A write another site caused is refused, cookie or none** (`@plitzi/sdk-server`, `@plitzi/sdk-shared`): the page
|
|
10
|
+
server guarded nothing it answers past `/auth` — a server action (`/_action`), a realtime publish, a space's `/fn/`
|
|
11
|
+
route, its runtime's endpoints, the deployment's own `data` stages, the adapter-only `loginPath`, `logoutPath` and
|
|
12
|
+
`exchangePath`. A plain `text/plain` POST from any page open in the same browser ran them, with the visitor's session
|
|
13
|
+
cookie when there was one (`SameSite=None` off this machine) and, in a project nobody signs in to, as whoever holds
|
|
14
|
+
the server's own credentials. Every unsafe request a browser says another site caused (`Sec-Fetch-Site: cross-site`,
|
|
15
|
+
or a foreign `Origin`) is now refused `403 reason: 'foreign'`, saying how to allow it: `crossSiteWrites.origins`
|
|
16
|
+
(with `auth`, `identity.platformOrigins` too) and `crossSiteWrites.paths` for a form other sites post to by design.
|
|
17
|
+
`Authorization`, and callers that are not browsers — webhooks, scripts — pass as before. One rule for this and the
|
|
18
|
+
sign-in check (`crossSiteRequest`, `csrf.ts`). A self-hosted runtime's endpoints now sit where the platform forwards a
|
|
19
|
+
space's — after the auth chain and this guard (`data`, was `preAuth`).
|
|
20
|
+
|
|
21
|
+
## Testing
|
|
22
|
+
- **`page check` and `page shot` anywhere the project is served** (`@plitzi/cli`): `--url <origin>` for a second server
|
|
23
|
+
of it, staging or a deploy, instead of `npm start`'s port; `page check --space <module>` holds the pages to that space
|
|
24
|
+
instead of the project's — an agent's page — authored with `authorIsolated`.
|
|
25
|
+
- **A route with a query is checked as its page** (`@plitzi/cli`): `page check "/?v=2"` matched the query as part of
|
|
26
|
+
the path, found no page, and checked the space's "not found" page in its place.
|
|
27
|
+
|
|
28
|
+
- **What an element that chooses its children draws is checked** (`@plitzi/sdk-authoring`, `@plitzi/cli`): with
|
|
29
|
+
`choosesChildren`, the page checks owed none of its children — and checked none of them either: a dashboard drawing
|
|
30
|
+
three panels reported `checked: 1`. Each child it may draw, and what is inside one under no condition of its own,
|
|
31
|
+
now carries `shownWith` (the child it comes with) on its handle: once that child is on screen it and everything
|
|
32
|
+
named in it are owed like the rest of the page, and `checked` counts them. `onScreenIfChosen(handles, page)` lists
|
|
33
|
+
them.
|
|
34
|
+
- **A page that met a server error is looked at once more** (`@plitzi/cli`): `page check` and `verify` failed a page
|
|
35
|
+
for a `500` from somebody else's service having a bad second (`/fn/<space>/weather`), and passed on the next run with
|
|
36
|
+
nothing changed. A page whose requests got a `5xx` is checked again once; its report is the second look, and says
|
|
37
|
+
what failed the first time (`retriedAfter`). A `4xx` fails the same way twice, and is never retried.
|
|
38
|
+
|
|
39
|
+
## Plugins
|
|
40
|
+
- **`useNotify`** (`@plitzi/plitzi-sdk`): a plugin shows the page's notifications — `notify('Deleted 79 recordings', {
|
|
41
|
+
appearance: 'danger' })` — the same toasts an `addNotification` step shows, in the space's theme and `notifications`
|
|
42
|
+
look, with no trigger, flow or `window` event of the space's to wire. The step's vocabulary (`NOTIFICATION_PLACEMENTS`,
|
|
43
|
+
`NOTIFICATION_APPEARANCES`, `NOTIFICATION_DEFAULTS`) is one list in `@plitzi/sdk-shared`, read by both.
|
|
44
|
+
- **`appearance: 'danger'` looks like one** (`@plitzi/plitzi-sdk`): it reached the toast library as a type it does not
|
|
45
|
+
know, so a danger notification showed neither the error icon nor the colour `notifications.danger` sets.
|
|
46
|
+
A value a notification does not take — from a flow, or a plugin in plain JavaScript — is set aside by name and said
|
|
47
|
+
in the console, rather than shown as the default in silence (`readNotificationOptions`, `@plitzi/sdk-shared`).
|
|
48
|
+
- **`useServerAction`** (`@plitzi/plitzi-sdk`, `@plitzi/sdk-authoring`, `@plitzi/sdk-interactions`,
|
|
49
|
+
`@plitzi/sdk-server`): a plugin runs one of the space's server actions, awaited —
|
|
50
|
+
`useServerAction<typeof habitDone>('habits-done')({ id })` answers `{ ok, output }` or `{ ok: false, reason, error }`
|
|
51
|
+
— through the `runServerAction` step itself: the same access, refusals, session handling and dev-tools entry.
|
|
52
|
+
`defineAction` keeps the literal types of its `call` way in, so the input is typed by the fields it declares (the
|
|
53
|
+
required ones required); `taskAction` types both ends from the task's `run`, and `FunctionTask<T, R>` takes what
|
|
54
|
+
`run` answers. `CallInputOf` / `CallOutputOf` read them. One operation is one action, never a `case` in one route.
|
|
55
|
+
What a run answers is typed (`ServerActionStatus`): a status the server never uses reads as a run that did not
|
|
56
|
+
complete, never as one that did.
|
|
57
|
+
The key those types ride under (`CALL_TYPES`) is exported, by `@plitzi/sdk-shared` and `@plitzi/sdk-authoring` with
|
|
58
|
+
`TypedActionEntry`, `CallInputOf` and `CallOutputOf`: a package that emits declarations could not write the type of an
|
|
59
|
+
action it exports (`TS4023` on every `export const actions = [defineAction(…)]`).
|
|
60
|
+
- **A plugin is drawn on the server first** (docs): a preference per device belongs in kept state (`keepState`,
|
|
61
|
+
`paintedState`) as a bound prop, never read from `localStorage` while rendering — React discards the whole tree on the
|
|
62
|
+
mismatch, and only in the browser of whoever saved something. `@plitzi/plitzi-ui` is not the page's: a plugin
|
|
63
|
+
importing it bundles its own copy, with its own contexts and its own look. The authoring skill's `plugin-reach.md`
|
|
64
|
+
says what a plugin says and to whom: a notification, another element (an event and the space's flow), a server
|
|
65
|
+
action, shared state, other pages.
|
|
66
|
+
|
|
67
|
+
## Realtime
|
|
68
|
+
- **A channel that retains** (`@plitzi/sdk-shared`, `@plitzi/sdk-server`, `@plitzi/sdk-elements`): declared
|
|
69
|
+
`retain: true` (with `publish: 'server'`, which authoring requires), the server keeps the latest message of each type
|
|
70
|
+
on a topic — said by `ctx.publish` or a flow's `realtime.publish` — and hands them to every page that joins, marked
|
|
71
|
+
`retained`. A page reads them as `<channel>.retained.<type>`, a plugin as `useChannel(topic).retained`, the newest of
|
|
72
|
+
a type holding whichever order they arrive in; `null` forgets a type. Kept in the store the actions' `kv` uses (beside
|
|
73
|
+
the grants, by compare-and-swap), so every replica hands out the same; a topic keeps at most 64 types and 512 KB, and
|
|
74
|
+
a publish past that is refused before anything is sent. State every screen shares is one publish and one binding —
|
|
75
|
+
no kv key, initial read or copy in `state` of the project's own. The fleet store `realtime.grants` is now
|
|
76
|
+
`realtime.kv`.
|
|
77
|
+
- **The MCP writes every field a channel takes** (`@plitzi/sdk-mcp`): `patchSettings`'s channel had no `grant` — which
|
|
78
|
+
the guide taught — nor `retain` or the limits, so an agent could not declare a private channel at all. All are there
|
|
79
|
+
now, and the guide says what `retain` is for and that `channel_<id>.retained` holds it.
|
|
80
|
+
|
|
81
|
+
## Authoring
|
|
82
|
+
- **`plitzi explain` knows every `@plitzi` package installed** (`@plitzi/cli`): a name that was not
|
|
83
|
+
`@plitzi/sdk-authoring`'s (`createServer`, `createJsonAdapters`, `useChannel`) was answered as the project's own code,
|
|
84
|
+
to `grep` in `src/`. It is read from the published types of every one installed, through their re-exports and entry
|
|
85
|
+
points (`@plitzi/sdk-server/handlers`), and a name two packages export — `createServer` is sdk-server's and sdk-mcp's
|
|
86
|
+
— is answered with both.
|
|
87
|
+
|
|
88
|
+
- **A space module somebody else wrote, authored where it reaches nothing** (`@plitzi/sdk-authoring`):
|
|
89
|
+
`authorIsolated(file, { timeoutMs })` of `@plitzi/sdk-authoring/node` runs `authorSpace` on it in a process Node's
|
|
90
|
+
permission model holds to reading the module's folder and its packages — no file written, no process started, no
|
|
91
|
+
variable of the caller's environment — kills one that runs too long, and validates what it answers again, so a forged
|
|
92
|
+
answer gains nothing. A refusal comes back as `authorSpace` says it, with the problems as data.
|
|
93
|
+
- **Pages an agent writes, served** (`@plitzi/sdk-server`): `createAuthoringHost({ dir, frameAncestors })` serves each
|
|
94
|
+
space it is handed at an address of its own (`put(space, { id }) → /?__pt=<id>`) as a draft of its one space, kept in
|
|
95
|
+
`dir` across restarts; `drop(id)` forgets one, and anything else answers `404` saying where the pages are.
|
|
96
|
+
- **A space is chosen by host or credential** (`@plitzi/sdk-server`, docs): `getSpaceDeployment` is asked on every
|
|
97
|
+
request, and what a page fetches after it carries nothing of its address but the host — a space chosen by the page's
|
|
98
|
+
query or path lost everything it asked for, and `/_plitzi/space/<hash>.json` answered a bare `404`. The contract and
|
|
99
|
+
the README say so (the README offered "by path prefix"), and that `404` says why.
|
|
100
|
+
|
|
101
|
+
- **`plitzi explain on` says how to read what a trigger carries** (`@plitzi/sdk-authoring`): its doc sat above a
|
|
102
|
+
helper, so `explain` printed only the signature. It now leads with `[named('picked', on('onPick')), …('{{ picked.seat
|
|
103
|
+
}}')]`, and the CLI skill's plugin section says the same.
|
|
104
|
+
|
|
3
105
|
## 0.38.14
|
|
4
106
|
|
|
5
107
|
### Patch Changes
|
|
@@ -71,7 +71,8 @@ export interface ElementDeclarationData {
|
|
|
71
71
|
/**
|
|
72
72
|
* Whether the element shows only some of the children put inside it, chosen as it runs — a dashboard showing the
|
|
73
73
|
* panels of the layout saved for this device, a wizard its current step. Read by the page checks, which then owe none
|
|
74
|
-
* of them on screen;
|
|
74
|
+
* of them on screen; the ones it draws are checked like the rest of the page — there, visible, and what is named
|
|
75
|
+
* inside them too.
|
|
75
76
|
*/
|
|
76
77
|
choosesChildren?: boolean;
|
|
77
78
|
/**
|
package/dist/authoring/index.mjs
CHANGED
|
@@ -2,5 +2,5 @@ import { toBuilderParams as e, toInteractionCallback as t, toInteractionCallback
|
|
|
2
2
|
import { definePlugin as r, elementDeclaration as i, valuesOf as a } from "./declare.mjs";
|
|
3
3
|
import { BUILTIN_PARAM_TYPES as o, hiddenParams as s, invalidParams as c, missingRequiredParams as l, reconcileParams as u } from "./paramSpec.mjs";
|
|
4
4
|
import { BUILTIN_ELEMENT_CALLBACKS as d, SCROLL_CALLBACKS as f, applyElementCallback as p, getElementCallback as m } from "./elementCallbacks.mjs";
|
|
5
|
-
import {
|
|
6
|
-
export { d as BUILTIN_ELEMENT_CALLBACKS, o as BUILTIN_PARAM_TYPES, f as SCROLL_CALLBACKS, p as applyElementCallback, r as definePlugin, i as elementDeclaration, m as getElementCallback, s as hiddenParams, c as invalidParams, l as missingRequiredParams, u as reconcileParams,
|
|
5
|
+
import { NOTIFICATION_APPEARANCES as h, NOTIFICATION_DEFAULTS as g, NOTIFICATION_PLACEMENTS as _, readNotificationOptions as v, spaceCallbacks as y } from "./spaceCallbacks.mjs";
|
|
6
|
+
export { d as BUILTIN_ELEMENT_CALLBACKS, o as BUILTIN_PARAM_TYPES, h as NOTIFICATION_APPEARANCES, g as NOTIFICATION_DEFAULTS, _ as NOTIFICATION_PLACEMENTS, f as SCROLL_CALLBACKS, p as applyElementCallback, r as definePlugin, i as elementDeclaration, m as getElementCallback, s as hiddenParams, c as invalidParams, l as missingRequiredParams, v as readNotificationOptions, u as reconcileParams, y as spaceCallbacks, e as toBuilderParams, t as toInteractionCallback, n as toInteractionCallbacks, a as valuesOf };
|
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
import { BuiltinGlobalCallback } from './builder';
|
|
2
|
+
/** Where a notification may appear: the `addNotification` step's `placement`, and `useNotify`'s. */
|
|
3
|
+
export declare const NOTIFICATION_PLACEMENTS: readonly ["top-right", "top-center", "top-left", "bottom-right", "bottom-center", "bottom-left"];
|
|
4
|
+
export type NotificationPlacement = (typeof NOTIFICATION_PLACEMENTS)[number];
|
|
5
|
+
/** How a notification looks: the `addNotification` step's `appearance`, and `useNotify`'s. */
|
|
6
|
+
export declare const NOTIFICATION_APPEARANCES: readonly ["success", "danger", "warning", "info"];
|
|
7
|
+
export type NotificationAppearance = (typeof NOTIFICATION_APPEARANCES)[number];
|
|
8
|
+
/** What a notification takes besides its text — every one with the default the `addNotification` step has. */
|
|
9
|
+
export type NotificationOptions = {
|
|
10
|
+
placement?: NotificationPlacement;
|
|
11
|
+
appearance?: NotificationAppearance;
|
|
12
|
+
autoDismiss?: boolean;
|
|
13
|
+
/** Milliseconds before it dismisses itself, with `autoDismiss`. */
|
|
14
|
+
autoDismissTimeout?: number;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* What a notification was handed, kept to what it takes — a flow's values arrive as written, and a plugin may be plain
|
|
18
|
+
* JavaScript — with each field set aside named, so the caller says so rather than showing the default in silence.
|
|
19
|
+
*/
|
|
20
|
+
export declare const readNotificationOptions: (given: Readonly<Record<string, unknown>>) => {
|
|
21
|
+
options: NotificationOptions;
|
|
22
|
+
ignored: string[];
|
|
23
|
+
};
|
|
24
|
+
/** The defaults of a notification, the step's and the hook's alike. */
|
|
25
|
+
export declare const NOTIFICATION_DEFAULTS: {
|
|
26
|
+
readonly placement: "top-right";
|
|
27
|
+
readonly appearance: "success";
|
|
28
|
+
readonly autoDismiss: true;
|
|
29
|
+
readonly autoDismissTimeout: 5000;
|
|
30
|
+
};
|
|
2
31
|
/**
|
|
3
32
|
* The `space` source, which the SDK application registers rather than this package.
|
|
4
33
|
*
|
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
//#region src/authoring/spaceCallbacks.ts
|
|
2
|
-
var e =
|
|
2
|
+
var e = [
|
|
3
|
+
"top-right",
|
|
4
|
+
"top-center",
|
|
5
|
+
"top-left",
|
|
6
|
+
"bottom-right",
|
|
7
|
+
"bottom-center",
|
|
8
|
+
"bottom-left"
|
|
9
|
+
], t = [
|
|
10
|
+
"success",
|
|
11
|
+
"danger",
|
|
12
|
+
"warning",
|
|
13
|
+
"info"
|
|
14
|
+
], n = (n) => {
|
|
15
|
+
let r = e.find((e) => e === n.placement), i = t.find((e) => e === n.appearance), { autoDismiss: a, autoDismissTimeout: o } = n, s = typeof o == "number" && Number.isFinite(o) && o > 0 ? o : void 0, c = {
|
|
16
|
+
placement: r !== void 0,
|
|
17
|
+
appearance: i !== void 0,
|
|
18
|
+
autoDismiss: typeof a == "boolean",
|
|
19
|
+
autoDismissTimeout: s !== void 0
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
options: {
|
|
23
|
+
...r === void 0 ? {} : { placement: r },
|
|
24
|
+
...i === void 0 ? {} : { appearance: i },
|
|
25
|
+
...typeof a == "boolean" ? { autoDismiss: a } : {},
|
|
26
|
+
...s === void 0 ? {} : { autoDismissTimeout: s }
|
|
27
|
+
},
|
|
28
|
+
ignored: Object.entries(c).filter(([e, t]) => !t && n[e] !== void 0).map(([e]) => e)
|
|
29
|
+
};
|
|
30
|
+
}, r = {
|
|
31
|
+
placement: "top-right",
|
|
32
|
+
appearance: "success",
|
|
33
|
+
autoDismiss: !0,
|
|
34
|
+
autoDismissTimeout: 5e3
|
|
35
|
+
}, i = { addNotification: {
|
|
3
36
|
source: "space",
|
|
4
37
|
title: "Add Notification",
|
|
5
38
|
strictParams: !0,
|
|
@@ -12,40 +45,28 @@ var e = { addNotification: {
|
|
|
12
45
|
placement: {
|
|
13
46
|
type: "select",
|
|
14
47
|
description: "Where the toast appears on screen.",
|
|
15
|
-
default:
|
|
16
|
-
options: [
|
|
17
|
-
"top-right",
|
|
18
|
-
"top-center",
|
|
19
|
-
"top-left",
|
|
20
|
-
"bottom-right",
|
|
21
|
-
"bottom-center",
|
|
22
|
-
"bottom-left"
|
|
23
|
-
]
|
|
48
|
+
default: r.placement,
|
|
49
|
+
options: [...e]
|
|
24
50
|
},
|
|
25
51
|
appearance: {
|
|
26
52
|
type: "select",
|
|
27
53
|
description: "Visual style of the notification: success, danger, warning or info.",
|
|
28
|
-
default:
|
|
29
|
-
options: [
|
|
30
|
-
"success",
|
|
31
|
-
"danger",
|
|
32
|
-
"warning",
|
|
33
|
-
"info"
|
|
34
|
-
]
|
|
54
|
+
default: r.appearance,
|
|
55
|
+
options: [...t]
|
|
35
56
|
},
|
|
36
57
|
autoDismiss: {
|
|
37
58
|
type: "boolean",
|
|
38
59
|
description: "Whether the notification dismisses itself after a timeout.",
|
|
39
|
-
default:
|
|
60
|
+
default: r.autoDismiss,
|
|
40
61
|
canBind: !1
|
|
41
62
|
},
|
|
42
63
|
autoDismissTimeout: {
|
|
43
64
|
type: "number",
|
|
44
65
|
description: "Milliseconds before auto-dismiss. Only applies when autoDismiss is true.",
|
|
45
|
-
default:
|
|
66
|
+
default: r.autoDismissTimeout,
|
|
46
67
|
when: (e) => e.autoDismiss === !0
|
|
47
68
|
}
|
|
48
69
|
}
|
|
49
70
|
} };
|
|
50
71
|
//#endregion
|
|
51
|
-
export { e as spaceCallbacks };
|
|
72
|
+
export { t as NOTIFICATION_APPEARANCES, r as NOTIFICATION_DEFAULTS, e as NOTIFICATION_PLACEMENTS, n as readNotificationOptions, i as spaceCallbacks };
|
package/dist/realtime/topics.mjs
CHANGED
|
@@ -14,9 +14,9 @@ var t = /^[A-Za-z0-9:_.-]{1,128}$/, n = "[A-Za-z0-9_.-]{1,64}", r = (e) => t.tes
|
|
|
14
14
|
]), c = (t, n) => {
|
|
15
15
|
let r = [];
|
|
16
16
|
if (o.test(t) || r.push("a pattern is letters, digits and `:_.-`, with `{name}` for the part a page fills in — `board:{id}`"), !e(n)) return [...r, "a channel is an object: `{ access: { mode: \"public\" } }`"];
|
|
17
|
-
let { access: i, publish: a, presence: c, grant: l,
|
|
18
|
-
typeof
|
|
19
|
-
for (let [e, t] of [["maxMessageBytes",
|
|
17
|
+
let { access: i, publish: a, presence: c, grant: l, retain: u, maxMessageBytes: d, messagesPerSecond: f } = n, p = e(i) ? i.mode : void 0;
|
|
18
|
+
typeof p != "string" || !s.has(p) ? r.push("`access` is { mode: \"public\" }, { mode: \"session\" } or { mode: \"role\", permissions: […] }") : p === "role" && !(e(i) && Array.isArray(i.permissions) && i.permissions.every((e) => typeof e == "string")) && r.push("`access: { mode: \"role\" }` names the permissions it needs: `permissions: [\"boardEdit\"]`"), a !== void 0 && a !== "clients" && a !== "server" && r.push("`publish` is \"clients\" (pages send) or \"server\" (only a flow's realtime.publish)"), c !== void 0 && typeof c != "boolean" && r.push("`presence` is true or false"), l !== void 0 && typeof l != "boolean" && r.push("`grant` is true (a page needs a grant from `realtime.grant` to open a topic) or false"), u !== void 0 && typeof u != "boolean" ? r.push("`retain` is true (the server keeps the latest message of each type for pages that join) or false") : u === !0 && a !== "server" && r.push("`retain: true` keeps what the server says, so only the server may speak: add `publish: \"server\"` — a page changes it through an action");
|
|
19
|
+
for (let [e, t] of [["maxMessageBytes", d], ["messagesPerSecond", f]]) t !== void 0 && (typeof t != "number" || !Number.isInteger(t) || t <= 0) && r.push(`\`${e}\` is a whole number above zero`);
|
|
20
20
|
return r;
|
|
21
21
|
}, l = (e) => ({
|
|
22
22
|
maxMessageBytes: e.maxMessageBytes ?? 4096,
|
|
@@ -17,6 +17,43 @@ export type ActionField = {
|
|
|
17
17
|
/** Shown in the builder, and read by the MCP agent when it wires a call. */
|
|
18
18
|
label?: string;
|
|
19
19
|
};
|
|
20
|
+
/** What a caller sends for a field of a type: text for `text`, `date` and `file`, anything parseable for `json`. */
|
|
21
|
+
type ActionFieldValue<T extends ActionFieldType> = T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'json' ? unknown : string;
|
|
22
|
+
/** The input a caller hands an action, from the fields it declares: those `required: true` must be there. */
|
|
23
|
+
export type ActionInputOf<Fields extends Readonly<Record<string, ActionField>>> = {
|
|
24
|
+
-readonly [K in keyof Fields as Fields[K]['required'] extends true ? K : never]: ActionFieldValue<Fields[K]['type']>;
|
|
25
|
+
} & {
|
|
26
|
+
-readonly [K in keyof Fields as Fields[K]['required'] extends true ? never : K]?: ActionFieldValue<Fields[K]['type']>;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The key a typed action's input and output ride under, in its type only — nothing holds it at run time. Exported so a
|
|
30
|
+
* package that emits declarations can name the type of an action it exports (`export const actions = [defineAction(…)]`):
|
|
31
|
+
* a symbol it cannot import is one its `.d.ts` cannot write (TS4023).
|
|
32
|
+
*/
|
|
33
|
+
export declare const CALL_TYPES: unique symbol;
|
|
34
|
+
/**
|
|
35
|
+
* An action whose `call` way in is known to the types — what it takes and what it answers: what `defineAction` and
|
|
36
|
+
* `taskAction` answer, so a page that runs it (`useServerAction<typeof saveHabit>('save-habit')`) is held to the input
|
|
37
|
+
* it declares and handed the output typed. Nothing of it exists at run time — the action is the same document.
|
|
38
|
+
*/
|
|
39
|
+
export type TypedActionEntry<Input, Output = unknown> = ActionEntry & {
|
|
40
|
+
readonly [CALL_TYPES]?: {
|
|
41
|
+
input: Input;
|
|
42
|
+
output: Output;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** The input an action's `call` way in takes, from its type — any object, for an action whose type does not say. */
|
|
46
|
+
export type CallInputOf<Action> = Action extends {
|
|
47
|
+
readonly [CALL_TYPES]?: {
|
|
48
|
+
input: infer Input;
|
|
49
|
+
};
|
|
50
|
+
} ? unknown extends Input ? Record<string, unknown> : Input : Record<string, unknown>;
|
|
51
|
+
/** What an action answers, from its type — `unknown` for one whose type does not say. */
|
|
52
|
+
export type CallOutputOf<Action> = Action extends {
|
|
53
|
+
readonly [CALL_TYPES]?: {
|
|
54
|
+
output: infer Output;
|
|
55
|
+
};
|
|
56
|
+
} ? Output : unknown;
|
|
20
57
|
/**
|
|
21
58
|
* Who may start a run through ONE way in. Resolved against the auth kernel — never re-implemented here.
|
|
22
59
|
*
|
|
@@ -709,3 +746,4 @@ export type ActionJobQueue = {
|
|
|
709
746
|
olderThan?: string;
|
|
710
747
|
}) => Promise<number>;
|
|
711
748
|
};
|
|
749
|
+
export {};
|
|
@@ -22,6 +22,13 @@ export type ChannelDeclaration = {
|
|
|
22
22
|
* a locked board.
|
|
23
23
|
*/
|
|
24
24
|
grant?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Whether the server keeps the latest message of each type on a topic, and hands them to every page that joins: state
|
|
27
|
+
* all the screens share — a dashboard's lists, a board's settings — said once by the server (`ctx.publish`, a flow's
|
|
28
|
+
* `realtime.publish`) and there for a page that opens later, as `<channel>.retained.<type>`. Kept until the next of
|
|
29
|
+
* its type; `null` forgets it. Only on a channel the server alone publishes on (`publish: 'server'`).
|
|
30
|
+
*/
|
|
31
|
+
retain?: boolean;
|
|
25
32
|
/** The largest `data` a page may send, as JSON bytes. 4 KB when absent. */
|
|
26
33
|
maxMessageBytes?: number;
|
|
27
34
|
/** How many messages one connection may send a second. 30 when absent. */
|
|
@@ -55,6 +62,11 @@ export type RealtimeMessage = RealtimeSender & {
|
|
|
55
62
|
* knows it for its own by `from` — its channel's `me`. Without it, a page never hears what it sent.
|
|
56
63
|
*/
|
|
57
64
|
echo?: true;
|
|
65
|
+
/**
|
|
66
|
+
* Kept by the server: on a `retain: true` channel, the latest message of its type — sent as it is said, and again to
|
|
67
|
+
* every page that joins. The newest of a type is the one that holds; `null` data forgets it.
|
|
68
|
+
*/
|
|
69
|
+
retained?: true;
|
|
58
70
|
};
|
|
59
71
|
/** What a page asks of a publish beyond the message itself. */
|
|
60
72
|
export type RealtimePublishOptions = {
|
|
@@ -359,8 +359,15 @@ export type SSRAdapters = {
|
|
|
359
359
|
* dedicated MCP server renders nothing, reads schema and style through its own adapters, and had to hand this
|
|
360
360
|
* one over anyway for the type to be satisfied — an adapter it never called. */
|
|
361
361
|
getOfflineData?: (spaceId: number, environment: string, revision?: number) => Promise<OfflineDataRaw | undefined>;
|
|
362
|
-
/**
|
|
363
|
-
*
|
|
362
|
+
/**
|
|
363
|
+
* Which space, environment and revision a request resolves to. Optional here for the same reason as
|
|
364
|
+
* `getOfflineData`: MCP resolves its space from the request token (`getGrant`), never from the host.
|
|
365
|
+
*
|
|
366
|
+
* Asked on EVERY request, not only a page's: what the page fetches after it — `/_plitzi/space/<hash>.json`, `/_action`,
|
|
367
|
+
* `/fn/…`, `/_realtime` — is resolved again, and carries nothing of the page's address but its host (and the
|
|
368
|
+
* visitor's credentials). So a space is chosen by the host — a domain, a sub-domain (`wildcardSubdomain`) — or by a
|
|
369
|
+
* credential (`authoringPreview`): one chosen by the page's path or query is lost on the requests that follow it.
|
|
370
|
+
*/
|
|
364
371
|
getSpaceDeployment?: (req: SSRRequest) => Promise<SSRSpaceDeployment>;
|
|
365
372
|
/** Resolve the grant the MCP request operates under, from the verified `Authorization` bearer. The consumer
|
|
366
373
|
* owns the JWT secret and the authorization model, so it decides here; the MCP service stays stateless.
|
|
@@ -1033,6 +1040,19 @@ export type SSRServerConfig = {
|
|
|
1033
1040
|
};
|
|
1034
1041
|
/** Realtime channels the spaces declare — see {@link SSRRealtimeConfig}. On, in memory, when absent. */
|
|
1035
1042
|
realtime?: SSRRealtimeConfig;
|
|
1043
|
+
/**
|
|
1044
|
+
* The writes a page on another site may cause here. None, by default: a browser's POST, PUT, PATCH or DELETE that it
|
|
1045
|
+
* says another site caused (`Sec-Fetch-Site: cross-site`, or a foreign `Origin`) is refused 403 `reason: 'foreign'` —
|
|
1046
|
+
* a session cookie or none, because a server holding credentials of its own, or a tool nobody signs in to, acts for
|
|
1047
|
+
* whoever reaches it. A request with `Authorization`, and one that is not a browser's (a webhook, a script), are never
|
|
1048
|
+
* asked. With `auth`, the deployment's `identity.platformOrigins` count as its own too.
|
|
1049
|
+
*/
|
|
1050
|
+
crossSiteWrites?: {
|
|
1051
|
+
/** Origins whose pages may write here, exactly as a browser sends them — `https://app.example.com`. */
|
|
1052
|
+
origins?: string[];
|
|
1053
|
+
/** Paths any site may post to: a form other sites embed by design. */
|
|
1054
|
+
paths?: (path: string) => boolean;
|
|
1055
|
+
};
|
|
1036
1056
|
/** Connector manifest and credential lookups — see {@link ConnectorLookupsConfig}. They serve the RSC read path
|
|
1037
1057
|
* and the `/_action` write endpoint alike; without them neither can reach a connector. */
|
|
1038
1058
|
connectors?: ConnectorLookupsConfig;
|