@sasjs/api 0.9.0 → 0.10.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/build/browser.js +90 -7
- package/build/cjs/code/index.js +12 -0
- package/build/cjs/code/service.js +57 -0
- package/build/cjs/corpus/code.js +62 -0
- package/build/cjs/host/dispatch.js +30 -3
- package/build/cjs/index.js +3 -1
- package/build/cjs/routes/overlay.js +8 -3
- package/build/esm/code/index.d.ts +10 -0
- package/build/esm/code/index.d.ts.map +1 -0
- package/build/esm/code/index.js +8 -0
- package/build/esm/code/service.d.ts +40 -0
- package/build/esm/code/service.d.ts.map +1 -0
- package/build/esm/code/service.js +53 -0
- package/build/esm/corpus/code.d.ts +17 -0
- package/build/esm/corpus/code.d.ts.map +1 -0
- package/build/esm/corpus/code.js +59 -0
- package/build/esm/corpus/drive.d.ts +9 -0
- package/build/esm/corpus/drive.d.ts.map +1 -1
- package/build/esm/host/dispatch.d.ts +3 -0
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +30 -3
- package/build/esm/index.d.ts +1 -0
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +1 -0
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +8 -3
- package/package.json +9 -1
package/build/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @sasjs/api 0.
|
|
1
|
+
/* @sasjs/api 0.10.0 - 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;
|
|
@@ -25,6 +25,7 @@ var SASjsApi = (() => {
|
|
|
25
25
|
ADMIN_SIGNED_IN: () => ADMIN_SIGNED_IN,
|
|
26
26
|
ALWAYS_VISIBLE_ROUTES: () => ALWAYS_VISIBLE_ROUTES,
|
|
27
27
|
ApiError: () => ApiError,
|
|
28
|
+
CODE_CORPUS: () => CODE_CORPUS,
|
|
28
29
|
DEFAULT_GRANTABLE_ROUTES: () => DEFAULT_GRANTABLE_ROUTES,
|
|
29
30
|
DEFAULT_IDENTITY: () => DEFAULT_IDENTITY,
|
|
30
31
|
DEPLOY_LIMITS: () => DEPLOY_LIMITS,
|
|
@@ -2459,7 +2460,6 @@ var SASjsApi = (() => {
|
|
|
2459
2460
|
Web: ["identity"]
|
|
2460
2461
|
};
|
|
2461
2462
|
var DIRECTORY = "Synchronising with an external auth provider needs a directory port, which this package does not have: a host decides which directory it holds accounts in, and answers this itself.";
|
|
2462
|
-
var EDITOR = "Linting and formatting come from @sasjs/lint, which this package does not depend on. They land with the code routes so an editor has one wiring.";
|
|
2463
2463
|
var RUNTIME = "Starting a program needs a runtime port, which this package does not have: a host decides which languages it holds, and answers this itself until then.";
|
|
2464
2464
|
var SESSION_RECORD = "A triggered session's record, log, output and state belong to the runtime that ran the program, and this package has no runtime port.";
|
|
2465
2465
|
var HOST_CONFIG = "The body is the host's own configuration - ports, run times and providers - which a mock states for itself and this package cannot know.";
|
|
@@ -2529,8 +2529,14 @@ var SASjsApi = (() => {
|
|
|
2529
2529
|
// Code - the editor's three calls, and the runtime one
|
|
2530
2530
|
"POST /SASjsApi/code/execute": { implemented: false, reason: RUNTIME },
|
|
2531
2531
|
"POST /SASjsApi/code/trigger": { implemented: false, reason: RUNTIME },
|
|
2532
|
-
"POST /SASjsApi/code/lint": {
|
|
2533
|
-
|
|
2532
|
+
"POST /SASjsApi/code/lint": {
|
|
2533
|
+
implemented: true,
|
|
2534
|
+
capabilities: ["identity"]
|
|
2535
|
+
},
|
|
2536
|
+
"POST /SASjsApi/code/format": {
|
|
2537
|
+
implemented: true,
|
|
2538
|
+
capabilities: ["identity"]
|
|
2539
|
+
},
|
|
2534
2540
|
// Drive - the file and folder rules, and the three deploy routes
|
|
2535
2541
|
// The body is the file itself, so there is nothing to annotate
|
|
2536
2542
|
"GET /SASjsApi/drive/file": { implemented: true },
|
|
@@ -2772,8 +2778,10 @@ var SASjsApi = (() => {
|
|
|
2772
2778
|
continue;
|
|
2773
2779
|
if (param.in === "path") continue;
|
|
2774
2780
|
const given = param.in === "query" ? request.query?.[param.name] : request.body?.[param.name];
|
|
2775
|
-
if (given === void 0
|
|
2781
|
+
if (given === void 0)
|
|
2776
2782
|
throw new ApiError(400, `"${param.name}" is required`);
|
|
2783
|
+
if (given === "")
|
|
2784
|
+
throw new ApiError(400, `"${param.name}" is not allowed to be empty`);
|
|
2777
2785
|
}
|
|
2778
2786
|
};
|
|
2779
2787
|
var targetPath = (request) => {
|
|
@@ -2786,7 +2794,7 @@ var SASjsApi = (() => {
|
|
|
2786
2794
|
var dispatch = async (services, routeId, request = {}) => {
|
|
2787
2795
|
const route = routeById(routeId);
|
|
2788
2796
|
if (!route) throw new ApiError(404, `No such operation: ${routeId}`);
|
|
2789
|
-
const { drive, identity, records } = services;
|
|
2797
|
+
const { drive, identity, records, code } = services;
|
|
2790
2798
|
const identityScoped = ["User", "Group", "Permission", "Client"].includes(route.tag) || routeId === "PATCH /SASjsApi/auth/updatePassword";
|
|
2791
2799
|
if (identityScoped && records) {
|
|
2792
2800
|
const subject = request.subject ?? (identity ? await identity.authenticate(request.token) : void 0);
|
|
@@ -2827,7 +2835,7 @@ var SASjsApi = (() => {
|
|
|
2827
2835
|
status: 200,
|
|
2828
2836
|
body: await identity.token({
|
|
2829
2837
|
clientId: String(body.clientId ?? ""),
|
|
2830
|
-
code:
|
|
2838
|
+
code: body.code
|
|
2831
2839
|
})
|
|
2832
2840
|
};
|
|
2833
2841
|
case "POST /SASjsApi/auth/refresh":
|
|
@@ -2971,6 +2979,32 @@ var SASjsApi = (() => {
|
|
|
2971
2979
|
{ password: body.password }
|
|
2972
2980
|
)
|
|
2973
2981
|
};
|
|
2982
|
+
case "POST /SASjsApi/code/lint":
|
|
2983
|
+
if (!code)
|
|
2984
|
+
throw new ApiError(
|
|
2985
|
+
501,
|
|
2986
|
+
`This host has no code service, so it cannot answer ${routeId}.`
|
|
2987
|
+
);
|
|
2988
|
+
return {
|
|
2989
|
+
status: 200,
|
|
2990
|
+
body: await code.lint(await caller(), {
|
|
2991
|
+
code: body.code,
|
|
2992
|
+
filePath: body.filePath
|
|
2993
|
+
})
|
|
2994
|
+
};
|
|
2995
|
+
case "POST /SASjsApi/code/format":
|
|
2996
|
+
if (!code)
|
|
2997
|
+
throw new ApiError(
|
|
2998
|
+
501,
|
|
2999
|
+
`This host has no code service, so it cannot answer ${routeId}.`
|
|
3000
|
+
);
|
|
3001
|
+
return {
|
|
3002
|
+
status: 200,
|
|
3003
|
+
body: await code.format(await caller(), {
|
|
3004
|
+
code: body.code,
|
|
3005
|
+
filePath: body.filePath
|
|
3006
|
+
})
|
|
3007
|
+
};
|
|
2974
3008
|
case "GET /SASjsApi/info/authorizedRoutes":
|
|
2975
3009
|
return {
|
|
2976
3010
|
status: 200,
|
|
@@ -3091,6 +3125,8 @@ var SASjsApi = (() => {
|
|
|
3091
3125
|
}
|
|
3092
3126
|
};
|
|
3093
3127
|
var HANDLED_ROUTES = [
|
|
3128
|
+
"POST /SASjsApi/code/lint",
|
|
3129
|
+
"POST /SASjsApi/code/format",
|
|
3094
3130
|
"GET /SASjsApi/info/authorizedRoutes",
|
|
3095
3131
|
"GET /SASjsApi/info/myAuthorizedRoutes",
|
|
3096
3132
|
"GET /SASjsApi/drive/file",
|
|
@@ -3602,6 +3638,53 @@ var SASjsApi = (() => {
|
|
|
3602
3638
|
};
|
|
3603
3639
|
};
|
|
3604
3640
|
|
|
3641
|
+
// src/corpus/code.ts
|
|
3642
|
+
var CODE_CORPUS = [
|
|
3643
|
+
{
|
|
3644
|
+
id: "code-lint-missing-header",
|
|
3645
|
+
route: "POST /SASjsApi/code/lint",
|
|
3646
|
+
description: "answers the diagnostics for a buffer, without touching a file",
|
|
3647
|
+
request: {
|
|
3648
|
+
// the routes are permissioned; the runner resolves the fixture's admin
|
|
3649
|
+
subject: { userId: "user-admin", clientId: "web_app" },
|
|
3650
|
+
body: { code: "data _null_;\n x = 1;\nrun;\n" }
|
|
3651
|
+
},
|
|
3652
|
+
expect: {
|
|
3653
|
+
status: 200,
|
|
3654
|
+
body: [
|
|
3655
|
+
{
|
|
3656
|
+
message: "File missing Doxygen header",
|
|
3657
|
+
lineNumber: 1,
|
|
3658
|
+
startColumnNumber: 1,
|
|
3659
|
+
endColumnNumber: 1,
|
|
3660
|
+
severity: 1
|
|
3661
|
+
}
|
|
3662
|
+
]
|
|
3663
|
+
}
|
|
3664
|
+
},
|
|
3665
|
+
// Two refusals are deliberately not pinned here yet: a `code` that is not a
|
|
3666
|
+
// string, and one that is empty. The package answers 400 `"code" must be a
|
|
3667
|
+
// string` / `"code" is not allowed to be empty` - the messages the server's
|
|
3668
|
+
// schema produces - but sasjs/server's code controller carries no
|
|
3669
|
+
// `@Response(400)` for these routes, so the document states 200 alone and the
|
|
3670
|
+
// parity gate refuses a case answering anything else. The cases land when the
|
|
3671
|
+
// decorators do.
|
|
3672
|
+
{
|
|
3673
|
+
id: "code-format-adds-the-header",
|
|
3674
|
+
route: "POST /SASjsApi/code/format",
|
|
3675
|
+
description: "answers the formatted buffer, header block included",
|
|
3676
|
+
request: {
|
|
3677
|
+
// the routes are permissioned; the runner resolves the fixture's admin
|
|
3678
|
+
subject: { userId: "user-admin", clientId: "web_app" },
|
|
3679
|
+
body: { code: "data _null_;\n x = 1;\nrun;\n" }
|
|
3680
|
+
},
|
|
3681
|
+
expect: {
|
|
3682
|
+
status: 200,
|
|
3683
|
+
body: "/**\n @file\n @brief <Your brief here>\n\n <h4> SAS Macros </h4>\n\n**/\ndata _null_;\n x = 1;\nrun;\n"
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
];
|
|
3687
|
+
|
|
3605
3688
|
// src/identity/fixture.ts
|
|
3606
3689
|
var IDENTITY_FORMAT = 1;
|
|
3607
3690
|
var SECONDS_IN_A_DAY2 = 86400;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCodeService = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The code service, on its own entry point.
|
|
6
|
+
*
|
|
7
|
+
* `@sasjs/lint` reads the filesystem (its config resolution walks up for a
|
|
8
|
+
* `.sasjslint`), so it cannot be bundled for a service worker: the package root
|
|
9
|
+
* stays free of it and a host that wants these routes imports `@sasjs/api/code`.
|
|
10
|
+
*/
|
|
11
|
+
var service_js_1 = require("./service.js");
|
|
12
|
+
Object.defineProperty(exports, "createCodeService", { enumerable: true, get: function () { return service_js_1.createCodeService; } });
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCodeService = void 0;
|
|
4
|
+
const lint_1 = require("@sasjs/lint");
|
|
5
|
+
const types_js_1 = require("../types.js");
|
|
6
|
+
/**
|
|
7
|
+
* `code`, refused the way the server's schema refuses it.
|
|
8
|
+
*
|
|
9
|
+
* Coercing with `String(...)` defeated the refusal: `{"code": 123}` was linted as
|
|
10
|
+
* the text "123" and `{"code": ""}` answered "required" where the server says it
|
|
11
|
+
* is empty. The three messages are Joi's, because they are the ones a client
|
|
12
|
+
* written against the document already handles.
|
|
13
|
+
*/
|
|
14
|
+
const codeOf = (body) => {
|
|
15
|
+
const value = body.code;
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
throw new types_js_1.ApiError(400, '"code" is required');
|
|
18
|
+
if (typeof value !== 'string')
|
|
19
|
+
throw new types_js_1.ApiError(400, '"code" must be a string');
|
|
20
|
+
if (value === '')
|
|
21
|
+
throw new types_js_1.ApiError(400, '"code" is not allowed to be empty');
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The two editor routes, over `@sasjs/lint`.
|
|
26
|
+
*
|
|
27
|
+
* The linter is a dependency because it is the same everywhere, and it is
|
|
28
|
+
* deliberately NOT reachable from the package root: `@sasjs/lint` resolves its
|
|
29
|
+
* configuration by walking the filesystem, so it cannot be bundled into the
|
|
30
|
+
* service worker the browser entry produces. A host that wants these routes
|
|
31
|
+
* imports `@sasjs/api/code`, which is Node-only, exactly like the linter.
|
|
32
|
+
*
|
|
33
|
+
* The *rules* are the host's: which `.sasjslint` applies to a path is a fact
|
|
34
|
+
* about a drive, so `lintConfig` is asked for it.
|
|
35
|
+
* Neither route touches the drive or runs anything; they answer about the text
|
|
36
|
+
* in the request, which is what lets an editor check unsaved changes.
|
|
37
|
+
*/
|
|
38
|
+
const createCodeService = (options = {}) => {
|
|
39
|
+
const requireCaller = (caller) => {
|
|
40
|
+
// both routes are permissioned at the gate, so a caller that reached the
|
|
41
|
+
// service without one is a host that skipped it rather than a public route
|
|
42
|
+
if (!caller)
|
|
43
|
+
throw new types_js_1.ApiError(401, 'Unauthorized');
|
|
44
|
+
};
|
|
45
|
+
const configFor = (filePath) => options.lintConfig ? options.lintConfig(filePath) : undefined;
|
|
46
|
+
return {
|
|
47
|
+
async lint(caller, body) {
|
|
48
|
+
requireCaller(caller);
|
|
49
|
+
return (0, lint_1.lintText)(codeOf(body), configFor(body.filePath));
|
|
50
|
+
},
|
|
51
|
+
async format(caller, body) {
|
|
52
|
+
requireCaller(caller);
|
|
53
|
+
return (0, lint_1.formatText)(codeOf(body), configFor(body.filePath));
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
exports.createCodeService = createCodeService;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CODE_CORPUS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The editor's corpus: the two routes that answer about a buffer rather than a
|
|
6
|
+
* file.
|
|
7
|
+
*
|
|
8
|
+
* Both expectations are the linter's own output for the same input, measured
|
|
9
|
+
* against `@sasjs/lint` with no configuration - the defaults an untitled buffer
|
|
10
|
+
* lints against. A host that supplies a `.sasjslint` changes the rules for its
|
|
11
|
+
* own drive, which is why the case names no `filePath`.
|
|
12
|
+
*
|
|
13
|
+
* lintText('data _null_;\n x = 1;\nrun;\n')
|
|
14
|
+
* -> [{ message: 'File missing Doxygen header', lineNumber: 1, ... }]
|
|
15
|
+
* formatText(...)
|
|
16
|
+
* -> the header block, then the program with its spacing normalised
|
|
17
|
+
*/
|
|
18
|
+
exports.CODE_CORPUS = [
|
|
19
|
+
{
|
|
20
|
+
id: 'code-lint-missing-header',
|
|
21
|
+
route: 'POST /SASjsApi/code/lint',
|
|
22
|
+
description: 'answers the diagnostics for a buffer, without touching a file',
|
|
23
|
+
request: {
|
|
24
|
+
// the routes are permissioned; the runner resolves the fixture's admin
|
|
25
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
26
|
+
body: { code: 'data _null_;\n x = 1;\nrun;\n' }
|
|
27
|
+
},
|
|
28
|
+
expect: {
|
|
29
|
+
status: 200,
|
|
30
|
+
body: [
|
|
31
|
+
{
|
|
32
|
+
message: 'File missing Doxygen header',
|
|
33
|
+
lineNumber: 1,
|
|
34
|
+
startColumnNumber: 1,
|
|
35
|
+
endColumnNumber: 1,
|
|
36
|
+
severity: 1
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
// Two refusals are deliberately not pinned here yet: a `code` that is not a
|
|
42
|
+
// string, and one that is empty. The package answers 400 `"code" must be a
|
|
43
|
+
// string` / `"code" is not allowed to be empty` - the messages the server's
|
|
44
|
+
// schema produces - but sasjs/server's code controller carries no
|
|
45
|
+
// `@Response(400)` for these routes, so the document states 200 alone and the
|
|
46
|
+
// parity gate refuses a case answering anything else. The cases land when the
|
|
47
|
+
// decorators do.
|
|
48
|
+
{
|
|
49
|
+
id: 'code-format-adds-the-header',
|
|
50
|
+
route: 'POST /SASjsApi/code/format',
|
|
51
|
+
description: 'answers the formatted buffer, header block included',
|
|
52
|
+
request: {
|
|
53
|
+
// the routes are permissioned; the runner resolves the fixture's admin
|
|
54
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
55
|
+
body: { code: 'data _null_;\n x = 1;\nrun;\n' }
|
|
56
|
+
},
|
|
57
|
+
expect: {
|
|
58
|
+
status: 200,
|
|
59
|
+
body: '/**\n @file\n @brief <Your brief here>\n\n <h4> SAS Macros </h4>\n\n**/\ndata _null_;\n x = 1;\nrun;\n'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
];
|
|
@@ -24,8 +24,13 @@ const validate = (route, request) => {
|
|
|
24
24
|
const given = param.in === 'query'
|
|
25
25
|
? request.query?.[param.name]
|
|
26
26
|
: request.body?.[param.name];
|
|
27
|
-
|
|
27
|
+
// missing and empty are two refusals, the way the schema has them: a
|
|
28
|
+
// client that sent the field an empty value has a different bug from one
|
|
29
|
+
// that omitted it
|
|
30
|
+
if (given === undefined)
|
|
28
31
|
throw new types_js_1.ApiError(400, `"${param.name}" is required`);
|
|
32
|
+
if (given === '')
|
|
33
|
+
throw new types_js_1.ApiError(400, `"${param.name}" is not allowed to be empty`);
|
|
29
34
|
}
|
|
30
35
|
};
|
|
31
36
|
/** The drive path a write names: the query parameter wins over the form field. */
|
|
@@ -44,7 +49,7 @@ const dispatch = async (services, routeId, request = {}) => {
|
|
|
44
49
|
const route = (0, index_js_1.routeById)(routeId);
|
|
45
50
|
if (!route)
|
|
46
51
|
throw new types_js_1.ApiError(404, `No such operation: ${routeId}`);
|
|
47
|
-
const { drive, identity, records } = services;
|
|
52
|
+
const { drive, identity, records, code } = services;
|
|
48
53
|
// The server authenticates before its payload schema runs, so an anonymous
|
|
49
54
|
// request with an incomplete body is a 401 there rather than a 400. The order
|
|
50
55
|
// is only observable on the route groups that gate on a caller: the sign-in
|
|
@@ -92,7 +97,7 @@ const dispatch = async (services, routeId, request = {}) => {
|
|
|
92
97
|
status: 200,
|
|
93
98
|
body: await identity.token({
|
|
94
99
|
clientId: String(body.clientId ?? ''),
|
|
95
|
-
code:
|
|
100
|
+
code: body.code
|
|
96
101
|
})
|
|
97
102
|
};
|
|
98
103
|
case 'POST /SASjsApi/auth/refresh':
|
|
@@ -219,6 +224,26 @@ const dispatch = async (services, routeId, request = {}) => {
|
|
|
219
224
|
status: 200,
|
|
220
225
|
body: await required().deleteUser(await caller(), { username: String(params.username ?? '') }, { password: body.password })
|
|
221
226
|
};
|
|
227
|
+
case 'POST /SASjsApi/code/lint':
|
|
228
|
+
if (!code)
|
|
229
|
+
throw new types_js_1.ApiError(501, `This host has no code service, so it cannot answer ${routeId}.`);
|
|
230
|
+
return {
|
|
231
|
+
status: 200,
|
|
232
|
+
body: await code.lint(await caller(), {
|
|
233
|
+
code: body.code,
|
|
234
|
+
filePath: body.filePath
|
|
235
|
+
})
|
|
236
|
+
};
|
|
237
|
+
case 'POST /SASjsApi/code/format':
|
|
238
|
+
if (!code)
|
|
239
|
+
throw new types_js_1.ApiError(501, `This host has no code service, so it cannot answer ${routeId}.`);
|
|
240
|
+
return {
|
|
241
|
+
status: 200,
|
|
242
|
+
body: await code.format(await caller(), {
|
|
243
|
+
code: body.code,
|
|
244
|
+
filePath: body.filePath
|
|
245
|
+
})
|
|
246
|
+
};
|
|
222
247
|
case 'GET /SASjsApi/info/authorizedRoutes':
|
|
223
248
|
return {
|
|
224
249
|
status: 200,
|
|
@@ -332,6 +357,8 @@ const dispatch = async (services, routeId, request = {}) => {
|
|
|
332
357
|
exports.dispatch = dispatch;
|
|
333
358
|
/** Whether a route is one `dispatch` can answer today. */
|
|
334
359
|
exports.HANDLED_ROUTES = [
|
|
360
|
+
'POST /SASjsApi/code/lint',
|
|
361
|
+
'POST /SASjsApi/code/format',
|
|
335
362
|
'GET /SASjsApi/info/authorizedRoutes',
|
|
336
363
|
'GET /SASjsApi/info/myAuthorizedRoutes',
|
|
337
364
|
'GET /SASjsApi/drive/file',
|
package/build/cjs/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.servicesFor = exports.runCase = exports.PROVIDER_MANAGED = exports.ADMIN_SIGNED_IN = exports.RECORD_CORPUS = exports.IDENTITY_CORPUS = exports.DRIVE_CORPUS = exports.parseIdentity = exports.IDENTITY_FORMAT = exports.DEFAULT_IDENTITY = exports.MemoryIdentityStore = exports.sanitiseDisplayName = exports.createRecordService = exports.createIdentityService = exports.HANDLED_ROUTES = exports.dispatch = exports.TAG_CAPABILITIES = exports.OVERLAY = exports.findRoute = exports.routeById = exports.ROUTES = exports.MemoryDriveStore = exports.measureDeployTree = exports.isDeployTree = exports.deployTree = exports.decodeBase64 = exports.checkDeployTree = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = exports.createDriveService = exports.isRoot = exports.basename = exports.dirname = exports.hasBlockedExtension = exports.normaliseDrivePath = void 0;
|
|
17
|
+
exports.servicesFor = exports.runCase = exports.PROVIDER_MANAGED = exports.ADMIN_SIGNED_IN = exports.RECORD_CORPUS = exports.IDENTITY_CORPUS = exports.DRIVE_CORPUS = exports.parseIdentity = exports.IDENTITY_FORMAT = exports.DEFAULT_IDENTITY = exports.MemoryIdentityStore = exports.CODE_CORPUS = exports.sanitiseDisplayName = exports.createRecordService = exports.createIdentityService = exports.HANDLED_ROUTES = exports.dispatch = exports.TAG_CAPABILITIES = exports.OVERLAY = exports.findRoute = exports.routeById = exports.ROUTES = exports.MemoryDriveStore = exports.measureDeployTree = exports.isDeployTree = exports.deployTree = exports.decodeBase64 = exports.checkDeployTree = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = exports.createDriveService = exports.isRoot = exports.basename = exports.dirname = exports.hasBlockedExtension = exports.normaliseDrivePath = void 0;
|
|
18
18
|
/**
|
|
19
19
|
* `@sasjs/api` - the SASjs Server API described once.
|
|
20
20
|
*
|
|
@@ -67,6 +67,8 @@ Object.defineProperty(exports, "createIdentityService", { enumerable: true, get:
|
|
|
67
67
|
var records_js_1 = require("./identity/records.js");
|
|
68
68
|
Object.defineProperty(exports, "createRecordService", { enumerable: true, get: function () { return records_js_1.createRecordService; } });
|
|
69
69
|
Object.defineProperty(exports, "sanitiseDisplayName", { enumerable: true, get: function () { return records_js_1.sanitiseDisplayName; } });
|
|
70
|
+
var code_js_1 = require("./corpus/code.js");
|
|
71
|
+
Object.defineProperty(exports, "CODE_CORPUS", { enumerable: true, get: function () { return code_js_1.CODE_CORPUS; } });
|
|
70
72
|
var memory_store_js_2 = require("./identity/memory-store.js");
|
|
71
73
|
Object.defineProperty(exports, "MemoryIdentityStore", { enumerable: true, get: function () { return memory_store_js_2.MemoryIdentityStore; } });
|
|
72
74
|
var fixture_js_1 = require("./identity/fixture.js");
|
|
@@ -26,7 +26,6 @@ exports.TAG_CAPABILITIES = {
|
|
|
26
26
|
* thing thirty times.
|
|
27
27
|
*/
|
|
28
28
|
const DIRECTORY = 'Synchronising with an external auth provider needs a directory port, which this package does not have: a host decides which directory it holds accounts in, and answers this itself.';
|
|
29
|
-
const EDITOR = 'Linting and formatting come from @sasjs/lint, which this package does not depend on. They land with the code routes so an editor has one wiring.';
|
|
30
29
|
const RUNTIME = 'Starting a program needs a runtime port, which this package does not have: a host decides which languages it holds, and answers this itself until then.';
|
|
31
30
|
const SESSION_RECORD = "A triggered session's record, log, output and state belong to the runtime that ran the program, and this package has no runtime port.";
|
|
32
31
|
const HOST_CONFIG = "The body is the host's own configuration - ports, run times and providers - which a mock states for itself and this package cannot know.";
|
|
@@ -96,8 +95,14 @@ exports.OVERLAY = {
|
|
|
96
95
|
// Code - the editor's three calls, and the runtime one
|
|
97
96
|
'POST /SASjsApi/code/execute': { implemented: false, reason: RUNTIME },
|
|
98
97
|
'POST /SASjsApi/code/trigger': { implemented: false, reason: RUNTIME },
|
|
99
|
-
'POST /SASjsApi/code/lint': {
|
|
100
|
-
|
|
98
|
+
'POST /SASjsApi/code/lint': {
|
|
99
|
+
implemented: true,
|
|
100
|
+
capabilities: ['identity']
|
|
101
|
+
},
|
|
102
|
+
'POST /SASjsApi/code/format': {
|
|
103
|
+
implemented: true,
|
|
104
|
+
capabilities: ['identity']
|
|
105
|
+
},
|
|
101
106
|
// Drive - the file and folder rules, and the three deploy routes
|
|
102
107
|
// The body is the file itself, so there is nothing to annotate
|
|
103
108
|
'GET /SASjsApi/drive/file': { implemented: true },
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code service, on its own entry point.
|
|
3
|
+
*
|
|
4
|
+
* `@sasjs/lint` reads the filesystem (its config resolution walks up for a
|
|
5
|
+
* `.sasjslint`), so it cannot be bundled for a service worker: the package root
|
|
6
|
+
* stays free of it and a host that wants these routes imports `@sasjs/api/code`.
|
|
7
|
+
*/
|
|
8
|
+
export { createCodeService } from './service.js';
|
|
9
|
+
export type { CodeRequest, CodeService, CodeServiceOptions } from './service.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/code/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The code service, on its own entry point.
|
|
3
|
+
*
|
|
4
|
+
* `@sasjs/lint` reads the filesystem (its config resolution walks up for a
|
|
5
|
+
* `.sasjslint`), so it cannot be bundled for a service worker: the package root
|
|
6
|
+
* stays free of it and a host that wants these routes imports `@sasjs/api/code`.
|
|
7
|
+
*/
|
|
8
|
+
export { createCodeService } from './service.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Diagnostic, LintConfig } from '@sasjs/lint';
|
|
2
|
+
import type { Subject } from '../identity/types.js';
|
|
3
|
+
/** What both code routes take: a buffer, and where it came from when it has a file. */
|
|
4
|
+
export interface CodeRequest {
|
|
5
|
+
code: string;
|
|
6
|
+
/** The drive path the code belongs to, so a host can pick that folder's rules. */
|
|
7
|
+
filePath?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CodeServiceOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The lint configuration that applies to a file.
|
|
12
|
+
*
|
|
13
|
+
* A host resolves the nearest `.sasjslint` at or above `filePath`, so each
|
|
14
|
+
* folder can carry its own rules; without one the linter's defaults apply,
|
|
15
|
+
* which is what an untitled buffer lints against.
|
|
16
|
+
*/
|
|
17
|
+
lintConfig?: (filePath?: string) => LintConfig | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface CodeService {
|
|
20
|
+
/** The diagnostics for a buffer. Nothing is written and nothing is executed. */
|
|
21
|
+
lint(caller: Subject | undefined, body: CodeRequest): Promise<Diagnostic[]>;
|
|
22
|
+
/** The formatted buffer. */
|
|
23
|
+
format(caller: Subject | undefined, body: CodeRequest): Promise<string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The two editor routes, over `@sasjs/lint`.
|
|
27
|
+
*
|
|
28
|
+
* The linter is a dependency because it is the same everywhere, and it is
|
|
29
|
+
* deliberately NOT reachable from the package root: `@sasjs/lint` resolves its
|
|
30
|
+
* configuration by walking the filesystem, so it cannot be bundled into the
|
|
31
|
+
* service worker the browser entry produces. A host that wants these routes
|
|
32
|
+
* imports `@sasjs/api/code`, which is Node-only, exactly like the linter.
|
|
33
|
+
*
|
|
34
|
+
* The *rules* are the host's: which `.sasjslint` applies to a path is a fact
|
|
35
|
+
* about a drive, so `lintConfig` is asked for it.
|
|
36
|
+
* Neither route touches the drive or runs anything; they answer about the text
|
|
37
|
+
* in the request, which is what lets an editor check unsaved changes.
|
|
38
|
+
*/
|
|
39
|
+
export declare const createCodeService: (options?: CodeServiceOptions) => CodeService;
|
|
40
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/code/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAEnD,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAA;CAC3D;AAED,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAC3E,4BAA4B;IAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACxE;AAmBD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,GAC5B,UAAS,kBAAuB,KAC/B,WAqBF,CAAA"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { formatText, lintText } from '@sasjs/lint';
|
|
2
|
+
import { ApiError } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* `code`, refused the way the server's schema refuses it.
|
|
5
|
+
*
|
|
6
|
+
* Coercing with `String(...)` defeated the refusal: `{"code": 123}` was linted as
|
|
7
|
+
* the text "123" and `{"code": ""}` answered "required" where the server says it
|
|
8
|
+
* is empty. The three messages are Joi's, because they are the ones a client
|
|
9
|
+
* written against the document already handles.
|
|
10
|
+
*/
|
|
11
|
+
const codeOf = (body) => {
|
|
12
|
+
const value = body.code;
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
throw new ApiError(400, '"code" is required');
|
|
15
|
+
if (typeof value !== 'string')
|
|
16
|
+
throw new ApiError(400, '"code" must be a string');
|
|
17
|
+
if (value === '')
|
|
18
|
+
throw new ApiError(400, '"code" is not allowed to be empty');
|
|
19
|
+
return value;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The two editor routes, over `@sasjs/lint`.
|
|
23
|
+
*
|
|
24
|
+
* The linter is a dependency because it is the same everywhere, and it is
|
|
25
|
+
* deliberately NOT reachable from the package root: `@sasjs/lint` resolves its
|
|
26
|
+
* configuration by walking the filesystem, so it cannot be bundled into the
|
|
27
|
+
* service worker the browser entry produces. A host that wants these routes
|
|
28
|
+
* imports `@sasjs/api/code`, which is Node-only, exactly like the linter.
|
|
29
|
+
*
|
|
30
|
+
* The *rules* are the host's: which `.sasjslint` applies to a path is a fact
|
|
31
|
+
* about a drive, so `lintConfig` is asked for it.
|
|
32
|
+
* Neither route touches the drive or runs anything; they answer about the text
|
|
33
|
+
* in the request, which is what lets an editor check unsaved changes.
|
|
34
|
+
*/
|
|
35
|
+
export const createCodeService = (options = {}) => {
|
|
36
|
+
const requireCaller = (caller) => {
|
|
37
|
+
// both routes are permissioned at the gate, so a caller that reached the
|
|
38
|
+
// service without one is a host that skipped it rather than a public route
|
|
39
|
+
if (!caller)
|
|
40
|
+
throw new ApiError(401, 'Unauthorized');
|
|
41
|
+
};
|
|
42
|
+
const configFor = (filePath) => options.lintConfig ? options.lintConfig(filePath) : undefined;
|
|
43
|
+
return {
|
|
44
|
+
async lint(caller, body) {
|
|
45
|
+
requireCaller(caller);
|
|
46
|
+
return lintText(codeOf(body), configFor(body.filePath));
|
|
47
|
+
},
|
|
48
|
+
async format(caller, body) {
|
|
49
|
+
requireCaller(caller);
|
|
50
|
+
return formatText(codeOf(body), configFor(body.filePath));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CorpusCase } from './drive.js';
|
|
2
|
+
/**
|
|
3
|
+
* The editor's corpus: the two routes that answer about a buffer rather than a
|
|
4
|
+
* file.
|
|
5
|
+
*
|
|
6
|
+
* Both expectations are the linter's own output for the same input, measured
|
|
7
|
+
* against `@sasjs/lint` with no configuration - the defaults an untitled buffer
|
|
8
|
+
* lints against. A host that supplies a `.sasjslint` changes the rules for its
|
|
9
|
+
* own drive, which is why the case names no `filePath`.
|
|
10
|
+
*
|
|
11
|
+
* lintText('data _null_;\n x = 1;\nrun;\n')
|
|
12
|
+
* -> [{ message: 'File missing Doxygen header', lineNumber: 1, ... }]
|
|
13
|
+
* formatText(...)
|
|
14
|
+
* -> the header block, then the program with its spacing normalised
|
|
15
|
+
*/
|
|
16
|
+
export declare const CODE_CORPUS: CorpusCase[];
|
|
17
|
+
//# sourceMappingURL=code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../../src/corpus/code.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,EAAE,UAAU,EA6CnC,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor's corpus: the two routes that answer about a buffer rather than a
|
|
3
|
+
* file.
|
|
4
|
+
*
|
|
5
|
+
* Both expectations are the linter's own output for the same input, measured
|
|
6
|
+
* against `@sasjs/lint` with no configuration - the defaults an untitled buffer
|
|
7
|
+
* lints against. A host that supplies a `.sasjslint` changes the rules for its
|
|
8
|
+
* own drive, which is why the case names no `filePath`.
|
|
9
|
+
*
|
|
10
|
+
* lintText('data _null_;\n x = 1;\nrun;\n')
|
|
11
|
+
* -> [{ message: 'File missing Doxygen header', lineNumber: 1, ... }]
|
|
12
|
+
* formatText(...)
|
|
13
|
+
* -> the header block, then the program with its spacing normalised
|
|
14
|
+
*/
|
|
15
|
+
export const CODE_CORPUS = [
|
|
16
|
+
{
|
|
17
|
+
id: 'code-lint-missing-header',
|
|
18
|
+
route: 'POST /SASjsApi/code/lint',
|
|
19
|
+
description: 'answers the diagnostics for a buffer, without touching a file',
|
|
20
|
+
request: {
|
|
21
|
+
// the routes are permissioned; the runner resolves the fixture's admin
|
|
22
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
23
|
+
body: { code: 'data _null_;\n x = 1;\nrun;\n' }
|
|
24
|
+
},
|
|
25
|
+
expect: {
|
|
26
|
+
status: 200,
|
|
27
|
+
body: [
|
|
28
|
+
{
|
|
29
|
+
message: 'File missing Doxygen header',
|
|
30
|
+
lineNumber: 1,
|
|
31
|
+
startColumnNumber: 1,
|
|
32
|
+
endColumnNumber: 1,
|
|
33
|
+
severity: 1
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
// Two refusals are deliberately not pinned here yet: a `code` that is not a
|
|
39
|
+
// string, and one that is empty. The package answers 400 `"code" must be a
|
|
40
|
+
// string` / `"code" is not allowed to be empty` - the messages the server's
|
|
41
|
+
// schema produces - but sasjs/server's code controller carries no
|
|
42
|
+
// `@Response(400)` for these routes, so the document states 200 alone and the
|
|
43
|
+
// parity gate refuses a case answering anything else. The cases land when the
|
|
44
|
+
// decorators do.
|
|
45
|
+
{
|
|
46
|
+
id: 'code-format-adds-the-header',
|
|
47
|
+
route: 'POST /SASjsApi/code/format',
|
|
48
|
+
description: 'answers the formatted buffer, header block included',
|
|
49
|
+
request: {
|
|
50
|
+
// the routes are permissioned; the runner resolves the fixture's admin
|
|
51
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
52
|
+
body: { code: 'data _null_;\n x = 1;\nrun;\n' }
|
|
53
|
+
},
|
|
54
|
+
expect: {
|
|
55
|
+
status: 200,
|
|
56
|
+
body: '/**\n @file\n @brief <Your brief here>\n\n <h4> SAS Macros </h4>\n\n**/\ndata _null_;\n x = 1;\nrun;\n'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
];
|
|
@@ -47,6 +47,15 @@ export interface CorpusCase {
|
|
|
47
47
|
* minted token is not a value a case can state.
|
|
48
48
|
*/
|
|
49
49
|
keys?: string[];
|
|
50
|
+
/**
|
|
51
|
+
* How the body travels, where a string could be either. `json` is a JSON
|
|
52
|
+
* string on the wire - what tsoa emits for `Promise<string>` and what the
|
|
53
|
+
* server's `res.json` sends; `text` is a body the route sends itself, which
|
|
54
|
+
* the document carries in the `description` with no content. A case that
|
|
55
|
+
* names neither is read as `text` when the document declares no content and
|
|
56
|
+
* as `json` when it does.
|
|
57
|
+
*/
|
|
58
|
+
encoding?: 'json' | 'text';
|
|
50
59
|
};
|
|
51
60
|
}
|
|
52
61
|
export declare const DRIVE_CORPUS: CorpusCase[];
|
|
@@ -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;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,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,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,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;QACd;;;;WAIG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC;;;WAGG;QACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;
|
|
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;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAE7D,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,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,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;QACd;;;;WAIG;QACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACjC;;;WAGG;QACH,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;QACf;;;;;;;WAOG;QACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;KAC3B,CAAA;CACF;AAmBD,eAAO,MAAM,YAAY,EAAE,UAAU,EAqapC,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DriveService } from '../drive/service.js';
|
|
2
2
|
import type { IdentityService } from '../identity/service.js';
|
|
3
3
|
import type { RecordService } from '../identity/records.js';
|
|
4
|
+
import type { CodeService } from '../code/service.js';
|
|
4
5
|
import type { Subject } from '../identity/types.js';
|
|
5
6
|
export interface ApiRequest {
|
|
6
7
|
query?: Record<string, string | undefined>;
|
|
@@ -33,6 +34,8 @@ export interface ApiResponse {
|
|
|
33
34
|
contents?: string | Uint8Array;
|
|
34
35
|
}
|
|
35
36
|
export interface Services {
|
|
37
|
+
/** The editor routes, where the host supplies a linter. */
|
|
38
|
+
code?: CodeService;
|
|
36
39
|
drive: DriveService;
|
|
37
40
|
/**
|
|
38
41
|
* Users, clients and tokens. A host without one answers 501 for the identity
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/host/dispatch.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAOnD,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,oFAAoF;IACpF,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAA;IACtD;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,mBAAmB;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,YAAY,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,yFAAyF;IACzF,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../../../src/host/dispatch.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AAOnD,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,oFAAoF;IACpF,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAA;KAAE,CAAA;IACtD;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,mBAAmB;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;CAC/B;AAED,MAAM,WAAW,QAAQ;IACvB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,KAAK,EAAE,YAAY,CAAA;IACnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,yFAAyF;IACzF,OAAO,CAAC,EAAE,aAAa,CAAA;CACxB;AA2CD,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,UAAS,UAAe,KACvB,OAAO,CAAC,WAAW,CA2YrB,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,MAAM,EA4ClC,CAAA"}
|
|
@@ -21,8 +21,13 @@ const validate = (route, request) => {
|
|
|
21
21
|
const given = param.in === 'query'
|
|
22
22
|
? request.query?.[param.name]
|
|
23
23
|
: request.body?.[param.name];
|
|
24
|
-
|
|
24
|
+
// missing and empty are two refusals, the way the schema has them: a
|
|
25
|
+
// client that sent the field an empty value has a different bug from one
|
|
26
|
+
// that omitted it
|
|
27
|
+
if (given === undefined)
|
|
25
28
|
throw new ApiError(400, `"${param.name}" is required`);
|
|
29
|
+
if (given === '')
|
|
30
|
+
throw new ApiError(400, `"${param.name}" is not allowed to be empty`);
|
|
26
31
|
}
|
|
27
32
|
};
|
|
28
33
|
/** The drive path a write names: the query parameter wins over the form field. */
|
|
@@ -41,7 +46,7 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
41
46
|
const route = routeById(routeId);
|
|
42
47
|
if (!route)
|
|
43
48
|
throw new ApiError(404, `No such operation: ${routeId}`);
|
|
44
|
-
const { drive, identity, records } = services;
|
|
49
|
+
const { drive, identity, records, code } = services;
|
|
45
50
|
// The server authenticates before its payload schema runs, so an anonymous
|
|
46
51
|
// request with an incomplete body is a 401 there rather than a 400. The order
|
|
47
52
|
// is only observable on the route groups that gate on a caller: the sign-in
|
|
@@ -89,7 +94,7 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
89
94
|
status: 200,
|
|
90
95
|
body: await identity.token({
|
|
91
96
|
clientId: String(body.clientId ?? ''),
|
|
92
|
-
code:
|
|
97
|
+
code: body.code
|
|
93
98
|
})
|
|
94
99
|
};
|
|
95
100
|
case 'POST /SASjsApi/auth/refresh':
|
|
@@ -216,6 +221,26 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
216
221
|
status: 200,
|
|
217
222
|
body: await required().deleteUser(await caller(), { username: String(params.username ?? '') }, { password: body.password })
|
|
218
223
|
};
|
|
224
|
+
case 'POST /SASjsApi/code/lint':
|
|
225
|
+
if (!code)
|
|
226
|
+
throw new ApiError(501, `This host has no code service, so it cannot answer ${routeId}.`);
|
|
227
|
+
return {
|
|
228
|
+
status: 200,
|
|
229
|
+
body: await code.lint(await caller(), {
|
|
230
|
+
code: body.code,
|
|
231
|
+
filePath: body.filePath
|
|
232
|
+
})
|
|
233
|
+
};
|
|
234
|
+
case 'POST /SASjsApi/code/format':
|
|
235
|
+
if (!code)
|
|
236
|
+
throw new ApiError(501, `This host has no code service, so it cannot answer ${routeId}.`);
|
|
237
|
+
return {
|
|
238
|
+
status: 200,
|
|
239
|
+
body: await code.format(await caller(), {
|
|
240
|
+
code: body.code,
|
|
241
|
+
filePath: body.filePath
|
|
242
|
+
})
|
|
243
|
+
};
|
|
219
244
|
case 'GET /SASjsApi/info/authorizedRoutes':
|
|
220
245
|
return {
|
|
221
246
|
status: 200,
|
|
@@ -328,6 +353,8 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
328
353
|
};
|
|
329
354
|
/** Whether a route is one `dispatch` can answer today. */
|
|
330
355
|
export const HANDLED_ROUTES = [
|
|
356
|
+
'POST /SASjsApi/code/lint',
|
|
357
|
+
'POST /SASjsApi/code/format',
|
|
331
358
|
'GET /SASjsApi/info/authorizedRoutes',
|
|
332
359
|
'GET /SASjsApi/info/myAuthorizedRoutes',
|
|
333
360
|
'GET /SASjsApi/drive/file',
|
package/build/esm/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export * from './identity/routeAccess.js';
|
|
|
29
29
|
export type { ApiRequest, ApiResponse, Services } from './host/dispatch.js';
|
|
30
30
|
export { createIdentityService } from './identity/service.js';
|
|
31
31
|
export { createRecordService, sanitiseDisplayName } from './identity/records.js';
|
|
32
|
+
export { CODE_CORPUS } from './corpus/code.js';
|
|
32
33
|
export type { AuthorizedRoutesResponse, GroupDetailsResponse, GroupResponse, PermissionResponse, RecordServiceOptions, RecordService, UserDetailsResponse, UserResponse } from './identity/records.js';
|
|
33
34
|
export type { IdentityService, LoginResult, SessionResult } from './identity/service.js';
|
|
34
35
|
export { MemoryIdentityStore } from './identity/memory-store.js';
|
package/build/esm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG7D,cAAc,2BAA2B,CAAA;AACzC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAChF,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACb,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,EACP,SAAS,EACT,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EACL,aAAa,EACb,eAAe,EACf,gBAAgB,EACjB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,OAAO,EACP,QAAQ,EACR,MAAM,EACP,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AACvD,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,UAAU,EACX,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACV,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAChE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAG7D,cAAc,2BAA2B,CAAA;AACzC,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAC3E,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAA;AAChF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACb,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,aAAa,EACd,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,yBAAyB,EACzB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,wBAAwB,EACxB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,EACP,SAAS,EACT,UAAU,EACX,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AACtD,OAAO,EACL,aAAa,EACb,eAAe,EACf,gBAAgB,EACjB,MAAM,qBAAqB,CAAA;AAC5B,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AACzD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA"}
|
package/build/esm/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export { dispatch, HANDLED_ROUTES } from './host/dispatch.js';
|
|
|
25
25
|
export * from './identity/routeAccess.js';
|
|
26
26
|
export { createIdentityService } from './identity/service.js';
|
|
27
27
|
export { createRecordService, sanitiseDisplayName } from './identity/records.js';
|
|
28
|
+
export { CODE_CORPUS } from './corpus/code.js';
|
|
28
29
|
export { MemoryIdentityStore } from './identity/memory-store.js';
|
|
29
30
|
export { DEFAULT_IDENTITY, IDENTITY_FORMAT, parseIdentity } from './identity/fixture.js';
|
|
30
31
|
export { DRIVE_CORPUS } from './corpus/drive.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../../src/routes/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGzD,yDAAyD;AACzD,UAAU,aAAa;IACrB,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;IAC3B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GACtC,CAAC;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,WAAW,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAElE,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAczD,CAAA;
|
|
1
|
+
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../../src/routes/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGzD,yDAAyD;AACzD,UAAU,aAAa;IACrB,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;IAC3B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GACtC,CAAC;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,WAAW,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AAElE,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAczD,CAAA;AA2CD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAyNhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,CAAA"}
|
|
@@ -23,7 +23,6 @@ export const TAG_CAPABILITIES = {
|
|
|
23
23
|
* thing thirty times.
|
|
24
24
|
*/
|
|
25
25
|
const DIRECTORY = 'Synchronising with an external auth provider needs a directory port, which this package does not have: a host decides which directory it holds accounts in, and answers this itself.';
|
|
26
|
-
const EDITOR = 'Linting and formatting come from @sasjs/lint, which this package does not depend on. They land with the code routes so an editor has one wiring.';
|
|
27
26
|
const RUNTIME = 'Starting a program needs a runtime port, which this package does not have: a host decides which languages it holds, and answers this itself until then.';
|
|
28
27
|
const SESSION_RECORD = "A triggered session's record, log, output and state belong to the runtime that ran the program, and this package has no runtime port.";
|
|
29
28
|
const HOST_CONFIG = "The body is the host's own configuration - ports, run times and providers - which a mock states for itself and this package cannot know.";
|
|
@@ -93,8 +92,14 @@ export const OVERLAY = {
|
|
|
93
92
|
// Code - the editor's three calls, and the runtime one
|
|
94
93
|
'POST /SASjsApi/code/execute': { implemented: false, reason: RUNTIME },
|
|
95
94
|
'POST /SASjsApi/code/trigger': { implemented: false, reason: RUNTIME },
|
|
96
|
-
'POST /SASjsApi/code/lint': {
|
|
97
|
-
|
|
95
|
+
'POST /SASjsApi/code/lint': {
|
|
96
|
+
implemented: true,
|
|
97
|
+
capabilities: ['identity']
|
|
98
|
+
},
|
|
99
|
+
'POST /SASjsApi/code/format': {
|
|
100
|
+
implemented: true,
|
|
101
|
+
capabilities: ['identity']
|
|
102
|
+
},
|
|
98
103
|
// Drive - the file and folder rules, and the three deploy routes
|
|
99
104
|
// The body is the file itself, so there is nothing to annotate
|
|
100
105
|
'GET /SASjsApi/drive/file': { implemented: true },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sasjs/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
"types": "./build/esm/index.d.ts",
|
|
18
18
|
"import": "./build/esm/index.js",
|
|
19
19
|
"require": "./build/cjs/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./code": {
|
|
22
|
+
"types": "./build/esm/code/index.d.ts",
|
|
23
|
+
"import": "./build/esm/code/index.js",
|
|
24
|
+
"require": "./build/cjs/code/index.js"
|
|
20
25
|
}
|
|
21
26
|
},
|
|
22
27
|
"files": [
|
|
@@ -60,5 +65,8 @@
|
|
|
60
65
|
"deepmerge": "$deepmerge",
|
|
61
66
|
"glob": "14.0.3",
|
|
62
67
|
"minimatch": "11.0.4"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@sasjs/lint": "5.0.0"
|
|
63
71
|
}
|
|
64
72
|
}
|