@polymorfa/store 0.1.0-dev.20260922094209
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/LICENSE +21 -0
- package/README.md +326 -0
- package/dist/backend.d.ts +89 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +341 -0
- package/dist/backend.js.map +1 -0
- package/dist/conversation-source.d.ts +28 -0
- package/dist/conversation-source.d.ts.map +1 -0
- package/dist/conversation-source.js +160 -0
- package/dist/conversation-source.js.map +1 -0
- package/dist/inbox-source.d.ts +19 -0
- package/dist/inbox-source.d.ts.map +1 -0
- package/dist/inbox-source.js +90 -0
- package/dist/inbox-source.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +13 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +45 -0
- package/dist/react.js.map +1 -0
- package/dist/reducers.d.ts +48 -0
- package/dist/reducers.d.ts.map +1 -0
- package/dist/reducers.js +821 -0
- package/dist/reducers.js.map +1 -0
- package/dist/sources.d.ts +170 -0
- package/dist/sources.d.ts.map +1 -0
- package/dist/sources.js +510 -0
- package/dist/sources.js.map +1 -0
- package/dist/store.d.ts +168 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +619 -0
- package/dist/store.js.map +1 -0
- package/dist/types.d.ts +212 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Polymorfa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# `@polymorfa/store`
|
|
2
|
+
|
|
3
|
+
An opt-in IndexedDB store for Polymorfa events in the browser.
|
|
4
|
+
|
|
5
|
+
When hosted message storage is off, Polymorfa does not keep your messages.
|
|
6
|
+
Your backend receives webhooks and stores what it needs. This package keeps a
|
|
7
|
+
local, queryable copy of those events in the browser so chat views open
|
|
8
|
+
instantly and stay current. It accepts the same event envelope as webhooks,
|
|
9
|
+
files each kind of data into its own store, and feeds the
|
|
10
|
+
`@polymorfa/browser` conversation controller.
|
|
11
|
+
|
|
12
|
+
> Polymorfa plans a client-token event stream that carries the webhook event
|
|
13
|
+
> shapes, limited to what the client token allows. It is not available yet.
|
|
14
|
+
> Until it is, stream events from your own backend with
|
|
15
|
+
> `fromEventSource()` or `fromEventStream()`, or replay a backfill with
|
|
16
|
+
> `fromIterable()`.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
flowchart LR
|
|
22
|
+
P[Polymorfa] -- webhooks --> B[Your backend]
|
|
23
|
+
B -- stores --> DB[(Your database)]
|
|
24
|
+
B -- SSE or WebSocket --> S[connectEventSource]
|
|
25
|
+
P -. planned client-token SSE .-> S
|
|
26
|
+
DB -- history load --> C
|
|
27
|
+
S -- ingest --> R[Reducers]
|
|
28
|
+
R --> I[(IndexedDB stores)]
|
|
29
|
+
I -- change notifications --> C[createStoreConversationSource]
|
|
30
|
+
I -- BroadcastChannel --> T[Other tabs]
|
|
31
|
+
C --> CC[ConversationController]
|
|
32
|
+
CC --> UI["React and Web Component chat views"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Each `ingest()` call:
|
|
36
|
+
|
|
37
|
+
1. Drops envelopes for other sessions (when `session` is set) and malformed
|
|
38
|
+
envelopes.
|
|
39
|
+
2. Removes token-like fields and your `redact` paths from the payload.
|
|
40
|
+
3. Skips event IDs already in the `events` log.
|
|
41
|
+
4. Runs the reducer for the event type. Types without a reducer, or whose
|
|
42
|
+
reducer does not file them, go to `custom`.
|
|
43
|
+
5. Writes the whole batch in one transaction, then notifies subscribers in
|
|
44
|
+
this tab and, through `BroadcastChannel`, in other tabs.
|
|
45
|
+
|
|
46
|
+
Reducers never replace newer state with older state. Message status only
|
|
47
|
+
moves forward (`pending`, `sent`, `delivered`, `read`, `played`); other fields
|
|
48
|
+
keep the time of the event that set them and ignore older updates. A deleted
|
|
49
|
+
or revoked message stays a tombstone, so a replayed event cannot restore it.
|
|
50
|
+
Messages at or before a `chat.clear` are not restored.
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {
|
|
56
|
+
connectEventSource,
|
|
57
|
+
createPolymorfaStore,
|
|
58
|
+
fromEventSource,
|
|
59
|
+
} from "@polymorfa/store";
|
|
60
|
+
|
|
61
|
+
const store = await createPolymorfaStore({
|
|
62
|
+
name: `support:${user.id}`,
|
|
63
|
+
session: "support",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const connection = connectEventSource(
|
|
67
|
+
store,
|
|
68
|
+
fromEventSource("/api/polymorfa/events", { withCredentials: true }),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const conversations = await store.conversations.list({
|
|
72
|
+
limit: 20,
|
|
73
|
+
unreadOnly: true,
|
|
74
|
+
});
|
|
75
|
+
const messages = await store.messages.list({
|
|
76
|
+
conversationId: conversations[0].id,
|
|
77
|
+
limit: 50,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// On sign-out
|
|
81
|
+
connection.close();
|
|
82
|
+
await store.clear();
|
|
83
|
+
store.close();
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Your endpoint sends each webhook event as an SSE frame. Put the event ID in
|
|
87
|
+
`id:` so the stream can resume. The `data:` line holds one event envelope or
|
|
88
|
+
an array of envelopes. A frame without an `event` field takes the SSE event
|
|
89
|
+
name.
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
id: evt_123
|
|
93
|
+
event: message.received
|
|
94
|
+
data: {"id":"evt_123","session":"support","timestamp":"2026-09-01T10:00:00Z","event":"message.received","payload":{...}}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`fromEventSource()` passes the saved cursor as the `lastEventId` query
|
|
98
|
+
parameter on a new page load. `EventSource` cannot send headers; use
|
|
99
|
+
`fromEventStream()` when your endpoint needs an `Authorization` header.
|
|
100
|
+
`headers` runs before every connection, and the store never saves what it
|
|
101
|
+
returns. Reconnects wait at least 250 ms, even when the server sends a
|
|
102
|
+
shorter `retry`. A line longer than 1 MiB drops the connection.
|
|
103
|
+
|
|
104
|
+
If a write fails, `connectEventSource()` reports the error through
|
|
105
|
+
`onError`, keeps the checkpoint before the failed batch, and stores nothing
|
|
106
|
+
more. Close the connection and connect again to resume from the checkpoint.
|
|
107
|
+
|
|
108
|
+
Pass `format: "project"` to read frames in the Polymorfa project event stream
|
|
109
|
+
format. The adapter decodes the base64 webhook body in each `event` frame and
|
|
110
|
+
saves the frame's `cursor` as the checkpoint. It skips events without a
|
|
111
|
+
retained body, but still resumes after them. It ignores `ready` and
|
|
112
|
+
`heartbeat` frames. It reconnects after `expiry`, `dropped`, or a `gap` that
|
|
113
|
+
is not `retention_exceeded`. After `revoked`, it stops and reports an error.
|
|
114
|
+
The project stream requires a server credential, so relay it through your
|
|
115
|
+
backend. Never send a server credential to a browser.
|
|
116
|
+
|
|
117
|
+
### Chat components
|
|
118
|
+
|
|
119
|
+
`createStoreConversationSource()` implements `ConversationDataSource`, so
|
|
120
|
+
`ConversationController` and the React and Web Component chat views work
|
|
121
|
+
without changes.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { ConversationController } from "@polymorfa/browser";
|
|
125
|
+
import { createStoreConversationSource } from "@polymorfa/store";
|
|
126
|
+
|
|
127
|
+
const controller = new ConversationController(
|
|
128
|
+
createStoreConversationSource(store, {
|
|
129
|
+
conversationId: "chat_1",
|
|
130
|
+
load: (cursor, signal) => fetchHistory("chat_1", cursor, signal),
|
|
131
|
+
send: (message, signal) => sendThroughBackend("chat_1", message, signal),
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
await controller.load();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The first `load()` returns the local messages at once and reconciles with
|
|
138
|
+
your backend in the background. If the store has no messages for the
|
|
139
|
+
conversation, it waits for the backend. After the local pages run out, the
|
|
140
|
+
controller pages through your backend and the results are stored.
|
|
141
|
+
Store changes from live events reach the controller as upserts and deletes.
|
|
142
|
+
|
|
143
|
+
`createStoreInboxSource(store, { backend })` backs `<Inbox/>`: it lists
|
|
144
|
+
conversations from the store, falls back to `backend` while the store is
|
|
145
|
+
empty, and follows store changes.
|
|
146
|
+
|
|
147
|
+
### React
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { usePolymorfaStoreQuery } from "@polymorfa/store/react";
|
|
151
|
+
|
|
152
|
+
const { data, loading } = usePolymorfaStoreQuery(
|
|
153
|
+
store,
|
|
154
|
+
"conversations",
|
|
155
|
+
(current) => current.conversations.list({ limit: 20 }),
|
|
156
|
+
);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The hook runs the query again when a listed store changes in any tab. Pass a
|
|
160
|
+
fourth `key` argument that changes when the query inputs change.
|
|
161
|
+
|
|
162
|
+
## Privacy
|
|
163
|
+
|
|
164
|
+
- Nothing is stored until your application calls `createPolymorfaStore()`.
|
|
165
|
+
- Message text, captions, media URLs, contact names, and raw event payloads
|
|
166
|
+
are written to the device. Anyone with access to the browser profile can
|
|
167
|
+
read them unless you pass `encrypt` and `decrypt`.
|
|
168
|
+
- `encrypt` receives the fields in `SENSITIVE_FIELDS` for each row and
|
|
169
|
+
returns a structured-cloneable value. Indexed fields (IDs, times, unread
|
|
170
|
+
counts, status) stay readable so queries work. Keep the key out of
|
|
171
|
+
persistent storage, for example a non-extractable `CryptoKey` derived per
|
|
172
|
+
sign-in.
|
|
173
|
+
- `redact` removes payload paths before an event is reduced or logged:
|
|
174
|
+
`{ events: "message.*", paths: ["pushName"] }`.
|
|
175
|
+
- The store never keeps credentials. It removes fields named like tokens,
|
|
176
|
+
secrets, passwords, authorization, or API keys, and replaces any value
|
|
177
|
+
starting with `pmfa_` with `[redacted]`.
|
|
178
|
+
- Name each store for one signed-in user and session, and call `clear()` on
|
|
179
|
+
sign-out.
|
|
180
|
+
- Retention limits how long data stays. See [Retention](#retention).
|
|
181
|
+
|
|
182
|
+
## Storage modes
|
|
183
|
+
|
|
184
|
+
`store.mode` is `indexeddb` or `memory`. The store uses memory when IndexedDB
|
|
185
|
+
is missing (server rendering, some private browsing modes) or fails to open,
|
|
186
|
+
and `store.fallbackReason` says why. Memory data lasts for the page and is
|
|
187
|
+
not shared between tabs.
|
|
188
|
+
|
|
189
|
+
`estimateUsage()` returns `navigator.storage.estimate()` results, or
|
|
190
|
+
`undefined` when the browser does not support it. `persist()` asks the
|
|
191
|
+
browser not to evict the origin's storage and returns `false` when that is
|
|
192
|
+
not supported.
|
|
193
|
+
|
|
194
|
+
## Retention
|
|
195
|
+
|
|
196
|
+
Retention runs on open, every `sweepIntervalMs` (one hour by default; `0`
|
|
197
|
+
disables the timer), and on `sweep()`. Rows are ordered by the time of the
|
|
198
|
+
newest event applied to them.
|
|
199
|
+
|
|
200
|
+
| Store | Default |
|
|
201
|
+
| --------------- | ------------------- |
|
|
202
|
+
| `conversations` | 10,000 rows |
|
|
203
|
+
| `messages` | 100,000 rows |
|
|
204
|
+
| `contacts` | 20,000 rows |
|
|
205
|
+
| `presence` | 1 day, 5,000 rows |
|
|
206
|
+
| `calls` | 90 days, 5,000 rows |
|
|
207
|
+
| `labels` | 1,000 rows |
|
|
208
|
+
| `sessions` | 100 rows |
|
|
209
|
+
| `templates` | 2,000 rows |
|
|
210
|
+
| `events` | 7 days, 10,000 rows |
|
|
211
|
+
| `custom` | 30 days, 5,000 rows |
|
|
212
|
+
|
|
213
|
+
Override a store with `{ maxAgeMs, maxRows }`, or pass `false` to keep its
|
|
214
|
+
rows.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
await createPolymorfaStore({
|
|
218
|
+
name: "support:user_1",
|
|
219
|
+
retention: { messages: { maxAgeMs: 30 * 24 * 60 * 60 * 1000 } },
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Event routing
|
|
224
|
+
|
|
225
|
+
| Event types | Store |
|
|
226
|
+
| ------------------------------------------------------------------------------------ | ----------------------------------------------- |
|
|
227
|
+
| `message.received`, `message.update`, `message.edited` | `messages`, conversation summary |
|
|
228
|
+
| `message.sent` | `messages` (outbound, `sent`) |
|
|
229
|
+
| `message.ack` | message status |
|
|
230
|
+
| `message.reaction`, `message.vote` | message reactions and votes |
|
|
231
|
+
| `message.delete`, `message.revoked` | message tombstone |
|
|
232
|
+
| `chat.read`, `chat.archive`, `chat.mute`, `chat.clear`, `chat.delete` | `conversations` |
|
|
233
|
+
| `group.update` | `conversations` (name, description) |
|
|
234
|
+
| `contact.update`, `blocklist.update` | `contacts` |
|
|
235
|
+
| `presence.update` | `presence` |
|
|
236
|
+
| `call.*` | `calls` |
|
|
237
|
+
| `labels.update` | `labels`, conversation or message labels, stars |
|
|
238
|
+
| `session.status`, `session.connected`, `session.logged_out`, `session.phone_offline` | `sessions` |
|
|
239
|
+
| `template.status` | `templates` |
|
|
240
|
+
| Every other type, including unknown ones | `custom` |
|
|
241
|
+
|
|
242
|
+
Every accepted event is also appended to `events`. Payload timestamps in
|
|
243
|
+
seconds or milliseconds are stored as epoch milliseconds. `message.failed`
|
|
244
|
+
has no message ID, so it is kept in `custom`.
|
|
245
|
+
|
|
246
|
+
Add your own handling with `registerReducer()`:
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
store.registerReducer("campaign.completed", async (event, context) => {
|
|
250
|
+
const payload = event.payload as { campaignId: string };
|
|
251
|
+
context.put("custom", {
|
|
252
|
+
id: `campaign:${payload.campaignId}`,
|
|
253
|
+
_t: context.time,
|
|
254
|
+
type: "campaign.summary",
|
|
255
|
+
session: event.session,
|
|
256
|
+
timestamp: event.timestamp,
|
|
257
|
+
payload,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## API reference
|
|
263
|
+
|
|
264
|
+
### `createPolymorfaStore(options): Promise<PolymorfaStore>`
|
|
265
|
+
|
|
266
|
+
| Option | Description |
|
|
267
|
+
| ------------------ | ------------------------------------------------------------------ |
|
|
268
|
+
| `name` | Required. Database name; include the user and session. |
|
|
269
|
+
| `version` | Application data version. A change wipes stored data on open. |
|
|
270
|
+
| `session` | Store only events for this session. |
|
|
271
|
+
| `retention` | Per-store `{ maxAgeMs, maxRows }` or `false`. |
|
|
272
|
+
| `sweepIntervalMs` | Retention interval. Defaults to one hour; `0` sweeps only on open. |
|
|
273
|
+
| `indexedDB` | `IDBFactory` to use. `null` forces memory mode. |
|
|
274
|
+
| `broadcastChannel` | Factory for tab sync. `null` disables it. |
|
|
275
|
+
| `encrypt` | `(fields, { store, id }) => sealed`. Requires `decrypt`. |
|
|
276
|
+
| `decrypt` | `(sealed, { store, id }) => fields`. |
|
|
277
|
+
| `redact` | `{ events, paths }[]` removed before reducing and logging. |
|
|
278
|
+
| `batchSize` | Events per write transaction. Defaults to 250. |
|
|
279
|
+
| `now` | Clock used for retention. |
|
|
280
|
+
| `onError` | Receives background sweep and listener errors. |
|
|
281
|
+
|
|
282
|
+
### `PolymorfaStore`
|
|
283
|
+
|
|
284
|
+
| Member | Description |
|
|
285
|
+
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
286
|
+
| `mode`, `fallbackReason` | Storage mode and the reason for a memory fallback. |
|
|
287
|
+
| `ingest(event \| events)` | Returns `{ accepted, duplicates, ignored }`. |
|
|
288
|
+
| `registerReducer(type, reducer)` | Returns a function that removes the reducer. |
|
|
289
|
+
| `conversations.list({ limit?, unreadOnly? })` | Most recent activity first; deleted conversations are omitted. |
|
|
290
|
+
| `conversations.get(id)` | One conversation. |
|
|
291
|
+
| `messages.list({ conversationId, before?, beforeId?, limit?, includeDeleted? })` | Newest first. `before` is epoch milliseconds, exclusive. With `beforeId`, messages created exactly at `before` whose ID sorts before `beforeId` are included too, so pages with shared timestamps continue. `limit` defaults to 50. |
|
|
292
|
+
| `messages.get(id)` | One message, including tombstones. |
|
|
293
|
+
| `messages.upsert(messages, { session? })` | Writes backend history. Live events for the same message win. |
|
|
294
|
+
| `contacts`, `presence`, `calls`, `labels`, `sessions`, `templates` | `get(id)` and `list({ limit? })`, most recently updated first. |
|
|
295
|
+
| `events.list({ types?, since?, limit? })` | The event log, oldest first. `since` is epoch milliseconds, inclusive. |
|
|
296
|
+
| `custom.list({ types?, since?, limit? })` | Events without a reducer. |
|
|
297
|
+
| `checkpoints.get(id)`, `checkpoints.set(id, cursor)` | Resume positions. |
|
|
298
|
+
| `subscribe(store \| "*", listener)` | Receives `{ store, keys, deleted, cleared?, origin }`. |
|
|
299
|
+
| `sweep()` | Applies retention now. |
|
|
300
|
+
| `clear()` | Removes every row, including checkpoints. |
|
|
301
|
+
| `close()` | Stops the sweep timer and tab sync, then closes the database. |
|
|
302
|
+
| `estimateUsage()`, `persist()` | Storage quota helpers. |
|
|
303
|
+
|
|
304
|
+
### Sources
|
|
305
|
+
|
|
306
|
+
| Export | Description |
|
|
307
|
+
| --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `LiveEventSource` | `{ subscribe(listener, { cursor?, onError? }) => unsubscribe }`. |
|
|
309
|
+
| `connectEventSource(store, source, options?)` | Resumes from checkpoint `options.checkpoint` (default `default`), batches writes (`maxBatch`, default 500), and saves the cursor after each batch. Returns `{ idle(), cursor, close() }`. |
|
|
310
|
+
| `fromEventSource(url \| eventSource, options?)` | Uses `EventSource`. `eventTypes` lists named events to follow (default `DEFAULT_SSE_EVENT_TYPES`, every type a built-in reducer handles); `resumeParam` names the cursor query parameter. |
|
|
311
|
+
| `fromEventStream({ url, headers?, fetch?, credentials?, retryMs?, format?, maxLineLength? })` | Uses `fetch`, sends `Last-Event-ID`, and reconnects. `format: "project"` reads the project event stream frames. |
|
|
312
|
+
| `fromWebSocket(url \| socket, options?)` | JSON text frames; the last event ID is the cursor. |
|
|
313
|
+
| `fromIterable(events, { batchSize? })` | Replays an array or async iterable. `completed` resolves after delivery. |
|
|
314
|
+
| `SseParser`, `eventsFromFrame(frame)`, `readProjectStreamFrame(data)` | The `text/event-stream` parser, the envelope frame decoder, and the project stream frame decoder. |
|
|
315
|
+
|
|
316
|
+
### Chat integration
|
|
317
|
+
|
|
318
|
+
| Export | Description |
|
|
319
|
+
| ---------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
320
|
+
| `createStoreConversationSource(store, options)` | `conversationId`, `send`, and optional `load`, `pageSize`, `session`, `onError`. |
|
|
321
|
+
| `toConversationMessage(row)` | Maps a stored message to `ConversationMessage`. |
|
|
322
|
+
| `usePolymorfaStoreQuery(store, stores, query, key?)` | From `@polymorfa/store/react`. Returns `{ data, error, loading }`. |
|
|
323
|
+
|
|
324
|
+
`PolymorfaEvent` is the webhook envelope: `id`, `session`, `timestamp`,
|
|
325
|
+
`event`, `payload`, and optional `externalId`. The server SDK's
|
|
326
|
+
`WebhookEvent` union is assignable to it.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { StoreName } from "./types.js";
|
|
2
|
+
export type InternalStoreName = StoreName | "meta";
|
|
3
|
+
export type Row = Record<string, unknown> & {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
};
|
|
6
|
+
export type KeyValue = number | string | readonly KeyValue[];
|
|
7
|
+
export interface IndexSpec {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly keyPath: string | readonly string[];
|
|
10
|
+
}
|
|
11
|
+
export declare const SCHEMA_VERSION = 1;
|
|
12
|
+
export declare const SCHEMA: Readonly<Record<InternalStoreName, readonly IndexSpec[]>>;
|
|
13
|
+
export declare const INTERNAL_STORE_NAMES: InternalStoreName[];
|
|
14
|
+
export interface KeyRange {
|
|
15
|
+
readonly lower?: KeyValue;
|
|
16
|
+
readonly upper?: KeyValue;
|
|
17
|
+
readonly lowerOpen?: boolean;
|
|
18
|
+
readonly upperOpen?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface Query {
|
|
21
|
+
readonly index?: string;
|
|
22
|
+
readonly range?: KeyRange;
|
|
23
|
+
readonly direction?: "next" | "prev";
|
|
24
|
+
readonly limit?: number;
|
|
25
|
+
/** Applied before `limit`; sees rows as stored (possibly sealed). */
|
|
26
|
+
readonly filter?: (row: Row) => boolean;
|
|
27
|
+
}
|
|
28
|
+
export type WriteOp = {
|
|
29
|
+
readonly kind: "put";
|
|
30
|
+
readonly store: InternalStoreName;
|
|
31
|
+
readonly row: Row;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "delete";
|
|
34
|
+
readonly store: InternalStoreName;
|
|
35
|
+
readonly key: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: "deleteRange";
|
|
38
|
+
readonly store: InternalStoreName;
|
|
39
|
+
readonly index: string;
|
|
40
|
+
readonly range: KeyRange;
|
|
41
|
+
};
|
|
42
|
+
export interface SweepRule {
|
|
43
|
+
readonly olderThan?: number;
|
|
44
|
+
readonly maxRows?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface GetRequest {
|
|
47
|
+
readonly store: InternalStoreName;
|
|
48
|
+
readonly key: string;
|
|
49
|
+
}
|
|
50
|
+
export interface StoreBackend {
|
|
51
|
+
readonly mode: "indexeddb" | "memory";
|
|
52
|
+
getMany(requests: readonly GetRequest[]): Promise<(Row | undefined)[]>;
|
|
53
|
+
/** Applies all operations in one transaction; returns keys removed by ranges. */
|
|
54
|
+
commit(ops: readonly WriteOp[]): Promise<Map<InternalStoreName, string[]>>;
|
|
55
|
+
query(store: InternalStoreName, query: Query): Promise<Row[]>;
|
|
56
|
+
/** Deletes expired and excess rows by `by_time`; returns deleted keys. */
|
|
57
|
+
sweep(rules: ReadonlyMap<InternalStoreName, SweepRule>): Promise<Map<InternalStoreName, string[]>>;
|
|
58
|
+
clear(): Promise<void>;
|
|
59
|
+
close(): void;
|
|
60
|
+
}
|
|
61
|
+
/** IndexedDB key ordering for the key types this package uses. */
|
|
62
|
+
export declare function compareKeys(a: KeyValue, b: KeyValue): number;
|
|
63
|
+
export declare function isValidKey(value: unknown): value is KeyValue;
|
|
64
|
+
export declare function extractKey(row: Row, keyPath: string | readonly string[]): KeyValue | undefined;
|
|
65
|
+
export declare function inRange(key: KeyValue, range: KeyRange | undefined): boolean;
|
|
66
|
+
/** A bounded in-memory backend used when IndexedDB is unavailable. */
|
|
67
|
+
export declare class MemoryBackend implements StoreBackend {
|
|
68
|
+
#private;
|
|
69
|
+
readonly mode: "memory";
|
|
70
|
+
getMany(requests: readonly GetRequest[]): Promise<(Row | undefined)[]>;
|
|
71
|
+
commit(ops: readonly WriteOp[]): Promise<Map<InternalStoreName, string[]>>;
|
|
72
|
+
query(store: InternalStoreName, query: Query): Promise<Row[]>;
|
|
73
|
+
sweep(rules: ReadonlyMap<InternalStoreName, SweepRule>): Promise<Map<InternalStoreName, string[]>>;
|
|
74
|
+
clear(): Promise<void>;
|
|
75
|
+
close(): void;
|
|
76
|
+
}
|
|
77
|
+
export declare function openIndexedDbBackend(factory: IDBFactory, name: string): Promise<IndexedDbBackend>;
|
|
78
|
+
export declare class IndexedDbBackend implements StoreBackend {
|
|
79
|
+
#private;
|
|
80
|
+
readonly mode: "indexeddb";
|
|
81
|
+
constructor(database: IDBDatabase);
|
|
82
|
+
getMany(requests: readonly GetRequest[]): Promise<(Row | undefined)[]>;
|
|
83
|
+
commit(ops: readonly WriteOp[]): Promise<Map<InternalStoreName, string[]>>;
|
|
84
|
+
query(store: InternalStoreName, query: Query): Promise<Row[]>;
|
|
85
|
+
sweep(rules: ReadonlyMap<InternalStoreName, SweepRule>): Promise<Map<InternalStoreName, string[]>>;
|
|
86
|
+
clear(): Promise<void>;
|
|
87
|
+
close(): void;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|