@sasjs/api 0.9.0 → 0.11.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 (45) hide show
  1. package/build/browser.js +457 -102
  2. package/build/cjs/code/index.js +12 -0
  3. package/build/cjs/code/service.js +57 -0
  4. package/build/cjs/corpus/code.js +99 -0
  5. package/build/cjs/corpus/runner.js +4 -1
  6. package/build/cjs/corpus/session.js +73 -0
  7. package/build/cjs/host/dispatch.js +65 -3
  8. package/build/cjs/index.js +5 -1
  9. package/build/cjs/routes/generated.js +16 -0
  10. package/build/cjs/routes/overlay.js +15 -20
  11. package/build/cjs/session/memory-host.js +74 -0
  12. package/build/cjs/session/service.js +65 -0
  13. package/build/esm/code/index.d.ts +10 -0
  14. package/build/esm/code/index.d.ts.map +1 -0
  15. package/build/esm/code/index.js +8 -0
  16. package/build/esm/code/service.d.ts +40 -0
  17. package/build/esm/code/service.d.ts.map +1 -0
  18. package/build/esm/code/service.js +53 -0
  19. package/build/esm/corpus/code.d.ts +17 -0
  20. package/build/esm/corpus/code.d.ts.map +1 -0
  21. package/build/esm/corpus/code.js +96 -0
  22. package/build/esm/corpus/drive.d.ts +9 -0
  23. package/build/esm/corpus/drive.d.ts.map +1 -1
  24. package/build/esm/corpus/runner.d.ts.map +1 -1
  25. package/build/esm/corpus/runner.js +4 -1
  26. package/build/esm/corpus/session.d.ts +16 -0
  27. package/build/esm/corpus/session.d.ts.map +1 -0
  28. package/build/esm/corpus/session.js +70 -0
  29. package/build/esm/host/dispatch.d.ts +9 -0
  30. package/build/esm/host/dispatch.d.ts.map +1 -1
  31. package/build/esm/host/dispatch.js +65 -3
  32. package/build/esm/index.d.ts +2 -0
  33. package/build/esm/index.d.ts.map +1 -1
  34. package/build/esm/index.js +2 -0
  35. package/build/esm/routes/generated.d.ts.map +1 -1
  36. package/build/esm/routes/generated.js +16 -0
  37. package/build/esm/routes/overlay.d.ts.map +1 -1
  38. package/build/esm/routes/overlay.js +15 -20
  39. package/build/esm/session/memory-host.d.ts +19 -0
  40. package/build/esm/session/memory-host.d.ts.map +1 -0
  41. package/build/esm/session/memory-host.js +70 -0
  42. package/build/esm/session/service.d.ts +62 -0
  43. package/build/esm/session/service.d.ts.map +1 -0
  44. package/build/esm/session/service.js +61 -0
  45. package/package.json +9 -1
@@ -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,EAoFnC,CAAA"}
@@ -0,0 +1,96 @@
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
+ {
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
+ },
82
+ {
83
+ id: 'code-format-adds-the-header',
84
+ route: 'POST /SASjsApi/code/format',
85
+ description: 'answers the formatted buffer, header block included',
86
+ request: {
87
+ // the routes are permissioned; the runner resolves the fixture's admin
88
+ subject: { userId: 'user-admin', clientId: 'web_app' },
89
+ body: { code: 'data _null_;\n x = 1;\nrun;\n' }
90
+ },
91
+ expect: {
92
+ status: 200,
93
+ body: '/**\n @file\n @brief <Your brief here>\n\n <h4> SAS Macros </h4>\n\n**/\ndata _null_;\n x = 1;\nrun;\n'
94
+ }
95
+ }
96
+ ];
@@ -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;KAChB,CAAA;CACF;AAmBD,eAAO,MAAM,YAAY,EAAE,UAAU,EAqapC,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 +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;AASnD,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,QAkBlD,CAAA;AAkBD;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAClB,UAAU,UAAU,EACpB,WAAU,QAAgC,KACzC,OAAO,CAAC,UAAU,CAepB,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;AAWnD,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,QAmBlD,CAAA;AAkBD;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAClB,UAAU,UAAU,EACpB,WAAU,QAAgC,KACzC,OAAO,CAAC,UAAU,CAepB,CAAA"}
@@ -3,6 +3,8 @@ import { createDriveService } from '../drive/service.js';
3
3
  import { MemoryDriveStore } from '../drive/memory-store.js';
4
4
  import { createIdentityService } from '../identity/service.js';
5
5
  import { createRecordService } from '../identity/records.js';
6
+ import { createSessionService } from '../session/service.js';
7
+ import { MemorySessionHost } from '../session/memory-host.js';
6
8
  import { MemoryIdentityStore } from '../identity/memory-store.js';
7
9
  import { DEFAULT_IDENTITY } from '../identity/fixture.js';
8
10
  import { isApiError } from '../types.js';
@@ -23,7 +25,8 @@ export const servicesFor = (testCase) => {
23
25
  ? new RefusingDriveStore(testCase.seed || {})
24
26
  : new MemoryDriveStore(testCase.seed || {})),
25
27
  identity: createIdentityService(identity),
26
- records: createRecordService(identity)
28
+ records: createRecordService(identity),
29
+ session: createSessionService(identity, new MemorySessionHost())
27
30
  };
28
31
  };
29
32
  /**
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The session routes' conformance corpus.
3
+ *
4
+ * Only the `200` answers are here, and that is a statement about the document
5
+ * rather than about the routes: sasjs/server documents these four with a `200`
6
+ * and nothing else, so `spec/corpus-parity.spec.ts` refuses a case that pins a
7
+ * refusal. Their `401` and the `404` that covers both "no such session" and
8
+ * "not yours" are pinned in `spec/session.spec.ts` instead, which the parity
9
+ * gate does not govern.
10
+ *
11
+ * The routes are in `documentRefusals.baseline.json` upstream; when they carry
12
+ * their `@Response(401)` and `@Response(404)`, those cases move here.
13
+ */
14
+ import type { CorpusCase } from './drive.js';
15
+ export declare const SESSION_CORPUS: CorpusCase[];
16
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/corpus/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAK5C,eAAO,MAAM,cAAc,EAAE,UAAU,EAmEtC,CAAA"}
@@ -0,0 +1,70 @@
1
+ const ADMIN = { userId: 'user-admin', clientId: 'web_app' };
2
+ const RESTRICTED = { userId: 'user-restricted', clientId: 'web_app' };
3
+ export const SESSION_CORPUS = [
4
+ {
5
+ id: 'session-summary-own',
6
+ route: 'GET /SASjsApi/session/{sessionId}',
7
+ description: 'answers the owner the summary of their own session',
8
+ request: { subject: ADMIN, params: { sessionId: 'sess-admin' } },
9
+ expect: {
10
+ status: 200,
11
+ body: {
12
+ sessionId: 'sess-admin',
13
+ state: 'completed',
14
+ pid: 4242,
15
+ creationTimeStamp: '2026-11-07T09:00:00.000Z',
16
+ deathTimeStamp: '2026-11-07T09:00:04.000Z',
17
+ expiresAfterMins: 60
18
+ }
19
+ }
20
+ },
21
+ {
22
+ id: 'session-summary-as-admin',
23
+ route: 'GET /SASjsApi/session/{sessionId}',
24
+ description: "answers an administrator another account's session",
25
+ request: { subject: ADMIN, params: { sessionId: 'sess-restricted' } },
26
+ expect: {
27
+ status: 200,
28
+ body: {
29
+ sessionId: 'sess-restricted',
30
+ state: 'running',
31
+ pid: 4243,
32
+ creationTimeStamp: '2026-11-07T09:05:00.000Z'
33
+ }
34
+ }
35
+ },
36
+ {
37
+ id: 'session-log-own',
38
+ route: 'GET /SASjsApi/session/{sessionId}/log',
39
+ description: 'answers the log this session wrote',
40
+ request: { subject: ADMIN, params: { sessionId: 'sess-admin' } },
41
+ expect: {
42
+ status: 200,
43
+ contents: 'NOTE: session started\nNOTE: session completed\n'
44
+ }
45
+ },
46
+ {
47
+ id: 'session-log-nothing-written',
48
+ route: 'GET /SASjsApi/session/{sessionId}/log',
49
+ description: 'answers an empty body for a session that has written nothing',
50
+ request: { subject: ADMIN, params: { sessionId: 'sess-silent' } },
51
+ expect: { status: 200, contents: '' }
52
+ },
53
+ {
54
+ id: 'session-output-own',
55
+ route: 'GET /SASjsApi/session/{sessionId}/output',
56
+ description: 'answers the webout this session produced',
57
+ request: { subject: ADMIN, params: { sessionId: 'sess-admin' } },
58
+ expect: { status: 200, contents: '<html><body>42</body></html>' }
59
+ },
60
+ {
61
+ id: 'session-state-own',
62
+ route: 'GET /SASjsApi/session/{sessionId}/state',
63
+ description: "answers the run time's state for this session",
64
+ request: { subject: RESTRICTED, params: { sessionId: 'sess-restricted' } },
65
+ expect: {
66
+ status: 200,
67
+ body: { state: 'running', pid: 4243, lineCount: 3 }
68
+ }
69
+ }
70
+ ];
@@ -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 { CodeService } from '../code/service.js';
4
6
  import type { Subject } from '../identity/types.js';
5
7
  export interface ApiRequest {
6
8
  query?: Record<string, string | undefined>;
@@ -33,6 +35,8 @@ export interface ApiResponse {
33
35
  contents?: string | Uint8Array;
34
36
  }
35
37
  export interface Services {
38
+ /** The editor routes, where the host supplies a linter. */
39
+ code?: CodeService;
36
40
  drive: DriveService;
37
41
  /**
38
42
  * Users, clients and tokens. A host without one answers 501 for the identity
@@ -42,6 +46,11 @@ export interface Services {
42
46
  identity?: IdentityService;
43
47
  /** Users, groups, permission rules and clients. Absent, the record routes answer 501. */
44
48
  records?: RecordService;
49
+ /**
50
+ * A triggered session's record, log, output and state. A host without one
51
+ * answers 501 for these four, naming the capability.
52
+ */
53
+ session?: SessionService;
45
54
  }
46
55
  export declare const dispatch: (services: Services, routeId: string, request?: ApiRequest) => Promise<ApiResponse>;
47
56
  /** 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,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;AAsCD,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,UAAS,UAAe,KACvB,OAAO,CAAC,WAAW,CAgXrB,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,MAAM,EA0ClC,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,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;CACzB;AA2CD,eAAO,MAAM,QAAQ,GACnB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,UAAS,UAAe,KACvB,OAAO,CAAC,WAAW,CA8brB,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,MAAM,EAgDlC,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
- if (given === undefined || given === '')
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
@@ -66,6 +71,12 @@ export const dispatch = async (services, routeId, request = {}) => {
66
71
  const body = request.body || {};
67
72
  const params = request.params || {};
68
73
  /** The record service, or the 501 a host without one answers. */
74
+ /** The host's sessions, or the 501 that names the missing capability. */
75
+ const sessionRequired = () => {
76
+ if (!services.session)
77
+ throw new ApiError(501, `This host has no session service, so it cannot answer ${routeId}.`);
78
+ return services.session;
79
+ };
69
80
  const required = () => {
70
81
  if (!records)
71
82
  throw new ApiError(501, `This host has no record service, so it cannot answer ${routeId}.`);
@@ -89,7 +100,7 @@ export const dispatch = async (services, routeId, request = {}) => {
89
100
  status: 200,
90
101
  body: await identity.token({
91
102
  clientId: String(body.clientId ?? ''),
92
- code: String(body.code ?? '')
103
+ code: body.code
93
104
  })
94
105
  };
95
106
  case 'POST /SASjsApi/auth/refresh':
@@ -163,6 +174,31 @@ export const dispatch = async (services, routeId, request = {}) => {
163
174
  }
164
175
  case 'GET /SASjsApi/drive/filetree':
165
176
  return { status: 200, body: await drive.tree(query._folderPath) };
177
+ // ------------------------------------------------------------- sessions
178
+ case 'GET /SASjsApi/session/{sessionId}': {
179
+ return {
180
+ status: 200,
181
+ body: await sessionRequired().summary(await caller(), String(params.sessionId ?? ''))
182
+ };
183
+ }
184
+ case 'GET /SASjsApi/session/{sessionId}/log': {
185
+ return {
186
+ status: 200,
187
+ contents: await sessionRequired().log(await caller(), String(params.sessionId ?? ''))
188
+ };
189
+ }
190
+ case 'GET /SASjsApi/session/{sessionId}/output': {
191
+ return {
192
+ status: 200,
193
+ contents: await sessionRequired().output(await caller(), String(params.sessionId ?? ''))
194
+ };
195
+ }
196
+ case 'GET /SASjsApi/session/{sessionId}/state': {
197
+ return {
198
+ status: 200,
199
+ body: await sessionRequired().state(await caller(), String(params.sessionId ?? ''))
200
+ };
201
+ }
166
202
  // ------------------------------------------------------------- records
167
203
  //
168
204
  // The administration routes: users, groups, permission rules, clients and the
@@ -216,6 +252,26 @@ export const dispatch = async (services, routeId, request = {}) => {
216
252
  status: 200,
217
253
  body: await required().deleteUser(await caller(), { username: String(params.username ?? '') }, { password: body.password })
218
254
  };
255
+ case 'POST /SASjsApi/code/lint':
256
+ if (!code)
257
+ throw new ApiError(501, `This host has no code service, so it cannot answer ${routeId}.`);
258
+ return {
259
+ status: 200,
260
+ body: await code.lint(await caller(), {
261
+ code: body.code,
262
+ filePath: body.filePath
263
+ })
264
+ };
265
+ case 'POST /SASjsApi/code/format':
266
+ if (!code)
267
+ throw new ApiError(501, `This host has no code service, so it cannot answer ${routeId}.`);
268
+ return {
269
+ status: 200,
270
+ body: await code.format(await caller(), {
271
+ code: body.code,
272
+ filePath: body.filePath
273
+ })
274
+ };
219
275
  case 'GET /SASjsApi/info/authorizedRoutes':
220
276
  return {
221
277
  status: 200,
@@ -328,6 +384,12 @@ export const dispatch = async (services, routeId, request = {}) => {
328
384
  };
329
385
  /** Whether a route is one `dispatch` can answer today. */
330
386
  export const HANDLED_ROUTES = [
387
+ 'GET /SASjsApi/session/{sessionId}',
388
+ 'GET /SASjsApi/session/{sessionId}/log',
389
+ 'GET /SASjsApi/session/{sessionId}/output',
390
+ 'GET /SASjsApi/session/{sessionId}/state',
391
+ 'POST /SASjsApi/code/lint',
392
+ 'POST /SASjsApi/code/format',
331
393
  'GET /SASjsApi/info/authorizedRoutes',
332
394
  'GET /SASjsApi/info/myAuthorizedRoutes',
333
395
  'GET /SASjsApi/drive/file',
@@ -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';
@@ -37,6 +38,7 @@ export type { IdentityFixture, IdentityFixtureClient, IdentityFixtureCode, Ident
37
38
  export type { IdentityClient, IdentityClientRecord, IdentityGroupRecord, IdentityPermissionRecord, IdentityStore, IdentityUser, IdentityUserRecord, NewGroup, NewPermission, NewUser, Subject, TokenPair, UserChange } from './identity/types.js';
38
39
  export { DRIVE_CORPUS } from './corpus/drive.js';
39
40
  export { IDENTITY_CORPUS } from './corpus/identity.js';
41
+ export { SESSION_CORPUS } from './corpus/session.js';
40
42
  export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
41
43
  export type { CorpusCase } from './corpus/drive.js';
42
44
  export { runCase, servicesFor } from './corpus/runner.js';
@@ -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,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"}
@@ -25,9 +25,11 @@ 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';
31
32
  export { IDENTITY_CORPUS } from './corpus/identity.js';
33
+ export { SESSION_CORPUS } from './corpus/session.js';
32
34
  export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
33
35
  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,EAs3D5C,CAAA"}
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,EAw4D5C,CAAA"}
@@ -344,6 +344,14 @@ export const GENERATED_ROUTES = [
344
344
  {
345
345
  status: 200,
346
346
  description: 'Ok'
347
+ },
348
+ {
349
+ status: 400,
350
+ description: '"code" is required / "code" must be a string / "code" is not allowed to be empty'
351
+ },
352
+ {
353
+ status: 401,
354
+ description: 'Unauthorized'
347
355
  }
348
356
  ]
349
357
  },
@@ -372,6 +380,14 @@ export const GENERATED_ROUTES = [
372
380
  {
373
381
  status: 200,
374
382
  description: 'Ok'
383
+ },
384
+ {
385
+ status: 400,
386
+ description: '"code" is required / "code" must be a string / "code" is not allowed to be empty'
387
+ },
388
+ {
389
+ status: 401,
390
+ description: 'Unauthorized'
375
391
  }
376
392
  ]
377
393
  },
@@ -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;AA8CD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAmNhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,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;AAwCD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAgNhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,CAAA"}