@balena/pinejs 14.60.1 → 14.61.0

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.
@@ -42,7 +42,9 @@ 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;
@@ -53,6 +55,7 @@ export interface Hooks {
53
55
  data?: any;
54
56
  },
55
57
  ) => HookResponse;
58
+ /** These are run in reverse translation order from newest to oldest */
56
59
  'POSTRUN-ERROR'?: (
57
60
  options: HookArgs & { error: TypedError | any },
58
61
  ) => HookResponse;
@@ -126,13 +129,13 @@ class SideEffectHook<T extends HookFn> extends Hook<T> {
126
129
 
127
130
  // The execution order of rollback actions is unspecified
128
131
  export const rollbackRequestHooks = <T extends InstantiatedHooks>(
129
- hooks: T | undefined,
132
+ hooksList: Array<[modelName: string, hooks: T]> | undefined,
130
133
  ): void => {
131
- if (hooks == null) {
134
+ if (hooksList == null) {
132
135
  return;
133
136
  }
134
- const sideEffectHooks = Object.values(hooks)
135
- .flatMap((v): Array<Hook<HookFn>> => v)
137
+ const sideEffectHooks = hooksList
138
+ .flatMap(([, v]): Array<Hook<HookFn>> => Object.values(v).flat())
136
139
  .filter(
137
140
  (hook): hook is SideEffectHook<HookFn> => hook instanceof SideEffectHook,
138
141
  );
@@ -177,21 +180,37 @@ const getResourceHooks = (vocabHooks: VocabHooks, resourceName?: string) => {
177
180
  const getVocabHooks = (
178
181
  methodHooks: MethodHooks,
179
182
  vocabulary: string,
180
- resourceName?: string,
183
+ resourceName: string | undefined,
184
+ includeAllVocab: boolean,
181
185
  ) => {
182
186
  if (methodHooks == null) {
183
187
  return {};
184
188
  }
189
+ const vocabHooks = getResourceHooks(methodHooks[vocabulary], resourceName);
190
+ if (!includeAllVocab) {
191
+ // Do not include `vocabulary='all'` hooks, useful for translated vocabularies
192
+ return vocabHooks;
193
+ }
185
194
  return mergeHooks(
186
- getResourceHooks(methodHooks[vocabulary], resourceName),
195
+ vocabHooks,
187
196
  getResourceHooks(methodHooks['all'], resourceName),
188
197
  );
189
198
  };
190
199
  const getMethodHooks = memoize(
191
- (method: SupportedMethod, vocabulary: string, resourceName?: string) =>
200
+ (
201
+ method: SupportedMethod,
202
+ vocabulary: string,
203
+ resourceName: string | undefined,
204
+ includeAllVocab: boolean,
205
+ ) =>
192
206
  mergeHooks(
193
- getVocabHooks(apiHooks[method], vocabulary, resourceName),
194
- getVocabHooks(apiHooks['all'], vocabulary, resourceName),
207
+ getVocabHooks(
208
+ apiHooks[method],
209
+ vocabulary,
210
+ resourceName,
211
+ includeAllVocab,
212
+ ),
213
+ getVocabHooks(apiHooks['all'], vocabulary, resourceName, includeAllVocab),
195
214
  ),
196
215
  { primitive: true },
197
216
  );
@@ -200,6 +219,7 @@ export const getHooks = (
200
219
  OptionalField<ParsedODataRequest, 'resourceName'>,
201
220
  'resourceName' | 'method' | 'vocabulary'
202
221
  >,
222
+ includeAllVocab: boolean,
203
223
  ): InstantiatedHooks => {
204
224
  let { resourceName } = request;
205
225
  if (resourceName != null) {
@@ -208,10 +228,17 @@ export const getHooks = (
208
228
  ParsedODataRequest,
209
229
  'resourceName' | 'method' | 'vocabulary'
210
230
  >,
211
- );
231
+ )
232
+ // Remove version suffixes
233
+ .replace(/\$.*$/, '');
212
234
  }
213
235
  return instantiateHooks(
214
- getMethodHooks(request.method, request.vocabulary, resourceName),
236
+ getMethodHooks(
237
+ request.method,
238
+ request.vocabulary,
239
+ resourceName,
240
+ includeAllVocab,
241
+ ),
215
242
  );
216
243
  };
217
244
  getHooks.clear = () => getMethodHooks.clear();
@@ -343,12 +370,11 @@ export const addPureHook = (
343
370
  });
344
371
  };
345
372
 
346
- const defineApi = (args: HookArgs) => {
347
- const { request, req, tx } = args;
348
- const { vocabulary } = request;
373
+ const defineApi = (modelName: string, args: HookArgs) => {
374
+ const { req, tx } = args;
349
375
  Object.defineProperty(args, 'api', {
350
376
  get: _.once(() =>
351
- api[vocabulary].clone({
377
+ api[modelName].clone({
352
378
  passthrough: { req, tx },
353
379
  }),
354
380
  ),
@@ -360,6 +386,7 @@ type RunHookArgs<T extends keyof Hooks> = Omit<
360
386
  'api'
361
387
  >;
362
388
  const getReadOnlyArgs = <T extends keyof Hooks>(
389
+ modelName: string,
363
390
  args: RunHookArgs<T>,
364
391
  ): RunHookArgs<T> => {
365
392
  if (args.tx == null || args.tx.isReadOnly()) {
@@ -369,38 +396,58 @@ const getReadOnlyArgs = <T extends keyof Hooks>(
369
396
  let readOnlyArgs: typeof args;
370
397
  readOnlyArgs = { ...args, tx: args.tx.asReadOnly() };
371
398
  if ((args as HookArgs).request != null) {
372
- defineApi(readOnlyArgs as HookArgs);
399
+ defineApi(modelName, readOnlyArgs as HookArgs);
373
400
  }
374
401
  return readOnlyArgs;
375
402
  };
376
403
 
377
404
  export const runHooks = async <T extends keyof Hooks>(
378
405
  hookName: T,
379
- hooksList: InstantiatedHooks | undefined,
406
+ /**
407
+ * A list of modelName/hooks to run in order, which will be reversed for hooks after the "RUN" stage,
408
+ * ie POSTRUN/PRERESPOND/POSTRUN-ERROR
409
+ */
410
+ hooksList: Array<[modelName: string, hooks: InstantiatedHooks]> | undefined,
380
411
  args: RunHookArgs<T>,
381
412
  ) => {
382
413
  if (hooksList == null) {
383
414
  return;
384
415
  }
385
- const hooks = hooksList[hookName];
386
- if (hooks == null || hooks.length === 0) {
416
+ const hooks = hooksList
417
+ .map(([modelName, $hooks]): [string, InstantiatedHooks[T] | undefined] => [
418
+ modelName,
419
+ $hooks[hookName],
420
+ ])
421
+ .filter(
422
+ (v): v is [string, InstantiatedHooks[T]] =>
423
+ v[1] != null && v[1].length > 0,
424
+ );
425
+ if (hooks.length === 0) {
387
426
  return;
388
427
  }
389
-
390
- if ((args as HookArgs).request != null) {
391
- defineApi(args as HookArgs);
428
+ if (['POSTRUN', 'PRERESPOND', 'POSTRUN-ERROR'].includes(hookName)) {
429
+ // Any hooks after we "run" the query are executed in reverse order from newest to oldest
430
+ // as they'll be translating the query results from "latest" backwards to the model that
431
+ // was actually requested
432
+ hooks.reverse();
392
433
  }
393
434
 
394
- let readOnlyArgs: RunHookArgs<T>;
435
+ for (const [modelName, modelHooks] of hooks) {
436
+ const modelArgs = { ...args };
437
+ let modelReadOnlyArgs: typeof modelArgs;
438
+ if ((args as HookArgs).request != null) {
439
+ defineApi(modelName, modelArgs as HookArgs);
440
+ }
395
441
 
396
- await Promise.all(
397
- (hooks as Array<Hook<HookFn>>).map(async (hook) => {
398
- if (hook.readOnlyTx) {
399
- readOnlyArgs ??= getReadOnlyArgs(args);
400
- await hook.run(readOnlyArgs);
401
- } else {
402
- await hook.run(args);
403
- }
404
- }),
405
- );
442
+ await Promise.all(
443
+ (modelHooks as Array<Hook<HookFn>>).map(async (hook) => {
444
+ if (hook.readOnlyTx) {
445
+ modelReadOnlyArgs ??= getReadOnlyArgs(modelName, args);
446
+ await hook.run(modelReadOnlyArgs);
447
+ } else {
448
+ await hook.run(modelArgs);
449
+ }
450
+ }),
451
+ );
452
+ }
406
453
  };
@@ -140,7 +140,7 @@ const $parsePermissions = env.createCache(
140
140
  },
141
141
  );
142
142
 
143
- const rewriteBinds = (
143
+ const rewriteODataBinds = (
144
144
  { tree, extraBinds }: { tree: ODataQuery; extraBinds: ODataBinds },
145
145
  odataBinds: ODataBinds,
146
146
  ): ODataQuery => {
@@ -163,7 +163,7 @@ const parsePermissions = (
163
163
  odataBinds: ODataBinds,
164
164
  ): ODataQuery => {
165
165
  const odata = $parsePermissions(filter);
166
- return rewriteBinds(odata, odataBinds);
166
+ return rewriteODataBinds(odata, odataBinds);
167
167
  };
168
168
 
169
169
  // Traverses all values in `check`, actions for the following data types:
@@ -1050,6 +1050,7 @@ const getBoundConstrainedMemoizer = memoizeWeak(
1050
1050
  sqlNameToODataName(permissionsTable.name),
1051
1051
  ),
1052
1052
  );
1053
+
1053
1054
  return permissionsTable;
1054
1055
  },
1055
1056
  },
@@ -1678,7 +1679,8 @@ export const addPermissions = async (
1678
1679
  req: PermissionReq,
1679
1680
  request: ODataRequest & { permissionType?: PermissionCheck },
1680
1681
  ): Promise<void> => {
1681
- const { vocabulary, resourceName, odataQuery, odataBinds } = request;
1682
+ const { resourceName, odataQuery, odataBinds } = request;
1683
+ const vocabulary = _.last(request.translateVersions)!;
1682
1684
  let abstractSqlModel = sbvrUtils.getAbstractSqlModel(request);
1683
1685
 
1684
1686
  let { permissionType } = request;
@@ -1801,6 +1803,10 @@ export const setup = () => {
1801
1803
  0,
1802
1804
  -'#canAccess'.length,
1803
1805
  );
1806
+ request.originalResourceName = request.originalResourceName.slice(
1807
+ 0,
1808
+ -'#canAccess'.length,
1809
+ );
1804
1810
  const resourceName = sbvrUtils.resolveSynonym(request);
1805
1811
  const resourceTable = abstractSqlModel.tables[resourceName];
1806
1812
  if (resourceTable == null) {