@freqhole/playlistz 0.0.1
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/.changeset/config.json +11 -0
- package/.changeset/nice-wolves-thank.md +5 -0
- package/.freqhole-versions.json +4 -0
- package/.github/copilot-instructions.md +201 -0
- package/.github/workflows/changesets.yml +50 -0
- package/.github/workflows/npm-publish.yml +124 -0
- package/.github/workflows/pr-checks.yml +103 -0
- package/README.md +30 -0
- package/build-component.js +141 -0
- package/build-zip-bundle-lib.js +44 -0
- package/config/playwright.config.ts +47 -0
- package/config/vite.config.ts +44 -0
- package/config/vitest.config.ts +39 -0
- package/dist/assets/automerge_wasm_bg-Cik4BF9l.wasm +0 -0
- package/dist/assets/index-CbOXzGiA.js +216 -0
- package/dist/assets/index-CbOXzGiA.js.map +1 -0
- package/dist/assets/index-TvJ6RFpy.css +1 -0
- package/dist/assets/midden-DceCrT_L.js +2 -0
- package/dist/assets/midden-DceCrT_L.js.map +1 -0
- package/dist/assets/midden_bg-BLhfGIU-.wasm +0 -0
- package/dist/index.html +55 -0
- package/dist/sw.js +134 -0
- package/docs/AUTOMERGE_P2P_PLAN.md +233 -0
- package/docs/COLLABORATIVE_SHARING_PLAN.md +188 -0
- package/docs/E2E_TESTID_PLAN.md +234 -0
- package/docs/IROH_P2P_PLAN.md +302 -0
- package/docs/ROADMAP.md +695 -0
- package/docs/TODO.md +167 -0
- package/docs/bundle-embedding-plan.md +134 -0
- package/docs/standalone-refactor.md +184 -0
- package/e2e/all-playlists.spec.ts +220 -0
- package/e2e/audio-player.spec.ts +226 -0
- package/e2e/collaborative-features.spec.ts +229 -0
- package/e2e/contexts.ts +238 -0
- package/e2e/edit-panel.spec.ts +87 -0
- package/e2e/fixtures/bare-glitch-1s.m4a +0 -0
- package/e2e/fixtures/bare-glitch-1s.mp3 +0 -0
- package/e2e/fixtures/bare-glitch-1s.ogg +0 -0
- package/e2e/fixtures/chord-stack-3s.wav +0 -0
- package/e2e/fixtures/cover-anim.gif +0 -0
- package/e2e/fixtures/cover-blue.png +0 -0
- package/e2e/fixtures/cover-checkers.png +0 -0
- package/e2e/fixtures/cover-gradient.jpg +0 -0
- package/e2e/fixtures/cover-mono.gif +0 -0
- package/e2e/fixtures/cover-noise.png +0 -0
- package/e2e/fixtures/cover-plasma.webp +0 -0
- package/e2e/fixtures/cover-portrait.jpg +0 -0
- package/e2e/fixtures/cover-red.png +0 -0
- package/e2e/fixtures/cover-thumb.jpg +0 -0
- package/e2e/fixtures/cover-wide.webp +0 -0
- package/e2e/fixtures/generate.mjs +257 -0
- package/e2e/fixtures/long-drone-90s.mp3 +0 -0
- package/e2e/fixtures/noisy-binaural-8s.mp3 +0 -0
- package/e2e/fixtures/tagged-a3-4s.m4a +0 -0
- package/e2e/fixtures/tagged-a3-4s.mp3 +0 -0
- package/e2e/fixtures/tagged-a3-4s.ogg +0 -0
- package/e2e/fixtures/tagged-c5-3s.m4a +0 -0
- package/e2e/fixtures/tagged-c5-3s.mp3 +0 -0
- package/e2e/fixtures/tagged-c5-3s.ogg +0 -0
- package/e2e/fixtures/tagged-f4-6s.m4a +0 -0
- package/e2e/fixtures/tagged-f4-6s.mp3 +0 -0
- package/e2e/fixtures/tagged-f4-6s.ogg +0 -0
- package/e2e/fixtures/tone-220hz-10s.wav +0 -0
- package/e2e/fixtures/tone-440hz-2s.wav +0 -0
- package/e2e/fixtures/tone-880hz-5s.wav +0 -0
- package/e2e/fixtures/tone-stereo-3s.wav +0 -0
- package/e2e/fixtures/user-provided/README.md +1 -0
- package/e2e/helpers/app.ts +143 -0
- package/e2e/helpers/hooks.ts +133 -0
- package/e2e/helpers/index.ts +12 -0
- package/e2e/helpers/media.ts +125 -0
- package/e2e/helpers.ts +10 -0
- package/e2e/p2p-collaboration.spec.ts +356 -0
- package/e2e/p2p-multi-peer.spec.ts +723 -0
- package/e2e/p2p-states.spec.ts +302 -0
- package/e2e/playback.spec.ts +56 -0
- package/e2e/playlist-crud.spec.ts +126 -0
- package/e2e/share-link-autoplay.spec.ts +129 -0
- package/e2e/sharing-access.spec.ts +205 -0
- package/e2e/sharing.spec.ts +195 -0
- package/e2e/song-cache-state.spec.ts +202 -0
- package/e2e/zip-bundle.spec.ts +855 -0
- package/eslint.config.js +114 -0
- package/index.html +54 -0
- package/package.json +119 -0
- package/public/sw.js +134 -0
- package/scripts/use-local.mjs +37 -0
- package/scripts/use-published.mjs +37 -0
- package/src/App.tsx +9 -0
- package/src/cli/check.ts +164 -0
- package/src/cli/generate.ts +184 -0
- package/src/cli/http.ts +88 -0
- package/src/cli/index.ts +65 -0
- package/src/cli/init.ts +18 -0
- package/src/components/AllPlaylistsPanel.tsx +713 -0
- package/src/components/AudioPlayer.tsx +122 -0
- package/src/components/MarqueeText.tsx +101 -0
- package/src/components/PlaylistCoverModal.tsx +519 -0
- package/src/components/PlaylistEditPanel.tsx +803 -0
- package/src/components/PlaylistSharePanel.tsx +1020 -0
- package/src/components/ShareLinkKnockPanel.tsx +144 -0
- package/src/components/SharePanel.tsx +584 -0
- package/src/components/SongEditModal.tsx +453 -0
- package/src/components/SongEditPanel.tsx +578 -0
- package/src/components/SongRow.tsx +689 -0
- package/src/components/index.tsx +494 -0
- package/src/components/playlist/index.tsx +1203 -0
- package/src/context/PlaylistzContext.tsx +74 -0
- package/src/dev-hooks.ts +35 -0
- package/src/hooks/createDocIndexQuery.ts +53 -0
- package/src/hooks/createDocStore.test.ts +303 -0
- package/src/hooks/createDocStore.ts +90 -0
- package/src/hooks/useDragAndDrop.test.ts +474 -0
- package/src/hooks/useDragAndDrop.ts +400 -0
- package/src/hooks/useImageModal.test.ts +174 -0
- package/src/hooks/useImageModal.ts +201 -0
- package/src/hooks/usePlaylistManager.test.ts +453 -0
- package/src/hooks/usePlaylistManager.ts +685 -0
- package/src/hooks/usePlaylistsQuery.test.tsx +120 -0
- package/src/hooks/usePlaylistsQuery.ts +44 -0
- package/src/hooks/useSongState.test.ts +236 -0
- package/src/hooks/useSongState.ts +114 -0
- package/src/hooks/useUIState.ts +71 -0
- package/src/index.tsx +18 -0
- package/src/services/audioService.dev.ts +22 -0
- package/src/services/audioService.test.ts +1226 -0
- package/src/services/audioService.ts +1395 -0
- package/src/services/automergeRepo.test.ts +269 -0
- package/src/services/automergeRepo.ts +226 -0
- package/src/services/blobTransferService.dev.ts +119 -0
- package/src/services/blobTransferService.test.ts +441 -0
- package/src/services/blobTransferService.ts +702 -0
- package/src/services/docIndexService.test.ts +179 -0
- package/src/services/docIndexService.ts +118 -0
- package/src/services/fileProcessingService.test.ts +554 -0
- package/src/services/fileProcessingService.ts +239 -0
- package/src/services/imageService.test.ts +701 -0
- package/src/services/imageService.ts +365 -0
- package/src/services/indexedDBService.integration.test.ts +104 -0
- package/src/services/indexedDBService.test.ts +202 -0
- package/src/services/indexedDBService.ts +436 -0
- package/src/services/offlineService.test.ts +661 -0
- package/src/services/offlineService.ts +382 -0
- package/src/services/p2pService.test.ts +305 -0
- package/src/services/p2pService.ts +344 -0
- package/src/services/playlistDocService.test.ts +448 -0
- package/src/services/playlistDocService.ts +707 -0
- package/src/services/playlistDownloadService.test.ts +674 -0
- package/src/services/playlistDownloadService.ts +389 -0
- package/src/services/sharingService.test.ts +812 -0
- package/src/services/sharingService.ts +1073 -0
- package/src/services/sharingState.ts +161 -0
- package/src/services/songReactivity.test.ts +620 -0
- package/src/services/songReactivity.ts +145 -0
- package/src/services/standaloneService.test.ts +1025 -0
- package/src/services/standaloneService.ts +588 -0
- package/src/services/streamingAudioService.test.ts +275 -0
- package/src/services/streamingAudioService.ts +166 -0
- package/src/styles.css +428 -0
- package/src/test-setup.ts +547 -0
- package/src/types/global.d.ts +40 -0
- package/src/types/playlist.ts +99 -0
- package/src/utils/hashUtils.ts +41 -0
- package/src/utils/log.ts +97 -0
- package/src/utils/m3u.test.ts +172 -0
- package/src/utils/m3u.ts +136 -0
- package/src/utils/mockData.ts +166 -0
- package/src/utils/standaloneTemplates.test.ts +175 -0
- package/src/utils/standaloneTemplates.ts +83 -0
- package/src/utils/swTemplate.ts +84 -0
- package/src/utils/timeUtils.ts +166 -0
- package/src/utils/typeGuards.ts +171 -0
- package/src/web-component.tsx +98 -0
- package/src/zip-bundle/index.ts +7 -0
- package/src/zip-bundle/m3u.ts +45 -0
- package/src/zip-bundle/types.ts +50 -0
- package/src/zip-bundle/utils.ts +33 -0
- package/src/zip-bundle/zipBuilder.ts +309 -0
- package/tailwind.config.js +55 -0
- package/tsconfig.json +43 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// tests for docIndexService crud helpers.
|
|
2
|
+
// uses fake-indexeddb (wired via test-setup.ts) so no real IDB needed.
|
|
3
|
+
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
import { IDBFactory } from "fake-indexeddb";
|
|
6
|
+
import { resetDBCache } from "./indexedDBService.js";
|
|
7
|
+
import {
|
|
8
|
+
addDocIndexEntry,
|
|
9
|
+
removeDocIndexEntry,
|
|
10
|
+
getDocIndexEntry,
|
|
11
|
+
getAllDocIndexEntries,
|
|
12
|
+
upsertKnock,
|
|
13
|
+
getKnock,
|
|
14
|
+
getAllKnocks,
|
|
15
|
+
deleteKnock,
|
|
16
|
+
upsertAccessGrant,
|
|
17
|
+
getAccessGrant,
|
|
18
|
+
getAllAccessGrants,
|
|
19
|
+
deleteAccessGrant,
|
|
20
|
+
} from "./docIndexService.js";
|
|
21
|
+
import type { DocIndexEntry, KnockRecord, AccessGrantRecord } from "./indexedDBService.js";
|
|
22
|
+
|
|
23
|
+
// fresh idb + db connection for each test (avoids data leaking across tests)
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
globalThis.indexedDB = new IDBFactory();
|
|
26
|
+
resetDBCache();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// --- helpers ---
|
|
30
|
+
|
|
31
|
+
function makeEntry(overrides: Partial<DocIndexEntry> = {}): DocIndexEntry {
|
|
32
|
+
return {
|
|
33
|
+
docId: "automerge:abc123",
|
|
34
|
+
title: "test playlist",
|
|
35
|
+
addedAt: 1_000_000,
|
|
36
|
+
source: "local",
|
|
37
|
+
...overrides,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function makeKnock(overrides: Partial<KnockRecord> = {}): KnockRecord {
|
|
42
|
+
return {
|
|
43
|
+
id: "knock-1",
|
|
44
|
+
nodeId: "node-abc",
|
|
45
|
+
direction: "inbound",
|
|
46
|
+
name: "alice",
|
|
47
|
+
message: "let me in",
|
|
48
|
+
status: "pending",
|
|
49
|
+
createdAt: 1_000_000,
|
|
50
|
+
...overrides,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function makeGrant(overrides: Partial<AccessGrantRecord> = {}): AccessGrantRecord {
|
|
55
|
+
return {
|
|
56
|
+
nodeId: "node-abc",
|
|
57
|
+
name: "alice",
|
|
58
|
+
grantedAt: 1_000_000,
|
|
59
|
+
...overrides,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// --- docIndex ---
|
|
64
|
+
|
|
65
|
+
describe("docIndex CRUD", () => {
|
|
66
|
+
it("addDocIndexEntry and getDocIndexEntry round-trip", async () => {
|
|
67
|
+
const entry = makeEntry({ docId: "automerge:abc", title: "my list" });
|
|
68
|
+
await addDocIndexEntry(entry);
|
|
69
|
+
const fetched = await getDocIndexEntry("automerge:abc");
|
|
70
|
+
expect(fetched).toEqual(entry);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("getDocIndexEntry returns undefined for missing key", async () => {
|
|
74
|
+
const result = await getDocIndexEntry("automerge:missing");
|
|
75
|
+
expect(result).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("getAllDocIndexEntries returns all entries", async () => {
|
|
79
|
+
await addDocIndexEntry(makeEntry({ docId: "automerge:a", title: "A" }));
|
|
80
|
+
await addDocIndexEntry(makeEntry({ docId: "automerge:b", title: "B" }));
|
|
81
|
+
const all = await getAllDocIndexEntries();
|
|
82
|
+
expect(all).toHaveLength(2);
|
|
83
|
+
const titles = all.map((e) => e.title).sort();
|
|
84
|
+
expect(titles).toEqual(["A", "B"]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("removeDocIndexEntry removes the entry", async () => {
|
|
88
|
+
await addDocIndexEntry(makeEntry({ docId: "automerge:del" }));
|
|
89
|
+
await removeDocIndexEntry("automerge:del");
|
|
90
|
+
const result = await getDocIndexEntry("automerge:del");
|
|
91
|
+
expect(result).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("removeDocIndexEntry is idempotent for missing key", async () => {
|
|
95
|
+
// should not throw
|
|
96
|
+
await expect(
|
|
97
|
+
removeDocIndexEntry("automerge:never-existed")
|
|
98
|
+
).resolves.toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("addDocIndexEntry overwrites an existing entry", async () => {
|
|
102
|
+
await addDocIndexEntry(makeEntry({ docId: "automerge:x", title: "old" }));
|
|
103
|
+
await addDocIndexEntry(makeEntry({ docId: "automerge:x", title: "new" }));
|
|
104
|
+
const fetched = await getDocIndexEntry("automerge:x");
|
|
105
|
+
expect(fetched?.title).toBe("new");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("source field round-trips for all source types", async () => {
|
|
109
|
+
for (const source of ["local", "shared", "freqhole"] as const) {
|
|
110
|
+
await addDocIndexEntry(
|
|
111
|
+
makeEntry({ docId: `automerge:${source}`, source })
|
|
112
|
+
);
|
|
113
|
+
const result = await getDocIndexEntry(`automerge:${source}`);
|
|
114
|
+
expect(result?.source).toBe(source);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// --- knocks ---
|
|
120
|
+
|
|
121
|
+
describe("knocks CRUD", () => {
|
|
122
|
+
it("upsertKnock and getKnock round-trip", async () => {
|
|
123
|
+
const knock = makeKnock();
|
|
124
|
+
await upsertKnock(knock);
|
|
125
|
+
const fetched = await getKnock("knock-1");
|
|
126
|
+
expect(fetched).toEqual(knock);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("getAllKnocks returns all records", async () => {
|
|
130
|
+
await upsertKnock(makeKnock({ id: "k1" }));
|
|
131
|
+
await upsertKnock(makeKnock({ id: "k2", direction: "outbound" }));
|
|
132
|
+
const all = await getAllKnocks();
|
|
133
|
+
expect(all).toHaveLength(2);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("deleteKnock removes the record", async () => {
|
|
137
|
+
await upsertKnock(makeKnock({ id: "k-del" }));
|
|
138
|
+
await deleteKnock("k-del");
|
|
139
|
+
expect(await getKnock("k-del")).toBeUndefined();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("upsertKnock updates an existing record", async () => {
|
|
143
|
+
await upsertKnock(makeKnock({ id: "k-upd", status: "pending" }));
|
|
144
|
+
await upsertKnock(makeKnock({ id: "k-upd", status: "accepted" }));
|
|
145
|
+
const result = await getKnock("k-upd");
|
|
146
|
+
expect(result?.status).toBe("accepted");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// --- accessGrants ---
|
|
151
|
+
|
|
152
|
+
describe("accessGrants CRUD", () => {
|
|
153
|
+
it("upsertAccessGrant and getAccessGrant round-trip", async () => {
|
|
154
|
+
const grant = makeGrant();
|
|
155
|
+
await upsertAccessGrant(grant);
|
|
156
|
+
const fetched = await getAccessGrant("node-abc");
|
|
157
|
+
expect(fetched).toEqual(grant);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("getAllAccessGrants returns all records", async () => {
|
|
161
|
+
await upsertAccessGrant(makeGrant({ nodeId: "n1", name: "alice" }));
|
|
162
|
+
await upsertAccessGrant(makeGrant({ nodeId: "n2", name: "bob" }));
|
|
163
|
+
const all = await getAllAccessGrants();
|
|
164
|
+
expect(all).toHaveLength(2);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("deleteAccessGrant removes the record", async () => {
|
|
168
|
+
await upsertAccessGrant(makeGrant({ nodeId: "n-del" }));
|
|
169
|
+
await deleteAccessGrant("n-del");
|
|
170
|
+
expect(await getAccessGrant("n-del")).toBeUndefined();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("upsertAccessGrant overwrites an existing record", async () => {
|
|
174
|
+
await upsertAccessGrant(makeGrant({ nodeId: "n-upd", name: "old" }));
|
|
175
|
+
await upsertAccessGrant(makeGrant({ nodeId: "n-upd", name: "new" }));
|
|
176
|
+
const result = await getAccessGrant("n-upd");
|
|
177
|
+
expect(result?.name).toBe("new");
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// crud helpers for the automerge doc layer stores:
|
|
2
|
+
// docIndex, knocks, and accessGrants.
|
|
3
|
+
//
|
|
4
|
+
// all three stores live in musicPlaylistDB (same db as the rest of the app).
|
|
5
|
+
// docIndex entries are broadcast-invalidated for cross-tab reactivity;
|
|
6
|
+
// knocks and accessGrants are lower-traffic and not live-queried from here.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
setupDB,
|
|
10
|
+
DB_NAME,
|
|
11
|
+
DOC_INDEX_STORE,
|
|
12
|
+
KNOCKS_STORE,
|
|
13
|
+
ACCESS_GRANTS_STORE,
|
|
14
|
+
} from "./indexedDBService.js";
|
|
15
|
+
import type {
|
|
16
|
+
DocIndexEntry,
|
|
17
|
+
KnockRecord,
|
|
18
|
+
AccessGrantRecord,
|
|
19
|
+
} from "./indexedDBService.js";
|
|
20
|
+
import { log } from "../utils/log.js";
|
|
21
|
+
|
|
22
|
+
// event name for same-page doc index invalidation.
|
|
23
|
+
// used alongside BroadcastChannel so file:// (null origin) still works.
|
|
24
|
+
const DOC_INDEX_CHANGE_EVENT = "playlistz:docindex-changed";
|
|
25
|
+
|
|
26
|
+
// broadcast a docIndex mutation so same-tab queries and other tabs refresh.
|
|
27
|
+
function broadcastDocIndexChange(): void {
|
|
28
|
+
log.trace("idb.docindex", "broadcast mutation");
|
|
29
|
+
// CustomEvent works in any origin including file:// (null)
|
|
30
|
+
if (typeof window !== "undefined") {
|
|
31
|
+
window.dispatchEvent(new CustomEvent(DOC_INDEX_CHANGE_EVENT));
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const bc = new BroadcastChannel(`${DB_NAME}-changes`);
|
|
35
|
+
bc.postMessage({ type: "mutation", store: DOC_INDEX_STORE });
|
|
36
|
+
bc.close();
|
|
37
|
+
} catch {
|
|
38
|
+
// broadcastchannel unavailable in some environments (workers, tests)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { DOC_INDEX_CHANGE_EVENT };
|
|
43
|
+
|
|
44
|
+
// --- docIndex ---
|
|
45
|
+
|
|
46
|
+
export async function addDocIndexEntry(entry: DocIndexEntry): Promise<void> {
|
|
47
|
+
log.trace("idb.docindex", "addEntry", entry.docId);
|
|
48
|
+
const db = await setupDB();
|
|
49
|
+
await db.put(DOC_INDEX_STORE, entry);
|
|
50
|
+
broadcastDocIndexChange();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function removeDocIndexEntry(docId: string): Promise<void> {
|
|
54
|
+
log.trace("idb.docindex", "removeEntry", docId);
|
|
55
|
+
const db = await setupDB();
|
|
56
|
+
await db.delete(DOC_INDEX_STORE, docId);
|
|
57
|
+
broadcastDocIndexChange();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function getDocIndexEntry(
|
|
61
|
+
docId: string
|
|
62
|
+
): Promise<DocIndexEntry | undefined> {
|
|
63
|
+
const db = await setupDB();
|
|
64
|
+
return db.get(DOC_INDEX_STORE, docId);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function getAllDocIndexEntries(): Promise<DocIndexEntry[]> {
|
|
68
|
+
const db = await setupDB();
|
|
69
|
+
return db.getAll(DOC_INDEX_STORE);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// --- knocks ---
|
|
73
|
+
|
|
74
|
+
export async function upsertKnock(knock: KnockRecord): Promise<void> {
|
|
75
|
+
const db = await setupDB();
|
|
76
|
+
await db.put(KNOCKS_STORE, knock);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function getKnock(id: string): Promise<KnockRecord | undefined> {
|
|
80
|
+
const db = await setupDB();
|
|
81
|
+
return db.get(KNOCKS_STORE, id);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function getAllKnocks(): Promise<KnockRecord[]> {
|
|
85
|
+
const db = await setupDB();
|
|
86
|
+
return db.getAll(KNOCKS_STORE);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function deleteKnock(id: string): Promise<void> {
|
|
90
|
+
const db = await setupDB();
|
|
91
|
+
await db.delete(KNOCKS_STORE, id);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- accessGrants ---
|
|
95
|
+
|
|
96
|
+
export async function upsertAccessGrant(
|
|
97
|
+
grant: AccessGrantRecord
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
const db = await setupDB();
|
|
100
|
+
await db.put(ACCESS_GRANTS_STORE, grant);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function getAccessGrant(
|
|
104
|
+
nodeId: string
|
|
105
|
+
): Promise<AccessGrantRecord | undefined> {
|
|
106
|
+
const db = await setupDB();
|
|
107
|
+
return db.get(ACCESS_GRANTS_STORE, nodeId);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function getAllAccessGrants(): Promise<AccessGrantRecord[]> {
|
|
111
|
+
const db = await setupDB();
|
|
112
|
+
return db.getAll(ACCESS_GRANTS_STORE);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function deleteAccessGrant(nodeId: string): Promise<void> {
|
|
116
|
+
const db = await setupDB();
|
|
117
|
+
await db.delete(ACCESS_GRANTS_STORE, nodeId);
|
|
118
|
+
}
|