@balena/pinejs 17.1.0-build-model-based-typings-85488426453e79d26b93e0ac91f30b73beb2c181-1 → 17.1.0-build-joshbwlng-tasks-0e1e17c7d0be1ee7977c72ee5f1cafb04d93b900-1
Sign up to get free protection for your applications and to get access to all the features.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +4 -225
- package/CHANGELOG.md +2 -66
- package/out/config-loader/env.d.ts +4 -0
- package/out/config-loader/env.js +5 -1
- package/out/config-loader/env.js.map +1 -1
- package/out/data-server/sbvr-server.js +2 -3
- package/out/data-server/sbvr-server.js.map +1 -1
- package/out/database-layer/db.d.ts +3 -0
- package/out/database-layer/db.js +17 -0
- package/out/database-layer/db.js.map +1 -1
- package/out/migrator/sync.d.ts +0 -17
- package/out/migrator/sync.js +40 -39
- package/out/migrator/sync.js.map +1 -1
- package/out/sbvr-api/hooks.d.ts +26 -26
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/permissions.d.ts +2 -26
- package/out/sbvr-api/permissions.js +40 -39
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +6 -46
- package/out/sbvr-api/sbvr-utils.js +44 -44
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/server-glue/module.d.ts +1 -0
- package/out/server-glue/module.js +4 -1
- package/out/server-glue/module.js.map +1 -1
- package/out/tasks/common.d.ts +4 -0
- package/out/tasks/common.js +13 -0
- package/out/tasks/common.js.map +1 -0
- package/out/tasks/index.d.ts +8 -0
- package/out/tasks/index.js +142 -0
- package/out/tasks/index.js.map +1 -0
- package/out/tasks/tasks.sbvr +60 -0
- package/out/tasks/types.d.ts +38 -0
- package/out/tasks/types.js +10 -0
- package/out/tasks/types.js.map +1 -0
- package/out/tasks/worker.d.ts +16 -0
- package/out/tasks/worker.js +228 -0
- package/out/tasks/worker.js.map +1 -0
- package/package.json +19 -18
- package/src/config-loader/env.ts +6 -1
- package/src/data-server/sbvr-server.js +2 -3
- package/src/database-layer/db.ts +25 -0
- package/src/migrator/sync.ts +41 -46
- package/src/sbvr-api/hooks.ts +18 -21
- package/src/sbvr-api/permissions.ts +48 -54
- package/src/sbvr-api/sbvr-utils.ts +53 -90
- package/src/server-glue/module.ts +3 -0
- package/src/tasks/common.ts +14 -0
- package/src/tasks/index.ts +158 -0
- package/src/tasks/tasks.sbvr +60 -0
- package/src/tasks/types.ts +58 -0
- package/src/tasks/worker.ts +280 -0
- package/out/migrator/migrations.d.ts +0 -58
- package/out/migrator/migrations.js +0 -3
- package/out/migrator/migrations.js.map +0 -1
- package/out/sbvr-api/dev.d.ts +0 -22
- package/out/sbvr-api/dev.js +0 -3
- package/out/sbvr-api/dev.js.map +0 -1
- package/out/sbvr-api/user.d.ts +0 -236
- package/out/sbvr-api/user.js +0 -3
- package/out/sbvr-api/user.js.map +0 -1
- package/src/migrator/migrations.ts +0 -62
- package/src/sbvr-api/dev.ts +0 -24
- package/src/sbvr-api/user.ts +0 -214
package/src/database-layer/db.ts
CHANGED
@@ -98,6 +98,13 @@ export interface Database extends BaseDatabase {
|
|
98
98
|
) => Promise<Result>;
|
99
99
|
transaction: TransactionFn;
|
100
100
|
readTransaction: TransactionFn;
|
101
|
+
on?: (
|
102
|
+
name: 'notification',
|
103
|
+
fn: (...args: any[]) => Promise<void>,
|
104
|
+
options?: {
|
105
|
+
channel?: string;
|
106
|
+
},
|
107
|
+
) => void;
|
101
108
|
}
|
102
109
|
|
103
110
|
interface EngineParams {
|
@@ -689,6 +696,24 @@ if (maybePg != null) {
|
|
689
696
|
return {
|
690
697
|
engine: Engines.postgres,
|
691
698
|
executeSql: atomicExecuteSql,
|
699
|
+
on: async (name, fn, options) => {
|
700
|
+
if (name === 'notification' && options?.channel === undefined) {
|
701
|
+
throw new Error('Missing channel option for notification listener');
|
702
|
+
}
|
703
|
+
|
704
|
+
const client = await pool.connect();
|
705
|
+
client.on(name, async (msg) => {
|
706
|
+
try {
|
707
|
+
await fn(msg);
|
708
|
+
} catch (error) {
|
709
|
+
console.error('Error handling message:', error);
|
710
|
+
}
|
711
|
+
});
|
712
|
+
|
713
|
+
if (name === 'notification' && options?.channel !== undefined) {
|
714
|
+
await client.query(`LISTEN "${options.channel}";`);
|
715
|
+
}
|
716
|
+
},
|
692
717
|
transaction: createTransaction(async (stackTraceErr) => {
|
693
718
|
const client = await pool.connect();
|
694
719
|
const tx = new PostgresTx(client, false, stackTraceErr);
|
package/src/migrator/sync.ts
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
import type MigrationsModel from './migrations';
|
2
1
|
import {
|
3
2
|
type MigrationTuple,
|
4
3
|
MigrationError,
|
@@ -17,7 +16,7 @@ import _ from 'lodash';
|
|
17
16
|
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
18
17
|
|
19
18
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
20
|
-
const
|
19
|
+
const modelText = require('./migrations.sbvr');
|
21
20
|
|
22
21
|
type ApiRootModel = Model & { apiRoot: string };
|
23
22
|
|
@@ -137,49 +136,45 @@ const executeMigration = async (
|
|
137
136
|
}
|
138
137
|
};
|
139
138
|
|
140
|
-
declare module '../sbvr-api/sbvr-utils' {
|
141
|
-
export interface API {
|
142
|
-
[migrationModelConfig.apiRoot]: PinejsClient<MigrationsModel>;
|
143
|
-
}
|
144
|
-
}
|
145
|
-
const migrationModelConfig = {
|
146
|
-
modelName: 'migrations',
|
147
|
-
apiRoot: 'migrations',
|
148
|
-
modelText: migrationsModel,
|
149
|
-
migrations: {
|
150
|
-
'11.0.0-modified-at': `
|
151
|
-
ALTER TABLE "migration"
|
152
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
153
|
-
`,
|
154
|
-
'11.0.1-modified-at': `
|
155
|
-
ALTER TABLE "migration lock"
|
156
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
157
|
-
`,
|
158
|
-
'15.0.0-data-types': async (tx, { db }) => {
|
159
|
-
switch (db.engine) {
|
160
|
-
case 'mysql':
|
161
|
-
await tx.executeSql(`\
|
162
|
-
ALTER TABLE "migration"
|
163
|
-
MODIFY "executed migrations" JSON NOT NULL;`);
|
164
|
-
await tx.executeSql(`\
|
165
|
-
ALTER TABLE "migration status"
|
166
|
-
MODIFY "is backing off" BOOLEAN NOT NULL;`);
|
167
|
-
break;
|
168
|
-
case 'postgres':
|
169
|
-
await tx.executeSql(`\
|
170
|
-
ALTER TABLE "migration"
|
171
|
-
ALTER COLUMN "executed migrations" SET DATA TYPE JSONB USING "executed migrations"::JSONB;`);
|
172
|
-
await tx.executeSql(`\
|
173
|
-
ALTER TABLE "migration status"
|
174
|
-
ALTER COLUMN "is backing off" DROP DEFAULT,
|
175
|
-
ALTER COLUMN "is backing off" SET DATA TYPE BOOLEAN USING "is backing off"::BOOLEAN,
|
176
|
-
ALTER COLUMN "is backing off" SET DEFAULT FALSE;`);
|
177
|
-
break;
|
178
|
-
// No need to migrate for websql
|
179
|
-
}
|
180
|
-
},
|
181
|
-
},
|
182
|
-
} as const satisfies sbvrUtils.ExecutableModel;
|
183
139
|
export const config: Config = {
|
184
|
-
models: [
|
140
|
+
models: [
|
141
|
+
{
|
142
|
+
modelName: 'migrations',
|
143
|
+
apiRoot: 'migrations',
|
144
|
+
modelText,
|
145
|
+
migrations: {
|
146
|
+
'11.0.0-modified-at': `
|
147
|
+
ALTER TABLE "migration"
|
148
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
149
|
+
`,
|
150
|
+
'11.0.1-modified-at': `
|
151
|
+
ALTER TABLE "migration lock"
|
152
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
153
|
+
`,
|
154
|
+
'15.0.0-data-types': async (tx, { db }) => {
|
155
|
+
switch (db.engine) {
|
156
|
+
case 'mysql':
|
157
|
+
await tx.executeSql(`\
|
158
|
+
ALTER TABLE "migration"
|
159
|
+
MODIFY "executed migrations" JSON NOT NULL;`);
|
160
|
+
await tx.executeSql(`\
|
161
|
+
ALTER TABLE "migration status"
|
162
|
+
MODIFY "is backing off" BOOLEAN NOT NULL;`);
|
163
|
+
break;
|
164
|
+
case 'postgres':
|
165
|
+
await tx.executeSql(`\
|
166
|
+
ALTER TABLE "migration"
|
167
|
+
ALTER COLUMN "executed migrations" SET DATA TYPE JSONB USING "executed migrations"::JSONB;`);
|
168
|
+
await tx.executeSql(`\
|
169
|
+
ALTER TABLE "migration status"
|
170
|
+
ALTER COLUMN "is backing off" DROP DEFAULT,
|
171
|
+
ALTER COLUMN "is backing off" SET DATA TYPE BOOLEAN USING "is backing off"::BOOLEAN,
|
172
|
+
ALTER COLUMN "is backing off" SET DEFAULT FALSE;`);
|
173
|
+
break;
|
174
|
+
// No need to migrate for websql
|
175
|
+
}
|
176
|
+
},
|
177
|
+
},
|
178
|
+
},
|
179
|
+
],
|
185
180
|
};
|
package/src/sbvr-api/hooks.ts
CHANGED
@@ -9,6 +9,7 @@ import _ from 'lodash';
|
|
9
9
|
import { settleMapSeries } from './control-flow';
|
10
10
|
import memoize from 'memoizee';
|
11
11
|
import {
|
12
|
+
type PinejsClient,
|
12
13
|
type User,
|
13
14
|
type ApiKey,
|
14
15
|
resolveSynonym,
|
@@ -30,27 +31,23 @@ export interface HookReq {
|
|
30
31
|
hooks?: InstantiatedHooks;
|
31
32
|
is?: (type: string | string[]) => string | false | null;
|
32
33
|
}
|
33
|
-
export interface HookArgs
|
34
|
+
export interface HookArgs {
|
34
35
|
req: HookReq;
|
35
36
|
request: ODataRequest;
|
36
|
-
api:
|
37
|
+
api: PinejsClient;
|
37
38
|
tx?: Tx | undefined;
|
38
39
|
}
|
39
40
|
export type HookResponse = PromiseLike<any> | null | void;
|
40
41
|
|
41
|
-
export interface Hooks
|
42
|
-
PREPARSE?: (
|
43
|
-
|
44
|
-
) => HookResponse;
|
45
|
-
POSTPARSE?: (options: HookArgs<Vocab>) => HookResponse;
|
46
|
-
PRERUN?: (options: HookArgs<Vocab> & { tx: Tx }) => HookResponse;
|
42
|
+
export interface Hooks {
|
43
|
+
PREPARSE?: (options: Omit<HookArgs, 'request' | 'api'>) => HookResponse;
|
44
|
+
POSTPARSE?: (options: HookArgs) => HookResponse;
|
45
|
+
PRERUN?: (options: HookArgs & { tx: Tx }) => HookResponse;
|
47
46
|
/** These are run in reverse translation order from newest to oldest */
|
48
|
-
POSTRUN?: (
|
49
|
-
options: HookArgs<Vocab> & { tx: Tx; result: any },
|
50
|
-
) => HookResponse;
|
47
|
+
POSTRUN?: (options: HookArgs & { tx: Tx; result: any }) => HookResponse;
|
51
48
|
/** These are run in reverse translation order from newest to oldest */
|
52
49
|
PRERESPOND?: (
|
53
|
-
options: HookArgs
|
50
|
+
options: HookArgs & {
|
54
51
|
tx: Tx;
|
55
52
|
result: any;
|
56
53
|
/** This can be mutated to modify the response sent to the client */
|
@@ -59,7 +56,7 @@ export interface Hooks<Vocab extends string = string> {
|
|
59
56
|
) => HookResponse;
|
60
57
|
/** These are run in reverse translation order from newest to oldest */
|
61
58
|
'POSTRUN-ERROR'?: (
|
62
|
-
options: HookArgs
|
59
|
+
options: HookArgs & { tx: Tx; error: TypedError | any },
|
63
60
|
) => HookResponse;
|
64
61
|
}
|
65
62
|
export type HookBlueprints = {
|
@@ -265,9 +262,9 @@ const apiHooks = {
|
|
265
262
|
// Share hooks between merge and patch since they are the same operation,
|
266
263
|
// just MERGE was the OData intermediary until the HTTP spec added PATCH.
|
267
264
|
apiHooks.MERGE = apiHooks.PATCH;
|
268
|
-
export const addHook =
|
265
|
+
export const addHook = (
|
269
266
|
method: keyof typeof apiHooks,
|
270
|
-
vocabulary:
|
267
|
+
vocabulary: string,
|
271
268
|
resourceName: string,
|
272
269
|
hooks:
|
273
270
|
| { [key in keyof Hooks]: HookBlueprint<NonNullable<Hooks[key]>> }
|
@@ -346,11 +343,11 @@ export const addHook = <Vocab extends string>(
|
|
346
343
|
getHooks.clear();
|
347
344
|
};
|
348
345
|
|
349
|
-
export const addSideEffectHook =
|
346
|
+
export const addSideEffectHook = (
|
350
347
|
method: HookMethod,
|
351
|
-
apiRoot:
|
348
|
+
apiRoot: string,
|
352
349
|
resourceName: string,
|
353
|
-
hooks: Hooks
|
350
|
+
hooks: Hooks,
|
354
351
|
): void => {
|
355
352
|
addHook(method, apiRoot, resourceName, {
|
356
353
|
...hooks,
|
@@ -359,11 +356,11 @@ export const addSideEffectHook = <Vocab extends string>(
|
|
359
356
|
});
|
360
357
|
};
|
361
358
|
|
362
|
-
export const addPureHook =
|
359
|
+
export const addPureHook = (
|
363
360
|
method: HookMethod,
|
364
|
-
apiRoot:
|
361
|
+
apiRoot: string,
|
365
362
|
resourceName: string,
|
366
|
-
hooks: Hooks
|
363
|
+
hooks: Hooks,
|
367
364
|
): void => {
|
368
365
|
addHook(method, apiRoot, resourceName, {
|
369
366
|
...hooks,
|
@@ -1,4 +1,3 @@
|
|
1
|
-
import type AuthModel from './user';
|
2
1
|
import type {
|
3
2
|
AbstractSqlModel,
|
4
3
|
AbstractSqlQuery,
|
@@ -52,7 +51,6 @@ import {
|
|
52
51
|
type ODataRequest,
|
53
52
|
} from './uri-parser';
|
54
53
|
import memoizeWeak = require('memoizee/weak');
|
55
|
-
import type { Config } from '../config-loader/config-loader';
|
56
54
|
|
57
55
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
58
56
|
const userModel: string = require('./user.sbvr');
|
@@ -1120,7 +1118,7 @@ const memoizedGetConstrainedModel = (
|
|
1120
1118
|
getBoundConstrainedMemoizer(abstractSqlModel)(permissionsLookup, vocabulary);
|
1121
1119
|
|
1122
1120
|
const getCheckPasswordQuery = _.once(() =>
|
1123
|
-
sbvrUtils.api.Auth.prepare<{ username: string }
|
1121
|
+
sbvrUtils.api.Auth.prepare<{ username: string }>({
|
1124
1122
|
resource: 'user',
|
1125
1123
|
passthrough: {
|
1126
1124
|
req: rootRead,
|
@@ -1166,7 +1164,7 @@ export const checkPassword = async (
|
|
1166
1164
|
|
1167
1165
|
const $getUserPermissions = (() => {
|
1168
1166
|
const getUserPermissionsQuery = _.once(() =>
|
1169
|
-
sbvrUtils.api.Auth.prepare<{ userId: number }
|
1167
|
+
sbvrUtils.api.Auth.prepare<{ userId: number }>({
|
1170
1168
|
resource: 'permission',
|
1171
1169
|
passthrough: {
|
1172
1170
|
req: rootRead,
|
@@ -1277,7 +1275,7 @@ export const getUserPermissions = async (
|
|
1277
1275
|
|
1278
1276
|
const $getApiKeyPermissions = (() => {
|
1279
1277
|
const getApiKeyPermissionsQuery = _.once(() =>
|
1280
|
-
sbvrUtils.api.Auth.prepare<{ apiKey: string }
|
1278
|
+
sbvrUtils.api.Auth.prepare<{ apiKey: string }>({
|
1281
1279
|
resource: 'permission',
|
1282
1280
|
passthrough: {
|
1283
1281
|
req: rootRead,
|
@@ -1405,7 +1403,7 @@ export const getApiKeyPermissions = async (
|
|
1405
1403
|
|
1406
1404
|
const getApiKeyActorId = (() => {
|
1407
1405
|
const getApiKeyActorIdQuery = _.once(() =>
|
1408
|
-
sbvrUtils.api.Auth.prepare<{ apiKey: string }
|
1406
|
+
sbvrUtils.api.Auth.prepare<{ apiKey: string }>({
|
1409
1407
|
resource: 'api_key',
|
1410
1408
|
passthrough: {
|
1411
1409
|
req: rootRead,
|
@@ -1588,7 +1586,7 @@ let guestPermissionsInitialized = false;
|
|
1588
1586
|
const getGuestPermissions = memoize(
|
1589
1587
|
async () => {
|
1590
1588
|
// Get guest user
|
1591
|
-
const result = await sbvrUtils.api.Auth.get({
|
1589
|
+
const result = (await sbvrUtils.api.Auth.get({
|
1592
1590
|
resource: 'user',
|
1593
1591
|
passthrough: {
|
1594
1592
|
req: rootRead,
|
@@ -1599,7 +1597,7 @@ const getGuestPermissions = memoize(
|
|
1599
1597
|
options: {
|
1600
1598
|
$select: 'id',
|
1601
1599
|
},
|
1602
|
-
});
|
1600
|
+
})) as { id: number } | undefined;
|
1603
1601
|
if (result == null) {
|
1604
1602
|
throw new Error('No guest user');
|
1605
1603
|
}
|
@@ -1688,53 +1686,49 @@ export const addPermissions = async (
|
|
1688
1686
|
}
|
1689
1687
|
};
|
1690
1688
|
|
1691
|
-
declare module './sbvr-utils' {
|
1692
|
-
export interface API {
|
1693
|
-
[authModelConfig.apiRoot]: PinejsClient<AuthModel>;
|
1694
|
-
}
|
1695
|
-
}
|
1696
|
-
const authModelConfig = {
|
1697
|
-
apiRoot: 'Auth',
|
1698
|
-
modelText: userModel,
|
1699
|
-
customServerCode: exports,
|
1700
|
-
migrations: {
|
1701
|
-
'11.0.0-modified-at': `
|
1702
|
-
ALTER TABLE "actor"
|
1703
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1704
|
-
|
1705
|
-
ALTER TABLE "api key"
|
1706
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1707
|
-
ALTER TABLE "api key-has-permission"
|
1708
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1709
|
-
ALTER TABLE "api key-has-role"
|
1710
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1711
|
-
|
1712
|
-
ALTER TABLE "permission"
|
1713
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1714
|
-
|
1715
|
-
ALTER TABLE "role"
|
1716
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1717
|
-
|
1718
|
-
ALTER TABLE "user"
|
1719
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1720
|
-
ALTER TABLE "user-has-role"
|
1721
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1722
|
-
ALTER TABLE "user-has-permission"
|
1723
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1724
|
-
`,
|
1725
|
-
'11.0.1-modified-at': `
|
1726
|
-
ALTER TABLE "role-has-permission"
|
1727
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1728
|
-
`,
|
1729
|
-
'14.42.0-api-key-expiry-date': `
|
1730
|
-
ALTER TABLE "api key"
|
1731
|
-
ADD COLUMN IF NOT EXISTS "expiry date" TIMESTAMP NULL;
|
1732
|
-
`,
|
1733
|
-
},
|
1734
|
-
} as const satisfies sbvrUtils.ExecutableModel;
|
1735
1689
|
export const config = {
|
1736
|
-
models: [
|
1737
|
-
|
1690
|
+
models: [
|
1691
|
+
{
|
1692
|
+
apiRoot: 'Auth',
|
1693
|
+
modelText: userModel,
|
1694
|
+
customServerCode: exports,
|
1695
|
+
migrations: {
|
1696
|
+
'11.0.0-modified-at': `
|
1697
|
+
ALTER TABLE "actor"
|
1698
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1699
|
+
|
1700
|
+
ALTER TABLE "api key"
|
1701
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1702
|
+
ALTER TABLE "api key-has-permission"
|
1703
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1704
|
+
ALTER TABLE "api key-has-role"
|
1705
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1706
|
+
|
1707
|
+
ALTER TABLE "permission"
|
1708
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1709
|
+
|
1710
|
+
ALTER TABLE "role"
|
1711
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1712
|
+
|
1713
|
+
ALTER TABLE "user"
|
1714
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1715
|
+
ALTER TABLE "user-has-role"
|
1716
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1717
|
+
ALTER TABLE "user-has-permission"
|
1718
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1719
|
+
`,
|
1720
|
+
'11.0.1-modified-at': `
|
1721
|
+
ALTER TABLE "role-has-permission"
|
1722
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1723
|
+
`,
|
1724
|
+
'14.42.0-api-key-expiry-date': `
|
1725
|
+
ALTER TABLE "api key"
|
1726
|
+
ADD COLUMN IF NOT EXISTS "expiry date" TIMESTAMP NULL;
|
1727
|
+
`,
|
1728
|
+
},
|
1729
|
+
},
|
1730
|
+
] as sbvrUtils.ExecutableModel[],
|
1731
|
+
};
|
1738
1732
|
export const setup = () => {
|
1739
1733
|
addHook('all', 'all', 'all', {
|
1740
1734
|
sideEffects: false,
|
@@ -2,7 +2,6 @@ import type * as Express from 'express';
|
|
2
2
|
import type * as Db from '../database-layer/db';
|
3
3
|
import type { Model } from '../config-loader/config-loader';
|
4
4
|
import type { AnyObject, RequiredField } from './common-types';
|
5
|
-
import type { Resource } from '@balena/abstract-sql-to-typescript';
|
6
5
|
|
7
6
|
// Augment the Express typings
|
8
7
|
declare global {
|
@@ -33,7 +32,6 @@ import {
|
|
33
32
|
} from '@balena/odata-to-abstract-sql';
|
34
33
|
import sbvrTypes from '@balena/sbvr-types';
|
35
34
|
import deepFreeze = require('deep-freeze');
|
36
|
-
import type { AnyResource, Params } from 'pinejs-client-core';
|
37
35
|
import { PinejsClientCore, type PromiseResultTypes } from 'pinejs-client-core';
|
38
36
|
|
39
37
|
import { ExtendedSBVRParser } from '../extended-sbvr-parser/extended-sbvr-parser';
|
@@ -42,9 +40,9 @@ import * as asyncMigrator from '../migrator/async';
|
|
42
40
|
import * as syncMigrator from '../migrator/sync';
|
43
41
|
import { generateODataMetadata } from '../odata-metadata/odata-metadata-generator';
|
44
42
|
|
45
|
-
import type DevModel from './dev';
|
46
43
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
47
44
|
const devModel = require('./dev.sbvr');
|
45
|
+
import * as tasks from '../tasks';
|
48
46
|
import * as permissions from './permissions';
|
49
47
|
import {
|
50
48
|
BadRequestError,
|
@@ -80,6 +78,7 @@ export {
|
|
80
78
|
addPureHook,
|
81
79
|
addSideEffectHook,
|
82
80
|
} from './hooks';
|
81
|
+
export { addTaskHandler } from '../tasks';
|
83
82
|
|
84
83
|
import memoizeWeak = require('memoizee/weak');
|
85
84
|
import * as controlFlow from './control-flow';
|
@@ -713,7 +712,7 @@ export const executeModels = async (
|
|
713
712
|
},
|
714
713
|
});
|
715
714
|
}
|
716
|
-
const result = await api.dev.get({
|
715
|
+
const result = (await api.dev.get({
|
717
716
|
resource: 'model',
|
718
717
|
passthrough: {
|
719
718
|
tx,
|
@@ -726,7 +725,7 @@ export const executeModels = async (
|
|
726
725
|
model_type: modelType,
|
727
726
|
},
|
728
727
|
},
|
729
|
-
})
|
728
|
+
})) as Array<{ id: number }>;
|
730
729
|
|
731
730
|
let method: SupportedMethod = 'POST';
|
732
731
|
let uri = '/dev/model';
|
@@ -776,7 +775,7 @@ export const postExecuteModels = async (tx: Db.Tx): Promise<void> => {
|
|
776
775
|
// Hence, skipped migrations from earlier models are not set as executed as the `migration` table is missing
|
777
776
|
// Here the skipped migrations that haven't been set properly are covered
|
778
777
|
// This is mostly an edge case when running on an empty database schema and migrations model hasn't been executed, yet.
|
779
|
-
// One
|
778
|
+
// One specific case are tests to run tests against migrated and unmigrated database states
|
780
779
|
|
781
780
|
for (const modelKey of Object.keys(models)) {
|
782
781
|
const pendingToSetExecutedMigrations =
|
@@ -786,6 +785,9 @@ export const postExecuteModels = async (tx: Db.Tx): Promise<void> => {
|
|
786
785
|
await setExecutedMigrations(tx, modelKey, pendingToSetExecutedMigrations);
|
787
786
|
}
|
788
787
|
}
|
788
|
+
|
789
|
+
// Initialize task worker and create required hooks
|
790
|
+
await tasks.setup(db);
|
789
791
|
};
|
790
792
|
|
791
793
|
const cleanupModel = (vocab: string) => {
|
@@ -1005,16 +1007,7 @@ export type Passthrough = AnyObject & {
|
|
1005
1007
|
tx?: Db.Tx;
|
1006
1008
|
};
|
1007
1009
|
|
1008
|
-
export class PinejsClient
|
1009
|
-
M extends {
|
1010
|
-
[key in keyof M]: Resource;
|
1011
|
-
} = {
|
1012
|
-
[key in string]: {
|
1013
|
-
Read: AnyObject;
|
1014
|
-
Write: AnyObject;
|
1015
|
-
};
|
1016
|
-
},
|
1017
|
-
> extends PinejsClientCore<unknown, M> {
|
1010
|
+
export class PinejsClient extends PinejsClientCore {
|
1018
1011
|
public async _request({
|
1019
1012
|
method,
|
1020
1013
|
url,
|
@@ -1032,37 +1025,11 @@ export class PinejsClient<
|
|
1032
1025
|
}) {
|
1033
1026
|
return (await runURI(method, url, body, tx, req, custom)) as object;
|
1034
1027
|
}
|
1035
|
-
|
1036
|
-
public post<TResource extends keyof M & string>(
|
1037
|
-
params: {
|
1038
|
-
resource: TResource;
|
1039
|
-
options?: Params<M[TResource]>['options'] & { returnResource?: true };
|
1040
|
-
} & Params<M[TResource]>,
|
1041
|
-
): Promise<M[TResource]['Read']>;
|
1042
|
-
public post<TResource extends keyof M & string>(
|
1043
|
-
params: {
|
1044
|
-
resource: TResource;
|
1045
|
-
options: Params<M[TResource]>['options'] & { returnResource: boolean };
|
1046
|
-
} & Params<M[TResource]>,
|
1047
|
-
): Promise<Pick<M[TResource]['Read'], 'id'>>; // TODO: This should use the primary key rather than hardcoding `id`
|
1048
|
-
/**
|
1049
|
-
* @deprecated POSTing via `url` is deprecated
|
1050
|
-
*/
|
1051
|
-
public post<T extends Resource = AnyResource>(
|
1052
|
-
params: {
|
1053
|
-
resource?: undefined;
|
1054
|
-
url: NonNullable<Params<T>['url']>;
|
1055
|
-
} & Params<T>,
|
1056
|
-
): Promise<AnyObject>;
|
1057
|
-
public post(params: Params<AnyResource>): Promise<AnyObject> {
|
1058
|
-
return super.post(params as Parameters<PinejsClient['post']>[0]);
|
1059
|
-
}
|
1060
1028
|
}
|
1061
1029
|
|
1062
|
-
export
|
1030
|
+
export const api: {
|
1063
1031
|
[vocab: string]: PinejsClient;
|
1064
|
-
}
|
1065
|
-
export const api = {} as API;
|
1032
|
+
} = {};
|
1066
1033
|
export const logger: {
|
1067
1034
|
[vocab: string]: Console;
|
1068
1035
|
} = {};
|
@@ -1169,8 +1136,8 @@ const getIdField = (
|
|
1169
1136
|
// TODO: Should resolveSynonym also be using the finalAbstractSqlModel?
|
1170
1137
|
getFinalAbstractSqlModel(request).tables[resolveSynonym(request)].idField;
|
1171
1138
|
|
1172
|
-
export const getAffectedIds = async
|
1173
|
-
args: HookArgs
|
1139
|
+
export const getAffectedIds = async (
|
1140
|
+
args: HookArgs & {
|
1174
1141
|
tx: Db.Tx;
|
1175
1142
|
},
|
1176
1143
|
): Promise<number[]> => {
|
@@ -1192,11 +1159,11 @@ export const getAffectedIds = async <Vocab extends string>(
|
|
1192
1159
|
return request.affectedIds;
|
1193
1160
|
};
|
1194
1161
|
|
1195
|
-
const $getAffectedIds = async
|
1162
|
+
const $getAffectedIds = async ({
|
1196
1163
|
req,
|
1197
1164
|
request,
|
1198
1165
|
tx,
|
1199
|
-
}: HookArgs
|
1166
|
+
}: HookArgs & {
|
1200
1167
|
tx: Db.Tx;
|
1201
1168
|
}): Promise<number[]> => {
|
1202
1169
|
if (!['PATCH', 'DELETE'].includes(request.method)) {
|
@@ -1980,51 +1947,47 @@ const runDelete = async (
|
|
1980
1947
|
return undefined;
|
1981
1948
|
};
|
1982
1949
|
|
1983
|
-
export interface API {
|
1984
|
-
[devModelConfig.apiRoot]: PinejsClient<DevModel>;
|
1985
|
-
}
|
1986
|
-
const devModelConfig = {
|
1987
|
-
apiRoot: 'dev',
|
1988
|
-
modelText: devModel,
|
1989
|
-
logging: {
|
1990
|
-
log: false,
|
1991
|
-
},
|
1992
|
-
migrations: {
|
1993
|
-
'11.0.0-modified-at': `
|
1994
|
-
ALTER TABLE "model"
|
1995
|
-
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1996
|
-
`,
|
1997
|
-
'15.0.0-data-types': async ($tx, sbvrUtils) => {
|
1998
|
-
switch (sbvrUtils.db.engine) {
|
1999
|
-
case 'mysql':
|
2000
|
-
await $tx.executeSql(`\
|
2001
|
-
ALTER TABLE "model"
|
2002
|
-
MODIFY "model value" JSON NOT NULL;
|
2003
|
-
|
2004
|
-
UPDATE "model"
|
2005
|
-
SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
|
2006
|
-
WHERE "model type" IN ('se', 'odataMetadata')
|
2007
|
-
AND CAST("model value" AS CHAR) LIKE '"%';`);
|
2008
|
-
break;
|
2009
|
-
case 'postgres':
|
2010
|
-
await $tx.executeSql(`\
|
2011
|
-
ALTER TABLE "model"
|
2012
|
-
ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
|
2013
|
-
|
2014
|
-
UPDATE "model"
|
2015
|
-
SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
|
2016
|
-
WHERE "model type" IN ('se', 'odataMetadata')
|
2017
|
-
AND CAST("model value" AS TEXT) LIKE '"%';`);
|
2018
|
-
break;
|
2019
|
-
// No need to migrate for websql
|
2020
|
-
}
|
2021
|
-
},
|
2022
|
-
},
|
2023
|
-
} as const satisfies ExecutableModel;
|
2024
1950
|
export const executeStandardModels = async (tx: Db.Tx): Promise<void> => {
|
2025
1951
|
try {
|
2026
1952
|
// dev model must run first
|
2027
|
-
await executeModel(tx,
|
1953
|
+
await executeModel(tx, {
|
1954
|
+
apiRoot: 'dev',
|
1955
|
+
modelText: devModel,
|
1956
|
+
logging: {
|
1957
|
+
log: false,
|
1958
|
+
},
|
1959
|
+
migrations: {
|
1960
|
+
'11.0.0-modified-at': `
|
1961
|
+
ALTER TABLE "model"
|
1962
|
+
ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
|
1963
|
+
`,
|
1964
|
+
'15.0.0-data-types': async ($tx, sbvrUtils) => {
|
1965
|
+
switch (sbvrUtils.db.engine) {
|
1966
|
+
case 'mysql':
|
1967
|
+
await $tx.executeSql(`\
|
1968
|
+
ALTER TABLE "model"
|
1969
|
+
MODIFY "model value" JSON NOT NULL;
|
1970
|
+
|
1971
|
+
UPDATE "model"
|
1972
|
+
SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
|
1973
|
+
WHERE "model type" IN ('se', 'odataMetadata')
|
1974
|
+
AND CAST("model value" AS CHAR) LIKE '"%';`);
|
1975
|
+
break;
|
1976
|
+
case 'postgres':
|
1977
|
+
await $tx.executeSql(`\
|
1978
|
+
ALTER TABLE "model"
|
1979
|
+
ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
|
1980
|
+
|
1981
|
+
UPDATE "model"
|
1982
|
+
SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
|
1983
|
+
WHERE "model type" IN ('se', 'odataMetadata')
|
1984
|
+
AND CAST("model value" AS TEXT) LIKE '"%';`);
|
1985
|
+
break;
|
1986
|
+
// No need to migrate for websql
|
1987
|
+
}
|
1988
|
+
},
|
1989
|
+
},
|
1990
|
+
});
|
2028
1991
|
await executeModels(tx, permissions.config.models);
|
2029
1992
|
console.info('Successfully executed standard models.');
|
2030
1993
|
} catch (err: any) {
|
@@ -6,6 +6,7 @@ import * as dbModule from '../database-layer/db';
|
|
6
6
|
import * as configLoader from '../config-loader/config-loader';
|
7
7
|
import * as migrator from '../migrator/sync';
|
8
8
|
import type * as migratorUtils from '../migrator/utils';
|
9
|
+
import * as tasks from '../tasks';
|
9
10
|
|
10
11
|
import * as sbvrUtils from '../sbvr-api/sbvr-utils';
|
11
12
|
import { PINEJS_ADVISORY_LOCK } from '../config-loader/env';
|
@@ -19,6 +20,7 @@ export * as errors from '../sbvr-api/errors';
|
|
19
20
|
export * as env from '../config-loader/env';
|
20
21
|
export * as types from '../sbvr-api/common-types';
|
21
22
|
export * as hooks from '../sbvr-api/hooks';
|
23
|
+
export * as tasks from '../tasks';
|
22
24
|
export * as webResourceHandler from '../webresource-handler';
|
23
25
|
export type { configLoader as ConfigLoader };
|
24
26
|
export type { migratorUtils as Migrator };
|
@@ -63,6 +65,7 @@ export const init = async <T extends string>(
|
|
63
65
|
await sbvrUtils.setup(app, db);
|
64
66
|
const cfgLoader = await configLoader.setup(app);
|
65
67
|
await cfgLoader.loadConfig(migrator.config);
|
68
|
+
await cfgLoader.loadConfig(tasks.config);
|
66
69
|
|
67
70
|
const promises: Array<Promise<void>> = [];
|
68
71
|
if (process.env.SBVR_SERVER_ENABLED) {
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import Ajv from 'ajv';
|
2
|
+
|
3
|
+
// Root path for the tasks API
|
4
|
+
export const apiRoot = 'tasks';
|
5
|
+
|
6
|
+
// Channel name for task insert notifications
|
7
|
+
export const channel = 'pinejs$task_insert';
|
8
|
+
|
9
|
+
// Setting inlineRefs=false as without it we run into a
|
10
|
+
// "Maximum call stack size exceeded" error apprarently caused
|
11
|
+
// by String.prototype._uncountable_words being set in sbvr-parser?
|
12
|
+
export const ajv = new Ajv({
|
13
|
+
inlineRefs: false,
|
14
|
+
});
|