@adobe/data-ai 0.9.91 → 0.9.92
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/.claude/rules/data-modelling.md +4 -3
- package/.claude/rules/ecs.md +1 -1
- package/.claude/rules/element.md +1 -1
- package/.claude/rules/features/data/index.md +48 -9
- package/.claude/rules/features/data/state.md +10 -7
- package/.claude/rules/features/index.md +1 -1
- package/.claude/rules/features/services/main-service/actions.md +5 -5
- package/.claude/rules/features/services/main-service/computed.md +3 -3
- package/.claude/rules/features/services/main-service/conformance.md +4 -3
- package/.claude/rules/features/services/main-service/index.md +14 -10
- package/.claude/rules/features/services/main-service/services.md +8 -7
- package/.claude/rules/features/ui/binding-element.md +4 -4
- package/.claude/rules/global/namespace.md +38 -10
- package/.claude/rules/global/react.md +1 -1
- package/.claude/rules/lit.md +3 -3
- package/.claude/rules/observe.md +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/package.json +3 -3
- package/skills/build-app-entry/SKILL.md +1 -1
- package/skills/build-data/SKILL.md +2 -2
- package/skills/build-systems/SKILL.md +1 -1
|
@@ -15,9 +15,10 @@ discriminated unions.
|
|
|
15
15
|
## Example
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
// types/http-method/
|
|
19
|
-
export
|
|
20
|
-
|
|
18
|
+
// types/http-method/http-method.ts — the hand-authored type owns the member identity
|
|
19
|
+
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
20
|
+
// (a schema.ts, if/when a runtime boundary needs one, is written to match this
|
|
21
|
+
// type and pinned to it — see global/namespace.md and features/data/index.md)
|
|
21
22
|
|
|
22
23
|
// In an unrelated request-counter plugin:
|
|
23
24
|
|
package/.claude/rules/ecs.md
CHANGED
|
@@ -143,7 +143,7 @@ Synchronous, deterministic atomic mutations. Receive `store` and a payload.
|
|
|
143
143
|
|
|
144
144
|
### actions
|
|
145
145
|
|
|
146
|
-
UI components that call actions must never consume returned values — see
|
|
146
|
+
UI components that call actions must never consume returned values — see `features/ui/binding-element.md` (Actions are fire-and-forget).
|
|
147
147
|
|
|
148
148
|
**At most one transaction per action.** Multiple transactions in a single action corrupt the undo/redo stack.
|
|
149
149
|
|
package/.claude/rules/element.md
CHANGED
|
@@ -125,7 +125,7 @@ exists *only* for locator inputs — never for state, never for derived
|
|
|
125
125
|
values, never for parent-forwarded flags. The `P` in `DatabaseElement<P>` (and
|
|
126
126
|
the `get plugin()` it returns) is the surface the element *consumes* — never the
|
|
127
127
|
varying topmost-layer name (see `features/services/main-service/index.md`): `typeof
|
|
128
|
-
|
|
128
|
+
MainService.plugin` for a self-contained feature. **Exception:** an element
|
|
129
129
|
meant to be *extended* — injected with various databases that build on this
|
|
130
130
|
feature (a peer app that adds an agent, a p2p/presence build) — types on the
|
|
131
131
|
minimal base layer it consumes (e.g. `ComputedDatabase.plugin`) so every
|
|
@@ -12,23 +12,62 @@ It depends on nothing but `@adobe/data` and other `data/` declarations, and
|
|
|
12
12
|
needs no knowledge of anything built on top of it.
|
|
13
13
|
|
|
14
14
|
Each data type is its own namespace folder (see `global/namespace.md`), holding
|
|
15
|
-
its
|
|
15
|
+
its hand-authored type, its (optional, matching) schema, and its pure
|
|
16
|
+
synchronous helpers together:
|
|
16
17
|
|
|
17
18
|
```
|
|
18
19
|
data/player-mark/
|
|
19
|
-
player-mark.ts # type
|
|
20
|
-
schema.ts #
|
|
20
|
+
player-mark.ts # HAND-AUTHORED type + `export * as PlayerMark from "./public.js"`
|
|
21
|
+
schema.ts # (optional / later) schema matching the type, pinned via Assert<Equal<…>>
|
|
21
22
|
public.ts # re-exports schema + helpers
|
|
22
23
|
is.ts values.ts opponent.ts … # one pure helper per file
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
- The **type is
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
- The **type is authored by hand** in `<type>.ts` — the readable source of
|
|
27
|
+
truth. Write it with the richest types available (`F32`, `Vec3`, unions,
|
|
28
|
+
branded aliases) so members carry their semantic meaning and get strong
|
|
29
|
+
IntelliSense / hover docs.
|
|
30
|
+
- The **schema is derived from the type, not the reverse.** It is written to
|
|
31
|
+
match the type and **pinned** to it with a compile-time assertion so the two
|
|
32
|
+
cannot drift (see below). A field typed as a branded primitive reuses that
|
|
33
|
+
type's schema — `F32` → `F32.schema`, `Vec3` → `Vec3.schema`.
|
|
34
|
+
- **Schemas are optional until a runtime boundary needs one.** A pure `data/`
|
|
35
|
+
type is complete with `<type>.ts` alone. The schema is added only when
|
|
36
|
+
persistence, the wire, or an ECS component/resource requires it — and is
|
|
37
|
+
typically written by the agent in a later phase (e.g. when implementing the
|
|
38
|
+
ECS), not by the human up front.
|
|
29
39
|
- Helpers are synchronous and pure; each has a sibling `*.test.ts`.
|
|
30
|
-
- Objects
|
|
31
|
-
|
|
40
|
+
- Objects of only 32-bit numeric values / sub-structs: the schema uses
|
|
41
|
+
`Schema.fromStructProperties` (guarantees a valid struct schema, stored in
|
|
42
|
+
linear memory).
|
|
43
|
+
|
|
44
|
+
## Pinning the schema to the type
|
|
45
|
+
|
|
46
|
+
The type is written first and by hand; the schema is written to match it and
|
|
47
|
+
is locked to it at compile time, so schema authoring (whenever it happens)
|
|
48
|
+
cannot silently drift from the type readers rely on.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// player-mark/player-mark.ts — HAND-AUTHORED, the source of truth
|
|
52
|
+
export type PlayerMark = 'X' | 'O';
|
|
53
|
+
export * as PlayerMark from './public.js';
|
|
54
|
+
|
|
55
|
+
// player-mark/schema.ts — written to MATCH the type (often later, by the agent)
|
|
56
|
+
import { Schema } from '@adobe/data/schema';
|
|
57
|
+
import type { Assert, Equal } from '@adobe/data/types';
|
|
58
|
+
import type { PlayerMark } from './player-mark.js';
|
|
59
|
+
|
|
60
|
+
export const schema = { type: 'string', enum: ['X', 'O'] } as const satisfies Schema;
|
|
61
|
+
|
|
62
|
+
// Compile-time pin: fails to build if schema and type diverge.
|
|
63
|
+
type _Pin = Assert<Equal<Schema.ToType<typeof schema>, PlayerMark>>;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The `Assert<Equal<…>>` line is the whole safety net: `Schema.ToType<typeof
|
|
67
|
+
schema>` must be exactly the hand-authored type (including readonly/optional),
|
|
68
|
+
or the file does not compile. `Schema.ToType` is used **only** inside the
|
|
69
|
+
assertion — never as the exported type. Consumers always import the
|
|
70
|
+
hand-authored `PlayerMark`.
|
|
32
71
|
|
|
33
72
|
One folder is special: **`data/state/`** holds the feature's single `State`
|
|
34
73
|
aggregate and its pure transforms/derivations (its own rule, `state.md`);
|
|
@@ -41,13 +41,16 @@ export * as State from "./public.js";
|
|
|
41
41
|
- Guard and **return `state` unchanged** on a no-op / illegal input rather
|
|
42
42
|
than throwing — this keeps transforms idempotent under repeated application.
|
|
43
43
|
- Each transform has a sibling `*.test.ts`; performance is irrelevant here,
|
|
44
|
-
correctness is everything.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
correctness is everything. The test file **exports** its cases —
|
|
45
|
+
`export const cases: ConformanceCase<Args>[]` (the shared `conformance-case.ts`
|
|
46
|
+
type) — right alongside the `describe`/`it` that exercise them. This is
|
|
47
|
+
spec-owned truth the matching main-service conformance test imports **from the
|
|
48
|
+
`<transform>.test.js` file** unchanged (see `services/main-service/conformance.md`).
|
|
49
|
+
Keeping the cases in the test file rather than a separate `<transform>.cases.ts`
|
|
50
|
+
removes a file per transform — less folder clutter, same reuse. Author
|
|
51
|
+
`before`/`after` as full `State` (`{ ...State.create(), …overrides }`); the
|
|
52
|
+
generic-slice signature lets them flow through. Tolerant full-`State` equality is
|
|
53
|
+
the shared `expect-state-matches.ts`.
|
|
51
54
|
|
|
52
55
|
## Derivations — `(state) => value`
|
|
53
56
|
|
|
@@ -88,7 +88,7 @@ main-service mutation, seeded and read back through a test-only store↔`State`
|
|
|
88
88
|
projection, equals the pure `data/` transform it stands for. The projection and
|
|
89
89
|
its runner live in `services/main-service/conformance/` (see
|
|
90
90
|
`services/main-service/conformance.md`); the shared `{ before, args, after }`
|
|
91
|
-
cases are spec-owned (`data/state/<transform>.
|
|
91
|
+
cases are spec-owned (exported from `data/state/<transform>.test.ts`), so conforming the
|
|
92
92
|
implementation is "substitute the implementation, reuse the expectations." This
|
|
93
93
|
lets `main-service` be largely mechanical and agent-generated, with the spec as
|
|
94
94
|
oracle. *How* to author each layer lives in the per-folder rules below.
|
|
@@ -13,19 +13,19 @@ timing — and then commit the result through a transaction.
|
|
|
13
13
|
```ts
|
|
14
14
|
import type { ServiceDatabase } from "../../service-database/service-database.js";
|
|
15
15
|
|
|
16
|
-
export const addRandomTodo = async (
|
|
17
|
-
const name = await
|
|
18
|
-
|
|
16
|
+
export const addRandomTodo = async (service: ServiceDatabase) => {
|
|
17
|
+
const name = await service.services.nameGenerator.generateName(); // await a services/ port
|
|
18
|
+
service.transactions.createTodo({ name }); // then exactly one commit
|
|
19
19
|
};
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
- Type the `
|
|
22
|
+
- Type the `service` parameter on the lowest database layer exposing what the
|
|
23
23
|
action touches — usually `ServiceDatabase` (services **and**
|
|
24
24
|
transactions). Never the action layer itself (that would be a cycle).
|
|
25
25
|
- **Call at most one transaction** per action, so undo/redo stays one step
|
|
26
26
|
per operation.
|
|
27
27
|
- **Fire-and-forget.** An action's return value is not consumed; results flow
|
|
28
|
-
back through observables. `
|
|
28
|
+
back through observables. `service.services.*` calls are `void` or
|
|
29
29
|
awaited-internally, never surfaced to the caller.
|
|
30
30
|
- Do the outside-world work here: await/sequence `services/` calls, and if a
|
|
31
31
|
slow call needs timing, compute it here around the call.
|
|
@@ -7,7 +7,7 @@ paths:
|
|
|
7
7
|
|
|
8
8
|
One derived value per file: a `cached` function of a database layer that
|
|
9
9
|
returns an `Observe` of state projected through pure `data/` helpers.
|
|
10
|
-
Derivation logic itself lives in `data/`; a computed only wires a
|
|
10
|
+
Derivation logic itself lives in `data/`; a computed only wires a service
|
|
11
11
|
observable to it.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
@@ -16,8 +16,8 @@ import { Observe } from "@adobe/data/observe";
|
|
|
16
16
|
import { BoardState } from "../../../data/board-state/board-state.js";
|
|
17
17
|
import type { IndexDatabase } from "../../index-database/index-database.js";
|
|
18
18
|
|
|
19
|
-
export const status = cached((
|
|
20
|
-
Observe.withFilter(
|
|
19
|
+
export const status = cached((service: IndexDatabase) =>
|
|
20
|
+
Observe.withFilter(service.observe.resources.board, BoardState.deriveStatus),
|
|
21
21
|
);
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -29,8 +29,9 @@ Conformance test-support splits by concern along the layer boundary:
|
|
|
29
29
|
`fromState(before)` → `apply(store, args)` → `toState ≡ after`.
|
|
30
30
|
- **Spec-side, in `data/state/`** (State values, no store — so both the `data/`
|
|
31
31
|
transform tests and this runner import them without a layer violation):
|
|
32
|
-
`conformance-case.ts` (`ConformanceCase<Args>`), the
|
|
33
|
-
cases
|
|
32
|
+
`conformance-case.ts` (`ConformanceCase<Args>`), the cases **exported from each
|
|
33
|
+
`<transform>.test.ts`** (`import { cases } from "…/<transform>.test.js"` — no
|
|
34
|
+
separate `.cases.ts` file), and `expect-state-matches.ts` (State equality).
|
|
34
35
|
|
|
35
36
|
## No cast — build on `Store.create`, not a `Database`
|
|
36
37
|
|
|
@@ -39,7 +40,7 @@ A **transaction is `(store, args) => void`**, so transaction conformance needs n
|
|
|
39
40
|
`CoreDatabase.Store` — pass the plugin directly, `Store.create` reads its schema
|
|
40
41
|
facets. Source it from the **lowest layer that declares all the schema** —
|
|
41
42
|
`IndexDatabase`, or `CoreDatabase` if the feature has no indexes — **not**
|
|
42
|
-
`
|
|
43
|
+
`MainService`: the store needs only schema, and the behaviour layers
|
|
43
44
|
(transactions / computed / systems) add none.
|
|
44
45
|
`fromState`/`toState`/`apply` all operate on it, and `apply` calls the **raw
|
|
45
46
|
transaction function** directly:
|
|
@@ -119,26 +119,29 @@ export namespace IndexDatabase {
|
|
|
119
119
|
}
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
### The assembled database — `
|
|
122
|
+
### The assembled database — `MainService`
|
|
123
123
|
|
|
124
124
|
*Which* layer is topmost varies with the facets a feature uses (computed /
|
|
125
125
|
service / action / system). Consumers — `ui/`, the app entry, `services/main-service/conformance/`
|
|
126
126
|
— need the *whole* feature database but must not name that layer, or adding or
|
|
127
|
-
dropping a layer rewrites them all. Alias it once
|
|
127
|
+
dropping a layer rewrites them all. Alias it once from the **folder-eponymous
|
|
128
|
+
`main-service.ts`** (per `global/namespace.md`, a folder's primary export lives in
|
|
129
|
+
its same-named file), as a namespace so consumers reach `MainService.plugin` /
|
|
130
|
+
`MainService.Store`:
|
|
128
131
|
|
|
129
132
|
```ts
|
|
130
|
-
// services/main-service/
|
|
131
|
-
export { SystemDatabase as
|
|
133
|
+
// services/main-service/main-service.ts
|
|
134
|
+
export { SystemDatabase as MainService } from "./system-database/system-database.js";
|
|
132
135
|
```
|
|
133
136
|
|
|
134
|
-
Every consumer references **`
|
|
137
|
+
Every consumer references **`MainService`** (`.plugin`, `.Store`) — never the
|
|
135
138
|
topmost layer. Add or drop a layer → change only this one line.
|
|
136
139
|
|
|
137
140
|
**Cross-feature naming.** Inside a feature the core and assembled databases are
|
|
138
|
-
the bare `CoreDatabase` / `
|
|
141
|
+
the bare `CoreDatabase` / `MainService`. When *another* feature or package
|
|
139
142
|
imports one — a peer built on it, the base `imports` a peer's `core-database`, a
|
|
140
143
|
downstream package — reference it **feature-qualified**: `<Feature>CoreDatabase`
|
|
141
|
-
/ `<Feature>
|
|
144
|
+
/ `<Feature>MainService`, re-exported so from the feature's barrel
|
|
142
145
|
(`export { CoreDatabase as TodoCoreDatabase } from "…/core-database.js"`) — so
|
|
143
146
|
two features' `CoreDatabase`s never collide.
|
|
144
147
|
|
|
@@ -153,9 +156,10 @@ two features' `CoreDatabase`s never collide.
|
|
|
153
156
|
reads/writes entities, resources, or archetypes; **`IndexDatabase.Store`** the
|
|
154
157
|
moment it reads an index.
|
|
155
158
|
- **the whole `Database`** — **computed**, **services**, and **actions** each
|
|
156
|
-
take `
|
|
157
|
-
read/call): computed reads `
|
|
158
|
-
transactions; actions call `
|
|
159
|
+
take `service: <Layer>` (the lowest layer whose database exposes what they
|
|
160
|
+
read/call): computed reads `service.observe.*`; services read observables and
|
|
161
|
+
call transactions; actions call `service.services.*` then
|
|
162
|
+
`service.transactions.*`.
|
|
159
163
|
|
|
160
164
|
Each subfolder has its own rule. Modelling a plugin's authored vs. derived
|
|
161
165
|
surface is covered by `plugin-modelling.md`.
|
|
@@ -5,20 +5,21 @@ paths:
|
|
|
5
5
|
|
|
6
6
|
# database/services/ — database-bound service factories
|
|
7
7
|
|
|
8
|
-
One factory per file: `create<Name>Service(
|
|
8
|
+
One factory per file: `create<Name>Service(service, …)` that binds a service
|
|
9
9
|
implementation to a live database — reading its observables and invoking
|
|
10
10
|
its transactions. This is where the async `services/` contracts (or
|
|
11
11
|
framework services like `AgenticService`) are wired to ECS state.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
export const createAgentService = (
|
|
15
|
-
/* … reads
|
|
14
|
+
export const createAgentService = (service: ComputedDatabase, mark: PlayerMark): AgenticService =>
|
|
15
|
+
/* … reads service.observe.*, calls service.transactions.* … */;
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Type `
|
|
19
|
-
`service-database` itself — that would be a cycle).
|
|
18
|
+
Type `service` on the lowest layer exposing what the factory reads/calls (never
|
|
19
|
+
the `service-database` itself — that would be a cycle).
|
|
20
20
|
|
|
21
21
|
An `index.ts` barrel re-exports the factories; `service-database.ts`
|
|
22
22
|
registers them under the `services` facet, each keyed by the name
|
|
23
|
-
consumers read it as (`
|
|
24
|
-
contract (not yet bound to a
|
|
23
|
+
consumers read it as (`service.services.agent`). Defining a standalone
|
|
24
|
+
service contract (not yet bound to a database) belongs in the feature
|
|
25
|
+
`services/` layer.
|
|
@@ -38,7 +38,7 @@ A binding element is a thin wire between a service and a presentation. The class
|
|
|
38
38
|
|
|
39
39
|
No other members. No `@state`. No private handler fields. No lifecycle methods (`connectedCallback`, `updated`, `firstUpdated`,
|
|
40
40
|
`disconnectedCallback`) except on app-entrypoint classes where the lifecycle concern is app-boot shaped but cannot be expressed as a hook
|
|
41
|
-
(see
|
|
41
|
+
(see `hooks.md` for hook-first lifecycle).
|
|
42
42
|
|
|
43
43
|
## `@property` allowlist
|
|
44
44
|
|
|
@@ -114,7 +114,7 @@ one-liners** above.
|
|
|
114
114
|
## Lifecycle goes through hooks
|
|
115
115
|
|
|
116
116
|
Any need for mount/unmount/update behavior routes through a hook. **Reuse order, one hook per file, presentation vs binding placement, and
|
|
117
|
-
testing notes:**
|
|
117
|
+
testing notes:** `hooks.md`.
|
|
118
118
|
|
|
119
119
|
Never inline lifecycle logic in the binding. App-entrypoint classes may override lifecycle methods when the concern is app-boot shaped
|
|
120
120
|
(service wiring, ancestor lookup); even then, prefer a hook.
|
|
@@ -158,14 +158,14 @@ expose them by design; reaching for them means a missing computed.
|
|
|
158
158
|
|
|
159
159
|
A binding element imports a sibling `*-presentation.ts` file that exports exactly `render` (required) and optionally `unlocalized`. Nothing
|
|
160
160
|
else — no types, no styles, no helpers. Consumers derive the props type via `Parameters<typeof render>[0]`. Presentation stays hook-free by
|
|
161
|
-
default; element hooks live here or in shared modules per
|
|
161
|
+
default; element hooks live here or in shared modules per `hooks.md` and `features/ui/presentation.md`.
|
|
162
162
|
|
|
163
163
|
**For refactors whose stated scope does not include presentations**, the matching `*-presentation.ts` file must not change. Verify with
|
|
164
164
|
`git diff --name-only | grep presentation`. If a scope-limited refactor requires a presentation signature change, stop and reconsider the
|
|
165
165
|
binding-side helper — the scope fence exists because presentations are the public contract between binding and render output.
|
|
166
166
|
|
|
167
167
|
Callback prop names use **verb** or **verbNoun** form. Never `on*` prefix. The binding-side variable matches the presentation prop name 1:1.
|
|
168
|
-
See
|
|
168
|
+
See `features/ui/presentation.md` for full examples.
|
|
169
169
|
|
|
170
170
|
## Size budget
|
|
171
171
|
|
|
@@ -32,8 +32,10 @@ patterns (e.g., inline `export namespace` in shared files) that do not conform
|
|
|
32
32
|
- `<type-name>/<type-name>.ts` is the only public import surface
|
|
33
33
|
- Export a single type alias: `export type <type-name> = <type>` (or `export type * from "./<type-name>-types.js"` when the type lives in a
|
|
34
34
|
separate file)
|
|
35
|
-
-
|
|
36
|
-
`
|
|
35
|
+
- The type alias is **hand-authored** in `<type-name>.ts`. When a schema is needed it lives in `<type-name>-schema.ts` (or
|
|
36
|
+
`schema.ts` inside the folder), named `schema` (not `<type-name>Schema`), and is **written to match the hand-authored type and pinned
|
|
37
|
+
to it** with `Assert<Equal<Schema.ToType<typeof schema>, <TypeName>>>` — never the other way around. `Schema.ToType` appears only in
|
|
38
|
+
that assertion, never as the exported type.
|
|
37
39
|
- Export namespace: `export * as <type-name> from "./public.js"`
|
|
38
40
|
- In public.ts, re-export every public constant file
|
|
39
41
|
- Filenames map deterministically to single export:
|
|
@@ -43,6 +45,26 @@ patterns (e.g., inline `export namespace` in shared files) that do not conform
|
|
|
43
45
|
- Function files: name by purpose only (e.g., `add.ts`, `sub.ts`). **Do not prefix** with the type name — the folder provides the namespace
|
|
44
46
|
context.
|
|
45
47
|
|
|
48
|
+
## Constants file
|
|
49
|
+
|
|
50
|
+
Constants are the **one exception** to one-declaration-per-file. A namespace folder MAY have a single `constants.ts` exporting several
|
|
51
|
+
plain `const` literals (no functions, no types), re-exported through `public.ts` so callers reach them as `<TypeName>.<constant>`:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// duration/constants.ts
|
|
55
|
+
export const minSeconds = 5;
|
|
56
|
+
export const maxSeconds = 300;
|
|
57
|
+
export const defaultSeconds = 30;
|
|
58
|
+
|
|
59
|
+
// duration/public.ts
|
|
60
|
+
export * from './constants.js';
|
|
61
|
+
|
|
62
|
+
// consumers: Duration.minSeconds, Duration.maxSeconds, Duration.defaultSeconds
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Function declarations remain **one per file** (each in its own purpose-named file). If a value needs logic rather than being a plain
|
|
66
|
+
literal, it is a function and does not belong in `constants.ts`.
|
|
67
|
+
|
|
46
68
|
## Anti-patterns (do not copy)
|
|
47
69
|
|
|
48
70
|
- **`export namespace <TypeName> { ... }`** — Use `export * as <TypeName> from "./public.js"` instead.
|
|
@@ -63,18 +85,24 @@ patterns (e.g., inline `export namespace` in shared files) that do not conform
|
|
|
63
85
|
|
|
64
86
|
## Schema pattern example
|
|
65
87
|
|
|
88
|
+
The type is authored first and by hand; the schema (if/when needed) is written
|
|
89
|
+
to match it and pinned so it cannot drift.
|
|
90
|
+
|
|
66
91
|
```ts
|
|
67
|
-
// src/types/player-mark/player-mark
|
|
68
|
-
export
|
|
92
|
+
// src/types/player-mark/player-mark.ts — HAND-AUTHORED, the source of truth
|
|
93
|
+
export type PlayerMark = 'X' | 'O';
|
|
94
|
+
export * as PlayerMark from './public.js';
|
|
95
|
+
|
|
96
|
+
// src/types/player-mark/player-mark-schema.ts — written to MATCH the type
|
|
97
|
+
import { Schema } from '@adobe/data/schema';
|
|
98
|
+
import type { Assert, Equal } from '@adobe/data/types';
|
|
99
|
+
import type { PlayerMark } from './player-mark.js';
|
|
100
|
+
export const schema = { type: 'string', enum: ['X', 'O'] } as const satisfies Schema;
|
|
101
|
+
// Compile-time pin: fails to build if schema and type diverge.
|
|
102
|
+
type _Pin = Assert<Equal<Schema.ToType<typeof schema>, PlayerMark>>;
|
|
69
103
|
|
|
70
104
|
// src/types/player-mark/public.ts
|
|
71
105
|
export { schema } from './player-mark-schema.js';
|
|
72
|
-
|
|
73
|
-
// src/types/player-mark/player-mark.ts
|
|
74
|
-
import { Schema } from '@adobe/data/schema';
|
|
75
|
-
import { schema } from './player-mark-schema.js';
|
|
76
|
-
export type PlayerMark = Schema.ToType<typeof schema>;
|
|
77
|
-
export * as PlayerMark from './public.js';
|
|
78
106
|
```
|
|
79
107
|
|
|
80
108
|
## Example: `Point` type refactor
|
|
@@ -76,7 +76,7 @@ use `Parameters<typeof render>[0]`.
|
|
|
76
76
|
|
|
77
77
|
## Action callbacks (not events)
|
|
78
78
|
|
|
79
|
-
Use **verb** or **verbNoun** names — never `on*` prefix. See
|
|
79
|
+
Use **verb** or **verbNoun** names — never `on*` prefix. See `features/ui/presentation.md` for full examples.
|
|
80
80
|
|
|
81
81
|
**Pass function references directly** when the signature matches; wrap in an arrow function only to supply arguments:
|
|
82
82
|
|
package/.claude/rules/lit.md
CHANGED
|
@@ -76,7 +76,7 @@ there.
|
|
|
76
76
|
|
|
77
77
|
## Action callbacks (not events)
|
|
78
78
|
|
|
79
|
-
Use **verb** or **verbNoun** names — never `on*` prefix. See
|
|
79
|
+
Use **verb** or **verbNoun** names — never `on*` prefix. See `features/ui/presentation.md` for full examples and rationale.
|
|
80
80
|
|
|
81
81
|
---
|
|
82
82
|
|
|
@@ -91,7 +91,7 @@ view hosts. Multiple instances of the same element need a property to identify w
|
|
|
91
91
|
|
|
92
92
|
## Hooks
|
|
93
93
|
|
|
94
|
-
Canonical rules (reuse order, one hook per file, binding vs presentation):
|
|
94
|
+
Canonical rules (reuse order, one hook per file, binding vs presentation): `hooks.md`.
|
|
95
95
|
|
|
96
96
|
Use hooks from `src/elements/hooks/` and `@adobe/data-lit` instead of `connectedCallback`/`disconnectedCallback`. Use `useEffect` for
|
|
97
97
|
setup/teardown, `useKeyboardEvent` for single key events.
|
|
@@ -107,7 +107,7 @@ When creating or modifying a Lit element:
|
|
|
107
107
|
3. Use a single `useObservableValues` in the binding element. Observe only minimal values; use `Observe.withDefault` for slow-resolving
|
|
108
108
|
values.
|
|
109
109
|
4. Pass observed values and action callbacks to presentation.
|
|
110
|
-
5. Keep presentation pure — no hooks (see
|
|
110
|
+
5. Keep presentation pure — no hooks (see `hooks.md` for exceptions).
|
|
111
111
|
6. Add `@property` only when entity binding requires it (multiple instances).
|
|
112
112
|
7. Presentation exports only `render` (and localization bundles where appropriate). Binding element sets `static styles` from `*.css.ts`.
|
|
113
113
|
8. Add `*-presentation.test.ts` for presentation when appropriate; do not unit test binding elements.
|
package/.claude/rules/observe.md
CHANGED
|
@@ -147,7 +147,7 @@ Observe.withMap(rows$, rows => rows.filter(r => r.selectedByUsers?.includes(user
|
|
|
147
147
|
`observeSelectDeep` re-emits on **any** change to **any** listed component on **any** matched entity — so list only the fields actually
|
|
148
148
|
read, and when the matched set is large pair it with a structural deduplicate downstream so identical-looking emissions don't propagate
|
|
149
149
|
(`@adobe/data/observe`'s `withDeduplicate` is reference-equality only). `observeSelectDeep` from a UI element is banned — let each element
|
|
150
|
-
observe its own entity (see
|
|
150
|
+
observe its own entity (see `ecs.md`).
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/data-ai",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.92",
|
|
4
4
|
"description": "Cross-agent architecture skills for @adobe/data — installable as a Claude Code plugin or copied into any Agent-Skills-compatible agent (Cursor, Codex).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"skills"
|
|
30
30
|
],
|
|
31
31
|
"peerDependencies": {
|
|
32
|
-
"@adobe/data": "0.9.
|
|
32
|
+
"@adobe/data": "0.9.92"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "^5.8.3",
|
|
36
36
|
"vitest": "^1.6.0",
|
|
37
|
-
"@adobe/data": "0.9.
|
|
37
|
+
"@adobe/data": "0.9.92"
|
|
38
38
|
},
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"scripts": {
|
|
@@ -9,7 +9,7 @@ output: app
|
|
|
9
9
|
plugin, so all schemas coexist, persist, and sync while the base type stays decoupled.
|
|
10
10
|
- The base reaches a peer only through a lazy element wrapper (dynamic import), so peers
|
|
11
11
|
load on demand.
|
|
12
|
-
- The entry point creates the live database from `
|
|
12
|
+
- The entry point creates the live database from `MainService.plugin` (the base
|
|
13
13
|
feature's assembled database — see `features/services/main-service/index.md`) and mounts the root UI.
|
|
14
14
|
|
|
15
15
|
See `features/index.md` (one-app-many-features) for the wiring rules.
|
|
@@ -8,14 +8,14 @@ output: feature
|
|
|
8
8
|
Create the feature's `data/` layer (pure spec; depends on nothing but `@adobe/data`
|
|
9
9
|
and other `data/` declarations):
|
|
10
10
|
|
|
11
|
-
- one namespace folder per data type — `<type>/{<type>.ts,
|
|
11
|
+
- one namespace folder per data type — `<type>/{<type>.ts, public.ts, <helper>.ts}` (+ `schema.ts` only when a runtime boundary needs it);
|
|
12
12
|
- `data/state/` — the `State` aggregate plus its pure transforms/derivations.
|
|
13
13
|
|
|
14
14
|
**Always create `data/state/`** — even for a shell feature whose only field is a
|
|
15
15
|
boolean or enum. A feature with ECS resources/transactions but no `State` is
|
|
16
16
|
incomplete: the transaction must wrap a pure transform, not invent the logic. In rare circumstances, there may be no real state in a feature in which case the state type = {}
|
|
17
17
|
|
|
18
|
-
The
|
|
18
|
+
The hand-authored type in `<type>.ts` is the source of truth. The schema is optional up front — add `schema.ts` only when a runtime boundary (persistence, wire, or an ECS component/resource) needs one, write it to match the type, and pin it with `type _Pin = Assert<Equal<Schema.ToType<typeof schema>, <Type>>>` so it cannot drift. `Schema.ToType` is used only in that assertion, never as the exported type.
|
|
19
19
|
Helpers are pure and unit-tested. Run this first — every other layer imports `data/`.
|
|
20
20
|
|
|
21
21
|
The how is in the auto-loading rules: `features/data/index.md`, `features/data/state.md`, and
|
|
@@ -12,7 +12,7 @@ Create `services/main-service/system-database/system-database.ts`: `Database.Plu
|
|
|
12
12
|
Database.Plugin.combine(<currentTop>.plugin, scheduler), systems })` — combine `scheduler` with the
|
|
13
13
|
feature's **current top** main-service layer (`ActionDatabase` / `ServiceDatabase` / `ComputedDatabase`,
|
|
14
14
|
whichever it built), never a hardcoded `ComputedDatabase`, so systems *and* any services/actions
|
|
15
|
-
compose into the one `
|
|
15
|
+
compose into the one `MainService`. The `systems`
|
|
16
16
|
map is declared **inline** (see `features/services/main-service/systems.md` — inline is required for `db` to be
|
|
17
17
|
typed and for system-name inference; a `systems/` folder is optional, only for extracted
|
|
18
18
|
per-frame body helpers).
|