@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.
- package/.pinejs-cache.json +1 -1
- package/.versionbot/CHANGELOG.yml +13 -1
- package/CHANGELOG.md +5 -0
- package/VERSION +1 -1
- package/out/bin/odata-compiler.js +4 -1
- package/out/bin/odata-compiler.js.map +1 -1
- package/out/config-loader/config-loader.d.ts +5 -2
- package/out/config-loader/config-loader.js +32 -16
- package/out/config-loader/config-loader.js.map +1 -1
- package/out/sbvr-api/abstract-sql.js +3 -1
- package/out/sbvr-api/abstract-sql.js.map +1 -1
- package/out/sbvr-api/hooks.d.ts +3 -3
- package/out/sbvr-api/hooks.js +44 -29
- package/out/sbvr-api/hooks.js.map +1 -1
- package/out/sbvr-api/permissions.js +5 -3
- package/out/sbvr-api/permissions.js.map +1 -1
- package/out/sbvr-api/sbvr-utils.d.ts +8 -2
- package/out/sbvr-api/sbvr-utils.js +124 -56
- package/out/sbvr-api/sbvr-utils.js.map +1 -1
- package/out/sbvr-api/translations.d.ts +6 -0
- package/out/sbvr-api/translations.js +136 -0
- package/out/sbvr-api/translations.js.map +1 -0
- package/out/sbvr-api/uri-parser.d.ts +4 -1
- package/out/sbvr-api/uri-parser.js +2 -0
- package/out/sbvr-api/uri-parser.js.map +1 -1
- package/package.json +2 -2
- package/src/bin/odata-compiler.ts +4 -2
- package/src/config-loader/config-loader.ts +62 -24
- package/src/sbvr-api/abstract-sql.ts +2 -1
- package/src/sbvr-api/hooks.ts +80 -33
- package/src/sbvr-api/permissions.ts +9 -3
- package/src/sbvr-api/sbvr-utils.ts +193 -77
- package/src/sbvr-api/translations.ts +219 -0
- package/src/sbvr-api/uri-parser.ts +6 -1
package/src/sbvr-api/hooks.ts
CHANGED
|
@@ -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 (
|
|
134
|
+
if (hooksList == null) {
|
|
132
135
|
return;
|
|
133
136
|
}
|
|
134
|
-
const sideEffectHooks =
|
|
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
|
|
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
|
-
|
|
195
|
+
vocabHooks,
|
|
187
196
|
getResourceHooks(methodHooks['all'], resourceName),
|
|
188
197
|
);
|
|
189
198
|
};
|
|
190
199
|
const getMethodHooks = memoize(
|
|
191
|
-
(
|
|
200
|
+
(
|
|
201
|
+
method: SupportedMethod,
|
|
202
|
+
vocabulary: string,
|
|
203
|
+
resourceName: string | undefined,
|
|
204
|
+
includeAllVocab: boolean,
|
|
205
|
+
) =>
|
|
192
206
|
mergeHooks(
|
|
193
|
-
getVocabHooks(
|
|
194
|
-
|
|
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(
|
|
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 {
|
|
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[
|
|
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
|
-
|
|
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
|
|
386
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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
|
-
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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) {
|