--- 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. ```sudolang 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, 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: ```ts 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: ```ts 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: ```ts export const appPlugin = Database.Plugin.combine( corePlugin, themePlugin, dataPlugin, authPlugin, uiPlugin, featurePlugin ); export type AppPlugin = typeof appPlugin; export type AppDatabase = Database.Plugin.ToDatabase; ``` --- ## Property details ### services Factory functions creating singleton services. Extended plugin services initialize first, so `db.services` has access to them. ```ts 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](data-modeling.md) for a simple example. ```ts 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](data-modeling.md) for patterns. ```ts 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: ```ts resources: { connection: { default: null as unknown as WebSocket }, }, ``` ### archetypes Standard ECS archetypes. Used for querying and inserting related components. See [data-modeling.md](data-modeling.md) for a simple example. ```ts archetypes: { Layout: ['layout', 'layoutElement', 'layoutLayer'], }, ``` ### computed Factory returning `Observe` or `(...args) => Observe`. Receives full db. ```ts 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. ```ts transactions: { updateLayout: (store, { entity, layout }: { entity: Entity; layout: Layout }) => { store.update(entity, { layout }); }, setThemeColor: (store, color: ThemeColor) => { store.resources.themeColor = color; }, }, ``` ```sudolang 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. ```ts 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. ```ts 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): ```ts systems: { physics: { create: db => () => { /* per-tick work */ }, schedule: { before: ['render'], after: ['input'], during: ['simulation'], }, }, }, ``` ```sudolang Schedule { before: "hard ordering constraints" after: "hard ordering constraints" during: "soft preference for same execution tier" } ``` --- ## Naming conventions ```sudolang 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 ```ts export type MyDatabase = Database.Plugin.ToDatabase; export type MyStore = Database.Plugin.ToStore; ``` --- ## Execute ```sudolang 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 when consumers need typed db access Follow naming conventions for files, exports, and systems } } ``` ## Additional resources - [data-modeling.md](data-modeling.md) — Components, resources, and archetypes (particle simulation example)