tplm-lang 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,9 +40,19 @@ The original TPL was a language developed by the U.S. Bureau of Labor Statistics
40
40
  ## Installation
41
41
 
42
42
  ```bash
43
- npm install tplm-lang
43
+ # DuckDB (local CSV, Parquet files)
44
+ npm install tplm-lang @malloydata/db-duckdb
45
+
46
+ # BigQuery
47
+ npm install tplm-lang @malloydata/db-bigquery
48
+
49
+ # Both backends
50
+ npm install tplm-lang @malloydata/db-duckdb @malloydata/db-bigquery
44
51
  ```
45
52
 
53
+ Database backends are **optional peer dependencies** — install only the one(s) you need.
54
+ If you only need parsing and compilation (no query execution), `npm install tplm-lang` alone is sufficient.
55
+
46
56
  ## Quick Start - Query Your Data Immediately
47
57
 
48
58
  For basic use, no Malloy knowledge is required; just use your local data and start querying.
@@ -2,9 +2,14 @@
2
2
  * TPL Executor
3
3
  *
4
4
  * Executes Malloy queries against BigQuery or DuckDB (local mode) and returns results.
5
+ *
6
+ * Database backends are loaded lazily via dynamic imports, so consumers only need
7
+ * to install the backend(s) they actually use:
8
+ * npm install @malloydata/db-duckdb # for DuckDB support
9
+ * npm install @malloydata/db-bigquery # for BigQuery support
5
10
  */
6
- import { BigQueryConnection } from '@malloydata/db-bigquery';
7
- import { DuckDBConnection } from '@malloydata/db-duckdb';
11
+ import type { DuckDBConnection } from '@malloydata/db-duckdb';
12
+ import type { BigQueryConnection } from '@malloydata/db-bigquery';
8
13
  import { Runtime, Connection } from '@malloydata/malloy';
9
14
  export type ConnectionType = 'bigquery' | 'duckdb';
10
15
  export interface BigQueryConnectionOptions {
@@ -24,20 +29,26 @@ export interface DuckDBConnectionOptions {
24
29
  }[];
25
30
  }
26
31
  export type ConnectionOptions = BigQueryConnectionOptions | DuckDBConnectionOptions;
27
- export declare function createConnection(options: ConnectionOptions): Connection;
32
+ /**
33
+ * Store connection options for deferred creation.
34
+ * The connection will be created lazily on first getConnection() call.
35
+ */
36
+ export declare function setPendingConnection(options: ConnectionOptions): void;
37
+ export declare function createConnection(options: ConnectionOptions): Promise<Connection>;
28
38
  /**
29
39
  * Create a default local DuckDB connection with test data
30
40
  */
31
- export declare function createLocalConnection(): DuckDBConnection;
41
+ export declare function createLocalConnection(): Promise<Connection>;
32
42
  /**
33
43
  * Get the current connection or create default connection.
34
44
  *
35
45
  * Connection priority:
36
46
  * 1. If connection already exists, return it
37
- * 2. If TPL_BIGQUERY=true env var is set and credentials exist, use BigQuery
38
- * 3. Otherwise, use DuckDB (default)
47
+ * 2. If pending options were set (via setPendingConnection), use those
48
+ * 3. If TPL_BIGQUERY=true env var is set and credentials exist, use BigQuery
49
+ * 4. Otherwise, use DuckDB (default)
39
50
  */
40
- export declare function getConnection(): Connection;
51
+ export declare function getConnection(): Promise<Connection>;
41
52
  export declare function getConnectionType(): ConnectionType | null;
42
53
  export declare function listDatasets(): Promise<string[]>;
43
54
  export declare function listTables(dataset: string): Promise<{
@@ -68,4 +79,5 @@ export declare function executeMalloy(malloySource: string, options?: ExecuteOpt
68
79
  * Execute raw SQL against the current connection
69
80
  */
70
81
  export declare function executeSQL(sql: string): Promise<any[]>;
71
- export { BigQueryConnection, DuckDBConnection, Runtime };
82
+ export { Runtime };
83
+ export type { DuckDBConnection, BigQueryConnection };
@@ -2,9 +2,12 @@
2
2
  * TPL Executor
3
3
  *
4
4
  * Executes Malloy queries against BigQuery or DuckDB (local mode) and returns results.
5
+ *
6
+ * Database backends are loaded lazily via dynamic imports, so consumers only need
7
+ * to install the backend(s) they actually use:
8
+ * npm install @malloydata/db-duckdb # for DuckDB support
9
+ * npm install @malloydata/db-bigquery # for BigQuery support
5
10
  */
6
- import { BigQueryConnection } from '@malloydata/db-bigquery';
7
- import { DuckDBConnection } from '@malloydata/db-duckdb';
8
11
  import { Runtime } from '@malloydata/malloy';
9
12
  import * as fs from 'fs';
10
13
  import * as path from 'path';
@@ -12,11 +15,49 @@ import { fileURLToPath } from 'url';
12
15
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
16
  const PROJECT_ROOT = path.resolve(__dirname, '../..');
14
17
  // ---
18
+ // LAZY MODULE LOADERS
19
+ // ---
20
+ let _DuckDBModule = null;
21
+ let _BigQueryModule = null;
22
+ async function loadDuckDB() {
23
+ if (!_DuckDBModule) {
24
+ try {
25
+ _DuckDBModule = await import('@malloydata/db-duckdb');
26
+ }
27
+ catch {
28
+ throw new Error('@malloydata/db-duckdb is required for DuckDB support. Install it with: npm install @malloydata/db-duckdb');
29
+ }
30
+ }
31
+ return _DuckDBModule;
32
+ }
33
+ async function loadBigQuery() {
34
+ if (!_BigQueryModule) {
35
+ try {
36
+ _BigQueryModule = await import('@malloydata/db-bigquery');
37
+ }
38
+ catch {
39
+ throw new Error('@malloydata/db-bigquery is required for BigQuery support. Install it with: npm install @malloydata/db-bigquery');
40
+ }
41
+ }
42
+ return _BigQueryModule;
43
+ }
44
+ // ---
15
45
  // CONNECTION SETUP
16
46
  // ---
17
47
  let connectionInstance = null;
18
48
  let currentConnectionType = null;
19
- export function createConnection(options) {
49
+ let pendingOptions = null;
50
+ /**
51
+ * Store connection options for deferred creation.
52
+ * The connection will be created lazily on first getConnection() call.
53
+ */
54
+ export function setPendingConnection(options) {
55
+ pendingOptions = options;
56
+ // Clear any existing connection so the new options take effect
57
+ connectionInstance = null;
58
+ currentConnectionType = null;
59
+ }
60
+ export async function createConnection(options) {
20
61
  if (options.type === 'bigquery') {
21
62
  return createBigQueryConnection(options);
22
63
  }
@@ -24,7 +65,8 @@ export function createConnection(options) {
24
65
  return createDuckDBConnection(options);
25
66
  }
26
67
  }
27
- function createBigQueryConnection(options) {
68
+ async function createBigQueryConnection(options) {
69
+ const { BigQueryConnection } = await loadBigQuery();
28
70
  const credentialsPath = options.credentialsPath ?? './config/dev-credentials.json';
29
71
  // Read credentials to get project ID if not provided
30
72
  const credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8'));
@@ -36,7 +78,8 @@ function createBigQueryConnection(options) {
36
78
  currentConnectionType = 'bigquery';
37
79
  return connection;
38
80
  }
39
- function createDuckDBConnection(options) {
81
+ async function createDuckDBConnection(options) {
82
+ const { DuckDBConnection } = await loadDuckDB();
40
83
  const connection = new DuckDBConnection('duckdb', options.databasePath ?? ':memory:', undefined);
41
84
  connectionInstance = connection;
42
85
  currentConnectionType = 'duckdb';
@@ -45,7 +88,7 @@ function createDuckDBConnection(options) {
45
88
  /**
46
89
  * Create a default local DuckDB connection with test data
47
90
  */
48
- export function createLocalConnection() {
91
+ export async function createLocalConnection() {
49
92
  return createDuckDBConnection({
50
93
  type: 'duckdb',
51
94
  });
@@ -55,37 +98,48 @@ export function createLocalConnection() {
55
98
  *
56
99
  * Connection priority:
57
100
  * 1. If connection already exists, return it
58
- * 2. If TPL_BIGQUERY=true env var is set and credentials exist, use BigQuery
59
- * 3. Otherwise, use DuckDB (default)
101
+ * 2. If pending options were set (via setPendingConnection), use those
102
+ * 3. If TPL_BIGQUERY=true env var is set and credentials exist, use BigQuery
103
+ * 4. Otherwise, use DuckDB (default)
60
104
  */
61
- export function getConnection() {
105
+ export async function getConnection() {
62
106
  if (!connectionInstance) {
107
+ // Use pending options if set
108
+ if (pendingOptions) {
109
+ const opts = pendingOptions;
110
+ pendingOptions = null;
111
+ return await createConnection(opts);
112
+ }
63
113
  // BigQuery only if explicitly requested via env var AND credentials exist
64
114
  const useBigQuery = process.env.TPL_BIGQUERY === 'true';
65
115
  const credentialsPath = './config/dev-credentials.json';
66
116
  if (useBigQuery && fs.existsSync(credentialsPath)) {
67
117
  try {
68
- return createConnection({ type: 'bigquery', credentialsPath });
118
+ return await createConnection({ type: 'bigquery', credentialsPath });
69
119
  }
70
120
  catch (e) {
71
121
  console.log('BigQuery connection failed, falling back to DuckDB');
72
- return createLocalConnection();
122
+ return await createLocalConnection();
73
123
  }
74
124
  }
75
125
  // Default: DuckDB
76
- return createLocalConnection();
126
+ return await createLocalConnection();
77
127
  }
78
128
  return connectionInstance;
79
129
  }
80
130
  export function getConnectionType() {
131
+ // Return pending type if connection hasn't been created yet
132
+ if (!currentConnectionType && pendingOptions) {
133
+ return pendingOptions.type;
134
+ }
81
135
  return currentConnectionType;
82
136
  }
83
137
  // ---
84
138
  // SCHEMA EXPLORATION (BigQuery only)
85
139
  // ---
86
140
  export async function listDatasets() {
87
- const conn = getConnection();
88
- if (!(conn instanceof BigQueryConnection)) {
141
+ const conn = await getConnection();
142
+ if (currentConnectionType !== 'bigquery') {
89
143
  throw new Error('listDatasets only supported for BigQuery');
90
144
  }
91
145
  const result = await conn.runSQL(`
@@ -96,8 +150,8 @@ export async function listDatasets() {
96
150
  return result.rows.map((row) => row.schema_name);
97
151
  }
98
152
  export async function listTables(dataset) {
99
- const conn = getConnection();
100
- if (!(conn instanceof BigQueryConnection)) {
153
+ const conn = await getConnection();
154
+ if (currentConnectionType !== 'bigquery') {
101
155
  throw new Error('listTables only supported for BigQuery');
102
156
  }
103
157
  const result = await conn.runSQL(`
@@ -117,8 +171,8 @@ export async function listTables(dataset) {
117
171
  }));
118
172
  }
119
173
  export async function describeTable(dataset, table) {
120
- const conn = getConnection();
121
- if (!(conn instanceof BigQueryConnection)) {
174
+ const conn = await getConnection();
175
+ if (currentConnectionType !== 'bigquery') {
122
176
  throw new Error('describeTable only supported for BigQuery');
123
177
  }
124
178
  const result = await conn.runSQL(`
@@ -133,8 +187,8 @@ export async function describeTable(dataset, table) {
133
187
  }));
134
188
  }
135
189
  export async function sampleTable(dataset, table, limit = 10) {
136
- const conn = getConnection();
137
- if (!(conn instanceof BigQueryConnection)) {
190
+ const conn = await getConnection();
191
+ if (currentConnectionType !== 'bigquery') {
138
192
  throw new Error('sampleTable only supported for BigQuery');
139
193
  }
140
194
  const result = await conn.runSQL(`
@@ -150,7 +204,7 @@ export async function sampleTable(dataset, table, limit = 10) {
150
204
  */
151
205
  export function getDefaultSource() {
152
206
  const connType = getConnectionType();
153
- if (connType === 'duckdb') {
207
+ if (connType === 'duckdb' || connType === null) {
154
208
  const csvPath = path.join(PROJECT_ROOT, 'data/test_usa_names.csv');
155
209
  return `
156
210
  source: names is duckdb.table('${csvPath}') extend {
@@ -181,7 +235,7 @@ source: names is bigquery.table('slite-development.tpl_test.test_usa_names') ext
181
235
  * Execute a Malloy query string
182
236
  */
183
237
  export async function executeMalloy(malloySource, options = {}) {
184
- const conn = getConnection();
238
+ const conn = await getConnection();
185
239
  // Create a minimal URL reader (we're passing source directly)
186
240
  const urlReader = {
187
241
  readURL: async (url) => {
@@ -215,18 +269,11 @@ export async function executeMalloy(malloySource, options = {}) {
215
269
  * Execute raw SQL against the current connection
216
270
  */
217
271
  export async function executeSQL(sql) {
218
- const conn = getConnection();
219
- if (conn instanceof BigQueryConnection) {
220
- const result = await conn.runSQL(sql);
221
- return result.rows;
222
- }
223
- else if (conn instanceof DuckDBConnection) {
224
- const result = await conn.runSQL(sql);
225
- return result.rows;
226
- }
227
- throw new Error('Unknown connection type');
272
+ const conn = await getConnection();
273
+ const result = await conn.runSQL(sql);
274
+ return result.rows;
228
275
  }
229
276
  // ---
230
277
  // EXPORTS
231
278
  // ---
232
- export { BigQueryConnection, DuckDBConnection, Runtime };
279
+ export { Runtime };
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ export { buildTableSpec, generateQueryPlan, generateMalloyQueries, buildGridSpec
34
34
  export type { TableSpec, QueryPlan, GridSpec, HeaderNode, QueryResults, MalloyQuerySpec, } from './compiler/index.js';
35
35
  export { renderGridToHTML } from './renderer/index.js';
36
36
  export type { GridRenderOptions } from './renderer/index.js';
37
- export { createConnection, createLocalConnection, getConnection, getConnectionType, getDefaultSource, executeMalloy, executeSQL, } from './executor/index.js';
37
+ export { createConnection, createLocalConnection, getConnection, getConnectionType, getDefaultSource, executeMalloy, executeSQL, setPendingConnection, } from './executor/index.js';
38
38
  export type { ConnectionType, ConnectionOptions, BigQueryConnectionOptions, DuckDBConnectionOptions, ExecuteOptions, } from './executor/index.js';
39
39
  import type { TableSpec, QueryPlan, GridSpec, MalloyQuerySpec } from './compiler/index.js';
40
40
  import type { DimensionOrderingProvider } from './compiler/dimension-utils.js';
@@ -134,10 +134,12 @@ export declare class TPL {
134
134
  }
135
135
  /**
136
136
  * Create a TPL instance with DuckDB connection (simplest way to start).
137
+ * Connection is created lazily on first execute() call.
137
138
  */
138
139
  export declare function createTPL(options?: TPLOptions): TPL;
139
140
  /**
140
141
  * Create a TPL instance with BigQuery connection.
142
+ * Connection is created lazily on first execute() call.
141
143
  */
142
144
  export declare function createBigQueryTPL(options?: TPLOptions & {
143
145
  credentialsPath?: string;
package/dist/index.js CHANGED
@@ -34,11 +34,11 @@ export { buildTableSpec, generateQueryPlan, generateMalloyQueries, buildGridSpec
34
34
  // renderer
35
35
  export { renderGridToHTML } from './renderer/index.js';
36
36
  // executor
37
- export { createConnection, createLocalConnection, getConnection, getConnectionType, getDefaultSource, executeMalloy, executeSQL, } from './executor/index.js';
37
+ export { createConnection, createLocalConnection, getConnection, getConnectionType, getDefaultSource, executeMalloy, executeSQL, setPendingConnection, } from './executor/index.js';
38
38
  // --- internal imports ---
39
39
  import { parse } from './parser/index.js';
40
40
  import { buildTableSpec, generateQueryPlan, generateMalloyQueries, buildGridSpec } from './compiler/index.js';
41
- import { executeMalloy, createConnection, createLocalConnection, } from './executor/index.js';
41
+ import { executeMalloy, setPendingConnection, } from './executor/index.js';
42
42
  import { renderGridToHTML } from './renderer/index.js';
43
43
  /**
44
44
  * High-level TPL API for parsing, compiling, executing, and rendering.
@@ -102,16 +102,18 @@ export class TPL {
102
102
  }
103
103
  /**
104
104
  * Create a TPL instance with DuckDB connection (simplest way to start).
105
+ * Connection is created lazily on first execute() call.
105
106
  */
106
107
  export function createTPL(options = {}) {
107
- createLocalConnection();
108
+ setPendingConnection({ type: 'duckdb' });
108
109
  return new TPL(options);
109
110
  }
110
111
  /**
111
112
  * Create a TPL instance with BigQuery connection.
113
+ * Connection is created lazily on first execute() call.
112
114
  */
113
115
  export function createBigQueryTPL(options = {}) {
114
- createConnection({
116
+ setPendingConnection({
115
117
  type: 'bigquery',
116
118
  credentialsPath: options.credentialsPath,
117
119
  projectId: options.projectId,
@@ -134,7 +136,7 @@ import { analyzeAndGeneratePercentileConfig, postProcessMalloyForPercentiles, ge
134
136
  * ```
135
137
  */
136
138
  export function fromDuckDBTable(tablePath, options = {}) {
137
- createLocalConnection();
139
+ setPendingConnection({ type: 'duckdb' });
138
140
  const sourceName = 'data';
139
141
  const model = `source: ${sourceName} is duckdb.table('${tablePath}')`;
140
142
  return new EasyTPL(model, sourceName, {
@@ -170,7 +172,7 @@ export function fromCSV(csvPath, options = {}) {
170
172
  * ```
171
173
  */
172
174
  export function fromBigQueryTable(options) {
173
- createConnection({
175
+ setPendingConnection({
174
176
  type: 'bigquery',
175
177
  credentialsPath: options.credentialsPath,
176
178
  projectId: options.projectId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tplm-lang",
3
- "version": "0.2.6",
3
+ "version": "0.3.0",
4
4
  "description": "TPLm - Table Producing Language backed by Malloy.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,17 +29,29 @@
29
29
  "docs:preview": "cd docs-site && npm run docs:preview"
30
30
  },
31
31
  "devDependencies": {
32
+ "@malloydata/db-bigquery": "^0.0.326",
33
+ "@malloydata/db-duckdb": "^0.0.326",
32
34
  "@types/node": "^20.0.0",
33
35
  "peggy": "^4.0.0",
34
36
  "typescript": "^5.0.0",
35
37
  "vitest": "^4.0.16"
36
38
  },
37
39
  "dependencies": {
38
- "@malloydata/db-bigquery": "^0.0.326",
39
- "@malloydata/db-duckdb": "^0.0.326",
40
40
  "@malloydata/malloy": "^0.0.326",
41
41
  "chevrotain": "^11.0.3"
42
42
  },
43
+ "peerDependencies": {
44
+ "@malloydata/db-bigquery": "^0.0.326",
45
+ "@malloydata/db-duckdb": "^0.0.326"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "@malloydata/db-bigquery": {
49
+ "optional": true
50
+ },
51
+ "@malloydata/db-duckdb": {
52
+ "optional": true
53
+ }
54
+ },
43
55
  "engines": {
44
56
  "node": ">=18.0.0"
45
57
  },