@0xobelisk/ecs 1.2.0-pre.100

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/index.js ADDED
@@ -0,0 +1,2413 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ ComponentDiscoverer: () => ComponentDiscoverer,
34
+ DubheECSWorld: () => DubheECSWorld,
35
+ ECSQuery: () => ECSQuery,
36
+ ECSSubscription: () => ECSSubscription,
37
+ ResourceDiscoverer: () => ResourceDiscoverer,
38
+ calculateDelta: () => calculateDelta,
39
+ createCacheKey: () => createCacheKey,
40
+ createECSWorld: () => createECSWorld,
41
+ createTimestamp: () => createTimestamp,
42
+ debounce: () => debounce,
43
+ deepEqual: () => deepEqual,
44
+ default: () => DubheECSWorld,
45
+ extractEntityIds: () => extractEntityIds,
46
+ extractIntersectionFromBatchResult: () => extractIntersectionFromBatchResult,
47
+ extractPagedQueryResult: () => extractPagedQueryResult,
48
+ extractUnionFromBatchResult: () => extractUnionFromBatchResult,
49
+ findEntityIntersection: () => findEntityIntersection,
50
+ findEntityUnion: () => findEntityUnion,
51
+ formatError: () => formatError,
52
+ isValidComponentType: () => isValidComponentType,
53
+ isValidEntityId: () => isValidEntityId,
54
+ limitArray: () => limitArray,
55
+ normalizeComponentType: () => normalizeComponentType,
56
+ paginateArray: () => paginateArray,
57
+ safeJsonParse: () => safeJsonParse
58
+ });
59
+ module.exports = __toCommonJS(src_exports);
60
+
61
+ // src/utils.ts
62
+ function extractEntityIds(connection, options) {
63
+ const { idFields = ["nodeId", "entityId"], composite = false } = options || {};
64
+ return connection.edges.map((edge) => {
65
+ const node = edge.node;
66
+ if (composite) {
67
+ const idParts = idFields.map((field) => node[field] || "").filter(Boolean);
68
+ return idParts.join("|");
69
+ } else {
70
+ for (const field of idFields) {
71
+ if (node[field] !== void 0 && node[field] !== null) {
72
+ return node[field];
73
+ }
74
+ }
75
+ return Object.values(node)[0] || "";
76
+ }
77
+ }).filter(Boolean);
78
+ }
79
+ function extractPagedQueryResult(connection, options) {
80
+ const entityIds = extractEntityIds(connection, options);
81
+ const items = connection.edges.map((edge) => edge.node);
82
+ return {
83
+ entityIds,
84
+ items,
85
+ pageInfo: {
86
+ hasNextPage: connection.pageInfo.hasNextPage,
87
+ hasPreviousPage: connection.pageInfo.hasPreviousPage,
88
+ startCursor: connection.pageInfo.startCursor,
89
+ endCursor: connection.pageInfo.endCursor
90
+ },
91
+ totalCount: connection.totalCount || 0
92
+ };
93
+ }
94
+ function calculateDelta(oldResults, newResults) {
95
+ const oldSet = new Set(oldResults);
96
+ const newSet = new Set(newResults);
97
+ const added = newResults.filter((id) => !oldSet.has(id));
98
+ const removed = oldResults.filter((id) => !newSet.has(id));
99
+ return {
100
+ added,
101
+ removed,
102
+ current: newResults
103
+ };
104
+ }
105
+ function findEntityIntersection(entitySets) {
106
+ if (entitySets.length === 0) return [];
107
+ if (entitySets.length === 1) return entitySets[0];
108
+ return entitySets.reduce((intersection, currentSet) => {
109
+ const currentSetLookup = new Set(currentSet);
110
+ return intersection.filter((id) => currentSetLookup.has(id));
111
+ });
112
+ }
113
+ function findEntityUnion(entitySets) {
114
+ const unionSet = /* @__PURE__ */ new Set();
115
+ entitySets.forEach((set) => {
116
+ set.forEach((id) => unionSet.add(id));
117
+ });
118
+ return Array.from(unionSet);
119
+ }
120
+ function extractIntersectionFromBatchResult(batchResult, componentTypes, options) {
121
+ const entitySets = componentTypes.map((type) => {
122
+ const connection = batchResult[type];
123
+ return connection ? extractEntityIds(connection, options) : [];
124
+ });
125
+ return findEntityIntersection(entitySets);
126
+ }
127
+ function extractUnionFromBatchResult(batchResult, componentTypes, options) {
128
+ const entitySets = componentTypes.map((type) => {
129
+ const connection = batchResult[type];
130
+ return connection ? extractEntityIds(connection, options) : [];
131
+ });
132
+ return findEntityUnion(entitySets);
133
+ }
134
+ function debounce(func, waitMs) {
135
+ let timeoutId = null;
136
+ return (...args) => {
137
+ if (timeoutId) {
138
+ clearTimeout(timeoutId);
139
+ }
140
+ timeoutId = setTimeout(() => {
141
+ func(...args);
142
+ timeoutId = null;
143
+ }, waitMs);
144
+ };
145
+ }
146
+ function normalizeComponentType(componentType) {
147
+ const singular = componentType.endsWith("s") ? componentType.slice(0, -1) : componentType;
148
+ const plural = componentType.endsWith("s") ? componentType : componentType + "s";
149
+ return { singular, plural };
150
+ }
151
+ function createCacheKey(operation, componentTypes, options) {
152
+ const sortedTypes = [...componentTypes].sort();
153
+ const optionsStr = options ? JSON.stringify(options) : "";
154
+ return `${operation}:${sortedTypes.join(",")}:${optionsStr}`;
155
+ }
156
+ function isValidEntityId(entityId) {
157
+ return typeof entityId === "string" && entityId.length > 0;
158
+ }
159
+ function isValidComponentType(componentType) {
160
+ return typeof componentType === "string" && componentType.length > 0;
161
+ }
162
+ function deepEqual(obj1, obj2) {
163
+ if (obj1 === obj2) return true;
164
+ if (obj1 == null || obj2 == null) return false;
165
+ if (typeof obj1 !== typeof obj2) return false;
166
+ if (typeof obj1 !== "object") return false;
167
+ const keys1 = Object.keys(obj1);
168
+ const keys2 = Object.keys(obj2);
169
+ if (keys1.length !== keys2.length) return false;
170
+ for (const key of keys1) {
171
+ if (!keys2.includes(key)) return false;
172
+ if (!deepEqual(obj1[key], obj2[key])) return false;
173
+ }
174
+ return true;
175
+ }
176
+ function safeJsonParse(json, defaultValue) {
177
+ try {
178
+ return JSON.parse(json);
179
+ } catch {
180
+ return defaultValue;
181
+ }
182
+ }
183
+ function formatError(error) {
184
+ if (error instanceof Error) {
185
+ return error.message;
186
+ }
187
+ if (typeof error === "string") {
188
+ return error;
189
+ }
190
+ return JSON.stringify(error);
191
+ }
192
+ function createTimestamp() {
193
+ return Date.now();
194
+ }
195
+ function limitArray(array, limit) {
196
+ return limit > 0 ? array.slice(0, limit) : array;
197
+ }
198
+ function paginateArray(array, page, pageSize) {
199
+ const startIndex = (page - 1) * pageSize;
200
+ const endIndex = startIndex + pageSize;
201
+ const items = array.slice(startIndex, endIndex);
202
+ return {
203
+ items,
204
+ totalCount: array.length,
205
+ hasMore: endIndex < array.length,
206
+ page,
207
+ pageSize
208
+ };
209
+ }
210
+
211
+ // src/query.ts
212
+ var ECSQuery = class {
213
+ constructor(graphqlClient, componentDiscoverer) {
214
+ this.queryCache = /* @__PURE__ */ new Map();
215
+ this.cacheTimeout = 5e3;
216
+ // 5 second cache timeout
217
+ this.availableComponents = [];
218
+ this.componentDiscoverer = null;
219
+ // Component primary key cache - pre-parsed during initialization
220
+ this.componentPrimaryKeys = /* @__PURE__ */ new Map();
221
+ this.graphqlClient = graphqlClient;
222
+ this.componentDiscoverer = componentDiscoverer || null;
223
+ }
224
+ /**
225
+ * Set available component list
226
+ */
227
+ setAvailableComponents(componentTypes) {
228
+ this.availableComponents = componentTypes;
229
+ }
230
+ /**
231
+ * Pre-parse and cache all component primary key information
232
+ */
233
+ initializeComponentMetadata(componentMetadataList) {
234
+ this.componentPrimaryKeys.clear();
235
+ for (const metadata of componentMetadataList) {
236
+ if (metadata.primaryKeys.length === 1) {
237
+ this.componentPrimaryKeys.set(metadata.name, metadata.primaryKeys[0]);
238
+ }
239
+ }
240
+ }
241
+ /**
242
+ * Get component's primary key field name (quickly retrieve from cache)
243
+ */
244
+ getComponentPrimaryKeyField(componentType) {
245
+ return this.componentPrimaryKeys.get(componentType) || "entityId";
246
+ }
247
+ /**
248
+ * Set component discoverer
249
+ */
250
+ setComponentDiscoverer(discoverer) {
251
+ this.componentDiscoverer = discoverer;
252
+ }
253
+ buildPaginationParams(options) {
254
+ const params = {};
255
+ if (options?.first !== void 0) {
256
+ params.first = options.first;
257
+ } else if (options?.limit !== void 0) {
258
+ params.first = options.limit;
259
+ if (options?.offset !== void 0) {
260
+ console.warn(
261
+ "ECS Query: offset parameter is not supported with GraphQL cursor-based pagination. Use after/before instead."
262
+ );
263
+ }
264
+ }
265
+ if (options?.last !== void 0) {
266
+ params.last = options.last;
267
+ }
268
+ if (options?.after !== void 0) {
269
+ params.after = options.after;
270
+ }
271
+ if (options?.before !== void 0) {
272
+ params.before = options.before;
273
+ }
274
+ return params;
275
+ }
276
+ /**
277
+ * Get component field information
278
+ */
279
+ async getComponentFields(componentType) {
280
+ if (this.componentDiscoverer) {
281
+ try {
282
+ const metadata = this.componentDiscoverer.getComponentMetadata(componentType);
283
+ if (metadata) {
284
+ return metadata.fields.map((field) => field.name);
285
+ }
286
+ } catch (_error) {
287
+ }
288
+ }
289
+ throw new Error(
290
+ `Unable to get field information for component ${componentType}. Please explicitly specify fields in QueryOptions or ensure component discoverer is properly configured.`
291
+ );
292
+ }
293
+ /**
294
+ * Get component's primary key fields
295
+ */
296
+ async getComponentPrimaryKeys(componentType) {
297
+ if (this.componentDiscoverer) {
298
+ try {
299
+ const metadata = this.componentDiscoverer.getComponentMetadata(componentType);
300
+ if (metadata && metadata.primaryKeys.length > 0) {
301
+ return metadata.primaryKeys;
302
+ }
303
+ } catch (_error) {
304
+ }
305
+ }
306
+ throw new Error(
307
+ `Unable to get primary key information for component ${componentType}. Please explicitly specify idFields in QueryOptions or ensure component discoverer is properly configured.`
308
+ );
309
+ }
310
+ /**
311
+ * Get fields to use for queries (priority: user specified > dubhe config auto-parsed)
312
+ */
313
+ async getQueryFields(componentType, userFields) {
314
+ if (userFields && userFields.length > 0) {
315
+ return userFields;
316
+ }
317
+ return this.getComponentFields(componentType);
318
+ }
319
+ /**
320
+ * Check if entity exists
321
+ */
322
+ async hasEntity(entityId) {
323
+ if (!isValidEntityId(entityId)) return false;
324
+ try {
325
+ const tables = await this.getAvailableComponents();
326
+ for (const table of tables) {
327
+ try {
328
+ const condition = this.buildEntityCondition(table, entityId);
329
+ const component = await this.graphqlClient.getTableByCondition(table, condition);
330
+ if (component) return true;
331
+ } catch (_error) {
332
+ }
333
+ }
334
+ return false;
335
+ } catch (_error) {
336
+ return false;
337
+ }
338
+ }
339
+ /**
340
+ * Get all entity IDs (collected from all component tables)
341
+ */
342
+ async getAllEntities() {
343
+ try {
344
+ const tables = await this.getAvailableComponents();
345
+ const queries = await Promise.all(
346
+ tables.map(async (table) => {
347
+ const fields = await this.getQueryFields(table);
348
+ const primaryKey = this.componentPrimaryKeys.get(table) || "entityId";
349
+ return {
350
+ key: table,
351
+ tableName: table,
352
+ params: {
353
+ fields,
354
+ filter: {}
355
+ },
356
+ primaryKey
357
+ // Use cached primary key information
358
+ };
359
+ })
360
+ );
361
+ const batchResult = await this.graphqlClient.batchQuery(
362
+ queries.map((q) => ({
363
+ key: q.key,
364
+ tableName: q.tableName,
365
+ params: q.params
366
+ }))
367
+ );
368
+ return extractUnionFromBatchResult(batchResult, tables, {
369
+ idFields: void 0,
370
+ // Let extractEntityIds auto-infer
371
+ composite: false
372
+ });
373
+ } catch (_error) {
374
+ return [];
375
+ }
376
+ }
377
+ /**
378
+ * Get entity count
379
+ */
380
+ async getEntityCount() {
381
+ const entities = await this.getAllEntities();
382
+ return entities.length;
383
+ }
384
+ /**
385
+ * Check if entity has specific component
386
+ */
387
+ async hasComponent(entityId, componentType) {
388
+ if (!isValidEntityId(entityId) || !isValidComponentType(componentType)) {
389
+ return false;
390
+ }
391
+ if (!this.isECSComponent(componentType)) {
392
+ return false;
393
+ }
394
+ try {
395
+ const condition = this.buildEntityCondition(componentType, entityId);
396
+ const component = await this.graphqlClient.getTableByCondition(componentType, condition);
397
+ return component !== null;
398
+ } catch (_error) {
399
+ return false;
400
+ }
401
+ }
402
+ /**
403
+ * Get specific component data of entity
404
+ */
405
+ async getComponent(entityId, componentType) {
406
+ if (!isValidEntityId(entityId) || !isValidComponentType(componentType)) {
407
+ return null;
408
+ }
409
+ if (!this.isECSComponent(componentType)) {
410
+ return null;
411
+ }
412
+ try {
413
+ const condition = this.buildEntityCondition(componentType, entityId);
414
+ const component = await this.graphqlClient.getTableByCondition(componentType, condition);
415
+ return component;
416
+ } catch (_error) {
417
+ return null;
418
+ }
419
+ }
420
+ /**
421
+ * Get all component types that entity has
422
+ */
423
+ async getComponents(entityId) {
424
+ if (!isValidEntityId(entityId)) return [];
425
+ try {
426
+ const tables = await this.getAvailableComponents();
427
+ const componentTypes = [];
428
+ await Promise.all(
429
+ tables.map(async (table) => {
430
+ const hasComp = await this.hasComponent(entityId, table);
431
+ if (hasComp) {
432
+ componentTypes.push(table);
433
+ }
434
+ })
435
+ );
436
+ return componentTypes;
437
+ } catch (_error) {
438
+ return [];
439
+ }
440
+ }
441
+ /**
442
+ * Validate if component type is ECS-compliant
443
+ */
444
+ isECSComponent(componentType) {
445
+ return this.availableComponents.includes(componentType);
446
+ }
447
+ /**
448
+ * Build entity query condition (using cached primary key field name)
449
+ */
450
+ buildEntityCondition(componentType, entityId) {
451
+ const primaryKeyField = this.componentPrimaryKeys.get(componentType);
452
+ if (primaryKeyField) {
453
+ return { [primaryKeyField]: entityId };
454
+ } else {
455
+ return { entityId };
456
+ }
457
+ }
458
+ /**
459
+ * Filter and validate component type list, keeping only ECS-compliant components
460
+ */
461
+ filterValidECSComponents(componentTypes) {
462
+ const validComponents = componentTypes.filter((componentType) => {
463
+ if (!isValidComponentType(componentType)) {
464
+ return false;
465
+ }
466
+ if (!this.isECSComponent(componentType)) {
467
+ return false;
468
+ }
469
+ return true;
470
+ });
471
+ return validComponents;
472
+ }
473
+ /**
474
+ * Query all entities that have a specific component with full pagination info
475
+ */
476
+ async queryWithFullPagination(componentType, options) {
477
+ const emptyResult = {
478
+ entityIds: [],
479
+ items: [],
480
+ pageInfo: {
481
+ hasNextPage: false,
482
+ hasPreviousPage: false
483
+ },
484
+ totalCount: 0
485
+ };
486
+ if (!isValidComponentType(componentType)) return emptyResult;
487
+ if (!this.isECSComponent(componentType)) {
488
+ return emptyResult;
489
+ }
490
+ try {
491
+ const queryFields = await this.getQueryFields(componentType, options?.fields);
492
+ const primaryKeys = await this.getComponentPrimaryKeys(componentType);
493
+ const paginationParams = this.buildPaginationParams(options);
494
+ const connection = await this.graphqlClient.getAllTables(componentType, {
495
+ ...paginationParams,
496
+ fields: queryFields,
497
+ orderBy: options?.orderBy
498
+ });
499
+ return extractPagedQueryResult(connection, {
500
+ idFields: options?.idFields || primaryKeys,
501
+ composite: options?.compositeId
502
+ });
503
+ } catch (_error) {
504
+ return emptyResult;
505
+ }
506
+ }
507
+ /**
508
+ * Query all entities that have a specific component
509
+ * Returns complete pagination information including entity IDs, actual data, and page info
510
+ */
511
+ async queryWith(componentType, options) {
512
+ return this.queryWithFullPagination(componentType, options);
513
+ }
514
+ /**
515
+ * Query entities that have all specified components (intersection)
516
+ */
517
+ async queryWithAll(componentTypes, options) {
518
+ if (componentTypes.length === 0) return [];
519
+ if (componentTypes.length === 1) {
520
+ const result = await this.queryWith(componentTypes[0], options);
521
+ return result.entityIds;
522
+ }
523
+ const validTypes = this.filterValidECSComponents(componentTypes);
524
+ if (validTypes.length === 0) return [];
525
+ const cacheKey = createCacheKey("queryWithAll", validTypes, options);
526
+ const cached = this.getCachedResult(cacheKey);
527
+ if (cached && options?.cache !== false) return cached;
528
+ try {
529
+ const paginationParams = this.buildPaginationParams(options);
530
+ const queries = await Promise.all(
531
+ validTypes.map(async (type) => {
532
+ const queryFields = await this.getQueryFields(type, options?.fields);
533
+ return {
534
+ key: type,
535
+ tableName: type,
536
+ params: {
537
+ fields: queryFields,
538
+ ...paginationParams,
539
+ orderBy: options?.orderBy
540
+ }
541
+ };
542
+ })
543
+ );
544
+ const batchResult = await this.graphqlClient.batchQuery(queries);
545
+ let idFields = options?.idFields;
546
+ if (!idFields && validTypes.length > 0) {
547
+ try {
548
+ idFields = await this.getComponentPrimaryKeys(validTypes[0]);
549
+ } catch (_error) {
550
+ }
551
+ }
552
+ const result = extractIntersectionFromBatchResult(batchResult, validTypes, {
553
+ idFields,
554
+ composite: options?.compositeId
555
+ });
556
+ this.setCachedResult(cacheKey, result);
557
+ return result;
558
+ } catch (_error) {
559
+ return [];
560
+ }
561
+ }
562
+ /**
563
+ * Query entities that have any of the specified components (union)
564
+ */
565
+ async queryWithAny(componentTypes, options) {
566
+ if (componentTypes.length === 0) return [];
567
+ if (componentTypes.length === 1) {
568
+ const result = await this.queryWith(componentTypes[0], options);
569
+ return result.entityIds;
570
+ }
571
+ const validTypes = this.filterValidECSComponents(componentTypes);
572
+ if (validTypes.length === 0) return [];
573
+ const cacheKey = createCacheKey("queryWithAny", validTypes, options);
574
+ const cached = this.getCachedResult(cacheKey);
575
+ if (cached && options?.cache !== false) return cached;
576
+ try {
577
+ const paginationParams = this.buildPaginationParams(options);
578
+ const queries = await Promise.all(
579
+ validTypes.map(async (type) => {
580
+ const queryFields = await this.getQueryFields(type, options?.fields);
581
+ return {
582
+ key: type,
583
+ tableName: type,
584
+ params: {
585
+ fields: queryFields,
586
+ ...paginationParams,
587
+ orderBy: options?.orderBy
588
+ }
589
+ };
590
+ })
591
+ );
592
+ const batchResult = await this.graphqlClient.batchQuery(queries);
593
+ let idFields = options?.idFields;
594
+ if (!idFields && validTypes.length > 0) {
595
+ try {
596
+ idFields = await this.getComponentPrimaryKeys(validTypes[0]);
597
+ } catch (_error) {
598
+ }
599
+ }
600
+ const result = extractUnionFromBatchResult(batchResult, validTypes, {
601
+ idFields,
602
+ composite: options?.compositeId
603
+ });
604
+ this.setCachedResult(cacheKey, result);
605
+ return result;
606
+ } catch (_error) {
607
+ return [];
608
+ }
609
+ }
610
+ /**
611
+ * Query entities that have include components but not exclude components
612
+ */
613
+ async queryWithout(includeTypes, excludeTypes, options) {
614
+ if (includeTypes.length === 0) return [];
615
+ const validIncludeTypes = this.filterValidECSComponents(includeTypes);
616
+ if (validIncludeTypes.length === 0) return [];
617
+ const validExcludeTypes = this.filterValidECSComponents(excludeTypes);
618
+ try {
619
+ const includedEntities = await this.queryWithAll(validIncludeTypes, options);
620
+ if (validExcludeTypes.length === 0) return includedEntities;
621
+ const excludedEntities = await this.queryWithAny(validExcludeTypes);
622
+ const excludedSet = new Set(excludedEntities);
623
+ return includedEntities.filter((entityId) => !excludedSet.has(entityId));
624
+ } catch (_error) {
625
+ return [];
626
+ }
627
+ }
628
+ /**
629
+ * Query components based on conditions with full pagination info
630
+ */
631
+ async queryWhereFullPagination(componentType, predicate, options) {
632
+ const emptyResult = {
633
+ entityIds: [],
634
+ items: [],
635
+ pageInfo: {
636
+ hasNextPage: false,
637
+ hasPreviousPage: false
638
+ },
639
+ totalCount: 0
640
+ };
641
+ if (!isValidComponentType(componentType)) return emptyResult;
642
+ if (!this.isECSComponent(componentType)) {
643
+ return emptyResult;
644
+ }
645
+ try {
646
+ const queryFields = await this.getQueryFields(componentType, options?.fields);
647
+ const primaryKeys = await this.getComponentPrimaryKeys(componentType);
648
+ const paginationParams = this.buildPaginationParams(options);
649
+ const connection = await this.graphqlClient.getAllTables(componentType, {
650
+ filter: predicate,
651
+ ...paginationParams,
652
+ fields: queryFields,
653
+ orderBy: options?.orderBy
654
+ });
655
+ return extractPagedQueryResult(connection, {
656
+ idFields: options?.idFields || primaryKeys,
657
+ composite: options?.compositeId
658
+ });
659
+ } catch (_error) {
660
+ return emptyResult;
661
+ }
662
+ }
663
+ /**
664
+ * Query components based on conditions
665
+ */
666
+ async queryWhere(componentType, predicate, options) {
667
+ const result = await this.queryWhereFullPagination(componentType, predicate, options);
668
+ return result.entityIds;
669
+ }
670
+ /**
671
+ * Range query
672
+ */
673
+ async queryRange(componentType, field, min, max, options) {
674
+ if (!isValidComponentType(componentType)) return [];
675
+ if (!this.isECSComponent(componentType)) {
676
+ return [];
677
+ }
678
+ const predicate = {
679
+ [field]: {
680
+ greaterThanOrEqualTo: min,
681
+ lessThanOrEqualTo: max
682
+ }
683
+ };
684
+ return this.queryWhere(componentType, predicate, options);
685
+ }
686
+ /**
687
+ * Paginated query
688
+ */
689
+ async queryPaged(componentTypes, page, pageSize) {
690
+ try {
691
+ const allResults = componentTypes.length === 1 ? (await this.queryWith(componentTypes[0])).entityIds : await this.queryWithAll(componentTypes);
692
+ return paginateArray(allResults, page, pageSize);
693
+ } catch (_error) {
694
+ return {
695
+ items: [],
696
+ totalCount: 0,
697
+ hasMore: false,
698
+ page,
699
+ pageSize
700
+ };
701
+ }
702
+ }
703
+ /**
704
+ * Create query builder
705
+ */
706
+ query() {
707
+ return new ECSQueryBuilder(this);
708
+ }
709
+ /**
710
+ * Get cached result
711
+ */
712
+ getCachedResult(cacheKey) {
713
+ const cached = this.queryCache.get(cacheKey);
714
+ if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
715
+ return cached.result;
716
+ }
717
+ return null;
718
+ }
719
+ /**
720
+ * Set cached result
721
+ */
722
+ setCachedResult(cacheKey, result) {
723
+ this.queryCache.set(cacheKey, {
724
+ result,
725
+ timestamp: Date.now()
726
+ });
727
+ }
728
+ /**
729
+ * Clean expired cache
730
+ */
731
+ cleanExpiredCache() {
732
+ const now = Date.now();
733
+ for (const [key, cached] of this.queryCache.entries()) {
734
+ if (now - cached.timestamp >= this.cacheTimeout) {
735
+ this.queryCache.delete(key);
736
+ }
737
+ }
738
+ }
739
+ /**
740
+ * Get available component list
741
+ */
742
+ async getAvailableComponents() {
743
+ if (this.availableComponents.length > 0) {
744
+ return this.availableComponents;
745
+ }
746
+ return [];
747
+ }
748
+ /**
749
+ * Dispose resources
750
+ */
751
+ dispose() {
752
+ this.queryCache.clear();
753
+ }
754
+ };
755
+ var ECSQueryBuilder = class {
756
+ constructor(ecsQuery) {
757
+ this.includeTypes = [];
758
+ this.excludeTypes = [];
759
+ this.whereConditions = [];
760
+ this.orderByOptions = [];
761
+ this.ecsQuery = ecsQuery;
762
+ }
763
+ with(...componentTypes) {
764
+ this.includeTypes.push(...componentTypes);
765
+ return this;
766
+ }
767
+ without(...componentTypes) {
768
+ this.excludeTypes.push(...componentTypes);
769
+ return this;
770
+ }
771
+ where(componentType, predicate) {
772
+ this.whereConditions.push({ componentType, predicate });
773
+ return this;
774
+ }
775
+ orderBy(componentType, field, direction = "ASC") {
776
+ this.orderByOptions.push({ componentType, field, direction });
777
+ return this;
778
+ }
779
+ limit(count) {
780
+ this.limitValue = count;
781
+ return this;
782
+ }
783
+ offset(count) {
784
+ this.offsetValue = count;
785
+ return this;
786
+ }
787
+ async execute() {
788
+ try {
789
+ const options = {
790
+ limit: this.limitValue,
791
+ offset: this.offsetValue,
792
+ orderBy: this.orderByOptions.map((order) => ({
793
+ field: order.field,
794
+ direction: order.direction
795
+ }))
796
+ };
797
+ if (this.whereConditions.length > 0) {
798
+ const filteredResults = [];
799
+ for (const condition of this.whereConditions) {
800
+ const result = await this.ecsQuery.queryWhere(
801
+ condition.componentType,
802
+ condition.predicate,
803
+ options
804
+ );
805
+ filteredResults.push(result);
806
+ }
807
+ const intersection = filteredResults.reduce((acc, current) => {
808
+ const currentSet = new Set(current);
809
+ return acc.filter((id) => currentSet.has(id));
810
+ });
811
+ return intersection;
812
+ }
813
+ if (this.excludeTypes.length > 0) {
814
+ return this.ecsQuery.queryWithout(this.includeTypes, this.excludeTypes, options);
815
+ } else {
816
+ return this.ecsQuery.queryWithAll(this.includeTypes, options);
817
+ }
818
+ } catch (_error) {
819
+ return [];
820
+ }
821
+ }
822
+ };
823
+
824
+ // src/subscription.ts
825
+ var import_client = require("@apollo/client");
826
+ var import_pluralize = __toESM(require("pluralize"));
827
+ var ECSSubscription = class {
828
+ constructor(graphqlClient, componentDiscoverer) {
829
+ this.subscriptions = /* @__PURE__ */ new Map();
830
+ this.queryWatchers = /* @__PURE__ */ new Map();
831
+ this.componentDiscoverer = null;
832
+ this.availableComponents = [];
833
+ // Component primary key cache - consistent with implementation in query.ts
834
+ this.componentPrimaryKeys = /* @__PURE__ */ new Map();
835
+ this.graphqlClient = graphqlClient;
836
+ this.componentDiscoverer = componentDiscoverer || null;
837
+ }
838
+ /**
839
+ * Set available component list
840
+ */
841
+ setAvailableComponents(componentTypes) {
842
+ this.availableComponents = componentTypes;
843
+ }
844
+ /**
845
+ * Pre-parse and cache all component primary key information (consistent with query.ts)
846
+ */
847
+ initializeComponentMetadata(componentMetadataList) {
848
+ this.componentPrimaryKeys.clear();
849
+ for (const metadata of componentMetadataList) {
850
+ if (metadata.primaryKeys.length === 1) {
851
+ this.componentPrimaryKeys.set(metadata.name, metadata.primaryKeys[0]);
852
+ }
853
+ }
854
+ }
855
+ /**
856
+ * Get component's primary key field name (quickly retrieve from cache, consistent with query.ts)
857
+ */
858
+ getComponentPrimaryKeyField(componentType) {
859
+ return this.componentPrimaryKeys.get(componentType) || "entityId";
860
+ }
861
+ /**
862
+ * Set component discoverer
863
+ */
864
+ setComponentDiscoverer(discoverer) {
865
+ this.componentDiscoverer = discoverer;
866
+ }
867
+ /**
868
+ * Validate if component type is ECS-compliant
869
+ */
870
+ isECSComponent(componentType) {
871
+ return this.availableComponents.includes(componentType);
872
+ }
873
+ /**
874
+ * Get component field information (intelligent parsing)
875
+ */
876
+ async getComponentFields(componentType) {
877
+ if (this.componentDiscoverer) {
878
+ try {
879
+ const metadata = this.componentDiscoverer.getComponentMetadata(componentType);
880
+ if (metadata) {
881
+ return metadata.fields.map((field) => field.name);
882
+ }
883
+ } catch (_error) {
884
+ }
885
+ }
886
+ return ["createdAtTimestampMs", "updatedAtTimestampMs", "isDeleted", "lastUpdateDigest"];
887
+ }
888
+ /**
889
+ * Get fields to use for queries (priority: user specified > dubhe config auto-parsed > default fields)
890
+ */
891
+ async getQueryFields(componentType, userFields) {
892
+ if (userFields && userFields.length > 0) {
893
+ return userFields;
894
+ }
895
+ return this.getComponentFields(componentType);
896
+ }
897
+ /**
898
+ * Listen to component added events
899
+ */
900
+ onComponentAdded(componentType, options) {
901
+ if (!isValidComponentType(componentType)) {
902
+ return new import_client.Observable((observer) => {
903
+ observer.error(new Error(`Invalid component type: ${componentType}`));
904
+ });
905
+ }
906
+ if (!this.isECSComponent(componentType)) {
907
+ return new import_client.Observable((observer) => {
908
+ observer.error(
909
+ new Error(`Component type ${componentType} is not ECS-compliant or not available`)
910
+ );
911
+ });
912
+ }
913
+ return new import_client.Observable((observer) => {
914
+ let subscription = null;
915
+ this.getQueryFields(componentType, options?.fields).then((subscriptionFields) => {
916
+ const debouncedEmit = options?.debounceMs ? debounce(
917
+ (result) => observer.next(result),
918
+ options.debounceMs
919
+ ) : (result) => observer.next(result);
920
+ const observable = this.graphqlClient.subscribeToTableChanges(componentType, {
921
+ initialEvent: options?.initialEvent ?? false,
922
+ fields: subscriptionFields,
923
+ onData: (data) => {
924
+ try {
925
+ const pluralTableName = this.getPluralTableName(componentType);
926
+ const nodes = data?.listen?.query?.[pluralTableName]?.nodes;
927
+ if (nodes && Array.isArray(nodes)) {
928
+ nodes.forEach((node) => {
929
+ if (node) {
930
+ const entityId = node.entityId || this.extractEntityId(node, componentType);
931
+ if (entityId) {
932
+ const result = {
933
+ entityId,
934
+ data: node,
935
+ changeType: "added",
936
+ timestamp: Date.now()
937
+ };
938
+ debouncedEmit(result);
939
+ }
940
+ }
941
+ });
942
+ }
943
+ } catch (error) {
944
+ observer.error(error);
945
+ }
946
+ },
947
+ onError: (error) => {
948
+ observer.error(error);
949
+ },
950
+ onComplete: () => {
951
+ observer.complete();
952
+ }
953
+ });
954
+ subscription = observable.subscribe({});
955
+ }).catch((error) => {
956
+ observer.error(error);
957
+ });
958
+ return () => {
959
+ if (subscription) {
960
+ subscription.unsubscribe();
961
+ }
962
+ };
963
+ });
964
+ }
965
+ /**
966
+ * Listen to component removed events
967
+ */
968
+ onComponentRemoved(componentType, options) {
969
+ if (!isValidComponentType(componentType)) {
970
+ return new import_client.Observable((observer) => {
971
+ observer.error(new Error(`Invalid component type: ${componentType}`));
972
+ });
973
+ }
974
+ if (!this.isECSComponent(componentType)) {
975
+ return new import_client.Observable((observer) => {
976
+ observer.error(
977
+ new Error(`Component type ${componentType} is not ECS-compliant or not available`)
978
+ );
979
+ });
980
+ }
981
+ return new import_client.Observable((observer) => {
982
+ let subscription = null;
983
+ let lastKnownEntities = /* @__PURE__ */ new Set();
984
+ try {
985
+ const debouncedEmit = options?.debounceMs ? debounce(
986
+ (result) => observer.next(result),
987
+ options.debounceMs
988
+ ) : (result) => observer.next(result);
989
+ this.initializeLastKnownEntities(componentType, lastKnownEntities);
990
+ const observable = this.graphqlClient.subscribeToTableChanges(componentType, {
991
+ initialEvent: false,
992
+ fields: ["updatedAtTimestampMs"],
993
+ // Removal detection only needs basic fields
994
+ onData: (data) => {
995
+ try {
996
+ const pluralTableName = this.getPluralTableName(componentType);
997
+ const nodes = data?.listen?.query?.[pluralTableName]?.nodes || [];
998
+ const currentEntities = new Set(
999
+ nodes.map((node) => {
1000
+ const entityId = node.entityId || this.extractEntityId(node, componentType);
1001
+ return entityId;
1002
+ }).filter(Boolean)
1003
+ );
1004
+ const removedEntities = Array.from(lastKnownEntities).filter(
1005
+ (entityId) => !currentEntities.has(entityId)
1006
+ );
1007
+ removedEntities.forEach((entityId) => {
1008
+ const result = {
1009
+ entityId,
1010
+ data: null,
1011
+ changeType: "removed",
1012
+ timestamp: Date.now()
1013
+ };
1014
+ debouncedEmit(result);
1015
+ });
1016
+ lastKnownEntities = currentEntities;
1017
+ } catch (error) {
1018
+ observer.error(error);
1019
+ }
1020
+ },
1021
+ onError: (error) => {
1022
+ observer.error(error);
1023
+ },
1024
+ onComplete: () => {
1025
+ observer.complete();
1026
+ }
1027
+ });
1028
+ subscription = observable.subscribe({});
1029
+ } catch (error) {
1030
+ observer.error(error);
1031
+ }
1032
+ return () => {
1033
+ if (subscription) {
1034
+ subscription.unsubscribe();
1035
+ }
1036
+ };
1037
+ });
1038
+ }
1039
+ /**
1040
+ * Listen to component changed events (added, removed, modified)
1041
+ */
1042
+ onComponentChanged(componentType, options) {
1043
+ if (!isValidComponentType(componentType)) {
1044
+ return new import_client.Observable((observer) => {
1045
+ observer.error(new Error(`Invalid component type: ${componentType}`));
1046
+ });
1047
+ }
1048
+ if (!this.isECSComponent(componentType)) {
1049
+ return new import_client.Observable((observer) => {
1050
+ observer.error(
1051
+ new Error(`Component type ${componentType} is not ECS-compliant or not available`)
1052
+ );
1053
+ });
1054
+ }
1055
+ return new import_client.Observable((observer) => {
1056
+ let subscription = null;
1057
+ this.getQueryFields(componentType, options?.fields).then((subscriptionFields) => {
1058
+ const debouncedEmit = options?.debounceMs ? debounce(
1059
+ (result) => observer.next(result),
1060
+ options.debounceMs
1061
+ ) : (result) => observer.next(result);
1062
+ const observable = this.graphqlClient.subscribeToTableChanges(componentType, {
1063
+ initialEvent: options?.initialEvent ?? false,
1064
+ fields: subscriptionFields,
1065
+ onData: (data) => {
1066
+ try {
1067
+ const pluralTableName = this.getPluralTableName(componentType);
1068
+ const nodes = data?.listen?.query?.[pluralTableName]?.nodes;
1069
+ if (nodes && Array.isArray(nodes)) {
1070
+ nodes.forEach((node) => {
1071
+ if (node) {
1072
+ const entityId = node.entityId || this.extractEntityId(node, componentType);
1073
+ if (entityId) {
1074
+ const result = {
1075
+ entityId,
1076
+ data: node,
1077
+ changeType: "updated",
1078
+ timestamp: Date.now()
1079
+ };
1080
+ debouncedEmit(result);
1081
+ }
1082
+ }
1083
+ });
1084
+ }
1085
+ } catch (error) {
1086
+ observer.error(error);
1087
+ }
1088
+ },
1089
+ onError: (error) => {
1090
+ observer.error(error);
1091
+ },
1092
+ onComplete: () => {
1093
+ observer.complete();
1094
+ }
1095
+ });
1096
+ subscription = observable.subscribe({});
1097
+ }).catch((error) => {
1098
+ observer.error(error);
1099
+ });
1100
+ return () => {
1101
+ if (subscription) {
1102
+ subscription.unsubscribe();
1103
+ }
1104
+ };
1105
+ });
1106
+ }
1107
+ /**
1108
+ * Listen to component changes with specific conditions
1109
+ */
1110
+ onEntityComponent(componentType, entityId, options) {
1111
+ if (!isValidComponentType(componentType)) {
1112
+ return new import_client.Observable((observer) => {
1113
+ observer.error(new Error(`Invalid component type: ${componentType}`));
1114
+ });
1115
+ }
1116
+ if (!this.isECSComponent(componentType)) {
1117
+ return new import_client.Observable((observer) => {
1118
+ observer.error(
1119
+ new Error(`Component type ${componentType} is not ECS-compliant or not available`)
1120
+ );
1121
+ });
1122
+ }
1123
+ return new import_client.Observable((observer) => {
1124
+ let subscription = null;
1125
+ this.getQueryFields(componentType, options?.fields).then((subscriptionFields) => {
1126
+ const debouncedEmit = options?.debounceMs ? debounce(
1127
+ (result) => observer.next(result),
1128
+ options.debounceMs
1129
+ ) : (result) => observer.next(result);
1130
+ const primaryKeyField = this.getComponentPrimaryKeyField(componentType);
1131
+ const entityFilter = {
1132
+ [primaryKeyField]: { equalTo: entityId }
1133
+ };
1134
+ const observable = this.graphqlClient.subscribeToTableChanges(componentType, {
1135
+ initialEvent: options?.initialEvent ?? false,
1136
+ fields: subscriptionFields,
1137
+ filter: entityFilter,
1138
+ onData: (data) => {
1139
+ try {
1140
+ const pluralTableName = this.getPluralTableName(componentType);
1141
+ const nodes = data?.listen?.query?.[pluralTableName]?.nodes || [];
1142
+ nodes.forEach((node) => {
1143
+ if (node) {
1144
+ const entityId2 = node.entityId || this.extractEntityId(node, componentType);
1145
+ if (entityId2) {
1146
+ const result = {
1147
+ entityId: entityId2,
1148
+ data: node,
1149
+ changeType: "updated",
1150
+ timestamp: Date.now()
1151
+ };
1152
+ debouncedEmit(result);
1153
+ }
1154
+ }
1155
+ });
1156
+ } catch (error) {
1157
+ observer.error(error);
1158
+ }
1159
+ },
1160
+ onError: (error) => {
1161
+ observer.error(error);
1162
+ },
1163
+ onComplete: () => {
1164
+ observer.complete();
1165
+ }
1166
+ });
1167
+ subscription = observable.subscribe({});
1168
+ }).catch((error) => {
1169
+ observer.error(error);
1170
+ });
1171
+ return () => {
1172
+ if (subscription) {
1173
+ subscription.unsubscribe();
1174
+ }
1175
+ };
1176
+ });
1177
+ }
1178
+ /**
1179
+ * Listen to query result changes
1180
+ */
1181
+ watchQuery(componentTypes, options) {
1182
+ const validTypes = componentTypes.filter(isValidComponentType);
1183
+ if (validTypes.length === 0) {
1184
+ return new import_client.Observable((observer) => {
1185
+ observer.error(new Error("No valid component types for query watching"));
1186
+ });
1187
+ }
1188
+ return new import_client.Observable((observer) => {
1189
+ const watcher = new QueryWatcherImpl(
1190
+ this.graphqlClient,
1191
+ validTypes,
1192
+ (changes) => {
1193
+ const result = {
1194
+ changes
1195
+ };
1196
+ if (options?.debounceMs) {
1197
+ const debouncedEmit = debounce(() => observer.next(result), options.debounceMs);
1198
+ debouncedEmit();
1199
+ } else {
1200
+ observer.next(result);
1201
+ }
1202
+ },
1203
+ options
1204
+ );
1205
+ return () => {
1206
+ watcher.dispose();
1207
+ };
1208
+ });
1209
+ }
1210
+ /**
1211
+ * Create real-time data stream
1212
+ */
1213
+ createRealTimeStream(componentType, initialFilter) {
1214
+ if (!isValidComponentType(componentType)) {
1215
+ return new import_client.Observable((observer) => {
1216
+ observer.error(new Error(`Invalid component type: ${componentType}`));
1217
+ });
1218
+ }
1219
+ return new import_client.Observable((observer) => {
1220
+ try {
1221
+ const subscription = this.graphqlClient.createRealTimeDataStream(componentType, {
1222
+ filter: initialFilter
1223
+ });
1224
+ const streamSubscription = subscription.subscribe({
1225
+ next: (connection) => {
1226
+ const results = connection.edges.map((edge) => {
1227
+ const node = edge.node;
1228
+ const entityId = node.nodeId || node.entityId || Object.values(node)[0] || "";
1229
+ return {
1230
+ entityId,
1231
+ data: node
1232
+ };
1233
+ }).filter((result) => result.entityId);
1234
+ observer.next(results);
1235
+ },
1236
+ error: (error) => {
1237
+ observer.error(error);
1238
+ },
1239
+ complete: () => {
1240
+ observer.complete();
1241
+ }
1242
+ });
1243
+ return () => {
1244
+ streamSubscription.unsubscribe();
1245
+ };
1246
+ } catch (error) {
1247
+ observer.error(error);
1248
+ }
1249
+ });
1250
+ }
1251
+ /**
1252
+ * Initialize known entity list (for deletion detection)
1253
+ */
1254
+ async initializeLastKnownEntities(componentType, lastKnownEntities) {
1255
+ try {
1256
+ const connection = await this.graphqlClient.getAllTables(componentType, {
1257
+ fields: ["updatedAtTimestampMs"]
1258
+ });
1259
+ connection.edges.forEach((edge) => {
1260
+ const node = edge.node;
1261
+ const entityId = node.nodeId || node.entityId || Object.values(node)[0];
1262
+ if (entityId) {
1263
+ lastKnownEntities.add(entityId);
1264
+ }
1265
+ });
1266
+ } catch (_error) {
1267
+ }
1268
+ }
1269
+ /**
1270
+ * Convert singular table name to plural form (using pluralize library for correctness)
1271
+ */
1272
+ getPluralTableName(tableName) {
1273
+ const camelCaseName = this.toCamelCase(tableName);
1274
+ return import_pluralize.default.plural(camelCaseName);
1275
+ }
1276
+ /**
1277
+ * Convert snake_case to camelCase
1278
+ */
1279
+ toCamelCase(str) {
1280
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1281
+ }
1282
+ /**
1283
+ * Extract entity ID from node (using component's primary key field information)
1284
+ */
1285
+ extractEntityId(node, componentType) {
1286
+ if (!node || typeof node !== "object") {
1287
+ return "";
1288
+ }
1289
+ const primaryKeyField = this.getComponentPrimaryKeyField(componentType);
1290
+ if (node[primaryKeyField] && typeof node[primaryKeyField] === "string") {
1291
+ return node[primaryKeyField];
1292
+ }
1293
+ if (primaryKeyField !== "entityId" && node.entityId && typeof node.entityId === "string") {
1294
+ return node.entityId;
1295
+ }
1296
+ const values = Object.values(node);
1297
+ for (const value of values) {
1298
+ if (typeof value === "string" && value.length > 0) {
1299
+ return value;
1300
+ }
1301
+ }
1302
+ return "";
1303
+ }
1304
+ /**
1305
+ * Unsubscribe all subscriptions
1306
+ */
1307
+ unsubscribeAll() {
1308
+ this.subscriptions.forEach((subscription) => {
1309
+ try {
1310
+ subscription?.unsubscribe();
1311
+ } catch (_error) {
1312
+ }
1313
+ });
1314
+ this.subscriptions.clear();
1315
+ this.queryWatchers.forEach((watcher) => {
1316
+ try {
1317
+ watcher.dispose();
1318
+ } catch (_error) {
1319
+ }
1320
+ });
1321
+ this.queryWatchers.clear();
1322
+ }
1323
+ /**
1324
+ * Dispose resources
1325
+ */
1326
+ dispose() {
1327
+ this.unsubscribeAll();
1328
+ }
1329
+ };
1330
+ var QueryWatcherImpl = class {
1331
+ constructor(graphqlClient, componentTypes, callback, options) {
1332
+ this.subscriptions = [];
1333
+ this.currentResults = [];
1334
+ this.isInitialized = false;
1335
+ this.graphqlClient = graphqlClient;
1336
+ this.componentTypes = componentTypes;
1337
+ this.callback = callback;
1338
+ this.options = options;
1339
+ this.initialize();
1340
+ }
1341
+ async initialize() {
1342
+ try {
1343
+ await this.updateCurrentResults();
1344
+ this.componentTypes.forEach((componentType) => {
1345
+ const observable = this.graphqlClient.subscribeToTableChanges(componentType, {
1346
+ initialEvent: false,
1347
+ onData: () => {
1348
+ this.handleDataChange();
1349
+ },
1350
+ onError: (_error) => {
1351
+ }
1352
+ });
1353
+ const actualSubscription = observable.subscribe({});
1354
+ this.subscriptions.push(actualSubscription);
1355
+ });
1356
+ this.isInitialized = true;
1357
+ if (this.options?.initialEvent && this.currentResults.length > 0) {
1358
+ this.callback({
1359
+ added: this.currentResults,
1360
+ removed: [],
1361
+ current: this.currentResults
1362
+ });
1363
+ }
1364
+ } catch (_error) {
1365
+ }
1366
+ }
1367
+ async handleDataChange() {
1368
+ if (!this.isInitialized) return;
1369
+ try {
1370
+ const oldResults = [...this.currentResults];
1371
+ await this.updateCurrentResults();
1372
+ const changes = calculateDelta(oldResults, this.currentResults);
1373
+ if (changes.added.length > 0 || changes.removed.length > 0) {
1374
+ const debouncedCallback = this.options?.debounceMs ? debounce(this.callback, this.options.debounceMs) : this.callback;
1375
+ debouncedCallback(changes);
1376
+ }
1377
+ } catch (_error) {
1378
+ }
1379
+ }
1380
+ async updateCurrentResults() {
1381
+ try {
1382
+ if (this.componentTypes.length === 1) {
1383
+ const connection = await this.graphqlClient.getAllTables(this.componentTypes[0], {
1384
+ fields: ["updatedAtTimestampMs"]
1385
+ });
1386
+ this.currentResults = connection.edges.map((edge) => {
1387
+ const node = edge.node;
1388
+ return node.nodeId || node.entityId || Object.values(node)[0] || "";
1389
+ }).filter(Boolean);
1390
+ } else {
1391
+ const queries = this.componentTypes.map((type) => ({
1392
+ key: type,
1393
+ tableName: type,
1394
+ params: {
1395
+ fields: ["updatedAtTimestampMs"],
1396
+ filter: {}
1397
+ }
1398
+ }));
1399
+ const batchResult = await this.graphqlClient.batchQuery(queries);
1400
+ const entitySets = this.componentTypes.map((type) => {
1401
+ const connection = batchResult[type];
1402
+ return connection ? connection.edges.map((edge) => {
1403
+ const node = edge.node;
1404
+ return node.nodeId || node.entityId || Object.values(node)[0] || "";
1405
+ }).filter(Boolean) : [];
1406
+ });
1407
+ this.currentResults = entitySets.reduce((intersection, currentSet) => {
1408
+ const currentSetLookup = new Set(currentSet);
1409
+ return intersection.filter((id) => currentSetLookup.has(id));
1410
+ });
1411
+ }
1412
+ } catch (_error) {
1413
+ this.currentResults = [];
1414
+ }
1415
+ }
1416
+ getCurrentResults() {
1417
+ return [...this.currentResults];
1418
+ }
1419
+ dispose() {
1420
+ this.subscriptions.forEach((subscription) => {
1421
+ try {
1422
+ subscription?.unsubscribe();
1423
+ } catch (_error) {
1424
+ }
1425
+ });
1426
+ this.subscriptions = [];
1427
+ }
1428
+ };
1429
+
1430
+ // src/world.ts
1431
+ var ComponentDiscoverer = class {
1432
+ constructor(graphqlClient, dubheMetadata) {
1433
+ this.componentMetadataMap = /* @__PURE__ */ new Map();
1434
+ this.componentTypes = [];
1435
+ this.graphqlClient = graphqlClient;
1436
+ this.dubheMetadata = dubheMetadata;
1437
+ const components = [];
1438
+ const errors = [];
1439
+ this.parseFromDubheMetadata(components, errors);
1440
+ const result = {
1441
+ components,
1442
+ discoveredAt: Date.now(),
1443
+ errors: errors.length > 0 ? errors : void 0,
1444
+ totalDiscovered: components.length,
1445
+ fromDubheMetadata: true
1446
+ };
1447
+ this.discoveryResult = result;
1448
+ this.componentTypes = components.map((comp) => comp.name);
1449
+ components.forEach((comp) => {
1450
+ this.componentMetadataMap.set(comp.name, comp);
1451
+ });
1452
+ }
1453
+ /**
1454
+ * Parse components from DubheMetadata JSON format.
1455
+ *
1456
+ * Two sources are scanned:
1457
+ * 1. dubheMetadata.components – explicit ECS components (legacy / manual).
1458
+ * 2. dubheMetadata.resources – resources whose sole key is `entity_id` are
1459
+ * treated as ECS components because they follow the same 1-entity-1-record
1460
+ * semantics. Resources with composite keys (entity_id + extra keys) have
1461
+ * N records per entity and remain pure resources.
1462
+ */
1463
+ parseFromDubheMetadata(components, errors) {
1464
+ const entries = [];
1465
+ if (this.dubheMetadata?.components) {
1466
+ for (const record of this.dubheMetadata.components) {
1467
+ entries.push(...Object.entries(record));
1468
+ }
1469
+ }
1470
+ if (this.dubheMetadata?.resources) {
1471
+ for (const record of this.dubheMetadata.resources) {
1472
+ for (const [name, cfg] of Object.entries(record)) {
1473
+ if (cfg.keys && cfg.keys.length === 1 && cfg.keys[0] === "entity_id") {
1474
+ entries.push([name, cfg]);
1475
+ }
1476
+ }
1477
+ }
1478
+ }
1479
+ for (const [componentName, componentConfig] of entries) {
1480
+ const componentType = this.tableNameToComponentName(componentName);
1481
+ try {
1482
+ const fields = [];
1483
+ const primaryKeys = [];
1484
+ const enumFields = [];
1485
+ if (componentConfig.fields && Array.isArray(componentConfig.fields)) {
1486
+ for (const fieldRecord of componentConfig.fields) {
1487
+ for (const [fieldName, fieldType] of Object.entries(fieldRecord)) {
1488
+ const camelFieldName = this.snakeToCamel(fieldName);
1489
+ const typeStr = String(fieldType);
1490
+ const isCustomKey = componentConfig.keys && componentConfig.keys.includes(fieldName);
1491
+ fields.push({
1492
+ name: camelFieldName,
1493
+ type: this.dubheTypeToGraphQLType(typeStr),
1494
+ nullable: !isCustomKey,
1495
+ isPrimaryKey: isCustomKey,
1496
+ isEnum: this.isEnumType(typeStr)
1497
+ });
1498
+ if (isCustomKey) {
1499
+ primaryKeys.push(camelFieldName);
1500
+ }
1501
+ if (this.isEnumType(typeStr)) {
1502
+ enumFields.push(camelFieldName);
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+ if (primaryKeys.length === 0) {
1508
+ fields.unshift({
1509
+ name: "entityId",
1510
+ type: "String",
1511
+ nullable: false,
1512
+ isPrimaryKey: true,
1513
+ isEnum: false
1514
+ });
1515
+ primaryKeys.push("entityId");
1516
+ }
1517
+ fields.push(
1518
+ {
1519
+ name: "createdAtTimestampMs",
1520
+ type: "BigInt",
1521
+ nullable: false,
1522
+ isPrimaryKey: false,
1523
+ isEnum: false
1524
+ },
1525
+ {
1526
+ name: "updatedAtTimestampMs",
1527
+ type: "BigInt",
1528
+ nullable: false,
1529
+ isPrimaryKey: false,
1530
+ isEnum: false
1531
+ },
1532
+ {
1533
+ name: "isDeleted",
1534
+ type: "Boolean",
1535
+ nullable: false,
1536
+ isPrimaryKey: false,
1537
+ isEnum: false
1538
+ },
1539
+ {
1540
+ name: "lastUpdateDigest",
1541
+ type: "String",
1542
+ nullable: false,
1543
+ isPrimaryKey: false,
1544
+ isEnum: false
1545
+ }
1546
+ );
1547
+ if (primaryKeys.length !== 1) {
1548
+ continue;
1549
+ }
1550
+ const metadata = {
1551
+ name: componentType,
1552
+ tableName: componentName,
1553
+ fields,
1554
+ primaryKeys,
1555
+ hasDefaultId: primaryKeys.includes("entityId"),
1556
+ enumFields,
1557
+ lastUpdated: Date.now(),
1558
+ description: `Auto-discovered component: ${componentName}`
1559
+ };
1560
+ components.push(metadata);
1561
+ } catch (error) {
1562
+ const errorMsg = `Component ${componentType} validation failed: ${formatError(error)}`;
1563
+ errors.push(errorMsg);
1564
+ }
1565
+ }
1566
+ }
1567
+ getComponentTypes() {
1568
+ return this.componentTypes;
1569
+ }
1570
+ getComponentMetadata(componentType) {
1571
+ return this.componentMetadataMap.get(componentType) || null;
1572
+ }
1573
+ snakeToCamel(str) {
1574
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1575
+ }
1576
+ dubheTypeToGraphQLType(dubheType) {
1577
+ if (dubheType.startsWith("vector<") && dubheType.endsWith(">")) {
1578
+ return "String";
1579
+ }
1580
+ switch (dubheType) {
1581
+ case "u8":
1582
+ case "u16":
1583
+ case "u32":
1584
+ case "u64":
1585
+ case "u128":
1586
+ case "i8":
1587
+ case "i16":
1588
+ case "i32":
1589
+ case "i64":
1590
+ case "i128":
1591
+ return "Int";
1592
+ case "address":
1593
+ case "string":
1594
+ return "String";
1595
+ case "bool":
1596
+ return "Boolean";
1597
+ case "enum":
1598
+ return "String";
1599
+ default:
1600
+ return "String";
1601
+ }
1602
+ }
1603
+ componentNameToTableName(componentName) {
1604
+ if (!componentName.endsWith("s")) {
1605
+ return componentName + "s";
1606
+ }
1607
+ return componentName;
1608
+ }
1609
+ tableNameToComponentName(tableName) {
1610
+ if (tableName.endsWith("s") && tableName.length > 1) {
1611
+ return tableName.slice(0, -1);
1612
+ }
1613
+ return tableName;
1614
+ }
1615
+ isEnumType(typeStr) {
1616
+ return this.dubheMetadata.enums.some(
1617
+ (enumDef) => typeof enumDef === "object" && enumDef[typeStr]
1618
+ );
1619
+ }
1620
+ };
1621
+ var ResourceDiscoverer = class {
1622
+ constructor(graphqlClient, dubheMetadata) {
1623
+ this.resourceMetadataMap = /* @__PURE__ */ new Map();
1624
+ this.resourceTypes = [];
1625
+ this.graphqlClient = graphqlClient;
1626
+ this.dubheMetadata = dubheMetadata;
1627
+ const resources = [];
1628
+ const errors = [];
1629
+ this.parseFromDubheMetadata(resources, errors);
1630
+ const result = {
1631
+ resources,
1632
+ discoveredAt: Date.now(),
1633
+ errors: errors.length > 0 ? errors : void 0,
1634
+ totalDiscovered: resources.length,
1635
+ fromDubheMetadata: true
1636
+ };
1637
+ this.discoveryResult = result;
1638
+ this.resourceTypes = resources.map((res) => res.name);
1639
+ resources.forEach((res) => {
1640
+ this.resourceMetadataMap.set(res.name, res);
1641
+ });
1642
+ }
1643
+ /**
1644
+ * Parse resources from DubheMetadata JSON format
1645
+ */
1646
+ parseFromDubheMetadata(resources, errors) {
1647
+ if (!this.dubheMetadata?.resources) {
1648
+ return;
1649
+ }
1650
+ for (const resourceRecord of this.dubheMetadata.resources) {
1651
+ for (const [resourceName, resourceConfig] of Object.entries(resourceRecord)) {
1652
+ try {
1653
+ const fields = [];
1654
+ const primaryKeys = [];
1655
+ const enumFields = [];
1656
+ if (resourceConfig.fields && Array.isArray(resourceConfig.fields)) {
1657
+ for (const fieldRecord of resourceConfig.fields) {
1658
+ for (const [fieldName, fieldType] of Object.entries(fieldRecord)) {
1659
+ const camelFieldName = this.snakeToCamel(fieldName);
1660
+ const typeStr = String(fieldType);
1661
+ const isCustomKey = resourceConfig.keys && resourceConfig.keys.includes(fieldName);
1662
+ fields.push({
1663
+ name: camelFieldName,
1664
+ type: this.dubheTypeToGraphQLType(typeStr),
1665
+ nullable: !isCustomKey,
1666
+ isPrimaryKey: isCustomKey,
1667
+ isEnum: this.isEnumType(typeStr)
1668
+ });
1669
+ if (isCustomKey) {
1670
+ primaryKeys.push(camelFieldName);
1671
+ }
1672
+ if (this.isEnumType(typeStr)) {
1673
+ enumFields.push(camelFieldName);
1674
+ }
1675
+ }
1676
+ }
1677
+ }
1678
+ fields.push(
1679
+ {
1680
+ name: "createdAtTimestampMs",
1681
+ type: "BigInt",
1682
+ nullable: false,
1683
+ isPrimaryKey: false,
1684
+ isEnum: false
1685
+ },
1686
+ {
1687
+ name: "updatedAtTimestampMs",
1688
+ type: "BigInt",
1689
+ nullable: false,
1690
+ isPrimaryKey: false,
1691
+ isEnum: false
1692
+ },
1693
+ {
1694
+ name: "isDeleted",
1695
+ type: "Boolean",
1696
+ nullable: false,
1697
+ isPrimaryKey: false,
1698
+ isEnum: false
1699
+ },
1700
+ {
1701
+ name: "lastUpdateDigest",
1702
+ type: "String",
1703
+ nullable: false,
1704
+ isPrimaryKey: false,
1705
+ isEnum: false
1706
+ }
1707
+ );
1708
+ const resourceType = resourceName;
1709
+ const metadata = {
1710
+ name: resourceType,
1711
+ tableName: resourceName,
1712
+ fields,
1713
+ primaryKeys,
1714
+ hasCompositeKeys: primaryKeys.length > 1,
1715
+ hasNoKeys: primaryKeys.length === 0,
1716
+ enumFields,
1717
+ lastUpdated: Date.now(),
1718
+ description: `Auto-discovered resource: ${resourceName}`
1719
+ };
1720
+ resources.push(metadata);
1721
+ } catch (error) {
1722
+ const errorMsg = `Resource ${resourceName} validation failed: ${formatError(error)}`;
1723
+ errors.push(errorMsg);
1724
+ }
1725
+ }
1726
+ }
1727
+ }
1728
+ getResourceTypes() {
1729
+ return this.resourceTypes;
1730
+ }
1731
+ getResourceMetadata(resourceType) {
1732
+ return this.resourceMetadataMap.get(resourceType) || null;
1733
+ }
1734
+ snakeToCamel(str) {
1735
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
1736
+ }
1737
+ dubheTypeToGraphQLType(dubheType) {
1738
+ if (dubheType.startsWith("vector<") && dubheType.endsWith(">")) {
1739
+ return "String";
1740
+ }
1741
+ switch (dubheType) {
1742
+ case "u8":
1743
+ case "u16":
1744
+ case "u32":
1745
+ case "u64":
1746
+ case "u128":
1747
+ case "i8":
1748
+ case "i16":
1749
+ case "i32":
1750
+ case "i64":
1751
+ case "i128":
1752
+ return "Int";
1753
+ case "address":
1754
+ case "string":
1755
+ return "String";
1756
+ case "bool":
1757
+ return "Boolean";
1758
+ case "enum":
1759
+ return "String";
1760
+ default:
1761
+ return "String";
1762
+ }
1763
+ }
1764
+ isEnumType(typeStr) {
1765
+ return this.dubheMetadata.enums.some(
1766
+ (enumDef) => typeof enumDef === "object" && enumDef[typeStr]
1767
+ );
1768
+ }
1769
+ };
1770
+ var DubheECSWorld = class {
1771
+ constructor(graphqlClient, config) {
1772
+ this.graphqlClient = graphqlClient;
1773
+ this.config = {
1774
+ queryConfig: {
1775
+ defaultCacheTimeout: 5 * 60 * 1e3,
1776
+ maxConcurrentQueries: 10,
1777
+ enableBatchOptimization: true
1778
+ },
1779
+ subscriptionConfig: {
1780
+ defaultDebounceMs: 100,
1781
+ maxSubscriptions: 50,
1782
+ reconnectOnError: true
1783
+ },
1784
+ ...config
1785
+ };
1786
+ let dubheMetadata = this.config.dubheMetadata;
1787
+ if (!dubheMetadata) {
1788
+ dubheMetadata = this.graphqlClient.getDubheMetadata();
1789
+ if (!dubheMetadata) {
1790
+ throw new Error(
1791
+ "DubheMetadata is required for ECS World initialization. Please provide it either in ECSWorldConfig or in GraphQL client configuration."
1792
+ );
1793
+ }
1794
+ }
1795
+ this.dubheMetadata = dubheMetadata;
1796
+ this.componentDiscoverer = new ComponentDiscoverer(graphqlClient, this.dubheMetadata);
1797
+ this.resourceDiscoverer = new ResourceDiscoverer(graphqlClient, this.dubheMetadata);
1798
+ this.querySystem = new ECSQuery(graphqlClient, this.componentDiscoverer);
1799
+ this.subscriptionSystem = new ECSSubscription(graphqlClient, this.componentDiscoverer);
1800
+ this.initializeWithConfig();
1801
+ }
1802
+ initializeWithConfig() {
1803
+ try {
1804
+ const ecsComponents = this.componentDiscoverer.discoveryResult.components.filter((comp) => {
1805
+ return comp.primaryKeys.length === 1;
1806
+ });
1807
+ const _resources = this.resourceDiscoverer.discoveryResult.resources;
1808
+ this.querySystem.setAvailableComponents(ecsComponents.map((comp) => comp.name));
1809
+ this.querySystem.initializeComponentMetadata(
1810
+ ecsComponents.map((comp) => ({
1811
+ name: comp.name,
1812
+ primaryKeys: comp.primaryKeys
1813
+ }))
1814
+ );
1815
+ this.subscriptionSystem.setAvailableComponents(ecsComponents.map((comp) => comp.name));
1816
+ this.subscriptionSystem.initializeComponentMetadata(
1817
+ ecsComponents.map((comp) => ({
1818
+ name: comp.name,
1819
+ primaryKeys: comp.primaryKeys
1820
+ }))
1821
+ );
1822
+ if (this.config.queryConfig) {
1823
+ }
1824
+ if (this.config.subscriptionConfig) {
1825
+ }
1826
+ } catch (error) {
1827
+ throw new Error(`Failed to initialize ECS World: ${formatError(error)}`);
1828
+ }
1829
+ }
1830
+ // ============ Configuration and Initialization ============
1831
+ /**
1832
+ * Configure ECS world
1833
+ */
1834
+ configure(config) {
1835
+ this.config = { ...this.config, ...config };
1836
+ if (config.dubheMetadata) {
1837
+ this.dubheMetadata = config.dubheMetadata;
1838
+ this.componentDiscoverer = new ComponentDiscoverer(this.graphqlClient, this.dubheMetadata);
1839
+ this.resourceDiscoverer = new ResourceDiscoverer(this.graphqlClient, this.dubheMetadata);
1840
+ this.querySystem.setComponentDiscoverer(this.componentDiscoverer);
1841
+ this.subscriptionSystem.setComponentDiscoverer(this.componentDiscoverer);
1842
+ this.initializeWithConfig();
1843
+ }
1844
+ }
1845
+ // ============ Component Discovery and Information ============
1846
+ /**
1847
+ * Discover and return all available ECS component types
1848
+ */
1849
+ discoverComponents() {
1850
+ return this.componentDiscoverer.getComponentTypes();
1851
+ }
1852
+ /**
1853
+ * Get all available ECS component types (cached)
1854
+ */
1855
+ getAvailableComponents() {
1856
+ return this.componentDiscoverer.getComponentTypes();
1857
+ }
1858
+ /**
1859
+ * Get metadata for a specific ECS component
1860
+ */
1861
+ getComponentMetadata(componentType) {
1862
+ return this.componentDiscoverer.getComponentMetadata(componentType);
1863
+ }
1864
+ // ============ Resource Discovery and Information ============
1865
+ /**
1866
+ * Get all available resource types
1867
+ */
1868
+ getAvailableResources() {
1869
+ return this.resourceDiscoverer.getResourceTypes();
1870
+ }
1871
+ /**
1872
+ * Get metadata for a specific resource
1873
+ */
1874
+ getResourceMetadata(resourceType) {
1875
+ return this.resourceDiscoverer.getResourceMetadata(resourceType);
1876
+ }
1877
+ // ============ Entity Queries ============
1878
+ /**
1879
+ * Check if entity exists
1880
+ */
1881
+ async hasEntity(entityId) {
1882
+ return this.querySystem.hasEntity(entityId);
1883
+ }
1884
+ /**
1885
+ * Get all entity IDs
1886
+ */
1887
+ async getAllEntities() {
1888
+ return this.querySystem.getAllEntities();
1889
+ }
1890
+ /**
1891
+ * Get entity count
1892
+ */
1893
+ async getEntityCount() {
1894
+ return this.querySystem.getEntityCount();
1895
+ }
1896
+ // ============ Standard ECS Interface (camelCase naming) ============
1897
+ /**
1898
+ * Get complete data of a single entity
1899
+ * @param entityId Entity ID
1900
+ * @returns Complete component data of the entity, or null if entity doesn't exist
1901
+ */
1902
+ async getEntity(entityId) {
1903
+ try {
1904
+ const exists = await this.hasEntity(entityId);
1905
+ if (!exists) {
1906
+ return null;
1907
+ }
1908
+ const componentTypes = await this.getComponents(entityId);
1909
+ if (componentTypes.length === 0) {
1910
+ return null;
1911
+ }
1912
+ const entityData = {
1913
+ entityId,
1914
+ components: {}
1915
+ };
1916
+ for (const componentType of componentTypes) {
1917
+ const componentData = await this.getComponent(entityId, componentType);
1918
+ if (componentData) {
1919
+ entityData.components[componentType] = componentData;
1920
+ }
1921
+ }
1922
+ return entityData;
1923
+ } catch (error) {
1924
+ console.error(`Failed to get entity ${entityId}:`, formatError(error));
1925
+ return null;
1926
+ }
1927
+ }
1928
+ /**
1929
+ * Get all entity ID list
1930
+ * @returns Array of all entity IDs
1931
+ */
1932
+ async getEntities() {
1933
+ return this.getAllEntities();
1934
+ }
1935
+ /**
1936
+ * Get all entities that have a specific component
1937
+ * @param componentType Component type
1938
+ * @returns Array of entity IDs that have this component
1939
+ */
1940
+ async getEntitiesByComponent(componentType) {
1941
+ return this.queryWith(componentType);
1942
+ }
1943
+ // Note: getComponent, getComponents, hasComponent methods are defined below
1944
+ // ============ Component Queries ============
1945
+ /**
1946
+ * Check if entity has specific component
1947
+ */
1948
+ async hasComponent(entityId, componentType) {
1949
+ return this.querySystem.hasComponent(entityId, componentType);
1950
+ }
1951
+ /**
1952
+ * Get specific component data of entity
1953
+ */
1954
+ async getComponent(entityId, componentType) {
1955
+ return this.querySystem.getComponent(entityId, componentType);
1956
+ }
1957
+ /**
1958
+ * Get all component types that entity has
1959
+ */
1960
+ async getComponents(entityId) {
1961
+ return this.querySystem.getComponents(entityId);
1962
+ }
1963
+ // ============ World Queries ============
1964
+ /**
1965
+ * Query all entities that have a specific component
1966
+ */
1967
+ async queryWith(componentType, options) {
1968
+ return this.querySystem.queryWith(componentType, options);
1969
+ }
1970
+ /**
1971
+ * Query entities that have all specified components (intersection)
1972
+ */
1973
+ async queryWithAll(componentTypes, options) {
1974
+ return this.querySystem.queryWithAll(componentTypes, options);
1975
+ }
1976
+ /**
1977
+ * Query entities that have any of the specified components (union)
1978
+ */
1979
+ async queryWithAny(componentTypes, options) {
1980
+ return this.querySystem.queryWithAny(componentTypes, options);
1981
+ }
1982
+ /**
1983
+ * Query entities that have include components but not exclude components
1984
+ */
1985
+ async queryWithout(includeTypes, excludeTypes, options) {
1986
+ return this.querySystem.queryWithout(includeTypes, excludeTypes, options);
1987
+ }
1988
+ // ============ Conditional Queries ============
1989
+ /**
1990
+ * Query components based on conditions
1991
+ */
1992
+ async queryWhere(componentType, predicate, options) {
1993
+ return this.querySystem.queryWhere(componentType, predicate, options);
1994
+ }
1995
+ /**
1996
+ * Range query
1997
+ */
1998
+ async queryRange(componentType, field, min, max, options) {
1999
+ return this.querySystem.queryRange(componentType, field, min, max, options);
2000
+ }
2001
+ /**
2002
+ * Paginated query (legacy version using page/pageSize)
2003
+ */
2004
+ async queryPaged(componentTypes, page, pageSize) {
2005
+ return this.querySystem.queryPaged(componentTypes, page, pageSize);
2006
+ }
2007
+ /**
2008
+ * Query with complete pagination info using GraphQL cursor-based pagination
2009
+ */
2010
+ async queryWithPagination(componentType, options) {
2011
+ return this.querySystem.queryWith(componentType, options);
2012
+ }
2013
+ /**
2014
+ * Query components with conditions and complete pagination info
2015
+ */
2016
+ async queryWherePagination(componentType, predicate, options) {
2017
+ return this.querySystem.queryWhereFullPagination(componentType, predicate, options);
2018
+ }
2019
+ // ============ Query Builder ============
2020
+ /**
2021
+ * Create query builder
2022
+ */
2023
+ query() {
2024
+ return this.querySystem.query();
2025
+ }
2026
+ // ============ Subscription System ============
2027
+ /**
2028
+ * Listen to component added events
2029
+ */
2030
+ onComponentAdded(componentType, options) {
2031
+ return this.subscriptionSystem.onComponentAdded(componentType, options);
2032
+ }
2033
+ /**
2034
+ * Listen to component removed events
2035
+ */
2036
+ onComponentRemoved(componentType, options) {
2037
+ return this.subscriptionSystem.onComponentRemoved(componentType, options);
2038
+ }
2039
+ /**
2040
+ * Listen to component changed events
2041
+ */
2042
+ onComponentChanged(componentType, options) {
2043
+ return this.subscriptionSystem.onComponentChanged(componentType, options);
2044
+ }
2045
+ /**
2046
+ * Listen to component changes with specific conditions
2047
+ */
2048
+ onEntityComponent(componentType, entityId, options) {
2049
+ return this.subscriptionSystem.onEntityComponent(componentType, entityId, options);
2050
+ }
2051
+ /**
2052
+ * Listen to query result changes
2053
+ */
2054
+ watchQuery(componentTypes, options) {
2055
+ return this.subscriptionSystem.watchQuery(componentTypes, options);
2056
+ }
2057
+ /**
2058
+ * Create real-time data stream
2059
+ */
2060
+ createRealTimeStream(componentType, initialFilter) {
2061
+ return this.subscriptionSystem.createRealTimeStream(componentType, initialFilter);
2062
+ }
2063
+ // ============ Convenience Methods ============
2064
+ /**
2065
+ * Query entity data with specific component (includes component data)
2066
+ */
2067
+ async queryWithComponentData(componentType, options) {
2068
+ try {
2069
+ const data = await this.queryWith(componentType, options);
2070
+ const results = [];
2071
+ for (const entityId of data.entityIds) {
2072
+ const componentData = await this.getComponent(entityId, componentType);
2073
+ if (componentData) {
2074
+ results.push({ entityId, data: componentData });
2075
+ }
2076
+ }
2077
+ return results;
2078
+ } catch (_error) {
2079
+ return [];
2080
+ }
2081
+ }
2082
+ /**
2083
+ * Query multi-component entity data
2084
+ */
2085
+ async queryMultiComponentData(component1Type, component2Type, options) {
2086
+ try {
2087
+ const entityIds = await this.queryWithAll([component1Type, component2Type], options);
2088
+ const results = [];
2089
+ for (const entityId of entityIds) {
2090
+ const [data1, data2] = await Promise.all([
2091
+ this.getComponent(entityId, component1Type),
2092
+ this.getComponent(entityId, component2Type)
2093
+ ]);
2094
+ if (data1 && data2) {
2095
+ results.push({ entityId, data1, data2 });
2096
+ }
2097
+ }
2098
+ return results;
2099
+ } catch (_error) {
2100
+ return [];
2101
+ }
2102
+ }
2103
+ /**
2104
+ * Get complete entity state (all component data)
2105
+ */
2106
+ async getEntityState(entityId) {
2107
+ try {
2108
+ const componentTypes = await this.getComponents(entityId);
2109
+ if (componentTypes.length === 0) return null;
2110
+ const components = {};
2111
+ for (const componentType of componentTypes) {
2112
+ const componentData = await this.getComponent(entityId, componentType);
2113
+ if (componentData) {
2114
+ components[componentType] = componentData;
2115
+ }
2116
+ }
2117
+ return { entityId, components };
2118
+ } catch (_error) {
2119
+ return null;
2120
+ }
2121
+ }
2122
+ // ============ Statistics and Analysis ============
2123
+ /**
2124
+ * Get component statistics
2125
+ */
2126
+ async getComponentStats() {
2127
+ try {
2128
+ const stats = {};
2129
+ const componentTypes = await this.getAvailableComponents();
2130
+ await Promise.all(
2131
+ componentTypes.map(async (componentType) => {
2132
+ try {
2133
+ const entities = await this.queryWith(componentType);
2134
+ stats[componentType] = entities.totalCount;
2135
+ } catch (_error) {
2136
+ stats[componentType] = 0;
2137
+ }
2138
+ })
2139
+ );
2140
+ return stats;
2141
+ } catch (_error) {
2142
+ return {};
2143
+ }
2144
+ }
2145
+ /**
2146
+ * Find orphan entities (entities with only one component)
2147
+ */
2148
+ async findOrphanEntities() {
2149
+ try {
2150
+ const allEntities = await this.getAllEntities();
2151
+ const orphanEntities = [];
2152
+ for (const entityId of allEntities) {
2153
+ const components = await this.getComponents(entityId);
2154
+ if (components.length === 1) {
2155
+ orphanEntities.push(entityId);
2156
+ }
2157
+ }
2158
+ return orphanEntities;
2159
+ } catch (_error) {
2160
+ return [];
2161
+ }
2162
+ }
2163
+ // ============ Resource Management ============
2164
+ /**
2165
+ * Unsubscribe all subscriptions
2166
+ */
2167
+ unsubscribeAll() {
2168
+ this.subscriptionSystem.unsubscribeAll();
2169
+ }
2170
+ /**
2171
+ * Clear all caches
2172
+ */
2173
+ clearCache() {
2174
+ this.querySystem.dispose();
2175
+ }
2176
+ /**
2177
+ * Dispose resources
2178
+ */
2179
+ dispose() {
2180
+ this.querySystem.dispose();
2181
+ this.subscriptionSystem.dispose();
2182
+ }
2183
+ // ============ Get Underlying Clients ============
2184
+ /**
2185
+ * Get GraphQL client (for advanced operations)
2186
+ */
2187
+ getGraphQLClient() {
2188
+ return this.graphqlClient;
2189
+ }
2190
+ /**
2191
+ * Get query system (for advanced query operations)
2192
+ */
2193
+ getQuerySystem() {
2194
+ return this.querySystem;
2195
+ }
2196
+ /**
2197
+ * Get subscription system (for advanced subscription operations)
2198
+ */
2199
+ getSubscriptionSystem() {
2200
+ return this.subscriptionSystem;
2201
+ }
2202
+ /**
2203
+ * Get ECS world configuration
2204
+ */
2205
+ getConfig() {
2206
+ return { ...this.config };
2207
+ }
2208
+ /**
2209
+ * Update ECS World configuration dynamically
2210
+ * @param config - Partial configuration to update (same type as constructor)
2211
+ */
2212
+ updateConfig(config) {
2213
+ if (config.dubheMetadata !== void 0) {
2214
+ this.dubheMetadata = config.dubheMetadata;
2215
+ this.config.dubheMetadata = config.dubheMetadata;
2216
+ this.componentDiscoverer = new ComponentDiscoverer(this.graphqlClient, this.dubheMetadata);
2217
+ this.resourceDiscoverer = new ResourceDiscoverer(this.graphqlClient, this.dubheMetadata);
2218
+ this.querySystem.setComponentDiscoverer(this.componentDiscoverer);
2219
+ this.subscriptionSystem.setComponentDiscoverer(this.componentDiscoverer);
2220
+ this.initializeWithConfig();
2221
+ }
2222
+ if (config.queryConfig) {
2223
+ this.config.queryConfig = {
2224
+ ...this.config.queryConfig,
2225
+ ...config.queryConfig
2226
+ };
2227
+ if (config.queryConfig.defaultCacheTimeout !== void 0) {
2228
+ this.querySystem.cacheTimeout = config.queryConfig.defaultCacheTimeout;
2229
+ }
2230
+ }
2231
+ if (config.subscriptionConfig) {
2232
+ this.config.subscriptionConfig = {
2233
+ ...this.config.subscriptionConfig,
2234
+ ...config.subscriptionConfig
2235
+ };
2236
+ }
2237
+ }
2238
+ /**
2239
+ * Get dubhe metadata info (JSON format)
2240
+ */
2241
+ getDubheMetadata() {
2242
+ return this.dubheMetadata;
2243
+ }
2244
+ // ============ Resource Queries ============
2245
+ /**
2246
+ * Query resource by primary keys
2247
+ */
2248
+ async getResource(resourceType, keyValues, options) {
2249
+ try {
2250
+ const resourceMetadata = this.resourceDiscoverer.getResourceMetadata(resourceType);
2251
+ if (!resourceMetadata) {
2252
+ return null;
2253
+ }
2254
+ keyValues = keyValues || {};
2255
+ const whereConditions = {};
2256
+ for (const [key, value] of Object.entries(keyValues)) {
2257
+ whereConditions[key] = { equalTo: value };
2258
+ }
2259
+ const paginationParams = {
2260
+ first: options?.first ?? options?.limit ?? 1,
2261
+ last: options?.last,
2262
+ after: options?.after,
2263
+ before: options?.before
2264
+ };
2265
+ const result = await this.graphqlClient.getAllTables(resourceType, {
2266
+ ...paginationParams,
2267
+ filter: whereConditions,
2268
+ fields: options?.fields || resourceMetadata.fields.map((f) => f.name),
2269
+ orderBy: options?.orderBy
2270
+ });
2271
+ const record = result.edges[0]?.node;
2272
+ return record ? record : null;
2273
+ } catch (_error) {
2274
+ return null;
2275
+ }
2276
+ }
2277
+ /**
2278
+ * Query multiple resources with complete pagination info
2279
+ */
2280
+ async getResources(resourceType, options) {
2281
+ try {
2282
+ const resourceMetadata = this.resourceDiscoverer.getResourceMetadata(resourceType);
2283
+ if (!resourceMetadata) {
2284
+ return {
2285
+ entityIds: [],
2286
+ // Resources don't have entityIds, but include for compatibility
2287
+ items: [],
2288
+ pageInfo: {
2289
+ hasNextPage: false,
2290
+ hasPreviousPage: false
2291
+ },
2292
+ totalCount: 0
2293
+ };
2294
+ }
2295
+ const whereConditions = {};
2296
+ if (options?.filters) {
2297
+ for (const [key, value] of Object.entries(options.filters)) {
2298
+ if (typeof value === "object" && value !== null) {
2299
+ whereConditions[key] = value;
2300
+ } else {
2301
+ whereConditions[key] = { equalTo: value };
2302
+ }
2303
+ }
2304
+ }
2305
+ const paginationParams = {
2306
+ first: options?.first ?? options?.limit,
2307
+ last: options?.last,
2308
+ after: options?.after,
2309
+ before: options?.before
2310
+ };
2311
+ const result = await this.graphqlClient.getAllTables(resourceType, {
2312
+ ...paginationParams,
2313
+ filter: Object.keys(whereConditions).length > 0 ? whereConditions : void 0,
2314
+ fields: options?.fields || resourceMetadata.fields.map((f) => f.name),
2315
+ orderBy: options?.orderBy
2316
+ });
2317
+ const items = result.edges.map((edge) => edge.node);
2318
+ return {
2319
+ entityIds: [],
2320
+ // Resources don't have entityIds, but include for compatibility
2321
+ items,
2322
+ pageInfo: {
2323
+ hasNextPage: result.pageInfo.hasNextPage,
2324
+ hasPreviousPage: result.pageInfo.hasPreviousPage,
2325
+ startCursor: result.pageInfo.startCursor,
2326
+ endCursor: result.pageInfo.endCursor
2327
+ },
2328
+ totalCount: result.totalCount || 0
2329
+ };
2330
+ } catch (_error) {
2331
+ return {
2332
+ entityIds: [],
2333
+ // Resources don't have entityIds, but include for compatibility
2334
+ items: [],
2335
+ pageInfo: {
2336
+ hasNextPage: false,
2337
+ hasPreviousPage: false
2338
+ },
2339
+ totalCount: 0
2340
+ };
2341
+ }
2342
+ }
2343
+ /**
2344
+ * Check if a resource exists
2345
+ */
2346
+ async hasResource(resourceType, keyValues) {
2347
+ const resource = await this.getResource(resourceType, keyValues);
2348
+ return resource !== null;
2349
+ }
2350
+ /**
2351
+ * Get resource count
2352
+ */
2353
+ async getResourceCount(resourceType) {
2354
+ try {
2355
+ const result = await this.graphqlClient.getAllTables(resourceType, {
2356
+ first: 1
2357
+ // Only need count, not actual data
2358
+ });
2359
+ return result.totalCount || 0;
2360
+ } catch (_error) {
2361
+ return 0;
2362
+ }
2363
+ }
2364
+ /**
2365
+ * Subscribe to resource changes
2366
+ */
2367
+ subscribeToResourceChanges(resourceType, options) {
2368
+ const resourceMetadata = this.resourceDiscoverer.getResourceMetadata(resourceType);
2369
+ if (!resourceMetadata) {
2370
+ throw new Error(
2371
+ `Unknown resource type: ${resourceType}. Available resources: [${this.getAvailableResources().join(
2372
+ ", "
2373
+ )}]`
2374
+ );
2375
+ }
2376
+ const subscriptionFields = options?.fields || resourceMetadata.fields.map((f) => f.name);
2377
+ return this.graphqlClient.subscribeToTableChanges(resourceType, {
2378
+ ...options,
2379
+ fields: subscriptionFields
2380
+ });
2381
+ }
2382
+ };
2383
+ function createECSWorld(graphqlClient, config) {
2384
+ return new DubheECSWorld(graphqlClient, config);
2385
+ }
2386
+ // Annotate the CommonJS export names for ESM import in node:
2387
+ 0 && (module.exports = {
2388
+ ComponentDiscoverer,
2389
+ DubheECSWorld,
2390
+ ECSQuery,
2391
+ ECSSubscription,
2392
+ ResourceDiscoverer,
2393
+ calculateDelta,
2394
+ createCacheKey,
2395
+ createECSWorld,
2396
+ createTimestamp,
2397
+ debounce,
2398
+ deepEqual,
2399
+ extractEntityIds,
2400
+ extractIntersectionFromBatchResult,
2401
+ extractPagedQueryResult,
2402
+ extractUnionFromBatchResult,
2403
+ findEntityIntersection,
2404
+ findEntityUnion,
2405
+ formatError,
2406
+ isValidComponentType,
2407
+ isValidEntityId,
2408
+ limitArray,
2409
+ normalizeComponentType,
2410
+ paginateArray,
2411
+ safeJsonParse
2412
+ });
2413
+ //# sourceMappingURL=index.js.map