@sasjs/api 0.10.0 → 0.12.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 +557 -108
- package/build/cjs/corpus/code.js +44 -7
- package/build/cjs/corpus/deploy.js +50 -0
- package/build/cjs/corpus/runner.js +6 -1
- package/build/cjs/corpus/session.js +142 -0
- package/build/cjs/drive/registry.js +38 -0
- package/build/cjs/host/dispatch.js +44 -0
- package/build/cjs/index.js +5 -1
- package/build/cjs/routes/generated.js +48 -0
- package/build/cjs/routes/overlay.js +8 -22
- package/build/cjs/session/memory-host.js +74 -0
- package/build/cjs/session/service.js +65 -0
- package/build/esm/corpus/code.d.ts.map +1 -1
- package/build/esm/corpus/code.js +44 -7
- package/build/esm/corpus/deploy.d.ts +16 -0
- package/build/esm/corpus/deploy.d.ts.map +1 -0
- package/build/esm/corpus/deploy.js +47 -0
- package/build/esm/corpus/runner.d.ts.map +1 -1
- package/build/esm/corpus/runner.js +6 -1
- package/build/esm/corpus/session.d.ts +17 -0
- package/build/esm/corpus/session.d.ts.map +1 -0
- package/build/esm/corpus/session.js +139 -0
- package/build/esm/drive/registry.d.ts +52 -0
- package/build/esm/drive/registry.d.ts.map +1 -0
- package/build/esm/drive/registry.js +33 -0
- package/build/esm/host/dispatch.d.ts +12 -0
- package/build/esm/host/dispatch.d.ts.map +1 -1
- package/build/esm/host/dispatch.js +44 -0
- package/build/esm/index.d.ts +2 -0
- package/build/esm/index.d.ts.map +1 -1
- package/build/esm/index.js +2 -0
- package/build/esm/routes/generated.d.ts.map +1 -1
- package/build/esm/routes/generated.js +48 -0
- package/build/esm/routes/overlay.d.ts.map +1 -1
- package/build/esm/routes/overlay.js +8 -22
- package/build/esm/session/memory-host.d.ts +19 -0
- package/build/esm/session/memory-host.d.ts.map +1 -0
- package/build/esm/session/memory-host.js +70 -0
- package/build/esm/session/service.d.ts +62 -0
- package/build/esm/session/service.d.ts.map +1 -0
- package/build/esm/session/service.js +61 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSessionService = void 0;
|
|
4
|
+
const types_js_1 = require("../types.js");
|
|
5
|
+
/**
|
|
6
|
+
* The four session routes, over the host's own session store.
|
|
7
|
+
*
|
|
8
|
+
* The caller passes a `Subject` and nothing else: which account that is, whether
|
|
9
|
+
* it is an administrator, and whether the session is theirs are all resolved
|
|
10
|
+
* here, against the same store the records service reads. A host never states a
|
|
11
|
+
* role.
|
|
12
|
+
*
|
|
13
|
+
* `404` covers both "no such session" and "not yours", on purpose: a session id
|
|
14
|
+
* is handed to the caller that triggered the program and is built from a
|
|
15
|
+
* timestamp plus a small random number, so it is guessable within a window, and
|
|
16
|
+
* confirming the id exists would say it was right.
|
|
17
|
+
*/
|
|
18
|
+
const createSessionService = (store, host) => {
|
|
19
|
+
/** The server's wording, so adopting it here does not become a later change. */
|
|
20
|
+
const notFound = (id) => `Session with ID '${id}' was not found.`;
|
|
21
|
+
/** The caller, and the session if they may see it - or the 404 that hides it. */
|
|
22
|
+
const visible = async (caller, id) => {
|
|
23
|
+
if (!caller)
|
|
24
|
+
throw new types_js_1.ApiError(401, 'Unauthorized');
|
|
25
|
+
const reader = await store.userById(caller.userId);
|
|
26
|
+
if (!reader || !reader.isActive)
|
|
27
|
+
throw new types_js_1.ApiError(401, 'Unauthorized');
|
|
28
|
+
const session = await host.session(id);
|
|
29
|
+
if (!session)
|
|
30
|
+
throw new types_js_1.ApiError(404, notFound(id));
|
|
31
|
+
// the uid is the key to compare; a name is the fallback a host may need
|
|
32
|
+
const owner = session.owner;
|
|
33
|
+
if (owner !== reader.uid && owner !== reader.username && !reader.isAdmin)
|
|
34
|
+
throw new types_js_1.ApiError(404, notFound(id));
|
|
35
|
+
return session;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
async summary(caller, id) {
|
|
39
|
+
const session = await visible(caller, id);
|
|
40
|
+
return session.summary;
|
|
41
|
+
},
|
|
42
|
+
async state(caller, id) {
|
|
43
|
+
await visible(caller, id);
|
|
44
|
+
const state = await host.state(id);
|
|
45
|
+
if (!state)
|
|
46
|
+
throw new types_js_1.ApiError(404, notFound(id));
|
|
47
|
+
return state;
|
|
48
|
+
},
|
|
49
|
+
async log(caller, id) {
|
|
50
|
+
await visible(caller, id);
|
|
51
|
+
const log = await host.log(id);
|
|
52
|
+
if (log === undefined)
|
|
53
|
+
throw new types_js_1.ApiError(404, notFound(id));
|
|
54
|
+
return log;
|
|
55
|
+
},
|
|
56
|
+
async output(caller, id) {
|
|
57
|
+
await visible(caller, id);
|
|
58
|
+
const output = await host.output(id);
|
|
59
|
+
if (output === undefined)
|
|
60
|
+
throw new types_js_1.ApiError(404, notFound(id));
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
exports.createSessionService = createSessionService;
|
|
@@ -1 +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,
|
|
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,EAoFnC,CAAA"}
|
package/build/esm/corpus/code.js
CHANGED
|
@@ -35,13 +35,50 @@ export const CODE_CORPUS = [
|
|
|
35
35
|
]
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
{
|
|
39
|
+
id: 'code-lint-unauthenticated',
|
|
40
|
+
route: 'POST /SASjsApi/code/lint',
|
|
41
|
+
description: 'refuses a caller with no token',
|
|
42
|
+
request: { body: { code: 'data _null_; run;' } },
|
|
43
|
+
expect: { status: 401, error: 'Unauthorized' }
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'code-lint-non-string',
|
|
47
|
+
route: 'POST /SASjsApi/code/lint',
|
|
48
|
+
description: 'refuses a code value that is not a string, rather than coercing it',
|
|
49
|
+
request: {
|
|
50
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
51
|
+
body: { code: 123 }
|
|
52
|
+
},
|
|
53
|
+
expect: { status: 400, error: '"code" must be a string' }
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
id: 'code-lint-empty',
|
|
57
|
+
route: 'POST /SASjsApi/code/lint',
|
|
58
|
+
description: 'refuses an empty buffer, which is not the same as a missing one',
|
|
59
|
+
request: {
|
|
60
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
61
|
+
body: { code: '' }
|
|
62
|
+
},
|
|
63
|
+
expect: { status: 400, error: '"code" is not allowed to be empty' }
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'code-format-unauthenticated',
|
|
67
|
+
route: 'POST /SASjsApi/code/format',
|
|
68
|
+
description: 'refuses a caller with no token',
|
|
69
|
+
request: { body: { code: 'data _null_; run;' } },
|
|
70
|
+
expect: { status: 401, error: 'Unauthorized' }
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'code-format-non-string',
|
|
74
|
+
route: 'POST /SASjsApi/code/format',
|
|
75
|
+
description: 'refuses a code value that is not a string',
|
|
76
|
+
request: {
|
|
77
|
+
subject: { userId: 'user-admin', clientId: 'web_app' },
|
|
78
|
+
body: { code: 123 }
|
|
79
|
+
},
|
|
80
|
+
expect: { status: 400, error: '"code" must be a string' }
|
|
81
|
+
},
|
|
45
82
|
{
|
|
46
83
|
id: 'code-format-adds-the-header',
|
|
47
84
|
route: 'POST /SASjsApi/code/format',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The deploy routes' conformance corpus.
|
|
3
|
+
*
|
|
4
|
+
* `DELETE /SASjsApi/drive/deploy` documents three answers - `200`, `400` and
|
|
5
|
+
* `404` - and each has a case here, which is what
|
|
6
|
+
* `spec/corpus-parity.spec.ts` enforces in both directions.
|
|
7
|
+
*
|
|
8
|
+
* **The folder is the identity.** An app is one thing to deploy and one thing to
|
|
9
|
+
* remove; its name is a label on it. The document requires both body values, so a
|
|
10
|
+
* caller must supply both, but only the folder decides what is deleted - and the
|
|
11
|
+
* case below with a stale name and the right folder pins exactly that, because a
|
|
12
|
+
* name-keyed lookup would answer 404 there and leave the app in place.
|
|
13
|
+
*/
|
|
14
|
+
import type { CorpusCase } from './drive.js';
|
|
15
|
+
export declare const DEPLOY_CORPUS: CorpusCase[];
|
|
16
|
+
//# sourceMappingURL=deploy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/corpus/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAI5C,eAAO,MAAM,aAAa,EAAE,UAAU,EA6CrC,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const SUBJECT = { userId: 'user-admin', clientId: 'web_app' };
|
|
2
|
+
export const DEPLOY_CORPUS = [
|
|
3
|
+
{
|
|
4
|
+
id: 'deploy-unpublish',
|
|
5
|
+
route: 'DELETE /SASjsApi/drive/deploy',
|
|
6
|
+
description: 'removes a published app stream and answers the status',
|
|
7
|
+
request: {
|
|
8
|
+
subject: SUBJECT,
|
|
9
|
+
body: { streamServiceName: 'myApp', appLoc: '/Public/myApp' }
|
|
10
|
+
},
|
|
11
|
+
expect: { status: 200, body: { status: 'success' } }
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'deploy-unpublish-other-name',
|
|
15
|
+
route: 'DELETE /SASjsApi/drive/deploy',
|
|
16
|
+
description: 'removes the app by its folder when the name has been renamed',
|
|
17
|
+
request: {
|
|
18
|
+
subject: SUBJECT,
|
|
19
|
+
body: { streamServiceName: 'renamed-app', appLoc: '/Public/myApp' }
|
|
20
|
+
},
|
|
21
|
+
expect: { status: 200, body: { status: 'success' } }
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 'deploy-unpublish-no-stream-name',
|
|
25
|
+
route: 'DELETE /SASjsApi/drive/deploy',
|
|
26
|
+
description: 'refuses a body that does not say which stream to remove',
|
|
27
|
+
request: { subject: SUBJECT, body: { appLoc: '/Public/myApp' } },
|
|
28
|
+
expect: { status: 400, error: '"streamServiceName" is required' }
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'deploy-unpublish-no-app-loc',
|
|
32
|
+
route: 'DELETE /SASjsApi/drive/deploy',
|
|
33
|
+
description: 'refuses a body that does not say which folder to remove',
|
|
34
|
+
request: { subject: SUBJECT, body: { streamServiceName: 'myApp' } },
|
|
35
|
+
expect: { status: 400, error: '"appLoc" is required' }
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'deploy-unpublish-unknown-folder',
|
|
39
|
+
route: 'DELETE /SASjsApi/drive/deploy',
|
|
40
|
+
description: 'answers 404 for a folder nothing is published at',
|
|
41
|
+
request: {
|
|
42
|
+
subject: SUBJECT,
|
|
43
|
+
body: { streamServiceName: 'myApp', appLoc: '/Public/not-an-app' }
|
|
44
|
+
},
|
|
45
|
+
expect: { status: 404, error: 'No such app stream entry.' }
|
|
46
|
+
}
|
|
47
|
+
];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/corpus/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../src/corpus/runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAYnD,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CAAA;IAC9B,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,UAAU,KAAG,QAoBlD,CAAA;AAkBD;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAClB,UAAU,UAAU,EACpB,WAAU,QAAgC,KACzC,OAAO,CAAC,UAAU,CAepB,CAAA"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { dispatch } from '../host/dispatch.js';
|
|
2
2
|
import { createDriveService } from '../drive/service.js';
|
|
3
3
|
import { MemoryDriveStore } from '../drive/memory-store.js';
|
|
4
|
+
import { MemoryAppStreamRegistry } from '../drive/registry.js';
|
|
4
5
|
import { createIdentityService } from '../identity/service.js';
|
|
5
6
|
import { createRecordService } from '../identity/records.js';
|
|
7
|
+
import { createSessionService } from '../session/service.js';
|
|
8
|
+
import { MemorySessionHost } from '../session/memory-host.js';
|
|
6
9
|
import { MemoryIdentityStore } from '../identity/memory-store.js';
|
|
7
10
|
import { DEFAULT_IDENTITY } from '../identity/fixture.js';
|
|
8
11
|
import { isApiError } from '../types.js';
|
|
@@ -23,7 +26,9 @@ export const servicesFor = (testCase) => {
|
|
|
23
26
|
? new RefusingDriveStore(testCase.seed || {})
|
|
24
27
|
: new MemoryDriveStore(testCase.seed || {})),
|
|
25
28
|
identity: createIdentityService(identity),
|
|
26
|
-
records: createRecordService(identity)
|
|
29
|
+
records: createRecordService(identity),
|
|
30
|
+
registry: new MemoryAppStreamRegistry(),
|
|
31
|
+
session: createSessionService(identity, new MemorySessionHost())
|
|
27
32
|
};
|
|
28
33
|
};
|
|
29
34
|
/**
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session routes' conformance corpus.
|
|
3
|
+
*
|
|
4
|
+
* Every status the document states for these four routes has a case, in both
|
|
5
|
+
* directions - that is what `spec/corpus-parity.spec.ts` enforces. The refusals
|
|
6
|
+
* arrived here only after sasjs/server#574 documented them: until then the
|
|
7
|
+
* package answered a 401 and a 404 that the document did not state, and a case
|
|
8
|
+
* pinning one would have been called drift.
|
|
9
|
+
*
|
|
10
|
+
* `404` covers an unknown session and someone else's alike, on purpose - the id
|
|
11
|
+
* is guessable within a window, so confirming that it exists would say the id
|
|
12
|
+
* was right. The cases below keep the two apart so a change that starts
|
|
13
|
+
* answering `403` for a foreign session is visible.
|
|
14
|
+
*/
|
|
15
|
+
import type { CorpusCase } from './drive.js';
|
|
16
|
+
export declare const SESSION_CORPUS: CorpusCase[];
|
|
17
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/corpus/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAQ5C,eAAO,MAAM,cAAc,EAAE,UAAU,EAyItC,CAAA"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const ADMIN = { userId: 'user-admin', clientId: 'web_app' };
|
|
2
|
+
const RESTRICTED = { userId: 'user-restricted', clientId: 'web_app' };
|
|
3
|
+
const notFound = (id) => `Session with ID '${id}' was not found.`;
|
|
4
|
+
const UNAUTHORIZED = 'Unauthorized';
|
|
5
|
+
export const SESSION_CORPUS = [
|
|
6
|
+
// ---------------------------------------------------------------- summary
|
|
7
|
+
{
|
|
8
|
+
id: 'session-summary-own',
|
|
9
|
+
route: 'GET /SASjsApi/session/{sessionId}',
|
|
10
|
+
description: 'answers the owner the summary of their own session',
|
|
11
|
+
request: { subject: ADMIN, params: { sessionId: 'sess-admin' } },
|
|
12
|
+
expect: {
|
|
13
|
+
status: 200,
|
|
14
|
+
body: {
|
|
15
|
+
sessionId: 'sess-admin',
|
|
16
|
+
state: 'completed',
|
|
17
|
+
pid: 4242,
|
|
18
|
+
creationTimeStamp: '2026-11-07T09:00:00.000Z',
|
|
19
|
+
deathTimeStamp: '2026-11-07T09:00:04.000Z',
|
|
20
|
+
expiresAfterMins: 60
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'session-summary-as-admin',
|
|
26
|
+
route: 'GET /SASjsApi/session/{sessionId}',
|
|
27
|
+
description: "answers an administrator another account's session",
|
|
28
|
+
request: { subject: ADMIN, params: { sessionId: 'sess-restricted' } },
|
|
29
|
+
expect: {
|
|
30
|
+
status: 200,
|
|
31
|
+
body: {
|
|
32
|
+
sessionId: 'sess-restricted',
|
|
33
|
+
state: 'running',
|
|
34
|
+
pid: 4243,
|
|
35
|
+
creationTimeStamp: '2026-11-07T09:05:00.000Z'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'session-summary-unauthenticated',
|
|
41
|
+
route: 'GET /SASjsApi/session/{sessionId}',
|
|
42
|
+
description: 'refuses a caller with no token',
|
|
43
|
+
request: { params: { sessionId: 'sess-admin' } },
|
|
44
|
+
expect: { status: 401, error: UNAUTHORIZED }
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'session-summary-foreign',
|
|
48
|
+
route: 'GET /SASjsApi/session/{sessionId}',
|
|
49
|
+
description: "answers 404 for another account's session, not 403",
|
|
50
|
+
request: { subject: RESTRICTED, params: { sessionId: 'sess-admin' } },
|
|
51
|
+
expect: { status: 404, error: notFound('sess-admin') }
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'session-summary-unknown',
|
|
55
|
+
route: 'GET /SASjsApi/session/{sessionId}',
|
|
56
|
+
description: 'answers the same 404 for a session that does not exist',
|
|
57
|
+
request: { subject: ADMIN, params: { sessionId: 'sess-nope' } },
|
|
58
|
+
expect: { status: 404, error: notFound('sess-nope') }
|
|
59
|
+
},
|
|
60
|
+
// -------------------------------------------------------------------- log
|
|
61
|
+
{
|
|
62
|
+
id: 'session-log-own',
|
|
63
|
+
route: 'GET /SASjsApi/session/{sessionId}/log',
|
|
64
|
+
description: 'answers the log this session wrote',
|
|
65
|
+
request: { subject: ADMIN, params: { sessionId: 'sess-admin' } },
|
|
66
|
+
expect: {
|
|
67
|
+
status: 200,
|
|
68
|
+
contents: 'NOTE: session started\nNOTE: session completed\n'
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'session-log-nothing-written',
|
|
73
|
+
route: 'GET /SASjsApi/session/{sessionId}/log',
|
|
74
|
+
description: 'answers an empty body for a session that has written nothing',
|
|
75
|
+
request: { subject: ADMIN, params: { sessionId: 'sess-silent' } },
|
|
76
|
+
expect: { status: 200, contents: '' }
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'session-log-unauthenticated',
|
|
80
|
+
route: 'GET /SASjsApi/session/{sessionId}/log',
|
|
81
|
+
description: 'refuses a caller with no token',
|
|
82
|
+
request: { params: { sessionId: 'sess-admin' } },
|
|
83
|
+
expect: { status: 401, error: UNAUTHORIZED }
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'session-log-foreign',
|
|
87
|
+
route: 'GET /SASjsApi/session/{sessionId}/log',
|
|
88
|
+
description: "will not read another account's log",
|
|
89
|
+
request: { subject: RESTRICTED, params: { sessionId: 'sess-admin' } },
|
|
90
|
+
expect: { status: 404, error: notFound('sess-admin') }
|
|
91
|
+
},
|
|
92
|
+
// ----------------------------------------------------------------- output
|
|
93
|
+
{
|
|
94
|
+
id: 'session-output-own',
|
|
95
|
+
route: 'GET /SASjsApi/session/{sessionId}/output',
|
|
96
|
+
description: 'answers the webout this session produced',
|
|
97
|
+
request: { subject: ADMIN, params: { sessionId: 'sess-admin' } },
|
|
98
|
+
expect: { status: 200, contents: '<html><body>42</body></html>' }
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'session-output-unauthenticated',
|
|
102
|
+
route: 'GET /SASjsApi/session/{sessionId}/output',
|
|
103
|
+
description: 'refuses a caller with no token',
|
|
104
|
+
request: { params: { sessionId: 'sess-admin' } },
|
|
105
|
+
expect: { status: 401, error: UNAUTHORIZED }
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'session-output-foreign',
|
|
109
|
+
route: 'GET /SASjsApi/session/{sessionId}/output',
|
|
110
|
+
description: "will not read another account's webout",
|
|
111
|
+
request: { subject: RESTRICTED, params: { sessionId: 'sess-admin' } },
|
|
112
|
+
expect: { status: 404, error: notFound('sess-admin') }
|
|
113
|
+
},
|
|
114
|
+
// ------------------------------------------------------------------ state
|
|
115
|
+
{
|
|
116
|
+
id: 'session-state-own',
|
|
117
|
+
route: 'GET /SASjsApi/session/{sessionId}/state',
|
|
118
|
+
description: "answers the run time's state for this session",
|
|
119
|
+
request: { subject: RESTRICTED, params: { sessionId: 'sess-restricted' } },
|
|
120
|
+
expect: {
|
|
121
|
+
status: 200,
|
|
122
|
+
body: { state: 'running', pid: 4243, lineCount: 3 }
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'session-state-unauthenticated',
|
|
127
|
+
route: 'GET /SASjsApi/session/{sessionId}/state',
|
|
128
|
+
description: 'refuses a caller with no token',
|
|
129
|
+
request: { params: { sessionId: 'sess-restricted' } },
|
|
130
|
+
expect: { status: 401, error: UNAUTHORIZED }
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'session-state-foreign',
|
|
134
|
+
route: 'GET /SASjsApi/session/{sessionId}/state',
|
|
135
|
+
description: "will not report another account's session state",
|
|
136
|
+
request: { subject: RESTRICTED, params: { sessionId: 'sess-admin' } },
|
|
137
|
+
expect: { status: 404, error: notFound('sess-admin') }
|
|
138
|
+
}
|
|
139
|
+
];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app streams a host has published, as the API needs to unpublish one.
|
|
3
|
+
*
|
|
4
|
+
* A published stream is the host's own record - a directory convention, a
|
|
5
|
+
* registry table, whatever it uses to know that `/Public/myApp` is served at
|
|
6
|
+
* `/Public/myApp/index.html`. The package cannot invent it, so the route takes
|
|
7
|
+
* this port and answers 501 without it, the same way the session routes do.
|
|
8
|
+
*/
|
|
9
|
+
export interface AppStreamRegistry {
|
|
10
|
+
/**
|
|
11
|
+
* Removes the app published at `appLoc`.
|
|
12
|
+
*
|
|
13
|
+
* **The folder is the identity.** An app is one thing to deploy and one thing
|
|
14
|
+
* to delete, and what it is called is a label on it: two apps can share a
|
|
15
|
+
* folder only as a special case, and they are still one thing to load or
|
|
16
|
+
* remove. So the lookup is by folder, and a name that no longer matches the
|
|
17
|
+
* one recorded does not turn a correct deletion into a `404`.
|
|
18
|
+
*
|
|
19
|
+
* `false` when nothing is published at that folder, which the route reports as
|
|
20
|
+
* a 404 - a delete that silently succeeded on a folder nobody deployed would
|
|
21
|
+
* hide a caller's typo.
|
|
22
|
+
*/
|
|
23
|
+
unpublish(appLoc: string): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
export interface UnpublishBody {
|
|
26
|
+
streamServiceName?: string;
|
|
27
|
+
appLoc?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `DELETE /SASjsApi/drive/deploy`.
|
|
31
|
+
*
|
|
32
|
+
* The document requires both body values, so both are checked - a caller that
|
|
33
|
+
* names a stream but not the folder has not said what to remove. Only the folder
|
|
34
|
+
* reaches the registry, because that is what identifies the app.
|
|
35
|
+
*
|
|
36
|
+
* The checks are here rather than in the transport because this is the one
|
|
37
|
+
* implementation of the operation: a host calling it directly gets the same
|
|
38
|
+
* refusals in the same order, and with the same messages the document states.
|
|
39
|
+
*/
|
|
40
|
+
export declare const unpublishAppStream: (registry: AppStreamRegistry, body: UnpublishBody | undefined) => Promise<{
|
|
41
|
+
status: "success";
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* The registry the corpus and the package's own tests read: one published app,
|
|
45
|
+
* identified by its folder alone.
|
|
46
|
+
*/
|
|
47
|
+
export declare class MemoryAppStreamRegistry implements AppStreamRegistry {
|
|
48
|
+
private readonly folders;
|
|
49
|
+
constructor(folders?: string[]);
|
|
50
|
+
unpublish(appLoc: string): Promise<boolean>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/drive/registry.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5C;AAED,MAAM,WAAW,aAAa;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAC7B,UAAU,iBAAiB,EAC3B,MAAM,aAAa,GAAG,SAAS,KAC9B,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAU/B,CAAA;AAED;;;GAGG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB;IAC/D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;gBAEzB,OAAO,GAAE,MAAM,EAAsB;IAI3C,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGlD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ApiError } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* `DELETE /SASjsApi/drive/deploy`.
|
|
4
|
+
*
|
|
5
|
+
* The document requires both body values, so both are checked - a caller that
|
|
6
|
+
* names a stream but not the folder has not said what to remove. Only the folder
|
|
7
|
+
* reaches the registry, because that is what identifies the app.
|
|
8
|
+
*
|
|
9
|
+
* The checks are here rather than in the transport because this is the one
|
|
10
|
+
* implementation of the operation: a host calling it directly gets the same
|
|
11
|
+
* refusals in the same order, and with the same messages the document states.
|
|
12
|
+
*/
|
|
13
|
+
export const unpublishAppStream = async (registry, body) => {
|
|
14
|
+
if (!body?.streamServiceName)
|
|
15
|
+
throw new ApiError(400, '"streamServiceName" is required');
|
|
16
|
+
if (!body.appLoc)
|
|
17
|
+
throw new ApiError(400, '"appLoc" is required');
|
|
18
|
+
if (!(await registry.unpublish(body.appLoc)))
|
|
19
|
+
throw new ApiError(404, 'No such app stream entry.');
|
|
20
|
+
return { status: 'success' };
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The registry the corpus and the package's own tests read: one published app,
|
|
24
|
+
* identified by its folder alone.
|
|
25
|
+
*/
|
|
26
|
+
export class MemoryAppStreamRegistry {
|
|
27
|
+
constructor(folders = ['/Public/myApp']) {
|
|
28
|
+
this.folders = new Set(folders);
|
|
29
|
+
}
|
|
30
|
+
async unpublish(appLoc) {
|
|
31
|
+
return this.folders.delete(appLoc);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
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 { SessionService } from '../session/service.js';
|
|
5
|
+
import type { AppStreamRegistry } from '../drive/registry.js';
|
|
4
6
|
import type { CodeService } from '../code/service.js';
|
|
5
7
|
import type { Subject } from '../identity/types.js';
|
|
6
8
|
export interface ApiRequest {
|
|
@@ -45,6 +47,16 @@ export interface Services {
|
|
|
45
47
|
identity?: IdentityService;
|
|
46
48
|
/** Users, groups, permission rules and clients. Absent, the record routes answer 501. */
|
|
47
49
|
records?: RecordService;
|
|
50
|
+
/**
|
|
51
|
+
* A triggered session's record, log, output and state. A host without one
|
|
52
|
+
* answers 501 for these four, naming the capability.
|
|
53
|
+
*/
|
|
54
|
+
session?: SessionService;
|
|
55
|
+
/**
|
|
56
|
+
* The app streams a host has published. Absent, `DELETE /drive/deploy`
|
|
57
|
+
* answers 501 - the package cannot know a host's stream registry.
|
|
58
|
+
*/
|
|
59
|
+
registry?: AppStreamRegistry;
|
|
48
60
|
}
|
|
49
61
|
export declare const dispatch: (services: Services, routeId: string, request?: ApiRequest) => Promise<ApiResponse>;
|
|
50
62
|
/** Whether a route is one `dispatch` can answer today. */
|
|
@@ -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,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;
|
|
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,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAE7D,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;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB;;;OAGG;IACH,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CAC7B;AA2CD,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,UAAS,UAAe,KACvB,OAAO,CAAC,WAAW,CAycrB,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,MAAM,EAiDlC,CAAA"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { routeById } from '../routes/index.js';
|
|
2
|
+
import { unpublishAppStream } from '../drive/registry.js';
|
|
2
3
|
import { ApiError } from '../types.js';
|
|
3
4
|
import { DEPLOY_SUCCESS } from '../drive/deploy.js';
|
|
4
5
|
/** The defaults sasjs/server's Client schema applies to an omitted lifetime. */
|
|
@@ -71,6 +72,12 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
71
72
|
const body = request.body || {};
|
|
72
73
|
const params = request.params || {};
|
|
73
74
|
/** The record service, or the 501 a host without one answers. */
|
|
75
|
+
/** The host's sessions, or the 501 that names the missing capability. */
|
|
76
|
+
const sessionRequired = () => {
|
|
77
|
+
if (!services.session)
|
|
78
|
+
throw new ApiError(501, `This host has no session service, so it cannot answer ${routeId}.`);
|
|
79
|
+
return services.session;
|
|
80
|
+
};
|
|
74
81
|
const required = () => {
|
|
75
82
|
if (!records)
|
|
76
83
|
throw new ApiError(501, `This host has no record service, so it cannot answer ${routeId}.`);
|
|
@@ -166,8 +173,40 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
166
173
|
await drive.deploy(String(body.appLoc ?? ''), body.fileTree);
|
|
167
174
|
return { status: 200, body: DEPLOY_SUCCESS };
|
|
168
175
|
}
|
|
176
|
+
case 'DELETE /SASjsApi/drive/deploy':
|
|
177
|
+
if (!services.registry)
|
|
178
|
+
throw new ApiError(501, `This host has no app stream registry, so it cannot answer ${routeId}.`);
|
|
179
|
+
return {
|
|
180
|
+
status: 200,
|
|
181
|
+
body: await unpublishAppStream(services.registry, body)
|
|
182
|
+
};
|
|
169
183
|
case 'GET /SASjsApi/drive/filetree':
|
|
170
184
|
return { status: 200, body: await drive.tree(query._folderPath) };
|
|
185
|
+
// ------------------------------------------------------------- sessions
|
|
186
|
+
case 'GET /SASjsApi/session/{sessionId}': {
|
|
187
|
+
return {
|
|
188
|
+
status: 200,
|
|
189
|
+
body: await sessionRequired().summary(await caller(), String(params.sessionId ?? ''))
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
case 'GET /SASjsApi/session/{sessionId}/log': {
|
|
193
|
+
return {
|
|
194
|
+
status: 200,
|
|
195
|
+
contents: await sessionRequired().log(await caller(), String(params.sessionId ?? ''))
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
case 'GET /SASjsApi/session/{sessionId}/output': {
|
|
199
|
+
return {
|
|
200
|
+
status: 200,
|
|
201
|
+
contents: await sessionRequired().output(await caller(), String(params.sessionId ?? ''))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
case 'GET /SASjsApi/session/{sessionId}/state': {
|
|
205
|
+
return {
|
|
206
|
+
status: 200,
|
|
207
|
+
body: await sessionRequired().state(await caller(), String(params.sessionId ?? ''))
|
|
208
|
+
};
|
|
209
|
+
}
|
|
171
210
|
// ------------------------------------------------------------- records
|
|
172
211
|
//
|
|
173
212
|
// The administration routes: users, groups, permission rules, clients and the
|
|
@@ -353,6 +392,11 @@ export const dispatch = async (services, routeId, request = {}) => {
|
|
|
353
392
|
};
|
|
354
393
|
/** Whether a route is one `dispatch` can answer today. */
|
|
355
394
|
export const HANDLED_ROUTES = [
|
|
395
|
+
'DELETE /SASjsApi/drive/deploy',
|
|
396
|
+
'GET /SASjsApi/session/{sessionId}',
|
|
397
|
+
'GET /SASjsApi/session/{sessionId}/log',
|
|
398
|
+
'GET /SASjsApi/session/{sessionId}/output',
|
|
399
|
+
'GET /SASjsApi/session/{sessionId}/state',
|
|
356
400
|
'POST /SASjsApi/code/lint',
|
|
357
401
|
'POST /SASjsApi/code/format',
|
|
358
402
|
'GET /SASjsApi/info/authorizedRoutes',
|
package/build/esm/index.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export type { IdentityFixture, IdentityFixtureClient, IdentityFixtureCode, Ident
|
|
|
38
38
|
export type { IdentityClient, IdentityClientRecord, IdentityGroupRecord, IdentityPermissionRecord, IdentityStore, IdentityUser, IdentityUserRecord, NewGroup, NewPermission, NewUser, Subject, TokenPair, UserChange } from './identity/types.js';
|
|
39
39
|
export { DRIVE_CORPUS } from './corpus/drive.js';
|
|
40
40
|
export { IDENTITY_CORPUS } from './corpus/identity.js';
|
|
41
|
+
export { DEPLOY_CORPUS } from './corpus/deploy.js';
|
|
42
|
+
export { SESSION_CORPUS } from './corpus/session.js';
|
|
41
43
|
export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
|
|
42
44
|
export type { CorpusCase } from './corpus/drive.js';
|
|
43
45
|
export { runCase, servicesFor } from './corpus/runner.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,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"}
|
|
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,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,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
|
@@ -30,5 +30,7 @@ export { MemoryIdentityStore } from './identity/memory-store.js';
|
|
|
30
30
|
export { DEFAULT_IDENTITY, IDENTITY_FORMAT, parseIdentity } from './identity/fixture.js';
|
|
31
31
|
export { DRIVE_CORPUS } from './corpus/drive.js';
|
|
32
32
|
export { IDENTITY_CORPUS } from './corpus/identity.js';
|
|
33
|
+
export { DEPLOY_CORPUS } from './corpus/deploy.js';
|
|
34
|
+
export { SESSION_CORPUS } from './corpus/session.js';
|
|
33
35
|
export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
|
|
34
36
|
export { runCase, servicesFor } from './corpus/runner.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../../src/routes/generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,eAAO,MAAM,gBAAgB,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"generated.d.ts","sourceRoot":"","sources":["../../../src/routes/generated.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAw6D5C,CAAA"}
|