@0xobelisk/ecs 1.2.0-pre.98 → 2.0.0
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/LICENSE +92 -0
- package/dist/index.js +78 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -8
- package/dist/index.mjs.map +1 -1
- package/dist/query.d.ts +5 -0
- package/package.json +23 -23
- package/src/query.ts +40 -4
- package/src/subscription.ts +22 -2
- package/src/world.ts +37 -4
package/dist/query.d.ts
CHANGED
|
@@ -72,6 +72,11 @@ export declare class ECSQuery {
|
|
|
72
72
|
* Validate if component type is ECS-compliant
|
|
73
73
|
*/
|
|
74
74
|
private isECSComponent;
|
|
75
|
+
/**
|
|
76
|
+
* Resolve a componentType string to the canonical key stored in componentPrimaryKeys
|
|
77
|
+
* (handles snake_case ↔ camelCase mismatches)
|
|
78
|
+
*/
|
|
79
|
+
private resolveComponentType;
|
|
75
80
|
/**
|
|
76
81
|
* Build entity query condition (using cached primary key field name)
|
|
77
82
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xobelisk/ecs",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Tookit for interacting with dubhe ecs client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sui",
|
|
@@ -31,27 +31,7 @@
|
|
|
31
31
|
"dist",
|
|
32
32
|
"src"
|
|
33
33
|
],
|
|
34
|
-
"scripts": {
|
|
35
|
-
"build": "npm run build:types && npm run build:tsup",
|
|
36
|
-
"build:tsup": "tsup ./src/index.ts --format esm,cjs --sourcemap",
|
|
37
|
-
"build:types": "tsc --build",
|
|
38
|
-
"clean": "rm -rf tsconfig.tsbuildinfo ./dist",
|
|
39
|
-
"commit": "commit",
|
|
40
|
-
"doc": "typedoc --out docs src/index.ts",
|
|
41
|
-
"format": "prettier --write .",
|
|
42
|
-
"format:check": "prettier --check .",
|
|
43
|
-
"format:fix": "prettier --write '**/*.{ts,json,md}'",
|
|
44
|
-
"lint": "eslint . --ext .ts",
|
|
45
|
-
"test": "pnpm test:typecheck && pnpm test:unit",
|
|
46
|
-
"test:typecheck": "tsc -p ./test",
|
|
47
|
-
"type-check": "tsc --noEmit",
|
|
48
|
-
"validate": "pnpm format:check && pnpm type-check",
|
|
49
|
-
"watch": "pnpm run clean & pnpm run watch:types & pnpm run watch:tsup",
|
|
50
|
-
"watch:tsup": "tsup ./src/index.ts --format esm,cjs --clean --splitting --watch",
|
|
51
|
-
"watch:types": "tsc --watch"
|
|
52
|
-
},
|
|
53
34
|
"dependencies": {
|
|
54
|
-
"@0xobelisk/graphql-client": "workspace:*",
|
|
55
35
|
"@apollo/client": "^3.13.8",
|
|
56
36
|
"@graphql-typed-document-node/core": "^3.2.0",
|
|
57
37
|
"@types/pluralize": "^0.0.33",
|
|
@@ -64,7 +44,8 @@
|
|
|
64
44
|
"tmp": "^0.2.1",
|
|
65
45
|
"tweetnacl": "^1.0.3",
|
|
66
46
|
"valibot": "^1.2.0",
|
|
67
|
-
"ws": "^8.18.0"
|
|
47
|
+
"ws": "^8.18.0",
|
|
48
|
+
"@0xobelisk/graphql-client": "2.0.0"
|
|
68
49
|
},
|
|
69
50
|
"devDependencies": {
|
|
70
51
|
"@commitlint/cli": "^18.0.0",
|
|
@@ -95,5 +76,24 @@
|
|
|
95
76
|
},
|
|
96
77
|
"publishConfig": {
|
|
97
78
|
"access": "public"
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"build": "npm run build:types && npm run build:tsup",
|
|
82
|
+
"build:tsup": "tsup ./src/index.ts --format esm,cjs --sourcemap",
|
|
83
|
+
"build:types": "tsc --build",
|
|
84
|
+
"clean": "rm -rf tsconfig.tsbuildinfo ./dist",
|
|
85
|
+
"commit": "commit",
|
|
86
|
+
"doc": "typedoc --out docs src/index.ts",
|
|
87
|
+
"format": "prettier --write .",
|
|
88
|
+
"format:check": "prettier --check .",
|
|
89
|
+
"format:fix": "prettier --write '**/*.{ts,json,md}'",
|
|
90
|
+
"lint": "eslint . --ext .ts",
|
|
91
|
+
"test": "pnpm test:typecheck && pnpm test:unit",
|
|
92
|
+
"test:typecheck": "tsc -p ./test",
|
|
93
|
+
"type-check": "tsc --noEmit",
|
|
94
|
+
"validate": "pnpm format:check && pnpm type-check",
|
|
95
|
+
"watch": "pnpm run clean & pnpm run watch:types & pnpm run watch:tsup",
|
|
96
|
+
"watch:tsup": "tsup ./src/index.ts --format esm,cjs --clean --splitting --watch",
|
|
97
|
+
"watch:types": "tsc --watch"
|
|
98
98
|
}
|
|
99
|
-
}
|
|
99
|
+
}
|
package/src/query.ts
CHANGED
|
@@ -56,7 +56,20 @@ export class ECSQuery {
|
|
|
56
56
|
* Get component's primary key field name (quickly retrieve from cache)
|
|
57
57
|
*/
|
|
58
58
|
getComponentPrimaryKeyField(componentType: ComponentType): string {
|
|
59
|
-
|
|
59
|
+
if (this.componentPrimaryKeys.has(componentType)) {
|
|
60
|
+
return this.componentPrimaryKeys.get(componentType)!;
|
|
61
|
+
}
|
|
62
|
+
// Fallback: try camelCase → snake_case
|
|
63
|
+
const snake = componentType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
64
|
+
if (snake !== componentType && this.componentPrimaryKeys.has(snake)) {
|
|
65
|
+
return this.componentPrimaryKeys.get(snake)!;
|
|
66
|
+
}
|
|
67
|
+
// Fallback: try snake_case → camelCase
|
|
68
|
+
const camel = componentType.replace(/_([a-z])/g, (_, l) => l.toUpperCase());
|
|
69
|
+
if (camel !== componentType && this.componentPrimaryKeys.has(camel)) {
|
|
70
|
+
return this.componentPrimaryKeys.get(camel)!;
|
|
71
|
+
}
|
|
72
|
+
return 'entityId';
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
/**
|
|
@@ -281,6 +294,9 @@ export class ECSQuery {
|
|
|
281
294
|
try {
|
|
282
295
|
const condition = this.buildEntityCondition(componentType, entityId);
|
|
283
296
|
const component = await this.graphqlClient.getTableByCondition(componentType, condition);
|
|
297
|
+
// Soft-delete: the indexer marks deleted records with isDeleted=true but keeps the row.
|
|
298
|
+
// Treat any deleted record as if it doesn't exist.
|
|
299
|
+
if (component && (component as any).isDeleted === true) return null;
|
|
284
300
|
return component as T;
|
|
285
301
|
} catch (_error) {
|
|
286
302
|
return null;
|
|
@@ -317,7 +333,27 @@ export class ECSQuery {
|
|
|
317
333
|
* Validate if component type is ECS-compliant
|
|
318
334
|
*/
|
|
319
335
|
private isECSComponent(componentType: ComponentType): boolean {
|
|
320
|
-
|
|
336
|
+
if (this.availableComponents.includes(componentType)) return true;
|
|
337
|
+
// Fallback: try snake_case → camelCase conversion
|
|
338
|
+
const camel = componentType.replace(/_([a-z])/g, (_, l) => l.toUpperCase());
|
|
339
|
+
if (camel !== componentType && this.availableComponents.includes(camel)) return true;
|
|
340
|
+
// Fallback: try camelCase → snake_case conversion
|
|
341
|
+
const snake = componentType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
342
|
+
if (snake !== componentType && this.availableComponents.includes(snake)) return true;
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Resolve a componentType string to the canonical key stored in componentPrimaryKeys
|
|
348
|
+
* (handles snake_case ↔ camelCase mismatches)
|
|
349
|
+
*/
|
|
350
|
+
private resolveComponentType(componentType: ComponentType): ComponentType {
|
|
351
|
+
if (this.componentPrimaryKeys.has(componentType)) return componentType;
|
|
352
|
+
const snake = componentType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
353
|
+
if (snake !== componentType && this.componentPrimaryKeys.has(snake)) return snake;
|
|
354
|
+
const camel = componentType.replace(/_([a-z])/g, (_, l) => l.toUpperCase());
|
|
355
|
+
if (camel !== componentType && this.componentPrimaryKeys.has(camel)) return camel;
|
|
356
|
+
return componentType;
|
|
321
357
|
}
|
|
322
358
|
|
|
323
359
|
/**
|
|
@@ -327,8 +363,8 @@ export class ECSQuery {
|
|
|
327
363
|
componentType: ComponentType,
|
|
328
364
|
entityId: EntityId
|
|
329
365
|
): Record<string, any> {
|
|
330
|
-
|
|
331
|
-
const primaryKeyField = this.componentPrimaryKeys.get(
|
|
366
|
+
const resolved = this.resolveComponentType(componentType);
|
|
367
|
+
const primaryKeyField = this.componentPrimaryKeys.get(resolved);
|
|
332
368
|
if (primaryKeyField) {
|
|
333
369
|
return { [primaryKeyField]: entityId };
|
|
334
370
|
} else {
|
package/src/subscription.ts
CHANGED
|
@@ -79,7 +79,20 @@ export class ECSSubscription {
|
|
|
79
79
|
* Get component's primary key field name (quickly retrieve from cache, consistent with query.ts)
|
|
80
80
|
*/
|
|
81
81
|
getComponentPrimaryKeyField(componentType: ComponentType): string {
|
|
82
|
-
|
|
82
|
+
if (this.componentPrimaryKeys.has(componentType)) {
|
|
83
|
+
return this.componentPrimaryKeys.get(componentType)!;
|
|
84
|
+
}
|
|
85
|
+
// Fallback: try camelCase → snake_case
|
|
86
|
+
const snake = componentType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
87
|
+
if (snake !== componentType && this.componentPrimaryKeys.has(snake)) {
|
|
88
|
+
return this.componentPrimaryKeys.get(snake)!;
|
|
89
|
+
}
|
|
90
|
+
// Fallback: try snake_case → camelCase
|
|
91
|
+
const camel = componentType.replace(/_([a-z])/g, (_, l) => l.toUpperCase());
|
|
92
|
+
if (camel !== componentType && this.componentPrimaryKeys.has(camel)) {
|
|
93
|
+
return this.componentPrimaryKeys.get(camel)!;
|
|
94
|
+
}
|
|
95
|
+
return 'entityId';
|
|
83
96
|
}
|
|
84
97
|
|
|
85
98
|
/**
|
|
@@ -93,7 +106,14 @@ export class ECSSubscription {
|
|
|
93
106
|
* Validate if component type is ECS-compliant
|
|
94
107
|
*/
|
|
95
108
|
private isECSComponent(componentType: ComponentType): boolean {
|
|
96
|
-
|
|
109
|
+
if (this.availableComponents.includes(componentType)) return true;
|
|
110
|
+
// Fallback: try snake_case → camelCase conversion
|
|
111
|
+
const camel = componentType.replace(/_([a-z])/g, (_, l) => l.toUpperCase());
|
|
112
|
+
if (camel !== componentType && this.availableComponents.includes(camel)) return true;
|
|
113
|
+
// Fallback: try camelCase → snake_case conversion
|
|
114
|
+
const snake = componentType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
115
|
+
if (snake !== componentType && this.availableComponents.includes(snake)) return true;
|
|
116
|
+
return false;
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
/**
|
package/src/world.ts
CHANGED
|
@@ -195,7 +195,22 @@ export class ComponentDiscoverer {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
getComponentMetadata(componentType: ComponentType): ComponentMetadata | null {
|
|
198
|
-
|
|
198
|
+
// Try exact match first
|
|
199
|
+
const direct = this.componentMetadataMap.get(componentType);
|
|
200
|
+
if (direct) return direct;
|
|
201
|
+
// Fallback: try camelCase → snake_case
|
|
202
|
+
const snake = componentType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
203
|
+
if (snake !== componentType) {
|
|
204
|
+
const bySnake = this.componentMetadataMap.get(snake);
|
|
205
|
+
if (bySnake) return bySnake;
|
|
206
|
+
}
|
|
207
|
+
// Fallback: try snake_case → camelCase
|
|
208
|
+
const camel = componentType.replace(/_([a-z])/g, (_, l) => l.toUpperCase());
|
|
209
|
+
if (camel !== componentType) {
|
|
210
|
+
const byCamel = this.componentMetadataMap.get(camel);
|
|
211
|
+
if (byCamel) return byCamel;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
199
214
|
}
|
|
200
215
|
|
|
201
216
|
private snakeToCamel(str: string): string {
|
|
@@ -388,7 +403,23 @@ export class ResourceDiscoverer {
|
|
|
388
403
|
}
|
|
389
404
|
|
|
390
405
|
getResourceMetadata(resourceType: string): ResourceMetadata | null {
|
|
391
|
-
|
|
406
|
+
// Try exact match first (original snake_case key from dubhe.config.json)
|
|
407
|
+
const direct = this.resourceMetadataMap.get(resourceType);
|
|
408
|
+
if (direct) return direct;
|
|
409
|
+
|
|
410
|
+
// Fallback: try camelCase version so callers can use 'farmPlot' instead of 'farm_plot'
|
|
411
|
+
const camel = this.snakeToCamel(resourceType);
|
|
412
|
+
if (camel !== resourceType) {
|
|
413
|
+
return this.resourceMetadataMap.get(camel) || null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Fallback: try converting camelCase → snake_case for reverse lookup
|
|
417
|
+
const snake = resourceType.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
418
|
+
if (snake !== resourceType) {
|
|
419
|
+
return this.resourceMetadataMap.get(snake) || null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return null;
|
|
392
423
|
}
|
|
393
424
|
|
|
394
425
|
private snakeToCamel(str: string): string {
|
|
@@ -1164,8 +1195,10 @@ export class DubheECSWorld {
|
|
|
1164
1195
|
};
|
|
1165
1196
|
}
|
|
1166
1197
|
|
|
1167
|
-
// Build where condition
|
|
1168
|
-
const whereConditions: Record<string, any> = {
|
|
1198
|
+
// Build where condition — always exclude soft-deleted rows
|
|
1199
|
+
const whereConditions: Record<string, any> = {
|
|
1200
|
+
isDeleted: { equalTo: false }
|
|
1201
|
+
};
|
|
1169
1202
|
if (options?.filters) {
|
|
1170
1203
|
for (const [key, value] of Object.entries(options.filters)) {
|
|
1171
1204
|
if (typeof value === 'object' && value !== null) {
|