@malloydata/malloy 0.0.240-dev250305163014 → 0.0.240-dev250305213425

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.
@@ -4,7 +4,14 @@ export type TTL = {
4
4
  } | Date;
5
5
  export interface OptionsBase {
6
6
  ttl?: TTL;
7
+ session_id?: string;
7
8
  }
8
- export declare function compileModel(request: Malloy.CompileModelRequest, options?: OptionsBase): Malloy.CompileModelResponse;
9
- export declare function compileSource(request: Malloy.CompileSourceRequest, options?: OptionsBase): Malloy.CompileSourceResponse;
10
- export declare function compileQuery(request: Malloy.CompileQueryRequest, options?: OptionsBase): Malloy.CompileQueryResponse;
9
+ export declare function compileModel(request: Malloy.CompileModelRequest, options?: OptionsBase): Malloy.CompileModelResponse & {
10
+ session_id: string;
11
+ };
12
+ export declare function compileSource(request: Malloy.CompileSourceRequest, options?: OptionsBase): Malloy.CompileSourceResponse & {
13
+ session_id: string;
14
+ };
15
+ export declare function compileQuery(request: Malloy.CompileQueryRequest, options?: OptionsBase): Malloy.CompileQueryResponse & {
16
+ session_id: string;
17
+ };
@@ -32,6 +32,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
32
32
  exports.compileQuery = exports.compileSource = exports.compileModel = void 0;
33
33
  const Malloy = __importStar(require("@malloydata/malloy-interfaces"));
34
34
  const Core = __importStar(require("./core"));
35
+ const uuid_1 = require("uuid");
35
36
  function sessionInfosMatch(a, b) {
36
37
  if (a.type === 'compile_model') {
37
38
  if (b.type !== 'compile_model')
@@ -53,18 +54,23 @@ function sessionInfosMatch(a, b) {
53
54
  }
54
55
  class SessionManager {
55
56
  constructor() {
56
- this.sessions = [];
57
+ this.sessions = new Map();
57
58
  }
58
- purgeExpired(except) {
59
+ purgeExpired(options) {
59
60
  const now = new Date();
60
- this.sessions = this.sessions.filter(s => (except && sessionInfosMatch(s.sessionInfo, except)) ||
61
- s.expires === undefined ||
62
- s.expires > now);
61
+ for (const session of this.sessions.values()) {
62
+ if (session.sessionId === (options === null || options === void 0 ? void 0 : options.except))
63
+ continue;
64
+ if (session.expires && session.expires < now) {
65
+ this.sessions.delete(session.sessionId);
66
+ }
67
+ }
63
68
  }
64
- findSession(sessionInfo) {
65
- const session = this.sessions.find(s => sessionInfosMatch(s.sessionInfo, sessionInfo));
66
- this.purgeExpired(sessionInfo);
67
- return session;
69
+ findSession(sessionId, sessionInfo) {
70
+ const session = this.sessions.get(sessionId);
71
+ if (session && sessionInfosMatch(session.sessionInfo, sessionInfo)) {
72
+ return session;
73
+ }
68
74
  }
69
75
  getExpires(ttl) {
70
76
  if (ttl instanceof Date) {
@@ -75,6 +81,13 @@ class SessionManager {
75
81
  return new Date(Date.now() + ttl.seconds * 1000);
76
82
  }
77
83
  }
84
+ newSessionId() {
85
+ let id;
86
+ do {
87
+ id = (0, uuid_1.v4)();
88
+ } while (this.sessions.has(id));
89
+ return id;
90
+ }
78
91
  newCompileModelSession(request, sessionInfo, options) {
79
92
  const expires = (options === null || options === void 0 ? void 0 : options.ttl) ? this.getExpires(options.ttl) : undefined;
80
93
  const state = Core.newCompileModelState(request);
@@ -83,6 +96,7 @@ class SessionManager {
83
96
  sessionInfo,
84
97
  state,
85
98
  expires,
99
+ sessionId: this.newSessionId(),
86
100
  };
87
101
  }
88
102
  newCompileSourceSession(request, sessionInfo, options) {
@@ -93,6 +107,7 @@ class SessionManager {
93
107
  sessionInfo,
94
108
  state,
95
109
  expires,
110
+ sessionId: this.newSessionId(),
96
111
  };
97
112
  }
98
113
  newCompileQuerySession(request, sessionInfo, options) {
@@ -103,16 +118,17 @@ class SessionManager {
103
118
  sessionInfo,
104
119
  state,
105
120
  expires,
121
+ sessionId: this.newSessionId(),
106
122
  };
107
123
  }
108
- findCompileModelSession(sessionInfo) {
109
- const session = this.findSession(sessionInfo);
124
+ findCompileModelSession(sessionId, sessionInfo) {
125
+ const session = this.findSession(sessionId, sessionInfo);
110
126
  if ((session === null || session === void 0 ? void 0 : session.type) === 'compile_model') {
111
127
  return session;
112
128
  }
113
129
  }
114
- killSession(sessionInfo) {
115
- this.sessions = this.sessions.filter(s => !sessionInfosMatch(s.sessionInfo, sessionInfo));
130
+ killSession(sessionId) {
131
+ this.sessions.delete(sessionId);
116
132
  }
117
133
  hasErrors(log) {
118
134
  var _a;
@@ -124,22 +140,27 @@ class SessionManager {
124
140
  modelURL: request.model_url,
125
141
  extendModelURL: request.extend_model_url,
126
142
  };
127
- let session = this.findCompileModelSession(sessionInfo);
143
+ let session = (options === null || options === void 0 ? void 0 : options.session_id) &&
144
+ this.findCompileModelSession(options.session_id, sessionInfo);
145
+ this.purgeExpired({ except: options === null || options === void 0 ? void 0 : options.session_id });
128
146
  if (session) {
147
+ if (options === null || options === void 0 ? void 0 : options.ttl) {
148
+ session.expires = this.getExpires(options.ttl);
149
+ }
129
150
  Core.updateCompileModelState(session.state, request.compiler_needs);
130
151
  }
131
152
  else {
132
153
  session = this.newCompileModelSession(request, sessionInfo, options);
133
- this.sessions.push(session);
154
+ this.sessions.set(session.sessionId, session);
134
155
  }
135
156
  const result = Core.statedCompileModel(session.state);
136
157
  if (result.model || this.hasErrors(result.logs)) {
137
- this.killSession(sessionInfo);
158
+ this.killSession(session.sessionId);
138
159
  }
139
- return result;
160
+ return { ...result, session_id: session.sessionId };
140
161
  }
141
- findCompileSourceSession(sessionInfo) {
142
- const session = this.findSession(sessionInfo);
162
+ findCompileSourceSession(sessionId, sessionInfo) {
163
+ const session = this.findSession(sessionId, sessionInfo);
143
164
  if ((session === null || session === void 0 ? void 0 : session.type) === 'compile_source') {
144
165
  return session;
145
166
  }
@@ -151,22 +172,27 @@ class SessionManager {
151
172
  name: request.name,
152
173
  extendModelURL: request.extend_model_url,
153
174
  };
154
- let session = this.findCompileSourceSession(sessionInfo);
175
+ let session = (options === null || options === void 0 ? void 0 : options.session_id) &&
176
+ this.findCompileSourceSession(options.session_id, sessionInfo);
177
+ this.purgeExpired({ except: options === null || options === void 0 ? void 0 : options.session_id });
155
178
  if (session) {
179
+ if (options === null || options === void 0 ? void 0 : options.ttl) {
180
+ session.expires = this.getExpires(options.ttl);
181
+ }
156
182
  Core.updateCompileModelState(session.state, request.compiler_needs);
157
183
  }
158
184
  else {
159
185
  session = this.newCompileSourceSession(request, sessionInfo, options);
160
- this.sessions.push(session);
186
+ this.sessions.set(session.sessionId, session);
161
187
  }
162
188
  const result = Core.statedCompileSource(session.state, request.name);
163
189
  if (result.source || this.hasErrors(result.logs)) {
164
- this.killSession(sessionInfo);
190
+ this.killSession(session.sessionId);
165
191
  }
166
- return result;
192
+ return { ...result, session_id: session.sessionId };
167
193
  }
168
- findCompileQuerySession(sessionInfo) {
169
- const session = this.findSession(sessionInfo);
194
+ findCompileQuerySession(sessionId, sessionInfo) {
195
+ const session = this.findSession(sessionId, sessionInfo);
170
196
  if ((session === null || session === void 0 ? void 0 : session.type) === 'compile_query') {
171
197
  return session;
172
198
  }
@@ -179,19 +205,24 @@ class SessionManager {
179
205
  queryString,
180
206
  query: request.query,
181
207
  };
182
- let session = this.findCompileQuerySession(sessionInfo);
208
+ let session = (options === null || options === void 0 ? void 0 : options.session_id) &&
209
+ this.findCompileQuerySession(options.session_id, sessionInfo);
210
+ this.purgeExpired({ except: options === null || options === void 0 ? void 0 : options.session_id });
183
211
  if (session) {
212
+ if (options === null || options === void 0 ? void 0 : options.ttl) {
213
+ session.expires = this.getExpires(options.ttl);
214
+ }
184
215
  Core.updateCompileModelState(session.state, request.compiler_needs);
185
216
  }
186
217
  else {
187
218
  session = this.newCompileQuerySession(request, sessionInfo, options);
188
- this.sessions.push(session);
219
+ this.sessions.set(session.sessionId, session);
189
220
  }
190
221
  const result = Core.statedCompileQuery(session.state);
191
222
  if (result.result || this.hasErrors(result.logs)) {
192
- this.killSession(sessionInfo);
223
+ this.killSession(session.sessionId);
193
224
  }
194
- return result;
225
+ return { ...result, session_id: session.sessionId };
195
226
  }
196
227
  }
197
228
  const SESSION_MANAGER = new SessionManager();
@@ -33,7 +33,7 @@ describe('api', () => {
33
33
  },
34
34
  ],
35
35
  },
36
- });
36
+ }, { session_id: result.session_id });
37
37
  expected = {
38
38
  compiler_needs: {
39
39
  table_schemas: [
@@ -44,6 +44,7 @@ describe('api', () => {
44
44
  ],
45
45
  connections: [{ name: 'connection' }],
46
46
  },
47
+ session_id: result.session_id,
47
48
  };
48
49
  expect(result).toMatchObject(expected);
49
50
  result = (0, sessioned_1.compileModel)({
@@ -66,7 +67,7 @@ describe('api', () => {
66
67
  ],
67
68
  connections: [{ name: 'connection', dialect: 'duckdb' }],
68
69
  },
69
- });
70
+ }, { session_id: result.session_id });
70
71
  expected = {
71
72
  model: {
72
73
  entries: [
@@ -86,6 +87,7 @@ describe('api', () => {
86
87
  ],
87
88
  anonymous_queries: [],
88
89
  },
90
+ session_id: result.session_id,
89
91
  };
90
92
  expect(result).toMatchObject(expected);
91
93
  });
@@ -135,7 +137,7 @@ describe('api', () => {
135
137
  ],
136
138
  connections: [{ name: 'connection', dialect: 'duckdb' }],
137
139
  },
138
- });
140
+ }, { session_id: result.session_id });
139
141
  expected = {
140
142
  source: {
141
143
  name: 'flights',
@@ -149,6 +151,7 @@ describe('api', () => {
149
151
  ],
150
152
  },
151
153
  },
154
+ session_id: result.session_id,
152
155
  };
153
156
  expect(result).toMatchObject(expected);
154
157
  });
@@ -197,7 +200,7 @@ describe('api', () => {
197
200
  },
198
201
  ],
199
202
  },
200
- });
203
+ }, { session_id: result.session_id });
201
204
  expected = {
202
205
  compiler_needs: {
203
206
  table_schemas: [
@@ -231,7 +234,7 @@ describe('api', () => {
231
234
  ],
232
235
  connections: [{ name: 'connection', dialect: 'duckdb' }],
233
236
  },
234
- });
237
+ }, { session_id: result.session_id });
235
238
  expected = {
236
239
  result: {
237
240
  connection_name: 'connection',
@@ -251,6 +254,7 @@ ORDER BY 1 asc NULLS LAST
251
254
  ],
252
255
  },
253
256
  },
257
+ session_id: result.session_id,
254
258
  };
255
259
  expect(result).toMatchObject(expected);
256
260
  });
@@ -264,6 +268,7 @@ ORDER BY 1 asc NULLS LAST
264
268
  // This is in the past...
265
269
  ttl: new Date(Date.now() - 1000),
266
270
  });
271
+ const session_id = result.session_id;
267
272
  let expected = {
268
273
  compiler_needs: {
269
274
  files: [
@@ -277,19 +282,72 @@ ORDER BY 1 asc NULLS LAST
277
282
  (0, sessioned_1.compileModel)({
278
283
  model_url: 'file://some_other_model.malloy',
279
284
  });
280
- result = (0, sessioned_1.compileModel)({
285
+ result = (0, sessioned_1.compileModel)({ model_url: 'file://test.malloy' }, { session_id });
286
+ expected = {
287
+ compiler_needs: {
288
+ files: [
289
+ {
290
+ url: 'file://test.malloy',
291
+ },
292
+ ],
293
+ },
294
+ };
295
+ expect(result).toMatchObject(expected);
296
+ // New session
297
+ expect(result.session_id).not.toBe(session_id);
298
+ });
299
+ test('ttl should be updated if set in a subsequent request', () => {
300
+ let result = (0, sessioned_1.compileModel)({
281
301
  model_url: 'file://test.malloy',
302
+ }, {
303
+ // This is in the past...
304
+ ttl: new Date(Date.now() - 1000),
282
305
  });
283
- expected = {
306
+ const session_id = result.session_id;
307
+ let expected = {
308
+ compiler_needs: {
309
+ files: [
310
+ {
311
+ url: 'file://test.malloy',
312
+ },
313
+ ],
314
+ },
315
+ };
316
+ expect(result).toMatchObject(expected);
317
+ result = (0, sessioned_1.compileModel)({
318
+ model_url: 'file://test.malloy',
284
319
  compiler_needs: {
285
320
  files: [
286
321
  {
287
322
  url: 'file://test.malloy',
323
+ contents: 'source: flights is connection.table("flights")',
324
+ },
325
+ ],
326
+ },
327
+ }, {
328
+ session_id,
329
+ // Update TTL to be far in the future
330
+ ttl: { seconds: 100000 },
331
+ });
332
+ expected = {
333
+ compiler_needs: {
334
+ table_schemas: [
335
+ {
336
+ connection_name: 'connection',
337
+ name: 'flights',
288
338
  },
289
339
  ],
340
+ connections: [{ name: 'connection' }],
290
341
  },
342
+ session_id,
291
343
  };
292
344
  expect(result).toMatchObject(expected);
345
+ // Now asking for a different file should NOT purge the original session
346
+ (0, sessioned_1.compileModel)({
347
+ model_url: 'file://some_other_model.malloy',
348
+ });
349
+ result = (0, sessioned_1.compileModel)({ model_url: 'file://test.malloy' }, { session_id });
350
+ expect(result).toMatchObject(expected);
293
351
  });
294
352
  });
295
353
  test('getting an error should kill session', () => {
@@ -333,10 +391,9 @@ ORDER BY 1 asc NULLS LAST
333
391
  },
334
392
  ],
335
393
  };
394
+ const session_id = result.session_id;
336
395
  expect(result).toMatchObject(expected);
337
- result = (0, sessioned_1.compileModel)({
338
- model_url: 'file://test.malloy',
339
- });
396
+ result = (0, sessioned_1.compileModel)({ model_url: 'file://test.malloy' }, { session_id });
340
397
  expected = {
341
398
  compiler_needs: {
342
399
  files: [
@@ -347,6 +404,8 @@ ORDER BY 1 asc NULLS LAST
347
404
  },
348
405
  };
349
406
  expect(result).toMatchObject(expected);
407
+ // Should be a new session
408
+ expect(result.session_id).not.toBe(session_id);
350
409
  });
351
410
  test('sessions should be cleared when they successfully return a result', () => {
352
411
  let result = (0, sessioned_1.compileModel)({
@@ -396,10 +455,9 @@ ORDER BY 1 asc NULLS LAST
396
455
  anonymous_queries: [],
397
456
  },
398
457
  };
458
+ const session_id = result.session_id;
399
459
  expect(result).toMatchObject(expected);
400
- result = (0, sessioned_1.compileModel)({
401
- model_url: 'file://test.malloy',
402
- });
460
+ result = (0, sessioned_1.compileModel)({ model_url: 'file://test.malloy' }, { session_id });
403
461
  // Compiler should not know the contents of this file anymore because the session was cleared
404
462
  expected = {
405
463
  compiler_needs: {
@@ -411,6 +469,7 @@ ORDER BY 1 asc NULLS LAST
411
469
  },
412
470
  };
413
471
  expect(result).toMatchObject(expected);
472
+ expect(result.session_id).not.toBe(session_id);
414
473
  });
415
474
  });
416
475
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.240-dev250305163014",
3
+ "version": "0.0.240-dev250305213425",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -41,8 +41,8 @@
41
41
  "generate-version-file": "VERSION=$(npm pkg get version --workspaces=false | tr -d \\\")\necho \"// generated with 'generate-version-file' script; do not edit manually\\nexport const MALLOY_VERSION = '$VERSION';\" > src/version.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@malloydata/malloy-interfaces": "^0.0.240-dev250305163014",
45
- "@malloydata/malloy-tag": "^0.0.240-dev250305163014",
44
+ "@malloydata/malloy-interfaces": "^0.0.240-dev250305213425",
45
+ "@malloydata/malloy-tag": "^0.0.240-dev250305213425",
46
46
  "antlr4ts": "^0.5.0-alpha.4",
47
47
  "assert": "^2.0.0",
48
48
  "jest-diff": "^29.6.2",