@balena/pinejs 17.1.0-build-model-based-typings-437bb06f44567532aec78e550f3d545732466411-1 → 17.1.0-build-joshbwlng-tasks-29374df83aa26203cd952b1fe5d623d4f226d4dc-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 (68) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +16 -234
  3. package/CHANGELOG.md +6 -69
  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 +33 -33
  16. package/out/sbvr-api/hooks.js.map +1 -1
  17. package/out/sbvr-api/odata-response.d.ts +2 -1
  18. package/out/sbvr-api/odata-response.js +4 -4
  19. package/out/sbvr-api/odata-response.js.map +1 -1
  20. package/out/sbvr-api/permissions.d.ts +2 -26
  21. package/out/sbvr-api/permissions.js +40 -39
  22. package/out/sbvr-api/permissions.js.map +1 -1
  23. package/out/sbvr-api/sbvr-utils.d.ts +6 -46
  24. package/out/sbvr-api/sbvr-utils.js +76 -73
  25. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  26. package/out/server-glue/module.d.ts +1 -0
  27. package/out/server-glue/module.js +4 -1
  28. package/out/server-glue/module.js.map +1 -1
  29. package/out/tasks/common.d.ts +4 -0
  30. package/out/tasks/common.js +13 -0
  31. package/out/tasks/common.js.map +1 -0
  32. package/out/tasks/index.d.ts +8 -0
  33. package/out/tasks/index.js +142 -0
  34. package/out/tasks/index.js.map +1 -0
  35. package/out/tasks/tasks.sbvr +60 -0
  36. package/out/tasks/types.d.ts +38 -0
  37. package/out/tasks/types.js +10 -0
  38. package/out/tasks/types.js.map +1 -0
  39. package/out/tasks/worker.d.ts +16 -0
  40. package/out/tasks/worker.js +229 -0
  41. package/out/tasks/worker.js.map +1 -0
  42. package/package.json +20 -19
  43. package/src/config-loader/env.ts +6 -1
  44. package/src/data-server/sbvr-server.js +2 -3
  45. package/src/database-layer/db.ts +25 -0
  46. package/src/migrator/sync.ts +41 -46
  47. package/src/sbvr-api/hooks.ts +20 -21
  48. package/src/sbvr-api/odata-response.ts +13 -3
  49. package/src/sbvr-api/permissions.ts +48 -54
  50. package/src/sbvr-api/sbvr-utils.ts +92 -133
  51. package/src/server-glue/module.ts +3 -0
  52. package/src/tasks/common.ts +14 -0
  53. package/src/tasks/index.ts +158 -0
  54. package/src/tasks/tasks.sbvr +60 -0
  55. package/src/tasks/types.ts +58 -0
  56. package/src/tasks/worker.ts +279 -0
  57. package/out/migrator/migrations.d.ts +0 -58
  58. package/out/migrator/migrations.js +0 -3
  59. package/out/migrator/migrations.js.map +0 -1
  60. package/out/sbvr-api/dev.d.ts +0 -22
  61. package/out/sbvr-api/dev.js +0 -3
  62. package/out/sbvr-api/dev.js.map +0 -1
  63. package/out/sbvr-api/user.d.ts +0 -236
  64. package/out/sbvr-api/user.js +0 -3
  65. package/out/sbvr-api/user.js.map +0 -1
  66. package/src/migrator/migrations.ts +0 -64
  67. package/src/sbvr-api/dev.ts +0 -26
  68. package/src/sbvr-api/user.ts +0 -216
@@ -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,10 +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 {
6
- PickDeferred,
7
- Resource,
8
- } from '@balena/abstract-sql-to-typescript';
9
5
 
10
6
  // Augment the Express typings
11
7
  declare global {
@@ -36,7 +32,6 @@ import {
36
32
  } from '@balena/odata-to-abstract-sql';
37
33
  import sbvrTypes from '@balena/sbvr-types';
38
34
  import deepFreeze = require('deep-freeze');
39
- import type { AnyResource, Params } from 'pinejs-client-core';
40
35
  import { PinejsClientCore, type PromiseResultTypes } from 'pinejs-client-core';
41
36
 
42
37
  import { ExtendedSBVRParser } from '../extended-sbvr-parser/extended-sbvr-parser';
@@ -45,9 +40,9 @@ import * as asyncMigrator from '../migrator/async';
45
40
  import * as syncMigrator from '../migrator/sync';
46
41
  import { generateODataMetadata } from '../odata-metadata/odata-metadata-generator';
47
42
 
48
- import type DevModel from './dev';
49
43
  // eslint-disable-next-line @typescript-eslint/no-var-requires
50
44
  const devModel = require('./dev.sbvr');
45
+ import * as tasks from '../tasks';
51
46
  import * as permissions from './permissions';
52
47
  import {
53
48
  BadRequestError,
@@ -83,6 +78,7 @@ export {
83
78
  addPureHook,
84
79
  addSideEffectHook,
85
80
  } from './hooks';
81
+ export { addTaskHandler } from '../tasks';
86
82
 
87
83
  import memoizeWeak = require('memoizee/weak');
88
84
  import * as controlFlow from './control-flow';
@@ -716,7 +712,7 @@ export const executeModels = async (
716
712
  },
717
713
  });
718
714
  }
719
- const result = await api.dev.get({
715
+ const result = (await api.dev.get({
720
716
  resource: 'model',
721
717
  passthrough: {
722
718
  tx,
@@ -729,7 +725,7 @@ export const executeModels = async (
729
725
  model_type: modelType,
730
726
  },
731
727
  },
732
- });
728
+ })) as Array<{ id: number }>;
733
729
 
734
730
  let method: SupportedMethod = 'POST';
735
731
  let uri = '/dev/model';
@@ -779,7 +775,7 @@ export const postExecuteModels = async (tx: Db.Tx): Promise<void> => {
779
775
  // Hence, skipped migrations from earlier models are not set as executed as the `migration` table is missing
780
776
  // Here the skipped migrations that haven't been set properly are covered
781
777
  // This is mostly an edge case when running on an empty database schema and migrations model hasn't been executed, yet.
782
- // 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
783
779
 
784
780
  for (const modelKey of Object.keys(models)) {
785
781
  const pendingToSetExecutedMigrations =
@@ -789,6 +785,9 @@ export const postExecuteModels = async (tx: Db.Tx): Promise<void> => {
789
785
  await setExecutedMigrations(tx, modelKey, pendingToSetExecutedMigrations);
790
786
  }
791
787
  }
788
+
789
+ // Initialize task worker and create required hooks
790
+ await tasks.setup(db);
792
791
  };
793
792
 
794
793
  const cleanupModel = (vocab: string) => {
@@ -1008,16 +1007,7 @@ export type Passthrough = AnyObject & {
1008
1007
  tx?: Db.Tx;
1009
1008
  };
1010
1009
 
1011
- export class PinejsClient<
1012
- M extends {
1013
- [key in keyof M]: Resource;
1014
- } = {
1015
- [key in string]: {
1016
- Read: AnyObject;
1017
- Write: AnyObject;
1018
- };
1019
- },
1020
- > extends PinejsClientCore<unknown, M> {
1010
+ export class PinejsClient extends PinejsClientCore {
1021
1011
  public async _request({
1022
1012
  method,
1023
1013
  url,
@@ -1035,37 +1025,11 @@ export class PinejsClient<
1035
1025
  }) {
1036
1026
  return (await runURI(method, url, body, tx, req, custom)) as object;
1037
1027
  }
1038
-
1039
- public post<TResource extends keyof M & string>(
1040
- params: {
1041
- resource: TResource;
1042
- options?: Params<M[TResource]>['options'] & { returnResource?: true };
1043
- } & Params<M[TResource]>,
1044
- ): Promise<PickDeferred<M[TResource]['Read']>>;
1045
- public post<TResource extends keyof M & string>(
1046
- params: {
1047
- resource: TResource;
1048
- options: Params<M[TResource]>['options'] & { returnResource: boolean };
1049
- } & Params<M[TResource]>,
1050
- ): Promise<Pick<M[TResource]['Read'], 'id'>>; // TODO: This should use the primary key rather than hardcoding `id`
1051
- /**
1052
- * @deprecated POSTing via `url` is deprecated
1053
- */
1054
- public post<T extends Resource = AnyResource>(
1055
- params: {
1056
- resource?: undefined;
1057
- url: NonNullable<Params<T>['url']>;
1058
- } & Params<T>,
1059
- ): Promise<AnyObject>;
1060
- public post(params: Params<AnyResource>): Promise<AnyObject> {
1061
- return super.post(params as Parameters<PinejsClient['post']>[0]);
1062
- }
1063
1028
  }
1064
1029
 
1065
- export interface API {
1030
+ export const api: {
1066
1031
  [vocab: string]: PinejsClient;
1067
- }
1068
- export const api = {} as API;
1032
+ } = {};
1069
1033
  export const logger: {
1070
1034
  [vocab: string]: Console;
1071
1035
  } = {};
@@ -1172,8 +1136,8 @@ const getIdField = (
1172
1136
  // TODO: Should resolveSynonym also be using the finalAbstractSqlModel?
1173
1137
  getFinalAbstractSqlModel(request).tables[resolveSynonym(request)].idField;
1174
1138
 
1175
- export const getAffectedIds = async <Vocab extends string>(
1176
- args: HookArgs<Vocab> & {
1139
+ export const getAffectedIds = async (
1140
+ args: HookArgs & {
1177
1141
  tx: Db.Tx;
1178
1142
  },
1179
1143
  ): Promise<number[]> => {
@@ -1195,11 +1159,11 @@ export const getAffectedIds = async <Vocab extends string>(
1195
1159
  return request.affectedIds;
1196
1160
  };
1197
1161
 
1198
- const $getAffectedIds = async <Vocab extends string>({
1162
+ const $getAffectedIds = async ({
1199
1163
  req,
1200
1164
  request,
1201
1165
  tx,
1202
- }: HookArgs<Vocab> & {
1166
+ }: HookArgs & {
1203
1167
  tx: Db.Tx;
1204
1168
  }): Promise<number[]> => {
1205
1169
  if (!['PATCH', 'DELETE'].includes(request.method)) {
@@ -1582,7 +1546,8 @@ const runRequest = async (
1582
1546
  if (env.DEBUG) {
1583
1547
  log.log('Running', req.method, req.url);
1584
1548
  }
1585
- let result: Db.Result | number | undefined;
1549
+ let resultGet: Db.Result | undefined;
1550
+ let resultPost: number | undefined;
1586
1551
 
1587
1552
  try {
1588
1553
  try {
@@ -1591,18 +1556,18 @@ const runRequest = async (
1591
1556
 
1592
1557
  switch (request.method) {
1593
1558
  case 'GET':
1594
- result = await runGet(req, request, tx);
1559
+ resultGet = await runGet(req, request, tx);
1595
1560
  break;
1596
1561
  case 'POST':
1597
- result = await runPost(req, request, tx);
1562
+ resultPost = await runPost(req, request, tx);
1598
1563
  break;
1599
1564
  case 'PUT':
1600
1565
  case 'PATCH':
1601
1566
  case 'MERGE':
1602
- result = await runPut(req, request, tx);
1567
+ await runPut(req, request, tx);
1603
1568
  break;
1604
1569
  case 'DELETE':
1605
- result = await runDelete(req, request, tx);
1570
+ await runDelete(req, request, tx);
1606
1571
  break;
1607
1572
  }
1608
1573
  } catch (err: any) {
@@ -1628,7 +1593,12 @@ const runRequest = async (
1628
1593
  throw err;
1629
1594
  }
1630
1595
 
1631
- await runHooks('POSTRUN', request.hooks, { req, request, result, tx });
1596
+ await runHooks('POSTRUN', request.hooks, {
1597
+ req,
1598
+ request,
1599
+ result: resultGet ?? resultPost,
1600
+ tx,
1601
+ });
1632
1602
  } catch (err: any) {
1633
1603
  await runHooks('POSTRUN-ERROR', request.hooks, {
1634
1604
  req,
@@ -1638,7 +1608,23 @@ const runRequest = async (
1638
1608
  });
1639
1609
  throw err;
1640
1610
  }
1641
- return await prepareResponse(req, request, result, tx);
1611
+
1612
+ switch (request.method) {
1613
+ case 'GET':
1614
+ return await respondGet(req, request, resultGet, tx);
1615
+ case 'POST':
1616
+ return await respondPost(req, request, resultPost, tx);
1617
+ case 'PUT':
1618
+ case 'PATCH':
1619
+ case 'MERGE':
1620
+ return await respondPut(req, request, tx);
1621
+ case 'DELETE':
1622
+ return await respondDelete(req, request, tx);
1623
+ case 'OPTIONS':
1624
+ return await respondOptions(req, request, tx);
1625
+ default:
1626
+ throw new MethodNotAllowedError();
1627
+ }
1642
1628
  };
1643
1629
 
1644
1630
  const runChangeSet =
@@ -1686,30 +1672,6 @@ const updateBinds = (
1686
1672
  return request;
1687
1673
  };
1688
1674
 
1689
- const prepareResponse = async (
1690
- req: Express.Request,
1691
- request: uriParser.ODataRequest,
1692
- result: any,
1693
- tx: Db.Tx,
1694
- ): Promise<Response> => {
1695
- switch (request.method) {
1696
- case 'GET':
1697
- return await respondGet(req, request, result, tx);
1698
- case 'POST':
1699
- return await respondPost(req, request, result, tx);
1700
- case 'PUT':
1701
- case 'PATCH':
1702
- case 'MERGE':
1703
- return await respondPut(req, request, result, tx);
1704
- case 'DELETE':
1705
- return await respondDelete(req, request, result, tx);
1706
- case 'OPTIONS':
1707
- return await respondOptions(req, request, result, tx);
1708
- default:
1709
- throw new MethodNotAllowedError();
1710
- }
1711
- };
1712
-
1713
1675
  const checkReadOnlyRequests = (request: uriParser.ODataRequest) => {
1714
1676
  if (request.method !== 'GET') {
1715
1677
  // Only GET requests can be read-only
@@ -1811,11 +1773,17 @@ const runGet = async (
1811
1773
  const respondGet = async (
1812
1774
  req: Express.Request,
1813
1775
  request: uriParser.ODataRequest,
1814
- result: any,
1776
+ result: Db.Result | undefined,
1815
1777
  tx: Db.Tx,
1816
1778
  ): Promise<Response> => {
1817
1779
  const vocab = request.vocabulary;
1818
1780
  if (request.sqlQuery != null) {
1781
+ if (result == null) {
1782
+ // This shouldn't be able to happen because the result should only be null if there's no sqlQuery
1783
+ throw new Error(
1784
+ 'Null result passed to respond GET that has a sqlQuery defined',
1785
+ );
1786
+ }
1819
1787
  const format = request.odataQuery.options?.$format;
1820
1788
  const metadata =
1821
1789
  format != null && typeof format === 'object'
@@ -1880,7 +1848,7 @@ const runPost = async (
1880
1848
  const respondPost = async (
1881
1849
  req: Express.Request,
1882
1850
  request: uriParser.ODataRequest,
1883
- id: number,
1851
+ id: number | undefined,
1884
1852
  tx: Db.Tx,
1885
1853
  ): Promise<Response> => {
1886
1854
  const vocab = request.vocabulary;
@@ -1930,7 +1898,7 @@ const runPut = async (
1930
1898
  _req: Express.Request,
1931
1899
  request: uriParser.ODataRequest,
1932
1900
  tx: Db.Tx,
1933
- ): Promise<undefined> => {
1901
+ ): Promise<void> => {
1934
1902
  let rowsAffected: number;
1935
1903
  // If request.sqlQuery is an array it means it's an UPSERT, ie two queries: [InsertQuery, UpdateQuery]
1936
1904
  if (Array.isArray(request.sqlQuery)) {
@@ -1946,13 +1914,11 @@ const runPut = async (
1946
1914
  if (rowsAffected > 0) {
1947
1915
  await validateModel(tx, _.last(request.translateVersions)!, request);
1948
1916
  }
1949
- return undefined;
1950
1917
  };
1951
1918
 
1952
1919
  const respondPut = async (
1953
1920
  req: Express.Request,
1954
1921
  request: uriParser.ODataRequest,
1955
- result: any,
1956
1922
  tx: Db.Tx,
1957
1923
  ): Promise<Response> => {
1958
1924
  const response = {
@@ -1961,7 +1927,6 @@ const respondPut = async (
1961
1927
  await runHooks('PRERESPOND', request.hooks, {
1962
1928
  req,
1963
1929
  request,
1964
- result,
1965
1930
  response,
1966
1931
  tx,
1967
1932
  });
@@ -1974,60 +1939,54 @@ const runDelete = async (
1974
1939
  _req: Express.Request,
1975
1940
  request: uriParser.ODataRequest,
1976
1941
  tx: Db.Tx,
1977
- ): Promise<undefined> => {
1942
+ ): Promise<void> => {
1978
1943
  const { rowsAffected } = await runQuery(tx, request, undefined, true);
1979
1944
  if (rowsAffected > 0) {
1980
1945
  await validateModel(tx, _.last(request.translateVersions)!, request);
1981
1946
  }
1982
-
1983
- return undefined;
1984
1947
  };
1985
1948
 
1986
- export interface API {
1987
- [devModelConfig.apiRoot]: PinejsClient<DevModel>;
1988
- }
1989
- const devModelConfig = {
1990
- apiRoot: 'dev',
1991
- modelText: devModel,
1992
- logging: {
1993
- log: false,
1994
- },
1995
- migrations: {
1996
- '11.0.0-modified-at': `
1997
- ALTER TABLE "model"
1998
- ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
1999
- `,
2000
- '15.0.0-data-types': async ($tx, sbvrUtils) => {
2001
- switch (sbvrUtils.db.engine) {
2002
- case 'mysql':
2003
- await $tx.executeSql(`\
2004
- ALTER TABLE "model"
2005
- MODIFY "model value" JSON NOT NULL;
2006
-
2007
- UPDATE "model"
2008
- SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
2009
- WHERE "model type" IN ('se', 'odataMetadata')
2010
- AND CAST("model value" AS CHAR) LIKE '"%';`);
2011
- break;
2012
- case 'postgres':
2013
- await $tx.executeSql(`\
2014
- ALTER TABLE "model"
2015
- ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
2016
-
2017
- UPDATE "model"
2018
- SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
2019
- WHERE "model type" IN ('se', 'odataMetadata')
2020
- AND CAST("model value" AS TEXT) LIKE '"%';`);
2021
- break;
2022
- // No need to migrate for websql
2023
- }
2024
- },
2025
- },
2026
- } as const satisfies ExecutableModel;
2027
1949
  export const executeStandardModels = async (tx: Db.Tx): Promise<void> => {
2028
1950
  try {
2029
1951
  // dev model must run first
2030
- await executeModel(tx, devModelConfig);
1952
+ await executeModel(tx, {
1953
+ apiRoot: 'dev',
1954
+ modelText: devModel,
1955
+ logging: {
1956
+ log: false,
1957
+ },
1958
+ migrations: {
1959
+ '11.0.0-modified-at': `
1960
+ ALTER TABLE "model"
1961
+ ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
1962
+ `,
1963
+ '15.0.0-data-types': async ($tx, sbvrUtils) => {
1964
+ switch (sbvrUtils.db.engine) {
1965
+ case 'mysql':
1966
+ await $tx.executeSql(`\
1967
+ ALTER TABLE "model"
1968
+ MODIFY "model value" JSON NOT NULL;
1969
+
1970
+ UPDATE "model"
1971
+ SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
1972
+ WHERE "model type" IN ('se', 'odataMetadata')
1973
+ AND CAST("model value" AS CHAR) LIKE '"%';`);
1974
+ break;
1975
+ case 'postgres':
1976
+ await $tx.executeSql(`\
1977
+ ALTER TABLE "model"
1978
+ ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
1979
+
1980
+ UPDATE "model"
1981
+ SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
1982
+ WHERE "model type" IN ('se', 'odataMetadata')
1983
+ AND CAST("model value" AS TEXT) LIKE '"%';`);
1984
+ break;
1985
+ // No need to migrate for websql
1986
+ }
1987
+ },
1988
+ },
1989
+ });
2031
1990
  await executeModels(tx, permissions.config.models);
2032
1991
  console.info('Successfully executed standard models.');
2033
1992
  } 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
+ });