@balena/pinejs 21.4.0-build-add-actions-example-6e3f5d12bf1644c266f898558c5a1a25ef595f2b-1 → 21.4.0-build-add-actions-example-b618c940523a81638a208dcc973e5b68c185fa53-1
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/.versionbot/CHANGELOG.yml +4 -4
- package/out/sbvr-api/actions.js +8 -8
- package/out/sbvr-api/actions.js.map +1 -1
- package/out/webresource-handler/actions/beginUpload.js +31 -24
- package/out/webresource-handler/actions/beginUpload.js.map +1 -1
- package/package.json +2 -2
- package/src/sbvr-api/actions.ts +10 -10
- package/src/webresource-handler/actions/beginUpload.ts +43 -40
@@ -1,6 +1,6 @@
|
|
1
1
|
- commits:
|
2
2
|
- subject: Add multipart upload actions
|
3
|
-
hash:
|
3
|
+
hash: b618c940523a81638a208dcc973e5b68c185fa53
|
4
4
|
body: ""
|
5
5
|
footer:
|
6
6
|
Change-type: minor
|
@@ -8,7 +8,7 @@
|
|
8
8
|
author: Otavio Jacobi
|
9
9
|
nested: []
|
10
10
|
- subject: Add webresource vocabulary
|
11
|
-
hash:
|
11
|
+
hash: ee090f424ed4b62b969cbfa046cfc50384e658b7
|
12
12
|
body: ""
|
13
13
|
footer:
|
14
14
|
Change-type: minor
|
@@ -16,7 +16,7 @@
|
|
16
16
|
author: Otavio Jacobi
|
17
17
|
nested: []
|
18
18
|
- subject: Add support for defining odata actions
|
19
|
-
hash:
|
19
|
+
hash: ae9038ff8df4e301ebeef7c283c978fcae5ea4e4
|
20
20
|
body: ""
|
21
21
|
footer:
|
22
22
|
See: https://balena.fibery.io/search/9OI72#Work/Improvement/Add-odata-actions-foundation-for-pinejs-2818
|
@@ -27,7 +27,7 @@
|
|
27
27
|
nested: []
|
28
28
|
version: 21.4.0
|
29
29
|
title: ""
|
30
|
-
date: 2025-05-
|
30
|
+
date: 2025-05-01T14:26:05.381Z
|
31
31
|
- commits:
|
32
32
|
- subject: Add multipart upload interface for handlers
|
33
33
|
hash: d78ee0da169cb9b252bfe8d51c4f57cd10051475
|
package/out/sbvr-api/actions.js
CHANGED
@@ -14,7 +14,7 @@ export const runAction = async (request, req) => {
|
|
14
14
|
}
|
15
15
|
const actionName = request.odataQuery.property.resource;
|
16
16
|
const action = actions?.[request.vocabulary]?.[request.resourceName]?.[actionName];
|
17
|
-
if (
|
17
|
+
if (action == null) {
|
18
18
|
throw new BadRequestError();
|
19
19
|
}
|
20
20
|
return sbvrUtils.db.transaction(async (tx) => {
|
@@ -25,10 +25,10 @@ export const runAction = async (request, req) => {
|
|
25
25
|
passthrough: { tx, req },
|
26
26
|
});
|
27
27
|
return await action({
|
28
|
-
request
|
28
|
+
request,
|
29
29
|
tx,
|
30
30
|
api: applicationApi,
|
31
|
-
req
|
31
|
+
req,
|
32
32
|
id,
|
33
33
|
});
|
34
34
|
});
|
@@ -41,16 +41,16 @@ export const addAction = (vocabulary, resourceName, actionName, action) => {
|
|
41
41
|
export const canRunAction = async (request, req, actionName, tx) => {
|
42
42
|
const canAccessUrl = request.url
|
43
43
|
.slice(1)
|
44
|
-
.split('?')[0]
|
44
|
+
.split('?', 1)[0]
|
45
45
|
.replace(new RegExp(`(${actionName})$`), 'canAccess');
|
46
46
|
if (!canAccessUrl.endsWith('/canAccess')) {
|
47
47
|
throw new UnauthorizedError();
|
48
48
|
}
|
49
49
|
const applicationApi = api[request.vocabulary];
|
50
|
-
if (
|
50
|
+
if (applicationApi == null) {
|
51
51
|
throw new BadRequestError(`Could not find model ${request.vocabulary}`);
|
52
52
|
}
|
53
|
-
const res = await
|
53
|
+
const res = await applicationApi.request({
|
54
54
|
method: 'POST',
|
55
55
|
url: canAccessUrl,
|
56
56
|
body: { action: actionName },
|
@@ -60,11 +60,11 @@ export const canRunAction = async (request, req, actionName, tx) => {
|
|
60
60
|
};
|
61
61
|
const canAccessResourceId = (canAccessResponse) => {
|
62
62
|
const item = canAccessResponse?.d?.[0];
|
63
|
-
if (
|
63
|
+
if (item == null || typeof item !== 'object') {
|
64
64
|
throw new UnauthorizedError();
|
65
65
|
}
|
66
66
|
const keys = Object.keys(item);
|
67
|
-
if (keys.length !== 1 ||
|
67
|
+
if (keys.length !== 1 || item[keys[0]] == null) {
|
68
68
|
throw new UnauthorizedError();
|
69
69
|
}
|
70
70
|
return item[keys[0]];
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/sbvr-api/actions.ts"],"names":[],"mappings":"AACA,OAAO,EACN,eAAe,GAEf,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,GAAG,EAAiB,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAuB1D,MAAM,OAAO,
|
1
|
+
{"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/sbvr-api/actions.ts"],"names":[],"mappings":"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"}
|
@@ -5,6 +5,23 @@ import { BadRequestError, NotImplementedError } from '../../sbvr-api/errors.js';
|
|
5
5
|
import { permissions, sbvrUtils } from '../../server-glue/module.js';
|
6
6
|
import { TransactionClosedError } from '../../database-layer/db.js';
|
7
7
|
import { randomUUID } from 'crypto';
|
8
|
+
import { ajv } from '../../tasks/common.js';
|
9
|
+
const beginUploadPayloadSchema = {
|
10
|
+
type: 'object',
|
11
|
+
minProperties: 1,
|
12
|
+
maxProperties: 1,
|
13
|
+
additionalProperties: {
|
14
|
+
type: 'object',
|
15
|
+
properties: {
|
16
|
+
filename: { type: 'string' },
|
17
|
+
content_type: { type: 'string' },
|
18
|
+
size: { type: 'number' },
|
19
|
+
chunk_size: { type: 'number' },
|
20
|
+
},
|
21
|
+
required: ['filename', 'content_type', 'size'],
|
22
|
+
additionalProperties: false,
|
23
|
+
},
|
24
|
+
};
|
8
25
|
export const beginUploadAction = async ({ request, tx, id, req, }) => {
|
9
26
|
if (typeof id !== 'number') {
|
10
27
|
throw new NotImplementedError('multipart upload do not yet support non-numeric ids');
|
@@ -41,40 +58,30 @@ export const beginUploadAction = async ({ request, tx, id, req, }) => {
|
|
41
58
|
};
|
42
59
|
};
|
43
60
|
const parseBeginUpload = (request, webResourceHandler) => {
|
44
|
-
const
|
45
|
-
|
46
|
-
|
61
|
+
const { values } = request;
|
62
|
+
const isValid = ajv.validate(beginUploadPayloadSchema, values);
|
63
|
+
if (!isValid) {
|
64
|
+
throw new BadRequestError('Invalid begin upload payload');
|
47
65
|
}
|
48
|
-
const
|
66
|
+
const fieldName = Object.keys(values)[0];
|
49
67
|
const webResourceFields = getWebResourceFields(request, false);
|
50
68
|
if (!webResourceFields.includes(fieldName)) {
|
51
69
|
throw new BadRequestError(`The provided field '${fieldName}' is not a valid webresource`);
|
52
70
|
}
|
53
|
-
const beginUploadPayload =
|
54
|
-
|
55
|
-
|
71
|
+
const beginUploadPayload = {
|
72
|
+
...values[fieldName],
|
73
|
+
chunk_size: values[fieldName].chunk_size ??
|
74
|
+
webResourceHandler.multipartUpload.getMinimumPartSize(),
|
75
|
+
};
|
76
|
+
if (beginUploadPayload.chunk_size <
|
77
|
+
webResourceHandler.multipartUpload.getMinimumPartSize()) {
|
78
|
+
throw new BadRequestError('Chunk size is too small');
|
56
79
|
}
|
57
80
|
return {
|
58
|
-
beginUploadPayload,
|
81
|
+
beginUploadPayload: beginUploadPayload,
|
59
82
|
fieldName,
|
60
83
|
};
|
61
84
|
};
|
62
|
-
const parseBeginUploadPayload = (payload, webResourceHandler) => {
|
63
|
-
if (payload == null || typeof payload !== 'object') {
|
64
|
-
return null;
|
65
|
-
}
|
66
|
-
let { filename, content_type, size, chunk_size } = payload;
|
67
|
-
if (typeof filename !== 'string' ||
|
68
|
-
typeof content_type !== 'string' ||
|
69
|
-
typeof size !== 'number' ||
|
70
|
-
(chunk_size != null && typeof chunk_size !== 'number') ||
|
71
|
-
(chunk_size != null &&
|
72
|
-
chunk_size < webResourceHandler.multipartUpload.getMinimumPartSize())) {
|
73
|
-
return null;
|
74
|
-
}
|
75
|
-
chunk_size ??= webResourceHandler.multipartUpload.getDefaultPartSize();
|
76
|
-
return { filename, content_type, size, chunk_size };
|
77
|
-
};
|
78
85
|
const runFakeDbPatch = async (request, fakeDbPatch) => {
|
79
86
|
try {
|
80
87
|
await sbvrUtils.db.transaction(async (fakeTx) => {
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"beginUpload.js","sourceRoot":"","sources":["../../../src/webresource-handler/actions/beginUpload.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"beginUpload.js","sourceRoot":"","sources":["../../../src/webresource-handler/actions/beginUpload.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@balena/pinejs",
|
3
|
-
"version": "21.4.0-build-add-actions-example-
|
3
|
+
"version": "21.4.0-build-add-actions-example-b618c940523a81638a208dcc973e5b68c185fa53-1",
|
4
4
|
"main": "out/server-glue/module.js",
|
5
5
|
"type": "module",
|
6
6
|
"repository": "git@github.com:balena-io/pinejs.git",
|
@@ -149,6 +149,6 @@
|
|
149
149
|
"recursive": true
|
150
150
|
},
|
151
151
|
"versionist": {
|
152
|
-
"publishedAt": "2025-05-
|
152
|
+
"publishedAt": "2025-05-01T14:26:06.408Z"
|
153
153
|
}
|
154
154
|
}
|
package/src/sbvr-api/actions.ts
CHANGED
@@ -30,13 +30,13 @@ export type ODataAction<Vocab extends string = string> = (
|
|
30
30
|
args: ODataActionArgs<Vocab>,
|
31
31
|
) => Promise<Response>;
|
32
32
|
|
33
|
-
const actions
|
33
|
+
const actions: {
|
34
34
|
[vocab: string]: {
|
35
35
|
[resourceName: string]: {
|
36
36
|
[actionName: string]: ODataAction;
|
37
37
|
};
|
38
38
|
};
|
39
|
-
};
|
39
|
+
} = {};
|
40
40
|
|
41
41
|
export const isActionRequest = (
|
42
42
|
request: ParsedODataRequest,
|
@@ -63,7 +63,7 @@ export const runAction = async (
|
|
63
63
|
const actionName = request.odataQuery.property.resource;
|
64
64
|
const action =
|
65
65
|
actions?.[request.vocabulary]?.[request.resourceName]?.[actionName];
|
66
|
-
if (
|
66
|
+
if (action == null) {
|
67
67
|
throw new BadRequestError();
|
68
68
|
}
|
69
69
|
|
@@ -82,10 +82,10 @@ export const runAction = async (
|
|
82
82
|
});
|
83
83
|
|
84
84
|
return await action({
|
85
|
-
request
|
85
|
+
request,
|
86
86
|
tx,
|
87
87
|
api: applicationApi,
|
88
|
-
req
|
88
|
+
req,
|
89
89
|
id,
|
90
90
|
});
|
91
91
|
});
|
@@ -110,7 +110,7 @@ export const canRunAction = async (
|
|
110
110
|
) => {
|
111
111
|
const canAccessUrl = request.url
|
112
112
|
.slice(1)
|
113
|
-
.split('?')[0]
|
113
|
+
.split('?', 1)[0]
|
114
114
|
.replace(new RegExp(`(${actionName})$`), 'canAccess');
|
115
115
|
|
116
116
|
if (!canAccessUrl.endsWith('/canAccess')) {
|
@@ -118,11 +118,11 @@ export const canRunAction = async (
|
|
118
118
|
}
|
119
119
|
|
120
120
|
const applicationApi = api[request.vocabulary];
|
121
|
-
if (
|
121
|
+
if (applicationApi == null) {
|
122
122
|
throw new BadRequestError(`Could not find model ${request.vocabulary}`);
|
123
123
|
}
|
124
124
|
|
125
|
-
const res = await
|
125
|
+
const res = await applicationApi.request({
|
126
126
|
method: 'POST',
|
127
127
|
url: canAccessUrl,
|
128
128
|
body: { action: actionName },
|
@@ -134,11 +134,11 @@ export const canRunAction = async (
|
|
134
134
|
|
135
135
|
const canAccessResourceId = (canAccessResponse: AnyObject): unknown => {
|
136
136
|
const item = canAccessResponse?.d?.[0];
|
137
|
-
if (
|
137
|
+
if (item == null || typeof item !== 'object') {
|
138
138
|
throw new UnauthorizedError();
|
139
139
|
}
|
140
140
|
const keys = Object.keys(item);
|
141
|
-
if (keys.length !== 1 ||
|
141
|
+
if (keys.length !== 1 || item[keys[0]] == null) {
|
142
142
|
throw new UnauthorizedError();
|
143
143
|
}
|
144
144
|
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import type { AnyObject } from 'pinejs-client-core';
|
2
|
-
|
3
1
|
import { api, type Response } from '../../sbvr-api/sbvr-utils.js';
|
4
2
|
import type { MultipartUploadHandler } from '../multiparUpload.js';
|
5
3
|
import { getMultipartUploadHandler } from '../multiparUpload.js';
|
@@ -14,6 +12,8 @@ import type {
|
|
14
12
|
ODataActionArgs,
|
15
13
|
ODataActionRequest,
|
16
14
|
} from '../../sbvr-api/actions.js';
|
15
|
+
import { ajv } from '../../tasks/common.js';
|
16
|
+
import type { FromSchema } from 'json-schema-to-ts';
|
17
17
|
|
18
18
|
type FakeWebResourcePatch = {
|
19
19
|
[key: string]: Omit<WebResource, 'href'> & {
|
@@ -21,6 +21,25 @@ type FakeWebResourcePatch = {
|
|
21
21
|
};
|
22
22
|
};
|
23
23
|
|
24
|
+
const beginUploadPayloadSchema = {
|
25
|
+
type: 'object',
|
26
|
+
minProperties: 1,
|
27
|
+
maxProperties: 1,
|
28
|
+
additionalProperties: {
|
29
|
+
type: 'object',
|
30
|
+
properties: {
|
31
|
+
filename: { type: 'string' },
|
32
|
+
content_type: { type: 'string' },
|
33
|
+
size: { type: 'number' },
|
34
|
+
chunk_size: { type: 'number' },
|
35
|
+
},
|
36
|
+
required: ['filename', 'content_type', 'size'],
|
37
|
+
additionalProperties: false,
|
38
|
+
},
|
39
|
+
} as const;
|
40
|
+
|
41
|
+
type BeginUploadPayload = FromSchema<typeof beginUploadPayloadSchema>;
|
42
|
+
|
24
43
|
export const beginUploadAction = async ({
|
25
44
|
request,
|
26
45
|
tx,
|
@@ -73,58 +92,42 @@ const parseBeginUpload = (
|
|
73
92
|
request: ODataActionRequest,
|
74
93
|
webResourceHandler: MultipartUploadHandler,
|
75
94
|
) => {
|
76
|
-
const
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
95
|
+
const { values } = request;
|
96
|
+
const isValid = ajv.validate<BeginUploadPayload>(
|
97
|
+
beginUploadPayloadSchema,
|
98
|
+
values,
|
99
|
+
);
|
100
|
+
if (!isValid) {
|
101
|
+
throw new BadRequestError('Invalid begin upload payload');
|
81
102
|
}
|
82
103
|
|
83
|
-
const
|
104
|
+
const fieldName = Object.keys(values)[0];
|
105
|
+
|
84
106
|
const webResourceFields = getWebResourceFields(request, false);
|
85
107
|
if (!webResourceFields.includes(fieldName)) {
|
86
108
|
throw new BadRequestError(
|
87
109
|
`The provided field '${fieldName}' is not a valid webresource`,
|
88
110
|
);
|
89
111
|
}
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
if (beginUploadPayload == null) {
|
96
|
-
throw new BadRequestError('Invalid file metadata');
|
97
|
-
}
|
98
|
-
|
99
|
-
return {
|
100
|
-
beginUploadPayload,
|
101
|
-
fieldName,
|
112
|
+
const beginUploadPayload = {
|
113
|
+
...values[fieldName],
|
114
|
+
chunk_size:
|
115
|
+
values[fieldName].chunk_size ??
|
116
|
+
webResourceHandler.multipartUpload.getMinimumPartSize(),
|
102
117
|
};
|
103
|
-
};
|
104
118
|
|
105
|
-
const parseBeginUploadPayload = (
|
106
|
-
payload: AnyObject,
|
107
|
-
webResourceHandler: MultipartUploadHandler,
|
108
|
-
): BeginMultipartUploadPayload | null => {
|
109
|
-
if (payload == null || typeof payload !== 'object') {
|
110
|
-
return null;
|
111
|
-
}
|
112
|
-
|
113
|
-
let { filename, content_type, size, chunk_size } = payload;
|
114
119
|
if (
|
115
|
-
|
116
|
-
|
117
|
-
typeof size !== 'number' ||
|
118
|
-
(chunk_size != null && typeof chunk_size !== 'number') ||
|
119
|
-
(chunk_size != null &&
|
120
|
-
chunk_size < webResourceHandler.multipartUpload.getMinimumPartSize())
|
120
|
+
beginUploadPayload.chunk_size <
|
121
|
+
webResourceHandler.multipartUpload.getMinimumPartSize()
|
121
122
|
) {
|
122
|
-
|
123
|
+
throw new BadRequestError('Chunk size is too small');
|
123
124
|
}
|
124
125
|
|
125
|
-
|
126
|
-
|
127
|
-
|
126
|
+
return {
|
127
|
+
beginUploadPayload:
|
128
|
+
beginUploadPayload satisfies BeginMultipartUploadPayload,
|
129
|
+
fieldName,
|
130
|
+
};
|
128
131
|
};
|
129
132
|
|
130
133
|
// TODO: comment explaining why run this fake transaction is important
|