@estiva-app/platform 0.4.0 → 0.5.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.
- package/CHANGELOG.md +35 -0
- package/README.md +38 -1
- package/dist/event-snapshot.d.ts +179 -0
- package/dist/event-snapshot.d.ts.map +1 -0
- package/dist/event-snapshot.js +449 -0
- package/dist/event-snapshot.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/event-snapshot.ts +583 -0
- package/src/index.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# @estiva-app/platform
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
A copy on the device a reload draws from (PEE-2, Peek opens instantly). Additive.
|
|
6
|
+
|
|
7
|
+
`createEventSnapshot` keeps one person's signed events from one relay —
|
|
8
|
+
never a folded view — and answers the same filters from them on the next
|
|
9
|
+
load, before the relay has. Every relay answer it takes in is also a
|
|
10
|
+
correction, since the relay has no sequence number to replay: what matches a
|
|
11
|
+
filter the relay just answered and is not in that answer is dropped (a
|
|
12
|
+
deletion, a lost Folder); a cut answer judges only the window it covers; a
|
|
13
|
+
`kind:5` drops what its own author signed; a replaceable event keeps its
|
|
14
|
+
newest. A filter it never saw answered is not answered (`null`), and `since`
|
|
15
|
+
and `until` are not part of a filter's key. It keeps the plaintext of a
|
|
16
|
+
person's own blob beside its event, so the same ciphertext is never
|
|
17
|
+
decrypted twice.
|
|
18
|
+
|
|
19
|
+
- `createEventSnapshot({ storage, scope, version, maxEvents?, saveAfterMs?, now?, timers? })`
|
|
20
|
+
→ `{ ready, answer(filters), absorb(filters, events), plaintextOf, keepPlaintext, size, flush, close, discard }`.
|
|
21
|
+
An answer absorbed before `ready` is applied over the stored copy; a failed
|
|
22
|
+
save is asked again; `close` writes what changed, `discard` (sign-out) does not.
|
|
23
|
+
A `kind:5` drops `e` targets and `a` coordinates its own author signed.
|
|
24
|
+
A plaintext is kept only beside the person's own replaceable event.
|
|
25
|
+
- `SnapshotStorage` — `load(scope)` → `{ epoch, record }`, `save(scope, change)`
|
|
26
|
+
with `change.epoch`, `clear()`. `clear` counts a sign-out, and a save naming
|
|
27
|
+
an older count writes nothing: a copy opened before a sign-out, in any tab,
|
|
28
|
+
cannot write the person's data back after it.
|
|
29
|
+
A copy written under another `version` is dropped unread; past `maxEvents`
|
|
30
|
+
(10,000) the least recently seen go.
|
|
31
|
+
- `indexedDbSnapshotStorage(indexedDB, name?)` — the browser's storage, the
|
|
32
|
+
factory passed in (the package compiles without `lib: dom`);
|
|
33
|
+
`memorySnapshotStorage()` for tests and browsers without it; `clear()` is
|
|
34
|
+
sign-out.
|
|
35
|
+
- `snapshotScope(relayUrl, pubkey)`, `snapshotFilterKey(filter)`,
|
|
36
|
+
`matchesSnapshotFilter(filter, event)`.
|
|
37
|
+
|
|
3
38
|
## 0.4.0
|
|
4
39
|
|
|
5
40
|
One read a tab holds on to, and reads again when it fails (FOL-5). Additive.
|
package/README.md
CHANGED
|
@@ -140,9 +140,46 @@ await relayBudget.whenClear()
|
|
|
140
140
|
Nothing here gates a read. A read somebody asked for by clicking still goes out
|
|
141
141
|
at once; only the cadence backs off.
|
|
142
142
|
|
|
143
|
+
### A copy on the device, so a reload draws at once
|
|
144
|
+
|
|
145
|
+
`createEventSnapshot` keeps what the relay told one person — signed events,
|
|
146
|
+
never a folded view — and answers the same filters from it on the next load.
|
|
147
|
+
Fold its answer as you fold the relay's, then send the relay the read as
|
|
148
|
+
before and fold again: the relay's answer replaces the copy's.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { createEventSnapshot, indexedDbSnapshotStorage, snapshotScope } from '@estiva-app/platform'
|
|
152
|
+
|
|
153
|
+
const storage = indexedDbSnapshotStorage(indexedDB, 'my-app-copy')
|
|
154
|
+
const copy = createEventSnapshot({
|
|
155
|
+
storage,
|
|
156
|
+
scope: snapshotScope(relayUrl, pubkey),
|
|
157
|
+
version: 1, // bump when what you fold from it changes meaning
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// In the transport, on every answer the relay gives:
|
|
161
|
+
copy.absorb(filters, events)
|
|
162
|
+
|
|
163
|
+
// Before the relay answers: null means the copy never saw this read answered.
|
|
164
|
+
await copy.ready
|
|
165
|
+
const drawnFirst = copy.answer(filters)
|
|
166
|
+
|
|
167
|
+
// Signing out: every copy goes, and one still open — in this tab or another —
|
|
168
|
+
// can never write the person's data back (the storage counts sign-outs).
|
|
169
|
+
copy.discard()
|
|
170
|
+
await storage.clear()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Every answer is also a correction: an event the copy holds that matches a
|
|
174
|
+
filter the relay just answered, and is not in that answer, is dropped — a
|
|
175
|
+
deletion, or a Folder the person lost. A `kind:5` drops what its author
|
|
176
|
+
signed. `keepPlaintext` keeps the plaintext of the person's own blob beside
|
|
177
|
+
the event, so an unchanged blob needs no decrypt on the next load. Never
|
|
178
|
+
publish over something drawn from the copy: it may be older than the relay.
|
|
179
|
+
|
|
143
180
|
## What this deliberately does not know
|
|
144
181
|
|
|
145
|
-
Where anything is stored. Peek's version took a `LiveProjectionApi`, and
|
|
182
|
+
Where anything is stored, beyond the copy's storage you hand it. Peek's version took a `LiveProjectionApi`, and
|
|
146
183
|
extracting that as-is would have baked a Convex assumption into a package two
|
|
147
184
|
apps depend on. This delivers frames and connection state and nothing else, so
|
|
148
185
|
it bets on neither outcome of "do we need Convex": if Convex stays nothing here
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A copy of what the relay told one person, kept on the device (PEE-2).
|
|
3
|
+
*
|
|
4
|
+
* The copy holds **signed events**, the stores' raw input, never a folded view:
|
|
5
|
+
* a fold can change shape in any release, an event cannot. So an app draws
|
|
6
|
+
* from the copy by running the reads it would have sent, against the copy, and
|
|
7
|
+
* folding the answer exactly as it folds the relay's — and then sends the same
|
|
8
|
+
* reads to the relay and folds again. Nothing here knows which app or store it
|
|
9
|
+
* feeds.
|
|
10
|
+
*
|
|
11
|
+
* ## What the copy may answer
|
|
12
|
+
*
|
|
13
|
+
* A read whose every filter has been answered by the relay before, on this
|
|
14
|
+
* device, for this person. Its key leaves out `since` and `until`, which are
|
|
15
|
+
* usually a clock: "the last 30 days" asked yesterday is the same question.
|
|
16
|
+
* A filter never asked, or a `search`, is not answered — an empty answer from
|
|
17
|
+
* a copy that simply never saw the question would draw "nothing here", which
|
|
18
|
+
* is a claim, where no answer draws today's placeholder.
|
|
19
|
+
*
|
|
20
|
+
* ## Keeping it honest without a sequence number
|
|
21
|
+
*
|
|
22
|
+
* The relay has no change feed to replay, so every answer it gives is also a
|
|
23
|
+
* correction: an event the copy holds that matches a filter the relay just
|
|
24
|
+
* answered, and is not in that answer, is gone (deleted, or in a Folder this
|
|
25
|
+
* person has lost — Buzz answers lost access with an empty list). When the
|
|
26
|
+
* answer was cut by its `limit`, only the window it covers is judged: below
|
|
27
|
+
* its oldest event the relay said nothing. A `kind:5` drops the events it
|
|
28
|
+
* names that its own author signed. A replaceable event keeps only its newest.
|
|
29
|
+
*
|
|
30
|
+
* ## Plaintexts
|
|
31
|
+
*
|
|
32
|
+
* An app's own blob is ciphertext on the wire; decrypting it is a round trip
|
|
33
|
+
* the relay's signer meters. The plaintext is the person's own data, so it is
|
|
34
|
+
* kept beside the event that carried it and handed back for the same
|
|
35
|
+
* ciphertext — which always decrypts to the same text, so it is never stale.
|
|
36
|
+
*/
|
|
37
|
+
import type { SignedEvent } from '@estiva-app/protocol';
|
|
38
|
+
/** A Nostr filter as an app sends it: `ids`, `kinds`, `authors`, `#x`, `since`, `until`, `limit`. */
|
|
39
|
+
export type SnapshotFilter = Record<string, unknown>;
|
|
40
|
+
/** One event in storage, with the plaintext of its content when the app kept one. */
|
|
41
|
+
export interface StoredSnapshotEvent {
|
|
42
|
+
event: SignedEvent;
|
|
43
|
+
plaintext?: string;
|
|
44
|
+
/** Epoch ms of the last answer that carried it: the oldest go first past the cap. */
|
|
45
|
+
seenAt: number;
|
|
46
|
+
}
|
|
47
|
+
/** What a scope holds in storage. */
|
|
48
|
+
export interface SnapshotRecord {
|
|
49
|
+
version: number;
|
|
50
|
+
events: StoredSnapshotEvent[];
|
|
51
|
+
/** The keys of every filter the relay has answered (see {@link snapshotFilterKey}). */
|
|
52
|
+
filters: string[];
|
|
53
|
+
}
|
|
54
|
+
/** What changed since the last save. */
|
|
55
|
+
export interface SnapshotChange {
|
|
56
|
+
version: number;
|
|
57
|
+
/** The storage's sign-out count when the copy was read: a save from before a sign-out is refused. */
|
|
58
|
+
epoch: number;
|
|
59
|
+
put: StoredSnapshotEvent[];
|
|
60
|
+
remove: string[];
|
|
61
|
+
filters: string[];
|
|
62
|
+
}
|
|
63
|
+
/** Where a copy lives between page loads. {@link indexedDbSnapshotStorage} is the browser's. */
|
|
64
|
+
export interface SnapshotStorage {
|
|
65
|
+
/** A scope's record, or null; and the storage's sign-out count, which every save names. */
|
|
66
|
+
load(scope: string): Promise<{
|
|
67
|
+
epoch: number;
|
|
68
|
+
record: SnapshotRecord | null;
|
|
69
|
+
}>;
|
|
70
|
+
/** Writes nothing when `change.epoch` is not the current count: somebody signed out since. */
|
|
71
|
+
save(scope: string, change: SnapshotChange): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* Remove every scope and count a sign-out, so a copy still open — in this
|
|
74
|
+
* tab or another — can never write the person's data back afterwards.
|
|
75
|
+
*/
|
|
76
|
+
clear(): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
export interface SnapshotTimers {
|
|
79
|
+
setTimeout(run: () => void, ms: number): unknown;
|
|
80
|
+
clearTimeout(handle: unknown): void;
|
|
81
|
+
}
|
|
82
|
+
export interface EventSnapshotOptions {
|
|
83
|
+
storage: SnapshotStorage;
|
|
84
|
+
/** Whose blobs' plaintexts are kept; defaults to the pubkey in `scope`. */
|
|
85
|
+
owner?: string;
|
|
86
|
+
/** One person on one relay: {@link snapshotScope}. */
|
|
87
|
+
scope: string;
|
|
88
|
+
/** The app's schema version. A copy written under another one is dropped unread. */
|
|
89
|
+
version: number;
|
|
90
|
+
/** Past this many events the least recently seen are dropped. */
|
|
91
|
+
maxEvents?: number;
|
|
92
|
+
/** How long after a change the copy is written. */
|
|
93
|
+
saveAfterMs?: number;
|
|
94
|
+
now?: () => number;
|
|
95
|
+
timers?: SnapshotTimers;
|
|
96
|
+
}
|
|
97
|
+
export interface EventSnapshot {
|
|
98
|
+
/** Settles once the stored copy has been read, or found unusable. Never rejects. */
|
|
99
|
+
ready: Promise<void>;
|
|
100
|
+
/** The events the copy holds for these filters, as the relay would order them; `null` when it cannot answer. */
|
|
101
|
+
answer(filters: SnapshotFilter[]): SignedEvent[] | null;
|
|
102
|
+
/** Take in the relay's answer to these filters: add what it sent, drop what it no longer has. */
|
|
103
|
+
absorb(filters: SnapshotFilter[], events: SignedEvent[]): void;
|
|
104
|
+
/** The kept plaintext of a ciphertext this copy holds, if any. */
|
|
105
|
+
plaintextOf(content: string): string | undefined;
|
|
106
|
+
keepPlaintext(content: string, plaintext: string): void;
|
|
107
|
+
size(): number;
|
|
108
|
+
/** Write what changed now, rather than after `saveAfterMs`. */
|
|
109
|
+
flush(): Promise<void>;
|
|
110
|
+
/** Stop, writing what changed first; nothing more is written after. */
|
|
111
|
+
close(): void;
|
|
112
|
+
/** Stop and write nothing: the copy is about to be cleared (sign-out). */
|
|
113
|
+
discard(): void;
|
|
114
|
+
}
|
|
115
|
+
export declare const SNAPSHOT_MAX_EVENTS = 10000;
|
|
116
|
+
export declare const SNAPSHOT_SAVE_AFTER_MS = 1000;
|
|
117
|
+
/** The scope of one person's copy on one relay. */
|
|
118
|
+
export declare function snapshotScope(relayUrl: string, pubkey: string): string;
|
|
119
|
+
/** A filter's identity as a question, without its clock (`since`, `until`). */
|
|
120
|
+
export declare function snapshotFilterKey(filter: SnapshotFilter): string;
|
|
121
|
+
/** Whether an event matches a filter, `limit` aside — NIP-01's rule. */
|
|
122
|
+
export declare function matchesSnapshotFilter(filter: SnapshotFilter, event: SignedEvent): boolean;
|
|
123
|
+
export declare function createEventSnapshot(options: EventSnapshotOptions): EventSnapshot;
|
|
124
|
+
/**
|
|
125
|
+
* Keeps nothing between page loads: for a test, and for a browser without
|
|
126
|
+
* IndexedDB, where the copy still answers within one load.
|
|
127
|
+
*/
|
|
128
|
+
export declare function memorySnapshotStorage(): SnapshotStorage & {
|
|
129
|
+
records: Map<string, SnapshotRecord>;
|
|
130
|
+
};
|
|
131
|
+
interface IdbRequest<T = unknown> {
|
|
132
|
+
result: T;
|
|
133
|
+
error: unknown;
|
|
134
|
+
onsuccess: ((this: unknown, ev: unknown) => unknown) | null;
|
|
135
|
+
onerror: ((this: unknown, ev: unknown) => unknown) | null;
|
|
136
|
+
}
|
|
137
|
+
interface IdbOpenRequest extends IdbRequest<IdbDatabase> {
|
|
138
|
+
onupgradeneeded: ((this: unknown, ev: unknown) => unknown) | null;
|
|
139
|
+
}
|
|
140
|
+
interface IdbIndex {
|
|
141
|
+
getAll(query: unknown): IdbRequest<unknown[]>;
|
|
142
|
+
getAllKeys(query: unknown): IdbRequest<unknown[]>;
|
|
143
|
+
}
|
|
144
|
+
interface IdbObjectStore {
|
|
145
|
+
put(value: unknown): IdbRequest;
|
|
146
|
+
get(key: unknown): IdbRequest<unknown>;
|
|
147
|
+
delete(key: unknown): IdbRequest;
|
|
148
|
+
clear(): IdbRequest;
|
|
149
|
+
index(name: string): IdbIndex;
|
|
150
|
+
createIndex(name: string, keyPath: string): unknown;
|
|
151
|
+
}
|
|
152
|
+
interface IdbTransaction {
|
|
153
|
+
objectStore(name: string): IdbObjectStore;
|
|
154
|
+
oncomplete: ((this: unknown, ev: unknown) => unknown) | null;
|
|
155
|
+
onerror: ((this: unknown, ev: unknown) => unknown) | null;
|
|
156
|
+
onabort: ((this: unknown, ev: unknown) => unknown) | null;
|
|
157
|
+
error: unknown;
|
|
158
|
+
}
|
|
159
|
+
interface IdbDatabase {
|
|
160
|
+
objectStoreNames: {
|
|
161
|
+
contains(name: string): boolean;
|
|
162
|
+
};
|
|
163
|
+
createObjectStore(name: string, options: {
|
|
164
|
+
keyPath: string | string[];
|
|
165
|
+
}): IdbObjectStore;
|
|
166
|
+
transaction(stores: string[], mode: 'readonly' | 'readwrite'): IdbTransaction;
|
|
167
|
+
close(): void;
|
|
168
|
+
}
|
|
169
|
+
/** The part of `window.indexedDB` a snapshot uses. */
|
|
170
|
+
export interface IdbFactoryLike {
|
|
171
|
+
open(name: string, version: number): IdbOpenRequest;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The browser's storage for a copy: one database, `events` keyed by scope and
|
|
175
|
+
* id, and a `meta` row per scope with its version and answered filters.
|
|
176
|
+
*/
|
|
177
|
+
export declare function indexedDbSnapshotStorage(indexedDB: IdbFactoryLike, name?: string): SnapshotStorage;
|
|
178
|
+
export {};
|
|
179
|
+
//# sourceMappingURL=event-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-snapshot.d.ts","sourceRoot":"","sources":["../src/event-snapshot.ts"],"names":[],"mappings":"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"}
|