@balena/pinejs 17.1.0-build-model-based-typings-85488426453e79d26b93e0ac91f30b73beb2c181-1 → 17.1.0-build-joshbwlng-tasks-0c116dc98102e3ddd4aa6e8a4f350a1e18891e1a-1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +4 -225
  3. package/CHANGELOG.md +2 -66
  4. package/out/config-loader/env.d.ts +4 -0
  5. package/out/config-loader/env.js +5 -1
  6. package/out/config-loader/env.js.map +1 -1
  7. package/out/data-server/sbvr-server.js +2 -3
  8. package/out/data-server/sbvr-server.js.map +1 -1
  9. package/out/database-layer/db.d.ts +3 -0
  10. package/out/database-layer/db.js +17 -0
  11. package/out/database-layer/db.js.map +1 -1
  12. package/out/migrator/sync.d.ts +0 -17
  13. package/out/migrator/sync.js +40 -39
  14. package/out/migrator/sync.js.map +1 -1
  15. package/out/sbvr-api/hooks.d.ts +26 -26
  16. package/out/sbvr-api/hooks.js.map +1 -1
  17. package/out/sbvr-api/permissions.d.ts +2 -26
  18. package/out/sbvr-api/permissions.js +40 -39
  19. package/out/sbvr-api/permissions.js.map +1 -1
  20. package/out/sbvr-api/sbvr-utils.d.ts +6 -46
  21. package/out/sbvr-api/sbvr-utils.js +44 -44
  22. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  23. package/out/server-glue/module.d.ts +1 -0
  24. package/out/server-glue/module.js +4 -1
  25. package/out/server-glue/module.js.map +1 -1
  26. package/out/tasks/common.d.ts +4 -0
  27. package/out/tasks/common.js +13 -0
  28. package/out/tasks/common.js.map +1 -0
  29. package/out/tasks/index.d.ts +8 -0
  30. package/out/tasks/index.js +142 -0
  31. package/out/tasks/index.js.map +1 -0
  32. package/out/tasks/tasks.sbvr +60 -0
  33. package/out/tasks/types.d.ts +38 -0
  34. package/out/tasks/types.js +10 -0
  35. package/out/tasks/types.js.map +1 -0
  36. package/out/tasks/worker.d.ts +16 -0
  37. package/out/tasks/worker.js +228 -0
  38. package/out/tasks/worker.js.map +1 -0
  39. package/package.json +19 -18
  40. package/src/config-loader/env.ts +6 -1
  41. package/src/data-server/sbvr-server.js +2 -3
  42. package/src/database-layer/db.ts +25 -0
  43. package/src/migrator/sync.ts +41 -46
  44. package/src/sbvr-api/hooks.ts +18 -21
  45. package/src/sbvr-api/permissions.ts +48 -54
  46. package/src/sbvr-api/sbvr-utils.ts +53 -90
  47. package/src/server-glue/module.ts +3 -0
  48. package/src/tasks/common.ts +14 -0
  49. package/src/tasks/index.ts +158 -0
  50. package/src/tasks/tasks.sbvr +60 -0
  51. package/src/tasks/types.ts +58 -0
  52. package/src/tasks/worker.ts +278 -0
  53. package/out/migrator/migrations.d.ts +0 -58
  54. package/out/migrator/migrations.js +0 -3
  55. package/out/migrator/migrations.js.map +0 -1
  56. package/out/sbvr-api/dev.d.ts +0 -22
  57. package/out/sbvr-api/dev.js +0 -3
  58. package/out/sbvr-api/dev.js.map +0 -1
  59. package/out/sbvr-api/user.d.ts +0 -236
  60. package/out/sbvr-api/user.js +0 -3
  61. package/out/sbvr-api/user.js.map +0 -1
  62. package/src/migrator/migrations.ts +0 -62
  63. package/src/sbvr-api/dev.ts +0 -24
  64. package/src/sbvr-api/user.ts +0 -214
@@ -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);
@@ -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 migrationsModel = require('./migrations.sbvr');
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: [migrationModelConfig],
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
  };
@@ -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<Vocab extends string = string> {
34
+ export interface HookArgs {
34
35
  req: HookReq;
35
36
  request: ODataRequest;
36
- api: (typeof api)[Vocab];
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<Vocab extends string = string> {
42
- PREPARSE?: (
43
- options: Omit<HookArgs<Vocab>, 'request' | 'api'>,
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<Vocab> & {
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<Vocab> & { tx: Tx; error: TypedError | any },
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 = <Vocab extends string>(
265
+ export const addHook = (
269
266
  method: keyof typeof apiHooks,
270
- vocabulary: Vocab,
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 = <Vocab extends string>(
346
+ export const addSideEffectHook = (
350
347
  method: HookMethod,
351
- apiRoot: Vocab,
348
+ apiRoot: string,
352
349
  resourceName: string,
353
- hooks: Hooks<NoInfer<Vocab>>,
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 = <Vocab extends string>(
359
+ export const addPureHook = (
363
360
  method: HookMethod,
364
- apiRoot: Vocab,
361
+ apiRoot: string,
365
362
  resourceName: string,
366
- hooks: Hooks<NoInfer<Vocab>>,
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 }, 'user'>({
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 }, 'permission'>({
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 }, 'permission'>({
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 }, 'api_key'>({
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: [authModelConfig],
1737
- } satisfies Config;
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 specifc case are tests to run tests against migrated and unmigrated database states
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 interface API {
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 <Vocab extends string>(
1173
- args: HookArgs<Vocab> & {
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 <Vocab extends string>({
1162
+ const $getAffectedIds = async ({
1196
1163
  req,
1197
1164
  request,
1198
1165
  tx,
1199
- }: HookArgs<Vocab> & {
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, devModelConfig);
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
+ });