├── examples ├── config │ ├── cloudsim.json │ ├── kvstore_template.json │ └── cloud_template.json ├── typescript │ ├── tsconfig.json │ ├── package.json │ └── common.ts └── javascript │ ├── package.json │ ├── basic_example.js │ ├── query_example.js │ └── rate_limiting_example.js ├── .gitignore ├── tsconfig.json ├── test ├── unit │ ├── types │ │ ├── tsconfig.json │ │ ├── utils.ts │ │ ├── list_tables.ts │ │ ├── get_indexes.ts │ │ ├── db_number.ts │ │ ├── table_usage.ts │ │ └── get_table.ts │ ├── null_rate_limiter.js │ ├── delete_range_tests.js │ ├── list_tables.js │ ├── nson.js │ ├── auth │ │ └── iam │ │ │ ├── oci_region.js │ │ │ └── constants.js │ └── get.js ├── utils.js ├── config.js └── smoke.js ├── lib ├── auth │ ├── iam │ │ ├── utils.js │ │ ├── utils_browser.js │ │ ├── instance_metadata.js │ │ └── cached_profile.js │ ├── kvstore │ │ └── token_provider.js │ └── http_client.js ├── ops │ ├── index.js │ └── admin.js ├── binary_protocol │ ├── constants.js │ ├── protocol_manager.js │ ├── writer.js │ └── buffer.js ├── nson_protocol │ ├── admin.js │ ├── protocol_manager.js │ ├── constants.js │ └── writer.js └── query │ ├── sort.js │ ├── min_heap.js │ ├── func.js │ ├── utils.js │ └── value.js ├── doc ├── guides │ └── tutorials.json ├── index.md └── layout.tmpl ├── .mocharc.json ├── src └── types │ ├── type_utils.d.ts │ ├── events.d.ts │ ├── rate_limiter │ └── simple_rate_limiter.d.ts │ ├── auth │ └── kvstore │ │ ├── auth_provider.d.ts │ │ └── types.d.ts │ ├── durability.d.ts │ ├── stmt.d.ts │ └── ttl_util.d.ts ├── index.d.ts ├── eslint.config.js ├── package.json ├── index.js ├── SECURITY.md ├── LICENSE.txt ├── CONTRIBUTING.md └── typedoc.json /examples/config/cloudsim.json: -------------------------------------------------------------------------------- 1 | { 2 | "endpoint": "http://localhost:8080" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/api 3 | doc/api 4 | package-lock.json 5 | .DS_Store 6 | *.diff 7 | *.tgz 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ "index.d.ts", "src/types/*" ], 3 | "compilerOptions": { 4 | "target": "es2022", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "strict": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/unit/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ "*.ts" ], 3 | "compilerOptions": { 4 | "target": "es2022", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "strict": true, 9 | "noErrorTruncation": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ "*.ts" ], 3 | "compilerOptions": { 4 | "target": "es2022", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "outDir": "dist", 9 | "inlineSourceMap": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/config/kvstore_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceType": "KVSTORE", 3 | "endpoint": "", 4 | "auth": 5 | { 6 | "kvstore": 7 | { 8 | "credentials": "", 9 | "user": "", 10 | "password": "" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /lib/auth/iam/utils.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | module.exports = (typeof window === 'undefined') ? 9 | require('./utils_node') : 10 | require('./utils_browser'); 11 | -------------------------------------------------------------------------------- /doc/guides/tutorials.json: -------------------------------------------------------------------------------- 1 | { 2 | "connect-cloud": { 3 | "title": 4 | "Connecting an Application to Oracle NoSQL Database Cloud Service" 5 | }, 6 | "connect-on-prem": { 7 | "title": 8 | "Connecting an Application to On-Premise Oracle NoSQL Database" 9 | }, 10 | "tables": { 11 | "title": "Working With Tables" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/javascript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracle-nosqldb-js-examples", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Oracle NoSQL JavaScript Examples", 6 | "dependencies": { 7 | "oracle-nosqldb": "latest" 8 | }, 9 | "engines": { 10 | "node": ">=12.0.0" 11 | }, 12 | "author": "Oracle", 13 | "license": "UPL-1.0", 14 | "repository": {} 15 | } 16 | -------------------------------------------------------------------------------- /examples/config/cloud_template.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "your-region-id-here", 3 | "auth": { 4 | "iam": { 5 | "configFile": "", 6 | "profileName": "", 7 | "tenantId": "", 8 | "userId": "", 9 | "fingerprint": "", 10 | "privateKeyFile": "", 11 | "passphrase": "", 12 | "credentialsProvider": "" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/unit/null_rate_limiter.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | //Fake rate limiter class for testing 11 | 12 | class NullRateLimiter { 13 | setLimit() {} 14 | consumeUnits() { return 0; } 15 | onThrottle() {} 16 | } 17 | 18 | module.exports = NullRateLimiter; 19 | -------------------------------------------------------------------------------- /examples/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracle-nosqldb-ts-examples", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Oracle NoSQL TypeScript Examples", 6 | "dependencies": { 7 | "oracle-nosqldb": "latest", 8 | "decimal.js": "^10.4.3" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^12.12.6", 12 | "typescript": "^5.0.4", 13 | "tsx": "^3.12.7" 14 | }, 15 | "scripts": { 16 | "build": "tsc" 17 | }, 18 | "engines": { 19 | "node": ">=12.0.0" 20 | }, 21 | "author": "Oracle", 22 | "license": "UPL-1.0", 23 | "repository": {} 24 | } 25 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | let TestConfig = require('./config'); 11 | 12 | //This is to allow to run internal tests with more extensive configuration 13 | function initTestConfig() { 14 | let cfg; 15 | try { 16 | cfg = require('../../test/config'); // eslint-disable-line 17 | } catch { 18 | return; 19 | } 20 | if (typeof cfg === 'function' && 21 | cfg.name === 'IntTestConfig' && 22 | typeof cfg.getConfigObj === 'function' && 23 | typeof cfg.createNoSQLClientNoInit === 'function' && 24 | typeof cfg.createNoSQLClient === 'function') { 25 | TestConfig = cfg; 26 | } 27 | } 28 | 29 | initTestConfig(); 30 | 31 | module.exports = { 32 | TestConfig 33 | }; 34 | -------------------------------------------------------------------------------- /.mocharc.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": "min", 3 | "spec": [ 4 | "test/unit/config.js", 5 | "test/unit/table_ddl.js", 6 | "test/unit/admin_ddl.js", 7 | "test/unit/get_table.js", 8 | "test/unit/list_tables.js", 9 | "test/unit/get_indexes.js", 10 | "test/unit/table_usage.js", 11 | "test/unit/get.js", 12 | "test/unit/put.js", 13 | "test/unit/delete.js", 14 | "test/unit/delete_range.js", 15 | "test/unit/write_many.js", 16 | "test/unit/query.js", 17 | "test/unit/rate_limiter.js", 18 | "test/unit/nson.js", 19 | "test/unit/global_active_table.js", 20 | "test/unit/auth/iam/auth_provider.js", 21 | "test/unit/auth/iam/oci_region.js", 22 | "test/unit/auth/iam/instance_principal.js", 23 | "test/unit/auth/iam/oke_workload.js", 24 | "test/unit/auth/iam/resource_principal.js", 25 | "test/unit/auth/kvstore/auth_provider.js" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /src/types/type_utils.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | // Allow to display properties of the type in VSCode. 9 | export type Expand = T extends infer O ? { [K in keyof O]: O[K] } : never; 10 | 11 | declare const opaqueTypeKey: unique symbol; 12 | 13 | declare type Tag = { 14 | readonly [opaqueTypeKey]: T; 15 | // Prevent opaque type to be assignable to other index signature types. 16 | readonly [key: PropertyKey]: unknown; 17 | }; 18 | 19 | // Define opaque type that can be explicitly cast to its BaseType. 20 | export type OpaqueType = (BaseType & Tag) | Tag; 21 | 22 | // Construct a new type by picking only properties of specified type. 23 | export type ExtractByType = { 24 | [P in keyof T as T[P] extends PType | undefined ? P : never]: T[P] 25 | } 26 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Oracle NoSQL Database Node.js SDK 2 | 3 | This is version 5.5 of Oracle NoSQL Database Node.js SDK for The Oracle NoSQL 4 | Database. It supports both the 5 | [Oracle NoSQL Database Cloud Service](https://cloud.oracle.com/nosqldatabase) 6 | and the on-premise product, [Oracle NoSQL Database](https://www.oracle.com/database/technologies/related/nosql.html). You can use the SDK to access Oracle NoSQL Database in 7 | JavaScript or TypeScript. 8 | 9 | To get started, please see the following: 10 | 11 | * {@page connect-cloud.md} guide describes how to install and configure 12 | the SDK to use with Oracle NoSQL Database Cloud Service as well as how to 13 | connect your application to the service. 14 | * {@page connect-on-prem.md} guide describes how to install and configure 15 | the SDK to use with On-Premise Oracle NoSQL Database as well as how to 16 | connect your application to the database. 17 | * {@page tables.md} guide describes how to use the SDK to work with 18 | NoSQL Database tables in your application. 19 | 20 | The rest of the documentation is the API reference for Oracle NoSQL Database 21 | Node.js SDK. 22 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | export * from "./src/types/nosql_client"; 9 | export * from "./src/types/stmt"; 10 | export * from "./src/types/constants"; 11 | export * from "./src/types/region"; 12 | export * from "./src/types/durability"; 13 | export * from "./src/types/error_code"; 14 | export * from "./src/types/error"; 15 | export * from "./src/types/events"; 16 | export * from "./src/types/ttl_util"; 17 | export * from "./src/types/config"; 18 | export * from "./src/types/data"; 19 | export * from "./src/types/db_number"; 20 | export * from "./src/types/opt"; 21 | export * from "./src/types/param"; 22 | export * from "./src/types/result"; 23 | export * from "./src/types/auth/config"; 24 | export * from "./src/types/auth/iam/types"; 25 | export * from "./src/types/auth/iam/auth_provider"; 26 | export * from "./src/types/auth/kvstore/types"; 27 | export * from "./src/types/auth/kvstore/auth_provider"; 28 | export * from "./src/types/rate_limiter/rate_limiter"; 29 | export * from "./src/types/rate_limiter/simple_rate_limiter"; 30 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | defineConfig, 3 | } = require('eslint/config'); 4 | 5 | const globals = require('globals'); 6 | const _import = require('eslint-plugin-import'); 7 | const js = require('@eslint/js'); 8 | 9 | const config0 = { 10 | languageOptions: { 11 | globals: { 12 | ...globals.node, 13 | }, 14 | 'ecmaVersion': 2022, 15 | 'sourceType': 'module', 16 | parserOptions: {}, 17 | }, 18 | extends: [ js.configs.recommended ], 19 | rules: { 20 | 'indent': ['error', 4], 21 | 'linebreak-style': ['error', 'unix'], 22 | 'quotes': ['error', 'single'], 23 | 'semi': ['error', 'always'], 24 | 'no-fallthrough': 'off', 25 | 'no-console': 'off', 26 | 'import/no-unresolved': ['error', { commonjs: true }] 27 | }, 28 | plugins: { 29 | import: _import, 30 | } 31 | }; 32 | 33 | module.exports = defineConfig([{ 34 | files : [ 'test/unit/**/*.js' ], 35 | ...config0, 36 | languageOptions: { 37 | ...config0.languageOptions, 38 | globals: { 39 | ...globals.node, 40 | ...globals.mocha 41 | } 42 | } 43 | }, { 44 | files : [ 'lib/**/*.js', 'test/**/*.js'], 45 | ...config0 46 | }]); 47 | -------------------------------------------------------------------------------- /lib/auth/iam/utils_browser.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | const assert = require('assert'); 9 | const Utils = require('./utils_node'); 10 | 11 | assert(global.crypto); 12 | 13 | const SubtleCrypto = global.crypto.subtle; 14 | assert(SubtleCrypto); 15 | 16 | const CryptoKey = global.CryptoKey; 17 | assert(CryptoKey); 18 | 19 | const SIGN_ALG = { 20 | name: 'RSASSA-PKCS1-v1_5', 21 | modulusLength: 2048, 22 | publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 23 | hash: { name: 'SHA-256' } 24 | }; 25 | 26 | class WebUtils extends Utils { 27 | 28 | static get isInBrowser() { return true; } 29 | 30 | static async _sign(signingContent, privateKey) { 31 | if (!(privateKey instanceof CryptoKey)) { 32 | return super._sign(signingContent, privateKey); 33 | } 34 | const data = typeof signingContent === 'string' ? 35 | Buffer.from(signingContent, 'utf8') : signingContent; 36 | const sign = await SubtleCrypto.sign(SIGN_ALG, privateKey, data); 37 | return Buffer.from(sign).toString('base64'); 38 | } 39 | } 40 | 41 | module.exports = WebUtils; 42 | -------------------------------------------------------------------------------- /lib/ops/index.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const Op = require('./op'); 11 | const table = require('./table'); 12 | const admin = require('./admin'); 13 | const dml = require('./dml'); 14 | const query = require('./query'); 15 | 16 | module.exports = { 17 | Op, 18 | GetOp: dml.GetOp, 19 | PutOp: dml.PutOp, 20 | DeleteOp: dml.DeleteOp, 21 | MultiDeleteOp: dml.MultiDeleteOp, 22 | WriteMultipleOp: dml.WriteMultipleOp, 23 | TableDDLOp: table.TableDDLOp, 24 | TableLimitsOp: table.TableLimitsOp, 25 | GetTableOp: table.GetTableOp, 26 | TableUsageOp: table.TableUsageOp, 27 | GetIndexesOp: table.GetIndexesOp, 28 | ListTablesOp: table.ListTablesOp, 29 | AddReplicaOp: table.AddReplicaOp, 30 | DropReplicaOp: table.DropReplicaOp, 31 | ReplicaStatsOp: table.ReplicaStatsOp, 32 | PrepareOp: query.PrepareOp, 33 | QueryOp: query.QueryOp, 34 | PollTableOp: table.PollTableOp, 35 | PollTableStateOp: table.PollTableStateOp, 36 | AdminDDLOp: admin.AdminDDLOp, 37 | AdminStatusOp: admin.AdminStatusOp, 38 | AdminPollOp: admin.AdminPollOp, 39 | ccAsObj: Op.ccAsObj 40 | }; 41 | -------------------------------------------------------------------------------- /examples/typescript/common.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | import { ConsumedCapacity, PutOpResult, DeleteOpResult } 11 | from "oracle-nosqldb"; 12 | 13 | import Decimal from "decimal.js"; 14 | 15 | export interface PurchaseOrder { 16 | seller: string; 17 | orderId: number; 18 | customerName: string; 19 | shipAddress: { 20 | street: string; 21 | city: string; 22 | }; 23 | shipDate: Date; 24 | items: { 25 | id: number; 26 | name: string; 27 | price: Decimal; 28 | }[]; 29 | } 30 | 31 | export function printConsumedCapacity(cc?: ConsumedCapacity) { 32 | if (cc) { 33 | console.log("Operation consumed: %d read units, %d read KB, \ 34 | %d write units, %d write KB", cc.readUnits, cc.readKB, cc.writeUnits, 35 | cc.writeKB); 36 | } 37 | } 38 | 39 | export function printPutOrDeleteOpResult( 40 | res: PutOpResult | DeleteOpResult): void { 41 | console.log("Success: %s", res.success); 42 | if (!res.success) { 43 | if (res.existingRow) { 44 | console.log("Existing row: %o", res.existingRow); 45 | } 46 | if (res.existingModificationTime) { 47 | console.log("Existing modification time: %o", 48 | res.existingModificationTime); 49 | } 50 | } 51 | 52 | console.log(); 53 | } 54 | -------------------------------------------------------------------------------- /lib/binary_protocol/constants.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const TTLTimeUnit = { 11 | HOURS: 1, 12 | DAYS: 2 13 | }; 14 | 15 | const Type = { 16 | ARRAY: 0, 17 | BINARY: 1, 18 | BOOLEAN: 2, 19 | DOUBLE: 3, 20 | INTEGER: 4, 21 | LONG: 5, 22 | MAP: 6, 23 | STRING: 7, 24 | TIMESTAMP: 8, 25 | NUMBER: 9, 26 | JSON_NULL: 10, 27 | NULL: 11, 28 | EMPTY: 12 29 | }; 30 | 31 | const OpCode = { 32 | DELETE: 0, 33 | DELETE_IF_VERSION: 1, 34 | GET: 2, 35 | PUT: 3, 36 | PUT_IF_ABSENT: 4, 37 | PUT_IF_PRESENT: 5, 38 | PUT_IF_VERSION: 6, 39 | QUERY: 7, 40 | PREPARE: 8, 41 | WRITE_MULTIPLE: 9, 42 | MULTI_DELETE: 10, 43 | GET_TABLE: 11, 44 | GET_INDEXES: 12, 45 | GET_TABLE_USAGE: 13, 46 | LIST_TABLES: 14, 47 | TABLE_REQUEST: 15, 48 | SCAN: 16, 49 | INDEX_SCAN: 17, 50 | CREATE_TABLE: 18, 51 | ALTER_TABLE: 19, 52 | DROP_TABLE: 20, 53 | CREATE_INDEX: 21, 54 | DROP_INDEX: 22, 55 | /* added in V2 */ 56 | SYSTEM_REQUEST: 23, 57 | SYSTEM_STATUS_REQUEST: 24, 58 | /* added in V4 */ 59 | ADD_REPLICA: 33, 60 | DROP_REPLICA: 34, 61 | GET_REPLICA_STATS: 35 62 | }; 63 | 64 | const MathContext = { 65 | NONE: 0, 66 | DECIMAL32: 1, 67 | DECIMAL64: 2, 68 | DECIMAL128: 3, 69 | UNLIMITED: 4, 70 | CUSTOM: 5 71 | }; 72 | 73 | MathContext.DEFAULT = MathContext.DECIMAL64; 74 | 75 | module.exports = { 76 | TTLTimeUnit, 77 | Type, 78 | OpCode, 79 | MathContext 80 | }; 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oracle-nosqldb", 3 | "version": "5.5.2", 4 | "description": "Node.js driver for Oracle NoSQL Database", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "directories": { 8 | "doc": "./doc", 9 | "lib": "./lib", 10 | "src": "./src", 11 | "test": "./test", 12 | "examples": "./examples" 13 | }, 14 | "files": [ 15 | "README.md", 16 | "THIRD_PARTY_LICENSES.txt", 17 | "index.js", 18 | "index.d.ts", 19 | "lib/**/*.js", 20 | "src/**/*.ts", 21 | "examples/**" 22 | ], 23 | "scripts": { 24 | "test:mocha": "mocha", 25 | "test:tsc": "tsc --project test/unit/types", 26 | "test": "npm-run-all -nsc \"test:mocha -- {@}\" test:tsc --", 27 | "docs": "typedoc" 28 | }, 29 | "keywords": [ 30 | "oracle", 31 | "cloud", 32 | "oracle-cloud", 33 | "nosql", 34 | "database" 35 | ], 36 | "devDependencies": { 37 | "@knodes/typedoc-plugin-pages": "^0.23.4", 38 | "@types/node": "^12.12.6", 39 | "chai": "^4.3.7", 40 | "chai-as-promised": "^7.1.1", 41 | "compare-versions": "^6.1.1", 42 | "decimal.js": "^10.6.0", 43 | "eslint": "^9.35.0", 44 | "@eslint/js": "^9.35.0", 45 | "eslint-plugin-import": "^2.32.0", 46 | "expect-type": "^0.15.0", 47 | "geolib": "^3.3.4", 48 | "globals": "^16.4.0", 49 | "lodash": "^4.17.21", 50 | "mocha": "^11.7.2", 51 | "mock-fs": "^5.5.0", 52 | "npm-run-all": "^4.1.5", 53 | "sinon": "^21.0.0", 54 | "typedoc": "^0.23.28", 55 | "typescript": "~5.0.4" 56 | }, 57 | "overrides": { 58 | "@babel/parser": "7.14.7" 59 | }, 60 | "engines": { 61 | "node": ">=12.0.0" 62 | }, 63 | "author": "Oracle", 64 | "license": "UPL-1.0", 65 | "repository": {} 66 | } 67 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const con = require('./lib/constants'); 11 | const dur = require('./lib/durability'); 12 | const err = require('./lib/error'); 13 | 14 | exports.NoSQLClient =require('./lib/nosql_client'); 15 | exports.PreparedStatement = require('./lib/stmt').PreparedStatement; 16 | exports.ServiceType = con.ServiceType; 17 | exports.Region = require('./lib/region'); 18 | exports.Consistency = con.Consistency; 19 | exports.CapacityMode = con.CapacityMode; 20 | exports.ReplicaAckPolicy = dur.ReplicaAckPolicy; 21 | exports.Durabilities = dur.Durabilities; 22 | exports.SyncPolicy = dur.SyncPolicy; 23 | exports.ScanDirection = con.ScanDirection; 24 | exports.TableState = con.TableState; 25 | exports.AdminState = con.AdminState; 26 | exports.StatsLevel = con.StatsLevel; 27 | exports.ErrorCode = require('./lib/error_code'); 28 | exports.NoSQLError = err.NoSQLError; 29 | exports.NoSQLArgumentError = err.NoSQLArgumentError; 30 | exports.NoSQLProtocolError = err.NoSQLProtocolError; 31 | exports.NoSQLUnsupportedProtocolError = err.NoSQLUnsupportedProtocolError; 32 | exports.NoSQLNetworkError = err.NoSQLNetworkError; 33 | exports.NoSQLServiceError = err.NoSQLServiceError; 34 | exports.NoSQLTimeoutError = err.NoSQLTimeoutError; 35 | exports.NoSQLAuthorizationError = err.NoSQLAuthorizationError; 36 | exports.NoSQLQueryError = err.NoSQLQueryError; 37 | exports.TTLUtil = require('./lib/ttl_util'); 38 | exports.IAMAuthorizationProvider = require('./lib/auth/iam/auth_provider'); 39 | exports.KVStoreAuthorizationProvider = 40 | require('./lib/auth/kvstore/auth_provider'); 41 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security vulnerabilities 2 | 3 | Oracle values the independent security research community and believes that 4 | responsible disclosure of security vulnerabilities helps us ensure the security 5 | and privacy of all our users. 6 | 7 | Please do NOT raise a GitHub Issue to report a security vulnerability. If you 8 | believe you have found a security vulnerability, please submit a report to 9 | [secalert_us@oracle.com][1] preferably with a proof of concept. Please review 10 | some additional information on [how to report security vulnerabilities to Oracle][2]. 11 | We encourage people who contact Oracle Security to use email encryption using 12 | [our encryption key][3]. 13 | 14 | We ask that you do not use other channels or contact the project maintainers 15 | directly. 16 | 17 | Non-vulnerability related security issues including ideas for new or improved 18 | security features are welcome on GitHub Issues. 19 | 20 | ## Security updates, alerts and bulletins 21 | 22 | Security updates will be released on a regular cadence. Many of our projects 23 | will typically release security fixes in conjunction with the 24 | Oracle Critical Patch Update program. Additional 25 | information, including past advisories, is available on our [security alerts][4] 26 | page. 27 | 28 | ## Security-related information 29 | 30 | We will provide security related information such as a threat model, considerations 31 | for secure use, or any known security issues in our documentation. Please note 32 | that labs and sample code are intended to demonstrate a concept and may not be 33 | sufficiently hardened for production use. 34 | 35 | [1]: mailto:secalert_us@oracle.com 36 | [2]: https://www.oracle.com/corporate/security-practices/assurance/vulnerability/reporting.html 37 | [3]: https://www.oracle.com/security-alerts/encryptionkey.html 38 | [4]: https://www.oracle.com/security-alerts/ 39 | -------------------------------------------------------------------------------- /test/unit/types/utils.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | // Taken from here: 9 | // https://stackoverflow.com/questions/59535995/parameters-generic-of-overloaded-function-doesnt-contain-all-options/59538756#59538756 10 | 11 | export type Overloads = 12 | T extends { 13 | (...args: infer A1): infer R1; 14 | (...args: infer A2): infer R2; 15 | (...args: infer A3): infer R3; 16 | (...args: infer A4): infer R4; 17 | } ? [ 18 | (...args: A1) => R1, 19 | (...args: A2) => R2, 20 | (...args: A3) => R3, 21 | (...args: A4) => R4 22 | ] : T extends { 23 | (...args: infer A1): infer R1; 24 | (...args: infer A2): infer R2; 25 | (...args: infer A3): infer R3; 26 | } ? [ 27 | (...args: A1) => R1, 28 | (...args: A2) => R2, 29 | (...args: A3) => R3 30 | ] : T extends { 31 | (...args: infer A1): infer R1; 32 | (...args: infer A2): infer R2; 33 | } ? [ 34 | (...args: A1) => R1, 35 | (...args: A2) => R2 36 | ] : T extends { 37 | (...args: infer A1): infer R1 38 | } ? [ 39 | (...args: A1) => R1 40 | ] : any; 41 | 42 | export type OverloadedParameters = 43 | Overloads extends infer O ? 44 | { [K in keyof O]: Parameters any>> } : 45 | never; 46 | 47 | export type OverloadedReturnType = 48 | Overloads extends infer O ? 49 | { [K in keyof O]: ReturnType any>> } : 50 | never; 51 | 52 | // Taken from here: 53 | // https://dev.to/sarmunbustillo/typescript-series-length-of-a-tuple-4d8m 54 | 55 | export type Length = T["length"]; 56 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, 2023 Oracle and/or its affiliates. 2 | 3 | The Universal Permissive License (UPL), Version 1.0 4 | 5 | Subject to the condition set forth below, permission is hereby granted to any 6 | person obtaining a copy of this software, associated documentation and/or data 7 | (collectively the "Software"), free of charge and under any and all copyright 8 | rights in the Software, and any and all patent rights owned or freely 9 | licensable by each licensor hereunder covering either (i) the unmodified 10 | Software as contributed to or provided by such licensor, or (ii) the Larger 11 | Works (as defined below), to deal in both 12 | 13 | (a) the Software, and 14 | (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if 15 | one is included with the Software (each a "Larger Work" to which the Software 16 | is contributed by such licensors), 17 | 18 | without restriction, including without limitation the rights to copy, create 19 | derivative works of, display, perform, and distribute the Software and make, 20 | use, sell, offer for sale, import, export, have made, and have sold the 21 | Software and the Larger Work(s), and to sublicense the foregoing rights on 22 | either these or other terms. 23 | 24 | This license is subject to the following condition: 25 | The above copyright notice and either this complete permission notice or at 26 | a minimum a reference to the UPL must be included in all copies or 27 | substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | -------------------------------------------------------------------------------- /lib/nson_protocol/admin.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const OpCode = require('../binary_protocol/constants').OpCode; 11 | const Protocol = require('./protocol'); 12 | const Fields = require('./constants').Fields; 13 | 14 | class SystemRequestSerializer extends Protocol { 15 | 16 | static serialize(nw, req, serialVersion) { 17 | nw.startMap(); 18 | this.writeHeader(nw, OpCode.SYSTEM_REQUEST, serialVersion, req); 19 | nw.startMapField(Fields.PAYLOAD); 20 | 21 | if (Buffer.isBuffer(req.stmt)) { 22 | nw.writeBinaryField(Fields.STATEMENT, req.stmt); 23 | } else { 24 | const stmtBuf = Buffer.from(req.stmt); 25 | try { 26 | nw.writeBinaryField(Fields.STATEMENT, stmtBuf); 27 | } finally { 28 | stmtBuf.fill(0); 29 | } 30 | } 31 | 32 | nw.endMapField(); 33 | nw.endMap(); 34 | } 35 | 36 | static deserialize(dr, req) { 37 | return this.deserializeSystemResult(dr, req); 38 | } 39 | 40 | } 41 | 42 | class SystemStatusSerializer extends Protocol { 43 | 44 | static serialize(nw, req, serialVersion) { 45 | nw.startMap(); 46 | this.writeHeader(nw, OpCode.SYSTEM_STATUS_REQUEST, serialVersion, 47 | req); 48 | nw.startMapField(Fields.PAYLOAD); 49 | nw.writeStringField(Fields.OPERATION_ID, req.adminResult.operationId); 50 | nw.writeStringField(Fields.STATEMENT, req.adminResult.statement); 51 | nw.endMapField(); 52 | nw.endMap(); 53 | } 54 | 55 | static deserialize(dr, req) { 56 | return this.deserializeSystemResult(dr, req); 57 | } 58 | 59 | } 60 | 61 | module.exports = { 62 | SystemRequestSerializer, 63 | SystemStatusSerializer 64 | }; 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to this repository 2 | 3 | We welcome your contributions! There are multiple ways to contribute. 4 | 5 | ## Opening issues 6 | 7 | For bugs or enhancement requests, please file a GitHub issue unless it's 8 | security related. When filing a bug remember that the better written the bug is, 9 | the more likely it is to be fixed. If you think you've found a security 10 | vulnerability, do not raise a GitHub issue and follow the instructions in our 11 | [security policy](./SECURITY.md). 12 | 13 | ## Contributing code 14 | 15 | We welcome your code contributions. Before submitting code via a pull request, 16 | you will need to have signed the [Oracle Contributor Agreement][OCA] (OCA) and 17 | your commits need to include the following line using the name and e-mail 18 | address you used to sign the OCA: 19 | 20 | ```text 21 | Signed-off-by: Your Name 22 | ``` 23 | 24 | This can be automatically added to pull requests by committing with `--sign-off` 25 | or `-s`, e.g. 26 | 27 | ```text 28 | git commit --signoff 29 | ``` 30 | 31 | Only pull requests from committers that can be verified as having signed the OCA 32 | can be accepted. 33 | 34 | ## Pull request process 35 | 36 | 1. Ensure there is an issue created to track and discuss the fix or enhancement 37 | you intend to submit. 38 | 1. Fork this repository. 39 | 1. Create a branch in your fork to implement the changes. We recommend using 40 | the issue number as part of your branch name, e.g. `1234-fixes`. 41 | 1. Ensure that any documentation is updated with the changes that are required 42 | by your change. 43 | 1. Ensure that any samples are updated if the base image has been changed. 44 | 1. Submit the pull request. *Do not leave the pull request blank*. Explain exactly 45 | what your changes are meant to do and provide simple steps on how to validate. 46 | your changes. Ensure that you reference the issue you created as well. 47 | 1. We will assign the pull request to 2-3 people for review before it is merged. 48 | 49 | ## Code of conduct 50 | 51 | Follow the [Golden Rule](https://en.wikipedia.org/wiki/Golden_Rule). If you'd 52 | like more specific guidelines, see the [Contributor Covenant Code of Conduct][COC]. 53 | 54 | [OCA]: https://oca.opensource.oracle.com 55 | [COC]: https://www.contributor-covenant.org/version/1/4/code-of-conduct/ 56 | -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const path = require('path'); 11 | const NoSQLClient = require('../index').NoSQLClient; 12 | const ServiceType = require('../index').ServiceType; 13 | const NoSQLArgumentError = require('../index').NoSQLArgumentError; 14 | const AuthConfig = require('../lib/auth/config'); 15 | 16 | class TestConfig { 17 | 18 | static _initServiceType(cfg) { 19 | if (cfg.serviceType != null) { 20 | if (typeof cfg.serviceType === 'string') { 21 | cfg.serviceType = ServiceType[cfg.serviceType.toUpperCase()]; 22 | } 23 | if (!(cfg.serviceType instanceof ServiceType)) { 24 | throw new NoSQLArgumentError('Invalid service type', cfg); 25 | } 26 | return; 27 | } 28 | AuthConfig._chkInitProvider(cfg); 29 | AuthConfig._chkInitServiceType(cfg); 30 | } 31 | 32 | static getConfigObj(cfg) { 33 | if (typeof cfg === 'string') { 34 | //config file must be .json or .js 35 | if (cfg.includes('.')) { 36 | cfg = require(path.resolve(cfg)); 37 | } else { 38 | if (cfg === 'no-config') { 39 | return; 40 | } 41 | //otherwise we use deployment type with value of cfg 42 | //and default config 43 | cfg = { serviceType: cfg }; 44 | } 45 | } 46 | if (!cfg) { 47 | cfg = {}; 48 | } 49 | this._initServiceType(cfg); 50 | if (!cfg.serviceType) { 51 | return cfg; 52 | } 53 | const stProp = cfg.serviceType.name.toLowerCase(); 54 | return Object.assign({}, this.defaults[stProp], cfg); 55 | } 56 | 57 | static createNoSQLClientNoInit(cfg) { 58 | cfg = this.getConfigObj(cfg); 59 | return new NoSQLClient(cfg); 60 | } 61 | 62 | //For compatibility with internal tests that may require async 63 | //initialization 64 | static async createNoSQLClient(cfg) { 65 | return this.createNoSQLClientNoInit(cfg); 66 | } 67 | 68 | } 69 | 70 | TestConfig.defaults = { 71 | cloudsim: { 72 | endpoint: 'localhost:8080' 73 | }, 74 | cloud: {}, 75 | kvstore: {} 76 | }; 77 | 78 | module.exports = TestConfig; 79 | -------------------------------------------------------------------------------- /lib/query/sort.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const util = require('util'); 11 | const NoSQLQueryError = require('../error').NoSQLQueryError; 12 | const PlanIterator = require('./common').PlanIterator; 13 | const compareRows = require('./compare').compareRows; 14 | const sizeof = require('./utils').sizeof; 15 | const convertEmptyToNull = require('./utils').convertEmptyToNull; 16 | 17 | /** 18 | * Sorts MapValues based on their values on a specified set of top-level 19 | * fields. It is used by the driver to implement the geo_near function, 20 | * which sorts results by distance. 21 | */ 22 | class SortIterator extends PlanIterator { 23 | 24 | constructor(qpExec, step) { 25 | super(qpExec, step); 26 | this._inputIter = qpExec.makeIterator(step.input); 27 | this._rows = []; 28 | if (step.countMem) { 29 | this._mem = 0; 30 | } 31 | this._curr = -1; 32 | } 33 | 34 | static validateStep(step) { 35 | if (!step.input) { 36 | throw NoSQLQueryError.illegalState('Missing input iterator for \ 37 | SORT'); 38 | } 39 | } 40 | 41 | unsupportedComp(val) { 42 | return this.illegalArg(`Sort expression returns value not suitable \ 43 | for comparison: ${util.inspect(val)}`); 44 | } 45 | 46 | async next() { 47 | if (this._curr === -1) { 48 | while(await this._inputIter.next()) { 49 | const row = this._inputIter.result; 50 | this._rows.push(row); 51 | if (this._step.countMem) { 52 | const mem = sizeof(this, row); 53 | this._mem += mem; 54 | this._qpExec.incMem(mem); 55 | } 56 | } 57 | if (this._qpExec._needUserCont) { 58 | return false; 59 | } 60 | this._rows.sort((row1, row2) => 61 | compareRows(this, row1, row2, this._step.sortSpecs)); 62 | this._curr = 0; 63 | } 64 | if (this._curr < this._rows.length) { 65 | const res = this._rows[this._curr]; 66 | convertEmptyToNull(res); 67 | this._rows[this._curr++] = null; //release memory for the row 68 | this.result = res; 69 | return true; 70 | } 71 | return false; 72 | } 73 | 74 | reset() { 75 | this._rows = []; 76 | this._curr = -1; 77 | if (this._step.countMem) { 78 | this._qpExec.decMem(this._mem); 79 | this._mem = 0; 80 | } 81 | } 82 | 83 | } 84 | 85 | SortIterator._isAsync = true; 86 | 87 | module.exports = SortIterator; 88 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Oracle NoSQL Database Node.js SDK", 3 | "readme": "doc/index.md", 4 | "entryPoints": [ "./index.d.ts" ], 5 | "out": "doc/site", 6 | "includeVersion": true, 7 | "disableSources": true, 8 | "intentionallyNotExported": [ "Expand", "OpaqueTypeKey", "Tag", 9 | "OpaqueType", "ExtractByType" ], 10 | "validation": { 11 | "notExported": false, 12 | "invalidLink": true, 13 | "notDocumented": false 14 | }, 15 | "plugin": [ "@knodes/typedoc-plugin-pages" ], 16 | "externalSymbolLinkMappings": { 17 | "typescript": { 18 | "Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", 19 | "Date": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date" 20 | }, 21 | "global": { 22 | "Buffer": "https://nodejs.org/api/buffer.html", 23 | "Promise": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", 24 | "Date": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date", 25 | "Date.parse": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse", 26 | "Infinity": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Infinity", 27 | "Number.MAX_SAFE_INTEGER": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER", 28 | "Number.MIN_SAFE_INTEGER": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MIN_SAFE_INTEGER", 29 | "BigInt": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt", 30 | "SyntaxError": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError" 31 | }, 32 | "@types/node": { 33 | "Buffer": "https://nodejs.org/api/buffer.html" 34 | } 35 | }, 36 | "pluginPages": { 37 | "source": "doc/guides", 38 | "pages": [ 39 | { 40 | "name": "Guides", 41 | "children": [ 42 | { 43 | "name": "Connecting an Application to Oracle NoSQL Database Cloud Service", 44 | "source": "connect-cloud.md" 45 | }, 46 | { 47 | "name": "Connecting an Application to On-Premise Oracle NoSQL Database", 48 | "source": "connect-on-prem.md" 49 | }, 50 | { 51 | "name": "Working With Tables", 52 | "source": "tables.md" 53 | } 54 | ] 55 | } 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/unit/types/list_tables.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import { expectTypeOf } from "expect-type"; 9 | 10 | import { NoSQLClient, ListTablesOpt, ListTablesResult } 11 | from "../../../"; 12 | 13 | const client = new NoSQLClient("nosuchfile.json"); 14 | 15 | function testListTablesOpt() { 16 | let opt: ListTablesOpt = {}; 17 | 18 | opt.compartment = "c"; 19 | opt.timeout = 10000; 20 | opt.startIndex = 1; 21 | opt.limit = 10; 22 | opt.namespace = "namespace"; 23 | 24 | // @ts-expect-error Invalid type for compartment. 25 | opt.compartment = 1; 26 | // @ts-expect-error Invalid type for namespace. 27 | opt.namespace = 1; 28 | // @ts-expect-error Invalid type for timeout. 29 | opt.timeout = "10000"; 30 | // @ts-expect-error Invalid type for startIndex. 31 | opt.startIndex = "1"; 32 | // @ts-expect-error Invalid type for limit. 33 | opt.limit = true; 34 | // @ts-expect-error Invalid type for namespace. 35 | opt.namespace = true; 36 | // @ts-expect-error Invalid type for namespace. 37 | opt.namespace = 2; 38 | // @ts-expect-error Invalid extra property. 39 | opt.startTime = 0; 40 | } 41 | 42 | function testListTablesResult(res: ListTablesResult) { 43 | expectTypeOf(res.tables).toEqualTypeOf(); 44 | expectTypeOf(res.lastIndex).toBeNumber(); 45 | 46 | // all properties of ListTablesResult must be read-only 47 | expectTypeOf>() 48 | .toEqualTypeOf(); 49 | 50 | // @ts-expect-error Invalid property in TableUsageResult. 51 | res.last; 52 | } 53 | 54 | async function testListTables() { 55 | let res: ListTablesResult; 56 | 57 | expectTypeOf(client.listTables).toBeFunction(); 58 | expectTypeOf(client.listTables).parameters 59 | .toEqualTypeOf<[ListTablesOpt?]>(); 60 | expectTypeOf(client.listTables).parameter(0) 61 | .toEqualTypeOf(); 62 | expectTypeOf(client.listTables).returns.not 63 | .toEqualTypeOf(); 64 | expectTypeOf(client.listTables).returns.resolves 65 | .toEqualTypeOf(); 66 | expectTypeOf(client.listTables).toBeCallableWith(); 67 | expectTypeOf(client.listTables).toBeCallableWith({ timeout: 1 }); 68 | expectTypeOf(client.listTables).toBeCallableWith({ startIndex: 10, 69 | namespace: "namespace" }); 70 | 71 | res = await client.listTables(); 72 | res = await client.listTables({ compartment: "compartment", 73 | timeout: 5000, startIndex: 2 }); 74 | 75 | // @ts-expect-error Invalid parameter tableName. 76 | client.listTables("table"); 77 | // @ts-expect-error Invalid table name. 78 | client.listTables({ namespace: 0 }); 79 | } 80 | -------------------------------------------------------------------------------- /lib/query/min_heap.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl 5 | * 6 | * Please see LICENSE.txt file included in the top-level directory of the 7 | * appropriate download for a copy of the license and additional information. 8 | */ 9 | 10 | 'use strict'; 11 | 12 | const assert = require('assert'); 13 | 14 | class MinHeap { 15 | 16 | constructor(cmp, arr) { 17 | this._cmp = cmp; 18 | if (arr) { 19 | this._a = arr; 20 | this._build(); 21 | } else { 22 | this._a = []; 23 | } 24 | } 25 | 26 | _parent(i) { 27 | return Math.floor((i - 1) / 2); 28 | } 29 | 30 | _left(i) { 31 | return 2 * i + 1; 32 | } 33 | 34 | _right(i) { 35 | return 2 * i + 2; 36 | } 37 | 38 | _comp(i, j) { 39 | return this._cmp(this._a[i], this._a[j]); 40 | } 41 | 42 | _swap(i, j) { 43 | const val = this._a[i]; 44 | this._a[i] = this._a[j]; 45 | this._a[j] = val; 46 | } 47 | 48 | _filterDown(i) { 49 | for(;;) { 50 | const l = this._left(i); 51 | const r = this._right(i); 52 | let min = l < this.size && this._comp(l, i) < 0 ? l : i; 53 | if (r < this.size && this._comp(r, min) < 0) { 54 | min = r; 55 | } 56 | if (i === min) { 57 | break; 58 | } 59 | this._swap(i, min); 60 | i = min; 61 | } 62 | } 63 | 64 | _filterUp(i) { 65 | while(i > 0) { 66 | let p = this._parent(i); 67 | if (this._comp(p, i) <= 0) { 68 | break; 69 | } 70 | this._swap(i, p); 71 | i = p; 72 | } 73 | } 74 | 75 | _build() { 76 | for(let i = Math.floor(this.size / 2); i >= 0; i--) { 77 | this._filterDown(i); 78 | } 79 | } 80 | 81 | _remove(i) { 82 | assert(i < this._a.length); 83 | this._a[i] = this._a.pop(); 84 | if (!i || this._comp(this._parent(i), i) < 0) { 85 | this._filterDown(i); 86 | } else { 87 | this._filterUp(i); 88 | } 89 | } 90 | 91 | get size() { 92 | return this._a.length; 93 | } 94 | 95 | peek() { 96 | return this._a[0]; 97 | } 98 | 99 | pop() { 100 | if (!this._a.length) { 101 | return; 102 | } 103 | const min = this._a[0]; 104 | const last = this._a.pop(); 105 | if (this._a.length) { 106 | this._a[0] = last; 107 | this._filterDown(0); 108 | } 109 | return min; 110 | } 111 | 112 | add(val) { 113 | this._a.push(val); 114 | this._filterUp(this._a.length - 1); 115 | } 116 | 117 | filter(cb) { 118 | return new MinHeap(this._cmp, this._a.filter(cb)); 119 | } 120 | 121 | } 122 | 123 | module.exports = MinHeap; 124 | -------------------------------------------------------------------------------- /lib/query/func.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const PlanIterator = require('./common').PlanIterator; 12 | const SQLFuncCode = require('./common').SQLFuncCode; 13 | const MinMaxAggregator = require('./value_aggr').MinMaxAggregator; 14 | const SumAggregator = require('./value_aggr').SumAggregator; 15 | const CollectAggregator = require('./value_aggr').CollectAggregator; 16 | 17 | class AggrFuncIterator extends PlanIterator { 18 | 19 | _aggregator; 20 | 21 | constructor(qpExec, step) { 22 | super(qpExec, step); 23 | this._inputIter = qpExec.makeIterator(step.input); 24 | assert(this._inputIter && !this._inputIter.isAsync()); 25 | } 26 | 27 | static validateStep(step) { 28 | this._validateStepInputSync(step); 29 | } 30 | 31 | next() { 32 | for(;;) { 33 | if (!this._inputIter.next()) { 34 | return true; 35 | } 36 | const val = this._inputIter.result; 37 | this._aggregator.aggregate(val); 38 | } 39 | } 40 | 41 | reset(resetRes) { 42 | this._inputIter.reset(); 43 | if (resetRes) { 44 | this._aggregator.reset(); 45 | } 46 | } 47 | 48 | //Aggregate function iterators do not use the result register. 49 | get result() { 50 | return this._aggregator.result; 51 | } 52 | 53 | //This should never be called for aggregate function iterators. 54 | set result(val) { 55 | assert(false); 56 | } 57 | } 58 | 59 | /* 60 | * any_atomic min(any*) 61 | * any_atomic max(any*) 62 | * 63 | * Implements the MIN/MAX aggregate functions. It is needed by the driver 64 | * to compute the total min/max from the partial mins/maxs received from the 65 | * proxy. 66 | */ 67 | class FuncMinMaxIterator extends AggrFuncIterator { 68 | constructor(qpExec, step) { 69 | super(qpExec, step); 70 | this._aggregator = new MinMaxAggregator(this, undefined, 71 | step.funcCode === SQLFuncCode.FN_MIN); 72 | } 73 | } 74 | 75 | /* 76 | * any_atomic sum(any*) 77 | * 78 | * Implements the SUM aggregate function. It is needed by the driver to 79 | * re-sum partial sums and counts received from the proxy. 80 | */ 81 | class FuncSumIterator extends AggrFuncIterator { 82 | constructor(qpExec, step) { 83 | super(qpExec, step); 84 | this._aggregator = new SumAggregator(this); 85 | } 86 | } 87 | 88 | class FuncCollectIterator extends AggrFuncIterator { 89 | constructor(qpExec, step) { 90 | super(qpExec, step); 91 | this._mem = 0; 92 | this._aggregator = new CollectAggregator(this, val => { 93 | this._mem += val; 94 | this._qpExec.incMem(val); 95 | }, step.isDistinct, this._qpExec.opt._testMode); 96 | } 97 | } 98 | 99 | module.exports = { 100 | FuncMinMaxIterator, 101 | FuncSumIterator, 102 | FuncCollectIterator 103 | }; 104 | -------------------------------------------------------------------------------- /test/unit/delete_range_tests.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const Utils = require('./query_utils'); 11 | const AllTypesTest = require('./data_tests').AllTypesTest; 12 | const ROWS_PER_SHARD = require('./data_tests').ROWS_PER_SHARD; 13 | 14 | const DELETE_RANGE_TESTS = [ 15 | { 16 | desc: 'delete test 1', 17 | __proto__: new AllTypesTest(ROWS_PER_SHARD + 8), 18 | testCases: [ 19 | { 20 | desc: 'All keys in the shard, no field range', 21 | key: { shardId: 0 }, 22 | rowIds: new Set(Utils.range(ROWS_PER_SHARD)) 23 | }, 24 | { 25 | desc: 'One key only, no field range', 26 | key: { shardId: 0, pkString: 'id1' }, 27 | rowIds: new Set([ 1 ]) 28 | }, 29 | { 30 | desc: 'One key only, non-existing', 31 | key: { shardId: 0, pkString: 'nosuchvalue' }, 32 | rowIds: new Set() 33 | }, 34 | { 35 | desc: 'Field range, non-existing', 36 | key: { shardId: 0 }, 37 | fieldRange: { 38 | fieldName: 'pkString', 39 | startWith: 'nosuchvalue', 40 | //endWith: 'nosuchvalueeither' 41 | endsWith: 'nosuch' 42 | }, 43 | rowIds: new Set() 44 | }, 45 | { 46 | desc: 'Field range, left and right inclusive', 47 | key: { shardId: 0 }, 48 | fieldRange: { 49 | fieldName: 'pkString', 50 | startWith: 'id1', 51 | endWith: 'idididididididid8' 52 | }, 53 | rowIds: new Set(Utils.range(1, 9)) 54 | }, 55 | { 56 | desc: 'Field range, left, right exclusive', 57 | key: { shardId: 0 }, 58 | fieldRange: { 59 | fieldName: 'pkString', 60 | startAfter: 'id1', 61 | endBefore: 'idididididididid8' 62 | }, 63 | rowIds: new Set(Utils.range(2, 8)) 64 | }, 65 | { 66 | desc: 'Field range, left inclusive, right exclusive', 67 | key: { shardId: 0 }, 68 | fieldRange: { 69 | fieldName: 'pkString', 70 | startWith: 'id1', 71 | endBefore: 'idididididididid8' 72 | }, 73 | rowIds: new Set(Utils.range(1, 8)) 74 | }, 75 | { 76 | desc: 'Field range, left exclusive, right inclusive', 77 | key: { shardId: 0 }, 78 | fieldRange: { 79 | fieldName: 'pkString', 80 | startWith: 'id1', 81 | endBefore: 'idididididididid8' 82 | }, 83 | rowIds: new Set(Utils.range(1, 8)) 84 | }, 85 | ] 86 | } 87 | ]; 88 | 89 | module.exports = DELETE_RANGE_TESTS; 90 | -------------------------------------------------------------------------------- /lib/auth/iam/instance_metadata.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | const HttpConstants = require('../../constants').HttpConstants; 9 | const NoSQLServiceError = require('../../error').NoSQLServiceError; 10 | const NoSQLAuthorizationError = 11 | require('../../error').NoSQLAuthorizationError; 12 | const Region = require('../../region'); 13 | const HttpClient = require('../http_client'); 14 | 15 | /* Instance metadata service base URL */ 16 | const METADATA_SERVICE_BASE_URL = 'http://169.254.169.254/opc/v2/'; 17 | const FALLBACK_METADATA_SERVICE_URL = 'http://169.254.169.254/opc/v1/'; 18 | 19 | /* The authorization header need to send to metadata service since V2 */ 20 | const AUTHORIZATION_HEADER_VALUE = 'Bearer Oracle'; 21 | 22 | class InstanceMetadataClient { 23 | 24 | constructor(timeout) { 25 | this._timeout = timeout; 26 | this._httpClient = new HttpClient(null, false); 27 | } 28 | 29 | async getValue(path, desc) { 30 | let chkFallback; 31 | if (this._metadataUrl == null) { 32 | this._metadataUrl = METADATA_SERVICE_BASE_URL; 33 | chkFallback = true; 34 | } 35 | 36 | const req = { 37 | url: this._metadataUrl + path, 38 | method: HttpConstants.GET, 39 | headers: { 40 | [HttpConstants.AUTHORIZATION]: AUTHORIZATION_HEADER_VALUE 41 | }, 42 | timeout: this._timeout 43 | }; 44 | 45 | try { 46 | return await this._httpClient.request(req); 47 | } catch(err) { 48 | if (chkFallback && err instanceof NoSQLServiceError && 49 | err.statusCode === HttpConstants.HTTP_NOT_FOUND) { 50 | this._metadataUrl = FALLBACK_METADATA_SERVICE_URL; 51 | req.url = this._metadataUrl + path; 52 | try { 53 | return await this._httpClient.request(req); 54 | } catch(err2) { 55 | throw NoSQLAuthorizationError._httpError( 56 | `Unable to get ${desc} from instance metadata \ 57 | ${METADATA_SERVICE_BASE_URL}, error: ${err2.message}`, err2); 58 | } 59 | } else { 60 | throw NoSQLAuthorizationError._httpError( 61 | `Unable to get ${desc} from instance metadata \ 62 | ${METADATA_SERVICE_BASE_URL} or fall back to \ 63 | ${FALLBACK_METADATA_SERVICE_URL}, error: ${err.message}`, err); 64 | } 65 | } 66 | } 67 | 68 | //Get region from IMDS and cache it. 69 | async getRegion(ignoreNotFound) { 70 | if (this._region != null) { 71 | return this._region; 72 | } 73 | const res = await this.getValue('instance/region', 'region'); 74 | this._region = Region.fromRegionCodeOrId(res); 75 | 76 | if (this._region == null && !ignoreNotFound) { 77 | throw NoSQLAuthorizationError.illegalState(`Missing or unknown \ 78 | instance region: ${res}`); 79 | } 80 | 81 | return this._region; 82 | } 83 | 84 | close() { 85 | this._httpClient.shutdown(); 86 | } 87 | 88 | } 89 | 90 | module.exports = InstanceMetadataClient; 91 | -------------------------------------------------------------------------------- /test/smoke.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const util = require('util'); 11 | const nosqldb = require('../index'); 12 | const ErrorCode = nosqldb.ErrorCode; 13 | const TableState = nosqldb.TableState; 14 | const TestConfig = require('./utils').TestConfig; 15 | 16 | //If argv[2] not provided, defaults to cloudsim 17 | const config = process.argv[2]; 18 | 19 | async function async_smoke() { 20 | let client; 21 | try { 22 | client = await TestConfig.createNoSQLClient(config); 23 | process.stdout.write('NoSQLClient: '); 24 | console.log(client); 25 | let res; 26 | try { 27 | res = await client.tableDDL( 28 | 'CREATE TABLE items(id INTEGER, name STRING, price number, ' + 29 | 'PRIMARY KEY(id))', { tableLimits: { 30 | readUnits: 1, 31 | writeUnits: 5, 32 | storageGB: 1 33 | }}); 34 | process.stdout.write('tableDDL: '); 35 | console.log('tableDDL: ' + util.inspect(res)); 36 | await client.forCompletion(res); 37 | console.log('forCompletion: ' + util.inspect(res)); 38 | } catch(err) { //table exists 39 | if (err.errorCode == ErrorCode.TABLE_EXISTS) { 40 | console.log('Table already exists'); 41 | res = await client.forTableState('items', TableState.ACTIVE); 42 | console.log('forTableState: ' + util.inspect(res)); 43 | } else { 44 | throw err; 45 | } 46 | } 47 | res = await client.put('items', { id: 1, name: 'item1', price: '1.1'}); 48 | console.log('put: ' + util.inspect(res)); 49 | res = await client.put('items', { id: 2, name: 'item2', price: '1.2'}); 50 | console.log('put: ' + util.inspect(res)); 51 | res = await client.put('items', { id: 3, name: 'item3'}); 52 | console.log('put: ' + util.inspect(res)); 53 | res = await client.get('items', { id: 1}); 54 | console.log('get: ' + util.inspect(res)); 55 | res = await client.get('items', { id: 2}); 56 | console.log('get: ' + util.inspect(res)); 57 | res = await client.get('items', { id: 3}); 58 | console.log('get: ' + util.inspect(res)); 59 | res = await client.delete('items', { id: 2}); 60 | console.log('delete: ' + util.inspect(res)); 61 | res = await client.get('items', { id: 2 }); 62 | console.log('get: ' + util.inspect(res)); 63 | console.log(`row exists: ${res.row != null}`); 64 | res = await client.tableDDL('DROP TABLE items'); 65 | console.log('tableDDL: ' + util.inspect(res)); 66 | res = await client.forCompletion(res); 67 | console.log('forCompletion: ' + util.inspect(res)); 68 | } catch(err) { 69 | console.log(err.stack); 70 | for(let cause = err.cause; cause; cause = cause.cause) { 71 | console.log('Caused by -->'); 72 | console.log(cause.stack); 73 | } 74 | if (err.operation) { 75 | console.log('From:'); 76 | console.log(err.operation); 77 | } 78 | } finally { 79 | if (client) { 80 | client.close(); 81 | } 82 | } 83 | } 84 | 85 | async_smoke(); 86 | -------------------------------------------------------------------------------- /lib/nson_protocol/protocol_manager.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const BinaryProtocolManager = require('../binary_protocol/protocol_manager'); 11 | const NsonWriter = require('./writer'); 12 | const NsonReader = require('./reader'); 13 | const ErrorCode = require('../error_code'); 14 | const NoSQLUnsupportedProtocolError = require('../error') 15 | .NoSQLUnsupportedProtocolError; 16 | const TableSerializers = require('./table'); 17 | const DMLSerializers = require('./dml'); 18 | const QuerySerializers = require('./query'); 19 | const AdminSerializers = require('./admin'); 20 | 21 | const V4 = 4; 22 | 23 | class ProtocolManager extends BinaryProtocolManager { 24 | 25 | static get serialVersion() { 26 | return V4; 27 | } 28 | 29 | static decrementSerialVersion() { 30 | return false; 31 | } 32 | 33 | static getWriter(buf) { 34 | //Is this too much optimization? 35 | return !buf._nw ? (buf._nw = new NsonWriter(buf)) : buf._nw.reset(); 36 | } 37 | 38 | static getReader(buf) { 39 | return !buf._nr ? (buf._nr = new NsonReader(buf)) : buf._nr.reset(); 40 | } 41 | 42 | //Nson request always starts with serial version. 43 | static startWrite(writer) { 44 | writer.dataWriter.writeInt16BE(this.serialVersion); 45 | } 46 | 47 | //In Nson, the error information is part of the top-level Nson map. 48 | static startRead(reader, req) { 49 | const dr = reader.dataReader; 50 | var code = dr.readByte(); 51 | 52 | //If the client is connected to a pre-V4 server, the following 53 | //error codes can be returned by the pre-V4 servers: 54 | //V3: UNSUPPORTED_PROTOCOL (24) 55 | //V2: BAD_PROTOCOL_MESSAGE (17) 56 | //Neither of these currently maps to any valid Nson type, so we 57 | //know the server is not speaking V4 protocol. We can throw 58 | //NoSQLUnsupportedProtocolError so that the protocol serial 59 | //version will be decremented accordingly. 60 | if (code === ErrorCode.UNSUPPORTED_PROTOCOL.ordinal || 61 | code === ErrorCode.BAD_PROTOCOL_MESSAGE.ordinal) { 62 | throw new NoSQLUnsupportedProtocolError( 63 | `Unsupported protocol version ${this.serialVersion}`, null, 64 | req); 65 | } 66 | 67 | dr.offset = 0; 68 | } 69 | 70 | } 71 | 72 | //Serializers 73 | 74 | ProtocolManager._serializers = { 75 | GetOp: DMLSerializers.GetSerializer, 76 | PutOp: DMLSerializers.PutSerializer, 77 | DeleteOp: DMLSerializers.DeleteSerializer, 78 | MultiDeleteOp: DMLSerializers.MultiDeleteSerializer, 79 | WriteMultipleOp: DMLSerializers.WriteMultipleSerializer, 80 | TableDDLOp: TableSerializers.TableRequestSerializer, 81 | TableLimitsOp: TableSerializers.TableRequestSerializer, 82 | TableTagsOp: TableSerializers.TableRequestSerializer, 83 | AddReplicaOp: TableSerializers.ReplicaOpSerializer, 84 | DropReplicaOp: TableSerializers.ReplicaOpSerializer, 85 | GetTableOp: TableSerializers.GetTableSerializer, 86 | TableUsageOp: TableSerializers.TableUsageSerializer, 87 | ReplicaStatsOp: TableSerializers.ReplicaStatsSerializer, 88 | GetIndexesOp: TableSerializers.GetIndexesSerializer, 89 | ListTablesOp: TableSerializers.ListTablesSerializer, 90 | PrepareOp: QuerySerializers.PrepareSerializer, 91 | QueryOp: QuerySerializers.QuerySerializer, 92 | AdminDDLOp: AdminSerializers.SystemRequestSerializer, 93 | AdminStatusOp: AdminSerializers.SystemStatusSerializer, 94 | }; 95 | 96 | module.exports = ProtocolManager; 97 | -------------------------------------------------------------------------------- /lib/binary_protocol/protocol_manager.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | 12 | const ResizableBuffer = require('./buffer'); 13 | const DataReader = require('./reader'); 14 | const DataWriter = require('./writer'); 15 | const BinaryProtocol = require('./protocol'); 16 | const serializers = require('./serializers'); 17 | 18 | const V3 = 3; 19 | const V2 = 2; 20 | 21 | //We wish to handle multiple concurrent requests and at the same time 22 | //reuse the buffers (instead of allocating new buffer for 23 | //each new request). 24 | const _freeBuffers = []; 25 | 26 | class ProtocolManager { 27 | 28 | static _serialVersion = V3; 29 | 30 | static get serialVersion() { 31 | return this._serialVersion; 32 | } 33 | 34 | static decrementSerialVersion() { 35 | if (this._serialVersion === V3) { 36 | this._serialVersion = V2; 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | static get contentType() { 43 | return 'application/octet-stream'; 44 | } 45 | 46 | static get encoding() { 47 | return null; 48 | } 49 | 50 | static getBuffer() { 51 | if (!_freeBuffers.length) { 52 | return new ResizableBuffer(); 53 | } 54 | const buf = _freeBuffers.pop(); 55 | assert(buf._free); 56 | buf._free = false; 57 | buf.clear(); 58 | return buf; 59 | } 60 | 61 | static releaseBuffer(buf) { 62 | assert(!buf._free); 63 | buf._free = true; 64 | _freeBuffers.push(buf); 65 | } 66 | 67 | static addChunk(buf, chunk) { 68 | buf.appendBuffer(chunk); 69 | } 70 | 71 | static getWriter(buf) { 72 | //Is this too much optimization? 73 | return !buf._dw ? (buf._dw = new DataWriter(buf)) : buf._dw.reset(); 74 | } 75 | 76 | //Request content in a form suitable to write to stream. 77 | static getContent(buf) { 78 | return buf.slice(); 79 | } 80 | 81 | //Content length in bytes. 82 | static getContentLength(buf) { 83 | return buf.length; 84 | } 85 | 86 | static getReader(buf) { 87 | return !buf._dr ? (buf._dr = new DataReader(buf)) : buf._dr.reset(); 88 | } 89 | 90 | static startWrite() {} 91 | 92 | static startRead(reader, req) { 93 | const sc = reader.readByte(); 94 | if (sc !== 0) { 95 | throw BinaryProtocol.mapError(sc, reader.readString(), req); 96 | } 97 | } 98 | 99 | static serializer(op) { 100 | return this._serializers[op]; 101 | } 102 | 103 | } 104 | 105 | //Serializers 106 | 107 | ProtocolManager._serializers = { 108 | GetOp: serializers.GetSerializer, 109 | PutOp: serializers.PutSerializer, 110 | DeleteOp: serializers.DeleteSerializer, 111 | MultiDeleteOp: serializers.MultiDeleteSerializer, 112 | WriteMultipleOp: serializers.WriteMultipleSerializer, 113 | TableDDLOp: serializers.TableRequestSerializer, 114 | TableLimitsOp: serializers.TableRequestSerializer, 115 | GetTableOp: serializers.GetTableSerializer, 116 | TableUsageOp: serializers.TableUsageSerializer, 117 | GetIndexesOp: serializers.GetIndexesSerializer, 118 | ListTablesOp: serializers.ListTablesSerializer, 119 | PrepareOp: serializers.PrepareSerializer, 120 | QueryOp: serializers.QuerySerializer, 121 | AdminDDLOp: serializers.SystemRequestSerializer, 122 | AdminStatusOp: serializers.SystemStatusSerializer 123 | }; 124 | 125 | module.exports = ProtocolManager; 126 | -------------------------------------------------------------------------------- /examples/javascript/basic_example.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | /* 9 | * A simple example that 10 | * - creates a table 11 | * - inserts a row using the put() operation 12 | * - reads a row using the get() operation 13 | * - drops the table 14 | * 15 | * To run against the cloud simulator: 16 | * node basic_example.js cloudsim.json 17 | * 18 | * To run against the cloud service: 19 | * node basic_example.js config.json 20 | */ 21 | 'use strict'; 22 | 23 | const nosqldb = require('oracle-nosqldb'); 24 | 25 | const NoSQLClient = nosqldb.NoSQLClient; 26 | 27 | // Target table used by this example 28 | const TABLE_NAME = 'BasicExample'; 29 | 30 | // Usage: basic_example.js [] 31 | 32 | async function basicExample() { 33 | let client; 34 | try { 35 | // JSON config file path is an optional parameter. If not specified, 36 | // it is assumed we are using Oracle Cloud Service where credentials 37 | // are supplied in default OCI configuration file (~/.oci/config) 38 | // using default profile (DEFAULT). 39 | let configFile = process.argv[2]; 40 | client = new NoSQLClient(configFile); 41 | console.log('Created NoSQLClient instance'); 42 | 43 | await run(client); 44 | console.log('Success!'); 45 | } catch (err) { 46 | console.error(' Error: ' + err.message); 47 | if (err.operation) { 48 | console.error(' from: '); 49 | console.error(err.operation); 50 | } 51 | } finally { 52 | if (client) { 53 | client.close(); 54 | } 55 | } 56 | } 57 | 58 | /* 59 | * Create a table, read and write a record 60 | */ 61 | async function run(client) { 62 | const createDDL = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} \ 63 | (cookie_id LONG, audience_data JSON, PRIMARY KEY(cookie_id))`; 64 | console.log('Create table ' + TABLE_NAME); 65 | let res = await client.tableDDL(createDDL, { 66 | tableLimits: { 67 | readUnits: 1, 68 | writeUnits: 5, 69 | storageGB: 1 70 | } 71 | }); 72 | console.log(' Creating table %s', res.tableName); 73 | console.log(' Table state: %s', res.tableState.name); 74 | 75 | // Wait for the operation completion 76 | await client.forCompletion(res); 77 | console.log(' Table %s is created', res.tableName); 78 | console.log(' Table state: %s', res.tableState.name); 79 | 80 | // Write a record 81 | console.log('\nWrite a record'); 82 | res = await client.put(TABLE_NAME, { 83 | cookie_id: 456, 84 | audience_data: { 85 | ipaddr: '10.0.00.yyy', 86 | audience_segment: { 87 | sports_lover: '2019-01-05', 88 | foodie: '2018-12-31' 89 | } 90 | } 91 | }); 92 | if (res.consumedCapacity) { 93 | console.log(' Write used: %O', res.consumedCapacity); 94 | } 95 | 96 | // Read a record 97 | console.log('\nRead a record'); 98 | res = await client.get(TABLE_NAME, { cookie_id: 456 }); 99 | console.log(' Got record: %O', res.row); 100 | if (res.consumedCapacity) { 101 | console.log(' Read used: %O', res.consumedCapacity); 102 | } 103 | 104 | // Drop the table 105 | console.log('\nDrop table'); 106 | const dropDDL = `DROP TABLE ${TABLE_NAME}`; 107 | res = await client.tableDDL(dropDDL); 108 | console.log(' Dropping table %s', res.tableName); 109 | 110 | // Wait for the table to be removed 111 | await client.forCompletion(res); 112 | console.log(' Operation completed'); 113 | console.log(' Table state is %s', res.tableState.name); 114 | } 115 | 116 | basicExample(); 117 | -------------------------------------------------------------------------------- /src/types/events.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { EventEmitter } from "stream"; 9 | import type { TableState } from "./constants"; 10 | import type { NoSQLError } from "./error"; 11 | import type { Operation } from "./param"; 12 | import type { ConsumedCapacity } from "./result"; 13 | 14 | /** 15 | * This interface describes the events emitted by {@link NoSQLClient} 16 | */ 17 | export interface NoSQLClientEvents { 18 | /** 19 | * NoSQLClient error event. 20 | * 21 | * Emitted when any {@link NoSQLClient} method results in error. This 22 | * event is not emitted when automatic retries are performed, only when 23 | * the error is final. 24 | *

25 | * Also mote that this event will not be emitted if it has no listeners, 26 | * so it is not necessary to subscribe to it. 27 | * 28 | * @event 29 | * @param {NoSQLError} err Error of type NoSQLError or one of its subclasses 30 | * @param {Operation} op Object describing operation that 31 | * caused the error, see {@link Operation} 32 | */ 33 | error(err: NoSQLError, op: Operation): void; 34 | 35 | /** 36 | * NoSQLClient retryable event. 37 | * 38 | * Emitted when error from {@link NoSQLClient} operation will result in 39 | * automatic retry of operation. It will be emitted on each subsequent 40 | * retry. 41 | * @see {@link RetryConfig} for explanation of retries 42 | * 43 | * @event 44 | * @param {NoSQLError} err Error of type NoSQLError or one of its 45 | * subclasses that caused the retry 46 | * @param {Operation} op Object describing operation that caused the 47 | * error, see {@link Operation} 48 | * @param {number} numRetries Number of retries performed so far for this 49 | * operation, not counting the original API invokation or the retry about 50 | * to be performed 51 | */ 52 | retryable(err: NoSQLError, op: Operation, numRetries: number): void; 53 | 54 | /** 55 | * NoSQLClient consumedCapacity event. 56 | * 57 | * Emitted by {@link NoSQLClient} method calls that return 58 | * {@link ConsumedCapacity} as part of their result. These methods 59 | * include all data manipulation and query methods. This event may be 60 | * used to calculate relevant statistsics. 61 | * 62 | * @event 63 | * @param {ConsumedCapacity} consumedCapacity Capacity consumed by the 64 | * method call, {@link ConsumedCapacity} 65 | * @param {Operation} op Object describing operation that returned this 66 | * consumed capacity, see {@link Operation} 67 | */ 68 | consumedCapacity(consumedCapacity: ConsumedCapacity, op: Operation): void; 69 | 70 | /** 71 | * NoSQLClient tableState event. 72 | * 73 | * Emitted by {@link NoSQLClient} method calls that return table state as 74 | * part of their result, such as {@link NoSQLClient#getTable}, 75 | * {@link NoSQLClient#tableDDL} and {@link NoSQLClient#setTableLimits} and 76 | * also while table is polled waiting for DDL operation completion using 77 | * {@link NoSQLClient#forCompletion}. Can be used to perform actions on a 78 | * table reaching certain state. Note that this event may be emitted 79 | * mutliple times even while the table state did not change. 80 | * 81 | * @event 82 | * @param {string} tableName Table name 83 | * @param {TableState} tableState Current table state, see 84 | * {@link TableState} 85 | */ 86 | tableState(tableName: string, tableState: TableState): void; 87 | } 88 | 89 | declare module "nosql_client" { 90 | export interface NoSQLClient extends EventEmitter { 91 | on(event: EvName, 92 | listener: NoSQLClientEvents[EvName]): this; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /test/unit/list_tables.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const expect = require('chai').expect; 11 | const util = require('util'); 12 | 13 | const NoSQLArgumentError = require('../../index').NoSQLArgumentError; 14 | const ServiceType = require('../../index').ServiceType; 15 | const badOptions = require('./common').badOptions; 16 | const badMillis = require('./common').badMillis; 17 | const badNonNegInt32NotNull = require('./common').badNonNegInt32NotNull; 18 | const Utils = require('./utils'); 19 | const LIST_TABLES_TESTS = require('./test_schemas').LIST_TABLES_TESTS; 20 | 21 | const compartment = Utils.config.compartment; 22 | 23 | const badOpts = [ 24 | ...badOptions, 25 | ...badMillis.map(timeout => ({ timeout })), 26 | ...badNonNegInt32NotNull.map(startIndex => ({ startIndex, limit: 10 })), 27 | ...badNonNegInt32NotNull.map(limit => ({ limit })) 28 | ]; 29 | 30 | function testListTables(client, tbls, matchAll) { 31 | //Negative tests 32 | for(let badOpt of badOpts) { 33 | it(`listTables with invalid options: ${util.inspect(badOpt)}`, 34 | async function() { 35 | return expect(client.listTables(badOpt)).to.be.rejectedWith( 36 | NoSQLArgumentError); 37 | }); 38 | } 39 | 40 | //Positive tests 41 | const expTableNames = tbls.map(tbl => tbl.name).sort(); 42 | expect(expTableNames.length).to.be.at.least(3, 43 | 'Too few tables for listTables test (need >= 3)'); 44 | 45 | it('listTables', async function() { 46 | const res = await client.listTables(); 47 | expect(res.tables).to.be.an('array'); 48 | const tbls = matchAll ? res.tables : res.tables.filter( 49 | tblName => expTableNames.indexOf(tblName) !== -1 50 | ); 51 | expect(tbls).to.deep.equal(expTableNames); 52 | //TODO: remove if when lastIndex is implemented fully 53 | if (Utils.config.serviceType === ServiceType.CLOUDSIM) { 54 | expect(res.lastIndex).to.equal(res.tables.length); 55 | } 56 | const res2 = await client.listTables({ 57 | timeout: 20000, 58 | compartment 59 | }); 60 | expect(res2).to.deep.equal(res); 61 | }); 62 | it('listTables with limit', async function() { 63 | const tableNames = (await client.listTables()).tables; 64 | let limit = Math.floor(tableNames.length / 2); 65 | let res = await client.listTables({ limit }); 66 | expect(res.tables).to.be.an('array'); 67 | expect(res.tables.length).to.equal(limit); 68 | expect(res.lastIndex).to.equal(limit); 69 | let resTableNames = res.tables; 70 | res = await client.listTables( { 71 | timeout: 10000, 72 | startIndex: limit, 73 | limit: limit + 5 74 | }); 75 | expect(res.tables).to.be.an('array'); 76 | expect(res.tables.length).to.equal(tableNames.length - limit); 77 | expect(res.lastIndex).to.equal(tableNames.length); 78 | resTableNames = resTableNames.concat(res.tables); 79 | expect(resTableNames).to.deep.equal(tableNames); 80 | }); 81 | } 82 | 83 | function doTest(client, test) { 84 | describe(`Running ${test.desc}`, function() { 85 | before(async function() { 86 | for(let tbl of test.tables) { 87 | await Utils.dropTable(client, tbl); 88 | await Utils.createTable(client, tbl); 89 | } 90 | }); 91 | after(async function() { 92 | for(let tbl of test.tables) { 93 | await Utils.dropTable(client, tbl); 94 | } 95 | }); 96 | testListTables(client, test.tables); 97 | it('', () => {}); 98 | }); 99 | } 100 | 101 | Utils.runSequential('listTables tests', doTest, LIST_TABLES_TESTS); 102 | -------------------------------------------------------------------------------- /doc/layout.tmpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <?js= title ?> - <?js= ((env.conf.docdash.meta && env.conf.docdash.meta.title) || "Documentation") ?> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |

36 | 37 | 38 | 39 | 40 | 46 | 47 |
48 | 49 |

50 | 51 | 52 | 53 | 54 | 55 |
56 | 64 | 65 | 66 |
67 | 68 |
69 | 70 |
71 | Documentation generated by JSDoc using the docdash theme. 72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 87 | 88 | 91 | 92 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /lib/query/utils.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const EMPTY_VALUE = require('../constants').EMPTY_VALUE; 12 | 13 | function isNumeric(ctx, val) { 14 | return typeof val === 'number' || typeof val === 'bigint' || 15 | (ctx._dbNumber != null && ctx._dbNumber.isInstance(val)); 16 | } 17 | 18 | function resBuf2MapKey(buf) { 19 | //to be improved 20 | return buf.buffer.toString('latin1', 0, buf.length); 21 | } 22 | 23 | function sizeof(ctx, val) { 24 | if (val == null || val == EMPTY_VALUE) { 25 | return 0; 26 | } 27 | switch(typeof val) { 28 | case 'boolean': 29 | return 4; 30 | case 'number': 31 | return 8; 32 | case 'bigint': 33 | //From here: 34 | //https://stackoverflow.com/questions/54297544/v8-bigint-size-in-memory 35 | //We should not have any bigint greater than 64 bits. 36 | return 24; 37 | case 'string': 38 | return 2 * val.length; 39 | case 'object': { 40 | if (Buffer.isBuffer(val)) { 41 | return val.length; 42 | } 43 | if (val instanceof Date) { 44 | return 8; //rough estimate from testing 45 | } 46 | if (ctx._dbNumber != null && ctx._dbNumber.isInstance(val)) { 47 | //rough estimate for now, can be improved 48 | return sizeof(ctx, ctx._dbNumber.stringValue(val)); 49 | } 50 | let size = 0; 51 | if (Array.isArray(val)) { 52 | for(let i = 0; i < val.length; i++) { 53 | size += sizeof(ctx, val[i]); 54 | } 55 | } else { 56 | let ents = val instanceof Map ? val.entries() : 57 | Object.entries(val); 58 | for(let ent of ents) { 59 | const key = ent[0]; 60 | assert(typeof key === 'string'); 61 | size += 2 * key.length + sizeof(ctx, ent[1]); 62 | } 63 | } 64 | return size; 65 | } 66 | default: 67 | assert(false); 68 | } 69 | } 70 | 71 | //Ideally we should iterate only over sorting columns, but currently the 72 | //server does not convert EMPTY to null in any column when sorting takes 73 | //place, so we have to check all the columns. 74 | //We use undefined for SQL NULL. 75 | function convertEmptyToNull(row) { 76 | for(let key in row) { 77 | if (row[key] === EMPTY_VALUE) { 78 | row[key] = undefined; 79 | } 80 | } 81 | } 82 | 83 | //Make unique representation of numeric value so that values that are 84 | //query-equal are represented identically - this is to create unique string 85 | //key for grouping and duplicate elimination. Represent numerics as following: 86 | //1) If the value fits into JS number losslessly, represent as JS number. 87 | //2) If the value is of type bigint and cannot be represented as JS safe 88 | //integer, represent as dbNumber if dbNumber is defined, otherwise represent 89 | //as bigint. 90 | //3) Otherwise, if dbNumber is defined, represent as dbNumber. 91 | //Note that for simplicity we don't use bigint representation if dbNumber is 92 | //defined, this is to avoid checking and conversion from dbNumber to bigint. 93 | function normalizeNumeric(val, opt) { 94 | if (typeof val === 'bigint') { 95 | const numVal = Number(val); 96 | return BigInt(numVal) === val ? numVal : 97 | (opt._dbNumber ? opt._dbNumber.create(val.toString()) : val); 98 | } 99 | if (!opt._dbNumber || !opt._dbNumber.isInstance(val)) { 100 | return val; 101 | } 102 | const numVal = opt._dbNumber.numberValue(val); 103 | return opt._dbNumber.valuesEqual(val, numVal) ? numVal : val; 104 | } 105 | 106 | module.exports = { 107 | isNumeric, 108 | resBuf2MapKey, 109 | sizeof, 110 | convertEmptyToNull, 111 | normalizeNumeric 112 | }; 113 | -------------------------------------------------------------------------------- /test/unit/nson.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const Decimal = require('decimal.js'); 11 | const expect = require('chai').expect; 12 | const EMPTY_VALUE = require('../../lib/constants').EMPTY_VALUE; 13 | const NumberTypeHandler = require('../../lib/db_number'); 14 | const NsonReader = require('../../lib/nson_protocol/reader'); 15 | const NsonWriter = require('../../lib/nson_protocol/writer'); 16 | const NsonProtocol = require('../../lib/nson_protocol/protocol'); 17 | 18 | const DOUBLE_ZERO = Symbol('DOUBLE_ZERO'); 19 | 20 | const NSON_COMPAT_TESTS = [ 21 | { 22 | desc: 'compat-test1', 23 | nson: `BgAAAkAAAAAYiGludF92YWx1ZQT5BFmGaW50X21heAT7f///hoZpbnRfbWluBAS 24 | AAAB3iWxvbmdfdmFsdWUF/By+mRmbh2xvbmdfbWF4Bf9/////////hodsb25nX21pbgUAgAAAAAAAA 25 | HeLZG91YmxlX3ZhbHVlAz/zwIMSbpeNiWRvdWJsZV9tYXgDf+////////+JZG91YmxlX21pbgMAAAA 26 | AAAAAAYlkb3VibGVfTmFOA//4AAAAAAAAi251bWJlcl92YWx1ZQmJMjE0NzQ4MzY0N4tzdHJpbmdfd 27 | mFsdWUHhmFiY2RlZmeJdGltZV92YWx1ZQiXMjAxNy0wNy0xNVQxNToxODo1OS4xMjNainRpbWVfdmF 28 | sdWUxCJcyMDE3LTA3LTE1VDE1OjE4OjU5LjEyM1qKdGltZV92YWx1ZTIIlzE5MjctMDctMDVUMTU6M 29 | Dg6MDkuMTAwWop0aW1lX3ZhbHVlMwiXMTkyNy0wNy0wNVQwMDowMDowMC4wMDBainRpbWVfdmFsdWU 30 | 0CJcxOTI3LTA3LTA1VDAwOjAwOjAwLjAwMFqJdHJ1ZV92YWx1ZQIBimZhbHNlX3ZhbHVlAgCJbnVsb 31 | F92YWx1ZQuKZW1wdHlfdmFsdWUMi2JpbmFyeV92YWx1ZQGbYWJjZGVmZ0FCQ0RFRkdhYmNkZWZnQUJ 32 | DREVGR4htYXBfdmFsdWUGAAAADwAAAAKAYQSAgGIHgmRlZophcnJheV92YWx1ZQAAAAAOAAAABQSAB 33 | IEEggSDBIQ=`, 34 | value: { 35 | int_value: 1234, 36 | int_max: 2147483647, 37 | int_min: -2147483648, 38 | long_value: 123456789012n, 39 | long_max: 9223372036854775807n, 40 | long_min: -9223372036854775808n, 41 | double_value: 1.2345, 42 | double_max: 1.7976931348623157E308, 43 | double_min: 4.9E-324, 44 | double_NaN: NaN, 45 | number_value: new Decimal(2147483647), 46 | string_value: 'abcdefg', 47 | time_value: new Date('2017-07-15T15:18:59.123Z'), 48 | time_value1: new Date('2017-07-15T15:18:59.123Z'), 49 | time_value2: new Date('1927-07-05T15:08:09.100Z'), 50 | time_value3: new Date('1927-07-05T00:00:00.000Z'), 51 | time_value4: new Date('1927-07-05T00:00:00.000Z'), 52 | true_value: true, 53 | false_value: false, 54 | null_value: undefined, 55 | empty_value: EMPTY_VALUE, 56 | binary_value: Buffer.from( 57 | 'YWJjZGVmZ0FCQ0RFRkdhYmNkZWZnQUJDREVGRw==', 'base64'), 58 | map_value: { 59 | a: 1, 60 | b: 'def' 61 | }, 62 | array_value: [ 1, 2, 3, 4, 5 ] 63 | } 64 | } 65 | ]; 66 | 67 | const opt = { 68 | _dbNumber: new NumberTypeHandler({ dbNumber: Decimal }), 69 | longAsBigInt: true, 70 | _writeCustomFldVal: (nw, val) => { 71 | if (val === DOUBLE_ZERO) { 72 | nw.writeDouble(0); 73 | return true; 74 | } 75 | return false; 76 | } 77 | }; 78 | 79 | function testNsonCompat(test) { 80 | it('', function() { 81 | const nson = test.nson.replace(/\s/g, ''); 82 | const nsonBytes = Buffer.from(nson, 'base64'); 83 | const nr = new NsonReader(nsonBytes); 84 | nr.next(); 85 | const valueFromNson = NsonProtocol.readFieldValue(nr, opt); 86 | expect(valueFromNson).to.deep.equal(test.value); 87 | 88 | const nw = new NsonWriter(); 89 | NsonProtocol.writeFieldValue(nw, valueFromNson, opt); 90 | const valueToNson = nw.buffer.slice().toString('base64'); 91 | expect(valueToNson).to.equal(nson); 92 | }); 93 | } 94 | 95 | describe('Nson compatibility tests', function() { 96 | for(const test of NSON_COMPAT_TESTS) { 97 | testNsonCompat(test); 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /src/types/rate_limiter/simple_rate_limiter.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { RateLimiter } from "./rate_limiter"; 9 | 10 | /** 11 | * @classdesc 12 | * Cloud Service or Cloud Simulator only. 13 | *

14 | * Default implementation of rate limiter class used by the driver. 15 | * Two rate limiter instances are used per each table in use, one for reads 16 | * and another for writes. See {@link RateLimiter}. 17 | *

18 | * This implementation uses a token bucket based algorithm, although 19 | * the state is kept in terms of nano time instead of tokens. It is assumed 20 | * that the units refill at constant rate being equivalent to the set limit 21 | * of units per second. The state is kept in terms of nextNano which 22 | * is the time when the next operation can proceed without waiting, meaning 23 | * the limiter is at its limit. All operations issued before 24 | * nextNano will have to wait accordingly. Based on the value of 25 | * nextNano and the current time, the appropriate wait time may be 26 | * computed before the wait begins. If current time is >= nextNano, 27 | * no wait is needed. 28 | *

29 | * Note that when {@link SimpleRateLimiter#consumeUnits} is called, the 30 | * entered units will only affect the wait time of subsequent operations and 31 | * not the current operation, which will use the value of nextNano as 32 | * it was to determine its wait time. This should avoid needless wait time 33 | * when the operations come in rarely. 34 | *

Because every time when {@link SimpleRateLimiter#consumeUnits} is 35 | * called with units > 0, nextNano is pushed forward, the operations 36 | * will be effectively staggered in time accoridng to the order of their 37 | * arrival with no preferrential treatment given to any operation, thus 38 | * avoiding starvation. 39 | *

40 | * This limiter uses burst mode, allowing a set maximum number of stored units 41 | * that has not been used to be consumed immediately without waiting. This 42 | * value is expressed as maxBurstSecs or duration, which is 43 | * effectively a maximum number of seconds worth of unused stored units. 44 | * The minimum duration is internally bound such that at least one unused unit 45 | * may be consumed without waiting. The default value of duration is 46 | * 30 seconds. 47 | * 48 | * @see {@link RateLimiter} 49 | * @see {@link Config} 50 | */ 51 | export class SimpleRateLimiter implements RateLimiter { 52 | /** 53 | * Constructs an instance of SimpleRateLimiter 54 | * @param {number} [maxBurstSecs=30] Duration as described above 55 | */ 56 | constructor(maxBurstSecs?: number); 57 | 58 | /** 59 | * Implements {@link RateLimiter#consumeUnits} 60 | * @async 61 | * @see {@link RateLimiter} 62 | * @param {number} units Number of units to consume 63 | * @param {number} timeout Timeout in milliseconds 64 | * @param {boolean} consumeOnTimeout Whether to consume units on timeout 65 | * or throw an error, see {@link RateLimiter#consumeUnits} 66 | * @returns {Promise} Promise resolved with sleeping time in milliseconds 67 | * or rejected with {@link NoSQLTimeoutError} 68 | */ 69 | consumeUnits(units: number, timeout: number, consumeOnTimeout: boolean): 70 | Promise; 71 | 72 | /** 73 | * Implements {@link RateLimiter#setLimit}. Sets the limiter limit 74 | * (rate) in units per second. Also, enforces minimum duration to be able 75 | * to store at least one unused unit. When changing table limits, will 76 | * prorate any unused units according to the new limit. 77 | * @param {number} limit Limit in units 78 | */ 79 | setLimit(limit: number): void; 80 | 81 | /** 82 | * Implements {@link RateLimiter#onThrottle}. Called when throttling 83 | * error occurs when using this rate limiter instance. Current 84 | * implementation will remove any stored units by ensuring that 85 | * nextNano is at least the current time. 86 | */ 87 | onThrottle(): void; 88 | } 89 | -------------------------------------------------------------------------------- /lib/ops/admin.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const NoSQLArgumentError = require('../error').NoSQLArgumentError; 11 | const NoSQLProtocolError = require('../error').NoSQLProtocolError; 12 | const AdminState = require('../constants').AdminState; 13 | const isPlainObject = require('../utils').isPlainObject; 14 | const isPosInt32 = require('../utils').isPosInt32; 15 | const hasOwnProperty = require('../utils').hasOwnProperty; 16 | const Op = require('./op'); 17 | 18 | class AdminDDLOp extends Op { 19 | 20 | static applyDefaults(req, def) { 21 | super.applyDefaults(req, def); 22 | if (hasOwnProperty(req.opt, 'timeout')) { 23 | req.opt._ownsTimeout = true; 24 | } else { 25 | req.opt.timeout = def.ddlTimeout; 26 | } 27 | if (req.opt.complete && !hasOwnProperty(req.opt, 'delay')) { 28 | req.opt.delay = def.adminPollDelay; 29 | } 30 | } 31 | 32 | static _validateTimeout(req) { 33 | if ((!req.opt.complete || req.opt.timeout !== Infinity) && 34 | !isPosInt32(req.opt.timeout)) { 35 | throw new NoSQLArgumentError( 36 | `Invalid timeout for admin DDL: ${req.opt.timeout}`, req); 37 | } 38 | } 39 | 40 | static validate(req) { 41 | this._validateRequest(req); 42 | if ((!Buffer.isBuffer(req.stmt) && typeof req.stmt !== 'string') || 43 | !req.stmt.length) { 44 | throw new NoSQLArgumentError('Missing or invalid statement', req); 45 | } 46 | if (req.opt.complete) { 47 | this._validateDelay(req); 48 | } 49 | } 50 | 51 | static shouldRetry() { 52 | return false; 53 | } 54 | 55 | static onResult(client, req, res) { 56 | if (res.operationId == null && res.state !== AdminState.COMPLETE) { 57 | throw new NoSQLProtocolError('Missing operation id for \ 58 | incomplete admin result', null, req); 59 | } 60 | res._forAdmin = true; 61 | } 62 | 63 | } 64 | 65 | class AdminStatusOp extends Op { 66 | 67 | static validate(req) { 68 | this._validateRequest(req); 69 | const res = req.adminResult; 70 | if (!isPlainObject(res)) { 71 | throw new NoSQLArgumentError('Missing or invalid admin result', 72 | req); 73 | } 74 | if (res.operationId == null) { 75 | //If operationId is null, the request is not sent to the server, 76 | //see NoSQLClientImpl._adminStatus() 77 | if (res.state !== AdminState.COMPLETE) { 78 | throw new NoSQLArgumentError('Missing operation id for \ 79 | incomplete admin result', req); 80 | } 81 | } else if (typeof res.operationId !== 'string' || 82 | !res.operationId.length) { 83 | throw new NoSQLArgumentError('Invalid operation id', req); 84 | } 85 | if (res.statement != null && typeof res.statement !== 'string') { 86 | throw new NoSQLArgumentError('Invalid statememt', req); 87 | } 88 | } 89 | 90 | static onResult(client, req, res) { 91 | res._forAdmin = true; 92 | } 93 | 94 | } 95 | 96 | class AdminPollOp extends AdminStatusOp { 97 | 98 | static applyDefaults(req, def) { 99 | super.applyDefaults(req, def); 100 | if (!hasOwnProperty(req.opt, 'timeout')) { 101 | req.opt.timeout = def.adminPollTimeout; 102 | } 103 | if (!hasOwnProperty(req.opt, 'delay')) { 104 | req.opt.delay = def.adminPollDelay; 105 | } 106 | } 107 | 108 | static _validateTimeout(req) { 109 | if (req.opt.timeout !== Infinity && !isPosInt32(req.opt.timeout)) { 110 | throw new NoSQLArgumentError( 111 | `Invalid timeout for admin poll: ${req.opt.timeout}`, req); 112 | } 113 | } 114 | 115 | static validate(req) { 116 | super.validate(req); 117 | this._validateDelay(req); 118 | } 119 | 120 | } 121 | 122 | module.exports = { 123 | AdminDDLOp, 124 | AdminStatusOp, 125 | AdminPollOp 126 | }; 127 | -------------------------------------------------------------------------------- /lib/binary_protocol/writer.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const PackedInteger = require('./packed_integer'); 12 | const ResizableBuffer = require('./buffer'); 13 | 14 | /** 15 | * Utility classes to facilitate serialization/deserialization 16 | * 17 | * The numeric methods use packed_integer module which uses a format 18 | * that is always sorted. 19 | */ 20 | 21 | class DataWriter { 22 | constructor(buf) { 23 | this._buf = (buf instanceof ResizableBuffer) ? buf : 24 | new ResizableBuffer(buf); 25 | } 26 | 27 | get buffer() { 28 | return this._buf; 29 | } 30 | 31 | writeByte(value) { 32 | this._buf.writeUInt8(value, this._buf.length); 33 | } 34 | 35 | /** 36 | * Writes a packed integer to the buffer 37 | * 38 | * @param value the integer to be written 39 | */ 40 | writeInt(value) { 41 | this._buf.ensureExtraCapacity(PackedInteger.MAX_LENGTH); 42 | this._buf.length = PackedInteger.writeSortedInt(this._buf.buffer, 43 | this._buf.length, value); 44 | } 45 | 46 | /** 47 | * Writes a packed long to the buffer 48 | * 49 | * @param value the long to be written 50 | */ 51 | writeLong(value) { 52 | this._buf.ensureExtraCapacity(PackedInteger.MAX_LONG_LENGTH); 53 | this._buf.length = PackedInteger.writeSortedLong(this._buf.buffer, 54 | this._buf.length, value); 55 | } 56 | 57 | /** 58 | * Writes a string for reading by {@link #readString}, using standard UTF-8 59 | * format. The string may be null or empty. This code differentiates 60 | * between the two, maintaining the ability to round-trip null and empty 61 | * string values. 62 | * 63 | * The format is the standard UTF-8 format documented by RFC 2279. 65 | * 66 | *

Format: 67 | *

    68 | *
  1. ({@link #writePackedInt packed int}) string length, or -1 69 | * for null 70 | *
  2. [Optional] ({@code byte[]}) UTF-8 bytes 71 | *
72 | * 73 | * @param value the string or null 74 | */ 75 | writeString(value) { 76 | if (value == null) { //null or undefined 77 | return this.writeInt(-1); 78 | } 79 | assert(typeof value === 'string'); 80 | const b = Buffer.from(value, 'utf8'); 81 | this.writeInt(b.length); 82 | if (b.length > 0) { 83 | this._buf.appendBuffer(b); 84 | } 85 | } 86 | 87 | /** 88 | * Writes a possibly null binary as a {@link #writePackedInt 89 | * sequence length} followed by the array contents. 90 | * 91 | * @param array the byte array or null 92 | */ 93 | writeBinary(value) { 94 | const len = (value == null) ? -1 : value.length; 95 | this.writeInt(len); 96 | if (len > 0) { 97 | this._buf.appendBuffer(value); 98 | } 99 | } 100 | 101 | //Equivalent to writeByteArrayWithInt() in BinaryProtocol.java 102 | writeBinary2(value) { 103 | const len = value ? value.length : 0; 104 | this.writeInt32BE(len); 105 | if (len > 0) { 106 | this._buf.appendBuffer(value); 107 | } 108 | } 109 | 110 | writeBoolean(value) { 111 | this.writeByte(value ? 1 : 0); 112 | } 113 | 114 | writeDouble(value) { 115 | this._buf.ensureExtraCapacity(8); 116 | this._buf.length = this._buf.buffer.writeDoubleBE(value, 117 | this._buf.length); 118 | } 119 | 120 | writeDate(value) { 121 | assert(value instanceof Date); 122 | let s = value.toISOString(); 123 | this.writeString(s); 124 | } 125 | 126 | writeInt16BE(value) { 127 | this._buf.writeInt16BE(value, this._buf.length); 128 | } 129 | 130 | writeInt32BE(value) { 131 | this._buf.writeInt32BE(value, this._buf.length); 132 | } 133 | 134 | reset() { 135 | this._buf.length = 0; 136 | return this; 137 | } 138 | 139 | toString(encoding = 'utf8') { 140 | return this._buf.toString(encoding); 141 | } 142 | } 143 | 144 | module.exports = DataWriter; 145 | -------------------------------------------------------------------------------- /test/unit/auth/iam/oci_region.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const expect = require('chai').expect; 11 | const util = require('util'); 12 | const mockfs = require('mock-fs'); 13 | const NoSQLClient = require('../../../../index').NoSQLClient; 14 | const NoSQLArgumentError = require('../../../../index').NoSQLArgumentError; 15 | const defaultOCIFileLines = require('./config').defaultOCIFileLines; 16 | const Utils = require('../../utils'); 17 | const verifyEndpoint = require('../../common').verifyEndpoint; 18 | const writeFileLines = require('./utils').writeFileLines; 19 | const DEFAULT_OCI_DIR = require('./constants').DEFAULT_OCI_DIR; 20 | const DEFAULT_OCI_FILE = require('./constants').DEFAULT_OCI_FILE; 21 | 22 | const configNoRegion = { 23 | serviceType: 'CLOUD', 24 | auth: { 25 | iam: { 26 | configFile: DEFAULT_OCI_FILE, 27 | profileName: 'DEFAULT' 28 | } 29 | } 30 | }; 31 | 32 | const configs = [ undefined, null, configNoRegion ]; 33 | 34 | function verifyOCIRegion(client, region) { 35 | verifyEndpoint(client._client._url, null, region); 36 | } 37 | 38 | function testNegative() { 39 | describe('Missing region in OCI config negative test', function() { 40 | before(function() { 41 | writeFileLines(DEFAULT_OCI_FILE, defaultOCIFileLines); 42 | }); 43 | it('NoSQLClient(), OCI config without region', function() { 44 | expect(() => new NoSQLClient()).to.throw( 45 | NoSQLArgumentError); 46 | }); 47 | for(let cfg of configs) { 48 | it(`NoSQLClient(${util.inspect(cfg)}), OCI config without region`, 49 | function() { 50 | expect(() => new NoSQLClient(cfg)).to.throw( 51 | NoSQLArgumentError); 52 | }); 53 | } 54 | }); 55 | describe('Invalid region in OCI config negative test', function() { 56 | before(function() { 57 | writeFileLines(DEFAULT_OCI_FILE, [ ...defaultOCIFileLines, 58 | 'region=nosuchregion']); 59 | }); 60 | it('NoSQLClient(), OCI config with invalid region', function() { 61 | expect(() => new NoSQLClient()).to.throw( 62 | NoSQLArgumentError); 63 | }); 64 | for(let cfg of configs) { 65 | it(`NoSQLClient(${util.inspect(cfg)}), OCI config with invalid \ 66 | region`, function() { 67 | expect(() => new NoSQLClient(cfg)).to.throw( 68 | NoSQLArgumentError); 69 | }); 70 | } 71 | }); 72 | } 73 | 74 | const ociRegion1 = 'ap-mumbai-1'; 75 | const ociRegion2 = 'us-ashburn-1'; 76 | 77 | function testPositive() { 78 | describe('Region in OCI config positive test', function() { 79 | before(function() { 80 | writeFileLines(DEFAULT_OCI_FILE, [ ...defaultOCIFileLines, 81 | `region=${ociRegion1}`]); 82 | }); 83 | it('NoSQLClient(), OCI config with valid region', function() { 84 | const client = new NoSQLClient(); 85 | verifyOCIRegion(client, ociRegion1); 86 | }); 87 | for(let cfg of configs) { 88 | it(`NoSQLClient(${util.inspect(cfg)}), OCI config with valid \ 89 | region`, function() { 90 | const client = new NoSQLClient(cfg); 91 | verifyOCIRegion(client, ociRegion1); 92 | }); 93 | } 94 | it('NoSQLClient config and OCI config with different regions', 95 | function() { 96 | const client = new NoSQLClient( 97 | Object.assign({}, configNoRegion, 98 | { region: ociRegion2 })); 99 | verifyOCIRegion(client, ociRegion2); 100 | }); 101 | }); 102 | } 103 | 104 | //The driver loads this module after the test has already started, which would 105 | //not work with mocked file system. Instead, we pre-cache this module before 106 | //mockfs is invoked. 107 | require('../../../../lib/auth/iam/auth_provider'); 108 | 109 | if (!Utils.isOnPrem) { 110 | describe('Test region in OCI config file', function() { 111 | before(() => mockfs({ 112 | [DEFAULT_OCI_DIR] : {} 113 | })); 114 | after(() => mockfs.restore()); 115 | testNegative(); 116 | testPositive(); 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /lib/auth/kvstore/token_provider.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const isPosInt = require('../../utils').isPosInt; 12 | const HttpConstants = require('../../constants').HttpConstants; 13 | const NoSQLArgumentError = require('../../error').NoSQLArgumentError; 14 | const NoSQLTimeoutError = require('../../error').NoSQLTimeoutError; 15 | const NoSQLServiceError = require('../../error').NoSQLServiceError; 16 | const AuthError = require('../../error').NoSQLAuthorizationError; 17 | const HttpClient = require('../http_client'); 18 | 19 | const BASE_PATH = `/${HttpConstants.NOSQL_VERSION}/nosql/security`; 20 | 21 | const LOGIN_ENDPOINT = '/login'; 22 | 23 | const RENEW_ENDPOINT = '/renew'; 24 | 25 | const LOGOUT_ENDPOINT = '/logout'; 26 | 27 | class KVStoreTokenProvider { 28 | 29 | constructor(provider, cfg) { 30 | if (cfg.url == null) { 31 | throw new NoSQLArgumentError('Missing service endpoint', cfg); 32 | } 33 | assert(cfg.url instanceof URL); 34 | if (!cfg.url.protocol.startsWith('https')) { 35 | throw new NoSQLArgumentError(`Invalid protocol for \ 36 | authorization: ${cfg.url.protocol}, https is required`, cfg); 37 | } 38 | 39 | this._loginUrl = new URL(BASE_PATH + LOGIN_ENDPOINT, cfg.url); 40 | this._renewUrl = new URL(BASE_PATH + RENEW_ENDPOINT, cfg.url); 41 | this._logoutUrl = new URL(BASE_PATH + LOGOUT_ENDPOINT, cfg.url); 42 | 43 | this._timeout = provider._timeout; 44 | this._httpClient = new HttpClient(cfg.httpOpt); 45 | } 46 | 47 | async _doGet(req) { 48 | try { 49 | return await this._httpClient.request( 50 | Object.assign(req, { 51 | method: HttpConstants.GET, 52 | timeout: this._timeout 53 | })); 54 | } finally { 55 | this._httpClient.shutdown(); 56 | } 57 | } 58 | 59 | _parse(res) { 60 | try { 61 | res = JSON.parse(res); 62 | } catch(err) { 63 | throw AuthError.badProto('Failed to parse kvstore authentication \ 64 | token result', err); 65 | } 66 | if (typeof res.token !== 'string' || !res.token.length) { 67 | throw AuthError.badProto(`Token missing or invalid in \ 68 | kvstore authentication token result: ${res.token}`); 69 | } 70 | if (!isPosInt(res.expireAt)) { 71 | throw AuthError.badProto(`Expiration time missing or invalid in \ 72 | kvstore authentication token result: ${res.expireAt}`); 73 | } 74 | return res; 75 | } 76 | 77 | _handleError(err, action) { 78 | if (err instanceof NoSQLTimeoutError) { 79 | throw AuthError.timeout(`Failed to ${action}. Operation timed \ 80 | out, see the cause`, err); 81 | } 82 | if (err instanceof NoSQLServiceError) { 83 | throw AuthError.service(`Failed to ${action}, unexpected HTTP \ 84 | response. Status code: ${err.statusCode}. Error response: ${err.response}`, 85 | err); 86 | } 87 | throw AuthError.network(`Failed to ${action}, see the cause`, err); 88 | } 89 | 90 | async login(user, pwd) { 91 | try { 92 | const res = await this._doGet({ 93 | url: this._loginUrl, 94 | clientId: user, 95 | secret: Buffer.isBuffer(pwd) ? pwd : Buffer.from(pwd) 96 | }); 97 | return this._parse(res); 98 | } 99 | catch (err) { 100 | return this._handleError(err, 'login to kvstore'); 101 | } 102 | } 103 | 104 | async renew(auth) { 105 | try { 106 | const res = await this._doGet({ 107 | url: this._renewUrl, 108 | auth, 109 | }); 110 | return this._parse(res); 111 | } 112 | catch (err) { 113 | return this._handleError(err, 114 | 'renew kvstore authentication token'); 115 | } 116 | } 117 | 118 | async logout(auth) { 119 | try { 120 | return await this._doGet({ 121 | url: this._logoutUrl, 122 | auth 123 | }); 124 | } 125 | catch (err) { 126 | return this._handleError(err, 'logout from kvstore'); 127 | } 128 | } 129 | 130 | } 131 | 132 | module.exports = KVStoreTokenProvider; 133 | -------------------------------------------------------------------------------- /lib/binary_protocol/buffer.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | 12 | /** 13 | * Utility classes to facilitate serialization/deserialization 14 | * 15 | * The numeric methods use packed_integer module which uses a format 16 | * that is always sorted. 17 | */ 18 | 19 | class ResizableBuffer { 20 | constructor(arg) { 21 | switch(typeof arg) { 22 | case 'undefined': 23 | this._buf = Buffer.allocUnsafe(1024); 24 | this._len = 0; 25 | break; 26 | case 'number': 27 | this._buf = Buffer.allocUnsafe(arg); 28 | this._len = 0; 29 | break; 30 | case 'object': 31 | if (arg instanceof Buffer) { 32 | this._buf = arg; 33 | this._len = arg.length; 34 | break; 35 | } 36 | default: 37 | assert(false); 38 | } 39 | } 40 | 41 | _checkBound(off) { 42 | assert(off >= 0); 43 | if (off >= this._len) { 44 | throw new RangeError( 45 | `Index ${off} out of bounds for length ${this._len}`); 46 | } 47 | } 48 | 49 | _checkBounds(start, end) { 50 | assert(start >= 0 && end >= 0 && start <= end); 51 | if (end > this._len) { 52 | throw new RangeError( 53 | `[${start}, ${end}] out of bounds for length ${this._len}`); 54 | } 55 | } 56 | 57 | _ensureCapacity(cap) { 58 | if (this._buf.length < cap) { 59 | const newCap = Math.max(this._buf.length * 2, cap); 60 | const b = Buffer.allocUnsafe(newCap); 61 | this._buf.copy(b, 0, 0, this._len); 62 | this._buf = b; 63 | } 64 | } 65 | 66 | get buffer() { 67 | return this._buf; 68 | } 69 | 70 | get length() { 71 | return this._len; 72 | } 73 | 74 | set length(value) { 75 | this._ensureCapacity(value); 76 | this._len = value; 77 | } 78 | 79 | clear() { 80 | this._len = 0; 81 | return this; 82 | } 83 | 84 | readUInt8(off) { 85 | this._checkBound(off); 86 | return this._buf.readUInt8(off); 87 | } 88 | 89 | readInt8(off) { 90 | this._checkBound(off); 91 | return this._buf.readInt8(off); 92 | } 93 | 94 | writeUInt8(value, off) { 95 | const newCap = off + 1; 96 | this._ensureCapacity(newCap); 97 | this._buf.writeUInt8(value, off); 98 | if (this._len < newCap) { 99 | this._len = newCap; 100 | } 101 | } 102 | 103 | writeInt16BE(value, off) { 104 | const newCap = off + 2; 105 | this._ensureCapacity(newCap); 106 | this._buf.writeInt16BE(value, off); 107 | if (this._len < newCap) { 108 | this._len = newCap; 109 | } 110 | } 111 | 112 | readInt16BE(off) { 113 | this._checkBounds(off, off + 2); 114 | return this._buf.readInt16BE(off); 115 | } 116 | 117 | readInt32BE(off) { 118 | this._checkBounds(off, off + 4); 119 | return this._buf.readInt32BE(off); 120 | } 121 | 122 | writeInt32BE(value, off) { 123 | const newCap = off + 4; 124 | this._ensureCapacity(newCap); 125 | this._buf.writeInt32BE(value, off); 126 | if (this._len < newCap) { 127 | this._len = newCap; 128 | } 129 | } 130 | 131 | readDoubleBE(off) { 132 | this._checkBounds(off, off + 8); 133 | return this._buf.readDoubleBE(off); 134 | } 135 | 136 | slice(start = 0, end = start + this._len) { 137 | this._checkBounds(start, end); 138 | return this._buf.subarray(start, end); 139 | } 140 | 141 | readBuffer(off, len) { 142 | this._checkBounds(off, off + len); 143 | const b = Buffer.allocUnsafe(len); 144 | this._buf.copy(b, 0, off, off + len); 145 | return b; 146 | } 147 | 148 | writeBuffer(buf, off) { 149 | const newCap = off + buf.length; 150 | this._ensureCapacity(newCap); 151 | buf.copy(this._buf, off, 0, buf.length); 152 | if (this._len < newCap) { 153 | this._len = newCap; 154 | } 155 | } 156 | 157 | appendBuffer(value) { 158 | this.writeBuffer(value, this._len); 159 | } 160 | 161 | ensureExtraCapacity(extra) { 162 | this._ensureCapacity(this._len + extra); 163 | } 164 | 165 | toString(encoding = 'utf8') { 166 | return this._buf.toString(encoding, 0, this._len); 167 | } 168 | } 169 | 170 | module.exports = ResizableBuffer; 171 | -------------------------------------------------------------------------------- /lib/nson_protocol/constants.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const Fields = { 11 | 12 | //request fields 13 | ABORT_ON_FAIL: 'a', 14 | BATCH_COUNTER: 'bc', 15 | BIND_VARIABLES: 'bv', 16 | COMPARTMENT_OCID: 'cc', 17 | CONSISTENCY: 'co', 18 | CONTINUATION_KEY: 'ck', 19 | DATA: 'd', 20 | DEFINED_TAGS: 'dt', 21 | DURABILITY: 'du', 22 | END: 'en', 23 | ETAG: 'et', 24 | EXACT_MATCH: 'ec', 25 | FIELDS: 'f', 26 | FREE_FORM_TAGS: 'ff', 27 | GET_QUERY_PLAN: 'gq', 28 | GET_QUERY_SCHEMA: 'gs', 29 | HEADER: 'h', 30 | IDEMPOTENT: 'ip', 31 | IDENTITY_CACHE_SIZE: 'ic', 32 | INCLUSIVE: 'in', 33 | INDEX: 'i', 34 | INDEXES: 'ix', 35 | IS_JSON: 'j', 36 | IS_PREPARED: 'is', 37 | IS_SIMPLE_QUERY: 'iq', 38 | KEY: 'k', 39 | KV_VERSION: 'kv', 40 | LAST_INDEX: 'li', 41 | LIST_MAX_TO_READ: 'lx', 42 | LIST_START_INDEX: 'ls', 43 | MATCH_VERSION: 'mv', 44 | MAX_READ_KB: 'mr', 45 | MAX_SHARD_USAGE_PERCENT: 'ms', 46 | MAX_WRITE_KB: 'mw', 47 | NAME: 'm', 48 | NAMESPACE: 'ns', 49 | NUMBER_LIMIT: 'nl', 50 | NUM_OPERATIONS: 'no', 51 | OPERATIONS: 'os', 52 | OPERATION_ID: 'od', 53 | OP_CODE: 'o', 54 | PATH: 'pt', 55 | PAYLOAD: 'p', 56 | PREPARE: 'pp', 57 | PREPARED_QUERY: 'pq', 58 | PREPARED_STATEMENT: 'ps', 59 | QUERY: 'q', 60 | QUERY_VERSION: 'qv', 61 | QUERY_ID: 'qn', 62 | RANGE: 'rg', 63 | RANGE_PATH: 'rp', 64 | READ_THROTTLE_COUNT: 'rt', 65 | REGION: 'rn', 66 | RETURN_ROW: 'rr', 67 | SERVER_MEMORY_CONSUMPTION: 'sm', 68 | SHARD_ID: 'si', 69 | START: 'sr', 70 | STATEMENT: 'st', 71 | STORAGE_THROTTLE_COUNT: 'sl', 72 | TABLES: 'tb', 73 | TABLE_DDL: 'td', 74 | TABLE_NAME: 'n', 75 | TABLE_OCID: 'to', 76 | TABLE_USAGE: 'u', 77 | TABLE_USAGE_PERIOD: 'pd', 78 | TIMEOUT: 't', 79 | TOPO_SEQ_NUM: 'ts', 80 | TRACE_LEVEL: 'tl', 81 | TRACE_TO_LOG_FILES: 'tf', 82 | TTL: 'tt', 83 | TYPE: 'y', 84 | UPDATE_TTL: 'ut', 85 | VALUE: 'l', 86 | VERSION: 'v', 87 | WRITE_MULTIPLE: 'wm', 88 | WRITE_THROTTLE_COUNT: 'wt', 89 | 90 | //response fields 91 | ERROR_CODE: 'e', 92 | EXCEPTION: 'x', 93 | NUM_DELETIONS: 'nd', 94 | RETRY_HINT: 'rh', 95 | SUCCESS: 'ss', 96 | TOPOLOGY_INFO: 'tp', 97 | WM_FAILURE: 'wf', 98 | WM_FAIL_INDEX: 'wi', 99 | WM_FAIL_RESULT: 'wr', 100 | WM_SUCCESS: 'ws', 101 | 102 | //table metadata 103 | INITIALIZED: 'it', 104 | REPLICAS: 'rc', 105 | SCHEMA_FROZEN: 'sf', 106 | TABLE_SCHEMA: 'ac', 107 | TABLE_STATE: 'as', 108 | 109 | //system request 110 | SYSOP_RESULT: 'rs', 111 | SYSOP_STATE: 'ta', 112 | 113 | //throughput used and limits 114 | CONSUMED: 'c', 115 | LIMITS: 'lm', 116 | LIMITS_MODE: 'mo', 117 | READ_KB: 'rk', 118 | READ_UNITS: 'ru', 119 | STORAGE_GB: 'sg', 120 | WRITE_KB: 'wk', 121 | WRITE_UNITS: 'wu', 122 | 123 | //row metadata 124 | EXPIRATION: 'xp', 125 | MODIFIED: 'md', 126 | ROW: 'r', 127 | ROW_VERSION: 'rv', 128 | 129 | //operation metadata 130 | EXISTING_MOD_TIME: 'em', 131 | EXISTING_VALUE: 'el', 132 | EXISTING_VERSION: 'ev', 133 | GENERATED: 'gn', 134 | RETURN_INFO: 'ri', 135 | 136 | //query response fields 137 | DRIVER_QUERY_PLAN: 'dq', 138 | MATH_CONTEXT_CODE: 'mc', 139 | MATH_CONTEXT_ROUNDING_MODE: 'rm', 140 | MATH_CONTEXT_PRECISION: 'cp', 141 | NOT_TARGET_TABLES: 'nt', 142 | NUM_RESULTS: 'nr', 143 | PROXY_TOPO_SEQNUM: 'pn', 144 | QUERY_OPERATION: 'qo', 145 | QUERY_PLAN_STRING: 'qs', 146 | QUERY_RESULTS: 'qr', 147 | QUERY_RESULT_SCHEMA: 'qc', 148 | REACHED_LIMIT: 're', 149 | SHARD_IDS: 'sa', 150 | SORT_PHASE1_RESULTS: 'p1', 151 | TABLE_ACCESS_INFO: 'ai', 152 | QUERY_BATCH_TRACES: 'qts', 153 | 154 | //query virtual scan-related fields 155 | VIRTUAL_SCAN: 'vs', 156 | VIRTUAL_SCANS: 'vssa', 157 | VIRTUAL_SCAN_SID: 'vssid', 158 | VIRTUAL_SCAN_PID: 'vspid', 159 | VIRTUAL_SCAN_PRIM_KEY: 'vspk', 160 | VIRTUAL_SCAN_SEC_KEY: 'vssk', 161 | VIRTUAL_SCAN_MOVE_AFTER: 'vsma', 162 | VIRTUAL_SCAN_JOIN_DESC_RESUME_KEY: 'vsjdrk', 163 | VIRTUAL_SCAN_JOIN_PATH_TABLES: 'vsjpt', 164 | VIRTUAL_SCAN_JOIN_PATH_KEY: 'vsjpk', 165 | VIRTUAL_SCAN_JOIN_PATH_SEC_KEY: 'vsjpsk', 166 | VIRTUAL_SCAN_JOIN_PATH_MATCHED: 'vsjpm', 167 | 168 | //replica stats response fields 169 | NEXT_START_TIME: 'ni', 170 | REPLICA_STATS: 'ra', 171 | REPLICA_LAG: 'rl', 172 | TIME: 'tm' 173 | }; 174 | 175 | module.exports = { 176 | Fields 177 | }; 178 | -------------------------------------------------------------------------------- /lib/auth/iam/cached_profile.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | const assert = require('assert'); 9 | 10 | const isPosInt32OrZero = require('../../utils').isPosInt32OrZero; 11 | const Utils = require('./utils'); 12 | 13 | //Base class for providers that cache profile. 14 | //Derived classes need to implement the following: 15 | //this._profile - currently cached profile 16 | //this._isCurrentProfileValid() - if current profile is valid, only called 17 | //when this._profile exists 18 | //async this._doRefresh() - refreshes current profile and related data 19 | class CachedProfileProvider { 20 | 21 | isProfileValid(profile) { 22 | assert(profile != null); 23 | return profile === this._profile && this._isCurrentProfileValid(); 24 | } 25 | 26 | async getProfile(needRefresh) { 27 | if (needRefresh || this._profile == null || 28 | !this._isCurrentProfileValid()) { 29 | await this._doRefresh(needRefresh); 30 | } 31 | 32 | return this._profile; 33 | } 34 | } 35 | 36 | //Base class for providers that allow refreshing profile in the background. 37 | //Derived classes need to implement the following: 38 | //this._profile - currently cached profile 39 | //this._refreshAheadMs - (optional) interval in ms before expiration when to 40 | //do background refresh 41 | //this._isCurrentProfileValid() - if current profile is valid 42 | //this._getCurrentDuration() - get duration in ms of current profile, assuming 43 | //it was just retrieved and is valid 44 | //async this._refreshProfile() - refresh of the profile in 45 | //foreground or background 46 | class RefreshableProfileProvider extends CachedProfileProvider { 47 | 48 | async _doRefresh(isBackground) { 49 | //Avoid multiple concurrent requests for profile refresh. 50 | if (this._profilePromise == null) { 51 | this._profilePromise = this._refreshProfile(); 52 | } 53 | try { 54 | await this._profilePromise; 55 | } catch(err) { 56 | if (isBackground) { 57 | //If error occurred during background refresh, we don't throw 58 | //and don't reschedule next refresh. 59 | return; 60 | } else { 61 | throw err; 62 | } 63 | } finally { 64 | if (this._refreshTimer != null) { 65 | clearTimeout(this._refreshTimer); 66 | } 67 | this._profilePromise = null; 68 | } 69 | 70 | if (!this._refreshAheadMs) { 71 | return; 72 | } 73 | 74 | const dur = this._getCurrentDuration(); 75 | if (!Number.isFinite(dur)) { 76 | return; 77 | } 78 | 79 | const refreshInterval = dur - this._refreshAheadMs; 80 | if (refreshInterval <= 0) { 81 | return; 82 | } 83 | 84 | this._refreshTimer = setTimeout( 85 | () => this._refreshProfile(true, true), refreshInterval); 86 | } 87 | 88 | close() { 89 | if (this._refreshTimer != null) { 90 | clearTimeout(this._refreshTimer); 91 | this._refreshTimer = null; 92 | } 93 | } 94 | } 95 | 96 | //Base class for security token-based providers that allow refresh in the 97 | //background. 98 | //Derived classes need to implement the following: 99 | //async this._getSecurityToken() - retrieve security token 100 | //this._getPrivateKey() - get private key for the profile 101 | //this._profileExtraInit() - (optional) Initialize other properties in the 102 | //current profile (this._profile), other than keyId and privateKey 103 | class RefreshableTokenProvider extends RefreshableProfileProvider { 104 | 105 | constructor(opt) { 106 | super(); 107 | assert (opt != null); 108 | this._refreshAheadMs = opt.securityTokenRefreshAheadMs; 109 | assert(isPosInt32OrZero(this._refreshAheadMs)); 110 | this._expireBeforeMs = opt.securityTokenExpireBeforeMs; 111 | assert(isPosInt32OrZero(this._expireBeforeMs)); 112 | } 113 | 114 | async _refreshProfile() { 115 | const val = await this._getSecurityToken(); 116 | this._token = Utils.parseSecurityToken(val); 117 | this._profile = { 118 | keyId: 'ST$' + this._token.value, 119 | privateKey: this._getPrivateKey() 120 | }; 121 | this._profileExtraInit(); 122 | } 123 | 124 | //only called when this._profile exists 125 | _isCurrentProfileValid() { 126 | assert(this._token != null); 127 | return Utils.isSecurityTokenValid(this._token, this._expireBeforeMs); 128 | } 129 | 130 | _getCurrentDuration() { 131 | return Utils.getSecurityTokenExpiration(this._token, 132 | this._expireBeforeMs) - Date.now(); 133 | } 134 | 135 | _profileExtraInit() {} 136 | } 137 | 138 | module.exports = { 139 | CachedProfileProvider, 140 | RefreshableProfileProvider, 141 | RefreshableTokenProvider 142 | }; 143 | -------------------------------------------------------------------------------- /lib/auth/http_client.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const HttpConstants = require('../constants').HttpConstants; 12 | const NoSQLServiceError = require('../error').NoSQLServiceError; 13 | const NoSQLTimeoutError = require('../error').NoSQLTimeoutError; 14 | const promisified = require('../utils').promisified; 15 | 16 | const DEFAULT_DELAY_MS = 1000; 17 | 18 | class HttpClient { 19 | 20 | constructor(opt, isHttps = true) { 21 | this._httpMod = require(isHttps ? 'https': 'http'); 22 | if (opt != null) { 23 | this._agent = new this._httpMod.Agent(opt); 24 | } 25 | else { 26 | this._agent = this._httpMod.globalAgent; 27 | } 28 | } 29 | 30 | _getBasicAuth(clientId, secret) { 31 | let b; 32 | try { 33 | //secret is stored as Buffer 34 | b = Buffer.concat([Buffer.from(clientId), Buffer.from(':'), 35 | secret]); 36 | return 'Basic ' + b.toString('base64'); 37 | } finally { 38 | if (b) { 39 | b.fill(0); 40 | } 41 | } 42 | } 43 | 44 | _request(req, callback) { 45 | if (!(req.url instanceof URL)) { 46 | req.url = new URL(req.url); 47 | } 48 | 49 | const httpOpt = { 50 | hostname: req.url.hostname, 51 | port: req.url.port, 52 | path: req.url.pathname + req.url.search, 53 | method: req.method, 54 | headers: req.headers, 55 | agent: this._agent, 56 | timeout: req.timeout 57 | }; 58 | 59 | if (!httpOpt.headers) { 60 | httpOpt.headers = {}; 61 | } 62 | httpOpt.headers[HttpConstants.HOST] = req.url.host; 63 | if (!httpOpt.headers[HttpConstants.CONNECTION]) { 64 | httpOpt.headers[HttpConstants.CONNECTION] = 'keep-alive'; 65 | } 66 | if (!httpOpt.headers[HttpConstants.CACHE_CONTROL]) { 67 | httpOpt.headers[HttpConstants.CACHE_CONTROL] = 'no-store'; 68 | } 69 | if (req.auth) { 70 | httpOpt.headers[HttpConstants.AUTHORIZATION] = req.auth; 71 | } else if (!httpOpt.headers[HttpConstants.AUTHORIZATION]) { 72 | assert(req.clientId && req.secret); 73 | httpOpt.headers[HttpConstants.AUTHORIZATION] = this._getBasicAuth( 74 | req.clientId, req.secret); 75 | } 76 | if (req.contentType) { 77 | httpOpt.headers[HttpConstants.CONTENT_TYPE] = req.contentType; 78 | } 79 | let payload = req.payload; 80 | if (payload) { 81 | if (!Buffer.isBuffer(payload) && typeof payload !== 'string') { 82 | payload = JSON.stringify(payload); 83 | } 84 | httpOpt.headers[HttpConstants.CONTENT_LENGTH] = 85 | Buffer.byteLength(payload); 86 | } 87 | 88 | const doOnce = () => { 89 | const httpReq = this._httpMod.request(httpOpt, (res) => { 90 | res.setEncoding('utf8'); 91 | let body = ''; 92 | res.on('data', (chunk) => { 93 | body += chunk; 94 | }); 95 | res.on('end', () => { 96 | if (res.statusCode >= HttpConstants.HTTP_SERVER_ERROR && 97 | Date.now() - startTime <= req.timeout) { 98 | numRetries++; 99 | return setTimeout(doOnce, DEFAULT_DELAY_MS); 100 | } 101 | if (res.statusCode >= 200 && res.statusCode <= 299) { 102 | return callback(null, body); 103 | } 104 | const err = new NoSQLServiceError(res, body); 105 | if (res.statusCode >= HttpConstants.HTTP_SERVER_ERROR) { 106 | return callback(new NoSQLTimeoutError(req.timeout, 107 | numRetries, null, err)); 108 | } 109 | callback(err); 110 | }); 111 | }); 112 | httpReq.on('error', (err) => { 113 | //May need to check for the type of error 114 | if (Date.now() - startTime <= req.timeout) { 115 | numRetries++; 116 | setTimeout(doOnce, DEFAULT_DELAY_MS); 117 | } else { 118 | callback(new NoSQLTimeoutError(req.timeout, numRetries, 119 | null, err)); 120 | } 121 | }); 122 | if (payload) { 123 | httpReq.write(payload); 124 | } 125 | httpReq.end(); 126 | }; 127 | 128 | //To offset for waiting for retry 129 | const startTime = Date.now() - DEFAULT_DELAY_MS; 130 | let numRetries = 1; 131 | doOnce(); 132 | } 133 | 134 | request(req) { 135 | return promisified(this, this._request, req); 136 | } 137 | 138 | shutdown() { 139 | this._agent.destroy(); 140 | } 141 | 142 | } 143 | 144 | module.exports = HttpClient; 145 | -------------------------------------------------------------------------------- /src/types/auth/kvstore/auth_provider.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { NoSQLClient } from "../../nosql_client"; 9 | import type { Config } from "../../config"; 10 | import type { Operation } from "../../param"; 11 | import type { NoSQLArgumentError } from "../../error"; 12 | import type { AuthConfig, AuthorizationProvider, AuthResult } 13 | from "../config"; 14 | import type { KVStoreAuthConfig, KVStoreCredentialsProvider } from "./types"; 15 | 16 | /** 17 | * Authorization provider used to to authorize operations against secure 18 | * on-premises Oracle NoSQL Database. 19 | *

20 | * The driver uses this class internally for on-premises authentication. 21 | * Normally, you do not need to use this class. Instead, create 22 | * {@link NoSQLClient} instance by specifying {@link AuthConfig#kvstore} 23 | * property in {@link AuthConfig} as part of {@link Config#auth}, as described 24 | * in {@link KVStoreAuthConfig}. 25 | *

26 | * You may use this class as an alternative to specifying 27 | * {@link AuthConfig#kvstore}, as a value for {@link AuthConfig#provider} 28 | * property when creating {@link NoSQLClient} instance. This is shown in the 29 | * example. 30 | * 31 | * @see {@link KVStoreAuthConfig} 32 | * @see {@link AuthConfig} 33 | * 34 | * @example 35 | * Using KVStoreAuthorizationProvider when creating NoSQLClient instance. 36 | * 37 | * const userName = .....; 38 | * const pwd = .....; 39 | * 40 | * const client = new NoSQLClient({ 41 | * endpoint: "", 42 | * auth: { 43 | * provider: new KVStoreAuthorizationProvider({ 44 | * user: userName, 45 | * password: pwd 46 | * }) 47 | * } 48 | * }); 49 | */ 50 | export class KVStoreAuthorizationProvider implements AuthorizationProvider { 51 | /** 52 | * Initializes a new instance of {@link KVStoreAuthorizationProvider}. 53 | * @param config Configuration to create 54 | * {@link KVStoreAuthorizationProvider} specified as 55 | * {@link KVStoreAuthConfig}. 56 | * @throws {NoSQLArgumentError} if the configuration is has invalid 57 | * properties 58 | * @see {@link KVStoreAuthConfig} 59 | */ 60 | constructor(config: KVStoreAuthConfig); 61 | 62 | /** 63 | * A convenience method to create new instance of 64 | * {@link KVStoreAuthorizationProvider} with supplied user name and 65 | * password. 66 | *

67 | * Other applicable properties are initialized to their defaults as 68 | * described in {@link KVStoreAuthConfig}. 69 | * @param user User name, see {@link KVStoreAuthConfig#user} 70 | * @param pwd User's password, see {@link KVStoreAuthConfig#password} 71 | * @returns New instance of {@link KVStoreAuthorizationProvider} using 72 | * specified credentials 73 | * @see {@link KVStoreAuthConfig} 74 | */ 75 | static withCredentials(user: string, pwd: Buffer | string): 76 | KVStoreAuthorizationProvider; 77 | 78 | /** 79 | * A convenience method to create new instance of 80 | * {@link KVStoreAuthorizationProvider} with supplied credentials 81 | * provider. 82 | *

83 | * Other applicable properties are initialized to their defaults as 84 | * described in {@link KVStoreAuthConfig}. 85 | * @param provider Credentials provider, see 86 | * {@link KVStoreCredentialsProvider} 87 | * @returns New instance of {@link KVStoreAuthorizationProvider} using 88 | * specified credentials provider 89 | * @see {@link KVStoreCredentialsProvider} 90 | * @see {@link KVStoreAuthConfig} 91 | */ 92 | static withCredentialsProvider(provider: KVStoreCredentialsProvider): 93 | KVStoreAuthorizationProvider; 94 | 95 | /** 96 | * A convenience method to create new instance of 97 | * {@link KVStoreAuthorizationProvider} with supplied credentials 98 | * file path. 99 | *

100 | * Other applicable properties are initialized to their defaults as 101 | * described in {@link KVStoreAuthConfig}. 102 | * @param file Credentials file, see {@link KVStoreAuthConfig#credentials} 103 | * {@link KVStoreCredentialsProvider} 104 | * @returns New instance of {@link KVStoreAuthorizationProvider} using 105 | * specified credentials file 106 | * @see {@link KVStoreAuthConfig} 107 | */ 108 | static withCredentialsFile(file: string): KVStoreAuthorizationProvider; 109 | 110 | /** 111 | * Asynchronously acquires authorization information. This method is only 112 | * used by the driver. You don't need to call this method. This method can 113 | * only be used after {@link onInit} is called. 114 | * @async 115 | * @param {Operation} op {@link Operation} that requires authorization 116 | * @returns {Promise} Promise resolved with authorization information or 117 | * rejected with an error 118 | */ 119 | getAuthorization(op: Operation): Promise; 120 | 121 | /** 122 | * Initialization callback. 123 | * @param config {@link Config} object used to create {@link NoSQLClient} 124 | * instance. 125 | * @see {@link AuthorizationProvider#onInit} 126 | */ 127 | onInit(config: Config): void; 128 | 129 | /** 130 | * Releases resources associated with this provider. 131 | * @see {@link AuthorizationProvider#close} 132 | */ 133 | onClose(): void | Promise; 134 | } 135 | -------------------------------------------------------------------------------- /test/unit/types/get_indexes.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import { expectTypeOf } from "expect-type"; 9 | 10 | import { NoSQLClient, GetIndexesOpt, GetIndexOpt, IndexInfo } 11 | from "../../../"; 12 | 13 | const client = new NoSQLClient("nosuchfile.json"); 14 | 15 | function testGetIndexesOpt() { 16 | let opt: GetIndexesOpt = {}; 17 | opt.compartment = "c"; 18 | opt.namespace = "n"; 19 | 20 | // @ts-expect-error Invalid type for compartment. 21 | opt.compartment = 1; 22 | // @ts-expect-error Invalid type for namespace. 23 | opt.namespace = 1; 24 | 25 | opt.timeout = 10000; 26 | opt.indexName = "index"; 27 | 28 | // @ts-expect-error Invalid type for index name. 29 | opt.indexName = 1; 30 | // @ts-expect-error Invalid type for index name. 31 | opt.indexName = { name: "index" }; 32 | // @ts-expect-error Invalid type for timeout. 33 | opt.timeout = "10000"; 34 | // @ts-expect-error Invalid extra property. 35 | opt.complete = true; 36 | // @ts-expect-error Invalid extra property. 37 | opt.delay = 1000; 38 | // @ts-expect-error Invalid extra property. 39 | opt.other = 1; 40 | } 41 | 42 | function testGetIndexOpt() { 43 | let opt: GetIndexOpt = {}; 44 | opt.compartment = "c"; 45 | opt.namespace = "n"; 46 | 47 | // @ts-expect-error Invalid type for compartment. 48 | opt.compartment = 1; 49 | // @ts-expect-error Invalid type for namespace. 50 | opt.namespace = 1; 51 | 52 | opt.timeout = 10000; 53 | 54 | // @ts-expect-error Invalid type for timeout. 55 | opt.timeout = "10000"; 56 | // @ts-expect-error Invalid extra property. 57 | opt.indexName = "index"; 58 | // @ts-expect-error Invalid extra property. 59 | opt.delay = 1000; 60 | } 61 | 62 | function testIndexInfo(res: IndexInfo) { 63 | // check result types 64 | expectTypeOf(res.indexName).toBeString(); 65 | expectTypeOf(res.fields).toEqualTypeOf(); 66 | expectTypeOf(res.fieldTypes) 67 | .toEqualTypeOf<((string|undefined)[])|undefined>(); 68 | 69 | // all properties of IndexInfo must be read-only 70 | expectTypeOf>().toEqualTypeOf(); 71 | 72 | // @ts-expect-error Invalid property in IndexInfo. 73 | res.tableName; 74 | } 75 | 76 | async function testGetIndexes() { 77 | let res: IndexInfo[]; 78 | let idxInfo: IndexInfo; 79 | 80 | expectTypeOf(client.getIndexes).toBeFunction(); 81 | expectTypeOf(client.getIndexes).parameters 82 | .toEqualTypeOf<[string, GetIndexesOpt?]>(); 83 | expectTypeOf(client.getIndexes).parameter(0).toBeString(); 84 | expectTypeOf(client.getIndexes).parameter(1) 85 | .toEqualTypeOf(); 86 | expectTypeOf(client.getIndexes).returns.not.toEqualTypeOf(); 87 | expectTypeOf(client.getIndexes).returns.resolves 88 | .toEqualTypeOf(); 89 | expectTypeOf(client.getIndexes).toBeCallableWith("table"); 90 | expectTypeOf(client.getIndexes).toBeCallableWith("table", { timeout: 1 }); 91 | expectTypeOf(client.getIndexes).toBeCallableWith("table", 92 | { indexName: "indexName" }); 93 | 94 | res = await client.getIndexes("table"); 95 | res = await client.getIndexes("table", { compartment: "compartment", 96 | timeout: 5000, indexName: "indexName" }); 97 | 98 | // @ts-expect-error Missing table name. 99 | client.getIndexes(); 100 | // @ts-expect-error Missing table name. 101 | client.getIndexes({ timeout: 1 }); 102 | // @ts-expect-error Invalid table name. 103 | client.getIndexes(123); 104 | // @ts-expect-error Invalid option. 105 | client.getIndexes("table", { delay: 1000 }); 106 | // @ts-expect-error Wrong result type. 107 | idxInfo = await client.getIndexes("table"); 108 | } 109 | 110 | async function testGetIndex() { 111 | let res: IndexInfo; 112 | let arr: IndexInfo[]; 113 | 114 | expectTypeOf(client.getIndex).toBeFunction(); 115 | expectTypeOf(client.getIndex).parameters 116 | .toEqualTypeOf<[string, string, GetIndexOpt?]>(); 117 | expectTypeOf(client.getIndex).parameter(0).toBeString(); 118 | expectTypeOf(client.getIndex).parameter(1).toBeString(); 119 | expectTypeOf(client.getIndex).parameter(2) 120 | .toEqualTypeOf(); 121 | expectTypeOf(client.getIndex).returns.not.toEqualTypeOf(); 122 | expectTypeOf(client.getIndex).returns.resolves 123 | .toEqualTypeOf(); 124 | expectTypeOf(client.getIndex).toBeCallableWith("table", "index"); 125 | expectTypeOf(client.getIndex).toBeCallableWith("table", "index", 126 | { compartment: "compartment", timeout: 1 }); 127 | expectTypeOf(client.getIndex).toBeCallableWith("table", "index", 128 | { timeout: 1 }); 129 | 130 | res = await client.getIndex("table", "idx"); 131 | res = await client.getIndex("table", "idx", { compartment: "compartment", 132 | timeout: 5000 }); 133 | 134 | // @ts-expect-error Missing table name. 135 | client.getIndex(); 136 | // @ts-expect-error Missing index name. 137 | client.getIndex("table"); 138 | // @ts-expect-error Invalid table name. 139 | client.getIndex(123, "index"); 140 | // @ts-expect-error Invalid index name. 141 | client.getIndex("table", 1); 142 | // @ts-expect-error Invalid option. 143 | client.getIndex("table", "index", { delay: 1000 }); 144 | } 145 | -------------------------------------------------------------------------------- /lib/query/value.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const util = require('util'); 12 | const EMPTY_VALUE = require('../constants').EMPTY_VALUE; 13 | const PlanIterator = require('./common').PlanIterator; 14 | 15 | /** 16 | * ConstIter represents a reference to a constant value in the query. 17 | * Such a reference will need to be "executed" at the driver side when 18 | * the constant appears in the OFFSET or LIMIT clause. 19 | */ 20 | class ConstIterator extends PlanIterator { 21 | 22 | constructor(qpExec, step) { 23 | super(qpExec, step); 24 | } 25 | 26 | next() { 27 | if (this._done) { 28 | return false; 29 | } 30 | this.result = this._step.val; 31 | this._done = true; 32 | return true; 33 | } 34 | 35 | } 36 | 37 | /** 38 | * VarRefIter represents a reference to a non-external variable in the query. 39 | * It simply returns the value that the variable is currently bound to. This 40 | * value is computed by the variable's "domain iterator" (the iterator that 41 | * evaluates the domain expression of the variable). The domain iterator stores 42 | * the value in theResultReg of this VarRefIter. 43 | * 44 | * In the context of the driver, an implicit internal variable is used 45 | * to represent the results arriving from the proxy. All other expressions that 46 | * are computed at the driver operate on these results, so all such expressions 47 | * reference this variable. This is analogous to the internal variable used in 48 | * kvstore to represent the table alias in the FROM clause. 49 | */ 50 | class VarRefIterator extends PlanIterator { 51 | 52 | constructor(qpExec, step) { 53 | super(qpExec, step); 54 | } 55 | 56 | //the value is already stored in domain iterator registry 57 | } 58 | 59 | /** 60 | * In general, ExternalVarRefIter represents a reference to an external variable 61 | * in the query. Such a reference will need to be "executed" at the driver side 62 | * when the variable appears in the OFFSET or LIMIT clause. 63 | * ExternalVarRefIter simply returns the value that the variable is currently 64 | * bound to. This value is set by the app via the methods of QueryRequest. 65 | */ 66 | class ExtVarRefIterator extends PlanIterator { 67 | 68 | constructor(qpExec, step) { 69 | super(qpExec, step); 70 | } 71 | 72 | next() { 73 | if (this._done) { 74 | return false; 75 | } 76 | let val; 77 | if (this._qpExec._extVars) { 78 | val = this._qpExec._extVars[this._step.pos]; 79 | } 80 | if (val == null) { 81 | throw this.illegalArg( 82 | `Variable ${this._step.name} has not been set`); 83 | } 84 | this.result = val; 85 | this._done = true; 86 | return true; 87 | } 88 | 89 | } 90 | 91 | /** 92 | * FieldStepIter returns the value of a field in an input MapValue. It is 93 | * used by the driver to implement column references in the SELECT 94 | * list (see SFWIter). 95 | */ 96 | class FieldStepIterator extends PlanIterator { 97 | 98 | constructor(qpExec, step) { 99 | super(qpExec, step); 100 | this._inputIter = qpExec.makeIterator(step.input); 101 | assert(this._inputIter && !this._inputIter.isAsync()); 102 | } 103 | 104 | static validateStep(step) { 105 | this._validateStepInputSync(step); 106 | } 107 | 108 | next() { 109 | if (!this._inputIter.next()) { 110 | return false; 111 | } 112 | const res = this._inputIter.result; 113 | if (typeof res !== 'object') { 114 | throw this.illegalState(`Input value in field step is not \ 115 | object: ${util.inspect(res)}`); 116 | } 117 | if (!(this._step.fldName in res)) { 118 | return false; 119 | } 120 | const val = res[this._step.fldName]; 121 | if (val === EMPTY_VALUE) { 122 | return false; 123 | } 124 | this.result = val; 125 | return true; 126 | } 127 | 128 | reset() { 129 | this._inputIter.reset(); 130 | } 131 | 132 | } 133 | 134 | class FuncSizeIterator extends PlanIterator { 135 | constructor(qpExec, step) { 136 | super(qpExec, step); 137 | this._inputIter = qpExec.makeIterator(step.input); 138 | assert(this._inputIter && !this._inputIter.isAsync()); 139 | } 140 | 141 | next() { 142 | if (!this._inputIter.next()) { 143 | return false; 144 | } 145 | 146 | const res = this._inputIter.result; 147 | if (res == undefined) { 148 | this.result = undefined; 149 | } else if (Array.isArray(res)) { 150 | this.result = res.length; 151 | } else if (res instanceof Map) { 152 | this.result = res.size; 153 | } else if (typeof res === 'object') { 154 | this.result = Object.keys(res).length; 155 | } else { 156 | throw this.illegalState(`Input value in size step is not an \ 157 | array, object or map: ${util.inspect(res)}`); 158 | } 159 | 160 | return true; 161 | } 162 | 163 | reset() { 164 | this._inputIter.reset(); 165 | } 166 | } 167 | 168 | module.exports = { 169 | ConstIterator, 170 | VarRefIterator, 171 | ExtVarRefIterator, 172 | FieldStepIterator, 173 | FuncSizeIterator 174 | }; 175 | -------------------------------------------------------------------------------- /test/unit/auth/iam/constants.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const path = require('path'); 11 | const os = require('os'); 12 | 13 | const COMPARTMENT_ID = 'ocid1.compartment.oc1..compartment'; 14 | const SERVICE_HOST = 'localhost'; 15 | const SERVICE_ENDPOINT = new URL('https://' + SERVICE_HOST); 16 | const TENANT_ID = 'ocid1.tenancy.oc1..tenancy'; 17 | const USER_ID = 'ocid1.user.oc1..user'; 18 | const FINGERPRINT = 'fingerprint'; 19 | const PASSPHRASE = 'oracle'; 20 | const ST_HEADER = 'pseudo-header'; 21 | const ST_SIG = 'pseudo-signature'; 22 | const PRIVATE_KEY_FILE = path.resolve('key_private.pem'); 23 | const OCI_CONFIG_FILE = path.resolve('config'); 24 | const DELEGATION_TOKEN_FILE = path.resolve('delegation_token'); 25 | const DELEGATION_TOKEN = 'token-header.token-payload.token-sig'; 26 | const DELEGATION_TOKEN2 = 'token-header2.token-payload2.token-sig2'; 27 | const SESSION_TOKEN_FILE = path.resolve('security_token'); 28 | const SESSION_TOKEN = 'token-header.token-payload.token-sig'; 29 | const RES_COMPARTMENT = 'ocid1.compartment.oc1..resource'; 30 | const RES_TENANT = 'ocid1.tenancy.oc1..resource'; 31 | 32 | //default OCI config file 33 | const DEFAULT_OCI_DIR = path.join(os.homedir(), '.oci'); 34 | const DEFAULT_OCI_FILE = path.join(DEFAULT_OCI_DIR, 'config'); 35 | 36 | //instance certificates together with tenantId and fingerprint 37 | 38 | const CERT_INFO = [ 39 | { 40 | cert: `-----BEGIN CERTIFICATE----- 41 | MIIEATCCAumgAwIBAgIJAMOCcAhW4IYTMA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD 42 | VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNUmVkd29vZHMg 43 | Q2l0eTEPMA0GA1UECgwGT3JhY2xlMR4wHAYDVQQLDBVvcGMtdGVuYW50OlRlc3RU 44 | ZW5hbnQxKDAmBgNVBAMMH29jaWQxLmluc3RhbmNlLm9jMS5waHguaW5zdGFuY2Uw 45 | IBcNMTkwNzAxMjIwODE0WhgPMjExOTA2MDcyMjA4MTRaMIGVMQswCQYDVQQGEwJV 46 | UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNUmVkd29vZHMgQ2l0eTEP 47 | MA0GA1UECgwGT3JhY2xlMR4wHAYDVQQLDBVvcGMtdGVuYW50OlRlc3RUZW5hbnQx 48 | KDAmBgNVBAMMH29jaWQxLmluc3RhbmNlLm9jMS5waHguaW5zdGFuY2UwggEiMA0G 49 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYZPrL8qgK+1y+1fbrKs454eAg7h18 50 | 4smtcemd8eVvwX7oxIHM5Zq1cqVd8VFoiSpyXZB7Y2jZGhkIUPiXKXAsMcI8qCJX 51 | sJqrbmsGj/QQiuGpcO+3UFxUOWIqWHciYrPKOzPB1C74ECYRnH/CoZy9gIkKQ37q 52 | zuHIHa/EMNQEOEiSm2qopoUqCpbGXiTqtQxSBz+4AbFvAN7RHgi5gh6WJXs/Mxr9 53 | ZuHhusEgZJFaxAy6mOMwIZMStMN1F22pJnHEwllKtGq2kMVVkVA/oXGVXbHYxl5z 54 | MLSwZIbktAUhTc4nS490z27YCGoRGCQ173mUcsr/OtzW809sA3Q/i03tAgMBAAGj 55 | UDBOMB0GA1UdDgQWBBQLjyVU4YIwisrYp5P/yEn8xAN+QzAfBgNVHSMEGDAWgBQL 56 | jyVU4YIwisrYp5P/yEn8xAN+QzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA 57 | A4IBAQAZ6UOwrlzVCTsP7QybBeKXHZU9/Vw2DTEcJ/MdTEPYtQFLaOKTb1SmY27y 58 | 3BgZIQkofLq0ptaa99ceMHZiDzZRr4LKxIfk2mD7IDcpnrg+i+u6EX57TLtnfleY 59 | DwZcCWYZKSC0kC9C8YitTAJC8ydGjIbFtvp6S2XJOMRXQfNiYP4eXNeScs47Ja9f 60 | 5L9cb/g85F3Rg5Q9JaMgiPnhqKpXnHWo0dN54xY9qbI09IA3oVG6sK359/RIkyRl 61 | ARAYsZOahLRGGNB0sqFK80Bstkk22bI8VZXYtw6G6dp1EOdzZUZRoOHrEzAo+7Un 62 | 4HJ5KkzwNwuDhiF6CSthP+TBknWf 63 | -----END CERTIFICATE-----`, 64 | tenantId: 'TestTenant', 65 | fingerprint: 66 | '1E:7B:E4:0B:4E:50:C1:20:A8:2A:57:6C:95:10:F6:90:66:66:5A:AA', 67 | privateKey: `-----BEGIN PRIVATE KEY----- 68 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYZPrL8qgK+1y+ 69 | 1fbrKs454eAg7h184smtcemd8eVvwX7oxIHM5Zq1cqVd8VFoiSpyXZB7Y2jZGhkI 70 | UPiXKXAsMcI8qCJXsJqrbmsGj/QQiuGpcO+3UFxUOWIqWHciYrPKOzPB1C74ECYR 71 | nH/CoZy9gIkKQ37qzuHIHa/EMNQEOEiSm2qopoUqCpbGXiTqtQxSBz+4AbFvAN7R 72 | Hgi5gh6WJXs/Mxr9ZuHhusEgZJFaxAy6mOMwIZMStMN1F22pJnHEwllKtGq2kMVV 73 | kVA/oXGVXbHYxl5zMLSwZIbktAUhTc4nS490z27YCGoRGCQ173mUcsr/OtzW809s 74 | A3Q/i03tAgMBAAECggEAORvtVIXl84ADKhot4EKbyoriK86r2ZnAwBWgIh8E/kmC 75 | xMuXtguimOB45CIb6grJOQWYa/gAY8uPb7Ju6PX2tLMtH/T/m0TwjO3HMSQstXDx 76 | vVYg7bA3rcK3NZXDWz/RUz3smur0umMIqP00eplMVHbns928URvoWnf7OzvnuHTl 77 | 8INq7T+CbtlEslQndeL3FlFoKITSVuEzifw4H7VGXB1W86PfXiTig4GQgwGjO+Nb 78 | Oe6hIXa5dPQcYQx7M9cXUI77UKqJsPMPYAtPVLUm7hGyDNDcHF/R3g/lSb70NjU7 79 | IZph1LXnYVeqsVMXpCaD0HzQNG5fuXwK10gWJ+7YAQKBgQDv+2R1gJMUw+RMgk6G 80 | 9GblDhvOnoaXPRuEVQuH6K6dDlzQ6zY3kt/onYM6siExxqhygqLNMgKk++4GGuDK 81 | rbP5ndZh3E9/bztJDBH/HFsSYF4+3oAEQWIKUKB/OZmmwlHQ7TTYUCAA6azFQBX9 82 | jQ2uB9SaQyYpDVbUsGFhF5AIIQKBgQDm1osYbrrfukiTgfnr9p/megZ2KaERu6vj 83 | rGL9cTC47NmrvAcG6LArlZ8xG9GbxFIzt3XNwwl0bbl1SI7J0Mk3YzeSjVSpgpYh 84 | xtpDo5BS2EZBgTfGOXIYhpPoB2YuOHrnVql92MoCyD6S7AnVUvHH5LRw5NWlsvB8 85 | V2p16K1cTQKBgQDVwo+ROp3IeVT58XgRLdIZZZ/PQ9WPEZdZIIfM363pp8l1Lo50 86 | ohdgFC24MsLum42fsk1hiZJhcyZpubdR0bfmOHmlYaBOWr3sKxw8qP1WORC532cY 87 | Y0T4+yh7Kst6hsxp1WCk7XoUVhDXAmaUGvh8c+0kG3v6RS969EFJQrvBAQKBgQCY 88 | QayPWgICraFPQizxecNwRs5aRA0MYEf5LOxCFNW5M+hDAQt1gCcrKE5PGvU/k9dQ 89 | a1LVfC6RUApClLAx53fBA71U+cl84ThbYQj4EjuQmTyF2lBKe/uIt8N5COBZ3kEa 90 | s6up6UMdYKz9RZkaztHRMkXeLOHKoGNE8He0+9rVBQKBgEAM6FaNoBlUVjoZF8Y1 91 | KkMQWubYt6j3/G7WlIdqI6JW3rEZVJ57lD/9X8SzysIOh2QO41OVU7900rQwLmBW 92 | vWqUa2kXq7WgjkI4xt6nVEJIKxUfQpx7RQ4ygBJs1c0oEWlCwtKLwMvG4IcZNVBa 93 | JnTJyeO3vhWjdW9RI7+ms4h1 94 | -----END PRIVATE KEY-----`, 95 | get intermediateCert() { return this.cert; } 96 | }, 97 | ]; 98 | 99 | module.exports = { 100 | COMPARTMENT_ID, 101 | SERVICE_HOST, 102 | SERVICE_ENDPOINT, 103 | TENANT_ID, 104 | USER_ID, 105 | FINGERPRINT, 106 | PASSPHRASE, 107 | ST_HEADER, 108 | ST_SIG, 109 | PRIVATE_KEY_FILE, 110 | OCI_CONFIG_FILE, 111 | DELEGATION_TOKEN_FILE, 112 | DELEGATION_TOKEN, 113 | DELEGATION_TOKEN2, 114 | SESSION_TOKEN_FILE, 115 | SESSION_TOKEN, 116 | DEFAULT_OCI_DIR, 117 | DEFAULT_OCI_FILE, 118 | RES_COMPARTMENT, 119 | RES_TENANT, 120 | CERT_INFO 121 | }; 122 | -------------------------------------------------------------------------------- /lib/nson_protocol/writer.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('assert'); 11 | const DataWriter = require('../binary_protocol/writer'); 12 | const Type = require('../binary_protocol/constants').Type; 13 | 14 | class NsonWriter { 15 | 16 | constructor(buf) { 17 | this._dw = new DataWriter(buf); 18 | this._stack = []; 19 | } 20 | 21 | _incrSize() { 22 | if (this._stack.length) { 23 | this._stack[this._stack.length - 1].size++; 24 | } 25 | } 26 | 27 | _startComplexValue() { 28 | this._stack.push({ off: this._dw.buffer.length, size: 0}); 29 | this._dw.writeInt32BE(0); //size in bytes 30 | this._dw.writeInt32BE(0); //number of elements 31 | } 32 | 33 | _endComplexValue() { 34 | const elem = this._stack.pop(); 35 | assert(elem); 36 | //Write byte size and number of elements into the space reserved. 37 | //Object starts at off + 4. 38 | this._dw.buffer.writeInt32BE(this._dw.buffer.length - elem.off - 4, 39 | elem.off); 40 | this._dw.buffer.writeInt32BE(elem.size, elem.off + 4); 41 | this._incrSize(); 42 | } 43 | 44 | get dataWriter() { 45 | return this._dw; 46 | } 47 | 48 | get buffer() { 49 | return this._dw.buffer; 50 | } 51 | 52 | get length() { 53 | return this._dw.buffer.length; 54 | } 55 | 56 | reset() { 57 | this._stack.length = 0; 58 | this._dw.reset(); 59 | return this; 60 | } 61 | 62 | writeFieldName(name) { 63 | this._dw.writeString(name); 64 | } 65 | 66 | writeBoolean(value) { 67 | this._dw.writeByte(Type.BOOLEAN); 68 | this._dw.writeBoolean(value); 69 | this._incrSize(); 70 | } 71 | 72 | writeBooleanField(name, value) { 73 | this.writeFieldName(name); 74 | this.writeBoolean(value); 75 | } 76 | 77 | writeInt(value) { 78 | this._dw.writeByte(Type.INTEGER); 79 | this._dw.writeInt(value); 80 | this._incrSize(); 81 | } 82 | 83 | writeIntField(name, value) { 84 | this.writeFieldName(name); 85 | this.writeInt(value); 86 | } 87 | 88 | writeLong(value) { 89 | this._dw.writeByte(Type.LONG); 90 | this._dw.writeLong(value); 91 | this._incrSize(); 92 | } 93 | 94 | writeLongField(name, value) { 95 | this.writeFieldName(name); 96 | this.writeLong(value); 97 | } 98 | 99 | writeDouble(value) { 100 | this._dw.writeByte(Type.DOUBLE); 101 | this._dw.writeDouble(value); 102 | this._incrSize(); 103 | } 104 | 105 | writeDoubleField(name, value) { 106 | this.writeFieldName(name); 107 | this.writeDouble(value); 108 | } 109 | 110 | writeString(value) { 111 | this._dw.writeByte(Type.STRING); 112 | this._dw.writeString(value); 113 | this._incrSize(); 114 | } 115 | 116 | writeStringField(name, value) { 117 | this.writeFieldName(name); 118 | this.writeString(value); 119 | } 120 | 121 | writeBinary(value) { 122 | this._dw.writeByte(Type.BINARY); 123 | this._dw.writeBinary(value); 124 | this._incrSize(); 125 | } 126 | 127 | writeBinaryField(name, value) { 128 | this.writeFieldName(name); 129 | this.writeBinary(value); 130 | } 131 | 132 | writeDate(value) { 133 | this._dw.writeByte(Type.TIMESTAMP); 134 | this._dw.writeDate(value); 135 | this._incrSize(); 136 | } 137 | 138 | writeDateField(name, value) { 139 | this.writeFieldName(name); 140 | this.writeDate(value); 141 | } 142 | 143 | //write Nson NUMBER value supplied as string. 144 | writeStringAsNumber(value) { 145 | this._dw.writeByte(Type.NUMBER); 146 | this._dw.writeString(value); 147 | this._incrSize(); 148 | } 149 | 150 | writeStringAsNumberField(name, value) { 151 | this.writeFieldName(name); 152 | this.writeStringAsNumber(value); 153 | } 154 | 155 | writeJsonNull() { 156 | this._dw.writeByte(Type.JSON_NULL); 157 | this._incrSize(); 158 | } 159 | 160 | writeJsonNullField(name) { 161 | this.writeFieldName(name); 162 | this.writeJsonNull(); 163 | } 164 | 165 | writeNull() { 166 | this._dw.writeByte(Type.NULL); 167 | this._incrSize(); 168 | } 169 | 170 | writeNullField(name) { 171 | this.writeFieldName(name); 172 | this.writeNull(); 173 | } 174 | 175 | writeEmpty() { 176 | this._dw.writeByte(Type.EMPTY); 177 | this._incrSize(); 178 | } 179 | 180 | writeEmptyField(name) { 181 | this.writeFieldName(name); 182 | this.writeEmpty(); 183 | } 184 | 185 | startArray() { 186 | this._dw.writeByte(Type.ARRAY); 187 | this._startComplexValue(); 188 | } 189 | 190 | startArrayField(name) { 191 | this.writeFieldName(name); 192 | this.startArray(); 193 | } 194 | 195 | endArray() { 196 | this._endComplexValue(); 197 | } 198 | 199 | endArrayField() { 200 | this.endArray(); 201 | } 202 | 203 | startMap() { 204 | this._dw.writeByte(Type.MAP); 205 | this._startComplexValue(); 206 | } 207 | 208 | startMapField(name) { 209 | this.writeFieldName(name); 210 | this.startMap(); 211 | } 212 | 213 | endMap() { 214 | this._endComplexValue(); 215 | } 216 | 217 | endMapField() { 218 | this.endMap(); 219 | } 220 | 221 | } 222 | 223 | module.exports = NsonWriter; 224 | -------------------------------------------------------------------------------- /examples/javascript/query_example.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | /* 9 | * A simple example that uses Oracle NoSQL Database Cloud service. 10 | * - create a table 11 | * - create an index 12 | * - insert several rows 13 | * - prepare and use queries 14 | * - drop the table 15 | * 16 | * To run against the cloud simulator: 17 | * node query_example.js cloudsim.json 18 | * 19 | * To run against the cloud service 20 | * node query_example.js config.json 21 | */ 22 | 23 | 'use strict'; 24 | 25 | const nosqldb = require('oracle-nosqldb'); 26 | 27 | const NoSQLClient = nosqldb.NoSQLClient; 28 | 29 | // target table used by this example 30 | const TABLE_NAME = 'users'; 31 | 32 | /* 33 | * Usage: query_example.js [] 34 | */ 35 | async function queryExample() { 36 | let client; 37 | try { 38 | // JSON config file path is an optional parameter. If not specified, 39 | // it is assumed we are using Oracle Cloud Service where credentials 40 | // are supplied in default OCI configuration file (~/.oci/config) 41 | // using default profile (DEFAULT). 42 | let configFile = process.argv[2]; 43 | client = new NoSQLClient(configFile); 44 | console.log('Created NoSQLClient instance'); 45 | 46 | await run(client); 47 | console.log('Success!'); 48 | } catch (err) { 49 | console.error(' Error: ' + err.message); 50 | if (err.operation) { 51 | console.error(' from: '); 52 | console.error(err.operation); 53 | } 54 | } finally { 55 | if (client) { 56 | client.close(); 57 | } 58 | } 59 | } 60 | 61 | /* 62 | * Create a table, read and write a record 63 | */ 64 | async function run(client) { 65 | let ddl = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} \ 66 | (id INTEGER, name STRING, userInfo JSON, PRIMARY KEY(id))`; 67 | 68 | console.log('Create table ' + TABLE_NAME); 69 | let res = await client.tableDDL(ddl, { 70 | tableLimits: { 71 | readUnits: 1, 72 | writeUnits: 5, 73 | storageGB: 1 74 | } 75 | }); 76 | console.log(' Creating table %s', res.tableName); 77 | console.log(' Table state: %s', res.tableState.name); 78 | 79 | // Wait for the operation completion 80 | await client.forCompletion(res); 81 | console.log(' Table %s is created', res.tableName); 82 | console.log(' Table state: %s', res.tableState.name); 83 | 84 | ddl = `CREATE INDEX IF NOT EXISTS city_idx ON ${TABLE_NAME} \ 85 | (userInfo.city AS STRING)`; 86 | 87 | console.log('\nCreate index city_idx on %s', TABLE_NAME); 88 | res = await client.tableDDL(ddl); 89 | console.log(' Creating index city_idx'); 90 | console.log(' Table state: %s', res.tableState.name); 91 | 92 | // Wait for the operation completion 93 | await client.forCompletion(res); 94 | console.log(' Index city_idx is active'); 95 | 96 | // Write some records 97 | res = await client.put(TABLE_NAME, { 98 | id: 10, 99 | name: 'Taylor', 100 | userInfo: { 101 | age: 79, 102 | city: 'Seattle' 103 | } 104 | }); 105 | res = await client.put(TABLE_NAME, { 106 | id: 33, 107 | name: 'Xiao', 108 | userInfo: { 109 | age: 5, 110 | city: 'Shanghai' 111 | } 112 | }); 113 | res = await client.put(TABLE_NAME, { 114 | id: 49, 115 | name: 'Supriya', 116 | userInfo: { 117 | age: 16, 118 | city: 'Bangalore' 119 | } 120 | }); 121 | res = await client.put(TABLE_NAME, { 122 | id:55, 123 | name: 'Rosa', 124 | userInfo: { 125 | age: 39, 126 | city: 'Seattle' 127 | } 128 | }); 129 | 130 | // Find user with id 49 with a simple query 131 | let statement = `SELECT * FROM ${TABLE_NAME} WHERE id = 49`; 132 | console.log('\nUse a simple query: %s', statement); 133 | await runQuery(client, statement); 134 | 135 | // Find all the Seattle dwellers with a prepared statement 136 | statement = `DECLARE $city STRING; SELECT * FROM ${TABLE_NAME} u WHERE \ 137 | u.userInfo.city = $city`; 138 | console.log(`\nUse a prepared statement: '${statement}'`); 139 | const preparedStmt = await client.prepare(statement); 140 | const city = 'Seattle'; 141 | console.log(' Set variable $city to "%s" in prepared statement', city); 142 | preparedStmt.set('$city', city); 143 | // We limit number of rows to 1 in each query invocation to illustrate 144 | // the use of the continuation key 145 | await runQuery(client, preparedStmt, 1); 146 | 147 | // Drop the table 148 | console.log('\nDrop table'); 149 | ddl = `DROP TABLE ${TABLE_NAME}`; 150 | res = await client.tableDDL(ddl); 151 | console.log(' Dropping table %s', res.tableName); 152 | 153 | // Wait for the table to be removed 154 | await client.forCompletion(res); 155 | console.log(' Table state is %s', res.tableState.name); 156 | } 157 | 158 | /* 159 | * Execute a query and print the results. Optional limit parameter is used to 160 | * limit the results of each query API invocation to that many rows. 161 | */ 162 | async function runQuery(client, stmt, limit) { 163 | const opt = { limit }; 164 | console.log('Query results:'); 165 | 166 | // Issue the query by looping over NoSQLClient.queryIterable. 167 | for await(const res of client.queryIterable(stmt, opt)) { 168 | // Each iteration over NoSQLClient.queryIterable returns a portion of 169 | // the result set. We iterate over the whole result set by using 170 | // for-await-of loop. 171 | for(let row of res.rows) { 172 | console.log(' %O', row); 173 | } 174 | } 175 | } 176 | 177 | queryExample(); 178 | -------------------------------------------------------------------------------- /test/unit/types/db_number.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import { expectTypeOf } from "expect-type"; 9 | import { Decimal } from "decimal.js"; 10 | import { DBNumberConfig, DBNumberConstructor, NumberLibConfig, NumberLibMethods, RoundingModesMap } from "../../../"; 11 | 12 | function testDBNumberConfig(nlCfg: NumberLibConfig) { 13 | let cfg: DBNumberConfig; 14 | 15 | cfg = "decimal.js"; 16 | cfg = class { constructor(val: string|number) {} } 17 | cfg = Decimal; 18 | cfg = nlCfg; 19 | 20 | // @ts-expect-error Invalid type for DBNumberConfig. 21 | cfg = 1; 22 | // @ts-expect-error Invalid type for DBNumberConfig. 23 | cfg = true; 24 | // @ts-expect-error Invalid type for DBNumberConstructor. 25 | cfg = class { constructor(val: number) {} } 26 | // @ts-expect-error Invalid type for NumLibConfig. 27 | cfg = { module: "decimal.js" }; 28 | } 29 | 30 | function testNumberLibConfig(methods: NumberLibMethods) { 31 | let cfg: NumberLibConfig; 32 | 33 | cfg = { Constructor: Decimal }; 34 | cfg.Constructor = "Decimal"; 35 | cfg.module = "module"; 36 | cfg.static = methods; 37 | cfg.instance = methods; 38 | cfg.precision = 10; 39 | cfg.roundingMode = 8; 40 | cfg.roundingMode = "HALF_UP"; 41 | 42 | cfg = { Constructor: Decimal }; 43 | cfg.getPrecision = "precision"; 44 | cfg.getPrecision = () => Decimal.precision; 45 | cfg.getPrecision = (cons: DBNumberConstructor) => Decimal.precision; 46 | cfg.getRoundingMode = "rounding"; 47 | cfg.getRoundingMode = () => Decimal.rounding; 48 | cfg.getRoundingMode = (cons: DBNumberConstructor) => Decimal.rounding; 49 | cfg.getRoundingMode = () => "UP"; 50 | cfg.RoundingModes = "modes"; 51 | cfg.RoundingModes = { UP: 1, DOWN: 2 }; 52 | 53 | // @ts-expect-error Missing constructor. 54 | cfg = {}; 55 | // @ts-expect-error Invalid constructor. 56 | cfg = { Constructor: () => new Decimal(1) }; 57 | // @ts-expect-error Invalid type for module. 58 | cfg.module = Buffer.alloc(100); 59 | // @ts-expect-error Invalid type for static. 60 | cfg.static = true; 61 | // @ts-expect-error Invalid method name in NumberLibMethods. 62 | cfg.static = { sub: "sub" }; 63 | // @ts-expect-error Invalid type for instance. 64 | cfg.instance = "instance"; 65 | // @ts-expect-error Too many arguments for add in NumberLibMethods. 66 | cfg.instance = { add: (x: Decimal, y: Decimal, z: Decimal) => 67 | new Decimal(2) }; 68 | // @ts-expect-error Invalid type for precision. 69 | cfg.precision = "10"; 70 | // @ts-expect-error Invalid type for getPrecision. 71 | cfg.getPrecision = 10; 72 | // @ts-expect-error Invalid argument type for getPrecision. 73 | cfg.getPrecision = (d: Decimal) => Decimal.precision; 74 | // @ts-expect-error Invalid type for getRoundingMode. 75 | cfg.getRoundingMode = 1; 76 | // @ts-expect-error Invalid argument type for getRoundingMode. 77 | cfg.getRoundingMode = (d: Decimal) => Decimal.rounding; 78 | // @ts-expect-error Invalid type for RoundingModes. 79 | cfg.RoundingModes = 10; 80 | // @ts-expect-error Invalid property in RoundingModesMap. 81 | cfg.RoundingModes = { UNSUPPORTED: 10 }; 82 | } 83 | 84 | let v: object = new Decimal(1); 85 | 86 | 87 | function testNumberLibMethods() { 88 | let methods: NumberLibMethods = {}; 89 | 90 | methods.compare = "compare"; 91 | methods.compare = (a: Decimal, b: Decimal) => 0; 92 | methods.valuesEqual = "equals"; 93 | methods.valuesEqual = (a, b) => true; 94 | methods.add = "add"; 95 | methods.add = (a: Decimal, b: Decimal) => a.plus(b); 96 | methods.subtract = "subtract"; 97 | methods.subtract = (a: Decimal, b: Decimal) => a.minus(b); 98 | methods.multiply = "multiply"; 99 | methods.multiply = (a: Decimal, b: Decimal) => a.mul(b); 100 | methods.divide = "divide"; 101 | methods.divide = (a: Decimal, b: Decimal) => a.div(b); 102 | 103 | // @ts-expect-error Invalid type for compare. 104 | methods.compare = 1; 105 | // @ts-expect-error Invalid type for compare. 106 | methods.compare = (a: Decimal, b: Decimal) => true; 107 | // @ts-expect-error Invalid type for valuesEqual. 108 | methods.valuesEqual = true; 109 | // @ts-expect-error Invalid type for valuesEqual. 110 | methods.valuesEqual = (a, b, c) => true; 111 | // @ts-expect-error Invalid type for valuesEqual. 112 | methods.valuesEqual = (a, b) => 0; 113 | // @ts-expect-error Invalid type for add. 114 | methods.add = 1; 115 | // @ts-expect-error Invalid type for add. 116 | methods.add = (a, b, c) => new Decimal(1); 117 | // @ts-expect-error Invalid type for add. 118 | methods.add = { add: "add" }; 119 | // @ts-expect-error Invalid type for subtract. 120 | methods.subtract = 2; 121 | // @ts-expect-error Invalid type for subtract. 122 | methods.subtract = (a, b, c, d) => new Decimal(1); 123 | // @ts-expect-error Invalid type for multiply. 124 | methods.multiply = true; 125 | // @ts-expect-error Invalid type for multiply. 126 | methods.multiply = (a, b, c, d) => new Decimal(1); 127 | // @ts-expect-error Invalid type for divide. 128 | methods.divide = {}; 129 | // @ts-expect-error Invalid type for divide. 130 | methods.divide = (a, b, c) => new Decimal(1); 131 | } 132 | 133 | function testRoundingModesMap() { 134 | let map: RoundingModesMap = {}; 135 | expectTypeOf(map.UP).toBeUnknown(); 136 | expectTypeOf(map.DOWN).toBeUnknown(); 137 | expectTypeOf(map.CEILING).toBeUnknown(); 138 | expectTypeOf(map.FLOOR).toBeUnknown(); 139 | expectTypeOf(map.HALF_UP).toBeUnknown(); 140 | expectTypeOf(map.HALF_DOWN).toBeUnknown(); 141 | expectTypeOf(map.HALF_EVEN).toBeUnknown(); 142 | 143 | // @ts-expect-error Invalid property of RoundingModesMap. 144 | map.up = 1; 145 | } 146 | -------------------------------------------------------------------------------- /test/unit/get.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const expect = require('chai').expect; 11 | const util = require('util'); 12 | 13 | const NoSQLArgumentError = require('../../index').NoSQLArgumentError; 14 | const Consistency = require('../../index').Consistency; 15 | const ErrorCode = require('../../index').ErrorCode; 16 | const NoSQLError = require('../../index').NoSQLError; 17 | const badMillis = require('./common').badMillis; 18 | const badConsistencies = require('./common').badConsistencies; 19 | const badTblNames = require('./common').badTblNames; 20 | const badDriverKeys = require('./common').badDriverKeys; 21 | const getBadServerKeys = require('./common').getBadServerKeys; 22 | const badOptions = require('./common').badOptions; 23 | const Utils = require('./utils'); 24 | const GET_TESTS = require('./data_tests').GET_TESTS; 25 | 26 | const compartment = Utils.config.compartment; 27 | 28 | const badOpts = [ 29 | ...badOptions, 30 | ...badMillis.map(timeout => ({ timeout })), 31 | ...badConsistencies.map(consistency => ({ 32 | consistency 33 | })) 34 | ]; 35 | 36 | function testGetNegative(client, tbl, key) { 37 | for(let badTblName of badTblNames) { 38 | it(`get with invalid table name: ${badTblName}`, async function() { 39 | return expect(client.get(badTblName, key)).to.eventually 40 | .be.rejected.and.satisfy(err => 41 | err instanceof NoSQLArgumentError && 42 | err._rejectedByDriver); 43 | }); 44 | } 45 | 46 | it('get on non-existent table', async function() { 47 | return expect(client.get('nosuchtable', key)).to.eventually.be 48 | .rejected.and.satisfy(err => err instanceof NoSQLError && 49 | err.errorCode == ErrorCode.TABLE_NOT_FOUND); 50 | }); 51 | 52 | for(let badKey of badDriverKeys) { 53 | it(`get on table ${tbl.name} with invalid driver key: \ 54 | ${util.inspect(badKey)}`, async function() { 55 | return expect(client.get(tbl.name, badKey)).to.eventually. 56 | be.rejected.and.satisfy(err => 57 | err instanceof NoSQLArgumentError && 58 | err._rejectedByDriver); 59 | }); 60 | } 61 | 62 | for(let badKey of getBadServerKeys(tbl, key)) { 63 | it(`get on table ${tbl.name} with invalid server key: \ 64 | ${util.inspect(badKey)}`, async function() { 65 | return expect(client.get(tbl.name, badKey)).to.eventually. 66 | be.rejected.and.satisfy(err => 67 | err instanceof NoSQLArgumentError && 68 | !err._rejectedByDriver); 69 | }); 70 | } 71 | 72 | for(let badOpt of badOpts) { 73 | it(`get on table ${tbl.name} with invalid options: \ 74 | ${util.inspect(badOpt)}`, async function() { 75 | return expect(client.get(tbl.name, key, badOpt)).to.eventually 76 | .be.rejected.and.satisfy(err => 77 | err instanceof NoSQLArgumentError && 78 | err._rejectedByDriver); 79 | }); 80 | } 81 | } 82 | 83 | function testGetRow(client, tbl, row) { 84 | const key = Utils.makePrimaryKey(tbl, row); 85 | 86 | it(`get on ${tbl.name} for key ${util.inspect(key)}`, async function() { 87 | const res = await client.get(tbl.name, key); 88 | Utils.verifyGetResult(client, res, tbl, row); 89 | }); 90 | 91 | it(`get on ${tbl.name} for key ${util.inspect(key)} with timeout and \ 92 | ${Utils.testEventualConsistency ? 'eventual' : 'absolute'} \ 93 | consistency`, async function() { 94 | const res = await client.get(tbl.name, key, { 95 | timeout: 12000, 96 | consistency: Utils.testEventualConsistency ? 97 | Consistency.EVENTUAL : Consistency.ABSOLUTE, 98 | compartment 99 | }); 100 | Utils.verifyGetResult(client, res, tbl, row); 101 | }); 102 | 103 | it(`get on ${tbl.name} for key ${util.inspect(key)} with absolute \ 104 | consistency`, async function() { 105 | const consistency = Consistency.ABSOLUTE; 106 | const res = await client.get(tbl.name, key, { consistency }); 107 | Utils.verifyGetResult(client, res, tbl, row, { consistency }); 108 | }); 109 | } 110 | 111 | function testGetNonExistent(client, tbl, key) { 112 | it(`get on ${tbl.name} for missing key ${util.inspect(key)} with timeout`, 113 | async function() { 114 | const res = await client.get(tbl.name, key, { 115 | timeout: 12000 116 | }); 117 | Utils.verifyGetResult(client, res, tbl); 118 | }); 119 | 120 | it(`get on ${tbl.name} for missing key ${util.inspect(key)} with \ 121 | absolute consistency`, async function() { 122 | const consistency = Consistency.ABSOLUTE; 123 | const res = await client.get(tbl.name, key, { consistency }); 124 | Utils.verifyGetResult(client, res, tbl, null, { consistency }); 125 | }); 126 | } 127 | 128 | function doTest(client, test) { 129 | describe(`Running ${test.desc}`, function() { 130 | before(async function() { 131 | await Utils.createTable(client, test.table); 132 | for(let row of test.rows) { 133 | await Utils.putRow(client, test.table, row); 134 | } 135 | }); 136 | after(async function() { 137 | await Utils.dropTable(client, test.table); 138 | }); 139 | testGetNegative(client, test.table, Utils.makePrimaryKey( 140 | test.table, test.rows[0])); 141 | test.rows.forEach(row => testGetRow(client, test.table, row)); 142 | //Out of range of test.rowIdStart - test.rowIdEnd-1 143 | const row = test.makeRow(test.rowIdEnd); 144 | testGetNonExistent(client, test.table, Utils.makePrimaryKey( 145 | test.table, row)); 146 | it('', () => {}); 147 | }); 148 | } 149 | 150 | Utils.runSequential('get tests', doTest, GET_TESTS); 151 | -------------------------------------------------------------------------------- /src/types/auth/kvstore/types.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { KVStoreAuthorizationProvider } from "./auth_provider"; 9 | 10 | /** 11 | * This configuration is required to authenticate against secure On-Premises 12 | * Oracle NoSQL Database. It should be set as {@link AuthConfig#kvstore}. 13 | * To authenticate against secure kvstore the driver needs user name and 14 | * password of existing store user that has required permissions to perform 15 | * needed database operations (see {@page connect-on-prem.md} on how to set 16 | * up access to secure On-Premise Oracle NoSQL database). The are 3 ways in 17 | * which these credentials may be specified. In order of increased security, 18 | * they are: 19 | *

    20 | *
  • You may set user name and password explicitly as 21 | * {@link user} and {@link password} (see 22 | * example). This is not very secure since password will be in memory in 23 | * plain text. If password is specified as {@link !Buffer | Buffer}, it will 24 | * be copied when {@link NoSQLClient} is created and erased when 25 | * {@link NoSQLClient} is closed.
  • 26 | *
  • You may store user name and password in separate JSON file and set 27 | * the path to this file as {@link credentials} property. 28 | * This file should contain credentials in the form of 29 | * {@link KVStoreCredentials}. It is more secure to store these credentials 30 | * in a separate file instead of main config file as access to this file may 31 | * be further restricted by appropriate permissions. In addition, the 32 | * credentials will not be stored in memory but loaded from the file every 33 | * time when login is required.
  • 34 | *
  • You may implement and use your own {@link KVStoreCredentialsProvider} 35 | * to access credentials in secure way (e.g. by storing them in a keystore or 36 | * in encrypted form). Set the provider instance as 37 | * {@link credentials} property. The credentials should be returned in the 38 | * form of {@link KVStoreCredentials}.
  • 39 | *
40 | * 41 | * See {@page connect-on-prem.md} tutorial on how to set up the driver to 42 | * access secure on-premise NoSQL store via proxy. Note that secure 43 | * on-premise NoSQL store uses the same endpoint for authentication 44 | * and to perform database operations. This endpoint must be using 45 | * https protocol. See {@link Config#endpoint}. 46 | *

47 | * If {@link autoRenew} property is set to true (which is 48 | * the default), after initial login is done, the driver will renew 49 | * authentication token each time it reaches half of its lifetime (half-point 50 | * between acquisition and expiration). Renew request requires only existing 51 | * token and does not require user credentials. If renew request fails, the 52 | * token will eventually expire and the driver will perform another login 53 | * (errors due to token expiration are automatically retried by the driver). 54 | * @see {@link KVStoreAuthorizationProvider} 55 | * @see {@link KVStoreCredentials} 56 | * @see {@link KVStoreCredentialsProvider} 57 | */ 58 | export interface KVStoreAuthConfig { 59 | /** 60 | * NoSQL database user name. May not be specified together with 61 | * {@link credentials} property. 62 | */ 63 | user?: string; 64 | 65 | /** 66 | * User's password. May only be specified if {@link user} property is 67 | * also specified. If password is represented as {@link !Buffer | Buffer}, 68 | * it must be UTF8-encoded. 69 | */ 70 | password?: string|Buffer; 71 | 72 | /** 73 | * A string representing file path to credentials file (absolute or 74 | * relative to current directory) or an instance of user-defined 75 | * credentials provider. May not be specified together with {@link user} 76 | * or {@link password} properties. 77 | */ 78 | credentials?: string | KVStoreCredentialsProvider | 79 | KVStoreCredentialsProvider["loadCredentials"]; 80 | 81 | /** 82 | * Timeout used for login and renew-token requests, in milliseconds. 83 | * @defaultValue 30000 (30 seconds) 84 | */ 85 | timeout?: number; 86 | 87 | /** 88 | * If set to true, the driver will attempt to renew the authentication 89 | * token half way before its expiration time. 90 | */ 91 | autoRenew?: boolean; 92 | } 93 | 94 | /** 95 | * This type encapsulates credentials required for authenticating with 96 | * on-premise NoSQL secure store and consists of user name and password. It 97 | * is also the format in which to store credentials in a JSON file as 98 | * described in {@link KVStoreAuthConfig}. 99 | */ 100 | export interface KVStoreCredentials { 101 | /** 102 | * NoSQL Database user name 103 | */ 104 | user: string; 105 | 106 | /** 107 | * User's password. If using {@link !Buffer | Buffer}, it must be 108 | * UTF8-encoded. When using {@link KVStoreCredentialsProvider} and the 109 | * password is represented as {@link !Buffer | Buffer}, the driver will 110 | * erase it as soon as store login is performed. 111 | */ 112 | password: Buffer|string; 113 | } 114 | 115 | /** 116 | * This interface may be implemented by applications to allow obtaining 117 | * {@link KVStoreCredentials} required to login to on-premise store in a 118 | * secure manner. {@link KVStoreCredentialsProvider} is set as 119 | * {@link KVStoreAuthConfig#credentials} property of 120 | * {@link KVStoreAuthConfig}. Instead of a class implementing this interface, 121 | * you may also set {@link KVStoreAuthConfig#credentials} to a function with 122 | * the signature of {@link loadCredentials}. 123 | */ 124 | export interface KVStoreCredentialsProvider { 125 | /** 126 | * Asynchronously load credentials required to login to secure on-premise 127 | * NoSQL database. 128 | * @async 129 | * @returns {Promise} Promise resolved with {@link KVStoreCredentials} or 130 | * rejected with an error. Properties of type {@link !Buffer | Buffer} 131 | * such as {@link KVStoreCredentials#password} will be erased once the 132 | * store login is performed. 133 | */ 134 | loadCredentials(): Promise; 135 | } 136 | -------------------------------------------------------------------------------- /test/unit/types/table_usage.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import { expectTypeOf } from "expect-type"; 9 | 10 | import { NoSQLClient, TableUsageOpt, TableUsage, TableUsageResult } 11 | from "../../../"; 12 | 13 | const client = new NoSQLClient("nosuchfile.json"); 14 | 15 | function testTableUsageOpt() { 16 | let opt: TableUsageOpt = {}; 17 | 18 | opt.compartment = "c"; 19 | opt.timeout = 10000; 20 | opt.startTime = new Date(); 21 | opt.startTime = "2023-04-28T00:00:00Z"; 22 | opt.startTime = 1000000; 23 | opt.endTime = new Date(); 24 | opt.endTime = "2023-04-28T00:00:00Z"; 25 | opt.endTime = 1000000; 26 | opt.limit = 10; 27 | opt.startIndex = 10; 28 | 29 | // @ts-expect-error Invalid type for compartment. 30 | opt.compartment = 1; 31 | // @ts-expect-error Invalid namespace option for cloud-only API. 32 | opt.namespace = "namespace"; 33 | // @ts-expect-error Invalid type for timeout. 34 | opt.timeout = "10000"; 35 | // @ts-expect-error Invalid type for startTime. 36 | opt.startTime = { d: 1000 }; 37 | // @ts-expect-error Invalid type for endTime. 38 | opt.endTime = true; 39 | // @ts-expect-error Invalid type for limit. 40 | opt.limit = "10"; 41 | // @ts-expect-error Invalid type for startIndex. 42 | opt.startIndex = "10"; 43 | // @ts-expect-error Invalid extra property. 44 | opt.lastIndex = 0; 45 | } 46 | 47 | function testTableUsage(tu: TableUsage) { 48 | expectTypeOf(tu.startTime).toEqualTypeOf(); 49 | expectTypeOf(tu.secondsInPeriod).toBeNumber(); 50 | expectTypeOf(tu.readUnits).toBeNumber(); 51 | expectTypeOf(tu.writeUnits).toBeNumber(); 52 | expectTypeOf(tu.storageGB).toBeNumber(); 53 | expectTypeOf(tu.readThrottleCount).toBeNumber(); 54 | expectTypeOf(tu.writeThrottleCount).toBeNumber(); 55 | expectTypeOf(tu.storageThrottleCount).toBeNumber(); 56 | expectTypeOf(tu.maxShardUsagePercent).toBeNumber(); 57 | 58 | // all properties of TableUsage must be read-only 59 | expectTypeOf>().toEqualTypeOf(); 60 | 61 | // @ts-expect-error Invalid property in TableUsage. 62 | tu.tableName; 63 | } 64 | 65 | function testTableUsageResult(res: TableUsageResult) { 66 | expectTypeOf(res.tableName).toBeString(); 67 | expectTypeOf(res.usageRecords).toEqualTypeOf(); 68 | expectTypeOf(res.nextIndex).toBeNumber(); 69 | 70 | // all properties of TableUsageResult must be read-only 71 | expectTypeOf>() 72 | .toEqualTypeOf(); 73 | 74 | // @ts-expect-error Invalid property in TableUsageResult. 75 | res.lastIndex; 76 | } 77 | 78 | async function testGetTableUsage() { 79 | let res: TableUsageResult; 80 | 81 | expectTypeOf(client.getTableUsage).toBeFunction(); 82 | expectTypeOf(client.getTableUsage).parameters 83 | .toEqualTypeOf<[string, TableUsageOpt?]>(); 84 | expectTypeOf(client.getTableUsage).parameter(0).toBeString(); 85 | expectTypeOf(client.getTableUsage).parameter(1) 86 | .toEqualTypeOf(); 87 | expectTypeOf(client.getTableUsage).returns.not 88 | .toEqualTypeOf(); 89 | expectTypeOf(client.getTableUsage).returns.resolves 90 | .toEqualTypeOf(); 91 | expectTypeOf(client.getTableUsage).toBeCallableWith("table"); 92 | expectTypeOf(client.getTableUsage).toBeCallableWith("table", 93 | { timeout: 1 }); 94 | expectTypeOf(client.getTableUsage).toBeCallableWith("table", 95 | { startTime: new Date(), endTime: 1000000, limit: 1, startIndex: 2 }); 96 | 97 | res = await client.getTableUsage("table"); 98 | res = await client.getTableUsage("table", { compartment: "compartment", 99 | timeout: 5000, limit: 2 }); 100 | 101 | // @ts-expect-error Missing table name. 102 | client.getTableUsage(); 103 | // @ts-expect-error Missing table name. 104 | client.getTableUsage({ timeout: 1 }); 105 | // @ts-expect-error Invalid table name. 106 | client.getTableUsage(123); 107 | // @ts-expect-error Invalid option. 108 | client.getTableUsage("table", { lastIndex: 1 }); 109 | } 110 | 111 | async function testTableUsageIterable() { 112 | let it: AsyncIterable; 113 | let res: TableUsageResult; 114 | 115 | expectTypeOf(client.tableUsageIterable).toBeFunction(); 116 | expectTypeOf(client.tableUsageIterable).parameters 117 | .toEqualTypeOf<[string, TableUsageOpt?]>(); 118 | expectTypeOf(client.tableUsageIterable).parameter(0).toBeString(); 119 | expectTypeOf(client.tableUsageIterable).parameter(1) 120 | .toEqualTypeOf(); 121 | expectTypeOf(client.tableUsageIterable).returns.not 122 | .toEqualTypeOf(); 123 | expectTypeOf(client.tableUsageIterable).returns 124 | .toEqualTypeOf>(); 125 | expectTypeOf(client.tableUsageIterable).toBeCallableWith("table"); 126 | expectTypeOf(client.tableUsageIterable).toBeCallableWith("table", 127 | { timeout: 1 }); 128 | expectTypeOf(client.tableUsageIterable).toBeCallableWith("table", 129 | { startTime: new Date(), endTime: 1000000, limit: 1, startIndex: 2 }); 130 | 131 | it = client.tableUsageIterable("table"); 132 | it = client.tableUsageIterable("table", { compartment: "compartment", 133 | timeout: 5000, limit: 2 }); 134 | 135 | for await (const itRes of it) { 136 | expectTypeOf(itRes).toEqualTypeOf(); 137 | } 138 | 139 | // @ts-expect-error Missing table name. 140 | client.tableUsageIterable(); 141 | // @ts-expect-error Missing table name. 142 | client.tableUsageIterable({ timeout: 1 }); 143 | // @ts-expect-error Invalid table name. 144 | client.tableUsageIterable(123); 145 | // @ts-expect-error Invalid option. 146 | client.tableUsageIterable("table", { lastIndex: 1 }); 147 | // @ts-expect-error Using wrong return type. 148 | res = client.tableUsageIterable("table"); 149 | // @ts-expect-error Using wrong return type. 150 | res = await client.tableUsageIterable("table"); 151 | } 152 | -------------------------------------------------------------------------------- /src/types/durability.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { Durability } from "./param"; 9 | 10 | /** 11 | * On premise only: see {@link Durability} 12 | * Defines the synchronization policy to be used when committing a 13 | * transaction. High levels of synchronization offer a greater guarantee 14 | * that the transaction is persistent to disk, but trade that off for 15 | * lower performance. 16 | * @since 5.3.0 17 | */ 18 | export enum SyncPolicy { 19 | /** 20 | * Write and synchronously flush the log on transaction commit. 21 | * Transactions exhibit all the ACID (atomicity, consistency, 22 | * isolation, and durability) properties. 23 | * @since 5.3.0 24 | */ 25 | SYNC = "SYNC", 26 | 27 | /** 28 | * Do not write or synchronously flush the log on transaction commit. 29 | * Transactions exhibit the ACI (atomicity, consistency, and isolation) 30 | * properties, but not D (durability); that is, database integrity will 31 | * be maintained, but if the application or system fails, it is 32 | * possible some number of the most recently committed transactions may 33 | * be undone during recovery. The number of transactions at risk is 34 | * governed by how many log updates can fit into the log buffer, how 35 | * often the operating system flushes dirty buffers to disk, and how 36 | * often log checkpoints occur. 37 | * @since 5.3.0 38 | */ 39 | NO_SYNC = "NO_SYNC", 40 | 41 | /** 42 | * Write but do not synchronously flush the log on transaction commit. 43 | * Transactions exhibit the ACI (atomicity, consistency, and isolation) 44 | * properties, but not D (durability); that is, database integrity will 45 | * be maintained, but if the operating system fails, it is possible 46 | * some number of the most recently committed transactions may be 47 | * undone during recovery. The number of transactions at risk is 48 | * governed by how often the operating system flushes dirty buffers to 49 | * disk, and how often log checkpoints occur. 50 | * @since 5.3.0 51 | */ 52 | WRITE_NO_SYNC = "WRITE_NO_SYNC" 53 | } 54 | 55 | /** 56 | * On premise only: see {@link Durability} 57 | * A replicated environment makes it possible to increase an application's 58 | * transaction commit guarantees by committing changes to its replicas on 59 | * the network. ReplicaAckPolicy defines the policy for how such network 60 | * commits are handled. 61 | * @since 5.3.0 62 | */ 63 | export enum ReplicaAckPolicy { 64 | /** 65 | * All replicas must acknowledge that they have committed the 66 | * transaction. This policy should be selected only if your replication 67 | * group has a small number of replicas, and those replicas are on 68 | * extremely reliable networks and servers. 69 | * @since 5.3.0 70 | */ 71 | ALL = "ALL", 72 | 73 | /** 74 | * No transaction commit acknowledgments are required and the master 75 | * will never wait for replica acknowledgments. In this case, 76 | * transaction durability is determined entirely by the type of commit 77 | * that is being performed on the master. 78 | * @since 5.3.0 79 | */ 80 | NONE = "NONE", 81 | 82 | /** 83 | * A simple majority of replicas must acknowledge that they have 84 | * committed the transaction. This acknowledgment policy, in 85 | * conjunction with an election policy which requires at least a simple 86 | * majority, ensures that the changes made by the transaction remains 87 | * durable if a new election is held. 88 | * @since 5.3.0 89 | */ 90 | SIMPLE_MAJORITY = "SIMPLE_MAJORITY" 91 | } 92 | 93 | /** 94 | * @classdesc 95 | * On-premises service only. 96 | * Helper class that defines some useful {@link Durability} constants as well 97 | * as a method to create {@link Durability} instances. Note that durability 98 | * instance is a plain JavaScript object as described in {@link Durability}. 99 | * @see Durability 100 | */ 101 | export class Durabilities { 102 | /** 103 | * @hidden 104 | */ 105 | private constructor(); 106 | 107 | /** 108 | * A convenience constant that defines a durability policy with COMMIT_SYNC 109 | * for Master commit synchronization. 110 | *

111 | * The policies default to COMMIT_NO_SYNC for commits of replicated 112 | * transactions that need acknowledgment and SIMPLE_MAJORITY for the 113 | * acknowledgment policy. 114 | */ 115 | static readonly COMMIT_SYNC: Durability; 116 | 117 | /** 118 | * A convenience constant that defines a durability policy with 119 | * COMMIT_NO_SYNC for Master commit synchronization. 120 | *

121 | * The policies default to COMMIT_NO_SYNC for commits of replicated 122 | * transactions that need acknowledgment and SIMPLE_MAJORITY for the 123 | * acknowledgment policy. 124 | */ 125 | static readonly COMMIT_NO_SYNC: Durability; 126 | 127 | /** 128 | * A convenience constant that defines a durability policy with 129 | * COMMIT_WRITE_NO_SYNC for Master commit synchronization. 130 | *

131 | * The policies default to COMMIT_NO_SYNC for commits of replicated 132 | * transactions that need acknowledgment and SIMPLE_MAJORITY for the 133 | * acknowledgment policy. 134 | */ 135 | static COMMIT_WRITE_NO_SYNC: Durability; 136 | 137 | /** 138 | * Creates a {@link Durability} instance while validating the arguments 139 | * passed. 140 | * 141 | * @param {SyncPolicy} masterSync Sync policy when committing transaction 142 | * on the master node 143 | * @param {SyncPolicy} replicaSync Sync policy when committing transaction 144 | * at a replica node 145 | * @param {ReplicaAckPolicy} replicaAck The acknowledgment policy used 146 | * when obtaining transaction acknowledgments from replicas 147 | * @returns {Durability} Durability object 148 | * @throws {NoSQLArgumentError} If any of the provided values for 149 | * masterSync, replicaSync or replicaAck is 150 | * missing or invalid. 151 | */ 152 | static create(masterSync: SyncPolicy, replicaSync: SyncPolicy, 153 | replicaAck: ReplicaAckPolicy): Durability; 154 | } 155 | -------------------------------------------------------------------------------- /test/unit/types/get_table.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import { expectTypeOf } from "expect-type"; 9 | 10 | import { NoSQLClient, TableResult, AdminResult, TableState, CompletionOpt, 11 | GetTableOpt } 12 | from "../../../"; 13 | 14 | const client = new NoSQLClient("nosuchfile.json"); 15 | 16 | function testGetTableOpt() { 17 | let opt: GetTableOpt = {}; 18 | opt.compartment = "c"; 19 | opt.namespace = "n"; 20 | 21 | // @ts-expect-error Invalid type for compartment. 22 | opt.compartment = 1; 23 | // @ts-expect-error Invalid type for namespace. 24 | opt.namespace = 1; 25 | 26 | opt.timeout = 10000; 27 | 28 | // @ts-expect-error Invalid type for timeout. 29 | opt.timeout = "10000"; 30 | // @ts-expect-error Invalid extra property. 31 | opt.complete = true; 32 | // @ts-expect-error Invalid extra property. 33 | opt.delay = 1000; 34 | // @ts-expect-error Invalid extra property. 35 | opt.other = 1; 36 | } 37 | 38 | function testCompletionOpt() { 39 | let opt: CompletionOpt = {}; 40 | opt.compartment = "c"; 41 | opt.namespace = "n"; 42 | 43 | // @ts-expect-error Invalid type for compartment. 44 | opt.compartment = 1; 45 | // @ts-expect-error Invalid type for namespace. 46 | opt.namespace = 1; 47 | 48 | opt.timeout = 10000; 49 | 50 | // @ts-expect-error Invalid type for timeout. 51 | opt.timeout = "10000"; 52 | // @ts-expect-error Invalid extra property. 53 | opt.complete = true; 54 | // @ts-expect-error Invalid type for delay property. 55 | opt.delay = "1000"; 56 | 57 | opt.delay = 1000; 58 | 59 | // @ts-expect-error Invalid extra property. 60 | opt.other = 1; 61 | } 62 | 63 | async function testGetTable(res: TableResult) { 64 | expectTypeOf(client.getTable).toBeFunction(); 65 | expectTypeOf(client.getTable).parameters 66 | .toEqualTypeOf<[string|TableResult, GetTableOpt?]>(); 67 | expectTypeOf(client.getTable).parameter(0) 68 | .toEqualTypeOf(); 69 | expectTypeOf(client.getTable).parameter(1) 70 | .toEqualTypeOf(); 71 | expectTypeOf(client.getTable).returns.not.toEqualTypeOf(); 72 | expectTypeOf(client.getTable).returns.resolves 73 | .toEqualTypeOf(); 74 | expectTypeOf(client.getTable).toBeCallableWith("table"); 75 | expectTypeOf(client.getTable).toBeCallableWith("table", { timeout: 1 }); 76 | expectTypeOf(client.getTable).toBeCallableWith(res); 77 | expectTypeOf(client.getTable).toBeCallableWith(res, { timeout: 1 }); 78 | 79 | await client.getTable("table"); 80 | await client.getTable(res); 81 | await client.getTable(res, { compartment: "compartment", timeout: 5000 }); 82 | 83 | // @ts-expect-error Invalid table name or result. 84 | client.getTable({ timeout: 1 }); 85 | // @ts-expect-error Invalid table name or result. 86 | client.getTable(123); 87 | // @ts-expect-error Invalid option. 88 | client.getTable("table", { delay: 1000 }); 89 | } 90 | 91 | async function testForTableState(res: TableResult) { 92 | expectTypeOf(client.forTableState).toBeFunction(); 93 | expectTypeOf(client.forTableState).parameters 94 | .toEqualTypeOf<[string, TableState, CompletionOpt?]>(); 95 | expectTypeOf(client.forTableState).parameter(0).toBeString(); 96 | expectTypeOf(client.forTableState).parameter(1) 97 | .toEqualTypeOf(); 98 | expectTypeOf(client.forTableState).parameter(2) 99 | .toEqualTypeOf(); 100 | expectTypeOf(client.forTableState).returns.not 101 | .toEqualTypeOf(); 102 | expectTypeOf(client.forTableState).returns.resolves 103 | .toEqualTypeOf(); 104 | expectTypeOf(client.forTableState).toBeCallableWith("table", 105 | TableState.ACTIVE); 106 | expectTypeOf(client.forTableState).toBeCallableWith("table", 107 | TableState.ACTIVE, { timeout: 1, delay: 1 }); 108 | 109 | await client.forTableState("table", TableState.DROPPED); 110 | await client.forTableState("table", TableState.ACTIVE, 111 | { compartment: "compartment", timeout: 5000 }); 112 | 113 | // @ts-expect-error Missing table state argument. 114 | client.forTableState("table"); 115 | // @ts-expect-error Invalid table name. 116 | client.forTableState(res, TableState.ACTIVE); 117 | // @ts-expect-error Invalid table state. 118 | client.forTableState("table", "ACTIVE"); 119 | // @ts-expect-error Invalid table state. 120 | client.forTableState("table", 1); 121 | // @ts-expect-error Invalid table state. 122 | client.forTableState("table", TableState.OK); 123 | // @ts-expect-error Invalid CompletionOpt. 124 | client.forTableState("table", TableState.ACTIVE, { complete: true }); 125 | } 126 | 127 | async function testForCompletion(tableRes: TableResult, 128 | adminRes: AdminResult) { 129 | expectTypeOf(client.forCompletion).toBeFunction(); 130 | expectTypeOf(client.forCompletion).parameters 131 | .toMatchTypeOf<[TableResult|AdminResult, CompletionOpt?]>(); 132 | expectTypeOf(client.forCompletion).parameter(0) 133 | .toMatchTypeOf(); 134 | expectTypeOf(client.forCompletion).parameter(1) 135 | .toEqualTypeOf(); 136 | expectTypeOf(client.forCompletion).returns.not 137 | .toMatchTypeOf(); 138 | expectTypeOf(client.forCompletion).returns.resolves 139 | .toMatchTypeOf(); 140 | 141 | tableRes = await client.forCompletion(tableRes); 142 | tableRes = await client.forCompletion(tableRes, { timeout: 10000 }); 143 | adminRes = await client.forCompletion(adminRes); 144 | adminRes = await client.forCompletion(adminRes, { delay: 1000 }); 145 | 146 | // @ts-expect-error Missing table or admin state argument. 147 | client.forCompletion(); 148 | // @ts-expect-error Invalid table or admin state argument. 149 | client.forCompletion({}); 150 | // @ts-expect-error Invalid CompletionOpt. 151 | client.forCompletion(tableRes, 1); 152 | // @ts-expect-error Invalid CompletionOpt. 153 | client.forCompletion("table", TableState.ACTIVE, { complete: true }); 154 | 155 | // @ts-expect-error Wrong result type. 156 | adminRes = await client.forCompletion(tableRes); 157 | // @ts-expect-error Wrong result type. 158 | tableRes = await client.forCompletion(adminRes); 159 | } 160 | -------------------------------------------------------------------------------- /examples/javascript/rate_limiting_example.js: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | /** 9 | * A simple program to demonstrate how to enable and use rate limiting. 10 | * 11 | * This example should only be run against CloudSim, as the on-premise 12 | * Oracle NoSQL database currently does not report read/write throughput 13 | * used by rate limiting logic. 14 | * 15 | * This example could be used with the cloud service, but it generates a 16 | * significant amount of data, which may use up your resources. 17 | * 18 | * This example does the following: 19 | * 1) Create the table. 20 | * 2) Do bunch of put operations continuously for set period of time. Collect 21 | * usage statistics and display it together with expected limits. 22 | * 3) Do the same for get operations. 23 | * 4) Drop the table. 24 | */ 25 | 26 | 'use strict'; 27 | 28 | const fs = require('fs'); 29 | 30 | const nosqldb = require('oracle-nosqldb'); 31 | 32 | const NoSQLClient = nosqldb.NoSQLClient; 33 | 34 | // Target table used by this example 35 | const TABLE_NAME = 'AudienceData'; 36 | 37 | // Usage: rate_limiting_example.js [] 38 | 39 | async function rateLimitingExample() { 40 | let client; 41 | try { 42 | // JSON config file path is an optional parameter. If not specified, 43 | // it is assumed we are using Oracle Cloud Service where credentials 44 | // are supplied in default OCI configuration file (~/.oci/config) 45 | // using default profile (DEFAULT). 46 | const configFile = process.argv[2]; 47 | 48 | // In order to enable rate limiting, rateLimiter property should be 49 | // set in the initial config. In this example, we assume that the 50 | // config file may not necessarily have this property so we parse 51 | // and read the config file and set rateLimiter property if it was not 52 | // set. 53 | const config = JSON.parse(fs.readFileSync(configFile)); 54 | if (config.rateLimiter == null) { 55 | Object.assign(config, { 56 | rateLimiter: true 57 | }); 58 | } 59 | 60 | client = new NoSQLClient(config); 61 | console.log('Created NoSQLClient instance'); 62 | 63 | await run(client); 64 | console.log('Success!'); 65 | } catch (err) { 66 | console.error(' Error: ' + err.message); 67 | if (err.operation) { 68 | console.error(' from: '); 69 | console.error(err.operation); 70 | } 71 | } finally { 72 | if (client) { 73 | client.close(); 74 | } 75 | } 76 | } 77 | 78 | 79 | 80 | async function run(client) { 81 | // Create the table 82 | const createDDL = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} \ 83 | (id LONG, data STRING, PRIMARY KEY(id))`; 84 | console.log('Create table ' + TABLE_NAME); 85 | let res = await client.tableDDL(createDDL, { 86 | tableLimits: { 87 | readUnits: 50, 88 | writeUnits: 50, 89 | storageGB: 50 90 | } 91 | }); 92 | 93 | // Wait for the operation completion 94 | await client.forCompletion(res); 95 | console.log(' Table %s is created', res.tableName); 96 | 97 | 98 | // Create records of random sizes 99 | const minSize = 100; 100 | const maxSize = 10000; 101 | 102 | // Do a bunch of write ops, verify our usage matches limits 103 | await doRateLimitedOps(client, 104 | 15, // seconds 105 | true, // writes 106 | 50, // WUs limit 107 | 2000, // maxRows 108 | minSize, 109 | maxSize); 110 | 111 | // Do a bunch of read ops, verify our usage matches limits 112 | await doRateLimitedOps(client, 113 | 15, // seconds 114 | false, // reads 115 | 50, // RUs limit 116 | 2000, // maxRows 117 | minSize, 118 | maxSize); 119 | 120 | // Drop the table 121 | console.log('\nDrop table'); 122 | const dropDDL = `DROP TABLE IF EXISTS ${TABLE_NAME}`; 123 | res = await client.tableDDL(dropDDL); 124 | 125 | // Wait for the table to be removed 126 | await client.forCompletion(res); 127 | console.log(' Operation completed'); 128 | } 129 | 130 | /** 131 | * Runs puts and gets continuously for N seconds. 132 | * 133 | * Verify that the resultant RUs/WUs used match the 134 | * given rate limits. 135 | */ 136 | async function doRateLimitedOps(client, numSeconds, doWrites, limit, maxRows, 137 | minSize, maxSize) { 138 | // Generate a string of maxSize with all "x"s in it 139 | const userData = doWrites ? 'x'.repeat(maxSize) : null; 140 | 141 | const startTime = Date.now(); 142 | const endTime = startTime + (numSeconds * 1000); 143 | 144 | console.log(`Running continuous ${doWrites ? 'writes' : 'reads'}' for \ 145 | ${numSeconds} seconds`); 146 | 147 | // Keep track of how many units we used 148 | let unitsUsed = 0; 149 | 150 | // With rate limiting enabled, we can find the amount of time our 151 | // operation was delayed due to rate limiting by getting the value 152 | // from the result using getRateLimitDelayedMs(). 153 | let delayMs = 0; 154 | 155 | // Total count of read or write operations 156 | let opCount = 0; 157 | 158 | do { 159 | let id = Math.floor(Math.random() * maxRows); 160 | if (doWrites) { 161 | const recSize = minSize + 162 | Math.floor(Math.random() * (maxSize - minSize)); 163 | const pRes = await client.put(TABLE_NAME, { 164 | id, 165 | data: userData.substring(0, recSize) 166 | }); 167 | unitsUsed += pRes.consumedCapacity.writeUnits; 168 | delayMs += pRes.consumedCapacity.writeRateLimitDelay; 169 | } else { 170 | const gRes = await client.get(TABLE_NAME, { id }); 171 | unitsUsed += gRes.consumedCapacity.readUnits; 172 | delayMs += gRes.consumedCapacity.readRateLimitDelay; 173 | } 174 | opCount++; 175 | } while (Date.now() < endTime); 176 | 177 | numSeconds = (Date.now() - startTime) / 1000; 178 | 179 | unitsUsed = unitsUsed / numSeconds; 180 | 181 | console.log(`${doWrites ? 'Writes' : 'Reads'}: average usage = \ 182 | ${unitsUsed.toFixed(3)} ${doWrites ? 'WUs' : 'RUs'} \ 183 | (expected around ${limit})`); 184 | 185 | console.log(`Total operations performed: ${opCount}`); 186 | console.log(`Total rate limiter delay time = ${delayMs.toFixed(3)} ms`); 187 | } 188 | 189 | rateLimitingExample(); 190 | -------------------------------------------------------------------------------- /src/types/stmt.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { ConsumedCapacity, ConsumedCapacityResult } from "./result"; 9 | import type { FieldValue } from "./data"; 10 | 11 | /** 12 | * Defines classes related to SQL statement and query execution such as 13 | * {@link PreparedStatement}. 14 | */ 15 | 16 | /** 17 | * @classdesc A class encapsulating a prepared query statement. It includes 18 | * state that can be sent to a server and executed without re-parsing the 19 | * query. It includes bind variables which may be set for each successive use 20 | * of the query. PreparedStatement object is returned as a result of 21 | * {@link NoSQLClient#prepare} method. It can be passed to 22 | * {@link NoSQLClient#query} and {@link NoSQLClient#queryIterable} methods for 23 | * execution and be reused for multiple queries, potentially with different 24 | * values of bind variables. 25 | *

26 | * You may share an instance of {@link PreparedStatement} by queries running 27 | * async-concurrently, e.g. queries invoked concurrently by different async 28 | * functions. This is referred to as async-safety: 29 | *
30 | * An instance of {@link PreparedStatement} is async-safe if bind variables 31 | * are not used. If bind variables are used, it is not async-safe. In this 32 | * case, you can construct additional instances of {@link PreparedStatement} 33 | * using {@link PreparedStatement#copyStatement} method in order to share it 34 | * among async-concurrent queries. 35 | * @extends {PrepareResult} 36 | */ 37 | export class PreparedStatement implements ConsumedCapacityResult { 38 | 39 | /** 40 | * @hidden 41 | */ 42 | private constructor(); 43 | 44 | /** 45 | * @inheritDoc 46 | */ 47 | readonly consumedCapacity?: ConsumedCapacity; 48 | 49 | /** 50 | * Sets and gets the bindings object explicitly. Bindings object is an 51 | * object which properties contain the bind variables for this prepared 52 | * statement. For each variable, binding object has property which name 53 | * is the variable name and the value is the variable value. Note that 54 | * "$" in the variable name is included in its property name. For 55 | * positional variables, the names are determined by the query engine. 56 | * @example 57 | * Setting bindings. 58 | * ```ts 59 | * prepStmt.bindings = { 60 | * $id: 100, 61 | * $name: 'John' 62 | * }; 63 | * // This is equivalent to: 64 | * prepStmt.set('$id', 100); 65 | * prepStmt.set('$name', 'John'); 66 | * ``` 67 | */ 68 | bindings: { [name: string]: FieldValue }; 69 | 70 | /** 71 | * SQL text of this prepared statement. 72 | * @readonly 73 | */ 74 | readonly sql: string; 75 | 76 | /** 77 | * Query execution plan printout if was requested by 78 | * {@link NoSQLClient#prepare} (see {@link PrepareOpt#getQueryPlan}), 79 | * otherwise undefined. 80 | * @readonly 81 | */ 82 | readonly queryPlan: string; 83 | 84 | /** 85 | * JSON representation of the query result schema if was requested by 86 | * {@link NoSQLClient#prepare} (see {@link PrepareOpt#getResultSchema}), 87 | * otherwise undefined. 88 | * @readonly 89 | */ 90 | readonly resultSchema: string; 91 | 92 | /** 93 | * Binds a variable to use for the query. The variable can be identified 94 | * either by its name or its position. 95 | *

96 | * To bind by name, pass a name of the variable as it was declared in 97 | * DECLARE statement of the query. 98 | *

99 | * You can also bind a variable by its position within the query string. 100 | * The positions start at 1. The variable that appears first in the query 101 | * text has position 1, the variable that appears second has position 2 102 | * and so on. Binding by position is useful for queries where bind 103 | * variables identified by "?" are used instead of named variables (but it 104 | * can be used for both types of variables). 105 | *

106 | * Existing variables with the same name or position are silently 107 | * overwritten. The names, positions and types are validated when the 108 | * query is executed. 109 | * 110 | * @example 111 | * Binding variables by name. 112 | * ```ts 113 | * let client = new NoSQLClient(//..... 114 | * let prepStmt = await client.prepare( 115 | * 'DECLARE $id INTEGER; $sal DOUBLE; SELECT id, firstName, lastName ' + 116 | * 'FROM Emp WHERE id <= $id AND salary <= $sal'); 117 | * ps.set('$id', 1100); 118 | * .set('$sal', 100500); 119 | * for await(const res of client.queryIterable(stmt)) { 120 | * //..... 121 | * } 122 | * ps.set('$id', 2000); 123 | * for await(const res of client.queryIterable(stmt)) { 124 | * //..... 125 | * } 126 | * //..... 127 | * ``` 128 | * @example 129 | * Binding variables by position. 130 | * ```ts 131 | * let prepStmt = await client.prepare( 132 | * 'SELECT id, firstName, lastName FROM Emp WHERE ' + 133 | * 'id <= ? AND salary <= ?'); 134 | * ps.set(1, 1100) 135 | * .set(2, 100500); 136 | * //..... 137 | * ``` 138 | * @param {string|number} nameOrPosition Name or position of the variable 139 | * @param {FieldValue} val Value of the variable of the appropriate type 140 | * @returns {PreparedStatement} This instance for chaining 141 | * @throws {NoSQLArgumentError} If binding by position and the position is 142 | * invalid. 143 | */ 144 | set(nameOrPosition: string|number, val: FieldValue): PreparedStatement; 145 | 146 | /** 147 | * Clears all variables in bindings for this prepared statement. 148 | * @returns {PreparedStatement} This instance for chaining 149 | */ 150 | clearAll(): PreparedStatement; 151 | 152 | /** 153 | * Returns a copy of this prepared statement without its variables. 154 | *

155 | * This method returns a new instance of {@link PreparedStatement} that 156 | * shares this object's prepared query, which is immutable, but does not 157 | * share its variables. Use this method when you need to execute the same 158 | * prepared query async-concurrently (call this method to create a new copy 159 | * for each additional concurrent query). 160 | * @returns {PreparedStatement} A copy of this prepared statement without 161 | * its variables 162 | */ 163 | copyStatement(): PreparedStatement; 164 | } 165 | -------------------------------------------------------------------------------- /src/types/ttl_util.d.ts: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2018, 2025 Oracle and/or its affiliates. All rights reserved. 3 | * 4 | * Licensed under the Universal Permissive License v 1.0 as shown at 5 | * https://oss.oracle.com/licenses/upl/ 6 | */ 7 | 8 | import type { TimeToLive } from "./param"; 9 | import type { NoSQLArgumentError } from "./error"; 10 | 11 | /** 12 | * @classdesc TTLUtil is a utility class that may be used to create and 13 | * manage {@link TimeToLive} objects and convert between TTL and record 14 | * expiration time. TTL behavior and relation to record expiration time 15 | * is described in {@link TimeToLive}. Note that on input durations, TTL 16 | * objects and expiration times are validated for correctness and 17 | * {@link NoSQLArgumentError} is thrown if the representation is invalid. 18 | * @see {@link TimeToLive} 19 | */ 20 | export class TTLUtil { 21 | /** 22 | * @hidden 23 | */ 24 | private constructor(); 25 | 26 | /** 27 | * Convenience constant to indicate that the row should not expire. It 28 | * can be passed as TTL to a put operation to remove expiration from 29 | * existing row. 30 | */ 31 | static readonly DO_NOT_EXPIRE: TimeToLive; 32 | 33 | /** 34 | * Convenience constant representing number of milliseconds in 1 hour. 35 | */ 36 | static readonly MILLIS_IN_HOUR: number; 37 | 38 | /** 39 | * Convenience constant representing number of milliseconds in 1 day. 40 | */ 41 | static readonly MILLIS_IN_DAY: number; 42 | 43 | /** 44 | * Creates TTL with duration of hours 45 | * @param {number} hours Number of hours as positive integer or Infinity 46 | * @returns {TimeToLive} TTL object 47 | * @throws {NoSQLArgumentError} if 'hours' parameter is invalid 48 | */ 49 | static ofHours(hours: number): TimeToLive; 50 | 51 | /** 52 | * Creates TTL with duration of days 53 | * @param {number} days Number of days as positive integer or Infinity 54 | * @returns {TimeToLive} TTL object 55 | * @throws {NoSQLArgumentError} if 'days' parameter is invalid 56 | */ 57 | static ofDays(days: number): TimeToLive; 58 | 59 | /** 60 | * Returns the number of days in the TTL. If the TTL is specified in 61 | * hours, the resulting days value is rounded down, which will result in 0 62 | * if TTL.hours < 24. 63 | * @param {TimeToLive} ttl TTL object 64 | * @returns {number} Number of days in the TTL object or Infinity 65 | * @throws {NoSQLArgumentError} if TTL object is invalid 66 | */ 67 | static toDays(ttl: TimeToLive): number; 68 | 69 | /** 70 | * Returns the number of hours in the TTL. If the TTL is specified in 71 | * days, the return value is TTL.days * 24. 72 | * @param {TimeToLive} ttl TTL object 73 | * @returns {number} Number of hours in the TTL object or Infinity 74 | * @throws {NoSQLArgumentError} if TTL object is invalid 75 | */ 76 | static toHours(ttl: TimeToLive): number; 77 | 78 | /** 79 | * Returns number of milliseconds in the TTL. This is equivalent to 80 | * {@link TTLUtil.toHours} multiplied by {@link TTLUtil.MILLIS_IN_HOUR}. 81 | * @param {TimeToLive} ttl TTL object 82 | * @returns {number} Number of milliseconds in the TTL object or Infinity 83 | * @throws {NoSQLArgumentError} if TTL object is invalid 84 | * @see {@link toHours} 85 | */ 86 | static toMillis(ttl: TimeToLive): number; 87 | 88 | /** 89 | * Converts TTL to absolute expiration time in milliseconds since Unix 90 | * epoch (January 1, 1970, 00:00:00 UTC). This method is the same as 91 | * {@link TTLUtil.toExpirationTime} returning time in milliseconds 92 | * instead of as Date object. 93 | * @param {TimeToLive} ttl TTL object 94 | * @param {Date|number} referenceTime Reference time represented 95 | * as {@link !Date | Date} or number of milliseconds since Unix 96 | * epoch 97 | * @returns {number} Expiration time in milliseconds since Unix epoch or 98 | * Infinity 99 | * @throws {NoSQLArgumentError} if TTL object or referenceTime is invalid 100 | * @see {@link TTLUtil.toExpirationTime} 101 | */ 102 | static toExpirationTimeMillis(ttl: TimeToLive, 103 | referenceTime: Date|number): number; 104 | 105 | /** 106 | * Converts TTL to absolute expration time given the reference time from 107 | * which to measure the expiration. The semantics follows the rounding 108 | * behavior described in {@link TimeToLive} so that the returned value 109 | * will be rounded up to the nearest hour or day boundary. 110 | * @param {TimeToLive} ttl TTL object 111 | * @param {Date|number} referenceTime Reference time represented 112 | * as {@link !Date | Date} or number of milliseconds since Unix epoch 113 | * @returns {Date} Expiration time as Date 114 | * instance, may be invalid if ttl represents no expiration (Infinity) 115 | * @throws {NoSQLArgumentError} if TTL object or referenceTime is invalid 116 | */ 117 | static toExpirationTime(ttl: TimeToLive, 118 | referenceTime: Date|number): Date; 119 | 120 | /** 121 | * Constructs TTL from absolute expiration time given reference time from 122 | * which to measure record expration. TTL is computed as follows. First, 123 | * expirationTime is rounded up to the nearest hour boundary. If 124 | * inHours argument is specified, then the returned TTL will be 125 | * in hours or days depending on whether inHours is true or 126 | * false. If inHours is not specified, we check if the adjusted 127 | * expiration time indicates midnight in UTC time zone, in which case 128 | * the retured TTL will be in days, otherwise it will be in hours. Then, 129 | * the duration is computed as the difference between the adjusted 130 | * expiration time and the reference time rounded up to the nearest hour 131 | * or day (depending on which is used in the returned TTL as described 132 | * above) and TTL with that duration is returned. Note that if 133 | * expiration time is before or equal to the reference time, it is 134 | * possible that the returned value will contain 0 or negative duration, 135 | * which indicates that the record has already expired. 136 | * @param {Date|number} expirationTime Expiration time 137 | * represented as Date or number of milliseconds 138 | * since Unix epoch 139 | * @param {Date|number} referenceTime Reference time represented 140 | * as Date or number of milliseconds since Unix 141 | * epoch 142 | * @param {boolean} [inHours] Whether to return TTL in hours or days. If 143 | * not specified, the unit of hours or days is determined as described 144 | * above 145 | * @returns {TimeToLive} TTL object 146 | * @throws {NoSQLArgumentError} if expirationTime or referenceTime is 147 | * invalid 148 | */ 149 | static fromExpirationTime(expirationTime: Date|number, 150 | referenceTime: Date|number, inHours?: boolean): TimeToLive; 151 | } 152 | --------------------------------------------------------------------------------