@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.
- package/README.md +189 -305
- package/dist/an5Adapter.d.ts +85 -7
- package/dist/an5Adapter.js +736 -64
- package/dist/base/metadata.d.ts +10 -0
- package/dist/base/metadata.js +107 -0
- package/dist/base/sql.d.ts +19 -1
- package/dist/base/sql.js +152 -24
- package/dist/base/types.d.ts +4 -1
- package/dist/browser.d.ts +4 -1
- package/dist/browser.js +9 -2
- package/dist/googlesheets/adapter.d.ts +1 -0
- package/dist/googlesheets/adapter.js +9 -2
- package/dist/googlesheets/config.d.ts +4 -4
- package/dist/googlesheets/helpers.js +2 -0
- package/dist/googlesheets/parseConnectionString.js +5 -2
- package/dist/googlesheets/sqlExecutor.js +29 -24
- package/dist/googlesheets/tableClient.js +14 -5
- package/dist/index.d.ts +3 -1
- package/dist/index.js +7 -1
- package/dist/mssql/engine.js +8 -4
- package/dist/mysql/engine.js +1 -1
- package/dist/postgres/engine.js +1 -1
- package/dist/sqlite/browserEngine.d.ts +27 -0
- package/dist/sqlite/browserEngine.js +161 -0
- package/dist/sqlite/engine.js +15 -3
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/typed.d.ts +5 -0
- package/dist/typed.js +2 -0
- package/dotnet/An5Adapter.cs +1089 -275
- package/dotnet/Base/Core.cs +340 -82
- package/dotnet/Mssql/MssqlEngine.cs +145 -144
- package/dotnet/Postgres/PostgresEngine.cs +121 -121
- package/golang/an5_adapter.go +314 -0
- package/golang/base/base.go +100 -0
- package/golang/base/metadata.go +116 -0
- package/golang/base/where.go +311 -0
- package/golang/go.mod +3 -0
- package/golang/mssql/mssql.go +32 -0
- package/golang/postgres/postgres.go +37 -0
- package/golang/sqlite/sqlite.go +14 -0
- package/golang/sqlite_test.go +42 -0
- package/golang/table_client.go +1329 -0
- package/package.json +17 -7
- package/python/an5_adapter.py +119 -97
- package/python/base/__init__.py +3 -3
- package/python/base/dialects.py +11 -8
- package/python/base/metadata.py +35 -10
- package/python/base/sql.py +172 -121
- package/python/mssql/__init__.py +1 -1
- package/python/mssql/provider.py +62 -62
- package/python/postgres/__init__.py +1 -1
- package/python/postgres/provider.py +15 -15
- package/python/table_client.py +619 -216
- package/typescript/src/an5Adapter.ts +1220 -505
- package/typescript/src/base/index.ts +3 -3
- package/typescript/src/base/metadata.ts +148 -28
- package/typescript/src/base/sql.ts +264 -98
- package/typescript/src/base/types.ts +27 -24
- package/typescript/src/browser.ts +10 -6
- package/typescript/src/config.ts +95 -95
- package/typescript/src/googlesheets/adapter.ts +269 -259
- package/typescript/src/googlesheets/an5SheetsAdapter.ts +3 -3
- package/typescript/src/googlesheets/config.ts +47 -47
- package/typescript/src/googlesheets/helpers.ts +144 -143
- package/typescript/src/googlesheets/index.ts +3 -3
- package/typescript/src/googlesheets/parseConnectionString.ts +48 -46
- package/typescript/src/googlesheets/retry.ts +126 -126
- package/typescript/src/googlesheets/sqlExecutor.ts +304 -301
- package/typescript/src/googlesheets/tableClient.ts +461 -455
- package/typescript/src/googlesheets/types.ts +6 -6
- package/typescript/src/index.ts +31 -27
- package/typescript/src/mssql/engine.ts +134 -132
- package/typescript/src/mssql/index.ts +1 -1
- package/typescript/src/mysql/engine.ts +80 -80
- package/typescript/src/mysql/index.ts +1 -1
- package/typescript/src/postgres/engine.ts +87 -87
- package/typescript/src/postgres/index.ts +1 -1
- package/typescript/src/sqlite/browserEngine.ts +192 -0
- package/typescript/src/sqlite/engine.ts +66 -55
- package/typescript/src/sqlite/index.ts +3 -1
- package/typescript/src/typed.ts +52 -0
package/dist/base/metadata.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface RelationDef {
|
|
2
|
+
modelName: string;
|
|
3
|
+
relationType: "many" | "one";
|
|
4
|
+
foreignKey: string;
|
|
5
|
+
localKey: string;
|
|
6
|
+
}
|
|
1
7
|
export interface ModelFieldMeta {
|
|
2
8
|
name?: string;
|
|
3
9
|
ts?: string;
|
|
@@ -8,7 +14,11 @@ export interface ModelFieldMeta {
|
|
|
8
14
|
export interface AdapterMetadata {
|
|
9
15
|
modelToTable?: Record<string, string>;
|
|
10
16
|
modelFields?: Record<string, any>;
|
|
17
|
+
relationMap?: Record<string, Record<string, RelationDef>>;
|
|
11
18
|
}
|
|
12
19
|
export declare function setAdapterMetadata(metadata: AdapterMetadata): void;
|
|
13
20
|
export declare function getModelToTable(): Record<string, string>;
|
|
14
21
|
export declare function getFieldsForModel(modelName: string): any;
|
|
22
|
+
export declare function getRelationMap(): Record<string, Record<string, RelationDef>>;
|
|
23
|
+
export declare function getRelationsForModel(modelName: string): Record<string, RelationDef>;
|
|
24
|
+
export declare function createAdapterProxy<T extends object>(targetObj: T, tableGetter: (modelName: string) => any): T;
|
package/dist/base/metadata.js
CHANGED
|
@@ -3,15 +3,122 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.setAdapterMetadata = setAdapterMetadata;
|
|
4
4
|
exports.getModelToTable = getModelToTable;
|
|
5
5
|
exports.getFieldsForModel = getFieldsForModel;
|
|
6
|
+
exports.getRelationMap = getRelationMap;
|
|
7
|
+
exports.getRelationsForModel = getRelationsForModel;
|
|
8
|
+
exports.createAdapterProxy = createAdapterProxy;
|
|
6
9
|
let modelToTable = {};
|
|
7
10
|
let modelFields = {};
|
|
11
|
+
let relationMap = {};
|
|
12
|
+
let autoLoaded = false;
|
|
13
|
+
function loadMetadataFromConfig() {
|
|
14
|
+
// Auto-loading scans the filesystem at runtime, so it only applies in Node.
|
|
15
|
+
// Lazy requires keep Node builtins out of browser builds.
|
|
16
|
+
if (typeof process === 'undefined' || !process.cwd)
|
|
17
|
+
return null;
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const rootDir = process.cwd();
|
|
21
|
+
const configNames = ['an5Orm.config.js', 'an5Orm.config.cjs', 'an5Orm.config.json'];
|
|
22
|
+
const candidatesDirs = [rootDir, path.resolve(rootDir, '..'), path.resolve(rootDir, '../..')];
|
|
23
|
+
for (const dir of candidatesDirs) {
|
|
24
|
+
for (const name of configNames) {
|
|
25
|
+
const configPath = path.join(dir, name);
|
|
26
|
+
if (fs.existsSync(configPath)) {
|
|
27
|
+
try {
|
|
28
|
+
const config = name.endsWith('.json')
|
|
29
|
+
? JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
30
|
+
: require(configPath);
|
|
31
|
+
const metaFile = config?.outputs?.typescript?.metadataFile;
|
|
32
|
+
if (metaFile) {
|
|
33
|
+
const resolvedPath = path.isAbsolute(metaFile) ? metaFile : path.resolve(dir, metaFile);
|
|
34
|
+
const withoutExt = resolvedPath.replace(/\.(ts|js|cjs|mjs)$/, '');
|
|
35
|
+
const extensions = ['', '.js', '.ts', '.cjs'];
|
|
36
|
+
for (const ext of extensions) {
|
|
37
|
+
const checkPath = withoutExt + ext;
|
|
38
|
+
if (fs.existsSync(checkPath)) {
|
|
39
|
+
try {
|
|
40
|
+
const m = require(withoutExt);
|
|
41
|
+
if (m && (m.modelToTable || m.relationMap || m.modelFields)) {
|
|
42
|
+
return m;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function tryAutoLoadMetadata() {
|
|
57
|
+
if (autoLoaded || Object.keys(modelToTable).length > 0)
|
|
58
|
+
return;
|
|
59
|
+
autoLoaded = true;
|
|
60
|
+
const configMeta = loadMetadataFromConfig();
|
|
61
|
+
if (configMeta && (configMeta.modelToTable || configMeta.relationMap || configMeta.modelFields)) {
|
|
62
|
+
modelToTable = { ...(configMeta.modelToTable || {}) };
|
|
63
|
+
modelFields = { ...(configMeta.modelFields || {}) };
|
|
64
|
+
relationMap = { ...(configMeta.relationMap || {}) };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
8
67
|
function setAdapterMetadata(metadata) {
|
|
9
68
|
modelToTable = { ...(metadata.modelToTable || {}) };
|
|
10
69
|
modelFields = { ...(metadata.modelFields || {}) };
|
|
70
|
+
relationMap = { ...(metadata.relationMap || {}) };
|
|
11
71
|
}
|
|
12
72
|
function getModelToTable() {
|
|
73
|
+
tryAutoLoadMetadata();
|
|
13
74
|
return modelToTable;
|
|
14
75
|
}
|
|
15
76
|
function getFieldsForModel(modelName) {
|
|
77
|
+
tryAutoLoadMetadata();
|
|
16
78
|
return modelFields[modelName] || {};
|
|
17
79
|
}
|
|
80
|
+
function getRelationMap() {
|
|
81
|
+
tryAutoLoadMetadata();
|
|
82
|
+
return relationMap;
|
|
83
|
+
}
|
|
84
|
+
function getRelationsForModel(modelName) {
|
|
85
|
+
tryAutoLoadMetadata();
|
|
86
|
+
return relationMap[modelName] || {};
|
|
87
|
+
}
|
|
88
|
+
// ─── Dynamic model proxy ─────────────────────────────────────────────────────
|
|
89
|
+
// Shared by An5Adapter and An5SheetsAdapter: `db.user` / `db.User` /
|
|
90
|
+
// `db.users` resolve case-insensitively through the registered metadata and
|
|
91
|
+
// delegate to `table(modelName)`. Real members (`$`-prefixed helpers and
|
|
92
|
+
// methods) always win over model names.
|
|
93
|
+
function createAdapterProxy(targetObj, tableGetter) {
|
|
94
|
+
return new Proxy(targetObj, {
|
|
95
|
+
get(target, prop, receiver) {
|
|
96
|
+
if (typeof prop === 'string') {
|
|
97
|
+
if (prop in target || prop.startsWith('_') || prop.startsWith('$') || typeof target[prop] === 'function') {
|
|
98
|
+
const val = target[prop];
|
|
99
|
+
return typeof val === 'function' ? val.bind(target) : val;
|
|
100
|
+
}
|
|
101
|
+
let modelName = prop;
|
|
102
|
+
const modelToTable = getModelToTable();
|
|
103
|
+
if (!modelToTable[prop]) {
|
|
104
|
+
const lowerProp = prop.toLowerCase();
|
|
105
|
+
for (const [mName, tName] of Object.entries(modelToTable)) {
|
|
106
|
+
const lowerM = mName.toLowerCase();
|
|
107
|
+
const lowerT = tName.toLowerCase();
|
|
108
|
+
if (lowerM === lowerProp ||
|
|
109
|
+
lowerT === lowerProp ||
|
|
110
|
+
lowerM + 's' === lowerProp ||
|
|
111
|
+
lowerM + 'es' === lowerProp ||
|
|
112
|
+
lowerT + 's' === lowerProp ||
|
|
113
|
+
lowerT + 'es' === lowerProp) {
|
|
114
|
+
modelName = mName;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return tableGetter(modelName);
|
|
120
|
+
}
|
|
121
|
+
return Reflect.get(target, prop, receiver);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
package/dist/base/sql.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import type { Dialect } from './types';
|
|
2
|
+
import type { RelationDef } from './metadata';
|
|
2
3
|
export declare function quote(name: string, dialect: Dialect): string;
|
|
3
|
-
|
|
4
|
+
/** Quote a full table reference (may already be quoted, and may contain schema parts). */
|
|
5
|
+
export declare function quoteTable(name: string, dialect: Dialect): string;
|
|
6
|
+
export interface WhereContext {
|
|
7
|
+
/** Relations of the current model keyed by relation name. */
|
|
8
|
+
relationMap?: Record<string, Record<string, RelationDef>>;
|
|
9
|
+
/** model name → table name for resolving related tables. */
|
|
10
|
+
modelToTable?: Record<string, string>;
|
|
11
|
+
/** Fully-qualified reference to the current (self) table, e.g. `[users]` or `[dbo].[users]`. */
|
|
12
|
+
selfRef?: string;
|
|
13
|
+
/** Column prefix to disambiguate inside a correlated subquery (e.g. `__r.`). Includes trailing dot. */
|
|
14
|
+
colPrefix?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a WHERE clause. Supports scalar fields, operators and relation filters
|
|
18
|
+
* (`some`/`none`/`every` for to-many, `is`/`isNot` for to-one) via EXISTS
|
|
19
|
+
* subqueries when `ctx.relationMap` is provided.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseWhere(modelName: string, where: any, params: Record<string, any>, dialect: Dialect, prefix?: string, ctx?: WhereContext): string;
|
|
4
22
|
export declare function buildOrderBy(orderBy: any, dialect: Dialect): string;
|
package/dist/base/sql.js
CHANGED
|
@@ -1,29 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.quote = quote;
|
|
4
|
+
exports.quoteTable = quoteTable;
|
|
4
5
|
exports.parseWhere = parseWhere;
|
|
5
6
|
exports.buildOrderBy = buildOrderBy;
|
|
6
7
|
// ─── Quoting ───────────────────────────────────────────────────────────────────────
|
|
8
|
+
function stripWrapping(name, left, right) {
|
|
9
|
+
return name.startsWith(left) && name.endsWith(right) ? name.slice(left.length, -right.length) : name;
|
|
10
|
+
}
|
|
7
11
|
function quote(name, dialect) {
|
|
12
|
+
const raw = String(name);
|
|
8
13
|
if (dialect === 'postgres') {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (name.startsWith('"'))
|
|
12
|
-
return name;
|
|
13
|
-
return `"${name}"`;
|
|
14
|
+
const unwrapped = stripWrapping(stripWrapping(raw, '[', ']'), '"', '"');
|
|
15
|
+
return `"${unwrapped.replace(/"/g, '""')}"`;
|
|
14
16
|
}
|
|
15
17
|
if (dialect === 'mysql') {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (name.startsWith('`'))
|
|
19
|
-
return name;
|
|
20
|
-
return `\`${name}\``;
|
|
18
|
+
const unwrapped = stripWrapping(stripWrapping(raw, '[', ']'), '`', '`');
|
|
19
|
+
return `\`${unwrapped.replace(/`/g, '``')}\``;
|
|
21
20
|
}
|
|
22
21
|
// mssql / sqlite
|
|
23
|
-
|
|
22
|
+
const unwrapped = stripWrapping(raw, '[', ']');
|
|
23
|
+
return `[${unwrapped.replace(/\]/g, ']]')}]`;
|
|
24
|
+
}
|
|
25
|
+
/** Quote a full table reference (may already be quoted, and may contain schema parts). */
|
|
26
|
+
function quoteTable(name, dialect) {
|
|
27
|
+
const raw = String(name);
|
|
28
|
+
if (raw.startsWith('[') || raw.startsWith('"') || raw.startsWith('`'))
|
|
29
|
+
return raw;
|
|
30
|
+
if (raw.includes('.')) {
|
|
31
|
+
const open = dialect === 'mysql' ? '`' : dialect === 'postgres' ? '"' : '[';
|
|
32
|
+
const close = open === '[' ? ']' : open;
|
|
33
|
+
return raw.split('.').map(p => `${open}${p.replace(new RegExp(close.replace(/[[\]]/g, '\\$&'), 'g'), close.repeat(2))}${close}`).join('.');
|
|
34
|
+
}
|
|
35
|
+
return quote(raw, dialect);
|
|
36
|
+
}
|
|
37
|
+
function sanitizeParamName(name) {
|
|
38
|
+
const cleaned = String(name).replace(/[^A-Za-z0-9_]/g, '_');
|
|
39
|
+
return /^[A-Za-z]/.test(cleaned) ? cleaned : `p_${cleaned}`;
|
|
40
|
+
}
|
|
41
|
+
function normalizeSortDirection(dir) {
|
|
42
|
+
return typeof dir === 'string' && dir.toUpperCase() === 'DESC' ? 'DESC' : 'ASC';
|
|
24
43
|
}
|
|
25
44
|
// ─── Where clause builder ──────────────────────────────────────────────────────────
|
|
26
|
-
|
|
45
|
+
const OPERATOR_KEYS = ['equals', 'in', 'notIn', 'contains', 'startsWith', 'endsWith', 'not', 'gte', 'lte', 'gt', 'lt'];
|
|
46
|
+
function isOperatorValue(value) {
|
|
47
|
+
if (!value || typeof value !== 'object' || value instanceof Date)
|
|
48
|
+
return false;
|
|
49
|
+
return OPERATOR_KEYS.some(op => op in value);
|
|
50
|
+
}
|
|
51
|
+
function resolveTable(modelName, ctx) {
|
|
52
|
+
if (ctx?.modelToTable?.[modelName])
|
|
53
|
+
return ctx.modelToTable[modelName];
|
|
54
|
+
const camel = modelName.charAt(0).toLowerCase() + modelName.slice(1);
|
|
55
|
+
if (ctx?.modelToTable?.[camel])
|
|
56
|
+
return ctx.modelToTable[camel];
|
|
57
|
+
const lower = modelName.toLowerCase();
|
|
58
|
+
if (ctx?.modelToTable?.[lower])
|
|
59
|
+
return ctx.modelToTable[lower];
|
|
60
|
+
return modelName;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Build a WHERE clause. Supports scalar fields, operators and relation filters
|
|
64
|
+
* (`some`/`none`/`every` for to-many, `is`/`isNot` for to-one) via EXISTS
|
|
65
|
+
* subqueries when `ctx.relationMap` is provided.
|
|
66
|
+
*/
|
|
67
|
+
function parseWhere(modelName, where, params, dialect, prefix = '', ctx) {
|
|
27
68
|
if (!where)
|
|
28
69
|
return '';
|
|
29
70
|
const conditions = [];
|
|
@@ -32,29 +73,41 @@ function parseWhere(modelName, where, params, dialect, prefix = '') {
|
|
|
32
73
|
if (key.includes('_') &&
|
|
33
74
|
value && typeof value === 'object' &&
|
|
34
75
|
!(value instanceof Date) &&
|
|
35
|
-
!value
|
|
36
|
-
!value.not && !value.gte &&
|
|
37
|
-
!value.lte && !value.gt && !value.lt) {
|
|
76
|
+
!isOperatorValue(value)) {
|
|
38
77
|
Object.assign(cleanWhere, value);
|
|
39
78
|
}
|
|
40
79
|
else {
|
|
41
80
|
cleanWhere[key] = value;
|
|
42
81
|
}
|
|
43
82
|
}
|
|
83
|
+
const relations = ctx?.relationMap?.[modelName] || {};
|
|
84
|
+
const colPrefix = ctx?.colPrefix || '';
|
|
44
85
|
for (const [key, value] of Object.entries(cleanWhere)) {
|
|
45
86
|
if (key === 'OR' && Array.isArray(value)) {
|
|
46
|
-
const sub = value.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}or_${i}_
|
|
87
|
+
const sub = value.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}or_${i}_`, ctx)).filter(Boolean);
|
|
47
88
|
if (sub.length > 0)
|
|
48
89
|
conditions.push(`(${sub.join(' OR ')})`);
|
|
49
90
|
}
|
|
50
|
-
else if (key === 'AND'
|
|
51
|
-
const
|
|
91
|
+
else if (key === 'AND') {
|
|
92
|
+
const items = Array.isArray(value) ? value : [value];
|
|
93
|
+
const sub = items.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}and_${i}_`, ctx)).filter(Boolean);
|
|
52
94
|
if (sub.length > 0)
|
|
53
95
|
conditions.push(`(${sub.join(' AND ')})`);
|
|
54
96
|
}
|
|
97
|
+
else if (key === 'NOT') {
|
|
98
|
+
const items = Array.isArray(value) ? value : [value];
|
|
99
|
+
const sub = items.map((v, i) => parseWhere(modelName, v, params, dialect, `${prefix}not_${i}_`, ctx)).filter(Boolean);
|
|
100
|
+
if (sub.length > 0)
|
|
101
|
+
conditions.push(`NOT (${sub.join(' AND ')})`);
|
|
102
|
+
}
|
|
103
|
+
else if (relations[key] && value && typeof value === 'object') {
|
|
104
|
+
const clause = buildRelationClause(relations[key], value, modelName, params, dialect, prefix, ctx);
|
|
105
|
+
if (clause)
|
|
106
|
+
conditions.push(clause);
|
|
107
|
+
}
|
|
55
108
|
else {
|
|
56
|
-
const pname = `${prefix}${key}
|
|
57
|
-
const col = quote(key, dialect)
|
|
109
|
+
const pname = sanitizeParamName(`${prefix}${key}`);
|
|
110
|
+
const col = `${colPrefix}${quote(key, dialect)}`;
|
|
58
111
|
if (value === null) {
|
|
59
112
|
conditions.push(`${col} IS NULL`);
|
|
60
113
|
}
|
|
@@ -64,6 +117,13 @@ function parseWhere(modelName, where, params, dialect, prefix = '') {
|
|
|
64
117
|
if (v.not === null) {
|
|
65
118
|
conditions.push(`${col} IS NOT NULL`);
|
|
66
119
|
}
|
|
120
|
+
else if (v.not && typeof v.not === 'object' && !(v.not instanceof Date) && !Array.isArray(v.not)) {
|
|
121
|
+
const nestedParams = {};
|
|
122
|
+
const nestedSql = parseWhere(modelName, { [key]: v.not }, nestedParams, dialect, `${prefix}${key}_not_`, ctx);
|
|
123
|
+
Object.assign(params, nestedParams);
|
|
124
|
+
if (nestedSql)
|
|
125
|
+
conditions.push(`NOT (${nestedSql})`);
|
|
126
|
+
}
|
|
67
127
|
else {
|
|
68
128
|
const p = `${pname}_not`;
|
|
69
129
|
params[p] = v.not;
|
|
@@ -71,9 +131,14 @@ function parseWhere(modelName, where, params, dialect, prefix = '') {
|
|
|
71
131
|
}
|
|
72
132
|
}
|
|
73
133
|
if (v.equals !== undefined) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
134
|
+
if (v.equals === null) {
|
|
135
|
+
conditions.push(`${col} IS NULL`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const p = `${pname}_eq`;
|
|
139
|
+
params[p] = v.equals;
|
|
140
|
+
conditions.push(`${col} = @${p}`);
|
|
141
|
+
}
|
|
77
142
|
}
|
|
78
143
|
if (v.contains !== undefined) {
|
|
79
144
|
const p = `${pname}_co`;
|
|
@@ -119,6 +184,15 @@ function parseWhere(modelName, where, params, dialect, prefix = '') {
|
|
|
119
184
|
conditions.push('1=0');
|
|
120
185
|
}
|
|
121
186
|
}
|
|
187
|
+
if (v.notIn !== undefined) {
|
|
188
|
+
if (Array.isArray(v.notIn) && v.notIn.length > 0) {
|
|
189
|
+
const ps = v.notIn.map((x, i) => { const p = `${pname}_notin${i}`; params[p] = x; return `@${p}`; });
|
|
190
|
+
conditions.push(`${col} NOT IN (${ps.join(', ')})`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
conditions.push('1=1');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
122
196
|
}
|
|
123
197
|
else {
|
|
124
198
|
params[pname] = value;
|
|
@@ -128,6 +202,60 @@ function parseWhere(modelName, where, params, dialect, prefix = '') {
|
|
|
128
202
|
}
|
|
129
203
|
return conditions.join(' AND ');
|
|
130
204
|
}
|
|
205
|
+
function buildRelationClause(relation, value, modelName, params, dialect, prefix, ctx) {
|
|
206
|
+
const isMany = relation.relationType === 'many';
|
|
207
|
+
const qname = `${prefix}rel_${sanitizeParamName(relation.modelName)}`;
|
|
208
|
+
const childAlias = `__r_${sanitizeParamName(relation.modelName)}${qname.length}`.replace(/[^A-Za-z0-9_]/g, '_');
|
|
209
|
+
const childTable = quoteTable(resolveTable(relation.modelName, ctx), dialect);
|
|
210
|
+
const fk = quote(relation.foreignKey, dialect);
|
|
211
|
+
const lk = quote(relation.localKey, dialect);
|
|
212
|
+
const selfRef = (ctx?.selfRef || quoteTable(modelName, dialect)) + `.`;
|
|
213
|
+
// Correlation between the parent row and the related row.
|
|
214
|
+
const correlation = `${childAlias}.${fk} = ${selfRef}${lk}`;
|
|
215
|
+
// Correlated subquery for "there exists a related row matching <innerWhere>".
|
|
216
|
+
const buildExists = (innerWhere) => {
|
|
217
|
+
const inner = innerWhere ? ` AND (${innerWhere})` : '';
|
|
218
|
+
return `EXISTS (SELECT 1 FROM ${childTable} AS ${childAlias} WHERE ${correlation}${inner})`;
|
|
219
|
+
};
|
|
220
|
+
const parts = [];
|
|
221
|
+
if (isMany) {
|
|
222
|
+
if (value.some !== undefined) {
|
|
223
|
+
const inner = buildInnerWhere(relation.modelName, value.some === null ? {} : value.some, params, dialect, prefix, ctx, childAlias);
|
|
224
|
+
parts.push(buildExists(inner));
|
|
225
|
+
}
|
|
226
|
+
if (value.none !== undefined) {
|
|
227
|
+
const inner = buildInnerWhere(relation.modelName, value.none === null ? {} : value.none, params, dialect, prefix, ctx, childAlias);
|
|
228
|
+
parts.push(`NOT ${buildExists(inner)}`);
|
|
229
|
+
}
|
|
230
|
+
if (value.every !== undefined) {
|
|
231
|
+
// "every" = no related row violates the condition.
|
|
232
|
+
const inner = buildInnerWhere(relation.modelName, value.every === null ? {} : value.every, params, dialect, prefix, ctx, childAlias);
|
|
233
|
+
const negated = inner ? `NOT (${inner})` : '';
|
|
234
|
+
const violated = `EXISTS (SELECT 1 FROM ${childTable} AS ${childAlias} WHERE ${correlation}${negated ? ` AND ${negated}` : ' AND 1=0'})`;
|
|
235
|
+
parts.push(`NOT ${violated}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
if (value.is !== undefined) {
|
|
240
|
+
const inner = buildInnerWhere(relation.modelName, value.is === null ? {} : value.is, params, dialect, prefix, ctx, childAlias);
|
|
241
|
+
parts.push(buildExists(inner));
|
|
242
|
+
}
|
|
243
|
+
if (value.isNot !== undefined) {
|
|
244
|
+
const inner = buildInnerWhere(relation.modelName, value.isNot === null ? {} : value.isNot, params, dialect, prefix, ctx, childAlias);
|
|
245
|
+
parts.push(`NOT ${buildExists(inner)}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return parts.length > 0 ? parts.join(' AND ') : null;
|
|
249
|
+
}
|
|
250
|
+
function buildInnerWhere(relModel, where, params, dialect, prefix, ctx, childAlias) {
|
|
251
|
+
const subCtx = {
|
|
252
|
+
...(ctx?.relationMap !== undefined ? { relationMap: ctx.relationMap } : {}),
|
|
253
|
+
...(ctx?.modelToTable !== undefined ? { modelToTable: ctx.modelToTable } : {}),
|
|
254
|
+
selfRef: childAlias || quoteTable(resolveTable(relModel, ctx), dialect),
|
|
255
|
+
colPrefix: childAlias ? `${childAlias}.` : '',
|
|
256
|
+
};
|
|
257
|
+
return parseWhere(relModel, where, params, dialect, `${prefix}rel_inner_`, subCtx);
|
|
258
|
+
}
|
|
131
259
|
// ─── Order by builder ──────────────────────────────────────────────────────────────
|
|
132
260
|
function buildOrderBy(orderBy, dialect) {
|
|
133
261
|
if (!orderBy)
|
|
@@ -136,7 +264,7 @@ function buildOrderBy(orderBy, dialect) {
|
|
|
136
264
|
const parts = [];
|
|
137
265
|
for (const entry of entries) {
|
|
138
266
|
for (const [key, dir] of Object.entries(entry)) {
|
|
139
|
-
parts.push(`${quote(key, dialect)} ${dir
|
|
267
|
+
parts.push(`${quote(key, dialect)} ${normalizeSortDirection(dir)}`);
|
|
140
268
|
}
|
|
141
269
|
}
|
|
142
270
|
return parts.length > 0 ? `ORDER BY ${parts.join(', ')}` : '';
|
package/dist/base/types.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export type Dialect = 'mssql' | 'postgres' | 'mysql' | 'sqlite' | 'googlesheets';
|
|
2
2
|
export interface An5AdapterConfig {
|
|
3
|
-
connectionString
|
|
3
|
+
connectionString?: string;
|
|
4
|
+
engine?: QueryEngine;
|
|
5
|
+
db?: any;
|
|
6
|
+
driver?: any;
|
|
4
7
|
poolMax?: number;
|
|
5
8
|
requestTimeout?: number;
|
|
6
9
|
connectionTimeout?: number;
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { An5SheetsAdapter, SheetsTableClient, createAn5SheetsAdapter, parseSheetsConnectionString } from './googlesheets';
|
|
2
2
|
export type { An5SheetsAdapterConfig } from './googlesheets';
|
|
3
|
-
export
|
|
3
|
+
export { SqliteBrowserEngine, createBrowserSqliteAdapter } from './sqlite/browserEngine';
|
|
4
|
+
export type { SqliteDriver, SqliteBrowserConfig } from './sqlite/browserEngine';
|
|
5
|
+
export { An5Adapter, createAn5Adapter, createAdapter } from './an5Adapter';
|
|
6
|
+
export type { An5AdapterConfig, Dialect, QueryEngine, TransactionHandle } from './base/types';
|
|
4
7
|
export { getFieldsForModel, getModelToTable, setAdapterMetadata } from './base/metadata';
|
|
5
8
|
export type { AdapterMetadata } from './base/metadata';
|
package/dist/browser.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setAdapterMetadata = exports.getModelToTable = exports.getFieldsForModel = exports.parseSheetsConnectionString = exports.createAn5SheetsAdapter = exports.SheetsTableClient = exports.An5SheetsAdapter = void 0;
|
|
4
|
-
// Browser-safe entry —
|
|
3
|
+
exports.setAdapterMetadata = exports.getModelToTable = exports.getFieldsForModel = exports.createAdapter = exports.createAn5Adapter = exports.An5Adapter = exports.createBrowserSqliteAdapter = exports.SqliteBrowserEngine = exports.parseSheetsConnectionString = exports.createAn5SheetsAdapter = exports.SheetsTableClient = exports.An5SheetsAdapter = void 0;
|
|
4
|
+
// Browser-safe entry — supports in-browser SQLite (sql.js / WASM / custom driver) and Google Sheets
|
|
5
5
|
var googlesheets_1 = require("./googlesheets");
|
|
6
6
|
Object.defineProperty(exports, "An5SheetsAdapter", { enumerable: true, get: function () { return googlesheets_1.An5SheetsAdapter; } });
|
|
7
7
|
Object.defineProperty(exports, "SheetsTableClient", { enumerable: true, get: function () { return googlesheets_1.SheetsTableClient; } });
|
|
8
8
|
Object.defineProperty(exports, "createAn5SheetsAdapter", { enumerable: true, get: function () { return googlesheets_1.createAn5SheetsAdapter; } });
|
|
9
9
|
Object.defineProperty(exports, "parseSheetsConnectionString", { enumerable: true, get: function () { return googlesheets_1.parseSheetsConnectionString; } });
|
|
10
|
+
var browserEngine_1 = require("./sqlite/browserEngine");
|
|
11
|
+
Object.defineProperty(exports, "SqliteBrowserEngine", { enumerable: true, get: function () { return browserEngine_1.SqliteBrowserEngine; } });
|
|
12
|
+
Object.defineProperty(exports, "createBrowserSqliteAdapter", { enumerable: true, get: function () { return browserEngine_1.createBrowserSqliteAdapter; } });
|
|
13
|
+
var an5Adapter_1 = require("./an5Adapter");
|
|
14
|
+
Object.defineProperty(exports, "An5Adapter", { enumerable: true, get: function () { return an5Adapter_1.An5Adapter; } });
|
|
15
|
+
Object.defineProperty(exports, "createAn5Adapter", { enumerable: true, get: function () { return an5Adapter_1.createAn5Adapter; } });
|
|
16
|
+
Object.defineProperty(exports, "createAdapter", { enumerable: true, get: function () { return an5Adapter_1.createAdapter; } });
|
|
10
17
|
var metadata_1 = require("./base/metadata");
|
|
11
18
|
Object.defineProperty(exports, "getFieldsForModel", { enumerable: true, get: function () { return metadata_1.getFieldsForModel; } });
|
|
12
19
|
Object.defineProperty(exports, "getModelToTable", { enumerable: true, get: function () { return metadata_1.getModelToTable; } });
|
|
@@ -2,6 +2,7 @@ import { An5SheetsAdapterConfig, resolveConfig } from './config';
|
|
|
2
2
|
import { SheetsTableClient } from './tableClient';
|
|
3
3
|
import { SheetMeta } from './types';
|
|
4
4
|
export declare class An5SheetsAdapter {
|
|
5
|
+
[key: string]: any;
|
|
5
6
|
private sheets;
|
|
6
7
|
private fetchApi;
|
|
7
8
|
config: ReturnType<typeof resolveConfig>;
|
|
@@ -6,6 +6,7 @@ const config_1 = require("./config");
|
|
|
6
6
|
const sqlExecutor_1 = require("./sqlExecutor");
|
|
7
7
|
const tableClient_1 = require("./tableClient");
|
|
8
8
|
const retry_1 = require("./retry");
|
|
9
|
+
const metadata_1 = require("../base/metadata");
|
|
9
10
|
// ─── Fetch-based API proxy for OAuth Access Token / API Key (browser-compatible) ──
|
|
10
11
|
function createFetchApi(spreadsheetId, accessToken, apiKey) {
|
|
11
12
|
const base = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}`;
|
|
@@ -79,6 +80,8 @@ class An5SheetsAdapter {
|
|
|
79
80
|
this.fetchApi = null;
|
|
80
81
|
this.sheetsCache = null;
|
|
81
82
|
this.config = (0, config_1.resolveConfig)(config);
|
|
83
|
+
// Dynamic model access (`db.user` / `db.User`), same as An5Adapter.
|
|
84
|
+
return (0, metadata_1.createAdapterProxy)(this, (name) => this.table(name));
|
|
82
85
|
}
|
|
83
86
|
get isFetchMode() {
|
|
84
87
|
return !!this.config.accessToken || !!this.config.apiKey;
|
|
@@ -96,9 +99,13 @@ class An5SheetsAdapter {
|
|
|
96
99
|
}
|
|
97
100
|
if (!this.sheets) {
|
|
98
101
|
const { google } = await import('googleapis');
|
|
102
|
+
const { clientEmail, privateKey } = this.config;
|
|
103
|
+
if (!clientEmail || !privateKey) {
|
|
104
|
+
throw new Error('Google Sheets JWT auth requires clientEmail and privateKey (or use accessToken/apiKey fetch mode).');
|
|
105
|
+
}
|
|
99
106
|
const auth = new google.auth.JWT({
|
|
100
|
-
email:
|
|
101
|
-
key:
|
|
107
|
+
email: clientEmail,
|
|
108
|
+
key: privateKey,
|
|
102
109
|
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
|
|
103
110
|
});
|
|
104
111
|
this.sheets = google.sheets({ version: 'v4', auth });
|
|
@@ -20,13 +20,13 @@ export declare function resolveConfig(config: An5SheetsAdapterConfig): {
|
|
|
20
20
|
accessToken: string | undefined;
|
|
21
21
|
apiKey: string | undefined;
|
|
22
22
|
sheetMapping: Record<string, string> | undefined;
|
|
23
|
-
clientEmail?:
|
|
24
|
-
privateKey?:
|
|
23
|
+
clientEmail?: never;
|
|
24
|
+
privateKey?: never;
|
|
25
25
|
} | {
|
|
26
|
+
accessToken?: never;
|
|
27
|
+
apiKey?: never;
|
|
26
28
|
spreadsheetId: string;
|
|
27
29
|
clientEmail: string;
|
|
28
30
|
privateKey: string;
|
|
29
31
|
sheetMapping: Record<string, string> | undefined;
|
|
30
|
-
accessToken?: undefined;
|
|
31
|
-
apiKey?: undefined;
|
|
32
32
|
};
|
|
@@ -126,6 +126,8 @@ function sortRows(rows, orderBy) {
|
|
|
126
126
|
return [...rows].sort((a, b) => {
|
|
127
127
|
for (const clause of clauses) {
|
|
128
128
|
const [field, dir] = clause.split(/\s+/);
|
|
129
|
+
if (field === undefined)
|
|
130
|
+
continue;
|
|
129
131
|
const direction = dir?.toUpperCase() === 'DESC' ? -1 : 1;
|
|
130
132
|
const aVal = a[field];
|
|
131
133
|
const bVal = b[field];
|
|
@@ -13,10 +13,13 @@ exports.parseSheetsConnectionString = parseSheetsConnectionString;
|
|
|
13
13
|
function parseSheetsConnectionString(url) {
|
|
14
14
|
const clean = url.replace(/^googlesheets:\/\//, '');
|
|
15
15
|
const parts = clean.split(';');
|
|
16
|
-
const spreadsheetId = parts[0].trim();
|
|
16
|
+
const spreadsheetId = (parts[0] ?? '').trim();
|
|
17
17
|
const config = {};
|
|
18
18
|
for (let i = 1; i < parts.length; i++) {
|
|
19
|
-
const
|
|
19
|
+
const rawPart = parts[i];
|
|
20
|
+
if (rawPart === undefined)
|
|
21
|
+
continue;
|
|
22
|
+
const part = rawPart.trim();
|
|
20
23
|
if (!part)
|
|
21
24
|
continue;
|
|
22
25
|
const eqIdx = part.indexOf('=');
|