@an5/adapters 0.2.5 → 0.2.9

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 (99) hide show
  1. package/README.md +283 -305
  2. package/dist/an5Adapter.d.ts +142 -7
  3. package/dist/an5Adapter.js +998 -68
  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 +10 -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 +5 -1
  19. package/dist/index.js +15 -1
  20. package/dist/mssql/engine.js +8 -4
  21. package/dist/mysql/engine.js +1 -1
  22. package/dist/nbase/client.d.ts +92 -0
  23. package/dist/nbase/client.js +147 -0
  24. package/dist/nbase/index.d.ts +47 -0
  25. package/dist/nbase/index.js +80 -0
  26. package/dist/postgres/engine.js +1 -1
  27. package/dist/sqlite/browserEngine.d.ts +27 -0
  28. package/dist/sqlite/browserEngine.js +161 -0
  29. package/dist/sqlite/engine.js +15 -3
  30. package/dist/sqlite/index.d.ts +1 -0
  31. package/dist/sqlite/index.js +1 -0
  32. package/dist/typed.d.ts +5 -0
  33. package/dist/typed.js +2 -0
  34. package/dotnet/An5Adapter.cs +1112 -275
  35. package/dotnet/Base/Core.cs +380 -82
  36. package/dotnet/Mssql/MssqlEngine.cs +145 -144
  37. package/dotnet/Postgres/PostgresEngine.cs +149 -121
  38. package/dotnet/Sqlite/SqliteEngine.cs +214 -0
  39. package/golang/an5_adapter.go +314 -0
  40. package/golang/base/base.go +100 -0
  41. package/golang/base/metadata.go +116 -0
  42. package/golang/base/where.go +311 -0
  43. package/golang/go.mod +3 -0
  44. package/golang/mssql/mssql.go +32 -0
  45. package/golang/postgres/postgres.go +37 -0
  46. package/golang/sqlite/sqlite.go +14 -0
  47. package/golang/sqlite_test.go +42 -0
  48. package/golang/table_client.go +1329 -0
  49. package/package.json +26 -9
  50. package/python/an5_adapter.py +184 -97
  51. package/python/base/__init__.py +3 -3
  52. package/python/base/dialects.py +11 -8
  53. package/python/base/metadata.py +59 -10
  54. package/python/base/sql.py +202 -121
  55. package/python/mssql/__init__.py +1 -1
  56. package/python/mssql/provider.py +62 -62
  57. package/python/postgres/__init__.py +1 -1
  58. package/python/postgres/provider.py +15 -15
  59. package/python/sqlite/__init__.py +3 -0
  60. package/python/sqlite/provider.py +83 -0
  61. package/python/table_client.py +636 -216
  62. package/rust/Cargo.toml +31 -0
  63. package/rust/README.md +71 -0
  64. package/rust/src/adapter.rs +1312 -0
  65. package/rust/src/base/dialect.rs +172 -0
  66. package/rust/src/base/metadata.rs +134 -0
  67. package/rust/src/base/mod.rs +17 -0
  68. package/rust/src/base/where_builder.rs +405 -0
  69. package/rust/src/lib.rs +75 -0
  70. package/typescript/src/an5Adapter.ts +1515 -505
  71. package/typescript/src/base/index.ts +3 -3
  72. package/typescript/src/base/metadata.ts +148 -28
  73. package/typescript/src/base/sql.ts +264 -98
  74. package/typescript/src/base/types.ts +33 -24
  75. package/typescript/src/browser.ts +10 -6
  76. package/typescript/src/config.ts +95 -95
  77. package/typescript/src/googlesheets/adapter.ts +269 -259
  78. package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
  79. package/typescript/src/googlesheets/config.ts +47 -47
  80. package/typescript/src/googlesheets/helpers.ts +144 -143
  81. package/typescript/src/googlesheets/index.ts +3 -3
  82. package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
  83. package/typescript/src/googlesheets/retry.ts +126 -126
  84. package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
  85. package/typescript/src/googlesheets/tableClient.ts +461 -455
  86. package/typescript/src/googlesheets/types.ts +6 -6
  87. package/typescript/src/index.ts +48 -27
  88. package/typescript/src/mssql/engine.ts +134 -132
  89. package/typescript/src/mssql/index.ts +1 -1
  90. package/typescript/src/mysql/engine.ts +80 -80
  91. package/typescript/src/mysql/index.ts +1 -1
  92. package/typescript/src/nbase/client.ts +214 -0
  93. package/typescript/src/nbase/index.ts +104 -0
  94. package/typescript/src/postgres/engine.ts +87 -87
  95. package/typescript/src/postgres/index.ts +1 -1
  96. package/typescript/src/sqlite/browserEngine.ts +192 -0
  97. package/typescript/src/sqlite/engine.ts +66 -55
  98. package/typescript/src/sqlite/index.ts +3 -1
  99. package/typescript/src/typed.ts +52 -0
@@ -1,82 +1,380 @@
1
- using System;
2
- using System.Collections.Generic;
3
- using System.Data.Common;
4
-
5
- namespace An5Orm
6
- {
7
- public enum Dialect
8
- {
9
- Mssql,
10
- Postgres
11
- }
12
-
13
- // ─── Config ────────────────────────────────────────────────────────────────
14
-
15
- public class An5AdapterOptions
16
- {
17
- public string ConnectionString { get; set; }
18
- public int CommandTimeout { get; set; } = 60;
19
- public int ConnectRetryCount { get; set; } = 3;
20
- }
21
-
22
- // ─── Query Engine Interface ────────────────────────────────────────────────
23
-
24
- internal interface IQueryEngine
25
- {
26
- Dialect Dialect { get; }
27
- List<Dictionary<string, object>> QueryRaw(string sql, Dictionary<string, object> parameters);
28
- List<T> QueryRaw<T>(string sql, Dictionary<string, object> parameters) where T : new();
29
- int ExecuteRaw(string sql, Dictionary<string, object> parameters);
30
- An5TransactionBase BeginTransaction();
31
- }
32
-
33
- // ─── Transaction base ──────────────────────────────────────────────────────
34
-
35
- internal abstract class An5TransactionBase : IDisposable
36
- {
37
- public abstract void Commit();
38
- public abstract void Rollback();
39
- public abstract void Dispose();
40
- }
41
-
42
- // ─── Dialect detection ─────────────────────────────────────────────────────
43
-
44
- internal static class DialectDetector
45
- {
46
- public static Dialect Detect(string connectionString)
47
- {
48
- var cs = (connectionString ?? "").Trim().ToLowerInvariant();
49
- if (cs.StartsWith("postgres://") || cs.StartsWith("postgresql://") || cs.Contains("host="))
50
- return Dialect.Postgres;
51
- return Dialect.Mssql;
52
- }
53
- }
54
-
55
- // ─── Quoting ───────────────────────────────────────────────────────────────
56
-
57
- internal static class SqlQuote
58
- {
59
- public static string QuoteName(string name, Dialect dialect)
60
- {
61
- if (dialect == Dialect.Postgres)
62
- {
63
- if (name.StartsWith("[") && name.EndsWith("]"))
64
- return "\"" + name.Substring(1, name.Length - 2) + "\"";
65
- return "\"" + name + "\"";
66
- }
67
- return name.StartsWith("[") ? name : "[" + name + "]";
68
- }
69
-
70
- public static string QuoteTable(string tableName, Dialect dialect)
71
- {
72
- if (tableName.StartsWith("[") || tableName.StartsWith("\""))
73
- return tableName;
74
- if (tableName.Contains("."))
75
- {
76
- var parts = tableName.Split('.');
77
- return string.Join(".", Array.ConvertAll(parts, p => QuoteName(p, dialect)));
78
- }
79
- return QuoteName(tableName, dialect);
80
- }
81
- }
82
- }
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.Data.Common;
4
+
5
+ namespace An5Orm
6
+ {
7
+ public enum Dialect
8
+ {
9
+ Mssql,
10
+ Postgres,
11
+ Sqlite
12
+ }
13
+
14
+ // ─── Config ────────────────────────────────────────────────────────────────
15
+
16
+ public class An5AdapterOptions
17
+ {
18
+ public string ConnectionString { get; set; }
19
+ public int CommandTimeout { get; set; } = 60;
20
+ public int ConnectRetryCount { get; set; } = 3;
21
+ }
22
+
23
+ // ─── Query Engine Interface ────────────────────────────────────────────────
24
+
25
+ internal interface IQueryEngine
26
+ {
27
+ Dialect Dialect { get; }
28
+ List<Dictionary<string, object>> QueryRaw(string sql, Dictionary<string, object> parameters);
29
+ List<T> QueryRaw<T>(string sql, Dictionary<string, object> parameters) where T : new();
30
+ int ExecuteRaw(string sql, Dictionary<string, object> parameters);
31
+ An5TransactionBase BeginTransaction();
32
+ }
33
+
34
+ // ─── Transaction base ──────────────────────────────────────────────────────
35
+
36
+ internal abstract class An5TransactionBase : IDisposable
37
+ {
38
+ public abstract void Commit();
39
+ public abstract void Rollback();
40
+ public abstract void Dispose();
41
+ }
42
+
43
+ // ─── Connection scope ──────────────────────────────────────────────────────
44
+
45
+ /// <summary>
46
+ /// A connection borrowed for the length of one operation, and whether the
47
+ /// caller still owns it.
48
+ ///
49
+ /// A statement issued while a transaction is open has to run on that
50
+ /// transaction's connection. Opening a second one is not merely wasteful: on
51
+ /// SQLite the open transaction holds a write lock, so the second connection
52
+ /// is refused with "database is locked", and on the server engines the write
53
+ /// silently lands outside the transaction, so commit and rollback do not
54
+ /// cover it. Each engine keeps the transaction connection and hands it back
55
+ /// through this scope while it is set.
56
+ /// </summary>
57
+ internal readonly struct ConnectionScope<T> : IDisposable where T : class
58
+ {
59
+ public T Connection { get; }
60
+
61
+ private readonly bool _owned;
62
+
63
+ internal ConnectionScope(T connection, bool owned)
64
+ {
65
+ Connection = connection;
66
+ _owned = owned;
67
+ }
68
+
69
+ public void Dispose()
70
+ {
71
+ if (_owned && Connection is IDisposable disposable) disposable.Dispose();
72
+ }
73
+ }
74
+
75
+ // ─── Dialect detection ─────────────────────────────────────────────────────
76
+
77
+ internal static class DialectDetector
78
+ {
79
+ public static Dialect Detect(string connectionString)
80
+ {
81
+ var cs = (connectionString ?? "").Trim().ToLowerInvariant();
82
+ // SQLite first: `Data Source=app.db` and a bare file path would
83
+ // otherwise fall through, and a Postgres string never looks like
84
+ // any of these.
85
+ if (cs.StartsWith("sqlite:") || cs.StartsWith("file:") || cs == ":memory:"
86
+ || cs.Contains("data source=") || cs.Contains("datasource=")
87
+ || cs.EndsWith(".db") || cs.EndsWith(".sqlite") || cs.EndsWith(".sqlite3"))
88
+ return Dialect.Sqlite;
89
+ if (cs.StartsWith("postgres://") || cs.StartsWith("postgresql://") || cs.Contains("host="))
90
+ return Dialect.Postgres;
91
+ return Dialect.Mssql;
92
+ }
93
+ }
94
+
95
+ // ─── Quoting ───────────────────────────────────────────────────────────────
96
+
97
+ internal static class SqlQuote
98
+ {
99
+ private static string StripWrapping(string name, string left, string right)
100
+ {
101
+ return name.StartsWith(left) && name.EndsWith(right)
102
+ ? name.Substring(left.Length, name.Length - left.Length - right.Length)
103
+ : name;
104
+ }
105
+
106
+ public static string QuoteName(string name, Dialect dialect)
107
+ {
108
+ var raw = name ?? "";
109
+ if (dialect == Dialect.Postgres || dialect == Dialect.Sqlite)
110
+ {
111
+ var unwrapped = StripWrapping(StripWrapping(raw, "[", "]"), "\"", "\"");
112
+ return "\"" + unwrapped.Replace("\"", "\"\"") + "\"";
113
+ }
114
+ var bracketless = StripWrapping(raw, "[", "]");
115
+ return "[" + bracketless.Replace("]", "]]") + "]";
116
+ }
117
+
118
+ public static string QuoteTable(string tableName, Dialect dialect)
119
+ {
120
+ if (tableName.StartsWith("[") || tableName.StartsWith("\""))
121
+ return tableName;
122
+ if (tableName.Contains("."))
123
+ {
124
+ var parts = tableName.Split('.');
125
+ return string.Join(".", Array.ConvertAll(parts, p => QuoteName(p, dialect)));
126
+ }
127
+ return QuoteName(tableName, dialect);
128
+ }
129
+ }
130
+
131
+ // ─── Metadata registry ────────────────────────────────────────────────────
132
+
133
+ public class RelationDef
134
+ {
135
+ public string ModelName { get; set; }
136
+ public string RelationType { get; set; } // "many" | "one"
137
+ public string ForeignKey { get; set; }
138
+ public string LocalKey { get; set; }
139
+ }
140
+
141
+ public class AdapterMetadata
142
+ {
143
+ public Dictionary<string, string> ModelToTable { get; set; } = new();
144
+ public Dictionary<string, object> ModelFields { get; set; } = new();
145
+ public Dictionary<string, Dictionary<string, RelationDef>> RelationMap { get; set; } = new();
146
+ }
147
+
148
+ internal static class An5Metadata
149
+ {
150
+ private static readonly object _lock = new();
151
+ private static Dictionary<string, string> _modelToTable = new();
152
+ private static Dictionary<string, object> _modelFields = new();
153
+ private static Dictionary<string, Dictionary<string, RelationDef>> _relationMap = new();
154
+
155
+ public static void SetAdapterMetadata(AdapterMetadata meta)
156
+ {
157
+ lock (_lock)
158
+ {
159
+ _modelToTable = meta?.ModelToTable != null ? new Dictionary<string, string>(meta.ModelToTable) : new();
160
+ _modelFields = meta?.ModelFields != null ? new Dictionary<string, object>(meta.ModelFields) : new();
161
+ _relationMap = new Dictionary<string, Dictionary<string, RelationDef>>();
162
+ if (meta?.RelationMap != null)
163
+ {
164
+ foreach (var kv in meta.RelationMap)
165
+ {
166
+ _relationMap[kv.Key] = new Dictionary<string, RelationDef>(kv.Value);
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ public static Dictionary<string, string> GetModelToTable()
173
+ {
174
+ lock (_lock) return new Dictionary<string, string>(_modelToTable);
175
+ }
176
+
177
+ public static object GetFieldsForModel(string modelName)
178
+ {
179
+ lock (_lock)
180
+ {
181
+ return _modelFields.TryGetValue(modelName, out var v) ? v : null;
182
+ }
183
+ }
184
+
185
+ public static Dictionary<string, RelationDef> GetRelationsForModel(string modelName)
186
+ {
187
+ lock (_lock)
188
+ {
189
+ return _relationMap.TryGetValue(modelName, out var r) ? new Dictionary<string, RelationDef>(r) : new Dictionary<string, RelationDef>();
190
+ }
191
+ }
192
+ }
193
+
194
+ // ─── Where / OrderBy builder ───────────────────────────────────────────────
195
+ // Mirrors the TypeScript parseWhere/buildOrderBy used by the reference adapter.
196
+
197
+ internal static class SqlBuilder
198
+ {
199
+ private static readonly string[] OperatorKeys =
200
+ { "equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt" };
201
+
202
+ private static bool IsOperatorValue(object value)
203
+ {
204
+ if (!(value is Dictionary<string, object> dict)) return false;
205
+ foreach (var op in OperatorKeys)
206
+ if (dict.ContainsKey(op)) return true;
207
+ return false;
208
+ }
209
+
210
+ internal static string SanitizeParamName(string name)
211
+ {
212
+ var sb = new System.Text.StringBuilder();
213
+ foreach (var ch in (name ?? ""))
214
+ sb.Append(char.IsLetterOrDigit(ch) || ch == '_' ? ch : '_');
215
+ var cleaned = sb.ToString();
216
+ return cleaned.Length > 0 && char.IsLetter(cleaned[0]) ? cleaned : "p_" + cleaned;
217
+ }
218
+
219
+ private static string NormalizeSortDirection(object direction)
220
+ {
221
+ return direction is string s && s.ToUpperInvariant() == "DESC" ? "DESC" : "ASC";
222
+ }
223
+
224
+ private static string Placeholder(string paramName) => "@" + paramName;
225
+
226
+ public static string BuildOrderBy(object orderBy, Dialect dialect)
227
+ {
228
+ if (orderBy == null) return "";
229
+ var entries = orderBy is System.Collections.IList list ? list : new List<object> { orderBy };
230
+ var parts = new List<string>();
231
+ foreach (var entry in entries)
232
+ {
233
+ if (!(entry is Dictionary<string, object> map)) continue;
234
+ foreach (var kv in map)
235
+ parts.Add($"{SqlQuote.QuoteName(kv.Key, dialect)} {NormalizeSortDirection(kv.Value)}");
236
+ }
237
+ return parts.Count > 0 ? "ORDER BY " + string.Join(", ", parts) : "";
238
+ }
239
+
240
+ public static string ParseWhere(object where, Dictionary<string, object> parameters, Dialect dialect, string prefix = "")
241
+ {
242
+ if (!(where is Dictionary<string, object> dict) || dict.Count == 0) return "";
243
+
244
+ var clean = new Dictionary<string, object>();
245
+ foreach (var kv in dict)
246
+ {
247
+ if (kv.Key.Contains("_") && kv.Value is Dictionary<string, object> inner && !IsOperatorValue(inner))
248
+ {
249
+ foreach (var sub in inner) clean[sub.Key] = sub.Value;
250
+ }
251
+ else
252
+ {
253
+ clean[kv.Key] = kv.Value;
254
+ }
255
+ }
256
+
257
+ var conditions = new List<string>();
258
+ foreach (var kv in clean)
259
+ {
260
+ var key = kv.Key;
261
+ var value = kv.Value;
262
+ if (key == "OR" && value is System.Collections.IList orList && orList.Count > 0)
263
+ {
264
+ var subs = new List<string>();
265
+ var i = 0;
266
+ foreach (var item in orList)
267
+ {
268
+ if (item is Dictionary<string, object> subMap)
269
+ {
270
+ var s = ParseWhere(subMap, parameters, dialect, $"{prefix}or{i++}_");
271
+ if (!string.IsNullOrEmpty(s)) subs.Add(s);
272
+ }
273
+ }
274
+ if (subs.Count > 0) conditions.Add("(" + string.Join(" OR ", subs) + ")");
275
+ continue;
276
+ }
277
+ if (key == "AND" && value is System.Collections.IList andList && andList.Count > 0)
278
+ {
279
+ var subs = new List<string>();
280
+ var i = 0;
281
+ foreach (var item in andList)
282
+ {
283
+ if (item is Dictionary<string, object> subMap)
284
+ {
285
+ var s = ParseWhere(subMap, parameters, dialect, $"{prefix}and{i++}_");
286
+ if (!string.IsNullOrEmpty(s)) subs.Add(s);
287
+ }
288
+ }
289
+ if (subs.Count > 0) conditions.Add("(" + string.Join(" AND ", subs) + ")");
290
+ continue;
291
+ }
292
+ if (key == "NOT" && value is Dictionary<string, object> notMap)
293
+ {
294
+ var s = ParseWhere(notMap, parameters, dialect, $"{prefix}not_");
295
+ if (!string.IsNullOrEmpty(s)) conditions.Add("NOT (" + s + ")");
296
+ continue;
297
+ }
298
+
299
+ var col = SqlQuote.QuoteName(key, dialect);
300
+ var baseName = SanitizeParamName($"{prefix}{key}");
301
+ if (value == null)
302
+ {
303
+ conditions.Add($"{col} IS NULL");
304
+ }
305
+ else if (value is Dictionary<string, object> opMap)
306
+ {
307
+ foreach (var op in OperatorKeys)
308
+ {
309
+ if (!opMap.TryGetValue(op, out var opVal)) continue;
310
+ switch (op)
311
+ {
312
+ case "not":
313
+ if (opVal == null) conditions.Add($"{col} IS NOT NULL");
314
+ else if (opVal is Dictionary<string, object> nested)
315
+ {
316
+ var s = ParseWhere(new Dictionary<string, object> { { key, nested } }, parameters, dialect, $"{prefix}{key}_not_");
317
+ if (!string.IsNullOrEmpty(s)) conditions.Add("NOT (" + s + ")");
318
+ }
319
+ else { parameters[baseName + "_not"] = opVal; conditions.Add($"{col} <> {Placeholder(baseName + "_not")}"); }
320
+ break;
321
+ case "equals":
322
+ if (opVal == null) conditions.Add($"{col} IS NULL");
323
+ else { parameters[baseName + "_eq"] = opVal; conditions.Add($"{col} = {Placeholder(baseName + "_eq")}"); }
324
+ break;
325
+ case "contains":
326
+ parameters[baseName + "_co"] = "%" + opVal + "%"; conditions.Add($"{col} LIKE {Placeholder(baseName + "_co")}");
327
+ break;
328
+ case "startsWith":
329
+ parameters[baseName + "_sw"] = opVal + "%"; conditions.Add($"{col} LIKE {Placeholder(baseName + "_sw")}");
330
+ break;
331
+ case "endsWith":
332
+ parameters[baseName + "_ew"] = "%" + opVal; conditions.Add($"{col} LIKE {Placeholder(baseName + "_ew")}");
333
+ break;
334
+ case "gte":
335
+ parameters[baseName + "_gte"] = opVal; conditions.Add($"{col} >= {Placeholder(baseName + "_gte")}");
336
+ break;
337
+ case "lte":
338
+ parameters[baseName + "_lte"] = opVal; conditions.Add($"{col} <= {Placeholder(baseName + "_lte")}");
339
+ break;
340
+ case "gt":
341
+ parameters[baseName + "_gt"] = opVal; conditions.Add($"{col} > {Placeholder(baseName + "_gt")}");
342
+ break;
343
+ case "lt":
344
+ parameters[baseName + "_lt"] = opVal; conditions.Add($"{col} < {Placeholder(baseName + "_lt")}");
345
+ break;
346
+ case "in":
347
+ case "notIn":
348
+ if (opVal is System.Collections.IList vals && vals.Count > 0)
349
+ {
350
+ var phs = new List<string>();
351
+ var idx = 0;
352
+ foreach (var v in vals)
353
+ {
354
+ var pn = op == "in" ? $"{baseName}_in{idx}" : $"{baseName}_notin{idx}";
355
+ parameters[pn] = v;
356
+ phs.Add(Placeholder(pn));
357
+ idx++;
358
+ }
359
+ conditions.Add(op == "in"
360
+ ? $"{col} IN ({string.Join(", ", phs)})"
361
+ : $"{col} NOT IN ({string.Join(", ", phs)})");
362
+ }
363
+ else
364
+ {
365
+ conditions.Add(op == "in" ? "1=0" : "1=1");
366
+ }
367
+ break;
368
+ }
369
+ }
370
+ }
371
+ else
372
+ {
373
+ parameters[baseName] = value;
374
+ conditions.Add($"{col} = {Placeholder(baseName)}");
375
+ }
376
+ }
377
+ return string.Join(" AND ", conditions);
378
+ }
379
+ }
380
+ }