@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.
Files changed (71) hide show
  1. package/CHANGELOG.md +287 -0
  2. package/README.md +3 -3
  3. package/dist/auth.js +1 -1
  4. package/dist/core/auth/createAuth.js +1 -1
  5. package/dist/core/http/stages/signInRoute.js +1 -1
  6. package/dist/core/server/baseServer.js +11 -6
  7. package/dist/core/server/fleet/stores.d.ts +7 -0
  8. package/dist/core/server/fleet/stores.js +16 -2
  9. package/dist/core/server/fleet/supervisor.d.ts +2 -0
  10. package/dist/core/server/fleet/supervisor.js +14 -2
  11. package/dist/core/server/pageServer.js +1 -1
  12. package/dist/functions-api.d.ts +47 -2
  13. package/dist/helpers/cache/cacheManager.d.ts +9 -2
  14. package/dist/helpers/cache/cacheManager.js +46 -22
  15. package/dist/helpers/cache/keys.d.ts +12 -5
  16. package/dist/helpers/cache/keys.js +56 -11
  17. package/dist/modules/actions/index.js +6 -2
  18. package/dist/modules/actions/jobs/later.d.ts +43 -0
  19. package/dist/modules/actions/jobs/later.js +66 -0
  20. package/dist/modules/actions/jobs/later.test.d.ts +1 -0
  21. package/dist/modules/actions/jobs/memoryQueue.js +14 -1
  22. package/dist/modules/actions/jobs/worker.js +2 -2
  23. package/dist/modules/actions/runtime/credentials.d.ts +10 -0
  24. package/dist/modules/actions/runtime/credentials.js +28 -0
  25. package/dist/modules/actions/runtime/credentials.test.d.ts +1 -0
  26. package/dist/modules/actions/runtime/fileKv.d.ts +8 -3
  27. package/dist/modules/actions/runtime/fileKv.js +79 -7
  28. package/dist/modules/actions/runtime/precheck.js +1 -1
  29. package/dist/modules/actions/runtime/rateLimit.d.ts +7 -0
  30. package/dist/modules/actions/runtime/rateLimit.js +7 -1
  31. package/dist/modules/actions/runtime/runAction.d.ts +4 -1
  32. package/dist/modules/actions/runtime/runAction.js +46 -11
  33. package/dist/modules/actions/runtime/taskContext.d.ts +4 -1
  34. package/dist/modules/actions/runtime/taskContext.js +7 -1
  35. package/dist/modules/actions/tasks/flow.js +113 -35
  36. package/dist/modules/actions/types.d.ts +8 -0
  37. package/dist/modules/functions/build.js +1 -1
  38. package/dist/modules/functions/capabilities.js +21 -0
  39. package/dist/modules/functions/context.js +9 -3
  40. package/dist/modules/functions/contract.d.ts +10 -0
  41. package/dist/modules/functions/driver.js +16 -4
  42. package/dist/modules/functions/local.d.ts +1 -1
  43. package/dist/modules/functions/local.js +2 -1
  44. package/dist/modules/functions/manifest.js +8 -3
  45. package/dist/modules/functions/protocol.d.ts +7 -0
  46. package/dist/modules/mongo/jobQueue.js +26 -0
  47. package/dist/modules/mysql/jobs/jobQueue.js +21 -2
  48. package/dist/modules/mysql/jobs/schema.d.ts +3 -2
  49. package/dist/modules/mysql/jobs/schema.js +24 -11
  50. package/dist/modules/project/plugins.d.ts +1 -1
  51. package/dist/modules/project/serveProject.js +29 -4
  52. package/dist/modules/realtime/handlers.d.ts +4 -1
  53. package/dist/modules/realtime/handlers.js +8 -4
  54. package/dist/modules/realtime/hub.d.ts +3 -1
  55. package/dist/modules/realtime/hub.js +8 -4
  56. package/dist/modules/ssr/prepareRender.js +1 -1
  57. package/package.json +3 -3
  58. package/skills/plitzi-authoring/CHEATSHEET.md +4 -2
  59. package/skills/plitzi-authoring/recipes/plugin.ts +9 -7
  60. package/skills/plitzi-authoring/recipes/style-a-page.ts +1 -1
  61. package/skills/plitzi-authoring/reference/accessibility.md +1 -0
  62. package/skills/plitzi-authoring/reference/authoring-errors.md +8 -4
  63. package/skills/plitzi-authoring/reference/data-and-visibility.md +11 -10
  64. package/skills/plitzi-authoring/reference/elements-and-styles.md +1 -1
  65. package/skills/plitzi-authoring/reference/flows.md +6 -5
  66. package/skills/plitzi-authoring/reference/layouts.md +1 -0
  67. package/skills/plitzi-authoring/reference/lists.md +1 -0
  68. package/skills/plitzi-authoring/reference/plugins.md +43 -37
  69. package/skills/plitzi-authoring/reference/realtime.md +4 -1
  70. package/skills/plitzi-authoring/reference/rules.md +1 -1
  71. 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. (JSON text works — a value inside one of its strings
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'`, which treat missing and `''` alike.
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. Each is the `setState` it stands for, with the arithmetic written once.
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 — hosted in the space by a `custom`
4
- element that names it by `renderType`:
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
- custom({ id: 'arcade', renderType: 'nebulaRun', shipColor: 'amber', bind: { best: 'state.arcadeBest' } })
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 `type`, the `triggers` it fires, the
16
- `callbacks` it answers to, and the element the builder adds — data only), `Settings.tsx` (its panel in the builder), and
17
- `index.ts` (`Object.assign(Component, declaration, { pluginSettings: Settings })`). An event or an action is declared in
18
- `declaration.ts` and registered by the component from there, never only in the component.
19
-
20
- A plugin fires its events with `interactionsManager.interactionTrigger(id, action, payload)` whenever it has news —
21
- from its first effect too: a flow starts once the page has finished mounting, so the `setState` it calls is there. No
22
- `queueMicrotask` or delay of your own. An event that REPORTS a state, and may fire again before its flow ends (as a
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 host element's attributes ARE the component's props.** Whatever the space writes on the `custom` element arrives
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
- (`custom({ renderType: 'dock', children: [feed, tools] })`, or dropped into it in the builder), and the component
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 — `desktop`, `tablet` or `mobile` — at
87
- the widths the space's styles use, instead of a width of the plugin's own.
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 `renderType`: the
141
- third argument to `render()`, `<PlitziSdk.Plugin>` in a React application, `plugins` on a page server of your own. A `renderType` nothing registered
142
- renders "Custom Component … Not Found", and a page server logs the missing `renderType` at `error` once per
143
- space: on a server, register it in `plugins` AND name it in the deployment's `pluginNames`.
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
- A plugin PACKAGE, loaded by a space from its `plugin-manifest.json`, is an element TYPE of its own rather than a
146
- `custom` host — it is how the builder adds one somebody dropped. Author it with a typed factory:
147
- `defineElement<SeatPickerAttributes>(declaration)` from the plugin's own `declaration.ts`, or
148
- `elementsFromManifest<{ seatPicker: SeatPickerAttributes }>(manifest)` from what it published — or untyped,
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, whether it is authored as its own type or hosted by `custom({ renderType:
163
- 'seatPicker' })`: a flow on an event it never fires, a step sent to an action it does not answer, an attribute it does
164
- not read — each is refused with the name it should have been. (`pluginTypes: ['seatPicker']` only tells the linter the
165
- type exists; nothing about how the space uses it is checked.) A `custom` host whose component is NOT handed over is not
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, the builder shows it, and the same act can come from a button too.
184
+ the component: the flow is visible in the space, and a button can do the same.
178
185
 
179
- - A param declared `number` or `boolean` arrives as one, written or bound; any other arrives as written.
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.** `usePlitziServiceContext().settings.previewMode` is `false` while the
189
- page is edited. Declared interactions are already held back then; your own click handlers, timers, global listeners,
190
- permission prompts and map gestures are not — gate them on `previewMode`, and still render something to select.
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 `usePlitziServiceContext().utils.getWindow()`. An `instanceof` check takes its class from there too.
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`); a canvas or WebGL layer resolves them through a probe
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)`. State everyone must
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
- its own type, or a `custom({ renderType })` host — to the events, actions and attributes it declares. See
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` and
17
- `visible` as it is, and into a template through `twig` (a plain template literal writes the same text, but a
18
- type-checked lint refuses an object in one).
19
- - A list fed by a path hands its `row` the item typed: every field any item of the sample has.
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.