@an5/adapters 0.2.5 → 0.2.6

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 (82) hide show
  1. package/README.md +189 -305
  2. package/dist/an5Adapter.d.ts +85 -7
  3. package/dist/an5Adapter.js +736 -64
  4. package/dist/base/metadata.d.ts +10 -0
  5. package/dist/base/metadata.js +107 -0
  6. package/dist/base/sql.d.ts +19 -1
  7. package/dist/base/sql.js +152 -24
  8. package/dist/base/types.d.ts +4 -1
  9. package/dist/browser.d.ts +4 -1
  10. package/dist/browser.js +9 -2
  11. package/dist/googlesheets/adapter.d.ts +1 -0
  12. package/dist/googlesheets/adapter.js +9 -2
  13. package/dist/googlesheets/config.d.ts +4 -4
  14. package/dist/googlesheets/helpers.js +2 -0
  15. package/dist/googlesheets/parseConnectionString.js +5 -2
  16. package/dist/googlesheets/sqlExecutor.js +29 -24
  17. package/dist/googlesheets/tableClient.js +14 -5
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +7 -1
  20. package/dist/mssql/engine.js +8 -4
  21. package/dist/mysql/engine.js +1 -1
  22. package/dist/postgres/engine.js +1 -1
  23. package/dist/sqlite/browserEngine.d.ts +27 -0
  24. package/dist/sqlite/browserEngine.js +161 -0
  25. package/dist/sqlite/engine.js +15 -3
  26. package/dist/sqlite/index.d.ts +1 -0
  27. package/dist/sqlite/index.js +1 -0
  28. package/dist/typed.d.ts +5 -0
  29. package/dist/typed.js +2 -0
  30. package/dotnet/An5Adapter.cs +1089 -275
  31. package/dotnet/Base/Core.cs +340 -82
  32. package/dotnet/Mssql/MssqlEngine.cs +145 -144
  33. package/dotnet/Postgres/PostgresEngine.cs +121 -121
  34. package/golang/an5_adapter.go +314 -0
  35. package/golang/base/base.go +100 -0
  36. package/golang/base/metadata.go +116 -0
  37. package/golang/base/where.go +311 -0
  38. package/golang/go.mod +3 -0
  39. package/golang/mssql/mssql.go +32 -0
  40. package/golang/postgres/postgres.go +37 -0
  41. package/golang/sqlite/sqlite.go +14 -0
  42. package/golang/sqlite_test.go +42 -0
  43. package/golang/table_client.go +1329 -0
  44. package/package.json +17 -7
  45. package/python/an5_adapter.py +119 -97
  46. package/python/base/__init__.py +3 -3
  47. package/python/base/dialects.py +11 -8
  48. package/python/base/metadata.py +35 -10
  49. package/python/base/sql.py +172 -121
  50. package/python/mssql/__init__.py +1 -1
  51. package/python/mssql/provider.py +62 -62
  52. package/python/postgres/__init__.py +1 -1
  53. package/python/postgres/provider.py +15 -15
  54. package/python/table_client.py +619 -216
  55. package/typescript/src/an5Adapter.ts +1220 -505
  56. package/typescript/src/base/index.ts +3 -3
  57. package/typescript/src/base/metadata.ts +148 -28
  58. package/typescript/src/base/sql.ts +264 -98
  59. package/typescript/src/base/types.ts +27 -24
  60. package/typescript/src/browser.ts +10 -6
  61. package/typescript/src/config.ts +95 -95
  62. package/typescript/src/googlesheets/adapter.ts +269 -259
  63. package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
  64. package/typescript/src/googlesheets/config.ts +47 -47
  65. package/typescript/src/googlesheets/helpers.ts +144 -143
  66. package/typescript/src/googlesheets/index.ts +3 -3
  67. package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
  68. package/typescript/src/googlesheets/retry.ts +126 -126
  69. package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
  70. package/typescript/src/googlesheets/tableClient.ts +461 -455
  71. package/typescript/src/googlesheets/types.ts +6 -6
  72. package/typescript/src/index.ts +31 -27
  73. package/typescript/src/mssql/engine.ts +134 -132
  74. package/typescript/src/mssql/index.ts +1 -1
  75. package/typescript/src/mysql/engine.ts +80 -80
  76. package/typescript/src/mysql/index.ts +1 -1
  77. package/typescript/src/postgres/engine.ts +87 -87
  78. package/typescript/src/postgres/index.ts +1 -1
  79. package/typescript/src/sqlite/browserEngine.ts +192 -0
  80. package/typescript/src/sqlite/engine.ts +66 -55
  81. package/typescript/src/sqlite/index.ts +3 -1
  82. package/typescript/src/typed.ts +52 -0
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createAdapter = exports.AdapterTableClient = exports.An5AdapterTx = exports.An5Adapter = exports.setAdapterMetadata = void 0;
3
+ exports.createAdapter = exports.AdapterTableClient = exports.An5AdapterTx = exports.ViewClient = exports.An5Adapter = exports.createAdapterProxy = exports.setAdapterMetadata = void 0;
4
4
  exports.createAn5Adapter = createAn5Adapter;
5
+ exports.createBrowserSqliteAdapter = createBrowserSqliteAdapter;
6
+ exports.executorFromAdapter = executorFromAdapter;
5
7
  const uuid_1 = require("./base/uuid");
6
8
  const googlesheets_1 = require("./googlesheets");
7
9
  const parseConnectionString_1 = require("./googlesheets/parseConnectionString");
@@ -9,9 +11,164 @@ const sql_1 = require("./base/sql");
9
11
  const metadata_1 = require("./base/metadata");
10
12
  var metadata_2 = require("./base/metadata");
11
13
  Object.defineProperty(exports, "setAdapterMetadata", { enumerable: true, get: function () { return metadata_2.setAdapterMetadata; } });
14
+ Object.defineProperty(exports, "createAdapterProxy", { enumerable: true, get: function () { return metadata_2.createAdapterProxy; } });
12
15
  function isSheetsConfig(config) {
13
16
  return config.spreadsheetId !== undefined;
14
17
  }
18
+ function sanitizeParamName(name) {
19
+ const cleaned = String(name).replace(/[^A-Za-z0-9_]/g, '_');
20
+ return /^[A-Za-z]/.test(cleaned) ? cleaned : `p_${cleaned}`;
21
+ }
22
+ function appendUpdateSet(sets, params, col, val, dialect) {
23
+ const quoted = (0, sql_1.quote)(col, dialect);
24
+ const safeCol = sanitizeParamName(col);
25
+ if (val && typeof val === 'object' && !(val instanceof Date)) {
26
+ if (val.increment !== undefined) {
27
+ sets.push(`${quoted} = ${quoted} + @s_${safeCol}_inc`);
28
+ params[`s_${safeCol}_inc`] = val.increment;
29
+ return;
30
+ }
31
+ if (val.decrement !== undefined) {
32
+ sets.push(`${quoted} = ${quoted} - @s_${safeCol}_dec`);
33
+ params[`s_${safeCol}_dec`] = val.decrement;
34
+ return;
35
+ }
36
+ if (val.multiply !== undefined) {
37
+ sets.push(`${quoted} = ${quoted} * @s_${safeCol}_mul`);
38
+ params[`s_${safeCol}_mul`] = val.multiply;
39
+ return;
40
+ }
41
+ if (val.divide !== undefined) {
42
+ sets.push(`${quoted} = ${quoted} / @s_${safeCol}_div`);
43
+ params[`s_${safeCol}_div`] = val.divide;
44
+ return;
45
+ }
46
+ if (val.set !== undefined) {
47
+ sets.push(`${quoted} = @s_${safeCol}_set`);
48
+ params[`s_${safeCol}_set`] = val.set;
49
+ return;
50
+ }
51
+ }
52
+ sets.push(`${quoted} = @s_${safeCol}`);
53
+ params[`s_${safeCol}`] = val;
54
+ }
55
+ function selectedAggregateFields(fields) {
56
+ if (!fields || typeof fields !== 'object')
57
+ return [];
58
+ return Object.keys(fields).filter(key => fields[key]);
59
+ }
60
+ function normalizeByFields(by) {
61
+ if (typeof by === 'string')
62
+ return [by];
63
+ return Array.isArray(by) ? by.filter(field => typeof field === 'string' && field.length > 0) : [];
64
+ }
65
+ function toNonNegativeInt(value, fallback = 0) {
66
+ const n = Number.parseInt(String(value), 10);
67
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
68
+ }
69
+ function projectFields(row, select) {
70
+ if (!row || !select || typeof select !== 'object')
71
+ return row;
72
+ const projected = {};
73
+ for (const [key, val] of Object.entries(select)) {
74
+ if (val) {
75
+ projected[key] = row[key];
76
+ }
77
+ }
78
+ if (row._count) {
79
+ projected._count = row._count;
80
+ }
81
+ return projected;
82
+ }
83
+ async function resolveIncludes(modelName, rows, include, adapter) {
84
+ if (!rows || rows.length === 0 || !include || typeof include !== 'object')
85
+ return;
86
+ const modelRelations = (0, metadata_1.getRelationsForModel)(modelName);
87
+ for (const [key, value] of Object.entries(include)) {
88
+ if (!value)
89
+ continue;
90
+ if (key === '_count') {
91
+ for (const row of rows) {
92
+ if (!row._count)
93
+ row._count = {};
94
+ }
95
+ for (const [relKey, relation] of Object.entries(modelRelations)) {
96
+ if (relation.relationType === 'many') {
97
+ const uniqueKeys = Array.from(new Set(rows.map(r => r[relation.localKey]).filter(k => k !== undefined && k !== null)));
98
+ if (uniqueKeys.length > 0) {
99
+ const relClient = adapter.table(relation.modelName);
100
+ const relatedRows = await relClient.findMany({
101
+ where: { [relation.foreignKey]: { in: uniqueKeys } },
102
+ });
103
+ const countMap = new Map();
104
+ relatedRows.forEach((r) => {
105
+ const k = r[relation.foreignKey];
106
+ countMap.set(k, (countMap.get(k) || 0) + 1);
107
+ });
108
+ rows.forEach(r => {
109
+ const k = r[relation.localKey];
110
+ r._count[relKey] = countMap.get(k) || 0;
111
+ });
112
+ }
113
+ else {
114
+ rows.forEach(r => { r._count[relKey] = 0; });
115
+ }
116
+ }
117
+ }
118
+ continue;
119
+ }
120
+ const relation = modelRelations[key];
121
+ if (!relation)
122
+ continue;
123
+ const isMany = relation.relationType === 'many';
124
+ const localKey = relation.localKey;
125
+ const foreignKey = relation.foreignKey;
126
+ const joinKey = isMany ? localKey : foreignKey;
127
+ const matchKey = isMany ? foreignKey : localKey;
128
+ const uniqueKeys = Array.from(new Set(rows.map(r => r[joinKey]).filter(k => k !== undefined && k !== null)));
129
+ if (uniqueKeys.length === 0) {
130
+ rows.forEach(r => { r[key] = isMany ? [] : null; });
131
+ continue;
132
+ }
133
+ const subArgs = typeof value === 'object' ? { ...value } : {};
134
+ const nestedInclude = subArgs.include;
135
+ const nestedSelect = subArgs.select;
136
+ const subWhere = {
137
+ [matchKey]: { in: uniqueKeys },
138
+ ...(subArgs.where || {}),
139
+ };
140
+ const relClient = adapter.table(relation.modelName);
141
+ const relatedRows = await relClient.findMany({
142
+ where: subWhere,
143
+ orderBy: subArgs.orderBy,
144
+ take: subArgs.take,
145
+ skip: subArgs.skip,
146
+ });
147
+ if (nestedInclude) {
148
+ await resolveIncludes(relation.modelName, relatedRows, nestedInclude, adapter);
149
+ }
150
+ const outputRows = nestedSelect && typeof nestedSelect === 'object'
151
+ ? relatedRows.map((r) => projectFields(r, nestedSelect))
152
+ : relatedRows;
153
+ const groupMap = new Map();
154
+ relatedRows.forEach((r, idx) => {
155
+ const k = r[isMany ? foreignKey : localKey];
156
+ if (!groupMap.has(k))
157
+ groupMap.set(k, []);
158
+ groupMap.get(k).push(outputRows[idx]);
159
+ });
160
+ rows.forEach(r => {
161
+ const k = r[isMany ? localKey : foreignKey];
162
+ const matches = groupMap.get(k) || [];
163
+ if (isMany) {
164
+ r[key] = matches;
165
+ }
166
+ else {
167
+ r[key] = matches[0] || null;
168
+ }
169
+ });
170
+ }
171
+ }
15
172
  // ─── An5Adapter ────────────────────────────────────────────────────────────────────
16
173
  class An5Adapter {
17
174
  get dialect() {
@@ -32,14 +189,27 @@ class An5Adapter {
32
189
  this._engineType = null;
33
190
  this._engineConfig = null;
34
191
  this.sheetsAdapter = null;
192
+ this._listeners = {};
193
+ this._middlewares = [];
35
194
  if (isSheetsConfig(adapterConfig)) {
36
195
  this.sheetsAdapter = new googlesheets_1.An5SheetsAdapter(adapterConfig);
37
- return;
196
+ return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
197
+ }
198
+ if (adapterConfig.engine) {
199
+ this._engine = adapterConfig.engine;
200
+ this._engineType = this._engine?.dialect || 'sqlite';
201
+ return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
38
202
  }
39
- const cs = adapterConfig.connectionString.trim();
203
+ if (adapterConfig.db || adapterConfig.driver) {
204
+ const { SqliteBrowserEngine } = require('./sqlite/browserEngine.js');
205
+ this._engine = new SqliteBrowserEngine(adapterConfig);
206
+ this._engineType = 'sqlite';
207
+ return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
208
+ }
209
+ const cs = (adapterConfig.connectionString || '').trim();
40
210
  if (cs.startsWith('googlesheets://')) {
41
211
  this.sheetsAdapter = new googlesheets_1.An5SheetsAdapter((0, parseConnectionString_1.parseSheetsConnectionString)(cs));
42
- return;
212
+ return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
43
213
  }
44
214
  this._engineConfig = adapterConfig;
45
215
  if (cs.startsWith('postgres://') || cs.startsWith('postgresql://')) {
@@ -54,27 +224,28 @@ class An5Adapter {
54
224
  else {
55
225
  this._engineType = 'mssql';
56
226
  }
227
+ return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
57
228
  }
58
229
  async requireEngine() {
59
230
  if (!this._engine && this._engineType) {
60
231
  switch (this._engineType) {
61
232
  case 'postgres': {
62
- const { PostgresEngine } = require('./postgres.js');
233
+ const { PostgresEngine } = require('./postgres/index.js');
63
234
  this._engine = new PostgresEngine(this._engineConfig);
64
235
  break;
65
236
  }
66
237
  case 'mysql': {
67
- const { MysqlEngine } = require('./mysql.js');
238
+ const { MysqlEngine } = require('./mysql/index.js');
68
239
  this._engine = new MysqlEngine(this._engineConfig);
69
240
  break;
70
241
  }
71
242
  case 'sqlite': {
72
- const { SqliteEngine } = require('./sqlite.js');
243
+ const { SqliteEngine } = require('./sqlite/index.js');
73
244
  this._engine = new SqliteEngine(this._engineConfig);
74
245
  break;
75
246
  }
76
247
  case 'mssql': {
77
- const { MssqlEngine } = require('./mssql.js');
248
+ const { MssqlEngine } = require('./mssql/index.js');
78
249
  this._engine = new MssqlEngine(this._engineConfig);
79
250
  break;
80
251
  }
@@ -164,11 +335,107 @@ class An5Adapter {
164
335
  throw err;
165
336
  }
166
337
  }
338
+ async $begin() {
339
+ if (this.sheetsAdapter) {
340
+ throw new Error('Interactive transactions are not supported by the Google Sheets adapter');
341
+ }
342
+ const engine = await this.requireEngine();
343
+ return new An5AdapterTx(await engine.beginTransaction(), this.dialect);
344
+ }
167
345
  table(modelName) {
168
346
  if (this.sheetsAdapter)
169
347
  return this.sheetsAdapter.table(modelName);
170
348
  return new AdapterTableClient(this, modelName);
171
349
  }
350
+ $on(event, callback) {
351
+ if (!this._listeners[event])
352
+ this._listeners[event] = [];
353
+ this._listeners[event].push(callback);
354
+ return this;
355
+ }
356
+ $off(event, callback) {
357
+ if (!this._listeners[event])
358
+ return this;
359
+ this._listeners[event] = this._listeners[event].filter(cb => cb !== callback);
360
+ return this;
361
+ }
362
+ $emit(event, ...args) {
363
+ if (this._listeners[event]) {
364
+ for (const cb of this._listeners[event]) {
365
+ try {
366
+ cb(...args);
367
+ }
368
+ catch { }
369
+ }
370
+ }
371
+ }
372
+ $use(middleware) {
373
+ this._middlewares.push(middleware);
374
+ return this;
375
+ }
376
+ view(viewName) {
377
+ return new ViewClient(this, viewName);
378
+ }
379
+ async $queryProc(procName, params) {
380
+ if (this.sheetsAdapter)
381
+ throw new Error('Stored procedures are not supported by the Google Sheets adapter');
382
+ const paramObj = {};
383
+ let sql = '';
384
+ if (Array.isArray(params)) {
385
+ const placeholders = params.map((v, i) => {
386
+ paramObj[`p_${i}`] = v;
387
+ return `@p_${i}`;
388
+ });
389
+ sql = this.dialect === 'postgres' || this.dialect === 'mysql'
390
+ ? `CALL ${procName}(${placeholders.join(', ')})`
391
+ : `EXEC ${procName} ${placeholders.join(', ')}`;
392
+ }
393
+ else if (params && typeof params === 'object') {
394
+ const pairs = Object.entries(params).map(([k, v]) => {
395
+ paramObj[`p_${k}`] = v;
396
+ return `@${k} = @p_${k}`;
397
+ });
398
+ sql = this.dialect === 'postgres' || this.dialect === 'mysql'
399
+ ? `CALL ${procName}(${Object.keys(params).map(k => `@p_${k}`).join(', ')})`
400
+ : `EXEC ${procName} ${pairs.join(', ')}`;
401
+ }
402
+ else {
403
+ sql = this.dialect === 'postgres' || this.dialect === 'mysql'
404
+ ? `CALL ${procName}()`
405
+ : `EXEC ${procName}`;
406
+ }
407
+ return this.exec(sql, paramObj);
408
+ }
409
+ async $executeProc(procName, params) {
410
+ if (this.sheetsAdapter)
411
+ throw new Error('Stored procedures are not supported by the Google Sheets adapter');
412
+ const paramObj = {};
413
+ let sql = '';
414
+ if (Array.isArray(params)) {
415
+ const placeholders = params.map((v, i) => {
416
+ paramObj[`p_${i}`] = v;
417
+ return `@p_${i}`;
418
+ });
419
+ sql = this.dialect === 'postgres' || this.dialect === 'mysql'
420
+ ? `CALL ${procName}(${placeholders.join(', ')})`
421
+ : `EXEC ${procName} ${placeholders.join(', ')}`;
422
+ }
423
+ else if (params && typeof params === 'object') {
424
+ const pairs = Object.entries(params).map(([k, v]) => {
425
+ paramObj[`p_${k}`] = v;
426
+ return `@${k} = @p_${k}`;
427
+ });
428
+ sql = this.dialect === 'postgres' || this.dialect === 'mysql'
429
+ ? `CALL ${procName}(${Object.keys(params).map(k => `@p_${k}`).join(', ')})`
430
+ : `EXEC ${procName} ${pairs.join(', ')}`;
431
+ }
432
+ else {
433
+ sql = this.dialect === 'postgres' || this.dialect === 'mysql'
434
+ ? `CALL ${procName}()`
435
+ : `EXEC ${procName}`;
436
+ }
437
+ return this._executeRaw(sql, paramObj);
438
+ }
172
439
  async readRange(range) {
173
440
  return this.requireSheetsAdapter().readRange(range);
174
441
  }
@@ -186,21 +453,92 @@ class An5Adapter {
186
453
  }
187
454
  }
188
455
  exports.An5Adapter = An5Adapter;
456
+ // ─── View Client (Read-Only) ────────────────────────────────────────────────────────
457
+ class ViewClient {
458
+ constructor(adapter, viewName) {
459
+ this.adapter = adapter;
460
+ this.viewName = viewName;
461
+ this.tableClient = new AdapterTableClient(adapter, viewName);
462
+ }
463
+ get dialect() { return this.tableClient.dialect; }
464
+ get tableName() { return this.tableClient.tableName; }
465
+ async findMany(args) {
466
+ return this.tableClient.findMany(args);
467
+ }
468
+ async findFirst(args) {
469
+ return this.tableClient.findFirst(args);
470
+ }
471
+ async findUnique(args) {
472
+ return this.tableClient.findUnique(args);
473
+ }
474
+ async count(args) {
475
+ return this.tableClient.count(args);
476
+ }
477
+ async aggregate(args) {
478
+ return this.tableClient.aggregate(args);
479
+ }
480
+ async groupBy(args) {
481
+ return this.tableClient.groupBy(args);
482
+ }
483
+ async vectorSearch(args) {
484
+ return this.tableClient.vectorSearch(args);
485
+ }
486
+ async create() {
487
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (create) are not allowed.`);
488
+ }
489
+ async createMany() {
490
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (createMany) are not allowed.`);
491
+ }
492
+ async update() {
493
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (update) are not allowed.`);
494
+ }
495
+ async updateMany() {
496
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (updateMany) are not allowed.`);
497
+ }
498
+ async delete() {
499
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (delete) are not allowed.`);
500
+ }
501
+ async deleteMany() {
502
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (deleteMany) are not allowed.`);
503
+ }
504
+ async upsert() {
505
+ throw new Error(`View '${this.viewName}' is read-only. Mutation operations (upsert) are not allowed.`);
506
+ }
507
+ }
508
+ exports.ViewClient = ViewClient;
189
509
  // ─── Transaction-scoped adapter ────────────────────────────────────────────────────
190
510
  class An5AdapterTx {
191
511
  constructor(handle, dialect) {
192
512
  this.handle = handle;
193
513
  this.dialect = dialect;
514
+ this.closed = false;
515
+ return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
194
516
  }
195
517
  async exec(query, params) {
518
+ this.ensureOpen();
196
519
  return this.handle.exec(query, params);
197
520
  }
198
521
  async _executeRaw(query, params) {
522
+ this.ensureOpen();
199
523
  return this.handle.executeRaw(query, params);
200
524
  }
525
+ async $commit() {
526
+ this.ensureOpen();
527
+ this.closed = true;
528
+ await this.handle.commit();
529
+ }
530
+ async $rollback() {
531
+ this.ensureOpen();
532
+ this.closed = true;
533
+ await this.handle.rollback();
534
+ }
201
535
  table(modelName) {
202
536
  return new AdapterTableClient(this, modelName);
203
537
  }
538
+ ensureOpen() {
539
+ if (this.closed)
540
+ throw new Error('Transaction is already closed');
541
+ }
204
542
  }
205
543
  exports.An5AdapterTx = An5AdapterTx;
206
544
  // ─── Table Client ──────────────────────────────────────────────────────────────────
@@ -235,6 +573,14 @@ class AdapterTableClient {
235
573
  get nolock() {
236
574
  return this.dialect === 'mssql' ? ' WITH (NOLOCK)' : '';
237
575
  }
576
+ /** Build the shared context used by parseWhere to resolve relations and self references. */
577
+ whereContext() {
578
+ return {
579
+ relationMap: (0, metadata_1.getRelationMap)(),
580
+ modelToTable: (0, metadata_1.getModelToTable)(),
581
+ selfRef: this.tableName,
582
+ };
583
+ }
238
584
  async doExec(query, params) {
239
585
  return this.adapter.exec(query, params);
240
586
  }
@@ -243,49 +589,92 @@ class AdapterTableClient {
243
589
  }
244
590
  async findMany(args) {
245
591
  const params = {};
246
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
592
+ const whereCtx = this.whereContext();
593
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', whereCtx);
247
594
  const orderSql = (0, sql_1.buildOrderBy)(args?.orderBy, this.dialect);
248
595
  const take = args?.take;
249
- const skip = args?.skip ?? 0;
596
+ const skip = args?.skip;
597
+ const hasSkip = skip !== undefined && skip !== null;
598
+ let cols = "*";
599
+ if (args?.select && typeof args.select === "object") {
600
+ const relationKeys = (0, metadata_1.getRelationsForModel)(this.modelName);
601
+ const hasRelationSelect = Object.keys(args.select).some(k => k === '_count' || relationKeys[k]);
602
+ const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
603
+ const selectedKeys = Object.keys(args.select).filter(k => args.select[k] === true && (Object.keys(fields).length === 0 || fields[k]));
604
+ if (selectedKeys.length > 0 && !hasRelationSelect) {
605
+ cols = selectedKeys.map(k => (0, sql_1.quote)(k, this.dialect)).join(", ");
606
+ }
607
+ }
250
608
  let query;
251
- if (take !== undefined) {
609
+ if (take !== undefined && !hasSkip) {
610
+ if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
611
+ query = `SELECT ${cols} FROM ${this.tableName}`;
612
+ if (whereSql)
613
+ query += ` WHERE ${whereSql}`;
614
+ if (orderSql)
615
+ query += ` ${orderSql}`;
616
+ query += ` LIMIT ${take}`;
617
+ }
618
+ else {
619
+ query = `SELECT TOP (${take}) ${cols} FROM ${this.tableName}${this.nolock}`;
620
+ if (whereSql)
621
+ query += ` WHERE ${whereSql}`;
622
+ if (orderSql)
623
+ query += ` ${orderSql}`;
624
+ }
625
+ }
626
+ else if (hasSkip) {
252
627
  if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
253
- query = `SELECT * FROM ${this.tableName}`;
628
+ query = `SELECT ${cols} FROM ${this.tableName}`;
254
629
  if (whereSql)
255
630
  query += ` WHERE ${whereSql}`;
256
631
  if (orderSql)
257
632
  query += ` ${orderSql}`;
258
- query += ` LIMIT ${take} OFFSET ${skip}`;
633
+ query += ` LIMIT ${take ?? 'ALL'} OFFSET ${skip}`;
259
634
  }
260
635
  else {
261
636
  // mssql requires ORDER BY before OFFSET/FETCH
262
- query = `SELECT * FROM ${this.tableName}${this.nolock}`;
637
+ query = `SELECT ${cols} FROM ${this.tableName}${this.nolock}`;
263
638
  if (whereSql)
264
639
  query += ` WHERE ${whereSql}`;
265
640
  query += ` ${orderSql || 'ORDER BY (SELECT NULL)'}`;
266
- query += ` OFFSET ${skip} ROWS FETCH NEXT ${take} ROWS ONLY`;
641
+ query += ` OFFSET ${skip} ROWS`;
642
+ if (take !== undefined) {
643
+ query += ` FETCH NEXT ${take} ROWS ONLY`;
644
+ }
267
645
  }
268
646
  }
269
647
  else {
270
- query = `SELECT * FROM ${this.tableName}${this.nolock}`;
648
+ query = `SELECT ${cols} FROM ${this.tableName}${this.nolock}`;
271
649
  if (whereSql)
272
650
  query += ` WHERE ${whereSql}`;
273
651
  if (orderSql)
274
652
  query += ` ${orderSql}`;
275
653
  }
276
- return this.doExec(query, params);
654
+ const rows = await this.doExec(query, params);
655
+ if (args?.include) {
656
+ await resolveIncludes(this.modelName, rows, args.include, this.adapter);
657
+ }
658
+ if (args?.select && typeof args.select === 'object') {
659
+ const relationKeys = (0, metadata_1.getRelationsForModel)(this.modelName);
660
+ const hasRelationSelect = Object.keys(args.select).some(k => k === '_count' || relationKeys[k]);
661
+ if (hasRelationSelect) {
662
+ await resolveIncludes(this.modelName, rows, args.select, this.adapter);
663
+ }
664
+ return rows.map((r) => projectFields(r, args.select));
665
+ }
666
+ return rows;
277
667
  }
278
668
  async findFirst(args) {
279
669
  const rows = await this.findMany({ ...args, take: 1 });
280
670
  return rows[0] ?? null;
281
671
  }
282
672
  async findUnique(args) {
283
- return this.findFirst({ where: args.where });
673
+ return this.findFirst({ where: args.where, select: args.select, include: args.include });
284
674
  }
285
675
  async count(args) {
286
676
  const params = {};
287
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
288
- // Add NOLOCK for mssql count too
677
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
289
678
  let query = `SELECT COUNT(*) AS cnt FROM ${this.tableName}${this.nolock}`;
290
679
  if (whereSql)
291
680
  query += ` WHERE ${whereSql}`;
@@ -293,11 +682,20 @@ class AdapterTableClient {
293
682
  return Number(rows[0]?.cnt ?? rows[0]?.CNT ?? 0);
294
683
  }
295
684
  async create(args) {
685
+ const modelRelations = (0, metadata_1.getRelationsForModel)(this.modelName);
686
+ const rawData = { ...args.data };
687
+ const relationWrites = {};
688
+ for (const key of Object.keys(rawData)) {
689
+ if (modelRelations[key] && typeof rawData[key] === 'object' && rawData[key] !== null && !(rawData[key] instanceof Date)) {
690
+ relationWrites[key] = rawData[key];
691
+ delete rawData[key];
692
+ }
693
+ }
296
694
  const fields = (0, metadata_1.getFieldsForModel)(this.modelName);
297
695
  const idFieldName = Object.prototype.hasOwnProperty.call(fields, 'id')
298
696
  ? 'id'
299
697
  : Object.keys(fields).find((name) => name.endsWith('_id') || name.endsWith('Id') || name.toLowerCase() === 'id');
300
- const data = { ...args.data };
698
+ const data = { ...rawData };
301
699
  if (idFieldName) {
302
700
  const fieldDef = fields[idFieldName];
303
701
  const rawType = typeof fieldDef === 'string' ? fieldDef : (fieldDef?.ts || fieldDef?.sql || fieldDef?.type || '');
@@ -316,12 +714,41 @@ class AdapterTableClient {
316
714
  }
317
715
  const query = `INSERT INTO ${this.tableName} (${cols.map(c => (0, sql_1.quote)(c, this.dialect)).join(', ')}) VALUES (${vals.join(', ')})`;
318
716
  await this.doExec(query, params);
319
- return (await this.findFirst({ where: idFieldName ? { [idFieldName]: data[idFieldName] } : data }));
717
+ const createdRecord = (await this.findFirst({ where: idFieldName ? { [idFieldName]: data[idFieldName] } : data }));
718
+ // Handle nested relation writes
719
+ for (const [relKey, relWrite] of Object.entries(relationWrites)) {
720
+ const relation = modelRelations[relKey];
721
+ if (!relation)
722
+ continue;
723
+ const childClient = this.adapter.table(relation.modelName);
724
+ if (relWrite.create) {
725
+ const createItems = Array.isArray(relWrite.create) ? relWrite.create : [relWrite.create];
726
+ for (const item of createItems) {
727
+ await childClient.create({
728
+ data: {
729
+ ...item,
730
+ [relation.foreignKey]: createdRecord[relation.localKey],
731
+ },
732
+ });
733
+ }
734
+ }
735
+ }
736
+ if (args.include) {
737
+ await resolveIncludes(this.modelName, [createdRecord], args.include, this.adapter);
738
+ }
739
+ if (args.select) {
740
+ await resolveIncludes(this.modelName, [createdRecord], args.select, this.adapter);
741
+ return projectFields(createdRecord, args.select);
742
+ }
743
+ return createdRecord;
320
744
  }
321
745
  async createMany(args) {
322
746
  if (args.data.length === 0)
323
747
  return { count: 0 };
324
- const firstCols = Object.keys(args.data[0]).filter(k => args.data[0][k] !== undefined);
748
+ const firstRow = args.data[0];
749
+ if (firstRow === undefined)
750
+ return { count: 0 };
751
+ const firstCols = Object.keys(firstRow).filter(k => firstRow[k] !== undefined);
325
752
  // Bulk INSERT for the common case (same columns, no skipDuplicates)
326
753
  if (firstCols.length > 0 && !args.skipDuplicates) {
327
754
  const params = {};
@@ -359,29 +786,80 @@ class AdapterTableClient {
359
786
  return { count };
360
787
  }
361
788
  async update(args) {
362
- const params = {};
363
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_');
364
- const setCols = Object.keys(args.data).filter(k => args.data[k] !== undefined);
365
- const sets = [];
366
- for (const col of setCols) {
367
- const p = `s_${col}`;
368
- params[p] = args.data[col];
369
- sets.push(`${(0, sql_1.quote)(col, this.dialect)} = @${p}`);
789
+ const modelRelations = (0, metadata_1.getRelationsForModel)(this.modelName);
790
+ const rawData = { ...args.data };
791
+ const relationWrites = {};
792
+ for (const key of Object.keys(rawData)) {
793
+ if (modelRelations[key] && typeof rawData[key] === 'object' && rawData[key] !== null && !(rawData[key] instanceof Date)) {
794
+ relationWrites[key] = rawData[key];
795
+ delete rawData[key];
796
+ }
370
797
  }
371
- const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
372
- await this.doExecuteRaw(query, params);
373
- return (await this.findFirst({ where: args.where }));
798
+ const setCols = Object.keys(rawData).filter(k => rawData[k] !== undefined);
799
+ if (setCols.length > 0) {
800
+ const params = {};
801
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
802
+ const sets = [];
803
+ for (const col of setCols) {
804
+ appendUpdateSet(sets, params, col, rawData[col], this.dialect);
805
+ }
806
+ if (sets.length > 0) {
807
+ const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
808
+ await this.doExecuteRaw(query, params);
809
+ }
810
+ }
811
+ const updatedRecord = (await this.findFirst({ where: args.where }));
812
+ // Handle nested writes
813
+ for (const [relKey, relWrite] of Object.entries(relationWrites)) {
814
+ const relation = modelRelations[relKey];
815
+ if (!relation)
816
+ continue;
817
+ const childClient = this.adapter.table(relation.modelName);
818
+ if (relWrite.create) {
819
+ const createItems = Array.isArray(relWrite.create) ? relWrite.create : [relWrite.create];
820
+ for (const item of createItems) {
821
+ await childClient.create({
822
+ data: {
823
+ ...item,
824
+ [relation.foreignKey]: updatedRecord[relation.localKey],
825
+ },
826
+ });
827
+ }
828
+ }
829
+ if (relWrite.update) {
830
+ const updateObj = relWrite.update;
831
+ await childClient.update({
832
+ where: updateObj.where,
833
+ data: updateObj.data,
834
+ });
835
+ }
836
+ if (relWrite.disconnect) {
837
+ const disc = relWrite.disconnect;
838
+ await childClient.updateMany({
839
+ where: disc,
840
+ data: { [relation.foreignKey]: null },
841
+ });
842
+ }
843
+ }
844
+ if (args.include) {
845
+ await resolveIncludes(this.modelName, [updatedRecord], args.include, this.adapter);
846
+ }
847
+ if (args.select) {
848
+ await resolveIncludes(this.modelName, [updatedRecord], args.select, this.adapter);
849
+ return projectFields(updatedRecord, args.select);
850
+ }
851
+ return updatedRecord;
374
852
  }
375
853
  async updateMany(args) {
376
854
  const params = {};
377
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_');
855
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, 'w_', this.whereContext());
378
856
  const setCols = Object.keys(args.data).filter(k => args.data[k] !== undefined);
379
857
  const sets = [];
380
858
  for (const col of setCols) {
381
- const p = `s_${col}`;
382
- params[p] = args.data[col];
383
- sets.push(`${(0, sql_1.quote)(col, this.dialect)} = @${p}`);
859
+ appendUpdateSet(sets, params, col, args.data[col], this.dialect);
384
860
  }
861
+ if (sets.length === 0)
862
+ return { count: 0 };
385
863
  const query = `UPDATE ${this.tableName} SET ${sets.join(', ')}${whereSql ? ` WHERE ${whereSql}` : ''}`;
386
864
  const count = await this.doExecuteRaw(query, params);
387
865
  return { count };
@@ -389,13 +867,13 @@ class AdapterTableClient {
389
867
  async delete(args) {
390
868
  const existing = await this.findFirst({ where: args.where });
391
869
  const params = {};
392
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect);
870
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
393
871
  await this.doExecuteRaw(`DELETE FROM ${this.tableName} WHERE ${whereSql}`, params);
394
872
  return existing;
395
873
  }
396
874
  async deleteMany(args) {
397
875
  const params = {};
398
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
876
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
399
877
  const query = `DELETE FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''}`;
400
878
  const count = await this.doExecuteRaw(query, params);
401
879
  return { count };
@@ -408,37 +886,173 @@ class AdapterTableClient {
408
886
  }
409
887
  async aggregate(args) {
410
888
  const params = {};
411
- const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
889
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect, '', this.whereContext());
412
890
  const aggs = [];
413
- if (args._count)
414
- aggs.push('COUNT(*) AS _count');
891
+ if (args._count) {
892
+ aggs.push('COUNT(*) AS cnt_all');
893
+ if (typeof args._count === 'object') {
894
+ for (const f of selectedAggregateFields(args._count)) {
895
+ if (f !== '_all')
896
+ aggs.push(`COUNT(${(0, sql_1.quote)(f, this.dialect)}) AS cnt_${sanitizeParamName(f)}`);
897
+ }
898
+ }
899
+ }
415
900
  if (args._sum)
416
- for (const f of Object.keys(args._sum))
417
- aggs.push(`SUM(${(0, sql_1.quote)(f, this.dialect)}) AS _sum_${f}`);
901
+ for (const f of selectedAggregateFields(args._sum))
902
+ aggs.push(`SUM(${(0, sql_1.quote)(f, this.dialect)}) AS sum_${sanitizeParamName(f)}`);
418
903
  if (args._avg)
419
- for (const f of Object.keys(args._avg))
420
- aggs.push(`AVG(${(0, sql_1.quote)(f, this.dialect)}) AS _avg_${f}`);
904
+ for (const f of selectedAggregateFields(args._avg))
905
+ aggs.push(`AVG(${(0, sql_1.quote)(f, this.dialect)}) AS avg_${sanitizeParamName(f)}`);
421
906
  if (args._min)
422
- for (const f of Object.keys(args._min))
423
- aggs.push(`MIN(${(0, sql_1.quote)(f, this.dialect)}) AS _min_${f}`);
907
+ for (const f of selectedAggregateFields(args._min))
908
+ aggs.push(`MIN(${(0, sql_1.quote)(f, this.dialect)}) AS min_${sanitizeParamName(f)}`);
424
909
  if (args._max)
425
- for (const f of Object.keys(args._max))
426
- aggs.push(`MAX(${(0, sql_1.quote)(f, this.dialect)}) AS _max_${f}`);
427
- const query = `SELECT ${aggs.join(', ')} FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''}`;
910
+ for (const f of selectedAggregateFields(args._max))
911
+ aggs.push(`MAX(${(0, sql_1.quote)(f, this.dialect)}) AS max_${sanitizeParamName(f)}`);
912
+ if (aggs.length === 0)
913
+ throw new Error('Aggregate requires at least one aggregator field');
914
+ const query = `SELECT ${aggs.join(', ')} FROM ${this.tableName}${this.nolock}${whereSql ? ` WHERE ${whereSql}` : ''}`;
428
915
  const rows = await this.doExec(query, params);
429
- return rows[0] ?? {};
916
+ const row = rows[0] || {};
917
+ const result = {};
918
+ if (args._count) {
919
+ const allCount = Number(row.cnt_all ?? row._count ?? 0);
920
+ result._count = { _all: allCount };
921
+ if (typeof args._count === 'object') {
922
+ for (const f of selectedAggregateFields(args._count)) {
923
+ if (f === '_all')
924
+ continue;
925
+ const k = `cnt_${sanitizeParamName(f)}`;
926
+ result._count[f] = row[k] !== undefined ? Number(row[k]) : allCount;
927
+ }
928
+ }
929
+ }
930
+ if (args._sum) {
931
+ result._sum = {};
932
+ for (const f of selectedAggregateFields(args._sum)) {
933
+ const k = `sum_${sanitizeParamName(f)}`;
934
+ result._sum[f] = row[k] !== null && row[k] !== undefined ? Number(row[k]) : null;
935
+ }
936
+ }
937
+ if (args._avg) {
938
+ result._avg = {};
939
+ for (const f of selectedAggregateFields(args._avg)) {
940
+ const k = `avg_${sanitizeParamName(f)}`;
941
+ result._avg[f] = row[k] !== null && row[k] !== undefined ? Number(row[k]) : null;
942
+ }
943
+ }
944
+ if (args._min) {
945
+ result._min = {};
946
+ for (const f of selectedAggregateFields(args._min)) {
947
+ const k = `min_${sanitizeParamName(f)}`;
948
+ result._min[f] = row[k] ?? null;
949
+ }
950
+ }
951
+ if (args._max) {
952
+ result._max = {};
953
+ for (const f of selectedAggregateFields(args._max)) {
954
+ const k = `max_${sanitizeParamName(f)}`;
955
+ result._max[f] = row[k] ?? null;
956
+ }
957
+ }
958
+ return result;
430
959
  }
431
960
  async groupBy(args) {
432
961
  const params = {};
433
962
  const whereSql = (0, sql_1.parseWhere)(this.modelName, args?.where, params, this.dialect);
434
- const byCols = (args.by || []).map((b) => (0, sql_1.quote)(b, this.dialect)).join(', ');
435
- const query = `SELECT ${byCols}, COUNT(*) AS _count FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''} GROUP BY ${byCols}`;
963
+ const byFields = normalizeByFields(args?.by);
964
+ if (byFields.length === 0)
965
+ throw new Error("groupBy requires 'by' fields");
966
+ const byCols = byFields.map((b) => (0, sql_1.quote)(b, this.dialect)).join(', ');
967
+ const aggs = ['COUNT(*) AS _count'];
968
+ if (args?._sum)
969
+ for (const f of selectedAggregateFields(args._sum))
970
+ aggs.push(`SUM(${(0, sql_1.quote)(f, this.dialect)}) AS _sum_${f}`);
971
+ if (args?._avg)
972
+ for (const f of selectedAggregateFields(args._avg))
973
+ aggs.push(`AVG(${(0, sql_1.quote)(f, this.dialect)}) AS _avg_${f}`);
974
+ if (args?._min)
975
+ for (const f of selectedAggregateFields(args._min))
976
+ aggs.push(`MIN(${(0, sql_1.quote)(f, this.dialect)}) AS _min_${f}`);
977
+ if (args?._max)
978
+ for (const f of selectedAggregateFields(args._max))
979
+ aggs.push(`MAX(${(0, sql_1.quote)(f, this.dialect)}) AS _max_${f}`);
980
+ let query = `SELECT ${byCols}, ${aggs.join(', ')} FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''} GROUP BY ${byCols}`;
981
+ const orderSql = (0, sql_1.buildOrderBy)(args?.orderBy, this.dialect);
982
+ const hasSkip = args?.skip !== undefined && args?.skip !== null;
983
+ const hasTake = args?.take !== undefined && args?.take !== null;
984
+ if (orderSql)
985
+ query += ` ${orderSql}`;
986
+ else if (hasSkip || hasTake)
987
+ query += ` ORDER BY ${byCols}`;
988
+ if (hasSkip || hasTake) {
989
+ const skip = toNonNegativeInt(args?.skip);
990
+ const take = toNonNegativeInt(args?.take, 1);
991
+ if (this.dialect === 'postgres' || this.dialect === 'mysql' || this.dialect === 'sqlite') {
992
+ if (hasTake)
993
+ query += ` LIMIT ${take}`;
994
+ query += ` OFFSET ${skip}`;
995
+ }
996
+ else {
997
+ query += ` OFFSET ${skip} ROWS`;
998
+ if (hasTake)
999
+ query += ` FETCH NEXT ${take} ROWS ONLY`;
1000
+ }
1001
+ }
436
1002
  return this.doExec(query, params);
437
1003
  }
438
1004
  async vectorSearch(args) {
439
- const rows = await this.findMany({ where: args.where });
440
1005
  const vectorField = args.vectorField || 'embedding';
441
- const metric = args.distanceMetric || 'cosine';
1006
+ const METRICS = ['cosine', 'euclidean', 'dot'];
1007
+ const ELEMENT_TYPES = ['float32', 'float16', 'uint8'];
1008
+ const metric = METRICS.includes(args.distanceMetric) ? args.distanceMetric : 'cosine';
1009
+ const elementType = ELEMENT_TYPES.includes(args.vectorElementType) ? args.vectorElementType : 'float32';
1010
+ const take = args.take ?? 10;
1011
+ const dim = Array.isArray(args.vector) ? args.vector.length : 0;
1012
+ const vectorJson = JSON.stringify(args.vector);
1013
+ const col = (0, sql_1.quote)(vectorField, this.dialect);
1014
+ const params = { query_vector: vectorJson };
1015
+ // 1. Try native dialect execution
1016
+ if (this.dialect === 'postgres') {
1017
+ const op = metric === 'cosine' ? '<=>' : metric === 'dot' ? '<#>' : '<->';
1018
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
1019
+ const query = `SELECT *, (${col} ${op} @query_vector::vector) AS distance FROM ${this.tableName}${whereSql ? ` WHERE ${whereSql}` : ''} ORDER BY distance ASC LIMIT ${take}`;
1020
+ try {
1021
+ return await this.doExec(query, params);
1022
+ }
1023
+ catch (err) {
1024
+ const msg = String(err?.message || '').toLowerCase();
1025
+ if (!msg.includes('vector') && !msg.includes('operator does not exist'))
1026
+ throw err;
1027
+ }
1028
+ }
1029
+ else if (this.dialect === 'mssql') {
1030
+ const distFn = metric === 'cosine' ? 'cosine' : metric === 'euclidean' ? 'euclidean' : 'dot';
1031
+ const whereSql = (0, sql_1.parseWhere)(this.modelName, args.where, params, this.dialect, '', this.whereContext());
1032
+ const query = `SELECT TOP (${take}) *, VECTOR_DISTANCE('${distFn}', ${col}, CAST(@query_vector AS VECTOR(${dim}, ${elementType}))) AS distance FROM ${this.tableName}${this.nolock}${whereSql ? ` WHERE ${whereSql}` : ''} ORDER BY distance ASC`;
1033
+ try {
1034
+ return await this.doExec(query, params);
1035
+ }
1036
+ catch (err) {
1037
+ const msg = String(err?.message || '').toLowerCase();
1038
+ const isUnsupported = msg.includes('vector_distance') ||
1039
+ msg.includes('syntax near') ||
1040
+ msg.includes('not a recognized built-in function') ||
1041
+ msg.includes('not a defined system type') ||
1042
+ msg.includes('type "vector"') ||
1043
+ msg.includes('pgvector') ||
1044
+ msg.includes('operator does not exist') ||
1045
+ msg.includes('limit of 1998') ||
1046
+ err?.number === 195 ||
1047
+ err?.number === 102 ||
1048
+ err?.number === 319 ||
1049
+ err?.originalError?.number === 319;
1050
+ if (!isUnsupported)
1051
+ throw err;
1052
+ }
1053
+ }
1054
+ // 2. Fallback: in-memory similarity computation when native vector support is unavailable
1055
+ const rows = await this.findMany({ where: args.where });
442
1056
  const scored = [];
443
1057
  for (const row of rows) {
444
1058
  const raw = row[vectorField];
@@ -455,30 +1069,88 @@ class AdapterTableClient {
455
1069
  continue;
456
1070
  let dot = 0, m1 = 0, m2 = 0;
457
1071
  for (let i = 0; i < args.vector.length; i++) {
458
- dot += args.vector[i] * vec[i];
459
- m1 += args.vector[i] ** 2;
460
- m2 += vec[i] ** 2;
1072
+ const av = args.vector[i] ?? 0;
1073
+ const bv = vec[i] ?? 0;
1074
+ dot += av * bv;
1075
+ m1 += av ** 2;
1076
+ m2 += bv ** 2;
461
1077
  }
462
1078
  const cosine = m1 && m2 ? dot / (Math.sqrt(m1) * Math.sqrt(m2)) : 0;
463
1079
  const dist = metric === 'cosine'
464
1080
  ? 1 - cosine
465
1081
  : metric === 'dot'
466
1082
  ? -dot
467
- : Math.sqrt(args.vector.reduce((s, v, i) => s + (v - vec[i]) ** 2, 0));
1083
+ : Math.sqrt(args.vector.reduce((s, v, i) => s + (v - (vec[i] ?? 0)) ** 2, 0));
468
1084
  scored.push({ row, dist });
469
1085
  }
470
1086
  scored.sort((a, b) => a.dist - b.dist);
471
- return scored.slice(0, args.take ?? 10).map(s => ({ ...s.row, distance: s.dist }));
1087
+ return scored.slice(0, take).map(s => ({ ...s.row, distance: s.dist }));
472
1088
  }
473
1089
  }
474
1090
  exports.AdapterTableClient = AdapterTableClient;
475
- // ─── Factory ──────────────────────────────────────────────────────────────────────
476
1091
  function createAn5Adapter(config) {
477
1092
  if (isSheetsConfig(config))
478
1093
  return new googlesheets_1.An5SheetsAdapter(config);
479
- if (config.connectionString.trim().startsWith('googlesheets://')) {
1094
+ if (config.connectionString && config.connectionString.trim().startsWith('googlesheets://')) {
480
1095
  return new googlesheets_1.An5SheetsAdapter((0, parseConnectionString_1.parseSheetsConnectionString)(config.connectionString));
481
1096
  }
482
1097
  return new An5Adapter(config);
483
1098
  }
484
1099
  exports.createAdapter = createAn5Adapter;
1100
+ function createBrowserSqliteAdapter(config) {
1101
+ const { SqliteBrowserEngine } = require('./sqlite/browserEngine.js');
1102
+ return new An5Adapter({ engine: new SqliteBrowserEngine(config) });
1103
+ }
1104
+ function normalizeAffectedCount(result) {
1105
+ if (typeof result === 'number')
1106
+ return result;
1107
+ if (!result)
1108
+ return 0;
1109
+ if (Array.isArray(result.rowsAffected))
1110
+ return Number(result.rowsAffected[0] ?? 0);
1111
+ if (typeof result.rowsAffected === 'number')
1112
+ return result.rowsAffected;
1113
+ if (typeof result.count === 'number')
1114
+ return result.count;
1115
+ return 0;
1116
+ }
1117
+ function executorFromAdapter(adapterLike) {
1118
+ if (!adapterLike)
1119
+ return adapterLike;
1120
+ if (typeof adapterLike === 'function' && adapterLike.executeRaw) {
1121
+ return adapterLike;
1122
+ }
1123
+ return Object.assign(async (queryText, params) => {
1124
+ return typeof adapterLike === 'function'
1125
+ ? adapterLike(queryText, params)
1126
+ : adapterLike.exec(queryText, params);
1127
+ }, {
1128
+ executeRaw: async (queryText, params) => {
1129
+ if (typeof adapterLike._executeRaw === 'function') {
1130
+ return adapterLike._executeRaw(queryText, params);
1131
+ }
1132
+ if (typeof adapterLike.executeRaw === 'function') {
1133
+ return adapterLike.executeRaw(queryText, params);
1134
+ }
1135
+ const res = typeof adapterLike === 'function'
1136
+ ? await adapterLike(queryText, params)
1137
+ : await adapterLike.exec(queryText, params);
1138
+ return normalizeAffectedCount(res);
1139
+ },
1140
+ transaction: adapterLike.$transaction
1141
+ ? async (fn, options) => {
1142
+ return adapterLike.$transaction(async (tx) => fn(executorFromAdapter(tx)), options);
1143
+ }
1144
+ : undefined,
1145
+ beginTransaction: adapterLike.$begin
1146
+ ? async () => {
1147
+ const tx = await adapterLike.$begin();
1148
+ return {
1149
+ executor: executorFromAdapter(tx),
1150
+ commit: () => tx.$commit(),
1151
+ rollback: () => tx.$rollback(),
1152
+ };
1153
+ }
1154
+ : undefined,
1155
+ });
1156
+ }