@0xobelisk/ecs 1.2.0-pre.24

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