@estiva-app/platform 0.1.0 → 0.2.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 +17 -0
- package/dist/folder-activity.d.ts +18 -3
- package/dist/folder-activity.d.ts.map +1 -1
- package/dist/folder-activity.js +2 -2
- package/dist/folder-activity.js.map +1 -1
- package/dist/live-client.d.ts +10 -1
- package/dist/live-client.d.ts.map +1 -1
- package/dist/live-client.js +9 -3
- package/dist/live-client.js.map +1 -1
- package/package.json +1 -1
- package/src/folder-activity.ts +21 -5
- package/src/live-client.ts +19 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @estiva-app/platform
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
A Folder notification may carry the event that caused it (PER-8). Additive:
|
|
6
|
+
every existing listener keeps compiling and behaving as before.
|
|
7
|
+
|
|
8
|
+
- `watchFolders`' listener is called `(folder, event)`. The wide filter always
|
|
9
|
+
had the event in hand and dropped it, so a reader that keeps what it has read
|
|
10
|
+
— Peek's sidebar, which re-read 90 days of every Folder to learn about the one
|
|
11
|
+
message the socket had just delivered — had nothing to apply and could only
|
|
12
|
+
re-read, against a relay quota metered per HTTP request.
|
|
13
|
+
- `watchFolders` takes `since`, bounding what the REQ replays on subscribe and
|
|
14
|
+
on every reconnect. Without it the relay replays up to its default limit,
|
|
15
|
+
shared across every Folder in the filter.
|
|
16
|
+
- `FolderListener` is `(event?) => void` and `FolderActivity.notify` takes the
|
|
17
|
+
event. A notification with nothing to carry — another app's record moved —
|
|
18
|
+
still sends none, so a missing event means "re-read", never "nothing".
|
|
19
|
+
|
|
3
20
|
## 0.1.0
|
|
4
21
|
|
|
5
22
|
First release. The plumbing a second consumer of the live socket needs, taken
|
|
@@ -23,11 +23,26 @@
|
|
|
23
23
|
* reader watching its own Folders is in the same position mechanically and the
|
|
24
24
|
* word stops fitting, so the shared name is about the Folder rather than about
|
|
25
25
|
* whose records are in it.
|
|
26
|
+
*
|
|
27
|
+
* ## …unless it has one to give (PER-8)
|
|
28
|
+
*
|
|
29
|
+
* "Re-read now" was the right signal while nobody held what they had read. Once
|
|
30
|
+
* a reader keeps the events it already has — Peek's sidebar re-read 90 days of
|
|
31
|
+
* every Folder to learn about the one message the socket had just delivered, and
|
|
32
|
+
* spent the relay's per-request HTTP quota doing it — the event is the whole
|
|
33
|
+
* point. So a notification *may* carry the event that caused it. A listener
|
|
34
|
+
* that ignores the argument is exactly the listener it was before, and a
|
|
35
|
+
* notification with nothing to carry (another app's record moved) still sends
|
|
36
|
+
* none, so a reader must treat a missing event as "re-read", not as "nothing".
|
|
26
37
|
*/
|
|
27
|
-
|
|
38
|
+
import type { SignedEvent } from '@estiva-app/protocol';
|
|
39
|
+
export type FolderListener = (event?: SignedEvent) => void;
|
|
28
40
|
export interface FolderActivity {
|
|
29
|
-
/**
|
|
30
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Say that something changed in `folder`, and hand over the event when there
|
|
43
|
+
* is one. Safe for a folder nobody watches.
|
|
44
|
+
*/
|
|
45
|
+
notify(folder: string, event?: SignedEvent): void;
|
|
31
46
|
/** Watch one folder. Returns an unsubscribe; calling it twice is harmless. */
|
|
32
47
|
watch(folder: string, listener: FolderListener): () => void;
|
|
33
48
|
/** Folders with at least one listener. Test and diagnostic seam. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder-activity.d.ts","sourceRoot":"","sources":["../src/folder-activity.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"folder-activity.d.ts","sourceRoot":"","sources":["../src/folder-activity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAEvD,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,EAAE,WAAW,KAAK,IAAI,CAAA;AAE1D,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;IACjD,8EAA8E;IAC9E,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,MAAM,IAAI,CAAA;IAC3D,oEAAoE;IACpE,OAAO,IAAI,MAAM,EAAE,CAAA;CACpB;AAED,wBAAgB,oBAAoB,IAAI,cAAc,CA8BrD"}
|
package/dist/folder-activity.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export function createFolderActivity() {
|
|
2
2
|
const listeners = new Map();
|
|
3
3
|
return {
|
|
4
|
-
notify(folder) {
|
|
4
|
+
notify(folder, event) {
|
|
5
5
|
for (const listener of listeners.get(folder) ?? []) {
|
|
6
6
|
// One bad listener must not stop the others, and must not surface
|
|
7
7
|
// somewhere unrelated as a relay error.
|
|
8
8
|
try {
|
|
9
|
-
listener();
|
|
9
|
+
listener(event);
|
|
10
10
|
}
|
|
11
11
|
catch {
|
|
12
12
|
/* a listener's own problem */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder-activity.js","sourceRoot":"","sources":["../src/folder-activity.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"folder-activity.js","sourceRoot":"","sources":["../src/folder-activity.ts"],"names":[],"mappings":"AAqDA,MAAM,UAAU,oBAAoB;IAClC,MAAM,SAAS,GAAG,IAAI,GAAG,EAA+B,CAAA;IACxD,OAAO;QACL,MAAM,CAAC,MAAM,EAAE,KAAK;YAClB,KAAK,MAAM,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACnD,kEAAkE;gBAClE,wCAAwC;gBACxC,IAAI,CAAC;oBACH,QAAQ,CAAC,KAAK,CAAC,CAAA;gBACjB,CAAC;gBAAC,MAAM,CAAC;oBACP,8BAA8B;gBAChC,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,CAAC,MAAM,EAAE,QAAQ;YACpB,IAAI,SAAS,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YACrC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,SAAS,GAAG,IAAI,GAAG,EAAE,CAAA;gBACrB,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;YAClC,CAAC;YACD,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YACvB,OAAO,GAAG,EAAE;gBACV,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;gBACrC,IAAI,CAAC,OAAO;oBAAE,OAAM;gBACpB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;gBACxB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;oBAAE,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAClD,CAAC,CAAA;QACH,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;KACrC,CAAA;AACH,CAAC"}
|
package/dist/live-client.d.ts
CHANGED
|
@@ -126,9 +126,18 @@ export interface LiveClient {
|
|
|
126
126
|
* `kinds` is strongly recommended rather than optional-by-taste: a kindless
|
|
127
127
|
* wide filter was refused outright on that other build with `restricted:
|
|
128
128
|
* p-gated events require #p matching your pubkey`.
|
|
129
|
+
*
|
|
130
|
+
* **The listener is handed the event** (PER-8). A reader that keeps what it
|
|
131
|
+
* has read applies it instead of re-reading the Folder; one that only wants
|
|
132
|
+
* the signal ignores the second argument. Stored events arrive as well as
|
|
133
|
+
* live ones — the REQ's replay on subscribe and on every reconnect — so a
|
|
134
|
+
* reader must be idempotent by event id. `since` bounds that replay: without
|
|
135
|
+
* it the relay replays whatever its default limit allows, shared across every
|
|
136
|
+
* Folder in the filter, which a keeping reader then has to dedupe.
|
|
129
137
|
*/
|
|
130
|
-
watchFolders(folders: string[], listener: (folder: string) => void, options?: {
|
|
138
|
+
watchFolders(folders: string[], listener: (folder: string, event?: SignedEvent) => void, options?: {
|
|
131
139
|
kinds?: number[];
|
|
140
|
+
since?: number;
|
|
132
141
|
}): FolderWatch;
|
|
133
142
|
/** Watch one Folder by name. The refcounted half of {@link watchFolders}. */
|
|
134
143
|
onFolderActivity(folder: string, listener: FolderListener): () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-client.d.ts","sourceRoot":"","sources":["../src/live-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAA6C,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrG,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,MAAM,CAAA;AAE/C;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAA;IAChC,yFAAyF;IACzF,mBAAmB,CAAC,IAAI,IAAI,CAAA;IAC5B,QAAQ,CAAC,IAAI,IAAI,CAAA;IACjB,aAAa,CAAC,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;CACrC;AAED,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,8EAA8E;IAC9E,aAAa,IAAI,eAAe,GAAG,IAAI,CAAA;IACvC;;;;OAIG;IACH,IAAI,CAAC,QAAQ,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC1F,0FAA0F;IAC1F,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,OAAO,CAAC,EAAE,mBAAmB,CAAA;IAC7B,4EAA4E;IAC5E,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kDAAkD;IAClD,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;IAC7D,4CAA4C;IAC5C,aAAa,CAAC,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACjD,GAAG,CAAC,EAAE,gBAAgB,CAAC,KAAK,CAAC,CAAA;IAC7B,QAAQ,CAAC,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAA;IACvC,UAAU,CAAC,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IAC3C,OAAO,CAAC,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,IAAI,IAAI,IAAI,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,wFAAwF;IACxF,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAA;IAC5C,KAAK,IAAI,UAAU,CAAA;IACnB,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;IAC1D
|
|
1
|
+
{"version":3,"file":"live-client.d.ts","sourceRoot":"","sources":["../src/live-client.ts"],"names":[],"mappings":"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"}
|
package/dist/live-client.js
CHANGED
|
@@ -139,11 +139,17 @@ export function createLiveClient(options) {
|
|
|
139
139
|
},
|
|
140
140
|
onFolderActivity: (folder, listener) => activity.watch(folder, listener),
|
|
141
141
|
watchFolders(folders, listener, watchOptions) {
|
|
142
|
-
const unwatch = folders.map((folder) => activity.watch(folder, () => listener(folder)));
|
|
143
|
-
const subs = chunk([...new Set(folders)], MAX_FOLDERS_PER_SUBSCRIPTION).map((group) => relay.subscribe([
|
|
142
|
+
const unwatch = folders.map((folder) => activity.watch(folder, (event) => listener(folder, event)));
|
|
143
|
+
const subs = chunk([...new Set(folders)], MAX_FOLDERS_PER_SUBSCRIPTION).map((group) => relay.subscribe([
|
|
144
|
+
{
|
|
145
|
+
...(watchOptions?.kinds ? { kinds: watchOptions.kinds } : {}),
|
|
146
|
+
'#h': group,
|
|
147
|
+
...(watchOptions?.since !== undefined ? { since: watchOptions.since } : {}),
|
|
148
|
+
},
|
|
149
|
+
], (event) => {
|
|
144
150
|
const folder = folderOf(event);
|
|
145
151
|
if (folder)
|
|
146
|
-
activity.notify(folder);
|
|
152
|
+
activity.notify(folder, event);
|
|
147
153
|
}, {
|
|
148
154
|
onClosed: (reason) => options.log?.('folder subscription closed', { reason }),
|
|
149
155
|
}));
|
package/dist/live-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-client.js","sourceRoot":"","sources":["../src/live-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,OAAO,EACL,0BAA0B,EAC1B,eAAe,GAQhB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,oBAAoB,EAA4C,MAAM,sBAAsB,CAAA;AACrG,OAAO,EAAE,YAAY,EAAqB,MAAM,aAAa,CAAA;AAE7D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAA;
|
|
1
|
+
{"version":3,"file":"live-client.js","sourceRoot":"","sources":["../src/live-client.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@estiva-app/platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Browser lifecycle two Estiva apps share: the one live relay socket a tab should hold, its credential read per connect, and the network coming back.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/folder-activity.ts
CHANGED
|
@@ -23,12 +23,28 @@
|
|
|
23
23
|
* reader watching its own Folders is in the same position mechanically and the
|
|
24
24
|
* word stops fitting, so the shared name is about the Folder rather than about
|
|
25
25
|
* whose records are in it.
|
|
26
|
+
*
|
|
27
|
+
* ## …unless it has one to give (PER-8)
|
|
28
|
+
*
|
|
29
|
+
* "Re-read now" was the right signal while nobody held what they had read. Once
|
|
30
|
+
* a reader keeps the events it already has — Peek's sidebar re-read 90 days of
|
|
31
|
+
* every Folder to learn about the one message the socket had just delivered, and
|
|
32
|
+
* spent the relay's per-request HTTP quota doing it — the event is the whole
|
|
33
|
+
* point. So a notification *may* carry the event that caused it. A listener
|
|
34
|
+
* that ignores the argument is exactly the listener it was before, and a
|
|
35
|
+
* notification with nothing to carry (another app's record moved) still sends
|
|
36
|
+
* none, so a reader must treat a missing event as "re-read", not as "nothing".
|
|
26
37
|
*/
|
|
27
|
-
|
|
38
|
+
import type { SignedEvent } from '@estiva-app/protocol'
|
|
39
|
+
|
|
40
|
+
export type FolderListener = (event?: SignedEvent) => void
|
|
28
41
|
|
|
29
42
|
export interface FolderActivity {
|
|
30
|
-
/**
|
|
31
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Say that something changed in `folder`, and hand over the event when there
|
|
45
|
+
* is one. Safe for a folder nobody watches.
|
|
46
|
+
*/
|
|
47
|
+
notify(folder: string, event?: SignedEvent): void
|
|
32
48
|
/** Watch one folder. Returns an unsubscribe; calling it twice is harmless. */
|
|
33
49
|
watch(folder: string, listener: FolderListener): () => void
|
|
34
50
|
/** Folders with at least one listener. Test and diagnostic seam. */
|
|
@@ -38,12 +54,12 @@ export interface FolderActivity {
|
|
|
38
54
|
export function createFolderActivity(): FolderActivity {
|
|
39
55
|
const listeners = new Map<string, Set<FolderListener>>()
|
|
40
56
|
return {
|
|
41
|
-
notify(folder) {
|
|
57
|
+
notify(folder, event) {
|
|
42
58
|
for (const listener of listeners.get(folder) ?? []) {
|
|
43
59
|
// One bad listener must not stop the others, and must not surface
|
|
44
60
|
// somewhere unrelated as a relay error.
|
|
45
61
|
try {
|
|
46
|
-
listener()
|
|
62
|
+
listener(event)
|
|
47
63
|
} catch {
|
|
48
64
|
/* a listener's own problem */
|
|
49
65
|
}
|
package/src/live-client.ts
CHANGED
|
@@ -141,11 +141,19 @@ export interface LiveClient {
|
|
|
141
141
|
* `kinds` is strongly recommended rather than optional-by-taste: a kindless
|
|
142
142
|
* wide filter was refused outright on that other build with `restricted:
|
|
143
143
|
* p-gated events require #p matching your pubkey`.
|
|
144
|
+
*
|
|
145
|
+
* **The listener is handed the event** (PER-8). A reader that keeps what it
|
|
146
|
+
* has read applies it instead of re-reading the Folder; one that only wants
|
|
147
|
+
* the signal ignores the second argument. Stored events arrive as well as
|
|
148
|
+
* live ones — the REQ's replay on subscribe and on every reconnect — so a
|
|
149
|
+
* reader must be idempotent by event id. `since` bounds that replay: without
|
|
150
|
+
* it the relay replays whatever its default limit allows, shared across every
|
|
151
|
+
* Folder in the filter, which a keeping reader then has to dedupe.
|
|
144
152
|
*/
|
|
145
153
|
watchFolders(
|
|
146
154
|
folders: string[],
|
|
147
|
-
listener: (folder: string) => void,
|
|
148
|
-
options?: { kinds?: number[] },
|
|
155
|
+
listener: (folder: string, event?: SignedEvent) => void,
|
|
156
|
+
options?: { kinds?: number[]; since?: number },
|
|
149
157
|
): FolderWatch
|
|
150
158
|
/** Watch one Folder by name. The refcounted half of {@link watchFolders}. */
|
|
151
159
|
onFolderActivity(folder: string, listener: FolderListener): () => void
|
|
@@ -247,13 +255,19 @@ export function createLiveClient(options: LiveClientOptions): LiveClient {
|
|
|
247
255
|
},
|
|
248
256
|
onFolderActivity: (folder, listener) => activity.watch(folder, listener),
|
|
249
257
|
watchFolders(folders, listener, watchOptions) {
|
|
250
|
-
const unwatch = folders.map((folder) => activity.watch(folder, () => listener(folder)))
|
|
258
|
+
const unwatch = folders.map((folder) => activity.watch(folder, (event) => listener(folder, event)))
|
|
251
259
|
const subs = chunk([...new Set(folders)], MAX_FOLDERS_PER_SUBSCRIPTION).map((group) =>
|
|
252
260
|
relay.subscribe(
|
|
253
|
-
[
|
|
261
|
+
[
|
|
262
|
+
{
|
|
263
|
+
...(watchOptions?.kinds ? { kinds: watchOptions.kinds } : {}),
|
|
264
|
+
'#h': group,
|
|
265
|
+
...(watchOptions?.since !== undefined ? { since: watchOptions.since } : {}),
|
|
266
|
+
},
|
|
267
|
+
],
|
|
254
268
|
(event) => {
|
|
255
269
|
const folder = folderOf(event)
|
|
256
|
-
if (folder) activity.notify(folder)
|
|
270
|
+
if (folder) activity.notify(folder, event)
|
|
257
271
|
},
|
|
258
272
|
{
|
|
259
273
|
onClosed: (reason) => options.log?.('folder subscription closed', { reason }),
|