@surrealdb/memory 1.0.0-alpha.10 → 1.0.0-alpha.11
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/README.md +128 -4
- package/dist/memory.cjs +411 -14
- package/dist/memory.d.ts +2015 -244
- package/dist/memory.mjs +408 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,9 @@ await client.context("Summarise preferences", { k: 5 });
|
|
|
64
64
|
await client.reflect("What changed this week?", { persist: true });
|
|
65
65
|
await client.forget("Remove old project notes", { purge: true });
|
|
66
66
|
|
|
67
|
+
// What the context knows about a subject, in one round trip.
|
|
68
|
+
await client.lookup("Who is Tobie?");
|
|
69
|
+
|
|
67
70
|
// Snapshots and maintenance.
|
|
68
71
|
await client.state({ limit: 500 }); // bounded per table; check `truncated`
|
|
69
72
|
await client.profile();
|
|
@@ -90,12 +93,118 @@ for await (const chunk of stream) {
|
|
|
90
93
|
}
|
|
91
94
|
```
|
|
92
95
|
|
|
96
|
+
## Known-about lookups
|
|
97
|
+
|
|
98
|
+
`client.lookup(query)` answers what the context knows about a subject in one
|
|
99
|
+
round trip. Everything it returns is a stored row: nothing is generated, nothing
|
|
100
|
+
is summarised by a model, and an identical query returns an identical answer.
|
|
101
|
+
|
|
102
|
+
Branch on `resolution.kind` rather than inferring the case from an array length
|
|
103
|
+
— "one candidate" and "confidently one entity" are different answers:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const answer = await client.lookup("Atlas");
|
|
107
|
+
|
|
108
|
+
switch (answer.resolution.kind) {
|
|
109
|
+
case "entity":
|
|
110
|
+
render(answer.resolution.subject, answer.facts.items);
|
|
111
|
+
break;
|
|
112
|
+
case "ambiguous":
|
|
113
|
+
// Each candidate carries a `distinguisher` so they can be told apart.
|
|
114
|
+
offerChoice(answer.resolution.candidates);
|
|
115
|
+
break;
|
|
116
|
+
case "topic":
|
|
117
|
+
// No single subject: the answer is the cluster in `entities` / `passages`.
|
|
118
|
+
renderCluster(answer.entities.items, answer.passages.items);
|
|
119
|
+
break;
|
|
120
|
+
case "empty":
|
|
121
|
+
// `nearest` separates "not stored" from "stored under another name".
|
|
122
|
+
suggest(answer.resolution.nearest);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Every section is bounded and reports `truncated`; none of them page. A section
|
|
128
|
+
that was cut points at its own collection endpoint, which does walk:
|
|
129
|
+
|
|
130
|
+
| Section | Walk it with |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `facts` | `client.facts.attributes({ entity })` |
|
|
133
|
+
| `relations` | `client.facts.allEdgesOf(entity)` — the section carries edges in either direction, so one of `src` / `dst` alone reproduces half of it |
|
|
134
|
+
| `events` | `client.facts.actions({ actor: entity })` |
|
|
135
|
+
| `passages` | `client.recall(...)` |
|
|
136
|
+
| `uncertainty` | `client.uncertainty.list({ entity })` |
|
|
137
|
+
|
|
138
|
+
`facts` is ranked by importance while its collection pages in write order, so
|
|
139
|
+
the ranked head is a different question from the walk, not its first page.
|
|
140
|
+
|
|
141
|
+
Following a trail is cheap: pass `subject` to skip resolution when a hop already
|
|
142
|
+
knows which entity it landed on.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
await client.lookup("Atlas", { subject: "product/atlas", include: ["facts", "relations"] });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Finding a subject
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// Name search: lexical, deterministic, best match first. A ranked head, not a walk.
|
|
152
|
+
await client.entities.search("tobie", { type: "person", limit: 5 });
|
|
153
|
+
|
|
154
|
+
// Where to start when there is no query yet.
|
|
155
|
+
await client.entities.top({ by: "coverage" }); // also `importance`, `recency`
|
|
156
|
+
|
|
157
|
+
// One hop out, each neighbour carrying its own fact count.
|
|
158
|
+
await client.entities.neighbours("person", "tobie", { minFacts: 2 });
|
|
159
|
+
|
|
160
|
+
// What changed about a subject: every key's supersession chain, newest first.
|
|
161
|
+
await client.entities.changes("person", "tobie", { limit: 50 });
|
|
162
|
+
|
|
163
|
+
// The collections a bounded head points at, all cursor-paged in the same order.
|
|
164
|
+
await client.facts.attributes({ entity: "person/tobie" });
|
|
165
|
+
await client.facts.allEdgesOf("person/tobie"); // both directions
|
|
166
|
+
await client.facts.actions({ actor: "person/tobie", since: "2026-01-01T00:00:00Z" });
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`entities.get` returns a **bounded head** of the entity's attributes and
|
|
170
|
+
relations, newest first, and reports `truncated` per section:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const { entity, attributes, relations, truncated } = await client.entities.get("person", "tobie");
|
|
174
|
+
if (truncated.relations) {
|
|
175
|
+
// The head is a genuine prefix of the walk, so follow the collection for the rest.
|
|
176
|
+
const every = await client.facts.allEdgesOf("person/tobie");
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Settling a contradiction
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const open = await client.uncertainty.list({ resolved: false });
|
|
184
|
+
|
|
185
|
+
for (const flag of open.unknowns) {
|
|
186
|
+
// `resolvable` says whether settling would succeed for *this* key: it is false
|
|
187
|
+
// for a subject-less flag, an already-settled one, and one scoped beyond the
|
|
188
|
+
// caller's write region.
|
|
189
|
+
if (flag.resolvable) {
|
|
190
|
+
await client.uncertainty.resolve(flag.id, "SurrealDB", { note: "confirmed in the offer letter" });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
One call claims the flag, writes the accepted value through the reconciler, and
|
|
196
|
+
retires the values it beats. Settlement converges on retry rather than being
|
|
197
|
+
transactional: repeating a failed call dedups the value and finishes the
|
|
198
|
+
retirement.
|
|
199
|
+
|
|
93
200
|
## Namespaces
|
|
94
201
|
|
|
95
202
|
| Namespace | Highlights |
|
|
96
203
|
| --- | --- |
|
|
97
204
|
| `client.documents` | `upload`, `reprocess`, `get`, `raw`, `chunks`, `allChunks`, `list`, `listAll`, `count`, `delete`, `query`, `recomputeLinks`, `keywords.*` |
|
|
98
|
-
| `client.entities` | `list`, `listAll`, `count`, `get`, `history`, `delete` |
|
|
205
|
+
| `client.entities` | `list`, `listAll`, `count`, `search`, `top`, `get`, `neighbours`, `allNeighbours`, `changes`, `allChanges`, `history`, `delete` |
|
|
206
|
+
| `client.facts` | `attributes`, `allAttributes`, `relations`, `allRelations`, `allEdgesOf`, `actions`, `allActions` |
|
|
207
|
+
| `client.uncertainty` | `list`, `listAll`, `count`, `resolve` |
|
|
99
208
|
| `client.sessions` | `create` → `Session` (`turns`, `allTurns`, `context`, `close`) |
|
|
100
209
|
| `client.lifecycle` | `expire`, `decay` |
|
|
101
210
|
| `client.traces` | `list`, `listAll`, `get`, `stats` |
|
|
@@ -137,14 +246,27 @@ await client.documents.count(); // number, without fetching the documents
|
|
|
137
246
|
```
|
|
138
247
|
|
|
139
248
|
`listAll` is an unbounded read by construction — reach for it when the
|
|
140
|
-
collection is a tree or a filter source, not a screenful. `documents.allChunks
|
|
141
|
-
|
|
249
|
+
collection is a tree or a filter source, not a screenful. `documents.allChunks`,
|
|
250
|
+
`entities.allNeighbours` and `entities.allChanges` take a `max` for the bounded
|
|
251
|
+
case.
|
|
252
|
+
|
|
253
|
+
Not every bounded read is a page. `entities.search`, `entities.top`, the
|
|
254
|
+
sections of `client.lookup`, and the fact sections of `entities.get` are ranked
|
|
255
|
+
or aggregate heads: they carry no cursor, and a cut one is signalled by
|
|
256
|
+
`truncated` rather than continued. Follow the collection endpoint each one names
|
|
257
|
+
instead of re-requesting it with a bigger limit.
|
|
142
258
|
|
|
143
259
|
`totalSize` is opt-in (`count: true`) because it costs a full count of the
|
|
144
260
|
filtered set. Two endpoints do not offer it at all — `scopes.list` and
|
|
145
261
|
`client.audit` take `limit` and `cursor` only, typed as `CursorOptions`, so
|
|
146
262
|
asking them for a count is a type error rather than a rejected request.
|
|
147
263
|
|
|
264
|
+
`entities.neighbours` offers `count`, but not beside `minFacts`: the filter runs
|
|
265
|
+
after each page is hydrated, so honouring it in a total would cost the
|
|
266
|
+
per-neighbour counts the walk exists to avoid. The server rejects that pairing
|
|
267
|
+
with a `400`, and `NeighbourhoodOptions` is an exclusive union, so it fails to
|
|
268
|
+
compile instead.
|
|
269
|
+
|
|
148
270
|
`/documents`, `/documents/{id}/chunks`, and `/documents/keywords` also still
|
|
149
271
|
accept the pre-cursor `page`/`pageSize` parameters, for callers with numbered
|
|
150
272
|
page controls that need a page index and a total. The server rejects `cursor`
|
|
@@ -157,7 +279,9 @@ and `CursorOptions` types.
|
|
|
157
279
|
|
|
158
280
|
## Delegation
|
|
159
281
|
|
|
160
|
-
`client.onBehalfOf(principalId)` returns a new client whose every request carries the
|
|
282
|
+
`client.onBehalfOf(principalId)` returns a new client whose every request carries the delegation header, so calls run with that principal's authorisation. This requires the `manage` grant. The original client is left unchanged.
|
|
283
|
+
|
|
284
|
+
The header is spelled `X-Spectron-On-Behalf-Of`. That is a wire constant rather than branding: the service matches the name exactly and its CORS allowlist carries only that spelling, so it stays until the service renames it.
|
|
161
285
|
|
|
162
286
|
```ts
|
|
163
287
|
const asAlex = client.onBehalfOf("principal:alex");
|