@adobe/data 0.9.59 → 0.9.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ecs/database/combine-plugins.d.ts +5 -1
- package/dist/ecs/database/combine-plugins.js +3 -3
- package/dist/ecs/database/combine-plugins.js.map +1 -1
- package/dist/ecs/database/create-plugin.d.ts +17 -14
- package/dist/ecs/database/create-plugin.js +2 -1
- package/dist/ecs/database/create-plugin.js.map +1 -1
- package/dist/ecs/database/database.d.ts +32 -4
- package/dist/ecs/database/database.index.performance.test.d.ts +1 -0
- package/dist/ecs/database/database.index.performance.test.js +417 -0
- package/dist/ecs/database/database.index.performance.test.js.map +1 -0
- package/dist/ecs/database/database.index.test.d.ts +1 -0
- package/dist/ecs/database/database.index.test.js +762 -0
- package/dist/ecs/database/database.index.test.js.map +1 -0
- package/dist/ecs/database/database.index.type-test.d.ts +1 -0
- package/dist/ecs/database/database.index.type-test.js +275 -0
- package/dist/ecs/database/database.index.type-test.js.map +1 -0
- package/dist/ecs/database/database.js.map +1 -1
- package/dist/ecs/database/deep-extends-chain.type-test.d.ts +256 -256
- package/dist/ecs/database/index-api-proof.type-test.d.ts +1 -0
- package/dist/ecs/database/index-api-proof.type-test.js +88 -0
- package/dist/ecs/database/index-api-proof.type-test.js.map +1 -0
- package/dist/ecs/database/index-registry/create-index-registry.d.ts +53 -0
- package/dist/ecs/database/index-registry/create-index-registry.js +174 -0
- package/dist/ecs/database/index-registry/create-index-registry.js.map +1 -0
- package/dist/ecs/database/index-registry/create-index.d.ts +60 -0
- package/dist/ecs/database/index-registry/create-index.js +539 -0
- package/dist/ecs/database/index-registry/create-index.js.map +1 -0
- package/dist/ecs/database/index-registry/index.d.ts +4 -0
- package/dist/ecs/database/index-registry/index.js +4 -0
- package/dist/ecs/database/index-registry/index.js.map +1 -0
- package/dist/ecs/database/public/create-database.js +146 -0
- package/dist/ecs/database/public/create-database.js.map +1 -1
- package/dist/ecs/database/transactional-store/transactional-store.d.ts +3 -2
- package/dist/ecs/plugins/scheduler/scheduler.d.ts +1 -0
- package/dist/ecs/store/index-types.d.ts +132 -0
- package/dist/ecs/store/index-types.js +3 -0
- package/dist/ecs/store/index-types.js.map +1 -0
- package/dist/ecs/store/public/create-store.js +189 -3
- package/dist/ecs/store/public/create-store.js.map +1 -1
- package/dist/ecs/store/store.d.ts +30 -13
- package/dist/ecs/store/store.js +5 -4
- package/dist/ecs/store/store.js.map +1 -1
- package/dist/ecs/store/transaction-functions.d.ts +4 -3
- package/dist/equals.js +24 -6
- package/dist/equals.js.map +1 -1
- package/dist/math/aabb/face/from-position.d.ts +1 -1
- package/dist/math/aabb/face/from-position.js +4 -3
- package/dist/math/aabb/face/from-position.js.map +1 -1
- package/dist/math/aabb/schema.js +1 -1
- package/dist/math/aabb/schema.js.map +1 -1
- package/dist/schema/fractional-index/between-n.d.ts +2 -0
- package/dist/schema/fractional-index/between-n.js +34 -0
- package/dist/schema/fractional-index/between-n.js.map +1 -0
- package/dist/schema/fractional-index/between-n.test.d.ts +1 -0
- package/dist/schema/fractional-index/between-n.test.js +36 -0
- package/dist/schema/fractional-index/between-n.test.js.map +1 -0
- package/dist/schema/fractional-index/between.d.ts +2 -0
- package/dist/schema/fractional-index/between.js +3 -0
- package/dist/schema/fractional-index/between.js.map +1 -0
- package/dist/schema/fractional-index/between.test.d.ts +1 -0
- package/dist/schema/fractional-index/between.test.js +68 -0
- package/dist/schema/fractional-index/between.test.js.map +1 -0
- package/dist/schema/fractional-index/decrement-integer.d.ts +1 -0
- package/dist/schema/fractional-index/decrement-integer.js +32 -0
- package/dist/schema/fractional-index/decrement-integer.js.map +1 -0
- package/dist/schema/fractional-index/decrement-integer.test.d.ts +1 -0
- package/dist/schema/fractional-index/decrement-integer.test.js +31 -0
- package/dist/schema/fractional-index/decrement-integer.test.js.map +1 -0
- package/dist/schema/fractional-index/digits.d.ts +1 -0
- package/dist/schema/fractional-index/digits.js +3 -0
- package/dist/schema/fractional-index/digits.js.map +1 -0
- package/dist/schema/fractional-index/fractional-index.d.ts +4 -0
- package/dist/schema/fractional-index/fractional-index.js +2 -0
- package/dist/schema/fractional-index/fractional-index.js.map +1 -0
- package/dist/schema/fractional-index/increment-integer.d.ts +1 -0
- package/dist/schema/fractional-index/increment-integer.js +32 -0
- package/dist/schema/fractional-index/increment-integer.js.map +1 -0
- package/dist/schema/fractional-index/increment-integer.test.d.ts +1 -0
- package/dist/schema/fractional-index/increment-integer.test.js +34 -0
- package/dist/schema/fractional-index/increment-integer.test.js.map +1 -0
- package/dist/schema/fractional-index/initial.d.ts +2 -0
- package/dist/schema/fractional-index/initial.js +2 -0
- package/dist/schema/fractional-index/initial.js.map +1 -0
- package/dist/schema/fractional-index/integer-length.d.ts +1 -0
- package/dist/schema/fractional-index/integer-length.js +9 -0
- package/dist/schema/fractional-index/integer-length.js.map +1 -0
- package/dist/schema/fractional-index/integer-part.d.ts +1 -0
- package/dist/schema/fractional-index/integer-part.js +9 -0
- package/dist/schema/fractional-index/integer-part.js.map +1 -0
- package/dist/schema/fractional-index/key-between.d.ts +2 -0
- package/dist/schema/fractional-index/key-between.js +39 -0
- package/dist/schema/fractional-index/key-between.js.map +1 -0
- package/dist/schema/fractional-index/midpoint.d.ts +1 -0
- package/dist/schema/fractional-index/midpoint.js +23 -0
- package/dist/schema/fractional-index/midpoint.js.map +1 -0
- package/dist/schema/fractional-index/midpoint.test.d.ts +1 -0
- package/dist/schema/fractional-index/midpoint.test.js +83 -0
- package/dist/schema/fractional-index/midpoint.test.js.map +1 -0
- package/dist/schema/fractional-index/public.d.ts +4 -0
- package/dist/schema/fractional-index/public.js +6 -0
- package/dist/schema/fractional-index/public.js.map +1 -0
- package/dist/schema/fractional-index/schema.d.ts +4 -0
- package/dist/schema/fractional-index/schema.js +5 -0
- package/dist/schema/fractional-index/schema.js.map +1 -0
- package/dist/schema/fractional-index/smallest-integer.d.ts +1 -0
- package/dist/schema/fractional-index/smallest-integer.js +3 -0
- package/dist/schema/fractional-index/smallest-integer.js.map +1 -0
- package/dist/schema/fractional-index/validate-integer.d.ts +1 -0
- package/dist/schema/fractional-index/validate-integer.js +7 -0
- package/dist/schema/fractional-index/validate-integer.js.map +1 -0
- package/dist/schema/fractional-index/validate-order-key.d.ts +1 -0
- package/dist/schema/fractional-index/validate-order-key.js +11 -0
- package/dist/schema/fractional-index/validate-order-key.js.map +1 -0
- package/dist/schema/guid/create.d.ts +2 -0
- package/dist/schema/guid/create.js +10 -0
- package/dist/schema/guid/create.js.map +1 -0
- package/dist/schema/guid/create.test.d.ts +1 -0
- package/dist/schema/guid/create.test.js +39 -0
- package/dist/schema/guid/create.test.js.map +1 -0
- package/dist/schema/guid/equals.d.ts +2 -0
- package/dist/schema/guid/equals.js +3 -0
- package/dist/schema/guid/equals.js.map +1 -0
- package/dist/schema/guid/equals.test.d.ts +1 -0
- package/dist/schema/guid/equals.test.js +30 -0
- package/dist/schema/guid/equals.test.js.map +1 -0
- package/dist/schema/guid/from-uuid.d.ts +2 -0
- package/dist/schema/guid/from-uuid.js +15 -0
- package/dist/schema/guid/from-uuid.js.map +1 -0
- package/dist/schema/guid/from-uuid.test.d.ts +1 -0
- package/dist/schema/guid/from-uuid.test.js +39 -0
- package/dist/schema/guid/from-uuid.test.js.map +1 -0
- package/dist/schema/guid/guid.performance.test.d.ts +1 -0
- package/dist/schema/guid/guid.performance.test.js +297 -0
- package/dist/schema/guid/guid.performance.test.js.map +1 -0
- package/dist/schema/guid/index.d.ts +4 -0
- package/dist/schema/guid/index.js +3 -0
- package/dist/schema/guid/index.js.map +1 -0
- package/dist/schema/guid/layout.d.ts +1 -0
- package/dist/schema/guid/layout.js +5 -0
- package/dist/schema/guid/layout.js.map +1 -0
- package/dist/schema/guid/nil.d.ts +2 -0
- package/dist/schema/guid/nil.js +3 -0
- package/dist/schema/guid/nil.js.map +1 -0
- package/dist/schema/guid/public.d.ts +8 -0
- package/dist/schema/guid/public.js +10 -0
- package/dist/schema/guid/public.js.map +1 -0
- package/dist/schema/guid/schema.d.ts +12 -0
- package/dist/schema/guid/schema.js +10 -0
- package/dist/schema/guid/schema.js.map +1 -0
- package/dist/schema/guid/schema.test.d.ts +1 -0
- package/dist/schema/guid/schema.test.js +35 -0
- package/dist/schema/guid/schema.test.js.map +1 -0
- package/dist/schema/guid/to-unserializable-key.d.ts +2 -0
- package/dist/schema/guid/to-unserializable-key.js +10 -0
- package/dist/schema/guid/to-unserializable-key.js.map +1 -0
- package/dist/schema/guid/to-uuid.d.ts +2 -0
- package/dist/schema/guid/to-uuid.js +15 -0
- package/dist/schema/guid/to-uuid.js.map +1 -0
- package/dist/schema/guid/to-uuid.test.d.ts +1 -0
- package/dist/schema/guid/to-uuid.test.js +31 -0
- package/dist/schema/guid/to-uuid.test.js.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/table/select-rows.d.ts +7 -4
- package/dist/table/select-rows.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/references/data-lit/package.json +1 -1
- package/references/data-lit-tictactoe/package.json +1 -1
- package/references/data-react/package.json +1 -1
- package/references/data-react-hello/package.json +1 -1
- package/references/data-react-pixie/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { Store } from "../../store/index.js";
|
|
|
3
3
|
import { createReconcilingDatabase } from "../reconciling/create-reconciling-database.js";
|
|
4
4
|
import { calculateSystemOrder } from "../calculate-system-order.js";
|
|
5
5
|
import { createTransactionDispatcher } from "./create-transaction-dispatcher.js";
|
|
6
|
+
import { observeSelectEntities } from "../observe-select-entities.js";
|
|
6
7
|
/**
|
|
7
8
|
* For each system in newDeclarations that is not yet in systemFunctions: call create(db),
|
|
8
9
|
* store the returned value in systemFunctions, and assign by name. Uses natural declaration order.
|
|
@@ -63,6 +64,12 @@ function createEmptyDatabase(sync) {
|
|
|
63
64
|
const services = {};
|
|
64
65
|
const computed = {};
|
|
65
66
|
const extendedPlugins = new Set();
|
|
67
|
+
// The Store layer owns the IndexRegistry: it instantiates the registry,
|
|
68
|
+
// maintains it eagerly on every insert/update/delete, and exposes the
|
|
69
|
+
// typed handle map as `store.indexes`. The Database layer just surfaces
|
|
70
|
+
// the same reference (via spread / explicit field below) so users see
|
|
71
|
+
// `db.indexes.<name>` and `t.indexes.<name>` pointing at one source of
|
|
72
|
+
// truth.
|
|
66
73
|
const partialDatabase = {
|
|
67
74
|
serviceName: "ecs-database-service",
|
|
68
75
|
...reconcilingDatabase,
|
|
@@ -71,10 +78,39 @@ function createEmptyDatabase(sync) {
|
|
|
71
78
|
actions,
|
|
72
79
|
services,
|
|
73
80
|
computed,
|
|
81
|
+
indexes: store.indexes,
|
|
74
82
|
store,
|
|
75
83
|
system: { functions: systemFunctions, order: systemOrder },
|
|
76
84
|
extend: undefined,
|
|
77
85
|
};
|
|
86
|
+
// Auto-route `db.select(include, { where })` and `db.observe.select(...)`
|
|
87
|
+
// through a declared index when the where clause is exactly an equality
|
|
88
|
+
// match on the index's full key tuple and there is no order clause. Other
|
|
89
|
+
// shapes fall back to the archetype scan in the underlying store.
|
|
90
|
+
//
|
|
91
|
+
// The router dispatches through the user-visible handles in `store.indexes`
|
|
92
|
+
// so any override applied to the public handle — tests, instrumentation —
|
|
93
|
+
// takes effect for the routed path too.
|
|
94
|
+
const baseSelect = partialDatabase.select.bind(partialDatabase);
|
|
95
|
+
const indexAwareSelect = (include, options) => {
|
|
96
|
+
const routed = trySelectViaIndex(store, include, options);
|
|
97
|
+
if (routed !== null)
|
|
98
|
+
return routed;
|
|
99
|
+
return baseSelect(include, options);
|
|
100
|
+
};
|
|
101
|
+
partialDatabase.select = indexAwareSelect;
|
|
102
|
+
// `observeSelectEntities` reads from its `store` parameter via only
|
|
103
|
+
// `store.select` and `store.locate`. A minimal façade backed by the
|
|
104
|
+
// index-aware select + the real `store.locate` satisfies that contract,
|
|
105
|
+
// and replacing `observe.select` here mutates the shared `observe` object
|
|
106
|
+
// (same reference as `observedDatabase.observe`) before any user code
|
|
107
|
+
// can subscribe — so the default instance created inside
|
|
108
|
+
// `createObservedDatabase` is cleanly dropped, with no stale closures.
|
|
109
|
+
const indexAwareStoreFacade = {
|
|
110
|
+
select: indexAwareSelect,
|
|
111
|
+
locate: store.locate.bind(store),
|
|
112
|
+
};
|
|
113
|
+
partialDatabase.observe.select = observeSelectEntities(indexAwareStoreFacade, partialDatabase.observe.transactions);
|
|
78
114
|
const extend = (plugin) => {
|
|
79
115
|
if (!extendedPlugins.has(plugin)) {
|
|
80
116
|
extendedPlugins.add(plugin);
|
|
@@ -93,6 +129,12 @@ function createEmptyDatabase(sync) {
|
|
|
93
129
|
if (!(name in computed))
|
|
94
130
|
computed[name] = pluginComputed[name](partialDatabase);
|
|
95
131
|
}
|
|
132
|
+
// `reconcilingDatabase.extend(plugin)` above propagates down to
|
|
133
|
+
// `store.extend({ components, resources, archetypes, indexes })`,
|
|
134
|
+
// so the Store has already absorbed `plugin.indexes`. We refresh
|
|
135
|
+
// our local indexes reference in case the underlying map got a
|
|
136
|
+
// new identity (it doesn't today, but stay defensive).
|
|
137
|
+
partialDatabase.indexes = store.indexes;
|
|
96
138
|
if (plugin.systems && Object.keys(plugin.systems).length > 0) {
|
|
97
139
|
Object.assign(allSystemDeclarations, plugin.systems);
|
|
98
140
|
systemOrder = calculateSystemOrder(allSystemDeclarations);
|
|
@@ -106,4 +148,108 @@ function createEmptyDatabase(sync) {
|
|
|
106
148
|
partialDatabase.extend = extend;
|
|
107
149
|
return partialDatabase;
|
|
108
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Returns the equality value implied by `where[key]` if and only if the
|
|
153
|
+
* condition is a pure equality — either a direct primitive value or a
|
|
154
|
+
* comparison object with exactly `{ "==": v }`. Returns the sentinel
|
|
155
|
+
* `NOT_EQUALITY` when the condition uses any other operator.
|
|
156
|
+
*/
|
|
157
|
+
const NOT_EQUALITY = Symbol("not-equality");
|
|
158
|
+
const equalityValue = (cond) => {
|
|
159
|
+
if (cond === null || typeof cond !== "object")
|
|
160
|
+
return cond;
|
|
161
|
+
const keys = Object.keys(cond);
|
|
162
|
+
if (keys.length === 1 && keys[0] === "==")
|
|
163
|
+
return cond["=="];
|
|
164
|
+
return NOT_EQUALITY;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Attempts to serve `select(include, options)` from a declared index.
|
|
168
|
+
*
|
|
169
|
+
* Returns `null` when no index applies; the caller must fall back to the
|
|
170
|
+
* archetype scan. Returns an `Entity[]` when an index can answer the query.
|
|
171
|
+
*
|
|
172
|
+
* Match conditions (intentionally conservative for V2):
|
|
173
|
+
* - `options.where` is non-empty.
|
|
174
|
+
* - `options.order` is absent (sort orders are routed in a follow-up).
|
|
175
|
+
* - Every `where` key is a pure equality (`v` or `{ "==": v }`).
|
|
176
|
+
* - The `where` keys, as a set, equal some index's `components`.
|
|
177
|
+
*
|
|
178
|
+
* The query planner accesses `store.indexes` (the user-visible handle map),
|
|
179
|
+
* so test spies and any future user-installed instrumentation see the call.
|
|
180
|
+
* Components for each index are recovered from the registry-internal handle
|
|
181
|
+
* (the handle exposes `find`/`findRange`/`get`; the column set is held by
|
|
182
|
+
* the underlying `RuntimeIndex`, which is the registry's source of truth).
|
|
183
|
+
* For the planner we treat the handle map structurally as
|
|
184
|
+
* `{ [name]: { findByValues, routableColumns } }` because Store internally
|
|
185
|
+
* augments each handle with these fields — see `createStore`.
|
|
186
|
+
*
|
|
187
|
+
* After the index lookup returns candidate entities, each candidate is
|
|
188
|
+
* checked for archetype membership of every `include` component so the
|
|
189
|
+
* returned set respects the same archetype filter as the scan path.
|
|
190
|
+
*/
|
|
191
|
+
function trySelectViaIndex(store, include, options) {
|
|
192
|
+
const where = options?.where;
|
|
193
|
+
if (!where || options?.order)
|
|
194
|
+
return null;
|
|
195
|
+
const whereKeys = Object.keys(where);
|
|
196
|
+
if (whereKeys.length === 0)
|
|
197
|
+
return null;
|
|
198
|
+
// Collapse where to an { component -> equality-value } record, bailing if
|
|
199
|
+
// any condition is not a pure equality.
|
|
200
|
+
const values = {};
|
|
201
|
+
for (const k of whereKeys) {
|
|
202
|
+
const eq = equalityValue(where[k]);
|
|
203
|
+
if (eq === NOT_EQUALITY)
|
|
204
|
+
return null;
|
|
205
|
+
values[k] = eq;
|
|
206
|
+
}
|
|
207
|
+
// Iterate the public handle map (store.indexes). Each handle carries a
|
|
208
|
+
// non-public `routableColumns` field placed by `createStore` for exactly
|
|
209
|
+
// this purpose. `routableColumns: null` opts an index out of raw-where
|
|
210
|
+
// auto-routing (function and slot-map keys live in a value space the
|
|
211
|
+
// planner cannot infer from a where clause).
|
|
212
|
+
//
|
|
213
|
+
// For a matched index the planner builds the appropriate `find`
|
|
214
|
+
// argument: a scalar for a single-column key, the full values object
|
|
215
|
+
// for a multi-column key. The call goes through `handle.find` so any
|
|
216
|
+
// user-installed spy or instrumentation on the handle sees the
|
|
217
|
+
// dispatch.
|
|
218
|
+
const handles = store.indexes;
|
|
219
|
+
let matchedHandle;
|
|
220
|
+
let matchedCols;
|
|
221
|
+
for (const name of Object.keys(handles)) {
|
|
222
|
+
const handle = handles[name];
|
|
223
|
+
const cols = handle.routableColumns;
|
|
224
|
+
if (cols === null)
|
|
225
|
+
continue;
|
|
226
|
+
if (cols.length !== whereKeys.length)
|
|
227
|
+
continue;
|
|
228
|
+
if (cols.every(c => c in values)) {
|
|
229
|
+
matchedHandle = handle;
|
|
230
|
+
matchedCols = cols;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (!matchedHandle || !matchedCols)
|
|
235
|
+
return null;
|
|
236
|
+
const findArg = matchedCols.length === 1 ? values[matchedCols[0]] : values;
|
|
237
|
+
const candidates = matchedHandle.find(findArg);
|
|
238
|
+
if (candidates.length === 0)
|
|
239
|
+
return [];
|
|
240
|
+
const includeArr = Array.from(include);
|
|
241
|
+
if (includeArr.length === 0)
|
|
242
|
+
return candidates.slice();
|
|
243
|
+
const result = [];
|
|
244
|
+
for (const entity of candidates) {
|
|
245
|
+
const location = store.locate(entity);
|
|
246
|
+
if (!location)
|
|
247
|
+
continue;
|
|
248
|
+
const cols = location.archetype.columns;
|
|
249
|
+
if (includeArr.every(c => cols[c] !== undefined)) {
|
|
250
|
+
result.push(entity);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
109
255
|
//# sourceMappingURL=create-database.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-database.js","sourceRoot":"","sources":["../../../../src/ecs/database/public/create-database.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,
|
|
1
|
+
{"version":3,"file":"create-database.js","sourceRoot":"","sources":["../../../../src/ecs/database/public/create-database.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,7 @@ import { StringKeyof } from "../../../types/types.js";
|
|
|
8
8
|
import { ArchetypeComponents } from "../../store/archetype-components.js";
|
|
9
9
|
import { FromSchemas } from "../../../schema/from-schemas.js";
|
|
10
10
|
import { Undoable } from "../undoable.js";
|
|
11
|
+
import { IndexDeclarations } from "../../store/index-types.js";
|
|
11
12
|
/**
|
|
12
13
|
* The first argument passed to every transaction function. Extends the full
|
|
13
14
|
* store read/write surface with `userId` — the identifier of the peer or user
|
|
@@ -17,10 +18,10 @@ import { Undoable } from "../undoable.js";
|
|
|
17
18
|
*
|
|
18
19
|
* `userId` is `undefined` in local-only (no-sync) databases.
|
|
19
20
|
*/
|
|
20
|
-
export type TransactionContext<C extends Components, R extends ResourceComponents, A extends ArchetypeComponents<StringKeyof<C
|
|
21
|
+
export type TransactionContext<C extends Components, R extends ResourceComponents, A extends ArchetypeComponents<StringKeyof<C>>, IX extends IndexDeclarations<C> = {}> = Store<C, R, A, IX> & {
|
|
21
22
|
readonly userId: number | string | undefined;
|
|
22
23
|
};
|
|
23
|
-
export interface TransactionalStore<C extends Components = never, R extends ResourceComponents = never, A extends ArchetypeComponents<StringKeyof<C>> = never> extends ReadonlyStore<C, R, A> {
|
|
24
|
+
export interface TransactionalStore<C extends Components = never, R extends ResourceComponents = never, A extends ArchetypeComponents<StringKeyof<C>> = never, IX extends IndexDeclarations<C> = {}> extends ReadonlyStore<C, R, A, IX> {
|
|
24
25
|
/**
|
|
25
26
|
* Execute a transaction on the store.
|
|
26
27
|
* The transactionFunction must NOT directly mutate archetype rows as those changes would not be captured.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { StringKeyof } from "../../types/types.js";
|
|
2
|
+
import type { Entity } from "../entity/entity.js";
|
|
3
|
+
import { Components } from "./components.js";
|
|
4
|
+
/**
|
|
5
|
+
* If `T` is an array type, the element type; otherwise `T` unchanged.
|
|
6
|
+
*
|
|
7
|
+
* Multi-value indexes auto-fan-out array values into per-element bucket
|
|
8
|
+
* entries at insert time. The lookup methods therefore take the element
|
|
9
|
+
* type, not the array — a query like `find("joe")` against a
|
|
10
|
+
* `Task { assigned: string[] }` is exactly what users want, and an array
|
|
11
|
+
* as a single opaque key is virtually never useful (order-dependent, not
|
|
12
|
+
* a natural query shape).
|
|
13
|
+
*/
|
|
14
|
+
type ElementOf<T> = T extends readonly (infer E)[] ? E : T;
|
|
15
|
+
/**
|
|
16
|
+
* Per-slot extractor in a compound `key` declaration. Either:
|
|
17
|
+
* - a `StringKeyof<C>` — read the value of that column directly, or
|
|
18
|
+
* - a function — derive the slot's value from the index's read components.
|
|
19
|
+
*/
|
|
20
|
+
export type IndexKeySlot<C extends Components> = StringKeyof<C> | ((...args: any[]) => unknown);
|
|
21
|
+
/**
|
|
22
|
+
* The four shapes a `key` declaration can take. Drives the `find` / `get`
|
|
23
|
+
* argument type via {@link FindArg}.
|
|
24
|
+
*
|
|
25
|
+
* - `string` — read this one column. `find` takes a scalar.
|
|
26
|
+
* - `readonly string[]` — read each of these columns. `find` takes
|
|
27
|
+
* `{ col1: ..., col2: ... }` keyed by the column names themselves.
|
|
28
|
+
* - `(...args) => Value` — derive the bucket key from the components.
|
|
29
|
+
* `find` takes whatever the function returns (or its element type
|
|
30
|
+
* when the return is an array — multi-value fan-out).
|
|
31
|
+
* - slot map — name each part of a compound key; values are either
|
|
32
|
+
* column-name strings (identity) or extractor functions. `find`
|
|
33
|
+
* takes an object keyed by the slot names.
|
|
34
|
+
*/
|
|
35
|
+
export type IndexKey<C extends Components> = StringKeyof<C> | readonly StringKeyof<C>[] | ((...args: any[]) => unknown) | {
|
|
36
|
+
readonly [slot: string]: IndexKeySlot<C>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Within-bucket ordering. `by` declares the columns to read into the
|
|
40
|
+
* per-entity sort cache; `compare` (optional) is the comparator over
|
|
41
|
+
* `Pick<C, by[number]>`. When `compare` is omitted, the default is
|
|
42
|
+
* ascending across `by` from left to right with positional tie-break
|
|
43
|
+
* (`JS <` / `>` per column).
|
|
44
|
+
*
|
|
45
|
+
* `compare` is method shorthand for the same `IX & XIX` variance reason
|
|
46
|
+
* that `compute` was previously: `Store.extend`'s result intersects index
|
|
47
|
+
* declarations, which would otherwise trip `strictFunctionTypes` over
|
|
48
|
+
* contravariant parameter positions.
|
|
49
|
+
*/
|
|
50
|
+
export type IndexOrder<C extends Components, By extends readonly StringKeyof<C>[] = readonly StringKeyof<C>[]> = {
|
|
51
|
+
readonly by: By;
|
|
52
|
+
compare?(a: Pick<C, By[number]>, b: Pick<C, By[number]>): number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Type-level declaration of an index.
|
|
56
|
+
*
|
|
57
|
+
* - `key` is the only required field. Its shape drives the bucket layout
|
|
58
|
+
* and the lookup argument type of `find` / `get` / `findRange`.
|
|
59
|
+
* - `order` (optional) maintains sorted iteration within each bucket.
|
|
60
|
+
* - `unique` (optional) — when true, at most one entity may hold each
|
|
61
|
+
* bucket key; `get(arg)` is exposed and returns `Entity | null`.
|
|
62
|
+
* - `components` (optional) — names the columns extractor functions
|
|
63
|
+
* read. Only needed when at least one function reads a column not
|
|
64
|
+
* implied by a string-identity key entry.
|
|
65
|
+
*
|
|
66
|
+
* See `packages/data/src/ecs/README.md` for the full pattern catalogue.
|
|
67
|
+
*/
|
|
68
|
+
export type Index<C extends Components = any, K extends IndexKey<C> = IndexKey<C>, O extends IndexOrder<C, any> | undefined = IndexOrder<C, any> | undefined, U extends boolean = boolean> = {
|
|
69
|
+
readonly key: K;
|
|
70
|
+
readonly order?: O;
|
|
71
|
+
readonly unique?: U;
|
|
72
|
+
readonly components?: readonly StringKeyof<C>[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a slot's contributed `find` field type. Strings are identity
|
|
76
|
+
* reads on the named column; functions yield their return type (element
|
|
77
|
+
* type if the return is an array).
|
|
78
|
+
*/
|
|
79
|
+
type SlotFindType<C extends Components, V> = V extends StringKeyof<C> ? ElementOf<C[V]> : V extends (...args: any[]) => infer R ? ElementOf<R> : never;
|
|
80
|
+
/** Argument type of `find` / `get` derived from the `key` shape. */
|
|
81
|
+
type FindArg<C extends Components, K> = K extends StringKeyof<C> ? ElementOf<C[K]> : K extends readonly StringKeyof<C>[] ? {
|
|
82
|
+
readonly [P in K[number]]: ElementOf<C[P]>;
|
|
83
|
+
} : K extends (...args: any[]) => infer R ? ElementOf<R> : K extends Record<string, IndexKeySlot<C>> ? {
|
|
84
|
+
readonly [Slot in keyof K]: SlotFindType<C, K[Slot]>;
|
|
85
|
+
} : never;
|
|
86
|
+
/** Comparison-operator filter on a scalar bucket-key value. */
|
|
87
|
+
type OperatorFilter<T> = {
|
|
88
|
+
readonly "=="?: T;
|
|
89
|
+
readonly "!="?: T;
|
|
90
|
+
readonly "<"?: T;
|
|
91
|
+
readonly "<="?: T;
|
|
92
|
+
readonly ">"?: T;
|
|
93
|
+
readonly ">="?: T;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* `findRange` argument: each field of the find argument may be either an
|
|
97
|
+
* equality value or a `<,<=,>,>=,==,!=` operator filter. For scalar keys
|
|
98
|
+
* the entire argument may itself be an operator filter.
|
|
99
|
+
*/
|
|
100
|
+
type RangeArg<T> = T extends object ? {
|
|
101
|
+
readonly [P in keyof T]?: T[P] | OperatorFilter<T[P]>;
|
|
102
|
+
} : T | OperatorFilter<T>;
|
|
103
|
+
export declare namespace Index {
|
|
104
|
+
/** Public lookup handle exposed on `db.indexes.<name>` and `t.indexes.<name>`. */
|
|
105
|
+
type Handle<C extends Components, I extends Index<C, any, any, any>> = I extends Index<C, infer K, any, infer U> ? {
|
|
106
|
+
find(arg: FindArg<C, K>): readonly Entity[];
|
|
107
|
+
findRange(arg: RangeArg<FindArg<C, K>>): readonly Entity[];
|
|
108
|
+
} & (U extends true ? {
|
|
109
|
+
get(arg: FindArg<C, K>): Entity | null;
|
|
110
|
+
} : {}) : never;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Plugin-level / store-level map of indexes. Keys are user-chosen index
|
|
114
|
+
* names; values are `Index` declarations whose `key` references real
|
|
115
|
+
* columns of `C`.
|
|
116
|
+
*
|
|
117
|
+
* The structural shape is inlined (rather than `Index<C, any, any, any>`) so
|
|
118
|
+
* that the per-entry `key`/`order` fields are actually constrained at the
|
|
119
|
+
* declaration site. With `Index<C, any, any, any>` the wildcard `any`
|
|
120
|
+
* generics widen `key` past `IndexKey<C>`, so a typo like `{ key: "bogus" }`
|
|
121
|
+
* silently passes the constraint. Inlining `key: IndexKey<C>` here makes
|
|
122
|
+
* the typo a type error at the plugin descriptor.
|
|
123
|
+
*/
|
|
124
|
+
export type IndexDeclarations<C extends Components = any> = {
|
|
125
|
+
readonly [name: string]: {
|
|
126
|
+
readonly key: IndexKey<C>;
|
|
127
|
+
readonly order?: IndexOrder<C>;
|
|
128
|
+
readonly unique?: boolean;
|
|
129
|
+
readonly components?: readonly StringKeyof<C>[];
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-types.js","sourceRoot":"","sources":["../../../src/ecs/store/index-types.ts"],"names":[],"mappings":"AAAA,uDAAuD"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
2
|
import { createCore } from "../core/create-core.js";
|
|
3
3
|
import { selectEntities } from "../core/select-entities.js";
|
|
4
|
+
import { createIndexRegistry, } from "../../database/index-registry/index.js";
|
|
4
5
|
export function createStore(schema) {
|
|
5
6
|
const schemaArg = schema;
|
|
6
7
|
const hasSchemaShape = schemaArg &&
|
|
@@ -21,6 +22,96 @@ export function createStore(schema) {
|
|
|
21
22
|
const archetypeComponentNames = {};
|
|
22
23
|
const componentAndResourceSchemas = {};
|
|
23
24
|
const core = createCore(componentAndResourceSchemas);
|
|
25
|
+
// Index registry. Owned at the Store layer because index state is
|
|
26
|
+
// *derived* from store data, and every mutation that needs to keep
|
|
27
|
+
// indexes in sync flows through Store methods (insert / update /
|
|
28
|
+
// delete). Higher layers (`db.indexes`, `t.indexes`) expose this same
|
|
29
|
+
// map by reference.
|
|
30
|
+
//
|
|
31
|
+
// The reader is only used for `applyUpdate` — the patch passed to
|
|
32
|
+
// `store.update` may omit components an index covers, so the registry
|
|
33
|
+
// re-reads the full record to recompute the key. Seeding goes through
|
|
34
|
+
// `seedIndexFromArchetypes` instead (see below).
|
|
35
|
+
const indexRegistry = createIndexRegistry((entity) => {
|
|
36
|
+
const values = core.read(entity);
|
|
37
|
+
if (!values)
|
|
38
|
+
return null;
|
|
39
|
+
// Strip `id` — it is never a useful index key.
|
|
40
|
+
const { id: _id, ...rest } = values;
|
|
41
|
+
return rest;
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Populate an index from the archetypes whose component set is a
|
|
45
|
+
* superset of the index's `components`. Only those archetypes can
|
|
46
|
+
* possibly contribute — entities in any other archetype lack at
|
|
47
|
+
* least one of the indexed components, so they would never match
|
|
48
|
+
* `find` / `findRange` / `get` anyway.
|
|
49
|
+
*
|
|
50
|
+
* For each candidate archetype, the values record is built directly
|
|
51
|
+
* from the dense column buffers (no `store.read` / `locate`
|
|
52
|
+
* indirection) and handed to `idx.add` once per row.
|
|
53
|
+
*/
|
|
54
|
+
const seedIndexFromArchetypes = (idx) => {
|
|
55
|
+
// `idx.readColumns` already includes both bucket-key columns and
|
|
56
|
+
// sort columns (the RuntimeIndex unifies them at creation time).
|
|
57
|
+
// `queryArchetypes` filters to only those archetypes carrying the
|
|
58
|
+
// full set — entities in any other archetype lack at least one
|
|
59
|
+
// component the index touches and would never produce a key.
|
|
60
|
+
const required = idx.readColumns;
|
|
61
|
+
if (required.length === 0)
|
|
62
|
+
return;
|
|
63
|
+
const archetypes = core.queryArchetypes(required);
|
|
64
|
+
for (const archetype of archetypes) {
|
|
65
|
+
const idCol = archetype.columns.id;
|
|
66
|
+
for (let row = 0; row < archetype.rowCount; row++) {
|
|
67
|
+
const values = {};
|
|
68
|
+
for (const c of required) {
|
|
69
|
+
values[c] = archetype.columns[c].get(row);
|
|
70
|
+
}
|
|
71
|
+
idx.add(idCol.get(row), values);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
// Public handle map keyed by user-chosen name. Each entry exposes
|
|
76
|
+
// find/findRange (always) and get (when the index is unique). Same
|
|
77
|
+
// object reference flows up via spread to the Database layer.
|
|
78
|
+
const indexHandles = {};
|
|
79
|
+
const exposeIndexHandle = (name, idx) => {
|
|
80
|
+
if (name in indexHandles)
|
|
81
|
+
return;
|
|
82
|
+
// Decide whether the auto-router can dispatch a raw-equality `where`
|
|
83
|
+
// clause to this index. Routable iff the key declaration is a pure
|
|
84
|
+
// column reference (bare string) or column tuple (string array) —
|
|
85
|
+
// function keys derive a value from inputs (live in a different value
|
|
86
|
+
// space than the source columns) and slot maps name parts arbitrarily
|
|
87
|
+
// (`{ team: "team", role: ... }`), so the planner cannot infer the
|
|
88
|
+
// column set from a where clause alone for either.
|
|
89
|
+
const key = idx.key;
|
|
90
|
+
let routableColumns;
|
|
91
|
+
if (typeof key === "string") {
|
|
92
|
+
routableColumns = [key];
|
|
93
|
+
}
|
|
94
|
+
else if (Array.isArray(key)) {
|
|
95
|
+
routableColumns = key;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
routableColumns = null;
|
|
99
|
+
}
|
|
100
|
+
const handle = {
|
|
101
|
+
find: idx.find,
|
|
102
|
+
findRange: idx.findRange,
|
|
103
|
+
// Internal planner-only field. Not part of the public
|
|
104
|
+
// `Database.Index.Handle` type. `routableColumns: null` opts the
|
|
105
|
+
// index out of raw-where auto-routing (function/slot-map keys).
|
|
106
|
+
// The planner reads this to pick a matching index and then
|
|
107
|
+
// calls `handle.find` (so user-installed spies on the handle
|
|
108
|
+
// intercept the routed call).
|
|
109
|
+
routableColumns,
|
|
110
|
+
};
|
|
111
|
+
if (idx.unique)
|
|
112
|
+
handle.get = idx.get;
|
|
113
|
+
indexHandles[name] = handle;
|
|
114
|
+
};
|
|
24
115
|
// Each resource will be stored as the only entity in an archetype of [id, <resourceName>]
|
|
25
116
|
// The resource component we added above will contain the resource value
|
|
26
117
|
const ensureResourceInitialized = (name, resourceSchema) => {
|
|
@@ -34,6 +125,9 @@ export function createStore(schema) {
|
|
|
34
125
|
const insertValues = isEphemeral
|
|
35
126
|
? { [resourceId]: resourceSchema.default, ephemeral: true }
|
|
36
127
|
: { [resourceId]: resourceSchema.default };
|
|
128
|
+
// Resource singleton inserts bypass index pre-check because
|
|
129
|
+
// resources are not typically indexed by their schema name and
|
|
130
|
+
// the singleton row is created exactly once.
|
|
37
131
|
archetype.insert(insertValues);
|
|
38
132
|
}
|
|
39
133
|
if (!Object.prototype.hasOwnProperty.call(resources, name)) {
|
|
@@ -52,8 +146,66 @@ export function createStore(schema) {
|
|
|
52
146
|
return selectEntities(core, include, options);
|
|
53
147
|
};
|
|
54
148
|
const archetypes = {};
|
|
149
|
+
/**
|
|
150
|
+
* Wraps a raw Core archetype so `insert` maintains index state. Pre-checks
|
|
151
|
+
* unique constraints *before* the column mutation so a collision throws
|
|
152
|
+
* without partially mutating the store. Other archetype methods are
|
|
153
|
+
* passed through unchanged via the Proxy.
|
|
154
|
+
*
|
|
155
|
+
* Cached by raw archetype identity so repeated lookups
|
|
156
|
+
* (`store.archetypes.X`, `store.ensureArchetype([...])`) all return the
|
|
157
|
+
* same wrapper reference — code that uses `===` to compare archetypes
|
|
158
|
+
* keeps working.
|
|
159
|
+
*/
|
|
160
|
+
const archetypeWrapCache = new WeakMap();
|
|
161
|
+
const wrapArchetypeForIndexes = (archetype) => {
|
|
162
|
+
const cached = archetypeWrapCache.get(archetype);
|
|
163
|
+
if (cached)
|
|
164
|
+
return cached;
|
|
165
|
+
const rawInsert = archetype.insert.bind(archetype);
|
|
166
|
+
const wrappedInsert = (values) => {
|
|
167
|
+
indexRegistry.checkUniqueAvailableForInsert(values);
|
|
168
|
+
const entity = rawInsert(values);
|
|
169
|
+
indexRegistry.applyInsert(entity, values);
|
|
170
|
+
return entity;
|
|
171
|
+
};
|
|
172
|
+
const wrapped = new Proxy(archetype, {
|
|
173
|
+
get(target, prop, receiver) {
|
|
174
|
+
if (prop === "insert")
|
|
175
|
+
return wrappedInsert;
|
|
176
|
+
return Reflect.get(target, prop, receiver);
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
archetypeWrapCache.set(archetype, wrapped);
|
|
180
|
+
return wrapped;
|
|
181
|
+
};
|
|
182
|
+
const ensureArchetype = ((componentNames) => {
|
|
183
|
+
return wrapArchetypeForIndexes(core.ensureArchetype(componentNames));
|
|
184
|
+
});
|
|
185
|
+
// `queryArchetypes` and `locate` also surface archetypes — wrap them
|
|
186
|
+
// so `===` comparisons against `store.archetypes.X` continue to hold.
|
|
187
|
+
// Cached wrappers guarantee identity stability per raw archetype.
|
|
188
|
+
const queryArchetypes = ((include, options) => {
|
|
189
|
+
const raw = core.queryArchetypes(include, options);
|
|
190
|
+
return raw.map((a) => wrapArchetypeForIndexes(a));
|
|
191
|
+
});
|
|
192
|
+
const locate = ((entity) => {
|
|
193
|
+
const loc = core.locate(entity);
|
|
194
|
+
if (loc === null)
|
|
195
|
+
return null;
|
|
196
|
+
return { archetype: wrapArchetypeForIndexes(loc.archetype), row: loc.row };
|
|
197
|
+
});
|
|
198
|
+
const updateEntity = (entity, values) => {
|
|
199
|
+
indexRegistry.checkUniqueAvailableForUpdate(entity, values);
|
|
200
|
+
core.update(entity, values);
|
|
201
|
+
indexRegistry.applyUpdate(entity);
|
|
202
|
+
};
|
|
203
|
+
const deleteEntity = (entity) => {
|
|
204
|
+
core.delete(entity);
|
|
205
|
+
indexRegistry.applyDelete(entity);
|
|
206
|
+
};
|
|
55
207
|
const extend = (schema) => {
|
|
56
|
-
const { components: schemaComponents = {}, resources: schemaResources = {}, archetypes: schemaArchetypes = {} } = schema;
|
|
208
|
+
const { components: schemaComponents = {}, resources: schemaResources = {}, archetypes: schemaArchetypes = {}, indexes: schemaIndexes = {}, } = schema;
|
|
57
209
|
// components: existing must be identical if present
|
|
58
210
|
for (const [name, newComponentSchema] of Object.entries(schemaComponents)) {
|
|
59
211
|
if (name in componentAndResourceSchemas) {
|
|
@@ -81,7 +233,8 @@ export function createStore(schema) {
|
|
|
81
233
|
newResourceNames.push(name);
|
|
82
234
|
ensureResourceInitialized(name, newResourceSchema);
|
|
83
235
|
}
|
|
84
|
-
// archetypes: existing must be identical if present
|
|
236
|
+
// archetypes: existing must be identical if present.
|
|
237
|
+
// Wrap each archetype with index maintenance hooks at exposure time.
|
|
85
238
|
for (const [name, newComponents] of Object.entries(schemaArchetypes)) {
|
|
86
239
|
if (name in archetypeComponentNames) {
|
|
87
240
|
if (archetypeComponentNames[name] !== newComponents) {
|
|
@@ -91,21 +244,48 @@ export function createStore(schema) {
|
|
|
91
244
|
}
|
|
92
245
|
archetypeComponentNames[name] = newComponents;
|
|
93
246
|
const archetype = core.ensureArchetype(["id", ...newComponents]);
|
|
94
|
-
archetypes[name] = archetype;
|
|
247
|
+
archetypes[name] = wrapArchetypeForIndexes(archetype);
|
|
248
|
+
}
|
|
249
|
+
// indexes: registry enforces (===)-or-throw on same name and
|
|
250
|
+
// structural duplicate detection across names. A new RuntimeIndex
|
|
251
|
+
// is returned when registration actually happened (vs. a benign
|
|
252
|
+
// identity-match re-register), in which case we seed it from the
|
|
253
|
+
// archetypes that contain its component set — and only those.
|
|
254
|
+
const declaredIndexes = schemaIndexes;
|
|
255
|
+
for (const name in declaredIndexes) {
|
|
256
|
+
const newIdx = indexRegistry.register(name, declaredIndexes[name]);
|
|
257
|
+
if (newIdx) {
|
|
258
|
+
exposeIndexHandle(name, newIdx);
|
|
259
|
+
seedIndexFromArchetypes(newIdx);
|
|
260
|
+
}
|
|
95
261
|
}
|
|
96
262
|
return store;
|
|
97
263
|
};
|
|
98
264
|
const store = {
|
|
99
265
|
...core,
|
|
266
|
+
ensureArchetype,
|
|
267
|
+
queryArchetypes,
|
|
268
|
+
locate,
|
|
269
|
+
update: updateEntity,
|
|
270
|
+
delete: deleteEntity,
|
|
100
271
|
resources,
|
|
101
272
|
select,
|
|
102
273
|
archetypes,
|
|
274
|
+
indexes: indexHandles,
|
|
103
275
|
extend,
|
|
104
276
|
reset: () => {
|
|
105
277
|
core.reset();
|
|
106
278
|
for (const [name, resourceSchema] of Object.entries(resourceSchemas)) {
|
|
107
279
|
ensureResourceInitialized(name, resourceSchema);
|
|
108
280
|
}
|
|
281
|
+
// After reset, every index bucket is wiped. Resources are the
|
|
282
|
+
// only entities that exist post-reset; seed each index from
|
|
283
|
+
// archetypes so resource singletons whose components an index
|
|
284
|
+
// happens to cover still appear.
|
|
285
|
+
indexRegistry.clear();
|
|
286
|
+
for (const idx of indexRegistry.indexes.values()) {
|
|
287
|
+
seedIndexFromArchetypes(idx);
|
|
288
|
+
}
|
|
109
289
|
},
|
|
110
290
|
toData: () => core.toData(),
|
|
111
291
|
fromData: (data) => {
|
|
@@ -113,6 +293,12 @@ export function createStore(schema) {
|
|
|
113
293
|
for (const [name, resourceSchema] of Object.entries(resourceSchemas)) {
|
|
114
294
|
ensureResourceInitialized(name, resourceSchema);
|
|
115
295
|
}
|
|
296
|
+
// The loaded core has all entities; re-derive each index by
|
|
297
|
+
// walking only the archetypes that contain its components.
|
|
298
|
+
indexRegistry.clear();
|
|
299
|
+
for (const idx of indexRegistry.indexes.values()) {
|
|
300
|
+
seedIndexFromArchetypes(idx);
|
|
301
|
+
}
|
|
116
302
|
},
|
|
117
303
|
};
|
|
118
304
|
return store.extend(normalizedSchema);
|