@balena/pinejs 15.0.0-true-boolean-911aca4062d3132ad3c34712014739b6849fa13a → 15.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. package/.dockerignore +4 -0
  2. package/.github/workflows/flowzone.yml +21 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.pinejs-cache.json +1 -0
  5. package/.resinci.yml +1 -0
  6. package/.versionbot/CHANGELOG.yml +9678 -2001
  7. package/CHANGELOG.md +2975 -2
  8. package/Dockerfile +14 -0
  9. package/Gruntfile.ts +3 -6
  10. package/README.md +10 -1
  11. package/VERSION +1 -0
  12. package/build/browser.ts +1 -1
  13. package/build/config.ts +0 -1
  14. package/docker-compose.npm-test.yml +11 -0
  15. package/docs/AdvancedUsage.md +77 -63
  16. package/docs/GettingStarted.md +90 -41
  17. package/docs/Migrations.md +102 -1
  18. package/docs/ProjectConfig.md +12 -21
  19. package/docs/Testing.md +7 -0
  20. package/out/bin/abstract-sql-compiler.js +17 -17
  21. package/out/bin/abstract-sql-compiler.js.map +1 -1
  22. package/out/bin/odata-compiler.js +23 -20
  23. package/out/bin/odata-compiler.js.map +1 -1
  24. package/out/bin/sbvr-compiler.js +22 -22
  25. package/out/bin/sbvr-compiler.js.map +1 -1
  26. package/out/bin/utils.d.ts +2 -2
  27. package/out/bin/utils.js +3 -3
  28. package/out/bin/utils.js.map +1 -1
  29. package/out/config-loader/config-loader.d.ts +9 -8
  30. package/out/config-loader/config-loader.js +135 -78
  31. package/out/config-loader/config-loader.js.map +1 -1
  32. package/out/config-loader/env.d.ts +41 -16
  33. package/out/config-loader/env.js +46 -2
  34. package/out/config-loader/env.js.map +1 -1
  35. package/out/data-server/sbvr-server.d.ts +2 -19
  36. package/out/data-server/sbvr-server.js +44 -38
  37. package/out/data-server/sbvr-server.js.map +1 -1
  38. package/out/database-layer/db.d.ts +32 -14
  39. package/out/database-layer/db.js +120 -41
  40. package/out/database-layer/db.js.map +1 -1
  41. package/out/express-emulator/express.js +10 -11
  42. package/out/express-emulator/express.js.map +1 -1
  43. package/out/http-transactions/transactions.d.ts +2 -18
  44. package/out/http-transactions/transactions.js +29 -21
  45. package/out/http-transactions/transactions.js.map +1 -1
  46. package/out/migrator/async.d.ts +7 -0
  47. package/out/migrator/async.js +168 -0
  48. package/out/migrator/async.js.map +1 -0
  49. package/out/migrator/migrations.sbvr +43 -0
  50. package/out/migrator/sync.d.ts +9 -0
  51. package/out/migrator/sync.js +106 -0
  52. package/out/migrator/sync.js.map +1 -0
  53. package/out/migrator/utils.d.ts +78 -0
  54. package/out/migrator/utils.js +283 -0
  55. package/out/migrator/utils.js.map +1 -0
  56. package/out/odata-metadata/odata-metadata-generator.js +10 -13
  57. package/out/odata-metadata/odata-metadata-generator.js.map +1 -1
  58. package/out/passport-pinejs/passport-pinejs.d.ts +1 -1
  59. package/out/passport-pinejs/passport-pinejs.js +8 -7
  60. package/out/passport-pinejs/passport-pinejs.js.map +1 -1
  61. package/out/pinejs-session-store/pinejs-session-store.d.ts +1 -1
  62. package/out/pinejs-session-store/pinejs-session-store.js +20 -6
  63. package/out/pinejs-session-store/pinejs-session-store.js.map +1 -1
  64. package/out/sbvr-api/abstract-sql.d.ts +3 -2
  65. package/out/sbvr-api/abstract-sql.js +9 -9
  66. package/out/sbvr-api/abstract-sql.js.map +1 -1
  67. package/out/sbvr-api/cached-compile.js +1 -1
  68. package/out/sbvr-api/cached-compile.js.map +1 -1
  69. package/out/sbvr-api/common-types.d.ts +6 -5
  70. package/out/sbvr-api/control-flow.d.ts +8 -1
  71. package/out/sbvr-api/control-flow.js +36 -9
  72. package/out/sbvr-api/control-flow.js.map +1 -1
  73. package/out/sbvr-api/errors.d.ts +47 -40
  74. package/out/sbvr-api/errors.js +78 -77
  75. package/out/sbvr-api/errors.js.map +1 -1
  76. package/out/sbvr-api/express-extension.d.ts +4 -0
  77. package/out/sbvr-api/hooks.d.ts +16 -15
  78. package/out/sbvr-api/hooks.js +74 -48
  79. package/out/sbvr-api/hooks.js.map +1 -1
  80. package/out/sbvr-api/odata-response.d.ts +2 -2
  81. package/out/sbvr-api/odata-response.js +28 -30
  82. package/out/sbvr-api/odata-response.js.map +1 -1
  83. package/out/sbvr-api/permissions.d.ts +17 -16
  84. package/out/sbvr-api/permissions.js +369 -304
  85. package/out/sbvr-api/permissions.js.map +1 -1
  86. package/out/sbvr-api/sbvr-utils.d.ts +33 -15
  87. package/out/sbvr-api/sbvr-utils.js +397 -235
  88. package/out/sbvr-api/sbvr-utils.js.map +1 -1
  89. package/out/sbvr-api/translations.d.ts +6 -0
  90. package/out/sbvr-api/translations.js +150 -0
  91. package/out/sbvr-api/translations.js.map +1 -0
  92. package/out/sbvr-api/uri-parser.d.ts +23 -17
  93. package/out/sbvr-api/uri-parser.js +33 -27
  94. package/out/sbvr-api/uri-parser.js.map +1 -1
  95. package/out/sbvr-api/user.sbvr +2 -0
  96. package/out/server-glue/module.d.ts +6 -6
  97. package/out/server-glue/module.js +4 -2
  98. package/out/server-glue/module.js.map +1 -1
  99. package/out/server-glue/server.js +5 -5
  100. package/out/server-glue/server.js.map +1 -1
  101. package/package.json +89 -73
  102. package/pinejs.png +0 -0
  103. package/repo.yml +9 -9
  104. package/src/bin/abstract-sql-compiler.ts +5 -7
  105. package/src/bin/odata-compiler.ts +11 -13
  106. package/src/bin/sbvr-compiler.ts +11 -17
  107. package/src/bin/utils.ts +3 -5
  108. package/src/config-loader/config-loader.ts +167 -53
  109. package/src/config-loader/env.ts +106 -6
  110. package/src/data-server/sbvr-server.js +44 -38
  111. package/src/database-layer/db.ts +205 -64
  112. package/src/express-emulator/express.js +10 -11
  113. package/src/http-transactions/transactions.js +29 -21
  114. package/src/migrator/async.ts +323 -0
  115. package/src/migrator/migrations.sbvr +43 -0
  116. package/src/migrator/sync.ts +152 -0
  117. package/src/migrator/utils.ts +458 -0
  118. package/src/odata-metadata/odata-metadata-generator.ts +12 -15
  119. package/src/passport-pinejs/passport-pinejs.ts +9 -7
  120. package/src/pinejs-session-store/pinejs-session-store.ts +15 -1
  121. package/src/sbvr-api/abstract-sql.ts +17 -14
  122. package/src/sbvr-api/common-types.ts +2 -1
  123. package/src/sbvr-api/control-flow.ts +45 -11
  124. package/src/sbvr-api/errors.ts +82 -77
  125. package/src/sbvr-api/express-extension.ts +6 -1
  126. package/src/sbvr-api/hooks.ts +123 -50
  127. package/src/sbvr-api/odata-response.ts +23 -28
  128. package/src/sbvr-api/permissions.ts +548 -415
  129. package/src/sbvr-api/sbvr-utils.ts +581 -259
  130. package/src/sbvr-api/translations.ts +248 -0
  131. package/src/sbvr-api/uri-parser.ts +63 -49
  132. package/src/sbvr-api/user.sbvr +2 -0
  133. package/src/server-glue/module.ts +16 -10
  134. package/src/server-glue/server.ts +5 -5
  135. package/tsconfig.dev.json +1 -0
  136. package/tsconfig.json +1 -2
  137. package/typings/lf-to-abstract-sql.d.ts +6 -9
  138. package/typings/memoizee.d.ts +1 -1
  139. package/.github/CODEOWNERS +0 -1
  140. package/circle.yml +0 -37
  141. package/docs/todo.txt +0 -22
  142. package/out/migrator/migrator.d.ts +0 -20
  143. package/out/migrator/migrator.js +0 -188
  144. package/out/migrator/migrator.js.map +0 -1
  145. package/src/migrator/migrator.ts +0 -286
@@ -1,11 +1,10 @@
1
1
  import type { OptionalField, Resolvable } from './common-types';
2
2
  import type { Tx } from '../database-layer/db';
3
- import type { ODataRequest } from './uri-parser';
3
+ import type { ODataRequest, ParsedODataRequest } from './uri-parser';
4
4
  import type { AnyObject } from 'pinejs-client-core';
5
5
  import type { TypedError } from 'typed-error';
6
6
  import type { SupportedMethod } from '@balena/odata-to-abstract-sql';
7
7
 
8
- import * as Bluebird from 'bluebird';
9
8
  import * as _ from 'lodash';
10
9
  import { settleMapSeries } from './control-flow';
11
10
  import * as memoize from 'memoizee';
@@ -16,6 +15,7 @@ import {
16
15
  resolveSynonym,
17
16
  getAbstractSqlModel,
18
17
  api,
18
+ Response,
19
19
  } from './sbvr-utils';
20
20
 
21
21
  export interface HookReq {
@@ -34,7 +34,7 @@ export interface HookArgs {
34
34
  req: HookReq;
35
35
  request: ODataRequest;
36
36
  api: PinejsClient;
37
- tx?: Tx;
37
+ tx?: Tx | undefined;
38
38
  }
39
39
  export type HookResponse = PromiseLike<any> | null | void;
40
40
 
@@ -42,14 +42,18 @@ export interface Hooks {
42
42
  PREPARSE?: (options: Omit<HookArgs, 'request' | 'api'>) => HookResponse;
43
43
  POSTPARSE?: (options: HookArgs) => HookResponse;
44
44
  PRERUN?: (options: HookArgs & { tx: Tx }) => HookResponse;
45
+ /** These are run in reverse translation order from newest to oldest */
45
46
  POSTRUN?: (options: HookArgs & { tx: Tx; result: any }) => HookResponse;
47
+ /** These are run in reverse translation order from newest to oldest */
46
48
  PRERESPOND?: (
47
49
  options: HookArgs & {
48
50
  tx: Tx;
49
51
  result: any;
50
- data?: any;
52
+ /** This can be mutated to modify the response sent to the client */
53
+ response: Response;
51
54
  },
52
55
  ) => HookResponse;
56
+ /** These are run in reverse translation order from newest to oldest */
53
57
  'POSTRUN-ERROR'?: (
54
58
  options: HookArgs & { error: TypedError | any },
55
59
  ) => HookResponse;
@@ -97,7 +101,13 @@ class SideEffectHook<T extends HookFn> extends Hook<T> {
97
101
 
98
102
  public registerRollback(fn: RollbackAction): void {
99
103
  if (this.rolledBack) {
100
- Bluebird.try(fn);
104
+ (async () => {
105
+ try {
106
+ await fn();
107
+ } catch {
108
+ // Ignore any errors in the rollback callback
109
+ }
110
+ })();
101
111
  } else {
102
112
  this.rollbackFns.push(fn);
103
113
  }
@@ -117,15 +127,21 @@ class SideEffectHook<T extends HookFn> extends Hook<T> {
117
127
 
118
128
  // The execution order of rollback actions is unspecified
119
129
  export const rollbackRequestHooks = <T extends InstantiatedHooks>(
120
- hooks: T | undefined,
130
+ hooksList: Array<[modelName: string, hooks: T]> | undefined,
121
131
  ): void => {
122
- if (hooks == null) {
132
+ if (hooksList == null) {
123
133
  return;
124
134
  }
125
- settleMapSeries(_(hooks).flatMap().compact().value(), async (hook) => {
126
- if (hook instanceof SideEffectHook) {
127
- await hook.rollback();
128
- }
135
+ const sideEffectHooks = hooksList
136
+ .flatMap(([, v]): Array<Hook<HookFn>> => Object.values(v).flat())
137
+ .filter(
138
+ (hook): hook is SideEffectHook<HookFn> => hook instanceof SideEffectHook,
139
+ );
140
+ if (sideEffectHooks.length === 0) {
141
+ return;
142
+ }
143
+ settleMapSeries(sideEffectHooks, async (hook) => {
144
+ await hook.rollback();
129
145
  });
130
146
  };
131
147
 
@@ -162,38 +178,65 @@ const getResourceHooks = (vocabHooks: VocabHooks, resourceName?: string) => {
162
178
  const getVocabHooks = (
163
179
  methodHooks: MethodHooks,
164
180
  vocabulary: string,
165
- resourceName?: string,
181
+ resourceName: string | undefined,
182
+ includeAllVocab: boolean,
166
183
  ) => {
167
184
  if (methodHooks == null) {
168
185
  return {};
169
186
  }
187
+ const vocabHooks = getResourceHooks(methodHooks[vocabulary], resourceName);
188
+ if (!includeAllVocab) {
189
+ // Do not include `vocabulary='all'` hooks, useful for translated vocabularies
190
+ return vocabHooks;
191
+ }
170
192
  return mergeHooks(
171
- getResourceHooks(methodHooks[vocabulary], resourceName),
193
+ vocabHooks,
172
194
  getResourceHooks(methodHooks['all'], resourceName),
173
195
  );
174
196
  };
175
197
  const getMethodHooks = memoize(
176
- (method: SupportedMethod, vocabulary: string, resourceName?: string) =>
198
+ (
199
+ method: SupportedMethod,
200
+ vocabulary: string,
201
+ resourceName: string | undefined,
202
+ includeAllVocab: boolean,
203
+ ) =>
177
204
  mergeHooks(
178
- getVocabHooks(apiHooks[method], vocabulary, resourceName),
179
- getVocabHooks(apiHooks['all'], vocabulary, resourceName),
205
+ getVocabHooks(
206
+ apiHooks[method],
207
+ vocabulary,
208
+ resourceName,
209
+ includeAllVocab,
210
+ ),
211
+ getVocabHooks(apiHooks['all'], vocabulary, resourceName, includeAllVocab),
180
212
  ),
181
213
  { primitive: true },
182
214
  );
183
215
  export const getHooks = (
184
216
  request: Pick<
185
- OptionalField<ODataRequest, 'resourceName'>,
217
+ OptionalField<ParsedODataRequest, 'resourceName'>,
186
218
  'resourceName' | 'method' | 'vocabulary'
187
219
  >,
220
+ includeAllVocab: boolean,
188
221
  ): InstantiatedHooks => {
189
222
  let { resourceName } = request;
190
223
  if (resourceName != null) {
191
224
  resourceName = resolveSynonym(
192
- request as Pick<ODataRequest, 'resourceName' | 'method' | 'vocabulary'>,
193
- );
225
+ request as Pick<
226
+ ParsedODataRequest,
227
+ 'resourceName' | 'method' | 'vocabulary'
228
+ >,
229
+ )
230
+ // Remove version suffixes
231
+ .replace(/\$.*$/, '');
194
232
  }
195
233
  return instantiateHooks(
196
- getMethodHooks(request.method, request.vocabulary, resourceName),
234
+ getMethodHooks(
235
+ request.method,
236
+ request.vocabulary,
237
+ resourceName,
238
+ includeAllVocab,
239
+ ),
197
240
  );
198
241
  };
199
242
  getHooks.clear = () => getMethodHooks.clear();
@@ -325,54 +368,84 @@ export const addPureHook = (
325
368
  });
326
369
  };
327
370
 
328
- const defineApi = (args: HookArgs) => {
329
- const { request, req, tx } = args;
330
- const { vocabulary } = request;
371
+ const defineApi = (modelName: string, args: HookArgs) => {
372
+ const { req, tx } = args;
331
373
  Object.defineProperty(args, 'api', {
332
374
  get: _.once(() =>
333
- api[vocabulary].clone({
375
+ api[modelName].clone({
334
376
  passthrough: { req, tx },
335
377
  }),
336
378
  ),
337
379
  });
338
380
  };
339
381
 
382
+ type RunHookArgs<T extends keyof Hooks> = Omit<
383
+ Parameters<NonNullable<Hooks[T]>>[0],
384
+ 'api'
385
+ >;
386
+ const getReadOnlyArgs = <T extends keyof Hooks>(
387
+ modelName: string,
388
+ args: RunHookArgs<T>,
389
+ ): RunHookArgs<T> => {
390
+ if (args.tx == null || args.tx.isReadOnly()) {
391
+ // If we don't have a tx then read-only/writable is irrelevant
392
+ return args;
393
+ }
394
+ let readOnlyArgs: typeof args;
395
+ readOnlyArgs = { ...args, tx: args.tx.asReadOnly() };
396
+ if ((args as HookArgs).request != null) {
397
+ defineApi(modelName, readOnlyArgs as HookArgs);
398
+ }
399
+ return readOnlyArgs;
400
+ };
401
+
340
402
  export const runHooks = async <T extends keyof Hooks>(
341
403
  hookName: T,
342
- hooksList: InstantiatedHooks | undefined,
343
- args: Omit<Parameters<NonNullable<Hooks[T]>>[0], 'api'>,
404
+ /**
405
+ * A list of modelName/hooks to run in order, which will be reversed for hooks after the "RUN" stage,
406
+ * ie POSTRUN/PRERESPOND/POSTRUN-ERROR
407
+ */
408
+ hooksList: Array<[modelName: string, hooks: InstantiatedHooks]> | undefined,
409
+ args: RunHookArgs<T>,
344
410
  ) => {
345
411
  if (hooksList == null) {
346
412
  return;
347
413
  }
348
- const hooks = hooksList[hookName];
349
- if (hooks == null || hooks.length === 0) {
414
+ const hooks = hooksList
415
+ .map(([modelName, $hooks]): [string, InstantiatedHooks[T] | undefined] => [
416
+ modelName,
417
+ $hooks[hookName],
418
+ ])
419
+ .filter(
420
+ (v): v is [string, InstantiatedHooks[T]] =>
421
+ v[1] != null && v[1].length > 0,
422
+ );
423
+ if (hooks.length === 0) {
350
424
  return;
351
425
  }
352
-
353
- let readOnlyArgs: typeof args;
354
- if (args.tx != null) {
355
- readOnlyArgs = { ...args, tx: args.tx.asReadOnly() };
356
- } else {
357
- // If we don't have a tx then read-only/writable is irrelevant
358
- readOnlyArgs = args;
426
+ if (['POSTRUN', 'PRERESPOND', 'POSTRUN-ERROR'].includes(hookName)) {
427
+ // Any hooks after we "run" the query are executed in reverse order from newest to oldest
428
+ // as they'll be translating the query results from "latest" backwards to the model that
429
+ // was actually requested
430
+ hooks.reverse();
359
431
  }
360
432
 
361
- if ((args as HookArgs).request != null) {
362
- defineApi(args as HookArgs);
363
- if (args !== readOnlyArgs) {
364
- // Only try to define a separate read-only api if it's different
365
- defineApi(readOnlyArgs as HookArgs);
433
+ for (const [modelName, modelHooks] of hooks) {
434
+ const modelArgs = { ...args };
435
+ let modelReadOnlyArgs: typeof modelArgs;
436
+ if ((args as HookArgs).request != null) {
437
+ defineApi(modelName, modelArgs as HookArgs);
366
438
  }
367
- }
368
439
 
369
- await Promise.all(
370
- (hooks as Array<Hook<HookFn>>).map(async (hook) => {
371
- if (hook.readOnlyTx) {
372
- await hook.run(readOnlyArgs);
373
- } else {
374
- await hook.run(args);
375
- }
376
- }),
377
- );
440
+ await Promise.all(
441
+ (modelHooks as Array<Hook<HookFn>>).map(async (hook) => {
442
+ if (hook.readOnlyTx) {
443
+ modelReadOnlyArgs ??= getReadOnlyArgs(modelName, modelArgs);
444
+ await hook.run(modelReadOnlyArgs);
445
+ } else {
446
+ await hook.run(modelArgs);
447
+ }
448
+ }),
449
+ );
450
+ }
378
451
  };
@@ -1,7 +1,7 @@
1
1
  declare module '@balena/abstract-sql-compiler' {
2
2
  interface AbstractSqlTable {
3
3
  fetchProcessingFields?: {
4
- [field: string]: NonNullable<typeof sbvrTypes[string]['fetchProcessing']>;
4
+ [field: string]: NonNullable<SbvrType['fetchProcessing']>;
5
5
  };
6
6
  localFields?: {
7
7
  [odataName: string]: true;
@@ -16,7 +16,7 @@ import type {
16
16
  import type { Result, Row } from '../database-layer/db';
17
17
 
18
18
  import { sqlNameToODataName } from '@balena/odata-to-abstract-sql';
19
- import * as sbvrTypes from '@balena/sbvr-types';
19
+ import sbvrTypes, { SbvrType } from '@balena/sbvr-types';
20
20
  import * as _ from 'lodash';
21
21
  import { resolveNavigationResource, resolveSynonym } from './sbvr-utils';
22
22
 
@@ -36,20 +36,20 @@ const checkForExpansion = async (
36
36
  if (typeof field === 'string') {
37
37
  try {
38
38
  field = JSON.parse(field);
39
- } catch (_e) {
39
+ } catch {
40
40
  // If we can't JSON.parse the field then we use it directly.
41
41
  }
42
42
  }
43
43
 
44
+ const mappingResourceName = resolveNavigationResource(
45
+ {
46
+ abstractSqlModel,
47
+ vocabulary: vocab,
48
+ resourceName: parentResourceName,
49
+ },
50
+ fieldName,
51
+ );
44
52
  if (Array.isArray(field)) {
45
- const mappingResourceName = resolveNavigationResource(
46
- {
47
- abstractSqlModel,
48
- vocabulary: vocab,
49
- resourceName: parentResourceName,
50
- },
51
- fieldName,
52
- );
53
53
  const expandedField = await process(
54
54
  vocab,
55
55
  abstractSqlModel,
@@ -59,14 +59,6 @@ const checkForExpansion = async (
59
59
  );
60
60
  row[fieldName] = expandedField;
61
61
  } else {
62
- const mappingResourceName = resolveNavigationResource(
63
- {
64
- abstractSqlModel,
65
- vocabulary: vocab,
66
- resourceName: parentResourceName,
67
- },
68
- fieldName,
69
- );
70
62
  row[fieldName] = {
71
63
  __id: field,
72
64
  };
@@ -96,8 +88,7 @@ const getLocalFields = (table: AbstractSqlTable) => {
96
88
  if (table.localFields == null) {
97
89
  table.localFields = {};
98
90
  for (const { fieldName, dataType } of table.fields) {
99
- // TODO-MAJOR: This should also include ConceptType fields
100
- if (dataType !== 'ForeignKey') {
91
+ if (!['ForeignKey', 'ConceptType'].includes(dataType)) {
101
92
  const odataName = sqlNameToODataName(fieldName);
102
93
  table.localFields[odataName] = true;
103
94
  }
@@ -109,12 +100,16 @@ const getFetchProcessingFields = (table: AbstractSqlTable) => {
109
100
  return (table.fetchProcessingFields ??= _(table.fields)
110
101
  .filter(
111
102
  ({ dataType }) =>
112
- sbvrTypes[dataType] != null &&
113
- sbvrTypes[dataType].fetchProcessing != null,
103
+ (sbvrTypes[dataType as keyof typeof sbvrTypes] as SbvrType)
104
+ ?.fetchProcessing != null,
114
105
  )
115
106
  .map(({ fieldName, dataType }) => {
116
107
  const odataName = sqlNameToODataName(fieldName);
117
- return [odataName, sbvrTypes[dataType].fetchProcessing];
108
+ return [
109
+ odataName,
110
+ (sbvrTypes[dataType as keyof typeof sbvrTypes] as SbvrType)
111
+ .fetchProcessing,
112
+ ];
118
113
  })
119
114
  .fromPairs()
120
115
  .value());
@@ -160,16 +155,16 @@ export const process = async (
160
155
  );
161
156
 
162
157
  const odataIdField = sqlNameToODataName(table.idField);
163
- rows.forEach((row) => {
164
- processedFields.forEach((fieldName) => {
158
+ for (const row of rows) {
159
+ for (const fieldName of processedFields) {
165
160
  row[fieldName] = fetchProcessingFields[fieldName](row[fieldName]);
166
- });
161
+ }
167
162
  if (includeMetadata) {
168
163
  row.__metadata = {
169
164
  uri: resourceURI(vocab, resourceName, row[odataIdField]),
170
165
  };
171
166
  }
172
- });
167
+ }
173
168
 
174
169
  if (expandableFields.length > 0) {
175
170
  await Promise.all(