@proteinjs/db-driver-spanner 1.13.0 → 1.14.1
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 +24 -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 +194 -26
- 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 +204 -28
- 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.1",
|
|
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": "b29318b50c69f139b9f86829de1bbdf47f46a4e5"
|
|
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,8 +83,29 @@ export class SpannerDriver implements DbDriver {
|
|
|
75
83
|
|
|
76
84
|
private getSpannerDb(): Database {
|
|
77
85
|
if (!SpannerDriver.SPANNER_DB) {
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
const db = this.getSpannerInstance().database(this.config.databaseName, this.config.sessionPoolOptions);
|
|
87
|
+
// The session pool forwards its 'error' events to the Database (session keepalive /
|
|
88
|
+
// delete failures — advisory: the pool self-heals, and per-query failures reject their
|
|
89
|
+
// own calls instead of flowing through this channel). Without a listener Node treats
|
|
90
|
+
// the emit as an unhandled 'error' event and crashes the process; a recycled client's
|
|
91
|
+
// post-close session deletes make that a certainty, so the owner that creates the
|
|
92
|
+
// Database also owns its error channel.
|
|
93
|
+
db.on('error', (error: Error) => {
|
|
94
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
95
|
+
if (SpannerDriver.SPANNER_DB === db) {
|
|
96
|
+
this.logger.warn({
|
|
97
|
+
message: 'Spanner session pool error (advisory; the pool self-heals)',
|
|
98
|
+
obj: { error: detail },
|
|
99
|
+
});
|
|
100
|
+
} else {
|
|
101
|
+
this.logger.debug({
|
|
102
|
+
message: 'Spanner session pool error from a recycled/abandoned client (expected during teardown)',
|
|
103
|
+
obj: { error: detail },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
SpannerDriver.SPANNER_DB = db;
|
|
108
|
+
SpannerDriver.LIVENESS_MONITOR = new SpannerLivenessMonitor(db).start();
|
|
80
109
|
}
|
|
81
110
|
|
|
82
111
|
return SpannerDriver.SPANNER_DB;
|
|
@@ -174,13 +203,17 @@ export class SpannerDriver implements DbDriver {
|
|
|
174
203
|
|
|
175
204
|
try {
|
|
176
205
|
this.logger.debug({ message: `Executing query`, obj: { sql, params: namedParams } });
|
|
177
|
-
const [rows] = await this.
|
|
206
|
+
const [rows] = await this.withDeadline(
|
|
178
207
|
'spanner query',
|
|
179
208
|
sql,
|
|
180
209
|
runner.run({
|
|
181
210
|
sql,
|
|
182
211
|
params: namedParams?.params,
|
|
183
212
|
types: namedParams?.types,
|
|
213
|
+
// The gRPC deadline is what actually cancels the RPC on a dead channel: the stream
|
|
214
|
+
// errors, the library ends the snapshot, and the borrowed session RETURNS to the
|
|
215
|
+
// pool. The withDeadline race alone would fail the caller but leak the session.
|
|
216
|
+
gaxOptions: { timeout: this.operationDeadlineMs() },
|
|
184
217
|
})
|
|
185
218
|
);
|
|
186
219
|
const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
|
|
@@ -214,14 +247,17 @@ export class SpannerDriver implements DbDriver {
|
|
|
214
247
|
}
|
|
215
248
|
|
|
216
249
|
// Stalls in the transaction wrapper itself (session acquisition / begin / commit) happen
|
|
217
|
-
// OUTSIDE executeDml's instrumentation — wrap the whole round trip too.
|
|
218
|
-
|
|
250
|
+
// OUTSIDE executeDml's instrumentation — wrap the whole round trip too. Every await inside
|
|
251
|
+
// the run function is deadline-bounded (dml, commit, rollback): the run function therefore
|
|
252
|
+
// ALWAYS settles, which is what makes runTransactionAsync's own `finally` release the
|
|
253
|
+
// transaction's session back to the pool on a dead channel.
|
|
254
|
+
return await this.withDeadline(
|
|
219
255
|
'spanner dml transaction',
|
|
220
256
|
'(runTransactionAsync)',
|
|
221
257
|
this.getSpannerDb().runTransactionAsync(async (transaction) => {
|
|
222
258
|
try {
|
|
223
259
|
const rowCount = await this.executeDml(generateStatement, transaction);
|
|
224
|
-
await
|
|
260
|
+
await this.commit(transaction);
|
|
225
261
|
return rowCount;
|
|
226
262
|
} catch (error) {
|
|
227
263
|
await this.rollbackQuietly(transaction);
|
|
@@ -246,13 +282,16 @@ export class SpannerDriver implements DbDriver {
|
|
|
246
282
|
|
|
247
283
|
try {
|
|
248
284
|
this.logger.debug({ message: `Executing dml`, obj: { sql, params: namedParams } });
|
|
249
|
-
const [rowCount] = await this.
|
|
285
|
+
const [rowCount] = await this.withDeadline(
|
|
250
286
|
'spanner dml',
|
|
251
287
|
sql,
|
|
252
288
|
runner.runUpdate({
|
|
253
289
|
sql,
|
|
254
290
|
params: namedParams?.params,
|
|
255
291
|
types: namedParams?.types,
|
|
292
|
+
// gRPC deadline: cancels the RPC on a dead channel so the transaction's run function
|
|
293
|
+
// settles and the library releases the session (see runDml).
|
|
294
|
+
gaxOptions: { timeout: this.operationDeadlineMs() },
|
|
256
295
|
})
|
|
257
296
|
);
|
|
258
297
|
const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
|
|
@@ -278,13 +317,13 @@ export class SpannerDriver implements DbDriver {
|
|
|
278
317
|
* @returns the return of the `fn`
|
|
279
318
|
*/
|
|
280
319
|
async runTransaction<T>(fn: (transaction: Transaction) => Promise<T>): Promise<T> {
|
|
281
|
-
return await this.
|
|
320
|
+
return await this.withDeadline(
|
|
282
321
|
'spanner transaction',
|
|
283
322
|
'(runTransactionAsync)',
|
|
284
323
|
this.getSpannerDb().runTransactionAsync(async (transaction) => {
|
|
285
324
|
try {
|
|
286
325
|
const result = await fn(transaction);
|
|
287
|
-
await
|
|
326
|
+
await this.commit(transaction);
|
|
288
327
|
return result;
|
|
289
328
|
} catch (error) {
|
|
290
329
|
await this.rollbackQuietly(transaction);
|
|
@@ -294,6 +333,19 @@ export class SpannerDriver implements DbDriver {
|
|
|
294
333
|
);
|
|
295
334
|
}
|
|
296
335
|
|
|
336
|
+
/**
|
|
337
|
+
* Deadline-bounded commit: a commit hanging on a dead channel would otherwise keep the
|
|
338
|
+
* transaction's run function pending forever, and with it the session (runTransactionAsync
|
|
339
|
+
* only releases the session once the run function settles).
|
|
340
|
+
*/
|
|
341
|
+
private async commit(transaction: Transaction): Promise<void> {
|
|
342
|
+
await this.withDeadline(
|
|
343
|
+
'spanner commit',
|
|
344
|
+
'(commit)',
|
|
345
|
+
transaction.commit({ gaxOptions: { timeout: this.operationDeadlineMs() } })
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
297
349
|
/**
|
|
298
350
|
* Release a transaction whose work errored. The client library's runner does NOT roll back on
|
|
299
351
|
* non-retryable errors — it just rethrows — so a thrown `fn` (e.g. an application rollback, or
|
|
@@ -302,36 +354,54 @@ export class SpannerDriver implements DbDriver {
|
|
|
302
354
|
* so one leaked transaction blocks every subsequent DDL with FAILED_PRECONDITION ("a
|
|
303
355
|
* read-write transaction is already in progress") — poisoning whole test runs. Rollback of an
|
|
304
356
|
* 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 —
|
|
357
|
+
* transaction, or one whose commit was already called) is expected to fail; that failure is
|
|
358
|
+
* logged at debug and swallowed so the ORIGINAL error — the one that carries retry semantics —
|
|
359
|
+
* always propagates. Deadline-bounded like commit: an unbounded rollback on a dead channel
|
|
360
|
+
* would keep the run function (and its session) pending forever.
|
|
307
361
|
*/
|
|
308
362
|
private async rollbackQuietly(transaction: Transaction): Promise<void> {
|
|
309
363
|
try {
|
|
310
|
-
await
|
|
364
|
+
await this.withDeadline(
|
|
365
|
+
'spanner rollback',
|
|
366
|
+
'(rollback)',
|
|
367
|
+
transaction.rollback({ timeout: this.operationDeadlineMs() })
|
|
368
|
+
);
|
|
311
369
|
} catch (rollbackError: any) {
|
|
312
370
|
this.logger.debug({ message: `Rollback after transaction error failed`, obj: { rollbackError } });
|
|
313
371
|
}
|
|
314
372
|
}
|
|
315
373
|
|
|
316
374
|
/**
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
375
|
+
* The one choke point every query/dml/transaction op awaits through. Two jobs:
|
|
376
|
+
*
|
|
377
|
+
* 1. Stall diagnostics (2026-07-10 flow-hang investigation): logs ops still pending at 30s
|
|
378
|
+
* and 120s with the op and statement.
|
|
379
|
+
* 2. OP DEADLINE (2026-08-06 overnight wedge): an op that has not settled by
|
|
380
|
+
* `operationDeadlineMs` (default 60s) FAILS with an error naming the deadline instead of
|
|
381
|
+
* hanging forever on a dead gRPC channel. This race covers hangs the gRPC deadline cannot
|
|
382
|
+
* see (session-pool acquisition, library internals before the RPC starts); the
|
|
383
|
+
* wire-level cancellation + session return is the gRPC deadline attached per call
|
|
384
|
+
* (`gaxOptions.timeout`, same value) — see the call sites. Deadline failures feed the
|
|
385
|
+
* channel-death recycle counter; any success resets it.
|
|
322
386
|
*/
|
|
323
|
-
private
|
|
387
|
+
private withDeadline<T>(op: string, sql: string, promise: PromiseLike<T>): Promise<T> {
|
|
388
|
+
// Pool gauge (P4a): every op passes through here, so this is where waiting-on-the-pool
|
|
389
|
+
// becomes visible (throttled inside the monitor). The monitor exists by now — all ops
|
|
390
|
+
// require getSpannerDb() first.
|
|
391
|
+
SpannerDriver.LIVENESS_MONITOR.logPoolPressure();
|
|
392
|
+
const deadlineMs = this.operationDeadlineMs();
|
|
393
|
+
const generation = SpannerDriver.CLIENT_GENERATION;
|
|
324
394
|
let settled = false;
|
|
325
395
|
const logStall = (afterMs: number) =>
|
|
326
396
|
this.logger.error({
|
|
327
397
|
message: `Spanner op stalled: ${op}`,
|
|
328
|
-
obj: { afterMs, sql: String(sql).slice(0, 200) },
|
|
398
|
+
obj: { afterMs, sql: String(sql).slice(0, 200), pool: SpannerDriver.LIVENESS_MONITOR.poolStats() },
|
|
329
399
|
});
|
|
330
400
|
const t1 = setTimeout(() => {
|
|
331
401
|
if (!settled) {
|
|
332
|
-
logStall(
|
|
402
|
+
logStall(30_000);
|
|
333
403
|
}
|
|
334
|
-
},
|
|
404
|
+
}, 30_000);
|
|
335
405
|
const t2 = setTimeout(() => {
|
|
336
406
|
if (!settled) {
|
|
337
407
|
logStall(120_000);
|
|
@@ -339,11 +409,117 @@ export class SpannerDriver implements DbDriver {
|
|
|
339
409
|
}, 120_000);
|
|
340
410
|
t1.unref?.();
|
|
341
411
|
t2.unref?.();
|
|
342
|
-
return Promise
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
412
|
+
return new Promise<T>((resolve, reject) => {
|
|
413
|
+
const clear = () => {
|
|
414
|
+
settled = true;
|
|
415
|
+
clearTimeout(t1);
|
|
416
|
+
clearTimeout(t2);
|
|
417
|
+
clearTimeout(deadlineTimer);
|
|
418
|
+
};
|
|
419
|
+
const deadlineTimer = setTimeout(() => {
|
|
420
|
+
if (settled) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
clear();
|
|
424
|
+
this.logger.error({
|
|
425
|
+
message: `Spanner op exceeded its ${deadlineMs}ms deadline: ${op} — failing the op`,
|
|
426
|
+
obj: { sql: String(sql).slice(0, 200), pool: SpannerDriver.LIVENESS_MONITOR.poolStats() },
|
|
427
|
+
});
|
|
428
|
+
this.recordDeadlineFailure(generation);
|
|
429
|
+
// Deliberately no grpc `code` on this error: the liveness monitor's probe/exit
|
|
430
|
+
// escalation stays owned by genuine grpc errors; hang-shaped death is owned by the
|
|
431
|
+
// recycle counter.
|
|
432
|
+
reject(
|
|
433
|
+
new Error(
|
|
434
|
+
`Spanner op exceeded its ${deadlineMs}ms deadline: ${op} (configure via SpannerConfig.operationDeadlineMs)`
|
|
435
|
+
)
|
|
436
|
+
);
|
|
437
|
+
}, deadlineMs);
|
|
438
|
+
deadlineTimer.unref?.();
|
|
439
|
+
Promise.resolve(promise).then(
|
|
440
|
+
(value) => {
|
|
441
|
+
if (settled) {
|
|
442
|
+
return; // deadline already failed the caller; the library handles the late settle
|
|
443
|
+
}
|
|
444
|
+
clear();
|
|
445
|
+
this.recordOpSuccess(generation);
|
|
446
|
+
resolve(value);
|
|
447
|
+
},
|
|
448
|
+
(error) => {
|
|
449
|
+
if (settled) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
clear();
|
|
453
|
+
// A grpc DEADLINE_EXCEEDED (code 4) is the same dead-channel signal arriving via the
|
|
454
|
+
// per-call gRPC deadline — classification for the one counter, not a second path.
|
|
455
|
+
if (error?.code === 4) {
|
|
456
|
+
this.recordDeadlineFailure(generation);
|
|
457
|
+
}
|
|
458
|
+
reject(error);
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ── Channel-death recycle (one owner for the process-wide client) ─────────
|
|
465
|
+
|
|
466
|
+
private recordOpSuccess(generation: number): void {
|
|
467
|
+
if (generation !== SpannerDriver.CLIENT_GENERATION) {
|
|
468
|
+
return; // outcome from a recycled client says nothing about the fresh channel
|
|
469
|
+
}
|
|
470
|
+
SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES = 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private recordDeadlineFailure(generation: number): void {
|
|
474
|
+
if (generation !== SpannerDriver.CLIENT_GENERATION) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES += 1;
|
|
478
|
+
if (SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES < this.deadlineFailuresBeforeRecycle()) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
// Reset BEFORE recycling: the counter and generation swap happen in this same synchronous
|
|
482
|
+
// frame, so a burst of ops all timing out together triggers exactly one recycle.
|
|
483
|
+
SpannerDriver.CONSECUTIVE_DEADLINE_FAILURES = 0;
|
|
484
|
+
this.recycleClient();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Drop the process-wide Spanner client so the next op builds a fresh client/channel — the
|
|
489
|
+
* categorical cure for a dead gRPC channel (Mac sleep, NAT drop) that deadlines every op.
|
|
490
|
+
* The old liveness monitor is stopped so its probe/exit escalation can't kill the process
|
|
491
|
+
* for a channel we just abandoned; LIVENESS_MONITOR itself stays pointed at it (stopped)
|
|
492
|
+
* until getSpannerDb() installs the new client's monitor, keeping in-flight ops' error
|
|
493
|
+
* paths callable.
|
|
494
|
+
*/
|
|
495
|
+
private recycleClient(): void {
|
|
496
|
+
this.logger.error({
|
|
497
|
+
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`,
|
|
346
498
|
});
|
|
499
|
+
SpannerDriver.CLIENT_GENERATION += 1;
|
|
500
|
+
const oldMonitor = SpannerDriver.LIVENESS_MONITOR;
|
|
501
|
+
const oldDb = SpannerDriver.SPANNER_DB;
|
|
502
|
+
const oldSpanner = SpannerDriver.SPANNER;
|
|
503
|
+
SpannerDriver.SPANNER = undefined;
|
|
504
|
+
SpannerDriver.SPANNER_INSTANCE = undefined;
|
|
505
|
+
SpannerDriver.SPANNER_DB = undefined;
|
|
506
|
+
oldMonitor?.stop();
|
|
507
|
+
// Best-effort teardown of the old client: session deletes are themselves RPCs on the very
|
|
508
|
+
// channel we believe is dead — the recycle must not depend on them succeeding.
|
|
509
|
+
void oldDb?.close().catch(() => undefined);
|
|
510
|
+
try {
|
|
511
|
+
oldSpanner?.close();
|
|
512
|
+
} catch {
|
|
513
|
+
// already torn down
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private operationDeadlineMs(): number {
|
|
518
|
+
return this.config.operationDeadlineMs ?? 60_000;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
private deadlineFailuresBeforeRecycle(): number {
|
|
522
|
+
return this.config.deadlineFailuresBeforeRecycle ?? 3;
|
|
347
523
|
}
|
|
348
524
|
|
|
349
525
|
/**
|