@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.
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 +280 -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
+ });