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
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"name": "data-monorepo",
"version": "0.9.67",
"version": "0.9.68",
"private": true,
"scripts": {
"build": "pnpm -r run build",
"test": "pnpm -r run test",
"lint": "pnpm -r run lint",
"lint-fix": "pnpm -r run lint-fix",
"typecheck": "pnpm -r run typecheck",
"dev": "pnpm -r --parallel run dev",
"dev:data": "pnpm --filter @adobe/data run dev",
"link": "pnpm -r --filter @adobe/data* run link",
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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"description": "Serverless P2P tic-tac-toe — WebRTC DataChannel + @adobe/data-sync",
"type": "module",
"private": true,
Expand Down
3 changes: 1 addition & 2 deletions 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.67",
"version": "0.9.68",
"description": "Worker-based incremental persistence layer for @adobe/data ECS over OPFS (browser) and node:fs (server).",
"type": "module",
"sideEffects": false,
Expand All @@ -18,7 +18,6 @@
"build": "cp ../../LICENSE . 2>/dev/null || true; tsc -b",
"clean": "rm -rf dist node_modules",
"dev": "tsc -b -w --preserveWatchOutput",
"lint": "pnpm eslint .",
"test": "pnpm exec playwright install chromium && vitest --run",
"test:node": "vitest --run --project=node",
"test:browser": "vitest --run --project=browser",
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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"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.67",
"version": "0.9.68",
"description": "Adobe data SolidJS bindings — context and provider for ECS database",
"type": "module",
"private": false,
Expand Down
3 changes: 1 addition & 2 deletions 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.67",
"version": "0.9.68",
"description": "Multi-user real-time synchronisation for @adobe/data ECS — server, client, and in-process loopback.",
"type": "module",
"sideEffects": false,
Expand All @@ -18,7 +18,6 @@
"build": "cp ../../LICENSE . 2>/dev/null || true; tsc -b",
"clean": "rm -rf dist node_modules",
"dev": "tsc -b -w --preserveWatchOutput",
"lint": "pnpm eslint .",
"test": "vitest --run --project=node",
"test:node": "vitest --run --project=node"
},
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.67",
"version": "0.9.68",
"description": "Adobe data oriented programming library",
"type": "module",
"sideEffects": false,
Expand Down
15 changes: 10 additions & 5 deletions packages/data/src/ecs/database/create-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,16 @@ type FullDBForPlugin<
S | StringKeyof<XP['systems']>,
ToActionFunctions<AD & XP['actions']>,
FromServiceFactories<RemoveIndex<SVF> & XP['services']>,
// 8: CV — placeholder. Kept `unknown` (Database's own default for this slot)
// so a concrete computed-values type never constrains computed-factory
// inference / breaks contravariance. Must be supplied explicitly because
// slot 9 (IX) sits after it.
unknown,
// 8: CV — the base plugin's already-resolved computeds. XP is
// AmbientPlugin<XP, IP> at the call sites, so XP['computed'] carries the
// `extends` base + `imports` deps' computeds — all fully constructed, so
// surfacing them here is sound (nothing in-progress, no circularity).
// The current plugin's OWN computeds (CVF) are deliberately NOT a
// parameter to this type, so they never flow into their own factory db:
// a computed cannot reference an in-progress sibling, but it CAN compose
// on a base plugin's computed with full types — same rule actions/systems
// already follow. Resolves to `{}` for the common no-base-computeds case.
FromComputedFactories<XP['computed']>,
// 9: IX — thread the index declarations so `db.indexes` is populated inside
// computed factories, same as the actions/systems `db` already does.
// XP is AmbientPlugin<XP, IP> at the call sites, so XP['indexes'] carries
Expand Down
107 changes: 107 additions & 0 deletions packages/data/src/ecs/database/create-plugin.type-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,30 @@ function validTypeInferenceTests() {
},
});

// Test: a derived plugin's computed factory can read a BASE plugin's
// already-resolved computed (composition across `extends`). This is the
// payoff of FullDBForPlugin surfacing FromComputedFactories<XP['computed']>
// rather than `unknown` in the computed-factory db.
const baseComputedPlugin = createPlugin({
resources: {
n: { default: 10 as number },
},
computed: {
doubled: (db) => Observe.withMap(db.observe.resources.n, (v) => v * 2),
},
});

const derivedComputedPlugin = createPlugin({
extends: baseComputedPlugin,
computed: {
quadrupled: (db) => {
// Valid - the base plugin's computed is fully typed here.
const base: Observe<number> = db.computed.doubled;
return Observe.withMap(base, (v) => v * 2);
},
},
});

// Test: Computed + transactions co-inference
// When both computed and transactions are defined in the same plugin,
// TypeScript must infer TD from the transactions property independently
Expand Down Expand Up @@ -305,6 +329,72 @@ function validTypeInferenceTests() {
});
}

// ============================================================================
// Standalone computed factories aggregated in a create call
// ============================================================================
//
// A common app pattern: computed factories are authored as standalone
// functions in their own files, then aggregated into one `computed: {}` block
// in a single createPlugin call. Each standalone factory must annotate its
// `db` parameter with a NAMED, exported type (it lives in a different module
// from the plugin).
//
// Before the FullDBForPlugin fix the factory db's `computed` slot was
// `unknown`, so the annotation had to strip it:
// type CoreStateDatabase = Omit<Database.Plugin.ToDatabase<typeof core>, 'computed'>;
// Now the factory sees the base plugin's already-resolved computeds, so the
// plain exported `Database.Plugin.ToDatabase<typeof core>` is the correct,
// cast-free annotation — AND it lets a standalone factory compose on a base
// computed. This test proves the round trip type-checks cleanly: no `as`, no
// `Omit`, no `@ts-expect-error`.
function standaloneComputedFactoryAggregation() {
// The base "state" plugin owns the resources and one base computed.
const coreStatePlugin = createPlugin({
resources: {
count: { default: 0 as number },
},
computed: {
count: (db) => db.observe.resources.count,
},
});

// The single exported alias every standalone factory annotates against.
type CoreStateDatabase = Database.Plugin.ToDatabase<typeof coreStatePlugin>;

// Standalone factory (own file): reads a base resource.
const doubled = (db: CoreStateDatabase) =>
Observe.withMap(db.observe.resources.count, (v) => v * 2);

// Standalone factory (own file): composes on the base plugin's computed.
// This is the case the fix unlocks — `db.computed.count` is fully typed.
const isPositive = (db: CoreStateDatabase) =>
Observe.withMap(db.computed.count, (v) => v > 0);

// Standalone factory (own file): a parameterized computed that also reads
// a base computed.
const atLeast = (db: CoreStateDatabase) => (min: number) =>
Observe.withMap(db.computed.count, (v) => v >= min);

// Aggregate the independently-authored factories into the derived plugin's
// computed block — clean assignment, no cast.
const derived = createPlugin({
extends: coreStatePlugin,
computed: {
doubled,
isPositive,
atLeast,
},
});

// The resulting plugin exposes the aggregated computeds with exact types,
// alongside the inherited base computed.
type DerivedComputed = Database.FromPlugin<typeof derived>['computed'];
type _CheckDoubled = Assert<Equal<DerivedComputed['doubled'], Observe<number>>>;
type _CheckIsPositive = Assert<Equal<DerivedComputed['isPositive'], Observe<boolean>>>;
type _CheckAtLeast = Assert<Equal<DerivedComputed['atLeast'], (min: number) => Observe<boolean>>>;
type _CheckInheritedCount = Assert<Equal<DerivedComputed['count'], Observe<number>>>;
}

// ============================================================================
// INVALID TYPE INFERENCE TESTS
// ============================================================================
Expand Down Expand Up @@ -507,6 +597,23 @@ function invalidComputedReturnsObject() {
});
}

// Test: Invalid sibling computed access — an in-progress computed in the SAME
// plugin is not visible to a sibling factory (would be circular). Only a
// base plugin's already-resolved computeds are surfaced.
function invalidSiblingComputedAccess() {
createPlugin({
resources: { n: { default: 1 as number } },
computed: {
first: (db) => Observe.fromConstant(db.resources.n),
second: (db) => {
// @ts-expect-error - sibling computed 'first' is in-progress, not visible
const _f = db.computed.first;
return Observe.fromConstant(0);
},
},
});
}

// Test: Invalid transaction call in action
function invalidTransactionCallInAction() {
createPlugin({
Expand Down