@dxos/echo 0.8.4-main.03d5cd7b56 → 0.8.4-main.05e74ebcff
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/LICENSE +102 -5
- package/README.md +1 -1
- package/dist/lib/neutral/Database.mjs +1 -1
- package/dist/lib/neutral/Entity.mjs +6 -6
- package/dist/lib/neutral/Feed.mjs +14 -12
- package/dist/lib/neutral/Filter.mjs +9 -7
- package/dist/lib/neutral/JsonSchema.mjs +4 -4
- package/dist/lib/neutral/Migration.mjs +6 -6
- package/dist/lib/neutral/Obj.mjs +8 -8
- package/dist/lib/neutral/Query.mjs +12 -12
- package/dist/lib/neutral/Ref.mjs +6 -4
- package/dist/lib/neutral/Relation.mjs +9 -9
- package/dist/lib/neutral/Tag.mjs +10 -10
- package/dist/lib/neutral/Type.mjs +6 -6
- package/dist/lib/neutral/{chunk-6VIJV543.mjs → chunk-APHSOTIX.mjs} +2 -2
- package/dist/lib/neutral/{chunk-RF7ZBZ4A.mjs → chunk-APJKDGFL.mjs} +16 -5
- package/dist/lib/neutral/chunk-APJKDGFL.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-MPAI2MHO.mjs → chunk-BMB7IHGB.mjs} +2 -2
- package/dist/lib/neutral/{chunk-SUZMWP3Y.mjs → chunk-FIWO2FZK.mjs} +1 -1
- package/dist/lib/neutral/chunk-FIWO2FZK.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-SJKBWMJY.mjs → chunk-G54OX4IX.mjs} +34 -18
- package/dist/lib/neutral/chunk-G54OX4IX.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-FHYIM4RD.mjs → chunk-I2DARWPX.mjs} +1 -1
- package/dist/lib/neutral/chunk-I2DARWPX.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-SW5CUSBY.mjs → chunk-J54QMAKF.mjs} +5 -4
- package/dist/lib/neutral/{chunk-SW5CUSBY.mjs.map → chunk-J54QMAKF.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-QXIANHKU.mjs → chunk-MGSQGHOD.mjs} +8 -8
- package/dist/lib/neutral/chunk-MGSQGHOD.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-O5LRY6CO.mjs → chunk-MLS7U7AT.mjs} +2 -2
- package/dist/lib/neutral/{chunk-VYAGNFSJ.mjs → chunk-N7VOEPSV.mjs} +6 -3
- package/dist/lib/neutral/{chunk-VYAGNFSJ.mjs.map → chunk-N7VOEPSV.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-FZHVQEHN.mjs → chunk-PSZBLH53.mjs} +2 -2
- package/dist/lib/neutral/{chunk-WRVRDZDA.mjs → chunk-PT37DG2F.mjs} +4 -4
- package/dist/lib/neutral/{chunk-HPNQTEEQ.mjs → chunk-Q2KKKJSV.mjs} +3 -3
- package/dist/lib/neutral/{chunk-VGKLHHRT.mjs → chunk-Q7ZL2P5H.mjs} +18 -16
- package/dist/lib/neutral/{chunk-VGKLHHRT.mjs.map → chunk-Q7ZL2P5H.mjs.map} +3 -3
- package/dist/lib/neutral/{chunk-QGMIH2SN.mjs → chunk-QRZ2I3ZM.mjs} +2 -2
- package/dist/lib/neutral/{chunk-LVGOVFDV.mjs → chunk-SCPFDS2E.mjs} +2 -2
- package/dist/lib/neutral/{chunk-S7IMFVTB.mjs → chunk-ZFACXBY6.mjs} +19 -15
- package/dist/lib/neutral/chunk-ZFACXBY6.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +17 -17
- package/dist/lib/neutral/internal/index.mjs +5 -5
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/testing/index.mjs +17 -17
- package/dist/types/src/Database.d.ts +1 -1
- package/dist/types/src/Feed.d.ts +36 -12
- package/dist/types/src/Feed.d.ts.map +1 -1
- package/dist/types/src/Filter.d.ts +21 -0
- package/dist/types/src/Filter.d.ts.map +1 -1
- package/dist/types/src/Hypergraph.d.ts +3 -3
- package/dist/types/src/Hypergraph.d.ts.map +1 -1
- package/dist/types/src/Migration.d.ts +15 -3
- package/dist/types/src/Migration.d.ts.map +1 -1
- package/dist/types/src/Obj.d.ts +1 -1
- package/dist/types/src/Obj.d.ts.map +1 -1
- package/dist/types/src/Query.d.ts.map +1 -1
- package/dist/types/src/Ref.d.ts +1 -0
- package/dist/types/src/Ref.d.ts.map +1 -1
- package/dist/types/src/internal/Entity/model.d.ts +2 -0
- package/dist/types/src/internal/Entity/model.d.ts.map +1 -1
- package/dist/types/src/internal/Obj/ids.d.ts +1 -1
- package/dist/types/src/internal/Query.d.ts.map +1 -1
- package/dist/types/src/internal/common/types/meta.d.ts +10 -0
- package/dist/types/src/internal/common/types/meta.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -14
- package/src/Database.ts +1 -1
- package/src/Feed.ts +55 -24
- package/src/Filter.ts +31 -0
- package/src/Hypergraph.ts +3 -3
- package/src/Migration.ts +19 -7
- package/src/Obj.ts +1 -0
- package/src/Query.test.ts +16 -16
- package/src/Query.ts +12 -7
- package/src/Ref.ts +2 -0
- package/src/internal/Entity/model.ts +2 -0
- package/src/internal/Obj/ids.ts +1 -1
- package/src/internal/Obj/json-serializer.ts +9 -9
- package/src/internal/Query.ts +23 -4
- package/src/internal/common/proxy/reactive.test.ts +1 -1
- package/src/internal/common/types/meta.ts +12 -0
- package/dist/lib/neutral/chunk-FHYIM4RD.mjs.map +0 -7
- package/dist/lib/neutral/chunk-QXIANHKU.mjs.map +0 -7
- package/dist/lib/neutral/chunk-RF7ZBZ4A.mjs.map +0 -7
- package/dist/lib/neutral/chunk-S7IMFVTB.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SJKBWMJY.mjs.map +0 -7
- package/dist/lib/neutral/chunk-SUZMWP3Y.mjs.map +0 -7
- /package/dist/lib/neutral/{chunk-6VIJV543.mjs.map → chunk-APHSOTIX.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-MPAI2MHO.mjs.map → chunk-BMB7IHGB.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-O5LRY6CO.mjs.map → chunk-MLS7U7AT.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-FZHVQEHN.mjs.map → chunk-PSZBLH53.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-WRVRDZDA.mjs.map → chunk-PT37DG2F.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-HPNQTEEQ.mjs.map → chunk-Q2KKKJSV.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-QGMIH2SN.mjs.map → chunk-QRZ2I3ZM.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-LVGOVFDV.mjs.map → chunk-SCPFDS2E.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/echo",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.05e74ebcff",
|
|
4
4
|
"description": "ECHO API",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/dxos/dxos"
|
|
10
10
|
},
|
|
11
|
-
"license": "
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
12
12
|
"author": "info@dxos.org",
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"type": "module",
|
|
@@ -135,18 +135,18 @@
|
|
|
135
135
|
],
|
|
136
136
|
"dependencies": {
|
|
137
137
|
"effect": "3.20.0",
|
|
138
|
-
"@dxos/
|
|
139
|
-
"@dxos/
|
|
140
|
-
"@dxos/
|
|
141
|
-
"@dxos/echo-protocol": "0.8.4-main.
|
|
142
|
-
"@dxos/
|
|
143
|
-
"@dxos/invariant": "0.8.4-main.
|
|
144
|
-
"@dxos/
|
|
145
|
-
"@dxos/
|
|
146
|
-
"@dxos/
|
|
147
|
-
"@dxos/protocols": "0.8.4-main.
|
|
148
|
-
"@dxos/
|
|
149
|
-
"@dxos/
|
|
138
|
+
"@dxos/async": "0.8.4-main.05e74ebcff",
|
|
139
|
+
"@dxos/context": "0.8.4-main.05e74ebcff",
|
|
140
|
+
"@dxos/debug": "0.8.4-main.05e74ebcff",
|
|
141
|
+
"@dxos/echo-protocol": "0.8.4-main.05e74ebcff",
|
|
142
|
+
"@dxos/errors": "0.8.4-main.05e74ebcff",
|
|
143
|
+
"@dxos/invariant": "0.8.4-main.05e74ebcff",
|
|
144
|
+
"@dxos/keys": "0.8.4-main.05e74ebcff",
|
|
145
|
+
"@dxos/log": "0.8.4-main.05e74ebcff",
|
|
146
|
+
"@dxos/node-std": "0.8.4-main.05e74ebcff",
|
|
147
|
+
"@dxos/protocols": "0.8.4-main.05e74ebcff",
|
|
148
|
+
"@dxos/util": "0.8.4-main.05e74ebcff",
|
|
149
|
+
"@dxos/effect": "0.8.4-main.05e74ebcff"
|
|
150
150
|
},
|
|
151
151
|
"publishConfig": {
|
|
152
152
|
"access": "public"
|
package/src/Database.ts
CHANGED
package/src/Feed.ts
CHANGED
|
@@ -10,7 +10,7 @@ import * as Layer from 'effect/Layer';
|
|
|
10
10
|
import type * as Option from 'effect/Option';
|
|
11
11
|
import * as Schema from 'effect/Schema';
|
|
12
12
|
|
|
13
|
-
import { DXN
|
|
13
|
+
import { DXN } from '@dxos/keys';
|
|
14
14
|
|
|
15
15
|
import * as Annotation from './Annotation';
|
|
16
16
|
import type * as Entity from './Entity';
|
|
@@ -80,6 +80,16 @@ export interface RetentionOptions {
|
|
|
80
80
|
cursor?: string;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Sync options for a feed.
|
|
85
|
+
*/
|
|
86
|
+
export interface SyncOptions {
|
|
87
|
+
/** Push local changes to the server. Defaults to true. */
|
|
88
|
+
shouldPush?: boolean;
|
|
89
|
+
/** Pull remote changes from the server. Defaults to true. */
|
|
90
|
+
shouldPull?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
//
|
|
84
94
|
// Factory
|
|
85
95
|
//
|
|
@@ -109,27 +119,6 @@ export const getQueueDxn = (feed: Feed): DXN | undefined => {
|
|
|
109
119
|
return new DXN(DXN.kind.QUEUE, [feed.namespace ?? 'data', self.spaceId, self.echoId]);
|
|
110
120
|
};
|
|
111
121
|
|
|
112
|
-
/**
|
|
113
|
-
* Creates a Feed object from a queue DXN, inferring the feed's id and namespace from the DXN parts.
|
|
114
|
-
*
|
|
115
|
-
* The resulting Feed, when added to the same space as the queue, will have a queue DXN
|
|
116
|
-
* equal to the input (see `Feed.getQueueDxn`). Useful when migrating `Ref(Queue)` fields to
|
|
117
|
-
* `Ref(Feed.Feed)`.
|
|
118
|
-
*
|
|
119
|
-
* @remarks Unsafe because the caller must ensure the queue DXN's space matches the database
|
|
120
|
-
* the feed is added to; the feed id is set from the queue id, bypassing id generation.
|
|
121
|
-
*/
|
|
122
|
-
export const unsafeFromQueueDXN = (queueDxn: DXN): Feed => {
|
|
123
|
-
const parts = queueDxn.asQueueDXN();
|
|
124
|
-
if (!parts) {
|
|
125
|
-
throw new Error(`Expected a queue DXN, got: ${queueDxn.toString()}`);
|
|
126
|
-
}
|
|
127
|
-
return Obj.make(Feed, {
|
|
128
|
-
id: parts.queueId as ObjectId,
|
|
129
|
-
namespace: parts.subspaceTag === 'trace' ? 'trace' : undefined,
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
|
|
133
122
|
//
|
|
134
123
|
// Service
|
|
135
124
|
//
|
|
@@ -160,6 +149,11 @@ export class FeedService extends Context.Tag('@dxos/echo/Feed/FeedService')<
|
|
|
160
149
|
<Q extends Query.Any>(feed: Feed, query: Q): QueryResult.QueryResult<Query.Type<Q>>;
|
|
161
150
|
<F extends Filter.Any>(feed: Feed, filter: F): QueryResult.QueryResult<Filter.Type<F>>;
|
|
162
151
|
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Syncs the feed with the server.
|
|
155
|
+
*/
|
|
156
|
+
sync(feed: Feed, options?: SyncOptions): Promise<void>;
|
|
163
157
|
}
|
|
164
158
|
>() {}
|
|
165
159
|
|
|
@@ -187,8 +181,30 @@ export const notAvailable: Layer.Layer<FeedService> = Layer.succeed(FeedService,
|
|
|
187
181
|
query: () => {
|
|
188
182
|
throw new Error('Feed.FeedService not available');
|
|
189
183
|
},
|
|
184
|
+
sync: () => {
|
|
185
|
+
throw new Error('Feed.FeedService not available');
|
|
186
|
+
},
|
|
190
187
|
} as Context.Tag.Service<FeedService>);
|
|
191
188
|
|
|
189
|
+
//
|
|
190
|
+
// Context (per-call) service
|
|
191
|
+
//
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Effect service exposing a single `Feed.Feed` as the "current" feed for a call site.
|
|
195
|
+
*
|
|
196
|
+
* @deprecated Prefer threading a `Feed.Feed` explicitly through function signatures
|
|
197
|
+
* over hiding it behind a context service.
|
|
198
|
+
*/
|
|
199
|
+
export class ContextFeedService extends Context.Tag('@dxos/echo/Feed/ContextFeedService')<
|
|
200
|
+
ContextFeedService,
|
|
201
|
+
{
|
|
202
|
+
readonly feed: Feed;
|
|
203
|
+
}
|
|
204
|
+
>() {
|
|
205
|
+
static layer = (feed: Feed): Layer.Layer<ContextFeedService> => Layer.succeed(ContextFeedService, { feed });
|
|
206
|
+
}
|
|
207
|
+
|
|
192
208
|
//
|
|
193
209
|
// Operations
|
|
194
210
|
//
|
|
@@ -262,6 +278,21 @@ export const runQuery: {
|
|
|
262
278
|
} = (feed: Feed, queryOrFilter: Query.Any | Filter.Any) =>
|
|
263
279
|
query(feed, queryOrFilter as any).pipe(Effect.flatMap((queryResult) => Effect.promise(() => queryResult.run())));
|
|
264
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Syncs the feed with the server.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* ```ts
|
|
286
|
+
* yield* Feed.sync(feed);
|
|
287
|
+
* yield* Feed.sync(feed, { shouldPush: false });
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export const sync = (feed: Feed, options?: SyncOptions): Effect.Effect<void, never, FeedService> =>
|
|
291
|
+
Effect.gen(function* () {
|
|
292
|
+
const service = yield* FeedService;
|
|
293
|
+
yield* Effect.promise(() => service.sync(feed, options));
|
|
294
|
+
});
|
|
295
|
+
|
|
265
296
|
/**
|
|
266
297
|
* Creates a cursor for iterating over feed items.
|
|
267
298
|
* Currently stubbed — cursor operations are not yet implemented.
|
|
@@ -292,13 +323,13 @@ export const nextOption = <T = Obj.Snapshot>(_cursor: Cursor<T>): Effect.Effect<
|
|
|
292
323
|
|
|
293
324
|
/**
|
|
294
325
|
* Sets the local retention policy for a feed.
|
|
295
|
-
* Currently stubbed —
|
|
326
|
+
* Currently stubbed — feeds do not yet support retention.
|
|
296
327
|
*
|
|
297
328
|
* @example
|
|
298
329
|
* ```ts
|
|
299
330
|
* yield* Feed.setRetention(feed, { count: 1000 });
|
|
300
331
|
* ```
|
|
301
332
|
*/
|
|
302
|
-
// TODO(
|
|
333
|
+
// TODO(dmaretskyi): Implement when feed retention is supported.
|
|
303
334
|
export const setRetention = (_feed: Feed, _options: RetentionOptions): Effect.Effect<void, never, FeedService> =>
|
|
304
335
|
Effect.void;
|
package/src/Filter.ts
CHANGED
|
@@ -158,6 +158,37 @@ export const tag = (tag: string): Any => {
|
|
|
158
158
|
});
|
|
159
159
|
};
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Options for {@link key} filter.
|
|
163
|
+
*/
|
|
164
|
+
export type KeyFilterOptions = {
|
|
165
|
+
/**
|
|
166
|
+
* Optional semver range expression (e.g. `^1.2.3`, `~2.0.0`, `>=1.0.0 <2.0.0`).
|
|
167
|
+
* Matches the object's meta `version` field against the range.
|
|
168
|
+
* If omitted, matches any version (including objects with no version).
|
|
169
|
+
*/
|
|
170
|
+
version?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Filter by registry key stored in object meta.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* Filter.key('org.example.type.foo');
|
|
179
|
+
* Filter.key('org.example.type.foo', { version: '^1.2.3' });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export const key = (key: string, options?: KeyFilterOptions): Any => {
|
|
183
|
+
return new FilterClass({
|
|
184
|
+
type: 'object',
|
|
185
|
+
typename: null,
|
|
186
|
+
props: {},
|
|
187
|
+
metaKey: key,
|
|
188
|
+
metaVersion: options?.version,
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
161
192
|
/**
|
|
162
193
|
* Filter by properties.
|
|
163
194
|
*/
|
package/src/Hypergraph.ts
CHANGED
|
@@ -22,10 +22,10 @@ export interface RefResolutionContext {
|
|
|
22
22
|
space?: Key.SpaceId;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* This
|
|
25
|
+
* Feed that the resolution is happening from (as the underlying queue DXN).
|
|
26
|
+
* This feed will be searched first, and then the space it belongs to.
|
|
27
27
|
*/
|
|
28
|
-
|
|
28
|
+
feed?: DXN;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export interface RefResolverOptions {
|
package/src/Migration.ts
CHANGED
|
@@ -10,19 +10,31 @@ import { type DXN } from '@dxos/keys';
|
|
|
10
10
|
|
|
11
11
|
import type * as Database from './Database';
|
|
12
12
|
import type * as Entity from './Entity';
|
|
13
|
-
import { getSchemaDXN } from './internal';
|
|
13
|
+
import { MetaId, type ObjectMeta, getSchemaDXN } from './internal';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result returned by a migration's `transform` callback.
|
|
17
|
+
* The data shape matches the target schema; the optional `[Obj.Meta]` symbol key lets the
|
|
18
|
+
* transform update the object's meta (e.g. `key` / `version`) atomically with the data swap.
|
|
19
|
+
*/
|
|
20
|
+
export type TransformResult<To extends Schema.Schema.AnyNoContext> = Omit<
|
|
21
|
+
Schema.Schema.Type<To>,
|
|
22
|
+
'id' | Entity.KindId
|
|
23
|
+
> & {
|
|
24
|
+
[MetaId]?: Partial<ObjectMeta>;
|
|
25
|
+
};
|
|
14
26
|
|
|
15
27
|
type DefineObjectMigrationOptions<From extends Schema.Schema.AnyNoContext, To extends Schema.Schema.AnyNoContext> = {
|
|
16
28
|
from: From;
|
|
17
29
|
to: To;
|
|
18
30
|
/**
|
|
19
31
|
* Pure function that converts the old object data to the new object data.
|
|
32
|
+
*
|
|
33
|
+
* The returned object may include an optional `[Obj.Meta]` entry to update the object's meta
|
|
34
|
+
* (e.g. registry `key` / `version`) atomically with the data swap.
|
|
20
35
|
*/
|
|
21
36
|
// TODO(dmaretskyi): `id` should not be a part of the schema.
|
|
22
|
-
transform: (
|
|
23
|
-
from: Schema.Schema.Type<From>,
|
|
24
|
-
context: ObjectMigrationContext,
|
|
25
|
-
) => Promise<Omit<Schema.Schema.Type<To>, 'id' | Entity.KindId>>;
|
|
37
|
+
transform: (from: Schema.Schema.Type<From>, context: ObjectMigrationContext) => Promise<TransformResult<To>>;
|
|
26
38
|
|
|
27
39
|
/**
|
|
28
40
|
* Callback that is called after the object is migrated. Called for every object that is migrated.
|
|
@@ -30,7 +42,7 @@ type DefineObjectMigrationOptions<From extends Schema.Schema.AnyNoContext, To ex
|
|
|
30
42
|
* NOTE: Database mutations performed in this callback are not guaranteed to be idempotent.
|
|
31
43
|
* If multiple peers run the migration separately, the effects may be applied multiple times.
|
|
32
44
|
*/
|
|
33
|
-
onMigration
|
|
45
|
+
onMigration?: (params: OnMigrateProps<From, To>) => Promise<void>;
|
|
34
46
|
};
|
|
35
47
|
|
|
36
48
|
/**
|
|
@@ -55,7 +67,7 @@ export type ObjectMigration = {
|
|
|
55
67
|
fromSchema: Schema.Schema.AnyNoContext;
|
|
56
68
|
toSchema: Schema.Schema.AnyNoContext;
|
|
57
69
|
transform: (from: unknown, context: ObjectMigrationContext) => Promise<unknown>;
|
|
58
|
-
onMigration
|
|
70
|
+
onMigration?: (params: OnMigrateProps<any, any>) => Promise<void>;
|
|
59
71
|
};
|
|
60
72
|
|
|
61
73
|
/**
|
package/src/Obj.ts
CHANGED
|
@@ -664,6 +664,7 @@ export const setParent = (entity: Unknown, parent: Any | undefined) => {
|
|
|
664
664
|
assumeType<internal.InternalObjectProps>(entity);
|
|
665
665
|
assumeType<internal.InternalObjectProps | undefined>(parent);
|
|
666
666
|
entity[internal.ParentId] = parent;
|
|
667
|
+
return entity;
|
|
667
668
|
};
|
|
668
669
|
|
|
669
670
|
interface UpdateFromOptions<T> {
|
package/src/Query.test.ts
CHANGED
|
@@ -523,7 +523,7 @@ describe('query api', () => {
|
|
|
523
523
|
"from": {
|
|
524
524
|
"_tag": "scope",
|
|
525
525
|
"scope": {
|
|
526
|
-
"
|
|
526
|
+
"allFeedsFromSpaces": true,
|
|
527
527
|
},
|
|
528
528
|
},
|
|
529
529
|
"query": {
|
|
@@ -563,17 +563,17 @@ describe('query api', () => {
|
|
|
563
563
|
test('Query.type(...).from(feed) sets queue scope', async () => {
|
|
564
564
|
const spaceId = SpaceId.random();
|
|
565
565
|
const feedId = ObjectId.random();
|
|
566
|
-
const
|
|
566
|
+
const feedDXN = DXN.parse(`dxn:echo:${spaceId}:${feedId}`);
|
|
567
567
|
const feed = (await Obj.fromJSON(
|
|
568
568
|
{
|
|
569
569
|
'@type': 'dxn:type:org.dxos.type.feed:0.1.0',
|
|
570
570
|
id: feedId,
|
|
571
571
|
name: 'test-feed',
|
|
572
572
|
},
|
|
573
|
-
{ dxn:
|
|
573
|
+
{ dxn: feedDXN },
|
|
574
574
|
)) as Feed.Feed;
|
|
575
575
|
|
|
576
|
-
const
|
|
576
|
+
const expectedQueueDXN = new DXN(DXN.kind.QUEUE, ['data', spaceId, feedId]);
|
|
577
577
|
|
|
578
578
|
const query = Query.type(TestSchema.Person).from(feed);
|
|
579
579
|
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
@@ -582,7 +582,7 @@ describe('query api', () => {
|
|
|
582
582
|
from: {
|
|
583
583
|
_tag: 'scope',
|
|
584
584
|
scope: {
|
|
585
|
-
|
|
585
|
+
feeds: [expectedQueueDXN.toString()],
|
|
586
586
|
},
|
|
587
587
|
},
|
|
588
588
|
query: {
|
|
@@ -686,13 +686,13 @@ describe('query api', () => {
|
|
|
686
686
|
|
|
687
687
|
describe('Filter.childOf', () => {
|
|
688
688
|
test('childOf with Ref', () => {
|
|
689
|
-
const
|
|
690
|
-
const parentRef = Ref.fromDXN(
|
|
689
|
+
const parentDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
|
|
690
|
+
const parentRef = Ref.fromDXN(parentDXN);
|
|
691
691
|
const filter = Filter.childOf(parentRef);
|
|
692
692
|
|
|
693
693
|
expect(filter.ast).toMatchObject({
|
|
694
694
|
type: 'child-of',
|
|
695
|
-
parents: [
|
|
695
|
+
parents: [parentDXN.toString()],
|
|
696
696
|
transitive: true,
|
|
697
697
|
});
|
|
698
698
|
Schema.validateSync(QueryAST.Filter)(filter.ast);
|
|
@@ -736,15 +736,15 @@ describe('query api', () => {
|
|
|
736
736
|
});
|
|
737
737
|
|
|
738
738
|
test('childOf in select query', () => {
|
|
739
|
-
const
|
|
740
|
-
const parentRef = Ref.fromDXN(
|
|
739
|
+
const parentDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
|
|
740
|
+
const parentRef = Ref.fromDXN(parentDXN);
|
|
741
741
|
const query = Query.select(Filter.childOf(parentRef));
|
|
742
742
|
|
|
743
743
|
expect(query.ast).toMatchObject({
|
|
744
744
|
type: 'select',
|
|
745
745
|
filter: {
|
|
746
746
|
type: 'child-of',
|
|
747
|
-
parents: [
|
|
747
|
+
parents: [parentDXN.toString()],
|
|
748
748
|
transitive: true,
|
|
749
749
|
},
|
|
750
750
|
});
|
|
@@ -752,8 +752,8 @@ describe('query api', () => {
|
|
|
752
752
|
});
|
|
753
753
|
|
|
754
754
|
test('childOf combined with type filter', () => {
|
|
755
|
-
const
|
|
756
|
-
const parentRef = Ref.fromDXN(
|
|
755
|
+
const parentDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
|
|
756
|
+
const parentRef = Ref.fromDXN(parentDXN);
|
|
757
757
|
const query = Query.select(Filter.and(Filter.type(TestSchema.Person), Filter.childOf(parentRef)));
|
|
758
758
|
|
|
759
759
|
Schema.validateSync(QueryAST.Query)(query.ast);
|
|
@@ -763,7 +763,7 @@ describe('query api', () => {
|
|
|
763
763
|
type: 'and',
|
|
764
764
|
filters: [
|
|
765
765
|
{ type: 'object', typename: 'dxn:type:com.example.type.person:0.1.0' },
|
|
766
|
-
{ type: 'child-of', parents: [
|
|
766
|
+
{ type: 'child-of', parents: [parentDXN.toString()], transitive: true },
|
|
767
767
|
],
|
|
768
768
|
},
|
|
769
769
|
});
|
|
@@ -779,8 +779,8 @@ describe('query api', () => {
|
|
|
779
779
|
|
|
780
780
|
test('childOf with mixed objects and Refs', () => {
|
|
781
781
|
const parent = Obj.make(TestSchema.Person, { name: 'Parent' });
|
|
782
|
-
const
|
|
783
|
-
const parentRef = Ref.fromDXN(
|
|
782
|
+
const refDXN = DXN.fromSpaceAndObjectId(SpaceId.random(), ObjectId.random());
|
|
783
|
+
const parentRef = Ref.fromDXN(refDXN);
|
|
784
784
|
const filter = Filter.childOf([parent, parentRef]);
|
|
785
785
|
|
|
786
786
|
expect(filter.ast).toMatchObject({
|
package/src/Query.ts
CHANGED
|
@@ -359,7 +359,7 @@ class QueryClass implements Any {
|
|
|
359
359
|
from: {
|
|
360
360
|
_tag: 'scope',
|
|
361
361
|
scope: {
|
|
362
|
-
...(options?.includeFeeds ? {
|
|
362
|
+
...(options?.includeFeeds ? { allFeedsFromSpaces: true } : {}),
|
|
363
363
|
},
|
|
364
364
|
},
|
|
365
365
|
});
|
|
@@ -384,7 +384,7 @@ class QueryClass implements Any {
|
|
|
384
384
|
_tag: 'scope',
|
|
385
385
|
scope: {
|
|
386
386
|
spaceIds: databases.map((db) => db.spaceId),
|
|
387
|
-
...(options?.includeFeeds ? {
|
|
387
|
+
...(options?.includeFeeds ? { allFeedsFromSpaces: true } : {}),
|
|
388
388
|
},
|
|
389
389
|
},
|
|
390
390
|
});
|
|
@@ -410,10 +410,15 @@ class QueryClass implements Any {
|
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
const
|
|
414
|
-
const
|
|
413
|
+
const feedItems = items as Feed.Feed[];
|
|
414
|
+
const feedDXNs = feedItems.map((feed) => {
|
|
415
415
|
const dxn = Feed.getQueueDxn(feed);
|
|
416
|
-
|
|
416
|
+
if (!dxn) {
|
|
417
|
+
throw new TypeError(
|
|
418
|
+
`Query.from() expects persisted Feed objects with a queue DXN; got feed without a space (id=${Obj.getDXN(feed).toString()}).`,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return dxn.toString();
|
|
417
422
|
});
|
|
418
423
|
return new QueryClass({
|
|
419
424
|
type: 'from',
|
|
@@ -421,7 +426,7 @@ class QueryClass implements Any {
|
|
|
421
426
|
from: {
|
|
422
427
|
_tag: 'scope',
|
|
423
428
|
scope: {
|
|
424
|
-
|
|
429
|
+
feeds: feedDXNs,
|
|
425
430
|
},
|
|
426
431
|
},
|
|
427
432
|
});
|
|
@@ -551,7 +556,7 @@ export const from = (
|
|
|
551
556
|
return wrapper.from(source as any, options);
|
|
552
557
|
};
|
|
553
558
|
|
|
554
|
-
const SCOPE_KEYS = new Set(['spaceIds', '
|
|
559
|
+
const SCOPE_KEYS = new Set(['spaceIds', 'feeds', 'allFeedsFromSpaces']);
|
|
555
560
|
|
|
556
561
|
/** Detect a raw Scope object (plain object with only Scope-valid keys). */
|
|
557
562
|
const _isScope = (value: unknown): value is QueryAST.Scope => {
|
package/src/Ref.ts
CHANGED
|
@@ -85,6 +85,8 @@ export const make = refInternal.Ref.make;
|
|
|
85
85
|
// TODO(dmaretskyi): Consider just allowing `make` to accept DXN.
|
|
86
86
|
export const fromDXN = refInternal.Ref.fromDXN;
|
|
87
87
|
|
|
88
|
+
export const hasObjectId = refInternal.Ref.hasObjectId;
|
|
89
|
+
|
|
88
90
|
// TODO(wittjosiah): Factor out?
|
|
89
91
|
export const isRefType = (ast: SchemaAST.AST): boolean => {
|
|
90
92
|
return SchemaAST.getAnnotation<JsonSchema.JsonSchema>(ast, SchemaAST.JSONSchemaAnnotationId).pipe(
|
package/src/internal/Obj/ids.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { DXN, ObjectId, QueueSubspaceTags, SpaceId } from '@dxos/keys';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* @deprecated Use `db.
|
|
8
|
+
* @deprecated Use `Feed.make(...)` + `db.add(feed)` then `Feed.getQueueDxn(feed)`.
|
|
9
9
|
*/
|
|
10
10
|
// TODO(burdon): Move to @dxos/keys.
|
|
11
11
|
export const createQueueDXN = (spaceId = SpaceId.random(), queueId = ObjectId.random()) =>
|
|
@@ -116,15 +116,15 @@ export const objectFromJSON = async (
|
|
|
116
116
|
const isRelation =
|
|
117
117
|
typeof jsonData[ATTR_RELATION_SOURCE] === 'string' || typeof jsonData[ATTR_RELATION_TARGET] === 'string';
|
|
118
118
|
if (isRelation) {
|
|
119
|
-
const
|
|
120
|
-
const
|
|
119
|
+
const sourceDXN: DXN = DXN.parse(jsonData[ATTR_RELATION_SOURCE] ?? raise(new TypeError('Missing relation source')));
|
|
120
|
+
const targetDXN: DXN = DXN.parse(jsonData[ATTR_RELATION_TARGET] ?? raise(new TypeError('Missing relation target')));
|
|
121
121
|
|
|
122
|
-
const source = (await refResolver?.resolve(
|
|
123
|
-
const target = (await refResolver?.resolve(
|
|
122
|
+
const source = (await refResolver?.resolve(sourceDXN)) as AnyEntity | undefined;
|
|
123
|
+
const target = (await refResolver?.resolve(targetDXN)) as AnyEntity | undefined;
|
|
124
124
|
|
|
125
125
|
defineHiddenProperty(obj, KindId, EntityKind.Relation);
|
|
126
|
-
defineHiddenProperty(obj, RelationSourceDXNId,
|
|
127
|
-
defineHiddenProperty(obj, RelationTargetDXNId,
|
|
126
|
+
defineHiddenProperty(obj, RelationSourceDXNId, sourceDXN);
|
|
127
|
+
defineHiddenProperty(obj, RelationTargetDXNId, targetDXN);
|
|
128
128
|
defineHiddenProperty(obj, RelationSourceId, source);
|
|
129
129
|
defineHiddenProperty(obj, RelationTargetId, target);
|
|
130
130
|
} else {
|
|
@@ -142,8 +142,8 @@ export const objectFromJSON = async (
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
if (jsonData[ATTR_PARENT]) {
|
|
145
|
-
const
|
|
146
|
-
const resolvedParent = (await refResolver?.resolve(
|
|
145
|
+
const parentDXN = DXN.parse(jsonData[ATTR_PARENT]);
|
|
146
|
+
const resolvedParent = (await refResolver?.resolve(parentDXN)) as Obj.Unknown | undefined;
|
|
147
147
|
defineHiddenProperty(obj, ParentId, resolvedParent);
|
|
148
148
|
} else if (parent) {
|
|
149
149
|
defineHiddenProperty(obj, ParentId, parent);
|
|
@@ -184,7 +184,7 @@ const stripInternalJsonKeys = (jsonData: unknown) => {
|
|
|
184
184
|
[ATTR_TYPE]: _type,
|
|
185
185
|
[ATTR_META]: _meta,
|
|
186
186
|
[ATTR_DELETED]: _deleted,
|
|
187
|
-
[ATTR_SELF_DXN]:
|
|
187
|
+
[ATTR_SELF_DXN]: _selfDXN,
|
|
188
188
|
[ATTR_RELATION_SOURCE]: _relationSource,
|
|
189
189
|
[ATTR_RELATION_TARGET]: _relationTarget,
|
|
190
190
|
...props
|
package/src/internal/Query.ts
CHANGED
|
@@ -10,6 +10,18 @@ import { type QueryAST } from '@dxos/echo-protocol';
|
|
|
10
10
|
export const prettyFilter = (filter: QueryAST.Filter): string => {
|
|
11
11
|
switch (filter.type) {
|
|
12
12
|
case 'object': {
|
|
13
|
+
// A type-less object filter with only a meta-key constraint is `Filter.key(...)`.
|
|
14
|
+
if (
|
|
15
|
+
filter.typename === null &&
|
|
16
|
+
(filter.id === undefined || filter.id.length === 0) &&
|
|
17
|
+
Object.keys(filter.props).length === 0 &&
|
|
18
|
+
(filter.foreignKeys === undefined || filter.foreignKeys.length === 0) &&
|
|
19
|
+
filter.metaKey !== undefined
|
|
20
|
+
) {
|
|
21
|
+
return filter.metaVersion !== undefined
|
|
22
|
+
? `Filter.key(${JSON.stringify(filter.metaKey)}, { version: ${JSON.stringify(filter.metaVersion)} })`
|
|
23
|
+
: `Filter.key(${JSON.stringify(filter.metaKey)})`;
|
|
24
|
+
}
|
|
13
25
|
const parts: string[] = [];
|
|
14
26
|
if (filter.typename !== null) {
|
|
15
27
|
parts.push(String(filter.typename));
|
|
@@ -25,6 +37,13 @@ export const prettyFilter = (filter: QueryAST.Filter): string => {
|
|
|
25
37
|
if (filter.foreignKeys !== undefined && filter.foreignKeys.length > 0) {
|
|
26
38
|
parts.push(`foreignKeys: [${filter.foreignKeys.map((fk) => JSON.stringify(fk)).join(', ')}]`);
|
|
27
39
|
}
|
|
40
|
+
if (filter.metaKey !== undefined) {
|
|
41
|
+
parts.push(
|
|
42
|
+
filter.metaVersion !== undefined
|
|
43
|
+
? `metaKey: ${JSON.stringify(filter.metaKey)} (${filter.metaVersion})`
|
|
44
|
+
: `metaKey: ${JSON.stringify(filter.metaKey)}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
28
47
|
return parts.length > 0 ? `Filter.type(${parts.join(', ')})` : 'Filter.everything()';
|
|
29
48
|
}
|
|
30
49
|
case 'compare':
|
|
@@ -121,11 +140,11 @@ export const prettyQuery = (query: QueryAST.Query): string => {
|
|
|
121
140
|
if (scope.spaceIds !== undefined) {
|
|
122
141
|
parts.push(`spaceIds: [${scope.spaceIds.map((s) => JSON.stringify(s)).join(', ')}]`);
|
|
123
142
|
}
|
|
124
|
-
if (scope.
|
|
125
|
-
parts.push(`
|
|
143
|
+
if (scope.feeds !== undefined) {
|
|
144
|
+
parts.push(`feeds: [${scope.feeds.map(String).join(', ')}]`);
|
|
126
145
|
}
|
|
127
|
-
if (scope.
|
|
128
|
-
parts.push(`
|
|
146
|
+
if (scope.allFeedsFromSpaces !== undefined) {
|
|
147
|
+
parts.push(`allFeedsFromSpaces: ${scope.allFeedsFromSpaces}`);
|
|
129
148
|
}
|
|
130
149
|
return `${prettyQuery(query.query)}.from({ ${parts.join(', ')} })`;
|
|
131
150
|
}
|
|
@@ -28,7 +28,7 @@ describe('Obj.subscribe', () => {
|
|
|
28
28
|
expect(calls).toBe(seen);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
// Regression: queue-stored typed objects (e.g.
|
|
31
|
+
// Regression: queue-stored typed objects (e.g. AiContext.Binding) and other Obj-shaped values
|
|
32
32
|
// satisfy `Obj.isObject` (KindId is set) but are not wrapped in a reactive Proxy. Earlier
|
|
33
33
|
// versions invariant'd inside `getProxyTarget` when an atom body did `Obj.subscribe(obj)` on
|
|
34
34
|
// such an input. The contract is "no-op for non-reactive inputs"; verify the function
|
|
@@ -38,6 +38,18 @@ export const ObjectMetaSchema = Schema.Struct({
|
|
|
38
38
|
// Defaulting to an empty array is possible but requires a bit more work.
|
|
39
39
|
// TODO(dmaretskyi): In automerge this should be a map of { [tag]: boolean } for uniqueness and conflict resolution.
|
|
40
40
|
tags: Schema.optional(Schema.Array(Schema.String)),
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Fully-qualified registry key for the object (FQN format, e.g. `org.example.type.foo`).
|
|
44
|
+
* Identifies the canonical registry entry the object instance was created from.
|
|
45
|
+
*/
|
|
46
|
+
key: Schema.optional(Schema.String),
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Semantic version of the registry entry the object was created from.
|
|
50
|
+
* Must be a valid semver string (e.g. `1.2.3`).
|
|
51
|
+
*/
|
|
52
|
+
version: Schema.optional(Schema.String),
|
|
41
53
|
});
|
|
42
54
|
|
|
43
55
|
export type ObjectMeta = Schema.Schema.Type<typeof ObjectMetaSchema>;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/Database.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport * as Option from 'effect/Option';\nimport * as Schema from 'effect/Schema';\nimport type * as Types from 'effect/Types';\n\nimport { promiseWithCauseCapture } from '@dxos/effect';\nimport { invariant } from '@dxos/invariant';\nimport { DXN, type SpaceId } from '@dxos/keys';\n\nimport type * as Entity from './Entity';\nimport * as Err from './Err';\nimport type * as Filter from './Filter';\nimport type * as Hypergraph from './Hypergraph';\nimport { isInstanceOf } from './internal/Annotation';\nimport { type AnyProperties } from './internal/common/types';\nimport type { Ref } from './internal/Ref/ref';\nimport type * as Obj from './Obj';\nimport type * as Query from './Query';\nimport type * as QueryResult from './QueryResult';\nimport type * as SchemaRegistry from './SchemaRegistry';\nimport type * as Type from './Type';\n\n/**\n * `query` API function declaration.\n */\n// TODO(burdon): Reconcile Query and Filter (should only have one root type).\nexport interface QueryFn {\n <Q extends Query.Any>(query: Q): QueryResult.QueryResult<Query.Type<Q>>;\n <F extends Filter.Any>(filter: F): QueryResult.QueryResult<Filter.Type<F>>;\n}\n\n/**\n * Common interface for Database and Queue.\n */\nexport interface Queryable {\n query: QueryFn;\n}\n\nexport type GetObjectByIdOptions = {\n deleted?: boolean;\n};\n\nexport type ObjectPlacement = 'root-doc' | 'linked-doc';\n\nexport type AddOptions = {\n /**\n * Where to place the object in the Automerge document tree.\n * Root document is always loaded with the space.\n * Linked documents are loaded lazily.\n * Placing large number of objects in the root document may slow down the initial load.\n *\n * @default 'linked-doc'\n */\n placeIn?: ObjectPlacement;\n};\n\nexport type FlushOptions = {\n /**\n * Write any pending changes to disk.\n * @default true\n */\n disk?: boolean;\n\n /**\n * Wait for pending index updates.\n * @default true\n */\n indexes?: boolean;\n\n /**\n * Flush pending updates to objects and queries.\n * @default false\n */\n updates?: boolean;\n};\n\n/**\n * Identifier denoting an ECHO Database.\n */\nexport const TypeId = Symbol.for('@dxos/echo/Database');\nexport type TypeId = typeof TypeId;\n\n/**\n * ECHO Database interface.\n */\nexport interface Database extends Queryable {\n readonly [TypeId]: TypeId;\n\n get spaceId(): SpaceId;\n\n // TODO(burdon): Can we move this into graph?\n get schemaRegistry(): SchemaRegistry.SchemaRegistry;\n\n get graph(): Hypergraph.Hypergraph;\n\n toJSON(): object;\n\n /**\n * Return object by local ID.\n */\n getObjectById<T extends Obj.Unknown = Obj.OfShape<AnyProperties>>(\n id: string,\n opts?: GetObjectByIdOptions,\n ): T | undefined;\n\n /**\n * Query objects.\n */\n query: QueryFn;\n\n /**\n * Creates a reference to an existing object in the database.\n *\n * NOTE: The reference may be dangling if the object is not present in the database.\n * NOTE: Difference from `Ref.fromDXN`\n * `Ref.fromDXN(dxn)` returns an unhydrated reference. The `.load` and `.target` APIs will not work.\n * `db.makeRef(dxn)` is preferable in cases with access to the database.\n */\n makeRef<T extends Entity.Unknown = Entity.Unknown>(dxn: DXN): Ref<T>;\n\n /**\n * Adds object to the database.\n */\n add<T extends Entity.Unknown = Entity.Unknown>(obj: T, opts?: AddOptions): T;\n\n /**\n * Removes object from the database.\n */\n // TODO(burdon): Return true if removed (currently throws if not present).\n remove(obj: Entity.Unknown): void;\n\n /**\n * Wait for all pending changes to be saved to disk.\n * Optionaly waits for changes to be propagated to indexes and event handlers.\n */\n flush(opts?: FlushOptions): Promise<void>;\n}\n\nexport const isDatabase = (obj: unknown): obj is Database => {\n return obj ? typeof obj === 'object' && TypeId in obj && obj[TypeId] === TypeId : false;\n};\n\nexport const Database: Schema.Schema<Database> = Schema.Any.pipe(Schema.filter((space) => isDatabase(space)));\n\n/**\n * Effect service tag for Database dependency injection.\n */\nexport class Service extends Context.Tag('@dxos/echo/Database/Service')<\n Service,\n {\n readonly db: Database;\n }\n>() {}\n\n/**\n * Layer that provides a Database service that throws when accessed.\n * Useful as a default layer when no database is available.\n */\nexport const notAvailable = Layer.succeed(Service, {\n get db(): Database {\n throw new Error('Database not available');\n },\n});\n\n/**\n * Creates a Database service instance from a Database.\n */\nexport const makeService = (db: Database): Context.Tag.Service<Service> => {\n return {\n get db() {\n return db;\n },\n };\n};\n\n/**\n * Creates a Layer that provides the Database service.\n */\nexport const layer = (db: Database): Layer.Layer<Service> => {\n return Layer.succeed(Service, makeService(db));\n};\n\n/**\n * Returns the space ID of the database.\n */\nexport const spaceId = Effect.gen(function* () {\n const { db } = yield* Service;\n return db.spaceId;\n});\n\n/**\n * Resolves an object by its DXN.\n */\nexport const resolve: {\n // No type check.\n (ref: DXN | Ref<any>): Effect.Effect<Entity.Unknown, never, Service>;\n // Check matches schema.\n <S extends Type.AnyEntity>(\n ref: DXN | Ref<any>,\n schema: S,\n ): Effect.Effect<Schema.Schema.Type<S>, Err.ObjectNotFoundError, Service>;\n} = (<S extends Type.AnyEntity>(\n ref: DXN | Ref<any>,\n schema?: S,\n): Effect.Effect<Schema.Schema.Type<S>, Err.ObjectNotFoundError, Service> =>\n Effect.gen(function* () {\n const { db } = yield* Service;\n const dxn = ref instanceof DXN ? ref : ref.dxn;\n const object = yield* promiseWithCauseCapture(() =>\n db.graph\n .createRefResolver({\n context: {\n space: db.spaceId,\n },\n })\n .resolve(dxn),\n );\n\n if (!object) {\n return yield* Effect.fail(new Err.ObjectNotFoundError(dxn));\n }\n invariant(!schema || isInstanceOf(schema, object), 'Object type mismatch.');\n return object as any;\n }).pipe(Effect.withSpan('Database.resolve'))) as any;\n\n/**\n * Loads an object reference.\n */\nexport const load: <T>(ref: Ref<T>) => Effect.Effect<T, Err.ObjectNotFoundError, never> = Effect.fn('Database.load')(\n function* (ref) {\n const object = yield* promiseWithCauseCapture(() => ref.tryLoad());\n if (!object) {\n return yield* Effect.fail(new Err.ObjectNotFoundError(ref.dxn));\n }\n return object;\n },\n);\n\n/**\n * Loads an object reference option.\n */\n// TODO(dmaretskyi): Do we need this -- you can just use `Effect.catchTag` in calling code instead.\nexport const loadOption: <T>(ref: Ref<T>) => Effect.Effect<Option.Option<T>, never, never> = Effect.fn(\n 'Database.loadOption',\n)(function* (ref) {\n const object = yield* load(ref).pipe(Effect.catchTag('ObjectNotFoundError', () => Effect.succeed(undefined)));\n\n return Option.fromNullable(object);\n});\n\n/**\n * Adds an object to the database.\n * @see {@link Database.add}\n */\nexport const add = <T extends Entity.Unknown>(obj: T): Effect.Effect<T, never, Service> =>\n Service.pipe(Effect.map(({ db }) => db.add(obj))).pipe(Effect.withSpan('Database.add'));\n\n/**\n * Removes an object from the database.\n * @see {@link Database.remove}\n */\nexport const remove = <T extends Entity.Unknown>(obj: T): Effect.Effect<void, never, Service> =>\n Service.pipe(Effect.map(({ db }) => db.remove(obj))).pipe(Effect.withSpan('Database.remove'));\n\n/**\n * Flushes pending changes to disk.\n * @see {@link Database.flush}\n */\nexport const flush = (opts?: FlushOptions) =>\n Service.pipe(Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.flush(opts)))).pipe(\n Effect.withSpan('Database.flush'),\n );\n\n/**\n * Creates a `QueryResult` object that can be subscribed to.\n */\nexport const query: {\n <Q extends Query.Any>(query: Q): Effect.Effect<QueryResult.QueryResult<Query.Type<Q>>, never, Service>;\n <F extends Filter.Any>(filter: F): Effect.Effect<QueryResult.QueryResult<Filter.Type<F>>, never, Service>;\n} = (queryOrFilter: Query.Any | Filter.Any) =>\n Service.pipe(\n Effect.map(({ db }) => db.query(queryOrFilter as any) as QueryResult.QueryResult<any>),\n Effect.withSpan('Database.query'),\n );\n\n/**\n * Executes the query once and returns the results.\n */\nexport const runQuery: {\n <Q extends Query.Any>(query: Q): Effect.Effect<Query.Type<Q>[], never, Service>;\n <F extends Filter.Any>(filter: F): Effect.Effect<Filter.Type<F>[], never, Service>;\n} = (queryOrFilter: Query.Any | Filter.Any) =>\n query(queryOrFilter as any).pipe(\n Effect.flatMap((queryResult) => promiseWithCauseCapture(() => queryResult.run())),\n Effect.withSpan('Database.runQuery'),\n );\n\n/**\n * Executes the query once and returns the first result as or None.\n */\nexport const runQueryFirst: {\n <Q extends Query.Any>(query: Q): Effect.Effect<Option.Option<Query.Type<Q>>, never, Service>;\n <F extends Filter.Any>(filter: F): Effect.Effect<Option.Option<Filter.Type<F>>, never, Service>;\n} = (queryOrFilter: Query.Any | Filter.Any) =>\n query(queryOrFilter as any).pipe(\n Effect.flatMap((queryResult) =>\n promiseWithCauseCapture(async () => Option.fromNullable(await queryResult.firstOrUndefined())),\n ),\n Effect.withSpan('Database.runQueryFirst'),\n );\n\n/**\n * Persists schemas in the database so they replicate to other clients.\n * @see {@link SchemaRegistry.SchemaRegistry.register}\n */\nexport const registerSchema = (\n input: SchemaRegistry.RegisterSchemaInput[],\n): Effect.Effect<Type.RuntimeType[], never, Service> =>\n Service.pipe(\n Effect.flatMap(({ db }) => promiseWithCauseCapture(() => db.schemaRegistry.register(input))),\n Effect.withSpan('Database.registerSchema'),\n );\n\n/**\n * Creates a schema query result that can be subscribed to.\n */\n// TODO(dmaretskyi): Change API to `yield* Database.querySchema(...).first` and `yield* Database.querySchema(...).schema`.\nexport const schemaQuery = <Q extends Types.NoExcessProperties<SchemaRegistry.Query, Q>>(\n schemaQueryOptions?: Q & SchemaRegistry.Query,\n): Effect.Effect<QueryResult.QueryResult<SchemaRegistry.ExtractQueryResult<Q>>, never, Service> =>\n Service.pipe(\n Effect.map(({ db }) => db.schemaRegistry.query(schemaQueryOptions)),\n Effect.withSpan('Database.schemaQuery'),\n );\n\n/**\n * Executes a schema query once and returns the results.\n */\nexport const runSchemaQuery = <Q extends Types.NoExcessProperties<SchemaRegistry.Query, Q>>(\n schemaQueryOptions?: Q & SchemaRegistry.Query,\n): Effect.Effect<SchemaRegistry.ExtractQueryResult<Q>[], never, Service> =>\n schemaQuery(schemaQueryOptions).pipe(\n Effect.flatMap((queryResult) => promiseWithCauseCapture(() => queryResult.run())),\n );\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;;;AAMA,YAAYA,aAAa;AACzB,YAAYC,YAAY;AACxB,YAAYC,WAAW;AACvB,YAAYC,YAAY;AACxB,YAAYC,YAAY;AAGxB,SAASC,+BAA+B;AACxC,SAASC,iBAAiB;AAC1B,SAASC,WAAyB;AAqElC,IAAA,eAAA;AA+DSC,IAAM,SAAOA,uBAAQ,IAAA,qBAAsBA;AAClD,IAAA,aAAA,CAAA,QAAA;AAEF,SAAO,MAAMC,OAAoCC,QAAOC,YAASD,UAAc,OAACE,IAAUC,MAAAA,MAAWD,SAAS;AAE9G;;AAUA,IAAA,UAAA,cAAA,YAAA,6BAAA,EAAA,EAAA;;AAMI,IAAM,eAAU,cAAA,SAAA;EAClB,IAAA,KAAA;AACC,UAAA,IAAA,MAAA,wBAAA;EAEH;;AAKI,IAAIE,cAAK,CAAA,OAAA;;IAET,IAAA,KAAA;AACF,aAAA;IACA;EAEF;;AAKE,IAAA,QAAA,CAAA,OAAA;AAEF,SAAA,cAAA,SAAA,YAAA,EAAA,CAAA;;AAKSA,IAAGC,UAAO,WAAA,aAAA;AAChB,QAAA,EAAA,GAAA,IAAA,OAAA;AAEH,SAAA,GAAA;;AAiBI,IAAMC,UAAMC,CAAAA,KAAAA,WAA2BA,WAAID,aAAG;AAC9C,QAAME,EAAAA,GAAAA,IAAS,OAAOC;cAGhBC,eAAS,MAAA,MAAA,IAAA;iBACPR,OAAUG,wBAAO,MAAA,GAAA,MAAA,kBAAA;IACnB,SAAA;MAEDM,OAAQL,GAAAA;IAGRE;EACH,CAAA,EAAA,QAAO,GAAOI,CAAAA;AAChB,MAAA,CAAA,QAAA;AACAC,WAAWC,OAAUC,YAAAA,IAAaD,oBAAiB,GAAA,CAAA;EACnD;AACCE,YAAKJ,CAAOK,UAAS,aAAA,QAA6B,MAAA,GAAA,yBAAA,EAAA,YAAA,YAAA,GAAA,cAAA,GAAA,IAAA,GAAA,MAAA,GAAA,CAAA,2CAAA,yBAAA,EAAA,CAAA;AAEvD,SAAA;;AAMST,IAAQ,OAAA,UAAA,eAAA,EAAA,WAAA,KAAA;QACX,SAAO,OAAOI,wBAAoBM,MAAAA,IAAAA,QAAwBZ,CAAAA;AAC5D,MAAA,CAAA,QAAA;AACA,WAAOE,OAAAA,YAAAA,IAAAA,oBAAAA,IAAAA,GAAAA,CAAAA;EAET;AAEF,SAAA;;AASE,IAAOW,aAAoBX,UAAAA,qBAAAA,EAAAA,WAAAA,KAAAA;AAC1B,QAAA,SAAA,OAAA,KAAA,GAAA,EAAA,KAAA,gBAAA,uBAAA,MAAA,eAAA,MAAA,CAAA,CAAA;AAEH,SAAA,oBAAA,MAAA;;;;;;;;;;;",
|
|
6
|
-
"names": ["Context", "Effect", "Layer", "Option", "Schema", "promiseWithCauseCapture", "invariant", "DXN", "obj", "Database", "Schema", "Any", "space", "isDatabase", "db", "spaceId", "dxn", "ref", "object", "promiseWithCauseCapture", "context", "resolve", "Effect", "invariant", "schema", "isInstanceOf", "pipe", "withSpan", "ObjectNotFoundError", "Option"]
|
|
7
|
-
}
|