@contember/bindx 0.1.39 → 0.1.41

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.
@@ -24,6 +24,11 @@ export declare class PlaceholderHandle<TEntity extends object = object, TSelecte
24
24
  readonly __brands?: Set<symbol>;
25
25
  /** Placeholder ID for this handle */
26
26
  private readonly placeholderId;
27
+ /**
28
+ * Stable temp id this placeholder collapses into once it must become a real entity
29
+ * (the first time a child is added to one of its has-many relations). Lazily minted.
30
+ */
31
+ private materializedId;
27
32
  private constructor();
28
33
  static create<TEntity extends object = object, TSelected = TEntity>(parentEntityType: string, parentEntityId: string, fieldName: string, targetType: string, store: SnapshotStore, dispatcher: ActionDispatcher, schema?: SchemaRegistry | null, brands?: Set<symbol>): EntityAccessor<TEntity, TSelected>;
29
34
  static createRaw<TEntity extends object = object, TSelected = TEntity>(parentEntityType: string, parentEntityId: string, fieldName: string, targetType: string, store: SnapshotStore, dispatcher: ActionDispatcher, schema?: SchemaRegistry | null, brands?: Set<symbol>): PlaceholderHandle<TEntity, TSelected>;
@@ -56,9 +61,23 @@ export declare class PlaceholderHandle<TEntity extends object = object, TSelecte
56
61
  * Gets field accessors that read/write placeholder data.
57
62
  */
58
63
  get fields(): EntityFieldsAccessor<TEntity, TSelected>;
64
+ /**
65
+ * Stable temp id this placeholder collapses into when it must become a real entity.
66
+ */
67
+ private getMaterializedId;
68
+ /**
69
+ * Promotes this placeholder into a real (temp) entity connected to its parent relation.
70
+ * Called lazily the first time a child is added to one of the placeholder's has-many
71
+ * relations — a has-many child needs a real parent to belong to. The entity is seeded
72
+ * with whatever placeholder scalar data was typed so far, then connected via the normal
73
+ * create path, so persist collects both the entity and its children (see collectHasOne/
74
+ * collectHasManyOperations → create). Idempotent.
75
+ */
76
+ private materializeIntoParent;
59
77
  /**
60
78
  * Creates a field handle for placeholder data.
61
- * For has-many relations, returns an empty has-many-like handle with items/map/length.
79
+ * For has-many relations, returns a real (empty) has-many handle bound to this
80
+ * placeholder entity, so it behaves identically to a connected one.
62
81
  */
63
82
  private createPlaceholderFieldHandle;
64
83
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"PlaceholderHandle.d.ts","sourceRoot":"","sources":["../../src/handles/PlaceholderHandle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE9D,OAAO,EAEN,KAAK,oBAAoB,EAEzB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,MAAM,YAAY,CAAA;AACnB,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,KAAK,EACX,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,MAAM,oBAAoB,CAAA;AAE3B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAEjE;;;;;;;GAOG;AACH,qBAAa,iBAAiB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,SAAS,GAAG,OAAO;IAQjF,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbxB,2CAA2C;IAC3C,QAAQ,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAE/B,qCAAqC;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IAEtC,OAAO;IAcP,MAAM,CAAC,MAAM,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,SAAS,GAAG,OAAO,EACjE,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAClB,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC;IAIrC,MAAM,CAAC,SAAS,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EAAE,SAAS,GAAG,OAAO,EACpE,gBAAgB,EAAE,MAAM,EACxB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,aAAa,EACpB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,EAC9B,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,GAClB,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC;IAIxC,MAAM,CAAC,SAAS,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC;IAItI;;OAEG;IACH,IAAI,EAAE,IAAI,MAAM,CAEf;IAED;;OAEG;IACH,IAAI,IAAI,IAAI,SAAS,GAAG,IAAI,CAU3B;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAOrB;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,IAAI,CAEtB;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,OAAO,CAEnB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,oBAAoB,CAAC,OAAO,EAAE,SAAS,CAAC,CAMrD;IAED;;;OAGG;IACH,OAAO,CAAC,4BAA4B;IA6GpC;;OAEG;IACH,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;OAGG;IACH,IAAI,MAAM,IAAI,SAAS,UAAU,EAAE,CAElC;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;OAEG;IACH,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIlC;;OAEG;IACH,WAAW,IAAI,IAAI;IAInB;;OAEG;IACH,cAAc,IAAI,IAAI;IAOtB;;OAEG;IACH,EAAE,CAAC,CAAC,SAAS,eAAe,EAC3B,UAAU,EAAE,CAAC,EACb,SAAS,EAAE,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GACvC,WAAW;IAId;;OAEG;IACH,SAAS,CAAC,CAAC,SAAS,gBAAgB,EACnC,UAAU,EAAE,CAAC,EACb,YAAY,EAAE,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GACxC,WAAW;IAId;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,WAAW;IAIxE;;OAEG;IACH,mBAAmB,CAAC,YAAY,EAAE,WAAW,CAAC,qBAAqB,CAAC,GAAG,WAAW;CAIlF"}
1
+ {"version":3,"file":"PlaceholderHandle.d.ts","sourceRoot":"","sources":["../../src/handles/PlaceholderHandle.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/bindx",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "Type-safe data binding framework (core)",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "license": "MIT",
19
19
  "dependencies": {
20
- "@contember/bindx-client": "0.1.38",
20
+ "@contember/bindx-client": "0.1.41",
21
21
  "@contember/graphql-builder": "^2.1.0-beta.1",
22
22
  "@contember/graphql-client": "^2.1.0-beta.1"
23
23
  },
@@ -19,6 +19,9 @@ import type {
19
19
  EntityPersistingEvent,
20
20
  } from '../events/types.js'
21
21
  import { createHandleProxy } from './proxyFactory.js'
22
+ import { HasManyListHandle } from './HasManyListHandle.js'
23
+ import { generateTempId } from '../store/entityId.js'
24
+ import { connectRelation } from '../core/actions.js'
22
25
  import type { SchemaRegistry } from '../schema/SchemaRegistry.js'
23
26
 
24
27
  /**
@@ -36,6 +39,12 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
36
39
  /** Placeholder ID for this handle */
37
40
  private readonly placeholderId: string
38
41
 
42
+ /**
43
+ * Stable temp id this placeholder collapses into once it must become a real entity
44
+ * (the first time a child is added to one of its has-many relations). Lazily minted.
45
+ */
46
+ private materializedId: string | null = null
47
+
39
48
  private constructor(
40
49
  private readonly parentEntityType: string,
41
50
  private readonly parentEntityId: string,
@@ -146,24 +155,76 @@ export class PlaceholderHandle<TEntity extends object = object, TSelected = TEnt
146
155
  })
147
156
  }
148
157
 
158
+ /**
159
+ * Stable temp id this placeholder collapses into when it must become a real entity.
160
+ */
161
+ private getMaterializedId(): string {
162
+ if (!this.materializedId) this.materializedId = generateTempId()
163
+ return this.materializedId
164
+ }
165
+
166
+ /**
167
+ * Promotes this placeholder into a real (temp) entity connected to its parent relation.
168
+ * Called lazily the first time a child is added to one of the placeholder's has-many
169
+ * relations — a has-many child needs a real parent to belong to. The entity is seeded
170
+ * with whatever placeholder scalar data was typed so far, then connected via the normal
171
+ * create path, so persist collects both the entity and its children (see collectHasOne/
172
+ * collectHasManyOperations → create). Idempotent.
173
+ */
174
+ private materializeIntoParent(): void {
175
+ const tempId = this.getMaterializedId()
176
+ const relation = this.store.getRelation(this.parentEntityType, this.parentEntityId, this.fieldName)
177
+ if (relation?.currentId === tempId) return // already materialized
178
+ if (!this.store.getEntitySnapshot(this.targetType, tempId)) {
179
+ this.store.createEntity(this.targetType, { ...(relation?.placeholderData ?? {}), id: tempId })
180
+ }
181
+ this.dispatcher.dispatch(
182
+ connectRelation(this.parentEntityType, this.parentEntityId, this.fieldName, tempId, this.targetType),
183
+ )
184
+ }
185
+
149
186
  /**
150
187
  * Creates a field handle for placeholder data.
151
- * For has-many relations, returns an empty has-many-like handle with items/map/length.
188
+ * For has-many relations, returns a real (empty) has-many handle bound to this
189
+ * placeholder entity, so it behaves identically to a connected one.
152
190
  */
153
191
  private createPlaceholderFieldHandle(fieldName: string): unknown {
154
- // For has-many relations, return an empty has-many-like handle
192
+ // For has-many relations, return a real HasManyListHandle bound to this placeholder's
193
+ // stable temp id. Hand-rolling a degenerate stub here used to drop FIELD_REF_META,
194
+ // getById and a working add() — which crashed the BlockEditor
195
+ // (`references[FIELD_REF_META].entityType`) on render for any parent without a
196
+ // connected entity yet. Reads stay empty (no snapshot for the temp id until something
197
+ // is added); the first add() promotes the placeholder into a real connected entity
198
+ // (materializeIntoParent) so the children — and the parent itself — round-trip on persist.
155
199
  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](),
200
+ const itemType = this.schema.getRelationTarget(this.targetType, fieldName)
201
+ if (!itemType) {
202
+ throw new Error(`Field "${fieldName}" is not a relation on entity "${this.targetType}"`)
165
203
  }
166
- return emptyHasMany
204
+ const handle = HasManyListHandle.create(
205
+ this.targetType,
206
+ this.getMaterializedId(),
207
+ fieldName,
208
+ itemType,
209
+ this.store,
210
+ this.dispatcher,
211
+ this.schema,
212
+ this.__brands,
213
+ )
214
+ // Intercept add() to lazily promote the placeholder to a real entity first — a child
215
+ // can't belong to a parent that doesn't exist. Reads pass through untouched.
216
+ return new Proxy(handle as object, {
217
+ get: (target, prop, receiver) => {
218
+ const value = Reflect.get(target, prop, receiver)
219
+ if (prop === 'add' && typeof value === 'function') {
220
+ return (...args: unknown[]): unknown => {
221
+ this.materializeIntoParent()
222
+ return (value as (...a: unknown[]) => unknown).apply(target, args)
223
+ }
224
+ }
225
+ return value
226
+ },
227
+ })
167
228
  }
168
229
 
169
230
  const self = this