Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-monorepo",
"version": "0.9.64",
"version": "0.9.65",
"private": true,
"scripts": {
"build": "pnpm -r run build",
Expand Down
2 changes: 1 addition & 1 deletion packages/data-lit-tictactoe/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-lit-tictactoe",
"version": "0.9.64",
"version": "0.9.65",
"description": "Tic-Tac-Toe sample - Lit web components with @adobe/data-lit and AgenticService",
"type": "module",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-lit-todo/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-lit-todo",
"version": "0.9.64",
"version": "0.9.65",
"description": "Todo sample app demonstrating @adobe/data with Lit",
"type": "module",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-lit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-lit",
"version": "0.9.64",
"version": "0.9.65",
"description": "Adobe data Lit bindings - hooks, elements, decorators",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-p2p-tictactoe/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-p2p-tictactoe",
"version": "0.9.64",
"version": "0.9.65",
"description": "Serverless P2P tic-tac-toe — WebRTC DataChannel + @adobe/data-sync",
"type": "module",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-persistence/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-persistence",
"version": "0.9.64",
"version": "0.9.65",
"description": "Worker-based incremental persistence layer for @adobe/data ECS over OPFS (browser) and node:fs (server).",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-react-hello/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-react-hello",
"version": "0.9.64",
"version": "0.9.65",
"description": "Hello World sample - click counter using @adobe/data-react",
"type": "module",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-react-pixie/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-react-pixie",
"version": "0.9.64",
"version": "0.9.65",
"description": "PixiJS React sample - ECS sprites (bunny, fox) with @adobe/data-react",
"type": "module",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-react",
"version": "0.9.64",
"version": "0.9.65",
"description": "Adobe data React bindings — hooks and context for ECS database",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-solid-dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "data-solid-dashboard",
"version": "0.9.64",
"version": "0.9.65",
"description": "Mini dashboard sample — multiple components sharing one @adobe/data ECS database with SolidJS",
"type": "module",
"private": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-solid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-solid",
"version": "0.9.64",
"version": "0.9.65",
"description": "Adobe data SolidJS bindings — context and provider for ECS database",
"type": "module",
"private": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data-sync/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data-sync",
"version": "0.9.64",
"version": "0.9.65",
"description": "Multi-user real-time synchronisation for @adobe/data ECS — server, client, and in-process loopback.",
"type": "module",
"sideEffects": false,
Expand Down
2 changes: 1 addition & 1 deletion packages/data/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adobe/data",
"version": "0.9.64",
"version": "0.9.65",
"description": "Adobe data oriented programming library",
"type": "module",
"sideEffects": false,
Expand Down
9 changes: 4 additions & 5 deletions packages/data/scripts/typeperf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ the knee.
## Result — `imports` vs `extends`

`imports` makes ancestor types visible to local declarations **without**
re-exporting them into the result type. Each link's result stays
`O(local members)`, so the chain becomes **linear**; consumers compose the
union once via `Database.Plugin.combine(...)`. Measured (TS 5.8.3,
`node scripts/typeperf/measure.mjs extends imports`):
re-exporting them into the result type (it still merges them at runtime, like
`extends` — the difference is purely in the result type). Each link's result
type stays `O(local members)`, so the chain becomes **linear**. Measured
(TS 5.8.3, `node scripts/typeperf/measure.mjs extends imports`):

| depth | extends (inst) | imports (inst) | speedup |
|------:|---------------:|---------------:|--------:|
Expand All @@ -94,4 +94,3 @@ several ancestors imports a combined context
per import site over local-only (O(1)) operands, so the total stays linear in
total members as long as authors keep result types local — which `imports`
enforces by construction.
</content>
52 changes: 52 additions & 0 deletions packages/data/src/ecs/database/create-plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,5 +675,57 @@ describe("Database.Plugin.create", () => {
}).not.toThrow();
});
});

describe("imports runtime behavior", () => {
// `imports` differs from `extends` ONLY in the result type (imported
// members are not declared there — see imports-chain.type-test.ts).
// At runtime, imports merge into the assembled plugin exactly like
// extends, so the consumer does NOT have to re-list the imported plugin
// in the top-level combine for its members to exist.
const basePlugin = createPlugin({
resources: { baseScale: { default: 7 as number } },
transactions: {
setBaseScale: (_t, _input: { scale: number }) => { },
},
});

it("merges imported plugin members into the runtime plugin object", () => {
const featurePlugin = createPlugin({
imports: basePlugin,
resources: { featureFlag: { default: true as boolean } },
});
// Imported members are present on the assembled plugin at runtime...
expect((featurePlugin.resources as any).baseScale).toBeDefined();
expect((featurePlugin.transactions as any).setBaseScale).toBeDefined();
// ...alongside the local ones.
expect((featurePlugin.resources as any).featureFlag).toBeDefined();
});

it("imported members are usable on a database built from the plugin alone", () => {
const featurePlugin = createPlugin({
imports: basePlugin,
resources: { featureFlag: { default: true as boolean } },
});
// No separate combine(basePlugin, ...) — imports already merged.
const db = Database.create(featurePlugin);
expect((db.resources as any).baseScale).toBe(7);
expect(() => (db.transactions as any).setBaseScale({ scale: 9 })).not.toThrow();
});

it("merges imports first, then extends, then local declarations", () => {
const extendedBase = createPlugin({
resources: { fromExtends: { default: 1 as number } },
});
const combined = createPlugin({
imports: basePlugin,
extends: extendedBase,
resources: { fromLocal: { default: 2 as number } },
});
const r = combined.resources as any;
expect(r.baseScale).toBeDefined(); // from imports
expect(r.fromExtends).toBeDefined(); // from extends
expect(r.fromLocal).toBeDefined(); // local
});
});
});

20 changes: 13 additions & 7 deletions packages/data/src/ecs/database/create-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,19 @@ export function createPlugin<
systems: plugins.systems ?? {},
};

// `imports` is a type-only contract: it makes the imported plugins' types
// visible to this plugin's local declarations without re-exporting them into
// the result. At runtime it contributes nothing — the consumer is responsible
// for actually including the imported plugins in the final
// `Database.Plugin.combine(...)`. Only `extends` merges at runtime.
if (plugins.extends) {
return combinePlugins(plugins.extends, plugin) as any;
// `imports` differs from `extends` only at the TYPE level: the imported
// plugins' members are NOT declared in this plugin's result type, so they
// don't propagate through downstream result types (the source of the
// quadratic `extends` blowup). At RUNTIME, however, imports merge in exactly
// like extends — so the imported components/resources/transactions/etc. are
// present in the assembled database without the consumer having to re-list
// them in the top-level combine. Order: imports first, then extends, then
// this plugin's own declarations (preserves service initialization order).
const bases: Database.Plugin[] = [];
if (plugins.imports) bases.push(plugins.imports);
if (plugins.extends) bases.push(plugins.extends);
if (bases.length > 0) {
return combinePlugins(...bases, plugin) as any;
}
return plugin as any;
}
22 changes: 12 additions & 10 deletions packages/data/src/ecs/database/imports-chain.type-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import type { True, False } from "../../types/types.js";
* - `extends` — base types are visible to local declarations AND
* re-exported into the result plugin's type.
* - `imports` — base types are visible to local declarations ONLY;
* they do NOT flow into the result plugin's type.
* they do NOT flow into the result plugin's TYPE.
*
* The result-type asymmetry is what keeps deep dependency graphs cheap: an
* `imports` link's result stays O(local members) instead of accumulating the
* full chain (see scripts/typeperf — `imports` is linear where `extends` is
* quadratic in chain depth). The consumer reconstitutes the union once, at the
* top, via `Database.Plugin.combine(...)`.
*
* These tests verify BOTH halves of that contract:
* The two merge identically at RUNTIME (both pull the base's members into the
* assembled plugin); they differ only in the result TYPE. That result-type
* asymmetry is what keeps deep dependency graphs cheap: an `imports` link's
* result type stays O(local members) instead of accumulating the full chain
* (see scripts/typeperf — `imports` is linear where `extends` is quadratic in
* chain depth). Runtime-merge behavior is covered by create-plugin.test.ts
* ("imports runtime behavior"); this file covers the type contract:
* 1. Visibility — a plugin that `imports` a base gets FULL type safety on the
* base's components/resources/transactions (no weakening vs `extends`).
* 2. Non-export — the imported members are absent from the result type.
* 2. Non-export — the imported members are absent from the result TYPE.
*/

// ============================================================================
Expand Down Expand Up @@ -106,7 +106,9 @@ type _ExtendsReExportsComponent = True<'baseColor' extends keyof ExtendedResult[
type _ExtendsReExportsTx = True<'setBaseColor' extends keyof ExtendedResult['transactions'] ? true : false>;

// ============================================================================
// 3. Consumer reconstitutes the union via combine — full DB is type-safe
// 3. To regain the imported members in the TYPE, combine the base back in
// explicitly (runtime already merged them — this is purely to surface the
// base's members on the database type for the consumer).
// ============================================================================

function testCombinedUsage() {
Expand Down