@v1nt1248/3nclient-lib 0.0.13 → 0.0.15
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/index.d.ts +1 -0
- package/dist/libs/index.d.ts +3 -4
- package/dist/libs/ipc-service-caller.d.ts +2 -3
- package/dist/libs/ipc-service.d.ts +5 -6
- package/dist/libs/serialization-for-ipc/copy-json.d.ts +8 -0
- package/dist/libs/serialization-for-ipc/protobuf-type.d.ts +10 -0
- package/dist/libs/sqlite-on-3nstorage/deferred.d.ts +6 -0
- package/dist/libs/sqlite-on-3nstorage/index.d.ts +6 -6
- package/dist/libs/sqlite-on-3nstorage/synced.d.ts +1 -1
- package/dist/ui-3n-lib.js +11897 -7953
- package/package.json +8 -5
- package/src/libs/index.ts +11 -0
- package/src/libs/ipc-service-caller.ts +121 -0
- package/src/libs/ipc-service.ts +422 -0
- package/src/libs/serialization-for-ipc/copy-json.ts +55 -0
- package/src/libs/serialization-for-ipc/json-ipc.proto.d.ts +432 -0
- package/src/libs/serialization-for-ipc/json-ipc.proto.js +1093 -0
- package/src/libs/serialization-for-ipc/json-n-binary.ts +275 -0
- package/src/libs/serialization-for-ipc/protobuf-type.ts +61 -0
- package/src/libs/sqlite-on-3nstorage/deferred.ts +32 -0
- package/src/libs/sqlite-on-3nstorage/index.ts +291 -0
- package/{dist → src}/libs/sqlite-on-3nstorage/sqljs.d.ts +26 -26
- package/{dist/libs/sqlite-on-3nstorage/index.js → src/libs/sqlite-on-3nstorage/sqljs.js} +97 -2680
- package/src/libs/sqlite-on-3nstorage/synced.ts +194 -0
- package/dist/libs/index.js +0 -4
- package/dist/libs/ipc-service-caller.js +0 -6129
- package/dist/libs/ipc-service.js +0 -6337
- package/dist/libs/serialization-for-ipc/json-n-binary.js +0 -6040
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2022 - 2023 3NSoft Inc.
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful, but
|
|
10
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
12
|
+
See the GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License along with
|
|
15
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import root from './json-ipc.proto';
|
|
19
|
+
import { ProtoType } from './protobuf-type';
|
|
20
|
+
import { copyJSON } from './copy-json';
|
|
21
|
+
|
|
22
|
+
interface ValuesSequence {
|
|
23
|
+
values: Value[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Value {
|
|
27
|
+
json?: string;
|
|
28
|
+
binaryInJson?: BinaryValue[];
|
|
29
|
+
transferredInJson?: TransferredObj[];
|
|
30
|
+
arr?: BinaryValue;
|
|
31
|
+
transferred?: TransferredObj;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface BinaryValue {
|
|
35
|
+
arr: Uint8Array;
|
|
36
|
+
objLocation: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface TransferredObj {
|
|
40
|
+
indexInPassed: number;
|
|
41
|
+
objLocation: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const valuesType = ProtoType.for<ValuesSequence>(root.json_ipc.ValuesSequence);
|
|
45
|
+
|
|
46
|
+
export function serializeArgs(
|
|
47
|
+
args: any[]
|
|
48
|
+
): { bytes: Uint8Array; passedByReference?: any[]; } {
|
|
49
|
+
const { seq, passedByReference } = argsToValuesSequence(args);
|
|
50
|
+
return { bytes: valuesType.pack(seq), passedByReference };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function argsToValuesSequence(
|
|
54
|
+
args: any[]
|
|
55
|
+
): { seq: ValuesSequence; passedByReference?: any[]; } {
|
|
56
|
+
const seq: ValuesSequence = { values: [] };
|
|
57
|
+
const passedByReference: any[] = [];
|
|
58
|
+
for (const arg of args) {
|
|
59
|
+
if (arg && (typeof arg === 'object')) {
|
|
60
|
+
if (ArrayBuffer.isView(arg)) {
|
|
61
|
+
seq.values.push({
|
|
62
|
+
arr: { arr: arg as Uint8Array, objLocation: [] }
|
|
63
|
+
});
|
|
64
|
+
} else if ((arg as ObjectFromCore)._isObjectFromCore) {
|
|
65
|
+
const indexInPassed = addToArray(passedByReference, arg);
|
|
66
|
+
seq.values.push({
|
|
67
|
+
transferred: { indexInPassed, objLocation: [] }
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
seq.values.push(turnToJsonExtractingBinaryAndTransferable(
|
|
71
|
+
arg, passedByReference
|
|
72
|
+
));
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
seq.values.push({
|
|
76
|
+
json: JSON.stringify(arg)
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
seq,
|
|
82
|
+
passedByReference: ((passedByReference.length > 0) ?
|
|
83
|
+
passedByReference : undefined
|
|
84
|
+
)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function addToArray(arr: Array<any>, o: any): number {
|
|
89
|
+
let foundIndex = arr.indexOf(o);
|
|
90
|
+
if (foundIndex < 0) {
|
|
91
|
+
arr.push(o);
|
|
92
|
+
return arr.length - 1;
|
|
93
|
+
} else {
|
|
94
|
+
return foundIndex;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function turnToJsonExtractingBinaryAndTransferable<T extends object>(
|
|
99
|
+
arg: T, passedByReference: any[]
|
|
100
|
+
): {
|
|
101
|
+
json: string; binaryInJson?: BinaryValue[];
|
|
102
|
+
transferredInJson?: TransferredObj[];
|
|
103
|
+
} {
|
|
104
|
+
const parts = extractNonJsonableFrom(arg, passedByReference);
|
|
105
|
+
if (parts) {
|
|
106
|
+
const { copy, binaryInJson, transferredInJson } = parts;
|
|
107
|
+
return {
|
|
108
|
+
json: JSON.stringify(copy),
|
|
109
|
+
binaryInJson: ((binaryInJson.length > 0) ? binaryInJson : undefined),
|
|
110
|
+
transferredInJson: ((transferredInJson.length > 0) ?
|
|
111
|
+
transferredInJson : undefined
|
|
112
|
+
)
|
|
113
|
+
};
|
|
114
|
+
} else {
|
|
115
|
+
return { json: JSON.stringify(arg) };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function extractNonJsonableFrom<T extends object>(
|
|
120
|
+
arg: T, passedByReference: any[]
|
|
121
|
+
): {
|
|
122
|
+
copy: T; binaryInJson: BinaryValue[];
|
|
123
|
+
transferredInJson: TransferredObj[];
|
|
124
|
+
}|undefined {
|
|
125
|
+
const nonJsonLocations = findAllNonJsonable(arg);
|
|
126
|
+
if (!nonJsonLocations) { return; }
|
|
127
|
+
const copy = copyJSON(arg);
|
|
128
|
+
const binaryInJson: BinaryValue[] = [];
|
|
129
|
+
const transferredInJson: TransferredObj[] = [];
|
|
130
|
+
for (const objLocation of nonJsonLocations) {
|
|
131
|
+
const nonJson = getValueAtObjLocation(arg, objLocation);
|
|
132
|
+
setNewValueAtObjLocation(copy, objLocation, null);
|
|
133
|
+
if ((nonJson as ObjectFromCore)._isObjectFromCore) {
|
|
134
|
+
const indexInPassed = addToArray(passedByReference, nonJson);
|
|
135
|
+
transferredInJson.push({ indexInPassed, objLocation });
|
|
136
|
+
} else {
|
|
137
|
+
binaryInJson.push({ arr: nonJson, objLocation });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return { copy, binaryInJson, transferredInJson };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
interface ObjectFromCore {
|
|
144
|
+
_isObjectFromCore: true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function findAllNonJsonable(o: object): string[][]|undefined {
|
|
148
|
+
const foundObjLocations: string[][] = [];
|
|
149
|
+
if (ArrayBuffer.isView(o)
|
|
150
|
+
|| (o as ObjectFromCore)._isObjectFromCore) {
|
|
151
|
+
return [ [] ];
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(o)) {
|
|
154
|
+
for (let i=0; i<o.length; i+=1) {
|
|
155
|
+
const child = o[i];
|
|
156
|
+
if (child && (typeof child === 'object')) {
|
|
157
|
+
const inChild = findAllNonJsonable(child);
|
|
158
|
+
if (inChild) {
|
|
159
|
+
for (const objLocation of inChild) {
|
|
160
|
+
foundObjLocations.push([ `${i}`, ...objLocation ]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
for (const [ field, child ] of Object.entries(o)) {
|
|
167
|
+
if (child && (typeof child === 'object')) {
|
|
168
|
+
const inChild = findAllNonJsonable(child);
|
|
169
|
+
if (inChild) {
|
|
170
|
+
for (const objLocation of inChild) {
|
|
171
|
+
foundObjLocations.push([ field, ...objLocation ]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return ((foundObjLocations.length > 0) ? foundObjLocations : undefined);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getValueAtObjLocation(o: object, objLocation: string[]): any {
|
|
181
|
+
const value = (o as any)[objLocation[0]];
|
|
182
|
+
if (objLocation.length > 1) {
|
|
183
|
+
return getValueAtObjLocation(value, objLocation.slice(1));
|
|
184
|
+
} else {
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function setNewValueAtObjLocation(
|
|
190
|
+
o: object, objLocation: string[], newValue: any
|
|
191
|
+
): void {
|
|
192
|
+
const value = (o as any)[objLocation[0]];
|
|
193
|
+
if (objLocation.length > 1) {
|
|
194
|
+
setNewValueAtObjLocation(value, objLocation.slice(1), newValue);
|
|
195
|
+
} else {
|
|
196
|
+
(o as any)[objLocation[0]] = newValue;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getInitAndSetNewValueAt(
|
|
201
|
+
o: object, objLocation: string[], newValue: any
|
|
202
|
+
): any {
|
|
203
|
+
const value = (o as any)[objLocation[0]];
|
|
204
|
+
if (objLocation.length > 1) {
|
|
205
|
+
return getInitAndSetNewValueAt(value, objLocation.slice(1), newValue);
|
|
206
|
+
} else {
|
|
207
|
+
(o as any)[objLocation[0]] = newValue;
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function deserializeArgs(
|
|
213
|
+
bytes: Uint8Array, passedByReference: any[]|undefined
|
|
214
|
+
): any[] {
|
|
215
|
+
const values = valuesType.unpack(bytes as Buffer);
|
|
216
|
+
const args: any[] = [];
|
|
217
|
+
for (const val of values.values) {
|
|
218
|
+
const {
|
|
219
|
+
json, binaryInJson, transferredInJson, arr, transferred
|
|
220
|
+
} = val;
|
|
221
|
+
if (arr) {
|
|
222
|
+
args.push(arr.arr);
|
|
223
|
+
} else if (transferred) {
|
|
224
|
+
args.push(getTransferred(
|
|
225
|
+
transferred.indexInPassed, passedByReference
|
|
226
|
+
));
|
|
227
|
+
} else if ((typeof json === 'string') && (json.length > 0)) {
|
|
228
|
+
const arg = JSON.parse(json);
|
|
229
|
+
if (binaryInJson) {
|
|
230
|
+
attachBinaryArrays(arg, binaryInJson);
|
|
231
|
+
}
|
|
232
|
+
if (transferredInJson) {
|
|
233
|
+
attachTransferred(arg, transferredInJson, passedByReference);
|
|
234
|
+
}
|
|
235
|
+
args.push(arg);
|
|
236
|
+
} else {
|
|
237
|
+
// XXX throw error here
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return args;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function attachBinaryArrays<T extends object>(
|
|
244
|
+
arg: T, binaryInJson: BinaryValue[]
|
|
245
|
+
): void {
|
|
246
|
+
for (const { arr, objLocation } of binaryInJson) {
|
|
247
|
+
setNewValueAtObjLocation(arg, objLocation, arr);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function getTransferred(
|
|
252
|
+
indexInPassed: number, passedByReference: any[]|undefined
|
|
253
|
+
): any {
|
|
254
|
+
if (!passedByReference) {
|
|
255
|
+
// XXX throw error here
|
|
256
|
+
throw new Error(`need better error`);
|
|
257
|
+
}
|
|
258
|
+
const o = passedByReference[indexInPassed];
|
|
259
|
+
if (!o || !(o as ObjectFromCore)._isObjectFromCore) {
|
|
260
|
+
// XXX throw error here
|
|
261
|
+
throw new Error(`need better error`);
|
|
262
|
+
}
|
|
263
|
+
return o;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function attachTransferred<T extends object>(
|
|
267
|
+
arg: T, transferredInJson: TransferredObj[],
|
|
268
|
+
passedByReference: any[]|undefined
|
|
269
|
+
): void {
|
|
270
|
+
for (const { indexInPassed, objLocation } of transferredInJson) {
|
|
271
|
+
setNewValueAtObjLocation(arg, objLocation, getTransferred(
|
|
272
|
+
indexInPassed, passedByReference
|
|
273
|
+
));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2020 - 2022 3NSoft Inc.
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful, but
|
|
10
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
12
|
+
See the GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License along with
|
|
15
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Type as PBType } from 'protobufjs';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export class ProtoType<T extends object> {
|
|
22
|
+
|
|
23
|
+
private constructor(
|
|
24
|
+
private readonly type: PBType
|
|
25
|
+
) {
|
|
26
|
+
this.type = type;
|
|
27
|
+
Object.freeze(this);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static for<T extends object>(type: any): ProtoType<T> {
|
|
31
|
+
return new ProtoType<T>(type);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pack(msg: T): Buffer {
|
|
35
|
+
const err = this.type.verify(msg);
|
|
36
|
+
if (err) { throw new Error(err); }
|
|
37
|
+
return this.type.encode(msg).finish() as Buffer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
unpack(bytes: Buffer|void): T {
|
|
41
|
+
if (!bytes) {
|
|
42
|
+
throw {
|
|
43
|
+
runtimeException: true,
|
|
44
|
+
type: 'ipc',
|
|
45
|
+
missingBodyBytes: true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return this.type.decode(bytes) as T;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
packToBase64(msg: T): string {
|
|
52
|
+
return this.pack(msg).toString('base64');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
unpackFromBase64(str: string): T {
|
|
56
|
+
return this.unpack(Buffer.from(str, 'base64'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
Object.freeze(ProtoType.prototype);
|
|
61
|
+
Object.freeze(ProtoType);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2015, 2017, 2019 - 2022 3NSoft Inc.
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful, but
|
|
10
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
12
|
+
See the GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License along with
|
|
15
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface Deferred<T> {
|
|
19
|
+
promise: Promise<T>;
|
|
20
|
+
resolve: (result?: T|PromiseLike<T>) => void;
|
|
21
|
+
reject: (err: any) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function defer<T>(): Deferred<T> {
|
|
25
|
+
const d = <Deferred<T>> {};
|
|
26
|
+
d.promise = new Promise<T>((resolve, reject) => {
|
|
27
|
+
d.resolve = resolve as Deferred<T>['resolve'];
|
|
28
|
+
d.reject = reject;
|
|
29
|
+
});
|
|
30
|
+
Object.freeze(d);
|
|
31
|
+
return d;
|
|
32
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (C) 2022 3NSoft Inc.
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify it under
|
|
5
|
+
the terms of the GNU General Public License as published by the Free Software
|
|
6
|
+
Foundation, either version 3 of the License, or (at your option) any later
|
|
7
|
+
version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful, but
|
|
10
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
12
|
+
See the GNU General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU General Public License along with
|
|
15
|
+
this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import initSqlJs, { Database as DBClass, BindParams as QueryParams, QueryExecResult as QueryResult } from './sqljs';
|
|
19
|
+
import { SingleProc, Action } from './synced';
|
|
20
|
+
|
|
21
|
+
export type Database = DBClass;
|
|
22
|
+
export type BindParams = QueryParams;
|
|
23
|
+
export type QueryExecResult = QueryResult;
|
|
24
|
+
|
|
25
|
+
type WritableFile = web3n.files.WritableFile;
|
|
26
|
+
type ReadonlyFile = web3n.files.ReadonlyFile;
|
|
27
|
+
type FileException = web3n.files.FileException;
|
|
28
|
+
|
|
29
|
+
export interface SaveOpts {
|
|
30
|
+
skipUpload?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
export abstract class SQLiteOn3NStorage {
|
|
35
|
+
|
|
36
|
+
protected readonly syncProc = new SingleProc();
|
|
37
|
+
|
|
38
|
+
protected constructor(
|
|
39
|
+
protected readonly database: Database,
|
|
40
|
+
protected readonly file: WritableFile
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
static async makeAndStart(file: WritableFile): Promise<SQLiteOn3NStorage> {
|
|
44
|
+
const SQL = await initSqlJs(true);
|
|
45
|
+
const fileContent = await readFileContent(file);
|
|
46
|
+
const db = new SQL.Database(fileContent);
|
|
47
|
+
let sqlite: SQLiteOn3NStorage;
|
|
48
|
+
if (file.v?.sync) {
|
|
49
|
+
sqlite = new SQLiteOnSyncedFS(db, file);
|
|
50
|
+
} else if (file.v) {
|
|
51
|
+
sqlite = new SQLiteOnLocalFS(db, file);
|
|
52
|
+
} else {
|
|
53
|
+
sqlite = new SQLiteOnDeviceFS(db, file);
|
|
54
|
+
}
|
|
55
|
+
await sqlite.start();
|
|
56
|
+
return sqlite;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async start(): Promise<void> {
|
|
60
|
+
// XXX add listening process(es)
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async saveToFile(opts?: SaveOpts): Promise<void> {
|
|
65
|
+
await this.syncProc.startOrChain(async () => {
|
|
66
|
+
const dbFileContent = this.database.export();
|
|
67
|
+
await this.file.writeBytes(dbFileContent);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get db(): Database {
|
|
72
|
+
return this.database;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
sync<T>(action: Action<T>): Promise<T> {
|
|
76
|
+
return this.syncProc.startOrChain(action);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
listTables(): string[] {
|
|
80
|
+
const result = this.database.exec(
|
|
81
|
+
`SELECT tbl_name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%'`
|
|
82
|
+
);
|
|
83
|
+
return ((result.length > 0) ?
|
|
84
|
+
result[0].values.map(row => row[0] as string) :
|
|
85
|
+
[]
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
Object.freeze(SQLiteOn3NStorage.prototype);
|
|
91
|
+
Object.freeze(SQLiteOn3NStorage);
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class SQLiteOnSyncedFS extends SQLiteOn3NStorage {
|
|
95
|
+
|
|
96
|
+
constructor(db: Database, file: WritableFile) {
|
|
97
|
+
super(db, file);
|
|
98
|
+
Object.seal(this);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async saveToFile(opts?: SaveOpts): Promise<void> {
|
|
102
|
+
await super.saveToFile();
|
|
103
|
+
if (opts?.skipUpload) {
|
|
104
|
+
return;
|
|
105
|
+
} else {
|
|
106
|
+
await this.file.v!.sync!.upload();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
Object.freeze(SQLiteOnSyncedFS.prototype);
|
|
112
|
+
Object.freeze(SQLiteOnSyncedFS);
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class SQLiteOnLocalFS extends SQLiteOn3NStorage {
|
|
116
|
+
|
|
117
|
+
constructor(db: Database, file: WritableFile) {
|
|
118
|
+
super(db, file);
|
|
119
|
+
Object.seal(this);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
Object.freeze(SQLiteOnLocalFS.prototype);
|
|
124
|
+
Object.freeze(SQLiteOnLocalFS);
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class SQLiteOnDeviceFS extends SQLiteOn3NStorage {
|
|
128
|
+
|
|
129
|
+
constructor(db: Database, file: WritableFile) {
|
|
130
|
+
super(db, file);
|
|
131
|
+
Object.seal(this);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
}
|
|
135
|
+
Object.freeze(SQLiteOnDeviceFS.prototype);
|
|
136
|
+
Object.freeze(SQLiteOnDeviceFS);
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async function readFileContent(
|
|
140
|
+
file: ReadonlyFile
|
|
141
|
+
): Promise<Uint8Array|undefined> {
|
|
142
|
+
try {
|
|
143
|
+
return await file.readBytes();
|
|
144
|
+
} catch (exc) {
|
|
145
|
+
if ((exc as FileException).notFound) {
|
|
146
|
+
return undefined;
|
|
147
|
+
} else {
|
|
148
|
+
throw exc;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function objectFromQueryExecResult<T>(
|
|
154
|
+
sqlResult: QueryExecResult
|
|
155
|
+
): Array<T> {
|
|
156
|
+
const { columns, values: rows } = sqlResult;
|
|
157
|
+
return rows.map(row => row.reduce((obj, cellValue, index) => {
|
|
158
|
+
const field = columns[index] as keyof T;
|
|
159
|
+
obj[field] = cellValue as any;
|
|
160
|
+
return obj;
|
|
161
|
+
}, {} as T));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
export class TableColumnsAndParams<ColumnDefs extends object> {
|
|
166
|
+
|
|
167
|
+
public readonly c: { [columnName in keyof ColumnDefs]: string; };
|
|
168
|
+
public readonly cReversed: { [snakedColName: string ]: keyof ColumnDefs; };
|
|
169
|
+
public readonly p: { [columnName in keyof ColumnDefs]: string; };
|
|
170
|
+
public readonly q: { [columnName in keyof ColumnDefs]: string; };
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
public readonly name: string,
|
|
174
|
+
private readonly columnDefs: ColumnDefs
|
|
175
|
+
) {
|
|
176
|
+
this.c = {} as this['c'];
|
|
177
|
+
this.cReversed = {} as this['cReversed'];
|
|
178
|
+
this.p = {} as this['p'];
|
|
179
|
+
this.q = {} as this['q'];
|
|
180
|
+
for (const cName of Object.keys(this.columnDefs)) {
|
|
181
|
+
const snakedColName = toSnakeCaseName(cName);
|
|
182
|
+
this.c[cName as keyof ColumnDefs] = snakedColName;
|
|
183
|
+
this.cReversed[snakedColName] = cName as keyof ColumnDefs;
|
|
184
|
+
this.p[cName as keyof ColumnDefs] = `$${cName}`;
|
|
185
|
+
this.q[cName as keyof ColumnDefs] = `${this.name}.${snakedColName}`;
|
|
186
|
+
}
|
|
187
|
+
Object.freeze(this.c);
|
|
188
|
+
Object.freeze(this.p);
|
|
189
|
+
Object.freeze(this.q);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private toC(cName: string): string {
|
|
193
|
+
const snakedColName = this.c[cName as keyof ColumnDefs];
|
|
194
|
+
if (snakedColName === undefined) {
|
|
195
|
+
throw new Error(`Column ${cName} is not found among columns of table ${this.name}`);
|
|
196
|
+
}
|
|
197
|
+
return snakedColName;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
toParams<T extends { [columnName in keyof ColumnDefs]: any; }>(
|
|
201
|
+
value: Partial<T>, throwOnUnknownField = true
|
|
202
|
+
): any {
|
|
203
|
+
const params = {} as any;
|
|
204
|
+
for (const [cName, columnValue] of Object.entries(value)) {
|
|
205
|
+
this.toC(cName); // does implicit check for column existence
|
|
206
|
+
params[this.p[cName as keyof ColumnDefs]] = columnValue;
|
|
207
|
+
}
|
|
208
|
+
for (const paramName of Object.values(this.p)) {
|
|
209
|
+
if (params[paramName as string] === undefined) {
|
|
210
|
+
params[paramName as string] = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return params;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
getFromQueryExecResult<T>(
|
|
217
|
+
sqlResult: QueryExecResult
|
|
218
|
+
): Array<T> {
|
|
219
|
+
const { columns, values: rows } = sqlResult;
|
|
220
|
+
return rows.map(row => row.reduce((obj, cellValue, index) => {
|
|
221
|
+
const tabColumn = columns[index];
|
|
222
|
+
let field = this.cReversed[tabColumn as string];
|
|
223
|
+
if (field === undefined) {
|
|
224
|
+
field = tabColumn as keyof ColumnDefs;
|
|
225
|
+
}
|
|
226
|
+
obj[field as string as keyof T] = cellValue as any;
|
|
227
|
+
return obj;
|
|
228
|
+
}, {} as T));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
get insertQuery(): string {
|
|
232
|
+
const colAndParamNames = Object.entries(this.p);
|
|
233
|
+
return `INSERT INTO ${this.name} (${
|
|
234
|
+
colAndParamNames.map(([cName]) => this.toC(cName)).join(', ')
|
|
235
|
+
}) VALUES (${
|
|
236
|
+
colAndParamNames.map(([n, colParam]) => colParam).join(', ')
|
|
237
|
+
})`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
updateQuery(
|
|
241
|
+
withTabName: boolean,
|
|
242
|
+
columns: (keyof ColumnDefs)[]|undefined = undefined,
|
|
243
|
+
skipColumns = false
|
|
244
|
+
): string {
|
|
245
|
+
let colAndParamNames = Object.entries(this.p);
|
|
246
|
+
if (columns) {
|
|
247
|
+
if (skipColumns) {
|
|
248
|
+
colAndParamNames = colAndParamNames.filter(
|
|
249
|
+
([cName]) => !columns.includes(cName as keyof ColumnDefs)
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
colAndParamNames = colAndParamNames.filter(
|
|
253
|
+
([cName]) => columns.includes(cName as keyof ColumnDefs)
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return `UPDATE ${withTabName ? `${this.name} ` : ''}SET ${
|
|
258
|
+
colAndParamNames
|
|
259
|
+
.map(([cName, pName]) => `${this.toC(cName)}=${pName}`)
|
|
260
|
+
.join(', ')
|
|
261
|
+
}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
get columnsCreateSection(): string {
|
|
265
|
+
return Object.entries(this.columnDefs)
|
|
266
|
+
.map(([cName, columnDef]) => `${this.toC(cName)} ${columnDef}`)
|
|
267
|
+
.join(`,\n`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
selectQuery(
|
|
271
|
+
columnsToSelect: (keyof ColumnDefs)[] | string,
|
|
272
|
+
...whereAndColEqual: (keyof ColumnDefs)[]
|
|
273
|
+
): string {
|
|
274
|
+
const whereClause = (whereAndColEqual.length > 0) ?
|
|
275
|
+
` WHERE ${
|
|
276
|
+
(whereAndColEqual as string[]).map(n => `${n}=$${n}`).join(' AND ')
|
|
277
|
+
}` : '';
|
|
278
|
+
return `SELECT ${(typeof columnsToSelect === 'string') ?
|
|
279
|
+
this.toC(columnsToSelect) :
|
|
280
|
+
columnsToSelect.map(cName => this.toC(cName as string)).join(', ')
|
|
281
|
+
} FROM ${this.name}${whereClause}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
}
|
|
285
|
+
Object.freeze(TableColumnsAndParams.prototype);
|
|
286
|
+
Object.freeze(TableColumnsAndParams);
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
function toSnakeCaseName(str: string): string {
|
|
290
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
291
|
+
}
|