cojson 0.2.2 → 0.3.0-alpha.0

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.
Files changed (85) hide show
  1. package/.eslintrc.cjs +1 -0
  2. package/dist/account.d.ts +8 -8
  3. package/dist/account.js +2 -2
  4. package/dist/account.js.map +1 -1
  5. package/dist/coValue.d.ts +22 -27
  6. package/dist/coValue.js +21 -0
  7. package/dist/coValue.js.map +1 -1
  8. package/dist/coValueCore.d.ts +7 -7
  9. package/dist/coValueCore.js +11 -14
  10. package/dist/coValueCore.js.map +1 -1
  11. package/dist/coValues/coList.d.ts +107 -42
  12. package/dist/coValues/coList.js +163 -72
  13. package/dist/coValues/coList.js.map +1 -1
  14. package/dist/coValues/coMap.d.ts +109 -50
  15. package/dist/coValues/coMap.js +161 -109
  16. package/dist/coValues/coMap.js.map +1 -1
  17. package/dist/coValues/coStream.d.ts +78 -33
  18. package/dist/coValues/coStream.js +134 -53
  19. package/dist/coValues/coStream.js.map +1 -1
  20. package/dist/crypto.d.ts +8 -3
  21. package/dist/crypto.js +6 -6
  22. package/dist/crypto.js.map +1 -1
  23. package/dist/group.d.ts +59 -23
  24. package/dist/group.js +83 -25
  25. package/dist/group.js.map +1 -1
  26. package/dist/index.d.ts +14 -11
  27. package/dist/index.js +8 -8
  28. package/dist/index.js.map +1 -1
  29. package/dist/{node.d.ts → localNode.d.ts} +23 -11
  30. package/dist/{node.js → localNode.js} +80 -42
  31. package/dist/localNode.js.map +1 -0
  32. package/dist/media.d.ts +1 -2
  33. package/dist/permissions.js +6 -3
  34. package/dist/permissions.js.map +1 -1
  35. package/dist/queriedCoValues/queriedCoList.d.ts +66 -0
  36. package/dist/queriedCoValues/queriedCoList.js +120 -0
  37. package/dist/queriedCoValues/queriedCoList.js.map +1 -0
  38. package/dist/queriedCoValues/queriedCoMap.d.ts +47 -0
  39. package/dist/queriedCoValues/queriedCoMap.js +83 -0
  40. package/dist/queriedCoValues/queriedCoMap.js.map +1 -0
  41. package/dist/queriedCoValues/queriedCoStream.d.ts +40 -0
  42. package/dist/queriedCoValues/queriedCoStream.js +72 -0
  43. package/dist/queriedCoValues/queriedCoStream.js.map +1 -0
  44. package/dist/queries.d.ts +31 -0
  45. package/dist/queries.js +77 -0
  46. package/dist/queries.js.map +1 -0
  47. package/dist/sync.d.ts +1 -1
  48. package/dist/sync.js +1 -1
  49. package/dist/sync.js.map +1 -1
  50. package/dist/{testUtils.d.ts → tests/testUtils.d.ts} +9 -9
  51. package/dist/{testUtils.js → tests/testUtils.js} +9 -7
  52. package/dist/tests/testUtils.js.map +1 -0
  53. package/package.json +2 -2
  54. package/src/account.ts +6 -6
  55. package/src/coValue.ts +65 -34
  56. package/src/coValueCore.ts +18 -22
  57. package/src/coValues/coList.ts +272 -122
  58. package/src/coValues/coMap.ts +349 -152
  59. package/src/coValues/coStream.ts +258 -94
  60. package/src/crypto.ts +37 -24
  61. package/src/group.ts +112 -46
  62. package/src/index.ts +42 -30
  63. package/src/{node.ts → localNode.ts} +117 -66
  64. package/src/media.ts +1 -2
  65. package/src/permissions.ts +15 -18
  66. package/src/queriedCoValues/queriedCoList.ts +248 -0
  67. package/src/queriedCoValues/queriedCoMap.ts +180 -0
  68. package/src/queriedCoValues/queriedCoStream.ts +125 -0
  69. package/src/queries.ts +142 -0
  70. package/src/sync.ts +2 -2
  71. package/src/{account.test.ts → tests/account.test.ts} +6 -9
  72. package/src/{coValue.test.ts → tests/coValue.test.ts} +120 -114
  73. package/src/{coValueCore.test.ts → tests/coValueCore.test.ts} +7 -7
  74. package/src/{crypto.test.ts → tests/crypto.test.ts} +19 -21
  75. package/src/{group.test.ts → tests/group.test.ts} +2 -2
  76. package/src/{permissions.test.ts → tests/permissions.test.ts} +260 -247
  77. package/src/tests/queries.test.ts +318 -0
  78. package/src/{sync.test.ts → tests/sync.test.ts} +39 -39
  79. package/src/{testUtils.ts → tests/testUtils.ts} +10 -8
  80. package/dist/coValues/static.d.ts +0 -14
  81. package/dist/coValues/static.js +0 -20
  82. package/dist/coValues/static.js.map +0 -1
  83. package/dist/node.js.map +0 -1
  84. package/dist/testUtils.js.map +0 -1
  85. package/src/coValues/static.ts +0 -31
@@ -0,0 +1,180 @@
1
+ import { MutableCoMap } from "../coValues/coMap.js";
2
+ import { CoValueCore } from "../coValueCore.js";
3
+ import { Group } from "../group.js";
4
+ import { Account, AccountID, Profile, isAccountID } from "../account.js";
5
+ import { AnyCoMap, CoID, CoValue } from "../coValue.js";
6
+ import { TransactionID } from "../ids.js";
7
+ import { ValueOrSubQueried, QueryContext } from "../queries.js";
8
+
9
+ export type QueriedCoMap<M extends AnyCoMap> = {
10
+ [K in keyof M["_shape"] & string]: ValueOrSubQueried<M["_shape"][K]>;
11
+ } & QueriedCoMapBase<M>;
12
+
13
+ export type QueriedCoMapEdit<
14
+ M extends AnyCoMap,
15
+ K extends keyof M["_shape"]
16
+ > = {
17
+ by?: QueriedAccountAndProfile;
18
+ tx: TransactionID;
19
+ at: Date;
20
+ value: M["_shape"][K] extends CoValue
21
+ ? CoID<M["_shape"][K]>
22
+ : Exclude<M["_shape"][K], CoValue>;
23
+ };
24
+
25
+ export class QueriedCoMapBase<M extends AnyCoMap> {
26
+ coMap!: M;
27
+ id!: CoID<M>;
28
+ type!: "comap";
29
+
30
+ /** @internal */
31
+ static newWithKVPairs<M extends AnyCoMap>(
32
+ coMap: M,
33
+ queryContext: QueryContext
34
+ ): QueriedCoMap<M> {
35
+ const kv = {} as {
36
+ [K in keyof M["_shape"] & string]: ValueOrSubQueried<
37
+ M["_shape"][K]
38
+ >;
39
+ };
40
+
41
+ if (coMap.meta?.type === "account") {
42
+ const profileID = coMap.get("profile");
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ (kv as any).profile =
45
+ profileID && queryContext.resolveValue(profileID);
46
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
47
+ (kv as any).isMe =
48
+ (coMap as unknown as Account).id ===
49
+ queryContext.node.account.id;
50
+ } else {
51
+ for (const key of coMap.keys()) {
52
+ const value = coMap.get(key);
53
+
54
+ if (value === undefined) continue;
55
+
56
+ kv[key as keyof typeof kv] = queryContext.resolveValue(
57
+ value
58
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
+ ) as any;
60
+ }
61
+ }
62
+
63
+ return Object.assign(new QueriedCoMapBase(coMap, queryContext), kv);
64
+ }
65
+
66
+ /** @internal */
67
+ constructor(coMap: M, queryContext: QueryContext) {
68
+ Object.defineProperties(this, {
69
+ coMap: { value: coMap, enumerable: false },
70
+ id: { value: coMap.id, enumerable: false },
71
+ type: { value: "comap", enumerable: false },
72
+ edits: {
73
+ value: Object.fromEntries(
74
+ coMap.keys().flatMap((key) => {
75
+ const edits = [...coMap.editsAt(key)].map((edit) => ({
76
+ by:
77
+ edit.by && isAccountID(edit.by)
78
+ ? queryContext.resolveAccount(edit.by)
79
+ : undefined,
80
+ tx: edit.tx,
81
+ at: new Date(edit.at),
82
+ value:
83
+ edit.value &&
84
+ queryContext.resolveValue(edit.value),
85
+ }));
86
+ const lastEdit = edits[edits.length - 1];
87
+ if (!lastEdit) return [];
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ const editsAtKey = {
90
+ by: lastEdit.by,
91
+ tx: lastEdit.tx,
92
+ at: lastEdit.at,
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ value: lastEdit.value as any,
95
+ all: edits,
96
+ };
97
+
98
+ return [[key, editsAtKey]];
99
+ })
100
+ ),
101
+ enumerable: false,
102
+ },
103
+ });
104
+ }
105
+
106
+ edits!: {
107
+ [K in keyof M["_shape"] & string]:
108
+ | (QueriedCoMapEdit<M, K> & {
109
+ all: QueriedCoMapEdit<M, K>[];
110
+ })
111
+ | undefined;
112
+ };
113
+
114
+ get meta(): M["meta"] {
115
+ return this.coMap.meta;
116
+ }
117
+
118
+ get group(): Group {
119
+ return this.coMap.group;
120
+ }
121
+
122
+ get core(): CoValueCore {
123
+ return this.coMap.core;
124
+ }
125
+
126
+ set<K extends keyof M["_shape"] & string>(
127
+ key: K,
128
+ value: M["_shape"][K] extends CoValue
129
+ ? M["_shape"][K] | CoID<M["_shape"][K]>
130
+ : M["_shape"][K],
131
+ privacy?: "private" | "trusting"
132
+ ): M;
133
+ set(
134
+ kv: {
135
+ [K in keyof M["_shape"] & string]?: M["_shape"][K] extends CoValue
136
+ ? M["_shape"][K] | CoID<M["_shape"][K]>
137
+ : M["_shape"][K];
138
+ },
139
+ privacy?: "private" | "trusting"
140
+ ): M;
141
+ set<K extends keyof M["_shape"] & string>(
142
+ ...args:
143
+ | [
144
+ {
145
+ [K in keyof M["_shape"] &
146
+ string]?: M["_shape"][K] extends CoValue
147
+ ? M["_shape"][K] | CoID<M["_shape"][K]>
148
+ : M["_shape"][K];
149
+ },
150
+ ("private" | "trusting")?
151
+ ]
152
+ | [
153
+ K,
154
+ M["_shape"][K] extends CoValue
155
+ ? M["_shape"][K] | CoID<M["_shape"][K]>
156
+ : M["_shape"][K],
157
+ ("private" | "trusting")?
158
+ ]
159
+ ): M {
160
+ // eslint-disable-next-line @typescript-eslint/ban-types
161
+ return (this.coMap.set as Function)(...args);
162
+ }
163
+ delete(
164
+ key: keyof M["_shape"] & string,
165
+ privacy?: "private" | "trusting"
166
+ ): M {
167
+ return this.coMap.delete(key, privacy);
168
+ }
169
+ mutate(
170
+ mutator: (mutable: MutableCoMap<M["_shape"], M["meta"]>) => void
171
+ ): M {
172
+ return this.coMap.mutate(mutator);
173
+ }
174
+ }
175
+
176
+ export type QueriedAccountAndProfile = {
177
+ profile?: { name?: string; id: CoID<Profile> };
178
+ isMe?: boolean;
179
+ id: AccountID;
180
+ };
@@ -0,0 +1,125 @@
1
+ import { JsonValue } from "../jsonValue.js";
2
+ import { MutableCoStream } from "../coValues/coStream.js";
3
+ import { CoValueCore } from "../coValueCore.js";
4
+ import { Group } from "../group.js";
5
+ import { AccountID, isAccountID } from "../account.js";
6
+ import { AnyCoStream, CoID, CoValue } from "../coValue.js";
7
+ import { SessionID, TransactionID } from "../ids.js";
8
+ import { QueriedAccountAndProfile } from "./queriedCoMap.js";
9
+ import { ValueOrSubQueried, QueryContext } from "../queries.js";
10
+
11
+
12
+ export type QueriedCoStreamItems<Item extends JsonValue | CoValue> = {
13
+ last?: ValueOrSubQueried<Item>;
14
+ by?: QueriedAccountAndProfile;
15
+ tx?: TransactionID;
16
+ at?: Date;
17
+ all: {
18
+ value: ValueOrSubQueried<Item>;
19
+ by?: QueriedAccountAndProfile;
20
+ tx: TransactionID;
21
+ at: Date;
22
+ }[];
23
+ };
24
+
25
+ export class QueriedCoStream<S extends AnyCoStream> {
26
+ coStream: S;
27
+ id: CoID<S>;
28
+ type = "costream" as const;
29
+
30
+ /** @internal */
31
+ constructor(coStream: S, queryContext: QueryContext) {
32
+ this.coStream = coStream;
33
+ this.id = coStream.id;
34
+
35
+ this.perSession = Object.fromEntries(
36
+ coStream.sessions().map((sessionID) => {
37
+ const items = [...coStream.itemsIn(sessionID)].map((item) => ({
38
+ by: item.by && isAccountID(item.by)
39
+ ? queryContext.resolveAccount(item.by)
40
+ : undefined,
41
+ tx: item.tx,
42
+ at: new Date(item.at),
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ value: queryContext.resolveValue(item.value) as any,
45
+ }));
46
+
47
+ const lastItem = items[items.length - 1];
48
+
49
+ return [
50
+ sessionID,
51
+ {
52
+ last: lastItem?.value,
53
+ by: lastItem?.by,
54
+ tx: lastItem?.tx,
55
+ at: lastItem?.at,
56
+ all: items,
57
+ } satisfies QueriedCoStreamItems<S["_item"]>,
58
+ ];
59
+ })
60
+ );
61
+
62
+ this.perAccount = Object.fromEntries(
63
+ [...coStream.accounts()].map((accountID) => {
64
+ const items = [...coStream.itemsBy(accountID)].map((item) => ({
65
+ by: item.by && isAccountID(item.by)
66
+ ? queryContext.resolveAccount(item.by)
67
+ : undefined,
68
+ tx: item.tx,
69
+ at: new Date(item.at),
70
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
71
+ value: queryContext.resolveValue(item.value) as any,
72
+ }));
73
+
74
+ const lastItem = items[items.length - 1];
75
+
76
+ return [
77
+ accountID,
78
+ {
79
+ last: lastItem?.value,
80
+ by: lastItem?.by,
81
+ tx: lastItem?.tx,
82
+ at: lastItem?.at,
83
+ all: items,
84
+ } satisfies QueriedCoStreamItems<S["_item"]>,
85
+ ];
86
+ })
87
+ );
88
+
89
+ this.me = isAccountID(queryContext.node.account.id)
90
+ ? this.perAccount[queryContext.node.account.id]
91
+ : undefined;
92
+ }
93
+
94
+ get meta(): S["meta"] {
95
+ return this.coStream.meta;
96
+ }
97
+
98
+ get group(): Group {
99
+ return this.coStream.group;
100
+ }
101
+
102
+ get core(): CoValueCore {
103
+ return this.coStream.core;
104
+ }
105
+
106
+ me?: QueriedCoStreamItems<S["_item"]>;
107
+ perAccount: {
108
+ [account: AccountID]: QueriedCoStreamItems<S["_item"]>;
109
+ };
110
+ perSession: {
111
+ [session: SessionID]: QueriedCoStreamItems<S["_item"]>;
112
+ };
113
+
114
+ push(
115
+ item: S["_item"] extends CoValue ? S["_item"] | CoID<S["_item"]> : S["_item"],
116
+ privacy?: "private" | "trusting"
117
+ ): S {
118
+ return this.coStream.push(item, privacy);
119
+ }
120
+ mutate(
121
+ mutator: (mutable: MutableCoStream<S["_item"], S["meta"]>) => void
122
+ ): S {
123
+ return this.coStream.mutate(mutator);
124
+ }
125
+ }
package/src/queries.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { JsonValue } from "./jsonValue.js";
2
+ import { CoMap } from "./coValues/coMap.js";
3
+ import { CoStream } from "./coValues/coStream.js";
4
+ import { CoList } from "./coValues/coList.js";
5
+ import { AccountID } from "./account.js";
6
+ import { AnyCoList, AnyCoMap, AnyCoStream, CoID, CoValue } from "./coValue.js";
7
+ import { LocalNode } from "./localNode.js";
8
+ import {
9
+ QueriedAccountAndProfile,
10
+ QueriedCoMap,
11
+ QueriedCoMapBase,
12
+ } from "./queriedCoValues/queriedCoMap.js";
13
+ import { QueriedCoList } from "./queriedCoValues/queriedCoList.js";
14
+ import { QueriedCoStream } from "./queriedCoValues/queriedCoStream.js";
15
+
16
+ export type Queried<T extends CoValue> = T extends AnyCoMap
17
+ ? QueriedCoMap<T>
18
+ : T extends AnyCoList
19
+ ? QueriedCoList<T>
20
+ : T extends AnyCoStream
21
+ ? T["meta"] extends { type: "binary" }
22
+ ? never
23
+ : QueriedCoStream<T>
24
+ : never;
25
+
26
+ export type ValueOrSubQueried<
27
+ V extends JsonValue | CoValue | CoID<CoValue> | undefined
28
+ > = V extends CoID<infer C>
29
+ ? Queried<C> | undefined
30
+ : V extends CoValue
31
+ ? Queried<V> | undefined
32
+ : V;
33
+
34
+ export interface CleanupCallbackAndUsable {
35
+ (): void;
36
+ [Symbol.dispose]: () => void;
37
+ }
38
+
39
+ export class QueryContext {
40
+ values: {
41
+ [id: CoID<CoValue>]: {
42
+ lastQueried: Queried<CoValue> | undefined;
43
+ unsubscribe: () => void;
44
+ };
45
+ } = {};
46
+ node: LocalNode;
47
+ onUpdate: () => void;
48
+
49
+ constructor(node: LocalNode, onUpdate: () => void) {
50
+ this.node = node;
51
+ this.onUpdate = onUpdate;
52
+ }
53
+
54
+ getChildLastQueriedOrSubscribe<T extends CoValue>(valueID: CoID<T>) {
55
+ let value = this.values[valueID];
56
+ if (!value) {
57
+ value = {
58
+ lastQueried: undefined,
59
+ unsubscribe: query(valueID, this.node, (childQueried) => {
60
+ value!.lastQueried = childQueried as Queried<CoValue>;
61
+ this.onUpdate();
62
+ }),
63
+ };
64
+ this.values[valueID] = value;
65
+ }
66
+ return value.lastQueried as Queried<T> | undefined;
67
+ }
68
+
69
+ resolveAccount(accountID: AccountID) {
70
+ return this.getChildLastQueriedOrSubscribe(
71
+ accountID
72
+ ) as QueriedAccountAndProfile;
73
+ }
74
+
75
+ resolveValue<T extends JsonValue>(
76
+ value: T
77
+ ): T extends CoID<infer C> ? Queried<C> | undefined : T {
78
+ return (
79
+ typeof value === "string" && value.startsWith("co_")
80
+ ? this.getChildLastQueriedOrSubscribe(value as CoID<CoValue>)
81
+ : value
82
+ ) as T extends CoID<infer C> ? Queried<C> | undefined : T;
83
+ }
84
+
85
+ cleanup() {
86
+ for (const child of Object.values(this.values)) {
87
+ child.unsubscribe();
88
+ }
89
+ }
90
+ }
91
+
92
+ export function query<T extends CoValue>(
93
+ id: CoID<T>,
94
+ node: LocalNode,
95
+ callback: (queried: Queried<T> | undefined) => void,
96
+ parentContext?: QueryContext
97
+ ): CleanupCallbackAndUsable {
98
+ console.log("querying", id);
99
+
100
+ const context = parentContext || new QueryContext(node, onUpdate);
101
+
102
+ const unsubscribe = node.subscribe(id, (update) => {
103
+ lastRootValue = update;
104
+ onUpdate();
105
+ });
106
+
107
+ let lastRootValue: T | undefined;
108
+
109
+ function onUpdate() {
110
+ const rootValue = lastRootValue;
111
+
112
+ if (rootValue === undefined) {
113
+ return undefined;
114
+ }
115
+
116
+ if (rootValue instanceof CoMap) {
117
+ callback(
118
+ QueriedCoMapBase.newWithKVPairs(
119
+ rootValue,
120
+ context
121
+ ) as Queried<T>
122
+ );
123
+ } else if (rootValue instanceof CoList) {
124
+ callback(new QueriedCoList(rootValue, context) as Queried<T>);
125
+ } else if (rootValue instanceof CoStream) {
126
+ if (rootValue.meta?.type === "binary") {
127
+ // Querying binary string not yet implemented
128
+ return {};
129
+ } else {
130
+ callback(new QueriedCoStream(rootValue, context) as Queried<T>);
131
+ }
132
+ }
133
+ }
134
+
135
+ const cleanup = function cleanup() {
136
+ context.cleanup();
137
+ unsubscribe();
138
+ } as CleanupCallbackAndUsable;
139
+ cleanup[Symbol.dispose] = cleanup;
140
+
141
+ return cleanup;
142
+ }
package/src/sync.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Signature } from "./crypto.js";
2
2
  import { CoValueHeader, Transaction } from "./coValueCore.js";
3
3
  import { CoValueCore } from "./coValueCore.js";
4
- import { LocalNode } from "./node.js";
5
- import { newLoadingState } from "./node.js";
4
+ import { LocalNode } from "./localNode.js";
5
+ import { newLoadingState } from "./localNode.js";
6
6
  import {
7
7
  ReadableStream,
8
8
  WritableStream,
@@ -1,7 +1,7 @@
1
- import { newRandomSessionID } from "./coValueCore.js";
2
- import { cojsonReady } from "./index.js";
3
- import { LocalNode } from "./node.js";
4
- import { connectedPeers } from "./streamUtils.js";
1
+ import { newRandomSessionID } from "../coValueCore.js";
2
+ import { cojsonReady } from "../index.js";
3
+ import { LocalNode } from "../localNode.js";
4
+ import { connectedPeers } from "../streamUtils.js";
5
5
 
6
6
  beforeEach(async () => {
7
7
  await cojsonReady;
@@ -19,9 +19,6 @@ test("Can create a node while creating a new account with profile", async () =>
19
19
  expect(node.expectProfileLoaded(accountID).get("name")).toEqual(
20
20
  "Hermes Puggington"
21
21
  );
22
- expect((await node.loadProfile(accountID)).get("name")).toEqual(
23
- "Hermes Puggington"
24
- );
25
22
  });
26
23
 
27
24
  test("A node with an account can create groups and and objects within them", async () => {
@@ -39,7 +36,7 @@ test("A node with an account can create groups and and objects within them", asy
39
36
 
40
37
  expect(map.get("foo")).toEqual("bar");
41
38
 
42
- expect(map.whoEdited("foo")).toEqual(accountID);
39
+ expect(map.lastEditAt("foo")?.by).toEqual(accountID);
43
40
  });
44
41
 
45
42
  test("Can create account with one node, and then load it on another", async () => {
@@ -57,7 +54,7 @@ test("Can create account with one node, and then load it on another", async () =
57
54
 
58
55
  const [node1asPeer, node2asPeer] = connectedPeers("node1", "node2", {trace: true, peer1role: "server", peer2role: "client"});
59
56
 
60
- node.sync.addPeer(node2asPeer);
57
+ node.syncManager.addPeer(node2asPeer);
61
58
 
62
59
  const node2 = await LocalNode.withLoadedAccount(
63
60
  accountID,