@dxos/echo 0.10.0 → 0.11.1
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/Annotation.mjs +3 -0
- package/dist/lib/Blob.mjs +160 -0
- package/dist/lib/Blob.mjs.map +1 -0
- package/dist/lib/Database.mjs +2 -0
- package/dist/lib/Entity.mjs +231 -0
- package/dist/lib/Entity.mjs.map +1 -0
- package/dist/lib/Err.mjs +105 -0
- package/dist/lib/Err.mjs.map +1 -0
- package/dist/lib/Feed.mjs +338 -0
- package/dist/lib/Feed.mjs.map +1 -0
- package/dist/lib/Filter.mjs +380 -0
- package/dist/lib/Filter.mjs.map +1 -0
- package/dist/lib/Format.mjs +47 -0
- package/dist/lib/Format.mjs.map +1 -0
- package/dist/lib/JsonSchema.mjs +21 -0
- package/dist/lib/JsonSchema.mjs.map +1 -0
- package/dist/lib/Key.mjs +15 -0
- package/dist/lib/Key.mjs.map +1 -0
- package/dist/lib/Migration.mjs +38 -0
- package/dist/lib/Migration.mjs.map +1 -0
- package/dist/lib/Obj.mjs +660 -0
- package/dist/lib/Obj.mjs.map +1 -0
- package/dist/lib/Order.mjs +65 -0
- package/dist/lib/Order.mjs.map +1 -0
- package/dist/lib/Query.mjs +336 -0
- package/dist/lib/Query.mjs.map +1 -0
- package/dist/lib/QueryResult.mjs +7 -0
- package/dist/lib/QueryResult.mjs.map +1 -0
- package/dist/lib/Ref.mjs +44 -0
- package/dist/lib/Ref.mjs.map +1 -0
- package/dist/lib/Registry.mjs +35 -0
- package/dist/lib/Registry.mjs.map +1 -0
- package/dist/lib/Relation.mjs +397 -0
- package/dist/lib/Relation.mjs.map +1 -0
- package/dist/lib/Scope.mjs +51 -0
- package/dist/lib/Scope.mjs.map +1 -0
- package/dist/lib/Tag.mjs +70 -0
- package/dist/lib/Tag.mjs.map +1 -0
- package/dist/lib/Text.mjs +82 -0
- package/dist/lib/Text.mjs.map +1 -0
- package/dist/lib/Type.mjs +371 -0
- package/dist/lib/Type.mjs.map +1 -0
- package/dist/lib/chunk-Annotation.mjs +221 -0
- package/dist/lib/chunk-Annotation.mjs.map +1 -0
- package/dist/lib/chunk-Database.mjs +918 -0
- package/dist/lib/chunk-Database.mjs.map +1 -0
- package/dist/lib/chunk-atom-snapshot.mjs +249 -0
- package/dist/lib/chunk-atom-snapshot.mjs.map +1 -0
- package/dist/lib/chunk-internal.mjs +2775 -0
- package/dist/lib/chunk-internal.mjs.map +1 -0
- package/dist/lib/chunk-json-schema.mjs +660 -0
- package/dist/lib/chunk-json-schema.mjs.map +1 -0
- package/dist/lib/chunk-number.mjs +164 -0
- package/dist/lib/chunk-number.mjs.map +1 -0
- package/dist/lib/chunk-ref-array.mjs +31 -0
- package/dist/lib/chunk-ref-array.mjs.map +1 -0
- package/dist/lib/chunk-ref.mjs +590 -0
- package/dist/lib/chunk-ref.mjs.map +1 -0
- package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
- package/dist/lib/chunk-select.mjs +186 -0
- package/dist/lib/chunk-select.mjs.map +1 -0
- package/dist/lib/chunk-src.mjs +234 -0
- package/dist/lib/chunk-src.mjs.map +1 -0
- package/dist/lib/chunk-util.mjs +140 -0
- package/dist/lib/chunk-util.mjs.map +1 -0
- package/dist/lib/index.mjs +24 -0
- package/dist/lib/internal.mjs +10 -0
- package/dist/lib/testing.mjs +346 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/Aggregate.d.ts +86 -0
- package/dist/types/src/Aggregate.d.ts.map +1 -0
- package/dist/types/src/Blob.d.ts +153 -0
- package/dist/types/src/Blob.d.ts.map +1 -0
- package/dist/types/src/Blob.test.d.ts +2 -0
- package/dist/types/src/Blob.test.d.ts.map +1 -0
- package/dist/types/src/Database.d.ts +153 -4
- package/dist/types/src/Database.d.ts.map +1 -1
- package/dist/types/src/Err.d.ts +116 -0
- package/dist/types/src/Err.d.ts.map +1 -1
- package/dist/types/src/Feed.d.ts +116 -28
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Feed.test.d.ts +2 -0
- package/dist/types/src/Feed.test.d.ts.map +1 -0
- package/dist/types/src/Filter.d.ts +14 -6
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +18 -0
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Json.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +14 -1
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Order.d.ts +9 -3
- package/dist/types/src/Order.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts +107 -26
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/Relation.d.ts +6 -0
- package/dist/types/src/Relation.d.ts.map +1 -1
- package/dist/types/src/Type.d.ts.map +1 -1
- package/dist/types/src/View.d.ts +1 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/api.d.ts +6 -0
- package/dist/types/src/internal/Entity/api.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +3 -2
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Filter/match.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts +34 -8
- package/dist/types/src/internal/JsonSchema/json-schema-type.d.ts.map +1 -1
- package/dist/types/src/internal/JsonSchema/json-schema.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/atoms.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/json-serializer.d.ts.map +1 -1
- package/dist/types/src/internal/Query/pretty.d.ts.map +1 -1
- package/dist/types/src/internal/Query/query-result-effect.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/make-object.d.ts +13 -0
- package/dist/types/src/internal/common/proxy/make-object.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/reactive.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/symbols.d.ts.map +1 -1
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts +12 -0
- package/dist/types/src/internal/common/proxy/typed-handler.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/index.d.ts +1 -0
- package/dist/types/src/internal/common/types/index.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/model-symbols.d.ts +5 -0
- package/dist/types/src/internal/common/types/model-symbols.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/projection.d.ts +32 -0
- package/dist/types/src/internal/common/types/projection.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +40 -35
- package/src/Aggregate.ts +105 -0
- package/src/Blob.test.ts +45 -0
- package/src/Blob.ts +205 -0
- package/src/Database.ts +207 -17
- package/src/Err.ts +36 -0
- package/src/Feed.test.ts +188 -0
- package/src/Feed.ts +238 -62
- package/src/Filter.ts +28 -10
- package/src/Hypergraph.ts +18 -0
- package/src/Json.ts +1 -0
- package/src/Obj.test.ts +2 -2
- package/src/Obj.ts +21 -1
- package/src/Order.ts +16 -9
- package/src/Query.test.ts +319 -2
- package/src/Query.ts +165 -51
- package/src/Relation.ts +14 -0
- package/src/Type.ts +5 -6
- package/src/index.ts +2 -0
- package/src/internal/Entity/api.ts +11 -1
- package/src/internal/Entity/model.ts +3 -0
- package/src/internal/Filter/match.ts +30 -1
- package/src/internal/JsonSchema/json-schema-type.ts +14 -2
- package/src/internal/JsonSchema/json-schema.test.ts +28 -1
- package/src/internal/JsonSchema/json-schema.ts +33 -6
- package/src/internal/Obj/atoms.ts +4 -2
- package/src/internal/Obj/json-serializer.test.ts +162 -2
- package/src/internal/Obj/json-serializer.ts +6 -0
- package/src/internal/Query/pretty.ts +19 -1
- package/src/internal/Query/query-result-effect.ts +2 -4
- package/src/internal/Ref/atoms.ts +1 -1
- package/src/internal/common/proxy/change-context.ts +1 -1
- package/src/internal/common/proxy/make-object.ts +42 -3
- package/src/internal/common/proxy/reactive.ts +5 -0
- package/src/internal/common/proxy/symbols.ts +1 -0
- package/src/internal/common/proxy/typed-handler.ts +71 -29
- package/src/internal/common/types/index.ts +1 -0
- package/src/internal/common/types/model-symbols.ts +6 -0
- package/src/internal/common/types/projection.ts +45 -0
- package/src/testing/test-data.ts +14 -14
- package/dist/lib/neutral/Annotation.mjs +0 -90
- package/dist/lib/neutral/Annotation.mjs.map +0 -7
- package/dist/lib/neutral/Database.mjs +0 -42
- package/dist/lib/neutral/Database.mjs.map +0 -7
- package/dist/lib/neutral/Entity.mjs +0 -76
- package/dist/lib/neutral/Entity.mjs.map +0 -7
- package/dist/lib/neutral/Err.mjs +0 -16
- package/dist/lib/neutral/Err.mjs.map +0 -7
- package/dist/lib/neutral/Feed.mjs +0 -50
- package/dist/lib/neutral/Feed.mjs.map +0 -7
- package/dist/lib/neutral/Filter.mjs +0 -72
- package/dist/lib/neutral/Filter.mjs.map +0 -7
- package/dist/lib/neutral/Format.mjs +0 -82
- package/dist/lib/neutral/Format.mjs.map +0 -7
- package/dist/lib/neutral/JsonSchema.mjs +0 -18
- package/dist/lib/neutral/JsonSchema.mjs.map +0 -7
- package/dist/lib/neutral/Key.mjs +0 -16
- package/dist/lib/neutral/Key.mjs.map +0 -7
- package/dist/lib/neutral/Migration.mjs +0 -18
- package/dist/lib/neutral/Migration.mjs.map +0 -7
- package/dist/lib/neutral/Obj.mjs +0 -128
- package/dist/lib/neutral/Obj.mjs.map +0 -7
- package/dist/lib/neutral/Order.mjs +0 -16
- package/dist/lib/neutral/Order.mjs.map +0 -7
- package/dist/lib/neutral/Query.mjs +0 -38
- package/dist/lib/neutral/Query.mjs.map +0 -7
- package/dist/lib/neutral/QueryResult.mjs +0 -2
- package/dist/lib/neutral/QueryResult.mjs.map +0 -7
- package/dist/lib/neutral/Ref.mjs +0 -25
- package/dist/lib/neutral/Ref.mjs.map +0 -7
- package/dist/lib/neutral/Registry.mjs +0 -14
- package/dist/lib/neutral/Registry.mjs.map +0 -7
- package/dist/lib/neutral/Relation.mjs +0 -97
- package/dist/lib/neutral/Relation.mjs.map +0 -7
- package/dist/lib/neutral/Scope.mjs +0 -12
- package/dist/lib/neutral/Scope.mjs.map +0 -7
- package/dist/lib/neutral/Tag.mjs +0 -32
- package/dist/lib/neutral/Tag.mjs.map +0 -7
- package/dist/lib/neutral/Text.mjs +0 -25
- package/dist/lib/neutral/Text.mjs.map +0 -7
- package/dist/lib/neutral/Type.mjs +0 -65
- package/dist/lib/neutral/Type.mjs.map +0 -7
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs +0 -1394
- package/dist/lib/neutral/chunk-2HKGAQM4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs +0 -95
- package/dist/lib/neutral/chunk-2ZHECXUR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs +0 -336
- package/dist/lib/neutral/chunk-3KTE2Y7Q.mjs.map +0 -7
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs +0 -714
- package/dist/lib/neutral/chunk-3TN2VMI4.mjs.map +0 -7
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs +0 -104
- package/dist/lib/neutral/chunk-42L4HJ3Z.mjs.map +0 -7
- package/dist/lib/neutral/chunk-4A2NWKRJ.mjs +0 -747
- package/dist/lib/neutral/chunk-4A2NWKRJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-7LOUAPYZ.mjs +0 -25
- package/dist/lib/neutral/chunk-7LOUAPYZ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs +0 -1531
- package/dist/lib/neutral/chunk-ACFAOTKJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BEFR7LEA.mjs +0 -170
- package/dist/lib/neutral/chunk-BEFR7LEA.mjs.map +0 -7
- package/dist/lib/neutral/chunk-BXIKXT7Q.mjs +0 -89
- package/dist/lib/neutral/chunk-BXIKXT7Q.mjs.map +0 -7
- package/dist/lib/neutral/chunk-C57XMMBY.mjs +0 -206
- package/dist/lib/neutral/chunk-C57XMMBY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-C62JYFAD.mjs +0 -183
- package/dist/lib/neutral/chunk-C62JYFAD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-FSGX2R62.mjs +0 -48
- package/dist/lib/neutral/chunk-FSGX2R62.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs +0 -459
- package/dist/lib/neutral/chunk-HBXT3GSB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-IQR45HAD.mjs +0 -31
- package/dist/lib/neutral/chunk-IQR45HAD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +0 -10
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +0 -7
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs +0 -424
- package/dist/lib/neutral/chunk-KCBW4BR6.mjs.map +0 -7
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs +0 -575
- package/dist/lib/neutral/chunk-L3E5IXAE.mjs.map +0 -7
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs +0 -56
- package/dist/lib/neutral/chunk-PRC4FEKJ.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs +0 -351
- package/dist/lib/neutral/chunk-SCT6FFCR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs +0 -75
- package/dist/lib/neutral/chunk-TOCVVFK2.mjs.map +0 -7
- package/dist/lib/neutral/chunk-U53LSQQA.mjs +0 -41
- package/dist/lib/neutral/chunk-U53LSQQA.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UGFFV2FR.mjs +0 -27
- package/dist/lib/neutral/chunk-UGFFV2FR.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs +0 -7
- package/dist/lib/neutral/chunk-UWLP4RFM.mjs.map +0 -7
- package/dist/lib/neutral/chunk-UZHCQ6NI.mjs +0 -283
- package/dist/lib/neutral/chunk-UZHCQ6NI.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WISOH2XH.mjs +0 -36
- package/dist/lib/neutral/chunk-WISOH2XH.mjs.map +0 -7
- package/dist/lib/neutral/chunk-WKIT2YY5.mjs +0 -34
- package/dist/lib/neutral/chunk-WKIT2YY5.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs +0 -271
- package/dist/lib/neutral/chunk-XB6ISRAO.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XXL42WCB.mjs +0 -84
- package/dist/lib/neutral/chunk-XXL42WCB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-XXWK4J53.mjs +0 -117
- package/dist/lib/neutral/chunk-XXWK4J53.mjs.map +0 -7
- package/dist/lib/neutral/index.mjs +0 -115
- package/dist/lib/neutral/index.mjs.map +0 -7
- package/dist/lib/neutral/internal/index.mjs +0 -581
- package/dist/lib/neutral/internal/index.mjs.map +0 -7
- package/dist/lib/neutral/meta.json +0 -1
- package/dist/lib/neutral/testing/index.mjs +0 -477
- package/dist/lib/neutral/testing/index.mjs.map +0 -7
package/src/Feed.ts
CHANGED
|
@@ -11,17 +11,19 @@ import * as Layer from 'effect/Layer';
|
|
|
11
11
|
import type * as Option from 'effect/Option';
|
|
12
12
|
import * as Schema from 'effect/Schema';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { KEY_QUEUE_POSITION } from '@dxos/echo-protocol';
|
|
15
|
+
import { invariant } from '@dxos/invariant';
|
|
16
|
+
import { DXN, EID, EntityId } from '@dxos/keys';
|
|
15
17
|
|
|
16
18
|
import * as Annotation from './Annotation';
|
|
17
19
|
import * as Database from './Database';
|
|
18
20
|
import type * as Entity from './Entity';
|
|
19
21
|
import type * as Filter from './Filter';
|
|
20
22
|
import * as internal from './internal';
|
|
21
|
-
import * as queryInternal from './internal/Query';
|
|
22
23
|
import * as Obj from './Obj';
|
|
23
|
-
import
|
|
24
|
+
import * as Query from './Query';
|
|
24
25
|
import type * as QueryResult from './QueryResult';
|
|
26
|
+
import * as Scope from './Scope';
|
|
25
27
|
import * as Type from './Type';
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -36,6 +38,7 @@ export class Feed extends Type.makeObject<Feed>(DXN.make('org.dxos.type.feed', '
|
|
|
36
38
|
Schema.Struct({
|
|
37
39
|
/** User-facing display name. */
|
|
38
40
|
name: Schema.String.pipe(Schema.optional),
|
|
41
|
+
|
|
39
42
|
/** Identifier for the feed's kind (e.g., plugin id). */
|
|
40
43
|
kind: Schema.String.pipe(internal.FormInputAnnotation.set(false), Schema.optional),
|
|
41
44
|
|
|
@@ -46,6 +49,20 @@ export class Feed extends Type.makeObject<Feed>(DXN.make('org.dxos.type.feed', '
|
|
|
46
49
|
* - `trace`: Trace feed.
|
|
47
50
|
*/
|
|
48
51
|
namespace: Schema.optional(Schema.Literal('data', 'trace')),
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Earliest item a pending rewind discards — set when a soft fork is decided but not yet expressed,
|
|
55
|
+
* because the writer may be a different process than the one that decided it.
|
|
56
|
+
*
|
|
57
|
+
* Stored as the first *discarded* item rather than the new parent so that rewinding to the very
|
|
58
|
+
* first item needs no sentinel: nothing precedes it, so the continuation simply starts a new line.
|
|
59
|
+
* Readers show what precedes it; the next append parents to that and clears this.
|
|
60
|
+
*
|
|
61
|
+
* Transient intent, never a source of truth for history. The fork itself lives in item lineage
|
|
62
|
+
* (see {@link PARENT_KEY}), which is what {@link history} walks and what replicates in a defined
|
|
63
|
+
* order relative to the blocks.
|
|
64
|
+
*/
|
|
65
|
+
rewindFrom: Schema.optional(Obj.ID.pipe(internal.FormInputAnnotation.set(false))),
|
|
49
66
|
}).pipe(
|
|
50
67
|
internal.HiddenAnnotation.set(true),
|
|
51
68
|
Annotation.IconAnnotation.set({ icon: 'ph--rows--regular', hue: 'yellow' }),
|
|
@@ -66,6 +83,12 @@ export interface Cursor<T = Obj.Snapshot> {
|
|
|
66
83
|
|
|
67
84
|
/**
|
|
68
85
|
* Retention options for a feed.
|
|
86
|
+
*
|
|
87
|
+
* Live feed objects persist every `Obj.update` as a whole-object re-append reusing the object's id
|
|
88
|
+
* (see `EchoFeedCodec.encode`), so superseded blocks accumulate indefinitely — retention/compaction
|
|
89
|
+
* of those superseded blocks (driven by these options, once implemented) is the reclaim mechanism.
|
|
90
|
+
* TODO(wittjosiah): Implement when feed retention is supported (see `setRetention` below and
|
|
91
|
+
* `FeedStore.appendLocal`).
|
|
69
92
|
*/
|
|
70
93
|
export interface RetentionOptions {
|
|
71
94
|
/** Retain items after this cursor position. */
|
|
@@ -73,6 +96,42 @@ export interface RetentionOptions {
|
|
|
73
96
|
cursor?: string;
|
|
74
97
|
}
|
|
75
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Options for {@link append}.
|
|
101
|
+
*/
|
|
102
|
+
export interface AppendOptions {
|
|
103
|
+
/**
|
|
104
|
+
* Explicit lineage parent for the appended items — the soft-fork point.
|
|
105
|
+
* Applies to the first item only; the rest of the batch chain implicitly in append order.
|
|
106
|
+
*/
|
|
107
|
+
parent?: Entity.Unknown | Entity.Snapshot | EntityId;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Options for {@link history}.
|
|
112
|
+
*/
|
|
113
|
+
export interface HistoryOptions {
|
|
114
|
+
/**
|
|
115
|
+
* Item to walk the history from, as git's `HEAD` selects which commits are reachable.
|
|
116
|
+
* Defaults to the last item, so the most recently appended line of items wins.
|
|
117
|
+
*/
|
|
118
|
+
head?: Entity.Unknown | Entity.Snapshot | EntityId;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The items reachable from a head by following lineage — a feed's equivalent of
|
|
123
|
+
* `git log --first-parent`.
|
|
124
|
+
*/
|
|
125
|
+
export interface History<T> {
|
|
126
|
+
/** The reachable items, in append order. */
|
|
127
|
+
items: T[];
|
|
128
|
+
/**
|
|
129
|
+
* A parent was referenced but could not be resolved, so the walk stopped at a boundary and
|
|
130
|
+
* earlier history is missing — the same condition as a git shallow clone.
|
|
131
|
+
*/
|
|
132
|
+
shallow: boolean;
|
|
133
|
+
}
|
|
134
|
+
|
|
76
135
|
/**
|
|
77
136
|
* Sync options for a feed.
|
|
78
137
|
*/
|
|
@@ -110,50 +169,6 @@ export class ContextFeedService extends Context.Tag('@dxos/echo/Feed/ContextFeed
|
|
|
110
169
|
static layer = (feed: Feed) => Layer.succeed(ContextFeedService, { feed });
|
|
111
170
|
}
|
|
112
171
|
|
|
113
|
-
//
|
|
114
|
-
// Service
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Effect service for feed operations.
|
|
119
|
-
* @deprecated Use `Database.Service` instead — feed operations now run directly on the database.
|
|
120
|
-
*/
|
|
121
|
-
export class FeedService extends Context.Tag('@dxos/echo/Feed/FeedService')<
|
|
122
|
-
FeedService,
|
|
123
|
-
{
|
|
124
|
-
append(feed: Feed, items: Entity.Unknown[]): Promise<void>;
|
|
125
|
-
remove(feed: Feed, ids: string[]): Promise<void>;
|
|
126
|
-
query: {
|
|
127
|
-
<Q extends Query.Any>(feed: Feed, query: Q): QueryResult.QueryResult<Query.Type<Q>>;
|
|
128
|
-
<F extends Filter.Any>(feed: Feed, filter: F): QueryResult.QueryResult<Filter.Type<F>>;
|
|
129
|
-
};
|
|
130
|
-
sync(feed: Feed, options?: SyncOptions): Promise<void>;
|
|
131
|
-
getSyncState(feed: Feed): Promise<SyncState>;
|
|
132
|
-
}
|
|
133
|
-
>() {}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Layer that provides a `FeedService` that throws when accessed.
|
|
137
|
-
* @deprecated Use `Database.layer(db)` instead.
|
|
138
|
-
*/
|
|
139
|
-
export const notAvailable: Layer.Layer<FeedService> = Layer.succeed(FeedService, {
|
|
140
|
-
append: () => {
|
|
141
|
-
throw new Error('Feed.FeedService not available');
|
|
142
|
-
},
|
|
143
|
-
remove: () => {
|
|
144
|
-
throw new Error('Feed.FeedService not available');
|
|
145
|
-
},
|
|
146
|
-
query: () => {
|
|
147
|
-
throw new Error('Feed.FeedService not available') as never;
|
|
148
|
-
},
|
|
149
|
-
sync: () => {
|
|
150
|
-
throw new Error('Feed.FeedService not available');
|
|
151
|
-
},
|
|
152
|
-
getSyncState: () => {
|
|
153
|
-
throw new Error('Feed.FeedService not available');
|
|
154
|
-
},
|
|
155
|
-
} as Context.Tag.Service<FeedService>);
|
|
156
|
-
|
|
157
172
|
//
|
|
158
173
|
// Factory
|
|
159
174
|
//
|
|
@@ -172,9 +187,12 @@ export const make = (props: Obj.MakeProps<typeof Feed> = {}): Feed => Obj.make(F
|
|
|
172
187
|
/**
|
|
173
188
|
* Returns the feed object's EID when the feed is stored in a space.
|
|
174
189
|
*
|
|
175
|
-
*
|
|
190
|
+
* Private-ish and on track to be removed — prefer resolving feed scopes via higher-level APIs
|
|
191
|
+
* (e.g. `Feed.query`) rather than threading the raw queue URI. Used internally by the feed service
|
|
192
|
+
* layer.
|
|
176
193
|
*/
|
|
177
|
-
|
|
194
|
+
// TODO(dmaretskyi): Remove — private-ish, prefer higher-level feed-scope APIs.
|
|
195
|
+
export const getFeedUri = (feed: Feed): EID.EID | undefined => EID.tryParse(Obj.getURI(feed));
|
|
178
196
|
|
|
179
197
|
//
|
|
180
198
|
// Operations
|
|
@@ -183,15 +201,33 @@ export const getQueueUri = (feed: Feed): EID.EID | undefined => EID.tryParse(Obj
|
|
|
183
201
|
/**
|
|
184
202
|
* Appends items to a feed.
|
|
185
203
|
*
|
|
204
|
+
* Pass `options.parent` to soft-fork the feed: the first appended item continues from that item
|
|
205
|
+
* rather than from the feed's tip, so everything appended between the two becomes unreachable from
|
|
206
|
+
* {@link history}. Nothing is removed from the log.
|
|
207
|
+
*
|
|
186
208
|
* @example
|
|
187
209
|
* ```ts
|
|
188
210
|
* yield* Feed.append(feed, [Obj.make(Notification, { title: 'Hello' })]);
|
|
211
|
+
*
|
|
212
|
+
* // Continue from an earlier item, leaving what followed it unreachable.
|
|
213
|
+
* yield* Feed.append(feed, [Obj.make(Notification, { title: 'Take two' })], { parent: earlier });
|
|
189
214
|
* ```
|
|
190
215
|
*/
|
|
191
|
-
export const append = (
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
216
|
+
export const append = (
|
|
217
|
+
feed: Feed,
|
|
218
|
+
items: Entity.Unknown[],
|
|
219
|
+
options?: AppendOptions,
|
|
220
|
+
): Effect.Effect<void, never, Database.Service> =>
|
|
221
|
+
Database.Service.pipe(
|
|
222
|
+
Effect.flatMap(({ db }) =>
|
|
223
|
+
Effect.promise(() => {
|
|
224
|
+
if (options?.parent !== undefined && items.length > 0) {
|
|
225
|
+
setParent(items[0], options.parent);
|
|
226
|
+
}
|
|
227
|
+
return db.appendToFeed(feed, items);
|
|
228
|
+
}),
|
|
229
|
+
),
|
|
230
|
+
).pipe(Effect.withSpan('Feed.append'));
|
|
195
231
|
|
|
196
232
|
/**
|
|
197
233
|
* Removes items from a feed.
|
|
@@ -217,6 +253,147 @@ export const remove = (
|
|
|
217
253
|
),
|
|
218
254
|
).pipe(Effect.withSpan('Feed.remove'));
|
|
219
255
|
|
|
256
|
+
//
|
|
257
|
+
// Lineage (soft fork)
|
|
258
|
+
//
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Foreign-key source for an item's explicit lineage parent within a feed.
|
|
262
|
+
*
|
|
263
|
+
* Lineage is carried in item `@meta` because feed items are arbitrary ECHO objects that the feed
|
|
264
|
+
* does not own and cannot extend; `KEY_QUEUE_POSITION` rides along with feed blocks the same way.
|
|
265
|
+
*/
|
|
266
|
+
export const PARENT_KEY = 'org.dxos.key.feed-parent';
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Tri-state read of the lineage key, distinguishing an absent key from a present but malformed one.
|
|
270
|
+
* A replicated id that does not parse must not read as "no parent" — that would resolve a fork as
|
|
271
|
+
* an implicit continuation and silently expose the items it abandoned.
|
|
272
|
+
*/
|
|
273
|
+
const readParent = (item: Entity.Unknown | Entity.Snapshot): { present: boolean; id?: EntityId } => {
|
|
274
|
+
const id = internal.getKeys(item, PARENT_KEY).at(0)?.id;
|
|
275
|
+
if (id === undefined) {
|
|
276
|
+
return { present: false };
|
|
277
|
+
}
|
|
278
|
+
return EntityId.isValid(id) ? { present: true, id } : { present: true };
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Foreign-key source for the global position a position authority assigned a feed item.
|
|
283
|
+
* Exposed alongside {@link getPosition} so callers can stamp or inspect it without reaching for
|
|
284
|
+
* `@dxos/protocols`.
|
|
285
|
+
*/
|
|
286
|
+
export const POSITION_KEY = KEY_QUEUE_POSITION;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The global position a feed item was assigned, or `+Infinity` when it has none — a block written
|
|
290
|
+
* locally and not yet acknowledged, which sorts last because it is the newest.
|
|
291
|
+
*
|
|
292
|
+
* Use this to put items into append order before calling {@link history}: a query returns an unordered
|
|
293
|
+
* set, and `created` is a wall clock that peers do not agree on.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* const inAppendOrder = Array.sort(messages, Order.mapInput(Order.number, Feed.getPosition));
|
|
298
|
+
* const { items } = Feed.history(inAppendOrder);
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
export const getPosition = (item: Entity.Unknown | Entity.Snapshot): number => {
|
|
302
|
+
const key = internal.getKeys(item, KEY_QUEUE_POSITION).at(0)?.id;
|
|
303
|
+
const position = key !== undefined ? Number(key) : Number.NaN;
|
|
304
|
+
return Number.isNaN(position) ? Number.POSITIVE_INFINITY : position;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Returns an item's explicit lineage parent, or `undefined` when it continues from the item that
|
|
309
|
+
* precedes it in append order (the default for every feed).
|
|
310
|
+
*
|
|
311
|
+
* Also `undefined` for a malformed stored id; {@link history} tells the two apart and reports
|
|
312
|
+
* a malformed parent as truncation.
|
|
313
|
+
*/
|
|
314
|
+
export const getParent = (item: Entity.Unknown | Entity.Snapshot): EntityId | undefined => readParent(item).id;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Sets (or, with `undefined`, clears) an item's explicit lineage parent.
|
|
318
|
+
* Call before appending the item; {@link append}'s `parent` option does this for you.
|
|
319
|
+
*/
|
|
320
|
+
export const setParent = (
|
|
321
|
+
item: Entity.Unknown,
|
|
322
|
+
parent: Entity.Unknown | Entity.Snapshot | EntityId | undefined,
|
|
323
|
+
): void => {
|
|
324
|
+
internal.change(item, (mutable) => {
|
|
325
|
+
internal.deleteKeys(mutable, PARENT_KEY);
|
|
326
|
+
if (parent !== undefined) {
|
|
327
|
+
const id = typeof parent === 'string' ? parent : parent.id;
|
|
328
|
+
internal.getMetaChecked(mutable).keys.push(internal.foreignKey(PARENT_KEY, id));
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Returns the items reachable from a head by following lineage — a feed's `git log --first-parent`.
|
|
335
|
+
*
|
|
336
|
+
* Walks backwards from the head: an item with an explicit lineage parent jumps to that item, leaving
|
|
337
|
+
* everything appended in between unreachable; an item without one steps to its predecessor. So a
|
|
338
|
+
* feed appended as `M1, M2, M3, M4, M5(M3)` yields `M1, M2, M3, M5`, with `M4` unreachable but still
|
|
339
|
+
* present in the log — the log is the object store, this function is what HEAD reaches.
|
|
340
|
+
*
|
|
341
|
+
* `items` must be in **append order** — the walk is positional, and pre-sorting by a wall-clock
|
|
342
|
+
* field such as `created` would corrupt it. Lineage is resolved over exactly the list passed in, so
|
|
343
|
+
* a parent excluded by the caller's filter reads as absent (`shallow`), same as one that has not
|
|
344
|
+
* replicated yet or whose stored id does not parse.
|
|
345
|
+
*
|
|
346
|
+
* @example
|
|
347
|
+
* ```ts
|
|
348
|
+
* const messages = yield* Feed.query(feed, Filter.type(Message.Message)).run;
|
|
349
|
+
* const { items, shallow } = Feed.history(messages);
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
export const history = <T extends Entity.Unknown | Entity.Snapshot>(
|
|
353
|
+
items: readonly T[],
|
|
354
|
+
options?: HistoryOptions,
|
|
355
|
+
): History<T> => {
|
|
356
|
+
const indexById = new Map<EntityId, number>();
|
|
357
|
+
items.forEach((item, index) => indexById.set(item.id, index));
|
|
358
|
+
|
|
359
|
+
const head = options?.head;
|
|
360
|
+
const headIndex = head === undefined ? items.length - 1 : indexById.get(typeof head === 'string' ? head : head.id);
|
|
361
|
+
if (headIndex === undefined) {
|
|
362
|
+
return { items: [], shallow: true };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
let cursor = headIndex;
|
|
366
|
+
const reachable: T[] = [];
|
|
367
|
+
let shallow = false;
|
|
368
|
+
while (cursor >= 0) {
|
|
369
|
+
const item = items[cursor];
|
|
370
|
+
reachable.push(item);
|
|
371
|
+
|
|
372
|
+
const parent = readParent(item);
|
|
373
|
+
if (!parent.present) {
|
|
374
|
+
cursor -= 1;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (parent.id === undefined) {
|
|
378
|
+
// Key present but unparseable — a fork whose target cannot be identified, so stop rather than
|
|
379
|
+
// fall through to the predecessor and make what this item superseded reachable again.
|
|
380
|
+
shallow = true;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// A parent at or after its child is a cycle or a forward reference (possible with arbitrary
|
|
385
|
+
// multi-writer data); requiring the cursor to strictly decrease also guarantees termination.
|
|
386
|
+
const parentIndex = indexById.get(parent.id);
|
|
387
|
+
if (parentIndex === undefined || parentIndex >= cursor) {
|
|
388
|
+
shallow = true;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
cursor = parentIndex;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return { items: reachable.reverse(), shallow };
|
|
395
|
+
};
|
|
396
|
+
|
|
220
397
|
/**
|
|
221
398
|
* Creates a reactive query over items in a feed.
|
|
222
399
|
*
|
|
@@ -227,7 +404,7 @@ export const remove = (
|
|
|
227
404
|
* Supports both data-first and data-last (curried) forms; the latter composes with `pipe`.
|
|
228
405
|
*
|
|
229
406
|
* In non-Effect code, query a feed directly through the database with a feed scope:
|
|
230
|
-
* `db.query(Query.select(filter).from(Scope.feed(Feed.
|
|
407
|
+
* `db.query(Query.select(filter).from(Scope.feed(Feed.getFeedUri(feed))))`.
|
|
231
408
|
*
|
|
232
409
|
* @example
|
|
233
410
|
* ```ts
|
|
@@ -250,13 +427,12 @@ export const query: {
|
|
|
250
427
|
<F extends Filter.Any>(
|
|
251
428
|
filter: F,
|
|
252
429
|
): (feed: Feed) => QueryResult.QueryResultEffect<Filter.Type<F>, never, Database.Service>;
|
|
253
|
-
} = Function.dual(2, (feed: Feed, queryOrFilter: Query.Any | Filter.Any) =>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
);
|
|
430
|
+
} = Function.dual(2, (feed: Feed, queryOrFilter: Query.Any | Filter.Any) => {
|
|
431
|
+
const feedUri = getFeedUri(feed);
|
|
432
|
+
invariant(feedUri, 'Feed must be stored in the database before accessing its contents');
|
|
433
|
+
const query = Query.is(queryOrFilter) ? queryOrFilter : Query.select(queryOrFilter);
|
|
434
|
+
return Database.query(query.from(Scope.feed(feedUri.toString())));
|
|
435
|
+
});
|
|
260
436
|
|
|
261
437
|
/**
|
|
262
438
|
* Syncs the feed with the server.
|
|
@@ -273,7 +449,7 @@ export const sync = (feed: Feed, options?: SyncOptions): Effect.Effect<void, nev
|
|
|
273
449
|
);
|
|
274
450
|
|
|
275
451
|
/**
|
|
276
|
-
* Returns
|
|
452
|
+
* Returns the feed's replication backlog for its namespace.
|
|
277
453
|
*
|
|
278
454
|
* @example
|
|
279
455
|
* ```ts
|
package/src/Filter.ts
CHANGED
|
@@ -20,11 +20,15 @@ import type * as Obj from './Obj';
|
|
|
20
20
|
import * as Ref from './Ref';
|
|
21
21
|
// eslint-disable-next-line @dxos/rules/import-as-namespace
|
|
22
22
|
import type * as Type$ from './Type';
|
|
23
|
+
|
|
24
|
+
export const FilterTypeId = '~@dxos/echo/Filter' as const;
|
|
25
|
+
export type FilterTypeId = typeof FilterTypeId;
|
|
26
|
+
|
|
23
27
|
export interface Filter<T> {
|
|
24
28
|
// TODO(dmaretskyi): See new effect-schema approach to variance.
|
|
25
|
-
|
|
29
|
+
readonly [FilterTypeId]: { value: Types.Covariant<T> };
|
|
26
30
|
|
|
27
|
-
|
|
31
|
+
ast: QueryAST.Filter;
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
export type Props<T> = {
|
|
@@ -37,15 +41,15 @@ export type Any = Filter<any>;
|
|
|
37
41
|
export type Type<F extends Any> = F extends Filter<infer T> ? T : never;
|
|
38
42
|
|
|
39
43
|
class FilterClass implements Any {
|
|
40
|
-
private static 'variance': Any[
|
|
44
|
+
private static 'variance': Any[FilterTypeId] = {} as Any[FilterTypeId];
|
|
41
45
|
|
|
42
|
-
|
|
46
|
+
constructor(public readonly ast: QueryAST.Filter) {}
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
[FilterTypeId] = FilterClass.variance;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export const is = (value: unknown): value is Any => {
|
|
48
|
-
return typeof value === 'object' && value !== null &&
|
|
52
|
+
return typeof value === 'object' && value !== null && FilterTypeId in value;
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
/** Construct a filter from an ast. */
|
|
@@ -307,13 +311,27 @@ export const lte = <T>(value: T): Filter<T | undefined> => {
|
|
|
307
311
|
};
|
|
308
312
|
|
|
309
313
|
/**
|
|
310
|
-
* Predicate for property to be in the provided array
|
|
311
|
-
*
|
|
314
|
+
* Predicate for property to be in the provided array, or in the set of values projected
|
|
315
|
+
* from a subquery's results (an uncorrelated `col IN (SELECT property FROM ...)` semi-join —
|
|
316
|
+
* see `Query.project`). The subquery may target a different scope than the parent query; it
|
|
317
|
+
* is resolved once at execution time.
|
|
318
|
+
* @param values - Values to check against, or a single subquery projection.
|
|
312
319
|
*/
|
|
313
|
-
const in
|
|
320
|
+
const in$: {
|
|
321
|
+
<T>(projection: internal.Projection<T>): Filter<T>;
|
|
322
|
+
<T>(...values: T[]): Filter<T>;
|
|
323
|
+
} = <T>(...args: [internal.Projection<T>] | T[]): Filter<T> => {
|
|
324
|
+
if (args.length === 1 && internal.isProjection(args[0])) {
|
|
325
|
+
const projection = args[0];
|
|
326
|
+
return new FilterClass({
|
|
327
|
+
type: 'in-query',
|
|
328
|
+
subquery: projection.query,
|
|
329
|
+
property: projection.property,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
314
332
|
return new FilterClass({
|
|
315
333
|
type: 'in',
|
|
316
|
-
values,
|
|
334
|
+
values: args,
|
|
317
335
|
});
|
|
318
336
|
};
|
|
319
337
|
export { in$ as in };
|
package/src/Hypergraph.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type CleanupFn } from '@dxos/async';
|
|
6
|
+
import { type BlobBackend } from '@dxos/echo-protocol';
|
|
5
7
|
import { type URI } from '@dxos/keys';
|
|
6
8
|
|
|
7
9
|
import type * as Database from './Database';
|
|
@@ -72,4 +74,20 @@ export interface Hypergraph extends Database.Queryable {
|
|
|
72
74
|
* @returns The database for the given space ID, or undefined if not found.
|
|
73
75
|
*/
|
|
74
76
|
getDatabase(spaceId: Key.SpaceId): Database.Database | undefined;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Registers a pluggable blob storage backend under `name`, claiming its declared URI schemes.
|
|
80
|
+
* Registering a scheme already claimed by another backend is an error.
|
|
81
|
+
*
|
|
82
|
+
* @param options.default - When true, `name` becomes the storage used when
|
|
83
|
+
* `Blob.fromBytes`'s `storage` option is omitted.
|
|
84
|
+
* @returns A cleanup function that unregisters the backend.
|
|
85
|
+
*/
|
|
86
|
+
registerBlobBackend(name: string, backend: BlobBackend, options?: { default?: boolean }): CleanupFn;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Storage name `Blob.fromBytes` uses when its `storage` option is omitted. Starts as `'inline'`;
|
|
90
|
+
* reflects the most recent `registerBlobBackend(name, backend, { default: true })` call.
|
|
91
|
+
*/
|
|
92
|
+
get defaultBlobStorage(): string;
|
|
75
93
|
}
|
package/src/Json.ts
CHANGED
|
@@ -47,6 +47,7 @@ const toJson = (obj: Obj.Any): unknown => (typeof (obj as any).toJSON === 'funct
|
|
|
47
47
|
* Note: ECHO objects' `toJSON` runs before the replacer is invoked, so by the time we see a
|
|
48
48
|
* value refs are already encoded as `{ "/": "dxn:..." }`.
|
|
49
49
|
*/
|
|
50
|
+
// TODO(dmaretskyi): is this used anywhere?
|
|
50
51
|
export const createRefReplacer = ({ db, depth = 1 }: CreateRefReplacerOptions): JsonReplacer => {
|
|
51
52
|
// Per-object hop count. Set when we return an object (via ref resolution or pass-through) so
|
|
52
53
|
// the child callbacks (which carry that object as `this`) can read it.
|
package/src/Obj.test.ts
CHANGED
|
@@ -123,10 +123,10 @@ describe('Obj', () => {
|
|
|
123
123
|
expect(EID.isEID(uri)).toBe(true);
|
|
124
124
|
});
|
|
125
125
|
|
|
126
|
-
test("prefer: 'relative' returns local EID echo
|
|
126
|
+
test("prefer: 'relative' returns local EID echo:///<id>", ({ expect }) => {
|
|
127
127
|
const obj = Obj.make(TestSchema.Person, { name: 'Alice' });
|
|
128
128
|
const uri = Obj.getURI(obj, { prefer: 'relative' });
|
|
129
|
-
expect(uri).toMatch(/^echo
|
|
129
|
+
expect(uri).toMatch(/^echo:\/\/\/[^/]/);
|
|
130
130
|
expect(EID.isLocal(EID.parse(uri))).toBe(true);
|
|
131
131
|
});
|
|
132
132
|
|
package/src/Obj.ts
CHANGED
|
@@ -380,8 +380,9 @@ export type Mutable<T> = internal.Mutable<T>;
|
|
|
380
380
|
*
|
|
381
381
|
* Note: Only accepts objects. Use `Relation.update` for relations.
|
|
382
382
|
*/
|
|
383
|
-
export const update = <T extends Unknown>(obj: T, callback: internal.ChangeCallback<T>):
|
|
383
|
+
export const update = <T extends Unknown>(obj: T, callback: internal.ChangeCallback<T>): T => {
|
|
384
384
|
internal.change(obj, callback);
|
|
385
|
+
return obj;
|
|
385
386
|
};
|
|
386
387
|
|
|
387
388
|
/**
|
|
@@ -576,6 +577,24 @@ export const getTypename = (entity: Unknown | Snapshot): string | undefined => i
|
|
|
576
577
|
export const getDatabase = (entity: Entity.Unknown | Entity.Snapshot): Database.Database | undefined =>
|
|
577
578
|
internal.getDatabase(entity);
|
|
578
579
|
|
|
580
|
+
/**
|
|
581
|
+
* Get the branch this object instance is bound to: `'main'` for the canonical object, or the branch of
|
|
582
|
+
* a `db.branch()` independent instance. The branch is a property of the instance — two instances of the
|
|
583
|
+
* same object id on different branches each report their own branch.
|
|
584
|
+
*/
|
|
585
|
+
export const getBranch = (obj: Unknown): string => internal.getBranch(obj);
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Get an immutable snapshot of the object at the given historical heads — a detached instance, not a
|
|
589
|
+
* pin on the live object. Only the surface that asks for it sees the historical value; the live
|
|
590
|
+
* object and every other surface are unaffected. The functional alternative to a read-time-travel pin.
|
|
591
|
+
*/
|
|
592
|
+
export const getVersion = <T extends Unknown>(obj: T, heads: readonly string[]): Snapshot<T> => {
|
|
593
|
+
const db = getDatabase(obj);
|
|
594
|
+
invariant(db, 'object is not bound to a database');
|
|
595
|
+
return db.getVersion(obj, heads);
|
|
596
|
+
};
|
|
597
|
+
|
|
579
598
|
//
|
|
580
599
|
// Meta
|
|
581
600
|
//
|
|
@@ -897,6 +916,7 @@ export const toJSON = (entity: Unknown | Snapshot): JSON => objInternal.objectTo
|
|
|
897
916
|
* @param options.refResolver - Resolver for references. Produces hydrated references that can be resolved.
|
|
898
917
|
* @param options.uri - Override object URI. Changes the result of `Obj.getURI`.
|
|
899
918
|
* @param options.database - Database to associate with the object.
|
|
919
|
+
* @param options.parent - Parent entity to associate with the object (used when the JSON has no `@parent`). Changes the result of `Obj.getParent`.
|
|
900
920
|
*/
|
|
901
921
|
export const fromJSON: (
|
|
902
922
|
json: unknown,
|
package/src/Order.ts
CHANGED
|
@@ -6,28 +6,35 @@
|
|
|
6
6
|
|
|
7
7
|
import { type QueryAST } from '@dxos/echo-protocol';
|
|
8
8
|
|
|
9
|
+
export const OrderTypeId = '~@dxos/echo/Order' as const;
|
|
10
|
+
export type OrderTypeId = typeof OrderTypeId;
|
|
11
|
+
|
|
9
12
|
export interface Order<T> {
|
|
10
|
-
|
|
11
|
-
'~Order': { value: T };
|
|
13
|
+
readonly [OrderTypeId]: { value: T };
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
ast: QueryAST.Order;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export type Any = Order<any>;
|
|
17
19
|
|
|
18
20
|
class OrderClass implements Order<any> {
|
|
19
|
-
private static 'variance': Order<any>[
|
|
21
|
+
private static 'variance': Order<any>[OrderTypeId] = {} as Order<any>[OrderTypeId];
|
|
20
22
|
|
|
21
|
-
static
|
|
22
|
-
return typeof value === 'object' && value !== null &&
|
|
23
|
+
static is(value: unknown): value is Any {
|
|
24
|
+
return typeof value === 'object' && value !== null && OrderTypeId in value;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
constructor(public readonly ast: QueryAST.Order) {}
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
[OrderTypeId] = OrderClass.variance;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Order by the database's default order. For non-feed sources this is by id; for feed sources
|
|
34
|
+
* this is insertion order, so `desc` reads newest-first. Defaults to `asc`.
|
|
35
|
+
*/
|
|
36
|
+
export const natural = (direction: QueryAST.OrderDirection = 'asc'): Order<any> =>
|
|
37
|
+
new OrderClass({ kind: 'natural', direction });
|
|
31
38
|
export const property = <T>(property: keyof T & string, direction: QueryAST.OrderDirection): Order<T> =>
|
|
32
39
|
new OrderClass({
|
|
33
40
|
kind: 'property',
|