@live-change/db-server 0.9.82 → 0.9.84
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/Dockerfile +2 -0
- package/build-docker-and-upload.sh +2 -2
- package/dist/bin/server.d.ts +2 -0
- package/dist/bin/server.js +181 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/lib/Replicator.d.ts +13 -0
- package/dist/lib/Replicator.js +370 -0
- package/dist/lib/Server.d.ts +68 -0
- package/dist/lib/Server.js +423 -0
- package/dist/lib/backend.d.ts +142 -0
- package/dist/lib/backend.js +202 -0
- package/dist/lib/dbDao.d.ts +180 -0
- package/dist/lib/dbDao.js +1071 -0
- package/dist/lib/profileOutput.d.ts +2 -0
- package/dist/lib/profileOutput.js +21 -0
- package/dist/lib/storeDao.d.ts +14 -0
- package/dist/lib/storeDao.js +67 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/link-deps-4-build.sh +5 -0
- package/package.json +19 -17
package/Dockerfile
CHANGED
|
@@ -9,6 +9,7 @@ RUN npm install -g levelup
|
|
|
9
9
|
RUN npm install -g memdown
|
|
10
10
|
RUN npm install -g node-lmdb
|
|
11
11
|
RUN npm install -g subleveldown
|
|
12
|
+
RUN npm install -g typescript
|
|
12
13
|
|
|
13
14
|
COPY package.json .
|
|
14
15
|
RUN npm install -g @live-change/db-server@`echo "console.log(require('./package.json').version)" | node`
|
|
@@ -16,6 +17,7 @@ RUN npm install -g @live-change/db-client@`echo "console.log(require('./package.
|
|
|
16
17
|
RUN rm package.json
|
|
17
18
|
|
|
18
19
|
RUN cd /usr/local/lib/node_modules/@live-change/db-server/node_modules/@live-change/db-admin/; npm run build
|
|
20
|
+
RUN cd /usr/local/lib/node_modules/@live-change/db-server/; npm run build
|
|
19
21
|
|
|
20
22
|
EXPOSE 9417
|
|
21
23
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
VERSION=`echo "console.log(require('./package.json').version)" | node`
|
|
3
|
-
PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7"
|
|
4
|
-
|
|
3
|
+
#PLATFORMS="linux/amd64,linux/arm64,linux/arm/v7"
|
|
4
|
+
PLATFORMS="linux/amd64"
|
|
5
5
|
echo building docker image for version $VERSION
|
|
6
6
|
docker buildx build --debug \
|
|
7
7
|
--platform $PLATFORMS \
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import service from "os-service";
|
|
5
|
+
import Server from '../lib/Server.js';
|
|
6
|
+
import { client as WSClient } from "@live-change/dao-websocket";
|
|
7
|
+
import ReactiveDao from '@live-change/dao';
|
|
8
|
+
import * as db from "@live-change/db";
|
|
9
|
+
import profileOutput from "../lib/profileOutput.js";
|
|
10
|
+
import { performance } from 'perf_hooks';
|
|
11
|
+
import yargs from 'yargs';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { SsrServer, createLoopbackDao } from "@live-change/server";
|
|
14
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
15
|
+
console.error('Unhandled Promise Rejection', "reason", reason, "stack", reason.stack, "promise", promise);
|
|
16
|
+
//process.exit(1) // TODO: database should not fail because of it, but it should be logged somewhere
|
|
17
|
+
});
|
|
18
|
+
process.on('uncaughtException', function (err) {
|
|
19
|
+
console.error('uncaughtException', err);
|
|
20
|
+
});
|
|
21
|
+
function serverOptions(yargs) {
|
|
22
|
+
yargs.option('port', {
|
|
23
|
+
describe: 'port to bind on',
|
|
24
|
+
type: 'number',
|
|
25
|
+
default: 9417
|
|
26
|
+
});
|
|
27
|
+
yargs.option('host', {
|
|
28
|
+
describe: 'bind host',
|
|
29
|
+
type: 'string',
|
|
30
|
+
default: '::'
|
|
31
|
+
});
|
|
32
|
+
yargs.option('master', {
|
|
33
|
+
describe: 'replicate from master',
|
|
34
|
+
type: 'string',
|
|
35
|
+
default: null
|
|
36
|
+
});
|
|
37
|
+
yargs.option('slowStart', {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
description: 'start indexes one after another(better for debugging)'
|
|
40
|
+
});
|
|
41
|
+
yargs.option('profileLog', {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'profiling log file path'
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function storeOptions(yargs, defaults = {}) {
|
|
47
|
+
yargs.option('dbRoot', {
|
|
48
|
+
describe: 'server root directory',
|
|
49
|
+
type: 'string',
|
|
50
|
+
default: defaults.dbRoot || '.'
|
|
51
|
+
});
|
|
52
|
+
yargs.option('backend', {
|
|
53
|
+
describe: 'database backend engine ( lmdb | leveldb | rocksdb | memdown | mem )',
|
|
54
|
+
type: "string",
|
|
55
|
+
default: defaults.backend || 'lmdb'
|
|
56
|
+
});
|
|
57
|
+
yargs.option('backendUrl', {
|
|
58
|
+
describe: 'remote database backend address',
|
|
59
|
+
type: "string"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function serviceOptions(yargs) {
|
|
63
|
+
yargs.option('serviceName', {
|
|
64
|
+
describe: 'name of service',
|
|
65
|
+
type: 'string',
|
|
66
|
+
default: 'liveChangeDb'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const argv = yargs(process.argv.slice(2)) // eslint-disable-line
|
|
70
|
+
.command('create', 'create database root', (yargs) => {
|
|
71
|
+
storeOptions(yargs);
|
|
72
|
+
}, (argv) => create(argv))
|
|
73
|
+
.command('serve', 'start server', (yargs) => {
|
|
74
|
+
serverOptions(yargs);
|
|
75
|
+
storeOptions(yargs);
|
|
76
|
+
yargs.option('service', {
|
|
77
|
+
describe: 'run as service',
|
|
78
|
+
type: 'boolean'
|
|
79
|
+
});
|
|
80
|
+
}, (argv) => {
|
|
81
|
+
if (argv.service) {
|
|
82
|
+
if (argv.verbose) {
|
|
83
|
+
console.info('running as service!');
|
|
84
|
+
}
|
|
85
|
+
service.run(function () {
|
|
86
|
+
service.stop(0);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
serve(argv);
|
|
90
|
+
})
|
|
91
|
+
.command('install', 'install system service', (yargs) => {
|
|
92
|
+
serviceOptions(yargs);
|
|
93
|
+
serverOptions(yargs);
|
|
94
|
+
storeOptions(yargs, { dbRoot: '/var/db/liveChangeDb' });
|
|
95
|
+
}, async (argv) => {
|
|
96
|
+
const programArgs = ["serve", '--service',
|
|
97
|
+
'--port', argv.port, '--host', argv.host,
|
|
98
|
+
'--dbRoot', argv.dbRoot, '--backend', argv.backend];
|
|
99
|
+
if (argv.verbose)
|
|
100
|
+
console.info(`creating system service ${argv.serviceName}`);
|
|
101
|
+
await fs.promises.mkdir(argv.dbRoot, { recursive: true });
|
|
102
|
+
service.add(argv.serviceName, { programArgs }, function (error) {
|
|
103
|
+
if (error)
|
|
104
|
+
console.trace(error);
|
|
105
|
+
});
|
|
106
|
+
})
|
|
107
|
+
.command('uninstall', 'remove system service', (yargs) => {
|
|
108
|
+
serviceOptions(yargs);
|
|
109
|
+
}, (argv) => {
|
|
110
|
+
if (argv.verbose)
|
|
111
|
+
console.info(`removing system service ${argv.serviceName}`);
|
|
112
|
+
service.remove(argv.serviceName, function (error) {
|
|
113
|
+
if (error)
|
|
114
|
+
console.trace(error);
|
|
115
|
+
});
|
|
116
|
+
})
|
|
117
|
+
.option('verbose', {
|
|
118
|
+
alias: 'v',
|
|
119
|
+
type: 'boolean',
|
|
120
|
+
description: 'Run with verbose logging'
|
|
121
|
+
}).argv;
|
|
122
|
+
async function create({ dbRoot, backend, verbose }) {
|
|
123
|
+
await fs.promises.mkdir(dbRoot, { recursive: true });
|
|
124
|
+
if (verbose)
|
|
125
|
+
console.info(`creating database in ${path.resolve(dbRoot)}`);
|
|
126
|
+
let server = new Server({ dbRoot, backend });
|
|
127
|
+
await server.initialize({ forceNew: true });
|
|
128
|
+
if (verbose)
|
|
129
|
+
console.info(`database server root directory created.`);
|
|
130
|
+
}
|
|
131
|
+
async function serve(argv) {
|
|
132
|
+
const { dbRoot, backend, backendUrl, verbose, host, port, master, slowStart, profileLog } = argv;
|
|
133
|
+
if (profileLog) {
|
|
134
|
+
const out = profileOutput(profileLog);
|
|
135
|
+
await db.profileLog.startLog(out, performance);
|
|
136
|
+
}
|
|
137
|
+
const profileOp = await db.profileLog.begin({ operation: "startingDbServer", ...argv });
|
|
138
|
+
if (verbose)
|
|
139
|
+
console.info(`starting server in ${path.resolve(dbRoot)}`);
|
|
140
|
+
let server = new Server({
|
|
141
|
+
dbRoot, backend, backendUrl, master,
|
|
142
|
+
slowStart
|
|
143
|
+
});
|
|
144
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
145
|
+
if (reason.stack && reason.stack.match(/\s(userCode:([a-z0-9_.\/-]+):([0-9]+):([0-9]+))\n/i)) {
|
|
146
|
+
server.handleUnhandledRejectionInQuery(reason, promise);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.error('Unhandled Promise Rejection', (reason && reason.stack) || reason, "Promise:", promise);
|
|
150
|
+
//process.exit(1) // TODO: database should not fail because of it, but it should be logged somewhere
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
await server.initialize();
|
|
154
|
+
if (verbose)
|
|
155
|
+
console.info(`database initialized!`);
|
|
156
|
+
if (verbose)
|
|
157
|
+
console.info(`listening on: ${argv.host}:${argv.port}`);
|
|
158
|
+
const ssrRoot = path.dirname(fileURLToPath(import.meta.resolve("@live-change/db-admin/front/vite.config.js")));
|
|
159
|
+
const http = await server.getHttp();
|
|
160
|
+
const { app } = http;
|
|
161
|
+
const dev = await fs.promises.access(path.resolve(ssrRoot, './dist'), fs.constants.R_OK)
|
|
162
|
+
.then(r => false).catch(r => true);
|
|
163
|
+
if (dev)
|
|
164
|
+
console.log("STARTING ADMIN IN DEV MODE!");
|
|
165
|
+
const manifest = (dev || argv.spa)
|
|
166
|
+
? null
|
|
167
|
+
: JSON.parse(fs.readFileSync((path.resolve(ssrRoot, 'dist/client/.vite/ssr-manifest.json'))));
|
|
168
|
+
const admin = new SsrServer(app, manifest, {
|
|
169
|
+
dev,
|
|
170
|
+
fastAuth: true,
|
|
171
|
+
root: ssrRoot,
|
|
172
|
+
daoFactory: async (credentials, ip) => {
|
|
173
|
+
return await createLoopbackDao(credentials, () => server.apiServer.daoFactory(credentials, ip));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
admin.start();
|
|
177
|
+
http.server.listen(port, host);
|
|
178
|
+
if (verbose)
|
|
179
|
+
console.info(`server started!`);
|
|
180
|
+
await db.profileLog.end(profileOp);
|
|
181
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default Replicator;
|
|
2
|
+
declare class Replicator {
|
|
3
|
+
constructor(server: any);
|
|
4
|
+
server: any;
|
|
5
|
+
dao: any;
|
|
6
|
+
listObservable: any;
|
|
7
|
+
databases: Map<any, any>;
|
|
8
|
+
start(): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
set(dbList: any): void;
|
|
11
|
+
push(name: any): void;
|
|
12
|
+
remove(dbName: any): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
const bucketSize = 100;
|
|
2
|
+
const LOG_NOTSTARTED = 0;
|
|
3
|
+
const LOG_UPDATING = 2;
|
|
4
|
+
const LOG_READY = 3;
|
|
5
|
+
const LOG_CLOSED = 4;
|
|
6
|
+
class LogReplicator {
|
|
7
|
+
constructor(databaseName, server, logName, destination) {
|
|
8
|
+
this.server = server;
|
|
9
|
+
this.databaseName = databaseName;
|
|
10
|
+
this.logName = logName;
|
|
11
|
+
this.destination = destination;
|
|
12
|
+
this.dao = server.masterDao;
|
|
13
|
+
this.state = LOG_NOTSTARTED;
|
|
14
|
+
this.lastUpdateId = null;
|
|
15
|
+
this.observable = null;
|
|
16
|
+
}
|
|
17
|
+
observeMore() {
|
|
18
|
+
if (this.observable)
|
|
19
|
+
this.observable.unobserve(this);
|
|
20
|
+
this.observable = this.dao.observable(['database', 'logRange', this.databaseName, this.logName, {
|
|
21
|
+
gt: this.lastUpdateId,
|
|
22
|
+
limit: bucketSize
|
|
23
|
+
}]);
|
|
24
|
+
this.observable.observe(this);
|
|
25
|
+
}
|
|
26
|
+
async set(entries) {
|
|
27
|
+
for (let entry of entries) {
|
|
28
|
+
await this.destination.data.put(entry);
|
|
29
|
+
this.lastUpdateId = entry.id;
|
|
30
|
+
}
|
|
31
|
+
if (this.observable.list.length == bucketSize) {
|
|
32
|
+
this.observeMore();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
putByField(_fd, id, entry, _reverse, oldObject) {
|
|
36
|
+
this.destination.data.put(entry);
|
|
37
|
+
this.lastUpdateId = entry.id;
|
|
38
|
+
if (this.observable.list.length == bucketSize) {
|
|
39
|
+
this.observeMore();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async updateAll() {
|
|
43
|
+
let entries;
|
|
44
|
+
do {
|
|
45
|
+
entries = await this.dao.get(['database', 'logRange', this.databaseName, this.logName, {
|
|
46
|
+
gt: this.lastUpdateId,
|
|
47
|
+
limit: bucketSize
|
|
48
|
+
}]);
|
|
49
|
+
for (let entry of entries) {
|
|
50
|
+
await this.destination.data.put(entry);
|
|
51
|
+
this.lastUpdateId = entry.id;
|
|
52
|
+
}
|
|
53
|
+
} while (entries.length >= bucketSize && this.state != LOG_CLOSED);
|
|
54
|
+
this.state = LOG_READY;
|
|
55
|
+
this.observeMore();
|
|
56
|
+
}
|
|
57
|
+
async start() {
|
|
58
|
+
const lastLogOperations = await this.destination.rangeGet({ reverse: true, limit: 1 });
|
|
59
|
+
const lastLogOperation = lastLogOperations[0];
|
|
60
|
+
if (!lastLogOperation) {
|
|
61
|
+
this.lastUpdateId = '';
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.lastUpdateId = lastLogOperation.id; // one second overlap
|
|
65
|
+
}
|
|
66
|
+
this.state = LOG_UPDATING;
|
|
67
|
+
this.updateAll();
|
|
68
|
+
}
|
|
69
|
+
close() {
|
|
70
|
+
this.state = LOG_CLOSED;
|
|
71
|
+
if (this.observable)
|
|
72
|
+
this.observable.unobserve(this);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const TABLE_NOTSTARTED = 0;
|
|
76
|
+
const TABLE_COPYING = 1;
|
|
77
|
+
const TABLE_UPDATING = 2;
|
|
78
|
+
const TABLE_READY = 3;
|
|
79
|
+
const TABLE_CLOSED = 4;
|
|
80
|
+
class TableReplicator {
|
|
81
|
+
constructor(databaseName, server, tableName, destination) {
|
|
82
|
+
this.server = server;
|
|
83
|
+
this.databaseName = databaseName;
|
|
84
|
+
this.tableName = tableName;
|
|
85
|
+
this.destination = destination;
|
|
86
|
+
this.dao = server.masterDao;
|
|
87
|
+
this.state = TABLE_NOTSTARTED;
|
|
88
|
+
this.lastUpdateId = null;
|
|
89
|
+
this.observable = null;
|
|
90
|
+
}
|
|
91
|
+
observeMore() {
|
|
92
|
+
if (this.observable)
|
|
93
|
+
this.observable.unobserve(this);
|
|
94
|
+
this.observable = this.dao.observable(['database', 'tableOpLogRange', this.databaseName, this.tableName, {
|
|
95
|
+
gt: this.lastUpdateId,
|
|
96
|
+
limit: bucketSize
|
|
97
|
+
}]);
|
|
98
|
+
this.observable.observe(this);
|
|
99
|
+
}
|
|
100
|
+
async set(ops) {
|
|
101
|
+
for (let op of ops) {
|
|
102
|
+
if (op.type == 'put')
|
|
103
|
+
await this.destination.put(op.object);
|
|
104
|
+
if (op.type == 'delete')
|
|
105
|
+
await this.destination.delete(op.object);
|
|
106
|
+
if (op.type == 'clearOpLog')
|
|
107
|
+
await this.destination.clearOpLog(op.to);
|
|
108
|
+
this.lastUpdateId = op.id;
|
|
109
|
+
}
|
|
110
|
+
if (this.observable.list.length == bucketSize) {
|
|
111
|
+
this.observeMore();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async putByField(_fd, id, op, _reverse, oldObject) {
|
|
115
|
+
if (op.type == 'put')
|
|
116
|
+
this.destination.put(op.object);
|
|
117
|
+
if (op.type == 'delete')
|
|
118
|
+
this.destination.delete(op.object);
|
|
119
|
+
if (op.type == 'clearOpLog')
|
|
120
|
+
await this.destination.clearOpLog(op.to);
|
|
121
|
+
this.lastUpdateId = op.id;
|
|
122
|
+
if (this.observable.list.length == bucketSize) {
|
|
123
|
+
this.observeMore();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async applyOp(op) {
|
|
127
|
+
if (op.operation.type == 'put')
|
|
128
|
+
await this.destination.data.put(op.operation.object);
|
|
129
|
+
if (op.operation.type == 'delete')
|
|
130
|
+
await this.destination.data.delete(op.operation.object);
|
|
131
|
+
await this.destination.opLog.put(op);
|
|
132
|
+
this.lastUpdateId = op.id;
|
|
133
|
+
}
|
|
134
|
+
async updateAll() {
|
|
135
|
+
// console.log("UPDATE TABLE", this.databaseName, this.tableName, "FROM", this.lastUpdateId)
|
|
136
|
+
let ops;
|
|
137
|
+
do {
|
|
138
|
+
ops = await this.dao.get(['database', 'tableOpLogRange', this.databaseName, this.tableName, {
|
|
139
|
+
gt: this.lastUpdateId,
|
|
140
|
+
limit: bucketSize
|
|
141
|
+
}]);
|
|
142
|
+
//console.log("GOT TABLE OPS", this.databaseName, this.tableName, ":", JSON.stringify(ops, null, " "))
|
|
143
|
+
for (let op of ops) {
|
|
144
|
+
await this.applyOp(op);
|
|
145
|
+
}
|
|
146
|
+
} while (ops.length >= bucketSize && this.state != TABLE_CLOSED);
|
|
147
|
+
this.state = TABLE_READY;
|
|
148
|
+
this.observeMore();
|
|
149
|
+
}
|
|
150
|
+
async copyAll() {
|
|
151
|
+
await Promise.all([
|
|
152
|
+
this.destination.opLog.rangeDelete({}),
|
|
153
|
+
this.destination.data.rangeDelete({}),
|
|
154
|
+
]);
|
|
155
|
+
//console.log("COPY TABLE", this.databaseName, this.tableName)
|
|
156
|
+
let copyPosition = '';
|
|
157
|
+
let data;
|
|
158
|
+
do {
|
|
159
|
+
data = await this.dao.get(['database', 'tableRange', this.databaseName, this.tableName, {
|
|
160
|
+
gt: copyPosition,
|
|
161
|
+
limit: bucketSize
|
|
162
|
+
}]);
|
|
163
|
+
for (let obj of data) {
|
|
164
|
+
await this.destination.put(obj);
|
|
165
|
+
}
|
|
166
|
+
} while (data.length >= bucketSize && this.state != TABLE_CLOSED);
|
|
167
|
+
this.state = TABLE_READY;
|
|
168
|
+
this.observeMore();
|
|
169
|
+
}
|
|
170
|
+
async start() {
|
|
171
|
+
const lastTableOperation = await this.destination.opLog.rangeGet({ reverse: true, limit: 1 })[0];
|
|
172
|
+
const firstSourceOperation = await this.dao.get(['database', 'tableOpLogRange', this.databaseName, this.tableName, {
|
|
173
|
+
limit: 1
|
|
174
|
+
}]);
|
|
175
|
+
if (!lastTableOperation // If empty table, or opLog desynchronized(cleared)
|
|
176
|
+
|| (firstSourceOperation && firstSourceOperation.id > lastTableOperation.id)) { // Copy data first
|
|
177
|
+
let tableCreateTimestamp = Date.now();
|
|
178
|
+
this.state = TABLE_COPYING;
|
|
179
|
+
this.lastUpdateId = ('' + (tableCreateTimestamp - 1000)).padStart(16, '0'); // one second overlay
|
|
180
|
+
this.copyAll();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.state = TABLE_UPDATING;
|
|
184
|
+
this.lastUpdateId = lastTableOperation.id; // one second overlap
|
|
185
|
+
this.updateAll();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
close() {
|
|
189
|
+
this.state = TABLE_CLOSED;
|
|
190
|
+
if (this.observable)
|
|
191
|
+
this.observable.unobserve(this);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
class ListReplicator {
|
|
195
|
+
constructor(databaseName, server, list, type, copy, factory) {
|
|
196
|
+
this.name = databaseName;
|
|
197
|
+
this.database = null;
|
|
198
|
+
this.server = server;
|
|
199
|
+
this.dao = server.masterDao;
|
|
200
|
+
this.list = list;
|
|
201
|
+
this.type = type;
|
|
202
|
+
this.copy = copy;
|
|
203
|
+
this.factory = factory;
|
|
204
|
+
this.observable = null;
|
|
205
|
+
this.objects = new Map();
|
|
206
|
+
}
|
|
207
|
+
start() {
|
|
208
|
+
this.database = this.server.databases.get(this.name);
|
|
209
|
+
/// Observe system table because it is organized by table uids which is better for rename operations
|
|
210
|
+
this.observable = this.dao.observable(['database', 'tableRange', 'system', this.name + '_' + this.list, {}]);
|
|
211
|
+
this.observable.observe(this);
|
|
212
|
+
}
|
|
213
|
+
async createObject(obj) {
|
|
214
|
+
let foundName = null;
|
|
215
|
+
const configList = this.database.config[this.list];
|
|
216
|
+
for (let k in configList)
|
|
217
|
+
if (configList[k].uid == obj.id)
|
|
218
|
+
foundName = k;
|
|
219
|
+
if (foundName) {
|
|
220
|
+
if (foundName != obj.name) {
|
|
221
|
+
await this.database['rename' + this.type](foundName, obj.name);
|
|
222
|
+
await this.server.databases.get('system').table(this.name + '_' + this.list).update(obj.id, [
|
|
223
|
+
{ op: 'merge', property: 'name', value: obj.name }
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
/// TODO: compare configurations?
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
if (configList[obj.name]) { // Object with this name already exists, overwrite?!
|
|
230
|
+
console.error("NAME", obj.name, "EXISTS WITH ANOTHER UID", configList[obj.name].uid, "!=", obj.id, "IN DB", this.name);
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const object = await this.factory(obj);
|
|
234
|
+
await this.server.databases.get('system').table(this.name + '_' + this.list).put(obj);
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
console.log("CREATE OBJ", obj, "IN DB", this.name, "FAILED");
|
|
238
|
+
console.error(e);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (this.copy) {
|
|
242
|
+
const objRep = this.objects.get(obj.id);
|
|
243
|
+
if (objRep) {
|
|
244
|
+
objRep.close();
|
|
245
|
+
this.objects.delete(obj.id);
|
|
246
|
+
}
|
|
247
|
+
const objectReplicator = this.copy(obj);
|
|
248
|
+
this.objects.set(obj.id, objectReplicator);
|
|
249
|
+
objectReplicator.start();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
set(objects) {
|
|
253
|
+
for (const obj of objects) {
|
|
254
|
+
this.createObject(obj);
|
|
255
|
+
}
|
|
256
|
+
for (const key in this.database.config[this.list]) {
|
|
257
|
+
const value = this.database.config[this.list][key];
|
|
258
|
+
const obj = objects.find(o => o.id == value.uid);
|
|
259
|
+
if (!obj) {
|
|
260
|
+
this.database['delete' + this.type](key);
|
|
261
|
+
this.server.databases.get('system').table(this.name + '_' + this.list).delete(value.uid);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
putByField(_fd, uid, object, _reverse, oldObject) {
|
|
266
|
+
this.createObject(obj);
|
|
267
|
+
}
|
|
268
|
+
removeByField(_fd, uid, oldObject) {
|
|
269
|
+
this.database['delete' + this.type](oldObject.name);
|
|
270
|
+
this.server.databases.get('system').table(this.name + '_' + this.list).delete(uid);
|
|
271
|
+
if (this.copy) {
|
|
272
|
+
this.objects.get(uid).close();
|
|
273
|
+
this.objects.delete(uid);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
class DatabaseReplicator {
|
|
278
|
+
constructor(databaseName, server) {
|
|
279
|
+
this.name = databaseName;
|
|
280
|
+
this.server = server;
|
|
281
|
+
this.dao = server.masterDao;
|
|
282
|
+
this.tables = null;
|
|
283
|
+
this.indexes = null;
|
|
284
|
+
this.logs = null;
|
|
285
|
+
}
|
|
286
|
+
async start() {
|
|
287
|
+
this.databaseConfig = this.dao.get(['database', 'databaseConfig', this.name]);
|
|
288
|
+
if (!this.server.metadata.databases[this.name]) {
|
|
289
|
+
this.server.metadata.databases[this.name] = this.databaseConfig;
|
|
290
|
+
const database = await this.server.initDatabase(this.name, this.databaseConfig);
|
|
291
|
+
this.server.databases.set(this.name, database);
|
|
292
|
+
this.server.databasesListObservable.push(this.name);
|
|
293
|
+
await Promise.all([
|
|
294
|
+
this.server.databases.get('system').createTable(this.name + "_tables"),
|
|
295
|
+
this.server.databases.get('system').createTable(this.name + "_logs"),
|
|
296
|
+
this.server.databases.get('system').createTable(this.name + "_indexes")
|
|
297
|
+
]);
|
|
298
|
+
await this.server.saveMetadata();
|
|
299
|
+
}
|
|
300
|
+
const database = this.server.databases.get(this.name);
|
|
301
|
+
this.tables = new ListReplicator(this.name, this.server, 'tables', 'Table', (obj) => new TableReplicator(this.name, this.server, obj.name, database.table(obj.name)), (obj) => database.createTable(obj.name, obj.config));
|
|
302
|
+
this.indexes = new ListReplicator(this.name, this.server, 'indexes', 'Index', false, (obj) => {
|
|
303
|
+
//console.log("CREATE INDEX", obj.id, obj.name, "WITH UID", obj.config.uid)
|
|
304
|
+
return database.createIndex(obj.name, obj.config.code, obj.config.parameters, obj.config);
|
|
305
|
+
});
|
|
306
|
+
this.logs = new ListReplicator(this.name, this.server, 'logs', 'Log', (obj) => new LogReplicator(this.name, this.server, obj.name, database.log(obj.name)), (obj) => database.createLog(obj.name, obj.config));
|
|
307
|
+
this.tables.start();
|
|
308
|
+
this.indexes.start();
|
|
309
|
+
this.logs.start();
|
|
310
|
+
}
|
|
311
|
+
close() {
|
|
312
|
+
this.tables.close();
|
|
313
|
+
this.indexes.close();
|
|
314
|
+
this.logs.close();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
class Replicator {
|
|
318
|
+
constructor(server) {
|
|
319
|
+
this.server = server;
|
|
320
|
+
this.dao = server.masterDao;
|
|
321
|
+
this.listObservable = null;
|
|
322
|
+
this.databases = new Map();
|
|
323
|
+
}
|
|
324
|
+
start() {
|
|
325
|
+
this.listObservable = this.dao.observable(['database', 'databasesList']);
|
|
326
|
+
this.listObservable.observe(this);
|
|
327
|
+
}
|
|
328
|
+
close() {
|
|
329
|
+
if (this.listObservable)
|
|
330
|
+
this.listObservable.unobserve(this);
|
|
331
|
+
for (let replicator of this.databases.values())
|
|
332
|
+
replicator.close();
|
|
333
|
+
}
|
|
334
|
+
set(dbList) {
|
|
335
|
+
if (!Array.isArray(dbList))
|
|
336
|
+
return;
|
|
337
|
+
for (let [name, db] of this.server.databases.entries()) {
|
|
338
|
+
if (name != 'system' && dbList.indexOf(name) == -1) { // Database removed
|
|
339
|
+
this.remove(name);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
for (let name of dbList) {
|
|
343
|
+
this.push(name);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
push(name) {
|
|
347
|
+
if (!this.databases.get(name)) {
|
|
348
|
+
const replicator = new DatabaseReplicator(name, this.server);
|
|
349
|
+
this.databases.set(name, replicator);
|
|
350
|
+
replicator.start();
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
async remove(dbName) {
|
|
354
|
+
const replicator = this.databases.get(dbName);
|
|
355
|
+
if (replicator) {
|
|
356
|
+
replicator.close();
|
|
357
|
+
}
|
|
358
|
+
delete this.server.metadata.databases[dbName];
|
|
359
|
+
this.server.databases.get(dbName).delete();
|
|
360
|
+
this.server.databaseStores.get(dbName).delete();
|
|
361
|
+
this.server.databasesListObservable.remove(dbName);
|
|
362
|
+
await Promise.all([
|
|
363
|
+
this.server.databases.get('system').deleteTable(dbName + "_tables"),
|
|
364
|
+
this.server.databases.get('system').deleteTable(dbName + "_logs"),
|
|
365
|
+
this.server.databases.get('system').deleteTable(dbName + "_indexes")
|
|
366
|
+
]);
|
|
367
|
+
await this.server.saveMetadata();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
export default Replicator;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
export default Server;
|
|
3
|
+
declare class Server {
|
|
4
|
+
constructor(config: any);
|
|
5
|
+
config: any;
|
|
6
|
+
databases: Map<any, any>;
|
|
7
|
+
metadata: any;
|
|
8
|
+
databaseStores: Map<any, any>;
|
|
9
|
+
metadataSavePromise: Promise<void>;
|
|
10
|
+
databasesListObservable: any;
|
|
11
|
+
apiServer: any;
|
|
12
|
+
backends: {};
|
|
13
|
+
masterDao: import("@live-change/dao/lib/Dao.js").default;
|
|
14
|
+
replicator: Replicator;
|
|
15
|
+
createDaoConfig(session: any): {
|
|
16
|
+
database: {
|
|
17
|
+
type: string;
|
|
18
|
+
source: any;
|
|
19
|
+
};
|
|
20
|
+
serverDatabase: {
|
|
21
|
+
type: string;
|
|
22
|
+
source: any;
|
|
23
|
+
};
|
|
24
|
+
store: {
|
|
25
|
+
type: string;
|
|
26
|
+
source: any;
|
|
27
|
+
};
|
|
28
|
+
version: {
|
|
29
|
+
type: string;
|
|
30
|
+
source: any;
|
|
31
|
+
};
|
|
32
|
+
metadata: {
|
|
33
|
+
type: string;
|
|
34
|
+
source: any;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
createDao(session: any): import("@live-change/dao/lib/Dao.js").default;
|
|
38
|
+
initialize(initOptions?: {}): Promise<void>;
|
|
39
|
+
checkInfoIntegrity(): Promise<void>;
|
|
40
|
+
initDatabase(dbName: any, dbConfig: any): Promise<Database>;
|
|
41
|
+
initializingDatabase: Database;
|
|
42
|
+
doSaveMetadata(): Promise<void>;
|
|
43
|
+
saveMetadata(): Promise<void>;
|
|
44
|
+
getHttp(): Promise<{
|
|
45
|
+
app: any;
|
|
46
|
+
sockJsServer: import("@live-change/sockjs/lib/server.js");
|
|
47
|
+
wsServer: any;
|
|
48
|
+
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
49
|
+
}>;
|
|
50
|
+
httpPromise: Promise<{
|
|
51
|
+
app: any;
|
|
52
|
+
sockJsServer: import("@live-change/sockjs/lib/server.js");
|
|
53
|
+
wsServer: any;
|
|
54
|
+
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
55
|
+
}>;
|
|
56
|
+
http: {
|
|
57
|
+
app: any;
|
|
58
|
+
sockJsServer: import("@live-change/sockjs/lib/server.js");
|
|
59
|
+
wsServer: any;
|
|
60
|
+
server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
61
|
+
};
|
|
62
|
+
listen(...args: any[]): Promise<void>;
|
|
63
|
+
close(): Promise<void>;
|
|
64
|
+
handleUnhandledRejectionInQuery(reason: any, promise: any): void;
|
|
65
|
+
}
|
|
66
|
+
import Replicator from './Replicator.js';
|
|
67
|
+
import { Database } from '@live-change/db';
|
|
68
|
+
import http from 'http';
|