@threadbase-sh/streamer 1.37.0 → 1.39.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/dist/cli.cjs +1487 -387
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +1223 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -8
- package/dist/index.d.ts +175 -8
- package/dist/index.js +1221 -179
- package/dist/index.js.map +1 -1
- package/dist/launchd-entry.cjs +113 -30
- package/dist/launchd-entry.cjs.map +1 -1
- package/dist/migrations/013_add_push_token_kind.sql +63 -0
- package/dist/migrations/014_add_conversation_meta_file_path_index.sql +5 -0
- package/dist/pg-migrations/007_create_push_tokens.sql +93 -0
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
-- Push token kinds, for iOS Live Activities (Feature 12 phase 1b).
|
|
2
|
+
--
|
|
3
|
+
-- push_tokens (012) modelled exactly one token per device: the Expo relay token
|
|
4
|
+
-- used for ordinary notifications. A device now supplies three distinct token
|
|
5
|
+
-- types, and they are NOT interchangeable:
|
|
6
|
+
--
|
|
7
|
+
-- expo — Expo relay token. Ordinary push notifications.
|
|
8
|
+
-- liveactivity_start — ActivityKit push-to-start token. App-wide, one per
|
|
9
|
+
-- device, long-lived. Starts an activity when none exists.
|
|
10
|
+
-- liveactivity_update — ActivityKit per-activity update token. Issued by iOS
|
|
11
|
+
-- AFTER an activity starts, scoped to that one activity,
|
|
12
|
+
-- and short-lived.
|
|
13
|
+
--
|
|
14
|
+
-- Conflating them silently breaks delivery: an ActivityKit token posted to
|
|
15
|
+
-- Expo's relay is rejected, and an Expo token used to sign a
|
|
16
|
+
-- `.push-type.liveactivity` topic is rejected by APNs. Neither failure is
|
|
17
|
+
-- visible at registration time, which is exactly why kind is stored explicitly
|
|
18
|
+
-- rather than inferred from token shape.
|
|
19
|
+
ALTER TABLE push_tokens ADD COLUMN kind TEXT NOT NULL DEFAULT 'expo';
|
|
20
|
+
|
|
21
|
+
-- The ActivityKit activity this token updates, for kind =
|
|
22
|
+
-- 'liveactivity_update' only. A device runs several activities at once (one per
|
|
23
|
+
-- live session), so device_id alone cannot identify which row to update or end
|
|
24
|
+
-- — the reason 012's one-row-per-device shape does not stretch to cover this.
|
|
25
|
+
ALTER TABLE push_tokens ADD COLUMN activity_id TEXT;
|
|
26
|
+
|
|
27
|
+
-- The session this activity is rendering. Sending is driven by session
|
|
28
|
+
-- lifecycle transitions, so the send path looks tokens up by session, not by
|
|
29
|
+
-- device.
|
|
30
|
+
ALTER TABLE push_tokens ADD COLUMN session_id TEXT;
|
|
31
|
+
|
|
32
|
+
-- Per-activity tokens expire. Retained rather than deleted (matching
|
|
33
|
+
-- revoked_at's reasoning in 012) so the health report can say "expired"
|
|
34
|
+
-- instead of the row simply vanishing.
|
|
35
|
+
ALTER TABLE push_tokens ADD COLUMN expires_at INTEGER;
|
|
36
|
+
|
|
37
|
+
-- iOS ends a Live Activity ~8h after it starts, so a long session must be
|
|
38
|
+
-- renewed before the cap. Stored per activity rather than derived at read time:
|
|
39
|
+
-- a renewal must survive a restart, and re-arming timers on boot needs a
|
|
40
|
+
-- persisted deadline to read (an in-process timer does not survive).
|
|
41
|
+
ALTER TABLE push_tokens ADD COLUMN stale_date INTEGER;
|
|
42
|
+
|
|
43
|
+
-- The activity's ORIGINAL start, carried unchanged across every renewal.
|
|
44
|
+
-- iOS renders its own ticking elapsed timer from this value, so a renewal that
|
|
45
|
+
-- stamps a fresh start visibly resets the user's timer to zero. Persisting the
|
|
46
|
+
-- original is what makes elapsed time continuous across a renewal.
|
|
47
|
+
ALTER TABLE push_tokens ADD COLUMN started_at INTEGER;
|
|
48
|
+
|
|
49
|
+
-- Set once a renewal has been performed for this row, making renewal
|
|
50
|
+
-- idempotent: a restart mid-window re-arms the timer, and this column is what
|
|
51
|
+
-- stops the re-armed timer from sending a second time.
|
|
52
|
+
ALTER TABLE push_tokens ADD COLUMN renewed_at INTEGER;
|
|
53
|
+
|
|
54
|
+
-- The send path selects by (kind, session) — driven by a session status change,
|
|
55
|
+
-- never by scanning every token.
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_push_tokens_kind_session ON push_tokens (kind, session_id);
|
|
57
|
+
|
|
58
|
+
-- The renewal scheduler scans for activities approaching their cap. Partial
|
|
59
|
+
-- index: only unrenewed rows with a deadline are ever candidates, which keeps
|
|
60
|
+
-- the boot-time re-arm scan proportional to pending renewals rather than to
|
|
61
|
+
-- every token ever registered.
|
|
62
|
+
CREATE INDEX IF NOT EXISTS idx_push_tokens_stale_date ON push_tokens (stale_date)
|
|
63
|
+
WHERE stale_date IS NOT NULL AND renewed_at IS NULL;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- conversation_meta.file_path had no index, so getIdByFilePath /
|
|
2
|
+
-- getProviderByFilePath (called per JSONL line from each active session's
|
|
3
|
+
-- watcher) were full-table scans, blocking the single-threaded event loop
|
|
4
|
+
-- under concurrent active sessions.
|
|
5
|
+
CREATE INDEX IF NOT EXISTS idx_meta_file_path ON conversation_meta(file_path);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
-- Push registration and delivery state, mirroring SQLite migrations 012 + 013.
|
|
2
|
+
--
|
|
3
|
+
-- SQLite is the primary persistence layer; Postgres is dormant (see CLAUDE.md).
|
|
4
|
+
-- The two schemas are kept in sync so enabling Postgres later does not require
|
|
5
|
+
-- reconstructing history. SQLite reached this shape in two steps (012 created
|
|
6
|
+
-- the table, 013 added the kind columns); Postgres never had 012, so this
|
|
7
|
+
-- single migration creates the end state directly.
|
|
8
|
+
CREATE TABLE IF NOT EXISTS push_tokens (
|
|
9
|
+
-- The provider push token. Natural key: re-registering the same token must
|
|
10
|
+
-- update the existing row rather than accumulate duplicates, which is how one
|
|
11
|
+
-- device ends up receiving the same notification several times.
|
|
12
|
+
token TEXT PRIMARY KEY,
|
|
13
|
+
platform TEXT NOT NULL,
|
|
14
|
+
|
|
15
|
+
-- Optional device attribution. Nullable because push registration predates
|
|
16
|
+
-- device identity (C5) and must keep working without it.
|
|
17
|
+
device_id TEXT,
|
|
18
|
+
|
|
19
|
+
registered_at BIGINT NOT NULL,
|
|
20
|
+
|
|
21
|
+
-- Delivery health. Distinguishing "never tried" from "tried and failed" is
|
|
22
|
+
-- the difference between "not yet delivered" and "your notifications are
|
|
23
|
+
-- broken" — the report the user actually needs.
|
|
24
|
+
last_success_at BIGINT,
|
|
25
|
+
last_failure_at BIGINT,
|
|
26
|
+
last_failure_code TEXT,
|
|
27
|
+
|
|
28
|
+
-- Consecutive failures, reset on success. A token the provider has rejected
|
|
29
|
+
-- repeatedly is dead (app uninstalled, token rotated) and should stop being
|
|
30
|
+
-- retried rather than failing forever.
|
|
31
|
+
failure_streak INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
|
|
33
|
+
-- Set when the provider reports the token permanently invalid, or the user
|
|
34
|
+
-- unregisters. Retained rather than deleted so the health report can explain
|
|
35
|
+
-- why delivery stopped.
|
|
36
|
+
revoked_at BIGINT,
|
|
37
|
+
|
|
38
|
+
-- Token kind. A device supplies three non-interchangeable types: the Expo
|
|
39
|
+
-- relay token, the ActivityKit push-to-start token (app-wide), and an
|
|
40
|
+
-- ActivityKit per-activity update token (short-lived, issued after an
|
|
41
|
+
-- activity starts). Stored explicitly rather than inferred from token shape,
|
|
42
|
+
-- because a mismatch is rejected by the provider at send time with no signal
|
|
43
|
+
-- at registration time.
|
|
44
|
+
kind TEXT NOT NULL DEFAULT 'expo',
|
|
45
|
+
|
|
46
|
+
-- The ActivityKit activity this token updates (kind =
|
|
47
|
+
-- 'liveactivity_update'). A device runs several activities at once, one per
|
|
48
|
+
-- live session, so device_id alone cannot identify which row to update.
|
|
49
|
+
activity_id TEXT,
|
|
50
|
+
|
|
51
|
+
-- The session this activity renders. Sending is driven by session lifecycle
|
|
52
|
+
-- transitions, so the send path looks up by session rather than by device.
|
|
53
|
+
session_id TEXT,
|
|
54
|
+
|
|
55
|
+
-- Per-activity tokens expire. Retained rather than deleted so health can
|
|
56
|
+
-- report "expired" instead of the row vanishing.
|
|
57
|
+
expires_at BIGINT,
|
|
58
|
+
|
|
59
|
+
-- iOS ends a Live Activity ~8h after it starts. Persisted per activity
|
|
60
|
+
-- because a renewal must survive a restart: re-arming timers on boot needs a
|
|
61
|
+
-- durable deadline to read.
|
|
62
|
+
stale_date BIGINT,
|
|
63
|
+
|
|
64
|
+
-- The activity's ORIGINAL start, carried unchanged across every renewal. iOS
|
|
65
|
+
-- renders its own ticking timer from this, so a renewal stamping a fresh
|
|
66
|
+
-- start visibly resets the user's elapsed time to zero.
|
|
67
|
+
started_at BIGINT,
|
|
68
|
+
|
|
69
|
+
-- Set once this row has been renewed, making renewal idempotent: a restart
|
|
70
|
+
-- mid-window re-arms the timer, and this is what stops a second send.
|
|
71
|
+
renewed_at BIGINT
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
-- Delivery attempts keyed by event id so a retry, a reconnect reconciliation,
|
|
75
|
+
-- or a duplicate trigger cannot notify the user twice for one event.
|
|
76
|
+
CREATE TABLE IF NOT EXISTS push_events (
|
|
77
|
+
event_id TEXT PRIMARY KEY,
|
|
78
|
+
session_id TEXT,
|
|
79
|
+
created_at BIGINT NOT NULL,
|
|
80
|
+
delivered_at BIGINT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_push_tokens_revoked ON push_tokens (revoked_at);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_push_events_created ON push_events (created_at);
|
|
85
|
+
|
|
86
|
+
-- The send path selects by (kind, session), driven by a status change rather
|
|
87
|
+
-- than by scanning every token.
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_push_tokens_kind_session ON push_tokens (kind, session_id);
|
|
89
|
+
|
|
90
|
+
-- Renewal candidates only. Partial index keeps the boot-time re-arm scan
|
|
91
|
+
-- proportional to pending renewals, not to every token ever registered.
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_push_tokens_stale_date ON push_tokens (stale_date)
|
|
93
|
+
WHERE stale_date IS NOT NULL AND renewed_at IS NULL;
|
package/package.json
CHANGED