@plitzi/sdk-server 0.38.10 → 0.38.12
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 +287 -0
- package/README.md +3 -3
- package/dist/auth.js +1 -1
- package/dist/core/auth/createAuth.js +1 -1
- package/dist/core/http/stages/signInRoute.js +1 -1
- package/dist/core/server/baseServer.js +11 -6
- package/dist/core/server/fleet/stores.d.ts +7 -0
- package/dist/core/server/fleet/stores.js +16 -2
- package/dist/core/server/fleet/supervisor.d.ts +2 -0
- package/dist/core/server/fleet/supervisor.js +14 -2
- package/dist/core/server/pageServer.js +1 -1
- package/dist/functions-api.d.ts +47 -2
- package/dist/helpers/cache/cacheManager.d.ts +9 -2
- package/dist/helpers/cache/cacheManager.js +46 -22
- package/dist/helpers/cache/keys.d.ts +12 -5
- package/dist/helpers/cache/keys.js +56 -11
- package/dist/modules/actions/index.js +6 -2
- package/dist/modules/actions/jobs/later.d.ts +43 -0
- package/dist/modules/actions/jobs/later.js +66 -0
- package/dist/modules/actions/jobs/later.test.d.ts +1 -0
- package/dist/modules/actions/jobs/memoryQueue.js +14 -1
- package/dist/modules/actions/jobs/worker.js +2 -2
- package/dist/modules/actions/runtime/credentials.d.ts +10 -0
- package/dist/modules/actions/runtime/credentials.js +28 -0
- package/dist/modules/actions/runtime/credentials.test.d.ts +1 -0
- package/dist/modules/actions/runtime/fileKv.d.ts +8 -3
- package/dist/modules/actions/runtime/fileKv.js +79 -7
- package/dist/modules/actions/runtime/precheck.js +1 -1
- package/dist/modules/actions/runtime/rateLimit.d.ts +7 -0
- package/dist/modules/actions/runtime/rateLimit.js +7 -1
- package/dist/modules/actions/runtime/runAction.d.ts +4 -1
- package/dist/modules/actions/runtime/runAction.js +46 -11
- package/dist/modules/actions/runtime/taskContext.d.ts +4 -1
- package/dist/modules/actions/runtime/taskContext.js +7 -1
- package/dist/modules/actions/tasks/flow.js +113 -35
- package/dist/modules/actions/types.d.ts +8 -0
- package/dist/modules/functions/build.js +1 -1
- package/dist/modules/functions/capabilities.js +21 -0
- package/dist/modules/functions/context.js +9 -3
- package/dist/modules/functions/contract.d.ts +10 -0
- package/dist/modules/functions/driver.js +16 -4
- package/dist/modules/functions/local.d.ts +1 -1
- package/dist/modules/functions/local.js +2 -1
- package/dist/modules/functions/manifest.js +8 -3
- package/dist/modules/functions/protocol.d.ts +7 -0
- package/dist/modules/mongo/jobQueue.js +26 -0
- package/dist/modules/mysql/jobs/jobQueue.js +21 -2
- package/dist/modules/mysql/jobs/schema.d.ts +3 -2
- package/dist/modules/mysql/jobs/schema.js +24 -11
- package/dist/modules/project/plugins.d.ts +1 -1
- package/dist/modules/project/serveProject.js +29 -4
- package/dist/modules/realtime/handlers.d.ts +4 -1
- package/dist/modules/realtime/handlers.js +8 -4
- package/dist/modules/realtime/hub.d.ts +3 -1
- package/dist/modules/realtime/hub.js +8 -4
- package/dist/modules/ssr/prepareRender.js +1 -1
- package/package.json +3 -3
- package/skills/plitzi-authoring/CHEATSHEET.md +4 -2
- package/skills/plitzi-authoring/recipes/plugin.ts +9 -7
- package/skills/plitzi-authoring/recipes/style-a-page.ts +1 -1
- package/skills/plitzi-authoring/reference/accessibility.md +1 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +8 -4
- package/skills/plitzi-authoring/reference/data-and-visibility.md +11 -10
- package/skills/plitzi-authoring/reference/elements-and-styles.md +1 -1
- package/skills/plitzi-authoring/reference/flows.md +6 -5
- package/skills/plitzi-authoring/reference/layouts.md +1 -0
- package/skills/plitzi-authoring/reference/lists.md +1 -0
- package/skills/plitzi-authoring/reference/plugins.md +43 -37
- package/skills/plitzi-authoring/reference/realtime.md +4 -1
- package/skills/plitzi-authoring/reference/rules.md +1 -1
- package/skills/plitzi-authoring/reference/typed-sources.md +6 -4
|
@@ -69,12 +69,11 @@ A step's params are templates, evaluated in full (conditions, loops, filters). A
|
|
|
69
69
|
a row's button posts `{ jobId: '{{ list_jobRows.item.id }}' }` — the row that was clicked; the short name is refused.
|
|
70
70
|
|
|
71
71
|
**Pass objects, not JSON text.** `input: { title: '{{ form.values.title }}' }` rather than `input: '{"title": …}'`:
|
|
72
|
-
each value keeps its own type, and nothing depends on the quoting.
|
|
73
|
-
is escaped for it — but an object says the same without the punctuation.)
|
|
72
|
+
each value keeps its own type, and nothing depends on the quoting.
|
|
74
73
|
|
|
75
74
|
**A field left empty is not in `values`.** A form sends what was typed, and a field nobody typed in sends nothing —
|
|
76
75
|
so `when({ field: 'sent.values.code', operator: '=', value: '' }, …)` never holds for it. Ask
|
|
77
|
-
`operator: 'empty'` / `'notEmpty'
|
|
76
|
+
`operator: 'empty'` / `'notEmpty'` (no `value`), which treat missing and `''` alike.
|
|
78
77
|
|
|
79
78
|
## Writes and what they refresh
|
|
80
79
|
|
|
@@ -106,7 +105,7 @@ ticks while the element is mounted and the tab is in view, never in the builder
|
|
|
106
105
|
|
|
107
106
|
**A number that goes round, or stops:** `cycleState({ key: 'slide', length: 4 })` is "next" — after the last the first
|
|
108
107
|
— and `by: -1` "previous"; `stepState({ key: 'shown', by: 40, max: 'apiContainer_site.data.total' })` adds and stops
|
|
109
|
-
at the bound.
|
|
108
|
+
at the bound.
|
|
110
109
|
|
|
111
110
|
**Scrolling** is a step on the element whose box scrolls (`overflow-x: auto` on a row of cards): `scrollBy('cards',
|
|
112
111
|
{ x: '80%' })` moves it by most of what it shows (`'-80%'` back, `'240'` pixels), `scrollTo('cards', { x: 'end' })`
|
|
@@ -165,6 +164,8 @@ Pages that see each other — cursors, presence, a shared board — talk over a
|
|
|
165
164
|
asks it. `appendState({ key, value, withId: true })` stores `{ id, value }` — bind `.value`, address by `.id`.
|
|
166
165
|
`whenFailed` matches every outcome that is not `completed` (also `skipped`, `aborted`). A failed run gives `reason`,
|
|
167
166
|
and `error` only when the server or a step wrote one for the caller (a task throwing `ActionRefusal`, `flow.fail` with
|
|
168
|
-
`tellCaller`) — so show `{{ saved.error ? saved.error : "…" }}`, never the bare `error`.
|
|
167
|
+
`tellCaller`) — so show `{{ saved.error ? saved.error : "…" }}`, never the bare `error`. That pair is one call:
|
|
168
|
+
`...runServerActionOrNotify('saved', { actionId, input }, 'Could not save')` — the named run, then that toast
|
|
169
|
+
(`{ appearance, placement }` fourth).
|
|
169
170
|
|
|
170
171
|
`when(rule, step)` around a step that already has a `when` adds to it: both must hold.
|
|
@@ -25,6 +25,7 @@ export const docsLayout: LayoutSpec = {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
// in the space
|
|
28
|
+
pageFolders: [{ id: 'docs', name: 'Docs' }], // declared once: its slug (the id) starts its pages' addresses — /docs/quickstart
|
|
28
29
|
layouts: [appLayout, docsLayout],
|
|
29
30
|
pages: [{ name: 'Quickstart', slug: 'quickstart', folder: 'docs', layout: { id: 'docs-layout', slot: 'docs-slot' }, body: [ … ] }]
|
|
30
31
|
```
|
|
@@ -9,6 +9,7 @@ list bound to its records (see the provider in data-and-visibility).
|
|
|
9
9
|
```ts
|
|
10
10
|
list({ id: 'games', class: gameGrid, items: 'catalog.data.games', row: 'game-card' }) // a component per game
|
|
11
11
|
list({ id: 'games', items: 'catalog.data.games', row: r => listItem({ children: [text({ from: `${r.item}.title` })] }) })
|
|
12
|
+
// typed by a sample (`items: catalog.data.games`): `r.item` is a path — `from: r.item.title`, never `${r.item}.title`
|
|
12
13
|
list({ id: 'plans', items: ['Free', 'Pro'], children: [listItem({ children: [text({ from: 'plans.item' })] })] }) // fixed
|
|
13
14
|
```
|
|
14
15
|
|
|
@@ -1,31 +1,37 @@
|
|
|
1
1
|
# Plugins
|
|
2
2
|
|
|
3
|
-
A plugin is a React component of the project's own — a chart, a map, a game —
|
|
4
|
-
|
|
3
|
+
A plugin is a React component of the project's own — a chart, a map, a game — and an element type of its own. The
|
|
4
|
+
space places it from its `declaration.ts`, typed by what it declares:
|
|
5
5
|
|
|
6
6
|
```ts
|
|
7
|
-
|
|
7
|
+
const nebulaRun = defineElement(nebulaRunDeclaration);
|
|
8
|
+
nebulaRun({ id: 'arcade', shipColor: 'amber', bind: { best: 'state.arcadeBest' } })
|
|
8
9
|
```
|
|
9
10
|
|
|
11
|
+
(`custom({ renderType })` is for a component registered by name, with no declaration.)
|
|
12
|
+
|
|
10
13
|
## Creating one
|
|
11
14
|
|
|
12
15
|
The CLI writes it, in the shape Plitzi's own elements are written in: `npx @plitzi/cli plugin add seat-picker` (a
|
|
13
16
|
package of its own and packing it: the CLI skill).
|
|
14
17
|
|
|
15
|
-
One folder, four files: `SeatPicker.tsx` (the component), `declaration.ts` (its
|
|
16
|
-
`
|
|
17
|
-
`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`
|
|
18
|
+
One folder, four files: `SeatPicker.tsx` (the component), `declaration.ts`, `Settings.tsx` (its panel in the builder),
|
|
19
|
+
and `index.ts` (`Object.assign(Component, declaration, { pluginSettings: Settings })`). The declaration is data only,
|
|
20
|
+
`definePlugin<SeatPickerAttributes>()({ type, label, attributes, triggers, callbacks })` from
|
|
21
|
+
`@plitzi/sdk-authoring/plugin`: the rest of what the builder needs is derived. An event or an action is declared there
|
|
22
|
+
and registered by the component from there, never only in the component.
|
|
23
|
+
|
|
24
|
+
A plugin fires its events with `const fire = usePluginTrigger(declaration)` (`@plitzi/plitzi-sdk`), typed by the
|
|
25
|
+
declaration: `fire('onPick', { seat })` — an undeclared event, or a payload missing a field its `preview` names, does
|
|
26
|
+
not compile. Nothing fires while the page is edited. Fire whenever there is news — from the first effect too: a flow
|
|
27
|
+
starts once the page has finished mounting, so the `setState` it calls is there. No `queueMicrotask` or delay of your
|
|
28
|
+
own. An event that REPORTS a state, and may fire again before its flow ends (as a
|
|
23
29
|
component reads its real value right after mounting), wants `whileRunning('queue', …)` on the flow: by default a firing
|
|
24
30
|
while the flow runs is dropped, and the stale first report would stick.
|
|
25
31
|
|
|
26
32
|
## Props
|
|
27
33
|
|
|
28
|
-
**The
|
|
34
|
+
**The element's attributes ARE the component's props.** Whatever the space writes on it arrives
|
|
29
35
|
by the same name, and so does whatever a binding writes — which is what makes a plugin live: bind an attribute to
|
|
30
36
|
`state`, a provider or a row, and the component re-renders with the answer. Prefer this to anything else.
|
|
31
37
|
|
|
@@ -34,6 +40,8 @@ panel edits for a plugin it has no controls for; from code, write attributes. A
|
|
|
34
40
|
"Settings Malformed" in the page.
|
|
35
41
|
|
|
36
42
|
Every prop is optional with a default: an attribute not authored yet, or a binding not answered yet, is `undefined`.
|
|
43
|
+
One whose declared default is a number or a boolean arrives as one, written or bound: type it `number`, never
|
|
44
|
+
`number | string`.
|
|
37
45
|
|
|
38
46
|
## The component
|
|
39
47
|
|
|
@@ -73,7 +81,7 @@ on ([kept state](kept-state.md)), what a plugin writes there — its layout, a c
|
|
|
73
81
|
## Laying out the space's elements
|
|
74
82
|
|
|
75
83
|
A plugin that arranges elements — a dock, tabs, a masonry — HOLDS them: they are its children in the space
|
|
76
|
-
(`
|
|
84
|
+
(`dock({ id: 'dock', children: [feed, tools] })`, or dropped into it in the builder), and the component
|
|
77
85
|
places each in a box of its own, by the id it was authored under:
|
|
78
86
|
|
|
79
87
|
```tsx
|
|
@@ -83,8 +91,8 @@ import { elementChildren } from '@plitzi/plitzi-sdk';
|
|
|
83
91
|
```
|
|
84
92
|
|
|
85
93
|
Never write `style` onto an element the plugin does not render: nothing promises to keep it. Its boxes share one
|
|
86
|
-
stacking context, the plugin's. `useDisplayMode()` names the breakpoint showing
|
|
87
|
-
|
|
94
|
+
stacking context, the plugin's. `useDisplayMode()` names the breakpoint showing (`desktop`, `tablet`, `mobile`) at the
|
|
95
|
+
space's widths; `useElementSize(ref)`, its own box (`undefined` until measured).
|
|
88
96
|
|
|
89
97
|
## Its own server code
|
|
90
98
|
|
|
@@ -108,6 +116,7 @@ import { useChannel } from '@plitzi/plitzi-sdk';
|
|
|
108
116
|
|
|
109
117
|
const room = useChannel(roomTopic, { onMessage: message => move(message.from, message.data) });
|
|
110
118
|
room.publish('pointer', { x, y }); // throttle it: ~20 a second, the latest position each time
|
|
119
|
+
room.publish('react', { emoji }, { echo: true }); // and heard here too
|
|
111
120
|
room.members; // who is here, with the state each announced
|
|
112
121
|
```
|
|
113
122
|
|
|
@@ -137,16 +146,15 @@ with thousands of shapes fast — see [drawing](drawing.md).
|
|
|
137
146
|
## Registering
|
|
138
147
|
|
|
139
148
|
A project `plitzi create` wrote registers every folder of `src/plugins` by itself, under the folder's name in
|
|
140
|
-
camelCase (`StatCard` → `statCard`). Anywhere else, the entry registers the component under its
|
|
141
|
-
third argument to `render()`, `<PlitziSdk.Plugin>` in a React application, `plugins` on a page server of your own. A
|
|
142
|
-
renders "Custom Component … Not Found", and a page server logs
|
|
143
|
-
|
|
149
|
+
camelCase (`StatCard` → `statCard`): its type. Anywhere else, the entry registers the component under its type: the
|
|
150
|
+
third argument to `render()`, `<PlitziSdk.Plugin>` in a React application, `plugins` on a page server of your own. A
|
|
151
|
+
type nothing registered renders "Custom Component … Not Found", and a page server logs it at `error` once per space:
|
|
152
|
+
on a server, register it in `plugins` AND name it in the deployment's `pluginNames`.
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
`defineElement
|
|
148
|
-
|
|
149
|
-
`element('seatPicker', { id: 'seats', start: 3 })`.
|
|
154
|
+
On Plitzi, every element loads from the plugin the space installed (`plitzi plugin pack` and `upload`: several folders
|
|
155
|
+
are one plugin). A plugin PACKAGE is placed the same way:
|
|
156
|
+
`defineElement` from its `declaration.ts`, or `elementsFromManifest<{ seatPicker: SeatPickerAttributes }>(manifest)`
|
|
157
|
+
from what it published.
|
|
150
158
|
|
|
151
159
|
## Checked like a built-in element
|
|
152
160
|
|
|
@@ -159,11 +167,10 @@ import declaration from './plugins/SeatPicker/declaration';
|
|
|
159
167
|
authorSpace(space, { plugins: [declaration] });
|
|
160
168
|
```
|
|
161
169
|
|
|
162
|
-
It is then held to what it declares
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
judged on its events at all — nothing here knows them.
|
|
170
|
+
It is then held to what it declares: a flow on an event it never fires, a step sent to an action it does not answer,
|
|
171
|
+
an attribute it does not read — each is refused with the name it should have been. (`pluginTypes: ['seatPicker']`
|
|
172
|
+
only tells the linter the type exists; nothing about how the space uses it is checked.) A plugin whose declaration is
|
|
173
|
+
NOT handed over is not judged on its events at all — nothing here knows them.
|
|
167
174
|
|
|
168
175
|
Its events and actions have builders typed from the same declaration, so a name it does not declare is a compile error:
|
|
169
176
|
|
|
@@ -174,9 +181,9 @@ button({ content: 'Clear', flows: [[onClick(), declaredCallback(declaration, 're
|
|
|
174
181
|
|
|
175
182
|
A plugin says what HAPPENED through its events (`onPick`, with the seat in the payload) and lets the space's flows
|
|
176
183
|
decide what that means — write `state`, open a modal, call a server action. Prefer that to writing `state` from inside
|
|
177
|
-
the component: the flow is visible in the space,
|
|
184
|
+
the component: the flow is visible in the space, and a button can do the same.
|
|
178
185
|
|
|
179
|
-
-
|
|
186
|
+
- An action's param declared `number` or `boolean` arrives as one, written or bound; any other arrives as written.
|
|
180
187
|
- To act when another element appears, `useElementVisible('tools')` (`@plitzi/plitzi-sdk`) — never watch classes.
|
|
181
188
|
- A name for screen readers is a prop of its own: declare `label`, write it as the root's `aria-label`.
|
|
182
189
|
|
|
@@ -185,14 +192,14 @@ the component: the flow is visible in the space, the builder shows it, and the s
|
|
|
185
192
|
The builder draws the element on its canvas while somebody edits the page: a click selects it, a drag moves it, a key
|
|
186
193
|
belongs to the editor.
|
|
187
194
|
|
|
188
|
-
- **Outside preview, do nothing on your own.** `
|
|
189
|
-
page is edited
|
|
190
|
-
|
|
195
|
+
- **Outside preview, do nothing on your own.** `usePlitzi().settings.previewMode` is `false` while the
|
|
196
|
+
page is edited: gate your own click handlers, timers, listeners, permission prompts and map gestures on it, and
|
|
197
|
+
still render something to select.
|
|
191
198
|
- **A drag lives in state, not in refs read while rendering** (the project's lint refuses that): keep the gesture
|
|
192
199
|
in `useState`, mirror the latest props into a ref inside an effect, and attach `pointermove` on `pointerdown`.
|
|
193
200
|
- **Never the global `window` or `document`.** The canvas is a frame of its own and the code runs in the builder's
|
|
194
201
|
window: listen, measure and go full screen through the node's own page (`ref.current.ownerDocument`, its
|
|
195
|
-
`defaultView`) or `
|
|
202
|
+
`defaultView`) or `usePlitzi().utils.getWindow()`. An `instanceof` check takes its class from there too.
|
|
196
203
|
|
|
197
204
|
## The plugin's stylesheet and the space's CSS
|
|
198
205
|
|
|
@@ -208,5 +215,4 @@ fonts a stylesheet names are carried the same way.
|
|
|
208
215
|
A map, a chart library, anything that positions its own markers or popups: its roots are the library's to place. Never
|
|
209
216
|
give them a class that sets `position` — the element falls into the page's flow, offset by every marker before it. Put
|
|
210
217
|
the look in the plugin's own stylesheet (imported CSS ships beside the bundle) and take colours from custom properties
|
|
211
|
-
the space sets (`--seat-accent: var(--accent)` in `customCss`)
|
|
212
|
-
element with `getComputedStyle(probe).color`, again whenever `theme.resolved` changes (bind it as a prop).
|
|
218
|
+
the space sets (`--seat-accent: var(--accent)` in `customCss`).
|
|
@@ -14,7 +14,10 @@ channel({ id: 'room', topic: 'room:{{ id }}', keep: 0, bind: { presence: 'comput
|
|
|
14
14
|
|
|
15
15
|
Its descendants bind `room.members`, `room.connected`, `room.last`; flows use `on('onMessage')` (`type`, `data`,
|
|
16
16
|
`from`), `onJoin`/`onLeave` (`from`, `user` and the `state` the member announced — `onJoin` only for who came after
|
|
17
|
-
this page), `publishOn('room', 'reaction', data)` and `announceOn('room', state)`.
|
|
17
|
+
this page), `publishOn('room', 'reaction', data)` and `announceOn('room', state)`. What a page publishes reaches
|
|
18
|
+
every OTHER page on the topic: one that shows it the moment it sends — a reaction, a chat line — says
|
|
19
|
+
`publishOn('room', 'reaction', data, { echo: true })` and hears it back once the server took it (its `from` is
|
|
20
|
+
`room.me`). What a server action publishes reaches every page, the one that ran it included. State everyone must
|
|
18
21
|
agree on goes through a server action whose last step is `realtime.publish` — validated and saved first, announced
|
|
19
22
|
after.
|
|
20
23
|
|
|
@@ -50,7 +50,7 @@ refuses or warns of, with the fix.
|
|
|
50
50
|
every switch through `computed` (`plates: '{{ state.platesOff ? false : true }}'`) so its default shows before
|
|
51
51
|
anybody touches it.
|
|
52
52
|
12. **Hand plugins over as their declarations**: `authorSpace(space, { plugins: [declaration] })` holds a plugin —
|
|
53
|
-
|
|
53
|
+
placed with `defineElement(declaration)` — to the events, actions and attributes it declares. See
|
|
54
54
|
[plugins](plugins.md).
|
|
55
55
|
13. **Usable without sight.** Screen readers and browser agents (Claude in Chrome) find a page's controls in its
|
|
56
56
|
accessibility tree: every button and link has words (an icon-only button a `title`), every field a `label`
|
|
@@ -13,10 +13,12 @@ apiContainer({ id: site.id, query: '/data/home.json', children: [
|
|
|
13
13
|
] })
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
- A path is the source's full name (`apiContainer_site.data.hero.title`), so it goes in `from`, `items`, `bind
|
|
17
|
-
`visible`
|
|
18
|
-
type-checked lint refuses an object in
|
|
19
|
-
|
|
16
|
+
- A path is the source's full name (`apiContainer_site.data.hero.title`), so it goes in `from`, `items`, `bind`,
|
|
17
|
+
`visible` and the binding helpers (`bindTemplate`, `visibleWhen`, `hiddenWhen`, `variantFrom`) as it is, and into a
|
|
18
|
+
template through `twig` (a plain template literal writes the same text, but a type-checked lint refuses an object in
|
|
19
|
+
one).
|
|
20
|
+
- A list fed by a path hands its `row` the item typed: every field any item of the sample has — `p.item`, `p.index`,
|
|
21
|
+
and `p.inTemplate` as an untyped row has it, so turning a list typed changes none of its templates.
|
|
20
22
|
- An item of a list is read by position: `site.data.sections[1]`.
|
|
21
23
|
- A provider fed by a server action publishes the action's output at its root: `actionSource('feed', sample)` types
|
|
22
24
|
it — `feed.stories`, and `feed.data` is a type error — with a sample of what the action's last step answers.
|