@chipmobilesdk/rn-notification 0.1.2 → 0.2.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/CHANGELOG.md +96 -42
- package/README.md +399 -306
- package/package.json +2 -1
- package/skills/sdk-rn-notification/SKILL.md +68 -3
- package/src/channel/migrate.ts +86 -12
- package/src/channel/tone-map.ts +130 -90
- package/src/channel/validate-tone.ts +61 -0
- package/src/contract/channel.ts +188 -89
- package/src/contract/diagnostics.ts +102 -102
- package/src/contract/errors.ts +130 -123
- package/src/contract/index.ts +10 -10
- package/src/contract/interaction.ts +62 -62
- package/src/contract/notification.ts +118 -118
- package/src/contract/permission.ts +60 -60
- package/src/contract/reconcile.ts +88 -88
- package/src/contract/recurrence.ts +123 -123
- package/src/diagnostics/burst.ts +101 -101
- package/src/diagnostics/events.ts +72 -72
- package/src/diagnostics/redact.ts +72 -72
- package/src/diagnostics/snapshot.ts +64 -64
- package/src/engine/adapter.ts +172 -79
- package/src/engine/notify-kit-engine.ts +761 -535
- package/src/index.ts +24 -0
- package/src/initialize.ts +174 -174
- package/src/interaction/index.ts +146 -146
- package/src/maintenance/cycle.ts +147 -147
- package/src/maintenance/permission-refresh.ts +41 -41
- package/src/maintenance/reanchor.ts +95 -95
- package/src/maintenance/toggle.ts +60 -60
- package/src/maintenance/verify.ts +83 -83
- package/src/maintenance/window-refresh.ts +61 -61
- package/src/permission/index.ts +183 -183
- package/src/permission/state.ts +77 -77
- package/src/push/envelope.ts +165 -165
- package/src/push/fcm-provider.ts +146 -146
- package/src/push/gate.ts +190 -190
- package/src/push/instructions.ts +109 -109
- package/src/push/provider-adapter.ts +54 -54
- package/src/push/signout.ts +65 -65
- package/src/reconcile/chunk.ts +53 -53
- package/src/reconcile/diff.ts +145 -145
- package/src/reconcile/duplicates.ts +73 -73
- package/src/reconcile/index.ts +14 -1
- package/src/reconcile/truncate.ts +56 -56
- package/src/recurrence/clock-guard.ts +71 -71
- package/src/recurrence/expand.ts +292 -292
- package/src/repeat/expiry.ts +55 -0
- package/src/repeat/index.ts +54 -0
- package/src/repeat/marker.ts +72 -0
- package/src/repeat/place.ts +73 -0
- package/src/schedule/anchor.ts +267 -267
- package/src/schedule/index.ts +184 -173
- package/src/schedule/partition.ts +85 -85
- package/src/schedule/resolve-moment.ts +80 -80
- package/src/schedule/validate-content.ts +111 -111
- package/src/schedule/validate-identifier.ts +126 -114
- package/src/state/caps.ts +71 -71
- package/src/state/interaction-buffer.ts +73 -73
- package/src/state/mmkv-store.ts +75 -71
- package/src/state/permission-history.ts +41 -41
- package/src/state/record.ts +258 -246
- package/src/state/reset.ts +41 -41
- package/src/state/series-ledger.ts +84 -84
- package/src/state/store.ts +40 -40
- package/src/testing/conformance/index.ts +248 -194
- package/src/testing/fake-engine.ts +469 -284
- package/src/testing/fake-provider.ts +129 -129
- package/src/testing/faults.ts +72 -72
- package/src/testing/measure.ts +337 -334
package/src/state/record.ts
CHANGED
|
@@ -1,246 +1,258 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The persisted state record.
|
|
3
|
-
*
|
|
4
|
-
* Six collections, holding only what the platform will not tell us. No
|
|
5
|
-
* notification content, no push token, no desired set. The interaction buffer
|
|
6
|
-
* is the sole content-bearing exception, and it evicts on delivery.
|
|
7
|
-
*
|
|
8
|
-
* Corrupt or incompatible state is discarded and rebuilt rather than blocking:
|
|
9
|
-
* everything already scheduled lives in the OS and is unaffected, which is what
|
|
10
|
-
* makes discarding safe rather than destructive.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { StateStore } from './store';
|
|
14
|
-
import { evict, COLLECTION_CAPS, type CollectionName, type Timestamped } from './caps';
|
|
15
|
-
import type { AnchoringMode, RoutingPayload } from '../contract/notification';
|
|
16
|
-
import type { CollectionHealth, StateHealth } from '../contract/diagnostics';
|
|
17
|
-
import { DIAGNOSTIC_CODES, type DiagnosticCode } from '../contract/errors';
|
|
18
|
-
|
|
19
|
-
const STATE_KEY = 'chipmobilesdk.notification.state';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Bumped whenever the shape below changes. A mismatch discards and rebuilds —
|
|
23
|
-
* never a migration, because none of this data is worth migrating and all of it
|
|
24
|
-
* is reconstructible.
|
|
25
|
-
*/
|
|
26
|
-
export const STATE_SCHEMA_VERSION = 1;
|
|
27
|
-
|
|
28
|
-
export interface DedupEntry extends Timestamped {
|
|
29
|
-
readonly dedupId: string;
|
|
30
|
-
readonly ownerTag?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface BufferedInteraction extends Timestamped {
|
|
34
|
-
readonly id: string;
|
|
35
|
-
readonly kind: 'tap' | 'action' | 'dismiss';
|
|
36
|
-
readonly notificationId: string;
|
|
37
|
-
readonly actionId?: string;
|
|
38
|
-
readonly inputText?: string;
|
|
39
|
-
/** The one content-bearing thing persisted. Evicted the moment it is delivered. */
|
|
40
|
-
readonly routing?: RoutingPayload;
|
|
41
|
-
readonly ownerTag?: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface MigrationEntry extends Timestamped {
|
|
45
|
-
readonly toneId: string;
|
|
46
|
-
readonly appliedVersion: number;
|
|
47
|
-
readonly channelId: string;
|
|
48
|
-
readonly retired: boolean;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
readonly
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
readonly
|
|
66
|
-
readonly
|
|
67
|
-
readonly
|
|
68
|
-
readonly
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
readonly
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
private
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.
|
|
205
|
-
this.
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
this.record =
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The persisted state record.
|
|
3
|
+
*
|
|
4
|
+
* Six collections, holding only what the platform will not tell us. No
|
|
5
|
+
* notification content, no push token, no desired set. The interaction buffer
|
|
6
|
+
* is the sole content-bearing exception, and it evicts on delivery.
|
|
7
|
+
*
|
|
8
|
+
* Corrupt or incompatible state is discarded and rebuilt rather than blocking:
|
|
9
|
+
* everything already scheduled lives in the OS and is unaffected, which is what
|
|
10
|
+
* makes discarding safe rather than destructive.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { StateStore } from './store';
|
|
14
|
+
import { evict, COLLECTION_CAPS, type CollectionName, type Timestamped } from './caps';
|
|
15
|
+
import type { AnchoringMode, RoutingPayload } from '../contract/notification';
|
|
16
|
+
import type { CollectionHealth, StateHealth } from '../contract/diagnostics';
|
|
17
|
+
import { DIAGNOSTIC_CODES, type DiagnosticCode } from '../contract/errors';
|
|
18
|
+
|
|
19
|
+
const STATE_KEY = 'chipmobilesdk.notification.state';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Bumped whenever the shape below changes. A mismatch discards and rebuilds —
|
|
23
|
+
* never a migration, because none of this data is worth migrating and all of it
|
|
24
|
+
* is reconstructible.
|
|
25
|
+
*/
|
|
26
|
+
export const STATE_SCHEMA_VERSION = 1;
|
|
27
|
+
|
|
28
|
+
export interface DedupEntry extends Timestamped {
|
|
29
|
+
readonly dedupId: string;
|
|
30
|
+
readonly ownerTag?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BufferedInteraction extends Timestamped {
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly kind: 'tap' | 'action' | 'dismiss';
|
|
36
|
+
readonly notificationId: string;
|
|
37
|
+
readonly actionId?: string;
|
|
38
|
+
readonly inputText?: string;
|
|
39
|
+
/** The one content-bearing thing persisted. Evicted the moment it is delivered. */
|
|
40
|
+
readonly routing?: RoutingPayload;
|
|
41
|
+
readonly ownerTag?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface MigrationEntry extends Timestamped {
|
|
45
|
+
readonly toneId: string;
|
|
46
|
+
readonly appliedVersion: number;
|
|
47
|
+
readonly channelId: string;
|
|
48
|
+
readonly retired: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Fingerprint of the labels last pushed to the platform.
|
|
51
|
+
*
|
|
52
|
+
* Optional because records written before labels were carried at all have
|
|
53
|
+
* none — and their absence is exactly right: an unknown fingerprint refreshes
|
|
54
|
+
* once, which is the one-time correction every existing device needs, since
|
|
55
|
+
* every existing channel currently carries the tone id as its name.
|
|
56
|
+
*
|
|
57
|
+
* A fingerprint rather than the labels themselves. Persisted state holds no
|
|
58
|
+
* consumer text, and a hash answers the only question asked of it.
|
|
59
|
+
*/
|
|
60
|
+
readonly labelFingerprint?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SeriesEntry extends Timestamped {
|
|
64
|
+
readonly seriesId: string;
|
|
65
|
+
readonly anchoring: AnchoringMode;
|
|
66
|
+
readonly expandedThrough: number;
|
|
67
|
+
readonly emittedCount: number;
|
|
68
|
+
readonly exhausted: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface PermissionHistoryEntry extends Timestamped {
|
|
72
|
+
readonly key: string;
|
|
73
|
+
readonly requested: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface StateRecord {
|
|
77
|
+
readonly schemaVersion: number;
|
|
78
|
+
readonly dedupLedger: readonly DedupEntry[];
|
|
79
|
+
readonly interactionBuffer: readonly BufferedInteraction[];
|
|
80
|
+
readonly migrationLedger: readonly MigrationEntry[];
|
|
81
|
+
readonly seriesLedger: readonly SeriesEntry[];
|
|
82
|
+
readonly permissionHistory: readonly PermissionHistoryEntry[];
|
|
83
|
+
readonly lastReconcileSummary?: Readonly<Record<string, unknown>>;
|
|
84
|
+
readonly lastMaintenanceSummary?: Readonly<Record<string, unknown>>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function emptyRecord(): StateRecord {
|
|
88
|
+
return {
|
|
89
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
90
|
+
dedupLedger: [],
|
|
91
|
+
interactionBuffer: [],
|
|
92
|
+
migrationLedger: [],
|
|
93
|
+
seriesLedger: [],
|
|
94
|
+
permissionHistory: [],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface StateEvent {
|
|
99
|
+
readonly code: DiagnosticCode;
|
|
100
|
+
readonly context?: Readonly<Record<string, unknown>>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface StateManagerOptions {
|
|
104
|
+
readonly store: StateStore;
|
|
105
|
+
readonly now: () => number;
|
|
106
|
+
readonly emit?: (event: StateEvent) => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Owns the record's lifecycle. Reads are synchronous and cheap; writes are
|
|
111
|
+
* best-effort and never propagate a failure to the caller's operation.
|
|
112
|
+
*/
|
|
113
|
+
export class StateManager {
|
|
114
|
+
private record: StateRecord;
|
|
115
|
+
private rebuilt = false;
|
|
116
|
+
private readonly store: StateStore;
|
|
117
|
+
private readonly now: () => number;
|
|
118
|
+
private readonly emit: (event: StateEvent) => void;
|
|
119
|
+
|
|
120
|
+
constructor(options: StateManagerOptions) {
|
|
121
|
+
this.store = options.store;
|
|
122
|
+
this.now = options.now;
|
|
123
|
+
this.emit = options.emit ?? (() => {});
|
|
124
|
+
this.record = this.load();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private load(): StateRecord {
|
|
128
|
+
const raw = this.store.getString(STATE_KEY);
|
|
129
|
+
if (raw === undefined) {
|
|
130
|
+
return emptyRecord();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(raw) as Partial<StateRecord>;
|
|
135
|
+
if (parsed.schemaVersion !== STATE_SCHEMA_VERSION) {
|
|
136
|
+
return this.discardAndRebuild('schema-version-mismatch', {
|
|
137
|
+
found: parsed.schemaVersion,
|
|
138
|
+
expected: STATE_SCHEMA_VERSION,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
schemaVersion: STATE_SCHEMA_VERSION,
|
|
143
|
+
dedupLedger: parsed.dedupLedger ?? [],
|
|
144
|
+
interactionBuffer: parsed.interactionBuffer ?? [],
|
|
145
|
+
migrationLedger: parsed.migrationLedger ?? [],
|
|
146
|
+
seriesLedger: parsed.seriesLedger ?? [],
|
|
147
|
+
permissionHistory: parsed.permissionHistory ?? [],
|
|
148
|
+
lastReconcileSummary: parsed.lastReconcileSummary,
|
|
149
|
+
lastMaintenanceSummary: parsed.lastMaintenanceSummary,
|
|
150
|
+
};
|
|
151
|
+
} catch {
|
|
152
|
+
return this.discardAndRebuild('unparseable');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private discardAndRebuild(
|
|
157
|
+
reason: string,
|
|
158
|
+
extra?: Readonly<Record<string, unknown>>,
|
|
159
|
+
): StateRecord {
|
|
160
|
+
this.rebuilt = true;
|
|
161
|
+
this.store.delete(STATE_KEY);
|
|
162
|
+
this.emit({
|
|
163
|
+
code: DIAGNOSTIC_CODES.STATE_CORRUPT_REBUILT,
|
|
164
|
+
// Note what is actually lost, so the consequence is stated rather than implied.
|
|
165
|
+
context: { reason, lost: ['dedupLedger', 'interactionBuffer'], ...extra },
|
|
166
|
+
});
|
|
167
|
+
return emptyRecord();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Never throws. A storage failure degrades the affected guarantee, nothing more. */
|
|
171
|
+
private persist(): void {
|
|
172
|
+
try {
|
|
173
|
+
this.store.setString(STATE_KEY, JSON.stringify(this.record));
|
|
174
|
+
} catch (error) {
|
|
175
|
+
this.emit({
|
|
176
|
+
code: DIAGNOSTIC_CODES.STATE_WRITE_FAILED,
|
|
177
|
+
context: { reason: error instanceof Error ? error.name : 'unknown' },
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
read(): StateRecord {
|
|
183
|
+
return this.record;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Applies a change, evicts the touched collection, and persists. */
|
|
187
|
+
update<K extends CollectionName>(
|
|
188
|
+
collection: K,
|
|
189
|
+
mutate: (current: StateRecord[K]) => StateRecord[K],
|
|
190
|
+
): void {
|
|
191
|
+
const next = mutate(this.record[collection]);
|
|
192
|
+
const { kept, evicted, reason } = evict(next as readonly Timestamped[], collection, this.now());
|
|
193
|
+
|
|
194
|
+
if (evicted.length > 0) {
|
|
195
|
+
this.emit({
|
|
196
|
+
code:
|
|
197
|
+
collection === 'interactionBuffer'
|
|
198
|
+
? DIAGNOSTIC_CODES.INTERACTION_BUFFER_EVICTED
|
|
199
|
+
: DIAGNOSTIC_CODES.STATE_CAP_EVICTED,
|
|
200
|
+
context: { collection, evicted: evicted.length, reason },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.record = { ...this.record, [collection]: kept } as StateRecord;
|
|
205
|
+
this.persist();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
setSummary(key: 'lastReconcileSummary' | 'lastMaintenanceSummary', value: Readonly<Record<string, unknown>>): void {
|
|
209
|
+
this.record = { ...this.record, [key]: value };
|
|
210
|
+
this.persist();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Clears everything. Public through `resetState()`. */
|
|
214
|
+
reset(): void {
|
|
215
|
+
this.record = emptyRecord();
|
|
216
|
+
this.rebuilt = false;
|
|
217
|
+
this.store.delete(STATE_KEY);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Clears one owner tag's entries, leaving every other tag untouched.
|
|
222
|
+
*
|
|
223
|
+
* Collections without an owner tag are left alone: a migration ledger and a
|
|
224
|
+
* series ledger describe the device, not an account.
|
|
225
|
+
*/
|
|
226
|
+
clearOwner(ownerTag: string): void {
|
|
227
|
+
this.record = {
|
|
228
|
+
...this.record,
|
|
229
|
+
dedupLedger: this.record.dedupLedger.filter(e => e.ownerTag !== ownerTag),
|
|
230
|
+
interactionBuffer: this.record.interactionBuffer.filter(e => e.ownerTag !== ownerTag),
|
|
231
|
+
permissionHistory: this.record.permissionHistory.filter(
|
|
232
|
+
e => !e.key.startsWith(`${ownerTag}:`),
|
|
233
|
+
),
|
|
234
|
+
};
|
|
235
|
+
this.persist();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
health(): StateHealth {
|
|
239
|
+
const collections: CollectionHealth[] = (
|
|
240
|
+
Object.keys(COLLECTION_CAPS) as CollectionName[]
|
|
241
|
+
).map(name => {
|
|
242
|
+
const entries = this.record[name] as readonly Timestamped[];
|
|
243
|
+
return {
|
|
244
|
+
name,
|
|
245
|
+
size: entries.length,
|
|
246
|
+
cap: COLLECTION_CAPS[name].maxEntries,
|
|
247
|
+
oldestEntryAt: entries.length > 0 ? Math.min(...entries.map(e => e.at)) : undefined,
|
|
248
|
+
};
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
readable: true,
|
|
253
|
+
rebuilt: this.rebuilt,
|
|
254
|
+
collections,
|
|
255
|
+
approximateBytes: this.store.approximateBytes(),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
package/src/state/reset.ts
CHANGED
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public state reset and per-owner clearing.
|
|
3
|
-
*
|
|
4
|
-
* These are the only two ways a consumer may touch the persisted record. The
|
|
5
|
-
* record itself is unreachable — a consumer can discard state, but can never
|
|
6
|
-
* read or write it.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { StateManager } from './record';
|
|
10
|
-
|
|
11
|
-
export interface ResetOutcome {
|
|
12
|
-
readonly clearedCollections: readonly string[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/** Discards the entire record. Nothing already scheduled with the OS is affected. */
|
|
16
|
-
export function resetState(state: StateManager): ResetOutcome {
|
|
17
|
-
state.reset();
|
|
18
|
-
return {
|
|
19
|
-
clearedCollections: [
|
|
20
|
-
'dedupLedger',
|
|
21
|
-
'interactionBuffer',
|
|
22
|
-
'migrationLedger',
|
|
23
|
-
'seriesLedger',
|
|
24
|
-
'permissionHistory',
|
|
25
|
-
],
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Clears one owner tag's entries on sign-out.
|
|
31
|
-
*
|
|
32
|
-
* Deliberately narrower than a full reset: the migration and series ledgers
|
|
33
|
-
* describe the device rather than an account, so signing one account out must
|
|
34
|
-
* not discard channel migration history that every account depends on.
|
|
35
|
-
*/
|
|
36
|
-
export function clearOwner(state: StateManager, ownerTag: string): ResetOutcome {
|
|
37
|
-
state.clearOwner(ownerTag);
|
|
38
|
-
return {
|
|
39
|
-
clearedCollections: ['dedupLedger', 'interactionBuffer', 'permissionHistory'],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Public state reset and per-owner clearing.
|
|
3
|
+
*
|
|
4
|
+
* These are the only two ways a consumer may touch the persisted record. The
|
|
5
|
+
* record itself is unreachable — a consumer can discard state, but can never
|
|
6
|
+
* read or write it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { StateManager } from './record';
|
|
10
|
+
|
|
11
|
+
export interface ResetOutcome {
|
|
12
|
+
readonly clearedCollections: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Discards the entire record. Nothing already scheduled with the OS is affected. */
|
|
16
|
+
export function resetState(state: StateManager): ResetOutcome {
|
|
17
|
+
state.reset();
|
|
18
|
+
return {
|
|
19
|
+
clearedCollections: [
|
|
20
|
+
'dedupLedger',
|
|
21
|
+
'interactionBuffer',
|
|
22
|
+
'migrationLedger',
|
|
23
|
+
'seriesLedger',
|
|
24
|
+
'permissionHistory',
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Clears one owner tag's entries on sign-out.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately narrower than a full reset: the migration and series ledgers
|
|
33
|
+
* describe the device rather than an account, so signing one account out must
|
|
34
|
+
* not discard channel migration history that every account depends on.
|
|
35
|
+
*/
|
|
36
|
+
export function clearOwner(state: StateManager, ownerTag: string): ResetOutcome {
|
|
37
|
+
state.clearOwner(ownerTag);
|
|
38
|
+
return {
|
|
39
|
+
clearedCollections: ['dedupLedger', 'interactionBuffer', 'permissionHistory'],
|
|
40
|
+
};
|
|
41
|
+
}
|