@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.
@@ -1,6 +1,6 @@
1
1
  - commits:
2
2
  - subject: Add multipart upload actions
3
- hash: 6e3f5d12bf1644c266f898558c5a1a25ef595f2b
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: b20dcfaa27fbc7a3d64600b55882a65cdaa99c59
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: 57ea3eb16fe9ec8e28ea12b969da062ec4374a2d
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-01T13:16:35.320Z
30
+ date: 2025-05-01T14:26:05.381Z
31
31
  - commits:
32
32
  - subject: Add multipart upload interface for handlers
33
33
  hash: d78ee0da169cb9b252bfe8d51c4f57cd10051475
@@ -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 (!action) {
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: request,
28
+ request,
29
29
  tx,
30
30
  api: applicationApi,
31
- req: 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 (!applicationApi) {
50
+ if (applicationApi == null) {
51
51
  throw new BadRequestError(`Could not find model ${request.vocabulary}`);
52
52
  }
53
- const res = await api[request.vocabulary].request({
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 (!item || typeof item !== 'object') {
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 || !item[keys[0]]) {
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":"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"}
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 fieldNames = Object.keys(request.values);
45
- if (fieldNames.length !== 1) {
46
- throw new BadRequestError('You can only get upload url for one field at a time');
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 [fieldName] = fieldNames;
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 = parseBeginUploadPayload(request.values[fieldName], webResourceHandler);
54
- if (beginUploadPayload == null) {
55
- throw new BadRequestError('Invalid file metadata');
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":"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"}
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-6e3f5d12bf1644c266f898558c5a1a25ef595f2b-1",
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-01T13:16:36.421Z"
152
+ "publishedAt": "2025-05-01T14:26:06.408Z"
153
153
  }
154
154
  }
@@ -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 = {} as {
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 (!action) {
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: request,
85
+ request,
86
86
  tx,
87
87
  api: applicationApi,
88
- req: 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 (!applicationApi) {
121
+ if (applicationApi == null) {
122
122
  throw new BadRequestError(`Could not find model ${request.vocabulary}`);
123
123
  }
124
124
 
125
- const res = await api[request.vocabulary].request({
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 (!item || typeof item !== 'object') {
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 || !item[keys[0]]) {
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 fieldNames = Object.keys(request.values);
77
- if (fieldNames.length !== 1) {
78
- throw new BadRequestError(
79
- 'You can only get upload url for one field at a time',
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 [fieldName] = fieldNames;
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
- const beginUploadPayload = parseBeginUploadPayload(
92
- request.values[fieldName],
93
- webResourceHandler,
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
- typeof filename !== 'string' ||
116
- typeof content_type !== 'string' ||
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
- return null;
123
+ throw new BadRequestError('Chunk size is too small');
123
124
  }
124
125
 
125
- chunk_size ??= webResourceHandler.multipartUpload.getDefaultPartSize();
126
-
127
- return { filename, content_type, size, chunk_size };
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