@pilaniaanand/driver-interface 0.5.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/dist/index.d.ts +273 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/package.json +21 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pilaniaanand/driver-interface
|
|
3
|
+
*
|
|
4
|
+
* This is the single contract every database adapter implements. The registry
|
|
5
|
+
* (apps/server) only ever talks to this interface — it never imports a
|
|
6
|
+
* concrete driver directly. Adding support for a new database means writing
|
|
7
|
+
* one new package that implements `DatabaseDriver` and registering it; no
|
|
8
|
+
* other part of the system changes.
|
|
9
|
+
*
|
|
10
|
+
* Design rules baked into this contract (see architecture plan):
|
|
11
|
+
* - Nothing here returns a full result set. Reads are either paginated
|
|
12
|
+
* (`queryRows`) or streamed (`streamQuery`), so a caller can never
|
|
13
|
+
* accidentally materialize a trillion-row table in memory.
|
|
14
|
+
* - Row counts are explicitly split into a fast estimate and a slow exact
|
|
15
|
+
* count, so the UI can show something instantly and let the user opt in
|
|
16
|
+
* to the expensive version.
|
|
17
|
+
* - Everything long-running is cancellable via an AbortSignal.
|
|
18
|
+
*/
|
|
19
|
+
export type QueryLanguage = "sql" | "mongo" | "redis-command";
|
|
20
|
+
export type ColumnType = "string" | "number" | "boolean" | "date" | "datetime" | "json" | "binary" | "null" | "unknown";
|
|
21
|
+
export interface ColumnDefinition {
|
|
22
|
+
name: string;
|
|
23
|
+
type: ColumnType;
|
|
24
|
+
nativeType: string;
|
|
25
|
+
nullable: boolean;
|
|
26
|
+
isPrimaryKey: boolean;
|
|
27
|
+
isForeignKey: boolean;
|
|
28
|
+
references?: {
|
|
29
|
+
table: string;
|
|
30
|
+
column: string;
|
|
31
|
+
};
|
|
32
|
+
defaultValue?: string | null;
|
|
33
|
+
}
|
|
34
|
+
export interface TableDefinition {
|
|
35
|
+
schema?: string;
|
|
36
|
+
name: string;
|
|
37
|
+
kind: "table" | "view" | "collection" | "materialized_view";
|
|
38
|
+
columns: ColumnDefinition[];
|
|
39
|
+
estimatedRowCount?: number;
|
|
40
|
+
}
|
|
41
|
+
export interface SchemaSummary {
|
|
42
|
+
name: string;
|
|
43
|
+
tables: Array<Pick<TableDefinition, "schema" | "name" | "kind">>;
|
|
44
|
+
}
|
|
45
|
+
/** TLS options for connecting directly to a database that requires (or accepts) certificate-based auth. */
|
|
46
|
+
export interface SslConfig {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
rejectUnauthorized?: boolean;
|
|
49
|
+
ca?: string;
|
|
50
|
+
cert?: string;
|
|
51
|
+
key?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Connects to `host`/`port` through an SSH server first — the standard way
|
|
55
|
+
* to reach a database sitting in a private subnet behind a bastion/jump
|
|
56
|
+
* host (e.g. AWS RDS). `privateKey` accepts OpenSSH PEM or PuTTY PPK
|
|
57
|
+
* contents; ssh2 auto-detects the format.
|
|
58
|
+
*/
|
|
59
|
+
export interface SshTunnelConfig {
|
|
60
|
+
enabled: boolean;
|
|
61
|
+
host: string;
|
|
62
|
+
port?: number;
|
|
63
|
+
username: string;
|
|
64
|
+
privateKey?: string;
|
|
65
|
+
passphrase?: string;
|
|
66
|
+
password?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface ConnectionConfig {
|
|
69
|
+
id: string;
|
|
70
|
+
driver: string;
|
|
71
|
+
host?: string;
|
|
72
|
+
port?: number;
|
|
73
|
+
database?: string;
|
|
74
|
+
username?: string;
|
|
75
|
+
password?: string;
|
|
76
|
+
filePath?: string;
|
|
77
|
+
ssl?: boolean | SslConfig;
|
|
78
|
+
sshTunnel?: SshTunnelConfig;
|
|
79
|
+
/**
|
|
80
|
+
* App-level safety net: when true, every write/DDL operation against this
|
|
81
|
+
* connection is rejected before it reaches the driver — insertRow/
|
|
82
|
+
* updateCell/deleteRow outright, and execute() for any query whose intent
|
|
83
|
+
* isn't a plain read (see isDestructiveExec). This is enforced by the
|
|
84
|
+
* server (routes/connections.ts), not by individual drivers.
|
|
85
|
+
*
|
|
86
|
+
* This is a UX/compliance safety net, not a substitute for real access
|
|
87
|
+
* control — a connection string with write privileges can still write if
|
|
88
|
+
* something reaches the underlying client directly. For a guarantee that
|
|
89
|
+
* survives an application bug, connect with a database role that only
|
|
90
|
+
* has SELECT granted (or point this at a read replica) — that's the one
|
|
91
|
+
* layer this app cannot bypass no matter what.
|
|
92
|
+
*/
|
|
93
|
+
readOnly?: boolean;
|
|
94
|
+
extra?: Record<string, unknown>;
|
|
95
|
+
}
|
|
96
|
+
/** Normalizes the two `ssl` shapes drivers can receive into one, or undefined if TLS isn't requested. */
|
|
97
|
+
export declare function resolveSsl(ssl: ConnectionConfig["ssl"]): SslConfig | undefined;
|
|
98
|
+
export declare function assertSafeIdentifier(name: string, kind: string): void;
|
|
99
|
+
export interface CursorPage {
|
|
100
|
+
/** Opaque, driver-defined cursor. Callers must not parse this string. */
|
|
101
|
+
cursor: string | null;
|
|
102
|
+
}
|
|
103
|
+
export interface QueryRowsOptions {
|
|
104
|
+
table: string;
|
|
105
|
+
schema?: string;
|
|
106
|
+
columns?: string[];
|
|
107
|
+
filters?: QueryFilter[];
|
|
108
|
+
sort?: {
|
|
109
|
+
column: string;
|
|
110
|
+
direction: "asc" | "desc";
|
|
111
|
+
}[];
|
|
112
|
+
pageSize: number;
|
|
113
|
+
afterCursor?: string | null;
|
|
114
|
+
signal?: AbortSignal;
|
|
115
|
+
}
|
|
116
|
+
export interface QueryFilter {
|
|
117
|
+
column: string;
|
|
118
|
+
op: "=" | "!=" | ">" | ">=" | "<" | "<=" | "like" | "in" | "is_null" | "is_not_null";
|
|
119
|
+
value?: unknown;
|
|
120
|
+
}
|
|
121
|
+
/** Emitted by drivers that support native change notification (see DriverConnection.watchTable). */
|
|
122
|
+
export interface RowChangeEvent {
|
|
123
|
+
type: "insert" | "update" | "delete";
|
|
124
|
+
row?: Record<string, unknown>;
|
|
125
|
+
primaryKey?: Record<string, unknown>;
|
|
126
|
+
column?: string;
|
|
127
|
+
value?: unknown;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Shared building block for drivers with no native change feed: given two
|
|
131
|
+
* full-table snapshots and the table's primary key columns, produces the
|
|
132
|
+
* same RowChangeEvent shape a native watcher would emit. A changed row is
|
|
133
|
+
* reported as a single "__row__" update (see useTableRows.ts) rather than
|
|
134
|
+
* diffed field-by-field — the snapshot doesn't tell us which columns moved,
|
|
135
|
+
* only that the row did.
|
|
136
|
+
*/
|
|
137
|
+
export declare function diffTableSnapshots(prevRows: Record<string, unknown>[], currRows: Record<string, unknown>[], pkColumns: string[]): RowChangeEvent[];
|
|
138
|
+
export interface QueryRowsResult {
|
|
139
|
+
rows: Record<string, unknown>[];
|
|
140
|
+
nextCursor: string | null;
|
|
141
|
+
columns: ColumnDefinition[];
|
|
142
|
+
}
|
|
143
|
+
export interface RowCountEstimate {
|
|
144
|
+
value: number;
|
|
145
|
+
exact: false;
|
|
146
|
+
source: "statistics" | "unsupported";
|
|
147
|
+
}
|
|
148
|
+
export interface RowCountExact {
|
|
149
|
+
value: number;
|
|
150
|
+
exact: true;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* A read query in whatever shape the target driver's queryLanguage expects,
|
|
154
|
+
* used by streamQuery(). Discriminated on `language` so each driver's
|
|
155
|
+
* implementation only has to handle the one variant matching its own
|
|
156
|
+
* `capabilities.queryLanguage` — TypeScript rejects the others at compile
|
|
157
|
+
* time, and callers (the query editor UI, the stream route) never need to
|
|
158
|
+
* guess or JSON.parse a string to figure out what they're holding.
|
|
159
|
+
*/
|
|
160
|
+
export type RedisKeyType = "string" | "hash" | "list" | "set" | "zset" | "stream";
|
|
161
|
+
export type QuerySpec = {
|
|
162
|
+
language: "sql";
|
|
163
|
+
sql: string;
|
|
164
|
+
params?: unknown[];
|
|
165
|
+
} | {
|
|
166
|
+
language: "mongo";
|
|
167
|
+
collection: string;
|
|
168
|
+
/** Either a find() (filter/sort/limit) or an aggregate() (pipeline) — not both. */
|
|
169
|
+
filter?: Record<string, unknown>;
|
|
170
|
+
sort?: Record<string, 1 | -1>;
|
|
171
|
+
limit?: number;
|
|
172
|
+
pipeline?: Record<string, unknown>[];
|
|
173
|
+
} | {
|
|
174
|
+
language: "redis-command";
|
|
175
|
+
/** Browse keys of one type (SCAN under the hood) — see DriverConnection.queryRows for the "table" equivalent. */
|
|
176
|
+
type: RedisKeyType;
|
|
177
|
+
pattern?: string;
|
|
178
|
+
limit?: number;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* A write/DDL query for execute(). Separate from QuerySpec because a
|
|
182
|
+
* driver's write shape can genuinely differ from its read shape (e.g.
|
|
183
|
+
* MongoDB reads via filter/sort/pipeline but writes via an explicit
|
|
184
|
+
* op + filter/update/doc) — forcing them into one type would leave unused
|
|
185
|
+
* fields on one side or the other.
|
|
186
|
+
*/
|
|
187
|
+
export type ExecSpec = {
|
|
188
|
+
language: "sql";
|
|
189
|
+
sql: string;
|
|
190
|
+
params?: unknown[];
|
|
191
|
+
} | {
|
|
192
|
+
language: "mongo";
|
|
193
|
+
op: "insertOne" | "updateOne" | "deleteOne" | "deleteMany";
|
|
194
|
+
collection: string;
|
|
195
|
+
filter?: Record<string, unknown>;
|
|
196
|
+
update?: Record<string, unknown>;
|
|
197
|
+
doc?: Record<string, unknown>;
|
|
198
|
+
} | {
|
|
199
|
+
language: "redis-command";
|
|
200
|
+
/** Raw write command + args, e.g. ["SET", "foo", "bar"] or ["DEL", "foo"]. */
|
|
201
|
+
command: string[];
|
|
202
|
+
};
|
|
203
|
+
export interface StreamQueryOptions {
|
|
204
|
+
query: QuerySpec;
|
|
205
|
+
chunkSize?: number;
|
|
206
|
+
signal?: AbortSignal;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* True if this write/DDL/execute request should be blocked when the
|
|
210
|
+
* connection is read-only. Deliberately fails closed: for SQL, anything not
|
|
211
|
+
* lexically recognizable as a plain read is treated as destructive; for
|
|
212
|
+
* Mongo, execute() only ever carries write ops (reads go through
|
|
213
|
+
* queryRows), so it's always destructive; for Redis, only a fixed allowlist
|
|
214
|
+
* of read commands is permitted.
|
|
215
|
+
*/
|
|
216
|
+
export declare function isDestructiveExec(query: ExecSpec): boolean;
|
|
217
|
+
export interface QueryExecResult {
|
|
218
|
+
columns: ColumnDefinition[];
|
|
219
|
+
affectedRows?: number;
|
|
220
|
+
durationMs: number;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The contract. All methods are async / async-iterable so a driver can wrap
|
|
224
|
+
* a network call, a local file read, or an in-process embedded engine
|
|
225
|
+
* identically from the registry's point of view.
|
|
226
|
+
*/
|
|
227
|
+
export interface DatabaseDriver {
|
|
228
|
+
readonly key: string;
|
|
229
|
+
readonly displayName: string;
|
|
230
|
+
readonly capabilities: {
|
|
231
|
+
transactions: boolean;
|
|
232
|
+
schemas: boolean;
|
|
233
|
+
streaming: boolean;
|
|
234
|
+
cancellation: boolean;
|
|
235
|
+
queryLanguage: QueryLanguage;
|
|
236
|
+
};
|
|
237
|
+
testConnection(config: ConnectionConfig): Promise<{
|
|
238
|
+
ok: boolean;
|
|
239
|
+
message?: string;
|
|
240
|
+
}>;
|
|
241
|
+
connect(config: ConnectionConfig): Promise<DriverConnection>;
|
|
242
|
+
}
|
|
243
|
+
export interface DriverConnection {
|
|
244
|
+
readonly id: string;
|
|
245
|
+
listSchemas(): Promise<SchemaSummary[]>;
|
|
246
|
+
listTables(schema?: string): Promise<TableDefinition[]>;
|
|
247
|
+
describeTable(table: string, schema?: string): Promise<TableDefinition>;
|
|
248
|
+
/** Keyset-paginated row browsing for the data grid. Never uses OFFSET. */
|
|
249
|
+
queryRows(options: QueryRowsOptions): Promise<QueryRowsResult>;
|
|
250
|
+
/** Inserts a new record. Returns the inserted row as the driver sees it (with any DB-generated defaults filled in). */
|
|
251
|
+
insertRow(table: string, schema: string | undefined, values: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
252
|
+
/** Deletes the record(s) matching every column in primaryKey. */
|
|
253
|
+
deleteRow(table: string, schema: string | undefined, primaryKey: Record<string, unknown>): Promise<void>;
|
|
254
|
+
/**
|
|
255
|
+
* Optional: subscribe to native change notifications for a table (e.g.
|
|
256
|
+
* MongoDB Change Streams). Only implemented by drivers whose database
|
|
257
|
+
* supports this without extra setup (triggers, replication config,
|
|
258
|
+
* etc). Returns an unsubscribe function. Callers must call it exactly
|
|
259
|
+
* once when no longer interested — drivers that implement this should
|
|
260
|
+
* treat it as a reference-counted resource internally if needed.
|
|
261
|
+
*/
|
|
262
|
+
watchTable?(table: string, schema: string | undefined, onChange: (event: RowChangeEvent) => void): () => void;
|
|
263
|
+
/** Fast, approximate — reads DB statistics, not a full scan. */
|
|
264
|
+
estimateRowCount(table: string, schema?: string): Promise<RowCountEstimate>;
|
|
265
|
+
/** Slow, exact — an opt-in COUNT(*) style scan. Must respect signal. */
|
|
266
|
+
countRowsExact(table: string, schema?: string, signal?: AbortSignal): Promise<RowCountExact>;
|
|
267
|
+
/** Arbitrary SQL/query execution for the query editor, streamed in chunks. */
|
|
268
|
+
streamQuery(options: StreamQueryOptions): AsyncIterableIterator<QueryRowsResult>;
|
|
269
|
+
/** Non-SELECT execution (INSERT/UPDATE/DELETE/DDL, a mongo write command, or a redis write command), also cancellable. */
|
|
270
|
+
execute(query: ExecSpec, signal?: AbortSignal): Promise<QueryExecResult>;
|
|
271
|
+
updateCell(table: string, schema: string | undefined, primaryKey: Record<string, unknown>, column: string, value: unknown): Promise<void>;
|
|
272
|
+
close(): Promise<void>;
|
|
273
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pilaniaanand/driver-interface
|
|
3
|
+
*
|
|
4
|
+
* This is the single contract every database adapter implements. The registry
|
|
5
|
+
* (apps/server) only ever talks to this interface — it never imports a
|
|
6
|
+
* concrete driver directly. Adding support for a new database means writing
|
|
7
|
+
* one new package that implements `DatabaseDriver` and registering it; no
|
|
8
|
+
* other part of the system changes.
|
|
9
|
+
*
|
|
10
|
+
* Design rules baked into this contract (see architecture plan):
|
|
11
|
+
* - Nothing here returns a full result set. Reads are either paginated
|
|
12
|
+
* (`queryRows`) or streamed (`streamQuery`), so a caller can never
|
|
13
|
+
* accidentally materialize a trillion-row table in memory.
|
|
14
|
+
* - Row counts are explicitly split into a fast estimate and a slow exact
|
|
15
|
+
* count, so the UI can show something instantly and let the user opt in
|
|
16
|
+
* to the expensive version.
|
|
17
|
+
* - Everything long-running is cancellable via an AbortSignal.
|
|
18
|
+
*/
|
|
19
|
+
/** Normalizes the two `ssl` shapes drivers can receive into one, or undefined if TLS isn't requested. */
|
|
20
|
+
export function resolveSsl(ssl) {
|
|
21
|
+
if (!ssl)
|
|
22
|
+
return undefined;
|
|
23
|
+
if (ssl === true)
|
|
24
|
+
return { enabled: true };
|
|
25
|
+
return ssl.enabled ? ssl : undefined;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Matches a bare SQL identifier: letters/digits/underscore, not starting
|
|
29
|
+
* with a digit. SQL has no parameterized-identifier mechanism the way it
|
|
30
|
+
* does for values (no driver supports `WHERE $1 = $2` binding a column
|
|
31
|
+
* name) — every SQL driver's structured operations (queryRows filters,
|
|
32
|
+
* insertRow/updateCell/deleteRow column names, the table/schema name
|
|
33
|
+
* itself) must run every client-supplied identifier through
|
|
34
|
+
* `assertSafeIdentifier` before interpolating it into a query string.
|
|
35
|
+
* Rejecting anything outside this charset closes identifier-based SQL
|
|
36
|
+
* injection outright — no quote, backtick, semicolon, or comment sequence
|
|
37
|
+
* can ever reach the query text.
|
|
38
|
+
*/
|
|
39
|
+
const SAFE_SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
40
|
+
export function assertSafeIdentifier(name, kind) {
|
|
41
|
+
if (typeof name !== "string" || !SAFE_SQL_IDENTIFIER.test(name)) {
|
|
42
|
+
throw new Error(`Invalid ${kind} name: ${JSON.stringify(name)}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Shared building block for drivers with no native change feed: given two
|
|
47
|
+
* full-table snapshots and the table's primary key columns, produces the
|
|
48
|
+
* same RowChangeEvent shape a native watcher would emit. A changed row is
|
|
49
|
+
* reported as a single "__row__" update (see useTableRows.ts) rather than
|
|
50
|
+
* diffed field-by-field — the snapshot doesn't tell us which columns moved,
|
|
51
|
+
* only that the row did.
|
|
52
|
+
*/
|
|
53
|
+
export function diffTableSnapshots(prevRows, currRows, pkColumns) {
|
|
54
|
+
const keyOf = (row) => JSON.stringify(pkColumns.map((c) => row[c]));
|
|
55
|
+
const pkOf = (row) => Object.fromEntries(pkColumns.map((c) => [c, row[c]]));
|
|
56
|
+
const prevByKey = new Map(prevRows.map((r) => [keyOf(r), r]));
|
|
57
|
+
const currByKey = new Map(currRows.map((r) => [keyOf(r), r]));
|
|
58
|
+
const events = [];
|
|
59
|
+
for (const [key, row] of currByKey) {
|
|
60
|
+
const prevRow = prevByKey.get(key);
|
|
61
|
+
if (!prevRow)
|
|
62
|
+
events.push({ type: "insert", row });
|
|
63
|
+
else if (JSON.stringify(prevRow) !== JSON.stringify(row)) {
|
|
64
|
+
events.push({ type: "update", primaryKey: pkOf(row), column: "__row__", value: row });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const [key, row] of prevByKey) {
|
|
68
|
+
if (!currByKey.has(key))
|
|
69
|
+
events.push({ type: "delete", primaryKey: pkOf(row) });
|
|
70
|
+
}
|
|
71
|
+
return events;
|
|
72
|
+
}
|
|
73
|
+
const SQL_SAFE_LEADING_KEYWORDS = new Set(["select", "with", "explain", "show", "describe", "desc", "pragma", "values"]);
|
|
74
|
+
// Whole-word scan for write/DDL verbs anywhere in the statement — catches a
|
|
75
|
+
// write smuggled inside a CTE (`WITH x AS (DELETE FROM t RETURNING *) SELECT * FROM x`),
|
|
76
|
+
// which a leading-keyword check alone would miss.
|
|
77
|
+
const SQL_WRITE_VERB = /\b(insert|update|delete|drop|alter|truncate|create|grant|revoke|merge|call|copy|vacuum|reindex|lock|replace|into\s+outfile)\b/i;
|
|
78
|
+
const REDIS_SAFE_READ_COMMANDS = new Set([
|
|
79
|
+
"get", "mget", "strlen", "getrange", "exists", "type", "ttl", "pttl", "scan", "keys", "dbsize",
|
|
80
|
+
"hget", "hmget", "hgetall", "hkeys", "hvals", "hlen", "hrandfield", "hexists", "hscan", "hstrlen",
|
|
81
|
+
"lrange", "llen", "lindex", "lpos",
|
|
82
|
+
"smembers", "scard", "sismember", "smismember", "srandmember", "sscan", "sinter", "sunion", "sdiff",
|
|
83
|
+
"zrange", "zrangebyscore", "zrevrange", "zrevrangebyscore", "zscore", "zmscore", "zcard", "zcount",
|
|
84
|
+
"zrank", "zrevrank", "zscan",
|
|
85
|
+
"xrange", "xrevrange", "xlen", "xread",
|
|
86
|
+
"ping", "echo", "info", "time", "config", "client", "object", "memory", "randomkey", "touch",
|
|
87
|
+
]);
|
|
88
|
+
/**
|
|
89
|
+
* True if this write/DDL/execute request should be blocked when the
|
|
90
|
+
* connection is read-only. Deliberately fails closed: for SQL, anything not
|
|
91
|
+
* lexically recognizable as a plain read is treated as destructive; for
|
|
92
|
+
* Mongo, execute() only ever carries write ops (reads go through
|
|
93
|
+
* queryRows), so it's always destructive; for Redis, only a fixed allowlist
|
|
94
|
+
* of read commands is permitted.
|
|
95
|
+
*/
|
|
96
|
+
export function isDestructiveExec(query) {
|
|
97
|
+
if (query.language === "mongo")
|
|
98
|
+
return true;
|
|
99
|
+
if (query.language === "redis-command") {
|
|
100
|
+
const cmd = query.command[0]?.toLowerCase();
|
|
101
|
+
return !cmd || !REDIS_SAFE_READ_COMMANDS.has(cmd);
|
|
102
|
+
}
|
|
103
|
+
// SQL
|
|
104
|
+
const statements = query.sql
|
|
105
|
+
.replace(/--[^\n]*/g, " ") // line comments
|
|
106
|
+
.replace(/\/\*[\s\S]*?\*\//g, " ") // block comments
|
|
107
|
+
.split(";")
|
|
108
|
+
.map((s) => s.trim())
|
|
109
|
+
.filter(Boolean);
|
|
110
|
+
if (statements.length !== 1)
|
|
111
|
+
return true; // no legitimate read needs multiple statements
|
|
112
|
+
const stmt = statements[0];
|
|
113
|
+
const leading = stmt.match(/^[A-Za-z]+/)?.[0]?.toLowerCase();
|
|
114
|
+
if (!leading || !SQL_SAFE_LEADING_KEYWORDS.has(leading))
|
|
115
|
+
return true;
|
|
116
|
+
return SQL_WRITE_VERB.test(stmt);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pilaniaanand/driver-interface",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/node": "^26.4.1",
|
|
9
|
+
"typescript": "^7.0.2"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"build": "tsc -p tsconfig.json"
|
|
20
|
+
}
|
|
21
|
+
}
|