@sasjs/api 0.12.0 → 0.14.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 CHANGED
@@ -1,4 +1,4 @@
1
- /* @sasjs/api 0.12.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
1
+ /* @sasjs/api 0.14.0 - the SASjs Server API, for a service worker. https://github.com/sasjs/api */
2
2
  "use strict";
3
3
  var SASjsApi = (() => {
4
4
  var __defProp = Object.defineProperty;
@@ -28,6 +28,7 @@ var SASjsApi = (() => {
28
28
  CODE_CORPUS: () => CODE_CORPUS,
29
29
  DEFAULT_GRANTABLE_ROUTES: () => DEFAULT_GRANTABLE_ROUTES,
30
30
  DEFAULT_IDENTITY: () => DEFAULT_IDENTITY,
31
+ DEFAULT_SESSIONS: () => DEFAULT_SESSIONS,
31
32
  DEPLOY_CORPUS: () => DEPLOY_CORPUS,
32
33
  DEPLOY_LIMITS: () => DEPLOY_LIMITS,
33
34
  DEPLOY_PAYLOAD_BYTES: () => DEPLOY_PAYLOAD_BYTES,
@@ -36,8 +37,10 @@ var SASjsApi = (() => {
36
37
  HANDLED_ROUTES: () => HANDLED_ROUTES,
37
38
  IDENTITY_CORPUS: () => IDENTITY_CORPUS,
38
39
  IDENTITY_FORMAT: () => IDENTITY_FORMAT,
40
+ MemoryAppStreamRegistry: () => MemoryAppStreamRegistry,
39
41
  MemoryDriveStore: () => MemoryDriveStore,
40
42
  MemoryIdentityStore: () => MemoryIdentityStore,
43
+ MemorySessionHost: () => MemorySessionHost,
41
44
  OVERLAY: () => OVERLAY,
42
45
  PROVIDER_MANAGED: () => PROVIDER_MANAGED,
43
46
  RECORD_CORPUS: () => RECORD_CORPUS,
@@ -51,6 +54,7 @@ var SASjsApi = (() => {
51
54
  createDriveService: () => createDriveService,
52
55
  createIdentityService: () => createIdentityService,
53
56
  createRecordService: () => createRecordService,
57
+ createSessionService: () => createSessionService,
54
58
  decodeBase64: () => decodeBase64,
55
59
  deployTree: () => deployTree,
56
60
  dirname: () => dirname,
@@ -68,7 +72,8 @@ var SASjsApi = (() => {
68
72
  runCase: () => runCase,
69
73
  sanitiseDisplayName: () => sanitiseDisplayName,
70
74
  servicesFor: () => servicesFor,
71
- topLevelRouteFor: () => topLevelRouteFor
75
+ topLevelRouteFor: () => topLevelRouteFor,
76
+ unpublishAppStream: () => unpublishAppStream
72
77
  });
73
78
 
74
79
  // src/types.ts
@@ -1007,7 +1012,7 @@ var SASjsApi = (() => {
1007
1012
  },
1008
1013
  {
1009
1014
  status: 404,
1010
- description: "No such app stream entry"
1015
+ description: "No app is published at that folder"
1011
1016
  }
1012
1017
  ]
1013
1018
  },
@@ -2514,7 +2519,6 @@ var SASjsApi = (() => {
2514
2519
  var HOST_CONFIG = "The body is the host's own configuration - ports, run times and providers - which a mock states for itself and this package cannot know.";
2515
2520
  var HOST_OWNED = "The host owns this report: it describes the host's own process and files, which a browser build has no equivalent of.";
2516
2521
  var WEB_BUNDLE = "The host serves its own web bundle; there is no API decision here for this package to answer.";
2517
- var 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.";
2518
2522
  var success = { status: "success" };
2519
2523
  var user = {
2520
2524
  uid: "",
@@ -2642,10 +2646,7 @@ var SASjsApi = (() => {
2642
2646
  500: { status: "failure", message: "Deployment failed!" }
2643
2647
  }
2644
2648
  },
2645
- "POST /SASjsApi/drive/deploy/upload": {
2646
- implemented: false,
2647
- reason: ARCHIVE
2648
- },
2649
+ "POST /SASjsApi/drive/deploy/upload": { implemented: true },
2649
2650
  // `404` is a pair - the registry name and the appLoc it was published from -
2650
2651
  // that matches no entry, and the drive's content is deliberately left alone.
2651
2652
  "DELETE /SASjsApi/drive/deploy": { implemented: true },
@@ -2812,7 +2813,7 @@ var SASjsApi = (() => {
2812
2813
  throw new ApiError(400, '"streamServiceName" is required');
2813
2814
  if (!body.appLoc) throw new ApiError(400, '"appLoc" is required');
2814
2815
  if (!await registry.unpublish(body.appLoc))
2815
- throw new ApiError(404, "No such app stream entry.");
2816
+ throw new ApiError(404, "No app is published at that folder.");
2816
2817
  return { status: "success" };
2817
2818
  };
2818
2819
  var MemoryAppStreamRegistry = class {
@@ -2966,6 +2967,11 @@ var SASjsApi = (() => {
2966
2967
  String(request.body?.newPath || "")
2967
2968
  )
2968
2969
  };
2970
+ // The archive is the host's transport - multer and a zip library on a
2971
+ // server, `DecompressionStream` in a worker - and it hands this the same tree
2972
+ // `POST /deploy` takes. The deploy cap is measured on that tree, after
2973
+ // expansion, which is why the two share this path rather than reading bytes.
2974
+ case "POST /SASjsApi/drive/deploy/upload":
2969
2975
  case "POST /SASjsApi/drive/deploy": {
2970
2976
  await drive.deploy(String(body.appLoc ?? ""), body.fileTree);
2971
2977
  return { status: 200, body: DEPLOY_SUCCESS };
@@ -3234,6 +3240,7 @@ var SASjsApi = (() => {
3234
3240
  }
3235
3241
  };
3236
3242
  var HANDLED_ROUTES = [
3243
+ "POST /SASjsApi/drive/deploy/upload",
3237
3244
  "DELETE /SASjsApi/drive/deploy",
3238
3245
  "GET /SASjsApi/session/{sessionId}",
3239
3246
  "GET /SASjsApi/session/{sessionId}/log",
@@ -4958,6 +4965,11 @@ var SASjsApi = (() => {
4958
4965
 
4959
4966
  // src/corpus/deploy.ts
4960
4967
  var SUBJECT = { userId: "user-admin", clientId: "web_app" };
4968
+ var manyMembers2 = (count) => Array.from({ length: count }, (_, at) => ({
4969
+ name: `file-${at}.txt`,
4970
+ type: "file",
4971
+ code: ""
4972
+ }));
4961
4973
  var DEPLOY_CORPUS = [
4962
4974
  {
4963
4975
  id: "deploy-unpublish",
@@ -5001,7 +5013,90 @@ var SASjsApi = (() => {
5001
5013
  subject: SUBJECT,
5002
5014
  body: { streamServiceName: "myApp", appLoc: "/Public/not-an-app" }
5003
5015
  },
5004
- expect: { status: 404, error: "No such app stream entry." }
5016
+ expect: { status: 404, error: "No app is published at that folder." }
5017
+ },
5018
+ // ------------------------------------------- the uploaded-archive route
5019
+ // The archive is expanded by the host, so what arrives here is the same tree
5020
+ // `POST /deploy` takes - which is why every answer below matches its twin.
5021
+ {
5022
+ id: "upload-tree",
5023
+ route: "POST /SASjsApi/drive/deploy/upload",
5024
+ description: "writes the tree an uploaded archive expanded to",
5025
+ request: {
5026
+ subject: SUBJECT,
5027
+ body: {
5028
+ appLoc: "/Public/apps/uploaded",
5029
+ fileTree: {
5030
+ members: [
5031
+ { name: "index.html", type: "file", code: "PGgxPkhlbGxvPC9oMT4=" }
5032
+ ]
5033
+ }
5034
+ }
5035
+ },
5036
+ expect: {
5037
+ status: 200,
5038
+ body: {
5039
+ status: "success",
5040
+ message: "Files deployed successfully to @sasjs/server."
5041
+ }
5042
+ }
5043
+ },
5044
+ {
5045
+ id: "upload-refused-by-shape",
5046
+ route: "POST /SASjsApi/drive/deploy/upload",
5047
+ description: "refuses an expansion that is not a tree",
5048
+ request: {
5049
+ subject: SUBJECT,
5050
+ body: { appLoc: "/Public/apps/bad", fileTree: { members: 1 } }
5051
+ },
5052
+ expect: {
5053
+ status: 400,
5054
+ body: {
5055
+ status: "failure",
5056
+ message: "Provided not supported data format."
5057
+ },
5058
+ error: "Provided not supported data format."
5059
+ }
5060
+ },
5061
+ {
5062
+ id: "upload-refused-by-entry-count",
5063
+ route: "POST /SASjsApi/drive/deploy/upload",
5064
+ description: "applies the deploy cap to the expanded tree, not the bytes",
5065
+ request: {
5066
+ subject: SUBJECT,
5067
+ body: {
5068
+ appLoc: "/Public/apps/many",
5069
+ fileTree: { members: manyMembers2(10001) }
5070
+ }
5071
+ },
5072
+ expect: {
5073
+ status: 413,
5074
+ body: {
5075
+ status: "failure",
5076
+ message: "The file tree exceeds the deploy limit: 10001 entries (maximum 10000)."
5077
+ },
5078
+ error: "The file tree exceeds the deploy limit: 10001 entries (maximum 10000)."
5079
+ }
5080
+ },
5081
+ {
5082
+ id: "upload-refused-by-store",
5083
+ route: "POST /SASjsApi/drive/deploy/upload",
5084
+ description: "answers the routes 500 when the drive refuses the write",
5085
+ unwritable: true,
5086
+ request: {
5087
+ subject: SUBJECT,
5088
+ body: {
5089
+ appLoc: "/Public/apps/demo",
5090
+ fileTree: {
5091
+ members: [{ name: "run", type: "service", code: "%put hi;" }]
5092
+ }
5093
+ }
5094
+ },
5095
+ expect: {
5096
+ status: 500,
5097
+ body: { status: "failure", message: "Deployment failed!" },
5098
+ error: "Deployment failed!"
5099
+ }
5005
5100
  }
5006
5101
  ];
5007
5102
 
@@ -5146,6 +5241,112 @@ var SASjsApi = (() => {
5146
5241
  }
5147
5242
  ];
5148
5243
 
5244
+ // src/session/service.ts
5245
+ var createSessionService = (store, host) => {
5246
+ const notFound2 = (id) => `Session with ID '${id}' was not found.`;
5247
+ const visible = async (caller, id) => {
5248
+ if (!caller) throw new ApiError(401, "Unauthorized");
5249
+ const reader = await store.userById(
5250
+ caller.userId
5251
+ );
5252
+ if (!reader || !reader.isActive) throw new ApiError(401, "Unauthorized");
5253
+ const session = await host.session(id);
5254
+ if (!session) throw new ApiError(404, notFound2(id));
5255
+ const owner = session.owner;
5256
+ if (owner !== reader.uid && owner !== reader.username && !reader.isAdmin)
5257
+ throw new ApiError(404, notFound2(id));
5258
+ return session;
5259
+ };
5260
+ return {
5261
+ async summary(caller, id) {
5262
+ const session = await visible(caller, id);
5263
+ return session.summary;
5264
+ },
5265
+ async state(caller, id) {
5266
+ await visible(caller, id);
5267
+ const state = await host.state(id);
5268
+ if (!state) throw new ApiError(404, notFound2(id));
5269
+ return state;
5270
+ },
5271
+ async log(caller, id) {
5272
+ await visible(caller, id);
5273
+ const log = await host.log(id);
5274
+ if (log === void 0) throw new ApiError(404, notFound2(id));
5275
+ return log;
5276
+ },
5277
+ async output(caller, id) {
5278
+ await visible(caller, id);
5279
+ const output = await host.output(id);
5280
+ if (output === void 0) throw new ApiError(404, notFound2(id));
5281
+ return output;
5282
+ }
5283
+ };
5284
+ };
5285
+
5286
+ // src/session/memory-host.ts
5287
+ var DEFAULT_SESSIONS = {
5288
+ "sess-admin": {
5289
+ owner: "user-admin",
5290
+ summary: {
5291
+ sessionId: "sess-admin",
5292
+ state: "completed",
5293
+ pid: 4242,
5294
+ creationTimeStamp: "2026-11-07T09:00:00.000Z",
5295
+ deathTimeStamp: "2026-11-07T09:00:04.000Z",
5296
+ expiresAfterMins: 60
5297
+ }
5298
+ },
5299
+ "sess-restricted": {
5300
+ owner: "user-restricted",
5301
+ summary: {
5302
+ sessionId: "sess-restricted",
5303
+ state: "running",
5304
+ pid: 4243,
5305
+ creationTimeStamp: "2026-11-07T09:05:00.000Z"
5306
+ }
5307
+ },
5308
+ "sess-silent": {
5309
+ owner: "user-admin",
5310
+ summary: {
5311
+ sessionId: "sess-silent",
5312
+ state: "running",
5313
+ pid: 4244,
5314
+ creationTimeStamp: "2026-11-07T09:10:00.000Z"
5315
+ }
5316
+ }
5317
+ };
5318
+ var STATES = {
5319
+ "sess-admin": { state: "completed", pid: 4242, lineCount: 12 },
5320
+ "sess-restricted": { state: "running", pid: 4243, lineCount: 3 }
5321
+ };
5322
+ var LOGS = {
5323
+ "sess-admin": "NOTE: session started\nNOTE: session completed\n",
5324
+ "sess-restricted": "NOTE: session started\n",
5325
+ "sess-silent": ""
5326
+ };
5327
+ var OUTPUTS = {
5328
+ "sess-admin": "<html><body>42</body></html>",
5329
+ "sess-restricted": "",
5330
+ "sess-silent": ""
5331
+ };
5332
+ var MemorySessionHost = class {
5333
+ constructor(sessions = DEFAULT_SESSIONS) {
5334
+ this.sessions = sessions;
5335
+ }
5336
+ async session(id) {
5337
+ return this.sessions[id];
5338
+ }
5339
+ async state(id) {
5340
+ return STATES[id];
5341
+ }
5342
+ async log(id) {
5343
+ return LOGS[id];
5344
+ }
5345
+ async output(id) {
5346
+ return OUTPUTS[id];
5347
+ }
5348
+ };
5349
+
5149
5350
  // src/corpus/records.ts
5150
5351
  var SIGNED_IN_TOKENS = [
5151
5352
  {
@@ -6362,112 +6563,6 @@ var SASjsApi = (() => {
6362
6563
  }
6363
6564
  ];
6364
6565
 
6365
- // src/session/service.ts
6366
- var createSessionService = (store, host) => {
6367
- const notFound2 = (id) => `Session with ID '${id}' was not found.`;
6368
- const visible = async (caller, id) => {
6369
- if (!caller) throw new ApiError(401, "Unauthorized");
6370
- const reader = await store.userById(
6371
- caller.userId
6372
- );
6373
- if (!reader || !reader.isActive) throw new ApiError(401, "Unauthorized");
6374
- const session = await host.session(id);
6375
- if (!session) throw new ApiError(404, notFound2(id));
6376
- const owner = session.owner;
6377
- if (owner !== reader.uid && owner !== reader.username && !reader.isAdmin)
6378
- throw new ApiError(404, notFound2(id));
6379
- return session;
6380
- };
6381
- return {
6382
- async summary(caller, id) {
6383
- const session = await visible(caller, id);
6384
- return session.summary;
6385
- },
6386
- async state(caller, id) {
6387
- await visible(caller, id);
6388
- const state = await host.state(id);
6389
- if (!state) throw new ApiError(404, notFound2(id));
6390
- return state;
6391
- },
6392
- async log(caller, id) {
6393
- await visible(caller, id);
6394
- const log = await host.log(id);
6395
- if (log === void 0) throw new ApiError(404, notFound2(id));
6396
- return log;
6397
- },
6398
- async output(caller, id) {
6399
- await visible(caller, id);
6400
- const output = await host.output(id);
6401
- if (output === void 0) throw new ApiError(404, notFound2(id));
6402
- return output;
6403
- }
6404
- };
6405
- };
6406
-
6407
- // src/session/memory-host.ts
6408
- var DEFAULT_SESSIONS = {
6409
- "sess-admin": {
6410
- owner: "user-admin",
6411
- summary: {
6412
- sessionId: "sess-admin",
6413
- state: "completed",
6414
- pid: 4242,
6415
- creationTimeStamp: "2026-11-07T09:00:00.000Z",
6416
- deathTimeStamp: "2026-11-07T09:00:04.000Z",
6417
- expiresAfterMins: 60
6418
- }
6419
- },
6420
- "sess-restricted": {
6421
- owner: "user-restricted",
6422
- summary: {
6423
- sessionId: "sess-restricted",
6424
- state: "running",
6425
- pid: 4243,
6426
- creationTimeStamp: "2026-11-07T09:05:00.000Z"
6427
- }
6428
- },
6429
- "sess-silent": {
6430
- owner: "user-admin",
6431
- summary: {
6432
- sessionId: "sess-silent",
6433
- state: "running",
6434
- pid: 4244,
6435
- creationTimeStamp: "2026-11-07T09:10:00.000Z"
6436
- }
6437
- }
6438
- };
6439
- var STATES = {
6440
- "sess-admin": { state: "completed", pid: 4242, lineCount: 12 },
6441
- "sess-restricted": { state: "running", pid: 4243, lineCount: 3 }
6442
- };
6443
- var LOGS = {
6444
- "sess-admin": "NOTE: session started\nNOTE: session completed\n",
6445
- "sess-restricted": "NOTE: session started\n",
6446
- "sess-silent": ""
6447
- };
6448
- var OUTPUTS = {
6449
- "sess-admin": "<html><body>42</body></html>",
6450
- "sess-restricted": "",
6451
- "sess-silent": ""
6452
- };
6453
- var MemorySessionHost = class {
6454
- constructor(sessions = DEFAULT_SESSIONS) {
6455
- this.sessions = sessions;
6456
- }
6457
- async session(id) {
6458
- return this.sessions[id];
6459
- }
6460
- async state(id) {
6461
- return STATES[id];
6462
- }
6463
- async log(id) {
6464
- return LOGS[id];
6465
- }
6466
- async output(id) {
6467
- return OUTPUTS[id];
6468
- }
6469
- };
6470
-
6471
6566
  // src/corpus/runner.ts
6472
6567
  var servicesFor = (testCase) => {
6473
6568
  const identity = new MemoryIdentityStore(
@@ -2,6 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DEPLOY_CORPUS = void 0;
4
4
  const SUBJECT = { userId: 'user-admin', clientId: 'web_app' };
5
+ /** A tree of `count` file members, for the deploy cap. */
6
+ const manyMembers = (count) => Array.from({ length: count }, (_, at) => ({
7
+ name: `file-${at}.txt`,
8
+ type: 'file',
9
+ code: ''
10
+ }));
5
11
  exports.DEPLOY_CORPUS = [
6
12
  {
7
13
  id: 'deploy-unpublish',
@@ -45,6 +51,89 @@ exports.DEPLOY_CORPUS = [
45
51
  subject: SUBJECT,
46
52
  body: { streamServiceName: 'myApp', appLoc: '/Public/not-an-app' }
47
53
  },
48
- expect: { status: 404, error: 'No such app stream entry.' }
54
+ expect: { status: 404, error: 'No app is published at that folder.' }
55
+ },
56
+ // ------------------------------------------- the uploaded-archive route
57
+ // The archive is expanded by the host, so what arrives here is the same tree
58
+ // `POST /deploy` takes - which is why every answer below matches its twin.
59
+ {
60
+ id: 'upload-tree',
61
+ route: 'POST /SASjsApi/drive/deploy/upload',
62
+ description: 'writes the tree an uploaded archive expanded to',
63
+ request: {
64
+ subject: SUBJECT,
65
+ body: {
66
+ appLoc: '/Public/apps/uploaded',
67
+ fileTree: {
68
+ members: [
69
+ { name: 'index.html', type: 'file', code: 'PGgxPkhlbGxvPC9oMT4=' }
70
+ ]
71
+ }
72
+ }
73
+ },
74
+ expect: {
75
+ status: 200,
76
+ body: {
77
+ status: 'success',
78
+ message: 'Files deployed successfully to @sasjs/server.'
79
+ }
80
+ }
81
+ },
82
+ {
83
+ id: 'upload-refused-by-shape',
84
+ route: 'POST /SASjsApi/drive/deploy/upload',
85
+ description: 'refuses an expansion that is not a tree',
86
+ request: {
87
+ subject: SUBJECT,
88
+ body: { appLoc: '/Public/apps/bad', fileTree: { members: 1 } }
89
+ },
90
+ expect: {
91
+ status: 400,
92
+ body: {
93
+ status: 'failure',
94
+ message: 'Provided not supported data format.'
95
+ },
96
+ error: 'Provided not supported data format.'
97
+ }
98
+ },
99
+ {
100
+ id: 'upload-refused-by-entry-count',
101
+ route: 'POST /SASjsApi/drive/deploy/upload',
102
+ description: 'applies the deploy cap to the expanded tree, not the bytes',
103
+ request: {
104
+ subject: SUBJECT,
105
+ body: {
106
+ appLoc: '/Public/apps/many',
107
+ fileTree: { members: manyMembers(10001) }
108
+ }
109
+ },
110
+ expect: {
111
+ status: 413,
112
+ body: {
113
+ status: 'failure',
114
+ message: 'The file tree exceeds the deploy limit: 10001 entries (maximum 10000).'
115
+ },
116
+ error: 'The file tree exceeds the deploy limit: 10001 entries (maximum 10000).'
117
+ }
118
+ },
119
+ {
120
+ id: 'upload-refused-by-store',
121
+ route: 'POST /SASjsApi/drive/deploy/upload',
122
+ description: 'answers the routes 500 when the drive refuses the write',
123
+ unwritable: true,
124
+ request: {
125
+ subject: SUBJECT,
126
+ body: {
127
+ appLoc: '/Public/apps/demo',
128
+ fileTree: {
129
+ members: [{ name: 'run', type: 'service', code: '%put hi;' }]
130
+ }
131
+ }
132
+ },
133
+ expect: {
134
+ status: 500,
135
+ body: { status: 'failure', message: 'Deployment failed!' },
136
+ error: 'Deployment failed!'
137
+ }
49
138
  }
50
139
  ];
@@ -19,7 +19,7 @@ const unpublishAppStream = async (registry, body) => {
19
19
  if (!body.appLoc)
20
20
  throw new types_js_1.ApiError(400, '"appLoc" is required');
21
21
  if (!(await registry.unpublish(body.appLoc)))
22
- throw new types_js_1.ApiError(404, 'No such app stream entry.');
22
+ throw new types_js_1.ApiError(404, 'No app is published at that folder.');
23
23
  return { status: 'success' };
24
24
  };
25
25
  exports.unpublishAppStream = unpublishAppStream;
@@ -170,6 +170,11 @@ const dispatch = async (services, routeId, request = {}) => {
170
170
  status: 200,
171
171
  body: await drive.rename(String(request.body?.oldPath || ''), String(request.body?.newPath || ''))
172
172
  };
173
+ // The archive is the host's transport - multer and a zip library on a
174
+ // server, `DecompressionStream` in a worker - and it hands this the same tree
175
+ // `POST /deploy` takes. The deploy cap is measured on that tree, after
176
+ // expansion, which is why the two share this path rather than reading bytes.
177
+ case 'POST /SASjsApi/drive/deploy/upload':
173
178
  case 'POST /SASjsApi/drive/deploy': {
174
179
  // `streamWebFolder` is the host's: publishing an app stream is not a drive
175
180
  // operation, and a host that has no app stream ignores it.
@@ -396,6 +401,7 @@ const dispatch = async (services, routeId, request = {}) => {
396
401
  exports.dispatch = dispatch;
397
402
  /** Whether a route is one `dispatch` can answer today. */
398
403
  exports.HANDLED_ROUTES = [
404
+ 'POST /SASjsApi/drive/deploy/upload',
399
405
  'DELETE /SASjsApi/drive/deploy',
400
406
  'GET /SASjsApi/session/{sessionId}',
401
407
  'GET /SASjsApi/session/{sessionId}/log',
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.servicesFor = exports.runCase = exports.PROVIDER_MANAGED = exports.ADMIN_SIGNED_IN = exports.RECORD_CORPUS = exports.SESSION_CORPUS = exports.DEPLOY_CORPUS = exports.IDENTITY_CORPUS = exports.DRIVE_CORPUS = exports.parseIdentity = exports.IDENTITY_FORMAT = exports.DEFAULT_IDENTITY = exports.MemoryIdentityStore = exports.CODE_CORPUS = exports.sanitiseDisplayName = exports.createRecordService = exports.createIdentityService = exports.HANDLED_ROUTES = exports.dispatch = exports.TAG_CAPABILITIES = exports.OVERLAY = exports.findRoute = exports.routeById = exports.ROUTES = exports.MemoryDriveStore = exports.measureDeployTree = exports.isDeployTree = exports.deployTree = exports.decodeBase64 = exports.checkDeployTree = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = exports.createDriveService = exports.isRoot = exports.basename = exports.dirname = exports.hasBlockedExtension = exports.normaliseDrivePath = void 0;
17
+ exports.servicesFor = exports.runCase = exports.PROVIDER_MANAGED = exports.ADMIN_SIGNED_IN = exports.RECORD_CORPUS = exports.unpublishAppStream = exports.MemoryAppStreamRegistry = exports.MemorySessionHost = exports.DEFAULT_SESSIONS = exports.createSessionService = exports.SESSION_CORPUS = exports.DEPLOY_CORPUS = exports.IDENTITY_CORPUS = exports.DRIVE_CORPUS = exports.parseIdentity = exports.IDENTITY_FORMAT = exports.DEFAULT_IDENTITY = exports.MemoryIdentityStore = exports.CODE_CORPUS = exports.sanitiseDisplayName = exports.createRecordService = exports.createIdentityService = exports.HANDLED_ROUTES = exports.dispatch = exports.TAG_CAPABILITIES = exports.OVERLAY = exports.findRoute = exports.routeById = exports.ROUTES = exports.MemoryDriveStore = exports.measureDeployTree = exports.isDeployTree = exports.deployTree = exports.decodeBase64 = exports.checkDeployTree = exports.DEPLOY_SUCCESS = exports.DEPLOY_PAYLOAD_BYTES = exports.DEPLOY_LIMITS = exports.createDriveService = exports.isRoot = exports.basename = exports.dirname = exports.hasBlockedExtension = exports.normaliseDrivePath = void 0;
18
18
  /**
19
19
  * `@sasjs/api` - the SASjs Server API described once.
20
20
  *
@@ -83,6 +83,14 @@ var deploy_js_2 = require("./corpus/deploy.js");
83
83
  Object.defineProperty(exports, "DEPLOY_CORPUS", { enumerable: true, get: function () { return deploy_js_2.DEPLOY_CORPUS; } });
84
84
  var session_js_1 = require("./corpus/session.js");
85
85
  Object.defineProperty(exports, "SESSION_CORPUS", { enumerable: true, get: function () { return session_js_1.SESSION_CORPUS; } });
86
+ var service_js_3 = require("./session/service.js");
87
+ Object.defineProperty(exports, "createSessionService", { enumerable: true, get: function () { return service_js_3.createSessionService; } });
88
+ var memory_host_js_1 = require("./session/memory-host.js");
89
+ Object.defineProperty(exports, "DEFAULT_SESSIONS", { enumerable: true, get: function () { return memory_host_js_1.DEFAULT_SESSIONS; } });
90
+ Object.defineProperty(exports, "MemorySessionHost", { enumerable: true, get: function () { return memory_host_js_1.MemorySessionHost; } });
91
+ var registry_js_1 = require("./drive/registry.js");
92
+ Object.defineProperty(exports, "MemoryAppStreamRegistry", { enumerable: true, get: function () { return registry_js_1.MemoryAppStreamRegistry; } });
93
+ Object.defineProperty(exports, "unpublishAppStream", { enumerable: true, get: function () { return registry_js_1.unpublishAppStream; } });
86
94
  var records_js_2 = require("./corpus/records.js");
87
95
  Object.defineProperty(exports, "RECORD_CORPUS", { enumerable: true, get: function () { return records_js_2.RECORD_CORPUS; } });
88
96
  Object.defineProperty(exports, "ADMIN_SIGNED_IN", { enumerable: true, get: function () { return records_js_2.ADMIN_SIGNED_IN; } });
@@ -460,7 +460,7 @@ exports.GENERATED_ROUTES = [
460
460
  },
461
461
  {
462
462
  status: 404,
463
- description: 'No such app stream entry'
463
+ description: 'No app is published at that folder'
464
464
  }
465
465
  ]
466
466
  },
@@ -30,7 +30,6 @@ const RUNTIME = 'Starting a program needs a runtime port, which this package doe
30
30
  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.";
31
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
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
33
  const success = { status: 'success' };
35
34
  const user = {
36
35
  uid: '',
@@ -158,10 +157,7 @@ exports.OVERLAY = {
158
157
  500: { status: 'failure', message: 'Deployment failed!' }
159
158
  }
160
159
  },
161
- 'POST /SASjsApi/drive/deploy/upload': {
162
- implemented: false,
163
- reason: ARCHIVE
164
- },
160
+ 'POST /SASjsApi/drive/deploy/upload': { implemented: true },
165
161
  // `404` is a pair - the registry name and the appLoc it was published from -
166
162
  // that matches no entry, and the drive's content is deliberately left alone.
167
163
  'DELETE /SASjsApi/drive/deploy': { implemented: true },
@@ -1 +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"}
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;AAW5C,eAAO,MAAM,aAAa,EAAE,UAAU,EAmIrC,CAAA"}
@@ -1,4 +1,10 @@
1
1
  const SUBJECT = { userId: 'user-admin', clientId: 'web_app' };
2
+ /** A tree of `count` file members, for the deploy cap. */
3
+ const manyMembers = (count) => Array.from({ length: count }, (_, at) => ({
4
+ name: `file-${at}.txt`,
5
+ type: 'file',
6
+ code: ''
7
+ }));
2
8
  export const DEPLOY_CORPUS = [
3
9
  {
4
10
  id: 'deploy-unpublish',
@@ -42,6 +48,89 @@ export const DEPLOY_CORPUS = [
42
48
  subject: SUBJECT,
43
49
  body: { streamServiceName: 'myApp', appLoc: '/Public/not-an-app' }
44
50
  },
45
- expect: { status: 404, error: 'No such app stream entry.' }
51
+ expect: { status: 404, error: 'No app is published at that folder.' }
52
+ },
53
+ // ------------------------------------------- the uploaded-archive route
54
+ // The archive is expanded by the host, so what arrives here is the same tree
55
+ // `POST /deploy` takes - which is why every answer below matches its twin.
56
+ {
57
+ id: 'upload-tree',
58
+ route: 'POST /SASjsApi/drive/deploy/upload',
59
+ description: 'writes the tree an uploaded archive expanded to',
60
+ request: {
61
+ subject: SUBJECT,
62
+ body: {
63
+ appLoc: '/Public/apps/uploaded',
64
+ fileTree: {
65
+ members: [
66
+ { name: 'index.html', type: 'file', code: 'PGgxPkhlbGxvPC9oMT4=' }
67
+ ]
68
+ }
69
+ }
70
+ },
71
+ expect: {
72
+ status: 200,
73
+ body: {
74
+ status: 'success',
75
+ message: 'Files deployed successfully to @sasjs/server.'
76
+ }
77
+ }
78
+ },
79
+ {
80
+ id: 'upload-refused-by-shape',
81
+ route: 'POST /SASjsApi/drive/deploy/upload',
82
+ description: 'refuses an expansion that is not a tree',
83
+ request: {
84
+ subject: SUBJECT,
85
+ body: { appLoc: '/Public/apps/bad', fileTree: { members: 1 } }
86
+ },
87
+ expect: {
88
+ status: 400,
89
+ body: {
90
+ status: 'failure',
91
+ message: 'Provided not supported data format.'
92
+ },
93
+ error: 'Provided not supported data format.'
94
+ }
95
+ },
96
+ {
97
+ id: 'upload-refused-by-entry-count',
98
+ route: 'POST /SASjsApi/drive/deploy/upload',
99
+ description: 'applies the deploy cap to the expanded tree, not the bytes',
100
+ request: {
101
+ subject: SUBJECT,
102
+ body: {
103
+ appLoc: '/Public/apps/many',
104
+ fileTree: { members: manyMembers(10001) }
105
+ }
106
+ },
107
+ expect: {
108
+ status: 413,
109
+ body: {
110
+ status: 'failure',
111
+ message: 'The file tree exceeds the deploy limit: 10001 entries (maximum 10000).'
112
+ },
113
+ error: 'The file tree exceeds the deploy limit: 10001 entries (maximum 10000).'
114
+ }
115
+ },
116
+ {
117
+ id: 'upload-refused-by-store',
118
+ route: 'POST /SASjsApi/drive/deploy/upload',
119
+ description: 'answers the routes 500 when the drive refuses the write',
120
+ unwritable: true,
121
+ request: {
122
+ subject: SUBJECT,
123
+ body: {
124
+ appLoc: '/Public/apps/demo',
125
+ fileTree: {
126
+ members: [{ name: 'run', type: 'service', code: '%put hi;' }]
127
+ }
128
+ }
129
+ },
130
+ expect: {
131
+ status: 500,
132
+ body: { status: 'failure', message: 'Deployment failed!' },
133
+ error: 'Deployment failed!'
134
+ }
46
135
  }
47
136
  ];
@@ -16,7 +16,7 @@ export const unpublishAppStream = async (registry, body) => {
16
16
  if (!body.appLoc)
17
17
  throw new ApiError(400, '"appLoc" is required');
18
18
  if (!(await registry.unpublish(body.appLoc)))
19
- throw new ApiError(404, 'No such app stream entry.');
19
+ throw new ApiError(404, 'No app is published at that folder.');
20
20
  return { status: 'success' };
21
21
  };
22
22
  /**
@@ -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,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
+ {"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,CA8crB,CAAA;AAED,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,MAAM,EAkDlC,CAAA"}
@@ -167,6 +167,11 @@ export const dispatch = async (services, routeId, request = {}) => {
167
167
  status: 200,
168
168
  body: await drive.rename(String(request.body?.oldPath || ''), String(request.body?.newPath || ''))
169
169
  };
170
+ // The archive is the host's transport - multer and a zip library on a
171
+ // server, `DecompressionStream` in a worker - and it hands this the same tree
172
+ // `POST /deploy` takes. The deploy cap is measured on that tree, after
173
+ // expansion, which is why the two share this path rather than reading bytes.
174
+ case 'POST /SASjsApi/drive/deploy/upload':
170
175
  case 'POST /SASjsApi/drive/deploy': {
171
176
  // `streamWebFolder` is the host's: publishing an app stream is not a drive
172
177
  // operation, and a host that has no app stream ignores it.
@@ -392,6 +397,7 @@ export const dispatch = async (services, routeId, request = {}) => {
392
397
  };
393
398
  /** Whether a route is one `dispatch` can answer today. */
394
399
  export const HANDLED_ROUTES = [
400
+ 'POST /SASjsApi/drive/deploy/upload',
395
401
  'DELETE /SASjsApi/drive/deploy',
396
402
  'GET /SASjsApi/session/{sessionId}',
397
403
  'GET /SASjsApi/session/{sessionId}/log',
@@ -40,6 +40,11 @@ export { DRIVE_CORPUS } from './corpus/drive.js';
40
40
  export { IDENTITY_CORPUS } from './corpus/identity.js';
41
41
  export { DEPLOY_CORPUS } from './corpus/deploy.js';
42
42
  export { SESSION_CORPUS } from './corpus/session.js';
43
+ export { createSessionService } from './session/service.js';
44
+ export type { HostSession, SessionHost, SessionService, SessionStateResponse, SessionSummary } from './session/service.js';
45
+ export { DEFAULT_SESSIONS, MemorySessionHost } from './session/memory-host.js';
46
+ export { MemoryAppStreamRegistry, unpublishAppStream } from './drive/registry.js';
47
+ export type { AppStreamRegistry, UnpublishBody } from './drive/registry.js';
43
48
  export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
44
49
  export type { CorpusCase } from './corpus/drive.js';
45
50
  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,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -32,5 +32,8 @@ export { DRIVE_CORPUS } from './corpus/drive.js';
32
32
  export { IDENTITY_CORPUS } from './corpus/identity.js';
33
33
  export { DEPLOY_CORPUS } from './corpus/deploy.js';
34
34
  export { SESSION_CORPUS } from './corpus/session.js';
35
+ export { createSessionService } from './session/service.js';
36
+ export { DEFAULT_SESSIONS, MemorySessionHost } from './session/memory-host.js';
37
+ export { MemoryAppStreamRegistry, unpublishAppStream } from './drive/registry.js';
35
38
  export { RECORD_CORPUS, ADMIN_SIGNED_IN, PROVIDER_MANAGED } from './corpus/records.js';
36
39
  export { runCase, servicesFor } from './corpus/runner.js';
@@ -457,7 +457,7 @@ export const GENERATED_ROUTES = [
457
457
  },
458
458
  {
459
459
  status: 404,
460
- description: 'No such app stream entry'
460
+ description: 'No app is published at that folder'
461
461
  }
462
462
  ]
463
463
  },
@@ -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;AAqCD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA6MhD,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;AAkCD,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA0MhD,CAAA;AAED,wEAAwE;AACxE,eAAO,MAAM,OAAO,EAAE,UAAU,EAA8C,CAAA"}
@@ -27,7 +27,6 @@ const RUNTIME = 'Starting a program needs a runtime port, which this package doe
27
27
  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.";
28
28
  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.";
29
29
  const WEB_BUNDLE = 'The host serves its own web bundle; there is no API decision here for this package to answer.';
30
- 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.";
31
30
  const success = { status: 'success' };
32
31
  const user = {
33
32
  uid: '',
@@ -155,10 +154,7 @@ export const OVERLAY = {
155
154
  500: { status: 'failure', message: 'Deployment failed!' }
156
155
  }
157
156
  },
158
- 'POST /SASjsApi/drive/deploy/upload': {
159
- implemented: false,
160
- reason: ARCHIVE
161
- },
157
+ 'POST /SASjsApi/drive/deploy/upload': { implemented: true },
162
158
  // `404` is a pair - the registry name and the appLoc it was published from -
163
159
  // that matches no entry, and the drive's content is deliberately left alone.
164
160
  'DELETE /SASjsApi/drive/deploy': { implemented: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sasjs/api",
3
- "version": "0.12.0",
3
+ "version": "0.14.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",