@sasjs/api 0.0.0-stage → 0.1.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/LICENCE.md +21 -0
- package/README.md +85 -2
- package/build/browser.js +2469 -0
- package/build/cjs/corpus/drive.js +288 -0
- package/build/cjs/corpus/runner.js +35 -0
- package/build/cjs/drive/memory-store.js +126 -0
- package/build/cjs/drive/paths.js +82 -0
- package/build/cjs/drive/service.js +143 -0
- package/build/cjs/host/dispatch.js +102 -0
- package/build/cjs/index.js +58 -0
- package/build/cjs/routes/generated.js +1603 -0
- package/build/cjs/routes/index.js +111 -0
- package/build/cjs/routes/overlay.js +75 -0
- package/build/cjs/types.js +35 -0
- package/build/esm/corpus/drive.d.ts +33 -0
- package/build/esm/corpus/drive.d.ts.map +1 -0
- package/build/esm/corpus/drive.js +285 -0
- package/build/esm/corpus/runner.d.ts +27 -0
- package/build/esm/corpus/runner.d.ts.map +1 -0
- package/build/esm/corpus/runner.js +30 -0
- package/build/esm/drive/memory-store.d.ts +37 -0
- package/build/esm/drive/memory-store.d.ts.map +1 -0
- package/build/esm/drive/memory-store.js +122 -0
- package/build/esm/drive/paths.d.ts +49 -0
- package/build/esm/drive/paths.d.ts.map +1 -0
- package/build/esm/drive/paths.js +73 -0
- package/build/esm/drive/service.d.ts +42 -0
- package/build/esm/drive/service.d.ts.map +1 -0
- package/build/esm/drive/service.js +139 -0
- package/build/esm/host/dispatch.d.ts +24 -0
- package/build/esm/host/dispatch.d.ts.map +1 -0
- package/build/esm/host/dispatch.js +98 -0
- package/build/esm/index.d.ts +31 -0
- package/build/esm/index.d.ts.map +1 -0
- package/build/esm/index.js +24 -0
- package/build/esm/package.json +3 -0
- package/build/esm/routes/generated.d.ts +14 -0
- package/build/esm/routes/generated.d.ts.map +1 -0
- package/build/esm/routes/generated.js +1600 -0
- package/build/esm/routes/index.d.ts +18 -0
- package/build/esm/routes/index.d.ts.map +1 -0
- package/build/esm/routes/index.js +106 -0
- package/build/esm/routes/overlay.d.ts +40 -0
- package/build/esm/routes/overlay.d.ts.map +1 -0
- package/build/esm/routes/overlay.js +72 -0
- package/build/esm/types.d.ts +110 -0
- package/build/esm/types.d.ts.map +1 -0
- package/build/esm/types.js +30 -0
- package/package.json +55 -4
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Route } from '../types.js';
|
|
2
|
+
export declare const ROUTES: Route[];
|
|
3
|
+
/** The route with this `METHOD /path` identity, if the document has one. */
|
|
4
|
+
export declare const routeById: (id: string) => Route | undefined;
|
|
5
|
+
export interface RouteMatch {
|
|
6
|
+
route: Route;
|
|
7
|
+
/** Values captured from the path's `{placeholders}`. */
|
|
8
|
+
params: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The route a request names, or undefined when the API has no such operation.
|
|
12
|
+
*
|
|
13
|
+
* A host that gets undefined answers 404 "not part of the API"; a host that gets
|
|
14
|
+
* a route it cannot serve answers 501 naming the capability. Keeping those two
|
|
15
|
+
* apart is the difference between a client's bug and a host's limit.
|
|
16
|
+
*/
|
|
17
|
+
export declare const findRoute: (method: string, path: string) => RouteMatch | undefined;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/routes/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAiB,MAAM,aAAa,CAAA;AAyCvD,eAAO,MAAM,MAAM,EAAE,KAAK,EAAgC,CAAA;AAW1D,4EAA4E;AAC5E,eAAO,MAAM,SAAS,GAAI,IAAI,MAAM,KAAG,KAAK,GAAG,SAAyB,CAAA;AAExE,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,KAAK,CAAA;IACZ,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC/B;AAgCD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GACpB,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,UAAU,GAAG,SAuBf,CAAA"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The route table, and the one matcher both hosts dispatch with.
|
|
3
|
+
*
|
|
4
|
+
* `GENERATED_ROUTES` is the OpenAPI document, `OVERLAY` is the part a document
|
|
5
|
+
* cannot carry, and this file is where they meet. Importing it validates the
|
|
6
|
+
* join as a side effect: an overlay entry naming an operation the document does
|
|
7
|
+
* not have is a stale entry, and it throws here rather than being silently
|
|
8
|
+
* ignored - the failure mode that lets a "documented" behaviour rot.
|
|
9
|
+
*/
|
|
10
|
+
import { GENERATED_ROUTES } from './generated.js';
|
|
11
|
+
import { OVERLAY, TAG_CAPABILITIES } from './overlay.js';
|
|
12
|
+
const merge = (generated) => {
|
|
13
|
+
const overlay = OVERLAY[generated.id] || {};
|
|
14
|
+
const documented = new Map(generated.responses.map((r) => [r.status, r.description]));
|
|
15
|
+
// The document states the statuses and the overlay only annotates the ones a
|
|
16
|
+
// client keys a body on. Naming a status the document does not state is a
|
|
17
|
+
// build failure: the alternative - listing it anyway - advertises a status
|
|
18
|
+
// nothing can observe without the parity test failing, which is a documented
|
|
19
|
+
// wish rather than a contract. A host that genuinely answers a status the
|
|
20
|
+
// document omits needs the document corrected in sasjs/server.
|
|
21
|
+
for (const status of Object.keys(overlay.responses || {}).map(Number)) {
|
|
22
|
+
if (!documented.has(status)) {
|
|
23
|
+
throw new Error(`src/routes/overlay.ts annotates status ${status} for ${generated.id}, which the OpenAPI document does not state. Correct the document in sasjs/server and re-vendor spec/swagger.yaml, or drop the annotation.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const responses = [...documented.keys()]
|
|
27
|
+
.sort((a, b) => a - b)
|
|
28
|
+
.map((status) => {
|
|
29
|
+
const body = overlay.responses ? overlay.responses[status] : undefined;
|
|
30
|
+
return {
|
|
31
|
+
status,
|
|
32
|
+
description: documented.get(status) || '',
|
|
33
|
+
...(body === undefined ? {} : { body })
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
...generated,
|
|
38
|
+
responses,
|
|
39
|
+
capabilities: overlay.capabilities || TAG_CAPABILITIES[generated.tag] || [],
|
|
40
|
+
implemented: overlay.implemented === true
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export const ROUTES = GENERATED_ROUTES.map(merge);
|
|
44
|
+
const known = new Set(ROUTES.map((route) => route.id));
|
|
45
|
+
for (const id of Object.keys(OVERLAY))
|
|
46
|
+
if (!known.has(id))
|
|
47
|
+
throw new Error(`src/routes/overlay.ts names an operation the OpenAPI document does not have: ${id}. Remove the entry or refresh spec/swagger.yaml.`);
|
|
48
|
+
const byId = new Map(ROUTES.map((route) => [route.id, route]));
|
|
49
|
+
/** The route with this `METHOD /path` identity, if the document has one. */
|
|
50
|
+
export const routeById = (id) => byId.get(id);
|
|
51
|
+
const escape = (segment) => segment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
52
|
+
/**
|
|
53
|
+
* Compiles a documented path into a matcher. The table is static, so this runs
|
|
54
|
+
* once at import; `findRoute` then tests exact paths first (most of the API is
|
|
55
|
+
* static) and only falls through to the templates for `{uid}`-style paths.
|
|
56
|
+
*/
|
|
57
|
+
const compiled = ROUTES.filter((route) => route.path.includes('{')).map((route) => {
|
|
58
|
+
const keys = [];
|
|
59
|
+
const pattern = route.path
|
|
60
|
+
.split('/')
|
|
61
|
+
.map((segment) => {
|
|
62
|
+
if (segment.startsWith('{') && segment.endsWith('}')) {
|
|
63
|
+
keys.push(segment.slice(1, -1));
|
|
64
|
+
return '([^/]+)';
|
|
65
|
+
}
|
|
66
|
+
return escape(segment);
|
|
67
|
+
})
|
|
68
|
+
.join('/');
|
|
69
|
+
return { route, keys, regex: new RegExp('^' + pattern + '/?$') };
|
|
70
|
+
});
|
|
71
|
+
const exact = new Map();
|
|
72
|
+
for (const route of ROUTES)
|
|
73
|
+
if (!route.path.includes('{'))
|
|
74
|
+
exact.set(route.method + ' ' + route.path, route);
|
|
75
|
+
/**
|
|
76
|
+
* The route a request names, or undefined when the API has no such operation.
|
|
77
|
+
*
|
|
78
|
+
* A host that gets undefined answers 404 "not part of the API"; a host that gets
|
|
79
|
+
* a route it cannot serve answers 501 naming the capability. Keeping those two
|
|
80
|
+
* apart is the difference between a client's bug and a host's limit.
|
|
81
|
+
*/
|
|
82
|
+
export const findRoute = (method, path) => {
|
|
83
|
+
const verb = method.toUpperCase();
|
|
84
|
+
const clean = path.split('?')[0].replace(/\/+$/, '') || '/';
|
|
85
|
+
const hit = exact.get(verb + ' ' + clean);
|
|
86
|
+
if (hit)
|
|
87
|
+
return { route: hit, params: {} };
|
|
88
|
+
for (const candidate of compiled) {
|
|
89
|
+
if (candidate.route.method !== verb)
|
|
90
|
+
continue;
|
|
91
|
+
const match = candidate.regex.exec(clean);
|
|
92
|
+
if (!match)
|
|
93
|
+
continue;
|
|
94
|
+
const params = {};
|
|
95
|
+
candidate.keys.forEach((key, index) => {
|
|
96
|
+
try {
|
|
97
|
+
params[key] = decodeURIComponent(match[index + 1]);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
params[key] = match[index + 1];
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return { route: candidate.route, params };
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the OpenAPI document cannot say.
|
|
3
|
+
*
|
|
4
|
+
* The generated table states the route's shape - method, path, parameters,
|
|
5
|
+
* documented responses. This file adds the three things a document has no room
|
|
6
|
+
* for, each of which a host or a test needs:
|
|
7
|
+
*
|
|
8
|
+
* - the body a response carries, because a client keys on `{"status":
|
|
9
|
+
* "success"}` and on the wording of a refusal, not on a status alone. A
|
|
10
|
+
* refusal's body is the message as a plain string: the drive routes answer
|
|
11
|
+
* `res.status(code).send(err.message)`, and sasjs/server's own suite asserts
|
|
12
|
+
* `res.text`. A host renders that in its own transport - a text body on the
|
|
13
|
+
* server, `text(...)` in the worker - and never wraps it in an envelope the
|
|
14
|
+
* server does not send;
|
|
15
|
+
* - which capability the route needs, so a host can answer 501 with a reason
|
|
16
|
+
* instead of 404 with none;
|
|
17
|
+
* - whether this package implements it yet, so "not done" is visible.
|
|
18
|
+
*
|
|
19
|
+
* The statuses come from the document, and an overlay body is attached to the
|
|
20
|
+
* status it belongs to: naming a status the document does not state is a build
|
|
21
|
+
* failure, not a way to describe a divergence. A divergence between the document
|
|
22
|
+
* and the code is a bug in one of them, corrected at source in `sasjs/server` and
|
|
23
|
+
* re-vendored here.
|
|
24
|
+
*/
|
|
25
|
+
import type { Capability, HttpMethod } from '../types.js';
|
|
26
|
+
export interface RouteOverlay {
|
|
27
|
+
capabilities?: Capability[];
|
|
28
|
+
implemented?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* The body the operation sends with a status, where a client keys on it. The
|
|
31
|
+
* statuses come from the document; this only annotates them.
|
|
32
|
+
*/
|
|
33
|
+
responses?: Record<number, unknown>;
|
|
34
|
+
}
|
|
35
|
+
/** The default capability for each tag, overridden per route where it differs. */
|
|
36
|
+
export declare const TAG_CAPABILITIES: Record<string, Capability[]>;
|
|
37
|
+
export declare const OVERLAY: Record<string, RouteOverlay>;
|
|
38
|
+
/** Every method the table may state, in the order a diff reads best. */
|
|
39
|
+
export declare const METHODS: HttpMethod[];
|
|
40
|
+
//# sourceMappingURL=overlay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay.d.ts","sourceRoot":"","sources":["../../../src/routes/overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEzD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,UAAU,EAAE,CAAA;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACpC;AAED,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAczD,CAAA;AAID,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAoDhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/** The default capability for each tag, overridden per route where it differs. */
|
|
2
|
+
export const TAG_CAPABILITIES = {
|
|
3
|
+
Drive: ['storage'],
|
|
4
|
+
STP: ['runtime', 'storage'],
|
|
5
|
+
Code: ['runtime'],
|
|
6
|
+
Session: ['runtime'],
|
|
7
|
+
Info: [],
|
|
8
|
+
Admin: ['storage'],
|
|
9
|
+
Auth: ['identity'],
|
|
10
|
+
'Auth Config': ['identity'],
|
|
11
|
+
User: ['identity'],
|
|
12
|
+
Group: ['identity'],
|
|
13
|
+
Permission: ['identity'],
|
|
14
|
+
Client: ['identity'],
|
|
15
|
+
Web: ['identity']
|
|
16
|
+
};
|
|
17
|
+
const success = { status: 'success' };
|
|
18
|
+
export const OVERLAY = {
|
|
19
|
+
// the route inventory reports what the CALLER may use, so it needs an identity
|
|
20
|
+
// even though the rest of the Info tag needs nothing
|
|
21
|
+
'GET /SASjsApi/info/authorizedRoutes': { capabilities: ['identity'] },
|
|
22
|
+
'GET /SASjsApi/info/myAuthorizedRoutes': { capabilities: ['identity'] },
|
|
23
|
+
// the body is the file itself, so there is nothing to annotate
|
|
24
|
+
'GET /SASjsApi/drive/file': { implemented: true },
|
|
25
|
+
'POST /SASjsApi/drive/file': {
|
|
26
|
+
implemented: true,
|
|
27
|
+
responses: { 200: success, 409: 'File already exists.' }
|
|
28
|
+
},
|
|
29
|
+
'PATCH /SASjsApi/drive/file': {
|
|
30
|
+
implemented: true,
|
|
31
|
+
responses: { 200: success, 404: `File doesn't exist.` }
|
|
32
|
+
},
|
|
33
|
+
'DELETE /SASjsApi/drive/file': {
|
|
34
|
+
implemented: true,
|
|
35
|
+
responses: { 200: success, 404: `File doesn't exist.` }
|
|
36
|
+
},
|
|
37
|
+
'GET /SASjsApi/drive/folder': {
|
|
38
|
+
implemented: true,
|
|
39
|
+
responses: { 200: { files: [], folders: [] } }
|
|
40
|
+
},
|
|
41
|
+
'POST /SASjsApi/drive/folder': {
|
|
42
|
+
implemented: true,
|
|
43
|
+
responses: { 200: success, 409: 'Folder already exists.' }
|
|
44
|
+
},
|
|
45
|
+
'DELETE /SASjsApi/drive/folder': {
|
|
46
|
+
implemented: true,
|
|
47
|
+
responses: { 200: success, 404: `Folder doesn't exist.` }
|
|
48
|
+
},
|
|
49
|
+
'POST /SASjsApi/drive/rename': {
|
|
50
|
+
implemented: true,
|
|
51
|
+
responses: {
|
|
52
|
+
200: success,
|
|
53
|
+
409: 'File with new name already exists.',
|
|
54
|
+
404: 'No file/folder found for provided path.'
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
'GET /SASjsApi/drive/filetree': {
|
|
58
|
+
implemented: true,
|
|
59
|
+
responses: { 200: { status: 'success', tree: {} } }
|
|
60
|
+
},
|
|
61
|
+
// The two deploy routes write a whole tree in one request. They are the only
|
|
62
|
+
// ones where a caller can exhaust the host's storage, so they carry a cap -
|
|
63
|
+
// see the plan in sasjs/cli#1550 - and they are not implemented yet.
|
|
64
|
+
'POST /SASjsApi/drive/deploy': {
|
|
65
|
+
implemented: false
|
|
66
|
+
},
|
|
67
|
+
'POST /SASjsApi/drive/deploy/upload': {
|
|
68
|
+
implemented: false
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/** Every method the table may state, in the order a diff reads best. */
|
|
72
|
+
export const METHODS = ['GET', 'POST', 'PATCH', 'DELETE', 'PUT'];
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary the two hosts share.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here imports a runtime: the package is consumed by an Express server
|
|
5
|
+
* in node and by a service worker in a browser, so the types are all this file
|
|
6
|
+
* may contain. That is also why the build compiles `src` with `types: []` - an
|
|
7
|
+
* accidental `Buffer` or `process` reference fails the build rather than the
|
|
8
|
+
* worker at run time.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A refusal the API answers with: the status a client keys on, and the message
|
|
12
|
+
* it prints. Both hosts throw this and both render it the same way, so the
|
|
13
|
+
* corpus can assert on the pair.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ApiError extends Error {
|
|
16
|
+
readonly status: number;
|
|
17
|
+
/** Told apart by marker rather than `instanceof` - see `isApiError`. */
|
|
18
|
+
readonly apiError = true;
|
|
19
|
+
constructor(status: number, message: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* `instanceof` survives here (the package targets ES2020, not ES5), but a
|
|
23
|
+
* consumer that downlevels it - the CLI does - loses the prototype chain, so the
|
|
24
|
+
* marker is what the checks use. Asserting both in a spec is what keeps that
|
|
25
|
+
* honest.
|
|
26
|
+
*/
|
|
27
|
+
export declare const isApiError: (value: unknown) => value is ApiError;
|
|
28
|
+
/**
|
|
29
|
+
* What a route needs from a host in order to answer it.
|
|
30
|
+
*
|
|
31
|
+
* `runtime` is a program runtime - the host decides which languages it holds
|
|
32
|
+
* (the browser build runs JavaScript, a server runs JavaScript and SAS) and
|
|
33
|
+
* answers 501 when the program it was given is in a language it has no runtime
|
|
34
|
+
* for. `sas` is a SAS engine specifically, for a route that cannot be answered
|
|
35
|
+
* without one.
|
|
36
|
+
*/
|
|
37
|
+
export type Capability = 'runtime' | 'storage' | 'identity' | 'sas' | 'node';
|
|
38
|
+
export type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT';
|
|
39
|
+
export type ParamLocation = 'query' | 'path' | 'body' | 'formData' | 'header';
|
|
40
|
+
export interface RouteParam {
|
|
41
|
+
name: string;
|
|
42
|
+
in: ParamLocation;
|
|
43
|
+
required: boolean;
|
|
44
|
+
/** JSON-schema type as the OpenAPI document words it. */
|
|
45
|
+
type: string;
|
|
46
|
+
enum?: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface RouteResponse {
|
|
49
|
+
status: number;
|
|
50
|
+
description: string;
|
|
51
|
+
/** The body the server actually sends, where the corpus pins one. */
|
|
52
|
+
body?: unknown;
|
|
53
|
+
}
|
|
54
|
+
/** An operation exactly as the OpenAPI document states it. */
|
|
55
|
+
export interface GeneratedRoute {
|
|
56
|
+
id: string;
|
|
57
|
+
method: HttpMethod;
|
|
58
|
+
path: string;
|
|
59
|
+
tag: string;
|
|
60
|
+
summary: string;
|
|
61
|
+
params: RouteParam[];
|
|
62
|
+
requestBodies: string[];
|
|
63
|
+
responses: Array<{
|
|
64
|
+
status: number;
|
|
65
|
+
description: string;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
export interface Route extends GeneratedRoute {
|
|
69
|
+
responses: RouteResponse[];
|
|
70
|
+
capabilities: Capability[];
|
|
71
|
+
/**
|
|
72
|
+
* Whether this package carries a handler for the operation. A host may still
|
|
73
|
+
* refuse it for a missing capability - that is a 501 naming the capability,
|
|
74
|
+
* not an absence nobody can see.
|
|
75
|
+
*/
|
|
76
|
+
implemented: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* The drive, as the services see it.
|
|
80
|
+
*
|
|
81
|
+
* Every path is drive-relative and already normalised by `normaliseDrivePath`:
|
|
82
|
+
* it starts with `/`, never contains `.` or `..`, and cannot leave the drive.
|
|
83
|
+
* The store owns the translation to wherever the bytes actually live - a folder
|
|
84
|
+
* under node, a Map in the browser - and owns the representation it uses to do
|
|
85
|
+
* it (text vs bytes).
|
|
86
|
+
*/
|
|
87
|
+
export interface DriveStore {
|
|
88
|
+
read(path: string): Promise<string | Uint8Array>;
|
|
89
|
+
write(path: string, contents: string | Uint8Array): Promise<void>;
|
|
90
|
+
exists(path: string): Promise<boolean>;
|
|
91
|
+
stat(path: string): Promise<{
|
|
92
|
+
directory: boolean;
|
|
93
|
+
size: number;
|
|
94
|
+
}>;
|
|
95
|
+
list(path: string): Promise<{
|
|
96
|
+
files: string[];
|
|
97
|
+
folders: string[];
|
|
98
|
+
}>;
|
|
99
|
+
mkdirp(path: string): Promise<void>;
|
|
100
|
+
/** Removes a file, or a folder with everything under it. */
|
|
101
|
+
remove(path: string): Promise<void>;
|
|
102
|
+
move(from: string, to: string): Promise<void>;
|
|
103
|
+
}
|
|
104
|
+
export interface TreeNode {
|
|
105
|
+
name: string;
|
|
106
|
+
relativePath: string;
|
|
107
|
+
isFolder: boolean;
|
|
108
|
+
children: TreeNode[];
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary the two hosts share.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here imports a runtime: the package is consumed by an Express server
|
|
5
|
+
* in node and by a service worker in a browser, so the types are all this file
|
|
6
|
+
* may contain. That is also why the build compiles `src` with `types: []` - an
|
|
7
|
+
* accidental `Buffer` or `process` reference fails the build rather than the
|
|
8
|
+
* worker at run time.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* A refusal the API answers with: the status a client keys on, and the message
|
|
12
|
+
* it prints. Both hosts throw this and both render it the same way, so the
|
|
13
|
+
* corpus can assert on the pair.
|
|
14
|
+
*/
|
|
15
|
+
export class ApiError extends Error {
|
|
16
|
+
constructor(status, message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.status = status;
|
|
19
|
+
/** Told apart by marker rather than `instanceof` - see `isApiError`. */
|
|
20
|
+
this.apiError = true;
|
|
21
|
+
this.name = 'ApiError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `instanceof` survives here (the package targets ES2020, not ES5), but a
|
|
26
|
+
* consumer that downlevels it - the CLI does - loses the prototype chain, so the
|
|
27
|
+
* marker is what the checks use. Asserting both in a spec is what keeps that
|
|
28
|
+
* honest.
|
|
29
|
+
*/
|
|
30
|
+
export const isApiError = (value) => !!value && value.apiError === true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sasjs/api",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The SASjs Server API, described once - a generated route table, the services behind it, and the conformance corpus both hosts run",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "SASjs",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/sasjs/api.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/sasjs/api#readme",
|
|
12
|
+
"main": "build/cjs/index.js",
|
|
13
|
+
"module": "build/esm/index.js",
|
|
14
|
+
"types": "build/esm/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./build/esm/index.d.ts",
|
|
18
|
+
"import": "./build/esm/index.js",
|
|
19
|
+
"require": "./build/cjs/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"build",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENCE.md"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
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",
|
|
33
|
+
"test": "jest --coverage",
|
|
34
|
+
"test:watch": "jest --watch",
|
|
35
|
+
"lint": "prettier --check \"{src,spec,scripts}/**/*.{ts,mjs,md}\" \"*.{json,md}\"",
|
|
36
|
+
"lint:fix": "prettier --write \"{src,spec,scripts}/**/*.{ts,mjs,md}\" \"*.{json,md}\"",
|
|
37
|
+
"sync:routes": "node scripts/sync-routes.mjs",
|
|
38
|
+
"check:routes": "node scripts/sync-routes.mjs --check"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@semantic-release/commit-analyzer": "13.0.1",
|
|
42
|
+
"@semantic-release/exec": "7.1.0",
|
|
43
|
+
"@semantic-release/github": "12.0.10",
|
|
44
|
+
"@semantic-release/npm": "13.2.0",
|
|
45
|
+
"@semantic-release/release-notes-generator": "14.1.1",
|
|
46
|
+
"@types/jest": "29.5.14",
|
|
47
|
+
"@types/node": "22.20.5",
|
|
48
|
+
"esbuild": "0.28.2",
|
|
49
|
+
"jest": "29.7.0",
|
|
50
|
+
"prettier": "3.9.10",
|
|
51
|
+
"rimraf": "6.1.4",
|
|
52
|
+
"semantic-release": "25.0.9",
|
|
53
|
+
"ts-jest": "29.4.15",
|
|
54
|
+
"typescript": "5.9.3",
|
|
55
|
+
"yaml": "2.9.1"
|
|
56
|
+
}
|
|
57
|
+
}
|