Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: aidd-ecs description: Enforces @adobe/data/ecs best practices. Use this whenever @adobe/data/ecs is imported, when creating or modifying Database.Plugin definitions, or when working with ECS components, resources, transactions, actions, systems, or services.
Database.Plugin authoring
Plugins are created with Database.Plugin.create() from @adobe/data/ecs.
Property order (enforced at runtime)
Properties must appear in this exact order. All are optional.
PluginPropertyOrder ["extends — Plugin, base plugin to extend""services — (db) => ServiceInstance, singleton service factories""components — schema object, ECS component schemas""resources — { default: value as Type }, global resource schemas""archetypes — ['comp1', 'comp2'], standard ECS archetypes; storage tables for efficient insertions""computed — (db) => Observe<T>, computed observables""transactions — (store, payload) => void, synchronous deterministic atomic mutations""actions — (db, payload) => T, general functions""systems — { create: (db) => fn | void }, per-frame (60fps) or init-only"]Constraints {Properties must appear in this exact order; wrong order throws at runtime}
Composition
Single extension — one plugin extends another:
export const authPlugin = Database.Plugin.create({extends: environmentPlugin,services: {auth: db => AuthService.createLazy({ services: db.services }),},});
Combine — extends accepts only one plugin. To extend from multiple use Database.Plugin.combine:
export const generationPlugin = Database.Plugin.create({extends: Database.Plugin.combine(aPlugin, bPlugin),computed: {max: db => Observe.withFilter(Observe.fromProperties({a: db.observe.resources.a,b: db.observe.resources.b}),({ a, b }) => Math.max(a, b))},});
Final composition — combine all plugins into the app plugin:
export const appPlugin = Database.Plugin.combine(corePlugin, themePlugin, dataPlugin,authPlugin, uiPlugin, featurePlugin);export type AppPlugin = typeof appPlugin;export type AppDatabase = Database.Plugin.ToDatabase<AppPlugin>;
Property details
services
Factory functions creating singleton services. Extended plugin services initialize first, so db.services has access to them.
services: {environment: _db => EnvironmentService.create(),}
components
Schema objects defining ECS component data. Use schema imports from type namespaces or inline schemas. See data-modeling.md for a simple example.
components: {layout: Layout.schema,layoutElement: { default: null as unknown as HTMLElement, transient: true },layoutLayer: F32.schema,},
Non-persistable values (e.g. HTML elements, DOM refs) must use transient: true — excluded from serialization.
resources
Global state not tied to entities. Use as Type to provide the compile-time type — without it the value is treated as a const literal. See data-modeling.md for patterns.
resources: {themeColor: { default: 'dark' as ThemeColor },themeScale: { default: 'medium' as ThemeScale },},
Use null as unknown as Type for resources initialized later in a system initializer:
resources: {connection: { default: null as unknown as WebSocket },},
archetypes
Standard ECS archetypes. Used for querying and inserting related components. See data-modeling.md for a simple example.
archetypes: {Layout: ['layout', 'layoutElement', 'layoutLayer'],},
computed
Factory returning Observe<T> or (...args) => Observe<T>. Receives full db.
computed: {max: db => Observe.withFilter(Observe.fromProperties({a: db.observe.resources.a,b: db.observe.resources.b,}),({ a, b }) => Math.max(a, b)),},
transactions
Synchronous, deterministic atomic mutations. Receive store and a payload. Store allows direct, immediate mutation of all entities, components, and resources.
transactions: {updateLayout: (store, { entity, layout }: { entity: Entity; layout: Layout }) => {store.update(entity, { layout });},setThemeColor: (store, color: ThemeColor) => {store.resources.themeColor = color;},},
StoreAPI {"store.update(entity, data)" = "update entity components""store.resources.x = value" = "mutate resources""store.get(entity, 'component')" = "read component value""store.read(entity)" = "read all entity component values""store.read(entity, archetype)" = "read entity component values in archetype""store.select(archetype.components, { where })" = "query entities"}
actions
General functions with access to the full db. Can return anything or nothing. UI components that call actions MUST never consume returned values — call for side effects only. Consuming return values violates unidirectional flow (data down via Observe, actions up as void). Call at most one transaction per action; multiple transactions corrupt the undo/redo stack.
actions: {generateNewName: async (db) => {const generatedName = await db.services.nameGenerator.generateName();db.transactions.setName(generatedName);},getAuth: db => db.services.auth,},
systems
create receives db and may optionally return a per-frame function (60fps) or just initialize values. Always called synchronously when database.extend(plugin) runs.
systems: {ui_state_plugin_initialize: {create: db => {db.transactions.registerViews(views);},},layout_plugin__system: {create: db => {const observer = new ResizeObserver(/* ... */);Database.observeSelectDeep(db, db.archetypes.Layout.components)(entries => {// react to entity changes});},},},
System scheduling (optional):
systems: {physics: {create: db => () => { /* per-tick work */ },schedule: {before: ['render'],after: ['input'],during: ['simulation'],},},},
Schedule {before: "hard ordering constraints"after: "hard ordering constraints"during: "soft preference for same execution tier"}
Naming conventions
PluginNaming {file: "*-plugin.ts (kebab-case) — e.g. layout-plugin.ts"export: "*Plugin (camelCase) — e.g. layoutPlugin"system: "plugin_name__system (snake_case, double underscore) — e.g. layout_plugin__system"initSystem: "plugin_name_initialize — e.g. ui_state_plugin_initialize"}
Type utilities
export type MyDatabase = Database.Plugin.ToDatabase<typeof myPlugin>;export type MyStore = Database.Plugin.ToStore<typeof myPlugin>;
Execute
fn whenCreatingOrModifyingPlugin() {Constraints {Verify property order matches (extends, services, components, resources, archetypes, computed, transactions, actions, systems)Use extends for single-parent; Database.Plugin.combine() for multiple peersEnsure services only access db.services from extended plugins (not forward references)Export type *Database = Database.Plugin.ToDatabase<typeof *Plugin> when consumers need typed db accessFollow naming conventions for files, exports, and systems}}
Additional resources
- data-modeling.md — Components, resources, and archetypes (particle simulation example)