@mimersql/node-mimer 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/lib/native.js ADDED
@@ -0,0 +1,52 @@
1
+ // Copyright (c) 2026 Mimer Information Technology
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ //
21
+ // See license for more details.
22
+
23
+ 'use strict';
24
+
25
+ /**
26
+ * Backend selection for node-mimer.
27
+ *
28
+ * Default: Koffi FFI (pure JS, no compilation needed).
29
+ * If the optional `node-mimer-native` package is installed, it is used instead.
30
+ *
31
+ * Override with NODE_MIMER_BACKEND environment variable:
32
+ * NODE_MIMER_BACKEND=koffi — force Koffi FFI backend
33
+ * NODE_MIMER_BACKEND=native — force node-mimer-native (C++ addon)
34
+ */
35
+
36
+ let binding;
37
+ const backend = process.env.NODE_MIMER_BACKEND;
38
+
39
+ if (backend === 'koffi') {
40
+ binding = require('./koffi-binding');
41
+ } else if (backend === 'native') {
42
+ binding = require('node-mimer-native');
43
+ } else {
44
+ // Default: try node-mimer-native if installed, otherwise Koffi
45
+ try {
46
+ binding = require('node-mimer-native');
47
+ } catch {
48
+ binding = require('./koffi-binding');
49
+ }
50
+ }
51
+
52
+ module.exports = binding;
package/lib/pool.js ADDED
@@ -0,0 +1,242 @@
1
+ // Copyright (c) 2026 Mimer Information Technology
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ //
21
+ // See license for more details.
22
+
23
+ const { connect } = require('./client');
24
+ const { ResultSet } = require('./resultset');
25
+
26
+ /**
27
+ * PoolClient wraps a MimerClient checked out from a Pool.
28
+ * Call release() to return the connection to the pool instead of close().
29
+ */
30
+ class PoolClient {
31
+ constructor(client, releaseCallback) {
32
+ this._client = client;
33
+ this._releaseCallback = releaseCallback;
34
+ this._released = false;
35
+ }
36
+
37
+ async query(sql, params) {
38
+ return this._client.query(sql, params);
39
+ }
40
+
41
+ async queryCursor(sql, params) {
42
+ return this._client.queryCursor(sql, params);
43
+ }
44
+
45
+ async prepare(sql) {
46
+ return this._client.prepare(sql);
47
+ }
48
+
49
+ async beginTransaction() {
50
+ return this._client.beginTransaction();
51
+ }
52
+
53
+ async commit() {
54
+ return this._client.commit();
55
+ }
56
+
57
+ async rollback() {
58
+ return this._client.rollback();
59
+ }
60
+
61
+ release() {
62
+ if (this._released) {
63
+ return;
64
+ }
65
+ this._released = true;
66
+ this._releaseCallback(this._client);
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Pool manages a set of reusable MimerClient connections.
72
+ */
73
+ class Pool {
74
+ constructor(options) {
75
+ const { dsn, user, password, max, idleTimeout, acquireTimeout } = options;
76
+ if (!dsn || !user || !password) {
77
+ throw new Error('dsn, user, and password are required');
78
+ }
79
+ this._dsn = dsn;
80
+ this._user = user;
81
+ this._password = password;
82
+ this._max = max || 10;
83
+ this._idleTimeout = idleTimeout !== undefined ? idleTimeout : 30000;
84
+ this._acquireTimeout = acquireTimeout !== undefined ? acquireTimeout : 5000;
85
+
86
+ this._pool = []; // idle clients
87
+ this._active = 0; // checked-out count
88
+ this._waiters = []; // { resolve, reject, timer }
89
+ this._closed = false;
90
+ this._idleTimers = new Map();
91
+ }
92
+
93
+ get totalCount() {
94
+ return this._pool.length + this._active;
95
+ }
96
+
97
+ get idleCount() {
98
+ return this._pool.length;
99
+ }
100
+
101
+ get activeCount() {
102
+ return this._active;
103
+ }
104
+
105
+ get waitingCount() {
106
+ return this._waiters.length;
107
+ }
108
+
109
+ async _acquire() {
110
+ if (this._closed) {
111
+ throw new Error('Pool is closed');
112
+ }
113
+
114
+ // 1. Reuse an idle connection
115
+ if (this._pool.length > 0) {
116
+ const client = this._pool.pop();
117
+ const timer = this._idleTimers.get(client);
118
+ if (timer) {
119
+ clearTimeout(timer);
120
+ this._idleTimers.delete(client);
121
+ }
122
+ this._active++;
123
+ return client;
124
+ }
125
+
126
+ // 2. Create a new connection if under the limit
127
+ if (this.totalCount < this._max) {
128
+ this._active++;
129
+ try {
130
+ const client = await connect({
131
+ dsn: this._dsn,
132
+ user: this._user,
133
+ password: this._password,
134
+ });
135
+ return client;
136
+ } catch (err) {
137
+ this._active--;
138
+ throw err;
139
+ }
140
+ }
141
+
142
+ // 3. Wait for a connection to be released
143
+ return new Promise((resolve, reject) => {
144
+ const timer = setTimeout(() => {
145
+ const idx = this._waiters.findIndex((w) => w.resolve === resolve);
146
+ if (idx !== -1) {
147
+ this._waiters.splice(idx, 1);
148
+ }
149
+ reject(new Error('Acquire timeout: no available connections'));
150
+ }, this._acquireTimeout);
151
+
152
+ this._waiters.push({ resolve, reject, timer });
153
+ });
154
+ }
155
+
156
+ _release(client) {
157
+ if (this._closed) {
158
+ this._active--;
159
+ client.close().catch(() => {});
160
+ return;
161
+ }
162
+
163
+ // Hand off to a waiter if one is waiting
164
+ if (this._waiters.length > 0) {
165
+ const waiter = this._waiters.shift();
166
+ clearTimeout(waiter.timer);
167
+ waiter.resolve(client);
168
+ return;
169
+ }
170
+
171
+ // Return to idle pool
172
+ this._active--;
173
+ this._pool.push(client);
174
+
175
+ // Set idle timer
176
+ if (this._idleTimeout > 0) {
177
+ const timer = setTimeout(() => {
178
+ const idx = this._pool.indexOf(client);
179
+ if (idx !== -1) {
180
+ this._pool.splice(idx, 1);
181
+ this._idleTimers.delete(client);
182
+ client.close().catch(() => {});
183
+ }
184
+ }, this._idleTimeout);
185
+ timer.unref();
186
+ this._idleTimers.set(client, timer);
187
+ }
188
+ }
189
+
190
+ async query(sql, params) {
191
+ const client = await this._acquire();
192
+ try {
193
+ return await client.query(sql, params);
194
+ } finally {
195
+ this._release(client);
196
+ }
197
+ }
198
+
199
+ async queryCursor(sql, params) {
200
+ const client = await this._acquire();
201
+ try {
202
+ const nativeRs = client.connection.executeQuery(sql, params || []);
203
+ const rs = new ResultSet(nativeRs, () => {
204
+ this._release(client);
205
+ });
206
+ return rs;
207
+ } catch (err) {
208
+ this._release(client);
209
+ throw err;
210
+ }
211
+ }
212
+
213
+ async connect() {
214
+ const client = await this._acquire();
215
+ return new PoolClient(client, (c) => this._release(c));
216
+ }
217
+
218
+ async end() {
219
+ this._closed = true;
220
+
221
+ // Reject all waiters
222
+ for (const waiter of this._waiters) {
223
+ clearTimeout(waiter.timer);
224
+ waiter.reject(new Error('Pool is closed'));
225
+ }
226
+ this._waiters = [];
227
+
228
+ // Close all idle connections and clear their timers
229
+ for (const [, timer] of this._idleTimers) {
230
+ clearTimeout(timer);
231
+ }
232
+ this._idleTimers.clear();
233
+
234
+ const closePromises = this._pool.map((client) =>
235
+ client.close().catch(() => {})
236
+ );
237
+ this._pool = [];
238
+ await Promise.all(closePromises);
239
+ }
240
+ }
241
+
242
+ module.exports = { Pool, PoolClient };
@@ -0,0 +1,73 @@
1
+ // Copyright (c) 2026 Mimer Information Technology
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ //
21
+ // See license for more details.
22
+
23
+ /**
24
+ * PreparedStatement wraps a native prepared statement for reuse
25
+ */
26
+ class PreparedStatement {
27
+ constructor(nativeStmt) {
28
+ this._stmt = nativeStmt;
29
+ this._closed = false;
30
+ }
31
+
32
+ /**
33
+ * Execute the prepared statement with parameters
34
+ * @param {Array} params - Parameter values for ? placeholders
35
+ * @returns {Promise<Object>} Result object with rows and metadata
36
+ */
37
+ async execute(params = []) {
38
+ if (this._closed) {
39
+ throw new Error('Statement is closed');
40
+ }
41
+
42
+ return new Promise((resolve, reject) => {
43
+ try {
44
+ const result = this._stmt.execute(params);
45
+ resolve(result);
46
+ } catch (error) {
47
+ reject(error);
48
+ }
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Close the prepared statement and release resources
54
+ * @returns {Promise<void>}
55
+ */
56
+ async close() {
57
+ if (this._closed) {
58
+ return;
59
+ }
60
+
61
+ return new Promise((resolve, reject) => {
62
+ try {
63
+ this._stmt.close();
64
+ this._closed = true;
65
+ resolve();
66
+ } catch (error) {
67
+ reject(error);
68
+ }
69
+ });
70
+ }
71
+ }
72
+
73
+ module.exports = { PreparedStatement };
@@ -0,0 +1,120 @@
1
+ // Copyright (c) 2026 Mimer Information Technology
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in all
11
+ // copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ // SOFTWARE.
20
+ //
21
+ // See license for more details.
22
+
23
+ /**
24
+ * ResultSet wraps a native cursor for row-at-a-time iteration.
25
+ * Supports both manual next()/close() and async iteration (for-await-of).
26
+ */
27
+ class ResultSet {
28
+ constructor(nativeRs, onClose) {
29
+ this._rs = nativeRs;
30
+ this._fields = null;
31
+ this._closed = false;
32
+ this._onClose = onClose || null;
33
+ this._onCloseCalled = false;
34
+ }
35
+
36
+ _invokeOnClose() {
37
+ if (this._onClose && !this._onCloseCalled) {
38
+ this._onCloseCalled = true;
39
+ this._onClose();
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Column metadata (lazy-loaded).
45
+ * Each element: { name, dataTypeCode, dataTypeName, nullable }
46
+ */
47
+ get fields() {
48
+ if (this._fields === null) {
49
+ this._fields = this._rs.getFields();
50
+ }
51
+ return this._fields;
52
+ }
53
+
54
+ /**
55
+ * Fetch the next row, or null when exhausted.
56
+ * Automatically closes the cursor when the last row has been read.
57
+ * @returns {Promise<Object|null>}
58
+ */
59
+ async next() {
60
+ if (this._closed) {
61
+ return null;
62
+ }
63
+
64
+ return new Promise((resolve, reject) => {
65
+ try {
66
+ const row = this._rs.fetchNext();
67
+ if (row === null) {
68
+ this._closed = true;
69
+ this._rs.close();
70
+ this._invokeOnClose();
71
+ }
72
+ resolve(row);
73
+ } catch (error) {
74
+ reject(error);
75
+ }
76
+ });
77
+ }
78
+
79
+ /**
80
+ * Close the cursor and release resources. Safe to call multiple times.
81
+ * @returns {Promise<void>}
82
+ */
83
+ async close() {
84
+ if (this._closed) {
85
+ return;
86
+ }
87
+
88
+ return new Promise((resolve, reject) => {
89
+ try {
90
+ this._rs.close();
91
+ this._closed = true;
92
+ this._invokeOnClose();
93
+ resolve();
94
+ } catch (error) {
95
+ reject(error);
96
+ }
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Async iterator protocol for for-await-of support.
102
+ */
103
+ [Symbol.asyncIterator]() {
104
+ return {
105
+ next: async () => {
106
+ const row = await this.next();
107
+ if (row === null) {
108
+ return { done: true, value: undefined };
109
+ }
110
+ return { done: false, value: row };
111
+ },
112
+ return: async () => {
113
+ await this.close();
114
+ return { done: true, value: undefined };
115
+ }
116
+ };
117
+ }
118
+ }
119
+
120
+ module.exports = { ResultSet };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@mimersql/node-mimer",
3
+ "version": "1.0.0",
4
+ "description": "Node.js bindings for Mimer SQL using Mimer API",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts",
10
+ "lib/"
11
+ ],
12
+ "scripts": {
13
+ "test": "node --test test/*.test.js",
14
+ "check-mimer": "node scripts/check-mimer.js"
15
+ },
16
+ "keywords": [
17
+ "mimer",
18
+ "mimersql",
19
+ "database",
20
+ "sql"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/mimersql/node-mimer.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/mimersql/node-mimer/issues"
28
+ },
29
+ "homepage": "https://github.com/mimersql/node-mimer#readme",
30
+ "author": "Fredrik Ålund <fredrik.alund@mimer.com>",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "koffi": "^2.15.1"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ }
38
+ }