@sasjs/api 0.1.0 → 0.2.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/README.md +2 -0
- package/build/browser.js +23 -1
- package/build/cjs/corpus/drive.js +20 -0
- package/build/cjs/drive/service.js +8 -0
- package/build/esm/corpus/drive.d.ts.map +1 -1
- package/build/esm/corpus/drive.js +20 -0
- package/build/esm/drive/service.d.ts.map +1 -1
- package/build/esm/drive/service.js +9 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -30,6 +30,8 @@ The same cases are meant to run against a live server and against the browser bu
|
|
|
30
30
|
npm install @sasjs/api
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Requires Node `^22.14.0 || >=24.10.0` - the floor the release toolchain needs, and what CI runs.
|
|
34
|
+
|
|
33
35
|
A host supplies a store and, later, an identity.
|
|
34
36
|
In node:
|
|
35
37
|
|
package/build/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @sasjs/api 0.1
|
|
1
|
+
/* @sasjs/api 0.2.1 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
|
|
2
2
|
"use strict";
|
|
3
3
|
var SASjsApi = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -203,6 +203,8 @@ var SASjsApi = (() => {
|
|
|
203
203
|
409,
|
|
204
204
|
stat.directory ? "Folder with new name already exists." : "File with new name already exists."
|
|
205
205
|
);
|
|
206
|
+
if (!stat.directory && hasBlockedExtension(to))
|
|
207
|
+
throw new ApiError(400, "Invalid file extension");
|
|
206
208
|
await store.move(from, to);
|
|
207
209
|
return { status: "success" };
|
|
208
210
|
}
|
|
@@ -2398,6 +2400,26 @@ var SASjsApi = (() => {
|
|
|
2398
2400
|
request: { body: { oldPath: "/data/nope.csv", newPath: "/data/x.csv" } },
|
|
2399
2401
|
expect: { status: 404, error: "No file/folder found for provided path." }
|
|
2400
2402
|
},
|
|
2403
|
+
{
|
|
2404
|
+
id: "rename-file-to-blocked-extension",
|
|
2405
|
+
route: "POST /SASjsApi/drive/rename",
|
|
2406
|
+
description: "refuses a rename that would produce a name the file routes refuse",
|
|
2407
|
+
seed: { "/data/report.csv": "a,b" },
|
|
2408
|
+
request: {
|
|
2409
|
+
body: { oldPath: "/data/report.csv", newPath: "/data/report.sh" }
|
|
2410
|
+
},
|
|
2411
|
+
expect: { status: 400, error: "Invalid file extension" }
|
|
2412
|
+
},
|
|
2413
|
+
{
|
|
2414
|
+
id: "rename-folder-to-dotted-name",
|
|
2415
|
+
route: "POST /SASjsApi/drive/rename",
|
|
2416
|
+
description: "renames a folder whose new name carries a blocked suffix",
|
|
2417
|
+
seed: { "/data/reports/a.csv": "a" },
|
|
2418
|
+
request: {
|
|
2419
|
+
body: { oldPath: "/data/reports", newPath: "/data/reports.sh" }
|
|
2420
|
+
},
|
|
2421
|
+
expect: { status: 200, body: { status: "success" } }
|
|
2422
|
+
},
|
|
2401
2423
|
{
|
|
2402
2424
|
id: "rename-folder-into-itself",
|
|
2403
2425
|
route: "POST /SASjsApi/drive/rename",
|
|
@@ -238,6 +238,26 @@ exports.DRIVE_CORPUS = [
|
|
|
238
238
|
request: { body: { oldPath: '/data/nope.csv', newPath: '/data/x.csv' } },
|
|
239
239
|
expect: { status: 404, error: 'No file/folder found for provided path.' }
|
|
240
240
|
},
|
|
241
|
+
{
|
|
242
|
+
id: 'rename-file-to-blocked-extension',
|
|
243
|
+
route: 'POST /SASjsApi/drive/rename',
|
|
244
|
+
description: 'refuses a rename that would produce a name the file routes refuse',
|
|
245
|
+
seed: { '/data/report.csv': 'a,b' },
|
|
246
|
+
request: {
|
|
247
|
+
body: { oldPath: '/data/report.csv', newPath: '/data/report.sh' }
|
|
248
|
+
},
|
|
249
|
+
expect: { status: 400, error: 'Invalid file extension' }
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'rename-folder-to-dotted-name',
|
|
253
|
+
route: 'POST /SASjsApi/drive/rename',
|
|
254
|
+
description: 'renames a folder whose new name carries a blocked suffix',
|
|
255
|
+
seed: { '/data/reports/a.csv': 'a' },
|
|
256
|
+
request: {
|
|
257
|
+
body: { oldPath: '/data/reports', newPath: '/data/reports.sh' }
|
|
258
|
+
},
|
|
259
|
+
expect: { status: 200, body: { status: 'success' } }
|
|
260
|
+
},
|
|
241
261
|
{
|
|
242
262
|
id: 'rename-folder-into-itself',
|
|
243
263
|
route: 'POST /SASjsApi/drive/rename',
|
|
@@ -124,6 +124,14 @@ const createDriveService = (store) => {
|
|
|
124
124
|
throw new types_js_1.ApiError(409, stat.directory
|
|
125
125
|
? 'Folder with new name already exists.'
|
|
126
126
|
: 'File with new name already exists.');
|
|
127
|
+
// the extension rule is the drive's file-path rule, so a rename may not
|
|
128
|
+
// be the way around it: without this a file could be renamed into a name
|
|
129
|
+
// that GET, PATCH and DELETE then refuse, leaving an entry the API that
|
|
130
|
+
// created it can no longer read or remove. A folder is not subject to it
|
|
131
|
+
// (nothing puts a folder through the file-path schema), so only a file
|
|
132
|
+
// move is checked.
|
|
133
|
+
if (!stat.directory && (0, paths_js_1.hasBlockedExtension)(to))
|
|
134
|
+
throw new types_js_1.ApiError(400, 'Invalid file extension');
|
|
127
135
|
await store.move(from, to);
|
|
128
136
|
return { status: 'success' };
|
|
129
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/corpus/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAC1C,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9B,yDAAyD;QACzD,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAWD,eAAO,MAAM,YAAY,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"drive.d.ts","sourceRoot":"","sources":["../../../src/corpus/drive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAA;IACV,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,kCAAkC;IAClC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAA;IAC1C,OAAO,EAAE,UAAU,CAAA;IACnB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAA;QACd,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;QAC9B,yDAAyD;QACzD,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAWD,eAAO,MAAM,YAAY,EAAE,UAAU,EA8SpC,CAAA"}
|
|
@@ -235,6 +235,26 @@ export const DRIVE_CORPUS = [
|
|
|
235
235
|
request: { body: { oldPath: '/data/nope.csv', newPath: '/data/x.csv' } },
|
|
236
236
|
expect: { status: 404, error: 'No file/folder found for provided path.' }
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
id: 'rename-file-to-blocked-extension',
|
|
240
|
+
route: 'POST /SASjsApi/drive/rename',
|
|
241
|
+
description: 'refuses a rename that would produce a name the file routes refuse',
|
|
242
|
+
seed: { '/data/report.csv': 'a,b' },
|
|
243
|
+
request: {
|
|
244
|
+
body: { oldPath: '/data/report.csv', newPath: '/data/report.sh' }
|
|
245
|
+
},
|
|
246
|
+
expect: { status: 400, error: 'Invalid file extension' }
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'rename-folder-to-dotted-name',
|
|
250
|
+
route: 'POST /SASjsApi/drive/rename',
|
|
251
|
+
description: 'renames a folder whose new name carries a blocked suffix',
|
|
252
|
+
seed: { '/data/reports/a.csv': 'a' },
|
|
253
|
+
request: {
|
|
254
|
+
body: { oldPath: '/data/reports', newPath: '/data/reports.sh' }
|
|
255
|
+
},
|
|
256
|
+
expect: { status: 200, body: { status: 'success' } }
|
|
257
|
+
},
|
|
238
258
|
{
|
|
239
259
|
id: 'rename-folder-into-itself',
|
|
240
260
|
route: 'POST /SASjsApi/drive/rename',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/drive/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/drive/service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAWvD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAA;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,OAAO,EAAE,MAAM,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;IAC7B,MAAM,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,SAAS,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAChD,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC3E,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC7E,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC/C,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAC9D,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CAC9C;AAqBD,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KAAG,YAoJtD,CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ApiError } from '../types.js';
|
|
2
|
-
import { basename, checkFilePath, dirname, isRoot, normaliseDrivePath } from './paths.js';
|
|
2
|
+
import { basename, checkFilePath, dirname, hasBlockedExtension, isRoot, normaliseDrivePath } from './paths.js';
|
|
3
3
|
/** A path a file operation may act on, or the refusal the operation answers. */
|
|
4
4
|
const filePath = (input, outside) => {
|
|
5
5
|
const checked = checkFilePath(input);
|
|
@@ -121,6 +121,14 @@ export const createDriveService = (store) => {
|
|
|
121
121
|
throw new ApiError(409, stat.directory
|
|
122
122
|
? 'Folder with new name already exists.'
|
|
123
123
|
: 'File with new name already exists.');
|
|
124
|
+
// the extension rule is the drive's file-path rule, so a rename may not
|
|
125
|
+
// be the way around it: without this a file could be renamed into a name
|
|
126
|
+
// that GET, PATCH and DELETE then refuse, leaving an entry the API that
|
|
127
|
+
// created it can no longer read or remove. A folder is not subject to it
|
|
128
|
+
// (nothing puts a folder through the file-path schema), so only a file
|
|
129
|
+
// move is checked.
|
|
130
|
+
if (!stat.directory && hasBlockedExtension(to))
|
|
131
|
+
throw new ApiError(400, 'Invalid file extension');
|
|
124
132
|
await store.move(from, to);
|
|
125
133
|
return { status: 'success' };
|
|
126
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sasjs/api",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The SASjs Server API, described once - a generated route table, the services behind it, and the conformance corpus both hosts run",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "SASjs",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": "^22.14.0 || >=24.10.0"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "rimraf build && tsc -p tsconfig.build.cjs.json && tsc -p tsconfig.build.esm.json && node scripts/postbuild.mjs && node scripts/build-browser.mjs",
|
|
@@ -43,15 +43,21 @@
|
|
|
43
43
|
"@semantic-release/github": "12.0.10",
|
|
44
44
|
"@semantic-release/npm": "13.2.0",
|
|
45
45
|
"@semantic-release/release-notes-generator": "14.1.1",
|
|
46
|
-
"@types/jest": "
|
|
46
|
+
"@types/jest": "30.0.0",
|
|
47
47
|
"@types/node": "22.20.5",
|
|
48
|
+
"deepmerge": "file:./vendor/deepmerge",
|
|
48
49
|
"esbuild": "0.28.2",
|
|
49
|
-
"jest": "
|
|
50
|
+
"jest": "30.5.2",
|
|
50
51
|
"prettier": "3.9.10",
|
|
51
52
|
"rimraf": "6.1.4",
|
|
52
53
|
"semantic-release": "25.0.9",
|
|
53
54
|
"ts-jest": "29.4.15",
|
|
54
55
|
"typescript": "5.9.3",
|
|
55
56
|
"yaml": "2.9.1"
|
|
57
|
+
},
|
|
58
|
+
"overrides": {
|
|
59
|
+
"deepmerge": "$deepmerge",
|
|
60
|
+
"glob": "14.0.3",
|
|
61
|
+
"minimatch": "11.0.4"
|
|
56
62
|
}
|
|
57
63
|
}
|