@an5/adapters 0.2.0 → 0.2.2

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 (43) hide show
  1. package/README.md +226 -226
  2. package/package.json +46 -38
  3. package/typescript/an5Adapter.d.ts +4 -2
  4. package/typescript/an5Adapter.js +58 -21
  5. package/typescript/browser.d.ts +5 -0
  6. package/typescript/browser.js +12 -0
  7. package/typescript/googlesheets/adapter.d.ts +3 -2
  8. package/typescript/googlesheets/adapter.js +77 -7
  9. package/typescript/googlesheets/an5SheetsAdapter.d.ts +1 -1
  10. package/typescript/googlesheets/config.d.ts +11 -0
  11. package/typescript/googlesheets/config.js +7 -0
  12. package/typescript/googlesheets/helpers.js +1 -1
  13. package/typescript/googlesheets/index.d.ts +2 -1
  14. package/typescript/googlesheets/retry.js +21 -8
  15. package/typescript/googlesheets/tableClient.js +4 -3
  16. package/typescript/index.d.ts +4 -2
  17. package/typescript/{an5Adapter.ts → src/an5Adapter.ts} +505 -474
  18. package/typescript/{base → src/base}/index.ts +3 -3
  19. package/typescript/{base → src/base}/metadata.ts +28 -28
  20. package/typescript/{base → src/base}/sql.ts +98 -98
  21. package/typescript/{base → src/base}/types.ts +24 -24
  22. package/typescript/src/browser.ts +6 -0
  23. package/typescript/{config.ts → src/config.ts} +95 -95
  24. package/typescript/{googlesheets → src/googlesheets}/adapter.ts +242 -167
  25. package/typescript/{googlesheets → src/googlesheets}/an5SheetsAdapter.ts +3 -3
  26. package/typescript/{googlesheets → src/googlesheets}/config.ts +44 -33
  27. package/typescript/{googlesheets → src/googlesheets}/helpers.ts +124 -124
  28. package/typescript/{googlesheets → src/googlesheets}/index.ts +3 -2
  29. package/typescript/{googlesheets → src/googlesheets}/parseConnectionString.ts +44 -44
  30. package/typescript/src/googlesheets/retry.ts +32 -0
  31. package/typescript/{googlesheets → src/googlesheets}/sqlExecutor.ts +301 -301
  32. package/typescript/{googlesheets → src/googlesheets}/tableClient.ts +435 -434
  33. package/typescript/{googlesheets → src/googlesheets}/types.ts +6 -6
  34. package/typescript/{index.ts → src/index.ts} +19 -15
  35. package/typescript/{mssql → src/mssql}/engine.ts +132 -132
  36. package/typescript/{mysql → src/mssql}/index.ts +1 -1
  37. package/typescript/{mysql → src/mysql}/engine.ts +80 -80
  38. package/typescript/{postgres → src/mysql}/index.ts +1 -1
  39. package/typescript/{postgres → src/postgres}/engine.ts +87 -87
  40. package/typescript/{sqlite → src/postgres}/index.ts +1 -1
  41. package/typescript/{sqlite → src/sqlite}/engine.ts +55 -55
  42. package/typescript/{mssql → src/sqlite}/index.ts +1 -1
  43. package/typescript/googlesheets/retry.ts +0 -23
@@ -7,10 +7,6 @@ const googlesheets_1 = require("./googlesheets");
7
7
  const parseConnectionString_1 = require("./googlesheets/parseConnectionString");
8
8
  const sql_1 = require("./base/sql");
9
9
  const metadata_1 = require("./base/metadata");
10
- const mssql_1 = require("./mssql");
11
- const postgres_1 = require("./postgres");
12
- const mysql_1 = require("./mysql");
13
- const sqlite_1 = require("./sqlite");
14
10
  var metadata_2 = require("./base/metadata");
15
11
  Object.defineProperty(exports, "setAdapterMetadata", { enumerable: true, get: function () { return metadata_2.setAdapterMetadata; } });
16
12
  function isSheetsConfig(config) {
@@ -18,9 +14,23 @@ function isSheetsConfig(config) {
18
14
  }
19
15
  // ─── An5Adapter ────────────────────────────────────────────────────────────────────
20
16
  class An5Adapter {
21
- get dialect() { return this.sheetsAdapter ? 'googlesheets' : this.requireEngine().dialect; }
17
+ get dialect() {
18
+ if (this.sheetsAdapter)
19
+ return 'googlesheets';
20
+ if (this._engine)
21
+ return this._engine.dialect;
22
+ if (this._engineType === 'postgres')
23
+ return 'postgres';
24
+ if (this._engineType === 'mysql')
25
+ return 'mysql';
26
+ if (this._engineType === 'sqlite')
27
+ return 'sqlite';
28
+ return 'mssql';
29
+ }
22
30
  constructor(adapterConfig) {
23
- this.engine = null;
31
+ this._engine = null;
32
+ this._engineType = null;
33
+ this._engineConfig = null;
24
34
  this.sheetsAdapter = null;
25
35
  if (isSheetsConfig(adapterConfig)) {
26
36
  this.sheetsAdapter = new googlesheets_1.An5SheetsAdapter(adapterConfig);
@@ -31,23 +41,48 @@ class An5Adapter {
31
41
  this.sheetsAdapter = new googlesheets_1.An5SheetsAdapter((0, parseConnectionString_1.parseSheetsConnectionString)(cs));
32
42
  return;
33
43
  }
44
+ this._engineConfig = adapterConfig;
34
45
  if (cs.startsWith('postgres://') || cs.startsWith('postgresql://')) {
35
- this.engine = new postgres_1.PostgresEngine(adapterConfig);
46
+ this._engineType = 'postgres';
36
47
  }
37
48
  else if (cs.startsWith('mysql://') || cs.startsWith('mariadb://')) {
38
- this.engine = new mysql_1.MysqlEngine(adapterConfig);
49
+ this._engineType = 'mysql';
39
50
  }
40
51
  else if (cs.startsWith('sqlite://') || cs.endsWith('.sqlite') || cs.endsWith('.db')) {
41
- this.engine = new sqlite_1.SqliteEngine(adapterConfig);
52
+ this._engineType = 'sqlite';
42
53
  }
43
54
  else {
44
- this.engine = new mssql_1.MssqlEngine(adapterConfig);
55
+ this._engineType = 'mssql';
45
56
  }
46
57
  }
47
- requireEngine() {
48
- if (!this.engine)
58
+ async requireEngine() {
59
+ if (!this._engine && this._engineType) {
60
+ switch (this._engineType) {
61
+ case 'postgres': {
62
+ const { PostgresEngine } = require('./postgres.js');
63
+ this._engine = new PostgresEngine(this._engineConfig);
64
+ break;
65
+ }
66
+ case 'mysql': {
67
+ const { MysqlEngine } = require('./mysql.js');
68
+ this._engine = new MysqlEngine(this._engineConfig);
69
+ break;
70
+ }
71
+ case 'sqlite': {
72
+ const { SqliteEngine } = require('./sqlite.js');
73
+ this._engine = new SqliteEngine(this._engineConfig);
74
+ break;
75
+ }
76
+ case 'mssql': {
77
+ const { MssqlEngine } = require('./mssql.js');
78
+ this._engine = new MssqlEngine(this._engineConfig);
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ if (!this._engine)
49
84
  throw new Error('SQL engine is not available for Google Sheets adapter');
50
- return this.engine;
85
+ return this._engine;
51
86
  }
52
87
  requireSheetsAdapter() {
53
88
  if (!this.sheetsAdapter)
@@ -57,19 +92,19 @@ class An5Adapter {
57
92
  async exec(query, params) {
58
93
  if (this.sheetsAdapter)
59
94
  return this.sheetsAdapter.exec(query, params);
60
- return this.requireEngine().exec(query, params);
95
+ return (await this.requireEngine()).exec(query, params);
61
96
  }
62
97
  /** INTERNAL: used by AdapterTableClient for DML statements needing row count */
63
98
  async _executeRaw(query, params) {
64
- return this.requireEngine().executeRaw(query, params);
99
+ return (await this.requireEngine()).executeRaw(query, params);
65
100
  }
66
101
  /** Execute a raw query with positional values → @p_0, @p_1, ... per dialect */
67
102
  async $queryRawUnsafe(query, ...values) {
68
103
  if (this.sheetsAdapter)
69
104
  return this.sheetsAdapter.$queryRawUnsafe(query, ...values);
105
+ const engine = await this.requireEngine();
70
106
  const params = {};
71
107
  values.forEach((v, i) => { params[`p_${i}`] = v; });
72
- // Normalise external positional placeholders → @p_N so each engine can re-transform
73
108
  let q = query;
74
109
  if (this.dialect === 'postgres') {
75
110
  let idx = 0;
@@ -79,11 +114,12 @@ class An5Adapter {
79
114
  let idx = 0;
80
115
  q = q.replace(/\?/g, () => `@p_${idx++}`);
81
116
  }
82
- return this.exec(q, params);
117
+ return engine.exec(q, params);
83
118
  }
84
119
  async $executeRaw(query, ...values) {
85
120
  if (this.sheetsAdapter)
86
121
  return this.sheetsAdapter.$executeRaw(query, ...values);
122
+ const engine = await this.requireEngine();
87
123
  const params = {};
88
124
  values.forEach((v, i) => { params[`p_${i}`] = v; });
89
125
  let q = query;
@@ -95,7 +131,7 @@ class An5Adapter {
95
131
  let idx = 0;
96
132
  q = q.replace(/\?/g, () => `@p_${idx++}`);
97
133
  }
98
- return this.requireEngine().executeRaw(q, params);
134
+ return engine.executeRaw(q, params);
99
135
  }
100
136
  async $executeRawUnsafe(query, ...values) {
101
137
  return this.$executeRaw(query, ...values);
@@ -103,19 +139,20 @@ class An5Adapter {
103
139
  async $connect() {
104
140
  if (this.sheetsAdapter)
105
141
  return this.sheetsAdapter.$connect();
106
- await this.requireEngine().connect();
142
+ await (await this.requireEngine()).connect();
107
143
  }
108
144
  async $disconnect() {
109
145
  if (this.sheetsAdapter)
110
146
  return this.sheetsAdapter.$disconnect();
111
- await this.requireEngine().disconnect();
147
+ await (await this.requireEngine()).disconnect();
112
148
  }
113
149
  async $transaction(fn, _options) {
114
150
  if (this.sheetsAdapter)
115
151
  return this.sheetsAdapter.$transaction(fn, _options);
116
152
  if (Array.isArray(fn))
117
153
  return Promise.all(fn);
118
- const handle = await this.requireEngine().beginTransaction();
154
+ const engine = await this.requireEngine();
155
+ const handle = await engine.beginTransaction();
119
156
  const txAdapter = new An5AdapterTx(handle, this.dialect);
120
157
  try {
121
158
  const result = await fn(txAdapter);
@@ -0,0 +1,5 @@
1
+ export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter } from './googlesheets';
2
+ export type { An5SheetsAdapterConfig } from './googlesheets';
3
+ export type { An5AdapterConfig, Dialect } from './base/types';
4
+ export { getFieldsForModel, getModelToTable, setAdapterMetadata } from './base/metadata';
5
+ export type { AdapterMetadata } from './base/metadata';
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setAdapterMetadata = exports.getModelToTable = exports.getFieldsForModel = exports.createAn5SheetsAdapter = exports.SheetsTableClient = exports.An5SheetsAdapter = void 0;
4
+ // Browser-safe entry — no SQL engine imports
5
+ var googlesheets_1 = require("./googlesheets");
6
+ Object.defineProperty(exports, "An5SheetsAdapter", { enumerable: true, get: function () { return googlesheets_1.An5SheetsAdapter; } });
7
+ Object.defineProperty(exports, "SheetsTableClient", { enumerable: true, get: function () { return googlesheets_1.SheetsTableClient; } });
8
+ Object.defineProperty(exports, "createAn5SheetsAdapter", { enumerable: true, get: function () { return googlesheets_1.createAn5SheetsAdapter; } });
9
+ var metadata_1 = require("./base/metadata");
10
+ Object.defineProperty(exports, "getFieldsForModel", { enumerable: true, get: function () { return metadata_1.getFieldsForModel; } });
11
+ Object.defineProperty(exports, "getModelToTable", { enumerable: true, get: function () { return metadata_1.getModelToTable; } });
12
+ Object.defineProperty(exports, "setAdapterMetadata", { enumerable: true, get: function () { return metadata_1.setAdapterMetadata; } });
@@ -1,13 +1,14 @@
1
- import { sheets_v4 } from 'googleapis';
2
1
  import { An5SheetsAdapterConfig, resolveConfig } from './config';
3
2
  import { SheetsTableClient } from './tableClient';
4
3
  import { SheetMeta } from './types';
5
4
  export declare class An5SheetsAdapter {
6
5
  private sheets;
6
+ private fetchApi;
7
7
  config: ReturnType<typeof resolveConfig>;
8
8
  private sheetsCache;
9
9
  constructor(config: An5SheetsAdapterConfig);
10
- getSheets(): Promise<sheets_v4.Sheets>;
10
+ private get isOAuth();
11
+ getSheets(): Promise<any>;
11
12
  private getApi;
12
13
  invalidateCache(): void;
13
14
  listSheets(): Promise<string[]>;
@@ -2,26 +2,95 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.An5SheetsAdapter = void 0;
4
4
  exports.createAn5SheetsAdapter = createAn5SheetsAdapter;
5
- const googleapis_1 = require("googleapis");
6
5
  const config_1 = require("./config");
7
6
  const sqlExecutor_1 = require("./sqlExecutor");
8
7
  const tableClient_1 = require("./tableClient");
9
8
  const retry_1 = require("./retry");
9
+ // ─── Fetch-based API proxy for OAuth Access Token (browser-compatible) ────────
10
+ function createFetchApi(spreadsheetId, accessToken) {
11
+ const base = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}`;
12
+ const headers = { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' };
13
+ async function request(method, path, body) {
14
+ const url = path.startsWith('http') ? path : `${base}${path}`;
15
+ const opts = { method, headers };
16
+ if (body)
17
+ opts.body = JSON.stringify(body);
18
+ const res = await fetch(url, opts);
19
+ if (!res.ok) {
20
+ const text = await res.text().catch(() => '');
21
+ const err = new Error(`Google Sheets API error (${res.status}): ${text}`);
22
+ err.status = res.status;
23
+ err.body = text;
24
+ const retryAfter = res.headers.get('Retry-After');
25
+ if (retryAfter)
26
+ err.retryAfter = parseInt(retryAfter, 10);
27
+ throw err;
28
+ }
29
+ const text = await res.text();
30
+ return text ? JSON.parse(text) : undefined;
31
+ }
32
+ function fmtRange(range) {
33
+ return `/${encodeURIComponent(range)}`;
34
+ }
35
+ return {
36
+ spreadsheets: {
37
+ get: async (params) => {
38
+ const data = await request('GET', `?fields=${params.fields || ''}`);
39
+ return { data };
40
+ },
41
+ batchUpdate: async (params) => {
42
+ const data = await request('POST', ':batchUpdate', params.requestBody);
43
+ return { data };
44
+ },
45
+ values: {
46
+ get: async (params) => {
47
+ const data = await request('GET', fmtRange(params.range));
48
+ return { data };
49
+ },
50
+ update: async (params) => {
51
+ const qs = `?valueInputOption=${params.valueInputOption || 'RAW'}`;
52
+ const data = await request('PUT', `${fmtRange(params.range)}${qs}`, params.requestBody);
53
+ return { data };
54
+ },
55
+ append: async (params) => {
56
+ const qs = `?valueInputOption=${params.valueInputOption || 'RAW'}&insertDataOption=${params.insertDataOption || 'INSERT_ROWS'}`;
57
+ await request('POST', `${fmtRange(params.range)}:append${qs}`, params.requestBody);
58
+ return { data: { updates: { updatedRows: (params.requestBody?.values || []).length } } };
59
+ },
60
+ clear: async (params) => {
61
+ await request('POST', `${fmtRange(params.range)}:clear`);
62
+ return { data: {} };
63
+ },
64
+ },
65
+ },
66
+ };
67
+ }
10
68
  // ─── Adapter ──────────────────────────────────────────────────────────────────
11
69
  class An5SheetsAdapter {
12
70
  constructor(config) {
13
71
  this.sheets = null;
72
+ this.fetchApi = null;
14
73
  this.sheetsCache = null;
15
74
  this.config = (0, config_1.resolveConfig)(config);
16
75
  }
76
+ get isOAuth() {
77
+ return !!this.config.accessToken;
78
+ }
17
79
  async getSheets() {
80
+ if (this.isOAuth) {
81
+ if (!this.fetchApi) {
82
+ this.fetchApi = createFetchApi(this.config.spreadsheetId, this.config.accessToken);
83
+ }
84
+ return this.fetchApi;
85
+ }
18
86
  if (!this.sheets) {
19
- const auth = new googleapis_1.google.auth.JWT({
87
+ const { google } = await import('googleapis');
88
+ const auth = new google.auth.JWT({
20
89
  email: this.config.clientEmail,
21
90
  key: this.config.privateKey,
22
91
  scopes: ['https://www.googleapis.com/auth/spreadsheets'],
23
92
  });
24
- this.sheets = googleapis_1.google.sheets({ version: 'v4', auth });
93
+ this.sheets = google.sheets({ version: 'v4', auth });
25
94
  }
26
95
  return this.sheets;
27
96
  }
@@ -37,7 +106,7 @@ class An5SheetsAdapter {
37
106
  spreadsheetId: this.config.spreadsheetId,
38
107
  fields: 'sheets.properties.title',
39
108
  }));
40
- return (res.data.sheets || []).map(s => s.properties?.title || '');
109
+ return (res.data.sheets || []).map((s) => s.properties?.title || '');
41
110
  }
42
111
  async getSheetMeta(name) {
43
112
  const api = await this.getApi();
@@ -47,14 +116,14 @@ class An5SheetsAdapter {
47
116
  spreadsheetId: this.config.spreadsheetId,
48
117
  fields: 'sheets.properties',
49
118
  });
50
- return (res.data.sheets || []).map(s => ({
119
+ return (res.data.sheets || []).map((s) => ({
51
120
  sheetId: s.properties?.sheetId ?? -1,
52
121
  title: s.properties?.title ?? '',
53
- })).filter(m => m.sheetId >= 0);
122
+ })).filter((m) => m.sheetId >= 0);
54
123
  });
55
124
  }
56
125
  const all = await this.sheetsCache;
57
- return all.find(m => m.title === name) || null;
126
+ return all.find((m) => m.title === name) || null;
58
127
  }
59
128
  async deleteSheet(name) {
60
129
  const meta = await this.getSheetMeta(name);
@@ -74,6 +143,7 @@ class An5SheetsAdapter {
74
143
  }
75
144
  async $disconnect() {
76
145
  this.sheets = null;
146
+ this.fetchApi = null;
77
147
  this.sheetsCache = null;
78
148
  }
79
149
  async exec(query, params) {
@@ -1,3 +1,3 @@
1
1
  export { An5SheetsAdapter, createAn5SheetsAdapter } from './adapter';
2
2
  export { SheetsTableClient } from './tableClient';
3
- export { An5SheetsAdapterConfig } from './config';
3
+ export type { An5SheetsAdapterConfig } from './config';
@@ -7,11 +7,22 @@ export interface An5SheetsAdapterConfig {
7
7
  private_key: string;
8
8
  };
9
9
  sheetMapping?: Record<string, string>;
10
+ /** OAuth2 access token for browser-based authentication (e.g. from Firebase Auth Google sign-in).
11
+ * When provided, the adapter uses raw fetch() instead of googleapis JWT, making it compatible
12
+ * with browser environments. Mutually exclusive with clientEmail/privateKey/credentials. */
13
+ accessToken?: string;
10
14
  }
11
15
  export declare function normalizeKey(key: string): string;
12
16
  export declare function resolveConfig(config: An5SheetsAdapterConfig): {
17
+ spreadsheetId: string;
18
+ accessToken: string;
19
+ sheetMapping: Record<string, string> | undefined;
20
+ clientEmail?: undefined;
21
+ privateKey?: undefined;
22
+ } | {
13
23
  spreadsheetId: string;
14
24
  clientEmail: string;
15
25
  privateKey: string;
16
26
  sheetMapping: Record<string, string> | undefined;
27
+ accessToken?: undefined;
17
28
  };
@@ -12,6 +12,13 @@ function normalizeKey(key) {
12
12
  return `-----BEGIN PRIVATE KEY-----\n${lines}\n-----END PRIVATE KEY-----`;
13
13
  }
14
14
  function resolveConfig(config) {
15
+ if (config.accessToken) {
16
+ return {
17
+ spreadsheetId: config.spreadsheetId,
18
+ accessToken: config.accessToken,
19
+ sheetMapping: config.sheetMapping,
20
+ };
21
+ }
15
22
  if (config.credentials) {
16
23
  return {
17
24
  spreadsheetId: config.spreadsheetId,
@@ -95,7 +95,7 @@ function buildOrderBy(orderBy) {
95
95
  const parts = [];
96
96
  for (const entry of entries) {
97
97
  for (const [key, dir] of Object.entries(entry)) {
98
- parts.push(`${key} ${dir.toUpperCase()}`);
98
+ parts.push(`${key} ${String(dir || 'asc').toUpperCase()}`);
99
99
  }
100
100
  }
101
101
  return parts.join(', ');
@@ -1,2 +1,3 @@
1
- export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter, An5SheetsAdapterConfig } from './an5SheetsAdapter';
1
+ export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter } from './an5SheetsAdapter';
2
+ export type { An5SheetsAdapterConfig } from './an5SheetsAdapter';
2
3
  export { parseSheetsConnectionString } from './parseConnectionString';
@@ -5,19 +5,32 @@ exports.BASE_DELAY = exports.MAX_RETRIES = void 0;
5
5
  exports.withRetry = withRetry;
6
6
  exports.MAX_RETRIES = 3;
7
7
  exports.BASE_DELAY = 1000;
8
+ function isRateLimitError(err) {
9
+ const code = err?.code || err?.status || err?.response?.status;
10
+ if (code === 429)
11
+ return true;
12
+ if (code === 500 || code === 503)
13
+ return true;
14
+ if (code === 403) {
15
+ const reason = err?.errors?.[0]?.reason || '';
16
+ if (reason === 'rateLimitExceeded')
17
+ return true;
18
+ if (typeof err?.body === 'string' && err.body.includes('rateLimitExceeded'))
19
+ return true;
20
+ if (typeof err?.message === 'string' && err.message.includes('rateLimitExceeded'))
21
+ return true;
22
+ }
23
+ return false;
24
+ }
8
25
  async function withRetry(fn, attempt = 1) {
9
26
  try {
10
27
  return await fn();
11
28
  }
12
29
  catch (err) {
13
- const code = err?.code || err?.status || err?.response?.status;
14
- if ((code === 429 || code === 500 || code === 503) && attempt <= exports.MAX_RETRIES) {
15
- const delay = exports.BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 500;
16
- await new Promise(r => setTimeout(r, delay));
17
- return withRetry(fn, attempt + 1);
18
- }
19
- if (code === 403 && err?.errors?.[0]?.reason === 'rateLimitExceeded' && attempt <= exports.MAX_RETRIES) {
20
- const delay = exports.BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 500;
30
+ if (isRateLimitError(err) && attempt <= exports.MAX_RETRIES) {
31
+ const delay = err.retryAfter
32
+ ? err.retryAfter * 1000
33
+ : exports.BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 500;
21
34
  await new Promise(r => setTimeout(r, delay));
22
35
  return withRetry(fn, attempt + 1);
23
36
  }
@@ -315,13 +315,14 @@ class SheetsTableClient {
315
315
  result._count = rows.length;
316
316
  if (args._sum) {
317
317
  for (const f of Object.keys(args._sum)) {
318
- result[`_sum_${f}`] = rows.reduce((s, r) => s + (Number(r[f]) || 0), 0);
318
+ const nums = rows.map((r) => Number(r[f])).filter(n => !isNaN(n));
319
+ result[`_sum_${f}`] = nums.length > 0 ? nums.reduce((s, n) => s + n, 0) : null;
319
320
  }
320
321
  }
321
322
  if (args._avg) {
322
323
  for (const f of Object.keys(args._avg)) {
323
- const sum = rows.reduce((s, r) => s + (Number(r[f]) || 0), 0);
324
- result[`_avg_${f}`] = rows.length > 0 ? sum / rows.length : 0;
324
+ const nums = rows.map((r) => Number(r[f])).filter(n => !isNaN(n));
325
+ result[`_avg_${f}`] = nums.length > 0 ? nums.reduce((s, n) => s + n, 0) / nums.length : null;
325
326
  }
326
327
  }
327
328
  if (args._min) {
@@ -1,2 +1,4 @@
1
- export { An5Adapter, AdapterTableClient, createAn5Adapter, setAdapterMetadata, An5AdapterConfig, AdapterMetadata, Dialect, } from './an5Adapter';
2
- export { getLlmConfig, setLlmConfig, getEmbeddingConfig, setEmbeddingConfig, resetAdapter, LlmConfigData, EmbeddingConfigData, } from './config';
1
+ export { An5Adapter, AdapterTableClient, createAn5Adapter, setAdapterMetadata, } from './an5Adapter';
2
+ export type { An5AdapterConfig, AdapterMetadata, Dialect, } from './an5Adapter';
3
+ export { getLlmConfig, setLlmConfig, getEmbeddingConfig, setEmbeddingConfig, resetAdapter, } from './config';
4
+ export type { LlmConfigData, EmbeddingConfigData, } from './config';