@juit/pgproxy-client 1.2.1 → 1.3.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/README.md +23 -2
- package/dist/client.cjs +137 -39
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.ts +42 -6
- package/dist/client.mjs +138 -39
- package/dist/client.mjs.map +1 -1
- package/dist/provider.cjs +12 -0
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.d.ts +14 -10
- package/dist/provider.mjs +12 -0
- package/dist/provider.mjs.map +1 -1
- package/dist/result.cjs.map +1 -1
- package/dist/result.d.ts +2 -2
- package/dist/result.mjs.map +1 -1
- package/dist/sql.cjs +7 -2
- package/dist/sql.cjs.map +1 -1
- package/dist/sql.d.ts +2 -0
- package/dist/sql.mjs +5 -1
- package/dist/sql.mjs.map +1 -1
- package/dist/websocket.cjs +2 -1
- package/dist/websocket.cjs.map +1 -1
- package/dist/websocket.d.ts +5 -4
- package/dist/websocket.mjs +2 -1
- package/dist/websocket.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +165 -61
- package/src/provider.ts +31 -11
- package/src/result.ts +3 -3
- package/src/sql.ts +5 -0
- package/src/websocket.ts +9 -8
package/README.md
CHANGED
|
@@ -86,7 +86,23 @@ const result = await client.query('SELECT * FROM test WHERE value = $1', [ 'theV
|
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
More complex queries (e.g. transactions) can be performed using the
|
|
89
|
-
`connect(
|
|
89
|
+
_connection_ returned by the `connect()` method:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const client = new PGClient()
|
|
93
|
+
|
|
94
|
+
await using connection = await client.connect() // the "connection" is AsyncDisposable
|
|
95
|
+
|
|
96
|
+
await connection.begin() // ... begin transaction
|
|
97
|
+
await connection.query(...) // ... all transaction queries
|
|
98
|
+
await connection.commit() // ... commit transaction, or "rollback()"
|
|
99
|
+
|
|
100
|
+
// if not declaring with "async using" connections can be closed manually:
|
|
101
|
+
//
|
|
102
|
+
// await connection.close()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or using a _connection consumer_:
|
|
90
106
|
|
|
91
107
|
```ts
|
|
92
108
|
const client = new PGClient()
|
|
@@ -109,7 +125,8 @@ A second form of the `query(...)` function accepts an object with two keys:
|
|
|
109
125
|
* `query`: the SQL query to execute optionally containing placeholders
|
|
110
126
|
* `params`: any parameter replacement for `$x` placeholders
|
|
111
127
|
|
|
112
|
-
|
|
128
|
+
When used with callbacks, the object passed to the `connect(...)` callback
|
|
129
|
+
provides the following methods:
|
|
113
130
|
|
|
114
131
|
* `query(...)`: as above
|
|
115
132
|
* `begin()`: issues the `BEGIN` SQL statement (starts a transaction)
|
|
@@ -118,6 +135,10 @@ The object passed to the `connect(...)` callback provides the following methods:
|
|
|
118
135
|
|
|
119
136
|
Uncommitted transactions will always be rolled back by the connection pool code.
|
|
120
137
|
|
|
138
|
+
Otherwise, when using `connect()` without callbacks, the returned _connection_
|
|
139
|
+
will be `AsyncDisposable`, and (if not using `async using`) can be manually
|
|
140
|
+
disposed of calling the `close()` function.
|
|
141
|
+
|
|
121
142
|
### Result
|
|
122
143
|
|
|
123
144
|
The result returned by the `query(...)` method is a simple object containing:
|
package/dist/client.cjs
CHANGED
|
@@ -3,6 +3,10 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
7
|
+
var __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
6
10
|
var __export = (target, all) => {
|
|
7
11
|
for (var name in all)
|
|
8
12
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -16,6 +20,47 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
20
|
return to;
|
|
17
21
|
};
|
|
18
22
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
23
|
+
var __using = (stack, value, async) => {
|
|
24
|
+
if (value != null) {
|
|
25
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
26
|
+
var dispose, inner;
|
|
27
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
28
|
+
if (dispose === void 0) {
|
|
29
|
+
dispose = value[__knownSymbol("dispose")];
|
|
30
|
+
if (async) inner = dispose;
|
|
31
|
+
}
|
|
32
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
33
|
+
if (inner) dispose = function() {
|
|
34
|
+
try {
|
|
35
|
+
inner.call(this);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
return Promise.reject(e);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
stack.push([async, dispose, value]);
|
|
41
|
+
} else if (async) {
|
|
42
|
+
stack.push([async]);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
};
|
|
46
|
+
var __callDispose = (stack, error, hasError) => {
|
|
47
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
48
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
49
|
+
};
|
|
50
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
51
|
+
var next = (it) => {
|
|
52
|
+
while (it = stack.pop()) {
|
|
53
|
+
try {
|
|
54
|
+
var result = it[1] && it[1].call(it[2]);
|
|
55
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
56
|
+
} catch (e) {
|
|
57
|
+
fail(e);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (hasError) throw error;
|
|
61
|
+
};
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
19
64
|
|
|
20
65
|
// client.ts
|
|
21
66
|
var client_exports = {};
|
|
@@ -36,62 +81,115 @@ function serializeParams(params) {
|
|
|
36
81
|
return result;
|
|
37
82
|
}
|
|
38
83
|
var PGClient = class PGClientImpl {
|
|
39
|
-
|
|
84
|
+
_registry = new import_pgproxy_types.Registry();
|
|
40
85
|
_provider;
|
|
41
|
-
constructor(
|
|
42
|
-
|
|
43
|
-
(0, import_assert.assert)(
|
|
44
|
-
if (typeof
|
|
45
|
-
(0, import_assert.assert)(
|
|
46
|
-
if (
|
|
47
|
-
if (!(
|
|
86
|
+
constructor(arg) {
|
|
87
|
+
arg = arg || globalThis?.process?.env?.PGURL;
|
|
88
|
+
(0, import_assert.assert)(arg, "No URL to connect to (PGURL environment variable missing?)");
|
|
89
|
+
if (typeof arg === "string") arg = new URL(arg, "psql:///");
|
|
90
|
+
(0, import_assert.assert)(arg, "Missing URL or provider for client");
|
|
91
|
+
if ("href" in arg) {
|
|
92
|
+
if (!(arg.username || arg.password)) {
|
|
48
93
|
const username = globalThis?.process?.env?.PGUSER || "";
|
|
49
94
|
const password = globalThis?.process?.env?.PGPASSWORD || "";
|
|
50
|
-
|
|
51
|
-
|
|
95
|
+
arg.username = encodeURIComponent(username);
|
|
96
|
+
arg.password = encodeURIComponent(password);
|
|
97
|
+
}
|
|
98
|
+
this._provider = (0, import_provider.createProvider)(arg);
|
|
99
|
+
} else if ("query" in arg && "acquire" in arg && "release" in arg) {
|
|
100
|
+
this._provider = arg;
|
|
101
|
+
} else {
|
|
102
|
+
const {
|
|
103
|
+
protocol = "psql",
|
|
104
|
+
database = "",
|
|
105
|
+
username = globalThis?.process?.env?.PGUSER,
|
|
106
|
+
password = globalThis?.process?.env?.PGPASSWORD,
|
|
107
|
+
host = "localhost",
|
|
108
|
+
port,
|
|
109
|
+
parameters = {}
|
|
110
|
+
} = arg;
|
|
111
|
+
const url = new URL(`${protocol}://`);
|
|
112
|
+
if (host) url.hostname = host;
|
|
113
|
+
if (port) url.port = String(port);
|
|
114
|
+
if (username) url.username = encodeURIComponent(username);
|
|
115
|
+
if (password) url.password = encodeURIComponent(password);
|
|
116
|
+
url.pathname = `/${database}`;
|
|
117
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
118
|
+
url.searchParams.set(key, String(value));
|
|
52
119
|
}
|
|
120
|
+
this._provider = (0, import_provider.createProvider)(url);
|
|
53
121
|
}
|
|
54
|
-
|
|
122
|
+
}
|
|
123
|
+
get registry() {
|
|
124
|
+
return this._registry;
|
|
125
|
+
}
|
|
126
|
+
get url() {
|
|
127
|
+
return this._provider.url;
|
|
55
128
|
}
|
|
56
129
|
async query(textOrQuery, maybeParams = []) {
|
|
57
130
|
const [text, params = []] = typeof textOrQuery === "string" ? [textOrQuery, maybeParams] : [textOrQuery.query, textOrQuery.params];
|
|
58
131
|
const result = await this._provider.query(text, serializeParams(params));
|
|
59
|
-
return new import_result.PGResult(result, this.
|
|
132
|
+
return new import_result.PGResult(result, this._registry);
|
|
60
133
|
}
|
|
61
134
|
async connect(consumer) {
|
|
62
135
|
const connection = await this._provider.acquire();
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
},
|
|
77
|
-
async commit() {
|
|
78
|
-
await connection.query("COMMIT");
|
|
79
|
-
transaction = false;
|
|
80
|
-
},
|
|
81
|
-
async rollback() {
|
|
82
|
-
await connection.query("ROLLBACK");
|
|
83
|
-
transaction = false;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
return await consumer(consumable);
|
|
87
|
-
} finally {
|
|
88
|
-
if (transaction) await connection.query("ROLLBACK");
|
|
89
|
-
await this._provider.release(connection);
|
|
136
|
+
if (!consumer) {
|
|
137
|
+
return new PGConnectionImpl(connection, this._provider, this._registry);
|
|
138
|
+
} else {
|
|
139
|
+
var _stack = [];
|
|
140
|
+
try {
|
|
141
|
+
const conn = __using(_stack, new PGConnectionImpl(connection, this._provider, this._registry), true);
|
|
142
|
+
return await consumer(conn);
|
|
143
|
+
} catch (_) {
|
|
144
|
+
var _error = _, _hasError = true;
|
|
145
|
+
} finally {
|
|
146
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
147
|
+
_promise && await _promise;
|
|
148
|
+
}
|
|
90
149
|
}
|
|
91
150
|
}
|
|
92
151
|
async destroy() {
|
|
93
152
|
return await this._provider.destroy();
|
|
94
153
|
}
|
|
154
|
+
async [Symbol.asyncDispose]() {
|
|
155
|
+
await this.destroy();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var PGConnectionImpl = class {
|
|
159
|
+
_transaction = false;
|
|
160
|
+
_connection;
|
|
161
|
+
_provider;
|
|
162
|
+
_registry;
|
|
163
|
+
constructor(connection, provider, registry) {
|
|
164
|
+
this._connection = connection;
|
|
165
|
+
this._provider = provider;
|
|
166
|
+
this._registry = registry;
|
|
167
|
+
}
|
|
168
|
+
async query(textOrQuery, maybeParams = []) {
|
|
169
|
+
const [text, params = []] = typeof textOrQuery === "string" ? [textOrQuery, maybeParams] : [textOrQuery.query, textOrQuery.params];
|
|
170
|
+
const result = await this._connection.query(text, serializeParams(params));
|
|
171
|
+
return new import_result.PGResult(result, this._registry);
|
|
172
|
+
}
|
|
173
|
+
async begin() {
|
|
174
|
+
if (this._transaction) return false;
|
|
175
|
+
await this._connection.query("BEGIN");
|
|
176
|
+
return this._transaction = true;
|
|
177
|
+
}
|
|
178
|
+
async commit() {
|
|
179
|
+
await this._connection.query("COMMIT");
|
|
180
|
+
this._transaction = false;
|
|
181
|
+
}
|
|
182
|
+
async rollback() {
|
|
183
|
+
await this._connection.query("ROLLBACK");
|
|
184
|
+
this._transaction = false;
|
|
185
|
+
}
|
|
186
|
+
async close() {
|
|
187
|
+
if (this._transaction) await this._connection.query("ROLLBACK");
|
|
188
|
+
await this._provider.release(this._connection);
|
|
189
|
+
}
|
|
190
|
+
[Symbol.asyncDispose]() {
|
|
191
|
+
return this.close();
|
|
192
|
+
}
|
|
95
193
|
};
|
|
96
194
|
// Annotate the CommonJS export names for ESM import in node:
|
|
97
195
|
0 && (module.exports = {
|
package/dist/client.cjs.map
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client.ts"],
|
|
4
|
-
"mappings": "
|
|
4
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAAoC;AAEpC,oBAAuB;AACvB,sBAA+B;AAC/B,oBAAyB;AAIzB,SAAS,gBAAgB,QAA2C;AAClE,MAAI,OAAO,UAAU,EAAG,QAAO,CAAC;AAEhC,QAAM,SAA4B,IAAI,MAAM,OAAO,MAAM;AACzD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAM;AACvC,WAAO,CAAC,IACN,OAAO,CAAC,MAAM,SAAY,OAC1B,OAAO,CAAC,MAAM,OAAO,WACrB,gCAAU,OAAO,CAAC,CAAC;AAAA,EACvB;AAEA,SAAO;AACT;AA4JO,IAAM,WAAgC,MAAM,aAAiC;AAAA,EACjE,YAAsB,IAAI,8BAAS;AAAA,EACnC;AAAA,EAKjB,YAAY,KAAmD;AAE7D,UAAM,OAAS,YAAoB,SAAS,KAAK;AACjD,8BAAO,KAAK,4DAA4D;AAGxE,QAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,IAAI,KAAK,UAAU;AAC1D,8BAAO,KAAK,oCAAoC;AAIhD,QAAI,UAAU,KAAK;AACjB,UAAI,EAAE,IAAI,YAAY,IAAI,WAAW;AACnC,cAAM,WAAa,YAAoB,SAAS,KAAK,UAAiC;AACtF,cAAM,WAAa,YAAoB,SAAS,KAAK,cAAqC;AAC1F,YAAI,WAAW,mBAAmB,QAAQ;AAC1C,YAAI,WAAW,mBAAmB,QAAQ;AAAA,MAC5C;AACA,WAAK,gBAAY,gCAAe,GAAG;AAAA,IAGrC,WAAY,WAAW,OAAS,aAAa,OAAS,aAAa,KAAM;AACvE,WAAK,YAAY;AAAA,IAGnB,OAAO;AACL,YAAM;AAAA,QACJ,WAAW;AAAA,QACX,WAAW;AAAA,QACX,WAAa,YAAoB,SAAS,KAAK;AAAA,QAC/C,WAAa,YAAoB,SAAS,KAAK;AAAA,QAC/C,OAAO;AAAA,QACP;AAAA,QACA,aAAa,CAAC;AAAA,MAChB,IAAI;AAEJ,YAAM,MAAM,IAAI,IAAI,GAAG,QAAQ,KAAK;AACpC,UAAI,KAAM,KAAI,WAAW;AACzB,UAAI,KAAM,KAAI,OAAO,OAAO,IAAI;AAChC,UAAI,SAAU,KAAI,WAAW,mBAAmB,QAAQ;AACxD,UAAI,SAAU,KAAI,WAAW,mBAAmB,QAAQ;AACxD,UAAI,WAAW,IAAI,QAAQ;AAE3B,iBAAW,CAAE,KAAK,KAAM,KAAK,OAAO,QAAQ,UAAU,GAAG;AACvD,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAEA,WAAK,gBAAY,gCAAe,GAAG;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,IAAI,WAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,MAAqB;AACvB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAYA,MAAM,MAGJ,aAA+B,cAA8B,CAAC,GAAkC;AAChG,UAAM,CAAE,MAAM,SAAS,CAAC,CAAE,IAAI,OAAO,gBAAgB,WACnD,CAAE,aAAa,WAAY,IAAI,CAAE,YAAY,OAAO,YAAY,MAAO;AAEzE,UAAM,SAAS,MAAM,KAAK,UAAU,MAAM,MAAM,gBAAgB,MAAM,CAAC;AACvE,WAAO,IAAI,uBAAqB,QAAQ,KAAK,SAAS;AAAA,EACxD;AAAA,EAEA,MAAM,QAAW,UAAqD;AACpE,UAAM,aAAa,MAAM,KAAK,UAAU,QAAQ;AAEhD,QAAI,CAAE,UAAU;AACd,aAAO,IAAI,iBAAiB,YAAY,KAAK,WAAW,KAAK,SAAS;AAAA,IACxE,OAAO;AACL;AAAA;AAAA,cAAY,OAAO,oBAAI,iBAAiB,YAAY,KAAK,WAAW,KAAK,SAAS,GAA/D;AACnB,eAAO,MAAM,SAAS,IAAI;AAAA,eAD1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAEF;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,MAAM,KAAK,UAAU,QAAQ;AAAA,EACtC;AAAA,EAEA,OAAO,OAAO,YAAY,IAAmB;AAC3C,UAAM,KAAK,QAAQ;AAAA,EACrB;AACF;AAIA,IAAM,mBAAN,MAA+C;AAAA,EACrC,eAAwB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YACI,YACA,UACA,UAAoB;AACtB,SAAK,cAAc;AACnB,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,MAGJ,aAA+B,cAA8B,CAAC,GAAkC;AAChG,UAAM,CAAE,MAAM,SAAS,CAAC,CAAE,IAAI,OAAO,gBAAgB,WACnD,CAAE,aAAa,WAAY,IAAI,CAAE,YAAY,OAAO,YAAY,MAAO;AAEzE,UAAM,SAAS,MAAM,KAAK,YAAY,MAAM,MAAM,gBAAgB,MAAM,CAAC;AACzE,WAAO,IAAI,uBAAS,QAAQ,KAAK,SAAS;AAAA,EAC5C;AAAA,EAEA,MAAM,QAA0B;AAC9B,QAAI,KAAK,aAAc,QAAO;AAC9B,UAAM,KAAK,YAAY,MAAM,OAAO;AACpC,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAwB;AAC5B,UAAM,KAAK,YAAY,MAAM,QAAQ;AACrC,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,YAAY,MAAM,UAAU;AACvC,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,aAAc,OAAM,KAAK,YAAY,MAAM,UAAU;AAC9D,UAAM,KAAK,UAAU,QAAQ,KAAK,WAAW;AAAA,EAC/C;AAAA,EAEA,CAAC,OAAO,YAAY,IAAmB;AACrC,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
import { Registry } from '@juit/pgproxy-types';
|
|
2
2
|
import { PGResult } from './result';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PGProvider } from './provider';
|
|
4
|
+
/**
|
|
5
|
+
* Options to create a `PGClient`
|
|
6
|
+
*
|
|
7
|
+
* As an alternative to using URLs, a `PGClient` can be instantiated with
|
|
8
|
+
* options passed in this object.
|
|
9
|
+
*/
|
|
10
|
+
export interface PGClientOptions {
|
|
11
|
+
/** The protocol used to connect to the database (defaults to "psql") */
|
|
12
|
+
readonly protocol?: string;
|
|
13
|
+
/** The PostgreSQL database to connect to */
|
|
14
|
+
readonly database?: string;
|
|
15
|
+
/** The user to authenticate as */
|
|
16
|
+
readonly username?: string;
|
|
17
|
+
/** The password to use for authentication */
|
|
18
|
+
readonly password?: string;
|
|
19
|
+
/** The host to connect to */
|
|
20
|
+
readonly host?: string;
|
|
21
|
+
/** The port to connect to */
|
|
22
|
+
readonly port?: number;
|
|
23
|
+
/** Any additional options to pass to the provider */
|
|
24
|
+
readonly parameters?: Record<string, string | number | boolean>;
|
|
25
|
+
}
|
|
4
26
|
/** An interface representing a SQL query to a database */
|
|
5
27
|
export interface PGQuery {
|
|
6
28
|
/** The SQL query to execute optionally containing placeholders. */
|
|
@@ -27,7 +49,8 @@ export interface PGQueryable {
|
|
|
27
49
|
}
|
|
28
50
|
/**
|
|
29
51
|
* An interface for an object that can execute queries _and transactions_
|
|
30
|
-
* on a database
|
|
52
|
+
* on a database
|
|
53
|
+
*/
|
|
31
54
|
export interface PGTransactionable extends PGQueryable {
|
|
32
55
|
/**
|
|
33
56
|
* Start a transaction by issuing a `BEGIN` statement
|
|
@@ -41,12 +64,21 @@ export interface PGTransactionable extends PGQueryable {
|
|
|
41
64
|
/** Cancel a transaction by issuing a `ROLLBACK` statement */
|
|
42
65
|
rollback(): Promise<void>;
|
|
43
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* A connection to a database that can be asynchronously disposed of.
|
|
69
|
+
*/
|
|
70
|
+
export interface PGConnection extends PGTransactionable, AsyncDisposable {
|
|
71
|
+
/** Forcedly close the underlying connection to the database */
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
}
|
|
44
74
|
/** A consumer for a {@link PGTransactionable} connection */
|
|
45
75
|
export type PGConsumer<T> = (connection: PGTransactionable) => T | PromiseLike<T>;
|
|
46
76
|
/** The PostgreSQL client */
|
|
47
|
-
export interface PGClient extends PGQueryable {
|
|
77
|
+
export interface PGClient extends PGQueryable, AsyncDisposable {
|
|
48
78
|
/** The {@link @juit/pgproxy-types#Registry} used to parse results from PostgreSQL */
|
|
49
79
|
readonly registry: Registry;
|
|
80
|
+
/** The URL used to create this provider, devoid of any credentials */
|
|
81
|
+
readonly url: Readonly<URL>;
|
|
50
82
|
/**
|
|
51
83
|
* Execute a _single_ query on the database.
|
|
52
84
|
*
|
|
@@ -69,6 +101,11 @@ export interface PGClient extends PGQueryable {
|
|
|
69
101
|
* related parameters) to execute
|
|
70
102
|
*/
|
|
71
103
|
query<Row extends Record<string, any> = Record<string, any>, Tuple extends readonly any[] = readonly any[]>(query: PGQuery): Promise<PGResult<Row, Tuple>>;
|
|
104
|
+
/**
|
|
105
|
+
* Connect to the database and return an _async disposable_
|
|
106
|
+
* {@link PGConnection}.
|
|
107
|
+
*/
|
|
108
|
+
connect(): Promise<PGConnection>;
|
|
72
109
|
/**
|
|
73
110
|
* Connect to the database to execute a number of different queries.
|
|
74
111
|
*
|
|
@@ -86,11 +123,10 @@ export interface PGClient extends PGQueryable {
|
|
|
86
123
|
/** A constructor for {@link (PGClient:interface)} instances */
|
|
87
124
|
export interface PGClientConstructor {
|
|
88
125
|
new (url?: string | URL): PGClient;
|
|
89
|
-
new (provider: PGProvider
|
|
126
|
+
new (provider: PGProvider): PGClient;
|
|
127
|
+
new (options: PGClientOptions): PGClient;
|
|
90
128
|
}
|
|
91
129
|
/**
|
|
92
130
|
* The PostgreSQL client
|
|
93
|
-
*
|
|
94
|
-
* @constructor
|
|
95
131
|
*/
|
|
96
132
|
export declare const PGClient: PGClientConstructor;
|
package/dist/client.mjs
CHANGED
|
@@ -1,3 +1,49 @@
|
|
|
1
|
+
var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
2
|
+
var __typeError = (msg) => {
|
|
3
|
+
throw TypeError(msg);
|
|
4
|
+
};
|
|
5
|
+
var __using = (stack, value, async) => {
|
|
6
|
+
if (value != null) {
|
|
7
|
+
if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
|
|
8
|
+
var dispose, inner;
|
|
9
|
+
if (async) dispose = value[__knownSymbol("asyncDispose")];
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
dispose = value[__knownSymbol("dispose")];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") __typeError("Object not disposable");
|
|
15
|
+
if (inner) dispose = function() {
|
|
16
|
+
try {
|
|
17
|
+
inner.call(this);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return Promise.reject(e);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
stack.push([async, dispose, value]);
|
|
23
|
+
} else if (async) {
|
|
24
|
+
stack.push([async]);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
var __callDispose = (stack, error, hasError) => {
|
|
29
|
+
var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
|
|
30
|
+
return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
|
|
31
|
+
};
|
|
32
|
+
var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
|
|
33
|
+
var next = (it) => {
|
|
34
|
+
while (it = stack.pop()) {
|
|
35
|
+
try {
|
|
36
|
+
var result = it[1] && it[1].call(it[2]);
|
|
37
|
+
if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
fail(e);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (hasError) throw error;
|
|
43
|
+
};
|
|
44
|
+
return next();
|
|
45
|
+
};
|
|
46
|
+
|
|
1
47
|
// client.ts
|
|
2
48
|
import { Registry, serialize } from "@juit/pgproxy-types";
|
|
3
49
|
import { assert } from "./assert.mjs";
|
|
@@ -12,62 +58,115 @@ function serializeParams(params) {
|
|
|
12
58
|
return result;
|
|
13
59
|
}
|
|
14
60
|
var PGClient = class PGClientImpl {
|
|
15
|
-
|
|
61
|
+
_registry = new Registry();
|
|
16
62
|
_provider;
|
|
17
|
-
constructor(
|
|
18
|
-
|
|
19
|
-
assert(
|
|
20
|
-
if (typeof
|
|
21
|
-
assert(
|
|
22
|
-
if (
|
|
23
|
-
if (!(
|
|
63
|
+
constructor(arg) {
|
|
64
|
+
arg = arg || globalThis?.process?.env?.PGURL;
|
|
65
|
+
assert(arg, "No URL to connect to (PGURL environment variable missing?)");
|
|
66
|
+
if (typeof arg === "string") arg = new URL(arg, "psql:///");
|
|
67
|
+
assert(arg, "Missing URL or provider for client");
|
|
68
|
+
if ("href" in arg) {
|
|
69
|
+
if (!(arg.username || arg.password)) {
|
|
24
70
|
const username = globalThis?.process?.env?.PGUSER || "";
|
|
25
71
|
const password = globalThis?.process?.env?.PGPASSWORD || "";
|
|
26
|
-
|
|
27
|
-
|
|
72
|
+
arg.username = encodeURIComponent(username);
|
|
73
|
+
arg.password = encodeURIComponent(password);
|
|
74
|
+
}
|
|
75
|
+
this._provider = createProvider(arg);
|
|
76
|
+
} else if ("query" in arg && "acquire" in arg && "release" in arg) {
|
|
77
|
+
this._provider = arg;
|
|
78
|
+
} else {
|
|
79
|
+
const {
|
|
80
|
+
protocol = "psql",
|
|
81
|
+
database = "",
|
|
82
|
+
username = globalThis?.process?.env?.PGUSER,
|
|
83
|
+
password = globalThis?.process?.env?.PGPASSWORD,
|
|
84
|
+
host = "localhost",
|
|
85
|
+
port,
|
|
86
|
+
parameters = {}
|
|
87
|
+
} = arg;
|
|
88
|
+
const url = new URL(`${protocol}://`);
|
|
89
|
+
if (host) url.hostname = host;
|
|
90
|
+
if (port) url.port = String(port);
|
|
91
|
+
if (username) url.username = encodeURIComponent(username);
|
|
92
|
+
if (password) url.password = encodeURIComponent(password);
|
|
93
|
+
url.pathname = `/${database}`;
|
|
94
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
95
|
+
url.searchParams.set(key, String(value));
|
|
28
96
|
}
|
|
97
|
+
this._provider = createProvider(url);
|
|
29
98
|
}
|
|
30
|
-
|
|
99
|
+
}
|
|
100
|
+
get registry() {
|
|
101
|
+
return this._registry;
|
|
102
|
+
}
|
|
103
|
+
get url() {
|
|
104
|
+
return this._provider.url;
|
|
31
105
|
}
|
|
32
106
|
async query(textOrQuery, maybeParams = []) {
|
|
33
107
|
const [text, params = []] = typeof textOrQuery === "string" ? [textOrQuery, maybeParams] : [textOrQuery.query, textOrQuery.params];
|
|
34
108
|
const result = await this._provider.query(text, serializeParams(params));
|
|
35
|
-
return new PGResult(result, this.
|
|
109
|
+
return new PGResult(result, this._registry);
|
|
36
110
|
}
|
|
37
111
|
async connect(consumer) {
|
|
38
112
|
const connection = await this._provider.acquire();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
},
|
|
53
|
-
async commit() {
|
|
54
|
-
await connection.query("COMMIT");
|
|
55
|
-
transaction = false;
|
|
56
|
-
},
|
|
57
|
-
async rollback() {
|
|
58
|
-
await connection.query("ROLLBACK");
|
|
59
|
-
transaction = false;
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
return await consumer(consumable);
|
|
63
|
-
} finally {
|
|
64
|
-
if (transaction) await connection.query("ROLLBACK");
|
|
65
|
-
await this._provider.release(connection);
|
|
113
|
+
if (!consumer) {
|
|
114
|
+
return new PGConnectionImpl(connection, this._provider, this._registry);
|
|
115
|
+
} else {
|
|
116
|
+
var _stack = [];
|
|
117
|
+
try {
|
|
118
|
+
const conn = __using(_stack, new PGConnectionImpl(connection, this._provider, this._registry), true);
|
|
119
|
+
return await consumer(conn);
|
|
120
|
+
} catch (_) {
|
|
121
|
+
var _error = _, _hasError = true;
|
|
122
|
+
} finally {
|
|
123
|
+
var _promise = __callDispose(_stack, _error, _hasError);
|
|
124
|
+
_promise && await _promise;
|
|
125
|
+
}
|
|
66
126
|
}
|
|
67
127
|
}
|
|
68
128
|
async destroy() {
|
|
69
129
|
return await this._provider.destroy();
|
|
70
130
|
}
|
|
131
|
+
async [Symbol.asyncDispose]() {
|
|
132
|
+
await this.destroy();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
var PGConnectionImpl = class {
|
|
136
|
+
_transaction = false;
|
|
137
|
+
_connection;
|
|
138
|
+
_provider;
|
|
139
|
+
_registry;
|
|
140
|
+
constructor(connection, provider, registry) {
|
|
141
|
+
this._connection = connection;
|
|
142
|
+
this._provider = provider;
|
|
143
|
+
this._registry = registry;
|
|
144
|
+
}
|
|
145
|
+
async query(textOrQuery, maybeParams = []) {
|
|
146
|
+
const [text, params = []] = typeof textOrQuery === "string" ? [textOrQuery, maybeParams] : [textOrQuery.query, textOrQuery.params];
|
|
147
|
+
const result = await this._connection.query(text, serializeParams(params));
|
|
148
|
+
return new PGResult(result, this._registry);
|
|
149
|
+
}
|
|
150
|
+
async begin() {
|
|
151
|
+
if (this._transaction) return false;
|
|
152
|
+
await this._connection.query("BEGIN");
|
|
153
|
+
return this._transaction = true;
|
|
154
|
+
}
|
|
155
|
+
async commit() {
|
|
156
|
+
await this._connection.query("COMMIT");
|
|
157
|
+
this._transaction = false;
|
|
158
|
+
}
|
|
159
|
+
async rollback() {
|
|
160
|
+
await this._connection.query("ROLLBACK");
|
|
161
|
+
this._transaction = false;
|
|
162
|
+
}
|
|
163
|
+
async close() {
|
|
164
|
+
if (this._transaction) await this._connection.query("ROLLBACK");
|
|
165
|
+
await this._provider.release(this._connection);
|
|
166
|
+
}
|
|
167
|
+
[Symbol.asyncDispose]() {
|
|
168
|
+
return this.close();
|
|
169
|
+
}
|
|
71
170
|
};
|
|
72
171
|
export {
|
|
73
172
|
PGClient
|
package/dist/client.mjs.map
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/client.ts"],
|
|
4
|
-
"mappings": "
|
|
4
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,UAAU,iBAAiB;AAEpC,SAAS,cAAc;AACvB,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB;AAIzB,SAAS,gBAAgB,QAA2C;AAClE,MAAI,OAAO,UAAU,EAAG,QAAO,CAAC;AAEhC,QAAM,SAA4B,IAAI,MAAM,OAAO,MAAM;AACzD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAM;AACvC,WAAO,CAAC,IACN,OAAO,CAAC,MAAM,SAAY,OAC1B,OAAO,CAAC,MAAM,OAAO,OACrB,UAAU,OAAO,CAAC,CAAC;AAAA,EACvB;AAEA,SAAO;AACT;AA4JO,IAAM,WAAgC,MAAM,aAAiC;AAAA,EACjE,YAAsB,IAAI,SAAS;AAAA,EACnC;AAAA,EAKjB,YAAY,KAAmD;AAE7D,UAAM,OAAS,YAAoB,SAAS,KAAK;AACjD,WAAO,KAAK,4DAA4D;AAGxE,QAAI,OAAO,QAAQ,SAAU,OAAM,IAAI,IAAI,KAAK,UAAU;AAC1D,WAAO,KAAK,oCAAoC;AAIhD,QAAI,UAAU,KAAK;AACjB,UAAI,EAAE,IAAI,YAAY,IAAI,WAAW;AACnC,cAAM,WAAa,YAAoB,SAAS,KAAK,UAAiC;AACtF,cAAM,WAAa,YAAoB,SAAS,KAAK,cAAqC;AAC1F,YAAI,WAAW,mBAAmB,QAAQ;AAC1C,YAAI,WAAW,mBAAmB,QAAQ;AAAA,MAC5C;AACA,WAAK,YAAY,eAAe,GAAG;AAAA,IAGrC,WAAY,WAAW,OAAS,aAAa,OAAS,aAAa,KAAM;AACvE,WAAK,YAAY;AAAA,IAGnB,OAAO;AACL,YAAM;AAAA,QACJ,WAAW;AAAA,QACX,WAAW;AAAA,QACX,WAAa,YAAoB,SAAS,KAAK;AAAA,QAC/C,WAAa,YAAoB,SAAS,KAAK;AAAA,QAC/C,OAAO;AAAA,QACP;AAAA,QACA,aAAa,CAAC;AAAA,MAChB,IAAI;AAEJ,YAAM,MAAM,IAAI,IAAI,GAAG,QAAQ,KAAK;AACpC,UAAI,KAAM,KAAI,WAAW;AACzB,UAAI,KAAM,KAAI,OAAO,OAAO,IAAI;AAChC,UAAI,SAAU,KAAI,WAAW,mBAAmB,QAAQ;AACxD,UAAI,SAAU,KAAI,WAAW,mBAAmB,QAAQ;AACxD,UAAI,WAAW,IAAI,QAAQ;AAE3B,iBAAW,CAAE,KAAK,KAAM,KAAK,OAAO,QAAQ,UAAU,GAAG;AACvD,YAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MACzC;AAEA,WAAK,YAAY,eAAe,GAAG;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,IAAI,WAAqB;AACvB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,MAAqB;AACvB,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAYA,MAAM,MAGJ,aAA+B,cAA8B,CAAC,GAAkC;AAChG,UAAM,CAAE,MAAM,SAAS,CAAC,CAAE,IAAI,OAAO,gBAAgB,WACnD,CAAE,aAAa,WAAY,IAAI,CAAE,YAAY,OAAO,YAAY,MAAO;AAEzE,UAAM,SAAS,MAAM,KAAK,UAAU,MAAM,MAAM,gBAAgB,MAAM,CAAC;AACvE,WAAO,IAAI,SAAqB,QAAQ,KAAK,SAAS;AAAA,EACxD;AAAA,EAEA,MAAM,QAAW,UAAqD;AACpE,UAAM,aAAa,MAAM,KAAK,UAAU,QAAQ;AAEhD,QAAI,CAAE,UAAU;AACd,aAAO,IAAI,iBAAiB,YAAY,KAAK,WAAW,KAAK,SAAS;AAAA,IACxE,OAAO;AACL;AAAA;AAAA,cAAY,OAAO,oBAAI,iBAAiB,YAAY,KAAK,WAAW,KAAK,SAAS,GAA/D;AACnB,eAAO,MAAM,SAAS,IAAI;AAAA,eAD1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAEF;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,WAAO,MAAM,KAAK,UAAU,QAAQ;AAAA,EACtC;AAAA,EAEA,OAAO,OAAO,YAAY,IAAmB;AAC3C,UAAM,KAAK,QAAQ;AAAA,EACrB;AACF;AAIA,IAAM,mBAAN,MAA+C;AAAA,EACrC,eAAwB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YACI,YACA,UACA,UAAoB;AACtB,SAAK,cAAc;AACnB,SAAK,YAAY;AACjB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,MAGJ,aAA+B,cAA8B,CAAC,GAAkC;AAChG,UAAM,CAAE,MAAM,SAAS,CAAC,CAAE,IAAI,OAAO,gBAAgB,WACnD,CAAE,aAAa,WAAY,IAAI,CAAE,YAAY,OAAO,YAAY,MAAO;AAEzE,UAAM,SAAS,MAAM,KAAK,YAAY,MAAM,MAAM,gBAAgB,MAAM,CAAC;AACzE,WAAO,IAAI,SAAS,QAAQ,KAAK,SAAS;AAAA,EAC5C;AAAA,EAEA,MAAM,QAA0B;AAC9B,QAAI,KAAK,aAAc,QAAO;AAC9B,UAAM,KAAK,YAAY,MAAM,OAAO;AACpC,WAAO,KAAK,eAAe;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAwB;AAC5B,UAAM,KAAK,YAAY,MAAM,QAAQ;AACrC,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,WAA0B;AAC9B,UAAM,KAAK,YAAY,MAAM,UAAU;AACvC,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,aAAc,OAAM,KAAK,YAAY,MAAM,UAAU;AAC9D,UAAM,KAAK,UAAU,QAAQ,KAAK,WAAW;AAAA,EAC/C;AAAA,EAEA,CAAC,OAAO,YAAY,IAAmB;AACrC,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|
package/dist/provider.cjs
CHANGED
|
@@ -26,7 +26,19 @@ __export(provider_exports, {
|
|
|
26
26
|
});
|
|
27
27
|
module.exports = __toCommonJS(provider_exports);
|
|
28
28
|
var import_assert = require("./assert.cjs");
|
|
29
|
+
var providerUrls = /* @__PURE__ */ new WeakMap();
|
|
29
30
|
var AbstractPGProvider = class {
|
|
31
|
+
constructor(url) {
|
|
32
|
+
providerUrls.set(this, new URL(url));
|
|
33
|
+
}
|
|
34
|
+
get url() {
|
|
35
|
+
const url = providerUrls.get(this);
|
|
36
|
+
(0, import_assert.assert)(url, "Internal error: missing provider URL");
|
|
37
|
+
const sanitizedUrl = new URL(url);
|
|
38
|
+
sanitizedUrl.username = "";
|
|
39
|
+
sanitizedUrl.password = "";
|
|
40
|
+
return sanitizedUrl;
|
|
41
|
+
}
|
|
30
42
|
async query(text, params = []) {
|
|
31
43
|
const connection = await this.acquire();
|
|
32
44
|
try {
|
package/dist/provider.cjs.map
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/provider.ts"],
|
|
4
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAuB;
|
|
4
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAuB;AAyCvB,IAAM,eAAe,oBAAI,QAAiC;AAEnD,IAAe,qBAAf,MAC2B;AAAA,EAChC,YAAY,KAAmB;AAC7B,iBAAa,IAAI,MAAM,IAAI,IAAI,GAAG,CAAC;AAAA,EACrC;AAAA,EAEA,IAAI,MAAqB;AACvB,UAAM,MAAM,aAAa,IAAI,IAAI;AACjC,8BAAO,KAAK,sCAAsC;AAClD,UAAM,eAAe,IAAI,IAAI,GAAG;AAChC,iBAAa,WAAW;AACxB,iBAAa,WAAW;AACxB,WAAO;AAAA,EACT;AAAA,EAKA,MAAM,MAAM,MAAc,SAA4B,CAAC,GAA8B;AACnF,UAAM,aAAa,MAAM,KAAK,QAAQ;AACtC,QAAI;AACF,aAAO,MAAM,WAAW,MAAM,MAAM,MAAM;AAAA,IAC5C,UAAE;AACA,YAAM,KAAK,QAAQ,UAAU;AAAA,IAC/B;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAAA,EAE/B;AACF;AAQA,IAAM,YAAY,oBAAI,IAAmC;AAGlD,SAAS,iBACZ,UACA,aACI;AACN,aAAW,GAAG,QAAQ;AACtB,4BAAO,CAAE,UAAU,IAAI,QAAQ,GAAG,4BAA4B,QAAQ,yBAAyB;AAC/F,YAAU,IAAI,UAAU,WAAW;AACnC,YAAU,IAAI,UAAU,WAAW;AACrC;AAGO,SAAS,eAAe,KAAsB;AACnD,QAAM,WAAW,UAAU,IAAI,IAAI,QAAQ;AAC3C,4BAAO,UAAU,0CAA0C,IAAI,QAAQ,MAAM;AAC7E,SAAO,IAAI,SAAS,GAAG;AACzB;",
|
|
5
5
|
"names": []
|
|
6
6
|
}
|