@balena/pinejs 17.1.0-build-model-based-typings-85488426453e79d26b93e0ac91f30b73beb2c181-1 → 17.1.0-build-joshbwlng-tasks-0e1e17c7d0be1ee7977c72ee5f1cafb04d93b900-1
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/.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
|
+
});
|