@contember/bindx 0.1.14 → 0.1.16

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.
@@ -11,7 +11,7 @@ import {
11
11
  clearEntityErrors,
12
12
  clearAllErrors as clearAllErrorsAction,
13
13
  } from '../core/actions.js'
14
- import { type FieldRef, type HasOneAccessor, type HasManyRef, type SelectedEntityFields, type Unsubscribe, type EntityAccessor } from './types.js'
14
+ import { type FieldAccessor, type HasOneAccessor, type HasManyAccessor, type EntityFieldsAccessor, type Unsubscribe, type EntityAccessor } from './types.js'
15
15
  import { deepEqual } from '../utils/deepEqual.js'
16
16
  import { createClientError, type ErrorInput, type FieldError } from '../errors/types.js'
17
17
  import type {
@@ -38,12 +38,12 @@ interface RelationHandleRaw {
38
38
 
39
39
  interface CachedFieldHandle {
40
40
  readonly raw: FieldHandle<unknown>
41
- readonly proxy: FieldRef<unknown>
41
+ readonly proxy: FieldAccessor<unknown>
42
42
  }
43
43
 
44
44
  interface CachedRelationHandle {
45
45
  readonly raw: RelationHandleRaw
46
- readonly proxy: HasOneAccessor<object> | HasManyRef<object>
46
+ readonly proxy: HasOneAccessor<object> | HasManyAccessor<object>
47
47
  }
48
48
 
49
49
  /**
@@ -241,12 +241,12 @@ export class EntityHandle<T extends object = object, TSelected = T> extends Enti
241
241
  * Gets a field handle for a specific field.
242
242
  * Returns cached handle to ensure stable identity.
243
243
  */
244
- field<K extends keyof T>(fieldName: K): FieldRef<T[K]> {
244
+ field<K extends keyof T>(fieldName: K): FieldAccessor<T[K]> {
245
245
  const cacheKey = String(fieldName)
246
246
  const cached = this.fieldHandleCache.get(cacheKey)
247
247
 
248
248
  if (cached) {
249
- return cached.proxy as FieldRef<T[K]>
249
+ return cached.proxy as FieldAccessor<T[K]>
250
250
  }
251
251
 
252
252
  const enumName = this.schema.getEnumName(this.entityType, cacheKey)
@@ -307,13 +307,13 @@ export class EntityHandle<T extends object = object, TSelected = T> extends Enti
307
307
  * @param alias - Optional alias for the relation. When the same field is used multiple times
308
308
  * with different parameters (filter, orderBy, limit), each needs a unique alias.
309
309
  */
310
- hasMany<TItem extends object>(fieldName: string, alias?: string, nestedSelection?: SelectionMeta): HasManyRef<TItem> {
310
+ hasMany<TItem extends object>(fieldName: string, alias?: string, nestedSelection?: SelectionMeta): HasManyAccessor<TItem> {
311
311
  const effectiveAlias = alias ?? fieldName
312
312
  const cacheKey = `hasMany:${effectiveAlias}`
313
313
  const cached = this.relationHandleCache.get(cacheKey)
314
314
 
315
315
  if (cached) {
316
- return cached.proxy as HasManyRef<TItem>
316
+ return cached.proxy as HasManyAccessor<TItem>
317
317
  }
318
318
 
319
319
  const targetType = this.schema.getRelationTarget(this.entityType, fieldName)
@@ -380,12 +380,12 @@ export class EntityHandle<T extends object = object, TSelected = T> extends Enti
380
380
  * Gets a raw (unproxied) has-many relation handle from cache.
381
381
  * Creates the handle if not cached yet.
382
382
  */
383
- private getHasManyHandleRaw(fieldName: string): HasManyRef<object> {
383
+ private getHasManyHandleRaw(fieldName: string): HasManyAccessor<object> {
384
384
  const cacheKey = `hasMany:${fieldName}`
385
385
  const cached = this.relationHandleCache.get(cacheKey)
386
386
 
387
387
  if (cached) {
388
- return cached.proxy as HasManyRef<object>
388
+ return cached.proxy as HasManyAccessor<object>
389
389
  }
390
390
 
391
391
  const targetType = this.schema.getRelationTarget(this.entityType, fieldName)
@@ -460,8 +460,8 @@ export class EntityHandle<T extends object = object, TSelected = T> extends Enti
460
460
  *
461
461
  * Implements EntityRef.fields - returns selection-aware field accessors.
462
462
  */
463
- get fields(): SelectedEntityFields<T, TSelected> {
464
- return new Proxy({} as SelectedEntityFields<T, TSelected>, {
463
+ get fields(): EntityFieldsAccessor<T, TSelected> {
464
+ return new Proxy({} as EntityFieldsAccessor<T, TSelected>, {
465
465
  get: (_, fieldName: string) => {
466
466
  // Selection validation
467
467
  if (this.selection && !this.selection.fields.has(fieldName)) {
@@ -2,7 +2,7 @@ import { EntityRelatedHandle } from './BaseHandle.js'
2
2
  import type { ActionDispatcher } from '../core/ActionDispatcher.js'
3
3
  import type { SnapshotStore } from '../store/SnapshotStore.js'
4
4
  import { addFieldError, clearFieldErrors, setField } from '../core/actions.js'
5
- import { FIELD_REF_META, type FieldRef, type FieldRefMeta, type InputProps, type Unsubscribe } from './types.js'
5
+ import { FIELD_REF_META, type FieldAccessor, type FieldRefMeta, type InputProps, type Unsubscribe } from './types.js'
6
6
  import { deepEqual } from '../utils/deepEqual.js'
7
7
  import { createClientError, type ErrorInput, type FieldError } from '../errors/types.js'
8
8
  import type {
@@ -49,8 +49,8 @@ export class FieldHandle<T = unknown> extends EntityRelatedHandle {
49
49
  dispatcher: ActionDispatcher,
50
50
  enumName?: string,
51
51
  columnType?: string,
52
- ): FieldRef<T> {
53
- return createAliasProxy<FieldHandle<T>, FieldRef<T>>(new FieldHandle<T>(entityType, entityId, fieldPath, store, dispatcher, enumName, columnType))
52
+ ): FieldAccessor<T> {
53
+ return createAliasProxy<FieldHandle<T>, FieldAccessor<T>>(new FieldHandle<T>(entityType, entityId, fieldPath, store, dispatcher, enumName, columnType))
54
54
  }
55
55
 
56
56
  static createRaw<T = unknown>(
@@ -65,8 +65,8 @@ export class FieldHandle<T = unknown> extends EntityRelatedHandle {
65
65
  return new FieldHandle<T>(entityType, entityId, fieldPath, store, dispatcher, enumName, columnType)
66
66
  }
67
67
 
68
- static wrapProxy<T>(handle: FieldHandle<T>): FieldRef<T> {
69
- return createAliasProxy<FieldHandle<T>, FieldRef<T>>(handle)
68
+ static wrapProxy<T>(handle: FieldHandle<T>): FieldAccessor<T> {
69
+ return createAliasProxy<FieldHandle<T>, FieldAccessor<T>>(handle)
70
70
  }
71
71
 
72
72
  /**
@@ -220,7 +220,7 @@ export class FieldHandle<T = unknown> extends EntityRelatedHandle {
220
220
  */
221
221
  nested<K extends keyof NonNullable<T>>(
222
222
  key: K,
223
- ): FieldRef<NonNullable<T>[K]> {
223
+ ): FieldAccessor<NonNullable<T>[K]> {
224
224
  return FieldHandle.create<NonNullable<T>[K]>(
225
225
  this.entityType,
226
226
  this.entityId,
@@ -8,7 +8,7 @@ import {
8
8
  addRelationError,
9
9
  clearRelationErrors,
10
10
  } from '../core/actions.js'
11
- import { FIELD_REF_META, type HasManyRef, type FieldRefMeta, type EntityAccessor, type Unsubscribe } from './types.js'
11
+ import { FIELD_REF_META, type HasManyAccessor, type FieldRefMeta, type EntityAccessor, type Unsubscribe } from './types.js'
12
12
  import { createClientError, type ErrorInput, type FieldError } from '../errors/types.js'
13
13
  import type {
14
14
  EventListener,
@@ -85,8 +85,8 @@ export class HasManyListHandle<TEntity extends object = object, TSelected = TEnt
85
85
  brands?: Set<symbol>,
86
86
  alias?: string,
87
87
  selection?: SelectionMeta,
88
- ): HasManyRef<TEntity, TSelected> {
89
- return createAliasProxy<HasManyListHandle<TEntity, TSelected>, HasManyRef<TEntity, TSelected>>(new HasManyListHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, itemType, store, dispatcher, schema, brands, alias, selection))
88
+ ): HasManyAccessor<TEntity, TSelected> {
89
+ return createAliasProxy<HasManyListHandle<TEntity, TSelected>, HasManyAccessor<TEntity, TSelected>>(new HasManyListHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, itemType, store, dispatcher, schema, brands, alias, selection))
90
90
  }
91
91
 
92
92
  /**
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  FIELD_REF_META,
15
15
  type FieldRefMeta,
16
- type SelectedEntityFields,
16
+ type EntityFieldsAccessor,
17
17
  type Unsubscribe,
18
18
  type EntityAccessor,
19
19
  type HasOneAccessor,
@@ -198,7 +198,7 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
198
198
  * Implements HasOneRef interface.
199
199
  * Delegates to the entity (either real EntityHandle or PlaceholderHandle).
200
200
  */
201
- get fields(): SelectedEntityFields<TEntity, TSelected> {
201
+ get fields(): EntityFieldsAccessor<TEntity, TSelected> {
202
202
  return this.entity.$fields
203
203
  }
204
204
 
@@ -236,6 +236,7 @@ export class HasOneHandle<TEntity extends object = object, TSelected = TEntity>
236
236
  this.targetType,
237
237
  this.store,
238
238
  this.dispatcher,
239
+ this.schema,
239
240
  this.__brands,
240
241
  )
241
242
  this.placeholderCacheProxy = PlaceholderHandle.wrapProxy(this.placeholderCacheRaw)
@@ -3,7 +3,7 @@ import type { SnapshotStore } from '../store/SnapshotStore.js'
3
3
  import { generatePlaceholderId } from '../store/SnapshotStore.js'
4
4
  import {
5
5
  FIELD_REF_META,
6
- type SelectedEntityFields,
6
+ type EntityFieldsAccessor,
7
7
  type FieldRefMeta,
8
8
  type Unsubscribe,
9
9
  type EntityAccessor,
@@ -19,6 +19,7 @@ import type {
19
19
  EntityPersistingEvent,
20
20
  } from '../events/types.js'
21
21
  import { createAliasProxy } from './proxyFactory.js'
22
+ import type { SchemaRegistry } from '../schema/SchemaRegistry.js'
22
23
 
23
24
  /**
24
25
  * PlaceholderHandle provides access to a placeholder entity (for creating new entities).
@@ -42,6 +43,7 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
42
43
  private readonly targetType: string,
43
44
  private readonly store: SnapshotStore,
44
45
  private readonly dispatcher: ActionDispatcher,
46
+ private readonly schema: SchemaRegistry | null,
45
47
  brands?: Set<symbol>,
46
48
  ) {
47
49
  this.__brands = brands
@@ -55,9 +57,10 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
55
57
  targetType: string,
56
58
  store: SnapshotStore,
57
59
  dispatcher: ActionDispatcher,
60
+ schema?: SchemaRegistry | null,
58
61
  brands?: Set<symbol>,
59
62
  ): EntityAccessor<TEntity, TSelected> {
60
- return PlaceholderHandle.wrapProxy(new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, brands))
63
+ return PlaceholderHandle.wrapProxy(new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, schema ?? null, brands))
61
64
  }
62
65
 
63
66
  static createRaw<TEntity extends object = object, TSelected = TEntity>(
@@ -67,9 +70,10 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
67
70
  targetType: string,
68
71
  store: SnapshotStore,
69
72
  dispatcher: ActionDispatcher,
73
+ schema?: SchemaRegistry | null,
70
74
  brands?: Set<symbol>,
71
75
  ): PlaceholderHandle<TEntity, TSelected> {
72
- return new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, brands)
76
+ return new PlaceholderHandle<TEntity, TSelected>(parentEntityType, parentEntityId, fieldName, targetType, store, dispatcher, schema ?? null, brands)
73
77
  }
74
78
 
75
79
  static wrapProxy<TEntity extends object, TSelected>(handle: PlaceholderHandle<TEntity, TSelected>): EntityAccessor<TEntity, TSelected> {
@@ -134,8 +138,8 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
134
138
  /**
135
139
  * Gets field accessors that read/write placeholder data.
136
140
  */
137
- get fields(): SelectedEntityFields<TEntity, TSelected> {
138
- return new Proxy({} as SelectedEntityFields<TEntity, TSelected>, {
141
+ get fields(): EntityFieldsAccessor<TEntity, TSelected> {
142
+ return new Proxy({} as EntityFieldsAccessor<TEntity, TSelected>, {
139
143
  get: (_, fieldName: string) => {
140
144
  return this.createPlaceholderFieldHandle(fieldName)
141
145
  },
@@ -144,8 +148,24 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
144
148
 
145
149
  /**
146
150
  * Creates a field handle for placeholder data.
151
+ * For has-many relations, returns an empty has-many-like handle with items/map/length.
147
152
  */
148
153
  private createPlaceholderFieldHandle(fieldName: string): unknown {
154
+ // For has-many relations, return an empty has-many-like handle
155
+ if (this.schema?.isHasMany(this.targetType, fieldName)) {
156
+ const emptyItems: EntityAccessor<object>[] = []
157
+ const emptyHasMany: Record<string, unknown> = {
158
+ get items() { return emptyItems },
159
+ get length() { return 0 },
160
+ map: () => [],
161
+ add: () => { throw new Error(`Cannot add items to disconnected has-many relation "${fieldName}" on placeholder entity`) },
162
+ remove: () => {},
163
+ $state: 'disconnected',
164
+ [Symbol.iterator]: () => emptyItems[Symbol.iterator](),
165
+ }
166
+ return emptyHasMany
167
+ }
168
+
149
169
  const self = this
150
170
 
151
171
  return {
@@ -8,27 +8,25 @@ export {
8
8
  FIELD_REF_META,
9
9
  type HasOneRelationState,
10
10
  type EntityFields,
11
- type SelectedEntityFields,
12
- type SelectedEntityFieldsBase,
11
+ type EntityFieldsAccessor,
12
+ type EntityFieldsRef,
13
13
  type ScalarKeys,
14
14
  type HasManyKeys,
15
15
  type HasOneKeys,
16
16
  type FieldRefMeta,
17
17
  type InputProps,
18
- // Full types (for explicit selection - have .value, .length, etc.)
18
+ // Ref types (pointer, no data access)
19
19
  type FieldRef,
20
20
  type HasManyRef,
21
21
  type HasOneRef,
22
- type HasOneAccessor,
22
+ type HasOneRefInterface,
23
23
  type EntityRef,
24
+ type EntityRefInterface,
25
+ // Accessor types (live data access, extends Ref)
26
+ type FieldAccessor,
27
+ type HasManyAccessor,
28
+ type HasOneAccessor,
24
29
  type EntityAccessor,
25
- // Base types (for implicit selection - no .value, .length, etc.)
26
- type FieldRefBase,
27
- type HasManyRefBase,
28
- type HasOneRefBase,
29
- type HasOneAccessorBase,
30
- type EntityRefBase,
31
- type EntityAccessorBase,
32
30
  type Unsubscribe,
33
31
  // Type extraction helpers
34
32
  type ExtractHasOneEntityName,