@proteinjs/db-driver-spanner 1.13.0 → 1.14.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 +13 -0
- package/LICENSE +21 -0
- package/dist/generated/index.js +1 -1
- package/dist/generated/index.js.map +1 -1
- package/dist/generated/test/index.js +1 -1
- package/dist/generated/test/index.js.map +1 -1
- package/dist/src/SpannerConfig.d.ts +22 -0
- package/dist/src/SpannerConfig.d.ts.map +1 -1
- package/dist/src/SpannerDriver.d.ts +46 -11
- package/dist/src/SpannerDriver.d.ts.map +1 -1
- package/dist/src/SpannerDriver.js +170 -25
- package/dist/src/SpannerDriver.js.map +1 -1
- package/dist/src/SpannerLivenessMonitor.d.ts +25 -0
- package/dist/src/SpannerLivenessMonitor.d.ts.map +1 -1
- package/dist/src/SpannerLivenessMonitor.js +48 -2
- package/dist/src/SpannerLivenessMonitor.js.map +1 -1
- package/dist/test/SessionPoolExhaustion.test.d.ts +2 -0
- package/dist/test/SessionPoolExhaustion.test.d.ts.map +1 -0
- package/dist/test/SessionPoolExhaustion.test.js +230 -0
- package/dist/test/SessionPoolExhaustion.test.js.map +1 -0
- package/dist/test/SpannerLivenessMonitor.test.js +42 -0
- package/dist/test/SpannerLivenessMonitor.test.js.map +1 -1
- package/dist/test/SpannerOperationDeadline.test.d.ts +2 -0
- package/dist/test/SpannerOperationDeadline.test.d.ts.map +1 -0
- package/dist/test/SpannerOperationDeadline.test.js +227 -0
- package/dist/test/SpannerOperationDeadline.test.js.map +1 -0
- package/dist/test/TransactionSafety.test.d.ts +2 -0
- package/dist/test/TransactionSafety.test.d.ts.map +1 -0
- package/dist/test/TransactionSafety.test.js +325 -0
- package/dist/test/TransactionSafety.test.js.map +1 -0
- package/generated/index.ts +6 -9
- package/generated/test/index.ts +8 -11
- package/package.json +6 -5
- package/src/SpannerConfig.ts +22 -0
- package/src/SpannerDriver.ts +185 -27
- package/src/SpannerLivenessMonitor.ts +61 -3
- package/test/SessionPoolExhaustion.test.ts +127 -0
- package/test/SpannerLivenessMonitor.test.ts +55 -0
- package/test/SpannerOperationDeadline.test.ts +156 -0
- package/test/TransactionSafety.test.ts +157 -0
package/generated/test/index.ts
CHANGED
|
@@ -9,11 +9,10 @@ import '@proteinjs/logger';
|
|
|
9
9
|
import '@proteinjs/reflection';
|
|
10
10
|
import '@proteinjs/util';
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
/** Generate Source Graph */
|
|
14
13
|
|
|
15
|
-
const sourceGraph = "{\"options\":{\"directed\":true,\"multigraph\":false,\"compound\":false},\"nodes\":[{\"v\":\"@proteinjs/db-driver-spanner/TestDbDriverFactory\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"TestDbDriverFactory\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa2/packages/proteinjs/packages/db/packages/drivers/spanner/test/config/TestDbDriverFactory.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/TestDbDriverFactory\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[{\"name\":\"getDbDriver\",\"returnType\":{\"packageName\":\"@proteinjs/db\",\"name\":\"DbDriver\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/DbDriver\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"DefaultDbDriverFactory\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/DefaultDbDriverFactory\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db/DefaultDbDriverFactory\"},{\"v\":\"@proteinjs/db-driver-spanner/ServiceVerbsDoc\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"ServiceVerbsDoc\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa2/packages/proteinjs/packages/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/ServiceVerbsDoc\",\"properties\":[{\"name\":\"title\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"scope\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":true,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"members\",\"type\":{\"packageName\":\"\",\"name\":\"ReferenceArray<ServiceVerbsDoc>\",\"filePath\":null,\"qualifiedName\":\"/ReferenceArray<ServiceVerbsDoc>\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":true,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"body\",\"type\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"ServiceVerbsDocBody\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-driver-spanner/ServiceVerbsDocBody\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":true,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[],\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"Record\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/Record\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"sourceType\":3}},{\"v\":\"@proteinjs/db/Record\"},{\"v\":\"@proteinjs/db-driver-spanner/ServiceVerbsDocTable\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"ServiceVerbsDocTable\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa2/packages/proteinjs/packages/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/ServiceVerbsDocTable\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"name\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"columns\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"Table\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/Table\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[],\"typeParameters\":[\"@proteinjs/db-driver-spanner/ServiceVerbsDoc\"],\"directParentInterfaces\":[],\"directParentClasses\":[]}],\"sourceType\":2}},{\"v\":\"@proteinjs/db/Table\"},{\"v\":\"@proteinjs/db-driver-spanner/SpannerDriver\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerDriver\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa2/packages/proteinjs/packages/db/packages/drivers/spanner/src/SpannerDriver.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerDriver\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"SPANNER\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Spanner\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Spanner\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"SPANNER_INSTANCE\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Instance\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Instance\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"SPANNER_DB\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Database\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Database\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"LIVENESS_MONITOR\",\"type\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerLivenessMonitor\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerLivenessMonitor\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"logger\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"config\",\"type\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerConfig\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerConfig\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"getTable\",\"type\":{\"packageName\":\"\",\"name\":\"((name: string) => Table<any>) | undefined\",\"filePath\":null,\"qualifiedName\":\"/((name: string) => Table<any>) | undefined\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"getSpanner\",\"returnType\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Spanner\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Spanner\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[]},{\"name\":\"getSpannerInstance\",\"returnType\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Instance\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Instance\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[]},{\"name\":\"getSpannerDb\",\"returnType\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Database\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Database\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[]},{\"name\":\"getDbName\",\"returnType\":null,\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]},{\"name\":\"getTableManager\",\"returnType\":{\"packageName\":\"@proteinjs/db\",\"name\":\"TableManager\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/TableManager\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]},{\"name\":\"getColumnType\",\"returnType\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"tableName\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"columnName\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"handleCaseSensitivity\",\"returnType\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"tableName\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"columnName\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"caseSensitive\",\"type\":{\"packageName\":\"\",\"name\":\"boolean\",\"filePath\":null,\"qualifiedName\":\"/boolean\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"createDbIfNotExists\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[]},{\"name\":\"dbExists\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<boolean>\",\"filePath\":null,\"qualifiedName\":\"/Promise<boolean>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"databaseName\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"runQuery\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<any[]>\",\"filePath\":null,\"qualifiedName\":\"/Promise<any[]>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"generateStatement\",\"type\":{\"packageName\":\"\",\"name\":\"(config: DbDriverQueryStatementConfig) => Statement\",\"filePath\":null,\"qualifiedName\":\"/(config: DbDriverQueryStatementConfig) => Statement\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"transaction\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Transaction\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Transaction\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"executeQuery\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<any[]>\",\"filePath\":null,\"qualifiedName\":\"/Promise<any[]>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"generateStatement\",\"type\":{\"packageName\":\"\",\"name\":\"(config: DbDriverQueryStatementConfig) => Statement\",\"filePath\":null,\"qualifiedName\":\"/(config: DbDriverQueryStatementConfig) => Statement\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"runner\",\"type\":{\"packageName\":\"\",\"name\":\"Database | Transaction\",\"filePath\":null,\"qualifiedName\":\"/Database | Transaction\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"runDml\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<number>\",\"filePath\":null,\"qualifiedName\":\"/Promise<number>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"generateStatement\",\"type\":{\"packageName\":\"\",\"name\":\"(config: DbDriverDmlStatementConfig) => Statement\",\"filePath\":null,\"qualifiedName\":\"/(config: DbDriverDmlStatementConfig) => Statement\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"transaction\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Transaction\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Transaction\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"executeDml\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<number>\",\"filePath\":null,\"qualifiedName\":\"/Promise<number>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"generateStatement\",\"type\":{\"packageName\":\"\",\"name\":\"(config: DbDriverDmlStatementConfig) => Statement\",\"filePath\":null,\"qualifiedName\":\"/(config: DbDriverDmlStatementConfig) => Statement\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"runner\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Transaction\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Transaction\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"runTransaction\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<T>\",\"filePath\":null,\"qualifiedName\":\"/Promise<T>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"fn\",\"type\":{\"packageName\":\"\",\"name\":\"(transaction: Transaction) => Promise<T>\",\"filePath\":null,\"qualifiedName\":\"/(transaction: Transaction) => Promise<T>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"rollbackQuietly\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"transaction\",\"type\":{\"packageName\":\"@google-cloud/spanner\",\"name\":\"Transaction\",\"filePath\":null,\"qualifiedName\":\"@google-cloud/spanner/Transaction\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"logIfStalled\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<T>\",\"filePath\":null,\"qualifiedName\":\"/Promise<T>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"op\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"sql\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"promise\",\"type\":{\"packageName\":\"\",\"name\":\"PromiseLike<T>\",\"filePath\":null,\"qualifiedName\":\"/PromiseLike<T>\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"timeoutMs\",\"type\":null}]},{\"name\":\"runUpdateSchema\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"sql\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"DbDriver\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/DbDriver\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db/DbDriver\"},{\"v\":\"@proteinjs/db-driver-spanner/SpannerSchemaMetadata\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerSchemaMetadata\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa2/packages/proteinjs/packages/db/packages/drivers/spanner/src/SpannerSchemaMetadata.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerSchemaMetadata\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[{\"name\":\"tableExists\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<boolean>\",\"filePath\":null,\"qualifiedName\":\"/Promise<boolean>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"getColumnMetadata\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>\",\"filePath\":null,\"qualifiedName\":\"/Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"getPrimaryKey\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<string[]>\",\"filePath\":null,\"qualifiedName\":\"/Promise<string[]>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"getForeignKeys\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>\",\"filePath\":null,\"qualifiedName\":\"/Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"getUniqueColumns\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<string[]>\",\"filePath\":null,\"qualifiedName\":\"/Promise<string[]>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"getIndexes\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<{ [keyName: string]: string[] }>\",\"filePath\":null,\"qualifiedName\":\"/Promise<{ [keyName: string]: string[] }>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"SchemaMetadata\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/SchemaMetadata\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[]}],\"sourceType\":2}},{\"v\":\"@proteinjs/db/SchemaMetadata\"},{\"v\":\"@proteinjs/db-driver-spanner/SpannerSchemaOperations\",\"value\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerSchemaOperations\",\"filePath\":\"/Users/brentbahry/repos/farm/n3xa2/packages/proteinjs/packages/db/packages/drivers/spanner/src/SpannerSchemaOperations.ts\",\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerSchemaOperations\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"logger\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"spannerDriver\",\"type\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"SpannerDriver\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-driver-spanner/SpannerDriver\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"}],\"methods\":[{\"name\":\"createTable\",\"returnType\":null,\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"alterTable\",\"returnType\":null,\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"tableChanges\",\"type\":{\"packageName\":\"@proteinjs/db\",\"name\":\"TableChanges\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/TableChanges\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"stringWideningStatement\",\"returnType\":{\"packageName\":\"\",\"name\":\"string | null\",\"filePath\":null,\"qualifiedName\":\"/string | null\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[{\"name\":\"table\",\"type\":{\"packageName\":\"\",\"name\":\"Table<any>\",\"filePath\":null,\"qualifiedName\":\"/Table<any>\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"change\",\"type\":{\"packageName\":\"@proteinjs/db-driver-spanner\",\"name\":\"{ name: string; newType: string; oldType: string }\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-driver-spanner/{ name: string; newType: string; oldType: string }\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"SchemaOperations\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/SchemaOperations\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db/SchemaOperations\"}],\"edges\":[{\"v\":\"@proteinjs/db-driver-spanner/TestDbDriverFactory\",\"w\":\"@proteinjs/db/DefaultDbDriverFactory\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/db-driver-spanner/ServiceVerbsDoc\",\"w\":\"@proteinjs/db/Record\",\"value\":\"extends interface\"},{\"v\":\"@proteinjs/db-driver-spanner/ServiceVerbsDocTable\",\"w\":\"@proteinjs/db/Table\",\"value\":\"extends class\"},{\"v\":\"@proteinjs/db-driver-spanner/SpannerDriver\",\"w\":\"@proteinjs/db/DbDriver\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/db-driver-spanner/SpannerSchemaMetadata\",\"w\":\"@proteinjs/db/SchemaMetadata\",\"value\":\"extends class\"},{\"v\":\"@proteinjs/db-driver-spanner/SpannerSchemaOperations\",\"w\":\"@proteinjs/db/SchemaOperations\",\"value\":\"implements interface\"}]}";
|
|
16
|
-
|
|
14
|
+
const sourceGraph =
|
|
15
|
+
'{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"@proteinjs/db-driver-spanner/TestDbDriverFactory","value":{"packageName":"@proteinjs/db-driver-spanner","name":"TestDbDriverFactory","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/config/TestDbDriverFactory.ts","qualifiedName":"@proteinjs/db-driver-spanner/TestDbDriverFactory","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"getDbDriver","returnType":{"packageName":"@proteinjs/db","name":"DbDriver","filePath":null,"qualifiedName":"@proteinjs/db/DbDriver","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"DefaultDbDriverFactory","filePath":null,"qualifiedName":"@proteinjs/db/DefaultDbDriverFactory","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/DefaultDbDriverFactory"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","value":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDoc","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts","qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","properties":[{"name":"title","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"scope","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"members","type":{"packageName":"","name":"ReferenceArray<ServiceVerbsDoc>","filePath":null,"qualifiedName":"/ReferenceArray<ServiceVerbsDoc>","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"body","type":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDocBody","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDocBody","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParents":[{"packageName":"@proteinjs/db","name":"Record","filePath":null,"qualifiedName":"@proteinjs/db/Record","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"sourceType":3}},{"v":"@proteinjs/db/Record"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","value":{"packageName":"@proteinjs/db-driver-spanner","name":"ServiceVerbsDocTable","filePath":"/home/runner/work/db/db/packages/drivers/spanner/test/util/serviceUpdateVerbsTestTables.ts","qualifiedName":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"name","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"},{"name":"columns","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"Table","filePath":null,"qualifiedName":"@proteinjs/db/Table","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":["@proteinjs/db-driver-spanner/ServiceVerbsDoc"],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/Table"},{"v":"@proteinjs/db-driver-spanner/SpannerDriver","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerDriver","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerDriver.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerDriver","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"SPANNER","type":{"packageName":"@google-cloud/spanner","name":"Spanner","filePath":null,"qualifiedName":"@google-cloud/spanner/Spanner","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"SPANNER_INSTANCE","type":{"packageName":"@google-cloud/spanner","name":"Instance","filePath":null,"qualifiedName":"@google-cloud/spanner/Instance","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"SPANNER_DB","type":{"packageName":"@google-cloud/spanner","name":"Database","filePath":null,"qualifiedName":"@google-cloud/spanner/Database","typeParameters":null,"directParents":null},"isOptional":true,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"LIVENESS_MONITOR","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerLivenessMonitor","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerLivenessMonitor","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"CLIENT_GENERATION","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"CONSECUTIVE_DEADLINE_FAILURES","type":null,"isOptional":false,"isAbstract":false,"isStatic":true,"visibility":"private"},{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"config","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerConfig","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerConfig","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"getTable","type":{"packageName":"","name":"((name: string) => Table<any>) | undefined","filePath":null,"qualifiedName":"/((name: string) => Table<any>) | undefined","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public"}],"methods":[{"name":"getSpanner","returnType":{"packageName":"@google-cloud/spanner","name":"Spanner","filePath":null,"qualifiedName":"@google-cloud/spanner/Spanner","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getSpannerInstance","returnType":{"packageName":"@google-cloud/spanner","name":"Instance","filePath":null,"qualifiedName":"@google-cloud/spanner/Instance","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getSpannerDb","returnType":{"packageName":"@google-cloud/spanner","name":"Database","filePath":null,"qualifiedName":"@google-cloud/spanner/Database","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"getDbName","returnType":null,"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"getTableManager","returnType":{"packageName":"@proteinjs/db","name":"TableManager","filePath":null,"qualifiedName":"@proteinjs/db/TableManager","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"getColumnType","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"tableName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"columnName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"handleCaseSensitivity","returnType":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"tableName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"columnName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"caseSensitive","type":{"packageName":"","name":"boolean","filePath":null,"qualifiedName":"/boolean","typeParameters":null,"directParents":null}}]},{"name":"createDbIfNotExists","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[]},{"name":"dbExists","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"databaseName","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]},{"name":"runQuery","returnType":{"packageName":"","name":"Promise<any[]>","filePath":null,"qualifiedName":"/Promise<any[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverQueryStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverQueryStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"executeQuery","returnType":{"packageName":"","name":"Promise<any[]>","filePath":null,"qualifiedName":"/Promise<any[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverQueryStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverQueryStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"runner","type":{"packageName":"","name":"Database | Transaction","filePath":null,"qualifiedName":"/Database | Transaction","typeParameters":null,"directParents":null}}]},{"name":"runDml","returnType":{"packageName":"","name":"Promise<number>","filePath":null,"qualifiedName":"/Promise<number>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverDmlStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverDmlStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"executeDml","returnType":{"packageName":"","name":"Promise<number>","filePath":null,"qualifiedName":"/Promise<number>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generateStatement","type":{"packageName":"","name":"(config: DbDriverDmlStatementConfig) => Statement","filePath":null,"qualifiedName":"/(config: DbDriverDmlStatementConfig) => Statement","typeParameters":null,"directParents":null}},{"name":"runner","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"runTransaction","returnType":{"packageName":"","name":"Promise<T>","filePath":null,"qualifiedName":"/Promise<T>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"fn","type":{"packageName":"","name":"(transaction: Transaction) => Promise<T>","filePath":null,"qualifiedName":"/(transaction: Transaction) => Promise<T>","typeParameters":null,"directParents":null}}]},{"name":"commit","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"rollbackQuietly","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"transaction","type":{"packageName":"@google-cloud/spanner","name":"Transaction","filePath":null,"qualifiedName":"@google-cloud/spanner/Transaction","typeParameters":null,"directParents":null}}]},{"name":"withDeadline","returnType":{"packageName":"","name":"Promise<T>","filePath":null,"qualifiedName":"/Promise<T>","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"op","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"sql","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}},{"name":"promise","type":{"packageName":"","name":"PromiseLike<T>","filePath":null,"qualifiedName":"/PromiseLike<T>","typeParameters":null,"directParents":null}}]},{"name":"recordOpSuccess","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generation","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]},{"name":"recordDeadlineFailure","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"generation","type":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null}}]},{"name":"recycleClient","returnType":{"packageName":"","name":"void","filePath":null,"qualifiedName":"/void","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"operationDeadlineMs","returnType":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"deadlineFailuresBeforeRecycle","returnType":{"packageName":"","name":"number","filePath":null,"qualifiedName":"/number","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[]},{"name":"runUpdateSchema","returnType":{"packageName":"","name":"Promise<void>","filePath":null,"qualifiedName":"/Promise<void>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"sql","type":{"packageName":"","name":"string","filePath":null,"qualifiedName":"/string","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"DbDriver","filePath":null,"qualifiedName":"@proteinjs/db/DbDriver","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/DbDriver"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerSchemaMetadata","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerSchemaMetadata.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[{"name":"tableExists","returnType":{"packageName":"","name":"Promise<boolean>","filePath":null,"qualifiedName":"/Promise<boolean>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getColumnMetadata","returnType":{"packageName":"","name":"Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>","filePath":null,"qualifiedName":"/Promise<{ [columnName: string]: { type: string; isNullable: boolean } }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getPrimaryKey","returnType":{"packageName":"","name":"Promise<string[]>","filePath":null,"qualifiedName":"/Promise<string[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getForeignKeys","returnType":{"packageName":"","name":"Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>","filePath":null,"qualifiedName":"/Promise<{ [columnName: string]: { referencedTableName: string; referencedColumnName: string } }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getUniqueColumns","returnType":{"packageName":"","name":"Promise<string[]>","filePath":null,"qualifiedName":"/Promise<string[]>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"getIndexes","returnType":{"packageName":"","name":"Promise<{ [keyName: string]: string[] }>","filePath":null,"qualifiedName":"/Promise<{ [keyName: string]: string[] }>","typeParameters":null,"directParents":null},"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[{"packageName":"@proteinjs/db","name":"SchemaMetadata","filePath":null,"qualifiedName":"@proteinjs/db/SchemaMetadata","isAbstract":false,"isStatic":false,"visibility":"public","properties":[],"methods":[],"typeParameters":[],"directParentInterfaces":[],"directParentClasses":[]}],"sourceType":2}},{"v":"@proteinjs/db/SchemaMetadata"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","value":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerSchemaOperations","filePath":"/home/runner/work/db/db/packages/drivers/spanner/src/SpannerSchemaOperations.ts","qualifiedName":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","isAbstract":false,"isStatic":false,"visibility":"public","properties":[{"name":"logger","type":null,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"},{"name":"spannerDriver","type":{"packageName":"@proteinjs/db-driver-spanner","name":"SpannerDriver","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/SpannerDriver","typeParameters":null,"directParents":null},"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private"}],"methods":[{"name":"createTable","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}}]},{"name":"alterTable","returnType":null,"isAsync":true,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"public","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}},{"name":"tableChanges","type":{"packageName":"@proteinjs/db","name":"TableChanges","filePath":null,"qualifiedName":"@proteinjs/db/TableChanges","typeParameters":null,"directParents":null}}]},{"name":"stringWideningStatement","returnType":{"packageName":"","name":"string | null","filePath":null,"qualifiedName":"/string | null","typeParameters":null,"directParents":null},"isAsync":false,"isOptional":false,"isAbstract":false,"isStatic":false,"visibility":"private","parameters":[{"name":"table","type":{"packageName":"","name":"Table<any>","filePath":null,"qualifiedName":"/Table<any>","typeParameters":null,"directParents":null}},{"name":"change","type":{"packageName":"@proteinjs/db-driver-spanner","name":"{ name: string; newType: string; oldType: string }","filePath":null,"qualifiedName":"@proteinjs/db-driver-spanner/{ name: string; newType: string; oldType: string }","typeParameters":null,"directParents":null}}]}],"typeParameters":[],"directParentInterfaces":[{"packageName":"@proteinjs/db","name":"SchemaOperations","filePath":null,"qualifiedName":"@proteinjs/db/SchemaOperations","properties":[],"methods":[],"typeParameters":[],"directParents":[]}],"directParentClasses":[],"sourceType":2}},{"v":"@proteinjs/db/SchemaOperations"}],"edges":[{"v":"@proteinjs/db-driver-spanner/TestDbDriverFactory","w":"@proteinjs/db/DefaultDbDriverFactory","value":"implements interface"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDoc","w":"@proteinjs/db/Record","value":"extends interface"},{"v":"@proteinjs/db-driver-spanner/ServiceVerbsDocTable","w":"@proteinjs/db/Table","value":"extends class"},{"v":"@proteinjs/db-driver-spanner/SpannerDriver","w":"@proteinjs/db/DbDriver","value":"implements interface"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaMetadata","w":"@proteinjs/db/SchemaMetadata","value":"extends class"},{"v":"@proteinjs/db-driver-spanner/SpannerSchemaOperations","w":"@proteinjs/db/SchemaOperations","value":"implements interface"}]}';
|
|
17
16
|
|
|
18
17
|
/** Generate Source Links */
|
|
19
18
|
|
|
@@ -24,18 +23,16 @@ import { SpannerSchemaMetadata } from '../../src/SpannerSchemaMetadata';
|
|
|
24
23
|
import { SpannerSchemaOperations } from '../../src/SpannerSchemaOperations';
|
|
25
24
|
|
|
26
25
|
const sourceLinks = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
'@proteinjs/db-driver-spanner/TestDbDriverFactory': TestDbDriverFactory,
|
|
27
|
+
'@proteinjs/db-driver-spanner/ServiceVerbsDocTable': ServiceVerbsDocTable,
|
|
28
|
+
'@proteinjs/db-driver-spanner/SpannerDriver': SpannerDriver,
|
|
29
|
+
'@proteinjs/db-driver-spanner/SpannerSchemaMetadata': SpannerSchemaMetadata,
|
|
30
|
+
'@proteinjs/db-driver-spanner/SpannerSchemaOperations': SpannerSchemaOperations,
|
|
32
31
|
};
|
|
33
32
|
|
|
34
|
-
|
|
35
33
|
/** Load Source Graph and Links */
|
|
36
34
|
|
|
37
35
|
import { SourceRepository } from '@proteinjs/reflection';
|
|
38
36
|
SourceRepository.merge(sourceGraph, sourceLinks);
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
export * from '../../test/index';
|
|
38
|
+
export * from '../../test/index';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteinjs/db-driver-spanner",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"main": "./dist/generated/index.js",
|
|
5
5
|
"types": "./dist/generated/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@google-cloud/spanner": "7.5.0",
|
|
45
|
-
"@proteinjs/db": "^1.
|
|
45
|
+
"@proteinjs/db": "^1.27.0",
|
|
46
46
|
"@proteinjs/db-query": "^1.5.0",
|
|
47
|
-
"@proteinjs/db-spanner-common": "^1.2.
|
|
48
|
-
"@proteinjs/db-transaction-context": "^0.
|
|
47
|
+
"@proteinjs/db-spanner-common": "^1.2.1",
|
|
48
|
+
"@proteinjs/db-transaction-context": "^0.4.0",
|
|
49
49
|
"@proteinjs/logger": "^1.0.21",
|
|
50
50
|
"@proteinjs/reflection": "^1.1.12",
|
|
51
51
|
"@proteinjs/util": "^1.6.1"
|
|
@@ -62,5 +62,6 @@
|
|
|
62
62
|
"jest": "29.7.0",
|
|
63
63
|
"ts-jest": "29.1.1",
|
|
64
64
|
"typescript": "5.2.2"
|
|
65
|
-
}
|
|
65
|
+
},
|
|
66
|
+
"gitHead": "020e0d6b909cdd60925952aa05c23cfdecd8353d"
|
|
66
67
|
}
|
package/src/SpannerConfig.ts
CHANGED
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import { SpannerOptions } from '@google-cloud/spanner';
|
|
2
|
+
import { SessionPoolOptions } from '@google-cloud/spanner/build/src/session-pool';
|
|
2
3
|
|
|
3
4
|
export type SpannerConfig = {
|
|
4
5
|
projectId: string;
|
|
5
6
|
instanceName: string;
|
|
6
7
|
databaseName: string;
|
|
7
8
|
spannerOptions?: SpannerOptions;
|
|
9
|
+
/**
|
|
10
|
+
* Session pool options passed through to `Instance.database()` (library defaults apply when
|
|
11
|
+
* omitted). Note: the driver's Database is a process-wide singleton — the first driver to
|
|
12
|
+
* touch the db fixes the pool for the process, like `spannerOptions`.
|
|
13
|
+
*/
|
|
14
|
+
sessionPoolOptions?: SessionPoolOptions;
|
|
15
|
+
/**
|
|
16
|
+
* Deadline applied to every query/dml/transaction op (default 60_000ms). An op that has not
|
|
17
|
+
* settled by the deadline FAILS with an error naming the deadline instead of hanging forever
|
|
18
|
+
* on a dead gRPC channel (the 2026-08-06 overnight wedge: Mac sleep killed the channel, every
|
|
19
|
+
* in-flight op hung, borrowed sessions never returned, pending ops OOM'd the heap). The same
|
|
20
|
+
* value is passed as the gRPC deadline (`gaxOptions.timeout`) so the library cancels the RPC
|
|
21
|
+
* and returns the op's session to the pool. Schema updates (DDL) are exempt — they are
|
|
22
|
+
* legitimately long-running.
|
|
23
|
+
*/
|
|
24
|
+
operationDeadlineMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Consecutive op-deadline failures that trigger a recycle of the process-wide Spanner
|
|
27
|
+
* client/channel (default 3). Any op success resets the count.
|
|
28
|
+
*/
|
|
29
|
+
deadlineFailuresBeforeRecycle?: number;
|
|
8
30
|
};
|
package/src/SpannerDriver.ts
CHANGED
|
@@ -19,10 +19,18 @@ import { SpannerSchemaMetadata } from './SpannerSchemaMetadata';
|
|
|
19
19
|
* Google Spanner driver for ProteinJs Db
|
|
20
20
|
*/
|
|
21
21
|
export class SpannerDriver implements DbDriver {
|
|
22
|
-
private static SPANNER
|
|
23
|
-
private static SPANNER_INSTANCE
|
|
24
|
-
private static SPANNER_DB
|
|
22
|
+
private static SPANNER?: Spanner;
|
|
23
|
+
private static SPANNER_INSTANCE?: Instance;
|
|
24
|
+
private static SPANNER_DB?: Database;
|
|
25
25
|
private static LIVENESS_MONITOR: SpannerLivenessMonitor;
|
|
26
|
+
/**
|
|
27
|
+
* Channel-death recycle accounting (2026-08-06 overnight wedge). Ops are tagged with the
|
|
28
|
+
* generation of the client they started on; only same-generation outcomes touch the counter,
|
|
29
|
+
* so a burst of deadline failures from the OLD (recycled) channel can never spuriously
|
|
30
|
+
* recycle the fresh one.
|
|
31
|
+
*/
|
|
32
|
+
private static CLIENT_GENERATION = 0;
|
|
33
|
+
private static CONSECUTIVE_DEADLINE_FAILURES = 0;
|
|
26
34
|
private logger = new Logger({ name: this.constructor.name });
|
|
27
35
|
private config: SpannerConfig;
|
|
28
36
|
public getTable: ((name: string) => Table<any>) | undefined;
|
|
@@ -75,7 +83,10 @@ export class SpannerDriver implements DbDriver {
|
|
|
75
83
|
|
|
76
84
|
private getSpannerDb(): Database {
|
|
77
85
|
if (!SpannerDriver.SPANNER_DB) {
|
|
78
|
-
SpannerDriver.SPANNER_DB = this.getSpannerInstance().database(
|
|
86
|
+
SpannerDriver.SPANNER_DB = this.getSpannerInstance().database(
|
|
87
|
+
this.config.databaseName,
|
|
88
|
+
this.config.sessionPoolOptions
|
|
89
|
+
);
|
|
79
90
|
SpannerDriver.LIVENESS_MONITOR = new SpannerLivenessMonitor(SpannerDriver.SPANNER_DB).start();
|
|
80
91
|
}
|
|
81
92
|
|
|
@@ -174,13 +185,17 @@ export class SpannerDriver implements DbDriver {
|
|
|
174
185
|
|
|
175
186
|
try {
|
|
176
187
|
this.logger.debug({ message: `Executing query`, obj: { sql, params: namedParams } });
|
|
177
|
-
const [rows] = await this.
|
|
188
|
+
const [rows] = await this.withDeadline(
|
|
178
189
|
'spanner query',
|
|
179
190
|
sql,
|
|
180
191
|
runner.run({
|
|
181
192
|
sql,
|
|
182
193
|
params: namedParams?.params,
|
|
183
194
|
types: namedParams?.types,
|
|
195
|
+
// The gRPC deadline is what actually cancels the RPC on a dead channel: the stream
|
|
196
|
+
// errors, the library ends the snapshot, and the borrowed session RETURNS to the
|
|
197
|
+
// pool. The withDeadline race alone would fail the caller but leak the session.
|
|
198
|
+
gaxOptions: { timeout: this.operationDeadlineMs() },
|
|
184
199
|
})
|
|
185
200
|
);
|
|
186
201
|
const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
|
|
@@ -214,14 +229,17 @@ export class SpannerDriver implements DbDriver {
|
|
|
214
229
|
}
|
|
215
230
|
|
|
216
231
|
// Stalls in the transaction wrapper itself (session acquisition / begin / commit) happen
|
|
217
|
-
// OUTSIDE executeDml's instrumentation — wrap the whole round trip too.
|
|
218
|
-
|
|
232
|
+
// OUTSIDE executeDml's instrumentation — wrap the whole round trip too. Every await inside
|
|
233
|
+
// the run function is deadline-bounded (dml, commit, rollback): the run function therefore
|
|
234
|
+
// ALWAYS settles, which is what makes runTransactionAsync's own `finally` release the
|
|
235
|
+
// transaction's session back to the pool on a dead channel.
|
|
236
|
+
return await this.withDeadline(
|
|
219
237
|
'spanner dml transaction',
|
|
220
238
|
'(runTransactionAsync)',
|
|
221
239
|
this.getSpannerDb().runTransactionAsync(async (transaction) => {
|
|
222
240
|
try {
|
|
223
241
|
const rowCount = await this.executeDml(generateStatement, transaction);
|
|
224
|
-
await
|
|
242
|
+
await this.commit(transaction);
|
|
225
243
|
return rowCount;
|
|
226
244
|
} catch (error) {
|
|
227
245
|
await this.rollbackQuietly(transaction);
|
|
@@ -246,13 +264,16 @@ export class SpannerDriver implements DbDriver {
|
|
|
246
264
|
|
|
247
265
|
try {
|
|
248
266
|
this.logger.debug({ message: `Executing dml`, obj: { sql, params: namedParams } });
|
|
249
|
-
const [rowCount] = await this.
|
|
267
|
+
const [rowCount] = await this.withDeadline(
|
|
250
268
|
'spanner dml',
|
|
251
269
|
sql,
|
|
252
270
|
runner.runUpdate({
|
|
253
271
|
sql,
|
|
254
272
|
params: namedParams?.params,
|
|
255
273
|
types: namedParams?.types,
|
|
274
|
+
// gRPC deadline: cancels the RPC on a dead channel so the transaction's run function
|
|
275
|
+
// settles and the library releases the session (see runDml).
|
|
276
|
+
gaxOptions: { timeout: this.operationDeadlineMs() },
|
|
256
277
|
})
|
|
257
278
|
);
|
|
258
279
|
const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
|
|
@@ -278,13 +299,13 @@ export class SpannerDriver implements DbDriver {
|
|
|
278
299
|
* @returns the return of the `fn`
|
|
279
300
|
*/
|
|
280
301
|
async runTransaction<T>(fn: (transaction: Transaction) => Promise<T>): Promise<T> {
|
|
281
|
-
return await this.
|
|
302
|
+
return await this.withDeadline(
|
|
282
303
|
'spanner transaction',
|
|
283
304
|
'(runTransactionAsync)',
|
|
284
305
|
this.getSpannerDb().runTransactionAsync(async (transaction) => {
|
|
285
306
|
try {
|
|
286
307
|
const result = await fn(transaction);
|
|
287
|
-
await
|
|
308
|
+
await this.commit(transaction);
|
|
288
309
|
return result;
|
|
289
310
|
} catch (error) {
|
|
290
311
|
await this.rollbackQuietly(transaction);
|
|
@@ -294,6 +315,19 @@ export class SpannerDriver implements DbDriver {
|
|
|
294
315
|
);
|
|
295
316
|
}
|
|
296
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Deadline-bounded commit: a commit hanging on a dead channel would otherwise keep the
|
|
320
|
+
* transaction's run function pending forever, and with it the session (runTransactionAsync
|
|
321
|
+
* only releases the session once the run function settles).
|
|
322
|
+
*/
|
|
323
|
+
private async commit(transaction: Transaction): Promise<void> {
|
|
324
|
+
await this.withDeadline(
|
|
325
|
+
'spanner commit',
|
|
326
|
+
'(commit)',
|
|
327
|
+
transaction.commit({ gaxOptions: { timeout: this.operationDeadlineMs() } })
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
297
331
|
/**
|
|
298
332
|
* Release a transaction whose work errored. The client library's runner does NOT roll back on
|
|
299
333
|
* non-retryable errors — it just rethrows — so a thrown `fn` (e.g. an application rollback, or
|
|
@@ -302,36 +336,54 @@ export class SpannerDriver implements DbDriver {
|
|
|
302
336
|
* so one leaked transaction blocks every subsequent DDL with FAILED_PRECONDITION ("a
|
|
303
337
|
* read-write transaction is already in progress") — poisoning whole test runs. Rollback of an
|
|
304
338
|
* already-invalid transaction (e.g. ABORTED, about to be retried by the runner with a fresh
|
|
305
|
-
* transaction) is expected to fail; that failure is
|
|
306
|
-
* — the one that carries retry semantics —
|
|
339
|
+
* transaction, or one whose commit was already called) is expected to fail; that failure is
|
|
340
|
+
* logged at debug and swallowed so the ORIGINAL error — the one that carries retry semantics —
|
|
341
|
+
* always propagates. Deadline-bounded like commit: an unbounded rollback on a dead channel
|
|
342
|
+
* would keep the run function (and its session) pending forever.
|
|
307
343
|
*/
|
|
308
344
|
private async rollbackQuietly(transaction: Transaction): Promise<void> {
|
|
309
345
|
try {
|
|
310
|
-
await
|
|
346
|
+
await this.withDeadline(
|
|
347
|
+
'spanner rollback',
|
|
348
|
+
'(rollback)',
|
|
349
|
+
transaction.rollback({ timeout: this.operationDeadlineMs() })
|
|
350
|
+
);
|
|
311
351
|
} catch (rollbackError: any) {
|
|
312
352
|
this.logger.debug({ message: `Rollback after transaction error failed`, obj: { rollbackError } });
|
|
313
353
|
}
|
|
314
354
|
}
|
|
315
355
|
|
|
316
356
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
357
|
+
* The one choke point every query/dml/transaction op awaits through. Two jobs:
|
|
358
|
+
*
|
|
359
|
+
* 1. Stall diagnostics (2026-07-10 flow-hang investigation): logs ops still pending at 30s
|
|
360
|
+
* and 120s with the op and statement.
|
|
361
|
+
* 2. OP DEADLINE (2026-08-06 overnight wedge): an op that has not settled by
|
|
362
|
+
* `operationDeadlineMs` (default 60s) FAILS with an error naming the deadline instead of
|
|
363
|
+
* hanging forever on a dead gRPC channel. This race covers hangs the gRPC deadline cannot
|
|
364
|
+
* see (session-pool acquisition, library internals before the RPC starts); the
|
|
365
|
+
* wire-level cancellation + session return is the gRPC deadline attached per call
|
|
366
|
+
* (`gaxOptions.timeout`, same value) — see the call sites. Deadline failures feed the
|
|
367
|
+
* channel-death recycle counter; any success resets it.
|
|
322
368
|
*/
|
|
323
|
-
private
|
|
369
|
+
private withDeadline<T>(op: string, sql: string, promise: PromiseLike<T>): Promise<T> {
|
|
370
|
+
// Pool gauge (P4a): every op passes through here, so this is where waiting-on-the-pool
|
|
371
|
+
// becomes visible (throttled inside the monitor). The monitor exists by now — all ops
|
|
372
|
+
// require getSpannerDb() first.
|
|
373
|
+
SpannerDriver.LIVENESS_MONITOR.logPoolPressure();
|
|
374
|
+
const deadlineMs = this.operationDeadlineMs();
|
|
375
|
+
const generation = SpannerDriver.CLIENT_GENERATION;
|
|
324
376
|
let settled = false;
|
|
325
377
|
const logStall = (afterMs: number) =>
|
|
326
378
|
this.logger.error({
|
|
327
379
|
message: `Spanner op stalled: ${op}`,
|
|
328
|
-
obj: { afterMs, sql: String(sql).slice(0, 200) },
|
|
380
|
+
obj: { afterMs, sql: String(sql).slice(0, 200), pool: SpannerDriver.LIVENESS_MONITOR.poolStats() },
|
|
329
381
|
});
|
|
330
382
|
const t1 = setTimeout(() => {
|
|
331
383
|
if (!settled) {
|
|
332
|
-
logStall(
|
|
384
|
+
logStall(30_000);
|
|
333
385
|
}
|
|
334
|
-
},
|
|
386
|
+
}, 30_000);
|
|
335
387
|
const t2 = setTimeout(() => {
|
|
336
388
|
if (!settled) {
|
|
337
389
|
logStall(120_000);
|
|
@@ -339,13 +391,119 @@ export class SpannerDriver implements DbDriver {
|
|
|
339
391
|
}, 120_000);
|
|
340
392
|
t1.unref?.();
|
|
341
393
|
t2.unref?.();
|
|
342
|
-
return Promise
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
394
|
+
return new Promise<T>((resolve, reject) => {
|
|
395
|
+
const clear = () => {
|
|
396
|
+
settled = true;
|
|
397
|
+
clearTimeout(t1);
|
|
398
|
+
clearTimeout(t2);
|
|
399
|
+
clearTimeout(deadlineTimer);
|
|
400
|
+
};
|
|
401
|
+
const deadlineTimer = setTimeout(() => {
|
|
402
|
+
if (settled) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
clear();
|
|
406
|
+
this.logger.error({
|
|
407
|
+
message: `Spanner op exceeded its ${deadlineMs}ms deadline: ${op} — failing the op`,
|
|
408
|
+
obj: { sql: String(sql).slice(0, 200), pool: SpannerDriver.LIVENESS_MONITOR.poolStats() },
|
|
409
|
+
});
|
|
410
|
+
this.recordDeadlineFailure(generation);
|
|
411
|
+
// Deliberately no grpc `code` on this error: the liveness monitor's probe/exit
|
|
412
|
+
// escalation stays owned by genuine grpc errors; hang-shaped death is owned by the
|
|
413
|
+
// recycle counter.
|
|
414
|
+
reject(
|
|
415
|
+
new Error(
|
|
416
|
+
`Spanner op exceeded its ${deadlineMs}ms deadline: ${op} (configure via SpannerConfig.operationDeadlineMs)`
|
|
417
|
+
)
|
|
418
|
+
);
|
|
419
|
+
}, deadlineMs);
|
|
420
|
+
deadlineTimer.unref?.();
|
|
421
|
+
Promise.resolve(promise).then(
|
|
422
|
+
(value) => {
|
|
423
|
+
if (settled) {
|
|
424
|
+
return; // deadline already failed the caller; the library handles the late settle
|
|
425
|
+
}
|
|
426
|
+
clear();
|
|
427
|
+
this.recordOpSuccess(generation);
|
|
428
|
+
resolve(value);
|
|
429
|
+
},
|
|
430
|
+
(error) => {
|
|
431
|
+
if (settled) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
clear();
|
|
435
|
+
// A grpc DEADLINE_EXCEEDED (code 4) is the same dead-channel signal arriving via the
|
|
436
|
+
// per-call gRPC deadline — classification for the one counter, not a second path.
|
|
437
|
+
if (error?.code === 4) {
|
|
438
|
+
this.recordDeadlineFailure(generation);
|
|
439
|
+
}
|
|
440
|
+
reject(error);
|
|
441
|
+
}
|
|
442
|
+
);
|
|
346
443
|
});
|
|
347
444
|
}
|
|
348
445
|
|
|
446
|
+
// ── Channel-death recycle (one owner for the process-wide client) ─────────
|
|
447
|
+
|
|
448
|
+
private recordOpSuccess(generation: number): void {
|
|
449
|
+
if (generation !== SpannerDriver.CLIENT_GENERATION) {
|
|
450
|
+
return; // outcome from a recycled client says nothing about the fresh channel
|
|
451
|
+
}
|
|
452
|
+
SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES = 0;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private recordDeadlineFailure(generation: number): void {
|
|
456
|
+
if (generation !== SpannerDriver.CLIENT_GENERATION) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES += 1;
|
|
460
|
+
if (SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES < this.deadlineFailuresBeforeRecycle()) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Reset BEFORE recycling: the counter and generation swap happen in this same synchronous
|
|
464
|
+
// frame, so a burst of ops all timing out together triggers exactly one recycle.
|
|
465
|
+
SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES = 0;
|
|
466
|
+
this.recycleClient();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Drop the process-wide Spanner client so the next op builds a fresh client/channel — the
|
|
471
|
+
* categorical cure for a dead gRPC channel (Mac sleep, NAT drop) that deadlines every op.
|
|
472
|
+
* The old liveness monitor is stopped so its probe/exit escalation can't kill the process
|
|
473
|
+
* for a channel we just abandoned; LIVENESS_MONITOR itself stays pointed at it (stopped)
|
|
474
|
+
* until getSpannerDb() installs the new client's monitor, keeping in-flight ops' error
|
|
475
|
+
* paths callable.
|
|
476
|
+
*/
|
|
477
|
+
private recycleClient(): void {
|
|
478
|
+
this.logger.error({
|
|
479
|
+
message: `Recycling Spanner client after ${this.deadlineFailuresBeforeRecycle()} consecutive op-deadline failures (dead gRPC channel suspected) — a fresh client/channel will be created on the next op`,
|
|
480
|
+
});
|
|
481
|
+
SpannerDriver.CLIENT_GENERATION += 1;
|
|
482
|
+
const oldMonitor = SpannerDriver.LIVENESS_MONITOR;
|
|
483
|
+
const oldDb = SpannerDriver.SPANNER_DB;
|
|
484
|
+
const oldSpanner = SpannerDriver.SPANNER;
|
|
485
|
+
SpannerDriver.SPANNER = undefined;
|
|
486
|
+
SpannerDriver.SPANNER_INSTANCE = undefined;
|
|
487
|
+
SpannerDriver.SPANNER_DB = undefined;
|
|
488
|
+
oldMonitor?.stop();
|
|
489
|
+
// Best-effort teardown of the old client: session deletes are themselves RPCs on the very
|
|
490
|
+
// channel we believe is dead — the recycle must not depend on them succeeding.
|
|
491
|
+
void oldDb?.close().catch(() => undefined);
|
|
492
|
+
try {
|
|
493
|
+
oldSpanner?.close();
|
|
494
|
+
} catch {
|
|
495
|
+
// already torn down
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private operationDeadlineMs(): number {
|
|
500
|
+
return this.config.operationDeadlineMs ?? 60_000;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
private deadlineFailuresBeforeRecycle(): number {
|
|
504
|
+
return this.config.deadlineFailuresBeforeRecycle ?? 3;
|
|
505
|
+
}
|
|
506
|
+
|
|
349
507
|
/**
|
|
350
508
|
* Execute a schema write operation.
|
|
351
509
|
*/
|