@balena/pinejs 17.1.0-build-model-based-typings-437bb06f44567532aec78e550f3d545732466411-1 → 17.1.0-build-joshbwlng-tasks-61ce10e444abec6afea3fec43e9a5c37c7cedea6-1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +13 -239
  3. package/CHANGELOG.md +5 -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 +228 -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 +278 -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
+ });