@nativewindow/tsdb 1.0.1 → 1.0.3
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/dist/client.js +169 -109
- package/dist/index.js +122 -57
- package/package.json +5 -5
package/dist/client.js
CHANGED
|
@@ -1,113 +1,173 @@
|
|
|
1
|
+
//#region client.ts
|
|
2
|
+
/** @internal Type guard for IPC envelope. */
|
|
1
3
|
function isEnvelope(data) {
|
|
2
|
-
|
|
4
|
+
return typeof data === "object" && data !== null && "$ch" in data && typeof data.$ch === "string";
|
|
3
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Create TanStack DB collection options that sync data from a
|
|
8
|
+
* native-window host process via IPC.
|
|
9
|
+
*
|
|
10
|
+
* The returned config is **read-only** — no `onInsert`/`onUpdate`/
|
|
11
|
+
* `onDelete` handlers. Data flows host to webview only.
|
|
12
|
+
*
|
|
13
|
+
* The sync function prefers `window.__native_message_listeners__`
|
|
14
|
+
* (the external listener registry exposed by `createChannelClient`)
|
|
15
|
+
* when available. This avoids the frozen-property crash caused by
|
|
16
|
+
* assigning to the read-only `window.__native_message__`. When the
|
|
17
|
+
* IPC client is not present, it falls back to direct interposition
|
|
18
|
+
* of `window.__native_message__` with a try/catch guard.
|
|
19
|
+
*
|
|
20
|
+
* @param config - Collection and channel configuration.
|
|
21
|
+
* @returns An object compatible with `createCollection()` from `@tanstack/db`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { createCollection } from "@tanstack/db";
|
|
26
|
+
* import { useLiveQuery } from "@tanstack/react-db";
|
|
27
|
+
* import { nativeWindowCollectionOptions } from "@nativewindow/tsdb/client";
|
|
28
|
+
*
|
|
29
|
+
* type Todo = { id: string; text: string; done: boolean };
|
|
30
|
+
*
|
|
31
|
+
* const todoCollection = createCollection(
|
|
32
|
+
* nativeWindowCollectionOptions<Todo, string>({
|
|
33
|
+
* id: "todos",
|
|
34
|
+
* channel: "tsdb:todos",
|
|
35
|
+
* getKey: (todo) => todo.id,
|
|
36
|
+
* }),
|
|
37
|
+
* );
|
|
38
|
+
*
|
|
39
|
+
* function TodoList() {
|
|
40
|
+
* const { data: todos } = useLiveQuery((q) =>
|
|
41
|
+
* q.from({ todos: todoCollection }),
|
|
42
|
+
* );
|
|
43
|
+
* return (
|
|
44
|
+
* <ul>
|
|
45
|
+
* {todos.map((todo) => (
|
|
46
|
+
* <li key={todo.id}>{todo.text}</li>
|
|
47
|
+
* ))}
|
|
48
|
+
* </ul>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
4
53
|
function nativeWindowCollectionOptions(config) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
54
|
+
const { id, channel, getKey } = config;
|
|
55
|
+
return {
|
|
56
|
+
id,
|
|
57
|
+
getKey,
|
|
58
|
+
sync: { sync: (params) => {
|
|
59
|
+
const { begin, write, commit, markReady } = params;
|
|
60
|
+
function processPayload(payload) {
|
|
61
|
+
switch (payload.t) {
|
|
62
|
+
case "i":
|
|
63
|
+
begin();
|
|
64
|
+
write({
|
|
65
|
+
type: "insert",
|
|
66
|
+
value: payload.v
|
|
67
|
+
});
|
|
68
|
+
commit();
|
|
69
|
+
break;
|
|
70
|
+
case "u":
|
|
71
|
+
begin();
|
|
72
|
+
write({
|
|
73
|
+
type: "update",
|
|
74
|
+
value: payload.v
|
|
75
|
+
});
|
|
76
|
+
commit();
|
|
77
|
+
break;
|
|
78
|
+
case "d":
|
|
79
|
+
begin();
|
|
80
|
+
write({
|
|
81
|
+
key: payload.k,
|
|
82
|
+
type: "delete"
|
|
83
|
+
});
|
|
84
|
+
commit();
|
|
85
|
+
break;
|
|
86
|
+
case "s":
|
|
87
|
+
begin();
|
|
88
|
+
for (const item of payload.items) write({
|
|
89
|
+
type: "insert",
|
|
90
|
+
value: item.v
|
|
91
|
+
});
|
|
92
|
+
commit();
|
|
93
|
+
markReady();
|
|
94
|
+
break;
|
|
95
|
+
case "b":
|
|
96
|
+
begin();
|
|
97
|
+
for (const op of payload.ops) switch (op.t) {
|
|
98
|
+
case "i":
|
|
99
|
+
write({
|
|
100
|
+
type: "insert",
|
|
101
|
+
value: op.v
|
|
102
|
+
});
|
|
103
|
+
break;
|
|
104
|
+
case "u":
|
|
105
|
+
write({
|
|
106
|
+
type: "update",
|
|
107
|
+
value: op.v
|
|
108
|
+
});
|
|
109
|
+
break;
|
|
110
|
+
case "d":
|
|
111
|
+
write({
|
|
112
|
+
key: op.k,
|
|
113
|
+
type: "delete"
|
|
114
|
+
});
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
commit();
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Handler that filters for our channel and dispatches. */
|
|
122
|
+
function handler(msg) {
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(msg);
|
|
126
|
+
} catch {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!isEnvelope(parsed) || parsed.$ch !== channel) return;
|
|
130
|
+
const payload = parsed.p;
|
|
131
|
+
if (!payload || typeof payload.t !== "string") return;
|
|
132
|
+
processPayload(payload);
|
|
133
|
+
}
|
|
134
|
+
const registry = window.__native_message_listeners__;
|
|
135
|
+
if (registry && typeof registry.add === "function") {
|
|
136
|
+
registry.add(handler);
|
|
137
|
+
return () => {
|
|
138
|
+
registry.remove(handler);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const prev = window.__native_message__;
|
|
142
|
+
function fallbackHandler(msg) {
|
|
143
|
+
let parsed;
|
|
144
|
+
try {
|
|
145
|
+
parsed = JSON.parse(msg);
|
|
146
|
+
} catch {
|
|
147
|
+
prev?.(msg);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!isEnvelope(parsed) || parsed.$ch !== channel) {
|
|
151
|
+
prev?.(msg);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const payload = parsed.p;
|
|
155
|
+
if (!payload || typeof payload.t !== "string") {
|
|
156
|
+
prev?.(msg);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
processPayload(payload);
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
window.__native_message__ = fallbackHandler;
|
|
163
|
+
} catch {}
|
|
164
|
+
return () => {
|
|
165
|
+
try {
|
|
166
|
+
if (window.__native_message__ === fallbackHandler) window.__native_message__ = prev;
|
|
167
|
+
} catch {}
|
|
168
|
+
};
|
|
169
|
+
} }
|
|
170
|
+
};
|
|
110
171
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
};
|
|
172
|
+
//#endregion
|
|
173
|
+
export { nativeWindowCollectionOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,59 +1,124 @@
|
|
|
1
|
+
//#region index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a host-side data source that pushes mutations to the
|
|
4
|
+
* webview via native-window IPC.
|
|
5
|
+
*
|
|
6
|
+
* The data source maintains an internal `Map` of all items so it
|
|
7
|
+
* can send full snapshots on demand (e.g. after a page reload).
|
|
8
|
+
* Individual mutations (`insert`, `update`, `delete`) update the
|
|
9
|
+
* map *and* send an incremental IPC message.
|
|
10
|
+
*
|
|
11
|
+
* @param sender - Any object with a `postMessage` method (e.g. a `NativeWindow`).
|
|
12
|
+
* @param options - Data source configuration.
|
|
13
|
+
* @returns A {@link DataSource} with imperative push methods.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { NativeWindow } from "@nativewindow/webview";
|
|
18
|
+
* import { createDataSource } from "@nativewindow/tsdb";
|
|
19
|
+
*
|
|
20
|
+
* const win = new NativeWindow({ title: "My App" });
|
|
21
|
+
* win.loadUrl("http://localhost:5173");
|
|
22
|
+
*
|
|
23
|
+
* const todos = createDataSource<Todo, string>(win, {
|
|
24
|
+
* channel: "tsdb:todos",
|
|
25
|
+
* getKey: (todo) => todo.id,
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* todos.insert({ id: "1", text: "Hello", done: false });
|
|
29
|
+
*
|
|
30
|
+
* // Re-send full state after page load
|
|
31
|
+
* win.onPageLoad((event) => {
|
|
32
|
+
* if (event === "finished") todos.sync();
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
1
36
|
function createDataSource(sender, options) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
const { channel, getKey } = options;
|
|
38
|
+
const items = /* @__PURE__ */ new Map();
|
|
39
|
+
function send(payload) {
|
|
40
|
+
sender.postMessage(JSON.stringify({
|
|
41
|
+
$ch: channel,
|
|
42
|
+
p: payload
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
function sendSnapshot() {
|
|
46
|
+
const entries = [];
|
|
47
|
+
items.forEach((v, k) => entries.push({
|
|
48
|
+
k,
|
|
49
|
+
v
|
|
50
|
+
}));
|
|
51
|
+
send({
|
|
52
|
+
t: "s",
|
|
53
|
+
items: entries
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
insert(item) {
|
|
58
|
+
const key = getKey(item);
|
|
59
|
+
items.set(key, item);
|
|
60
|
+
send({
|
|
61
|
+
t: "i",
|
|
62
|
+
k: key,
|
|
63
|
+
v: item
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
update(key, item) {
|
|
67
|
+
items.set(key, item);
|
|
68
|
+
send({
|
|
69
|
+
t: "u",
|
|
70
|
+
k: key,
|
|
71
|
+
v: item
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
delete(key) {
|
|
75
|
+
items.delete(key);
|
|
76
|
+
send({
|
|
77
|
+
t: "d",
|
|
78
|
+
k: key
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
batch(fn) {
|
|
82
|
+
const ops = [];
|
|
83
|
+
fn({
|
|
84
|
+
insert(item) {
|
|
85
|
+
const key = getKey(item);
|
|
86
|
+
items.set(key, item);
|
|
87
|
+
ops.push({
|
|
88
|
+
t: "i",
|
|
89
|
+
k: key,
|
|
90
|
+
v: item
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
update(key, item) {
|
|
94
|
+
items.set(key, item);
|
|
95
|
+
ops.push({
|
|
96
|
+
t: "u",
|
|
97
|
+
k: key,
|
|
98
|
+
v: item
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
delete(key) {
|
|
102
|
+
items.delete(key);
|
|
103
|
+
ops.push({
|
|
104
|
+
t: "d",
|
|
105
|
+
k: key
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
if (ops.length > 0) send({
|
|
110
|
+
t: "b",
|
|
111
|
+
ops
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
sync(newItems) {
|
|
115
|
+
if (newItems !== void 0) {
|
|
116
|
+
items.clear();
|
|
117
|
+
for (const item of newItems) items.set(getKey(item), item);
|
|
118
|
+
}
|
|
119
|
+
sendSnapshot();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
56
122
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
};
|
|
123
|
+
//#endregion
|
|
124
|
+
export { createDataSource };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nativewindow/tsdb",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "TanStack DB collection adapter for native-window IPC (beta)",
|
|
5
5
|
"homepage": "https://nativewindow.fcannizzaro.com",
|
|
6
6
|
"bugs": {
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/bun": "^1.3.9",
|
|
49
|
-
"vite": "^
|
|
49
|
+
"vite": "^8.0.3",
|
|
50
50
|
"vite-plugin-dts": "^4.5.4",
|
|
51
|
-
"vitest": "^4.
|
|
51
|
+
"vitest": "^4.1.2",
|
|
52
52
|
"zod": "^4.3.6"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@nativewindow/webview": "
|
|
56
|
-
"typescript": "^
|
|
55
|
+
"@nativewindow/webview": "^1.0.0",
|
|
56
|
+
"typescript": "^6.0.2"
|
|
57
57
|
}
|
|
58
58
|
}
|