@malloydata/malloy 0.0.343 → 0.0.344

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/api/core.js CHANGED
@@ -500,7 +500,8 @@ function statedCompileQuery(state) {
500
500
  source = query.structRef;
501
501
  }
502
502
  else {
503
- source = result.modelDef.contents[query.structRef];
503
+ // TODO: `as StructDef` cast is pre-existing type-safety issue — contents holds NamedModelObject
504
+ source = (0, model_1.safeRecordGet)(result.modelDef.contents, query.structRef);
504
505
  }
505
506
  }
506
507
  const sourceAnnotations = (0, annotation_1.annotationToTaglines)(source.annotation).map(l => ({
@@ -1,5 +1,5 @@
1
1
  import type { LogMessage } from '../../lang';
2
- import type { BuildID, CompiledQuery, DocumentLocation, BooleanFieldDef, JSONFieldDef, NumberFieldDef, StringFieldDef, FilterCondition, Query as InternalQuery, ModelDef, DocumentPosition as ModelDocumentPosition, NamedQueryDef, StructDef, TurtleDef, NativeUnsupportedFieldDef, ImportLocation, Annotation, AtomicFieldDef, DateFieldDef, ATimestampFieldDef, SourceDef, Argument, SourceComponentInfo, DocumentReference, PersistableSourceDef } from '../../model';
2
+ import type { BuildID, CompiledQuery, DocumentLocation, BooleanFieldDef, JSONFieldDef, NumberFieldDef, StringFieldDef, FilterCondition, Query as InternalQuery, ModelDef, DocumentPosition as ModelDocumentPosition, NamedQueryDef, StructDef, TurtleDef, NativeUnsupportedFieldDef, ImportLocation, Annotation, NamedModelObject, AtomicFieldDef, DateFieldDef, ATimestampFieldDef, SourceDef, Argument, SourceComponentInfo, DocumentReference, PersistableSourceDef } from '../../model';
3
3
  import { QueryModel } from '../../model';
4
4
  import type { Dialect } from '../../dialect';
5
5
  import type { BuildGraph, CompileQueryOptions } from './types';
@@ -235,7 +235,10 @@ export declare class Model implements Taggable {
235
235
  readonly fromSources: string[];
236
236
  private readonly references;
237
237
  private _queryModel?;
238
+ private readonly contentsMap;
238
239
  constructor(modelDef: ModelDef, problems: LogMessage[], fromSources: string[], existingQueryModel?: QueryModel);
240
+ /** Safe lookup in model contents by name. */
241
+ getContent(name: string): NamedModelObject | undefined;
239
242
  get queryModel(): QueryModel;
240
243
  /**
241
244
  * Returns the cached QueryModel if it exists, without creating one.
@@ -750,6 +750,11 @@ class Model {
750
750
  this.fromSources = fromSources;
751
751
  this.references = new reference_list_1.ReferenceList((_a = fromSources[0]) !== null && _a !== void 0 ? _a : '', (_b = modelDef.references) !== null && _b !== void 0 ? _b : []);
752
752
  this._queryModel = existingQueryModel;
753
+ this.contentsMap = new Map(Object.entries(modelDef.contents));
754
+ }
755
+ /** Safe lookup in model contents by name. */
756
+ getContent(name) {
757
+ return this.contentsMap.get(name);
753
758
  }
754
759
  get queryModel() {
755
760
  if (!this._queryModel) {
@@ -799,7 +804,7 @@ class Model {
799
804
  * @return A prepared query.
800
805
  */
801
806
  getPreparedQueryByName(queryName) {
802
- const query = this.modelDef.contents[queryName];
807
+ const query = this.getContent(queryName);
803
808
  if ((query === null || query === void 0 ? void 0 : query.type) === 'query') {
804
809
  return new PreparedQuery(query, this, this.problems, queryName);
805
810
  }
@@ -846,7 +851,7 @@ class Model {
846
851
  * @return An `Explore`.
847
852
  */
848
853
  getExploreByName(name) {
849
- const struct = this.modelDef.contents[name];
854
+ const struct = this.getContent(name);
850
855
  if (struct && (0, model_1.isSourceDef)(struct)) {
851
856
  return new Explore(struct);
852
857
  }
@@ -1129,9 +1134,9 @@ class PreparedQuery {
1129
1134
  get dialect() {
1130
1135
  const sourceRef = this._query.structRef;
1131
1136
  const source = typeof sourceRef === 'string'
1132
- ? this._modelDef.contents[sourceRef]
1137
+ ? this._model.getContent(sourceRef)
1133
1138
  : sourceRef;
1134
- if (!(0, model_1.isSourceDef)(source)) {
1139
+ if (source === undefined || !(0, model_1.isSourceDef)(source)) {
1135
1140
  throw new Error('Invalid source for query');
1136
1141
  }
1137
1142
  return source.dialect;
@@ -1221,7 +1226,7 @@ class PreparedResult {
1221
1226
  }
1222
1227
  get sourceExplore() {
1223
1228
  const name = this.inner.sourceExplore;
1224
- const explore = this.modelDef.contents[name];
1229
+ const explore = (0, model_1.safeRecordGet)(this.modelDef.contents, name);
1225
1230
  if (explore && (0, model_1.isSourceDef)(explore)) {
1226
1231
  return new Explore(explore);
1227
1232
  }
@@ -572,7 +572,7 @@ class QueryMaterializer extends FluentState {
572
572
  }
573
573
  const plan = preparedQuery.model.getBuildPlan();
574
574
  const connectionNames = new Set(Object.values(plan.sources).map(s => s.connectionName));
575
- connectionDigests = {};
575
+ connectionDigests = (0, model_1.mkSafeRecord)();
576
576
  for (const connName of connectionNames) {
577
577
  const conn = await this.runtime.connections.lookupConnection(connName);
578
578
  connectionDigests[connName] = conn.getDigest();
@@ -515,12 +515,12 @@ ${(0, utils_1.indent)(sql)}
515
515
  return sqlType.match(/^[A-Za-z\s(),[\]0-9]*$/) !== null;
516
516
  }
517
517
  sqlLiteralRecord(lit) {
518
- var _a, _b;
518
+ var _a, _b, _c;
519
519
  const rowVals = [];
520
520
  for (const f of lit.typeDef.fields) {
521
521
  const name = (_a = f.as) !== null && _a !== void 0 ? _a : f.name;
522
522
  const propName = `'${name}'`;
523
- const propVal = (_b = lit.kids[name].sql) !== null && _b !== void 0 ? _b : 'internal-error-record-literal';
523
+ const propVal = (_c = (_b = (0, malloy_types_1.safeRecordGet)(lit.kids, name)) === null || _b === void 0 ? void 0 : _b.sql) !== null && _c !== void 0 ? _c : 'internal-error-record-literal';
524
524
  rowVals.push(`${propName},${propVal}`);
525
525
  }
526
526
  return `OBJECT_CONSTRUCT_KEEP_NULL(${rowVals.join(',')})`;
@@ -594,13 +594,13 @@ ${(0, utils_1.indent)(sql)}
594
594
  return from.units === 'day_of_week' ? `mod(${extracted}+1,7)` : extracted;
595
595
  }
596
596
  sqlLiteralRecord(lit) {
597
- var _a, _b;
597
+ var _a, _b, _c;
598
598
  const rowVals = [];
599
599
  const rowTypes = [];
600
600
  for (const f of lit.typeDef.fields) {
601
601
  if ((0, malloy_types_1.isAtomic)(f)) {
602
602
  const name = (_a = f.as) !== null && _a !== void 0 ? _a : f.name;
603
- rowVals.push((_b = lit.kids[name].sql) !== null && _b !== void 0 ? _b : 'internal-error-record-literal');
603
+ rowVals.push((_c = (_b = (0, malloy_types_1.safeRecordGet)(lit.kids, name)) === null || _b === void 0 ? void 0 : _b.sql) !== null && _c !== void 0 ? _c : 'internal-error-record-literal');
604
604
  const elType = this.malloyTypeToSQLType(f);
605
605
  rowTypes.push(`${this.sqlMaybeQuoteIdentifier(name)} ${elType}`);
606
606
  }
@@ -57,6 +57,7 @@ var __importStar = (this && this.__importStar) || (function () {
57
57
  Object.defineProperty(exports, "__esModule", { value: true });
58
58
  exports.DynamicSpace = void 0;
59
59
  const model = __importStar(require("../../../model/malloy_types"));
60
+ const malloy_types_1 = require("../../../model/malloy_types");
60
61
  const field_utils_1 = require("../../field-utils");
61
62
  const error_factory_1 = require("../error-factory");
62
63
  const space_field_1 = require("../types/space-field");
@@ -116,7 +117,7 @@ class DynamicSpace extends static_space_1.StaticSpace {
116
117
  if (this.sourceDef === undefined) {
117
118
  // Grab all the parameters so that we can populate the "final" structDef
118
119
  // with parameters immediately so that views can see them when they are translating
119
- const parameters = {};
120
+ const parameters = (0, malloy_types_1.mkSafeRecord)();
120
121
  for (const [name, entry] of this.entries()) {
121
122
  if (entry instanceof space_param_1.SpaceParam) {
122
123
  parameters[name] = entry.parameter();
@@ -76,7 +76,7 @@ class IndexFieldSpace extends query_spaces_1.QueryOperationSpace {
76
76
  for (const [name, field] of this.entries()) {
77
77
  if (field instanceof space_field_1.SpaceField) {
78
78
  let nextFieldUsage = undefined;
79
- const wild = this.expandedWild[name];
79
+ const wild = this.expandedWild.get(name);
80
80
  if (wild) {
81
81
  indexFields.push({ type: 'fieldref', path: wild.path, at: wild.at });
82
82
  fieldUsage.push({ path: wild.path });
@@ -104,7 +104,7 @@ class IndexFieldSpace extends query_spaces_1.QueryOperationSpace {
104
104
  }
105
105
  addRefineFromFields(_refineThis) { }
106
106
  addWild(wild) {
107
- var _a;
107
+ var _a, _b;
108
108
  let current = this.exprSpace;
109
109
  const joinPath = [];
110
110
  if (wild.joinPath) {
@@ -142,7 +142,7 @@ class IndexFieldSpace extends query_spaces_1.QueryOperationSpace {
142
142
  name,
143
143
  ]);
144
144
  if (this.entry(indexName)) {
145
- const conflict = (_a = this.expandedWild[indexName].path) === null || _a === void 0 ? void 0 : _a.join('.');
145
+ const conflict = (_b = (_a = this.expandedWild.get(indexName)) === null || _a === void 0 ? void 0 : _a.path) === null || _b === void 0 ? void 0 : _b.join('.');
146
146
  wild.logError('name-conflict-in-wildcard-expansion', `Cannot expand '${name}' in '${wild.refString}' because a field with that name already exists${conflict ? ` (conflicts with ${conflict})` : ''}`);
147
147
  }
148
148
  else {
@@ -152,11 +152,11 @@ class IndexFieldSpace extends query_spaces_1.QueryOperationSpace {
152
152
  (0, malloy_types_1.expressionIsScalar)(eTypeDesc.expressionType) &&
153
153
  (dialect === undefined || !dialect.ignoreInProject(name))) {
154
154
  expandEntries.push({ name: indexName, entry });
155
- this.expandedWild[indexName] = {
155
+ this.expandedWild.set(indexName, {
156
156
  path: joinPath.concat(name),
157
157
  entry,
158
158
  at: wild.location,
159
- };
159
+ });
160
160
  }
161
161
  }
162
162
  }
@@ -11,9 +11,9 @@ const space_param_1 = require("../types/space-param");
11
11
  class ParameterSpace {
12
12
  constructor(parameters) {
13
13
  this.type = 'fieldSpace';
14
- this._map = {};
14
+ this._map = new Map();
15
15
  for (const parameter of parameters) {
16
- this._map[parameter.name] = new space_param_1.AbstractParameter(parameter);
16
+ this._map.set(parameter.name, new space_param_1.AbstractParameter(parameter));
17
17
  }
18
18
  }
19
19
  structDef() {
@@ -23,7 +23,7 @@ class ParameterSpace {
23
23
  throw new Error('Parameter space does not have an emptyStructDef');
24
24
  }
25
25
  entry(name) {
26
- return this._map[name];
26
+ return this._map.get(name);
27
27
  }
28
28
  lookup(symbol) {
29
29
  const name = symbol[0];
@@ -65,7 +65,7 @@ class ParameterSpace {
65
65
  };
66
66
  }
67
67
  entries() {
68
- return Object.entries(this._map);
68
+ return [...this._map.entries()];
69
69
  }
70
70
  dialectName() {
71
71
  return '~parameter-space-unknown-dialect~';
@@ -24,7 +24,7 @@ export declare abstract class QueryOperationSpace extends RefinedSpace implement
24
24
  readonly astEl: MalloyElement;
25
25
  protected exprSpace: QueryInputSpace;
26
26
  abstract readonly segmentType: 'reduce' | 'project' | 'index';
27
- expandedWild: Record<string, {
27
+ expandedWild: Map<string, {
28
28
  path: string[];
29
29
  entry: SpaceEntry;
30
30
  at: model.DocumentLocation;
@@ -85,7 +85,7 @@ class QueryOperationSpace extends refined_space_1.RefinedSpace {
85
85
  super(queryInputSpace.emptyStructDef());
86
86
  this.nestParent = nestParent;
87
87
  this.astEl = astEl;
88
- this.expandedWild = {};
88
+ this.expandedWild = new Map();
89
89
  this.drillDimensions = [];
90
90
  this.compositeFieldUsers = [];
91
91
  // Composite field usage is not computed until `queryFieldDefs` is called
@@ -149,7 +149,7 @@ class QueryOperationSpace extends refined_space_1.RefinedSpace {
149
149
  continue;
150
150
  }
151
151
  if (this.entry(name)) {
152
- const conflict = (_a = this.expandedWild[name]) === null || _a === void 0 ? void 0 : _a.path.join('.');
152
+ const conflict = (_a = this.expandedWild.get(name)) === null || _a === void 0 ? void 0 : _a.path.join('.');
153
153
  wild.logError('name-conflict-in-wildcard-expansion', `Cannot expand '${name}' in '${wild.refString}' because a field with that name already exists${conflict ? ` (conflicts with ${conflict})` : ''}`);
154
154
  }
155
155
  else {
@@ -158,11 +158,11 @@ class QueryOperationSpace extends refined_space_1.RefinedSpace {
158
158
  model.expressionIsScalar(eType.expressionType) &&
159
159
  (dialect === undefined || !dialect.ignoreInProject(name))) {
160
160
  expandEntries.push({ name, entry });
161
- this.expandedWild[name] = {
161
+ this.expandedWild.set(name, {
162
162
  path: joinPath.concat(name),
163
163
  entry,
164
164
  at: wild.location,
165
- };
165
+ });
166
166
  }
167
167
  }
168
168
  }
@@ -348,7 +348,7 @@ class QuerySpace extends QueryOperationSpace {
348
348
  }
349
349
  else {
350
350
  const { name, field } = user;
351
- const wildPath = this.expandedWild[name];
351
+ const wildPath = this.expandedWild.get(name);
352
352
  if (wildPath) {
353
353
  const typeDesc = wildPath.entry.typeDesc();
354
354
  fields.push({
@@ -62,16 +62,16 @@ class StaticSpace {
62
62
  }
63
63
  get map() {
64
64
  if (this.memoMap === undefined) {
65
- this.memoMap = {};
65
+ this.memoMap = new Map();
66
66
  for (const f of this.fromStruct.fields) {
67
67
  const name = f.as || f.name;
68
- this.memoMap[name] = this.defToSpaceField(f);
68
+ this.memoMap.set(name, this.defToSpaceField(f));
69
69
  }
70
70
  if ((0, malloy_types_1.isSourceDef)(this.fromStruct)) {
71
71
  if (this.fromStruct.parameters) {
72
72
  for (const [paramName, paramDef] of Object.entries(this.fromStruct.parameters)) {
73
- if (!(paramName in this.memoMap)) {
74
- this.memoMap[paramName] = new space_param_1.DefinedParameter(paramDef);
73
+ if (!this.memoMap.has(paramName)) {
74
+ this.memoMap.set(paramName, new space_param_1.DefinedParameter(paramDef));
75
75
  }
76
76
  }
77
77
  }
@@ -83,20 +83,20 @@ class StaticSpace {
83
83
  return 'internal';
84
84
  }
85
85
  dropEntries() {
86
- this.memoMap = {};
86
+ this.memoMap = new Map();
87
87
  }
88
88
  dropEntry(name) {
89
- delete this.map[name];
89
+ this.map.delete(name);
90
90
  }
91
91
  // TODO this was protected
92
92
  entry(name) {
93
- return this.map[name];
93
+ return this.map.get(name);
94
94
  }
95
95
  setEntry(name, value) {
96
- this.map[name] = value;
96
+ this.map.set(name, value);
97
97
  }
98
98
  entries() {
99
- return Object.entries(this.map);
99
+ return [...this.map.entries()];
100
100
  }
101
101
  structDef() {
102
102
  return this.fromStruct;
@@ -104,7 +104,7 @@ class StaticSpace {
104
104
  emptyStructDef() {
105
105
  const ret = { ...this.fromStruct };
106
106
  if ((0, malloy_types_1.isSourceDef)(ret)) {
107
- ret.parameters = {};
107
+ ret.parameters = (0, malloy_types_1.mkSafeRecord)();
108
108
  }
109
109
  ret.fields = [];
110
110
  return ret;
@@ -117,7 +117,7 @@ class NamedSource extends source_1.Source {
117
117
  }
118
118
  evaluateArguments(parameterSpace, parametersIn, parametersOut) {
119
119
  var _a, _b, _c;
120
- const outArguments = { ...this.sourceArguments };
120
+ const outArguments = Object.assign((0, malloy_types_1.mkSafeRecord)(), this.sourceArguments);
121
121
  const passedNames = new Set();
122
122
  for (const argument of (_a = this.args) !== null && _a !== void 0 ? _a : []) {
123
123
  const id = (_b = argument.id) !== null && _b !== void 0 ? _b : (argument.value instanceof expr_id_reference_1.ExprIdReference
@@ -133,7 +133,9 @@ class NamedSource extends source_1.Source {
133
133
  continue;
134
134
  }
135
135
  passedNames.add(name);
136
- const parameter = (parametersIn !== null && parametersIn !== void 0 ? parametersIn : {})[name];
136
+ const parameter = parametersIn
137
+ ? (0, malloy_types_1.safeRecordGet)(parametersIn, name)
138
+ : undefined;
137
139
  if (!parameter) {
138
140
  id.logError('source-parameter-not-found', `\`${this.refName}\` has no declared parameter named \`${id.refString}\``);
139
141
  }
@@ -200,7 +202,7 @@ class NamedSource extends source_1.Source {
200
202
  notFound.dialect = notFound.dialect + err;
201
203
  return notFound;
202
204
  }
203
- const outParameters = {};
205
+ const outParameters = (0, malloy_types_1.mkSafeRecord)();
204
206
  for (const parameter of pList !== null && pList !== void 0 ? pList : []) {
205
207
  const compiled = parameter.parameter();
206
208
  outParameters[compiled.name] = compiled;
@@ -208,8 +210,10 @@ class NamedSource extends source_1.Source {
208
210
  const outArguments = this.evaluateArguments(parameterSpace, base.parameters, pList);
209
211
  for (const paramName in base.parameters) {
210
212
  if (!(paramName in outArguments) &&
211
- (0, malloy_types_1.paramHasValue)(base.parameters[paramName])) {
212
- outArguments[paramName] = { ...base.parameters[paramName] };
213
+ (0, malloy_types_1.paramHasValue)((0, malloy_types_1.safeRecordGet)(base.parameters, paramName))) {
214
+ outArguments[paramName] = {
215
+ ...(0, malloy_types_1.safeRecordGet)(base.parameters, paramName),
216
+ };
213
217
  }
214
218
  }
215
219
  const ret = { ...base, parameters: outParameters, arguments: outArguments };
@@ -23,6 +23,7 @@
23
23
  */
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.Source = void 0;
26
+ const malloy_types_1 = require("../../../model/malloy_types");
26
27
  const malloy_element_1 = require("../types/malloy-element");
27
28
  /**
28
29
  * A "Source" is a thing which you can run queries against. The main
@@ -37,7 +38,7 @@ class Source extends malloy_element_1.MalloyElement {
37
38
  packParameters(pList) {
38
39
  if (pList === undefined)
39
40
  return undefined;
40
- const parameters = {};
41
+ const parameters = (0, malloy_types_1.mkSafeRecord)();
41
42
  for (const hasP of pList) {
42
43
  const pVal = hasP.parameter();
43
44
  parameters[pVal.name] = pVal;
@@ -102,7 +102,7 @@ class ImportStatement extends malloy_element_1.ListOf {
102
102
  for (const importOne of this.list) {
103
103
  const dstName = importOne.text;
104
104
  const srcName = importOne.from ? importOne.from.text : dstName;
105
- if (importedModel.contents[srcName] === undefined) {
105
+ if ((0, malloy_types_1.safeRecordGet)(importedModel.contents, srcName) === undefined) {
106
106
  importOne.logError('selective-import-not-found', `Cannot find '${srcName}', not imported`);
107
107
  }
108
108
  else if (doc.getEntry(dstName)) {
@@ -117,7 +117,9 @@ class ImportStatement extends malloy_element_1.ListOf {
117
117
  const picked = explicitImport[srcName];
118
118
  const dstName = picked || srcName;
119
119
  if (importAll || picked) {
120
- const importMe = { ...importedModel.contents[srcName] };
120
+ const importMe = {
121
+ ...(0, malloy_types_1.safeRecordGet)(importedModel.contents, srcName),
122
+ };
121
123
  importMe.as = dstName;
122
124
  doc.setEntry(dstName, { entry: importMe, exported: false });
123
125
  // Collect dependencies for persistable sources
@@ -136,7 +138,7 @@ class ImportStatement extends malloy_element_1.ListOf {
136
138
  // (parent can't resolve by name since it's not in namespace)
137
139
  let entry = value.entry;
138
140
  if ((0, malloy_types_1.isSourceRegistryReference)(entry)) {
139
- const resolved = importedModel.contents[entry.name];
141
+ const resolved = (0, malloy_types_1.safeRecordGet)(importedModel.contents, entry.name);
140
142
  if (resolved &&
141
143
  (0, malloy_types_1.isSourceDef)(resolved) &&
142
144
  (0, malloy_types_1.isPersistableSourceDef)(resolved)) {
@@ -120,7 +120,7 @@ export declare class DocStatementList extends ListOf<DocStatement | DocStatement
120
120
  export declare class Document extends MalloyElement implements NameSpace {
121
121
  elementType: string;
122
122
  globalNameSpace: NameSpace;
123
- documentModel: Record<string, ModelEntry>;
123
+ documentModel: Map<string, ModelEntry>;
124
124
  documentSrcRegistry: Record<SourceID, SourceRegistryValue>;
125
125
  queryList: Query[];
126
126
  statements: DocStatementList;
@@ -136,7 +136,7 @@ export declare class Document extends MalloyElement implements NameSpace {
136
136
  hasAnnotation(): boolean;
137
137
  currentModelAnnotation(): ModelAnnotation | undefined;
138
138
  modelDef(): ModelDef;
139
- getEntry(str: string): ModelEntry;
139
+ getEntry(str: string): ModelEntry | undefined;
140
140
  setEntry(str: string, ent: ModelEntry): void;
141
141
  /**
142
142
  * Return an error message if this dialect is the first reference to this particular
@@ -437,7 +437,7 @@ class Document extends MalloyElement {
437
437
  super();
438
438
  this.elementType = 'document';
439
439
  this.globalNameSpace = new global_name_space_1.GlobalNameSpace();
440
- this.documentModel = {};
440
+ this.documentModel = new Map();
441
441
  this.documentSrcRegistry = {};
442
442
  this.queryList = [];
443
443
  this.didInitModel = false;
@@ -453,7 +453,7 @@ class Document extends MalloyElement {
453
453
  if (this.didInitModel) {
454
454
  return;
455
455
  }
456
- this.documentModel = {};
456
+ this.documentModel = new Map();
457
457
  this.documentSrcRegistry = {};
458
458
  this.queryList = [];
459
459
  if (extendingModelDef) {
@@ -516,17 +516,17 @@ class Document extends MalloyElement {
516
516
  if (this.hasAnnotation()) {
517
517
  def.annotation = this.currentModelAnnotation();
518
518
  }
519
- for (const entry in this.documentModel) {
520
- const entryDef = this.documentModel[entry].entry;
519
+ for (const [name, modelEntry] of this.documentModel) {
520
+ const entryDef = modelEntry.entry;
521
521
  if ((0, malloy_types_1.isSourceDef)(entryDef) || entryDef.type === 'query') {
522
- if (this.documentModel[entry].exported) {
523
- def.exports.push(entry);
522
+ if (modelEntry.exported) {
523
+ def.exports.push(name);
524
524
  }
525
525
  const newEntry = { ...entryDef };
526
526
  if (newEntry.modelAnnotation === undefined && def.annotation) {
527
527
  newEntry.modelAnnotation = def.annotation;
528
528
  }
529
- def.contents[entry] = newEntry;
529
+ def.contents[name] = newEntry;
530
530
  }
531
531
  }
532
532
  // Copy the accumulated sourceRegistry
@@ -535,7 +535,7 @@ class Document extends MalloyElement {
535
535
  }
536
536
  getEntry(str) {
537
537
  var _a;
538
- return (_a = this.globalNameSpace.getEntry(str)) !== null && _a !== void 0 ? _a : this.documentModel[str];
538
+ return (_a = this.globalNameSpace.getEntry(str)) !== null && _a !== void 0 ? _a : this.documentModel.get(str);
539
539
  }
540
540
  setEntry(str, ent) {
541
541
  // TODO this error message is going to be in the wrong place everywhere...
@@ -549,7 +549,7 @@ class Document extends MalloyElement {
549
549
  if (this.didInitModel) {
550
550
  this.modelWasModified = true;
551
551
  }
552
- this.documentModel[str] = ent;
552
+ this.documentModel.set(str, ent);
553
553
  // Maintain sourceRegistry for persistable sources with sourceID
554
554
  if ((0, malloy_types_1.isSourceDef)(ent.entry) &&
555
555
  (0, malloy_types_1.isPersistableSourceDef)(ent.entry) &&
@@ -255,8 +255,8 @@ function generateAppliedFilter(context, filterMatchExpr, qi) {
255
255
  if (filterExpr.node === 'parameter') {
256
256
  const name = filterExpr.path[0];
257
257
  (_a = context.eventStream) === null || _a === void 0 ? void 0 : _a.emit('source-argument-compiled', { name });
258
- const argument = context.arguments()[name];
259
- if (argument.value) {
258
+ const argument = (0, malloy_types_1.safeRecordGet)(context.arguments(), name);
259
+ if (argument === null || argument === void 0 ? void 0 : argument.value) {
260
260
  filterExpr = argument.value;
261
261
  }
262
262
  else {
@@ -574,8 +574,8 @@ function generateParameterFragment(resultSet, context, expr, state) {
574
574
  var _a;
575
575
  const name = expr.path[0];
576
576
  (_a = context.eventStream) === null || _a === void 0 ? void 0 : _a.emit('source-argument-compiled', { name });
577
- const argument = context.arguments()[name];
578
- if (argument.value) {
577
+ const argument = (0, malloy_types_1.safeRecordGet)(context.arguments(), name);
578
+ if (argument === null || argument === void 0 ? void 0 : argument.value) {
579
579
  return exprToSQL(resultSet, context, argument.value, state);
580
580
  }
581
581
  throw new Error(`Can't generate SQL, no value for ${expr.path}`);
@@ -1,5 +1,16 @@
1
1
  import type * as Malloy from '@malloydata/malloy-interfaces';
2
2
  import type { EventStream } from '../runtime_types';
3
+ /**
4
+ * A Record<string, V> used as a string-keyed map in serializable IR types.
5
+ * Direct bracket access (record[key]) is UNSAFE because Object.prototype
6
+ * property names like 'constructor', 'toString', 'valueOf' return inherited
7
+ * functions instead of undefined. Use safeRecordGet() to read entries.
8
+ */
9
+ export type SafeRecord<V> = Record<string, V>;
10
+ /** Safely read from a SafeRecord. Returns undefined for non-own properties. */
11
+ export declare function safeRecordGet<V>(record: SafeRecord<V>, key: string): V | undefined;
12
+ /** Create an empty null-prototype SafeRecord (immune to prototype pollution). */
13
+ export declare function mkSafeRecord<V>(): SafeRecord<V>;
3
14
  /**
4
15
  * Field computations are compiled into an expression tree of "Expr"
5
16
  * type nodes. Each node is one of these three interfaces. The
@@ -229,7 +240,7 @@ export interface BooleanLiteralNode extends ExprLeaf {
229
240
  }
230
241
  export interface RecordLiteralNode extends ExprWithKids {
231
242
  node: 'recordLiteral';
232
- kids: Record<string, Expr>;
243
+ kids: SafeRecord<Expr>;
233
244
  typeDef: RecordTypeDef;
234
245
  }
235
246
  export interface ArrayLiteralNode extends ExprWithKids {
@@ -561,7 +572,7 @@ export type StructRef = string | SourceDef;
561
572
  export declare function refIsStructDef(ref: StructRef): ref is SourceDef;
562
573
  export type InvokedStructRef = {
563
574
  structRef: StructRef;
564
- sourceArguments?: Record<string, Argument>;
575
+ sourceArguments?: SafeRecord<Argument>;
565
576
  };
566
577
  export interface Filtered {
567
578
  filterList?: FilterCondition[];
@@ -579,7 +590,7 @@ export interface Query extends Pipeline, Filtered, HasLocation {
579
590
  type?: 'query';
580
591
  name?: string;
581
592
  structRef: StructRef;
582
- sourceArguments?: Record<string, Argument>;
593
+ sourceArguments?: SafeRecord<Argument>;
583
594
  annotation?: Annotation;
584
595
  modelAnnotation?: Annotation;
585
596
  compositeResolvedSourceDef?: SourceDef;
@@ -699,8 +710,8 @@ export interface PartitionCompositeDesc {
699
710
  compositeFields: string[];
700
711
  }
701
712
  interface SourceDefBase extends StructDefBase, Filtered, ResultStructMetadata {
702
- arguments?: Record<string, Argument>;
703
- parameters?: Record<string, Parameter>;
713
+ arguments?: SafeRecord<Argument>;
714
+ parameters?: SafeRecord<Parameter>;
704
715
  queryTimezone?: string;
705
716
  connection: string;
706
717
  primaryKey?: PrimaryKeyRef;
@@ -950,7 +961,7 @@ export interface DependencyTree {
950
961
  export interface ModelDef {
951
962
  name: string;
952
963
  exports: string[];
953
- contents: Record<string, NamedModelObject>;
964
+ contents: SafeRecord<NamedModelObject>;
954
965
  /**
955
966
  * Registry mapping sourceID to source definitions for build graph construction.
956
967
  * For sources in namespace: maps to SourceRegistryReference (look up in contents)
@@ -965,8 +976,8 @@ export interface ModelDef {
965
976
  imports?: ImportLocation[];
966
977
  }
967
978
  /** Very common record type */
968
- export type NamedSourceDefs = Record<string, SourceDef>;
969
- export type NamedModelObjects = Record<string, NamedModelObject>;
979
+ export type NamedSourceDefs = SafeRecord<SourceDef>;
980
+ export type NamedModelObjects = SafeRecord<NamedModelObject>;
970
981
  /** Malloy source annotations attached to objects */
971
982
  export interface Annotation {
972
983
  inherits?: Annotation;
@@ -1006,7 +1017,7 @@ export type MalloyQueryData = {
1006
1017
  export interface DrillSource {
1007
1018
  sourceExplore: string;
1008
1019
  sourceFilters?: FilterCondition[];
1009
- sourceArguments?: Record<string, Argument>;
1020
+ sourceArguments?: SafeRecord<Argument>;
1010
1021
  }
1011
1022
  export interface CompiledQuery extends DrillSource {
1012
1023
  structs: SourceDef[];
@@ -1065,7 +1076,7 @@ export interface PrepareResultOptions {
1065
1076
  /** Manifest of built tables (BuildID → entry), the build cache */
1066
1077
  buildManifest?: BuildManifest;
1067
1078
  /** Map from connectionName to connectionDigest (from Connection.getDigest()) */
1068
- connectionDigests?: Record<string, string>;
1079
+ connectionDigests?: SafeRecord<string>;
1069
1080
  /** If true, throw when a persist query's digest is not in the manifest */
1070
1081
  strictPersist?: boolean;
1071
1082
  }
@@ -24,6 +24,8 @@
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.TD = exports.ValueType = void 0;
27
+ exports.safeRecordGet = safeRecordGet;
28
+ exports.mkSafeRecord = mkSafeRecord;
27
29
  exports.exprHasKids = exprHasKids;
28
30
  exports.exprHasE = exprHasE;
29
31
  exports.exprIsLeaf = exprIsLeaf;
@@ -96,6 +98,15 @@ exports.isValueBoolean = isValueBoolean;
96
98
  exports.isValueTimestamp = isValueTimestamp;
97
99
  exports.isValueDate = isValueDate;
98
100
  exports.mergeUniqueKeyRequirement = mergeUniqueKeyRequirement;
101
+ const hasOwn = Object.prototype.hasOwnProperty;
102
+ /** Safely read from a SafeRecord. Returns undefined for non-own properties. */
103
+ function safeRecordGet(record, key) {
104
+ return hasOwn.call(record, key) ? record[key] : undefined;
105
+ }
106
+ /** Create an empty null-prototype SafeRecord (immune to prototype pollution). */
107
+ function mkSafeRecord() {
108
+ return Object.create(null);
109
+ }
99
110
  function exprHasKids(e) {
100
111
  return 'kids' in e;
101
112
  }
@@ -13,7 +13,7 @@ const annotation_1 = require("../annotation");
13
13
  * Resolve a source name to its definition from model contents.
14
14
  */
15
15
  function resolveSource(modelDef, name) {
16
- const obj = modelDef.contents[name];
16
+ const obj = (0, malloy_types_1.safeRecordGet)(modelDef.contents, name);
17
17
  return obj && (0, malloy_types_1.isSourceDef)(obj) ? obj : undefined;
18
18
  }
19
19
  /**
@@ -537,7 +537,7 @@ class QueryQuery extends query_node_1.QueryField {
537
537
  const { buildManifest, connectionDigests, strictPersist } = (_b = qs.prepareResultOptions) !== null && _b !== void 0 ? _b : {};
538
538
  // Check manifest for this source
539
539
  if (buildManifest && connectionDigests) {
540
- const connDigest = connectionDigests[qs.structDef.connection];
540
+ const connDigest = (0, malloy_types_1.safeRecordGet)(connectionDigests, qs.structDef.connection);
541
541
  if (connDigest) {
542
542
  // Compile with empty opts to get manifest-ignorant SQL for BuildID
543
543
  const fullRet = this.compileQueryToStages(qs.structDef.query, {}, undefined, false);
@@ -127,7 +127,7 @@ function resolveSourceID(modelDef, sourceID) {
127
127
  if (!value)
128
128
  return undefined;
129
129
  if ((0, malloy_types_1.isSourceRegistryReference)(value.entry)) {
130
- const obj = modelDef.contents[value.entry.name];
130
+ const obj = (0, malloy_types_1.safeRecordGet)(modelDef.contents, value.entry.name);
131
131
  return obj && (0, malloy_types_1.isSourceDef)(obj) && (0, malloy_types_1.isPersistableSourceDef)(obj)
132
132
  ? obj
133
133
  : undefined;
@@ -54,7 +54,7 @@ function expandPersistableSource(source, opts, quoteTablePath, compileQuery) {
54
54
  const { buildManifest, connectionDigests, strictPersist } = opts;
55
55
  // Try manifest lookup if we have the required info
56
56
  if (buildManifest && connectionDigests) {
57
- const connDigest = connectionDigests[source.connection];
57
+ const connDigest = (0, malloy_types_1.safeRecordGet)(connectionDigests, source.connection);
58
58
  if (connDigest) {
59
59
  // Get the SQL for this source to compute BuildID (no opts = full SQL)
60
60
  const sql = getSourceSQL(source, quoteTablePath, compileQuery);
@@ -293,7 +293,7 @@ function mkModelDef(name) {
293
293
  return {
294
294
  name,
295
295
  exports: [],
296
- contents: {},
296
+ contents: (0, malloy_types_1.mkSafeRecord)(),
297
297
  sourceRegistry: {},
298
298
  queryList: [],
299
299
  dependencies: {},
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const MALLOY_VERSION = "0.0.343";
1
+ export declare const MALLOY_VERSION = "0.0.344";
package/dist/version.js CHANGED
@@ -2,5 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MALLOY_VERSION = void 0;
4
4
  // generated with 'generate-version-file' script; do not edit manually
5
- exports.MALLOY_VERSION = '0.0.343';
5
+ exports.MALLOY_VERSION = '0.0.344';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@malloydata/malloy",
3
- "version": "0.0.343",
3
+ "version": "0.0.344",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
@@ -45,9 +45,9 @@
45
45
  "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"
46
46
  },
47
47
  "dependencies": {
48
- "@malloydata/malloy-filter": "0.0.343",
49
- "@malloydata/malloy-interfaces": "0.0.343",
50
- "@malloydata/malloy-tag": "0.0.343",
48
+ "@malloydata/malloy-filter": "0.0.344",
49
+ "@malloydata/malloy-interfaces": "0.0.344",
50
+ "@malloydata/malloy-tag": "0.0.344",
51
51
  "@noble/hashes": "^1.8.0",
52
52
  "antlr4ts": "^0.5.0-alpha.4",
53
53
  "assert": "^2.0.0",
package/CONTEXT.md DELETED
@@ -1,58 +0,0 @@
1
- # Malloy Core Package
2
-
3
- The `malloy` package is the heart of the Malloy language implementation. It contains the compiler, translator, and runtime system that powers Malloy's semantic modeling and query capabilities.
4
-
5
- ## Package Structure
6
-
7
- ```
8
- packages/malloy/
9
- ├── src/
10
- │ ├── lang/ # Translator: Parse tree → AST → IR (see src/lang/CONTEXT.md)
11
- │ ├── model/ # Compiler: IR → SQL (see src/model/CONTEXT.md)
12
- │ ├── dialect/ # Database-specific SQL generation
13
- │ ├── api/ # API layers (see src/api/CONTEXT.md)
14
- │ │ └── foundation/ # Public API classes (see MALLOY_API.md)
15
- │ └── connection/ # Database connection abstractions
16
- ```
17
-
18
- ## Two-Phase Architecture
19
-
20
- The Malloy compilation process is split into two distinct phases:
21
-
22
- ### Phase 1: Translation (src/lang/)
23
- The translator takes Malloy source code and transforms it into an Intermediate Representation (IR).
24
-
25
- **Process:**
26
- 1. ANTLR parser generates parse tree from source code
27
- 2. Parse tree is transformed into Abstract Syntax Tree (AST)
28
- 3. AST is analyzed and transformed into IR
29
-
30
- **Key characteristics:**
31
- - IR is a **serializable data format** (plain objects, not class instances)
32
- - IR fully describes the semantic model independent of SQL
33
- - IR can be cached, transmitted, and reused across compilations
34
-
35
- For detailed information about the translator, see [src/lang/CONTEXT.md](src/lang/CONTEXT.md).
36
-
37
- ### Phase 2: Compilation (src/model/)
38
- The compiler takes IR and generates SQL queries for specific database dialects.
39
-
40
- **Process:**
41
- 1. IR is read and analyzed
42
- 2. Query operations are transformed into SQL expressions
43
- 3. Dialect-specific SQL is generated
44
- 4. Metadata is generated for result processing
45
-
46
- **Key characteristics:**
47
- - Produces SQL that can be executed on target database
48
- - Includes metadata to interpret and render results
49
- - Dialect-agnostic until final SQL generation step
50
-
51
- For detailed information about the compiler, see [src/model/CONTEXT.md](src/model/CONTEXT.md).
52
-
53
- ## Subsystem Context
54
-
55
- For deeper details on specific subsystems:
56
- - [MALLOY_API.md](MALLOY_API.md) - Public API classes (Model, PreparedQuery, Runtime, Materializers)
57
- - [src/lang/CONTEXT.md](src/lang/CONTEXT.md) - Translator architecture (grammar, AST, IR generation)
58
- - [src/model/CONTEXT.md](src/model/CONTEXT.md) - Compiler architecture (SQL generation, expression compilation)
package/MALLOY_API.md DELETED
@@ -1,129 +0,0 @@
1
- # Foundation API - Public API Classes
2
-
3
- The directory `src/api/foundation/` is the main public API surface for the Malloy package. It exports classes that wrap internal IR types and provide user-facing functionality. See `src/api/CONTEXT.md` for a complete overview of all API layers.
4
-
5
- ## Core Data Classes
6
-
7
- ### `Model`
8
- Wraps `ModelDef` (internal IR). Represents a compiled Malloy model.
9
-
10
- - `explores: Explore[]` - All sources in the model (wrapped)
11
- - `getPreparedQueryByName(name): PreparedQuery`
12
- - `getPreparedQueryByIndex(index): PreparedQuery`
13
- - `getExploreByName(name): Explore`
14
- - `_modelDef` - Escape hatch to raw IR
15
-
16
- ### `PreparedQuery`
17
- Wraps `Query` (internal IR) + `ModelDef`. A query that can be compiled to SQL.
18
-
19
- - `getPreparedResult(options?): PreparedResult` - Compile to SQL
20
- - `dialect: string`
21
- - `name?: string`
22
- - `_query`, `_modelDef` - Escape hatches
23
-
24
- **Issue:** Each `getPreparedResult()` call creates a new `QueryModel`, reprocessing the ModelDef.
25
-
26
- ### `PreparedResult`
27
- Wraps `CompiledQuery` (internal). The compiled SQL and metadata.
28
-
29
- - `sql: string` - The generated SQL
30
- - `connectionName: string` - Which connection to run against
31
- - `resultExplore: Explore` - Schema of the result
32
-
33
- ### `Result`
34
- Extends `PreparedResult`. Adds actual query result data.
35
-
36
- - `data: DataArray` - The result rows
37
- - `totalRows: number`
38
- - `runStats: QueryRunStats`
39
-
40
- ### `Explore`
41
- Wraps `StructDef` (internal). Represents a source (historical name was "explore").
42
-
43
- - `name: string`
44
- - `allFields: Field[]`
45
- - `getFieldByName(name): Field`
46
- - `getQueryByName(name): PreparedQuery` - Get a view as a query
47
-
48
- ### `ExploreField`
49
- Extends `Explore`. A joined source (appears as a field in parent).
50
-
51
- ### `Query`
52
- Wraps `TurtleDef`. Represents a view definition. **Different from `PreparedQuery`.**
53
-
54
- ### `QueryField`
55
- Extends `Query`. A view that appears as a field in an explore.
56
-
57
- ### `AtomicField` and subclasses
58
- Field wrappers: `StringField`, `NumberField`, `DateField`, `TimestampField`, `BooleanField`, `JSONField`, `UnsupportedField`
59
-
60
- ## Runtime & Materializer Classes
61
-
62
- ### `Runtime`
63
- Entry point for loading and running Malloy. Holds URLReader, connections, event stream.
64
-
65
- - `loadModel(source): ModelMaterializer`
66
- - `loadQuery(query): QueryMaterializer`
67
- - `getModel(source): Promise<Model>`
68
- - `getQuery(query): Promise<PreparedQuery>`
69
-
70
- ### `SingleConnectionRuntime`
71
- Extends `Runtime`. For single-connection use cases.
72
-
73
- ### `ConnectionRuntime`
74
- Extends `Runtime`. Holds array of connections.
75
-
76
- ### `ModelMaterializer`
77
- Fluent builder for loading models. Returned by `runtime.loadModel()`.
78
-
79
- - `getModel(): Promise<Model>`
80
- - `loadQueryByName(name): QueryMaterializer`
81
- - `loadExploreByName(name): ExploreMaterializer`
82
- - `extendModel(source): ModelMaterializer`
83
-
84
- ### `QueryMaterializer`
85
- Fluent builder for queries. Returned by `modelMaterializer.loadQueryByName()`.
86
-
87
- - `getPreparedQuery(): Promise<PreparedQuery>`
88
- - `getPreparedResult(): Promise<PreparedResult>`
89
- - `getSQL(): Promise<string>`
90
- - `run(options?): Promise<Result>`
91
-
92
- ### `PreparedResultMaterializer`
93
- Fluent builder for prepared results.
94
-
95
- ### `ExploreMaterializer`
96
- Fluent builder for explores/sources.
97
-
98
- ## Result Data Classes
99
-
100
- ### `DataArray`
101
- Iterable array of `DataRecord`. Query result rows.
102
-
103
- ### `DataRecord`
104
- Single result row. Field values accessed by name.
105
-
106
- ## Utility Classes
107
-
108
- - `Parse` - Parsed (not compiled) Malloy document
109
- - `Malloy` - Static methods: `parse()`, `compile()`, `run()`
110
- - `MalloyError` - Error with structured problems
111
- - `EmptyURLReader`, `InMemoryURLReader` - URLReader implementations
112
- - `FixedConnectionMap` - LookupConnection implementation
113
- - `CacheManager`, `InMemoryModelCache` - Caching
114
- - `JSONWriter`, `CSVWriter` - Result writers
115
- - `DocumentSymbol`, `DocumentPosition`, `DocumentRange`, etc. - IDE support
116
-
117
- ## Naming Issues
118
-
119
- | Current Name | What It Actually Is |
120
- |--------------|---------------------|
121
- | `Explore` | A source (historical name) |
122
- | `PreparedQuery` | Uncompiled query holding IR |
123
- | `PreparedResult` | Compiled query with SQL (not a "result") |
124
- | `Query` | A view/turtle definition |
125
-
126
- ## Known Architectural Issues
127
-
128
- 1. **Transient QueryModel**: `PreparedQuery.getPreparedResult()` creates new `QueryModel` each call - expensive.
129
- 3. **Name collision**: `Query` class (view wrapper) vs internal `Query` type vs `PreparedQuery`