@xnetjs/data-bridge 0.0.2 → 0.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/README.md +5 -0
- package/dist/chunk-25WNZV7W.js +831 -0
- package/dist/chunk-5GTIP33X.js +201 -0
- package/dist/chunk-EPNW4GGU.js +460 -0
- package/dist/index.d.ts +259 -449
- package/dist/index.js +1335 -575
- package/dist/native-bridge.d.ts +126 -0
- package/dist/native-bridge.js +13 -0
- package/dist/query-descriptor-D0k2gUQ0.d.ts +298 -0
- package/dist/types-BRvuTwEn.d.ts +547 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +0 -0
- package/dist/worker/data-worker.d.ts +161 -1
- package/dist/worker/data-worker.js +468 -144
- package/package.json +16 -6
- package/dist/chunk-X6F5CPJI.js +0 -386
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// src/query-descriptor.ts
|
|
2
|
+
import {
|
|
3
|
+
applyNodeQueryDescriptor,
|
|
4
|
+
createNodeQueryDescriptor,
|
|
5
|
+
decodeNodeQueryCursor,
|
|
6
|
+
encodeNodeQueryCursor,
|
|
7
|
+
matchesNodeQueryDescriptor,
|
|
8
|
+
nodeQueryDescriptorNeedsBoundedReload,
|
|
9
|
+
nodeQueryDescriptorToOptions,
|
|
10
|
+
serializeNodeQueryDescriptor,
|
|
11
|
+
sortNodeQueryResults
|
|
12
|
+
} from "@xnetjs/data";
|
|
13
|
+
var BOUNDED_QUERY_OVERFETCH = 25;
|
|
14
|
+
var QUERY_EXECUTION_MODES = /* @__PURE__ */ new Set([
|
|
15
|
+
"local",
|
|
16
|
+
"local-then-remote",
|
|
17
|
+
"remote",
|
|
18
|
+
"live",
|
|
19
|
+
"stream"
|
|
20
|
+
]);
|
|
21
|
+
var QUERY_SOURCE_PREFERENCES = /* @__PURE__ */ new Set([
|
|
22
|
+
"auto",
|
|
23
|
+
"local",
|
|
24
|
+
"hub",
|
|
25
|
+
"federated"
|
|
26
|
+
]);
|
|
27
|
+
function toNodeDescriptor(descriptor) {
|
|
28
|
+
return descriptor;
|
|
29
|
+
}
|
|
30
|
+
function normalizeQueryExecutionMode(mode) {
|
|
31
|
+
return mode && QUERY_EXECUTION_MODES.has(mode) ? mode : void 0;
|
|
32
|
+
}
|
|
33
|
+
function normalizeQuerySourcePreference(source) {
|
|
34
|
+
return source && QUERY_SOURCE_PREFERENCES.has(source) ? source : void 0;
|
|
35
|
+
}
|
|
36
|
+
function createQueryDescriptor(schemaId, options) {
|
|
37
|
+
const descriptor = createNodeQueryDescriptor(
|
|
38
|
+
schemaId,
|
|
39
|
+
options
|
|
40
|
+
);
|
|
41
|
+
const mode = normalizeQueryExecutionMode(options?.mode);
|
|
42
|
+
const source = normalizeQuerySourcePreference(options?.source);
|
|
43
|
+
return {
|
|
44
|
+
...descriptor,
|
|
45
|
+
...mode ? { mode } : {},
|
|
46
|
+
...source ? { source } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function queryDescriptorToOptions(descriptor) {
|
|
50
|
+
return {
|
|
51
|
+
...nodeQueryDescriptorToOptions(toNodeDescriptor(descriptor)),
|
|
52
|
+
...descriptor.mode ? { mode: descriptor.mode } : {},
|
|
53
|
+
...descriptor.source ? { source: descriptor.source } : {}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function serializeQueryDescriptor(descriptor) {
|
|
57
|
+
return serializeNodeQueryDescriptor(toNodeDescriptor(descriptor));
|
|
58
|
+
}
|
|
59
|
+
function encodeQueryCursor(descriptor, node) {
|
|
60
|
+
return encodeNodeQueryCursor(toNodeDescriptor(descriptor), node);
|
|
61
|
+
}
|
|
62
|
+
function decodeQueryCursor(cursor) {
|
|
63
|
+
return decodeNodeQueryCursor(cursor);
|
|
64
|
+
}
|
|
65
|
+
function matchesQueryDescriptor(descriptor, node) {
|
|
66
|
+
return matchesNodeQueryDescriptor(toNodeDescriptor(descriptor), node);
|
|
67
|
+
}
|
|
68
|
+
function filterQueryNodes(nodes, descriptor) {
|
|
69
|
+
return nodes.filter((node) => matchesQueryDescriptor(descriptor, node));
|
|
70
|
+
}
|
|
71
|
+
function sortQueryNodes(nodes, descriptor) {
|
|
72
|
+
return sortNodeQueryResults(nodes, toNodeDescriptor(descriptor));
|
|
73
|
+
}
|
|
74
|
+
function applyQueryDescriptor(nodes, descriptor) {
|
|
75
|
+
return applyNodeQueryDescriptor(nodes, toNodeDescriptor(descriptor));
|
|
76
|
+
}
|
|
77
|
+
function queryDescriptorNeedsBoundedReload(descriptor) {
|
|
78
|
+
return nodeQueryDescriptorNeedsBoundedReload(toNodeDescriptor(descriptor));
|
|
79
|
+
}
|
|
80
|
+
function areNodeStatesEquivalent(left, right) {
|
|
81
|
+
if (left === right) return true;
|
|
82
|
+
if (left.id !== right.id || left.schemaId !== right.schemaId || left.deleted !== right.deleted || left.createdAt !== right.createdAt || left.createdBy !== right.createdBy || left.updatedAt !== right.updatedAt || left.updatedBy !== right.updatedBy) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
const leftKeys = Object.keys(left.properties);
|
|
86
|
+
const rightKeys = Object.keys(right.properties);
|
|
87
|
+
if (leftKeys.length !== rightKeys.length) return false;
|
|
88
|
+
for (const key of leftKeys) {
|
|
89
|
+
if (left.properties[key] !== right.properties[key]) return false;
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
function reuseEquivalentNodeReferences(nextNodes, previousNodes) {
|
|
94
|
+
if (!previousNodes || previousNodes.length === 0 || nextNodes.length === 0) {
|
|
95
|
+
return nextNodes;
|
|
96
|
+
}
|
|
97
|
+
const previousById = /* @__PURE__ */ new Map();
|
|
98
|
+
for (const node of previousNodes) {
|
|
99
|
+
if (node) previousById.set(node.id, node);
|
|
100
|
+
}
|
|
101
|
+
let reusedAny = false;
|
|
102
|
+
const merged = nextNodes.map((node) => {
|
|
103
|
+
const previous = previousById.get(node.id);
|
|
104
|
+
if (previous && areNodeStatesEquivalent(previous, node)) {
|
|
105
|
+
reusedAny = true;
|
|
106
|
+
return previous;
|
|
107
|
+
}
|
|
108
|
+
return node;
|
|
109
|
+
});
|
|
110
|
+
return reusedAny ? merged : nextNodes;
|
|
111
|
+
}
|
|
112
|
+
function queryDescriptorSupportsBoundedDelta(descriptor) {
|
|
113
|
+
return descriptor.limit !== void 0 && descriptor.limit > 0 && (descriptor.offset ?? 0) === 0 && descriptor.after === void 0 && descriptor.materializedView === void 0 && descriptor.orderBy !== void 0 && Object.keys(descriptor.orderBy).length > 0;
|
|
114
|
+
}
|
|
115
|
+
function createBoundedWorkingSetDescriptor(descriptor) {
|
|
116
|
+
return { ...descriptor, limit: descriptor.limit + BOUNDED_QUERY_OVERFETCH };
|
|
117
|
+
}
|
|
118
|
+
function createBoundedWorkingSet(descriptor, nodes) {
|
|
119
|
+
const capacity = descriptor.limit + BOUNDED_QUERY_OVERFETCH;
|
|
120
|
+
return { nodes, complete: nodes.length < capacity };
|
|
121
|
+
}
|
|
122
|
+
function applyNodeChangeToBoundedQueryResult(input) {
|
|
123
|
+
const { descriptor, workingSet, nodeId, nextNode } = input;
|
|
124
|
+
const limit = descriptor.limit;
|
|
125
|
+
const currentNodes = workingSet.nodes;
|
|
126
|
+
const currentContains = currentNodes.some((node) => node.id === nodeId);
|
|
127
|
+
const nextMatches = matchesQueryDescriptor(descriptor, nextNode);
|
|
128
|
+
if (!currentContains && !nextMatches) {
|
|
129
|
+
return { kind: "noop" };
|
|
130
|
+
}
|
|
131
|
+
const base = currentContains ? currentNodes.filter((node) => node.id !== nodeId) : currentNodes;
|
|
132
|
+
if (!nextMatches || !nextNode) {
|
|
133
|
+
if (!workingSet.complete && base.length < limit) {
|
|
134
|
+
return { kind: "reload" };
|
|
135
|
+
}
|
|
136
|
+
return boundedSet(base, workingSet.complete, limit);
|
|
137
|
+
}
|
|
138
|
+
const merged = sortQueryNodes([...base, nextNode], descriptor);
|
|
139
|
+
if (!workingSet.complete) {
|
|
140
|
+
if (base.length < limit) {
|
|
141
|
+
return { kind: "reload" };
|
|
142
|
+
}
|
|
143
|
+
if (merged[merged.length - 1]?.id === nodeId) {
|
|
144
|
+
return currentContains ? boundedSet(base, false, limit) : { kind: "noop" };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return boundedSet(merged, workingSet.complete, limit);
|
|
148
|
+
}
|
|
149
|
+
function boundedSet(nodes, complete, limit) {
|
|
150
|
+
return {
|
|
151
|
+
kind: "set",
|
|
152
|
+
data: nodes.slice(0, limit),
|
|
153
|
+
workingSet: { nodes, complete }
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function applyNodeChangeToQueryResult(input) {
|
|
157
|
+
const { descriptor, currentData, nodeId, nextNode } = input;
|
|
158
|
+
const currentIndex = currentData.findIndex((node) => node.id === nodeId);
|
|
159
|
+
const currentContains = currentIndex >= 0;
|
|
160
|
+
const nextMatches = matchesQueryDescriptor(descriptor, nextNode);
|
|
161
|
+
if (queryDescriptorNeedsBoundedReload(descriptor)) {
|
|
162
|
+
return currentContains || nextMatches ? { kind: "reload" } : { kind: "noop" };
|
|
163
|
+
}
|
|
164
|
+
if (!currentContains && !nextMatches) {
|
|
165
|
+
return { kind: "noop" };
|
|
166
|
+
}
|
|
167
|
+
if (currentContains && !nextMatches) {
|
|
168
|
+
return {
|
|
169
|
+
kind: "set",
|
|
170
|
+
data: currentData.filter((node) => node.id !== nodeId)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!nextNode) {
|
|
174
|
+
return { kind: "noop" };
|
|
175
|
+
}
|
|
176
|
+
const nextData = currentContains ? currentData.map((node) => node.id === nodeId ? nextNode : node) : [...currentData, nextNode];
|
|
177
|
+
return {
|
|
178
|
+
kind: "set",
|
|
179
|
+
data: applyQueryDescriptor(nextData, descriptor)
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
BOUNDED_QUERY_OVERFETCH,
|
|
185
|
+
createQueryDescriptor,
|
|
186
|
+
queryDescriptorToOptions,
|
|
187
|
+
serializeQueryDescriptor,
|
|
188
|
+
encodeQueryCursor,
|
|
189
|
+
decodeQueryCursor,
|
|
190
|
+
matchesQueryDescriptor,
|
|
191
|
+
filterQueryNodes,
|
|
192
|
+
sortQueryNodes,
|
|
193
|
+
applyQueryDescriptor,
|
|
194
|
+
queryDescriptorNeedsBoundedReload,
|
|
195
|
+
reuseEquivalentNodeReferences,
|
|
196
|
+
queryDescriptorSupportsBoundedDelta,
|
|
197
|
+
createBoundedWorkingSetDescriptor,
|
|
198
|
+
createBoundedWorkingSet,
|
|
199
|
+
applyNodeChangeToBoundedQueryResult,
|
|
200
|
+
applyNodeChangeToQueryResult
|
|
201
|
+
};
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
// src/utils/binary-state.ts
|
|
2
|
+
var TAG_NULL = 0;
|
|
3
|
+
var TAG_UNDEFINED = 1;
|
|
4
|
+
var TAG_BOOLEAN_FALSE = 2;
|
|
5
|
+
var TAG_BOOLEAN_TRUE = 3;
|
|
6
|
+
var TAG_NUMBER = 4;
|
|
7
|
+
var TAG_STRING = 5;
|
|
8
|
+
var TAG_UINT8ARRAY = 6;
|
|
9
|
+
var TAG_ARRAY = 7;
|
|
10
|
+
var TAG_OBJECT = 8;
|
|
11
|
+
var TAG_BIGINT = 9;
|
|
12
|
+
var NodeStateEncoder = class {
|
|
13
|
+
chunks = [];
|
|
14
|
+
textEncoder = new TextEncoder();
|
|
15
|
+
/**
|
|
16
|
+
* Encode an array of NodeState objects.
|
|
17
|
+
*/
|
|
18
|
+
encode(states) {
|
|
19
|
+
this.chunks = [];
|
|
20
|
+
this.writeUint32(states.length);
|
|
21
|
+
for (const state of states) {
|
|
22
|
+
this.writeNodeState(state);
|
|
23
|
+
}
|
|
24
|
+
return this.finish();
|
|
25
|
+
}
|
|
26
|
+
writeNodeState(state) {
|
|
27
|
+
this.writeString(state.id);
|
|
28
|
+
this.writeString(state.schemaId);
|
|
29
|
+
this.writeProperties(state.properties);
|
|
30
|
+
this.writeTimestamps(state.timestamps);
|
|
31
|
+
this.writeByte(state.deleted ? 1 : 0);
|
|
32
|
+
if (state.deletedAt) {
|
|
33
|
+
this.writeByte(1);
|
|
34
|
+
this.writeTimestamp(state.deletedAt);
|
|
35
|
+
} else {
|
|
36
|
+
this.writeByte(0);
|
|
37
|
+
}
|
|
38
|
+
this.writeFloat64(state.createdAt);
|
|
39
|
+
this.writeString(state.createdBy);
|
|
40
|
+
this.writeFloat64(state.updatedAt);
|
|
41
|
+
this.writeString(state.updatedBy);
|
|
42
|
+
if (state.documentContent) {
|
|
43
|
+
this.writeByte(1);
|
|
44
|
+
this.writeUint8Array(state.documentContent);
|
|
45
|
+
} else {
|
|
46
|
+
this.writeByte(0);
|
|
47
|
+
}
|
|
48
|
+
if (state._unknown && Object.keys(state._unknown).length > 0) {
|
|
49
|
+
this.writeByte(1);
|
|
50
|
+
this.writeProperties(state._unknown);
|
|
51
|
+
} else {
|
|
52
|
+
this.writeByte(0);
|
|
53
|
+
}
|
|
54
|
+
if (state._schemaVersion) {
|
|
55
|
+
this.writeByte(1);
|
|
56
|
+
this.writeString(state._schemaVersion);
|
|
57
|
+
} else {
|
|
58
|
+
this.writeByte(0);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
writeProperties(props) {
|
|
62
|
+
const keys = Object.keys(props);
|
|
63
|
+
this.writeUint32(keys.length);
|
|
64
|
+
for (const key of keys) {
|
|
65
|
+
this.writeString(key);
|
|
66
|
+
this.writeValue(props[key]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
writeTimestamps(timestamps) {
|
|
70
|
+
const keys = Object.keys(timestamps);
|
|
71
|
+
this.writeUint32(keys.length);
|
|
72
|
+
for (const key of keys) {
|
|
73
|
+
this.writeString(key);
|
|
74
|
+
this.writeTimestamp(timestamps[key]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
writeTimestamp(ts) {
|
|
78
|
+
this.writeUint32(ts.lamport);
|
|
79
|
+
this.writeString(ts.author);
|
|
80
|
+
this.writeFloat64(ts.wallTime);
|
|
81
|
+
}
|
|
82
|
+
writeValue(value) {
|
|
83
|
+
if (value === null) {
|
|
84
|
+
this.writeByte(TAG_NULL);
|
|
85
|
+
} else if (value === void 0) {
|
|
86
|
+
this.writeByte(TAG_UNDEFINED);
|
|
87
|
+
} else if (typeof value === "boolean") {
|
|
88
|
+
this.writeByte(value ? TAG_BOOLEAN_TRUE : TAG_BOOLEAN_FALSE);
|
|
89
|
+
} else if (typeof value === "number") {
|
|
90
|
+
this.writeByte(TAG_NUMBER);
|
|
91
|
+
this.writeFloat64(value);
|
|
92
|
+
} else if (typeof value === "string") {
|
|
93
|
+
this.writeByte(TAG_STRING);
|
|
94
|
+
this.writeString(value);
|
|
95
|
+
} else if (value instanceof Uint8Array) {
|
|
96
|
+
this.writeByte(TAG_UINT8ARRAY);
|
|
97
|
+
this.writeUint8Array(value);
|
|
98
|
+
} else if (Array.isArray(value)) {
|
|
99
|
+
this.writeByte(TAG_ARRAY);
|
|
100
|
+
this.writeUint32(value.length);
|
|
101
|
+
for (const item of value) {
|
|
102
|
+
this.writeValue(item);
|
|
103
|
+
}
|
|
104
|
+
} else if (typeof value === "bigint") {
|
|
105
|
+
this.writeByte(TAG_BIGINT);
|
|
106
|
+
this.writeString(value.toString());
|
|
107
|
+
} else if (typeof value === "object") {
|
|
108
|
+
this.writeByte(TAG_OBJECT);
|
|
109
|
+
const obj = value;
|
|
110
|
+
const keys = Object.keys(obj);
|
|
111
|
+
this.writeUint32(keys.length);
|
|
112
|
+
for (const key of keys) {
|
|
113
|
+
this.writeString(key);
|
|
114
|
+
this.writeValue(obj[key]);
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
this.writeByte(TAG_STRING);
|
|
118
|
+
this.writeString(JSON.stringify(value));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
writeByte(value) {
|
|
122
|
+
this.chunks.push(new Uint8Array([value]));
|
|
123
|
+
}
|
|
124
|
+
writeUint32(value) {
|
|
125
|
+
const buf = new ArrayBuffer(4);
|
|
126
|
+
new DataView(buf).setUint32(0, value, true);
|
|
127
|
+
this.chunks.push(new Uint8Array(buf));
|
|
128
|
+
}
|
|
129
|
+
writeFloat64(value) {
|
|
130
|
+
const buf = new ArrayBuffer(8);
|
|
131
|
+
new DataView(buf).setFloat64(0, value, true);
|
|
132
|
+
this.chunks.push(new Uint8Array(buf));
|
|
133
|
+
}
|
|
134
|
+
writeString(value) {
|
|
135
|
+
const bytes = this.textEncoder.encode(value);
|
|
136
|
+
this.writeUint32(bytes.length);
|
|
137
|
+
this.chunks.push(bytes);
|
|
138
|
+
}
|
|
139
|
+
writeUint8Array(value) {
|
|
140
|
+
this.writeUint32(value.length);
|
|
141
|
+
this.chunks.push(value);
|
|
142
|
+
}
|
|
143
|
+
finish() {
|
|
144
|
+
let totalSize = 0;
|
|
145
|
+
for (const chunk of this.chunks) {
|
|
146
|
+
totalSize += chunk.length;
|
|
147
|
+
}
|
|
148
|
+
const result = new Uint8Array(totalSize);
|
|
149
|
+
let offset = 0;
|
|
150
|
+
for (const chunk of this.chunks) {
|
|
151
|
+
result.set(chunk, offset);
|
|
152
|
+
offset += chunk.length;
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
var NodeStateDecoder = class {
|
|
158
|
+
data;
|
|
159
|
+
view;
|
|
160
|
+
offset = 0;
|
|
161
|
+
textDecoder = new TextDecoder();
|
|
162
|
+
constructor(data) {
|
|
163
|
+
this.data = data;
|
|
164
|
+
this.view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Decode a Uint8Array back to an array of NodeState objects.
|
|
168
|
+
*/
|
|
169
|
+
decode() {
|
|
170
|
+
const count = this.readUint32();
|
|
171
|
+
const states = [];
|
|
172
|
+
for (let i = 0; i < count; i++) {
|
|
173
|
+
states.push(this.readNodeState());
|
|
174
|
+
}
|
|
175
|
+
return states;
|
|
176
|
+
}
|
|
177
|
+
readNodeState() {
|
|
178
|
+
const id = this.readString();
|
|
179
|
+
const schemaId = this.readString();
|
|
180
|
+
const properties = this.readProperties();
|
|
181
|
+
const timestamps = this.readTimestamps();
|
|
182
|
+
const deleted = this.readByte() === 1;
|
|
183
|
+
const hasDeletedAt = this.readByte() === 1;
|
|
184
|
+
const deletedAt = hasDeletedAt ? this.readTimestamp() : void 0;
|
|
185
|
+
const createdAt = this.readFloat64();
|
|
186
|
+
const createdBy = this.readString();
|
|
187
|
+
const updatedAt = this.readFloat64();
|
|
188
|
+
const updatedBy = this.readString();
|
|
189
|
+
const hasDocumentContent = this.readByte() === 1;
|
|
190
|
+
const documentContent = hasDocumentContent ? this.readUint8Array() : void 0;
|
|
191
|
+
const hasUnknown = this.readByte() === 1;
|
|
192
|
+
const _unknown = hasUnknown ? this.readProperties() : void 0;
|
|
193
|
+
const hasSchemaVersion = this.readByte() === 1;
|
|
194
|
+
const _schemaVersion = hasSchemaVersion ? this.readString() : void 0;
|
|
195
|
+
const state = {
|
|
196
|
+
id,
|
|
197
|
+
schemaId,
|
|
198
|
+
properties,
|
|
199
|
+
timestamps,
|
|
200
|
+
deleted,
|
|
201
|
+
createdAt,
|
|
202
|
+
createdBy,
|
|
203
|
+
updatedAt,
|
|
204
|
+
updatedBy
|
|
205
|
+
};
|
|
206
|
+
if (deletedAt) state.deletedAt = deletedAt;
|
|
207
|
+
if (documentContent) state.documentContent = documentContent;
|
|
208
|
+
if (_unknown) state._unknown = _unknown;
|
|
209
|
+
if (_schemaVersion) state._schemaVersion = _schemaVersion;
|
|
210
|
+
return state;
|
|
211
|
+
}
|
|
212
|
+
readProperties() {
|
|
213
|
+
const count = this.readUint32();
|
|
214
|
+
const props = {};
|
|
215
|
+
for (let i = 0; i < count; i++) {
|
|
216
|
+
const key = this.readString();
|
|
217
|
+
const value = this.readValue();
|
|
218
|
+
props[key] = value;
|
|
219
|
+
}
|
|
220
|
+
return props;
|
|
221
|
+
}
|
|
222
|
+
readTimestamps() {
|
|
223
|
+
const count = this.readUint32();
|
|
224
|
+
const timestamps = {};
|
|
225
|
+
for (let i = 0; i < count; i++) {
|
|
226
|
+
const key = this.readString();
|
|
227
|
+
timestamps[key] = this.readTimestamp();
|
|
228
|
+
}
|
|
229
|
+
return timestamps;
|
|
230
|
+
}
|
|
231
|
+
readTimestamp() {
|
|
232
|
+
const lamport = this.readUint32();
|
|
233
|
+
const author = this.readString();
|
|
234
|
+
const wallTime = this.readFloat64();
|
|
235
|
+
return {
|
|
236
|
+
lamport,
|
|
237
|
+
author,
|
|
238
|
+
wallTime
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
readValue() {
|
|
242
|
+
const tag = this.readByte();
|
|
243
|
+
switch (tag) {
|
|
244
|
+
case TAG_NULL:
|
|
245
|
+
return null;
|
|
246
|
+
case TAG_UNDEFINED:
|
|
247
|
+
return void 0;
|
|
248
|
+
case TAG_BOOLEAN_FALSE:
|
|
249
|
+
return false;
|
|
250
|
+
case TAG_BOOLEAN_TRUE:
|
|
251
|
+
return true;
|
|
252
|
+
case TAG_NUMBER:
|
|
253
|
+
return this.readFloat64();
|
|
254
|
+
case TAG_STRING:
|
|
255
|
+
return this.readString();
|
|
256
|
+
case TAG_UINT8ARRAY:
|
|
257
|
+
return this.readUint8Array();
|
|
258
|
+
case TAG_ARRAY: {
|
|
259
|
+
const length = this.readUint32();
|
|
260
|
+
const arr = [];
|
|
261
|
+
for (let i = 0; i < length; i++) {
|
|
262
|
+
arr.push(this.readValue());
|
|
263
|
+
}
|
|
264
|
+
return arr;
|
|
265
|
+
}
|
|
266
|
+
case TAG_BIGINT:
|
|
267
|
+
return BigInt(this.readString());
|
|
268
|
+
case TAG_OBJECT: {
|
|
269
|
+
const length = this.readUint32();
|
|
270
|
+
const obj = {};
|
|
271
|
+
for (let i = 0; i < length; i++) {
|
|
272
|
+
const key = this.readString();
|
|
273
|
+
obj[key] = this.readValue();
|
|
274
|
+
}
|
|
275
|
+
return obj;
|
|
276
|
+
}
|
|
277
|
+
default:
|
|
278
|
+
throw new Error(`Unknown tag: ${tag}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
readByte() {
|
|
282
|
+
return this.data[this.offset++];
|
|
283
|
+
}
|
|
284
|
+
readUint32() {
|
|
285
|
+
const value = this.view.getUint32(this.offset, true);
|
|
286
|
+
this.offset += 4;
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
readFloat64() {
|
|
290
|
+
const value = this.view.getFloat64(this.offset, true);
|
|
291
|
+
this.offset += 8;
|
|
292
|
+
return value;
|
|
293
|
+
}
|
|
294
|
+
readString() {
|
|
295
|
+
const length = this.readUint32();
|
|
296
|
+
const bytes = this.data.subarray(this.offset, this.offset + length);
|
|
297
|
+
this.offset += length;
|
|
298
|
+
return this.textDecoder.decode(bytes);
|
|
299
|
+
}
|
|
300
|
+
readUint8Array() {
|
|
301
|
+
const length = this.readUint32();
|
|
302
|
+
const bytes = this.data.slice(this.offset, this.offset + length);
|
|
303
|
+
this.offset += length;
|
|
304
|
+
return bytes;
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
function encodeNodeStates(states) {
|
|
308
|
+
return new NodeStateEncoder().encode(states);
|
|
309
|
+
}
|
|
310
|
+
function decodeNodeStates(data) {
|
|
311
|
+
return new NodeStateDecoder(data).decode();
|
|
312
|
+
}
|
|
313
|
+
function shouldUseBinaryEncoding(states) {
|
|
314
|
+
if (states.length > 100) return true;
|
|
315
|
+
return states.some((s) => s.documentContent && s.documentContent.length > 1e3);
|
|
316
|
+
}
|
|
317
|
+
function encodeWorkerQuerySnapshot(nodes) {
|
|
318
|
+
if (!shouldUseBinaryEncoding(nodes)) {
|
|
319
|
+
return { encoding: "json", nodes };
|
|
320
|
+
}
|
|
321
|
+
return { encoding: "binary", data: encodeNodeStates(nodes) };
|
|
322
|
+
}
|
|
323
|
+
function decodeWorkerQuerySnapshot(snapshot) {
|
|
324
|
+
return snapshot.encoding === "binary" ? decodeNodeStates(snapshot.data) : snapshot.nodes;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/worker/port-sqlite-adapter.ts
|
|
328
|
+
import { wrap } from "comlink";
|
|
329
|
+
var PortSQLiteAdapter = class {
|
|
330
|
+
port;
|
|
331
|
+
proxy;
|
|
332
|
+
inTransaction = false;
|
|
333
|
+
constructor(port) {
|
|
334
|
+
this.port = port;
|
|
335
|
+
this.proxy = wrap(port);
|
|
336
|
+
}
|
|
337
|
+
async open(_config) {
|
|
338
|
+
if (!this.proxy) throw new Error("Port closed");
|
|
339
|
+
const isOpen = await this.proxy.isOpen();
|
|
340
|
+
if (!isOpen) {
|
|
341
|
+
throw new Error("PortSQLiteAdapter: database not open on the SQLite worker");
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async close() {
|
|
345
|
+
this.proxy = null;
|
|
346
|
+
this.port?.close();
|
|
347
|
+
this.port = null;
|
|
348
|
+
this.inTransaction = false;
|
|
349
|
+
}
|
|
350
|
+
isOpen() {
|
|
351
|
+
return this.proxy !== null;
|
|
352
|
+
}
|
|
353
|
+
requireProxy() {
|
|
354
|
+
if (!this.proxy) throw new Error("Database not open");
|
|
355
|
+
return this.proxy;
|
|
356
|
+
}
|
|
357
|
+
async query(sql, params) {
|
|
358
|
+
const result = await this.requireProxy().query(sql, params);
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
async queryOne(sql, params) {
|
|
362
|
+
const result = await this.requireProxy().queryOne(sql, params);
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
async run(sql, params) {
|
|
366
|
+
return this.requireProxy().run(sql, params);
|
|
367
|
+
}
|
|
368
|
+
async exec(sql) {
|
|
369
|
+
return this.requireProxy().exec(sql);
|
|
370
|
+
}
|
|
371
|
+
async transaction(_fn) {
|
|
372
|
+
throw new Error("Complex transactions not supported over a port. Use transactionBatch().");
|
|
373
|
+
}
|
|
374
|
+
async transactionBatch(operations) {
|
|
375
|
+
await this.requireProxy().transaction(operations);
|
|
376
|
+
}
|
|
377
|
+
async applyNodeBatch(input) {
|
|
378
|
+
return this.requireProxy().applyNodeBatch(input);
|
|
379
|
+
}
|
|
380
|
+
async beginTransaction() {
|
|
381
|
+
if (this.inTransaction) {
|
|
382
|
+
throw new Error("Transaction already in progress");
|
|
383
|
+
}
|
|
384
|
+
await this.requireProxy().exec("BEGIN IMMEDIATE");
|
|
385
|
+
this.inTransaction = true;
|
|
386
|
+
}
|
|
387
|
+
async commit() {
|
|
388
|
+
if (!this.inTransaction) {
|
|
389
|
+
throw new Error("No transaction in progress");
|
|
390
|
+
}
|
|
391
|
+
await this.requireProxy().exec("COMMIT");
|
|
392
|
+
this.inTransaction = false;
|
|
393
|
+
}
|
|
394
|
+
async rollback() {
|
|
395
|
+
if (!this.inTransaction) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
await this.requireProxy().exec("ROLLBACK");
|
|
399
|
+
this.inTransaction = false;
|
|
400
|
+
}
|
|
401
|
+
async prepare(_sql) {
|
|
402
|
+
throw new Error("Prepared statements not supported over a port. Use query() or run().");
|
|
403
|
+
}
|
|
404
|
+
async getSchemaVersion() {
|
|
405
|
+
return this.requireProxy().getSchemaVersion();
|
|
406
|
+
}
|
|
407
|
+
// Schema versioning mirrors WebSQLiteProxy byte-for-byte on purpose:
|
|
408
|
+
// both speak the same worker protocol against the same _schema_version
|
|
409
|
+
// table, and diverging here would corrupt version tracking.
|
|
410
|
+
async setSchemaVersion(version) {
|
|
411
|
+
await this.requireProxy().run(
|
|
412
|
+
"INSERT INTO _schema_version (version, applied_at) VALUES (?, ?)",
|
|
413
|
+
[version, Date.now()]
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
async applySchema(version, sql) {
|
|
417
|
+
const currentVersion = await this.getSchemaVersion();
|
|
418
|
+
if (currentVersion >= version) return false;
|
|
419
|
+
await this.exec(sql);
|
|
420
|
+
await this.setSchemaVersion(version);
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
async getDatabaseSize() {
|
|
424
|
+
return this.requireProxy().getDatabaseSize();
|
|
425
|
+
}
|
|
426
|
+
async vacuum() {
|
|
427
|
+
return this.requireProxy().vacuum();
|
|
428
|
+
}
|
|
429
|
+
async checkpoint() {
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
async getStorageMode() {
|
|
433
|
+
return this.requireProxy().getStorageMode();
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/utils/change-events.ts
|
|
438
|
+
function groupNodeChangeEventsBySchema(events) {
|
|
439
|
+
const eventsBySchema = /* @__PURE__ */ new Map();
|
|
440
|
+
for (const event of events) {
|
|
441
|
+
const schemaId = event.node?.schemaId ?? event.change.payload.schemaId;
|
|
442
|
+
if (!schemaId) continue;
|
|
443
|
+
const next = eventsBySchema.get(schemaId) ?? [];
|
|
444
|
+
next.push(event);
|
|
445
|
+
eventsBySchema.set(schemaId, next);
|
|
446
|
+
}
|
|
447
|
+
return eventsBySchema;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export {
|
|
451
|
+
groupNodeChangeEventsBySchema,
|
|
452
|
+
NodeStateEncoder,
|
|
453
|
+
NodeStateDecoder,
|
|
454
|
+
encodeNodeStates,
|
|
455
|
+
decodeNodeStates,
|
|
456
|
+
shouldUseBinaryEncoding,
|
|
457
|
+
encodeWorkerQuerySnapshot,
|
|
458
|
+
decodeWorkerQuerySnapshot,
|
|
459
|
+
PortSQLiteAdapter
|
|
460
|
+
};
|