@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/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": "1.2.0-pre.98",
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
- return this.componentPrimaryKeys.get(componentType) || 'entityId';
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
- return this.availableComponents.includes(componentType);
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
- // Get primary key field name from cache
331
- const primaryKeyField = this.componentPrimaryKeys.get(componentType);
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 {
@@ -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
- return this.componentPrimaryKeys.get(componentType) || 'entityId';
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
- return this.availableComponents.includes(componentType);
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
- return this.componentMetadataMap.get(componentType) || null;
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
- return this.resourceMetadataMap.get(resourceType) || null;
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) {