@adobe/data
Adobe Data Oriented Programming Library
Documentation
API Reference
ECS Performance Test
P2P Tic-Tac-Toe Demo — serverless browser-to-browser multiplayer via WebRTC + @adobe/data-sync
Breaking API Changes
Until we reach 1.0.0, minor version changes may be API breaking.
0.9.70
TransactionResult.ephemeral → TransactionResult.persistent (inverted)
The boolean is now true when at least one changed entity is persistent (id ≥ 0), rather than true when all changed entities are non-persistent.
// before
if (!result.ephemeral) save(result);
// after
if (result.persistent) save(result);
TransactionResult.transient → TransactionResult.intermediate
Renamed to remove the false antonym relationship with persistent. Both are orthogonal: intermediate = preview step (will be rolled back), persistent = touches saved entities.
// before
if (!result.transient && !result.ephemeral) save(result);
// after
if (!result.intermediate && result.persistent) save(result);
TransactionOptions.transient → TransactionOptions.intermediate
// before
db.execute(fn, { transient: true });
// after
db.execute(fn, { intermediate: true });
TransactionIntent "transient" → "intermediate" (sync wire protocol)
Affects observe.envelopes intent values and ClientMessage kind strings in @adobe/data-sync.
// before
db.observe.envelopes(({ intent }) => {
if (intent === "transient") { ... }
});
transport.send({ kind: "transient", envelope });
// after
db.observe.envelopes(({ intent }) => {
if (intent === "intermediate") { ... }
});
transport.send({ kind: "intermediate", envelope });
SyncServiceOptions.maxTransientsPerSecond → maxIntermediatesPerSecond
Schema.ephemeral deprecated → use Schema.nonPersistent
The "ephemeral" component string still works at runtime with a console.warn; update to "nonPersistent".
// before
{ ..., ephemeral: true }
ensureArchetype(["id", "cursor", "ephemeral"])
// after
{ ..., nonPersistent: true }
ensureArchetype(["id", "cursor", "nonPersistent"])
Data Oriented Programming
This library is built using a data oriented and functional programming paradigm.
We prefer composition over inheritance, avoid classes when possible and emphasize separation of concerns.
Data
This library uses data oriented design paradigm and prefers pure functional interfaces whenever practical.
For our purposes, Data is immutable JSON (de)serializable objects and primitives.
Why immutable Data?
We prefer Data because it is easy to:
- serialize
- deserialize
- inspect
- compare
- hash
- validate
We prefer immutable Data because it is easy to:
- reason about
- avoid side effects
- avoid defensive copying
- use for pure function arguments and return values
- use with concurrency
- memoize results
- use as cache key (stringified)
- use as cache value
What is Data Oriented Design?
Sanders Mertens covers this thoroughly in his ECS FAQ:
https://github.com/SanderMertens/ecs-faq?tab=readme-ov-file#what-is-data-oriented-design
Data Schemas
When runtime type information is required we use JSON Schema. In order to avoid redundancy, we can use FromSchema to derive a typescript type at compile time.
Example:
const Float32Schema = { type: "number", precision: 1 } as const satisfies Schema;
const Float32 = FromSchema<typeof Float32Schema>; // number
It is important that we use as const for the schema definition. Without that, the typeof Float32Schema would be { type: string, precision: number } instead of { type: "number", precision: 1 } and we would not be able to derive a correct type using FromSchema.
Normalized Data
Data is considered "Normalized" when all object keys contained anywhere within it are lexigraphically sorted. We include a normalize function which performs this conversion. This is particularly useful when you want to use data as a cache key.
const notNormalized = { b: 2, a: 1 };
const normalized = normalize(notNormalized); // { a: 1, b: 2 }
Observables
An Observable<T> is a subscription function that you can pass a callback function to. Your callback function can accept a single argument of type T. The subscription function returns a dispose function that accepts no parameters and can be called at any point in the future to cancel your subscription.
Your callback function may be called back synchronously (before the initial call returns) zero or one times and asynchronously later any number of times.
For more information see the Observable API documentation
Observable Types