@mblaney/holster 1.0.2 → 1.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblaney/holster",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Holster is a service that allows synchronising data between devices using Node, Deno, Bun or the browser.",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/src/radisk.js CHANGED
@@ -135,11 +135,12 @@ const Radisk = opt => {
135
135
  save.end = file
136
136
  // ! is used as the first file name as it's the first printable
137
137
  // character, so always matches as lexically less than any node.
138
- save.mix(save.file || "!", save.start, save.end)
139
- return true
138
+ // Also save.start can be set to undefined by a previous call to
139
+ // save.mix, so don't continue in this case.
140
+ if (save.start) save.mix(save.file || "!", save.start, save.end)
141
+ } else {
142
+ save.file = file
140
143
  }
141
-
142
- save.file = file
143
144
  },
144
145
  mix: (file, start, end) => {
145
146
  save.start = save.end = save.file = u
@@ -151,7 +152,7 @@ const Radisk = opt => {
151
152
 
152
153
  if (end && end < key) {
153
154
  save.start = key
154
- return save.start
155
+ return
155
156
  }
156
157
 
157
158
  disk(key, value)
@@ -185,8 +186,13 @@ const Radisk = opt => {
185
186
  Radisk.encode(k) +
186
187
  (typeof value === "undefined" ? "" : "=" + Radisk.encode(value)) +
187
188
  "\n"
188
- // Cannot split the file if only have one entry to write.
189
- if (write.count > 1 && write.text.length + enc.length > opt.size) {
189
+ // Cannot split the file if only have one entry to write. Also don't
190
+ // start a split if in the middle of writing a node (pre > 0).
191
+ if (
192
+ pre.length === 0 &&
193
+ write.count > 1 &&
194
+ write.text.length + enc.length > opt.size
195
+ ) {
190
196
  write.text = ""
191
197
  // Otherwise split the entries in half.
192
198
  write.limit = Math.ceil(write.count / 2)
@@ -214,10 +220,6 @@ const Radisk = opt => {
214
220
  } else {
215
221
  write.file = key.substring(0, end)
216
222
  }
217
- // write.limit can be reached after already writing properties of
218
- // the current node, so remove it from write.sub before writing to
219
- // disk so that it's not duplicated across files.
220
- write.sub(write.file, null)
221
223
  write.count = 0
222
224
  radisk.write(name, write.sub, write.next)
223
225
  return true
package/src/store.js CHANGED
@@ -35,11 +35,11 @@ const fileSystem = opt => {
35
35
  })
36
36
  },
37
37
  put: (file, data, cb) => {
38
- var random = Math.random().toString(36).slice(-9)
39
38
  // Don't put tmp files under dir so that they're not listed.
40
- var tmp = file + "." + random + ".tmp"
39
+ var tmp = file + "." + utils.text.random(9) + ".tmp"
41
40
  fs.writeFile(tmp, data, err => {
42
41
  if (err) {
42
+ console.log("fs.writeFile error:", err)
43
43
  cb(err)
44
44
  return
45
45
  }
@@ -90,7 +90,7 @@ const fileSystem = opt => {
90
90
 
91
91
  return {
92
92
  get: (file, cb) => {
93
- if (db) {
93
+ const _get = (file, cb) => {
94
94
  const tx = db.transaction([dir], "readonly")
95
95
  const req = tx.objectStore(dir).get(file)
96
96
  req.onerror = () => {
@@ -99,12 +99,28 @@ const fileSystem = opt => {
99
99
  req.onsuccess = () => {
100
100
  cb(null, req.result)
101
101
  }
102
- } else {
103
- cb("error indexedDB not available")
104
102
  }
103
+ if (db) {
104
+ _get(file, cb)
105
+ return
106
+ }
107
+
108
+ let retry = 0
109
+ const interval = setInterval(() => {
110
+ if (db) {
111
+ clearInterval(interval)
112
+ _get(file, cb)
113
+ return
114
+ }
115
+
116
+ if (retry++ > 5) {
117
+ clearInterval(interval)
118
+ cb("error indexedDB not available")
119
+ }
120
+ }, 1000)
105
121
  },
106
122
  put: (file, data, cb) => {
107
- if (db) {
123
+ const _put = (file, data, cb) => {
108
124
  const tx = db.transaction([dir], "readwrite")
109
125
  const req = tx.objectStore(dir).put(data, file)
110
126
  req.onerror = () => {
@@ -113,12 +129,28 @@ const fileSystem = opt => {
113
129
  req.onsuccess = () => {
114
130
  cb(null)
115
131
  }
116
- } else {
117
- cb("error indexedDB not available")
118
132
  }
133
+ if (db) {
134
+ _put(file, data, cb)
135
+ return
136
+ }
137
+
138
+ let retry = 0
139
+ const interval = setInterval(() => {
140
+ if (db) {
141
+ clearInterval(interval)
142
+ _put(file, data, cb)
143
+ return
144
+ }
145
+
146
+ if (retry++ > 5) {
147
+ clearInterval(interval)
148
+ cb("error indexedDB not available")
149
+ }
150
+ }, 1000)
119
151
  },
120
152
  list: cb => {
121
- if (db) {
153
+ const _list = cb => {
122
154
  const tx = db.transaction([dir], "readonly")
123
155
  const req = tx.objectStore(dir).getAllKeys()
124
156
  req.onerror = () => console.log("error getting keys for", dir)
@@ -126,10 +158,26 @@ const fileSystem = opt => {
126
158
  req.result.forEach(cb)
127
159
  cb()
128
160
  }
129
- } else {
130
- console.log("error indexedDB not available")
131
- cb()
132
161
  }
162
+ if (db) {
163
+ _list(cb)
164
+ return
165
+ }
166
+
167
+ let retry = 0
168
+ const interval = setInterval(() => {
169
+ if (db) {
170
+ clearInterval(interval)
171
+ _list(cb)
172
+ return
173
+ }
174
+
175
+ if (retry++ > 5) {
176
+ clearInterval(interval)
177
+ console.log("error indexedDB not available")
178
+ cb()
179
+ }
180
+ }, 1000)
133
181
  },
134
182
  }
135
183
  }
package/src/wire.js CHANGED
@@ -220,8 +220,22 @@ const Wire = opt => {
220
220
 
221
221
  const send = (data, isBinary) => {
222
222
  clients().forEach(client => {
223
- if (client.readyState === WebSocket.OPEN) {
223
+ if (client && client.readyState === WebSocket.OPEN) {
224
224
  client.send(data, {binary: isBinary})
225
+ } else {
226
+ let retry = 0
227
+ const interval = setInterval(() => {
228
+ if (client && client.readyState === WebSocket.OPEN) {
229
+ clearInterval(interval)
230
+ client.send(data, {binary: isBinary})
231
+ return
232
+ }
233
+
234
+ if (retry++ > 5) {
235
+ clearInterval(interval)
236
+ console.log("websocket not available")
237
+ }
238
+ }, 1000)
225
239
  }
226
240
  })
227
241
  }
@@ -257,7 +271,19 @@ const Wire = opt => {
257
271
  if (peer && peer.readyState === WebSocket.OPEN) {
258
272
  peer.send(data)
259
273
  } else {
260
- console.log("websocket not available")
274
+ let retry = 0
275
+ const interval = setInterval(() => {
276
+ if (peer && peer.readyState === WebSocket.OPEN) {
277
+ clearInterval(interval)
278
+ peer.send(data)
279
+ return
280
+ }
281
+
282
+ if (retry++ > 5) {
283
+ clearInterval(interval)
284
+ console.log("websocket not available")
285
+ }
286
+ }, 1000)
261
287
  }
262
288
  })
263
289
  }
@@ -265,10 +291,9 @@ const Wire = opt => {
265
291
  opt.peers = [`ws://localhost:${opt.port || 8765}`]
266
292
  }
267
293
  opt.peers.forEach(peer => {
268
- let ws = new WebSocket(peer)
269
- peers.push(ws)
270
294
  const start = () => {
271
- if (!ws) ws = new WebSocket(peer)
295
+ let ws = new WebSocket(peer)
296
+ peers.push(ws)
272
297
  ws.onclose = c => {
273
298
  if (peers.indexOf(ws) !== -1) {
274
299
  peers.splice(peers.indexOf(ws), 1)
@@ -25,6 +25,8 @@ describe("radisk", () => {
25
25
  },
26
26
  }
27
27
  const radisk = Radisk(opt)
28
+ const big =
29
+ "file size is only 100 bytes so writing this value requires calling slice"
28
30
 
29
31
  test("write and read from memory", (t, done) => {
30
32
  radisk("key", "value", () => {
@@ -164,8 +166,6 @@ describe("radisk", () => {
164
166
  })
165
167
 
166
168
  test("write and read value bigger than file size", (t, done) => {
167
- const big =
168
- "file size is only 100 bytes so writing this value requires calling slice"
169
169
  radisk("newFile", big)
170
170
  radisk("newFile", (err, value) => {
171
171
  assert.equal(value, big)
@@ -175,11 +175,8 @@ describe("radisk", () => {
175
175
  "!":
176
176
  '\x1F+0\x1F#\x1F"key\x1F=\x1F"value\ncontinued\x0312345\x1F\n' +
177
177
  '\x1F+1\x1F#\x1F"A\x1F=\x1F"valueA\x1F\n' +
178
- '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n' +
179
- '\x1F+1\x1F#\x1F"C\x1F=\x1F \x1F\n',
180
- keyC:
181
- '\x1F+0\x1F#\x1F"keyC\x1F=\x1F"valueC\x1F\n' +
182
- '\x1F+0\x1F#\x1F"newFile\x1F=\x1F \x1F\n',
178
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
179
+ keyC: '\x1F+0\x1F#\x1F"keyC\x1F=\x1F"valueC\x1F\n',
183
180
  newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"' + big + "\x1F\n",
184
181
  })
185
182
  radisk("newFile", (err, value) => {
@@ -188,4 +185,150 @@ describe("radisk", () => {
188
185
  })
189
186
  }, 10)
190
187
  })
188
+
189
+ // This tests trying to add to a file that is already too long, so needs to
190
+ // split again. Previously wasn't able to do this and would loop.
191
+ test("write and read a small value after split", (t, done) => {
192
+ radisk("small", "small value")
193
+ radisk("small", (err, value) => {
194
+ assert.equal(value, "small value")
195
+ })
196
+ setTimeout(() => {
197
+ assert.deepEqual(puts, {
198
+ "!":
199
+ '\x1F+0\x1F#\x1F"key\x1F=\x1F"value\ncontinued\x0312345\x1F\n' +
200
+ '\x1F+1\x1F#\x1F"A\x1F=\x1F"valueA\x1F\n' +
201
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
202
+ keyC: '\x1F+0\x1F#\x1F"keyC\x1F=\x1F"valueC\x1F\n',
203
+ newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"' + big + "\x1F\n",
204
+ small: '\x1F+0\x1F#\x1F"small\x1F=\x1F"small value\x1F\n',
205
+ })
206
+ done()
207
+ }, 10)
208
+ })
209
+
210
+ // This tests how existing files are handled when they are made smaller than
211
+ // the maximum file size. (They should be kept as they are created so that
212
+ // we don't have to deal with removing files.)
213
+ test("write small value to newFile", (t, done) => {
214
+ radisk("newFile", "removed...")
215
+ radisk("newFile", (err, value) => {
216
+ assert.equal(value, "removed...")
217
+ })
218
+ setTimeout(() => {
219
+ assert.deepEqual(puts, {
220
+ "!":
221
+ '\x1F+0\x1F#\x1F"key\x1F=\x1F"value\ncontinued\x0312345\x1F\n' +
222
+ '\x1F+1\x1F#\x1F"A\x1F=\x1F"valueA\x1F\n' +
223
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
224
+ keyC: '\x1F+0\x1F#\x1F"keyC\x1F=\x1F"valueC\x1F\n',
225
+ newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"removed...\x1F\n',
226
+ small: '\x1F+0\x1F#\x1F"small\x1F=\x1F"small value\x1F\n',
227
+ })
228
+ done()
229
+ }, 10)
230
+ })
231
+
232
+ // This tests ignoring the maximum file size when writing sub-keys. Since
233
+ // radisk.write has already been called for a key it would previously get
234
+ // duplicated when trying to split due to reaching the max length.
235
+ test("write big value to first file", (t, done) => {
236
+ radisk("keyA", big)
237
+ radisk("keyA", (err, value) => {
238
+ assert.equal(value, big)
239
+ })
240
+ setTimeout(() => {
241
+ assert.deepEqual(puts, {
242
+ "!":
243
+ '\x1F+0\x1F#\x1F"key\x1F=\x1F"value\ncontinued\x0312345\x1F\n' +
244
+ '\x1F+1\x1F#\x1F"A\x1F=\x1F"' +
245
+ big +
246
+ "\x1F\n" +
247
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
248
+ keyC: '\x1F+0\x1F#\x1F"keyC\x1F=\x1F"valueC\x1F\n',
249
+ newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"removed...\x1F\n',
250
+ small: '\x1F+0\x1F#\x1F"small\x1F=\x1F"small value\x1F\n',
251
+ })
252
+ done()
253
+ }, 10)
254
+ })
255
+
256
+ test("add to last file", (t, done) => {
257
+ radisk("smallContinued", "continued value")
258
+ radisk("smallContinued", (err, value) => {
259
+ assert.equal(value, "continued value")
260
+ })
261
+ setTimeout(() => {
262
+ assert.deepEqual(puts, {
263
+ "!":
264
+ '\x1F+0\x1F#\x1F"key\x1F=\x1F"value\ncontinued\x0312345\x1F\n' +
265
+ '\x1F+1\x1F#\x1F"A\x1F=\x1F"' +
266
+ big +
267
+ "\x1F\n" +
268
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
269
+ keyC: '\x1F+0\x1F#\x1F"keyC\x1F=\x1F"valueC\x1F\n',
270
+ newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"removed...\x1F\n',
271
+ small:
272
+ '\x1F+0\x1F#\x1F"small\x1F=\x1F"small value\x1F\n' +
273
+ '\x1F+1\x1F#\x1F"Continued\x1F=\x1F"continued value\x1F\n',
274
+ })
275
+ done()
276
+ }, 10)
277
+ })
278
+
279
+ test("set a key to null and add a value to that file", (t, done) => {
280
+ radisk("keyC", null)
281
+ radisk("keyC", (err, value) => {
282
+ assert.equal(value, null)
283
+ })
284
+ radisk("keyD", "valueD")
285
+ radisk("keyD", (err, value) => {
286
+ assert.equal(value, "valueD")
287
+ })
288
+ setTimeout(() => {
289
+ assert.deepEqual(puts, {
290
+ "!":
291
+ '\x1F+0\x1F#\x1F"key\x1F=\x1F"value\ncontinued\x0312345\x1F\n' +
292
+ '\x1F+1\x1F#\x1F"A\x1F=\x1F"' +
293
+ big +
294
+ "\x1F\n" +
295
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
296
+ keyC:
297
+ '\x1F+0\x1F#\x1F"key\x1F\n\x1F+1\x1F#\x1F"C\x1F=\x1F \x1F\n' +
298
+ '\x1F+1\x1F#\x1F"D\x1F=\x1F"valueD\x1F\n',
299
+ newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"removed...\x1F\n',
300
+ small:
301
+ '\x1F+0\x1F#\x1F"small\x1F=\x1F"small value\x1F\n' +
302
+ '\x1F+1\x1F#\x1F"Continued\x1F=\x1F"continued value\x1F\n',
303
+ })
304
+ done()
305
+ }, 10)
306
+ })
307
+
308
+ test("set first keys to null", (t, done) => {
309
+ radisk("key", null)
310
+ radisk("key", (err, value) => {
311
+ assert.equal(value, null)
312
+ })
313
+ radisk("keyA", null)
314
+ radisk("keyA", (err, value) => {
315
+ assert.equal(value, null)
316
+ })
317
+ setTimeout(() => {
318
+ assert.deepEqual(puts, {
319
+ "!":
320
+ '\x1F+0\x1F#\x1F"key\x1F=\x1F \x1F\n' +
321
+ '\x1F+1\x1F#\x1F"A\x1F=\x1F \x1F\n' +
322
+ '\x1F+1\x1F#\x1F"B\x1F=\x1F"valueB\x1F\n',
323
+ keyC:
324
+ '\x1F+0\x1F#\x1F"key\x1F\n\x1F+1\x1F#\x1F"C\x1F=\x1F \x1F\n' +
325
+ '\x1F+1\x1F#\x1F"D\x1F=\x1F"valueD\x1F\n',
326
+ newFile: '\x1F+0\x1F#\x1F"newFile\x1F=\x1F"removed...\x1F\n',
327
+ small:
328
+ '\x1F+0\x1F#\x1F"small\x1F=\x1F"small value\x1F\n' +
329
+ '\x1F+1\x1F#\x1F"Continued\x1F=\x1F"continued value\x1F\n',
330
+ })
331
+ done()
332
+ }, 10)
333
+ })
191
334
  })
@@ -75,7 +75,7 @@ describe("store", () => {
75
75
  },
76
76
  err => {
77
77
  assert.equal(err, null)
78
- fs.access("test/multiple/keyC", err => {
78
+ fs.access("test/multiple/!", err => {
79
79
  assert.equal(err, null)
80
80
  fs.access("test/multiple/newFile", err => {
81
81
  assert.equal(err, null)
@@ -103,7 +103,7 @@ describe("store", () => {
103
103
  })
104
104
  })
105
105
 
106
- test("get keys from second file", (t, done) => {
106
+ test("get another key from first file", (t, done) => {
107
107
  multiple.get({"#": "keyC"}, (err, value) => {
108
108
  assert.deepEqual(value, {
109
109
  keyC: {
@@ -135,7 +135,7 @@ describe("store", () => {
135
135
  })
136
136
  })
137
137
 
138
- test("get key from third file", (t, done) => {
138
+ test("get key from second file", (t, done) => {
139
139
  multiple.get({"#": "newFile"}, (err, value) => {
140
140
  assert.deepEqual(value, {
141
141
  newFile: {