@verdant-web/common 1.11.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 (241) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/EventSubscriber.d.ts +17 -0
  3. package/dist/cjs/EventSubscriber.js +67 -0
  4. package/dist/cjs/EventSubscriber.js.map +1 -0
  5. package/dist/cjs/baseline.d.ts +5 -0
  6. package/dist/cjs/baseline.js +3 -0
  7. package/dist/cjs/baseline.js.map +1 -0
  8. package/dist/cjs/batching.d.ts +41 -0
  9. package/dist/cjs/batching.js +89 -0
  10. package/dist/cjs/batching.js.map +1 -0
  11. package/dist/cjs/files.d.ts +19 -0
  12. package/dist/cjs/files.js +19 -0
  13. package/dist/cjs/files.js.map +1 -0
  14. package/dist/cjs/files.test.d.ts +1 -0
  15. package/dist/cjs/files.test.js +81 -0
  16. package/dist/cjs/files.test.js.map +1 -0
  17. package/dist/cjs/index.d.ts +18 -0
  18. package/dist/cjs/index.js +36 -0
  19. package/dist/cjs/index.js.map +1 -0
  20. package/dist/cjs/indexes.d.ts +14 -0
  21. package/dist/cjs/indexes.js +116 -0
  22. package/dist/cjs/indexes.js.map +1 -0
  23. package/dist/cjs/indexes.test.d.ts +1 -0
  24. package/dist/cjs/indexes.test.js +128 -0
  25. package/dist/cjs/indexes.test.js.map +1 -0
  26. package/dist/cjs/migration.d.ts +98 -0
  27. package/dist/cjs/migration.js +128 -0
  28. package/dist/cjs/migration.js.map +1 -0
  29. package/dist/cjs/oids.d.ts +87 -0
  30. package/dist/cjs/oids.js +375 -0
  31. package/dist/cjs/oids.js.map +1 -0
  32. package/dist/cjs/oids.test.d.ts +1 -0
  33. package/dist/cjs/oids.test.js +282 -0
  34. package/dist/cjs/oids.test.js.map +1 -0
  35. package/dist/cjs/operation.d.ts +139 -0
  36. package/dist/cjs/operation.js +533 -0
  37. package/dist/cjs/operation.js.map +1 -0
  38. package/dist/cjs/operation.test.d.ts +1 -0
  39. package/dist/cjs/operation.test.js +827 -0
  40. package/dist/cjs/operation.test.js.map +1 -0
  41. package/dist/cjs/patch.d.ts +26 -0
  42. package/dist/cjs/patch.js +227 -0
  43. package/dist/cjs/patch.js.map +1 -0
  44. package/dist/cjs/presence.d.ts +27 -0
  45. package/dist/cjs/presence.js +3 -0
  46. package/dist/cjs/presence.js.map +1 -0
  47. package/dist/cjs/protocol.d.ts +145 -0
  48. package/dist/cjs/protocol.js +4 -0
  49. package/dist/cjs/protocol.js.map +1 -0
  50. package/dist/cjs/refs.d.ts +5 -0
  51. package/dist/cjs/refs.js +22 -0
  52. package/dist/cjs/refs.js.map +1 -0
  53. package/dist/cjs/replica.d.ts +28 -0
  54. package/dist/cjs/replica.js +29 -0
  55. package/dist/cjs/replica.js.map +1 -0
  56. package/dist/cjs/schema/defaults.d.ts +4 -0
  57. package/dist/cjs/schema/defaults.js +72 -0
  58. package/dist/cjs/schema/defaults.js.map +1 -0
  59. package/dist/cjs/schema/defaults.test.d.ts +1 -0
  60. package/dist/cjs/schema/defaults.test.js +21 -0
  61. package/dist/cjs/schema/defaults.test.js.map +1 -0
  62. package/dist/cjs/schema/index.d.ts +8 -0
  63. package/dist/cjs/schema/index.js +29 -0
  64. package/dist/cjs/schema/index.js.map +1 -0
  65. package/dist/cjs/schema/indexFilters.d.ts +6 -0
  66. package/dist/cjs/schema/indexFilters.js +31 -0
  67. package/dist/cjs/schema/indexFilters.js.map +1 -0
  68. package/dist/cjs/schema/types/collection.d.ts +51 -0
  69. package/dist/cjs/schema/types/collection.js +3 -0
  70. package/dist/cjs/schema/types/collection.js.map +1 -0
  71. package/dist/cjs/schema/types/compounds.d.ts +12 -0
  72. package/dist/cjs/schema/types/compounds.js +3 -0
  73. package/dist/cjs/schema/types/compounds.js.map +1 -0
  74. package/dist/cjs/schema/types/fields.d.ts +59 -0
  75. package/dist/cjs/schema/types/fields.js +3 -0
  76. package/dist/cjs/schema/types/fields.js.map +1 -0
  77. package/dist/cjs/schema/types/filters.d.ts +29 -0
  78. package/dist/cjs/schema/types/filters.js +3 -0
  79. package/dist/cjs/schema/types/filters.js.map +1 -0
  80. package/dist/cjs/schema/types/shapes.d.ts +21 -0
  81. package/dist/cjs/schema/types/shapes.js +3 -0
  82. package/dist/cjs/schema/types/shapes.js.map +1 -0
  83. package/dist/cjs/schema/types/synthetics.d.ts +28 -0
  84. package/dist/cjs/schema/types/synthetics.js +3 -0
  85. package/dist/cjs/schema/types/synthetics.js.map +1 -0
  86. package/dist/cjs/schema/types.d.ts +18 -0
  87. package/dist/cjs/schema/types.js +23 -0
  88. package/dist/cjs/schema/types.js.map +1 -0
  89. package/dist/cjs/timestamp.d.ts +48 -0
  90. package/dist/cjs/timestamp.js +244 -0
  91. package/dist/cjs/timestamp.js.map +1 -0
  92. package/dist/cjs/timestamp.test.d.ts +1 -0
  93. package/dist/cjs/timestamp.test.js +94 -0
  94. package/dist/cjs/timestamp.test.js.map +1 -0
  95. package/dist/cjs/undo.d.ts +3 -0
  96. package/dist/cjs/undo.js +199 -0
  97. package/dist/cjs/undo.js.map +1 -0
  98. package/dist/cjs/utils.d.ts +15 -0
  99. package/dist/cjs/utils.js +128 -0
  100. package/dist/cjs/utils.js.map +1 -0
  101. package/dist/cjs/utils.test.d.ts +1 -0
  102. package/dist/cjs/utils.test.js +37 -0
  103. package/dist/cjs/utils.test.js.map +1 -0
  104. package/dist/esm/EventSubscriber.d.ts +17 -0
  105. package/dist/esm/EventSubscriber.js +63 -0
  106. package/dist/esm/EventSubscriber.js.map +1 -0
  107. package/dist/esm/baseline.d.ts +5 -0
  108. package/dist/esm/baseline.js +2 -0
  109. package/dist/esm/baseline.js.map +1 -0
  110. package/dist/esm/batching.d.ts +41 -0
  111. package/dist/esm/batching.js +84 -0
  112. package/dist/esm/batching.js.map +1 -0
  113. package/dist/esm/files.d.ts +19 -0
  114. package/dist/esm/files.js +13 -0
  115. package/dist/esm/files.js.map +1 -0
  116. package/dist/esm/files.test.d.ts +1 -0
  117. package/dist/esm/files.test.js +79 -0
  118. package/dist/esm/files.test.js.map +1 -0
  119. package/dist/esm/index.d.ts +18 -0
  120. package/dist/esm/index.js +16 -0
  121. package/dist/esm/index.js.map +1 -0
  122. package/dist/esm/indexes.d.ts +14 -0
  123. package/dist/esm/indexes.js +106 -0
  124. package/dist/esm/indexes.js.map +1 -0
  125. package/dist/esm/indexes.test.d.ts +1 -0
  126. package/dist/esm/indexes.test.js +126 -0
  127. package/dist/esm/indexes.test.js.map +1 -0
  128. package/dist/esm/migration.d.ts +98 -0
  129. package/dist/esm/migration.js +122 -0
  130. package/dist/esm/migration.js.map +1 -0
  131. package/dist/esm/oids.d.ts +87 -0
  132. package/dist/esm/oids.js +348 -0
  133. package/dist/esm/oids.js.map +1 -0
  134. package/dist/esm/oids.test.d.ts +1 -0
  135. package/dist/esm/oids.test.js +280 -0
  136. package/dist/esm/oids.test.js.map +1 -0
  137. package/dist/esm/operation.d.ts +139 -0
  138. package/dist/esm/operation.js +518 -0
  139. package/dist/esm/operation.js.map +1 -0
  140. package/dist/esm/operation.test.d.ts +1 -0
  141. package/dist/esm/operation.test.js +825 -0
  142. package/dist/esm/operation.test.js.map +1 -0
  143. package/dist/esm/patch.d.ts +26 -0
  144. package/dist/esm/patch.js +223 -0
  145. package/dist/esm/patch.js.map +1 -0
  146. package/dist/esm/presence.d.ts +27 -0
  147. package/dist/esm/presence.js +2 -0
  148. package/dist/esm/presence.js.map +1 -0
  149. package/dist/esm/protocol.d.ts +145 -0
  150. package/dist/esm/protocol.js +3 -0
  151. package/dist/esm/protocol.js.map +1 -0
  152. package/dist/esm/refs.d.ts +5 -0
  153. package/dist/esm/refs.js +17 -0
  154. package/dist/esm/refs.js.map +1 -0
  155. package/dist/esm/replica.d.ts +28 -0
  156. package/dist/esm/replica.js +26 -0
  157. package/dist/esm/replica.js.map +1 -0
  158. package/dist/esm/schema/defaults.d.ts +4 -0
  159. package/dist/esm/schema/defaults.js +66 -0
  160. package/dist/esm/schema/defaults.js.map +1 -0
  161. package/dist/esm/schema/defaults.test.d.ts +1 -0
  162. package/dist/esm/schema/defaults.test.js +19 -0
  163. package/dist/esm/schema/defaults.test.js.map +1 -0
  164. package/dist/esm/schema/index.d.ts +8 -0
  165. package/dist/esm/schema/index.js +10 -0
  166. package/dist/esm/schema/index.js.map +1 -0
  167. package/dist/esm/schema/indexFilters.d.ts +6 -0
  168. package/dist/esm/schema/indexFilters.js +23 -0
  169. package/dist/esm/schema/indexFilters.js.map +1 -0
  170. package/dist/esm/schema/types/collection.d.ts +51 -0
  171. package/dist/esm/schema/types/collection.js +2 -0
  172. package/dist/esm/schema/types/collection.js.map +1 -0
  173. package/dist/esm/schema/types/compounds.d.ts +12 -0
  174. package/dist/esm/schema/types/compounds.js +2 -0
  175. package/dist/esm/schema/types/compounds.js.map +1 -0
  176. package/dist/esm/schema/types/fields.d.ts +59 -0
  177. package/dist/esm/schema/types/fields.js +2 -0
  178. package/dist/esm/schema/types/fields.js.map +1 -0
  179. package/dist/esm/schema/types/filters.d.ts +29 -0
  180. package/dist/esm/schema/types/filters.js +2 -0
  181. package/dist/esm/schema/types/filters.js.map +1 -0
  182. package/dist/esm/schema/types/shapes.d.ts +21 -0
  183. package/dist/esm/schema/types/shapes.js +2 -0
  184. package/dist/esm/schema/types/shapes.js.map +1 -0
  185. package/dist/esm/schema/types/synthetics.d.ts +28 -0
  186. package/dist/esm/schema/types/synthetics.js +2 -0
  187. package/dist/esm/schema/types/synthetics.js.map +1 -0
  188. package/dist/esm/schema/types.d.ts +18 -0
  189. package/dist/esm/schema/types.js +7 -0
  190. package/dist/esm/schema/types.js.map +1 -0
  191. package/dist/esm/timestamp.d.ts +48 -0
  192. package/dist/esm/timestamp.js +229 -0
  193. package/dist/esm/timestamp.js.map +1 -0
  194. package/dist/esm/timestamp.test.d.ts +1 -0
  195. package/dist/esm/timestamp.test.js +92 -0
  196. package/dist/esm/timestamp.test.js.map +1 -0
  197. package/dist/esm/undo.d.ts +3 -0
  198. package/dist/esm/undo.js +195 -0
  199. package/dist/esm/undo.js.map +1 -0
  200. package/dist/esm/utils.d.ts +15 -0
  201. package/dist/esm/utils.js +114 -0
  202. package/dist/esm/utils.js.map +1 -0
  203. package/dist/esm/utils.test.d.ts +1 -0
  204. package/dist/esm/utils.test.js +35 -0
  205. package/dist/esm/utils.test.js.map +1 -0
  206. package/dist/tsconfig-cjs.tsbuildinfo +1 -0
  207. package/dist/tsconfig.tsbuildinfo +1 -0
  208. package/package.json +38 -0
  209. package/src/EventSubscriber.ts +81 -0
  210. package/src/baseline.ts +9 -0
  211. package/src/batching.ts +135 -0
  212. package/src/files.ts +34 -0
  213. package/src/index.ts +26 -0
  214. package/src/indexes.test.ts +158 -0
  215. package/src/indexes.ts +151 -0
  216. package/src/migration.ts +391 -0
  217. package/src/oids.test.ts +395 -0
  218. package/src/oids.ts +442 -0
  219. package/src/operation.test.ts +966 -0
  220. package/src/operation.ts +755 -0
  221. package/src/patch.ts +277 -0
  222. package/src/presence.ts +29 -0
  223. package/src/protocol.ts +184 -0
  224. package/src/refs.ts +16 -0
  225. package/src/replica.ts +29 -0
  226. package/src/schema/defaults.test.ts +19 -0
  227. package/src/schema/defaults.ts +72 -0
  228. package/src/schema/index.ts +28 -0
  229. package/src/schema/indexFilters.ts +49 -0
  230. package/src/schema/types/collection.ts +133 -0
  231. package/src/schema/types/compounds.ts +33 -0
  232. package/src/schema/types/fields.ts +85 -0
  233. package/src/schema/types/filters.ts +39 -0
  234. package/src/schema/types/shapes.ts +83 -0
  235. package/src/schema/types/synthetics.ts +45 -0
  236. package/src/schema/types.ts +28 -0
  237. package/src/timestamp.test.ts +113 -0
  238. package/src/timestamp.ts +284 -0
  239. package/src/undo.ts +205 -0
  240. package/src/utils.test.ts +38 -0
  241. package/src/utils.ts +137 -0
package/src/patch.ts ADDED
@@ -0,0 +1,277 @@
1
+ /**
2
+ * High-level patch creation for use with complex nested objects.
3
+ */
4
+
5
+ import { createRef, createSubOid, KeyPath, ObjectIdentifier } from './oids.js';
6
+ import {
7
+ diffToPatches,
8
+ initialToPatches,
9
+ ObjectRef,
10
+ Operation,
11
+ PropertyName,
12
+ } from './operation.js';
13
+ import { isRef } from './refs.js';
14
+ import { isObject } from './utils.js';
15
+
16
+ export class PatchCreator {
17
+ constructor(
18
+ private getNow: () => string,
19
+ private createSubId?: () => string,
20
+ ) {}
21
+
22
+ createDiff = (
23
+ from: any,
24
+ to: any,
25
+ keyPath: KeyPath,
26
+ options: { mergeUnknownObjects?: boolean; defaultUndefined?: boolean } = {},
27
+ ) => {
28
+ return diffToPatches(
29
+ from,
30
+ to,
31
+ this.getNow,
32
+ keyPath,
33
+ this.createSubId,
34
+ [],
35
+ options,
36
+ );
37
+ };
38
+
39
+ createInitialize = (obj: any, oid: ObjectIdentifier) => {
40
+ return initialToPatches(obj, oid, this.getNow, this.createSubId);
41
+ };
42
+
43
+ createSet = (
44
+ oid: ObjectIdentifier,
45
+ key: PropertyName,
46
+ value: any,
47
+ keyPath: KeyPath,
48
+ ): Operation[] => {
49
+ // incoming value must be normalized. if it's not a primitive, it and all sub-objects
50
+ // must be created
51
+ if (!isObject(value) || isRef(value)) {
52
+ return [
53
+ {
54
+ oid,
55
+ timestamp: this.getNow(),
56
+ data: {
57
+ op: 'set',
58
+ name: key,
59
+ value,
60
+ },
61
+ },
62
+ ];
63
+ } else {
64
+ const itemOid = createSubOid(oid, key, this.createSubId);
65
+ return [
66
+ // since we're setting a complex nested object, we can initialize it wholesale.
67
+ // no diffing to do.
68
+ ...initialToPatches(value, itemOid, this.getNow),
69
+ // then set the reference to the object
70
+ {
71
+ oid,
72
+ timestamp: this.getNow(),
73
+ data: {
74
+ op: 'set',
75
+ value: createRef(itemOid),
76
+ name: key,
77
+ },
78
+ },
79
+ ];
80
+ }
81
+ };
82
+
83
+ createRemove = (oid: ObjectIdentifier, key: PropertyName): Operation[] => {
84
+ return [
85
+ {
86
+ oid,
87
+ timestamp: this.getNow(),
88
+ data: {
89
+ op: 'remove',
90
+ name: key,
91
+ },
92
+ },
93
+ ];
94
+ };
95
+
96
+ createListPush = (oid: ObjectIdentifier, value: any): Operation[] => {
97
+ if (!isObject(value)) {
98
+ return [
99
+ {
100
+ oid,
101
+ timestamp: this.getNow(),
102
+ data: {
103
+ op: 'list-push',
104
+ value,
105
+ },
106
+ },
107
+ ];
108
+ } else {
109
+ const itemOid = createSubOid(oid, 0, this.createSubId);
110
+ return [
111
+ ...initialToPatches(value, itemOid, this.getNow),
112
+ {
113
+ oid,
114
+ timestamp: this.getNow(),
115
+ data: {
116
+ op: 'list-push',
117
+ value: createRef(itemOid),
118
+ },
119
+ },
120
+ ];
121
+ }
122
+ };
123
+
124
+ createListAdd = (oid: ObjectIdentifier, value: any): Operation[] => {
125
+ if (isObject(value)) {
126
+ return [
127
+ {
128
+ oid,
129
+ timestamp: this.getNow(),
130
+ data: {
131
+ op: 'list-add',
132
+ value: createRef(value),
133
+ },
134
+ },
135
+ ];
136
+ } else {
137
+ return [
138
+ {
139
+ oid,
140
+ timestamp: this.getNow(),
141
+ data: {
142
+ op: 'list-add',
143
+ value,
144
+ },
145
+ },
146
+ ];
147
+ }
148
+ };
149
+
150
+ createListInsert = (
151
+ oid: ObjectIdentifier,
152
+ index: number,
153
+ value: any,
154
+ ): Operation[] => {
155
+ if (!isObject(value)) {
156
+ return [
157
+ {
158
+ oid,
159
+ timestamp: this.getNow(),
160
+ data: {
161
+ op: 'list-insert',
162
+ value,
163
+ index,
164
+ },
165
+ },
166
+ ];
167
+ } else {
168
+ const itemOid = createSubOid(oid, index, this.createSubId);
169
+ return [
170
+ ...initialToPatches(value, itemOid, this.getNow),
171
+ {
172
+ oid,
173
+ timestamp: this.getNow(),
174
+ data: {
175
+ op: 'list-insert',
176
+ value: createRef(itemOid),
177
+ index,
178
+ },
179
+ },
180
+ ];
181
+ }
182
+ };
183
+
184
+ createListRemove = (
185
+ oid: ObjectIdentifier,
186
+ value: any,
187
+ only?: 'first' | 'last',
188
+ ): Operation[] => {
189
+ return [
190
+ {
191
+ oid,
192
+ timestamp: this.getNow(),
193
+ data: {
194
+ op: 'list-remove',
195
+ value,
196
+ only,
197
+ },
198
+ },
199
+ ];
200
+ };
201
+
202
+ createListDelete = (
203
+ oid: ObjectIdentifier,
204
+ index: number,
205
+ count: number = 1,
206
+ ): Operation[] => {
207
+ return [
208
+ {
209
+ oid,
210
+ timestamp: this.getNow(),
211
+ data: {
212
+ op: 'list-delete',
213
+ index,
214
+ count,
215
+ },
216
+ },
217
+ ];
218
+ };
219
+
220
+ createListMoveByRef = (
221
+ oid: ObjectIdentifier,
222
+ value: ObjectRef,
223
+ index: number,
224
+ ): Operation[] => {
225
+ return [
226
+ {
227
+ oid,
228
+ timestamp: this.getNow(),
229
+ data: {
230
+ op: 'list-move-by-ref',
231
+ value,
232
+ index,
233
+ },
234
+ },
235
+ ];
236
+ };
237
+
238
+ createListMoveByIndex = (
239
+ oid: ObjectIdentifier,
240
+ fromIndex: number,
241
+ toIndex: number,
242
+ ): Operation[] => {
243
+ return [
244
+ {
245
+ oid,
246
+ timestamp: this.getNow(),
247
+ data: {
248
+ op: 'list-move-by-index',
249
+ from: fromIndex,
250
+ to: toIndex,
251
+ },
252
+ },
253
+ ];
254
+ };
255
+
256
+ createDelete = (oid: ObjectIdentifier): Operation[] => {
257
+ return [
258
+ {
259
+ oid,
260
+ timestamp: this.getNow(),
261
+ data: {
262
+ op: 'delete',
263
+ },
264
+ },
265
+ ];
266
+ };
267
+
268
+ createDeleteAll = (oids: ObjectIdentifier[]): Operation[] => {
269
+ return oids.map((oid) => ({
270
+ oid,
271
+ timestamp: this.getNow(),
272
+ data: {
273
+ op: 'delete',
274
+ },
275
+ }));
276
+ };
277
+ }
@@ -0,0 +1,29 @@
1
+ export interface UserInfo<Profile, Presence> {
2
+ /**
3
+ * This is the ID representing the user who is utilizing a
4
+ * replica client to connect to the storage network.
5
+ * One user may have multiple replica clients active at once,
6
+ * but their presence will only reflect the most recent
7
+ * replica used.
8
+ */
9
+ id: string;
10
+ /**
11
+ * This is the ID representing the replica client that is
12
+ * connected to the storage network. This is used to
13
+ * identify the client when sending messages to it.
14
+ */
15
+ replicaId: string;
16
+
17
+ /**
18
+ * This is the user's server profile. This data is associated
19
+ * with the logged in user of the app and cannot be modified
20
+ * by the local replica directly.
21
+ */
22
+ profile: Profile;
23
+
24
+ /**
25
+ * Presence info which can update frequently as a replica
26
+ * makes changes. The shape of presence is up to you.
27
+ */
28
+ presence: Presence;
29
+ }
@@ -0,0 +1,184 @@
1
+ // client ID, and therefore library ID, is inferred
2
+
3
+ import { DocumentBaseline } from './baseline.js';
4
+ import { Operation, OperationPatch } from './operation.js';
5
+ import { UserInfo } from './presence.js';
6
+
7
+ export type HeartbeatMessage = {
8
+ type: 'heartbeat';
9
+ timestamp: string;
10
+ replicaId: string;
11
+ };
12
+
13
+ export type HeartbeatResponseMessage = {
14
+ type: 'heartbeat-response';
15
+ };
16
+
17
+ /**
18
+ * Used by clients to indicate they have
19
+ * successfully applied all operations from the
20
+ * server up to this one. Uses the nonce from the
21
+ * operation rebroadcast message as the ack.
22
+ */
23
+ export type AckMessage = {
24
+ type: 'ack';
25
+ replicaId: string;
26
+ timestamp?: string;
27
+ nonce?: string;
28
+ };
29
+
30
+ export type OperationMessage = {
31
+ type: 'op';
32
+ replicaId: string;
33
+ operations: Operation[];
34
+ timestamp: string;
35
+ };
36
+
37
+ export type OperationRebroadcastMessage = {
38
+ type: 'op-re';
39
+ operations: Operation[];
40
+ baselines: DocumentBaseline[];
41
+ replicaId: string;
42
+ globalAckTimestamp: string | undefined;
43
+ ackThisNonce?: string;
44
+ };
45
+
46
+ export type SyncMessage = {
47
+ type: 'sync';
48
+ /** This client's replica ID */
49
+ replicaId: string;
50
+ /** the logical time this message was sent */
51
+ timestamp: string;
52
+ /** Any new operations created since the requested time */
53
+ operations: Operation[];
54
+ /** Any new baselines created since the requested time */
55
+ baselines: DocumentBaseline[];
56
+ /** the schema version known by this client */
57
+ schemaVersion: number;
58
+ /**
59
+ * the client may have lost its local data, in which
60
+ * case it may set this flag to be treated as a new client
61
+ * and receive a full baseline
62
+ */
63
+ resyncAll?: boolean;
64
+ /**
65
+ * timestamp of when the replica changes began. null means
66
+ * full changeset from start of time
67
+ */
68
+ since: string | null;
69
+ };
70
+
71
+ export type SyncResponseMessage = {
72
+ type: 'sync-resp';
73
+ // operations this client should apply
74
+ operations: Operation[];
75
+ // baselines this client should apply
76
+ baselines: DocumentBaseline[];
77
+ /**
78
+ * If this flag is set, the client should discard local data
79
+ * and reset to incoming data only. Used when a client requested
80
+ * resyncAll in its sync message, or for clients which have been
81
+ * offline for too long. When specified true, provideChangesSince
82
+ * should be ignored.
83
+ */
84
+ overwriteLocalData: boolean;
85
+ /**
86
+ * Update client on the global ack
87
+ */
88
+ globalAckTimestamp: string | undefined;
89
+
90
+ /**
91
+ * A map of connected clients' presences values
92
+ */
93
+ peerPresence: Record<string, UserInfo<any, any>>;
94
+
95
+ /**
96
+ * The timestamp sent in the original sync message -
97
+ * this confirms the server has received the client's
98
+ * state up to this point. Subsequent syncs should not
99
+ * include operations or baselines older than this timestamp.
100
+ */
101
+ ackedTimestamp: string;
102
+
103
+ /**
104
+ * The client should respond with a sync-ack containing
105
+ * this nonce to confirm it has received this message if
106
+ * it is not undefined
107
+ */
108
+ ackThisNonce?: string;
109
+ };
110
+
111
+ /** @deprecated use ack */
112
+ export type SyncAckMessage = {
113
+ type: 'sync-ack';
114
+ /** The client's replica ID */
115
+ replicaId: string;
116
+ /** the logical time this message was sent */
117
+ timestamp: string;
118
+ /** the nonce sent in the sync-resp message */
119
+ nonce: string;
120
+ };
121
+
122
+ export type PresenceUpdateMessage = {
123
+ type: 'presence-update';
124
+ /** The client's replica ID */
125
+ replicaId: string;
126
+ /** new presence value */
127
+ presence: any;
128
+ };
129
+
130
+ export type PresenceChangedMessage = {
131
+ type: 'presence-changed';
132
+ /** The client's replica ID */
133
+ replicaId: string;
134
+
135
+ userInfo: UserInfo<any, any>;
136
+ };
137
+
138
+ /**
139
+ * This is only emitted when all of a user's replicas
140
+ * go offline.
141
+ */
142
+ export type PresenceOfflineMessage = {
143
+ type: 'presence-offline';
144
+ userId: string;
145
+ /** The last replicaID seen by the server before the user was offline */
146
+ replicaId: string;
147
+ };
148
+
149
+ export type GlobalAckMessage = {
150
+ type: 'global-ack';
151
+ timestamp: string;
152
+ };
153
+
154
+ export type ForbiddenMessage = {
155
+ type: 'forbidden';
156
+ };
157
+
158
+ export type ServerAckMessage = {
159
+ type: 'server-ack';
160
+ timestamp: string;
161
+ };
162
+
163
+ export type ServerNeedSinceMessage = {
164
+ type: 'need-since';
165
+ since: string | null;
166
+ };
167
+
168
+ export type ClientMessage =
169
+ | HeartbeatMessage
170
+ | SyncMessage
171
+ | OperationMessage
172
+ | AckMessage
173
+ | SyncAckMessage
174
+ | PresenceUpdateMessage;
175
+ export type ServerMessage =
176
+ | HeartbeatResponseMessage
177
+ | SyncResponseMessage
178
+ | OperationRebroadcastMessage
179
+ | PresenceChangedMessage
180
+ | PresenceOfflineMessage
181
+ | GlobalAckMessage
182
+ | ForbiddenMessage
183
+ | ServerAckMessage
184
+ | ServerNeedSinceMessage;
package/src/refs.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { FileRef, isFileRef } from './files.js';
2
+ import { isObjectRef, ObjectRef } from './operation.js';
3
+
4
+ export function isRef(obj: any) {
5
+ return isObjectRef(obj) || isFileRef(obj);
6
+ }
7
+
8
+ export function compareRefs(a: any, b: any) {
9
+ if (a === b) return true;
10
+ if (!isRef(a) || !isRef(b)) return false;
11
+ if (a['@@type'] !== b['@@type']) return false;
12
+ if (a.id !== b.id) return false;
13
+ return true;
14
+ }
15
+
16
+ export type Ref = ObjectRef | FileRef;
package/src/replica.ts ADDED
@@ -0,0 +1,29 @@
1
+ export interface ReplicaInfo {
2
+ id: string;
3
+ ackedLogicalTime: string | null;
4
+ }
5
+
6
+ /**
7
+ * Different token types allow different replica client behaviors.
8
+ * - Realtime: allows the client to subscribe to realtime events.
9
+ * - Push: allows the client to push and pull data with HTTP, but not use realtime.
10
+ * - PassivePush: allows the client to push and pull data with HTTP, but offline changes
11
+ * will be discarded on reconnect.
12
+ * - PassiveRealtime: allows the client to subscribe to realtime events, but offline changes
13
+ * will be discarded on reconnect.
14
+ * - ReadOnlyPull: the client may only pull changes using HTTP. It may not subscribe
15
+ * to realtime events or push changes.
16
+ * - ReadOnlyRealtime: the client may only subscribe to realtime events or pull from HTTP.
17
+ * It may not push changes.
18
+ *
19
+ * Choosing the right token type can optimize client storage metrics significantly when
20
+ * many replicas are connecting to a library.
21
+ */
22
+ export enum ReplicaType {
23
+ Realtime,
24
+ Push,
25
+ PassiveRealtime,
26
+ PassivePush,
27
+ ReadOnlyPull,
28
+ ReadOnlyRealtime,
29
+ }
@@ -0,0 +1,19 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getFieldDefault } from './defaults.js';
3
+ import { assignOid, createOid, getOid, maybeGetOid } from '../oids.js';
4
+
5
+ describe('getting field defaults', () => {
6
+ it('does not apply the same oid from the previous application', () => {
7
+ const field = {
8
+ type: 'any',
9
+ default: {
10
+ id: '123',
11
+ name: 'test',
12
+ },
13
+ } as const;
14
+ const first = getFieldDefault(field);
15
+ assignOid(first, createOid('test', '1', []));
16
+ const second = getFieldDefault(field);
17
+ expect(maybeGetOid(second)).toBe(undefined);
18
+ });
19
+ });
@@ -0,0 +1,72 @@
1
+ import { LEGACY_OID_KEY, OID_KEY } from '../oids.js';
2
+ import type { StorageFieldSchema, StorageCollectionSchema } from './types.js';
3
+
4
+ export function addFieldDefaults(
5
+ collection: StorageCollectionSchema,
6
+ value: any,
7
+ ) {
8
+ for (const [key, field] of Object.entries(collection.fields)) {
9
+ const defaultValue = getFieldDefault(field);
10
+ if (defaultValue !== undefined && value[key] === undefined) {
11
+ value[key] = defaultValue;
12
+ }
13
+ if (value[key]) {
14
+ traverseCollectionFieldsAndApplyDefaults(value[key], field);
15
+ }
16
+ }
17
+ return value;
18
+ }
19
+
20
+ export function traverseCollectionFieldsAndApplyDefaults(
21
+ value: any,
22
+ field: StorageFieldSchema,
23
+ ) {
24
+ if (value === undefined || value === null) return value;
25
+ if (field.type === 'object') {
26
+ for (const [key, subField] of Object.entries(field.properties)) {
27
+ if (value[key] === undefined) {
28
+ const defaultValue = getFieldDefault(subField);
29
+ if (defaultValue !== undefined) {
30
+ value[key] = defaultValue;
31
+ }
32
+ }
33
+ traverseCollectionFieldsAndApplyDefaults(value[key], subField);
34
+ }
35
+ } else if (field.type === 'array') {
36
+ for (const item of value) {
37
+ traverseCollectionFieldsAndApplyDefaults(item, field.items);
38
+ }
39
+ } else if (field.type === 'map') {
40
+ for (const [key, item] of Object.entries(value)) {
41
+ // santiy check to weed out any id field
42
+ if (key === OID_KEY || key === LEGACY_OID_KEY) continue;
43
+ traverseCollectionFieldsAndApplyDefaults(item, field.values);
44
+ }
45
+ }
46
+ }
47
+
48
+ export function getFieldDefault(field: StorageFieldSchema) {
49
+ if (
50
+ field.type === 'string' ||
51
+ field.type === 'number' ||
52
+ field.type === 'boolean' ||
53
+ field.type === 'any'
54
+ ) {
55
+ if (field.default && typeof field.default === 'function') {
56
+ return field.default();
57
+ } else if (field.default !== undefined) {
58
+ // TODO: structuredClone?
59
+ return JSON.parse(JSON.stringify(field.default));
60
+ }
61
+ }
62
+ if (field.type === 'array') {
63
+ return [];
64
+ }
65
+ if (field.type === 'map') {
66
+ return {};
67
+ }
68
+ if (field.type !== 'any' && field.nullable) {
69
+ return null;
70
+ }
71
+ return undefined;
72
+ }
@@ -0,0 +1,28 @@
1
+ import {
2
+ CollectionCompoundIndices,
3
+ StorageCollectionSchema,
4
+ StorageFieldsSchema,
5
+ StorageSchema,
6
+ StorageSyntheticIndices,
7
+ } from './types.js';
8
+
9
+ export function collection<
10
+ Fields extends StorageFieldsSchema,
11
+ Synthetics extends StorageSyntheticIndices<Fields>,
12
+ Compounds extends CollectionCompoundIndices<Fields, Synthetics>,
13
+ >(input: StorageCollectionSchema<Fields, Synthetics, Compounds>) {
14
+ return input;
15
+ }
16
+
17
+ export function schema<
18
+ Schema extends StorageSchema<{
19
+ [key: string]: StorageCollectionSchema<any, any, any>;
20
+ }>,
21
+ >(input: Schema) {
22
+ return input;
23
+ }
24
+
25
+ export * from './types.js';
26
+
27
+ export * from './indexFilters.js';
28
+ export * from './defaults.js';
@@ -0,0 +1,49 @@
1
+ import {
2
+ CollectionCompoundIndexFilter,
3
+ CollectionFilter,
4
+ MatchCollectionIndexFilter,
5
+ RangeCollectionIndexFilter,
6
+ SortIndexFilter,
7
+ StartsWithIndexFilter,
8
+ } from './types.js';
9
+
10
+ export function isMatchIndexFilter(
11
+ filter: CollectionFilter,
12
+ ): filter is MatchCollectionIndexFilter {
13
+ return (filter as any).equals !== undefined;
14
+ }
15
+
16
+ export function isRangeIndexFilter(
17
+ filter: CollectionFilter,
18
+ ): filter is RangeCollectionIndexFilter {
19
+ return (
20
+ (filter as any).gte !== undefined ||
21
+ (filter as any).lte !== undefined ||
22
+ (filter as any).gt !== undefined ||
23
+ (filter as any).lt !== undefined
24
+ );
25
+ }
26
+
27
+ export function isCompoundIndexFilter(
28
+ filter: CollectionFilter,
29
+ ): filter is CollectionCompoundIndexFilter {
30
+ return !!(filter as any).match;
31
+ }
32
+
33
+ export function isStartsWithIndexFilter(
34
+ filter: CollectionFilter,
35
+ ): filter is StartsWithIndexFilter {
36
+ return (filter as any).startsWith !== undefined;
37
+ }
38
+
39
+ export function isSortIndexFilter(
40
+ filter: CollectionFilter,
41
+ ): filter is SortIndexFilter {
42
+ return (
43
+ !isRangeIndexFilter(filter) &&
44
+ !isMatchIndexFilter(filter) &&
45
+ !isCompoundIndexFilter(filter) &&
46
+ !isStartsWithIndexFilter(filter) &&
47
+ (filter as any).order
48
+ );
49
+ }