Skip to content

Commit bd7b2f8

Browse files
committed
feat(spannerlib-node): resolve all lint errors and add documentation
1 parent 8a59d7d commit bd7b2f8

11 files changed

Lines changed: 482 additions & 414 deletions

File tree

spannerlib/wrappers/spannerlib-node/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"mocha": "^10.2.0",
5151
"typescript": "^5.6.3",
5252
"@types/node": "^22.7.5",
53+
"@types/bindings": "^1.5.0",
5354
"@types/mocha": "^10.0.6",
5455
"@babel/core": "^7.24.0",
5556
"@babel/cli": "^7.23.9",

spannerlib/wrappers/spannerlib-node/src/ffi/utils.ts

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,57 +12,72 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { createRequire } from 'module';
16-
// @ts-ignore
17-
const _require = typeof require !== 'undefined' ? require : createRequire(import.meta.url);
18-
const addon = _require('bindings')('spanner_napi');
15+
import bindings from 'bindings';
16+
const addon = bindings('spanner_napi');
1917

2018
export const ENCODING_JSON = 0;
2119
export const ENCODING_PROTOBUF = 1;
2220

2321
export interface HandledResult {
24-
objectId: number;
25-
pinnerId: number;
26-
protobufBytes: Buffer | null;
22+
objectId: number;
23+
pinnerId: number;
24+
protobufBytes: Buffer | null;
2725
}
2826

29-
function invokeAsync(funcName: string, constructor1: any, constructor2: any, ...args: any[]): Promise<HandledResult> {
30-
return new Promise((resolve, reject) => {
31-
const callback = (err: any, result: any) => {
32-
if (err) {
33-
return reject(err);
34-
}
35-
if (result.r1 !== 0) {
36-
if (result.r4 && result.r3 > 0) {
37-
const errorJson = result.r4.toString('utf8');
38-
try {
39-
const parsed = JSON.parse(errorJson);
40-
return reject(new Error(parsed.message || errorJson));
41-
} catch (e) {
42-
return reject(new Error(errorJson));
43-
}
44-
}
45-
return reject(new Error(`Native Spanner Error Code: ${result.r1}`));
46-
}
27+
/**
28+
* Represents the structure of the result object returned by the native C++ addon.
29+
* The fields map to the output parameters of the underlying Go library functions.
30+
*/
31+
interface AddonResult {
32+
r0: number;
33+
r1: number;
34+
r2: number;
35+
r3: number;
36+
r4: Buffer | null;
37+
}
38+
39+
function invokeAsync(
40+
funcName: string,
41+
constructor1: unknown,
42+
constructor2: unknown,
43+
...args: unknown[]
44+
): Promise<HandledResult> {
45+
return new Promise((resolve, reject) => {
46+
const callback = (err: unknown, result: AddonResult) => {
47+
if (err) {
48+
return reject(err);
49+
}
50+
if (result.r1 !== 0) {
51+
if (result.r4 && result.r3 > 0) {
52+
const errorJson = result.r4.toString('utf8');
53+
try {
54+
const parsed = JSON.parse(errorJson);
55+
return reject(new Error(parsed.message || errorJson));
56+
} catch {
57+
return reject(new Error(errorJson));
58+
}
59+
}
60+
return reject(new Error(`Native Spanner Error Code: ${result.r1}`));
61+
}
4762

48-
resolve({
49-
objectId: result.r2,
50-
pinnerId: result.r0,
51-
protobufBytes: result.r4
52-
});
53-
};
63+
resolve({
64+
objectId: result.r2,
65+
pinnerId: result.r0,
66+
protobufBytes: result.r4,
67+
});
68+
};
5469

55-
addon[funcName](...args, callback);
56-
});
70+
addon[funcName](...args, callback);
71+
});
5772
}
5873

5974
export const ffi = {
60-
invokeAsync,
61-
Release: addon.Release
75+
invokeAsync,
76+
Release: addon.Release,
6277
};
6378
export class SpannerLibError extends Error {
64-
constructor(message: string) {
65-
super(message);
66-
this.name = 'SpannerLibError';
67-
}
79+
constructor(message: string) {
80+
super(message);
81+
this.name = 'SpannerLibError';
82+
}
6883
}

spannerlib/wrappers/spannerlib-node/src/index.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,7 @@ import { SpannerLibError } from './ffi/utils.js';
2323
* This method should be called when shutting down the application or when the wrapper is no longer needed to prevent native memory leaks.
2424
*/
2525
export function cleanup(): void {
26-
spannerLib.releaseAll();
26+
spannerLib.releaseAll();
2727
}
2828

29-
export {
30-
Pool,
31-
Connection,
32-
Rows,
33-
SpannerLibError
34-
};
29+
export { Pool, Connection, Rows, SpannerLibError };

spannerlib/wrappers/spannerlib-node/src/lib/connection.ts

Lines changed: 83 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -16,102 +16,106 @@ import { ffi } from '../ffi/utils.js';
1616
import { spannerLib } from './spannerlib.js';
1717
import { Pool } from './pool.js';
1818
import { Rows } from './rows.js';
19-
import { createRequire } from 'module';
20-
// @ts-ignore
21-
const _require = typeof require !== 'undefined' ? require : createRequire(import.meta.url);
22-
// TODO: Avoid tight coupling to internal paths of full client libraries.
23-
// Unlike other languages like Java, Python , Node client does not export its protos.
19+
// TODO: Avoid tight coupling to internal paths of full client libraries.
20+
// Unlike other languages like Java, Python , Node client does not export its protos.
2421
// We need to explore how to import protos in Node
25-
const { google } = _require('@google-cloud/spanner/build/protos/protos.js');
22+
import pkg from '@google-cloud/spanner/build/protos/protos.js';
23+
const { google } = pkg;
2624

2725
/**
2826
* Manages a connection to the Spanner database.
29-
*
27+
*
3028
* This class wraps the connection handle from the underlying Go library,
3129
* providing methods to execute SQL statements and manage transactions.
3230
*/
3331
export class Connection {
34-
public pool: Pool | null;
35-
public oid: number | null;
36-
public pinnerId: number | null;
37-
public closed: boolean;
32+
public pool: Pool | null;
33+
public oid: number | null;
34+
public pinnerId: number | null;
35+
public closed: boolean;
3836

39-
/**
40-
* Creates a new connection within the specified pool.
41-
*
42-
* @param pool The pool to create the connection in.
43-
* @returns A Promise that resolves to a new Connection instance.
44-
* @throws {SpannerLibError} If creation fails in the Go library.
45-
*/
46-
static async create(pool: Pool): Promise<Connection> {
47-
const c = new Connection();
48-
c.pool = pool;
37+
/**
38+
* Creates a new connection within the specified pool.
39+
*
40+
* @param pool The pool to create the connection in.
41+
* @returns A Promise that resolves to a new Connection instance.
42+
* @throws {SpannerLibError} If creation fails in the Go library.
43+
*/
44+
static async create(pool: Pool): Promise<Connection> {
45+
const c = new Connection();
46+
c.pool = pool;
4947

50-
const handled = await ffi.invokeAsync(
51-
"CreateConnection",
52-
c,
53-
spannerLib,
54-
pool.oid
55-
);
48+
const handled = await ffi.invokeAsync(
49+
'CreateConnection',
50+
c,
51+
spannerLib,
52+
pool.oid
53+
);
5654

57-
c.oid = handled.objectId;
58-
c.pinnerId = handled.pinnerId;
59-
return c;
60-
}
55+
c.oid = handled.objectId;
56+
c.pinnerId = handled.pinnerId;
57+
return c;
58+
}
6159

62-
constructor() {
63-
this.pool = null;
64-
this.oid = null;
65-
this.pinnerId = null;
66-
this.closed = false;
67-
}
60+
constructor() {
61+
this.pool = null;
62+
this.oid = null;
63+
this.pinnerId = null;
64+
this.closed = false;
65+
}
6866

69-
/**
70-
* Executes a SQL statement on this connection.
71-
*
72-
* @param sqlString The SQL query string to execute.
73-
* @returns A Promise that resolves to a Rows instance containing results.
74-
* @throws {Error} If the connection is closed or not bound to a pool.
75-
* @throws {SpannerLibError} If execution fails in the Go library.
76-
*/
77-
async executeSql(sqlString: string): Promise<Rows> {
78-
if (this.closed) throw new Error("Connection is already closed");
79-
if (!this.pool) throw new Error("Connection is not bound to a Pool");
67+
/**
68+
* Executes a SQL statement on this connection.
69+
*
70+
* @param sqlString The SQL query string to execute.
71+
* @returns A Promise that resolves to a Rows instance containing results.
72+
* @throws {Error} If the connection is closed or not bound to a pool.
73+
* @throws {SpannerLibError} If execution fails in the Go library.
74+
*/
75+
async executeSql(sqlString: string): Promise<Rows> {
76+
if (this.closed) throw new Error('Connection is already closed');
77+
if (!this.pool) throw new Error('Connection is not bound to a Pool');
8078

81-
const requestObj = { sql: sqlString, session: "poc/dummy" };
82-
const ExecuteSqlRequestProto = google.spanner.v1.ExecuteSqlRequest;
83-
const serializedPb = ExecuteSqlRequestProto.encode(requestObj).finish();
79+
const requestObj = { sql: sqlString, session: 'poc/dummy' };
80+
const ExecuteSqlRequestProto = google.spanner.v1.ExecuteSqlRequest;
81+
const serializedPb = ExecuteSqlRequestProto.encode(requestObj).finish();
8482

85-
const rowsResult = await ffi.invokeAsync(
86-
"Execute",
87-
null,
88-
null,
89-
this.pool.oid,
90-
this.oid,
91-
serializedPb
92-
);
93-
const rowsId = rowsResult.objectId;
83+
const rowsResult = await ffi.invokeAsync(
84+
'Execute',
85+
null,
86+
null,
87+
this.pool.oid,
88+
this.oid,
89+
serializedPb
90+
);
91+
const rowsId = rowsResult.objectId;
9492

95-
return new Rows(this, rowsId);
96-
}
93+
return new Rows(this, rowsId);
94+
}
9795

98-
/**
99-
* Closes the connection and releases associated resources.
100-
*
101-
* @returns A Promise that resolves when the connection is closed.
102-
*/
103-
async close(): Promise<void> {
104-
if (!this.closed) {
105-
this.closed = true;
106-
try {
107-
if (this.pool && this.oid !== null) {
108-
await ffi.invokeAsync("CloseConnection", this, spannerLib, this.pool.oid, this.oid);
109-
}
110-
} finally {
111-
if (this.pinnerId !== null) {
112-
spannerLib.unregister(this, this.pinnerId);
113-
}
114-
}
96+
/**
97+
* Closes the connection and releases associated resources.
98+
*
99+
* @returns A Promise that resolves when the connection is closed.
100+
*/
101+
async close(): Promise<void> {
102+
if (!this.closed) {
103+
this.closed = true;
104+
try {
105+
if (this.pool && this.oid !== null) {
106+
await ffi.invokeAsync(
107+
'CloseConnection',
108+
this,
109+
spannerLib,
110+
this.pool.oid,
111+
this.oid
112+
);
113+
}
114+
} finally {
115+
if (this.pinnerId !== null) {
116+
spannerLib.unregister(this, this.pinnerId);
115117
}
118+
}
116119
}
120+
}
117121
}

0 commit comments

Comments
 (0)