@balena/pinejs 14.59.1 → 15.0.0-build-15-x-9f7b9bfe63741d7ae5f446e013251758d9d6ca26-1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. package/.pinejs-cache.json +1 -1
  2. package/.versionbot/CHANGELOG.yml +110 -1
  3. package/CHANGELOG.md +18 -1
  4. package/VERSION +1 -1
  5. package/build/browser.ts +0 -1
  6. package/out/bin/abstract-sql-compiler.js +0 -0
  7. package/out/bin/abstract-sql-compiler.js.map +1 -1
  8. package/out/bin/odata-compiler.js +0 -0
  9. package/out/bin/sbvr-compiler.js +0 -0
  10. package/out/bin/sbvr-compiler.js.map +1 -1
  11. package/out/config-loader/config-loader.js +3 -6
  12. package/out/config-loader/config-loader.js.map +1 -1
  13. package/out/config-loader/env.js +4 -4
  14. package/out/config-loader/env.js.map +1 -1
  15. package/out/data-server/sbvr-server.d.ts +1 -13
  16. package/out/data-server/sbvr-server.js +17 -1
  17. package/out/data-server/sbvr-server.js.map +1 -1
  18. package/out/database-layer/db.js +12 -15
  19. package/out/database-layer/db.js.map +1 -1
  20. package/out/express-emulator/express.js +4 -4
  21. package/out/express-emulator/express.js.map +1 -1
  22. package/out/http-transactions/transactions.d.ts +1 -12
  23. package/out/http-transactions/transactions.js +18 -0
  24. package/out/http-transactions/transactions.js.map +1 -1
  25. package/out/migrator/async.js +12 -16
  26. package/out/migrator/async.js.map +1 -1
  27. package/out/migrator/sync.js +6 -12
  28. package/out/migrator/sync.js.map +1 -1
  29. package/out/migrator/utils.d.ts +5 -4
  30. package/out/migrator/utils.js +38 -20
  31. package/out/migrator/utils.js.map +1 -1
  32. package/out/pinejs-session-store/pinejs-session-store.js +18 -3
  33. package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
  34. package/out/sbvr-api/abstract-sql.js +1 -1
  35. package/out/sbvr-api/abstract-sql.js.map +1 -1
  36. package/out/sbvr-api/cached-compile.js +1 -1
  37. package/out/sbvr-api/cached-compile.js.map +1 -1
  38. package/out/sbvr-api/hooks.js +4 -5
  39. package/out/sbvr-api/hooks.js.map +1 -1
  40. package/out/sbvr-api/odata-response.js +4 -5
  41. package/out/sbvr-api/odata-response.js.map +1 -1
  42. package/out/sbvr-api/permissions.js +22 -60
  43. package/out/sbvr-api/permissions.js.map +1 -1
  44. package/out/sbvr-api/sbvr-utils.d.ts +3 -3
  45. package/out/sbvr-api/sbvr-utils.js +38 -21
  46. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  47. package/out/sbvr-api/uri-parser.js +4 -9
  48. package/out/sbvr-api/uri-parser.js.map +1 -1
  49. package/package.json +7 -7
  50. package/src/bin/abstract-sql-compiler.ts +2 -1
  51. package/src/bin/sbvr-compiler.ts +2 -1
  52. package/src/config-loader/env.ts +3 -7
  53. package/src/data-server/sbvr-server.js +17 -1
  54. package/src/database-layer/db.ts +1 -1
  55. package/src/express-emulator/express.js +4 -4
  56. package/src/http-transactions/transactions.js +18 -0
  57. package/src/migrator/utils.ts +40 -20
  58. package/src/pinejs-session-store/pinejs-session-store.ts +15 -0
  59. package/src/sbvr-api/odata-response.ts +1 -2
  60. package/src/sbvr-api/permissions.ts +17 -59
  61. package/src/sbvr-api/sbvr-utils.ts +30 -2
  62. package/src/sbvr-api/uri-parser.ts +4 -2
  63. package/tsconfig.json +1 -1
@@ -3,6 +3,7 @@ import { odataNameToSqlName } from '@balena/odata-to-abstract-sql';
3
3
  // @ts-ignore
4
4
  const transactionModel = require('./transaction.sbvr');
5
5
 
6
+ /** @type {import('../config-loader/config-loader').Config} */
6
7
  export let config = {
7
8
  models: [
8
9
  {
@@ -24,6 +25,23 @@ ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT N
24
25
  ALTER TABLE "transaction"
25
26
  ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;\
26
27
  `,
28
+ '15.0.0-data-types': async (tx, sbvrUtils) => {
29
+ switch (sbvrUtils.db.engine) {
30
+ case 'mysql':
31
+ await tx.executeSql(`\
32
+ ALTER TABLE "lock"
33
+ MODIFY "is exclusive" BOOLEAN NOT NULL,
34
+ MODIFY "is under lock" BOOLEAN NOT NULL;`);
35
+ break;
36
+ case 'postgres':
37
+ await tx.executeSql(`\
38
+ ALTER TABLE "lock"
39
+ ALTER COLUMN "is exclusive" SET DATA TYPE BOOLEAN USING b::BOOLEAN,
40
+ ALTER COLUMN "is under lock" SET DATA TYPE BOOLEAN USING b::BOOLEAN;`);
41
+ break;
42
+ // No need to migrate for websql
43
+ }
44
+ },
27
45
  },
28
46
  },
29
47
  ],
@@ -12,6 +12,23 @@ import { delay } from '../sbvr-api/control-flow';
12
12
 
13
13
  // tslint:disable-next-line:no-var-requires
14
14
  export const modelText = require('./migrations.sbvr');
15
+ export const migrations: Migrations = {
16
+ '15.0.0-data-types': async (tx, { db }) => {
17
+ switch (db.engine) {
18
+ case 'mysql':
19
+ await tx.executeSql(`\
20
+ ALTER TABLE "migration"
21
+ MODIFY "executed migrations" JSON NOT NULL;`);
22
+ break;
23
+ case 'postgres':
24
+ await tx.executeSql(`\
25
+ ALTER TABLE "migration"
26
+ ALTER COLUMN "executed migrations" SET DATA TYPE JSONB USING b::JSONB;`);
27
+ break;
28
+ // No need to migrate for websql
29
+ }
30
+ },
31
+ };
15
32
 
16
33
  import * as sbvrUtils from '../sbvr-api/sbvr-utils';
17
34
  export enum MigrationCategories {
@@ -73,14 +90,14 @@ export function isSyncMigration(
73
90
  return typeof migration === 'function' || typeof migration === 'string';
74
91
  }
75
92
  export function areCategorizedMigrations(
76
- migrations: Migrations,
77
- ): migrations is CategorizedMigrations {
93
+ $migrations: Migrations,
94
+ ): $migrations is CategorizedMigrations {
78
95
  const containsCategories = Object.keys(MigrationCategories).some(
79
- (key) => key in migrations,
96
+ (key) => key in $migrations,
80
97
  );
81
98
  if (
82
99
  containsCategories &&
83
- Object.keys(migrations).some((key) => !(key in MigrationCategories))
100
+ Object.keys($migrations).some((key) => !(key in MigrationCategories))
84
101
  ) {
85
102
  throw new Error(
86
103
  'Mixing categorized and uncategorized migrations is not supported',
@@ -104,31 +121,31 @@ export type MigrationStatus = {
104
121
  };
105
122
 
106
123
  export const getRunnableAsyncMigrations = (
107
- migrations: Migrations,
124
+ $migrations: Migrations,
108
125
  ): RunnableAsyncMigrations | undefined => {
109
- if (migrations[MigrationCategories.async]) {
126
+ if ($migrations[MigrationCategories.async]) {
110
127
  if (
111
- Object.values(migrations[MigrationCategories.async]).some(
128
+ Object.values($migrations[MigrationCategories.async]).some(
112
129
  (migration) => !isAsyncMigration(migration),
113
130
  ) ||
114
- typeof migrations[MigrationCategories.async] !== 'object'
131
+ typeof $migrations[MigrationCategories.async] !== 'object'
115
132
  ) {
116
133
  throw new Error(
117
134
  `All loaded async migrations need to be of type: ${MigrationCategories.async}`,
118
135
  );
119
136
  }
120
- return migrations[MigrationCategories.async] as RunnableAsyncMigrations;
137
+ return $migrations[MigrationCategories.async] as RunnableAsyncMigrations;
121
138
  }
122
139
  };
123
140
 
124
141
  // migration loader should either get migrations from model
125
142
  // or from the filepath
126
143
  export const getRunnableSyncMigrations = (
127
- migrations: Migrations,
144
+ $migrations: Migrations,
128
145
  ): RunnableMigrations => {
129
- if (areCategorizedMigrations(migrations)) {
146
+ if (areCategorizedMigrations($migrations)) {
130
147
  const runnableMigrations: RunnableMigrations = {};
131
- for (const [category, categoryMigrations] of Object.entries(migrations)) {
148
+ for (const [category, categoryMigrations] of Object.entries($migrations)) {
132
149
  if (category in MigrationCategories) {
133
150
  for (const [key, migration] of Object.entries(
134
151
  categoryMigrations as Migrations,
@@ -145,16 +162,16 @@ export const getRunnableSyncMigrations = (
145
162
  }
146
163
  return runnableMigrations;
147
164
  }
148
- return migrations;
165
+ return $migrations;
149
166
  };
150
167
 
151
168
  // turns {"key1": migration, "key3": migration, "key2": migration}
152
169
  // into [["key1", migration], ["key2", migration], ["key3", migration]]
153
170
  export const filterAndSortPendingMigrations = (
154
- migrations: NonNullable<RunnableMigrations | RunnableAsyncMigrations>,
171
+ $migrations: NonNullable<RunnableMigrations | RunnableAsyncMigrations>,
155
172
  executedMigrations: string[],
156
173
  ): MigrationTuple[] =>
157
- (_(migrations).omit(executedMigrations) as _.Object<typeof migrations>)
174
+ (_($migrations).omit(executedMigrations) as _.Object<typeof $migrations>)
158
175
  .toPairs()
159
176
  .sortBy(([migrationKey]) => migrationKey)
160
177
  .value();
@@ -313,8 +330,10 @@ WHERE "migration"."model name" = ${1}`,
313
330
  if (data == null) {
314
331
  return [];
315
332
  }
316
-
317
- return JSON.parse(data.executed_migrations) as string[];
333
+ if (typeof data.executed_migrations === 'string') {
334
+ return JSON.parse(data.executed_migrations);
335
+ }
336
+ return data.executed_migrations;
318
337
  };
319
338
 
320
339
  export const migrationTablesExist = async (tx: Tx) => {
@@ -338,7 +357,7 @@ WHERE NOT EXISTS (SELECT 1 FROM "migration status" WHERE "migration key" = ${5})
338
357
  [
339
358
  migrationStatus['migration_key'],
340
359
  migrationStatus['start_time'],
341
- migrationStatus['is_backing_off'] ? 1 : 0,
360
+ migrationStatus['is_backing_off'],
342
361
  migrationStatus['run_count'],
343
362
  migrationStatus['migration_key'],
344
363
  ],
@@ -372,7 +391,7 @@ WHERE "migration status"."migration key" = ${7};`,
372
391
  migrationStatus['migrated_row_count'],
373
392
  migrationStatus['error_count'],
374
393
  migrationStatus['converged_time'],
375
- migrationStatus['is_backing_off'] ? 1 : 0,
394
+ migrationStatus['is_backing_off'],
376
395
  migrationStatus['migration_key'],
377
396
  ],
378
397
  );
@@ -409,7 +428,8 @@ LIMIT 1;`,
409
428
  migrated_row_count: data['migrated row count'],
410
429
  error_count: data['error count'],
411
430
  converged_time: data['converged time'],
412
- is_backing_off: data['is backing off'] === 1,
431
+ is_backing_off:
432
+ data['is backing off'] === true || data['is backing off'] === 1,
413
433
  };
414
434
  } catch (err: any) {
415
435
  // we report any error here, as no error should happen at all
@@ -173,6 +173,21 @@ export class PinejsSessionStore extends Store {
173
173
  ALTER TABLE "session"
174
174
  ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
175
175
  `,
176
+ '15.0.0-data-types': async (tx, sbvrUtils) => {
177
+ switch (sbvrUtils.db.engine) {
178
+ case 'mysql':
179
+ await tx.executeSql(`\
180
+ ALTER TABLE "session"
181
+ MODIFY "data" JSON NOT NULL;`);
182
+ break;
183
+ case 'postgres':
184
+ await tx.executeSql(`\
185
+ ALTER TABLE "session"
186
+ ALTER COLUMN "data" SET DATA TYPE JSONB USING b::JSONB;`);
187
+ break;
188
+ // No need to migrate for websql
189
+ }
190
+ },
176
191
  },
177
192
  },
178
193
  ],
@@ -90,8 +90,7 @@ const getLocalFields = (table: AbstractSqlTable) => {
90
90
  if (table.localFields == null) {
91
91
  table.localFields = {};
92
92
  for (const { fieldName, dataType } of table.fields) {
93
- // TODO-MAJOR: This should also include ConceptType fields
94
- if (dataType !== 'ForeignKey') {
93
+ if (!['ForeignKey', 'ConceptType'].includes(dataType)) {
95
94
  const odataName = sqlNameToODataName(fieldName);
96
95
  table.localFields[odataName] = true;
97
96
  }
@@ -862,9 +862,7 @@ const rewriteRelationship = memoizeWeak(
862
862
  foundCanAccessLink = true;
863
863
  }
864
864
  // return a true expression to not select the relationship, which might be virtual
865
- // this should be a boolean expression, but needs to be a subquery in case it
866
- // is wrapped in an `or` or `and`
867
- return ['Equals', ['Boolean', true], ['Boolean', true]];
865
+ return ['Boolean', true];
868
866
  };
869
867
 
870
868
  try {
@@ -1446,26 +1444,13 @@ const checkApiKey = async (
1446
1444
  apiKey: string,
1447
1445
  tx?: Tx,
1448
1446
  ): Promise<PermissionReq['apiKey']> => {
1449
- let permissions: string[];
1450
- try {
1451
- permissions = await getApiKeyPermissions(apiKey, tx);
1452
- } catch (err: any) {
1453
- console.warn('Error with API key:', err);
1454
- // Ignore errors getting the api key and just use an empty permissions object.
1455
- permissions = [];
1456
- }
1457
- let actor;
1458
- if (permissions.length > 0) {
1459
- actor = await getApiKeyActorId(apiKey, tx);
1460
- }
1461
- const resolvedApiKey: PermissionReq['apiKey'] = {
1447
+ const permissions = await getApiKeyPermissions(apiKey, tx);
1448
+ const actor = await getApiKeyActorId(apiKey, tx);
1449
+ return {
1462
1450
  key: apiKey,
1463
1451
  permissions,
1452
+ actor,
1464
1453
  };
1465
- if (actor != null) {
1466
- resolvedApiKey.actor = actor;
1467
- }
1468
- return resolvedApiKey;
1469
1454
  };
1470
1455
 
1471
1456
  export const resolveAuthHeader = async (
@@ -1474,11 +1459,6 @@ export const resolveAuthHeader = async (
1474
1459
  // TODO: Consider making tx the second argument in the next major
1475
1460
  tx?: Tx,
1476
1461
  ): Promise<PermissionReq['apiKey']> => {
1477
- // TODO-MAJOR: remove this check
1478
- if (req.apiKey != null) {
1479
- return;
1480
- }
1481
-
1482
1462
  const auth = req.header('Authorization');
1483
1463
  if (!auth) {
1484
1464
  return;
@@ -1524,11 +1504,6 @@ export const resolveApiKey = async (
1524
1504
  // TODO: Consider making tx the second argument in the next major
1525
1505
  tx?: Tx,
1526
1506
  ): Promise<PermissionReq['apiKey']> => {
1527
- // TODO-MAJOR: remove this check
1528
- if (req.apiKey != null) {
1529
- return;
1530
- }
1531
-
1532
1507
  const apiKey =
1533
1508
  req.params[paramName] ?? req.body[paramName] ?? req.query[paramName];
1534
1509
  if (apiKey == null) {
@@ -1538,9 +1513,6 @@ export const resolveApiKey = async (
1538
1513
  };
1539
1514
 
1540
1515
  export const customApiKeyMiddleware = (paramName = 'apikey') => {
1541
- if (paramName == null) {
1542
- paramName = 'apikey';
1543
- }
1544
1516
  return async (
1545
1517
  req: HookReq | Express.Request,
1546
1518
  _res?: Express.Response,
@@ -1632,32 +1604,18 @@ const getReqPermissions = async (
1632
1604
  req: PermissionReq,
1633
1605
  odataBinds: ODataBinds = [],
1634
1606
  ) => {
1635
- const [guestPermissions] = await Promise.all([
1636
- (async () => {
1637
- if (
1638
- guestPermissionsInitialized === false &&
1639
- (req.user === root.user || req.user === rootRead.user)
1640
- ) {
1641
- // In the case that guest permissions are not initialized yet and the query is being made with root permissions
1642
- // then we need to bypass `getGuestPermissions` as it will cause an infinite loop back to here.
1643
- // Therefore to break that loop we just ignore guest permissions.
1644
- return [];
1645
- }
1646
- return await getGuestPermissions();
1647
- })(),
1648
- (async () => {
1649
- // TODO: Remove this extra actor ID lookup making actor non-optional and updating open-balena-api.
1650
- if (
1651
- req.apiKey != null &&
1652
- req.apiKey.actor == null &&
1653
- req.apiKey.permissions != null &&
1654
- req.apiKey.permissions.length > 0
1655
- ) {
1656
- const actorId = await getApiKeyActorId(req.apiKey.key);
1657
- req.apiKey!.actor = actorId;
1658
- }
1659
- })(),
1660
- ]);
1607
+ const guestPermissions = await (async () => {
1608
+ if (
1609
+ guestPermissionsInitialized === false &&
1610
+ (req.user === root.user || req.user === rootRead.user)
1611
+ ) {
1612
+ // In the case that guest permissions are not initialized yet and the query is being made with root permissions
1613
+ // then we need to bypass `getGuestPermissions` as it will cause an infinite loop back to here.
1614
+ // Therefore to break that loop we just ignore guest permissions.
1615
+ return [];
1616
+ }
1617
+ return await getGuestPermissions();
1618
+ })();
1661
1619
 
1662
1620
  let actorPermissions: string[] = [];
1663
1621
  const addActorPermissions = (actorId: number, perms: string[]) => {
@@ -123,7 +123,7 @@ export interface User extends Actor {
123
123
 
124
124
  export interface ApiKey extends Actor {
125
125
  key: string;
126
- actor?: number;
126
+ actor: number;
127
127
  }
128
128
 
129
129
  export interface Response {
@@ -569,7 +569,10 @@ export const executeModels = async (
569
569
  let uri = '/dev/model';
570
570
  const body: AnyObject = {
571
571
  is_of__vocabulary: model.vocab,
572
- model_value: model[modelType],
572
+ model_value:
573
+ typeof model[modelType] === 'string'
574
+ ? { value: model[modelType] }
575
+ : model[modelType],
573
576
  model_type: modelType,
574
577
  };
575
578
  const id = result?.[0]?.id;
@@ -1696,6 +1699,31 @@ export const executeStandardModels = async (tx: Db.Tx): Promise<void> => {
1696
1699
  ALTER TABLE "model"
1697
1700
  ADD COLUMN IF NOT EXISTS "modified at" TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL;
1698
1701
  `,
1702
+ '15.0.0-data-types': async ($tx, sbvrUtils) => {
1703
+ switch (sbvrUtils.db.engine) {
1704
+ case 'mysql':
1705
+ await $tx.executeSql(`\
1706
+ ALTER TABLE "model"
1707
+ MODIFY "model value" JSON NOT NULL;
1708
+
1709
+ UPDATE "model"
1710
+ SET "model value" = CAST('{"value":' || CAST("model value" AS CHAR) || '}' AS JSON)
1711
+ WHERE "model type" IN ('se', 'odataMetadata')
1712
+ AND CAST("model value" AS CHAR) LIKE '"%';`);
1713
+ break;
1714
+ case 'postgres':
1715
+ await $tx.executeSql(`\
1716
+ ALTER TABLE "model"
1717
+ ALTER COLUMN "model value" SET DATA TYPE JSONB USING "model value"::JSONB;
1718
+
1719
+ UPDATE "model"
1720
+ SET "model value" = CAST('{"value":' || CAST("model value" AS TEXT) || '}' AS JSON)
1721
+ WHERE "model type" IN ('se', 'odataMetadata')
1722
+ AND CAST("model value" AS TEXT) LIKE '"%';`);
1723
+ break;
1724
+ // No need to migrate for websql
1725
+ }
1726
+ },
1699
1727
  },
1700
1728
  });
1701
1729
  await executeModels(tx, permissions.config.models);
@@ -144,7 +144,8 @@ export const memoizedParseOdata = (() => {
144
144
 
145
145
  export const memoizedGetOData2AbstractSQL = memoizeWeak(
146
146
  (abstractSqlModel: AbstractSQLCompiler.AbstractSqlModel) => {
147
- return new OData2AbstractSQL(abstractSqlModel, undefined, {
147
+ // TODO: REMOVE THIS, it's temporary due to mismatched abstract-sql-compiler versions
148
+ return new OData2AbstractSQL(abstractSqlModel as any, undefined, {
148
149
  // Use minimized aliases when not in debug mode for smaller queries
149
150
  minimizeAliases: !env.DEBUG,
150
151
  });
@@ -418,7 +419,8 @@ export const translateUri = <
418
419
  return true;
419
420
  },
420
421
  });
421
- request.abstractSqlQuery = abstractSqlQuery;
422
+ // TODO: REMOVE THIS, it's temporary due to mismatched abstract-sql-compiler versions
423
+ request.abstractSqlQuery = abstractSqlQuery as any;
422
424
  return request;
423
425
  }
424
426
  return request;
package/tsconfig.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "removeComments": true,
12
12
  "rootDir": "src",
13
13
  "sourceMap": true,
14
- "target": "es2019",
14
+ "target": "es2021",
15
15
  "declaration": true,
16
16
  "skipLibCheck": true,
17
17
  "resolveJsonModule": true,