snapback4 0.0.5 → 0.0.7
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 +25 -11
- package/dist/replica/interpreter.d.ts +8 -1
- package/dist/replica/interpreter.js +17 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -206,7 +206,11 @@ The horizon `last N by index` bounds what a device acquires per group;
|
|
|
206
206
|
older rows are online-only, and a device's read past them says
|
|
207
207
|
`complete: false`. Joining a group delivers its rows; leaving it delivers
|
|
208
208
|
one scope tombstone, and the table's `retain` says what the device keeps.
|
|
209
|
-
|
|
209
|
+
What it keeps under `delivered-history` it can still read locally through
|
|
210
|
+
a query without a membership `require` (the server refuses the same query
|
|
211
|
+
once the membership is gone, so a "history" query answers on the device
|
|
212
|
+
and is empty online). A table without `sync` is online-only: its queries
|
|
213
|
+
run on the server.
|
|
210
214
|
|
|
211
215
|
## 4. The client
|
|
212
216
|
|
|
@@ -227,10 +231,12 @@ durably and predicts its rows, and follows the server's stream for the
|
|
|
227
231
|
rest. Its options: `url`; one of `persona` (development), `session` (from
|
|
228
232
|
`snapback4/auth`, kept and restored by the client), or `guest: true`
|
|
229
233
|
(mint one when none is kept); `store`; `sessionStore`; `fetch`;
|
|
230
|
-
`waitSeconds`. It has `query
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
`waitSeconds`. It has `query(op, args) → Read`, `observe(op, args) →
|
|
235
|
+
{ getSnapshot(), subscribe(listener) → unsubscribe, close() }` (a live
|
|
236
|
+
read; React's `useSyncExternalStore` shape), `mutate(op, args) → Write`,
|
|
237
|
+
`link()`, `onLink`, `viewer()`, `session()`, `signOut()`, `sync()`,
|
|
238
|
+
`replica`, `close()`. The online client takes `url` with `persona`,
|
|
239
|
+
`session` or `token`, and has the same card without a store.
|
|
234
240
|
|
|
235
241
|
### The card
|
|
236
242
|
|
|
@@ -245,10 +251,14 @@ type Write = { state: "pending"; id } | { state: "sent"; id; seq } | { state: "f
|
|
|
245
251
|
type Refusal = { code: string; family: "input" | "schema" | "auth" | "rule" | "constraint" | "bound" | "op" | "link" | "generation"; message: string; site?: string; rewrite?: string; guide?: string; retryable?: boolean };
|
|
246
252
|
```
|
|
247
253
|
|
|
248
|
-
`complete` says
|
|
249
|
-
the
|
|
250
|
-
|
|
251
|
-
|
|
254
|
+
`complete` says this is everything the query can answer: false when the
|
|
255
|
+
meter cut the page, when a scan's read rule is not decided by its prefix
|
|
256
|
+
(§2, the meter), or when a page declared without a cursor had more rows
|
|
257
|
+
than it holds (declare `c: cursor ?` and page with `after c`; `check`
|
|
258
|
+
notes it). `fresh` says the device has everything the server had at the
|
|
259
|
+
last sync (`since` is the last sync's time when it does not); `next` is
|
|
260
|
+
the cursor for the next page (pass it as the query's `cursor ?` argument);
|
|
261
|
+
a page with a cursor and more rows is `complete: true` with `next`. A row this device wrote and the server has not yet
|
|
252
262
|
confirmed carries `pending: true`. A write is `pending` while the link is
|
|
253
263
|
down (it lands when it returns), `sent` with the server's sequence, or
|
|
254
264
|
`failed` with the refusal and its predicted rows withdrawn. `isReady`,
|
|
@@ -302,8 +312,12 @@ flips the link.
|
|
|
302
312
|
- **`snapback4 run <op> '<json args>' --as alice [--json]`** runs one
|
|
303
313
|
operation against the local store and prints the result and the meter.
|
|
304
314
|
- **`snapback4 test [--fixture dir] [--as persona]`** loads a fixture
|
|
305
|
-
(one `<table>.jsonl` per table)
|
|
306
|
-
|
|
315
|
+
(one `<table>.jsonl` per table) into a fresh store in memory (without a
|
|
316
|
+
fixture, the project's dev store), runs every query as each persona with
|
|
317
|
+
arguments derived from the data (a reference is the table's first row,
|
|
318
|
+
a principal the persona, a text the shortest that fits), prints the
|
|
319
|
+
meters beside the ceilings and the arguments it used, and names any
|
|
320
|
+
query it could not run.
|
|
307
321
|
- **`snapback4 check [--cost] [--json]`** compiles, writes
|
|
308
322
|
`snapback/generated/api.ts` (`Rows`, `Ops`, `api`), and prints every
|
|
309
323
|
diagnostic with its site and rewrite; `--cost` adds each site's
|
|
@@ -58,6 +58,8 @@ export declare class Interpreter {
|
|
|
58
58
|
private written;
|
|
59
59
|
private capped;
|
|
60
60
|
private beyondHorizon;
|
|
61
|
+
/** The program's page had more rows and no cursor to continue on. */
|
|
62
|
+
private pageOverflow;
|
|
61
63
|
private page;
|
|
62
64
|
private pageDepth;
|
|
63
65
|
private created;
|
|
@@ -89,7 +91,12 @@ export declare class Interpreter {
|
|
|
89
91
|
* counted. */
|
|
90
92
|
private visibility;
|
|
91
93
|
private ruleTrue;
|
|
92
|
-
/**
|
|
94
|
+
/** Whether the viewer may read a held row. A synced table's rows are
|
|
95
|
+
* the partition: the server delivered each under the read rule and
|
|
96
|
+
* withdrew what it revoked, so what the device holds it may read — and
|
|
97
|
+
* what `retain delivered-history` kept after a leave stays readable here
|
|
98
|
+
* even though the server now refuses it. Only an online-only table's
|
|
99
|
+
* rows (none, unless predicted) meet the rule on the device. */
|
|
93
100
|
readable(table: string, row: Row): Promise<boolean>;
|
|
94
101
|
private rule;
|
|
95
102
|
private permitted;
|
|
@@ -135,6 +135,8 @@ export class Interpreter {
|
|
|
135
135
|
written = 0;
|
|
136
136
|
capped = false;
|
|
137
137
|
beyondHorizon = false;
|
|
138
|
+
/** The program's page had more rows and no cursor to continue on. */
|
|
139
|
+
pageOverflow = false;
|
|
138
140
|
page = null;
|
|
139
141
|
pageDepth = 0;
|
|
140
142
|
created = new Set();
|
|
@@ -176,7 +178,7 @@ export class Interpreter {
|
|
|
176
178
|
}
|
|
177
179
|
const flow = await this.statements(program.body, program.name);
|
|
178
180
|
const data = flow instanceof Flow && flow.kind === "return" ? flow.value : null;
|
|
179
|
-
return { data, complete: !this.capped && !this.beyondHorizon, next: this.capped ? null : this.nextCursor(), tables: this.tables };
|
|
181
|
+
return { data, complete: !this.capped && !this.beyondHorizon && !this.pageOverflow, next: this.capped ? null : this.nextCursor(), tables: this.tables };
|
|
180
182
|
}
|
|
181
183
|
nextCursor() {
|
|
182
184
|
if (!this.page || !this.page.next)
|
|
@@ -595,6 +597,8 @@ export class Interpreter {
|
|
|
595
597
|
if (held >= horizon.last)
|
|
596
598
|
this.beyondHorizon = true;
|
|
597
599
|
}
|
|
600
|
+
if (this.pageDepth === 0 && !scan.opaque_cursor && hasMore)
|
|
601
|
+
this.pageOverflow = true;
|
|
598
602
|
if (this.pageDepth === 0 && scan.opaque_cursor) {
|
|
599
603
|
const columns = definition.indexes[index].components.length;
|
|
600
604
|
this.page = { table, index, order, prefixLen: prefix.length, next: hasMore && lastKey && visible.length ? suffixOf(this.schema, table, index, visible[visible.length - 1], prefix.length).slice(0, columns - prefix.length) : null };
|
|
@@ -634,6 +638,8 @@ export class Interpreter {
|
|
|
634
638
|
candidates.sort((a, b) => { const c = totalCompare(a.key, b.key); return order === "Asc" ? c : -c; });
|
|
635
639
|
const hasMore = candidates.length > take;
|
|
636
640
|
const page = candidates.slice(0, take);
|
|
641
|
+
if (this.pageDepth === 0 && !first.opaque_cursor && hasMore)
|
|
642
|
+
this.pageOverflow = true;
|
|
637
643
|
if (this.pageDepth === 0 && first.opaque_cursor)
|
|
638
644
|
this.page = { table, index, order, prefixLen, next: hasMore && page.length ? page[page.length - 1].key : null };
|
|
639
645
|
return page.map((c) => c.row);
|
|
@@ -697,9 +703,17 @@ export class Interpreter {
|
|
|
697
703
|
default: return false;
|
|
698
704
|
}
|
|
699
705
|
}
|
|
700
|
-
/**
|
|
706
|
+
/** Whether the viewer may read a held row. A synced table's rows are
|
|
707
|
+
* the partition: the server delivered each under the read rule and
|
|
708
|
+
* withdrew what it revoked, so what the device holds it may read — and
|
|
709
|
+
* what `retain delivered-history` kept after a leave stays readable here
|
|
710
|
+
* even though the server now refuses it. Only an online-only table's
|
|
711
|
+
* rows (none, unless predicted) meet the rule on the device. */
|
|
701
712
|
async readable(table, row) {
|
|
702
|
-
const
|
|
713
|
+
const definition = this.schema.tables[table];
|
|
714
|
+
if (definition?.sync)
|
|
715
|
+
return true;
|
|
716
|
+
const rule = definition.rules.read;
|
|
703
717
|
return this.rule(rule, undefined, row, false);
|
|
704
718
|
}
|
|
705
719
|
async rule(rule, old, next, insert) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "snapback4",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Snapback 4: the card (contract), the online and local-first clients (IndexedDB, SQLite), React hooks, sign-in, a labelled mock, and the `snapback4` CLI. LLP 3000.",
|
|
6
6
|
"bin": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"README.md"
|
|
43
43
|
],
|
|
44
44
|
"optionalDependencies": {
|
|
45
|
-
"snapback4-darwin-arm64": "0.0.
|
|
45
|
+
"snapback4-darwin-arm64": "0.0.7"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -p tsconfig.build.json",
|