@dxos/echo 0.9.0 → 0.9.1-main.c7dcc2e112
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/lib/neutral/Annotation.mjs +12 -4
- package/dist/lib/neutral/Database.mjs +5 -1
- package/dist/lib/neutral/Entity.mjs +12 -8
- package/dist/lib/neutral/Feed.mjs +12 -12
- package/dist/lib/neutral/Filter.mjs +8 -8
- package/dist/lib/neutral/JsonSchema.mjs +4 -4
- package/dist/lib/neutral/Migration.mjs +8 -8
- package/dist/lib/neutral/Obj.mjs +14 -10
- package/dist/lib/neutral/Query.mjs +14 -14
- package/dist/lib/neutral/Ref.mjs +4 -4
- package/dist/lib/neutral/Relation.mjs +9 -9
- package/dist/lib/neutral/Tag.mjs +12 -12
- package/dist/lib/neutral/Type.mjs +7 -7
- package/dist/lib/neutral/{chunk-UUP46KUQ.mjs → chunk-3JXQUMUI.mjs} +9 -9
- package/dist/lib/neutral/{chunk-WTQJHC75.mjs → chunk-4TPQZPLY.mjs} +37 -25
- package/dist/lib/neutral/chunk-4TPQZPLY.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-ZGNNFYHS.mjs → chunk-5GAIWRQB.mjs} +4 -4
- package/dist/lib/neutral/{chunk-ZGNNFYHS.mjs.map → chunk-5GAIWRQB.mjs.map} +2 -2
- package/dist/lib/neutral/{chunk-TYGKCRMK.mjs → chunk-6CEISA3V.mjs} +15 -7
- package/dist/lib/neutral/{chunk-TYGKCRMK.mjs.map → chunk-6CEISA3V.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-RIVWNMSF.mjs → chunk-A53NTBHE.mjs} +3 -3
- package/dist/lib/neutral/{chunk-QQIYS74I.mjs → chunk-AFANXSGV.mjs} +6 -6
- package/dist/lib/neutral/{chunk-5SMDBFVB.mjs → chunk-E7IOTOZD.mjs} +6 -6
- package/dist/lib/neutral/{chunk-5SMDBFVB.mjs.map → chunk-E7IOTOZD.mjs.map} +2 -2
- package/dist/lib/neutral/{chunk-T6E37YIP.mjs → chunk-HGWQR5KH.mjs} +18 -10
- package/dist/lib/neutral/chunk-HGWQR5KH.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-3PBP4V4O.mjs → chunk-JZ6LFQU5.mjs} +11 -3
- package/dist/lib/neutral/chunk-JZ6LFQU5.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-7PI7C4EF.mjs → chunk-K3S7D5PR.mjs} +3 -3
- package/dist/lib/neutral/{chunk-5SUJPHAE.mjs → chunk-L3OBWBV2.mjs} +2 -2
- package/dist/lib/neutral/{chunk-6YDI3J37.mjs → chunk-NOL7O3BL.mjs} +4 -4
- package/dist/lib/neutral/{chunk-IGK6FN65.mjs → chunk-PLDMJJJC.mjs} +2 -2
- package/dist/lib/neutral/{chunk-LWXVKPPW.mjs → chunk-SZ5WB6HF.mjs} +6 -6
- package/dist/lib/neutral/{chunk-MZ7K3MLL.mjs → chunk-TF5BSLF6.mjs} +3 -3
- package/dist/lib/neutral/chunk-TF5BSLF6.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-7FPIAJIV.mjs → chunk-VQYL2OQF.mjs} +89 -15
- package/dist/lib/neutral/chunk-VQYL2OQF.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-R5W6DXR4.mjs → chunk-WZ32BF53.mjs} +26 -2
- package/dist/lib/neutral/chunk-WZ32BF53.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-4ZUHOTCG.mjs → chunk-X3NUUON4.mjs} +3 -3
- package/dist/lib/neutral/{chunk-SBVFRTST.mjs → chunk-Z3WMRRAZ.mjs} +14 -6
- package/dist/lib/neutral/chunk-Z3WMRRAZ.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +19 -19
- package/dist/lib/neutral/internal/index.mjs +26 -6
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +19 -19
- package/dist/types/src/Annotation.d.ts +1 -1
- package/dist/types/src/Annotation.d.ts.map +1 -1
- package/dist/types/src/Database.d.ts +22 -0
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Entity.d.ts +2 -0
- package/dist/types/src/Entity.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +3 -0
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +2 -0
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +3 -3
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/Tag.d.ts.map +1 -1
- package/dist/types/src/Type.d.ts +5 -1
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/internal/Annotation/annotations.d.ts +41 -0
- package/dist/types/src/internal/Annotation/annotations.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts +5 -0
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/index.d.ts +1 -0
- package/dist/types/src/internal/Ref/index.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/ref.d.ts +66 -2
- package/dist/types/src/internal/Ref/ref.d.ts.map +1 -1
- package/dist/types/src/internal/Ref/strong-deps.d.ts +35 -0
- package/dist/types/src/internal/Ref/strong-deps.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/Annotation.ts +5 -0
- package/src/Database.ts +33 -1
- package/src/Entity.ts +3 -1
- package/src/Feed.ts +3 -0
- package/src/Obj.ts +2 -0
- package/src/Ref.ts +3 -3
- package/src/Tag.ts +1 -0
- package/src/Type.ts +16 -2
- package/src/internal/Annotation/annotations.ts +56 -0
- package/src/internal/Obj/atoms.ts +31 -0
- package/src/internal/Obj/json-serializer.ts +3 -3
- package/src/internal/Ref/index.ts +1 -0
- package/src/internal/Ref/ref.ts +96 -19
- package/src/internal/Ref/strong-deps.ts +107 -0
- package/dist/lib/neutral/chunk-3PBP4V4O.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7FPIAJIV.mjs.map +0 -7
- package/dist/lib/neutral/chunk-MZ7K3MLL.mjs.map +0 -7
- package/dist/lib/neutral/chunk-R5W6DXR4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SBVFRTST.mjs.map +0 -7
- package/dist/lib/neutral/chunk-T6E37YIP.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WTQJHC75.mjs.map +0 -7
- /package/dist/lib/neutral/{chunk-UUP46KUQ.mjs.map → chunk-3JXQUMUI.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-RIVWNMSF.mjs.map → chunk-A53NTBHE.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-QQIYS74I.mjs.map → chunk-AFANXSGV.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-7PI7C4EF.mjs.map → chunk-K3S7D5PR.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-5SUJPHAE.mjs.map → chunk-L3OBWBV2.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-6YDI3J37.mjs.map → chunk-NOL7O3BL.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-IGK6FN65.mjs.map → chunk-PLDMJJJC.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-LWXVKPPW.mjs.map → chunk-SZ5WB6HF.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-4ZUHOTCG.mjs.map → chunk-X3NUUON4.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1-main.c7dcc2e112",
|
|
4
4
|
"description": "ECHO API",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -136,16 +136,16 @@
|
|
|
136
136
|
"dependencies": {
|
|
137
137
|
"@effect-atom/atom": "^0.5.3",
|
|
138
138
|
"effect": "3.21.3",
|
|
139
|
-
"@dxos/
|
|
140
|
-
"@dxos/
|
|
141
|
-
"@dxos/
|
|
142
|
-
"@dxos/
|
|
143
|
-
"@dxos/
|
|
144
|
-
"@dxos/
|
|
145
|
-
"@dxos/
|
|
146
|
-
"@dxos/
|
|
147
|
-
"@dxos/
|
|
148
|
-
"@dxos/
|
|
139
|
+
"@dxos/async": "0.9.1-main.c7dcc2e112",
|
|
140
|
+
"@dxos/echo-protocol": "0.9.1-main.c7dcc2e112",
|
|
141
|
+
"@dxos/effect": "0.9.1-main.c7dcc2e112",
|
|
142
|
+
"@dxos/errors": "0.9.1-main.c7dcc2e112",
|
|
143
|
+
"@dxos/keys": "0.9.1-main.c7dcc2e112",
|
|
144
|
+
"@dxos/invariant": "0.9.1-main.c7dcc2e112",
|
|
145
|
+
"@dxos/log": "0.9.1-main.c7dcc2e112",
|
|
146
|
+
"@dxos/node-std": "0.9.1-main.c7dcc2e112",
|
|
147
|
+
"@dxos/util": "0.9.1-main.c7dcc2e112",
|
|
148
|
+
"@dxos/debug": "0.9.1-main.c7dcc2e112"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
151
|
"@types/node": "22.10.2"
|
package/src/Annotation.ts
CHANGED
|
@@ -9,6 +9,11 @@ export {
|
|
|
9
9
|
FieldLookupAnnotationId,
|
|
10
10
|
FormInputAnnotation,
|
|
11
11
|
FormInlineAnnotation,
|
|
12
|
+
FormOrderedAnnotation,
|
|
13
|
+
FormLayoutAnnotation,
|
|
14
|
+
FormLayoutAnnotationId,
|
|
15
|
+
type FormLayoutMap,
|
|
16
|
+
DEFAULT_LAYOUT_NAME,
|
|
12
17
|
GeneratorAnnotation,
|
|
13
18
|
GeneratorAnnotationId,
|
|
14
19
|
type GeneratorAnnotationValue,
|
package/src/Database.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { type SpaceId, type URI } from '@dxos/keys';
|
|
|
17
17
|
|
|
18
18
|
import type * as Entity from './Entity';
|
|
19
19
|
import * as Err from './Err';
|
|
20
|
+
import type * as Feed from './Feed';
|
|
20
21
|
import type * as Filter from './Filter';
|
|
21
22
|
import type * as Hypergraph from './Hypergraph';
|
|
22
23
|
import { type AnyProperties, EntityKind, KindId } from './internal/common/types';
|
|
@@ -166,6 +167,19 @@ export interface Database extends Queryable {
|
|
|
166
167
|
// TODO(burdon): Return true if removed (currently throws if not present).
|
|
167
168
|
remove(obj: Entity.Unknown): void;
|
|
168
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Appends entities to a feed.
|
|
172
|
+
*
|
|
173
|
+
* The feed must already be stored in the database (added via {@link add}); its underlying
|
|
174
|
+
* queue is addressed by the feed object's URI.
|
|
175
|
+
*/
|
|
176
|
+
appendToFeed(feed: Feed.Feed, entities: Entity.Unknown[]): Promise<void>;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Removes entities from a feed.
|
|
180
|
+
*/
|
|
181
|
+
deleteFromFeed(feed: Feed.Feed, entities: Entity.Unknown[]): Promise<void>;
|
|
182
|
+
|
|
169
183
|
/**
|
|
170
184
|
* Wait for all pending changes to be saved to disk.
|
|
171
185
|
* Optionaly waits for changes to be propagated to indexes and event handlers.
|
|
@@ -250,7 +264,7 @@ export const resolve: {
|
|
|
250
264
|
space: db.spaceId,
|
|
251
265
|
},
|
|
252
266
|
})
|
|
253
|
-
.
|
|
267
|
+
.resolveLegacy(dxn),
|
|
254
268
|
);
|
|
255
269
|
|
|
256
270
|
if (!object) {
|
|
@@ -305,6 +319,24 @@ export const addType = <T extends Type.AnyEntity>(type: T): Effect.Effect<T, nev
|
|
|
305
319
|
export const remove = <T extends Entity.Unknown>(obj: T): Effect.Effect<void, never, Service> =>
|
|
306
320
|
Service.pipe(Effect.map(({ db }) => db.remove(obj))).pipe(Effect.withSpan('Database.remove'));
|
|
307
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Appends entities to a feed.
|
|
324
|
+
* @see {@link Database.appendToFeed}
|
|
325
|
+
*/
|
|
326
|
+
export const appendToFeed = (feed: Feed.Feed, entities: Entity.Unknown[]): Effect.Effect<void, never, Service> =>
|
|
327
|
+
Service.pipe(
|
|
328
|
+
Effect.flatMap(({ db }) => EffectEx.promiseWithCauseCapture(() => db.appendToFeed(feed, entities))),
|
|
329
|
+
).pipe(Effect.withSpan('Database.appendToFeed'));
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Removes entities from a feed.
|
|
333
|
+
* @see {@link Database.deleteFromFeed}
|
|
334
|
+
*/
|
|
335
|
+
export const deleteFromFeed = (feed: Feed.Feed, entities: Entity.Unknown[]): Effect.Effect<void, never, Service> =>
|
|
336
|
+
Service.pipe(
|
|
337
|
+
Effect.flatMap(({ db }) => EffectEx.promiseWithCauseCapture(() => db.deleteFromFeed(feed, entities))),
|
|
338
|
+
).pipe(Effect.withSpan('Database.deleteFromFeed'));
|
|
339
|
+
|
|
308
340
|
/**
|
|
309
341
|
* Flushes pending changes to disk.
|
|
310
342
|
* @see {@link Database.flush}
|
package/src/Entity.ts
CHANGED
|
@@ -190,7 +190,7 @@ export type AnyInput = Unknown | Snapshot;
|
|
|
190
190
|
* @param options.prefer - Controls the URI form (see {@link internal.GetURIOptions}).
|
|
191
191
|
*/
|
|
192
192
|
export const getURI = (entity: AnyInput, options?: internal.GetURIOptions): URI.URI =>
|
|
193
|
-
isTypeEntity(entity) ? Type.getURI(entity as Type.AnyEntity) : internal.getUri(entity as Unknown, options);
|
|
193
|
+
isTypeEntity(entity) ? Type.getURI(entity as Type.AnyEntity, options) : internal.getUri(entity as Unknown, options);
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
196
|
* Get the DXN of an entity's type. For object/relation instances this is the URI
|
|
@@ -343,3 +343,5 @@ export const removeTag = (entity: Mutable<Unknown>, tag: Ref.Ref<Tag.Tag>): void
|
|
|
343
343
|
//
|
|
344
344
|
|
|
345
345
|
export const atom = objInternal.makeEntity;
|
|
346
|
+
export const labelAtom = objInternal.makeLabelAtom;
|
|
347
|
+
export const labelProperty = internal.getLabelProperty;
|
package/src/Feed.ts
CHANGED
|
@@ -245,6 +245,9 @@ export const remove = (feed: Feed, items: (Entity.Unknown | Obj.Snapshot)[]): Ef
|
|
|
245
245
|
/**
|
|
246
246
|
* Creates a reactive query over items in a feed.
|
|
247
247
|
*
|
|
248
|
+
* In non-Effect code, query a feed directly through the database with a feed scope:
|
|
249
|
+
* `db.query(Query.select(filter).from(Scope.feed(Feed.getQueueUri(feed))))`.
|
|
250
|
+
*
|
|
248
251
|
* @example
|
|
249
252
|
* ```ts
|
|
250
253
|
* const result = yield* Feed.query(feed, Filter.type(Person));
|
package/src/Obj.ts
CHANGED
|
@@ -926,3 +926,5 @@ export const version = (entity: Unknown | Snapshot): Version => internal.version
|
|
|
926
926
|
export const atom = objInternal.makeAtom;
|
|
927
927
|
export const atomReactive = objInternal.makeWithReactive;
|
|
928
928
|
export const atomProperty = objInternal.makeProperty;
|
|
929
|
+
export const labelAtom = objInternal.makeLabelAtom;
|
|
930
|
+
export const labelProperty = internal.getLabelProperty;
|
package/src/Ref.ts
CHANGED
|
@@ -16,7 +16,7 @@ import * as refInternal from './internal/Ref';
|
|
|
16
16
|
import type * as JsonSchema from './JsonSchema';
|
|
17
17
|
import type * as Obj from './Obj';
|
|
18
18
|
// eslint-disable-next-line @dxos/rules/import-as-namespace
|
|
19
|
-
import type * as
|
|
19
|
+
import type * as Type$ from './Type';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Instance type for a reference.
|
|
@@ -51,11 +51,11 @@ export type Unknown = refInternal.Ref<Obj.Unknown>;
|
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
53
|
export const Ref: {
|
|
54
|
-
<S extends
|
|
54
|
+
<S extends Type$.AnyObj | Type$.AnyRelation>(type: S): RefSchema<Type$.InstanceType<S> & Obj.Unknown>;
|
|
55
55
|
|
|
56
56
|
// `Type.Type` entities (the meta-schema kind) can be referenced too — e.g. a
|
|
57
57
|
// trigger that points to a stored function/workflow definition.
|
|
58
|
-
<T extends
|
|
58
|
+
<T extends Type$.Type<any>>(type: T): RefSchema<Type$.InstanceType<T>>;
|
|
59
59
|
|
|
60
60
|
// Schema-side overload for the well-known "any object" / "any relation" schemas.
|
|
61
61
|
// Other raw `Schema.Schema` values are intentionally rejected — callers should
|
package/src/Tag.ts
CHANGED
package/src/Type.ts
CHANGED
|
@@ -375,17 +375,31 @@ export type AnyRef = Schema.Schema<internal.Ref<any>, EncodedReference>;
|
|
|
375
375
|
* - Persisted `Type.Type` instance (has `id`) → local `EID` (`echo:/<objectId>`).
|
|
376
376
|
* - In-memory `Type.Type` draft (has `id`, no typename) → local `EID`.
|
|
377
377
|
*
|
|
378
|
+
* When `options.prefer === 'named'` the result is forced to a DXN — for
|
|
379
|
+
* persisted type entities the typename + version from meta are used to
|
|
380
|
+
* construct the DXN; static types already return a DXN by default.
|
|
381
|
+
*
|
|
378
382
|
* Only accepts `Type.AnyEntity` entities. Raw `Schema.Schema` values and the
|
|
379
383
|
* branded `Obj.Unknown` / `Relation.Unknown` schemas are intentionally not
|
|
380
384
|
* supported — use `internal.getSchemaURI` or the schema's typename annotation
|
|
381
385
|
* directly when working at the schema level.
|
|
382
386
|
*/
|
|
383
|
-
export const getURI = (input: AnyEntity): URI.URI => {
|
|
387
|
+
export const getURI = (input: AnyEntity, options?: internal.GetURIOptions): URI.URI => {
|
|
384
388
|
// For Type entities, route through `getTypeURIFromSpecifier` (id → EID,
|
|
385
389
|
// typename/version → DXN). For Obj/Relation entities, unwrap to the source
|
|
386
390
|
// Effect Schema first and read its annotations.
|
|
387
391
|
if (isType(input)) {
|
|
388
|
-
|
|
392
|
+
const uri = internal.getTypeURIFromSpecifier(input);
|
|
393
|
+
if (options?.prefer === 'named' && !DXN.isDXN(uri)) {
|
|
394
|
+
// Persisted type — the auto-detected URI is an EID, but caller wants a
|
|
395
|
+
// DXN. Construct from typename + version (both live in EntityMeta).
|
|
396
|
+
const typename = getTypename(input);
|
|
397
|
+
const version = getMeta(input).version;
|
|
398
|
+
if (typename && version) {
|
|
399
|
+
return DXN.make(typename, version);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return uri;
|
|
389
403
|
}
|
|
390
404
|
return internal.getSchemaURI(getSchema(input)) ?? raise(new TypeError('Type entity has no URI'));
|
|
391
405
|
};
|
|
@@ -413,6 +413,47 @@ export const FormInputAnnotation = createAnnotationHelper<boolean>(FormInputAnno
|
|
|
413
413
|
export const FormInlineAnnotationId = Symbol.for('@dxos/schema/annotation/FormInline');
|
|
414
414
|
export const FormInlineAnnotation = createAnnotationHelper<boolean>(FormInlineAnnotationId);
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* When set on an array property, the form renders it as an ordered,
|
|
418
|
+
* drag-to-reorder list (drag handles per row). Element order is meaningful and
|
|
419
|
+
* user-controllable; reordering rewrites the array.
|
|
420
|
+
*/
|
|
421
|
+
export const FormOrderedAnnotationId = Symbol.for('@dxos/schema/annotation/FormOrdered');
|
|
422
|
+
export const FormOrderedAnnotation = createAnnotationHelper<boolean>(FormOrderedAnnotationId);
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Annotation carrying one or more named layout DSL templates that control how a
|
|
426
|
+
* form arranges a schema's fields (consumed by `@dxos/react-ui-form`'s
|
|
427
|
+
* `Form.Layout` / `Form.FieldSet`). Callers select a variant by name; the
|
|
428
|
+
* implicit name is `DEFAULT_LAYOUT_NAME` (`'default'`).
|
|
429
|
+
*
|
|
430
|
+
* Templates use a minimal XML grammar. Example:
|
|
431
|
+
*
|
|
432
|
+
* FormLayoutAnnotation.set({
|
|
433
|
+
* default: `
|
|
434
|
+
* <grid cols="2">
|
|
435
|
+
* <field name="origin"/>
|
|
436
|
+
* <field name="destination"/>
|
|
437
|
+
* <field name="provider" span="2"/>
|
|
438
|
+
* </grid>
|
|
439
|
+
* `,
|
|
440
|
+
* card: `
|
|
441
|
+
* <grid cols="1">
|
|
442
|
+
* <field name="provider"/>
|
|
443
|
+
* <field name="number"/>
|
|
444
|
+
* </grid>
|
|
445
|
+
* `,
|
|
446
|
+
* })
|
|
447
|
+
*/
|
|
448
|
+
export const FormLayoutAnnotationId = Symbol.for('@dxos/react-ui-form/annotation/Layout');
|
|
449
|
+
|
|
450
|
+
export type FormLayoutMap = Record<string, string>;
|
|
451
|
+
|
|
452
|
+
export const FormLayoutAnnotation = createAnnotationHelper<FormLayoutMap>(FormLayoutAnnotationId);
|
|
453
|
+
|
|
454
|
+
/** Name used when no explicit form-layout variant is requested. */
|
|
455
|
+
export const DEFAULT_LAYOUT_NAME = 'default';
|
|
456
|
+
|
|
416
457
|
/**
|
|
417
458
|
* Default field to be used on referenced schema to lookup the value.
|
|
418
459
|
*/
|
|
@@ -557,6 +598,21 @@ export const setLabel = (entity: Mutable<AnyProperties>, label: string) => {
|
|
|
557
598
|
}
|
|
558
599
|
};
|
|
559
600
|
|
|
601
|
+
/**
|
|
602
|
+
* Returns the primary label property key for an entity.
|
|
603
|
+
* Reads the first accessor from {@link LabelAnnotation}, defaulting to 'name'.
|
|
604
|
+
*/
|
|
605
|
+
export const getLabelProperty = (entity: AnyProperties): string => {
|
|
606
|
+
const schema = getSchema(entity);
|
|
607
|
+
if (schema == null) {
|
|
608
|
+
return 'name';
|
|
609
|
+
}
|
|
610
|
+
return LabelAnnotation.get(schema).pipe(
|
|
611
|
+
Option.flatMap((fields) => Option.fromNullable(fields[0])),
|
|
612
|
+
Option.getOrElse(() => 'name'),
|
|
613
|
+
);
|
|
614
|
+
};
|
|
615
|
+
|
|
560
616
|
/**
|
|
561
617
|
* Get the description of an entity.
|
|
562
618
|
* Accepts both reactive entities and snapshots.
|
|
@@ -14,6 +14,7 @@ import type * as Entity from '../../Entity';
|
|
|
14
14
|
import type * as Obj from '../../Obj';
|
|
15
15
|
import type * as Ref from '../../Ref';
|
|
16
16
|
import type * as Relation from '../../Relation';
|
|
17
|
+
import { getLabel } from '../Annotation';
|
|
17
18
|
import { subscribe } from '../common/proxy/reactive';
|
|
18
19
|
import { isEntity, getDatabase } from '../Entity';
|
|
19
20
|
import { RefTypeId } from '../Ref/ref';
|
|
@@ -242,3 +243,33 @@ export const makeRelation = <T extends Relation.Unknown>(relation: T): Atom.Atom
|
|
|
242
243
|
assertArgument(isEntity(relation), 'relation', 'Must be a reactive ECHO relation');
|
|
243
244
|
return relationFamily(relation);
|
|
244
245
|
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Atom family for an entity's label string.
|
|
249
|
+
* Fires only when the computed label actually changes, not on every entity mutation.
|
|
250
|
+
*/
|
|
251
|
+
const labelAtomFamily = Atom.family(<T extends Entity.Unknown>(entity: T): Atom.Atom<string | undefined> => {
|
|
252
|
+
return Atom.make<string | undefined>((get) => {
|
|
253
|
+
let previous = getLabel(entity);
|
|
254
|
+
|
|
255
|
+
const unsubscribe = subscribe(entity, () => {
|
|
256
|
+
const next = getLabel(entity);
|
|
257
|
+
if (next !== previous) {
|
|
258
|
+
previous = next;
|
|
259
|
+
get.setSelf(next);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
get.addFinalizer(() => unsubscribe());
|
|
264
|
+
return previous;
|
|
265
|
+
}).pipe(Atom.keepAlive);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Create a read-only atom for the label of a reactive ECHO entity.
|
|
270
|
+
* Re-evaluates on entity mutation; only propagates when the label string changes.
|
|
271
|
+
*/
|
|
272
|
+
export const makeLabelAtom = <T extends Entity.Unknown>(entity: T): Atom.Atom<string | undefined> => {
|
|
273
|
+
assertArgument(isEntity(entity), 'entity', 'Must be a reactive ECHO entity');
|
|
274
|
+
return labelAtomFamily(entity);
|
|
275
|
+
};
|
|
@@ -129,8 +129,8 @@ export const objectFromJSON = async (
|
|
|
129
129
|
const sourceDxn = jsonData[ATTR_RELATION_SOURCE] ?? raise(new TypeError('Missing relation source'));
|
|
130
130
|
const targetDxn = jsonData[ATTR_RELATION_TARGET] ?? raise(new TypeError('Missing relation target'));
|
|
131
131
|
|
|
132
|
-
const source = (await refResolver?.
|
|
133
|
-
const target = (await refResolver?.
|
|
132
|
+
const source = (await refResolver?.resolveLegacy(sourceDxn)) as AnyEntity | undefined;
|
|
133
|
+
const target = (await refResolver?.resolveLegacy(targetDxn)) as AnyEntity | undefined;
|
|
134
134
|
|
|
135
135
|
defineHiddenProperty(obj, KindId, EntityKind.Relation);
|
|
136
136
|
defineHiddenProperty(obj, RelationSourceDXNId, sourceDxn);
|
|
@@ -161,7 +161,7 @@ export const objectFromJSON = async (
|
|
|
161
161
|
|
|
162
162
|
if (jsonData[ATTR_PARENT]) {
|
|
163
163
|
const parentDxn = jsonData[ATTR_PARENT];
|
|
164
|
-
const resolvedParent = (await refResolver?.
|
|
164
|
+
const resolvedParent = (await refResolver?.resolveLegacy(parentDxn)) as Obj.Unknown | undefined;
|
|
165
165
|
defineHiddenProperty(obj, ParentId, resolvedParent);
|
|
166
166
|
} else if (parent) {
|
|
167
167
|
defineHiddenProperty(obj, ParentId, parent);
|
package/src/internal/Ref/ref.ts
CHANGED
|
@@ -81,7 +81,8 @@ export const getReferenceAst = (ast: SchemaAST.AST): RefereneAST | undefined =>
|
|
|
81
81
|
};
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
export const RefTypeId: unique symbol = Symbol.for('@dxos/echo/internal/Ref') as any;
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
88
|
* Reference Schema.
|
|
@@ -253,7 +254,7 @@ export declare namespace Ref {
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
Ref.isRef = (obj: any): obj is Ref<any> => {
|
|
256
|
-
return obj && typeof obj === 'object' && RefTypeId in obj;
|
|
257
|
+
return obj != null && typeof obj === 'object' && RefTypeId in obj;
|
|
257
258
|
};
|
|
258
259
|
|
|
259
260
|
Ref.hasEntityId = (id: EntityId) => (ref: Ref<any>) => {
|
|
@@ -278,7 +279,8 @@ Ref.make = <T extends AnyProperties>(obj: T): Ref<T> => {
|
|
|
278
279
|
const id = obj.id;
|
|
279
280
|
invariant(EntityId.isValid(id), 'Invalid object ID');
|
|
280
281
|
const uri = EID.make({ entityId: id });
|
|
281
|
-
|
|
282
|
+
const ref = new RefImpl(uri, obj);
|
|
283
|
+
return ref;
|
|
282
284
|
};
|
|
283
285
|
|
|
284
286
|
Ref.fromURI = (uri: URI.URI): Ref<any> => {
|
|
@@ -380,22 +382,79 @@ const getSchemaExpectedName = (ast: SchemaAST.Annotated): string | undefined =>
|
|
|
380
382
|
);
|
|
381
383
|
};
|
|
382
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Load-source ceiling for a resolution request. Cheaper tiers are always probed first; the
|
|
387
|
+
* ceiling caps how far a request is allowed to reach:
|
|
388
|
+
* - `working-set`: synchronous, in-memory only. A miss settles `unavailable` immediately.
|
|
389
|
+
* - `disk`: probe the working set, then local storage; never the network.
|
|
390
|
+
* - `network`: probe the working set, disk, then fetch from peers.
|
|
391
|
+
*
|
|
392
|
+
* `unavailable` is always relative to the requested ceiling (disk-unavailable ≠ network-unavailable).
|
|
393
|
+
*/
|
|
394
|
+
export type RefSource = 'working-set' | 'disk' | 'network';
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* A stateful, closure-aware handle to an in-flight (or settled) reference resolution.
|
|
398
|
+
*
|
|
399
|
+
* `ready` means the entity is FULLY USABLE: its own body and its strong-dependency closure are all
|
|
400
|
+
* materialized. The body-vs-closure split is internal; `requesting` transparently covers "a
|
|
401
|
+
* dependency is still loading".
|
|
402
|
+
*/
|
|
403
|
+
export interface RefResolverRequest {
|
|
404
|
+
/**
|
|
405
|
+
* `pending` is a one-way entry state (never re-entered). `requesting` means the body and/or
|
|
406
|
+
* closure is still loading. `ready` means fully usable. `unavailable` means unreachable at the
|
|
407
|
+
* requested ceiling.
|
|
408
|
+
*/
|
|
409
|
+
readonly state: 'pending' | 'requesting' | 'ready' | 'unavailable';
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Fires (deferred to a microtask) whenever {@link state} changes.
|
|
413
|
+
*/
|
|
414
|
+
readonly stateChanged: Event<void>;
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Synchronous snapshot of the resolved entity; defined iff `state === 'ready'`.
|
|
418
|
+
*/
|
|
419
|
+
getResult(): AnyProperties | undefined;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Settles when `state ∈ {ready, unavailable}`.
|
|
423
|
+
*/
|
|
424
|
+
wait(): Promise<AnyProperties | undefined>;
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Decrements the refcount on the shared load op(s); cancels the underlying IO at zero.
|
|
428
|
+
*/
|
|
429
|
+
abort(): void;
|
|
430
|
+
}
|
|
431
|
+
|
|
383
432
|
export interface RefResolver {
|
|
433
|
+
/**
|
|
434
|
+
* Resolve a reference, returning a stateful handle. `source` is a required ceiling; cheaper
|
|
435
|
+
* tiers are always probed first.
|
|
436
|
+
*/
|
|
437
|
+
resolve(uri: URI.URI, options: { source: RefSource }): RefResolverRequest;
|
|
438
|
+
|
|
384
439
|
/**
|
|
385
440
|
* Resolve ref synchronously from the objects in the working set.
|
|
386
441
|
*
|
|
387
442
|
* @param uri
|
|
388
443
|
* @param load If true the resolver should attempt to load the object from disk.
|
|
389
444
|
* @param onLoad Callback to call when the object is loaded.
|
|
445
|
+
* @deprecated Use {@link resolve} with `{ source: 'working-set' }`. Removed in Task 11.
|
|
390
446
|
*/
|
|
391
447
|
resolveSync(uri: URI.URI, load: boolean, onLoad?: () => void): AnyProperties | undefined;
|
|
392
448
|
|
|
393
449
|
/**
|
|
394
450
|
* Resolver ref asynchronously.
|
|
451
|
+
* @deprecated Use {@link resolve} with `{ source: 'network' }`. Removed in Task 11.
|
|
395
452
|
*/
|
|
396
|
-
|
|
453
|
+
resolveLegacy(uri: URI.URI): Promise<AnyProperties | undefined>;
|
|
397
454
|
|
|
398
|
-
|
|
455
|
+
/**
|
|
456
|
+
* @deprecated Use {@link resolve} + `Type.getSchema`. Removed in Task 11.
|
|
457
|
+
*/
|
|
399
458
|
resolveSchema(uri: URI.URI): Promise<Schema.Schema.AnyNoContext | undefined>;
|
|
400
459
|
|
|
401
460
|
/**
|
|
@@ -404,10 +463,26 @@ export interface RefResolver {
|
|
|
404
463
|
* via `Obj.getType` / `Entity.getType`. Optional — resolvers that only
|
|
405
464
|
* carry raw schemas may leave this unimplemented; the deserializer falls
|
|
406
465
|
* back to leaving the type entity unset.
|
|
466
|
+
* @deprecated Use {@link resolve}. Removed in Task 11.
|
|
407
467
|
*/
|
|
408
468
|
resolveType?(uri: URI.URI): Promise<unknown | undefined>;
|
|
409
469
|
}
|
|
410
470
|
|
|
471
|
+
/**
|
|
472
|
+
* A settled {@link RefResolverRequest} with a fixed `state` and `result`, used by resolvers
|
|
473
|
+
* whose backends are fully synchronous (e.g. {@link StaticRefResolver}).
|
|
474
|
+
*/
|
|
475
|
+
export const makeSettledRequest = (
|
|
476
|
+
state: RefResolverRequest['state'],
|
|
477
|
+
result: AnyProperties | undefined,
|
|
478
|
+
): RefResolverRequest => ({
|
|
479
|
+
state,
|
|
480
|
+
stateChanged: new Event<void>(),
|
|
481
|
+
getResult: () => (state === 'ready' ? result : undefined),
|
|
482
|
+
wait: async () => (state === 'ready' ? result : undefined),
|
|
483
|
+
abort: () => {},
|
|
484
|
+
});
|
|
485
|
+
|
|
411
486
|
export class RefImpl<T> implements Ref<T> {
|
|
412
487
|
#uri: URI.URI;
|
|
413
488
|
#resolver?: RefResolver = undefined;
|
|
@@ -465,7 +540,7 @@ export class RefImpl<T> implements Ref<T> {
|
|
|
465
540
|
return this.#target;
|
|
466
541
|
}
|
|
467
542
|
invariant(this.#resolver, 'Resolver is not set');
|
|
468
|
-
const obj = await this.#resolver.
|
|
543
|
+
const obj = await this.#resolver.resolveLegacy(this.#uri);
|
|
469
544
|
if (obj == null) {
|
|
470
545
|
throw new Error('Object not found');
|
|
471
546
|
}
|
|
@@ -480,7 +555,7 @@ export class RefImpl<T> implements Ref<T> {
|
|
|
480
555
|
return this.#target;
|
|
481
556
|
}
|
|
482
557
|
invariant(this.#resolver, 'Resolver is not set');
|
|
483
|
-
return (await this.#resolver.
|
|
558
|
+
return (await this.#resolver.resolveLegacy(this.#uri)) as T | undefined;
|
|
484
559
|
}
|
|
485
560
|
|
|
486
561
|
/**
|
|
@@ -624,27 +699,29 @@ export class StaticRefResolver implements RefResolver {
|
|
|
624
699
|
return this;
|
|
625
700
|
}
|
|
626
701
|
|
|
702
|
+
resolve(uri: URI.URI, _options: { source: RefSource }): RefResolverRequest {
|
|
703
|
+
const obj = this.#lookup(uri);
|
|
704
|
+
return makeSettledRequest(obj ? 'ready' : 'unavailable', obj);
|
|
705
|
+
}
|
|
706
|
+
|
|
627
707
|
resolveSync(uri: URI.URI, _load: boolean, _onLoad?: () => void): AnyProperties | undefined {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
if (id == null) {
|
|
631
|
-
return undefined;
|
|
632
|
-
}
|
|
708
|
+
return this.#lookup(uri);
|
|
709
|
+
}
|
|
633
710
|
|
|
634
|
-
|
|
711
|
+
async resolveLegacy(uri: URI.URI): Promise<AnyProperties | undefined> {
|
|
712
|
+
return this.#lookup(uri);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
async resolveSchema(uri: URI.URI): Promise<Schema.Schema.AnyNoContext | undefined> {
|
|
716
|
+
return this.schemas.get(uri);
|
|
635
717
|
}
|
|
636
718
|
|
|
637
|
-
|
|
719
|
+
#lookup(uri: URI.URI): AnyProperties | undefined {
|
|
638
720
|
const echoUri = EID.tryParse(uri);
|
|
639
721
|
const id = echoUri ? EID.getEntityId(echoUri) : undefined;
|
|
640
722
|
if (id == null) {
|
|
641
723
|
return undefined;
|
|
642
724
|
}
|
|
643
|
-
|
|
644
725
|
return this.objects.get(id);
|
|
645
726
|
}
|
|
646
|
-
|
|
647
|
-
async resolveSchema(uri: URI.URI): Promise<Schema.Schema.AnyNoContext | undefined> {
|
|
648
|
-
return this.schemas.get(uri);
|
|
649
|
-
}
|
|
650
727
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { EID, type SpaceId, type URI } from '@dxos/keys';
|
|
6
|
+
|
|
7
|
+
import { EntityKind } from '../common/types/entity';
|
|
8
|
+
import { RelationSourceDXNId, RelationTargetDXNId } from '../common/types/model-symbols';
|
|
9
|
+
import { ParentId, TypeId } from '../common/types/typename';
|
|
10
|
+
import { getObjectEchoUri } from '../Entity/util';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalized view of the references that make up an entity's strong-dependency set, store-agnostic
|
|
14
|
+
* so it can be filled from an {@link ObjectCore} (db) or a decoded entity (queue item / snapshot).
|
|
15
|
+
*/
|
|
16
|
+
export interface StrongDepRefs {
|
|
17
|
+
/** Entity kind — relations additionally depend on their source/target. */
|
|
18
|
+
kind: EntityKind;
|
|
19
|
+
/** Type reference URI (`echo:` for a persisted stored schema, `dxn:` for a static/registry type). */
|
|
20
|
+
type?: URI.URI;
|
|
21
|
+
/** Relation source reference URI. */
|
|
22
|
+
source?: URI.URI;
|
|
23
|
+
/** Relation target reference URI. */
|
|
24
|
+
target?: URI.URI;
|
|
25
|
+
/** Parent reference URI. */
|
|
26
|
+
parent?: URI.URI;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Direct strong-dependency URIs of an entity: the schema (only when stored as an object), the
|
|
31
|
+
* relation source/target, and the parent. Returns URIs (cross-space `echo:` EIDs and queue-item
|
|
32
|
+
* EIDs included); a persisted schema is an `echo:` URI, a static/registry type is a `dxn:` URI.
|
|
33
|
+
*
|
|
34
|
+
* A static/registry type (`dxn:`) is intentionally NOT a strong dep: it is always synchronously
|
|
35
|
+
* available through the registry, so gating on it would needlessly delay surfacing. Only a
|
|
36
|
+
* persisted (db-backed) schema, addressed by an `echo:` URI, must load before its instances.
|
|
37
|
+
*/
|
|
38
|
+
export const getStrongDependencyUris = (refs: StrongDepRefs, owningSpaceId?: SpaceId): URI.URI[] => {
|
|
39
|
+
const res: URI.URI[] = [];
|
|
40
|
+
|
|
41
|
+
if (refs.type != null && EID.tryParse(refs.type) != null) {
|
|
42
|
+
res.push(qualifyToSpace(refs.type, owningSpaceId));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (refs.kind === EntityKind.Relation) {
|
|
46
|
+
if (refs.source != null) {
|
|
47
|
+
res.push(qualifyToSpace(refs.source, owningSpaceId));
|
|
48
|
+
}
|
|
49
|
+
if (refs.target != null) {
|
|
50
|
+
res.push(qualifyToSpace(refs.target, owningSpaceId));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (refs.parent != null) {
|
|
55
|
+
res.push(qualifyToSpace(refs.parent, owningSpaceId));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return res;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Qualifies a space-less (relative) `echo:` dependency URI with the space of the entity that owns it.
|
|
63
|
+
* Same-space references are persisted relative; resolution needs them absolute to route to a single
|
|
64
|
+
* space. Already-qualified URIs (cross-space) and non-`echo:` URIs (registry `dxn:`) pass through.
|
|
65
|
+
*/
|
|
66
|
+
const qualifyToSpace = (uri: URI.URI, owningSpaceId: SpaceId | undefined): URI.URI => {
|
|
67
|
+
if (owningSpaceId == null) {
|
|
68
|
+
return uri;
|
|
69
|
+
}
|
|
70
|
+
const eid = EID.tryParse(uri);
|
|
71
|
+
if (eid == null || !EID.isLocal(eid)) {
|
|
72
|
+
return uri;
|
|
73
|
+
}
|
|
74
|
+
const entityId = EID.getEntityId(eid);
|
|
75
|
+
return entityId != null ? EID.make({ spaceId: owningSpaceId, entityId }) : uri;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Direct strong-dependency URIs of a live (decoded) entity — a queue item, cross-space proxy, or
|
|
80
|
+
* snapshot. Reads the reference URIs off the entity's hidden model symbols. ECHO db cores compute
|
|
81
|
+
* the same set from their raw encoded references via {@link getStrongDependencyUris}.
|
|
82
|
+
*/
|
|
83
|
+
export const getStrongDependencies = (entity: unknown): URI.URI[] => {
|
|
84
|
+
if (entity == null || typeof entity !== 'object') {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const props = entity as Record<symbol, unknown>;
|
|
88
|
+
const kind =
|
|
89
|
+
props[RelationSourceDXNId] != null || props[RelationTargetDXNId] != null ? EntityKind.Relation : EntityKind.Object;
|
|
90
|
+
const parent = props[ParentId];
|
|
91
|
+
|
|
92
|
+
// The entity's own space qualifies any relative dependency URIs it carries.
|
|
93
|
+
const selfUri = getObjectEchoUri(entity);
|
|
94
|
+
const owningSpaceId = selfUri != null ? EID.getSpaceId(selfUri) : undefined;
|
|
95
|
+
|
|
96
|
+
return getStrongDependencyUris(
|
|
97
|
+
{
|
|
98
|
+
kind,
|
|
99
|
+
type: typeof props[TypeId] === 'string' ? (props[TypeId] as URI.URI) : undefined,
|
|
100
|
+
source: props[RelationSourceDXNId] as URI.URI | undefined,
|
|
101
|
+
target: props[RelationTargetDXNId] as URI.URI | undefined,
|
|
102
|
+
// A parent is held as the resolved entity; derive its absolute URI for the dependency edge.
|
|
103
|
+
parent: parent != null ? getObjectEchoUri(parent) : undefined,
|
|
104
|
+
},
|
|
105
|
+
owningSpaceId,
|
|
106
|
+
);
|
|
107
|
+
};
|