@malloydata/malloy 0.0.326 → 0.0.327

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/api/core.js +2 -0
  2. package/dist/api/index.d.ts +1 -0
  3. package/dist/api/index.js +1 -0
  4. package/dist/api/row_data_utils.d.ts +30 -0
  5. package/dist/api/row_data_utils.js +87 -0
  6. package/dist/api/util.js +20 -38
  7. package/dist/dialect/dialect.d.ts +36 -0
  8. package/dist/dialect/dialect.js +28 -1
  9. package/dist/dialect/duckdb/duckdb.d.ts +2 -1
  10. package/dist/dialect/duckdb/duckdb.js +12 -3
  11. package/dist/dialect/mysql/mysql.js +18 -4
  12. package/dist/dialect/pg_impl.d.ts +1 -0
  13. package/dist/dialect/pg_impl.js +2 -0
  14. package/dist/dialect/postgres/postgres.js +4 -1
  15. package/dist/dialect/snowflake/snowflake.d.ts +2 -1
  16. package/dist/dialect/snowflake/snowflake.js +37 -15
  17. package/dist/dialect/standardsql/standardsql.d.ts +2 -1
  18. package/dist/dialect/standardsql/standardsql.js +9 -3
  19. package/dist/dialect/trino/trino.js +10 -2
  20. package/dist/lang/ast/expressions/expr-avg.d.ts +5 -0
  21. package/dist/lang/ast/expressions/expr-avg.js +9 -0
  22. package/dist/lang/ast/expressions/expr-coalesce.js +6 -0
  23. package/dist/lang/ast/expressions/expr-number.d.ts +14 -1
  24. package/dist/lang/ast/expressions/expr-number.js +71 -6
  25. package/dist/lang/ast/expressions/expr-props.d.ts +1 -1
  26. package/dist/lang/ast/expressions/pick-when.js +10 -3
  27. package/dist/lang/ast/types/expression-def.js +36 -2
  28. package/dist/lang/parse-log.d.ts +1 -0
  29. package/dist/lang/test/test-translator.js +2 -0
  30. package/dist/malloy.d.ts +23 -2
  31. package/dist/malloy.js +204 -41
  32. package/dist/model/malloy_types.d.ts +2 -2
  33. package/dist/model/query_model_impl.js +5 -1
  34. package/dist/test/cellsToObject.d.ts +6 -0
  35. package/dist/test/cellsToObject.js +111 -0
  36. package/dist/test/index.d.ts +7 -0
  37. package/dist/test/index.js +16 -20
  38. package/dist/test/matchers.d.ts +10 -0
  39. package/dist/test/matchers.js +17 -0
  40. package/dist/test/resultMatchers.d.ts +42 -0
  41. package/dist/test/resultMatchers.js +722 -0
  42. package/dist/test/runQuery.d.ts +31 -0
  43. package/dist/test/runQuery.js +67 -0
  44. package/dist/test/test-models.d.ts +77 -0
  45. package/dist/test/test-models.js +319 -0
  46. package/dist/test/test-values.d.ts +66 -0
  47. package/dist/test/test-values.js +208 -0
  48. package/dist/to_stable.js +3 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/package.json +9 -5
package/dist/malloy.js CHANGED
@@ -25,7 +25,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
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
- const luxon_1 = require("luxon");
29
28
  const dialect_1 = require("./dialect");
30
29
  const version_1 = require("./version");
31
30
  const uuid_1 = require("uuid");
@@ -33,6 +32,7 @@ const annotation_1 = require("./annotation");
33
32
  const sql_block_1 = require("./model/sql_block");
34
33
  const utils_1 = require("./lang/utils");
35
34
  const reference_list_1 = require("./lang/reference-list");
35
+ const row_data_utils_1 = require("./api/row_data_utils");
36
36
  function isSourceComponent(source) {
37
37
  return (source.type === 'table' ||
38
38
  source.type === 'sql_select' ||
@@ -2122,7 +2122,15 @@ class ModelMaterializer extends FluentState {
2122
2122
  const result = await this.loadQuery(searchMapMalloy, options).run({
2123
2123
  rowLimit: 1000,
2124
2124
  });
2125
- return result._queryResult.result;
2125
+ const rawResult = result._queryResult.result;
2126
+ return rawResult.map(row => ({
2127
+ ...row,
2128
+ cardinality: (0, row_data_utils_1.rowDataToNumber)(row.cardinality),
2129
+ values: row.values.map(v => ({
2130
+ ...v,
2131
+ weight: (0, row_data_utils_1.rowDataToNumber)(v.weight),
2132
+ })),
2133
+ }));
2126
2134
  }
2127
2135
  /**
2128
2136
  * Materialize the final query contained within this loaded `Model`.
@@ -2630,7 +2638,7 @@ class DataJSON extends ScalarData {
2630
2638
  }
2631
2639
  class DataNumber extends ScalarData {
2632
2640
  constructor(value, field, parent, parentRecord) {
2633
- super(value, field, parent, parentRecord);
2641
+ super((0, row_data_utils_1.rowDataToNumber)(value), field, parent, parentRecord);
2634
2642
  this._field = field;
2635
2643
  }
2636
2644
  get field() {
@@ -2650,44 +2658,13 @@ class DataNumber extends ScalarData {
2650
2658
  return -1;
2651
2659
  }
2652
2660
  }
2653
- function valueToDate(value) {
2654
- // TODO properly map the data from BQ/Postgres types
2655
- if (value instanceof Date) {
2656
- return value;
2657
- }
2658
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2659
- const valAsAny = value;
2660
- if (valAsAny.constructor.name === 'Date') {
2661
- // For some reason duckdb TSTZ values come back as objects which do not
2662
- // pass "instance of" but do seem date like.
2663
- return new Date(value);
2664
- }
2665
- else if (typeof value === 'number') {
2666
- return new Date(value);
2667
- }
2668
- else if (typeof value !== 'string') {
2669
- return new Date(value.value);
2670
- }
2671
- else {
2672
- // Postgres timestamps end up here, and ideally we would know the system
2673
- // timezone of the postgres instance to correctly create a Date() object
2674
- // which represents the same instant in time, but we don't have the data
2675
- // flow to implement that. This may be a problem at some future day,
2676
- // so here is a comment, for that day.
2677
- let parsed = luxon_1.DateTime.fromISO(value, { zone: 'UTC' });
2678
- if (!parsed.isValid) {
2679
- parsed = luxon_1.DateTime.fromSQL(value, { zone: 'UTC' });
2680
- }
2681
- return parsed.toJSDate();
2682
- }
2683
- }
2684
2661
  class DataTimestamp extends ScalarData {
2685
2662
  constructor(value, field, parent, parentRecord) {
2686
2663
  super(value, field, parent, parentRecord);
2687
2664
  this._field = field;
2688
2665
  }
2689
2666
  get value() {
2690
- return valueToDate(this._value);
2667
+ return (0, row_data_utils_1.rowDataToDate)(this._value);
2691
2668
  }
2692
2669
  get field() {
2693
2670
  return this._field;
@@ -2711,7 +2688,7 @@ class DataDate extends ScalarData {
2711
2688
  this._field = field;
2712
2689
  }
2713
2690
  get value() {
2714
- return valueToDate(this._value);
2691
+ return (0, row_data_utils_1.rowDataToDate)(this._value);
2715
2692
  }
2716
2693
  get field() {
2717
2694
  return this._field;
@@ -2755,6 +2732,161 @@ class DataNull extends Data {
2755
2732
  return '<null>';
2756
2733
  }
2757
2734
  }
2735
+ /**
2736
+ * Safe bigint conversion - handles floats that are incorrectly typed as bigint
2737
+ * (e.g., avg() results which should be float but Malloy marks as bigint).
2738
+ */
2739
+ function safeRowDataToBigint(value) {
2740
+ const strValue = (0, row_data_utils_1.rowDataToSerializedBigint)(value);
2741
+ if (strValue.includes('.') ||
2742
+ strValue.includes('e') ||
2743
+ strValue.includes('E')) {
2744
+ return (0, row_data_utils_1.rowDataToNumber)(value);
2745
+ }
2746
+ try {
2747
+ return BigInt(strValue);
2748
+ }
2749
+ catch {
2750
+ return (0, row_data_utils_1.rowDataToNumber)(value);
2751
+ }
2752
+ }
2753
+ /**
2754
+ * Safe bigint serialization - returns number for floats that should stay as numbers.
2755
+ */
2756
+ function safeRowDataToSerializedBigint(value) {
2757
+ const strValue = (0, row_data_utils_1.rowDataToSerializedBigint)(value);
2758
+ if (strValue.includes('.') ||
2759
+ strValue.includes('e') ||
2760
+ strValue.includes('E')) {
2761
+ return (0, row_data_utils_1.rowDataToNumber)(value);
2762
+ }
2763
+ return strValue;
2764
+ }
2765
+ /**
2766
+ * Normalizers for toObject() - returns JS native types (number | bigint, Date)
2767
+ */
2768
+ const OBJECT_NORMALIZERS = {
2769
+ number: row_data_utils_1.rowDataToNumber,
2770
+ bigint: safeRowDataToBigint,
2771
+ date: row_data_utils_1.rowDataToDate,
2772
+ };
2773
+ /**
2774
+ * Normalizers for toJSON() - returns JSON-safe types (number | string, ISO strings)
2775
+ */
2776
+ const JSON_NORMALIZERS = {
2777
+ number: row_data_utils_1.rowDataToNumber,
2778
+ bigint: safeRowDataToSerializedBigint,
2779
+ date: (value) => (0, row_data_utils_1.rowDataToDate)(value).toISOString(),
2780
+ };
2781
+ /**
2782
+ * Walk a QueryData array and normalize values according to the given normalizers.
2783
+ */
2784
+ function walkQueryData(data, structDef, normalizers) {
2785
+ return data.map(row => walkQueryDataRow(row, structDef, normalizers));
2786
+ }
2787
+ /**
2788
+ * Walk a QueryDataRow and normalize values according to the given normalizers.
2789
+ */
2790
+ function walkQueryDataRow(row, structDef, normalizers) {
2791
+ var _a;
2792
+ const result = {};
2793
+ for (const fieldDef of structDef.fields) {
2794
+ const fieldName = (_a = fieldDef.as) !== null && _a !== void 0 ? _a : fieldDef.name;
2795
+ const value = row[fieldName];
2796
+ result[fieldName] = walkValue(value, fieldDef, normalizers);
2797
+ }
2798
+ return result;
2799
+ }
2800
+ /**
2801
+ * Normalize a single value based on its field definition.
2802
+ */
2803
+ function walkValue(value, fieldDef, normalizers) {
2804
+ if (value === null || value === undefined) {
2805
+ return null;
2806
+ }
2807
+ // Handle scalar types
2808
+ if (fieldDef.type === 'number') {
2809
+ const numberDef = fieldDef;
2810
+ if (numberDef.numberType === 'bigint') {
2811
+ return normalizers.bigint(value);
2812
+ }
2813
+ return normalizers.number(value);
2814
+ }
2815
+ if (fieldDef.type === 'date' ||
2816
+ fieldDef.type === 'timestamp' ||
2817
+ fieldDef.type === 'timestamptz') {
2818
+ return normalizers.date(value);
2819
+ }
2820
+ if (fieldDef.type === 'string' ||
2821
+ fieldDef.type === 'boolean' ||
2822
+ fieldDef.type === 'json' ||
2823
+ fieldDef.type === 'sql native') {
2824
+ // Pass through as-is (or with minimal conversion for booleans from numbers)
2825
+ if (fieldDef.type === 'boolean' && typeof value === 'number') {
2826
+ return value !== 0;
2827
+ }
2828
+ return value;
2829
+ }
2830
+ // Handle arrays
2831
+ if (fieldDef.type === 'array') {
2832
+ if (!Array.isArray(value)) {
2833
+ return value; // Unexpected, but don't crash
2834
+ }
2835
+ if ((0, model_1.isRepeatedRecord)(fieldDef)) {
2836
+ // Array of records - recurse into each record
2837
+ return value.map(item => walkQueryDataRow(item, fieldDef, normalizers));
2838
+ }
2839
+ else if ((0, model_1.isBasicArray)(fieldDef)) {
2840
+ // Scalar array - normalize each element based on elementTypeDef
2841
+ // Cast needed because QueryValue type doesn't cleanly express scalar arrays
2842
+ const elementType = fieldDef.elementTypeDef;
2843
+ return value.map(item => walkScalarValue(item, elementType, normalizers));
2844
+ }
2845
+ }
2846
+ // Handle records (non-array)
2847
+ if (fieldDef.type === 'record') {
2848
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
2849
+ return walkQueryDataRow(value, fieldDef, normalizers);
2850
+ }
2851
+ }
2852
+ // Fallback - pass through
2853
+ return value;
2854
+ }
2855
+ /**
2856
+ * Normalize a scalar value (not in a row context, e.g., elements of a scalar array).
2857
+ */
2858
+ function walkScalarValue(value, typeDef, normalizers) {
2859
+ if (value === null || value === undefined) {
2860
+ return null;
2861
+ }
2862
+ if (typeDef.type === 'number') {
2863
+ const numberDef = typeDef;
2864
+ if (numberDef.numberType === 'bigint') {
2865
+ return normalizers.bigint(value);
2866
+ }
2867
+ return normalizers.number(value);
2868
+ }
2869
+ if (typeDef.type === 'date' ||
2870
+ typeDef.type === 'timestamp' ||
2871
+ typeDef.type === 'timestamptz') {
2872
+ return normalizers.date(value);
2873
+ }
2874
+ if (typeDef.type === 'boolean' && typeof value === 'number') {
2875
+ return value !== 0;
2876
+ }
2877
+ // Handle nested arrays (array of arrays)
2878
+ if (typeDef.type === 'array' && Array.isArray(value)) {
2879
+ if ((0, model_1.isBasicArray)(typeDef)) {
2880
+ const elementType = typeDef.elementTypeDef;
2881
+ return value.map(item => walkScalarValue(item, elementType, normalizers));
2882
+ }
2883
+ else if ((0, model_1.isRepeatedRecord)(typeDef)) {
2884
+ return value.map(item => walkQueryDataRow(item, typeDef, normalizers));
2885
+ }
2886
+ }
2887
+ // Pass through other types
2888
+ return value;
2889
+ }
2758
2890
  class DataArray extends Data {
2759
2891
  constructor(queryData, field, parent, parentRecord) {
2760
2892
  super(field, parent, parentRecord);
@@ -2769,11 +2901,27 @@ class DataArray extends Data {
2769
2901
  return this._field;
2770
2902
  }
2771
2903
  /**
2772
- * @return The raw object form of the data.
2904
+ * @return The raw query data as returned by the database driver.
2905
+ * Values may be in various formats depending on the driver (wrapper objects, strings, etc.).
2906
+ * Use this for passing to mapData() which handles normalization itself.
2773
2907
  */
2774
- toObject() {
2908
+ get rawData() {
2775
2909
  return this.queryData;
2776
2910
  }
2911
+ /**
2912
+ * @return Normalized data with JS native types (number | bigint, Date).
2913
+ * Use this for CSV output, tests, and general programmatic access.
2914
+ */
2915
+ toObject() {
2916
+ return walkQueryData(this.queryData, this._field.structDef, OBJECT_NORMALIZERS);
2917
+ }
2918
+ /**
2919
+ * @return Normalized data with JSON-safe types (numbers as number | string, dates as ISO strings).
2920
+ * Use this for JSON serialization.
2921
+ */
2922
+ toJSON() {
2923
+ return walkQueryData(this.queryData, this._field.structDef, JSON_NORMALIZERS);
2924
+ }
2777
2925
  path(...path) {
2778
2926
  return getPath(this, path);
2779
2927
  }
@@ -2784,7 +2932,6 @@ class DataArray extends Data {
2784
2932
  this.rowCache.set(index, record);
2785
2933
  }
2786
2934
  return record;
2787
- return new DataRecord(this.queryData[index], index, this.field, this, this.parentRecord);
2788
2935
  }
2789
2936
  get rowCount() {
2790
2937
  return this.queryData.length;
@@ -2833,8 +2980,19 @@ class DataRecord extends Data {
2833
2980
  this._field = field;
2834
2981
  this.index = index;
2835
2982
  }
2983
+ /**
2984
+ * @return Normalized data with JS native types (number | bigint, Date).
2985
+ * Use this for CSV output, tests, and general programmatic access.
2986
+ */
2836
2987
  toObject() {
2837
- return this.queryDataRow;
2988
+ return walkQueryDataRow(this.queryDataRow, this._field.structDef, OBJECT_NORMALIZERS);
2989
+ }
2990
+ /**
2991
+ * @return Normalized data with JSON-safe types (numbers as number | string, dates as ISO strings).
2992
+ * Use this for JSON serialization.
2993
+ */
2994
+ toJSON() {
2995
+ return walkQueryDataRow(this.queryDataRow, this._field.structDef, JSON_NORMALIZERS);
2838
2996
  }
2839
2997
  path(...path) {
2840
2998
  return getPath(this, path);
@@ -2929,7 +3087,8 @@ class JSONWriter extends DataWriter {
2929
3087
  if (row.index !== undefined && row.index > 0) {
2930
3088
  this.stream.write(',\n');
2931
3089
  }
2932
- const json = JSON.stringify(row.toObject(), null, 2);
3090
+ // toJSON() returns JSON-safe values: bigints as strings, dates as ISO strings
3091
+ const json = JSON.stringify(row.toJSON(), null, 2);
2933
3092
  const jsonLines = json.split('\n');
2934
3093
  for (let i = 0; i < jsonLines.length; i++) {
2935
3094
  const line = jsonLines[i];
@@ -2991,6 +3150,10 @@ class CSVWriter extends DataWriter {
2991
3150
  else if (typeof value === 'boolean' || typeof value === 'number') {
2992
3151
  return JSON.stringify(value);
2993
3152
  }
3153
+ else if (typeof value === 'bigint') {
3154
+ // Bigints from toObject() - write as unquoted number string
3155
+ return value.toString();
3156
+ }
2994
3157
  else {
2995
3158
  return `${value}`;
2996
3159
  }
@@ -427,7 +427,7 @@ export interface StringTypeDef {
427
427
  export type StringFieldDef = StringTypeDef & AtomicFieldDef;
428
428
  export interface NumberTypeDef {
429
429
  type: 'number';
430
- numberType?: 'integer' | 'float';
430
+ numberType?: 'integer' | 'float' | 'bigint';
431
431
  }
432
432
  export type NumberFieldDef = NumberTypeDef & AtomicFieldDef;
433
433
  export interface BooleanTypeDef {
@@ -923,7 +923,7 @@ export interface Note {
923
923
  export interface ModelAnnotation extends Annotation {
924
924
  id: string;
925
925
  }
926
- export type QueryScalar = string | boolean | number | Date | Buffer | null;
926
+ export type QueryScalar = string | boolean | number | bigint | Date | Buffer | null;
927
927
  /** One value in one column of returned data. */
928
928
  export type QueryValue = QueryScalar | QueryData | QueryDataRow;
929
929
  /** A row of returned data. */
@@ -14,6 +14,7 @@ const stage_writer_1 = require("./stage_writer");
14
14
  const dialect_1 = require("../dialect");
15
15
  const utils_1 = require("./materialization/utils");
16
16
  const query_node_1 = require("./query_node");
17
+ const row_data_utils_1 = require("../api/row_data_utils");
17
18
  function makeQueryModel(modelDef, eventStream) {
18
19
  return new QueryModelImpl(modelDef, eventStream);
19
20
  }
@@ -252,7 +253,10 @@ class QueryModelImpl {
252
253
  const result = await connection.runSQL(query, {
253
254
  rowLimit: 1000,
254
255
  });
255
- return result.rows;
256
+ return result.rows.map(row => ({
257
+ ...row,
258
+ weight: (0, row_data_utils_1.rowDataToNumber)(row['weight']),
259
+ }));
256
260
  }
257
261
  }
258
262
  exports.QueryModelImpl = QueryModelImpl;
@@ -0,0 +1,6 @@
1
+ import type * as Malloy from '@malloydata/malloy-interfaces';
2
+ /**
3
+ * Convert Malloy.Data (array of record cells) to an array of plain JS objects.
4
+ * This is the inverse of mapData() in util.ts.
5
+ */
6
+ export declare function cellsToObjects(data: Malloy.Data, schema: Malloy.Schema): Record<string, unknown>[];
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.cellsToObjects = cellsToObjects;
10
+ /**
11
+ * Convert a Malloy.Cell to a plain JS value.
12
+ * - null_cell → null
13
+ * - string_cell → string
14
+ * - number_cell → number
15
+ * - boolean_cell → boolean
16
+ * - date_cell → string (ISO date format)
17
+ * - timestamp_cell → string (ISO timestamp format)
18
+ * - json_cell → parsed JSON value
19
+ * - sql_native_cell → parsed JSON value
20
+ * - array_cell → array of converted values
21
+ * - record_cell → object with field names from schema
22
+ */
23
+ function cellToValue(cell, fieldInfo) {
24
+ switch (cell.kind) {
25
+ case 'null_cell':
26
+ return null;
27
+ case 'string_cell':
28
+ return cell.string_value;
29
+ case 'number_cell':
30
+ return cell.number_value;
31
+ case 'big_number_cell':
32
+ return BigInt(cell.number_value);
33
+ case 'boolean_cell':
34
+ return cell.boolean_value;
35
+ case 'date_cell':
36
+ return cell.date_value;
37
+ case 'timestamp_cell':
38
+ return cell.timestamp_value;
39
+ case 'json_cell':
40
+ return JSON.parse(cell.json_value);
41
+ case 'sql_native_cell':
42
+ return JSON.parse(cell.sql_native_value);
43
+ case 'array_cell': {
44
+ if (fieldInfo.kind !== 'dimension') {
45
+ throw new Error(`Expected dimension for array, got ${fieldInfo.kind}`);
46
+ }
47
+ const type = fieldInfo.type;
48
+ if (type.kind !== 'array_type') {
49
+ throw new Error(`Expected array_type, got ${type.kind}`);
50
+ }
51
+ const elementFieldInfo = {
52
+ kind: 'dimension',
53
+ name: 'element',
54
+ type: type.element_type,
55
+ };
56
+ return cell.array_value.map(c => cellToValue(c, elementFieldInfo));
57
+ }
58
+ case 'record_cell': {
59
+ // Records can come from either a join or a dimension with record_type
60
+ // record_type.fields returns DimensionInfo[], schema.fields returns FieldInfo[]
61
+ let fields;
62
+ let getFieldInfo;
63
+ if (fieldInfo.kind === 'join') {
64
+ fields = fieldInfo.schema.fields;
65
+ getFieldInfo = i => fieldInfo.schema.fields[i];
66
+ }
67
+ else if (fieldInfo.kind === 'dimension' &&
68
+ fieldInfo.type.kind === 'record_type') {
69
+ // Capture the record type to help TypeScript narrow
70
+ const recordType = fieldInfo.type;
71
+ fields = recordType.fields;
72
+ // DimensionInfo needs to be wrapped as a FieldInfo
73
+ getFieldInfo = i => ({
74
+ kind: 'dimension',
75
+ name: recordType.fields[i].name,
76
+ type: recordType.fields[i].type,
77
+ });
78
+ }
79
+ else {
80
+ throw new Error(`Expected join or dimension with record_type for record, got ${fieldInfo.kind}`);
81
+ }
82
+ const result = {};
83
+ for (let i = 0; i < fields.length; i++) {
84
+ result[fields[i].name] = cellToValue(cell.record_value[i], getFieldInfo(i));
85
+ }
86
+ return result;
87
+ }
88
+ default:
89
+ throw new Error(`Unknown cell kind: ${cell.kind}`);
90
+ }
91
+ }
92
+ /**
93
+ * Convert Malloy.Data (array of record cells) to an array of plain JS objects.
94
+ * This is the inverse of mapData() in util.ts.
95
+ */
96
+ function cellsToObjects(data, schema) {
97
+ if (data.kind !== 'array_cell') {
98
+ throw new Error(`Expected array_cell at root, got ${data.kind}`);
99
+ }
100
+ const rootFieldInfo = {
101
+ kind: 'join',
102
+ name: 'root',
103
+ relationship: 'one',
104
+ schema,
105
+ };
106
+ return data.array_value.map(cell => {
107
+ const result = cellToValue(cell, rootFieldInfo);
108
+ return result;
109
+ });
110
+ }
111
+ //# sourceMappingURL=cellsToObject.js.map
@@ -1,3 +1,10 @@
1
+ export { TV } from './test-values';
2
+ export type { TypedValue } from './test-values';
3
+ export { mkTestModel, wrapTestModel, extendTestModel } from './test-models';
4
+ export type { TestModelSources, TestModel } from './test-models';
5
+ export { runQuery } from './runQuery';
6
+ export type { QueryResult } from './runQuery';
7
+ export type { ExpectedRow, MatcherOptions } from './resultMatchers';
1
8
  /**
2
9
  * Accepts databases in env, either via comma-separated dialect list
3
10
  * (MALLOY_DATABASES=) or a single database (MALLOY_DATABASE=). returns either
@@ -1,30 +1,26 @@
1
1
  "use strict";
2
2
  /*
3
- * Copyright 2024 Google LLC
4
- *
5
- * Permission is hereby granted, free of charge, to any person obtaining
6
- * a copy of this software and associated documentation files
7
- * (the "Software"), to deal in the Software without restriction,
8
- * including without limitation the rights to use, copy, modify, merge,
9
- * publish, distribute, sublicense, and/or sell copies of the Software,
10
- * and to permit persons to whom the Software is furnished to do so,
11
- * subject to the following conditions:
12
- *
13
- * The above copyright notice and this permission notice shall be
14
- * included in all copies or substantial portions of the Software.
15
- *
16
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
- * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
- * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
- * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
23
5
  */
24
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.runQuery = exports.extendTestModel = exports.wrapTestModel = exports.mkTestModel = exports.TV = void 0;
25
8
  exports.databasesFromEnvironmentOr = databasesFromEnvironmentOr;
26
9
  exports.describeIfDatabaseAvailable = describeIfDatabaseAvailable;
27
10
  exports.brokenIn = brokenIn;
11
+ // Test data creation
12
+ var test_values_1 = require("./test-values");
13
+ Object.defineProperty(exports, "TV", { enumerable: true, get: function () { return test_values_1.TV; } });
14
+ var test_models_1 = require("./test-models");
15
+ Object.defineProperty(exports, "mkTestModel", { enumerable: true, get: function () { return test_models_1.mkTestModel; } });
16
+ Object.defineProperty(exports, "wrapTestModel", { enumerable: true, get: function () { return test_models_1.wrapTestModel; } });
17
+ Object.defineProperty(exports, "extendTestModel", { enumerable: true, get: function () { return test_models_1.extendTestModel; } });
18
+ // Query execution
19
+ var runQuery_1 = require("./runQuery");
20
+ Object.defineProperty(exports, "runQuery", { enumerable: true, get: function () { return runQuery_1.runQuery; } });
21
+ /*
22
+ * Legacy exports - kept for backwards compatibility
23
+ */
28
24
  /**
29
25
  * Accepts databases in env, either via comma-separated dialect list
30
26
  * (MALLOY_DATABASES=) or a single database (MALLOY_DATABASE=). returns either
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Import this module to register Jest matchers (toMatchResult, toEqualResult).
3
+ *
4
+ * Usage in jest.config.js:
5
+ * setupFilesAfterEnv: ['@malloydata/malloy/test/matchers']
6
+ *
7
+ * Or import directly in a test file:
8
+ * import '@malloydata/malloy/test/matchers';
9
+ */
10
+ import './resultMatchers';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright Contributors to the Malloy project
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ /**
8
+ * Import this module to register Jest matchers (toMatchResult, toEqualResult).
9
+ *
10
+ * Usage in jest.config.js:
11
+ * setupFilesAfterEnv: ['@malloydata/malloy/test/matchers']
12
+ *
13
+ * Or import directly in a test file:
14
+ * import '@malloydata/malloy/test/matchers';
15
+ */
16
+ require("./resultMatchers");
17
+ //# sourceMappingURL=matchers.js.map
@@ -0,0 +1,42 @@
1
+ import type { TestModel } from './test-models';
2
+ /** Expected row shape for result matching */
3
+ export type ExpectedRow = Record<string, unknown>;
4
+ /** Options for result matchers */
5
+ export interface MatcherOptions {
6
+ debug?: boolean;
7
+ }
8
+ declare global {
9
+ namespace jest {
10
+ interface Matchers<R> {
11
+ /**
12
+ * Partial matching - extra fields and rows allowed.
13
+ *
14
+ * @example
15
+ * await expect(query).toMatchResult(tm, {name: 'alice'});
16
+ * await expect(query).toMatchResult(tm, {name: 'alice'}, {name: 'bob'});
17
+ */
18
+ toMatchResult(tm: TestModel, ...rowsOrOptions: (ExpectedRow | MatcherOptions)[]): Promise<R>;
19
+ /**
20
+ * Exact matching - exact fields and exact row count.
21
+ *
22
+ * @example
23
+ * await expect(query).toEqualResult(tm, [{name: 'alice'}]);
24
+ */
25
+ toEqualResult(tm: TestModel, rows: ExpectedRow[], options?: MatcherOptions): Promise<R>;
26
+ /**
27
+ * Partial fields but exact row count.
28
+ *
29
+ * @example
30
+ * await expect(query).toMatchRows(tm, [{name: 'alice'}, {name: 'bob'}]);
31
+ */
32
+ toMatchRows(tm: TestModel, rows: ExpectedRow[], options?: MatcherOptions): Promise<R>;
33
+ /**
34
+ * Check nested values via dotted paths. Takes first element at each array level.
35
+ *
36
+ * @example
37
+ * expect(result.data[0]).toHavePath({'by_state.state': 'TX'});
38
+ */
39
+ toHavePath(paths: Record<string, unknown>): R;
40
+ }
41
+ }
42
+ }