@fireproof/core 0.19.121 → 0.20.0-dev-preview-06

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 (63) hide show
  1. package/README.md +3 -2
  2. package/deno/index.d.ts +7 -0
  3. package/deno/index.js +66 -0
  4. package/deno/index.js.map +1 -0
  5. package/deno/metafile-esm.json +1 -0
  6. package/deno.json +2 -3
  7. package/index.cjs +1819 -1051
  8. package/index.cjs.map +1 -1
  9. package/index.d.cts +746 -333
  10. package/index.d.ts +746 -333
  11. package/index.js +1792 -1026
  12. package/index.js.map +1 -1
  13. package/metafile-cjs.json +1 -1
  14. package/metafile-esm.json +1 -1
  15. package/node/index.cjs +16 -293
  16. package/node/index.cjs.map +1 -1
  17. package/node/index.d.cts +4 -40
  18. package/node/index.d.ts +4 -40
  19. package/node/index.js +22 -237
  20. package/node/index.js.map +1 -1
  21. package/node/metafile-cjs.json +1 -1
  22. package/node/metafile-esm.json +1 -1
  23. package/package.json +12 -4
  24. package/react/index.cjs.map +1 -1
  25. package/react/index.js.map +1 -1
  26. package/react/metafile-cjs.json +1 -1
  27. package/react/metafile-esm.json +1 -1
  28. package/tests/blockstore/fp-envelope.test.ts-off +65 -0
  29. package/tests/blockstore/interceptor-gateway.test.ts +122 -0
  30. package/tests/blockstore/keyed-crypto-indexdb-file.test.ts +130 -0
  31. package/tests/blockstore/keyed-crypto.test.ts +73 -118
  32. package/tests/blockstore/loader.test.ts +18 -9
  33. package/tests/blockstore/store.test.ts +40 -31
  34. package/tests/blockstore/transaction.test.ts +14 -13
  35. package/tests/fireproof/all-gateway.test.ts +283 -213
  36. package/tests/fireproof/cars/bafkreidxwt2nhvbl4fnqfw3ctlt6zbrir4kqwmjo5im6rf4q5si27kgo2i.ts +324 -316
  37. package/tests/fireproof/crdt.test.ts +78 -19
  38. package/tests/fireproof/database.test.ts +225 -29
  39. package/tests/fireproof/fireproof.test.ts +92 -73
  40. package/tests/fireproof/hello.test.ts +17 -13
  41. package/tests/fireproof/indexer.test.ts +67 -43
  42. package/tests/fireproof/utils.test.ts +47 -6
  43. package/tests/gateway/file/loader-config.test.ts +307 -0
  44. package/tests/gateway/fp-envelope-serialize.test.ts +256 -0
  45. package/tests/gateway/indexdb/loader-config.test.ts +79 -0
  46. package/tests/helpers.ts +44 -17
  47. package/tests/react/useFireproof.test.tsx +1 -1
  48. package/tests/www/todo.html +24 -3
  49. package/web/index.cjs +102 -116
  50. package/web/index.cjs.map +1 -1
  51. package/web/index.d.cts +15 -29
  52. package/web/index.d.ts +15 -29
  53. package/web/index.js +91 -105
  54. package/web/index.js.map +1 -1
  55. package/web/metafile-cjs.json +1 -1
  56. package/web/metafile-esm.json +1 -1
  57. package/node/chunk-4A4RAVNS.js +0 -17
  58. package/node/chunk-4A4RAVNS.js.map +0 -1
  59. package/node/mem-filesystem-LPPT7QV5.js +0 -40
  60. package/node/mem-filesystem-LPPT7QV5.js.map +0 -1
  61. package/tests/fireproof/config.test.ts +0 -163
  62. /package/tests/blockstore/{fragment-gateway.test.ts → fragment-gateway.test.ts-off} +0 -0
  63. /package/tests/fireproof/{multiple-ledger.test.ts → multiple-database.test.ts} +0 -0
@@ -0,0 +1,256 @@
1
+ import { rt, bs } from "@fireproof/core";
2
+ import { mockSuperThis, simpleCID } from "../helpers.js";
3
+ import { BuildURI, Result } from "@adviser/cement";
4
+ import { toJSON } from "multiformats/link";
5
+
6
+ const FPEnvelopeType = bs.FPEnvelopeType;
7
+
8
+ describe("storage-content", () => {
9
+ const sthis = mockSuperThis();
10
+ it("car", async () => {
11
+ const raw = new Uint8Array([55, 56, 57]);
12
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data&suffix=.car").URI(), Result.Ok(raw));
13
+ expect(res.isOk()).toBeTruthy();
14
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.CAR);
15
+ expect(res.unwrap().payload).toEqual(raw);
16
+ });
17
+
18
+ it("file", async () => {
19
+ const raw = new Uint8Array([55, 56, 57]);
20
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data").URI(), Result.Ok(raw));
21
+ expect(res.isOk()).toBeTruthy();
22
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.FILE);
23
+ expect(res.unwrap().payload).toEqual(raw);
24
+ });
25
+
26
+ it("meta", async () => {
27
+ const ref = [
28
+ {
29
+ cid: "bafyreiaqmtw5jfudn6r6dq7mcmytc2z5z3ggohcj3gco3omjsp3hr73fpy",
30
+ data: "MomRkYXRhoWZkYk1ldGFYU3siY2FycyI6W3siLyI6ImJhZzR5dnFhYmNpcWNod29zeXQ3dTJqMmxtcHpyM2w3aWRlaTU1YzNmNnJ2Z3U3cXRmYXRoMnl2NnZuaWtjeXEifV19Z3BhcmVudHOA",
31
+ parents: [(await simpleCID(sthis)).toString(), (await simpleCID(sthis)).toString()],
32
+ },
33
+ ];
34
+ const raw = sthis.txt.encode(JSON.stringify(ref));
35
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=meta").URI(), Result.Ok(raw));
36
+ expect(res.isOk()).toBeTruthy();
37
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.META);
38
+ const dbMetas = res.unwrap().payload as bs.DbMetaEvent[];
39
+ expect(dbMetas.length).toBe(1);
40
+ const dbMeta = dbMetas[0];
41
+ expect(dbMeta.parents.map((i) => i.toString())).toStrictEqual(ref[0].parents);
42
+ expect(dbMeta.eventCid.toString()).toEqual("bafyreiaqmtw5jfudn6r6dq7mcmytc2z5z3ggohcj3gco3omjsp3hr73fpy");
43
+ expect(dbMeta.dbMeta.cars.map((i) => i.toString())).toEqual([
44
+ "bag4yvqabciqchwosyt7u2j2lmpzr3l7idei55c3f6rvgu7qtfath2yv6vnikcyq",
45
+ ]);
46
+ });
47
+
48
+ it("wal", async () => {
49
+ const ref = {
50
+ fileOperations: [
51
+ {
52
+ cid: "bafyreiaqmtw5jfudn6r6dq7mcmytc2z5z3ggohcj3gco3omjsp3hr73fpy",
53
+ public: false,
54
+ },
55
+ ],
56
+ noLoaderOps: [
57
+ {
58
+ cars: [
59
+ {
60
+ "/": "bag4yvqabciqchwosyt7u2j2lmpzr3l7idei55c3f6rvgu7qtfath2yv6vnikcyq",
61
+ },
62
+ ],
63
+ },
64
+ ],
65
+ operations: [
66
+ {
67
+ cars: [{ "/": "bag4yvqabciqchwosyt7u2j2lmpzr3l7idei55c3f6rvgu7qtfath2yv6vnikcyq" }],
68
+ },
69
+ ],
70
+ };
71
+ const raw = sthis.txt.encode(JSON.stringify(ref));
72
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=wal").URI(), Result.Ok(raw));
73
+ expect(res.isOk()).toBeTruthy();
74
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.WAL);
75
+ const walstate = res.unwrap().payload as bs.WALState;
76
+ expect(
77
+ walstate.fileOperations.map((i) => ({
78
+ ...i,
79
+ cid: i.cid.toString(),
80
+ })),
81
+ ).toEqual(ref.fileOperations);
82
+ expect(
83
+ walstate.noLoaderOps.map((i) => ({
84
+ cars: i.cars.map((i) => toJSON(i)),
85
+ })),
86
+ ).toEqual(ref.noLoaderOps);
87
+ expect(
88
+ walstate.operations.map((i) => ({
89
+ cars: i.cars.map((i) => toJSON(i)),
90
+ })),
91
+ ).toEqual(ref.operations);
92
+ });
93
+ });
94
+
95
+ describe("de-serialize", () => {
96
+ const sthis = mockSuperThis();
97
+ it("car", async () => {
98
+ const msg = {
99
+ type: FPEnvelopeType.CAR,
100
+ payload: new Uint8Array([55, 56, 57]),
101
+ } satisfies bs.FPEnvelopeCar;
102
+ const res = await rt.gw.fpSerialize(sthis, msg);
103
+ expect(res.Ok()).toEqual(msg.payload);
104
+ });
105
+
106
+ it("file", async () => {
107
+ const msg = {
108
+ type: FPEnvelopeType.FILE,
109
+ payload: new Uint8Array([55, 56, 57]),
110
+ } satisfies bs.FPEnvelopeFile;
111
+ const res = await rt.gw.fpSerialize(sthis, msg);
112
+ expect(res.Ok()).toEqual(msg.payload);
113
+ });
114
+
115
+ it("meta", async () => {
116
+ const msg = {
117
+ type: FPEnvelopeType.META,
118
+ payload: [
119
+ await bs.createDbMetaEvent(
120
+ sthis,
121
+ {
122
+ cars: [await simpleCID(sthis)],
123
+ },
124
+ [await simpleCID(sthis), await simpleCID(sthis)],
125
+ ),
126
+ ],
127
+ } satisfies bs.FPEnvelopeMeta;
128
+ const ser = await rt.gw.fpSerialize(sthis, msg);
129
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=meta").URI(), ser);
130
+ const dbMetas = res.unwrap().payload as bs.DbMetaEvent[];
131
+ expect(dbMetas.length).toBe(1);
132
+ const dbMeta = dbMetas[0];
133
+ expect(dbMeta.parents).toEqual(msg.payload[0].parents);
134
+ expect(dbMeta.dbMeta).toEqual(msg.payload[0].dbMeta);
135
+ expect(dbMeta.eventCid).toEqual(msg.payload[0].eventCid);
136
+ });
137
+
138
+ it("wal", async () => {
139
+ const msg = {
140
+ type: FPEnvelopeType.WAL,
141
+ payload: {
142
+ fileOperations: [
143
+ {
144
+ cid: await simpleCID(sthis),
145
+ public: false,
146
+ },
147
+ ],
148
+ noLoaderOps: [
149
+ {
150
+ cars: [await simpleCID(sthis)],
151
+ },
152
+ ],
153
+ operations: [
154
+ {
155
+ cars: [await simpleCID(sthis)],
156
+ },
157
+ ],
158
+ },
159
+ } satisfies bs.FPEnvelopeWAL;
160
+ const ser = await rt.gw.fpSerialize(sthis, msg);
161
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=wal").URI(), ser);
162
+ expect(res.isOk()).toBeTruthy();
163
+ expect(res.unwrap().type).toEqual("wal");
164
+ const walstate = res.unwrap().payload as bs.WALState;
165
+ expect(walstate.fileOperations).toEqual(msg.payload.fileOperations);
166
+ expect(walstate.noLoaderOps).toEqual(msg.payload.noLoaderOps);
167
+ expect(walstate.operations).toEqual(msg.payload.operations);
168
+ });
169
+
170
+ it("coerce into fpDeserialize Result", async () => {
171
+ const raw = new Uint8Array([55, 56, 57]);
172
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data&suffix=.car").URI(), Result.Ok(raw));
173
+ expect(res.isOk()).toBeTruthy();
174
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.CAR);
175
+ expect(res.unwrap().payload).toEqual(raw);
176
+ });
177
+
178
+ it("coerce into fpDeserialize Promise", async () => {
179
+ const raw = new Uint8Array([55, 56, 57]);
180
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data&suffix=.car").URI(), Promise.resolve(raw));
181
+ expect(res.isOk()).toBeTruthy();
182
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.CAR);
183
+ expect(res.unwrap().payload).toEqual(raw);
184
+ });
185
+
186
+ it("coerce into fpDeserialize Promise Result", async () => {
187
+ const raw = new Uint8Array([55, 56, 57]);
188
+ const res = await rt.gw.fpDeserialize(
189
+ sthis,
190
+ BuildURI.from("http://x.com?store=data&suffix=.car").URI(),
191
+ Promise.resolve(Result.Ok(raw)),
192
+ );
193
+ expect(res.isOk()).toBeTruthy();
194
+ expect(res.unwrap().type).toEqual(FPEnvelopeType.CAR);
195
+ expect(res.unwrap().payload).toEqual(raw);
196
+ });
197
+
198
+ it("coerce into fpDeserialize Promise Result.Err", async () => {
199
+ const raw = Promise.resolve(Result.Err<Uint8Array>("error"));
200
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data&suffix=.car").URI(), raw);
201
+ expect(res.isErr()).toBeTruthy();
202
+ expect(res.unwrap_err().message).toEqual("error");
203
+ });
204
+
205
+ it("coerce into fpDeserialize Promise.reject", async () => {
206
+ const raw = Promise.reject(new Error("error"));
207
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data&suffix=.car").URI(), raw);
208
+ expect(res.isErr()).toBeTruthy();
209
+ expect(res.unwrap_err().message).toEqual("error");
210
+ });
211
+
212
+ it("coerce into fpDeserialize Result.Err", async () => {
213
+ const raw = Result.Err<Uint8Array>("error");
214
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=data&suffix=.car").URI(), raw);
215
+ expect(res.isErr()).toBeTruthy();
216
+ expect(res.unwrap_err().message).toEqual("error");
217
+ });
218
+
219
+ it("attach Key to Meta", async () => {
220
+ const msg = {
221
+ type: FPEnvelopeType.META,
222
+ payload: [
223
+ await bs.createDbMetaEvent(
224
+ sthis,
225
+ {
226
+ cars: [await simpleCID(sthis)],
227
+ },
228
+ [await simpleCID(sthis), await simpleCID(sthis)],
229
+ ),
230
+ ],
231
+ } satisfies bs.FPEnvelopeMeta;
232
+ const ser = await rt.gw.fpSerialize(sthis, msg, {
233
+ meta: async (sthis, payload) => {
234
+ return Result.Ok(sthis.txt.encode(JSON.stringify(payload.map((i) => ({ ...i, key: "key" })))));
235
+ },
236
+ });
237
+ let key = "";
238
+ const res = await rt.gw.fpDeserialize(sthis, BuildURI.from("http://x.com?store=meta").URI(), ser, {
239
+ meta: async (sthis, payload) => {
240
+ const json = JSON.parse(sthis.txt.decode(payload));
241
+ key = json[0].key;
242
+ return Result.Ok(
243
+ json.map((i: { key?: string }) => {
244
+ delete i.key;
245
+ return i;
246
+ }) as rt.gw.SerializedMeta[],
247
+ );
248
+ },
249
+ });
250
+ expect(res.isOk()).toBeTruthy();
251
+ const meta = res.unwrap() as bs.FPEnvelopeMeta;
252
+ expect(meta.type).toEqual("meta");
253
+ expect(Object.keys(meta.payload).includes("key")).toBeFalsy();
254
+ expect(key).toEqual("key");
255
+ });
256
+ });
@@ -0,0 +1,79 @@
1
+ import { fireproof } from "@fireproof/core";
2
+ import { mockSuperThis } from "../../helpers.js";
3
+
4
+ describe("fireproof config indexdb", () => {
5
+ const _my_app = "my-app";
6
+ function my_app() {
7
+ return _my_app;
8
+ }
9
+ const sthis = mockSuperThis();
10
+ beforeAll(async () => {
11
+ await sthis.start();
12
+ });
13
+
14
+ it("indexdb-loader", async () => {
15
+ const db = fireproof(my_app());
16
+ await db.put({ name: "my-app" });
17
+ expect(db.name).toBe(my_app());
18
+
19
+ const fileStore = await db.crdt.blockstore.loader.fileStore();
20
+ expect(fileStore?.url().asObj()).toEqual({
21
+ pathname: "fp",
22
+ protocol: "indexdb:",
23
+ searchParams: {
24
+ name: "my-app",
25
+ store: "data",
26
+ runtime: "browser",
27
+ storekey: "@my-app-data@",
28
+ urlGen: "default",
29
+ version: "v0.19-indexdb",
30
+ },
31
+ style: "path",
32
+ });
33
+
34
+ const dataStore = await db.crdt.blockstore.loader.carStore();
35
+ expect(dataStore?.url().asObj()).toEqual({
36
+ pathname: "fp",
37
+ protocol: "indexdb:",
38
+ searchParams: {
39
+ name: "my-app",
40
+ store: "data",
41
+ runtime: "browser",
42
+ storekey: "@my-app-data@",
43
+ suffix: ".car",
44
+ urlGen: "default",
45
+ version: "v0.19-indexdb",
46
+ },
47
+ style: "path",
48
+ });
49
+ const metaStore = await db.crdt.blockstore.loader.metaStore();
50
+ expect(metaStore?.url().asObj()).toEqual({
51
+ pathname: "fp",
52
+ protocol: "indexdb:",
53
+ searchParams: {
54
+ name: "my-app",
55
+ store: "meta",
56
+ runtime: "browser",
57
+ storekey: "@my-app-meta@",
58
+ urlGen: "default",
59
+ version: "v0.19-indexdb",
60
+ },
61
+ style: "path",
62
+ });
63
+ const WALStore = await db.crdt.blockstore.loader.WALStore();
64
+ expect(WALStore?.url().asObj()).toEqual({
65
+ pathname: "fp",
66
+ protocol: "indexdb:",
67
+ searchParams: {
68
+ name: "my-app",
69
+ store: "wal",
70
+ runtime: "browser",
71
+ storekey: "@my-app-wal@",
72
+ urlGen: "default",
73
+ version: "v0.19-indexdb",
74
+ },
75
+ style: "path",
76
+ });
77
+ await db.close();
78
+ });
79
+ });
package/tests/helpers.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { runtimeFn, toCryptoRuntime, URI } from "@adviser/cement";
2
- import { dataDir, rt, SuperThis } from "@fireproof/core";
3
-
4
- export { dataDir };
1
+ import { BuildURI, MockLogger, runtimeFn, toCryptoRuntime, URI, utils, LogCollector } from "@adviser/cement";
2
+ import { ensureSuperThis, rt, SuperThis, SuperThisOpts, bs, PARAM } from "@fireproof/core";
3
+ import { CID } from "multiformats";
4
+ import { sha256 } from "multiformats/hashes/sha2";
5
+ import * as json from "multiformats/codecs/json";
5
6
 
6
7
  export function sleep(ms: number) {
7
8
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -37,16 +38,42 @@ export function storageURL(sthis: SuperThis): URI {
37
38
  return merged;
38
39
  }
39
40
 
40
- // export type MockSuperThis = SuperThis & { logCollector: LogCollector };
41
- // // eslint-disable-next-line @typescript-eslint/no-unused-vars
42
- // export function mockSuperThis(sthis?: Partial<SuperThisOpts>): MockSuperThis {
43
- // const mockLog = MockLogger();
44
- // const ethis = ensureSuperThis({
45
- // logger: mockLog.logger,
46
- // ctx: {
47
- // logCollector: mockLog.logCollector,
48
- // },
49
- // }) as MockSuperThis;
50
- // ethis.logCollector = mockLog.logCollector;
51
- // return ethis;
52
- // }
41
+ export type MockSuperThis = SuperThis & { ctx: { readonly logCollector: LogCollector } };
42
+ export function mockSuperThis(sthis?: Partial<SuperThisOpts>): MockSuperThis {
43
+ const mockLog = MockLogger({
44
+ pass: new utils.ConsoleWriterStreamDefaultWriter(new utils.ConsoleWriterStream()),
45
+ });
46
+ return ensureSuperThis({
47
+ ...sthis,
48
+ logger: mockLog.logger,
49
+ ctx: {
50
+ logCollector: mockLog.logCollector,
51
+ },
52
+ }) as MockSuperThis;
53
+ }
54
+
55
+ export function noopUrl(name?: string): URI {
56
+ const burl = BuildURI.from("memory://noop");
57
+ burl.setParam(PARAM.NAME, name || "test");
58
+ return burl.URI();
59
+ }
60
+
61
+ export function simpleBlockOpts(sthis: SuperThis, name?: string) {
62
+ const url = noopUrl(name);
63
+ return {
64
+ keyBag: rt.kb.defaultKeyBagOpts(sthis),
65
+ storeRuntime: bs.toStoreRuntime(sthis),
66
+ storeUrls: {
67
+ file: url,
68
+ wal: url,
69
+ meta: url,
70
+ data: url,
71
+ },
72
+ };
73
+ }
74
+
75
+ export async function simpleCID(sthis: SuperThis) {
76
+ const bytes = json.encode({ hello: sthis.nextId().str });
77
+ const hash = await sha256.digest(bytes);
78
+ return CID.create(1, json.code, hash);
79
+ }
@@ -13,7 +13,7 @@ describe("HOOK: useFireproof", () => {
13
13
  const { database, useLiveQuery, useDocument } = useFireproof("dbname");
14
14
  expect(typeof useLiveQuery).toBe("function");
15
15
  expect(typeof useDocument).toBe("function");
16
- expect(database?.constructor.name).toBe("Database");
16
+ expect(database?.constructor.name).toMatch(/^Database/);
17
17
  });
18
18
  });
19
19
  });
@@ -75,8 +75,9 @@
75
75
 
76
76
  let compactor = "🚗";
77
77
  function drawInfo() {
78
- document.querySelector("#carLog").innerText =
79
- ` ⏰ ${db._crdt.clock.head.length} ${compactor} ${cx.loader.carLog.length} 📩 ${1}`;
78
+ document.querySelector("#carLog").innerText = ` ⏰ ${db._crdt.clock.head.length} ${compactor} ${
79
+ cx.loader.carLog.length
80
+ } 📩 ${1}`;
80
81
  }
81
82
  const doRedraw = async () => {
82
83
  drawInfo();
@@ -164,7 +165,7 @@
164
165
  const timeout =
165
166
  10 +
166
167
  Math.pow(db._crdt.clock.head.length + cx.loader.taskManager.queue.length, 2) *
167
- (Math.floor(Math.random() * 2000) + 2000);
168
+ (Math.floor(Math.random() * 1000) + 1000);
168
169
  // console.log('go worker', timeout/ 1000)
169
170
  worker = setTimeout(async () => {
170
171
  await Promise.all(
@@ -195,6 +196,25 @@
195
196
  };
196
197
  window.toggleWorker = toggleWorker;
197
198
 
199
+ async function writeStorm(e) {
200
+ e.preventDefault();
201
+ compactor = "🐦‍🔥";
202
+ drawInfo();
203
+
204
+ const dcs = await db.allDocs();
205
+ const lastRow = dcs.rows[dcs.rows.length - 1];
206
+ const theDoc = lastRow.value;
207
+ for (let i = 0; i < 50; i++) {
208
+ theDoc.clicks = (theDoc.clicks || 0) + 1;
209
+ theDoc.completed = !theDoc.completed;
210
+ await db.put(theDoc);
211
+ }
212
+
213
+ drawInfo();
214
+ compactor = "🚗";
215
+ }
216
+ window.writeStorm = writeStorm;
217
+
198
218
  async function doCompact(e) {
199
219
  e.preventDefault();
200
220
  compactor = "🚕";
@@ -221,6 +241,7 @@
221
241
 
222
242
  <p>This version of the Fireprof test app uses PartyKit as the storage backend. Refresh is automatic and live.</p>
223
243
 
244
+ <button id="rocket" onclick="writeStorm(event)">🚀</button>
224
245
  <button id="robot" onclick="toggleWorker(event)">🤖</button>
225
246
  <span id="carLog" onclick="doCompact(event)"></span>
226
247
  <span id="cxInfo"></span>