@balena/pinejs 20.0.0-build-esm-78a099606f287bc0df4001425788a7ad1c708ba1-1 → 20.0.0-build-esm-8834afea86dbfce451b8d32756c87887efe80ba9-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,14 @@
1
1
  - commits:
2
+ - subject: Drop exposed S3 handler in favor of @balena/pinejs-webresource-s3 handler
3
+ hash: 8834afea86dbfce451b8d32756c87887efe80ba9
4
+ body: ""
5
+ footer:
6
+ Change-type: major
7
+ change-type: major
8
+ author: Otavio Jacobi
9
+ nested: []
2
10
  - subject: Update commander to 13.x
3
- hash: 78a099606f287bc0df4001425788a7ad1c708ba1
11
+ hash: f816886183f4050542f86623445c9066ca5576ee
4
12
  body: ""
5
13
  footer:
6
14
  Change-type: major
@@ -8,7 +16,7 @@
8
16
  author: Pagan Gazzard
9
17
  nested: []
10
18
  - subject: Update chai to 5.x
11
- hash: 04952e365972ca26a8734e3e9e31434ae3515902
19
+ hash: c5d4111f5d1c12e173240bb27f0f6db20869d965
12
20
  body: ""
13
21
  footer:
14
22
  Change-type: patch
@@ -16,7 +24,7 @@
16
24
  author: Pagan Gazzard
17
25
  nested: []
18
26
  - subject: Convert to ESM
19
- hash: 03fe33f53910a935c23eb721db25c984d6c8caae
27
+ hash: 0d24ee717f247343394f67988f7327ce6457b387
20
28
  body: ""
21
29
  footer:
22
30
  Change-type: patch
@@ -25,7 +33,35 @@
25
33
  nested: []
26
34
  version: 20.0.0
27
35
  title: ""
28
- date: 2025-01-03T18:27:56.107Z
36
+ date: 2025-01-06T21:58:49.000Z
37
+ - commits:
38
+ - subject: "Tests: use `@balena/pinejs` for resolving to the runtime files"
39
+ hash: fc28f2efec230ea40150c14cbf7f062649f2fc40
40
+ body: ""
41
+ footer:
42
+ Change-type: patch
43
+ change-type: patch
44
+ author: Pagan Gazzard
45
+ nested: []
46
+ - subject: "Tests: avoid deep imports in favor of the standard module entrypoint"
47
+ hash: 8e26f0f37189809bfc517a580a37ff659a207ba6
48
+ body: ""
49
+ footer:
50
+ Change-type: patch
51
+ change-type: patch
52
+ author: Pagan Gazzard
53
+ nested: []
54
+ - subject: "Tests: run against compiled source rather than transpiling on the fly"
55
+ hash: cf6351dac10a89633624a5ebf3b1aad29f0ab8ec
56
+ body: ""
57
+ footer:
58
+ Change-type: patch
59
+ change-type: patch
60
+ author: Pagan Gazzard
61
+ nested: []
62
+ version: 19.7.3
63
+ title: ""
64
+ date: 2025-01-04T23:36:23.908Z
29
65
  - commits:
30
66
  - subject: Update dependencies
31
67
  hash: a909ee054f395de407ec4e1275fd0f5b7514fe29
@@ -2087,6 +2123,7 @@
2087
2123
 
2088
2124
 
2089
2125
 
2126
+
2090
2127
 
2091
2128
 
2092
2129
  As balena-lint
@@ -2130,6 +2167,7 @@
2130
2167
 
2131
2168
 
2132
2169
 
2170
+
2133
2171
 
2134
2172
 
2135
2173
  As engine and npm is
@@ -2188,6 +2226,7 @@
2188
2226
 
2189
2227
 
2190
2228
 
2229
+
2191
2230
 
2192
2231
 
2193
2232
  Ensure that the
@@ -2309,6 +2348,7 @@
2309
2348
 
2310
2349
 
2311
2350
 
2351
+
2312
2352
 
2313
2353
 
2314
2354
  This also deprecates
@@ -2358,6 +2398,7 @@
2358
2398
 
2359
2399
 
2360
2400
 
2401
+
2361
2402
 
2362
2403
 
2363
2404
  It can in fact be a
@@ -2408,6 +2449,7 @@
2408
2449
 
2409
2450
 
2410
2451
 
2452
+
2411
2453
 
2412
2454
 
2413
2455
  We know what type
@@ -2463,6 +2505,7 @@
2463
2505
 
2464
2506
 
2465
2507
 
2508
+
2466
2509
 
2467
2510
 
2468
2511
  Update
@@ -2629,6 +2672,7 @@
2629
2672
 
2630
2673
 
2631
2674
 
2675
+
2632
2676
 
2633
2677
 
2634
2678
  This also deprecates
package/CHANGELOG.md CHANGED
@@ -5,12 +5,20 @@ automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
5
5
  This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
7
  # v20.0.0
8
- ## (2025-01-03)
8
+ ## (2025-01-06)
9
9
 
10
+ * Drop exposed S3 handler in favor of @balena/pinejs-webresource-s3 handler [Otavio Jacobi]
10
11
  * Update commander to 13.x [Pagan Gazzard]
11
12
  * Update chai to 5.x [Pagan Gazzard]
12
13
  * Convert to ESM [Pagan Gazzard]
13
14
 
15
+ # v19.7.3
16
+ ## (2025-01-04)
17
+
18
+ * Tests: use `@balena/pinejs` for resolving to the runtime files [Pagan Gazzard]
19
+ * Tests: avoid deep imports in favor of the standard module entrypoint [Pagan Gazzard]
20
+ * Tests: run against compiled source rather than transpiling on the fly [Pagan Gazzard]
21
+
14
22
  # v19.7.2
15
23
  ## (2025-01-02)
16
24
 
@@ -1,6 +1,6 @@
1
+ import type { User as PineUser } from './sbvr-utils.js';
1
2
  declare global {
2
3
  namespace Express {
3
- type PineUser = import('./sbvr-utils.js').User;
4
4
  interface User extends PineUser {
5
5
  }
6
6
  interface Request {
@@ -9,4 +9,3 @@ declare global {
9
9
  }
10
10
  }
11
11
  }
12
- export {};
@@ -1,2 +1 @@
1
1
  export * from './NoopHandler.js';
2
- export * from './S3Handler.js';
@@ -1,3 +1,2 @@
1
1
  export * from './NoopHandler.js';
2
- export * from './S3Handler.js';
3
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/webresource-handler/handlers/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/webresource-handler/handlers/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balena/pinejs",
3
- "version": "20.0.0-build-esm-78a099606f287bc0df4001425788a7ad1c708ba1-1",
3
+ "version": "20.0.0-build-esm-8834afea86dbfce451b8d32756c87887efe80ba9-1",
4
4
  "main": "out/server-glue/module.js",
5
5
  "type": "module",
6
6
  "repository": "git@github.com:balena-io/pinejs.git",
@@ -19,10 +19,9 @@
19
19
  "webpack-server": "grunt --preload ts-node/register/transpile-only --gruntfile Gruntfile.cts server",
20
20
  "webpack-build": "npm run webpack-browser && npm run webpack-module && npm run webpack-server",
21
21
  "lint": "balena-lint -t tsconfig.dev.json -e js -e ts src build typings Gruntfile.cts && npx tsc --project tsconfig.dev.json --noEmit",
22
- "test": "npm run lint && npm run build && npm run webpack-build && npm run test:compose && npm run test:generated-types",
23
- "test:compose": "trap 'docker compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' INT; docker compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 TZ=UTC npm run mocha",
22
+ "test": "npm run build && npm run lint && npm run webpack-build && npm run test:compose && npm run test:generated-types",
23
+ "test:compose": "trap 'docker compose -f docker-compose.npm-test.yml down ; echo Stopped ; exit 0' INT; docker compose -f docker-compose.npm-test.yml up -d && sleep 2 && DATABASE_URL=postgres://docker:docker@localhost:5431/postgres PINEJS_WEBRESOURCE_MAXFILESIZE=1000000000 S3_ENDPOINT=http://localhost:43680 S3_ACCESS_KEY=USERNAME S3_SECRET_KEY=PASSWORD S3_STORAGE_ADAPTER_BUCKET=balena-pine-web-resources S3_REGION=us-east-1 PINEJS_QUEUE_CONCURRENCY=1 TZ=UTC npx mocha",
24
24
  "test:generated-types": "npm run generate-types && git diff --exit-code ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts",
25
- "mocha": "TS_NODE_FILES=true mocha",
26
25
  "lint-fix": "balena-lint -t tsconfig.dev.json -e js -e ts --fix src test build typings Gruntfile.cts",
27
26
  "generate-types": "node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/user.sbvr ./src/sbvr-api/user.ts && node ./bin/sbvr-compiler.js generate-types ./src/migrator/migrations.sbvr ./src/migrator/migrations.ts && node ./bin/sbvr-compiler.js generate-types ./src/sbvr-api/dev.sbvr ./src/sbvr-api/dev.ts && node ./bin/sbvr-compiler.js generate-types ./src/tasks/tasks.sbvr ./src/tasks/tasks.ts && balena-lint -t tsconfig.dev.json --fix ./src/sbvr-api/user.ts ./src/migrator/migrations.ts ./src/sbvr-api/dev.ts"
28
27
  },
@@ -69,6 +68,8 @@
69
68
  },
70
69
  "devDependencies": {
71
70
  "@balena/lint": "^8.2.8",
71
+ "@balena/pinejs": "file:./",
72
+ "@balena/pinejs-webresource-s3": "^1.0.2",
72
73
  "@faker-js/faker": "^9.3.0",
73
74
  "@types/busboy": "^1.5.4",
74
75
  "@types/chai": "^5.0.1",
@@ -147,6 +148,6 @@
147
148
  "recursive": true
148
149
  },
149
150
  "versionist": {
150
- "publishedAt": "2025-01-03T18:27:57.056Z"
151
+ "publishedAt": "2025-01-06T21:58:50.126Z"
151
152
  }
152
153
  }
@@ -1,10 +1,9 @@
1
1
  // Augment express.js with pinejs-specific attributes via declaration merging.
2
+ import type { User as PineUser } from './sbvr-utils.js';
2
3
 
3
4
  declare global {
4
5
  // eslint-disable-next-line @typescript-eslint/no-namespace
5
6
  namespace Express {
6
- type PineUser = import('./sbvr-utils.js').User;
7
-
8
7
  // Augment Express.User to include the props of our PineUser.
9
8
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
10
9
  interface User extends PineUser {}
@@ -1,2 +1 @@
1
1
  export * from './NoopHandler.js';
2
- export * from './S3Handler.js';
@@ -1,28 +0,0 @@
1
- import { type IncomingFile, type UploadResponse, type WebResourceHandler } from '../index.js';
2
- import type { WebResourceType as WebResource } from '@balena/sbvr-types';
3
- export interface S3HandlerProps {
4
- region: string;
5
- accessKey: string;
6
- secretKey: string;
7
- endpoint: string;
8
- bucket: string;
9
- maxSize?: number;
10
- signedUrlExpireTimeSeconds?: number;
11
- signedUrlCacheExpireTimeSeconds?: number;
12
- }
13
- export declare class S3Handler implements WebResourceHandler {
14
- private readonly config;
15
- private readonly bucket;
16
- private readonly maxFileSize;
17
- protected readonly signedUrlExpireTimeSeconds: number;
18
- protected readonly signedUrlCacheExpireTimeSeconds: number;
19
- protected cachedGetSignedUrl: (fileKey: string) => Promise<string>;
20
- private client;
21
- constructor(config: S3HandlerProps);
22
- handleFile(resource: IncomingFile): Promise<UploadResponse>;
23
- removeFile(href: string): Promise<void>;
24
- onPreRespond(webResource: WebResource): Promise<WebResource>;
25
- private s3SignUrl;
26
- private getS3URL;
27
- private getKeyFromHref;
28
- }
@@ -1,97 +0,0 @@
1
- import { FileSizeExceededError, normalizeHref, WebResourceError, } from '../index.js';
2
- import { S3Client, DeleteObjectCommand, GetObjectCommand, } from '@aws-sdk/client-s3';
3
- import { Upload } from '@aws-sdk/lib-storage';
4
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
5
- import { randomUUID } from 'crypto';
6
- import memoize from 'memoizee';
7
- export class S3Handler {
8
- config;
9
- bucket;
10
- maxFileSize;
11
- signedUrlExpireTimeSeconds;
12
- signedUrlCacheExpireTimeSeconds;
13
- cachedGetSignedUrl;
14
- client;
15
- constructor(config) {
16
- this.config = {
17
- region: config.region,
18
- credentials: {
19
- accessKeyId: config.accessKey,
20
- secretAccessKey: config.secretKey,
21
- },
22
- endpoint: config.endpoint,
23
- forcePathStyle: true,
24
- };
25
- this.signedUrlExpireTimeSeconds =
26
- config.signedUrlExpireTimeSeconds ?? 86400;
27
- this.signedUrlCacheExpireTimeSeconds =
28
- config.signedUrlCacheExpireTimeSeconds ?? 82800;
29
- this.maxFileSize = config.maxSize ?? 52428800;
30
- this.bucket = config.bucket;
31
- this.client = new S3Client(this.config);
32
- this.cachedGetSignedUrl = memoize(this.s3SignUrl, {
33
- maxAge: this.signedUrlCacheExpireTimeSeconds * 1000,
34
- });
35
- }
36
- async handleFile(resource) {
37
- let size = 0;
38
- const key = `${resource.fieldname}_${randomUUID()}_${resource.originalname}`;
39
- const params = {
40
- Bucket: this.bucket,
41
- Key: key,
42
- Body: resource.stream,
43
- ContentType: resource.mimetype,
44
- };
45
- const upload = new Upload({ client: this.client, params });
46
- upload.on('httpUploadProgress', async (ev) => {
47
- size = ev.total ?? ev.loaded;
48
- if (size > this.maxFileSize) {
49
- await upload.abort();
50
- }
51
- });
52
- try {
53
- await upload.done();
54
- }
55
- catch (err) {
56
- resource.stream.resume();
57
- if (size > this.maxFileSize) {
58
- throw new FileSizeExceededError(this.maxFileSize);
59
- }
60
- throw new WebResourceError(err);
61
- }
62
- const filename = this.getS3URL(key);
63
- return { size, filename };
64
- }
65
- async removeFile(href) {
66
- const fileKey = this.getKeyFromHref(href);
67
- const command = new DeleteObjectCommand({
68
- Bucket: this.bucket,
69
- Key: fileKey,
70
- });
71
- await this.client.send(command);
72
- }
73
- async onPreRespond(webResource) {
74
- if (webResource.href != null) {
75
- const fileKey = this.getKeyFromHref(webResource.href);
76
- webResource.href = await this.cachedGetSignedUrl(fileKey);
77
- }
78
- return webResource;
79
- }
80
- s3SignUrl(fileKey) {
81
- const command = new GetObjectCommand({
82
- Bucket: this.bucket,
83
- Key: fileKey,
84
- });
85
- return getSignedUrl(this.client, command, {
86
- expiresIn: this.signedUrlExpireTimeSeconds,
87
- });
88
- }
89
- getS3URL(key) {
90
- return `${this.config.endpoint}/${this.bucket}/${key}`;
91
- }
92
- getKeyFromHref(href) {
93
- const hrefWithoutParams = normalizeHref(href);
94
- return hrefWithoutParams.substring(hrefWithoutParams.lastIndexOf('/') + 1);
95
- }
96
- }
97
- //# sourceMappingURL=S3Handler.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"S3Handler.js","sourceRoot":"","sources":["../../../src/webresource-handler/handlers/S3Handler.ts"],"names":[],"mappings":"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"}
@@ -1,143 +0,0 @@
1
- import {
2
- FileSizeExceededError,
3
- type IncomingFile,
4
- normalizeHref,
5
- type UploadResponse,
6
- WebResourceError,
7
- type WebResourceHandler,
8
- } from '../index.js';
9
- import {
10
- S3Client,
11
- type S3ClientConfig,
12
- DeleteObjectCommand,
13
- type PutObjectCommandInput,
14
- GetObjectCommand,
15
- } from '@aws-sdk/client-s3';
16
- import { Upload } from '@aws-sdk/lib-storage';
17
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
18
-
19
- import { randomUUID } from 'crypto';
20
- import type { WebResourceType as WebResource } from '@balena/sbvr-types';
21
- import memoize from 'memoizee';
22
-
23
- export interface S3HandlerProps {
24
- region: string;
25
- accessKey: string;
26
- secretKey: string;
27
- endpoint: string;
28
- bucket: string;
29
- maxSize?: number;
30
- signedUrlExpireTimeSeconds?: number;
31
- signedUrlCacheExpireTimeSeconds?: number;
32
- }
33
-
34
- export class S3Handler implements WebResourceHandler {
35
- private readonly config: S3ClientConfig;
36
- private readonly bucket: string;
37
- private readonly maxFileSize: number;
38
-
39
- protected readonly signedUrlExpireTimeSeconds: number;
40
- protected readonly signedUrlCacheExpireTimeSeconds: number;
41
- protected cachedGetSignedUrl: (fileKey: string) => Promise<string>;
42
-
43
- private client: S3Client;
44
-
45
- constructor(config: S3HandlerProps) {
46
- this.config = {
47
- region: config.region,
48
- credentials: {
49
- accessKeyId: config.accessKey,
50
- secretAccessKey: config.secretKey,
51
- },
52
- endpoint: config.endpoint,
53
- forcePathStyle: true,
54
- };
55
-
56
- this.signedUrlExpireTimeSeconds =
57
- config.signedUrlExpireTimeSeconds ?? 86400; // 24h
58
- this.signedUrlCacheExpireTimeSeconds =
59
- config.signedUrlCacheExpireTimeSeconds ?? 82800; // 22h
60
-
61
- this.maxFileSize = config.maxSize ?? 52428800;
62
- this.bucket = config.bucket;
63
- this.client = new S3Client(this.config);
64
-
65
- // Memoize expects maxAge in MS and s3 signing method in seconds.
66
- // Normalization to use only seconds and therefore convert here from seconds to MS
67
- this.cachedGetSignedUrl = memoize(this.s3SignUrl, {
68
- maxAge: this.signedUrlCacheExpireTimeSeconds * 1000,
69
- });
70
- }
71
-
72
- public async handleFile(resource: IncomingFile): Promise<UploadResponse> {
73
- let size = 0;
74
- const key = `${resource.fieldname}_${randomUUID()}_${
75
- resource.originalname
76
- }`;
77
- const params: PutObjectCommandInput = {
78
- Bucket: this.bucket,
79
- Key: key,
80
- Body: resource.stream,
81
- ContentType: resource.mimetype,
82
- };
83
- const upload = new Upload({ client: this.client, params });
84
-
85
- upload.on('httpUploadProgress', async (ev) => {
86
- size = ev.total ?? ev.loaded!;
87
- if (size > this.maxFileSize) {
88
- await upload.abort();
89
- }
90
- });
91
-
92
- try {
93
- await upload.done();
94
- } catch (err: any) {
95
- resource.stream.resume();
96
- if (size > this.maxFileSize) {
97
- throw new FileSizeExceededError(this.maxFileSize);
98
- }
99
- throw new WebResourceError(err);
100
- }
101
-
102
- const filename = this.getS3URL(key);
103
- return { size, filename };
104
- }
105
-
106
- public async removeFile(href: string): Promise<void> {
107
- const fileKey = this.getKeyFromHref(href);
108
-
109
- const command = new DeleteObjectCommand({
110
- Bucket: this.bucket,
111
- Key: fileKey,
112
- });
113
-
114
- await this.client.send(command);
115
- }
116
-
117
- public async onPreRespond(webResource: WebResource): Promise<WebResource> {
118
- if (webResource.href != null) {
119
- const fileKey = this.getKeyFromHref(webResource.href);
120
- webResource.href = await this.cachedGetSignedUrl(fileKey);
121
- }
122
- return webResource;
123
- }
124
-
125
- private s3SignUrl(fileKey: string): Promise<string> {
126
- const command = new GetObjectCommand({
127
- Bucket: this.bucket,
128
- Key: fileKey,
129
- });
130
- return getSignedUrl(this.client, command, {
131
- expiresIn: this.signedUrlExpireTimeSeconds,
132
- });
133
- }
134
-
135
- private getS3URL(key: string): string {
136
- return `${this.config.endpoint}/${this.bucket}/${key}`;
137
- }
138
-
139
- private getKeyFromHref(href: string): string {
140
- const hrefWithoutParams = normalizeHref(href);
141
- return hrefWithoutParams.substring(hrefWithoutParams.lastIndexOf('/') + 1);
142
- }
143
- }