@malloydata/malloy 0.0.232-dev250118020814 → 0.0.232-dev250127221938

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/dist/index.d.ts CHANGED
@@ -4,10 +4,10 @@ export type { QueryDataRow, StructDef, TableSourceDef, SQLSourceDef, SourceDef,
4
4
  export { isSourceDef, Segment, isLeafAtomic, isJoined, isJoinedSource, isSamplingEnable, isSamplingPercent, isSamplingRows, isRepeatedRecord, isScalarArray, mkArrayDef, mkFieldDef, expressionIsAggregate, expressionIsAnalytic, expressionIsCalculation, expressionIsScalar, expressionIsUngroupedAggregate, indent, composeSQLExpr, } from './model';
5
5
  export { MalloyTranslator, } from './lang';
6
6
  export type { LogMessage, TranslateResponse } from './lang';
7
- export { Model, Malloy, Runtime, AtomicFieldType, ConnectionRuntime, SingleConnectionRuntime, EmptyURLReader, InMemoryURLReader, FixedConnectionMap, MalloyError, JoinRelationship, SourceRelationship, DateTimeframe, TimestampTimeframe, PreparedResult, Result, QueryMaterializer, CSVWriter, JSONWriter, Parse, DataWriter, Explore, } from './malloy';
8
- export type { PreparedQuery, Field, AtomicField, ExploreField, QueryField, SortableField, DataArray, DataRecord, DataColumn, DataArrayOrRecord, Loggable, ModelMaterializer, DocumentTablePath, DocumentSymbol, ResultJSON, PreparedResultMaterializer, ExploreMaterializer, WriteStream, SerializedExplore, DateField, TimestampField, } from './malloy';
7
+ export { Model, Malloy, Runtime, AtomicFieldType, ConnectionRuntime, SingleConnectionRuntime, EmptyURLReader, InMemoryURLReader, FixedConnectionMap, MalloyError, JoinRelationship, SourceRelationship, DateTimeframe, TimestampTimeframe, PreparedResult, Result, QueryMaterializer, CSVWriter, JSONWriter, Parse, DataWriter, Explore, InMemoryModelCache, CacheManager, } from './malloy';
8
+ export type { PreparedQuery, Field, AtomicField, ExploreField, QueryField, SortableField, DataArray, DataRecord, DataColumn, DataArrayOrRecord, Loggable, ModelMaterializer, DocumentTablePath, DocumentSymbol, ResultJSON, PreparedResultMaterializer, ExploreMaterializer, WriteStream, SerializedExplore, ModelCache, CachedModel, DateField, TimestampField, } from './malloy';
9
9
  export type { QueryOptionsReader, RunSQLOptions } from './run_sql_options';
10
- export type { EventStream, ModelString, ModelURL, QueryString, QueryURL, URLReader, } from './runtime_types';
10
+ export type { EventStream, ModelString, ModelURL, QueryString, QueryURL, URLReader, InvalidationKey, } from './runtime_types';
11
11
  export type { Connection, ConnectionConfig, ConnectionFactory, ConnectionParameter, ConnectionParameterValue, ConnectionConfigSchema, FetchSchemaOptions, InfoConnection, LookupConnection, PersistSQLResults, PooledConnection, TestableConnection, StreamingConnection, } from './connection/types';
12
12
  export { toAsyncGenerator } from './connection_utils';
13
13
  export { type TagParse, Tag, type TagDict } from './tags';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.InMemoryURLReader = exports.EmptyURLReader = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.AtomicFieldType = exports.Runtime = exports.Malloy = exports.Model = exports.MalloyTranslator = exports.composeSQLExpr = exports.indent = exports.expressionIsUngroupedAggregate = exports.expressionIsScalar = exports.expressionIsCalculation = exports.expressionIsAnalytic = exports.expressionIsAggregate = exports.mkFieldDef = exports.mkArrayDef = exports.isScalarArray = exports.isRepeatedRecord = exports.isSamplingRows = exports.isSamplingPercent = exports.isSamplingEnable = exports.isJoinedSource = exports.isJoined = exports.isLeafAtomic = exports.Segment = exports.isSourceDef = exports.TinyParser = exports.Dialect = exports.spread = exports.literal = exports.variadicParam = exports.param = exports.makeParam = exports.sql = exports.maxScalar = exports.minAggregate = exports.anyExprType = exports.minScalar = exports.overload = exports.qtz = exports.arg = exports.registerDialect = exports.MySQLDialect = exports.SnowflakeDialect = exports.PostgresDialect = exports.TrinoDialect = exports.StandardSQLDialect = exports.DuckDBDialect = void 0;
4
- exports.Tag = exports.toAsyncGenerator = exports.Explore = exports.DataWriter = exports.Parse = exports.JSONWriter = exports.CSVWriter = exports.QueryMaterializer = exports.Result = exports.PreparedResult = exports.TimestampTimeframe = exports.DateTimeframe = exports.SourceRelationship = exports.JoinRelationship = exports.MalloyError = exports.FixedConnectionMap = void 0;
4
+ exports.Tag = exports.toAsyncGenerator = exports.CacheManager = exports.InMemoryModelCache = exports.Explore = exports.DataWriter = exports.Parse = exports.JSONWriter = exports.CSVWriter = exports.QueryMaterializer = exports.Result = exports.PreparedResult = exports.TimestampTimeframe = exports.DateTimeframe = exports.SourceRelationship = exports.JoinRelationship = exports.MalloyError = exports.FixedConnectionMap = void 0;
5
5
  /*
6
6
  * Copyright 2023 Google LLC
7
7
  *
@@ -94,6 +94,8 @@ Object.defineProperty(exports, "JSONWriter", { enumerable: true, get: function (
94
94
  Object.defineProperty(exports, "Parse", { enumerable: true, get: function () { return malloy_1.Parse; } });
95
95
  Object.defineProperty(exports, "DataWriter", { enumerable: true, get: function () { return malloy_1.DataWriter; } });
96
96
  Object.defineProperty(exports, "Explore", { enumerable: true, get: function () { return malloy_1.Explore; } });
97
+ Object.defineProperty(exports, "InMemoryModelCache", { enumerable: true, get: function () { return malloy_1.InMemoryModelCache; } });
98
+ Object.defineProperty(exports, "CacheManager", { enumerable: true, get: function () { return malloy_1.CacheManager; } });
97
99
  var connection_utils_1 = require("./connection_utils");
98
100
  Object.defineProperty(exports, "toAsyncGenerator", { enumerable: true, get: function () { return connection_utils_1.toAsyncGenerator; } });
99
101
  var tags_1 = require("./tags");
package/dist/malloy.d.ts CHANGED
@@ -3,7 +3,7 @@ import { RunSQLOptions } from './run_sql_options';
3
3
  import { DocumentCompletion as DocumentCompletionDefinition, DocumentSymbol as DocumentSymbolDefinition, LogMessage, MalloyTranslator } from './lang';
4
4
  import { DocumentHelpContext } from './lang/parse-tree-walkers/document-help-context-walker';
5
5
  import { CompiledQuery, DocumentLocation, DocumentReference, BooleanFieldDef, JSONFieldDef, NumberFieldDef, StringFieldDef, FilterCondition, Query as InternalQuery, ModelDef, DocumentPosition as ModelDocumentPosition, NamedQuery, QueryData, QueryDataRow, QueryResult, SearchIndexResult, SearchValueMapResult, StructDef, TurtleDef, NativeUnsupportedFieldDef, QueryRunStats, ImportLocation, Annotation, SQLSentence, SQLSourceDef, AtomicFieldDef, DateFieldDef, TimestampFieldDef, SourceDef, QueryToMaterialize } from './model';
6
- import { EventStream, ModelString, ModelURL, QueryString, QueryURL, URLReader } from './runtime_types';
6
+ import { EventStream, InvalidationKey, ModelString, ModelURL, QueryString, QueryURL, URLReader } from './runtime_types';
7
7
  import { Connection, FetchSchemaOptions, InfoConnection, LookupConnection } from './connection/types';
8
8
  import { Tag, TagParse, TagParseSpec, Taggable } from './tags';
9
9
  import { Dialect } from './dialect';
@@ -30,6 +30,19 @@ interface CompileQueryOptions {
30
30
  eventStream?: EventStream;
31
31
  defaultRowLimit?: number;
32
32
  }
33
+ type Compilable = {
34
+ parse: Parse;
35
+ url?: undefined;
36
+ source?: undefined;
37
+ } | {
38
+ url: URL;
39
+ parse?: undefined;
40
+ source?: undefined;
41
+ } | {
42
+ source: string;
43
+ parse?: undefined;
44
+ url?: undefined;
45
+ };
33
46
  export declare class Malloy {
34
47
  static get version(): string;
35
48
  private static _parse;
@@ -68,13 +81,13 @@ export declare class Malloy {
68
81
  * @param model A compiled model to build upon (optional).
69
82
  * @return A (promise of a) compiled `Model`.
70
83
  */
71
- static compile({ urlReader, connections, parse, model, refreshSchemaCache, noThrowOnError, eventStream, replaceMaterializedReferences, materializedTablePrefix, }: {
84
+ static compile({ url, source, parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, replaceMaterializedReferences, materializedTablePrefix, importBaseURL, cacheManager, }: {
72
85
  urlReader: URLReader;
73
86
  connections: LookupConnection<InfoConnection>;
74
- parse: Parse;
75
87
  model?: Model;
76
88
  replaceMaterializedReferences?: boolean;
77
- } & CompileOptions & CompileQueryOptions): Promise<Model>;
89
+ cacheManager?: CacheManager;
90
+ } & Compilable & CompileOptions & CompileQueryOptions & ParseOptions): Promise<Model>;
78
91
  /**
79
92
  * A dialect must provide a response for every table, or the translator loop
80
93
  * will never exit. Because there was a time when this happened, we throw
@@ -273,7 +286,8 @@ export declare class PreparedQuery implements Taggable {
273
286
  */
274
287
  export declare class Parse {
275
288
  private translator;
276
- constructor(translator: MalloyTranslator);
289
+ private invalidationKey?;
290
+ constructor(translator: MalloyTranslator, invalidationKey?: InvalidationKey | undefined);
277
291
  /**
278
292
  * Retrieve the symbols defined in the parsed document.
279
293
  *
@@ -292,6 +306,7 @@ export declare class Parse {
292
306
  */
293
307
  get tablePathInfo(): DocumentTablePath[];
294
308
  get _translator(): MalloyTranslator;
309
+ get _invalidationKey(): InvalidationKey | undefined;
295
310
  completions(position: {
296
311
  line: number;
297
312
  character: number;
@@ -473,15 +488,24 @@ export declare class PreparedResult implements Taggable {
473
488
  * Useful for scenarios in which `import` statements are not required.
474
489
  */
475
490
  export declare class EmptyURLReader implements URLReader {
476
- readURL(_url: URL): Promise<string>;
491
+ readURL(_url: URL): Promise<{
492
+ contents: string;
493
+ invalidationKey: InvalidationKey;
494
+ }>;
495
+ getInvalidationKey(_url: URL): Promise<InvalidationKey>;
477
496
  }
478
497
  /**
479
498
  * A URL reader backed by an in-memory mapping of URL contents.
480
499
  */
481
500
  export declare class InMemoryURLReader implements URLReader {
482
- private files;
501
+ protected files: Map<string, string>;
483
502
  constructor(files: Map<string, string>);
484
- readURL(url: URL): Promise<string>;
503
+ readURL(url: URL): Promise<{
504
+ contents: string;
505
+ invalidationKey: InvalidationKey;
506
+ }>;
507
+ getInvalidationKey(url: URL): Promise<InvalidationKey>;
508
+ private invalidationKey;
485
509
  }
486
510
  /**
487
511
  * A fixed mapping of connection names to connections.
@@ -726,6 +750,13 @@ export declare class ExploreField extends Explore {
726
750
  get parentExplore(): Explore;
727
751
  get sourceClasses(): string[];
728
752
  }
753
+ type Connectionable = {
754
+ connection: Connection;
755
+ connections?: undefined;
756
+ } | {
757
+ connections: LookupConnection<Connection>;
758
+ connection?: undefined;
759
+ };
729
760
  /**
730
761
  * An environment for compiling and running Malloy queries.
731
762
  */
@@ -734,11 +765,16 @@ export declare class Runtime {
734
765
  private _urlReader;
735
766
  private _connections;
736
767
  private _eventStream;
737
- constructor(runtime: LookupConnection<Connection> & URLReader);
738
- constructor(urls: URLReader, connections: LookupConnection<Connection>, eventStream?: EventStream);
739
- constructor(urls: URLReader, connection: Connection, eventStream?: EventStream);
740
- constructor(connection: Connection, eventStream?: EventStream);
741
- constructor(connections: LookupConnection<Connection>, eventStream?: EventStream);
768
+ private _cacheManager;
769
+ constructor({ urlReader, connections, connection, eventStream, cacheManager, }: {
770
+ urlReader?: URLReader;
771
+ eventStream?: EventStream;
772
+ cacheManager?: CacheManager;
773
+ } & Connectionable);
774
+ /**
775
+ * @return The `CacheManager` for this runtime instance.
776
+ */
777
+ get cacheManager(): CacheManager | undefined;
742
778
  /**
743
779
  * @return The `URLReader` for this runtime instance.
744
780
  */
@@ -823,15 +859,19 @@ export declare class Runtime {
823
859
  }
824
860
  export declare class ConnectionRuntime extends Runtime {
825
861
  readonly rawConnections: Connection[];
826
- constructor(urls: URLReader, connections: Connection[]);
827
- constructor(connections: Connection[]);
862
+ constructor({ urlReader, connections, }: {
863
+ urlReader?: URLReader;
864
+ connections: Connection[];
865
+ });
828
866
  }
829
867
  export declare class SingleConnectionRuntime<T extends Connection = Connection> extends Runtime {
830
868
  readonly connection: T;
831
- constructor(urlReader: URLReader, connection: T);
832
- constructor(connection: T);
833
- constructor(connection: T, eventStream: EventStream);
834
- constructor(urlReader: URLReader, connection: T, eventStream: EventStream);
869
+ constructor({ urlReader, connection, eventStream, cacheManager, }: {
870
+ urlReader?: URLReader;
871
+ eventStream?: EventStream;
872
+ cacheManager?: CacheManager;
873
+ connection: T;
874
+ });
835
875
  get supportsNesting(): boolean;
836
876
  quote(column: string): string;
837
877
  get dialect(): Dialect;
@@ -1257,4 +1297,33 @@ export declare class CSVWriter extends DataWriter {
1257
1297
  private getRowMatrix;
1258
1298
  process(data: AsyncIterableIterator<DataRecord>): Promise<void>;
1259
1299
  }
1300
+ interface CacheGetModelDefResponse {
1301
+ modelDef: ModelDef;
1302
+ invalidationKeys: {
1303
+ [url: string]: InvalidationKey;
1304
+ };
1305
+ }
1306
+ export interface ModelCache {
1307
+ getModel(url: URL): Promise<CachedModel | undefined>;
1308
+ setModel(url: URL, cachedModel: CachedModel): Promise<boolean>;
1309
+ }
1310
+ export declare class CacheManager {
1311
+ private modelCache;
1312
+ private modelDependencies;
1313
+ private modelInvalidationKeys;
1314
+ constructor(modelCache: ModelCache);
1315
+ getCachedModelDef(urlReader: URLReader, url: string): Promise<CacheGetModelDefResponse | undefined>;
1316
+ setCachedModelDef(url: string, cachedModel: CachedModel): Promise<boolean>;
1317
+ }
1318
+ export interface CachedModel {
1319
+ modelDef: ModelDef;
1320
+ invalidationKeys: {
1321
+ [url: string]: InvalidationKey;
1322
+ };
1323
+ }
1324
+ export declare class InMemoryModelCache implements ModelCache {
1325
+ private readonly models;
1326
+ getModel(url: URL): Promise<CachedModel | undefined>;
1327
+ setModel(url: URL, cachedModel: CachedModel): Promise<boolean>;
1328
+ }
1260
1329
  export {};
package/dist/malloy.js CHANGED
@@ -22,20 +22,22 @@
22
22
  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.CSVWriter = exports.JSONWriter = exports.DataWriter = exports.DataRecord = exports.DataArray = exports.Result = exports.ExploreMaterializer = exports.PreparedResultMaterializer = exports.QueryMaterializer = exports.ModelMaterializer = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.Runtime = exports.ExploreField = exports.JoinRelationship = exports.QueryField = exports.Query = exports.StringField = exports.UnsupportedField = exports.JSONField = exports.BooleanField = exports.NumberField = exports.TimestampField = exports.DateField = exports.TimestampTimeframe = exports.DateTimeframe = exports.AtomicField = exports.AtomicFieldType = exports.Explore = exports.SourceRelationship = exports.FixedConnectionMap = exports.InMemoryURLReader = exports.EmptyURLReader = exports.PreparedResult = exports.DocumentCompletion = exports.DocumentSymbol = exports.DocumentPosition = exports.DocumentRange = exports.DocumentTablePath = exports.Parse = exports.PreparedQuery = exports.Model = exports.MalloyError = exports.Malloy = void 0;
25
+ exports.InMemoryModelCache = exports.CacheManager = exports.CSVWriter = exports.JSONWriter = exports.DataWriter = exports.DataRecord = exports.DataArray = exports.Result = exports.ExploreMaterializer = exports.PreparedResultMaterializer = exports.QueryMaterializer = exports.ModelMaterializer = exports.SingleConnectionRuntime = exports.ConnectionRuntime = exports.Runtime = exports.ExploreField = exports.JoinRelationship = exports.QueryField = exports.Query = exports.StringField = exports.UnsupportedField = exports.JSONField = exports.BooleanField = exports.NumberField = exports.TimestampField = exports.DateField = exports.TimestampTimeframe = exports.DateTimeframe = exports.AtomicField = exports.AtomicFieldType = exports.Explore = exports.SourceRelationship = exports.FixedConnectionMap = exports.InMemoryURLReader = exports.EmptyURLReader = exports.PreparedResult = exports.DocumentCompletion = exports.DocumentSymbol = exports.DocumentPosition = exports.DocumentRange = exports.DocumentTablePath = exports.Parse = exports.PreparedQuery = exports.Model = exports.MalloyError = exports.Malloy = void 0;
26
26
  const lang_1 = require("./lang");
27
27
  const model_1 = require("./model");
28
28
  const luxon_1 = require("luxon");
29
29
  const tags_1 = require("./tags");
30
30
  const dialect_1 = require("./dialect");
31
31
  const version_1 = require("./version");
32
+ const uuid_1 = require("uuid");
33
+ const MALLOY_INTERNAL_URL = 'internal://internal.malloy';
32
34
  class Malloy {
33
35
  static get version() {
34
36
  return version_1.MALLOY_VERSION;
35
37
  }
36
- static _parse(source, url, eventStream, options) {
38
+ static _parse(source, url, eventStream, options, invalidationKey) {
37
39
  if (url === undefined) {
38
- url = new URL('internal://internal.malloy');
40
+ url = new URL(MALLOY_INTERNAL_URL);
39
41
  }
40
42
  let importBaseURL = url;
41
43
  if (options === null || options === void 0 ? void 0 : options.importBaseURL) {
@@ -47,7 +49,7 @@ class Malloy {
47
49
  if (options === null || options === void 0 ? void 0 : options.testEnvironment) {
48
50
  translator.allDialectsEnabled = true;
49
51
  }
50
- return new Parse(translator);
52
+ return new Parse(translator, invalidationKey);
51
53
  }
52
54
  static parse({ url, urlReader, source, eventStream, options, }) {
53
55
  if (source !== undefined) {
@@ -60,8 +62,8 @@ class Malloy {
60
62
  if (url === undefined) {
61
63
  throw new Error('Internal Error: url is required if source not present.');
62
64
  }
63
- return urlReader.readURL(url).then(source => {
64
- return Malloy._parse(source, url, eventStream, options);
65
+ return readURL(urlReader, url).then(({ contents, invalidationKey }) => {
66
+ return Malloy._parse(contents, url, eventStream, options, invalidationKey);
65
67
  });
66
68
  }
67
69
  }
@@ -74,8 +76,8 @@ class Malloy {
74
76
  * @param model A compiled model to build upon (optional).
75
77
  * @return A (promise of a) compiled `Model`.
76
78
  */
77
- static async compile({ urlReader, connections, parse, model, refreshSchemaCache, noThrowOnError, eventStream, replaceMaterializedReferences, materializedTablePrefix, }) {
78
- var _a, _b, _c, _d;
79
+ static async compile({ url, source, parse, urlReader, connections, model, refreshSchemaCache, noThrowOnError, eventStream, replaceMaterializedReferences, materializedTablePrefix, importBaseURL, cacheManager, }) {
80
+ var _a, _b, _c, _d, _e;
79
81
  let refreshTimestamp;
80
82
  if (refreshSchemaCache) {
81
83
  refreshTimestamp =
@@ -83,12 +85,67 @@ class Malloy {
83
85
  ? refreshSchemaCache
84
86
  : Date.now();
85
87
  }
86
- const translator = parse._translator;
88
+ if (url === undefined && source === undefined && parse === undefined) {
89
+ throw new Error('Internal Error: url, source, or parse required.');
90
+ }
91
+ if (url === undefined) {
92
+ if (parse !== undefined) {
93
+ url = new URL(parse._translator.sourceURL);
94
+ }
95
+ else {
96
+ url = new URL(MALLOY_INTERNAL_URL);
97
+ }
98
+ }
99
+ const invalidationKeys = {};
100
+ // Before anything, if we have a URL and not source code, we check if that URL
101
+ // is cached.
102
+ if (source === undefined && cacheManager !== undefined) {
103
+ const cached = await cacheManager.getCachedModelDef(urlReader, url.toString());
104
+ if (cached) {
105
+ return new Model(cached.modelDef, [], // TODO when using a model from cache, should we also store the problems??
106
+ [url.toString(), ...flatDeps(cached.modelDef.dependencies)]
107
+ // TODO maybe implement referenceAt and importAt by re-translating the model?
108
+ );
109
+ }
110
+ }
111
+ importBaseURL !== null && importBaseURL !== void 0 ? importBaseURL : (importBaseURL = url);
112
+ let translator;
113
+ // It's not cached, so we may need to get the actual source
114
+ const _url = url.toString();
115
+ if (parse !== undefined) {
116
+ translator = parse._translator;
117
+ const invalidationKey = (_a = parse._invalidationKey) !== null && _a !== void 0 ? _a : (await getInvalidationKey(urlReader, url));
118
+ invalidationKeys[_url] = invalidationKey;
119
+ }
120
+ else {
121
+ if (source === undefined) {
122
+ const { contents, invalidationKey } = await readURL(urlReader, url);
123
+ invalidationKeys[_url] = invalidationKey;
124
+ source = contents;
125
+ }
126
+ else {
127
+ const invalidationKey = await getInvalidationKey(urlReader, url);
128
+ invalidationKeys[_url] = invalidationKey;
129
+ }
130
+ translator = new lang_1.MalloyTranslator(_url, importBaseURL.toString(), {
131
+ urls: { [_url]: source },
132
+ }, eventStream);
133
+ }
87
134
  for (;;) {
88
135
  const result = translator.translate(model === null || model === void 0 ? void 0 : model._modelDef);
89
136
  if (result.final) {
90
137
  if (result.modelDef) {
91
- return new Model(result.modelDef, result.problems || [], [...((_a = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _a !== void 0 ? _a : []), ...((_b = result.fromSources) !== null && _b !== void 0 ? _b : [])], (position) => translator.referenceAt(position), (position) => translator.importAt(position));
138
+ await (cacheManager === null || cacheManager === void 0 ? void 0 : cacheManager.setCachedModelDef(url.toString(), {
139
+ modelDef: result.modelDef,
140
+ invalidationKeys,
141
+ }));
142
+ for (const model of translator.newlyTranslatedDependencies()) {
143
+ await (cacheManager === null || cacheManager === void 0 ? void 0 : cacheManager.setCachedModelDef(model.url, {
144
+ modelDef: model.modelDef,
145
+ invalidationKeys,
146
+ }));
147
+ }
148
+ return new Model(result.modelDef, result.problems || [], [...((_b = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _b !== void 0 ? _b : []), ...((_c = result.fromSources) !== null && _c !== void 0 ? _c : [])], (position) => translator.referenceAt(position), (position) => translator.importAt(position));
92
149
  }
93
150
  else if (noThrowOnError) {
94
151
  const emptyModel = {
@@ -99,7 +156,7 @@ class Malloy {
99
156
  queryList: [],
100
157
  };
101
158
  const modelFromCompile = (model === null || model === void 0 ? void 0 : model._modelDef) || emptyModel;
102
- return new Model(modelFromCompile, result.problems || [], [...((_c = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _c !== void 0 ? _c : []), ...((_d = result.fromSources) !== null && _d !== void 0 ? _d : [])], (position) => translator.referenceAt(position), (position) => translator.importAt(position));
159
+ return new Model(modelFromCompile, result.problems || [], [...((_d = model === null || model === void 0 ? void 0 : model.fromSources) !== null && _d !== void 0 ? _d : []), ...((_e = result.fromSources) !== null && _e !== void 0 ? _e : [])], (position) => translator.referenceAt(position), (position) => translator.importAt(position));
103
160
  }
104
161
  else {
105
162
  const errors = result.problems || [];
@@ -113,11 +170,27 @@ class Malloy {
113
170
  if (result.urls) {
114
171
  for (const neededUrl of result.urls) {
115
172
  try {
116
- if (neededUrl.startsWith('internal://')) {
173
+ if (isInternalURL(neededUrl)) {
117
174
  throw new Error('In order to use relative imports, you must compile a file via a URL.');
118
175
  }
119
- const neededText = await urlReader.readURL(new URL(neededUrl));
120
- const urls = { [neededUrl]: neededText };
176
+ // First, check the cache
177
+ if (cacheManager !== undefined) {
178
+ const cached = await cacheManager.getCachedModelDef(urlReader, neededUrl);
179
+ if (cached) {
180
+ for (const dependency in cached.invalidationKeys) {
181
+ invalidationKeys[dependency] =
182
+ cached.invalidationKeys[dependency];
183
+ }
184
+ translator.update({
185
+ translations: { [neededUrl]: cached.modelDef },
186
+ });
187
+ continue;
188
+ }
189
+ }
190
+ // Otherwise, fetch the URL contents
191
+ const { contents, invalidationKey } = await readURL(urlReader, new URL(neededUrl));
192
+ const urls = { [neededUrl]: contents };
193
+ invalidationKeys[neededUrl] = invalidationKey;
121
194
  translator.update({ urls });
122
195
  }
123
196
  catch (error) {
@@ -548,8 +621,9 @@ exports.PreparedQuery = PreparedQuery;
548
621
  * A parsed Malloy document.
549
622
  */
550
623
  class Parse {
551
- constructor(translator) {
624
+ constructor(translator, invalidationKey) {
552
625
  this.translator = translator;
626
+ this.invalidationKey = invalidationKey;
553
627
  }
554
628
  /**
555
629
  * Retrieve the symbols defined in the parsed document.
@@ -577,6 +651,9 @@ class Parse {
577
651
  get _translator() {
578
652
  return this.translator;
579
653
  }
654
+ get _invalidationKey() {
655
+ return this.invalidationKey;
656
+ }
580
657
  completions(position) {
581
658
  return (this.translator.completions(position).completions || []).map(completion => new DocumentCompletion(completion));
582
659
  }
@@ -846,6 +923,9 @@ class EmptyURLReader {
846
923
  async readURL(_url) {
847
924
  throw new Error('No files.');
848
925
  }
926
+ async getInvalidationKey(_url) {
927
+ throw new Error('No files.');
928
+ }
849
929
  }
850
930
  exports.EmptyURLReader = EmptyURLReader;
851
931
  /**
@@ -858,12 +938,30 @@ class InMemoryURLReader {
858
938
  async readURL(url) {
859
939
  const file = this.files.get(url.toString());
860
940
  if (file !== undefined) {
861
- return Promise.resolve(file);
941
+ return Promise.resolve({
942
+ contents: file,
943
+ invalidationKey: this.invalidationKey(url, file),
944
+ });
945
+ }
946
+ else {
947
+ throw new Error(`File not found '${url}'`);
948
+ }
949
+ }
950
+ async getInvalidationKey(url) {
951
+ const file = this.files.get(url.toString());
952
+ if (file !== undefined) {
953
+ return Promise.resolve(this.invalidationKey(url, file));
862
954
  }
863
955
  else {
864
956
  throw new Error(`File not found '${url}'`);
865
957
  }
866
958
  }
959
+ invalidationKey(url, contents) {
960
+ if (isInternalURL(url.toString())) {
961
+ return null;
962
+ }
963
+ return hashForInvalidationKey(contents);
964
+ }
867
965
  }
868
966
  exports.InMemoryURLReader = InMemoryURLReader;
869
967
  /**
@@ -1560,39 +1658,29 @@ exports.ExploreField = ExploreField;
1560
1658
  * An environment for compiling and running Malloy queries.
1561
1659
  */
1562
1660
  class Runtime {
1563
- constructor(...args) {
1661
+ constructor({ urlReader, connections, connection, eventStream, cacheManager, }) {
1564
1662
  this.isTestRuntime = false;
1565
- let urlReader;
1566
- let connections;
1567
- let eventStream;
1568
- for (const arg of args) {
1569
- if (arg === undefined) {
1570
- continue;
1571
- }
1572
- else if (isURLReader(arg)) {
1573
- urlReader = arg;
1574
- }
1575
- else if (isLookupConnection(arg)) {
1576
- connections = arg;
1577
- }
1578
- else if (isEventStream(arg)) {
1579
- eventStream = arg;
1580
- }
1581
- else {
1582
- connections = {
1583
- lookupConnection: () => Promise.resolve(arg),
1584
- };
1663
+ if (connections === undefined) {
1664
+ if (connection === undefined) {
1665
+ throw new Error('A LookupConnection<Connection> or Connection is required.');
1585
1666
  }
1667
+ connections = {
1668
+ lookupConnection: () => Promise.resolve(connection),
1669
+ };
1586
1670
  }
1587
1671
  if (urlReader === undefined) {
1588
1672
  urlReader = new EmptyURLReader();
1589
1673
  }
1590
- if (connections === undefined) {
1591
- throw new Error('A LookupConnection<Connection> or Connection is required.');
1592
- }
1593
1674
  this._urlReader = urlReader;
1594
1675
  this._connections = connections;
1595
1676
  this._eventStream = eventStream;
1677
+ this._cacheManager = cacheManager;
1678
+ }
1679
+ /**
1680
+ * @return The `CacheManager` for this runtime instance.
1681
+ */
1682
+ get cacheManager() {
1683
+ return this._cacheManager;
1596
1684
  }
1597
1685
  /**
1598
1686
  * @return The `URLReader` for this runtime instance.
@@ -1629,28 +1717,20 @@ class Runtime {
1629
1717
  options = { ...options, testEnvironment: true };
1630
1718
  }
1631
1719
  }
1720
+ const compilable = source instanceof URL ? { url: source } : { source };
1632
1721
  return new ModelMaterializer(this, async () => {
1633
- const parse = source instanceof URL
1634
- ? await Malloy.parse({
1635
- url: source,
1636
- urlReader: this.urlReader,
1637
- eventStream: this.eventStream,
1638
- options,
1639
- })
1640
- : Malloy.parse({
1641
- source,
1642
- eventStream: this.eventStream,
1643
- options,
1644
- });
1645
1722
  return Malloy.compile({
1723
+ ...compilable,
1646
1724
  urlReader: this.urlReader,
1647
1725
  connections: this.connections,
1648
- parse,
1649
1726
  refreshSchemaCache,
1650
1727
  noThrowOnError,
1651
1728
  eventStream: this.eventStream,
1652
1729
  replaceMaterializedReferences: options === null || options === void 0 ? void 0 : options.replaceMaterializedReferences,
1653
1730
  materializedTablePrefix: options === null || options === void 0 ? void 0 : options.materializedTablePrefix,
1731
+ importBaseURL: options === null || options === void 0 ? void 0 : options.importBaseURL,
1732
+ testEnvironment: options === null || options === void 0 ? void 0 : options.testEnvironment,
1733
+ cacheManager: this.cacheManager,
1654
1734
  });
1655
1735
  }, options);
1656
1736
  }
@@ -1741,40 +1821,23 @@ class Runtime {
1741
1821
  }
1742
1822
  exports.Runtime = Runtime;
1743
1823
  class ConnectionRuntime extends Runtime {
1744
- constructor(urlsOrConnections, maybeConnections) {
1745
- if (maybeConnections === undefined) {
1746
- const connections = urlsOrConnections;
1747
- super(FixedConnectionMap.fromArray(connections));
1748
- this.rawConnections = connections;
1749
- }
1750
- else {
1751
- const connections = maybeConnections;
1752
- super(urlsOrConnections, FixedConnectionMap.fromArray(connections));
1753
- this.rawConnections = connections;
1754
- }
1824
+ constructor({ urlReader, connections, }) {
1825
+ super({
1826
+ connections: FixedConnectionMap.fromArray(connections),
1827
+ urlReader,
1828
+ });
1829
+ this.rawConnections = connections;
1755
1830
  }
1756
1831
  }
1757
1832
  exports.ConnectionRuntime = ConnectionRuntime;
1758
1833
  class SingleConnectionRuntime extends Runtime {
1759
- constructor(...params) {
1760
- let urlReader;
1761
- let connection;
1762
- let eventStream;
1763
- for (const param of params) {
1764
- if (isURLReader(param)) {
1765
- urlReader = param;
1766
- }
1767
- if (isConnection(param)) {
1768
- connection = param;
1769
- }
1770
- if (isEventStream(param)) {
1771
- eventStream = param;
1772
- }
1773
- }
1774
- if (connection === undefined) {
1775
- throw new Error('Expected connection to be passed into SingleConnectionRuntime');
1776
- }
1777
- super(urlReader, connection, eventStream);
1834
+ constructor({ urlReader, connection, eventStream, cacheManager, }) {
1835
+ super({
1836
+ urlReader,
1837
+ eventStream,
1838
+ cacheManager,
1839
+ connection,
1840
+ });
1778
1841
  this.connection = connection;
1779
1842
  }
1780
1843
  get supportsNesting() {
@@ -1892,24 +1955,17 @@ class ModelMaterializer extends FluentState {
1892
1955
  options = { ...options, testEnvironment: true };
1893
1956
  }
1894
1957
  }
1895
- const parse = query instanceof URL
1896
- ? await Malloy.parse({
1897
- url: query,
1898
- urlReader,
1899
- options,
1900
- })
1901
- : Malloy.parse({
1902
- source: query,
1903
- options,
1904
- });
1958
+ const compilable = query instanceof URL ? { url: query } : { source: query };
1905
1959
  const model = await this.getModel();
1906
1960
  const queryModel = await Malloy.compile({
1961
+ ...compilable,
1907
1962
  urlReader,
1908
1963
  connections,
1909
- parse,
1910
1964
  model,
1911
1965
  refreshSchemaCache,
1912
1966
  noThrowOnError,
1967
+ importBaseURL: options === null || options === void 0 ? void 0 : options.importBaseURL,
1968
+ testEnvironment: options === null || options === void 0 ? void 0 : options.testEnvironment,
1913
1969
  ...this.compileQueryOptions,
1914
1970
  });
1915
1971
  return queryModel.preparedQuery;
@@ -1934,24 +1990,17 @@ class ModelMaterializer extends FluentState {
1934
1990
  return new ModelMaterializer(this.runtime, async () => {
1935
1991
  const urlReader = this.runtime.urlReader;
1936
1992
  const connections = this.runtime.connections;
1937
- const parse = query instanceof URL
1938
- ? await Malloy.parse({
1939
- url: query,
1940
- urlReader,
1941
- options,
1942
- })
1943
- : Malloy.parse({
1944
- source: query,
1945
- options,
1946
- });
1993
+ const compilable = query instanceof URL ? { url: query } : { source: query };
1947
1994
  const model = await this.getModel();
1948
1995
  const queryModel = await Malloy.compile({
1996
+ ...compilable,
1949
1997
  urlReader,
1950
1998
  connections,
1951
- parse,
1952
1999
  model,
1953
2000
  refreshSchemaCache: options === null || options === void 0 ? void 0 : options.refreshSchemaCache,
1954
2001
  noThrowOnError: options === null || options === void 0 ? void 0 : options.noThrowOnError,
2002
+ importBaseURL: options === null || options === void 0 ? void 0 : options.importBaseURL,
2003
+ testEnvironment: options === null || options === void 0 ? void 0 : options.testEnvironment,
1955
2004
  ...this.compileQueryOptions,
1956
2005
  });
1957
2006
  return queryModel;
@@ -2790,18 +2839,6 @@ class DataRecord extends Data {
2790
2839
  }
2791
2840
  }
2792
2841
  exports.DataRecord = DataRecord;
2793
- function isURLReader(thing) {
2794
- return 'readURL' in thing;
2795
- }
2796
- function isLookupConnection(thing) {
2797
- return 'lookupConnection' in thing;
2798
- }
2799
- function isEventStream(thing) {
2800
- return 'emit' in thing;
2801
- }
2802
- function isConnection(thing) {
2803
- return 'runSQL' in thing;
2804
- }
2805
2842
  class DataWriter {
2806
2843
  constructor(stream) {
2807
2844
  this.stream = stream;
@@ -3010,4 +3047,115 @@ class CSVWriter extends DataWriter {
3010
3047
  }
3011
3048
  }
3012
3049
  exports.CSVWriter = CSVWriter;
3050
+ class CacheManager {
3051
+ constructor(modelCache) {
3052
+ this.modelCache = modelCache;
3053
+ this.modelDependencies = new Map();
3054
+ this.modelInvalidationKeys = new Map();
3055
+ }
3056
+ async getCachedModelDef(urlReader, url) {
3057
+ const _dependencies = this.modelDependencies.get(url);
3058
+ if (_dependencies === undefined) {
3059
+ return undefined;
3060
+ }
3061
+ const dependencies = [url, ...flatDeps(_dependencies)];
3062
+ const invalidationKeys = {};
3063
+ for (const dependency of dependencies) {
3064
+ const invalidationKey = this.modelInvalidationKeys.get(dependency);
3065
+ if (invalidationKey === undefined || invalidationKey === null) {
3066
+ return undefined;
3067
+ }
3068
+ invalidationKeys[dependency] = invalidationKey;
3069
+ }
3070
+ for (const dependency of dependencies) {
3071
+ const invalidationKey = await getInvalidationKey(urlReader, new URL(dependency));
3072
+ if (invalidationKey !== invalidationKeys[dependency]) {
3073
+ return undefined;
3074
+ }
3075
+ }
3076
+ const cached = await this.modelCache.getModel(new URL(url));
3077
+ if (cached === undefined) {
3078
+ return undefined;
3079
+ }
3080
+ for (const dependency of dependencies) {
3081
+ if (cached.invalidationKeys[dependency] !== invalidationKeys[dependency]) {
3082
+ return undefined;
3083
+ }
3084
+ }
3085
+ // Return the cached model def and the invalidation keys for this
3086
+ // model def's dependencies
3087
+ return { modelDef: cached.modelDef, invalidationKeys };
3088
+ }
3089
+ async setCachedModelDef(url, cachedModel) {
3090
+ this.modelDependencies.set(url, cachedModel.modelDef.dependencies);
3091
+ const invalidationKeys = {};
3092
+ for (const dependency of [
3093
+ url,
3094
+ ...flatDeps(cachedModel.modelDef.dependencies),
3095
+ ]) {
3096
+ if (cachedModel.invalidationKeys[dependency] === null) {
3097
+ return false;
3098
+ }
3099
+ if (cachedModel.invalidationKeys[dependency] === undefined) {
3100
+ throw new Error(`Missing invalidation key for dependency ${dependency}`);
3101
+ }
3102
+ this.modelInvalidationKeys.set(dependency, cachedModel.invalidationKeys[dependency]);
3103
+ invalidationKeys[dependency] = cachedModel.invalidationKeys[dependency];
3104
+ }
3105
+ const result = await this.modelCache.setModel(new URL(url), {
3106
+ modelDef: cachedModel.modelDef,
3107
+ invalidationKeys,
3108
+ });
3109
+ if (result) {
3110
+ return true; // TODO just return `result` when it's a boolean
3111
+ }
3112
+ return false;
3113
+ }
3114
+ }
3115
+ exports.CacheManager = CacheManager;
3116
+ function flatDeps(tree) {
3117
+ return [...Object.keys(tree), ...Object.values(tree).map(flatDeps).flat()];
3118
+ }
3119
+ // TODO maybe make this memory bounded....
3120
+ class InMemoryModelCache {
3121
+ constructor() {
3122
+ this.models = new Map();
3123
+ }
3124
+ async getModel(url) {
3125
+ return Promise.resolve(this.models.get(url.toString()));
3126
+ }
3127
+ async setModel(url, cachedModel) {
3128
+ this.models.set(url.toString(), cachedModel);
3129
+ return Promise.resolve(true);
3130
+ }
3131
+ }
3132
+ exports.InMemoryModelCache = InMemoryModelCache;
3133
+ function hashForInvalidationKey(input) {
3134
+ const MALLOY_UUID = '76c17e9d-f3ce-5f2d-bfde-98ad3d2a37f6';
3135
+ return (0, uuid_1.v5)(input, MALLOY_UUID);
3136
+ }
3137
+ async function readURL(urlReader, url) {
3138
+ const result = await urlReader.readURL(url);
3139
+ const { contents, invalidationKey } = typeof result === 'string'
3140
+ ? { contents: result, invalidationKey: undefined }
3141
+ : result;
3142
+ return {
3143
+ contents,
3144
+ invalidationKey: isInternalURL(url.toString())
3145
+ ? null
3146
+ : invalidationKey !== null && invalidationKey !== void 0 ? invalidationKey : hashForInvalidationKey(contents),
3147
+ };
3148
+ }
3149
+ async function getInvalidationKey(urlReader, url) {
3150
+ if (isInternalURL(url.toString())) {
3151
+ return null;
3152
+ }
3153
+ if (urlReader.getInvalidationKey !== undefined) {
3154
+ return await urlReader.getInvalidationKey(url);
3155
+ }
3156
+ return (await readURL(urlReader, url)).invalidationKey;
3157
+ }
3158
+ function isInternalURL(url) {
3159
+ return url.startsWith('internal://');
3160
+ }
3013
3161
  //# sourceMappingURL=malloy.js.map
@@ -14,6 +14,7 @@ export type ModelURL = URL;
14
14
  * A URL whose contents is a Malloy query.
15
15
  */
16
16
  export type QueryURL = URL;
17
+ export type InvalidationKey = string | number | Date | null;
17
18
  /**
18
19
  * An object capable of reading the contents of a URL in some context.
19
20
  */
@@ -24,7 +25,11 @@ export interface URLReader {
24
25
  * @param url The URL to read.
25
26
  * @return A promise to the contents of the URL.
26
27
  */
27
- readURL: (url: URL) => Promise<string>;
28
+ readURL: (url: URL) => Promise<string | {
29
+ contents: string;
30
+ invalidationKey?: InvalidationKey;
31
+ }>;
32
+ getInvalidationKey?: (url: URL) => Promise<InvalidationKey>;
28
33
  }
29
34
  export interface EventStream {
30
35
  emit(id: string, data: any): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.232-dev250118020814",
3
+ "version": "0.0.232-dev250127221938",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",