@semilayer/bridge-postgres 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +303 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SemiLayer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @semilayer/bridge-postgres
|
|
2
|
+
|
|
3
|
+
First-party PostgreSQL adapter for [SemiLayer](https://semilayer.com) — the intelligence layer for any database.
|
|
4
|
+
|
|
5
|
+
This package implements the SemiLayer `Bridge` interface for PostgreSQL, enabling ingest, search, similarity, and direct queries against any Postgres database (including hosted flavors like Neon, Supabase, CockroachDB, and others that speak the Postgres wire protocol).
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @semilayer/bridge-postgres
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @semilayer/bridge-postgres
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { PostgresBridge } from '@semilayer/bridge-postgres'
|
|
19
|
+
|
|
20
|
+
const bridge = new PostgresBridge({
|
|
21
|
+
url: 'postgresql://user:pass@host:5432/db',
|
|
22
|
+
pool: { min: 0, max: 5 }, // optional
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
await bridge.connect()
|
|
26
|
+
|
|
27
|
+
// Paginated read with keyset cursor
|
|
28
|
+
const result = await bridge.read('articles', {
|
|
29
|
+
limit: 100,
|
|
30
|
+
cursor: undefined, // first page
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
console.log(result.rows) // BridgeRow[]
|
|
34
|
+
console.log(result.nextCursor) // string | undefined
|
|
35
|
+
console.log(result.total) // total row count
|
|
36
|
+
|
|
37
|
+
// Direct query with filters
|
|
38
|
+
const { rows } = await bridge.query('articles', {
|
|
39
|
+
where: { status: 'published', views: { $gt: 100 } },
|
|
40
|
+
orderBy: [{ field: 'created_at', dir: 'desc' }],
|
|
41
|
+
limit: 20,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
await bridge.disconnect()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Schema Introspection
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { introspect, listTables } from '@semilayer/bridge-postgres'
|
|
51
|
+
import pg from 'pg'
|
|
52
|
+
|
|
53
|
+
const pool = new pg.Pool({ connectionString: '...' })
|
|
54
|
+
|
|
55
|
+
const tables = await listTables(pool)
|
|
56
|
+
const info = await introspect(pool, 'articles')
|
|
57
|
+
// info.columns: ColumnInfo[] with type, nullable, primaryKey
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
| Option | Type | Default | Description |
|
|
63
|
+
|--------|------|---------|-------------|
|
|
64
|
+
| `url` | `string` | **required** | Postgres connection string |
|
|
65
|
+
| `pool.min` | `number` | `0` | Minimum pool connections |
|
|
66
|
+
| `pool.max` | `number` | `3` | Maximum pool connections |
|
|
67
|
+
|
|
68
|
+
Accepts `connectionString` as an alias for `url`.
|
|
69
|
+
|
|
70
|
+
## Query Operators
|
|
71
|
+
|
|
72
|
+
The `query()` method supports MongoDB-style operators in the `where` clause:
|
|
73
|
+
|
|
74
|
+
| Operator | SQL | Example |
|
|
75
|
+
|----------|-----|---------|
|
|
76
|
+
| `$eq` | `=` | `{ status: { $eq: 'active' } }` |
|
|
77
|
+
| `$gt` | `>` | `{ age: { $gt: 18 } }` |
|
|
78
|
+
| `$gte` | `>=` | `{ score: { $gte: 50 } }` |
|
|
79
|
+
| `$lt` | `<` | `{ age: { $lt: 65 } }` |
|
|
80
|
+
| `$lte` | `<=` | `{ score: { $lte: 100 } }` |
|
|
81
|
+
| `$in` | `= ANY(...)` | `{ status: { $in: ['a', 'b'] } }` |
|
|
82
|
+
|
|
83
|
+
Bare values default to `$eq`: `{ status: 'active' }` is equivalent to `{ status: { $eq: 'active' } }`.
|
|
84
|
+
|
|
85
|
+
## Incremental Ingest
|
|
86
|
+
|
|
87
|
+
`read()` supports `changedSince` for incremental reads, tracking a configurable column (default `updated_at`):
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
await bridge.read('articles', {
|
|
91
|
+
changedSince: new Date('2026-01-01'),
|
|
92
|
+
changeTrackingColumn: 'modified_at', // optional
|
|
93
|
+
limit: 1000,
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If the column does not exist on the table, the `changedSince` filter is silently dropped and a full read is performed.
|
|
98
|
+
|
|
99
|
+
## Requirements
|
|
100
|
+
|
|
101
|
+
- Node.js 20+
|
|
102
|
+
- PostgreSQL 13+ (any version supported by the `pg` driver)
|
|
103
|
+
- `@semilayer/core` (peer-compatible version)
|
|
104
|
+
|
|
105
|
+
## Compliance Testing
|
|
106
|
+
|
|
107
|
+
This package passes the SemiLayer bridge compliance suite:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { createBridgeTestSuite } from '@semilayer/bridge-sdk'
|
|
111
|
+
import { PostgresBridge } from '@semilayer/bridge-postgres'
|
|
112
|
+
|
|
113
|
+
createBridgeTestSuite(
|
|
114
|
+
() => new PostgresBridge({ url: process.env.DATABASE_URL! }),
|
|
115
|
+
)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for details on running tests.
|
|
119
|
+
|
|
120
|
+
## Links
|
|
121
|
+
|
|
122
|
+
- [SemiLayer documentation](https://semilayer.dev)
|
|
123
|
+
- [Bridge authoring guide](https://semilayer.dev/guides/bridges)
|
|
124
|
+
- [Bridge resolver & community bridges](https://github.com/semilayer/bridge-resolver)
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT © SemiLayer
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Bridge, ReadOptions, ReadResult, QueryOptions, QueryResult, BridgeRow, TargetSchema } from '@semilayer/core';
|
|
2
|
+
import pg from 'pg';
|
|
3
|
+
|
|
4
|
+
interface PostgresBridgeConfig {
|
|
5
|
+
url: string;
|
|
6
|
+
pool?: {
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
declare class PostgresBridge implements Bridge {
|
|
12
|
+
private pool;
|
|
13
|
+
private config;
|
|
14
|
+
private pkCache;
|
|
15
|
+
constructor(config: Record<string, unknown>);
|
|
16
|
+
connect(): Promise<void>;
|
|
17
|
+
read(target: string, options?: ReadOptions): Promise<ReadResult>;
|
|
18
|
+
count(target: string): Promise<number>;
|
|
19
|
+
disconnect(): Promise<void>;
|
|
20
|
+
query(target: string, options: QueryOptions): Promise<QueryResult<BridgeRow>>;
|
|
21
|
+
listTargets(): Promise<string[]>;
|
|
22
|
+
introspectTarget(target: string): Promise<TargetSchema>;
|
|
23
|
+
private assertPool;
|
|
24
|
+
private getPrimaryKey;
|
|
25
|
+
private hasColumn;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ColumnInfo {
|
|
29
|
+
name: string;
|
|
30
|
+
type: string;
|
|
31
|
+
nullable: boolean;
|
|
32
|
+
primaryKey: boolean;
|
|
33
|
+
}
|
|
34
|
+
interface TableInfo {
|
|
35
|
+
name: string;
|
|
36
|
+
schema: string;
|
|
37
|
+
columns: ColumnInfo[];
|
|
38
|
+
rowCount: number;
|
|
39
|
+
}
|
|
40
|
+
declare function introspect(pool: pg.Pool, table: string, schema?: string): Promise<TableInfo>;
|
|
41
|
+
declare function listTables(pool: pg.Pool, schema?: string): Promise<string[]>;
|
|
42
|
+
|
|
43
|
+
export { type ColumnInfo, PostgresBridge, type PostgresBridgeConfig, type TableInfo, introspect, listTables };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
// src/bridge.ts
|
|
2
|
+
import pg from "pg";
|
|
3
|
+
|
|
4
|
+
// src/introspect.ts
|
|
5
|
+
async function introspect(pool, table, schema = "public") {
|
|
6
|
+
const colResult = await pool.query(
|
|
7
|
+
`SELECT
|
|
8
|
+
c.column_name,
|
|
9
|
+
c.data_type,
|
|
10
|
+
c.is_nullable,
|
|
11
|
+
CASE WHEN kcu.column_name IS NOT NULL THEN true ELSE false END AS is_pk
|
|
12
|
+
FROM information_schema.columns c
|
|
13
|
+
LEFT JOIN information_schema.table_constraints tc
|
|
14
|
+
ON tc.table_name = c.table_name
|
|
15
|
+
AND tc.table_schema = c.table_schema
|
|
16
|
+
AND tc.constraint_type = 'PRIMARY KEY'
|
|
17
|
+
LEFT JOIN information_schema.key_column_usage kcu
|
|
18
|
+
ON kcu.constraint_name = tc.constraint_name
|
|
19
|
+
AND kcu.table_schema = tc.table_schema
|
|
20
|
+
AND kcu.column_name = c.column_name
|
|
21
|
+
WHERE c.table_name = $1
|
|
22
|
+
AND c.table_schema = $2
|
|
23
|
+
ORDER BY c.ordinal_position`,
|
|
24
|
+
[table, schema]
|
|
25
|
+
);
|
|
26
|
+
const columns = colResult.rows.map((row) => ({
|
|
27
|
+
name: row.column_name,
|
|
28
|
+
type: row.data_type,
|
|
29
|
+
nullable: row.is_nullable === "YES",
|
|
30
|
+
primaryKey: row.is_pk
|
|
31
|
+
}));
|
|
32
|
+
const countResult = await pool.query(
|
|
33
|
+
`SELECT count(*)::int AS total FROM "${schema}"."${table}"`
|
|
34
|
+
);
|
|
35
|
+
const rowCount = countResult.rows[0].total;
|
|
36
|
+
return { name: table, schema, columns, rowCount };
|
|
37
|
+
}
|
|
38
|
+
async function listTables(pool, schema = "public") {
|
|
39
|
+
const result = await pool.query(
|
|
40
|
+
`SELECT table_name
|
|
41
|
+
FROM information_schema.tables
|
|
42
|
+
WHERE table_schema = $1
|
|
43
|
+
AND table_type = 'BASE TABLE'
|
|
44
|
+
ORDER BY table_name`,
|
|
45
|
+
[schema]
|
|
46
|
+
);
|
|
47
|
+
return result.rows.map(
|
|
48
|
+
(r) => r.table_name
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/bridge.ts
|
|
53
|
+
var TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_.]*$/;
|
|
54
|
+
var PostgresBridge = class {
|
|
55
|
+
pool = null;
|
|
56
|
+
config;
|
|
57
|
+
pkCache = /* @__PURE__ */ new Map();
|
|
58
|
+
constructor(config) {
|
|
59
|
+
const url = config["url"] ?? config["connectionString"];
|
|
60
|
+
if (!url || typeof url !== "string") {
|
|
61
|
+
throw new Error('PostgresBridge requires a "url" config string');
|
|
62
|
+
}
|
|
63
|
+
this.config = {
|
|
64
|
+
url,
|
|
65
|
+
pool: config["pool"]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async connect() {
|
|
69
|
+
this.pool = new pg.Pool({
|
|
70
|
+
connectionString: this.config.url,
|
|
71
|
+
min: this.config.pool?.min ?? 0,
|
|
72
|
+
max: this.config.pool?.max ?? 3
|
|
73
|
+
});
|
|
74
|
+
const client = await this.pool.connect();
|
|
75
|
+
try {
|
|
76
|
+
await client.query("SELECT 1");
|
|
77
|
+
} finally {
|
|
78
|
+
client.release();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async read(target, options) {
|
|
82
|
+
const pool = this.assertPool();
|
|
83
|
+
const table = target;
|
|
84
|
+
assertTableName(table);
|
|
85
|
+
const pk = await this.getPrimaryKey(table);
|
|
86
|
+
const fields = options?.fields;
|
|
87
|
+
const selectClause = fields ? fields.map(quote).join(", ") : "*";
|
|
88
|
+
const limit = options?.limit ?? 1e3;
|
|
89
|
+
const conditions = [];
|
|
90
|
+
const params = [];
|
|
91
|
+
let paramIdx = 1;
|
|
92
|
+
if (options?.cursor) {
|
|
93
|
+
conditions.push(`${quote(pk)} > $${paramIdx}`);
|
|
94
|
+
params.push(options.cursor);
|
|
95
|
+
paramIdx++;
|
|
96
|
+
}
|
|
97
|
+
if (options?.changedSince) {
|
|
98
|
+
const col = options.changeTrackingColumn ?? "updated_at";
|
|
99
|
+
const hasCol = await this.hasColumn(table, col);
|
|
100
|
+
if (hasCol) {
|
|
101
|
+
conditions.push(`${quote(col)} > $${paramIdx}`);
|
|
102
|
+
params.push(options.changedSince);
|
|
103
|
+
paramIdx++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
107
|
+
params.push(limit + 1);
|
|
108
|
+
const sql = `SELECT ${selectClause} FROM ${quote(table)} ${whereClause} ORDER BY ${quote(pk)} ASC LIMIT $${paramIdx}`;
|
|
109
|
+
const result = await pool.query(sql, params);
|
|
110
|
+
const allRows = result.rows;
|
|
111
|
+
const hasMore = allRows.length > limit;
|
|
112
|
+
const rows = hasMore ? allRows.slice(0, limit) : allRows;
|
|
113
|
+
const nextCursor = hasMore ? String(rows[rows.length - 1][pk]) : void 0;
|
|
114
|
+
const countResult = await pool.query(
|
|
115
|
+
`SELECT count(*)::int AS total FROM ${quote(table)}`
|
|
116
|
+
);
|
|
117
|
+
const total = countResult.rows[0].total;
|
|
118
|
+
return { rows, nextCursor, total };
|
|
119
|
+
}
|
|
120
|
+
async count(target) {
|
|
121
|
+
const pool = this.assertPool();
|
|
122
|
+
const table = target;
|
|
123
|
+
assertTableName(table);
|
|
124
|
+
const result = await pool.query(
|
|
125
|
+
`SELECT count(*)::int AS total FROM ${quote(table)}`
|
|
126
|
+
);
|
|
127
|
+
return result.rows[0].total;
|
|
128
|
+
}
|
|
129
|
+
async disconnect() {
|
|
130
|
+
if (this.pool) {
|
|
131
|
+
await this.pool.end();
|
|
132
|
+
this.pool = null;
|
|
133
|
+
}
|
|
134
|
+
this.pkCache.clear();
|
|
135
|
+
}
|
|
136
|
+
async query(target, options) {
|
|
137
|
+
const pool = this.assertPool();
|
|
138
|
+
const table = target;
|
|
139
|
+
assertTableName(table);
|
|
140
|
+
const selectClause = options.select ? options.select.map(quote).join(", ") : "*";
|
|
141
|
+
const params = [];
|
|
142
|
+
let paramIdx = 1;
|
|
143
|
+
const conditions = [];
|
|
144
|
+
if (options.where) {
|
|
145
|
+
for (const [key, value] of Object.entries(options.where)) {
|
|
146
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
147
|
+
const ops = value;
|
|
148
|
+
for (const [op, opVal] of Object.entries(ops)) {
|
|
149
|
+
switch (op) {
|
|
150
|
+
case "$eq":
|
|
151
|
+
conditions.push(`${quote(key)} = $${paramIdx}`);
|
|
152
|
+
params.push(opVal);
|
|
153
|
+
paramIdx++;
|
|
154
|
+
break;
|
|
155
|
+
case "$gt":
|
|
156
|
+
conditions.push(`${quote(key)} > $${paramIdx}`);
|
|
157
|
+
params.push(opVal);
|
|
158
|
+
paramIdx++;
|
|
159
|
+
break;
|
|
160
|
+
case "$gte":
|
|
161
|
+
conditions.push(`${quote(key)} >= $${paramIdx}`);
|
|
162
|
+
params.push(opVal);
|
|
163
|
+
paramIdx++;
|
|
164
|
+
break;
|
|
165
|
+
case "$lt":
|
|
166
|
+
conditions.push(`${quote(key)} < $${paramIdx}`);
|
|
167
|
+
params.push(opVal);
|
|
168
|
+
paramIdx++;
|
|
169
|
+
break;
|
|
170
|
+
case "$lte":
|
|
171
|
+
conditions.push(`${quote(key)} <= $${paramIdx}`);
|
|
172
|
+
params.push(opVal);
|
|
173
|
+
paramIdx++;
|
|
174
|
+
break;
|
|
175
|
+
case "$in":
|
|
176
|
+
conditions.push(
|
|
177
|
+
`${quote(key)} = ANY($${paramIdx})`
|
|
178
|
+
);
|
|
179
|
+
params.push(opVal);
|
|
180
|
+
paramIdx++;
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
throw new Error(`Unknown operator "${op}" on field "${key}"`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
conditions.push(`${quote(key)} = $${paramIdx}`);
|
|
188
|
+
params.push(value);
|
|
189
|
+
paramIdx++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
194
|
+
let orderByClause = "";
|
|
195
|
+
if (options.orderBy) {
|
|
196
|
+
const clauses = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy];
|
|
197
|
+
const parts = clauses.map(
|
|
198
|
+
(c) => `${quote(c.field)} ${c.dir === "desc" ? "DESC" : "ASC"}`
|
|
199
|
+
);
|
|
200
|
+
orderByClause = `ORDER BY ${parts.join(", ")}`;
|
|
201
|
+
}
|
|
202
|
+
let limitClause = "";
|
|
203
|
+
if (options.limit != null) {
|
|
204
|
+
limitClause = `LIMIT $${paramIdx}`;
|
|
205
|
+
params.push(options.limit);
|
|
206
|
+
paramIdx++;
|
|
207
|
+
}
|
|
208
|
+
let offsetClause = "";
|
|
209
|
+
if (options.offset != null) {
|
|
210
|
+
offsetClause = `OFFSET $${paramIdx}`;
|
|
211
|
+
params.push(options.offset);
|
|
212
|
+
paramIdx++;
|
|
213
|
+
}
|
|
214
|
+
const sql = [
|
|
215
|
+
`SELECT ${selectClause} FROM ${quote(table)}`,
|
|
216
|
+
whereClause,
|
|
217
|
+
orderByClause,
|
|
218
|
+
limitClause,
|
|
219
|
+
offsetClause
|
|
220
|
+
].filter(Boolean).join(" ");
|
|
221
|
+
const countSql = `SELECT count(*)::int AS total FROM ${quote(table)} ${whereClause}`;
|
|
222
|
+
const countParams = options.where ? params.slice(0, conditions.length) : [];
|
|
223
|
+
const [dataResult, countResult] = await Promise.all([
|
|
224
|
+
pool.query(sql, params),
|
|
225
|
+
pool.query(countSql, countParams)
|
|
226
|
+
]);
|
|
227
|
+
return {
|
|
228
|
+
rows: dataResult.rows,
|
|
229
|
+
total: countResult.rows[0].total
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// -------------------------------------------------------------------
|
|
233
|
+
// Introspection
|
|
234
|
+
// -------------------------------------------------------------------
|
|
235
|
+
async listTargets() {
|
|
236
|
+
const pool = this.assertPool();
|
|
237
|
+
return listTables(pool);
|
|
238
|
+
}
|
|
239
|
+
async introspectTarget(target) {
|
|
240
|
+
const pool = this.assertPool();
|
|
241
|
+
const info = await introspect(pool, target);
|
|
242
|
+
return {
|
|
243
|
+
name: info.name,
|
|
244
|
+
columns: info.columns,
|
|
245
|
+
rowCount: info.rowCount
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// -------------------------------------------------------------------
|
|
249
|
+
// Internal helpers
|
|
250
|
+
// -------------------------------------------------------------------
|
|
251
|
+
assertPool() {
|
|
252
|
+
if (!this.pool) throw new Error("PostgresBridge is not connected");
|
|
253
|
+
return this.pool;
|
|
254
|
+
}
|
|
255
|
+
async getPrimaryKey(table) {
|
|
256
|
+
const cached = this.pkCache.get(table);
|
|
257
|
+
if (cached) return cached;
|
|
258
|
+
const pool = this.assertPool();
|
|
259
|
+
const result = await pool.query(
|
|
260
|
+
`SELECT kcu.column_name
|
|
261
|
+
FROM information_schema.table_constraints tc
|
|
262
|
+
JOIN information_schema.key_column_usage kcu
|
|
263
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
264
|
+
AND tc.table_schema = kcu.table_schema
|
|
265
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
266
|
+
AND tc.table_name = $1
|
|
267
|
+
LIMIT 1`,
|
|
268
|
+
[table]
|
|
269
|
+
);
|
|
270
|
+
const row = result.rows[0];
|
|
271
|
+
if (!row) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Could not detect primary key for table "${table}"`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
this.pkCache.set(table, row.column_name);
|
|
277
|
+
return row.column_name;
|
|
278
|
+
}
|
|
279
|
+
async hasColumn(table, column) {
|
|
280
|
+
const pool = this.assertPool();
|
|
281
|
+
const result = await pool.query(
|
|
282
|
+
`SELECT 1 FROM information_schema.columns
|
|
283
|
+
WHERE table_name = $1 AND column_name = $2
|
|
284
|
+
LIMIT 1`,
|
|
285
|
+
[table, column]
|
|
286
|
+
);
|
|
287
|
+
return result.rowCount != null && result.rowCount > 0;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
function quote(identifier) {
|
|
291
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
292
|
+
}
|
|
293
|
+
function assertTableName(table) {
|
|
294
|
+
if (!TABLE_NAME_RE.test(table)) {
|
|
295
|
+
throw new Error(`Invalid table name: "${table}"`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
export {
|
|
299
|
+
PostgresBridge,
|
|
300
|
+
introspect,
|
|
301
|
+
listTables
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridge.ts","../src/introspect.ts"],"sourcesContent":["import pg from 'pg'\nimport type {\n Bridge,\n BridgeRow,\n ReadOptions,\n ReadResult,\n QueryOptions,\n QueryResult,\n TargetSchema,\n} from '@semilayer/core'\nimport { introspect, listTables } from './introspect.js'\n\nconst TABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_.]*$/\n\nexport interface PostgresBridgeConfig {\n url: string\n pool?: { min?: number; max?: number }\n}\n\nexport class PostgresBridge implements Bridge {\n private pool: pg.Pool | null = null\n private config: PostgresBridgeConfig\n private pkCache = new Map<string, string>()\n\n constructor(config: Record<string, unknown>) {\n const url = (config['url'] ?? config['connectionString']) as string | undefined\n if (!url || typeof url !== 'string') {\n throw new Error('PostgresBridge requires a \"url\" config string')\n }\n this.config = {\n url,\n pool: config['pool'] as PostgresBridgeConfig['pool'],\n }\n }\n\n async connect(): Promise<void> {\n this.pool = new pg.Pool({\n connectionString: this.config.url,\n min: this.config.pool?.min ?? 0,\n max: this.config.pool?.max ?? 3,\n })\n const client = await this.pool.connect()\n try {\n await client.query('SELECT 1')\n } finally {\n client.release()\n }\n }\n\n async read(target: string, options?: ReadOptions): Promise<ReadResult> {\n const pool = this.assertPool()\n const table = target\n assertTableName(table)\n\n const pk = await this.getPrimaryKey(table)\n const fields = options?.fields\n const selectClause = fields ? fields.map(quote).join(', ') : '*'\n const limit = options?.limit ?? 1000\n\n const conditions: string[] = []\n const params: unknown[] = []\n let paramIdx = 1\n\n if (options?.cursor) {\n conditions.push(`${quote(pk)} > $${paramIdx}`)\n params.push(options.cursor)\n paramIdx++\n }\n\n if (options?.changedSince) {\n const col = options.changeTrackingColumn ?? 'updated_at'\n const hasCol = await this.hasColumn(table, col)\n if (hasCol) {\n conditions.push(`${quote(col)} > $${paramIdx}`)\n params.push(options.changedSince)\n paramIdx++\n }\n }\n\n const whereClause =\n conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n\n // Fetch limit+1 to detect whether there's a next page\n params.push(limit + 1)\n\n const sql = `SELECT ${selectClause} FROM ${quote(table)} ${whereClause} ORDER BY ${quote(pk)} ASC LIMIT $${paramIdx}`\n\n const result = await pool.query(sql, params)\n const allRows: BridgeRow[] = result.rows as BridgeRow[]\n\n const hasMore = allRows.length > limit\n const rows = hasMore ? allRows.slice(0, limit) : allRows\n const nextCursor = hasMore\n ? String(rows[rows.length - 1]![pk])\n : undefined\n\n const countResult = await pool.query(\n `SELECT count(*)::int AS total FROM ${quote(table)}`,\n )\n const total = (countResult.rows as Array<{ total: number }>)[0]!.total\n\n return { rows, nextCursor, total }\n }\n\n async count(target: string): Promise<number> {\n const pool = this.assertPool()\n const table = target\n assertTableName(table)\n\n const result = await pool.query(\n `SELECT count(*)::int AS total FROM ${quote(table)}`,\n )\n return (result.rows as Array<{ total: number }>)[0]!.total\n }\n\n async disconnect(): Promise<void> {\n if (this.pool) {\n await this.pool.end()\n this.pool = null\n }\n this.pkCache.clear()\n }\n\n async query(\n target: string,\n options: QueryOptions,\n ): Promise<QueryResult<BridgeRow>> {\n const pool = this.assertPool()\n const table = target\n assertTableName(table)\n\n const selectClause = options.select\n ? options.select.map(quote).join(', ')\n : '*'\n\n const params: unknown[] = []\n let paramIdx = 1\n\n // WHERE\n const conditions: string[] = []\n if (options.where) {\n for (const [key, value] of Object.entries(options.where)) {\n if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n const ops = value as Record<string, unknown>\n for (const [op, opVal] of Object.entries(ops)) {\n switch (op) {\n case '$eq':\n conditions.push(`${quote(key)} = $${paramIdx}`)\n params.push(opVal)\n paramIdx++\n break\n case '$gt':\n conditions.push(`${quote(key)} > $${paramIdx}`)\n params.push(opVal)\n paramIdx++\n break\n case '$gte':\n conditions.push(`${quote(key)} >= $${paramIdx}`)\n params.push(opVal)\n paramIdx++\n break\n case '$lt':\n conditions.push(`${quote(key)} < $${paramIdx}`)\n params.push(opVal)\n paramIdx++\n break\n case '$lte':\n conditions.push(`${quote(key)} <= $${paramIdx}`)\n params.push(opVal)\n paramIdx++\n break\n case '$in':\n conditions.push(\n `${quote(key)} = ANY($${paramIdx})`,\n )\n params.push(opVal)\n paramIdx++\n break\n default:\n throw new Error(`Unknown operator \"${op}\" on field \"${key}\"`)\n }\n }\n } else {\n conditions.push(`${quote(key)} = $${paramIdx}`)\n params.push(value)\n paramIdx++\n }\n }\n }\n\n const whereClause =\n conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''\n\n // ORDER BY\n let orderByClause = ''\n if (options.orderBy) {\n const clauses = Array.isArray(options.orderBy)\n ? options.orderBy\n : [options.orderBy]\n const parts = clauses.map(\n (c) => `${quote(c.field)} ${c.dir === 'desc' ? 'DESC' : 'ASC'}`,\n )\n orderByClause = `ORDER BY ${parts.join(', ')}`\n }\n\n // LIMIT / OFFSET\n let limitClause = ''\n if (options.limit != null) {\n limitClause = `LIMIT $${paramIdx}`\n params.push(options.limit)\n paramIdx++\n }\n\n let offsetClause = ''\n if (options.offset != null) {\n offsetClause = `OFFSET $${paramIdx}`\n params.push(options.offset)\n paramIdx++\n }\n\n const sql = [\n `SELECT ${selectClause} FROM ${quote(table)}`,\n whereClause,\n orderByClause,\n limitClause,\n offsetClause,\n ]\n .filter(Boolean)\n .join(' ')\n\n // Get total count (with same WHERE, without LIMIT/OFFSET)\n const countSql = `SELECT count(*)::int AS total FROM ${quote(table)} ${whereClause}`\n const countParams = options.where ? params.slice(0, conditions.length) : []\n\n const [dataResult, countResult] = await Promise.all([\n pool.query(sql, params),\n pool.query(countSql, countParams),\n ])\n\n return {\n rows: dataResult.rows as BridgeRow[],\n total: (countResult.rows as Array<{ total: number }>)[0]!.total,\n }\n }\n\n // -------------------------------------------------------------------\n // Introspection\n // -------------------------------------------------------------------\n\n async listTargets(): Promise<string[]> {\n const pool = this.assertPool()\n return listTables(pool)\n }\n\n async introspectTarget(target: string): Promise<TargetSchema> {\n const pool = this.assertPool()\n const info = await introspect(pool, target)\n return {\n name: info.name,\n columns: info.columns,\n rowCount: info.rowCount,\n }\n }\n\n // -------------------------------------------------------------------\n // Internal helpers\n // -------------------------------------------------------------------\n\n private assertPool(): pg.Pool {\n if (!this.pool) throw new Error('PostgresBridge is not connected')\n return this.pool\n }\n\n private async getPrimaryKey(table: string): Promise<string> {\n const cached = this.pkCache.get(table)\n if (cached) return cached\n\n const pool = this.assertPool()\n const result = await pool.query(\n `SELECT kcu.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.key_column_usage kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n WHERE tc.constraint_type = 'PRIMARY KEY'\n AND tc.table_name = $1\n LIMIT 1`,\n [table],\n )\n\n const row = (result.rows as Array<{ column_name: string }>)[0]\n if (!row) {\n throw new Error(\n `Could not detect primary key for table \"${table}\"`,\n )\n }\n this.pkCache.set(table, row.column_name)\n return row.column_name\n }\n\n private async hasColumn(table: string, column: string): Promise<boolean> {\n const pool = this.assertPool()\n const result = await pool.query(\n `SELECT 1 FROM information_schema.columns\n WHERE table_name = $1 AND column_name = $2\n LIMIT 1`,\n [table, column],\n )\n return result.rowCount != null && result.rowCount > 0\n }\n}\n\nfunction quote(identifier: string): string {\n return `\"${identifier.replace(/\"/g, '\"\"')}\"`\n}\n\nfunction assertTableName(table: string): void {\n if (!TABLE_NAME_RE.test(table)) {\n throw new Error(`Invalid table name: \"${table}\"`)\n }\n}\n","import type pg from 'pg'\n\nexport interface ColumnInfo {\n name: string\n type: string\n nullable: boolean\n primaryKey: boolean\n}\n\nexport interface TableInfo {\n name: string\n schema: string\n columns: ColumnInfo[]\n rowCount: number\n}\n\nexport async function introspect(\n pool: pg.Pool,\n table: string,\n schema = 'public',\n): Promise<TableInfo> {\n const colResult = await pool.query(\n `SELECT\n c.column_name,\n c.data_type,\n c.is_nullable,\n CASE WHEN kcu.column_name IS NOT NULL THEN true ELSE false END AS is_pk\n FROM information_schema.columns c\n LEFT JOIN information_schema.table_constraints tc\n ON tc.table_name = c.table_name\n AND tc.table_schema = c.table_schema\n AND tc.constraint_type = 'PRIMARY KEY'\n LEFT JOIN information_schema.key_column_usage kcu\n ON kcu.constraint_name = tc.constraint_name\n AND kcu.table_schema = tc.table_schema\n AND kcu.column_name = c.column_name\n WHERE c.table_name = $1\n AND c.table_schema = $2\n ORDER BY c.ordinal_position`,\n [table, schema],\n )\n\n const columns: ColumnInfo[] = (\n colResult.rows as Array<{\n column_name: string\n data_type: string\n is_nullable: string\n is_pk: boolean\n }>\n ).map((row) => ({\n name: row.column_name,\n type: row.data_type,\n nullable: row.is_nullable === 'YES',\n primaryKey: row.is_pk,\n }))\n\n const countResult = await pool.query(\n `SELECT count(*)::int AS total FROM \"${schema}\".\"${table}\"`,\n )\n const rowCount = (countResult.rows as Array<{ total: number }>)[0]!.total\n\n return { name: table, schema, columns, rowCount }\n}\n\nexport async function listTables(\n pool: pg.Pool,\n schema = 'public',\n): Promise<string[]> {\n const result = await pool.query(\n `SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = $1\n AND table_type = 'BASE TABLE'\n ORDER BY table_name`,\n [schema],\n )\n return (result.rows as Array<{ table_name: string }>).map(\n (r) => r.table_name,\n )\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@semilayer/bridge-postgres",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "SemiLayer Postgres Bridge — first-party adapter for connecting SemiLayer to PostgreSQL databases",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"semilayer",
|
|
7
|
+
"bridge",
|
|
8
|
+
"postgres",
|
|
9
|
+
"postgresql",
|
|
10
|
+
"adapter"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "SemiLayer",
|
|
14
|
+
"homepage": "https://github.com/semilayer/bridge-postgres#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/semilayer/bridge-postgres.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/semilayer/bridge-postgres/issues"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"dev": "tsup --watch",
|
|
39
|
+
"lint": "eslint src/",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"prepublishOnly": "npm run build"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@semilayer/core": "^0.1.0",
|
|
47
|
+
"pg": "^8.13.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@semilayer/bridge-sdk": "^0.1.1"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@semilayer/bridge-sdk": {
|
|
54
|
+
"optional": true
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@semilayer/bridge-sdk": "^0.1.1",
|
|
59
|
+
"@types/node": "^20.0.0",
|
|
60
|
+
"@types/pg": "^8.11.0",
|
|
61
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
62
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
63
|
+
"eslint": "^9.0.0",
|
|
64
|
+
"tsup": "^8.0.0",
|
|
65
|
+
"typescript": "^5.7.0",
|
|
66
|
+
"vitest": "^3.0.0"
|
|
67
|
+
},
|
|
68
|
+
"engines": {
|
|
69
|
+
"node": ">=20.0.0"
|
|
70
|
+
},
|
|
71
|
+
"publishConfig": {
|
|
72
|
+
"access": "public",
|
|
73
|
+
"provenance": true
|
|
74
|
+
}
|
|
75
|
+
}
|