| 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 => {
});
},
},
},
System scheduling (optional):
systems: {
physics: {
create: db => () => { },
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 peers
Ensure services only access db.services from extended plugins (not forward references)
Export type *Database = Database.Plugin.ToDatabase<typeof *Plugin> when consumers need typed db access
Follow naming conventions for files, exports, and systems
}
}
Additional resources
- data-modeling.md — Components, resources, and archetypes (particle simulation example)