@balena/pinejs 15.0.0-true-boolean-911aca4062d3132ad3c34712014739b6849fa13a → 15.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.dockerignore +4 -0
- package/.github/workflows/flowzone.yml +21 -0
- package/.husky/pre-commit +4 -0
- package/.pinejs-cache.json +1 -0
- package/.resinci.yml +1 -0
- package/.versionbot/CHANGELOG.yml +9678 -2001
- package/CHANGELOG.md +2975 -2
- package/Dockerfile +14 -0
- package/Gruntfile.ts +3 -6
- package/README.md +10 -1
- package/VERSION +1 -0
- package/build/browser.ts +1 -1
- package/build/config.ts +0 -1
- package/docker-compose.npm-test.yml +11 -0
- package/docs/AdvancedUsage.md +77 -63
- package/docs/GettingStarted.md +90 -41
- package/docs/Migrations.md +102 -1
- package/docs/ProjectConfig.md +12 -21
- package/docs/Testing.md +7 -0
- package/out/bin/abstract-sql-compiler.js +17 -17
- package/out/bin/abstract-sql-compiler.js.map +1 -1
- package/out/bin/odata-compiler.js +23 -20
- package/out/bin/odata-compiler.js.map +1 -1
- package/out/bin/sbvr-compiler.js +22 -22
- package/out/bin/sbvr-compiler.js.map +1 -1
- package/out/bin/utils.d.ts +2 -2
- package/out/bin/utils.js +3 -3
- package/out/bin/utils.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +9 -8
- package/out/config-loader/config-loader.js +135 -78
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/config-loader/env.d.ts +41 -16
- package/out/config-loader/env.js +46 -2
- package/out/config-loader/env.js.map +1 -1
- package/out/data-server/sbvr-server.d.ts +2 -19
- package/out/data-server/sbvr-server.js +44 -38
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.d.ts +32 -14
- package/out/database-layer/db.js +120 -41
- package/out/database-layer/db.js.map +1 -1
- package/out/express-emulator/express.js +10 -11
- package/out/express-emulator/express.js.map +1 -1
- package/out/http-transactions/transactions.d.ts +2 -18
- package/out/http-transactions/transactions.js +29 -21
- package/out/http-transactions/transactions.js.map +1 -1
- package/out/migrator/async.d.ts +7 -0
- package/out/migrator/async.js +168 -0
- package/out/migrator/async.js.map +1 -0
- package/out/migrator/migrations.sbvr +43 -0
- package/out/migrator/sync.d.ts +9 -0
- package/out/migrator/sync.js +106 -0
- package/out/migrator/sync.js.map +1 -0
- package/out/migrator/utils.d.ts +78 -0
- package/out/migrator/utils.js +283 -0
- package/out/migrator/utils.js.map +1 -0
- package/out/odata-metadata/odata-metadata-generator.js +10 -13
- package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
- package/out/passport-pinejs/passport-pinejs.d.ts +1 -1
- package/out/passport-pinejs/passport-pinejs.js +8 -7
- package/out/passport-pinejs/passport-pinejs.js.map +1 -1
- package/out/pinejs-session-store/pinejs-session-store.d.ts +1 -1
- package/out/pinejs-session-store/pinejs-session-store.js +20 -6
- package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
- package/out/sbvr-api/abstract-sql.d.ts +3 -2
- package/out/sbvr-api/abstract-sql.js +9 -9
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/cached-compile.js +1 -1
- package/out/sbvr-api/cached-compile.js.map +1 -1
- package/out/sbvr-api/common-types.d.ts +6 -5
- package/out/sbvr-api/control-flow.d.ts +8 -1
- package/out/sbvr-api/control-flow.js +36 -9
- package/out/sbvr-api/control-flow.js.map +1 -1
- package/out/sbvr-api/errors.d.ts +47 -40
- package/out/sbvr-api/errors.js +78 -77
- package/out/sbvr-api/errors.js.map +1 -1
- package/out/sbvr-api/express-extension.d.ts +4 -0
- package/out/sbvr-api/hooks.d.ts +16 -15
- package/out/sbvr-api/hooks.js +74 -48
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/odata-response.d.ts +2 -2
- package/out/sbvr-api/odata-response.js +28 -30
- package/out/sbvr-api/odata-response.js.map +1 -1
- package/out/sbvr-api/permissions.d.ts +17 -16
- package/out/sbvr-api/permissions.js +369 -304
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +33 -15
- package/out/sbvr-api/sbvr-utils.js +397 -235
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/translations.d.ts +6 -0
- package/out/sbvr-api/translations.js +150 -0
- package/out/sbvr-api/translations.js.map +1 -0
- package/out/sbvr-api/uri-parser.d.ts +23 -17
- package/out/sbvr-api/uri-parser.js +33 -27
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/out/sbvr-api/user.sbvr +2 -0
- package/out/server-glue/module.d.ts +6 -6
- package/out/server-glue/module.js +4 -2
- package/out/server-glue/module.js.map +1 -1
- package/out/server-glue/server.js +5 -5
- package/out/server-glue/server.js.map +1 -1
- package/package.json +89 -73
- package/pinejs.png +0 -0
- package/repo.yml +9 -9
- package/src/bin/abstract-sql-compiler.ts +5 -7
- package/src/bin/odata-compiler.ts +11 -13
- package/src/bin/sbvr-compiler.ts +11 -17
- package/src/bin/utils.ts +3 -5
- package/src/config-loader/config-loader.ts +167 -53
- package/src/config-loader/env.ts +106 -6
- package/src/data-server/sbvr-server.js +44 -38
- package/src/database-layer/db.ts +205 -64
- package/src/express-emulator/express.js +10 -11
- package/src/http-transactions/transactions.js +29 -21
- package/src/migrator/async.ts +323 -0
- package/src/migrator/migrations.sbvr +43 -0
- package/src/migrator/sync.ts +152 -0
- package/src/migrator/utils.ts +458 -0
- package/src/odata-metadata/odata-metadata-generator.ts +12 -15
- package/src/passport-pinejs/passport-pinejs.ts +9 -7
- package/src/pinejs-session-store/pinejs-session-store.ts +15 -1
- package/src/sbvr-api/abstract-sql.ts +17 -14
- package/src/sbvr-api/common-types.ts +2 -1
- package/src/sbvr-api/control-flow.ts +45 -11
- package/src/sbvr-api/errors.ts +82 -77
- package/src/sbvr-api/express-extension.ts +6 -1
- package/src/sbvr-api/hooks.ts +123 -50
- package/src/sbvr-api/odata-response.ts +23 -28
- package/src/sbvr-api/permissions.ts +548 -415
- package/src/sbvr-api/sbvr-utils.ts +581 -259
- package/src/sbvr-api/translations.ts +248 -0
- package/src/sbvr-api/uri-parser.ts +63 -49
- package/src/sbvr-api/user.sbvr +2 -0
- package/src/server-glue/module.ts +16 -10
- package/src/server-glue/server.ts +5 -5
- package/tsconfig.dev.json +1 -0
- package/tsconfig.json +1 -2
- package/typings/lf-to-abstract-sql.d.ts +6 -9
- package/typings/memoizee.d.ts +1 -1
- package/.github/CODEOWNERS +0 -1
- package/circle.yml +0 -37
- package/docs/todo.txt +0 -22
- package/out/migrator/migrator.d.ts +0 -20
- package/out/migrator/migrator.js +0 -188
- package/out/migrator/migrator.js.map +0 -1
- package/src/migrator/migrator.ts +0 -286
@@ -1,8 +1,24 @@
|
|
1
1
|
import type * as Express from 'express';
|
2
|
-
import type {
|
2
|
+
import type {
|
3
|
+
AbstractSqlModel,
|
4
|
+
Definition,
|
5
|
+
} from '@balena/abstract-sql-compiler';
|
3
6
|
import type { Database } from '../database-layer/db';
|
4
|
-
import type {
|
5
|
-
|
7
|
+
import type {
|
8
|
+
AnyObject,
|
9
|
+
Dictionary,
|
10
|
+
RequiredField,
|
11
|
+
Resolvable,
|
12
|
+
} from '../sbvr-api/common-types';
|
13
|
+
|
14
|
+
import {
|
15
|
+
Migration,
|
16
|
+
Migrations,
|
17
|
+
defaultMigrationCategory,
|
18
|
+
MigrationCategories,
|
19
|
+
isSyncMigration,
|
20
|
+
isAsyncMigration,
|
21
|
+
} from '../migrator/utils';
|
6
22
|
|
7
23
|
import * as fs from 'fs';
|
8
24
|
import * as _ from 'lodash';
|
@@ -11,6 +27,7 @@ import * as path from 'path';
|
|
11
27
|
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
12
28
|
|
13
29
|
import * as permissions from '../sbvr-api/permissions';
|
30
|
+
import { AliasValidNodeType } from '../sbvr-api/translations';
|
14
31
|
|
15
32
|
export type SetupFunction = (
|
16
33
|
app: Express.Application,
|
@@ -25,9 +42,7 @@ export interface Model {
|
|
25
42
|
modelText?: string;
|
26
43
|
abstractSql?: AbstractSqlModel;
|
27
44
|
migrationsPath?: string;
|
28
|
-
migrations?:
|
29
|
-
[index: string]: Migration;
|
30
|
-
};
|
45
|
+
migrations?: Migrations;
|
31
46
|
initSqlPath?: string;
|
32
47
|
initSql?: string;
|
33
48
|
customServerCode?:
|
@@ -36,11 +51,15 @@ export interface Model {
|
|
36
51
|
setup: SetupFunction;
|
37
52
|
};
|
38
53
|
logging?: { [key in keyof Console | 'default']?: boolean };
|
54
|
+
translateTo?: Model['apiRoot'];
|
55
|
+
translations?: Dictionary<
|
56
|
+
Definition | Dictionary<string | AliasValidNodeType>
|
57
|
+
>;
|
39
58
|
}
|
40
59
|
export interface User {
|
41
60
|
username: string;
|
42
61
|
password: string;
|
43
|
-
permissions
|
62
|
+
permissions?: string[];
|
44
63
|
}
|
45
64
|
export interface Config {
|
46
65
|
models: Model[];
|
@@ -77,7 +96,7 @@ const getOrCreatePermission = async (
|
|
77
96
|
) => {
|
78
97
|
try {
|
79
98
|
return await getOrCreate(authApiTx, 'permission', { name: permissionName });
|
80
|
-
} catch (e) {
|
99
|
+
} catch (e: any) {
|
81
100
|
e.message = `Could not create or find permission "${permissionName}": ${e.message}`;
|
82
101
|
throw e;
|
83
102
|
}
|
@@ -85,8 +104,8 @@ const getOrCreatePermission = async (
|
|
85
104
|
|
86
105
|
// Setup function
|
87
106
|
export const setup = (app: Express.Application) => {
|
88
|
-
const loadConfig = (data: Config): Promise<void> =>
|
89
|
-
sbvrUtils.db.transaction(async (tx) => {
|
107
|
+
const loadConfig = async (data: Config): Promise<void> => {
|
108
|
+
await sbvrUtils.db.transaction(async (tx) => {
|
90
109
|
const authApiTx = sbvrUtils.api.Auth.clone({
|
91
110
|
passthrough: {
|
92
111
|
tx,
|
@@ -99,20 +118,17 @@ export const setup = (app: Express.Application) => {
|
|
99
118
|
const permissionsCache: {
|
100
119
|
[index: string]: Promise<number>;
|
101
120
|
} = {};
|
102
|
-
|
121
|
+
for (const user of users) {
|
103
122
|
if (user.permissions == null) {
|
104
|
-
|
123
|
+
continue;
|
105
124
|
}
|
106
|
-
user.permissions
|
107
|
-
|
108
|
-
return;
|
109
|
-
}
|
110
|
-
permissionsCache[permissionName] = getOrCreatePermission(
|
125
|
+
for (const permissionName of user.permissions) {
|
126
|
+
permissionsCache[permissionName] ??= getOrCreatePermission(
|
111
127
|
authApiTx,
|
112
128
|
permissionName,
|
113
129
|
);
|
114
|
-
}
|
115
|
-
}
|
130
|
+
}
|
131
|
+
}
|
116
132
|
|
117
133
|
await Promise.all(
|
118
134
|
users.map(async (user) => {
|
@@ -138,7 +154,7 @@ export const setup = (app: Express.Application) => {
|
|
138
154
|
}),
|
139
155
|
);
|
140
156
|
}
|
141
|
-
} catch (e) {
|
157
|
+
} catch (e: any) {
|
142
158
|
e.message = `Could not create or find user "${user.username}": ${e.message}`;
|
143
159
|
throw e;
|
144
160
|
}
|
@@ -146,44 +162,76 @@ export const setup = (app: Express.Application) => {
|
|
146
162
|
);
|
147
163
|
}
|
148
164
|
|
149
|
-
|
150
|
-
|
151
|
-
|
165
|
+
const modelPromises: Dictionary<Promise<void>> = _(data.models)
|
166
|
+
.filter(
|
167
|
+
(model): model is RequiredField<typeof model, 'apiRoot'> =>
|
152
168
|
(model.abstractSql != null || model.modelText != null) &&
|
153
|
-
model.apiRoot != null
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
169
|
+
model.apiRoot != null,
|
170
|
+
)
|
171
|
+
.keyBy((model) => model.apiRoot)
|
172
|
+
.mapValues(async (model: Model) => {
|
173
|
+
try {
|
174
|
+
const { translateTo, translations } = model;
|
175
|
+
if (translateTo != null) {
|
176
|
+
// We add an async boundary here so that `mapValues` can complete and assign the
|
177
|
+
// waiting promises to `modelPromises` before we try to access it
|
178
|
+
await null;
|
179
|
+
if (modelPromises[translateTo] == null) {
|
180
|
+
throw new Error(
|
181
|
+
`Cannot translate to non-existent version '${translateTo}'`,
|
182
|
+
);
|
183
|
+
}
|
184
|
+
// Wait on the `translateTo` version since it needs to already be available
|
185
|
+
await modelPromises[translateTo];
|
186
|
+
} else if (translations != null) {
|
187
|
+
throw new Error(
|
188
|
+
'Cannot have translations without a translateTo target',
|
159
189
|
);
|
190
|
+
}
|
160
191
|
|
161
|
-
|
162
|
-
|
163
|
-
|
192
|
+
await sbvrUtils.executeModel(
|
193
|
+
tx,
|
194
|
+
model as sbvrUtils.ExecutableModel,
|
195
|
+
);
|
164
196
|
|
165
|
-
|
166
|
-
|
167
|
-
)
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
197
|
+
const apiRoute = `/${model.apiRoot}/*`;
|
198
|
+
app
|
199
|
+
.route(apiRoute)
|
200
|
+
.options((_req, res) => res.status(200).end())
|
201
|
+
.get(sbvrUtils.handleODataRequest)
|
202
|
+
.put(sbvrUtils.handleODataRequest)
|
203
|
+
.post(sbvrUtils.handleODataRequest)
|
204
|
+
.patch(sbvrUtils.handleODataRequest)
|
205
|
+
.merge(sbvrUtils.handleODataRequest)
|
206
|
+
.delete(sbvrUtils.handleODataRequest);
|
207
|
+
|
208
|
+
console.info(
|
209
|
+
'Successfully executed ' + model.modelName + ' model.',
|
210
|
+
);
|
211
|
+
} catch (err: any) {
|
212
|
+
const message = `Failed to execute '${model.modelName}' model from '${model.modelFile}'`;
|
213
|
+
if (_.isError(err)) {
|
214
|
+
err.message = `${message} due to: ${err.message}`;
|
215
|
+
throw err;
|
175
216
|
}
|
217
|
+
throw new Error(message);
|
176
218
|
}
|
219
|
+
})
|
220
|
+
.value();
|
221
|
+
await Promise.all(Object.values(modelPromises));
|
222
|
+
|
223
|
+
await Promise.all(
|
224
|
+
data.models.map(async (model) => {
|
177
225
|
if (model.customServerCode != null) {
|
178
226
|
let customCode: SetupFunction;
|
179
227
|
if (typeof model.customServerCode === 'string') {
|
180
228
|
try {
|
181
229
|
customCode = nodeRequire(model.customServerCode).setup;
|
182
|
-
} catch (e) {
|
230
|
+
} catch (e: any) {
|
183
231
|
e.message = `Error loading custom server code: '${e.message}'`;
|
184
232
|
throw e;
|
185
233
|
}
|
186
|
-
} else if (
|
234
|
+
} else if (typeof model.customServerCode === 'object') {
|
187
235
|
customCode = model.customServerCode.setup;
|
188
236
|
} else {
|
189
237
|
throw new Error(
|
@@ -200,7 +248,7 @@ export const setup = (app: Express.Application) => {
|
|
200
248
|
}),
|
201
249
|
);
|
202
250
|
});
|
203
|
-
|
251
|
+
};
|
204
252
|
const loadConfigFile = async (configPath: string): Promise<Config> => {
|
205
253
|
console.info('Loading config:', configPath);
|
206
254
|
return await import(configPath);
|
@@ -217,7 +265,7 @@ export const setup = (app: Express.Application) => {
|
|
217
265
|
} else if (typeof config === 'string') {
|
218
266
|
root = path.dirname(config);
|
219
267
|
configObj = await loadConfigFile(config);
|
220
|
-
} else if (
|
268
|
+
} else if (typeof config === 'object') {
|
221
269
|
root = process.cwd();
|
222
270
|
configObj = config;
|
223
271
|
} else {
|
@@ -251,18 +299,84 @@ export const setup = (app: Express.Application) => {
|
|
251
299
|
await Promise.all(
|
252
300
|
fileNames.map(async (filename) => {
|
253
301
|
const filePath = path.join(migrationsPath, filename);
|
302
|
+
const fileNameParts = filename.split('.', 3);
|
303
|
+
const fileExtension = path.extname(filename);
|
254
304
|
const [migrationKey] = filename.split('-', 1);
|
305
|
+
let migrationCategory = defaultMigrationCategory;
|
255
306
|
|
256
|
-
|
307
|
+
if (fileNameParts.length === 3) {
|
308
|
+
if (fileNameParts[1] in MigrationCategories) {
|
309
|
+
migrationCategory = fileNameParts[1] as MigrationCategories;
|
310
|
+
} else {
|
311
|
+
console.error(
|
312
|
+
`Unrecognized migration file category ${
|
313
|
+
fileNameParts[1]
|
314
|
+
}, skipping: ${path.extname(filename)}`,
|
315
|
+
);
|
316
|
+
return;
|
317
|
+
}
|
318
|
+
}
|
319
|
+
|
320
|
+
/**
|
321
|
+
* helper to assign migrations with category level to model
|
322
|
+
* example migration file names:
|
323
|
+
*
|
324
|
+
* key0-name.ts ==> defaults startup migration
|
325
|
+
* key1-name1.sql ==> defaults startup migration
|
326
|
+
* key2-name2.sync.sql ==> explicit synchrony migration
|
327
|
+
* key3-name3.async.ts ==> async migration (async datafiller)
|
328
|
+
* key4-name4.async.sql ==> async migration (async datafiller)
|
329
|
+
*
|
330
|
+
*/
|
331
|
+
const assignMigrationWithCategory = (
|
332
|
+
newMigrationKey: string,
|
333
|
+
newMigration: Migration,
|
334
|
+
) => {
|
335
|
+
const catMigrations = migrations[migrationCategory] || {};
|
336
|
+
if (
|
337
|
+
typeof newMigration === 'object' &&
|
338
|
+
migrationCategory === MigrationCategories.async
|
339
|
+
) {
|
340
|
+
newMigration.type = MigrationCategories.async;
|
341
|
+
}
|
342
|
+
if (typeof catMigrations === 'object') {
|
343
|
+
migrations[migrationCategory] = {
|
344
|
+
[newMigrationKey]: newMigration,
|
345
|
+
...catMigrations,
|
346
|
+
};
|
347
|
+
}
|
348
|
+
};
|
349
|
+
|
350
|
+
switch (fileExtension) {
|
257
351
|
case '.coffee':
|
258
352
|
case '.ts':
|
259
353
|
case '.js':
|
260
|
-
|
354
|
+
const loadeMigration = nodeRequire(filePath);
|
355
|
+
const migration = loadeMigration.default ?? loadeMigration;
|
356
|
+
|
357
|
+
if (
|
358
|
+
!isAsyncMigration(migration) &&
|
359
|
+
!isSyncMigration(migration)
|
360
|
+
) {
|
361
|
+
throw new Error(
|
362
|
+
`loaded migraton file at ${filePath} is neither a synchron nor a asyncron migration definition`,
|
363
|
+
);
|
364
|
+
}
|
365
|
+
|
366
|
+
assignMigrationWithCategory(migrationKey, migration);
|
261
367
|
break;
|
262
368
|
case '.sql':
|
263
|
-
|
264
|
-
|
265
|
-
|
369
|
+
if (migrationCategory === MigrationCategories.async) {
|
370
|
+
console.error(
|
371
|
+
`Plain async migration sql statement not supported, needs to be an object, skipping: ${path.extname(
|
372
|
+
filename,
|
373
|
+
)}`,
|
374
|
+
);
|
375
|
+
break;
|
376
|
+
}
|
377
|
+
assignMigrationWithCategory(
|
378
|
+
migrationKey,
|
379
|
+
await fs.promises.readFile(filePath, 'utf8'),
|
266
380
|
);
|
267
381
|
break;
|
268
382
|
default:
|
@@ -282,8 +396,8 @@ export const setup = (app: Express.Application) => {
|
|
282
396
|
}),
|
283
397
|
);
|
284
398
|
await loadConfig(configObj);
|
285
|
-
} catch (err) {
|
286
|
-
console.error('Error loading application config', err
|
399
|
+
} catch (err: any) {
|
400
|
+
console.error('Error loading application config', err);
|
287
401
|
process.exit(1);
|
288
402
|
}
|
289
403
|
};
|
package/src/config-loader/env.ts
CHANGED
@@ -1,21 +1,81 @@
|
|
1
|
-
|
1
|
+
const { PINEJS_DEBUG } = process.env;
|
2
|
+
if (![undefined, '', '0', '1'].includes(PINEJS_DEBUG)) {
|
3
|
+
throw new Error(`Invalid value for PINEJS_DEBUG '${PINEJS_DEBUG}'`);
|
4
|
+
}
|
5
|
+
export const DEBUG = PINEJS_DEBUG === '1';
|
6
|
+
|
7
|
+
type CacheFnOpts<T extends (...args: any[]) => any> =
|
8
|
+
| {
|
9
|
+
primitive?: true;
|
10
|
+
promise?: true;
|
11
|
+
normalizer?: memoizeWeak.MemoizeWeakOptions<T>['normalizer'];
|
12
|
+
weak: true;
|
13
|
+
}
|
14
|
+
| {
|
15
|
+
primitive?: true;
|
16
|
+
promise?: true;
|
17
|
+
normalizer?: memoize.Options<T>['normalizer'];
|
18
|
+
weak?: undefined;
|
19
|
+
};
|
20
|
+
export type CacheFn = <T extends (...args: any[]) => any>(
|
21
|
+
fn: T,
|
22
|
+
opts?: CacheFnOpts<T>,
|
23
|
+
) => T;
|
24
|
+
export type CacheOpts =
|
25
|
+
| {
|
26
|
+
max?: number;
|
27
|
+
}
|
28
|
+
| CacheFn
|
29
|
+
| false;
|
2
30
|
|
3
31
|
export const cache = {
|
4
32
|
permissionsLookup: {
|
5
33
|
max: 5000,
|
6
|
-
},
|
34
|
+
} as CacheOpts,
|
7
35
|
parsePermissions: {
|
8
36
|
max: 100000,
|
9
|
-
},
|
37
|
+
} as CacheOpts,
|
10
38
|
parseOData: {
|
11
39
|
max: 100000,
|
12
|
-
},
|
40
|
+
} as CacheOpts,
|
13
41
|
odataToAbstractSql: {
|
14
42
|
max: 10000,
|
15
|
-
},
|
43
|
+
} as CacheOpts,
|
16
44
|
abstractSqlCompiler: {
|
17
45
|
max: 10000,
|
18
|
-
},
|
46
|
+
} as CacheOpts,
|
47
|
+
userPermissions: false as CacheOpts,
|
48
|
+
apiKeyPermissions: false as CacheOpts,
|
49
|
+
apiKeyActorId: false as CacheOpts,
|
50
|
+
};
|
51
|
+
|
52
|
+
import { boolVar } from '@balena/env-parsing';
|
53
|
+
import * as memoize from 'memoizee';
|
54
|
+
import memoizeWeak = require('memoizee/weak');
|
55
|
+
export const createCache = <T extends (...args: any[]) => any>(
|
56
|
+
cacheName: keyof typeof cache,
|
57
|
+
fn: T,
|
58
|
+
// TODO: Mark this as optional once TS is able to infer the `normalizer` types
|
59
|
+
// when the `weak` differentiating property is not provided.
|
60
|
+
opts: CacheFnOpts<T>,
|
61
|
+
) => {
|
62
|
+
const cacheOpts = cache[cacheName];
|
63
|
+
if (cacheOpts === false) {
|
64
|
+
return fn;
|
65
|
+
}
|
66
|
+
if (typeof cacheOpts === 'function') {
|
67
|
+
return cacheOpts(fn, opts);
|
68
|
+
}
|
69
|
+
if (opts?.weak === true) {
|
70
|
+
return memoizeWeak(fn, {
|
71
|
+
...cacheOpts,
|
72
|
+
...opts,
|
73
|
+
});
|
74
|
+
}
|
75
|
+
return memoize(fn, {
|
76
|
+
...cacheOpts,
|
77
|
+
...opts,
|
78
|
+
});
|
19
79
|
};
|
20
80
|
|
21
81
|
let timeoutMS: number;
|
@@ -39,10 +99,50 @@ export const db = {
|
|
39
99
|
keepAlive: true as boolean | undefined,
|
40
100
|
rollbackTimeout: 30000,
|
41
101
|
timeoutMS,
|
102
|
+
maxUses: Infinity,
|
103
|
+
maxLifetimeSeconds: 0,
|
104
|
+
/**
|
105
|
+
* Check that queries in read-only TXs only contain `SELECT` statements, doing so adds a cost to each query
|
106
|
+
* in a read-only TX and is unnecessary if it is part of a read-only database transaction. The only time a
|
107
|
+
* writable transaction should be used with a read-only TX is during a read-only hook within a writable request
|
108
|
+
* and so should only be able to catch cases of hooks that are incorrectly marked as read-only
|
109
|
+
*
|
110
|
+
* Defaults to true when in DEBUG mode, false otherwise
|
111
|
+
*/
|
112
|
+
checkReadOnlyQueries: DEBUG,
|
42
113
|
};
|
43
114
|
|
115
|
+
export const PINEJS_ADVISORY_LOCK = {
|
116
|
+
namespaceKey: 'pinejs_advisory_lock_namespace',
|
117
|
+
namespaceId: -1,
|
118
|
+
};
|
119
|
+
// for better readability of logging
|
120
|
+
export const booleanToEnabledString = (input: boolean) =>
|
121
|
+
input ? 'enabled' : 'disabled';
|
122
|
+
|
44
123
|
export const migrator = {
|
45
124
|
lockTimeout: 5 * 60 * 1000,
|
46
125
|
// Used to delay the failure on lock taking, to avoid spam taking
|
47
126
|
lockFailDelay: 20 * 1000,
|
127
|
+
asyncMigrationDefaultDelayMS: 1000,
|
128
|
+
asyncMigrationDefaultBackoffDelayMS: 60000,
|
129
|
+
asyncMigrationDefaultErrorThreshold: 10,
|
130
|
+
asyncMigrationDefaultBatchSize: 1000,
|
131
|
+
|
132
|
+
/**
|
133
|
+
* @param asyncMigrationIsEnabled Switch on/off the execution of async migrations
|
134
|
+
* Example implementation with listening on SIGUSR2 to toggle the AsyncMigrationExecution enable switch
|
135
|
+
* For runtime switching the async migration execution the SIGUSR2 signal is interpreted as toggle.
|
136
|
+
* When the process receives a SIGUSR2 signal the async migrations will toggle to be enabled or disabled.
|
137
|
+
* @example
|
138
|
+
* process.on('SIGUSR2', () => {
|
139
|
+
* console.info(
|
140
|
+
* `Received SIGUSR2 to toggle async migration execution enabled
|
141
|
+
* from ${migrator.asyncMigrationIsEnabled}
|
142
|
+
* to ${!migrator.asyncMigrationIsEnabled} `,
|
143
|
+
* );
|
144
|
+
* migrator.asyncMigrationIsEnabled = !migrator.asyncMigrationIsEnabled;
|
145
|
+
* });
|
146
|
+
*/
|
147
|
+
asyncMigrationIsEnabled: boolVar('PINEJS_ASYNC_MIGRATION_ENABLED', true),
|
48
148
|
};
|
@@ -47,6 +47,7 @@ const serverIsOnAir = async (_req, _res, next) => {
|
|
47
47
|
}
|
48
48
|
};
|
49
49
|
|
50
|
+
/** @type {import('../config-loader/config-loader').Config} */
|
50
51
|
export let config = {
|
51
52
|
models: [
|
52
53
|
{
|
@@ -59,17 +60,22 @@ export let config = {
|
|
59
60
|
ALTER TABLE "textarea"
|
60
61
|
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;\
|
61
62
|
`,
|
62
|
-
'15.0.0-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
63
|
+
'15.0.0-data-types': async (tx, sbvrUtils) => {
|
64
|
+
switch (sbvrUtils.db.engine) {
|
65
|
+
case 'mysql':
|
66
|
+
await tx.executeSql(`\
|
67
|
+
ALTER TABLE "textarea"
|
68
|
+
MODIFY "is disabled" BOOLEAN NOT NULL;`);
|
69
|
+
break;
|
70
|
+
case 'postgres':
|
71
|
+
await tx.executeSql(`\
|
72
|
+
ALTER TABLE "textarea"
|
73
|
+
ALTER COLUMN "is disabled" DROP DEFAULT,
|
74
|
+
ALTER COLUMN "is disabled" SET DATA TYPE BOOLEAN USING "is disabled"::BOOLEAN,
|
75
|
+
ALTER COLUMN "is disabled" SET DEFAULT FALSE;`);
|
76
|
+
break;
|
77
|
+
// No need to migrate for websql
|
78
|
+
}
|
73
79
|
},
|
74
80
|
},
|
75
81
|
},
|
@@ -134,7 +140,7 @@ export async function setup(app, sbvrUtils, db) {
|
|
134
140
|
const instance = result[0];
|
135
141
|
await sbvrUtils.executeModel(tx, {
|
136
142
|
apiRoot: instance.is_of__vocabulary,
|
137
|
-
modelText: instance.model_value,
|
143
|
+
modelText: instance.model_value.value,
|
138
144
|
});
|
139
145
|
});
|
140
146
|
await isServerOnAir(true);
|
@@ -153,7 +159,7 @@ export async function setup(app, sbvrUtils, db) {
|
|
153
159
|
permissions.checkPermissionsMiddleware('all'),
|
154
160
|
serverIsOnAir,
|
155
161
|
(_req, res) => {
|
156
|
-
res.
|
162
|
+
res.status(404).end();
|
157
163
|
},
|
158
164
|
);
|
159
165
|
|
@@ -199,7 +205,7 @@ export async function setup(app, sbvrUtils, db) {
|
|
199
205
|
});
|
200
206
|
});
|
201
207
|
await isServerOnAir(true);
|
202
|
-
res.
|
208
|
+
res.status(200).end();
|
203
209
|
} catch (err) {
|
204
210
|
await isServerOnAir(false);
|
205
211
|
res.status(404).json(err);
|
@@ -215,7 +221,7 @@ export async function setup(app, sbvrUtils, db) {
|
|
215
221
|
res.json(results);
|
216
222
|
} catch (err) {
|
217
223
|
console.log('Error validating', err);
|
218
|
-
res.
|
224
|
+
res.status(404).end();
|
219
225
|
}
|
220
226
|
},
|
221
227
|
);
|
@@ -239,10 +245,10 @@ export async function setup(app, sbvrUtils, db) {
|
|
239
245
|
await sbvrUtils.executeModels(tx, exports.config.models);
|
240
246
|
await setupModels(tx);
|
241
247
|
});
|
242
|
-
res.
|
243
|
-
} catch (err) {
|
244
|
-
console.error('Error clearing db', err
|
245
|
-
res.
|
248
|
+
res.status(200).end();
|
249
|
+
} catch (/** @type any */ err) {
|
250
|
+
console.error('Error clearing db', err);
|
251
|
+
res.status(503).end();
|
246
252
|
}
|
247
253
|
},
|
248
254
|
);
|
@@ -266,10 +272,10 @@ export async function setup(app, sbvrUtils, db) {
|
|
266
272
|
}
|
267
273
|
}
|
268
274
|
});
|
269
|
-
res.
|
270
|
-
} catch (err) {
|
271
|
-
console.error('Error importing db', err
|
272
|
-
res.
|
275
|
+
res.status(200).end();
|
276
|
+
} catch (/** @type any */ err) {
|
277
|
+
console.error('Error importing db', err);
|
278
|
+
res.status(404).end();
|
273
279
|
}
|
274
280
|
},
|
275
281
|
);
|
@@ -290,11 +296,11 @@ export async function setup(app, sbvrUtils, db) {
|
|
290
296
|
'SELECT * FROM "' + tableName + '";',
|
291
297
|
);
|
292
298
|
let insQuery = '';
|
293
|
-
result.rows
|
299
|
+
for (const currRow of result.rows) {
|
294
300
|
let notFirst = false;
|
295
301
|
insQuery += 'INSERT INTO "' + tableName + '" (';
|
296
302
|
let valQuery = '';
|
297
|
-
for (
|
303
|
+
for (const propName of Object.keys(currRow)) {
|
298
304
|
if (notFirst) {
|
299
305
|
insQuery += ',';
|
300
306
|
valQuery += ',';
|
@@ -305,15 +311,15 @@ export async function setup(app, sbvrUtils, db) {
|
|
305
311
|
valQuery += "'" + currRow[propName] + "'";
|
306
312
|
}
|
307
313
|
insQuery += ') values (' + valQuery + ');\n';
|
308
|
-
}
|
314
|
+
}
|
309
315
|
exported += insQuery;
|
310
316
|
}),
|
311
317
|
);
|
312
318
|
});
|
313
319
|
res.json(exported);
|
314
|
-
} catch (err) {
|
315
|
-
console.error('Error exporting db', err
|
316
|
-
res.
|
320
|
+
} catch (/** @type any */ err) {
|
321
|
+
console.error('Error exporting db', err);
|
322
|
+
res.status(503).end();
|
317
323
|
}
|
318
324
|
},
|
319
325
|
);
|
@@ -340,10 +346,10 @@ export async function setup(app, sbvrUtils, db) {
|
|
340
346
|
}),
|
341
347
|
);
|
342
348
|
});
|
343
|
-
res.
|
344
|
-
} catch (err) {
|
345
|
-
console.error('Error backing up db', err
|
346
|
-
res.
|
349
|
+
res.status(200).end();
|
350
|
+
} catch (/** @type any */ err) {
|
351
|
+
console.error('Error backing up db', err);
|
352
|
+
res.status(404).end();
|
347
353
|
}
|
348
354
|
},
|
349
355
|
);
|
@@ -369,10 +375,10 @@ export async function setup(app, sbvrUtils, db) {
|
|
369
375
|
}),
|
370
376
|
);
|
371
377
|
});
|
372
|
-
res.
|
373
|
-
} catch (err) {
|
374
|
-
console.error('Error restoring db', err
|
375
|
-
res.
|
378
|
+
res.status(200).end();
|
379
|
+
} catch (/** @type any */ err) {
|
380
|
+
console.error('Error restoring db', err);
|
381
|
+
res.status(404).end();
|
376
382
|
}
|
377
383
|
},
|
378
384
|
);
|
@@ -398,7 +404,7 @@ export async function setup(app, sbvrUtils, db) {
|
|
398
404
|
sbvrUtils.deleteModel('data'),
|
399
405
|
]);
|
400
406
|
await isServerOnAir(false);
|
401
|
-
res.
|
407
|
+
res.status(200).end();
|
402
408
|
});
|
403
409
|
|
404
410
|
await db.transaction(setupModels);
|