diff --git a/packages/bindx-react/src/jsx/collectorProxy.ts b/packages/bindx-react/src/jsx/collectorProxy.ts index 3fcac14..59486ee 100644 --- a/packages/bindx-react/src/jsx/collectorProxy.ts +++ b/packages/bindx-react/src/jsx/collectorProxy.ts @@ -52,6 +52,7 @@ export function createCollectorProxy( $isDirty: false, $isPersisting: false, $persistedId: null, + $resolvedId: '__collector__', $isNew: true, __entityType: undefined as unknown as T, __entityName: '__collector__', @@ -235,6 +236,7 @@ function createCollectorFieldRef( $isPersisting: false, $isNew: false, $persistedId: null, + $resolvedId: placeholderId, __entityName: targetEntityName ?? '', __schema: {} as Record, diff --git a/packages/bindx/src/handles/EntityHandle.ts b/packages/bindx/src/handles/EntityHandle.ts index d5db13a..ce329c4 100644 --- a/packages/bindx/src/handles/EntityHandle.ts +++ b/packages/bindx/src/handles/EntityHandle.ts @@ -126,12 +126,22 @@ export class EntityHandle extends Enti } /** - * Gets the entity ID. + * Gets the entity ID (stable — always the original ID used to create the handle). + * For new entities this is the temp ID; for loaded entities it's the server ID. + * Use `$persistedId` to get the server-assigned ID after persist, or `$resolvedId` for best-known ID. */ get id(): string { return this.entityId } + /** + * Gets the best-known ID: the persisted (server-assigned) ID if available, + * otherwise the original ID (which may be a temp ID for new entities). + */ + get resolvedId(): string { + return this.store.getPersistedId(this.entityType, this.entityId) ?? this.entityId + } + /** * Gets the current entity snapshot. */ diff --git a/packages/bindx/src/handles/HasOneHandle.ts b/packages/bindx/src/handles/HasOneHandle.ts index 2afbce6..94616c3 100644 --- a/packages/bindx/src/handles/HasOneHandle.ts +++ b/packages/bindx/src/handles/HasOneHandle.ts @@ -524,6 +524,9 @@ export class HasOneHandle /** Server-assigned ID after persistence - delegates to entityRaw */ get persistedId(): string | null { return this.entityRaw.persistedId } + /** Best-known ID: persisted if available, otherwise original - delegates to entityRaw */ + get resolvedId(): string { return this.entityRaw.resolvedId } + /** Type brand for entity name */ get __entityName(): string { return this.targetType } diff --git a/packages/bindx/src/handles/PlaceholderHandle.ts b/packages/bindx/src/handles/PlaceholderHandle.ts index 368e5bb..1b8fd6d 100644 --- a/packages/bindx/src/handles/PlaceholderHandle.ts +++ b/packages/bindx/src/handles/PlaceholderHandle.ts @@ -128,6 +128,13 @@ export class PlaceholderHandle { expect(handle.id as string).toBe('a-1') }) + test('should return stable temp ID even after persisted ID mapping', () => { + const tempId = store.createEntity('Article', { title: 'New' }) + store.mapTempIdToPersistedId('Article', tempId, 'real-id-456') + + const handle = EntityHandle.create(tempId, 'Article', store, dispatcher, schema) + + // id is always the stable original ID (temp ID in this case) + expect(handle.id as string).toBe(tempId) + }) + + test('should return persisted ID via $resolvedId after temp ID mapping', () => { + const tempId = store.createEntity('Article', { title: 'New' }) + store.mapTempIdToPersistedId('Article', tempId, 'real-id-456') + + const handle = EntityHandle.create(tempId, 'Article', store, dispatcher, schema) + + expect(handle.$resolvedId as string).toBe('real-id-456') + }) + + test('should return original ID via $resolvedId when no persisted mapping', () => { + const handle = createEntityHandle('a-1') + + expect(handle.$resolvedId as string).toBe('a-1') + }) + test('should return entity type', () => { const handle = createEntityHandleRaw('a-1') expect(handle.__entityName).toBe('Article')