create-syncular-app 0.15.47 → 0.16.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/README.md +1 -1
- package/package.json +1 -1
- package/template/minimal/migrations/0001_initial/up.sql +4 -2
- package/template/minimal/src/clients.ts +15 -14
- package/template/minimal/src/smoke.test.ts +13 -7
- package/template/minimal/src/syncular.generated.ts +27 -19
- package/template/minimal/syncular.ir.json +14 -4
- package/template/minimal/syncular.json +3 -3
- package/template/minimal/syncular.migrations.lock.json +43 -31
- package/template/tauri/src/syncular.queries.ts +11 -4
- package/template/tauri/src-tauri/Cargo.toml +1 -1
- package/template/tauri/syncular.queries.ir.json +15 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ bunx create-syncular-app my-app --template web
|
|
|
13
13
|
|---|---|
|
|
14
14
|
| `minimal` | Server + a terminal two-client convergence demo (no browser) — migrations + manifest + `generate` wiring. Copy-evolved from `examples/quickstart`. The smallest honest starting point. |
|
|
15
15
|
| `web` | Hono server + WebSocket realtime + a single-pane browser todo app whose whole client core runs in a Web Worker on OPFS. Derived from `apps/demo`, slimmed to one pane (no conflict simulator, no blob attachments) — the minimal browser app a real user starts from. |
|
|
16
|
-
| `tauri` | One React codebase, web + desktop: the `web` template's server plus a shared React tree behind the `__TAURI_INTERNALS__` engine seam (`src/frontend/engine.ts`) — worker core on OPFS in the browser, native Rust core in a `src-tauri/` host (`tauri-plugin-syncular` from crates.io, `native-transport`). Derived from `bindings/tauri/example` + the [
|
|
16
|
+
| `tauri` | One React codebase, web + desktop: the `web` template's server plus a shared React tree behind the `__TAURI_INTERNALS__` engine seam (`src/frontend/engine.ts`) — worker core on OPFS in the browser, native Rust core in a `src-tauri/` host (`tauri-plugin-syncular` from crates.io, `native-transport`). Derived from `bindings/tauri/example` + the [Tauri guide](../../apps/docs/src/content/platform-tauri.md). |
|
|
17
17
|
|
|
18
18
|
Each template ships its own `README.md` (run steps, what to edit first),
|
|
19
19
|
`.gitignore` (as `gitignore` — see below), a working `tsconfig.json`, and a
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
-- Your schema, one table to start. typegen reads this to derive the schema
|
|
2
2
|
-- SHAPE (column types, primary key); the server manages its own sync_* tables
|
|
3
3
|
-- and never runs this migration.
|
|
4
|
-
CREATE TABLE
|
|
4
|
+
CREATE TABLE todos (
|
|
5
5
|
id TEXT PRIMARY KEY,
|
|
6
6
|
list_id TEXT NOT NULL,
|
|
7
|
-
|
|
7
|
+
title TEXT NOT NULL,
|
|
8
|
+
done BOOLEAN NOT NULL,
|
|
9
|
+
position INTEGER NOT NULL,
|
|
8
10
|
updated_at_ms INTEGER NOT NULL
|
|
9
11
|
);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Two clients, one server, terminal-visible convergence.
|
|
3
3
|
*
|
|
4
|
-
* A writes a
|
|
5
|
-
* database
|
|
4
|
+
* A writes a todo; B (a completely independent client core with its own local
|
|
5
|
+
* database) bootstraps, syncs, and reads the same row back. This is the proof
|
|
6
6
|
* that sync works end to end, no browser required.
|
|
7
7
|
*
|
|
8
8
|
* Run the server first (`bun run server`), then this script (`bun run
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { makeClient } from './make-client';
|
|
12
12
|
|
|
13
13
|
const BASE_URL = process.env.SERVER_URL ?? 'http://localhost:8787';
|
|
14
|
-
const LIST_ID = '
|
|
14
|
+
const LIST_ID = 'groceries';
|
|
15
15
|
|
|
16
16
|
const a = makeClient(BASE_URL, 'client-a');
|
|
17
17
|
const b = makeClient(BASE_URL, 'client-b');
|
|
@@ -19,34 +19,35 @@ await a.start();
|
|
|
19
19
|
await b.start();
|
|
20
20
|
|
|
21
21
|
// Both clients subscribe to the same list (the requested scope).
|
|
22
|
-
const sub = { id: '
|
|
22
|
+
const sub = { id: 'todos', table: 'todos', scopes: { list_id: [LIST_ID] } };
|
|
23
23
|
a.subscribe(sub);
|
|
24
24
|
b.subscribe(sub);
|
|
25
25
|
|
|
26
|
-
// A writes a
|
|
27
|
-
const now = Date.now();
|
|
26
|
+
// A writes a todo. mutate() records it locally + queues it for the next push.
|
|
28
27
|
a.mutate([
|
|
29
28
|
{
|
|
30
|
-
table: '
|
|
29
|
+
table: 'todos',
|
|
31
30
|
op: 'upsert',
|
|
32
31
|
values: {
|
|
33
|
-
id: '
|
|
32
|
+
id: 'todo-1',
|
|
34
33
|
list_id: LIST_ID,
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
title: 'Buy milk',
|
|
35
|
+
done: false,
|
|
36
|
+
position: 1,
|
|
37
|
+
updated_at_ms: Date.now(),
|
|
37
38
|
},
|
|
38
39
|
},
|
|
39
40
|
]);
|
|
40
|
-
console.log('A: wrote
|
|
41
|
+
console.log('A: wrote todo-1, pushing…');
|
|
41
42
|
await a.syncUntilIdle(); // push A's outbox to the server
|
|
42
43
|
|
|
43
44
|
console.log('B: syncing…');
|
|
44
|
-
await b.syncUntilIdle(); // B bootstraps the list and applies A's
|
|
45
|
+
await b.syncUntilIdle(); // B bootstraps the list and applies A's todo
|
|
45
46
|
|
|
46
|
-
const rows = b.query('SELECT id,
|
|
47
|
+
const rows = b.query('SELECT id, title FROM todos ORDER BY id');
|
|
47
48
|
console.log('B sees:', rows);
|
|
48
49
|
|
|
49
|
-
const converged = rows.length === 1 && rows[0]?.
|
|
50
|
+
const converged = rows.length === 1 && rows[0]?.title === 'Buy milk';
|
|
50
51
|
console.log(converged ? '\n✓ converged' : '\n✗ did NOT converge');
|
|
51
52
|
|
|
52
53
|
await a.close();
|
|
@@ -43,18 +43,24 @@ test('two clients converge through the server', async () => {
|
|
|
43
43
|
await a.start();
|
|
44
44
|
await b.start();
|
|
45
45
|
|
|
46
|
-
const sub = {
|
|
46
|
+
const sub = {
|
|
47
|
+
id: 'todos',
|
|
48
|
+
table: 'todos',
|
|
49
|
+
scopes: { list_id: ['groceries'] },
|
|
50
|
+
};
|
|
47
51
|
a.subscribe(sub);
|
|
48
52
|
b.subscribe(sub);
|
|
49
53
|
|
|
50
54
|
a.mutate([
|
|
51
55
|
{
|
|
52
|
-
table: '
|
|
56
|
+
table: 'todos',
|
|
53
57
|
op: 'upsert',
|
|
54
58
|
values: {
|
|
55
|
-
id: '
|
|
56
|
-
list_id: '
|
|
57
|
-
|
|
59
|
+
id: 'todo-1',
|
|
60
|
+
list_id: 'groceries',
|
|
61
|
+
title: 'Buy milk',
|
|
62
|
+
done: false,
|
|
63
|
+
position: 1,
|
|
58
64
|
updated_at_ms: Date.now(),
|
|
59
65
|
},
|
|
60
66
|
},
|
|
@@ -62,8 +68,8 @@ test('two clients converge through the server', async () => {
|
|
|
62
68
|
await a.syncUntilIdle();
|
|
63
69
|
await b.syncUntilIdle();
|
|
64
70
|
|
|
65
|
-
const rows = b.query('SELECT id,
|
|
66
|
-
expect(rows).toEqual([{ id: '
|
|
71
|
+
const rows = b.query('SELECT id, title FROM todos ORDER BY id');
|
|
72
|
+
expect(rows).toEqual([{ id: 'todo-1', title: 'Buy milk' }]);
|
|
67
73
|
|
|
68
74
|
await a.close();
|
|
69
75
|
await b.close();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by @syncular/typegen — DO NOT EDIT.
|
|
2
2
|
// irVersion: 1
|
|
3
|
-
// irHash: sha256:
|
|
3
|
+
// irHash: sha256:87f3b0e11fc0434b220472a00110771ac69dc0d56068d8ce69d0c1eb2930894e
|
|
4
4
|
|
|
5
5
|
/** Structural descriptor consumed by renderer bindings; phantom type
|
|
6
6
|
* fields make row/insert/update/id inference available without imports. */
|
|
@@ -21,11 +21,13 @@ export const schema = {
|
|
|
21
21
|
version: 1,
|
|
22
22
|
tables: [
|
|
23
23
|
{
|
|
24
|
-
name: '
|
|
24
|
+
name: 'todos',
|
|
25
25
|
columns: [
|
|
26
26
|
{ name: 'id', type: 'string', nullable: false },
|
|
27
27
|
{ name: 'list_id', type: 'string', nullable: false },
|
|
28
|
-
{ name: '
|
|
28
|
+
{ name: 'title', type: 'string', nullable: false },
|
|
29
|
+
{ name: 'done', type: 'boolean', nullable: false },
|
|
30
|
+
{ name: 'position', type: 'integer', nullable: false },
|
|
29
31
|
{ name: 'updated_at_ms', type: 'integer', nullable: false },
|
|
30
32
|
],
|
|
31
33
|
primaryKey: 'id',
|
|
@@ -36,46 +38,52 @@ export const schema = {
|
|
|
36
38
|
],
|
|
37
39
|
} as const;
|
|
38
40
|
|
|
39
|
-
/** One
|
|
40
|
-
export interface
|
|
41
|
+
/** One todos row (§2.4 column order). */
|
|
42
|
+
export interface TodosRow {
|
|
41
43
|
id: string;
|
|
42
44
|
listId: string;
|
|
43
|
-
|
|
45
|
+
title: string;
|
|
46
|
+
done: boolean;
|
|
47
|
+
position: number;
|
|
44
48
|
updatedAtMs: number;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
/** Insert shape: nullable columns may be omitted. */
|
|
48
|
-
export interface
|
|
52
|
+
export interface TodosInsert {
|
|
49
53
|
id: string;
|
|
50
54
|
listId: string;
|
|
51
|
-
|
|
55
|
+
title: string;
|
|
56
|
+
done: boolean;
|
|
57
|
+
position: number;
|
|
52
58
|
updatedAtMs: number;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
61
|
/** Update shape: primary key required, all other columns optional. */
|
|
56
|
-
export interface
|
|
62
|
+
export interface TodosUpdate {
|
|
57
63
|
id: string;
|
|
58
64
|
listId?: string;
|
|
59
|
-
|
|
65
|
+
title?: string;
|
|
66
|
+
done?: boolean;
|
|
67
|
+
position?: number;
|
|
60
68
|
updatedAtMs?: number;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
|
-
/** Typed mutation/resource descriptor for '
|
|
64
|
-
export const
|
|
65
|
-
name: '
|
|
71
|
+
/** Typed mutation/resource descriptor for 'todos'. */
|
|
72
|
+
export const todosTable: SyncTable<TodosRow, TodosInsert, TodosUpdate, string> = {
|
|
73
|
+
name: 'todos',
|
|
66
74
|
primaryKey: 'id',
|
|
67
75
|
physicalPrimaryKey: 'id',
|
|
68
76
|
};
|
|
69
77
|
|
|
70
|
-
export interface
|
|
78
|
+
export interface TodosInListParams {
|
|
71
79
|
listId: string;
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
/** Requested-scope template for the '
|
|
75
|
-
export const
|
|
76
|
-
name: '
|
|
77
|
-
table: '
|
|
78
|
-
scopes: (params:
|
|
82
|
+
/** Requested-scope template for the 'todosInList' subscription. */
|
|
83
|
+
export const todosInListSubscription = {
|
|
84
|
+
name: 'todosInList',
|
|
85
|
+
table: 'todos',
|
|
86
|
+
scopes: (params: TodosInListParams): Record<string, string[]> => ({
|
|
79
87
|
list_id: [params.listId],
|
|
80
88
|
}),
|
|
81
89
|
} as const;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"tables": [
|
|
13
13
|
{
|
|
14
|
-
"name": "
|
|
14
|
+
"name": "todos",
|
|
15
15
|
"primaryKey": "id",
|
|
16
16
|
"columns": [
|
|
17
17
|
{
|
|
@@ -25,10 +25,20 @@
|
|
|
25
25
|
"nullable": false
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
"name": "
|
|
28
|
+
"name": "title",
|
|
29
29
|
"type": "string",
|
|
30
30
|
"nullable": false
|
|
31
31
|
},
|
|
32
|
+
{
|
|
33
|
+
"name": "done",
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"nullable": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "position",
|
|
39
|
+
"type": "integer",
|
|
40
|
+
"nullable": false
|
|
41
|
+
},
|
|
32
42
|
{
|
|
33
43
|
"name": "updated_at_ms",
|
|
34
44
|
"type": "integer",
|
|
@@ -47,8 +57,8 @@
|
|
|
47
57
|
],
|
|
48
58
|
"subscriptions": [
|
|
49
59
|
{
|
|
50
|
-
"name": "
|
|
51
|
-
"table": "
|
|
60
|
+
"name": "todosInList",
|
|
61
|
+
"table": "todos",
|
|
52
62
|
"scopes": [
|
|
53
63
|
{
|
|
54
64
|
"variable": "list_id",
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
"module": "./src/syncular.generated.ts"
|
|
7
7
|
},
|
|
8
8
|
"schemaVersions": [{ "version": 1, "through": "0001_initial" }],
|
|
9
|
-
"tables": [{ "name": "
|
|
9
|
+
"tables": [{ "name": "todos", "scopes": ["list:{list_id}"] }],
|
|
10
10
|
"subscriptions": [
|
|
11
11
|
{
|
|
12
|
-
"name": "
|
|
13
|
-
"table": "
|
|
12
|
+
"name": "todosInList",
|
|
13
|
+
"table": "todos",
|
|
14
14
|
"scopes": { "list_id": ["{listId}"] }
|
|
15
15
|
}
|
|
16
16
|
]
|
|
@@ -1,37 +1,49 @@
|
|
|
1
1
|
{
|
|
2
|
-
"formatVersion":
|
|
2
|
+
"formatVersion": 2,
|
|
3
3
|
"migrations": [
|
|
4
4
|
{
|
|
5
5
|
"name": "0001_initial",
|
|
6
|
-
"sha256": "
|
|
7
|
-
"tables": [
|
|
8
|
-
{
|
|
9
|
-
"name": "notes",
|
|
10
|
-
"primaryKey": "id",
|
|
11
|
-
"columns": [
|
|
12
|
-
{
|
|
13
|
-
"name": "id",
|
|
14
|
-
"type": "string",
|
|
15
|
-
"nullable": false
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"name": "list_id",
|
|
19
|
-
"type": "string",
|
|
20
|
-
"nullable": false
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "body",
|
|
24
|
-
"type": "string",
|
|
25
|
-
"nullable": false
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"name": "updated_at_ms",
|
|
29
|
-
"type": "integer",
|
|
30
|
-
"nullable": false
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
}
|
|
34
|
-
]
|
|
6
|
+
"sha256": "87b23cd0f8e3677ca6fa7ca67eb7c8373a285607b6de10854cc43df1290bb0fa"
|
|
35
7
|
}
|
|
36
|
-
]
|
|
8
|
+
],
|
|
9
|
+
"head": {
|
|
10
|
+
"tables": [
|
|
11
|
+
{
|
|
12
|
+
"name": "todos",
|
|
13
|
+
"primaryKey": "id",
|
|
14
|
+
"columns": [
|
|
15
|
+
{
|
|
16
|
+
"name": "id",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"nullable": false
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "list_id",
|
|
22
|
+
"type": "string",
|
|
23
|
+
"nullable": false
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "title",
|
|
27
|
+
"type": "string",
|
|
28
|
+
"nullable": false
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "done",
|
|
32
|
+
"type": "boolean",
|
|
33
|
+
"nullable": false
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "position",
|
|
37
|
+
"type": "integer",
|
|
38
|
+
"nullable": false
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"name": "updated_at_ms",
|
|
42
|
+
"type": "integer",
|
|
43
|
+
"nullable": false
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
37
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by @syncular/typegen — DO NOT EDIT.
|
|
2
2
|
// irVersion: 1
|
|
3
|
-
// irHash: sha256:
|
|
3
|
+
// irHash: sha256:bc1a58e1aae968ebc7be7981d2bcc5f01f176719afab10e6b5b02f4bf1d2ec86
|
|
4
4
|
|
|
5
5
|
export type SyqlRuntimeErrorCode =
|
|
6
6
|
| 'SYQL_RUNTIME_MISSING_REQUIRED_INPUT'
|
|
@@ -65,6 +65,11 @@ export interface QueryClient {
|
|
|
65
65
|
* `@syncular/react`'s `useQuery`. `Row` is the projection row
|
|
66
66
|
* type; `Params` is `undefined` for a param-less query. `sqlFor`
|
|
67
67
|
* selects a checked revision-1 SYQL physical statement when needed. */
|
|
68
|
+
export interface QueryRelationPlan {
|
|
69
|
+
readonly sql: string;
|
|
70
|
+
readonly relations: readonly { readonly table: string; readonly start: number; readonly end: number; readonly alias?: string }[];
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
export interface NamedQuery<Row, Params = undefined> {
|
|
69
74
|
readonly id: string;
|
|
70
75
|
readonly hasParams: boolean;
|
|
@@ -72,6 +77,7 @@ export interface NamedQuery<Row, Params = undefined> {
|
|
|
72
77
|
readonly mapRow: (row: Readonly<Record<string, unknown>>) => Row;
|
|
73
78
|
readonly tables: readonly string[];
|
|
74
79
|
readonly resultColumns: readonly QueryResultColumn[];
|
|
80
|
+
readonly relationPlans: readonly QueryRelationPlan[];
|
|
75
81
|
readonly bind: (params: Params) => readonly QueryValue[];
|
|
76
82
|
readonly sqlFor?: (params: Params) => string;
|
|
77
83
|
readonly dependencies: (params: Params) => readonly QueryDependency[];
|
|
@@ -139,8 +145,8 @@ function listTodosValidate(raw?: ListTodosParams): ListTodosParams {
|
|
|
139
145
|
/** Tables 'listTodos' reads (compatibility/export surface). */
|
|
140
146
|
export const listTodosTables = ['todos'] as const;
|
|
141
147
|
|
|
142
|
-
const listTodosStatements:
|
|
143
|
-
{ sql: 'select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos where todos.list_id = ? order by position, id', bind: (params) => [params.listId] },
|
|
148
|
+
const listTodosStatements: (QueryRelationPlan & { bind: (params: ListTodosParams) => QueryValue[] })[] = [
|
|
149
|
+
{ sql: 'select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos where todos.list_id = ? order by position, id', relations: [{"table":"todos","start":87,"end":92}], bind: (params) => [params.listId] },
|
|
144
150
|
];
|
|
145
151
|
function listTodosSelect(raw: ListTodosParams): { sql: string; bind: QueryValue[] } {
|
|
146
152
|
const params = listTodosValidate(raw);
|
|
@@ -159,12 +165,13 @@ export async function listTodos(client: QueryClient, params: ListTodosParams): P
|
|
|
159
165
|
|
|
160
166
|
/** Revisioned reactive descriptor for `useQuery(listTodosQuery, params)`. */
|
|
161
167
|
export const listTodosQuery: NamedQuery<ListTodosRow, ListTodosParams> = {
|
|
162
|
-
id: 'sha256:
|
|
168
|
+
id: 'sha256:bc1a58e1aae968ebc7be7981d2bcc5f01f176719afab10e6b5b02f4bf1d2ec86/listTodos',
|
|
163
169
|
hasParams: true,
|
|
164
170
|
sql: 'select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos where todos.list_id = ? order by position, id',
|
|
165
171
|
mapRow: listTodosMapRow,
|
|
166
172
|
sqlFor: (params: ListTodosParams) => listTodosSelect(params).sql,
|
|
167
173
|
tables: listTodosTables,
|
|
174
|
+
relationPlans: listTodosStatements,
|
|
168
175
|
resultColumns: [{ name: 'id', type: 'string', nullable: false }, { name: 'listId', type: 'string', nullable: false }, { name: 'title', type: 'string', nullable: false }, { name: 'done', type: 'boolean', nullable: false }, { name: 'position', type: 'integer', nullable: false }, { name: 'updatedAtMs', type: 'integer', nullable: false }],
|
|
169
176
|
dependencies: (params) => [
|
|
170
177
|
{ table: 'todos', scopeKeys: ['list:' + String(params.listId) + ''] },
|
|
@@ -21,7 +21,7 @@ tauri = { version = "2", features = [] }
|
|
|
21
21
|
# The syncular plugin from crates.io. `native-transport` gives the core its
|
|
22
22
|
# real HTTP + WebSocket transport (ureq + tungstenite) inside the host
|
|
23
23
|
# process — the webview never talks to the sync server directly.
|
|
24
|
-
tauri-plugin-syncular = { version = "0.
|
|
24
|
+
tauri-plugin-syncular = { version = "0.16.1", features = ["native-transport"] }
|
|
25
25
|
|
|
26
26
|
[features]
|
|
27
27
|
default = []
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"queryIrVersion":
|
|
2
|
+
"queryIrVersion": 4,
|
|
3
3
|
"queries": [
|
|
4
4
|
{
|
|
5
5
|
"name": "listTodos",
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
"sourceSql": "select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos\n where todos.list_id = :listId\n order by position, id",
|
|
8
8
|
"sql": "select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos\n where todos.list_id = :listId\n order by position, id",
|
|
9
9
|
"positionalSql": "select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos where todos.list_id = ? order by position, id",
|
|
10
|
+
"relations": [
|
|
11
|
+
{
|
|
12
|
+
"table": "todos",
|
|
13
|
+
"start": 87,
|
|
14
|
+
"end": 92
|
|
15
|
+
}
|
|
16
|
+
],
|
|
10
17
|
"params": [
|
|
11
18
|
{
|
|
12
19
|
"name": "listId",
|
|
@@ -126,6 +133,13 @@
|
|
|
126
133
|
"activationMask": 0,
|
|
127
134
|
"sql": "select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos\n where todos.list_id = :listId\n order by position, id",
|
|
128
135
|
"positionalSql": "select id, list_id AS listId, title, done, position, updated_at_ms AS updatedAtMs from todos where todos.list_id = ? order by position, id",
|
|
136
|
+
"relations": [
|
|
137
|
+
{
|
|
138
|
+
"table": "todos",
|
|
139
|
+
"start": 87,
|
|
140
|
+
"end": 92
|
|
141
|
+
}
|
|
142
|
+
],
|
|
129
143
|
"binds": [
|
|
130
144
|
{
|
|
131
145
|
"kind": "value",
|