@proteinjs/db-driver-spanner 1.10.20 → 1.11.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [1.11.0](https://github.com/proteinjs/db/compare/@proteinjs/db-driver-spanner@1.10.21...@proteinjs/db-driver-spanner@1.11.0) (2026-07-23)
7
+
8
+
9
+ ### Features
10
+
11
+ * **spanner:** SpannerEmulatorProvisioner — shared test-harness emulator provisioning ([b9e634a](https://github.com/proteinjs/db/commit/b9e634ab74580a66e589ce277985aca21e13a518))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [1.10.20](https://github.com/proteinjs/db/compare/@proteinjs/db-driver-spanner@1.10.19...@proteinjs/db-driver-spanner@1.10.20) (2026-07-21)
7
18
 
8
19
 
@@ -1,2 +1,3 @@
1
1
  export * from './util/getDropTestTable';
2
+ export * from './util/SpannerEmulatorProvisioner';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC"}
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./util/getDropTestTable"), exports);
18
+ __exportStar(require("./util/SpannerEmulatorProvisioner"), exports);
18
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../test/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0DAAwC;AACxC,oEAAkD"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared test-harness provisioning for the Spanner emulator.
3
+ *
4
+ * The emulator holds instances IN MEMORY — any container restart (Docker updates, host reboots,
5
+ * fresh CI service containers) silently wipes them, and every suite then fast-fails with a
6
+ * cryptic gRPC "Instance not found" until someone re-provisions by hand. Harnesses call
7
+ * `ensureProvisioned` in beforeAll; it is idempotent (ALREADY_EXISTS answers are success) and
8
+ * gated on `SPANNER_EMULATOR_HOST`, so it can never touch real GCP.
9
+ *
10
+ * Provisioning uses the ADMIN API over gRPC on the SAME address the suites' data client uses —
11
+ * no second endpoint to derive or configure, any docker port mapping works (the emulator's REST
12
+ * port doesn't even need publishing), and it shares the data client's exact fate: if the address
13
+ * is wrong, no suite could pass anyway.
14
+ *
15
+ * @internal This class is intended to be used only in tests. Do not use it in production code.
16
+ */
17
+ export declare class SpannerEmulatorProvisioner {
18
+ /** Kept until release(): closing right after the create-operations races their trailing LRO
19
+ * callbacks into an unhandled "client has already been closed" that fails the suite. */
20
+ private static client;
21
+ /** Ensure the instance + database exist on the emulator. No-op without SPANNER_EMULATOR_HOST. */
22
+ static ensureProvisioned(config: {
23
+ projectId: string;
24
+ instanceName: string;
25
+ databaseName: string;
26
+ }): Promise<void>;
27
+ /** Close the admin client — call from afterAll so jest's event loop can drain. */
28
+ static release(): void;
29
+ private static swallowAlreadyExists;
30
+ }
31
+ //# sourceMappingURL=SpannerEmulatorProvisioner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpannerEmulatorProvisioner.d.ts","sourceRoot":"","sources":["../../../test/util/SpannerEmulatorProvisioner.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,0BAA0B;IACrC;6FACyF;IACzF,OAAO,CAAC,MAAM,CAAC,MAAM,CAAsB;IAE3C,iGAAiG;WACpF,iBAAiB,CAAC,MAAM,EAAE;QACrC,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;KACtB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBjB,kFAAkF;IAClF,MAAM,CAAC,OAAO,IAAI,IAAI;IAQtB,OAAO,CAAC,MAAM,CAAC,oBAAoB;CAKpC"}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.SpannerEmulatorProvisioner = void 0;
40
+ var spanner_1 = require("@google-cloud/spanner");
41
+ /**
42
+ * Shared test-harness provisioning for the Spanner emulator.
43
+ *
44
+ * The emulator holds instances IN MEMORY — any container restart (Docker updates, host reboots,
45
+ * fresh CI service containers) silently wipes them, and every suite then fast-fails with a
46
+ * cryptic gRPC "Instance not found" until someone re-provisions by hand. Harnesses call
47
+ * `ensureProvisioned` in beforeAll; it is idempotent (ALREADY_EXISTS answers are success) and
48
+ * gated on `SPANNER_EMULATOR_HOST`, so it can never touch real GCP.
49
+ *
50
+ * Provisioning uses the ADMIN API over gRPC on the SAME address the suites' data client uses —
51
+ * no second endpoint to derive or configure, any docker port mapping works (the emulator's REST
52
+ * port doesn't even need publishing), and it shares the data client's exact fate: if the address
53
+ * is wrong, no suite could pass anyway.
54
+ *
55
+ * @internal This class is intended to be used only in tests. Do not use it in production code.
56
+ */
57
+ var SpannerEmulatorProvisioner = /** @class */ (function () {
58
+ function SpannerEmulatorProvisioner() {
59
+ }
60
+ /** Ensure the instance + database exist on the emulator. No-op without SPANNER_EMULATOR_HOST. */
61
+ SpannerEmulatorProvisioner.ensureProvisioned = function (config) {
62
+ var _a;
63
+ return __awaiter(this, void 0, void 0, function () {
64
+ var spanner, instance, _b, operation, error_1, _c, operation, error_2;
65
+ return __generator(this, function (_d) {
66
+ switch (_d.label) {
67
+ case 0:
68
+ if (!process.env.SPANNER_EMULATOR_HOST) {
69
+ return [2 /*return*/];
70
+ }
71
+ spanner = (_a = SpannerEmulatorProvisioner.client) !== null && _a !== void 0 ? _a : new spanner_1.Spanner({ projectId: config.projectId });
72
+ SpannerEmulatorProvisioner.client = spanner;
73
+ instance = spanner.instance(config.instanceName);
74
+ _d.label = 1;
75
+ case 1:
76
+ _d.trys.push([1, 4, , 5]);
77
+ return [4 /*yield*/, instance.create({
78
+ config: 'emulator-config',
79
+ displayName: "".concat(config.instanceName, " (test)"),
80
+ nodes: 1,
81
+ })];
82
+ case 2:
83
+ _b = _d.sent(), operation = _b[1];
84
+ return [4 /*yield*/, operation.promise()];
85
+ case 3:
86
+ _d.sent();
87
+ return [3 /*break*/, 5];
88
+ case 4:
89
+ error_1 = _d.sent();
90
+ SpannerEmulatorProvisioner.swallowAlreadyExists(error_1);
91
+ return [3 /*break*/, 5];
92
+ case 5:
93
+ _d.trys.push([5, 8, , 9]);
94
+ return [4 /*yield*/, instance.createDatabase(config.databaseName)];
95
+ case 6:
96
+ _c = _d.sent(), operation = _c[1];
97
+ return [4 /*yield*/, operation.promise()];
98
+ case 7:
99
+ _d.sent();
100
+ return [3 /*break*/, 9];
101
+ case 8:
102
+ error_2 = _d.sent();
103
+ SpannerEmulatorProvisioner.swallowAlreadyExists(error_2);
104
+ return [3 /*break*/, 9];
105
+ case 9: return [2 /*return*/];
106
+ }
107
+ });
108
+ });
109
+ };
110
+ /** Close the admin client — call from afterAll so jest's event loop can drain. */
111
+ SpannerEmulatorProvisioner.release = function () {
112
+ var _a;
113
+ try {
114
+ (_a = SpannerEmulatorProvisioner.client) === null || _a === void 0 ? void 0 : _a.close();
115
+ }
116
+ finally {
117
+ SpannerEmulatorProvisioner.client = undefined;
118
+ }
119
+ };
120
+ SpannerEmulatorProvisioner.swallowAlreadyExists = function (error) {
121
+ if (error.code !== 6 /* gRPC ALREADY_EXISTS */) {
122
+ throw error;
123
+ }
124
+ };
125
+ return SpannerEmulatorProvisioner;
126
+ }());
127
+ exports.SpannerEmulatorProvisioner = SpannerEmulatorProvisioner;
128
+ //# sourceMappingURL=SpannerEmulatorProvisioner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpannerEmulatorProvisioner.js","sourceRoot":"","sources":["../../../test/util/SpannerEmulatorProvisioner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iDAAgD;AAEhD;;;;;;;;;;;;;;;GAeG;AACH;IAAA;IAiDA,CAAC;IA5CC,iGAAiG;IACpF,4CAAiB,GAA9B,UAA+B,MAI9B;;;;;;;wBACC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE;4BACtC,sBAAO;yBACR;wBACK,OAAO,GAAG,MAAA,0BAA0B,CAAC,MAAM,mCAAI,IAAI,iBAAO,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC;wBAClG,0BAA0B,CAAC,MAAM,GAAG,OAAO,CAAC;wBACtC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;;;;wBAE/B,qBAAM,QAAQ,CAAC,MAAM,CAAC;gCAC1C,MAAM,EAAE,iBAAiB;gCACzB,WAAW,EAAE,UAAG,MAAM,CAAC,YAAY,YAAS;gCAC5C,KAAK,EAAE,CAAC;6BACT,CAAC,EAAA;;wBAJI,KAAgB,SAIpB,EAJO,SAAS,QAAA;wBAKlB,qBAAM,SAAS,CAAC,OAAO,EAAE,EAAA;;wBAAzB,SAAyB,CAAC;;;;wBAE1B,0BAA0B,CAAC,oBAAoB,CAAC,OAAK,CAAC,CAAC;;;;wBAGjC,qBAAM,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,EAAA;;wBAAlE,KAAgB,SAAkD,EAA/D,SAAS,QAAA;wBAClB,qBAAM,SAAS,CAAC,OAAO,EAAE,EAAA;;wBAAzB,SAAyB,CAAC;;;;wBAE1B,0BAA0B,CAAC,oBAAoB,CAAC,OAAK,CAAC,CAAC;;;;;;KAE1D;IAED,kFAAkF;IAC3E,kCAAO,GAAd;;QACE,IAAI;YACF,MAAA,0BAA0B,CAAC,MAAM,0CAAE,KAAK,EAAE,CAAC;SAC5C;gBAAS;YACR,0BAA0B,CAAC,MAAM,GAAG,SAAS,CAAC;SAC/C;IACH,CAAC;IAEc,+CAAoB,GAAnC,UAAoC,KAAc;QAChD,IAAK,KAA2B,CAAC,IAAI,KAAK,CAAC,CAAC,yBAAyB,EAAE;YACrE,MAAM,KAAK,CAAC;SACb;IACH,CAAC;IACH,iCAAC;AAAD,CAAC,AAjDD,IAiDC;AAjDY,gEAA0B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db-driver-spanner",
3
- "version": "1.10.20",
3
+ "version": "1.11.0",
4
4
  "main": "./dist/generated/index.js",
5
5
  "types": "./dist/generated/index.d.ts",
6
6
  "exports": {
@@ -42,16 +42,16 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@google-cloud/spanner": "7.5.0",
45
- "@proteinjs/db": "^1.22.1",
46
- "@proteinjs/db-query": "^1.4.8",
47
- "@proteinjs/db-spanner-common": "^1.1.34",
48
- "@proteinjs/db-transaction-context": "^0.2.33",
49
- "@proteinjs/logger": "^1.0.19",
45
+ "@proteinjs/db": "^1.22.2",
46
+ "@proteinjs/db-query": "^1.4.9",
47
+ "@proteinjs/db-spanner-common": "^1.1.35",
48
+ "@proteinjs/db-transaction-context": "^0.2.34",
49
+ "@proteinjs/logger": "^1.0.20",
50
50
  "@proteinjs/reflection": "^1.1.11",
51
51
  "@proteinjs/util": "^1.6.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@proteinjs/reflection-build": "^1.4.7",
54
+ "@proteinjs/reflection-build": "^1.4.8",
55
55
  "@types/jest": "29.5.5",
56
56
  "@types/node": "14.0.27",
57
57
  "@typescript-eslint/eslint-plugin": "7.8.0",
@@ -63,5 +63,5 @@
63
63
  "ts-jest": "29.1.1",
64
64
  "typescript": "5.2.2"
65
65
  },
66
- "gitHead": "ae0911591d51c066ccf1f42526788401c8f153c6"
66
+ "gitHead": "fd394c848fa43ee594de4c84c827a607cddca3db"
67
67
  }
package/test/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export * from './util/getDropTestTable';
2
+ export * from './util/SpannerEmulatorProvisioner';
@@ -0,0 +1,68 @@
1
+ import { Spanner } from '@google-cloud/spanner';
2
+
3
+ /**
4
+ * Shared test-harness provisioning for the Spanner emulator.
5
+ *
6
+ * The emulator holds instances IN MEMORY — any container restart (Docker updates, host reboots,
7
+ * fresh CI service containers) silently wipes them, and every suite then fast-fails with a
8
+ * cryptic gRPC "Instance not found" until someone re-provisions by hand. Harnesses call
9
+ * `ensureProvisioned` in beforeAll; it is idempotent (ALREADY_EXISTS answers are success) and
10
+ * gated on `SPANNER_EMULATOR_HOST`, so it can never touch real GCP.
11
+ *
12
+ * Provisioning uses the ADMIN API over gRPC on the SAME address the suites' data client uses —
13
+ * no second endpoint to derive or configure, any docker port mapping works (the emulator's REST
14
+ * port doesn't even need publishing), and it shares the data client's exact fate: if the address
15
+ * is wrong, no suite could pass anyway.
16
+ *
17
+ * @internal This class is intended to be used only in tests. Do not use it in production code.
18
+ */
19
+ export class SpannerEmulatorProvisioner {
20
+ /** Kept until release(): closing right after the create-operations races their trailing LRO
21
+ * callbacks into an unhandled "client has already been closed" that fails the suite. */
22
+ private static client: Spanner | undefined;
23
+
24
+ /** Ensure the instance + database exist on the emulator. No-op without SPANNER_EMULATOR_HOST. */
25
+ static async ensureProvisioned(config: {
26
+ projectId: string;
27
+ instanceName: string;
28
+ databaseName: string;
29
+ }): Promise<void> {
30
+ if (!process.env.SPANNER_EMULATOR_HOST) {
31
+ return;
32
+ }
33
+ const spanner = SpannerEmulatorProvisioner.client ?? new Spanner({ projectId: config.projectId });
34
+ SpannerEmulatorProvisioner.client = spanner;
35
+ const instance = spanner.instance(config.instanceName);
36
+ try {
37
+ const [, operation] = await instance.create({
38
+ config: 'emulator-config',
39
+ displayName: `${config.instanceName} (test)`,
40
+ nodes: 1,
41
+ });
42
+ await operation.promise();
43
+ } catch (error) {
44
+ SpannerEmulatorProvisioner.swallowAlreadyExists(error);
45
+ }
46
+ try {
47
+ const [, operation] = await instance.createDatabase(config.databaseName);
48
+ await operation.promise();
49
+ } catch (error) {
50
+ SpannerEmulatorProvisioner.swallowAlreadyExists(error);
51
+ }
52
+ }
53
+
54
+ /** Close the admin client — call from afterAll so jest's event loop can drain. */
55
+ static release(): void {
56
+ try {
57
+ SpannerEmulatorProvisioner.client?.close();
58
+ } finally {
59
+ SpannerEmulatorProvisioner.client = undefined;
60
+ }
61
+ }
62
+
63
+ private static swallowAlreadyExists(error: unknown): void {
64
+ if ((error as { code?: number }).code !== 6 /* gRPC ALREADY_EXISTS */) {
65
+ throw error;
66
+ }
67
+ }
68
+ }