@adobe/data 0.9.39 → 0.9.40

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 (82) hide show
  1. package/AGENTS.md +85 -16
  2. package/package.json +1 -1
  3. package/references/data-lit/package.json +1 -1
  4. package/references/data-lit-tictactoe/package.json +22 -0
  5. package/references/data-lit-tictactoe/src/elements/tictactoe-app/tictactoe-app.css.ts +11 -0
  6. package/references/data-lit-tictactoe/src/elements/tictactoe-app/tictactoe-app.ts +22 -0
  7. package/references/data-lit-tictactoe/src/elements/tictactoe-board/tictactoe-board-presentation.ts +13 -0
  8. package/references/data-lit-tictactoe/src/elements/tictactoe-board/tictactoe-board.css.ts +17 -0
  9. package/references/data-lit-tictactoe/src/elements/tictactoe-board/tictactoe-board.ts +18 -0
  10. package/references/data-lit-tictactoe/src/elements/tictactoe-cell/tictactoe-cell-presentation.ts +22 -0
  11. package/references/data-lit-tictactoe/src/elements/tictactoe-cell/tictactoe-cell.css.ts +31 -0
  12. package/references/data-lit-tictactoe/src/elements/tictactoe-cell/tictactoe-cell.ts +41 -0
  13. package/references/data-lit-tictactoe/src/elements/tictactoe-hud/tictactoe-hud-presentation.ts +16 -0
  14. package/references/data-lit-tictactoe/src/elements/tictactoe-hud/tictactoe-hud.css.ts +18 -0
  15. package/references/data-lit-tictactoe/src/elements/tictactoe-hud/tictactoe-hud.ts +44 -0
  16. package/references/data-lit-tictactoe/src/main.ts +9 -0
  17. package/references/data-lit-tictactoe/src/state/agent-plugin.ts +117 -0
  18. package/references/data-lit-tictactoe/src/state/tictactoe-plugin.ts +56 -0
  19. package/references/data-lit-tictactoe/src/tictactoe-element.ts +10 -0
  20. package/references/data-lit-tictactoe/src/types/board-cell.ts +5 -0
  21. package/references/data-lit-tictactoe/src/types/board-state/board-state.ts +7 -0
  22. package/references/data-lit-tictactoe/src/types/board-state/create-initial-board.ts +5 -0
  23. package/references/data-lit-tictactoe/src/types/board-state/current-player.ts +13 -0
  24. package/references/data-lit-tictactoe/src/types/board-state/derive-status.ts +12 -0
  25. package/references/data-lit-tictactoe/src/types/board-state/get-cell.ts +7 -0
  26. package/references/data-lit-tictactoe/src/types/board-state/get-move-count.ts +6 -0
  27. package/references/data-lit-tictactoe/src/types/board-state/get-winner.ts +14 -0
  28. package/references/data-lit-tictactoe/src/types/board-state/get-winning-line.ts +25 -0
  29. package/references/data-lit-tictactoe/src/types/board-state/is-board-full.ts +5 -0
  30. package/references/data-lit-tictactoe/src/types/board-state/is-cell-playable.ts +13 -0
  31. package/references/data-lit-tictactoe/src/types/board-state/is-cell-winning.ts +9 -0
  32. package/references/data-lit-tictactoe/src/types/board-state/is-game-over.ts +8 -0
  33. package/references/data-lit-tictactoe/src/types/board-state/public.ts +15 -0
  34. package/references/data-lit-tictactoe/src/types/board-state/schema.ts +10 -0
  35. package/references/data-lit-tictactoe/src/types/board-state/set-board-cell.ts +13 -0
  36. package/references/data-lit-tictactoe/src/types/game-status.ts +3 -0
  37. package/references/data-lit-tictactoe/src/types/move-reject-reason.ts +7 -0
  38. package/references/data-lit-tictactoe/src/types/play-move-args/can-play-move.ts +33 -0
  39. package/references/data-lit-tictactoe/src/types/play-move-args/play-move-args.ts +4 -0
  40. package/references/data-lit-tictactoe/src/types/play-move-args/public.ts +3 -0
  41. package/references/data-lit-tictactoe/src/types/player-mark/player-mark.ts +7 -0
  42. package/references/data-lit-tictactoe/src/types/player-mark/public.ts +3 -0
  43. package/references/data-lit-tictactoe/src/types/player-mark/schema.ts +9 -0
  44. package/references/data-lit-tictactoe/src/types/winning-line.ts +3 -0
  45. package/references/data-lit-tictactoe/tsconfig.json +16 -0
  46. package/references/data-lit-todo/package.json +1 -1
  47. package/references/data-react/package.json +1 -1
  48. package/references/data-react-hello/package.json +1 -1
  49. package/references/data-react-hello/src/{App.tsx → app.tsx} +2 -2
  50. package/references/data-react-hello/src/components/counter/counter-presentation.tsx +13 -0
  51. package/references/data-react-hello/src/components/counter/counter.tsx +26 -0
  52. package/references/data-react-hello/src/main.tsx +1 -1
  53. package/references/data-react-hello/src/state/use-counter-database.ts +6 -0
  54. package/references/data-react-pixie/package.json +1 -1
  55. package/references/data-react-pixie/src/{App.tsx → app.tsx} +7 -7
  56. package/references/data-react-pixie/src/{filter-toggle.tsx → components/filter-selector/filter-selector-presentation.tsx} +9 -15
  57. package/references/data-react-pixie/src/components/filter-selector/filter-selector.tsx +22 -0
  58. package/references/data-react-pixie/src/{filters.ts → components/pixie-scene/pixie-filters.ts} +3 -1
  59. package/references/data-react-pixie/src/components/pixie-scene/pixie-scene-presentation.tsx +18 -0
  60. package/references/data-react-pixie/src/components/pixie-scene/pixie-scene.tsx +26 -0
  61. package/references/data-react-pixie/src/components/pixie-scene/pixie-tick.tsx +12 -0
  62. package/references/data-react-pixie/src/components/sprite/sprite-presentation.tsx +39 -0
  63. package/references/data-react-pixie/src/components/sprite/sprite.tsx +37 -0
  64. package/references/data-react-pixie/src/{hooks/use-texture.ts → components/sprite/use-sprite-texture.ts} +3 -1
  65. package/references/data-react-pixie/src/main.tsx +1 -1
  66. package/references/data-react-pixie/src/{pixie-plugin.ts → state/pixie-plugin.ts} +4 -7
  67. package/references/data-react-pixie/src/state/use-pixie-database.ts +6 -0
  68. package/references/data-react-pixie/src/types/filter-type.ts +3 -0
  69. package/references/data-react-pixie/src/types/sprite-type/image.ts +14 -0
  70. package/references/data-react-pixie/src/types/sprite-type/public.ts +4 -0
  71. package/references/data-react-pixie/src/types/sprite-type/schema.ts +5 -0
  72. package/references/data-react-pixie/src/types/sprite-type/sprite-type.ts +7 -0
  73. package/references/data-react-hello/src/Counter.tsx +0 -21
  74. package/references/data-react-pixie/src/hooks/use-database.ts +0 -6
  75. package/references/data-react-pixie/src/pixi-react.d.ts +0 -1
  76. package/references/data-react-pixie/src/sprite-container.tsx +0 -23
  77. package/references/data-react-pixie/src/sprite-urls.ts +0 -9
  78. package/references/data-react-pixie/src/sprite.tsx +0 -35
  79. package/references/data-react-pixie/src/tick.tsx +0 -10
  80. /package/references/data-react-hello/src/{counter-plugin.ts → state/counter-plugin.ts} +0 -0
  81. /package/references/data-react-pixie/src/{bunny.png → types/sprite-type/bunny.png} +0 -0
  82. /package/references/data-react-pixie/src/{fox.png → types/sprite-type/fox.png} +0 -0
package/AGENTS.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  Adobe data-oriented programming library. Use this file when editing code in this package or when consuming `@adobe/data` in an application.
4
4
 
5
+ ## References
6
+
7
+ Source for peer packages and sample apps is included under `references/` (when present). Use these as living reference code:
8
+
9
+ - **references/data-lit** — Support functions for Lit based projects.
10
+ - **references/data-lit-tictactoe** — **Exemplary types and state.** Tic-Tac-Toe sample with Lit UI. The `types/` (BoardState namespace, PlayMoveArgs, etc.) and `state/` (tictactoe-plugin, agent-plugin) are a very good reference for proper type namespaces, ECS resources, computed observables, and transactions — even for consumers not using Lit.
11
+ - **references/data-react** — Support functions for React based projects.
12
+ - **references/data-react-hello** — Very simple react hello world sample.
13
+ - **references/data-react-pixie** — Simple sample for react pixie based 2d games.
14
+ - **references/data-lit-todo** — Sample for older style applications that use createStore and ApplicationElement. Do not reference unless your legacy application uses those.
15
+
5
16
  ## Stack
6
17
 
7
18
  - **Package manager:** pnpm
@@ -52,10 +63,52 @@ Source lives under `src/` with one main `index.ts` per area; built output is in
52
63
 
53
64
  - **Store:** low-level, synchronous, direct mutations. No transactions, no observability.
54
65
  - **Database:** wraps a Store; **transaction-based** and **observable**. All mutations go through transaction functions; undo/redo and observers are supported.
55
- - **Plugins:** use `Database.Plugin.create()`. Property order is **enforced at runtime:** `extends` → `services` → `components` → `resources` → `archetypes` → `computed` → `transactions` → `actions` → `systems`. Wrong order throws.
56
- - **Composition:** one plugin via `extends`; multiple peers via `Database.Plugin.combine(...)`.
57
- - **Transactions:** synchronous and deterministic. Receive `(store, payload)`. Mutate via `store.update(entity, data)`, `store.resources.x = value`, etc.
58
- - **Actions:** may be async; call at most one transaction per action to keep undo/redo correct. Callers should not rely on return values for UI flow (data down via Observe, actions for side effects).
66
+ - **Plugins:** use `Database.Plugin.create()` from `@adobe/data/ecs`. Property order is **enforced at runtime**; wrong order throws.
67
+
68
+ ### Plugin property order (all optional)
69
+
70
+ `extends` → `services` → `components` → `resources` → `archetypes` → `computed` → `transactions` → `actions` → `systems`
71
+
72
+ | Property | Purpose |
73
+ |----------|---------|
74
+ | `extends` | Base plugin to extend (single parent only) |
75
+ | `services` | `(db) => ServiceInstance` — singleton service factories; extended plugin services initialize first |
76
+ | `components` | Schema object for ECS component data. Use `transient: true` for non-persistable values (DOM refs, HTML elements). |
77
+ | `resources` | `{ default: value as Type }` — global state. Use `as Type` for compile-time type; use `null as unknown as Type` for resources initialized later in a system. |
78
+ | `archetypes` | `['comp1', 'comp2']` — standard ECS archetypes for querying and inserting related components |
79
+ | `computed` | `(db) => Observe<T>` — factory returning observable; receives full db |
80
+ | `transactions` | `(store, payload) => void` — synchronous, deterministic atomic mutations |
81
+ | `actions` | `(db, payload) => T` — general functions; may be async |
82
+ | `systems` | `{ create: (db) => fn | void }` — per-frame (60fps) or init-only; optional `schedule: { before, after, during }` for ordering |
83
+
84
+ ### Composition
85
+
86
+ - **Single extension:** one plugin via `extends`.
87
+ - **Multiple peers:** use `Database.Plugin.combine(aPlugin, bPlugin, ...)` — `extends` accepts only one plugin.
88
+ - **Type utilities:** `Database.Plugin.ToDatabase<typeof myPlugin>`, `Database.Plugin.ToStore<typeof myPlugin>`.
89
+
90
+ ### Transactions and Store API
91
+
92
+ Transactions receive `(store, payload)`. Mutate via:
93
+
94
+ - `store.update(entity, data)` — update entity components
95
+ - `store.resources.x = value` — mutate resources
96
+ - `store.get(entity, 'component')` — read component value
97
+ - `store.read(entity)` — read all entity component values
98
+ - `store.read(entity, archetype)` — read entity components in archetype
99
+ - `store.select(archetype.components, { where })` — query entities
100
+
101
+ ### Actions
102
+
103
+ - May be async. Call **at most one transaction per action** to keep undo/redo correct.
104
+ - UI must **never consume return values** — call for side effects only. Data down via Observe, actions up as void.
105
+
106
+ ### Plugin naming
107
+
108
+ - **File:** `*-plugin.ts` (kebab-case), e.g. `layout-plugin.ts`
109
+ - **Export:** `*Plugin` (camelCase), e.g. `layoutPlugin`
110
+ - **System:** `plugin_name__system` (snake_case, double underscore)
111
+ - **Init system:** `plugin_name_initialize`
59
112
 
60
113
  ## Observables
61
114
 
@@ -71,21 +124,37 @@ Source lives under `src/` with one main `index.ts` per area; built output is in
71
124
 
72
125
  ## Data and schemas
73
126
 
74
- - **Data:** immutable, JSON-serializable values. Use `normalize()` for stable keys (e.g. cache keys).
75
- - **Schemas:** JSON Schema with `as const` so `FromSchema` (or equivalent) can derive TypeScript types. Use schema namespaces for component/resource definitions in ECS.
127
+ - **Data:** immutable, JSON-serializable values.
128
+ - **Schemas:** JSON Schema with `as const` so `FromSchema` (or equivalent) can derive TypeScript types. Use schema namespaces for component definitions in ECS if we the types are numeric and we want them stored in linear memory for performance. See Vec2, Vec3 etc. Follow those patterns for numeric values. Do not use an explicit schema for resources, just use `{ default: value as Type }` since there is only one value we don't need linear memory layout.
129
+
130
+ ### Data modeling example
131
+
132
+ ```ts
133
+ import { Database } from '@adobe/data/ecs';
134
+ import { F32, Vec3, Vec4 } from '@adobe/data/math';
135
+
136
+ const particleDataPlugin = Database.Plugin.create({
137
+ components: {
138
+ position: Vec3.schema,
139
+ velocity: Vec3.schema,
140
+ color: Vec4.schema,
141
+ mass: F32.schema,
142
+ },
143
+ resources: {
144
+ gravity: { default: 9.8 as number },
145
+ },
146
+ archetypes: {
147
+ Particle: ['position', 'velocity', 'color', 'mass'],
148
+ },
149
+ });
150
+ ```
151
+
152
+ - **Components:** per-entity data. Use numeric schemas from `@adobe/data/math` or type namespace schemas for custom shapes.
153
+ - **Resources:** global state. Use only `{ default: value as Type }`.
154
+ - **Archetypes:** one per entity kind. List all components that kind requires.
76
155
 
77
156
  ## Gotchas
78
157
 
79
158
  - **Breaking changes:** Before 1.0.0, minor version bumps may include breaking API changes.
80
159
  - **ECS plugin order:** Reordering plugin properties (e.g. putting `transactions` before `components`) will throw at runtime.
81
160
  - **BlobStore / BlobRef:** Prefer BlobRef for persistence and cache keys; resolve to Blob when needed.
82
-
83
- ## References
84
-
85
- Source for peer packages and sample apps is included under `references/` (when present). Use these as living reference code:
86
-
87
- - **references/data-lit** — Extra support for Lit-based projects.
88
- - **references/data-react** — Same but for React.
89
- - **references/data-lit-todo** — Sample with best practices for Lit apps.
90
- - **references/data-react-hello** — Minimal starting React sample.
91
- - **references/data-react-pixie** — Sample of using React Pixie for 2D rendering.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/data",
3
- "version": "0.9.39",
3
+ "version": "0.9.40",
4
4
  "description": "Adobe data oriented programming library",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/data-lit",
3
- "version": "0.9.39",
3
+ "version": "0.9.40",
4
4
  "description": "Adobe data Lit bindings - hooks, elements, decorators",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "data-lit-tictactoe",
3
+ "version": "0.9.40",
4
+ "description": "Tic-Tac-Toe sample - Lit web components with @adobe/data-lit and AgenticService",
5
+ "type": "module",
6
+ "private": true,
7
+ "scripts": {
8
+ "build": "vite build",
9
+ "dev": "vite",
10
+ "publish-public": "true"
11
+ },
12
+ "dependencies": {
13
+ "@adobe/data": "workspace:*",
14
+ "@adobe/data-lit": "workspace:*",
15
+ "lit": "^3.3.1"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "^5.8.3",
19
+ "vite": "^5.1.1",
20
+ "vite-plugin-checker": "^0.12.0"
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { css } from "lit";
4
+
5
+ export const styles = css`
6
+ :host {
7
+ display: flex;
8
+ flex-direction: column;
9
+ gap: 1rem;
10
+ }
11
+ `;
@@ -0,0 +1,22 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { customElement } from "lit/decorators.js";
4
+ import { html } from "lit";
5
+ import { TictactoeElement } from "../../tictactoe-element.js";
6
+ import { styles } from "./tictactoe-app.css.js";
7
+ import "../tictactoe-board/tictactoe-board.js";
8
+ import "../tictactoe-hud/tictactoe-hud.js";
9
+
10
+ export const tagName = "tictactoe-app";
11
+
12
+ @customElement(tagName)
13
+ export class TictactoeApp extends TictactoeElement {
14
+ static styles = styles;
15
+
16
+ render() {
17
+ return html`
18
+ <tictactoe-board></tictactoe-board>
19
+ <tictactoe-hud></tictactoe-hud>
20
+ `;
21
+ }
22
+ }
@@ -0,0 +1,13 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { html } from "lit";
4
+
5
+ export function render() {
6
+ return html`
7
+ <div class="board">
8
+ ${[0, 1, 2, 3, 4, 5, 6, 7, 8].map(
9
+ (index) => html`<tictactoe-cell .index=${index}></tictactoe-cell>`,
10
+ )}
11
+ </div>
12
+ `;
13
+ }
@@ -0,0 +1,17 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { css } from "lit";
4
+
5
+ export const styles = css`
6
+ :host {
7
+ display: block;
8
+ }
9
+
10
+ .board {
11
+ display: grid;
12
+ grid-template-columns: repeat(3, 100px);
13
+ grid-template-rows: repeat(3, 100px);
14
+ gap: 4px;
15
+ width: fit-content;
16
+ }
17
+ `;
@@ -0,0 +1,18 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { customElement } from "lit/decorators.js";
4
+ import { TictactoeElement } from "../../tictactoe-element.js";
5
+ import { styles } from "./tictactoe-board.css.js";
6
+ import * as presentation from "./tictactoe-board-presentation.js";
7
+ import "../tictactoe-cell/tictactoe-cell.js";
8
+
9
+ export const tagName = "tictactoe-board";
10
+
11
+ @customElement(tagName)
12
+ export class TictactoeBoard extends TictactoeElement {
13
+ static styles = styles;
14
+
15
+ render() {
16
+ return presentation.render();
17
+ }
18
+ }
@@ -0,0 +1,22 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { html } from "lit";
4
+
5
+ export function render(args: {
6
+ cell: string;
7
+ isWinning: boolean;
8
+ isPlayable: boolean;
9
+ playMove: () => void;
10
+ }) {
11
+ const { cell, isWinning, isPlayable, playMove } = args;
12
+ const hasMark = cell === "X" || cell === "O";
13
+
14
+ return html`
15
+ <div
16
+ class="cell ${isWinning ? "winning" : ""} ${isPlayable ? "playable" : ""}"
17
+ @click=${() => isPlayable && playMove()}
18
+ >
19
+ ${hasMark ? cell : ""}
20
+ </div>
21
+ `;
22
+ }
@@ -0,0 +1,31 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { css } from "lit";
4
+
5
+ export const styles = css`
6
+ :host {
7
+ display: block;
8
+ }
9
+
10
+ .cell {
11
+ width: 100px;
12
+ height: 100px;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ font-size: 48px;
17
+ font-weight: bold;
18
+ background: #1f2937;
19
+ border: 2px solid #6b7280;
20
+ color: #e5e7eb;
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ .cell.playable {
25
+ cursor: pointer;
26
+ }
27
+
28
+ .cell.winning {
29
+ background: #2e7d32;
30
+ }
31
+ `;
@@ -0,0 +1,41 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { customElement, property } from "lit/decorators.js";
4
+ import { useObservableValues } from "@adobe/data-lit";
5
+ import { BoardState } from "../../types/board-state/board-state.js";
6
+ import { TictactoeElement } from "../../tictactoe-element.js";
7
+ import { styles } from "./tictactoe-cell.css.js";
8
+ import * as presentation from "./tictactoe-cell-presentation.js";
9
+
10
+ export const tagName = "tictactoe-cell";
11
+
12
+ @customElement(tagName)
13
+ export class TictactoeCell extends TictactoeElement {
14
+ static styles = styles;
15
+
16
+ @property({ type: Number })
17
+ declare index: number;
18
+
19
+ render() {
20
+ const values = useObservableValues(
21
+ () => ({
22
+ board: this.service.observe.resources.board,
23
+ }),
24
+ [],
25
+ );
26
+
27
+ const board = values?.board ?? " ";
28
+ const cell = BoardState.getCell(board, this.index);
29
+ const isWinning = BoardState.isCellWinning(board, this.index);
30
+ const isPlayable = BoardState.isCellPlayable(board, this.index);
31
+
32
+ return presentation.render({
33
+ cell,
34
+ isWinning,
35
+ isPlayable,
36
+ playMove: () => {
37
+ this.service.transactions.playMove({ index: this.index });
38
+ },
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,16 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { html } from "lit";
4
+
5
+ export function render(args: {
6
+ statusText: string;
7
+ restartGame: () => void;
8
+ }) {
9
+ const { statusText, restartGame } = args;
10
+ return html`
11
+ <div class="hud">
12
+ <span>${statusText}</span>
13
+ <button type="button" @click=${restartGame}>Restart</button>
14
+ </div>
15
+ `;
16
+ }
@@ -0,0 +1,18 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { css } from "lit";
4
+
5
+ export const styles = css`
6
+ .hud {
7
+ padding: 0.5rem 1rem;
8
+ font-family: system-ui, sans-serif;
9
+ display: flex;
10
+ align-items: center;
11
+ gap: 0.5rem;
12
+ }
13
+
14
+ .hud button {
15
+ padding: 0.25rem 0.75rem;
16
+ cursor: pointer;
17
+ }
18
+ `;
@@ -0,0 +1,44 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { customElement } from "lit/decorators.js";
4
+ import { useObservableValues } from "@adobe/data-lit";
5
+ import { BoardState } from "../../types/board-state/board-state.js";
6
+ import { TictactoeElement } from "../../tictactoe-element.js";
7
+ import { styles } from "./tictactoe-hud.css.js";
8
+ import * as presentation from "./tictactoe-hud-presentation.js";
9
+
10
+ export const tagName = "tictactoe-hud";
11
+
12
+ @customElement(tagName)
13
+ export class TictactoeHud extends TictactoeElement {
14
+ static styles = styles;
15
+
16
+ render() {
17
+ const values = useObservableValues(
18
+ () => ({
19
+ board: this.service.observe.resources.board,
20
+ firstPlayer: this.service.observe.resources.firstPlayer,
21
+ }),
22
+ [],
23
+ );
24
+
25
+ const board = values?.board ?? " ";
26
+ const firstPlayer = values?.firstPlayer ?? "X";
27
+
28
+ const currentPlayer = BoardState.currentPlayer(board, firstPlayer);
29
+ const status = BoardState.deriveStatus(board);
30
+ const winner = BoardState.getWinner(board);
31
+
32
+ const statusText =
33
+ status === "won" && winner !== null
34
+ ? `Winner: ${winner}`
35
+ : status === "draw"
36
+ ? "Draw"
37
+ : `Current Player: ${currentPlayer}`;
38
+
39
+ return presentation.render({
40
+ statusText,
41
+ restartGame: () => this.service.transactions.restartGame(),
42
+ });
43
+ }
44
+ }
@@ -0,0 +1,9 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import "./elements/tictactoe-app/tictactoe-app.js";
4
+
5
+ const app = document.getElementById("app");
6
+ if (app) {
7
+ const el = document.createElement("tictactoe-app");
8
+ app.appendChild(el);
9
+ }
@@ -0,0 +1,117 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { Database } from "@adobe/data/ecs";
4
+ import { Observe } from "@adobe/data/observe";
5
+ import { AgenticService } from "@adobe/data/service";
6
+ import { BoardState } from "../types/board-state/board-state";
7
+ import type { PlayerMark } from "../types/player-mark/player-mark";
8
+ import { tictactoePlugin } from "./tictactoe-plugin";
9
+ import type { TictactoeDatabase } from "./tictactoe-plugin";
10
+
11
+ const roleDescription = (mark: PlayerMark): string =>
12
+ `You are playing as ${mark} in tic-tac-toe. Play to the best of your ability.`;
13
+
14
+ const createTictactoeAgentService = (
15
+ db: TictactoeDatabase,
16
+ agentMark: PlayerMark,
17
+ ): AgenticService => {
18
+ const board = db.observe.resources.board;
19
+ const isGameOver = Observe.withFilter(board, BoardState.isGameOver);
20
+ const currentPlayer = Observe.withFilter(
21
+ board,
22
+ (nextBoard) =>
23
+ BoardState.currentPlayer(nextBoard, db.resources.firstPlayer),
24
+ );
25
+ const yourTurn = Observe.withMap(
26
+ Observe.fromProperties({ isGameOver, currentPlayer }),
27
+ ({ isGameOver: over, currentPlayer: cur }) => !over && cur === agentMark,
28
+ );
29
+
30
+ return AgenticService.create({
31
+ interface: {
32
+ role: {
33
+ type: "state",
34
+ schema: { type: "string" as const },
35
+ description: "Your role and objective",
36
+ },
37
+ board: {
38
+ type: "state",
39
+ schema: BoardState.schema,
40
+ description: "Board state",
41
+ },
42
+ yourTurn: {
43
+ type: "state",
44
+ schema: { type: "boolean" as const },
45
+ description: "True when it is your turn to play",
46
+ },
47
+ resetGame: {
48
+ type: "action",
49
+ description: "Reset the game after completion",
50
+ parameters: [],
51
+ },
52
+ winner: {
53
+ type: "state",
54
+ schema: {
55
+ type: "string" as const,
56
+ enum: ["X", "O", "cat", null],
57
+ },
58
+ description: "Winner mark when game over",
59
+ },
60
+ playMove: {
61
+ type: "action",
62
+ description: `Play a ${agentMark} move on the board`,
63
+ parameters: [
64
+ {
65
+ title: "index",
66
+ type: "integer" as const,
67
+ minimum: 0,
68
+ maximum: 8,
69
+ },
70
+ ],
71
+ },
72
+ },
73
+ implementation: {
74
+ role: Observe.fromConstant(roleDescription(agentMark)),
75
+ board,
76
+ yourTurn,
77
+ winner: Observe.withFilter(board, BoardState.getWinner),
78
+ resetGame: async () => {
79
+ db.transactions.restartGame();
80
+ },
81
+ playMove: async (index: number) => {
82
+ db.transactions.playMove({ index });
83
+ },
84
+ },
85
+ conditional: {
86
+ resetGame: isGameOver,
87
+ playMove: yourTurn,
88
+ },
89
+ });
90
+ };
91
+
92
+ const tictactoeLinkDescriptions = { x: "Play as X", o: "Play as O" } as const;
93
+
94
+ const createTictactoeRootAgentService = (
95
+ db: TictactoeDatabase,
96
+ ): AgenticService => {
97
+ const root = AgenticService.create({
98
+ interface: {
99
+ x: { type: "link", description: tictactoeLinkDescriptions.x },
100
+ o: { type: "link", description: tictactoeLinkDescriptions.o },
101
+ },
102
+ implementation: {
103
+ x: createTictactoeAgentService(db, "X"),
104
+ o: createTictactoeAgentService(db, "O"),
105
+ },
106
+ });
107
+ return { ...root, linkDescriptions: tictactoeLinkDescriptions } as AgenticService;
108
+ };
109
+
110
+ export const agentPlugin = Database.Plugin.create({
111
+ extends: tictactoePlugin,
112
+ services: {
113
+ agent: (db): AgenticService => createTictactoeRootAgentService(db),
114
+ agentX: (db): AgenticService => createTictactoeAgentService(db, "X"),
115
+ agentO: (db): AgenticService => createTictactoeAgentService(db, "O"),
116
+ },
117
+ });
@@ -0,0 +1,56 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { Database } from "@adobe/data/ecs";
4
+ import { Observe } from "@adobe/data/observe";
5
+ import { BoardState } from "../types/board-state/board-state";
6
+ import type { PlayerMark } from "../types/player-mark/player-mark";
7
+ import { PlayMoveArgs } from "../types/play-move-args/play-move-args";
8
+
9
+ export const tictactoePlugin = Database.Plugin.create({
10
+ resources: {
11
+ board: { default: BoardState.createInitialBoard() },
12
+ firstPlayer: { default: "X" as PlayerMark },
13
+ },
14
+ computed: {
15
+ currentPlayer: (db) =>
16
+ Observe.withFilter(db.observe.resources.board, (board) =>
17
+ BoardState.currentPlayer(board, db.resources.firstPlayer),
18
+ ),
19
+ moveCount: (db) =>
20
+ Observe.withFilter(db.observe.resources.board, BoardState.getMoveCount),
21
+ status: (db) =>
22
+ Observe.withFilter(db.observe.resources.board, BoardState.deriveStatus),
23
+ winningLine: (db) =>
24
+ Observe.withFilter(db.observe.resources.board, BoardState.getWinningLine),
25
+ winner: (db) =>
26
+ Observe.withFilter(db.observe.resources.board, BoardState.getWinner),
27
+ isGameOver: (db) =>
28
+ Observe.withFilter(db.observe.resources.board, BoardState.isGameOver),
29
+ },
30
+ transactions: {
31
+ restartGame: (t) => {
32
+ t.resources.firstPlayer =
33
+ t.resources.firstPlayer === "X" ? "O" : "X";
34
+ t.resources.board = BoardState.createInitialBoard();
35
+ },
36
+ playMove: (t, { index }: PlayMoveArgs) => {
37
+ const validation = PlayMoveArgs.canPlayMove({
38
+ board: t.resources.board,
39
+ index,
40
+ });
41
+ if (!validation.ok) return;
42
+ const mark = BoardState.currentPlayer(
43
+ t.resources.board,
44
+ t.resources.firstPlayer,
45
+ );
46
+ const nextBoard = BoardState.setBoardCell({
47
+ board: t.resources.board,
48
+ index,
49
+ mark,
50
+ });
51
+ t.resources.board = nextBoard;
52
+ },
53
+ },
54
+ });
55
+
56
+ export type TictactoeDatabase = Database.FromPlugin<typeof tictactoePlugin>;
@@ -0,0 +1,10 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { DatabaseElement } from "@adobe/data-lit";
4
+ import { agentPlugin } from "./state/agent-plugin.js";
5
+
6
+ export class TictactoeElement extends DatabaseElement<typeof agentPlugin> {
7
+ get plugin() {
8
+ return agentPlugin;
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import type { PlayerMark } from "./player-mark/player-mark";
4
+
5
+ export type BoardCell = PlayerMark | " ";
@@ -0,0 +1,7 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import { Schema } from "@adobe/data/schema";
4
+ import { schema } from "./schema";
5
+
6
+ export type BoardState = Schema.ToType<typeof schema>;
7
+ export * as BoardState from "./public";
@@ -0,0 +1,5 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import type { BoardState } from "./board-state";
4
+
5
+ export const createInitialBoard = (): BoardState => " ";
@@ -0,0 +1,13 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ import type { BoardState } from "./board-state";
4
+ import type { PlayerMark } from "../player-mark/player-mark";
5
+
6
+ export const currentPlayer = (
7
+ board: BoardState,
8
+ firstPlayer: PlayerMark,
9
+ ): PlayerMark => {
10
+ const xCount = (board.match(/X/g) ?? []).length;
11
+ const oCount = (board.match(/O/g) ?? []).length;
12
+ return xCount === oCount ? firstPlayer : firstPlayer === "X" ? "O" : "X";
13
+ };