@sasjs/api 0.4.0 → 0.6.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.
Files changed (39) hide show
  1. package/README.md +12 -13
  2. package/build/browser.js +515 -54
  3. package/build/cjs/corpus/drive.js +119 -0
  4. package/build/cjs/corpus/runner.js +18 -2
  5. package/build/cjs/drive/deploy.js +270 -0
  6. package/build/cjs/drive/service.js +5 -0
  7. package/build/cjs/host/dispatch.js +15 -2
  8. package/build/cjs/index.js +10 -1
  9. package/build/cjs/routes/generated.js +8 -0
  10. package/build/cjs/routes/index.js +8 -2
  11. package/build/cjs/routes/overlay.js +181 -40
  12. package/build/cjs/types.js +11 -1
  13. package/build/esm/corpus/drive.d.ts +6 -0
  14. package/build/esm/corpus/drive.d.ts.map +1 -1
  15. package/build/esm/corpus/drive.js +119 -0
  16. package/build/esm/corpus/runner.d.ts.map +1 -1
  17. package/build/esm/corpus/runner.js +18 -2
  18. package/build/esm/drive/deploy.d.ts +143 -0
  19. package/build/esm/drive/deploy.d.ts.map +1 -0
  20. package/build/esm/drive/deploy.js +262 -0
  21. package/build/esm/drive/service.d.ts +8 -0
  22. package/build/esm/drive/service.d.ts.map +1 -1
  23. package/build/esm/drive/service.js +5 -0
  24. package/build/esm/host/dispatch.d.ts.map +1 -1
  25. package/build/esm/host/dispatch.js +15 -2
  26. package/build/esm/index.d.ts +2 -0
  27. package/build/esm/index.d.ts.map +1 -1
  28. package/build/esm/index.js +1 -0
  29. package/build/esm/routes/generated.d.ts.map +1 -1
  30. package/build/esm/routes/generated.js +8 -0
  31. package/build/esm/routes/index.d.ts.map +1 -1
  32. package/build/esm/routes/index.js +8 -2
  33. package/build/esm/routes/overlay.d.ts +26 -3
  34. package/build/esm/routes/overlay.d.ts.map +1 -1
  35. package/build/esm/routes/overlay.js +181 -40
  36. package/build/esm/types.d.ts +26 -1
  37. package/build/esm/types.d.ts.map +1 -1
  38. package/build/esm/types.js +11 -1
  39. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { DEPLOY_SUCCESS } from '../drive/deploy.js';
1
2
  /** The default capability for each tag, overridden per route where it differs. */
2
3
  export const TAG_CAPABILITIES = {
3
4
  Drive: ['storage'],
@@ -14,6 +15,23 @@ export const TAG_CAPABILITIES = {
14
15
  Client: ['identity'],
15
16
  Web: ['identity']
16
17
  };
18
+ /**
19
+ * Why a block of operations is not implemented here.
20
+ *
21
+ * One per dependency rather than one per route: the reason states what the
22
+ * operation waits on, and thirty copies of the same sentence would say the same
23
+ * thing thirty times.
24
+ */
25
+ const IDENTITY_RECORDS = 'The identity service answers the sign-in flow. User, group, permission rule, client and provider records need read and write methods on the same store, which this package does not have yet.';
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
+ 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
+ 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
+ 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.";
30
+ const PERMISSIONS = "The route inventory is the host's permission model, which this package does not hold.";
31
+ const HOST_OWNED = "The host owns this report: it describes the host's own process and files, which a browser build has no equivalent of.";
32
+ const WEB_BUNDLE = 'The host serves its own web bundle; there is no API decision here for this package to answer.';
33
+ const ARCHIVE = "Expanding the uploaded archive is the host's transport work - multer and a zip library on the server, `DecompressionStream` in a worker - and the host hands this package the tree it produces, which is the same tree `POST /SASjsApi/drive/deploy` takes.";
34
+ const UNPUBLISH = 'Unpublishing inverts the deploy route, so the two land together.';
17
35
  const success = { status: 'success' };
18
36
  const user = {
19
37
  uid: '',
@@ -23,11 +41,61 @@ const user = {
23
41
  needsToUpdatePassword: false
24
42
  };
25
43
  export const OVERLAY = {
26
- // the route inventory reports what the CALLER may use, so it needs an identity
27
- // even though the rest of the Info tag needs nothing
28
- 'GET /SASjsApi/info/authorizedRoutes': { capabilities: ['identity'] },
29
- 'GET /SASjsApi/info/myAuthorizedRoutes': { capabilities: ['identity'] },
30
- // the body is the file itself, so there is nothing to annotate
44
+ // Web - the sign-in pages the identity service answers, and the bundle itself
45
+ 'GET /': { implemented: false, reason: WEB_BUNDLE },
46
+ 'POST /SASLogon/login': {
47
+ implemented: true,
48
+ // The lockout and the provider failure are answered before the package is
49
+ // consulted (routes/web/web.ts in sasjs/server).
50
+ hostResponses: [429, 500],
51
+ responses: { 200: { loggedIn: true, user } }
52
+ },
53
+ 'POST /SASLogon/authorize': {
54
+ implemented: true,
55
+ responses: { 200: { code: '' } }
56
+ },
57
+ 'GET /SASLogon/logout': {
58
+ implemented: true,
59
+ hostResponses: [500],
60
+ responses: { 200: 'OK!' }
61
+ },
62
+ // Admin - the host's own process and files
63
+ 'GET /SASjsApi/admin/diagnostics': { implemented: false, reason: HOST_OWNED },
64
+ 'GET /SASjsApi/admin/logs': { implemented: false, reason: HOST_OWNED },
65
+ 'GET /SASjsApi/admin/logs/{fileName}': {
66
+ implemented: false,
67
+ reason: HOST_OWNED
68
+ },
69
+ // Auth - the token flow, plus a password change the identity store cannot write
70
+ 'POST /SASjsApi/auth/token': {
71
+ implemented: true,
72
+ responses: { 200: { accessToken: '', refreshToken: '' } }
73
+ },
74
+ 'POST /SASjsApi/auth/refresh': {
75
+ implemented: true,
76
+ responses: { 200: { accessToken: '', refreshToken: '' } }
77
+ },
78
+ 'POST /SASjsApi/auth/logout': { implemented: true },
79
+ 'PATCH /SASjsApi/auth/updatePassword': {
80
+ implemented: false,
81
+ reason: IDENTITY_RECORDS
82
+ },
83
+ // Auth Config - the providers a host was started with, and an LDAP import
84
+ 'GET /SASjsApi/authConfig': { implemented: false, reason: HOST_CONFIG },
85
+ 'POST /SASjsApi/authConfig/synchroniseWithLDAP': {
86
+ implemented: false,
87
+ reason: IDENTITY_RECORDS
88
+ },
89
+ // Client - the OAuth clients the identity store already holds for sign-in
90
+ 'GET /SASjsApi/client': { implemented: false, reason: IDENTITY_RECORDS },
91
+ 'POST /SASjsApi/client': { implemented: false, reason: IDENTITY_RECORDS },
92
+ // Code - the editor's three calls, and the runtime one
93
+ 'POST /SASjsApi/code/execute': { implemented: false, reason: RUNTIME },
94
+ 'POST /SASjsApi/code/trigger': { implemented: false, reason: RUNTIME },
95
+ 'POST /SASjsApi/code/lint': { implemented: false, reason: EDITOR },
96
+ 'POST /SASjsApi/code/format': { implemented: false, reason: EDITOR },
97
+ // Drive - the file and folder rules, and the three deploy routes
98
+ // The body is the file itself, so there is nothing to annotate
31
99
  'GET /SASjsApi/drive/file': { implemented: true },
32
100
  'POST /SASjsApi/drive/file': {
33
101
  implemented: true,
@@ -65,54 +133,127 @@ export const OVERLAY = {
65
133
  implemented: true,
66
134
  responses: { 200: { status: 'success', tree: {} } }
67
135
  },
68
- // The two deploy routes write a whole tree in one request. They are the only
69
- // ones where a caller can exhaust the host's storage, so they carry a cap -
70
- // see the plan in sasjs/cli#1550 - and they are not implemented yet.
136
+ // The deploy routes answer an ENVELOPE, unlike the file routes above: their
137
+ // `400` is `{status:'failure', message}` rather than the message as text, and
138
+ // `413` is the cap refusing the tree before anything is written.
139
+ // `"appLoc" is required` and `"fileTree" is required` are the other `400` this
140
+ // route sends - a missing body property, worded by the table's own
141
+ // validation - so the body annotated here is the substantial one.
71
142
  'POST /SASjsApi/drive/deploy': {
72
- implemented: false
143
+ implemented: true,
144
+ responses: {
145
+ 200: DEPLOY_SUCCESS,
146
+ 400: {
147
+ status: 'failure',
148
+ message: 'Provided not supported data format.'
149
+ },
150
+ 413: 'The file tree exceeds the deploy limit.',
151
+ 500: { status: 'failure', message: 'Deployment failed!' }
152
+ }
73
153
  },
74
154
  'POST /SASjsApi/drive/deploy/upload': {
75
- implemented: false
76
- },
77
- // Unpublishing is the counterpart of the two above and lands with them: the
78
- // document in sasjs/server states it (sasjs/server#543), so a host may answer
79
- // it in the meantime, but this package cannot until the publish side it would
80
- // invert is implemented here. The statuses are the document's; `404` is a pair
81
- // - the registry name and the appLoc it was published from - that matches no
82
- // entry, and the drive's content is deliberately left alone.
155
+ implemented: false,
156
+ reason: ARCHIVE
157
+ },
158
+ // `404` is a pair - the registry name and the appLoc it was published from -
159
+ // that matches no entry, and the drive's content is deliberately left alone.
83
160
  'DELETE /SASjsApi/drive/deploy': {
84
- implemented: false
161
+ implemented: false,
162
+ reason: UNPUBLISH
85
163
  },
86
- // The lockout and the provider failure are answered before the package is
87
- // consulted (routes/web/web.ts in sasjs/server).
88
- 'POST /SASLogon/login': {
89
- implemented: true,
90
- hostResponses: [429, 500],
91
- responses: { 200: { loggedIn: true, user } }
164
+ // Group - members and the groups themselves
165
+ 'GET /SASjsApi/group': { implemented: false, reason: IDENTITY_RECORDS },
166
+ 'GET /SASjsApi/group/{uid}': { implemented: false, reason: IDENTITY_RECORDS },
167
+ 'GET /SASjsApi/group/by/groupname/{name}': {
168
+ implemented: false,
169
+ reason: IDENTITY_RECORDS
92
170
  },
93
- // The sign-in flow. The statuses are the document's; these entries state the
94
- // body a client keys on, and that this package answers the operation.
95
- 'POST /SASjsApi/auth/token': {
96
- implemented: true,
97
- responses: { 200: { accessToken: '', refreshToken: '' } }
171
+ 'POST /SASjsApi/group': { implemented: false, reason: IDENTITY_RECORDS },
172
+ 'POST /SASjsApi/group/{groupUid}/{userUid}': {
173
+ implemented: false,
174
+ reason: IDENTITY_RECORDS
98
175
  },
99
- 'POST /SASjsApi/auth/refresh': {
100
- implemented: true,
101
- responses: { 200: { accessToken: '', refreshToken: '' } }
176
+ 'DELETE /SASjsApi/group/{groupUid}/{userUid}': {
177
+ implemented: false,
178
+ reason: IDENTITY_RECORDS
102
179
  },
103
- 'POST /SASjsApi/auth/logout': { implemented: true },
104
- 'POST /SASLogon/authorize': {
105
- implemented: true,
106
- responses: { 200: { code: '' } }
180
+ 'DELETE /SASjsApi/group/{uid}': {
181
+ implemented: false,
182
+ reason: IDENTITY_RECORDS
107
183
  },
108
- 'GET /SASLogon/logout': {
109
- implemented: true,
110
- hostResponses: [500],
111
- responses: { 200: 'OK!' }
184
+ // Info - what the host is, and what this caller may use
185
+ 'GET /SASjsApi/info': { implemented: false, reason: HOST_CONFIG },
186
+ 'GET /SASjsApi/info/authorizedRoutes': {
187
+ implemented: false,
188
+ // the route inventory reports what the CALLER may use, so it needs an
189
+ // identity even though the rest of the Info tag needs nothing
190
+ capabilities: ['identity'],
191
+ reason: PERMISSIONS
192
+ },
193
+ 'GET /SASjsApi/info/myAuthorizedRoutes': {
194
+ implemented: false,
195
+ capabilities: ['identity'],
196
+ reason: PERMISSIONS
112
197
  },
198
+ // Permission - the rules the inventory above is derived from
199
+ 'GET /SASjsApi/permission': { implemented: false, reason: IDENTITY_RECORDS },
200
+ 'POST /SASjsApi/permission': { implemented: false, reason: IDENTITY_RECORDS },
201
+ 'PATCH /SASjsApi/permission/{uid}': {
202
+ implemented: false,
203
+ reason: IDENTITY_RECORDS
204
+ },
205
+ 'DELETE /SASjsApi/permission/{uid}': {
206
+ implemented: false,
207
+ reason: IDENTITY_RECORDS
208
+ },
209
+ // Session - the caller's own session, then the record behind a triggered one
113
210
  'GET /SASjsApi/session': {
114
211
  implemented: true,
115
212
  responses: { 200: user }
213
+ },
214
+ 'GET /SASjsApi/session/{sessionId}': {
215
+ implemented: false,
216
+ reason: SESSION_RECORD
217
+ },
218
+ 'GET /SASjsApi/session/{sessionId}/log': {
219
+ implemented: false,
220
+ reason: SESSION_RECORD
221
+ },
222
+ 'GET /SASjsApi/session/{sessionId}/output': {
223
+ implemented: false,
224
+ reason: SESSION_RECORD
225
+ },
226
+ 'GET /SASjsApi/session/{sessionId}/state': {
227
+ implemented: false,
228
+ reason: SESSION_RECORD
229
+ },
230
+ // STP - a stored program, on the runtime port
231
+ 'GET /SASjsApi/stp/execute': { implemented: false, reason: RUNTIME },
232
+ 'POST /SASjsApi/stp/execute': { implemented: false, reason: RUNTIME },
233
+ 'POST /SASjsApi/stp/trigger': { implemented: false, reason: RUNTIME },
234
+ // User - the accounts the identity store already holds for sign-in
235
+ 'GET /SASjsApi/user': { implemented: false, reason: IDENTITY_RECORDS },
236
+ 'GET /SASjsApi/user/{uid}': { implemented: false, reason: IDENTITY_RECORDS },
237
+ 'GET /SASjsApi/user/by/username/{username}': {
238
+ implemented: false,
239
+ reason: IDENTITY_RECORDS
240
+ },
241
+ 'POST /SASjsApi/user': { implemented: false, reason: IDENTITY_RECORDS },
242
+ 'PATCH /SASjsApi/user/{uid}': {
243
+ implemented: false,
244
+ reason: IDENTITY_RECORDS
245
+ },
246
+ 'PATCH /SASjsApi/user/by/username/{username}': {
247
+ implemented: false,
248
+ reason: IDENTITY_RECORDS
249
+ },
250
+ 'DELETE /SASjsApi/user/{uid}': {
251
+ implemented: false,
252
+ reason: IDENTITY_RECORDS
253
+ },
254
+ 'DELETE /SASjsApi/user/by/username/{username}': {
255
+ implemented: false,
256
+ reason: IDENTITY_RECORDS
116
257
  }
117
258
  };
118
259
  /** Every method the table may state, in the order a diff reads best. */
@@ -14,9 +14,27 @@
14
14
  */
15
15
  export declare class ApiError extends Error {
16
16
  readonly status: number;
17
+ /**
18
+ * The body the route renders instead of the bare message.
19
+ *
20
+ * Most drive routes send the message as text (`res.status(code).send(err)`),
21
+ * and that stays the default: a body here is for the routes that answer an
22
+ * envelope, which today is the two deploy routes and their
23
+ * `{status: 'failure', message}`.
24
+ */
25
+ readonly body?: unknown | undefined;
17
26
  /** Told apart by marker rather than `instanceof` - see `isApiError`. */
18
27
  readonly apiError = true;
19
- constructor(status: number, message: string);
28
+ constructor(status: number, message: string,
29
+ /**
30
+ * The body the route renders instead of the bare message.
31
+ *
32
+ * Most drive routes send the message as text (`res.status(code).send(err)`),
33
+ * and that stays the default: a body here is for the routes that answer an
34
+ * envelope, which today is the two deploy routes and their
35
+ * `{status: 'failure', message}`.
36
+ */
37
+ body?: unknown | undefined);
20
38
  }
21
39
  /**
22
40
  * `instanceof` survives here (the package targets ES2020, not ES5), but a
@@ -85,6 +103,13 @@ export interface Route extends GeneratedRoute {
85
103
  * not an absence nobody can see.
86
104
  */
87
105
  implemented: boolean;
106
+ /**
107
+ * Why this package does not implement the operation, and what it waits on.
108
+ * Present exactly when `implemented` is false: the overlay's entries are a
109
+ * decision either way, and `src/routes/index.ts` refuses to build a table with
110
+ * an operation nobody has decided about. A 501 carries it to the caller.
111
+ */
112
+ reason?: string;
88
113
  }
89
114
  /**
90
115
  * The drive, as the services see it.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -13,9 +13,19 @@
13
13
  * corpus can assert on the pair.
14
14
  */
15
15
  export class ApiError extends Error {
16
- constructor(status, message) {
16
+ constructor(status, message,
17
+ /**
18
+ * The body the route renders instead of the bare message.
19
+ *
20
+ * Most drive routes send the message as text (`res.status(code).send(err)`),
21
+ * and that stays the default: a body here is for the routes that answer an
22
+ * envelope, which today is the two deploy routes and their
23
+ * `{status: 'failure', message}`.
24
+ */
25
+ body) {
17
26
  super(message);
18
27
  this.status = status;
28
+ this.body = body;
19
29
  /** Told apart by marker rather than `instanceof` - see `isApiError`. */
20
30
  this.apiError = true;
21
31
  this.name = 'ApiError';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sasjs/api",
3
- "version": "0.4.0",
3
+ "version": "0.6.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",