@0xobelisk/ecs 1.2.0-pre.44 → 1.2.0-pre.46
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.d.ts +2 -2
- package/dist/index.js +178 -48
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +177 -48
- package/dist/index.mjs.map +1 -1
- package/dist/query.d.ts +12 -2
- package/dist/types.d.ts +16 -0
- package/dist/utils.d.ts +12 -1
- package/dist/world.d.ts +14 -10
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/query.ts +129 -32
- package/src/types.ts +31 -3
- package/src/utils.ts +36 -1
- package/src/world.ts +87 -28
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export type { EntityId, ComponentType, Unsubscribe, ComponentCallback, ComponentChangeCallback, EntityCallback, QueryChange, QueryChangeCallback, QueryWatcher, PagedResult, BatchQueryResult, ComponentChangeEvent, EntityChangeEvent, QueryOptions, SubscriptionOptions, ComponentMetadata, ComponentField, ComponentDiscoveryResult, ResourceMetadata, ResourceDiscoveryResult, ECSWorldConfig, DubheMetadata, } from './types';
|
|
1
|
+
export type { EntityId, ComponentType, Unsubscribe, ComponentCallback, ComponentChangeCallback, EntityCallback, QueryChange, QueryChangeCallback, QueryWatcher, PagedResult, PagedQueryResult, BatchQueryResult, ComponentChangeEvent, EntityChangeEvent, QueryOptions, SubscriptionOptions, ComponentMetadata, ComponentField, ComponentDiscoveryResult, ResourceMetadata, ResourceDiscoveryResult, ECSWorldConfig, DubheMetadata, } from './types';
|
|
2
2
|
export { DubheECSWorld } from './world';
|
|
3
3
|
export { ComponentDiscoverer, ResourceDiscoverer } from './world';
|
|
4
4
|
export { ECSQuery } from './query';
|
|
5
5
|
export { ECSSubscription } from './subscription';
|
|
6
6
|
export { createECSWorld } from './world';
|
|
7
|
-
export { extractEntityIds, calculateDelta, findEntityIntersection, findEntityUnion, extractIntersectionFromBatchResult, extractUnionFromBatchResult, debounce, normalizeComponentType, createCacheKey, isValidEntityId, isValidComponentType, deepEqual, safeJsonParse, formatError, createTimestamp, limitArray, paginateArray, } from './utils';
|
|
7
|
+
export { extractEntityIds, extractPagedQueryResult, calculateDelta, findEntityIntersection, findEntityUnion, extractIntersectionFromBatchResult, extractUnionFromBatchResult, debounce, normalizeComponentType, createCacheKey, isValidEntityId, isValidComponentType, deepEqual, safeJsonParse, formatError, createTimestamp, limitArray, paginateArray, } from './utils';
|
|
8
8
|
export { DubheECSWorld as default } from './world';
|
package/dist/index.js
CHANGED
|
@@ -44,6 +44,7 @@ __export(src_exports, {
|
|
|
44
44
|
default: () => DubheECSWorld,
|
|
45
45
|
extractEntityIds: () => extractEntityIds,
|
|
46
46
|
extractIntersectionFromBatchResult: () => extractIntersectionFromBatchResult,
|
|
47
|
+
extractPagedQueryResult: () => extractPagedQueryResult,
|
|
47
48
|
extractUnionFromBatchResult: () => extractUnionFromBatchResult,
|
|
48
49
|
findEntityIntersection: () => findEntityIntersection,
|
|
49
50
|
findEntityUnion: () => findEntityUnion,
|
|
@@ -75,6 +76,21 @@ function extractEntityIds(connection, options) {
|
|
|
75
76
|
}
|
|
76
77
|
}).filter(Boolean);
|
|
77
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
|
+
}
|
|
78
94
|
function calculateDelta(oldResults, newResults) {
|
|
79
95
|
const oldSet = new Set(oldResults);
|
|
80
96
|
const newSet = new Set(newResults);
|
|
@@ -243,6 +259,29 @@ var ECSQuery = class {
|
|
|
243
259
|
setComponentDiscoverer(discoverer) {
|
|
244
260
|
this.componentDiscoverer = discoverer;
|
|
245
261
|
}
|
|
262
|
+
buildPaginationParams(options) {
|
|
263
|
+
const params = {};
|
|
264
|
+
if (options?.first !== void 0) {
|
|
265
|
+
params.first = options.first;
|
|
266
|
+
} else if (options?.limit !== void 0) {
|
|
267
|
+
params.first = options.limit;
|
|
268
|
+
if (options?.offset !== void 0) {
|
|
269
|
+
console.warn(
|
|
270
|
+
"ECS Query: offset parameter is not supported with GraphQL cursor-based pagination. Use after/before instead."
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (options?.last !== void 0) {
|
|
275
|
+
params.last = options.last;
|
|
276
|
+
}
|
|
277
|
+
if (options?.after !== void 0) {
|
|
278
|
+
params.after = options.after;
|
|
279
|
+
}
|
|
280
|
+
if (options?.before !== void 0) {
|
|
281
|
+
params.before = options.before;
|
|
282
|
+
}
|
|
283
|
+
return params;
|
|
284
|
+
}
|
|
246
285
|
/**
|
|
247
286
|
* Get component field information
|
|
248
287
|
*/
|
|
@@ -453,47 +492,60 @@ var ECSQuery = class {
|
|
|
453
492
|
return validComponents;
|
|
454
493
|
}
|
|
455
494
|
/**
|
|
456
|
-
* Query all entities that have a specific component
|
|
495
|
+
* Query all entities that have a specific component with full pagination info
|
|
457
496
|
*/
|
|
458
|
-
async
|
|
497
|
+
async queryWithFullPagination(componentType, options) {
|
|
498
|
+
const emptyResult = {
|
|
499
|
+
entityIds: [],
|
|
500
|
+
items: [],
|
|
501
|
+
pageInfo: {
|
|
502
|
+
hasNextPage: false,
|
|
503
|
+
hasPreviousPage: false
|
|
504
|
+
},
|
|
505
|
+
totalCount: 0
|
|
506
|
+
};
|
|
459
507
|
if (!isValidComponentType(componentType))
|
|
460
|
-
return
|
|
508
|
+
return emptyResult;
|
|
461
509
|
if (!this.isECSComponent(componentType)) {
|
|
462
|
-
return
|
|
510
|
+
return emptyResult;
|
|
463
511
|
}
|
|
464
|
-
const cacheKey = createCacheKey("queryWith", [componentType], options);
|
|
465
|
-
const cached = this.getCachedResult(cacheKey);
|
|
466
|
-
if (cached && options?.cache !== false)
|
|
467
|
-
return cached;
|
|
468
512
|
try {
|
|
469
513
|
const queryFields = await this.getQueryFields(
|
|
470
514
|
componentType,
|
|
471
515
|
options?.fields
|
|
472
516
|
);
|
|
473
517
|
const primaryKeys = await this.getComponentPrimaryKeys(componentType);
|
|
518
|
+
const paginationParams = this.buildPaginationParams(options);
|
|
474
519
|
const connection = await this.graphqlClient.getAllTables(componentType, {
|
|
475
|
-
|
|
520
|
+
...paginationParams,
|
|
476
521
|
fields: queryFields,
|
|
477
522
|
orderBy: options?.orderBy
|
|
478
523
|
});
|
|
479
|
-
|
|
524
|
+
return extractPagedQueryResult(connection, {
|
|
480
525
|
idFields: options?.idFields || primaryKeys,
|
|
481
526
|
composite: options?.compositeId
|
|
482
527
|
});
|
|
483
|
-
this.setCachedResult(cacheKey, result);
|
|
484
|
-
return result;
|
|
485
528
|
} catch (error) {
|
|
486
|
-
return
|
|
529
|
+
return emptyResult;
|
|
487
530
|
}
|
|
488
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Query all entities that have a specific component
|
|
534
|
+
* Returns complete pagination information including entity IDs, actual data, and page info
|
|
535
|
+
*/
|
|
536
|
+
async queryWith(componentType, options) {
|
|
537
|
+
return this.queryWithFullPagination(componentType, options);
|
|
538
|
+
}
|
|
489
539
|
/**
|
|
490
540
|
* Query entities that have all specified components (intersection)
|
|
491
541
|
*/
|
|
492
542
|
async queryWithAll(componentTypes, options) {
|
|
493
543
|
if (componentTypes.length === 0)
|
|
494
544
|
return [];
|
|
495
|
-
if (componentTypes.length === 1)
|
|
496
|
-
|
|
545
|
+
if (componentTypes.length === 1) {
|
|
546
|
+
const result = await this.queryWith(componentTypes[0], options);
|
|
547
|
+
return result.entityIds;
|
|
548
|
+
}
|
|
497
549
|
const validTypes = this.filterValidECSComponents(componentTypes);
|
|
498
550
|
if (validTypes.length === 0)
|
|
499
551
|
return [];
|
|
@@ -502,6 +554,7 @@ var ECSQuery = class {
|
|
|
502
554
|
if (cached && options?.cache !== false)
|
|
503
555
|
return cached;
|
|
504
556
|
try {
|
|
557
|
+
const paginationParams = this.buildPaginationParams(options);
|
|
505
558
|
const queries = await Promise.all(
|
|
506
559
|
validTypes.map(async (type) => {
|
|
507
560
|
const queryFields = await this.getQueryFields(type, options?.fields);
|
|
@@ -510,7 +563,7 @@ var ECSQuery = class {
|
|
|
510
563
|
tableName: type,
|
|
511
564
|
params: {
|
|
512
565
|
fields: queryFields,
|
|
513
|
-
|
|
566
|
+
...paginationParams,
|
|
514
567
|
orderBy: options?.orderBy
|
|
515
568
|
}
|
|
516
569
|
};
|
|
@@ -544,8 +597,10 @@ var ECSQuery = class {
|
|
|
544
597
|
async queryWithAny(componentTypes, options) {
|
|
545
598
|
if (componentTypes.length === 0)
|
|
546
599
|
return [];
|
|
547
|
-
if (componentTypes.length === 1)
|
|
548
|
-
|
|
600
|
+
if (componentTypes.length === 1) {
|
|
601
|
+
const result = await this.queryWith(componentTypes[0], options);
|
|
602
|
+
return result.entityIds;
|
|
603
|
+
}
|
|
549
604
|
const validTypes = this.filterValidECSComponents(componentTypes);
|
|
550
605
|
if (validTypes.length === 0)
|
|
551
606
|
return [];
|
|
@@ -554,6 +609,7 @@ var ECSQuery = class {
|
|
|
554
609
|
if (cached && options?.cache !== false)
|
|
555
610
|
return cached;
|
|
556
611
|
try {
|
|
612
|
+
const paginationParams = this.buildPaginationParams(options);
|
|
557
613
|
const queries = await Promise.all(
|
|
558
614
|
validTypes.map(async (type) => {
|
|
559
615
|
const queryFields = await this.getQueryFields(type, options?.fields);
|
|
@@ -562,7 +618,7 @@ var ECSQuery = class {
|
|
|
562
618
|
tableName: type,
|
|
563
619
|
params: {
|
|
564
620
|
fields: queryFields,
|
|
565
|
-
|
|
621
|
+
...paginationParams,
|
|
566
622
|
orderBy: options?.orderBy
|
|
567
623
|
}
|
|
568
624
|
};
|
|
@@ -611,13 +667,22 @@ var ECSQuery = class {
|
|
|
611
667
|
}
|
|
612
668
|
}
|
|
613
669
|
/**
|
|
614
|
-
* Query components based on conditions
|
|
670
|
+
* Query components based on conditions with full pagination info
|
|
615
671
|
*/
|
|
616
|
-
async
|
|
672
|
+
async queryWhereFullPagination(componentType, predicate, options) {
|
|
673
|
+
const emptyResult = {
|
|
674
|
+
entityIds: [],
|
|
675
|
+
items: [],
|
|
676
|
+
pageInfo: {
|
|
677
|
+
hasNextPage: false,
|
|
678
|
+
hasPreviousPage: false
|
|
679
|
+
},
|
|
680
|
+
totalCount: 0
|
|
681
|
+
};
|
|
617
682
|
if (!isValidComponentType(componentType))
|
|
618
|
-
return
|
|
683
|
+
return emptyResult;
|
|
619
684
|
if (!this.isECSComponent(componentType)) {
|
|
620
|
-
return
|
|
685
|
+
return emptyResult;
|
|
621
686
|
}
|
|
622
687
|
try {
|
|
623
688
|
const queryFields = await this.getQueryFields(
|
|
@@ -625,20 +690,32 @@ var ECSQuery = class {
|
|
|
625
690
|
options?.fields
|
|
626
691
|
);
|
|
627
692
|
const primaryKeys = await this.getComponentPrimaryKeys(componentType);
|
|
693
|
+
const paginationParams = this.buildPaginationParams(options);
|
|
628
694
|
const connection = await this.graphqlClient.getAllTables(componentType, {
|
|
629
695
|
filter: predicate,
|
|
630
|
-
|
|
696
|
+
...paginationParams,
|
|
631
697
|
fields: queryFields,
|
|
632
698
|
orderBy: options?.orderBy
|
|
633
699
|
});
|
|
634
|
-
return
|
|
700
|
+
return extractPagedQueryResult(connection, {
|
|
635
701
|
idFields: options?.idFields || primaryKeys,
|
|
636
702
|
composite: options?.compositeId
|
|
637
703
|
});
|
|
638
704
|
} catch (error) {
|
|
639
|
-
return
|
|
705
|
+
return emptyResult;
|
|
640
706
|
}
|
|
641
707
|
}
|
|
708
|
+
/**
|
|
709
|
+
* Query components based on conditions
|
|
710
|
+
*/
|
|
711
|
+
async queryWhere(componentType, predicate, options) {
|
|
712
|
+
const result = await this.queryWhereFullPagination(
|
|
713
|
+
componentType,
|
|
714
|
+
predicate,
|
|
715
|
+
options
|
|
716
|
+
);
|
|
717
|
+
return result.entityIds;
|
|
718
|
+
}
|
|
642
719
|
/**
|
|
643
720
|
* Range query
|
|
644
721
|
*/
|
|
@@ -661,7 +738,7 @@ var ECSQuery = class {
|
|
|
661
738
|
*/
|
|
662
739
|
async queryPaged(componentTypes, page, pageSize) {
|
|
663
740
|
try {
|
|
664
|
-
const allResults = componentTypes.length === 1 ? await this.queryWith(componentTypes[0]) : await this.queryWithAll(componentTypes);
|
|
741
|
+
const allResults = componentTypes.length === 1 ? (await this.queryWith(componentTypes[0])).entityIds : await this.queryWithAll(componentTypes);
|
|
665
742
|
return paginateArray(allResults, page, pageSize);
|
|
666
743
|
} catch (error) {
|
|
667
744
|
return {
|
|
@@ -1989,11 +2066,27 @@ var DubheECSWorld = class {
|
|
|
1989
2066
|
return this.querySystem.queryRange(componentType, field, min, max, options);
|
|
1990
2067
|
}
|
|
1991
2068
|
/**
|
|
1992
|
-
* Paginated query
|
|
2069
|
+
* Paginated query (legacy version using page/pageSize)
|
|
1993
2070
|
*/
|
|
1994
2071
|
async queryPaged(componentTypes, page, pageSize) {
|
|
1995
2072
|
return this.querySystem.queryPaged(componentTypes, page, pageSize);
|
|
1996
2073
|
}
|
|
2074
|
+
/**
|
|
2075
|
+
* Query with complete pagination info using GraphQL cursor-based pagination
|
|
2076
|
+
*/
|
|
2077
|
+
async queryWithPagination(componentType, options) {
|
|
2078
|
+
return this.querySystem.queryWith(componentType, options);
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Query components with conditions and complete pagination info
|
|
2082
|
+
*/
|
|
2083
|
+
async queryWherePagination(componentType, predicate, options) {
|
|
2084
|
+
return this.querySystem.queryWhereFullPagination(
|
|
2085
|
+
componentType,
|
|
2086
|
+
predicate,
|
|
2087
|
+
options
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
1997
2090
|
// ============ Query Builder ============
|
|
1998
2091
|
/**
|
|
1999
2092
|
* Create query builder
|
|
@@ -2057,9 +2150,9 @@ var DubheECSWorld = class {
|
|
|
2057
2150
|
*/
|
|
2058
2151
|
async queryWithComponentData(componentType, options) {
|
|
2059
2152
|
try {
|
|
2060
|
-
const
|
|
2153
|
+
const data = await this.queryWith(componentType, options);
|
|
2061
2154
|
const results = [];
|
|
2062
|
-
for (const entityId of entityIds) {
|
|
2155
|
+
for (const entityId of data.entityIds) {
|
|
2063
2156
|
const componentData = await this.getComponent(
|
|
2064
2157
|
entityId,
|
|
2065
2158
|
componentType
|
|
@@ -2129,7 +2222,7 @@ var DubheECSWorld = class {
|
|
|
2129
2222
|
componentTypes.map(async (componentType) => {
|
|
2130
2223
|
try {
|
|
2131
2224
|
const entities = await this.queryWith(componentType);
|
|
2132
|
-
stats[componentType] = entities.
|
|
2225
|
+
stats[componentType] = entities.totalCount;
|
|
2133
2226
|
} catch (error) {
|
|
2134
2227
|
stats[componentType] = 0;
|
|
2135
2228
|
}
|
|
@@ -2224,11 +2317,17 @@ var DubheECSWorld = class {
|
|
|
2224
2317
|
for (const [key, value] of Object.entries(keyValues)) {
|
|
2225
2318
|
whereConditions[key] = { equalTo: value };
|
|
2226
2319
|
}
|
|
2320
|
+
const paginationParams = {
|
|
2321
|
+
first: options?.first ?? options?.limit ?? 1,
|
|
2322
|
+
last: options?.last,
|
|
2323
|
+
after: options?.after,
|
|
2324
|
+
before: options?.before
|
|
2325
|
+
};
|
|
2227
2326
|
const result = await this.graphqlClient.getAllTables(resourceType, {
|
|
2228
|
-
|
|
2327
|
+
...paginationParams,
|
|
2229
2328
|
filter: whereConditions,
|
|
2230
2329
|
fields: options?.fields || resourceMetadata.fields.map((f) => f.name),
|
|
2231
|
-
|
|
2330
|
+
orderBy: options?.orderBy
|
|
2232
2331
|
});
|
|
2233
2332
|
const record = result.edges[0]?.node;
|
|
2234
2333
|
return record ? record : null;
|
|
@@ -2237,17 +2336,26 @@ var DubheECSWorld = class {
|
|
|
2237
2336
|
}
|
|
2238
2337
|
}
|
|
2239
2338
|
/**
|
|
2240
|
-
* Query multiple resources
|
|
2339
|
+
* Query multiple resources with complete pagination info
|
|
2241
2340
|
*/
|
|
2242
|
-
async getResources(resourceType,
|
|
2341
|
+
async getResources(resourceType, options) {
|
|
2243
2342
|
try {
|
|
2244
2343
|
const resourceMetadata = this.resourceDiscoverer.getResourceMetadata(resourceType);
|
|
2245
2344
|
if (!resourceMetadata) {
|
|
2246
|
-
return
|
|
2345
|
+
return {
|
|
2346
|
+
entityIds: [],
|
|
2347
|
+
// Resources don't have entityIds, but include for compatibility
|
|
2348
|
+
items: [],
|
|
2349
|
+
pageInfo: {
|
|
2350
|
+
hasNextPage: false,
|
|
2351
|
+
hasPreviousPage: false
|
|
2352
|
+
},
|
|
2353
|
+
totalCount: 0
|
|
2354
|
+
};
|
|
2247
2355
|
}
|
|
2248
2356
|
const whereConditions = {};
|
|
2249
|
-
if (filters) {
|
|
2250
|
-
for (const [key, value] of Object.entries(filters)) {
|
|
2357
|
+
if (options?.filters) {
|
|
2358
|
+
for (const [key, value] of Object.entries(options.filters)) {
|
|
2251
2359
|
if (typeof value === "object" && value !== null) {
|
|
2252
2360
|
whereConditions[key] = value;
|
|
2253
2361
|
} else {
|
|
@@ -2255,23 +2363,44 @@ var DubheECSWorld = class {
|
|
|
2255
2363
|
}
|
|
2256
2364
|
}
|
|
2257
2365
|
}
|
|
2366
|
+
const paginationParams = {
|
|
2367
|
+
first: options?.first ?? options?.limit,
|
|
2368
|
+
last: options?.last,
|
|
2369
|
+
after: options?.after,
|
|
2370
|
+
before: options?.before
|
|
2371
|
+
};
|
|
2258
2372
|
const result = await this.graphqlClient.getAllTables(resourceType, {
|
|
2373
|
+
...paginationParams,
|
|
2259
2374
|
filter: Object.keys(whereConditions).length > 0 ? whereConditions : void 0,
|
|
2260
2375
|
fields: options?.fields || resourceMetadata.fields.map((f) => f.name),
|
|
2261
|
-
|
|
2376
|
+
orderBy: options?.orderBy
|
|
2262
2377
|
});
|
|
2263
|
-
const
|
|
2264
|
-
return
|
|
2378
|
+
const items = result.edges.map((edge) => edge.node);
|
|
2379
|
+
return {
|
|
2380
|
+
entityIds: [],
|
|
2381
|
+
// Resources don't have entityIds, but include for compatibility
|
|
2382
|
+
items,
|
|
2383
|
+
pageInfo: {
|
|
2384
|
+
hasNextPage: result.pageInfo.hasNextPage,
|
|
2385
|
+
hasPreviousPage: result.pageInfo.hasPreviousPage,
|
|
2386
|
+
startCursor: result.pageInfo.startCursor,
|
|
2387
|
+
endCursor: result.pageInfo.endCursor
|
|
2388
|
+
},
|
|
2389
|
+
totalCount: result.totalCount || 0
|
|
2390
|
+
};
|
|
2265
2391
|
} catch (error) {
|
|
2266
|
-
return
|
|
2392
|
+
return {
|
|
2393
|
+
entityIds: [],
|
|
2394
|
+
// Resources don't have entityIds, but include for compatibility
|
|
2395
|
+
items: [],
|
|
2396
|
+
pageInfo: {
|
|
2397
|
+
hasNextPage: false,
|
|
2398
|
+
hasPreviousPage: false
|
|
2399
|
+
},
|
|
2400
|
+
totalCount: 0
|
|
2401
|
+
};
|
|
2267
2402
|
}
|
|
2268
2403
|
}
|
|
2269
|
-
/**
|
|
2270
|
-
* Get all resources of a specific type
|
|
2271
|
-
*/
|
|
2272
|
-
async getAllResources(resourceType, options) {
|
|
2273
|
-
return this.getResources(resourceType, void 0, options);
|
|
2274
|
-
}
|
|
2275
2404
|
/**
|
|
2276
2405
|
* Check if a resource exists
|
|
2277
2406
|
*/
|
|
@@ -2332,6 +2461,7 @@ function createECSWorld(graphqlClient, config) {
|
|
|
2332
2461
|
deepEqual,
|
|
2333
2462
|
extractEntityIds,
|
|
2334
2463
|
extractIntersectionFromBatchResult,
|
|
2464
|
+
extractPagedQueryResult,
|
|
2335
2465
|
extractUnionFromBatchResult,
|
|
2336
2466
|
findEntityIntersection,
|
|
2337
2467
|
findEntityUnion,
|