Counter
A small shared counter: state, a participant action, and a standard button.
What it demonstrates: State validation · participant actions · views.
Run this example
All learning examples appear in a compatible developer world. To export a standalone project, an operator with source access runs this from the Scape repository (choose a new destination under an existing directory):
sh
yarn sdk:starter /path/to/my-counter --template counter1
In the exported project:
sh
yarn install
yarn build
yarn dev --origin https://your-scape-host1
2
3
2
3
Follow connection and approval. The exported dev script invokes scape gizmo dev. The kit includes three private archives for the SDK, CLI and MCP server; keep them and the Yarn resolutions together. Third-party dependencies still need installation. See the CLI reference for commands; no public Scape registry install is available yet.
Try changing it
Add a decrement action. Keep the state a safe integer and reject unexpected payload fields.
Source
This code is included directly from the working example when the site builds.
src/definition.ts
ts
/** Counter owns the shared count and controls; Scape supplies persistence and permissions. */
import { defineObject, exactKeys, ObjectActionError, record, requirePayload } from '@scape/sdk';
/** Start here: state, validation, two actions, and a declarative view. */
export const counter = defineObject<{ count: number }>({
type: 'scape.counter',
version: 1,
emoji: '🔢',
label: 'Counter',
hint: 'count together',
initial: () => ({ count: 0 }),
valid: (state): state is { count: number } =>
record(state) &&
exactKeys(state, ['count']) &&
Number.isSafeInteger(state.count) &&
Number(state.count) >= 0 &&
Number(state.count) <= 9999,
actions: {
increment: {
permission: 'participant',
run: (state, payload) => {
requirePayload(payload, []);
if (state.count === 9999)
throw new ObjectActionError(409, 'Reset the counter before adding more');
return { count: state.count + 1 };
},
},
reset: {
permission: 'editor',
run: (_state, payload) => {
requirePayload(payload, []);
return { count: 0 };
},
},
},
view: (state, viewer) => ({
title: `${state.count}`,
description: 'Everyone can add one. The object editor can reset the count.',
controls: [
{
id: 'increment',
label: 'Add one',
action: { name: 'increment', payload: {} },
disabled: state.count === 9999,
},
...(viewer.canEdit
? [
{
id: 'reset',
label: 'Reset count',
confirm: 'Clear the count?',
action: { name: 'reset', payload: {} },
},
]
: []),
],
}),
});1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
src/project.ts
ts
/** Project entry: exports counter to Scape's SDK loader. */
import { defineProject } from '@scape/sdk';
import { counter } from './definition.js';
export default defineProject({ objects: [counter] });1
2
3
4
5
2
3
4
5
