Socket
Socket
Sign inDemoInstall

@cap-js/db-service

Package Overview
Dependencies
Maintainers
2
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@cap-js/db-service - npm Package Compare versions

Comparing version 1.0.1 to 1.1.0

lib/common/factory.d.ts

13

CHANGELOG.md

@@ -7,2 +7,12 @@ # Change Log

## Version 1.1.0 - 2023-08-01
### Fixed
- `UPDATE` with path expressions do not end up in a dump anymore. Instead, a proper error message is emitted.
- `UPDATE` is only noop if it does not include an element annotated with `@cds.on.update`.
- `SELECT` with `'*'` that is not expanded creates now a clearer error when the column name is required.
- `SELECT` with plain SQL statements will return correct result regardless of casing.
- View resolving for streams.
## Version 1.0.1 - 2023-07-03

@@ -16,5 +26,4 @@

## Version 1.0.0 - 2023-06-23
- Initial Release
- Initial Release

@@ -0,4 +1,18 @@

const DatabaseService = require('./lib/common/DatabaseService')
const SQLService = require('./lib/SQLService')
const CQN2SQL = require('./lib/cqn2sql').classDefinition
/**
* @template T
* @typedef {import('./lib/common/factory').Factory<T>} Factory
*/
/**
* @typedef {import('./lib/SQLService').prototype.PreparedStatement} PreparedStatement
*/
module.exports = {
DatabaseService: require('./lib/common/DatabaseService'),
SQLService: require('./lib/SQLService'),
DatabaseService,
SQLService,
CQN2SQL,
}

@@ -11,2 +11,4 @@ const infer = require('../infer')

/** @typedef {unknown} DatabaseDriver */
class DatabaseService extends cds.Service {

@@ -16,2 +18,4 @@ /**

* https://github.com/coopernurse/node-pool#createpool.
* @abstract
* @type {import('./factory').Factory<DatabaseDriver>}
*/

@@ -23,2 +27,5 @@ get factory() {

/**
* @returns {boolean} whether this service is multi tenant enabled
*/
get isMultitenant() {

@@ -29,3 +36,12 @@ return 'multiTenant' in this.options ? this.options.multiTenant : cds.env.requires.multitenancy

/**
* Set one or more session context variables like so:
* @typedef {Object} DefaultSessionVariables
* @property {string} '$user.id'
* @property {string} '$user.locale'
* @property {string} '$valid.from'
* @property {string} '$valid.to'
*/
/**
* Set one or more session context variables
* @example
* ```js

@@ -38,8 +54,14 @@ * const tx = cds.db.tx()

* ```
* @param {unknown|DefaultSessionVariables} variables
*/
// eslint-disable-next-line no-unused-vars
set(variables) {
variables
throw '2b overridden by subclass'
}
/**
* @param {import('@sap/cds/apis/cqn').Query} q
* @param {import('@sap/cds/apis/csn').CSN} m
* @returns {import('../infer/cqn').Query}
*/
infer(q, m = this.model) {

@@ -49,2 +71,5 @@ return infer(q, m)

/**
* @returns {Promise<DatabaseService>}
*/
async begin() {

@@ -58,2 +83,3 @@ const ctx = this.context

try {
/** @type {DatabaseDriver} */
dbc = this.dbc = await pool.acquire()

@@ -67,2 +93,5 @@ } catch (err) {

connections.push(this)
/**
* @param {DatabaseDriver} dbc
*/
this._release = async dbc => {

@@ -119,2 +148,5 @@ await pool.release(dbc)

// REVISIT: should happen automatically after a configurable time
/**
* @param {string} tenant
*/
async disconnect(tenant) {

@@ -128,2 +160,9 @@ const pool = this.pools[tenant]

/**
* Runs a Query on the database service
* @param {import("@sap/cds/apis/cqn").Query} query
* @param {unknown} data
* @param {...unknown} etc
* @returns {Promise<unknown>}
*/
run(query, data, ...etc) {

@@ -135,7 +174,19 @@ // Allow db.run('...',1,2,3,4)

url4(/*tenant*/) {
// eslint-disable-line no-unused-vars
/**
* Generated the database url for the given tenant
* @param {string} tenant
* @returns {string}
*/
url4(tenant) {
tenant
let { url } = this.options?.credentials || this.options || {}
return url
}
/**
* Old name of url4
* @deprecated
* @param {string} tenant
* @returns {string}
*/
getDbUrl(tenant) {

@@ -142,0 +193,0 @@ return this.url4(tenant)

@@ -5,4 +5,20 @@ const StandardFunctions = {

// String and Collection Functions
// length : (x) => `length(${x})`,
/**
* Generates SQL statement that produces the length of a given string
* @param {string} x
* @returns {string}
*/
length: x => `length(${x})`,
/**
* Generates SQL statement that produces the average of a given expression
* @param {string} x
* @returns {string}
*/
average: x => `avg(${x})`,
/**
* Generates SQL statement that produces a boolean value indicating whether the search term is contained in the given columns
* @param {string} ref
* @param {string} arg
* @returns {string}
*/
search: function (ref, arg) {

@@ -14,10 +30,61 @@ if (!('val' in arg)) throw `SQLite only supports single value arguments for $search`

},
/**
* Generates SQL statement that produces a string with all provided strings concatenated
* @param {...string} args
* @returns {string}
*/
concat: (...args) => args.join('||'),
/**
* Generates SQL statement that produces a boolean value indicating whether the first string contains the second string
* @param {...string} args
* @returns {string}
*/
contains: (...args) => `ifnull(instr(${args}),0)`,
/**
* Generates SQL statement that produces the number of elements in a given collection
* @param {string} x
* @returns {string}
*/
count: x => `count(${x || '*'})`,
/**
* Generates SQL statement that produces the number of distinct values of a given expression
* @param {string} x
* @returns {string}
*/
countdistinct: x => `count(distinct ${x || '*'})`,
/**
* Generates SQL statement that produces the index of the first occurrence of the second string in the first string
* @param {string} x
* @param {string} y
* @returns {string}
*/
indexof: (x, y) => `instr(${x},${y}) - 1`, // sqlite instr is 1 indexed
/**
* Generates SQL statement that produces a boolean value indicating whether the first string starts with the second string
* @param {string} x
* @param {string} y
* @returns {string}
*/
startswith: (x, y) => `instr(${x},${y}) = 1`, // sqlite instr is 1 indexed
// takes the end of the string of the size of the target and compares it with the target
/**
* Generates SQL statement that produces a boolean value indicating whether the first string ends with the second string
* @param {string} x
* @param {string} y
* @returns {string}
*/
endswith: (x, y) => `substr(${x}, length(${x}) + 1 - length(${y})) = ${y}`,
/**
* Generates SQL statement that produces the substring of a given string
* @example
* // returns 'bc'
* {func:'substring',args:[{val:'abc'},{val:1}]}
* @example
* // returns 'b'
* {func:'substring',args:[{val:'abc'},{val:1},{val:1}]}
* @param {string} x
* @param {string} y
* @param {string} z
* @returns {string}
*/
substring: (x, y, z) =>

@@ -29,23 +96,103 @@ z

// String Functions
/**
* Generates SQL statement that matches the given string against a regular expression
* @param {string} x
* @param {string} y
* @returns {string}
*/
matchesPattern: (x, y) => `${x} regexp ${y})`,
/**
* Generates SQL statement that produces the lower case value of a given string
* @param {string} x
* @returns {string}
*/
tolower: x => `lower(${x})`,
/**
* Generates SQL statement that produces the upper case value of a given string
* @param {string} x
* @returns {string}
*/
toupper: x => `upper(${x})`,
// trim : (x) => `trim(${x})`,
/**
* Generates SQL statement that produces the trimmed value of a given string
* @param {string} x
* @returns {string}
*/
trim: x => `trim(${x})`,
// Arithmetic Functions
/**
* Generates SQL statement that produces the rounded up value of a given number
* @param {string} x
* @returns {string}
*/
ceiling: x => `ceil(${x})`,
// floor : (x) => `floor(${x})`,
// round : (x) => `round(${x})`,
/**
* Generates SQL statement that produces the rounded down value of a given number
* @param {string} x
* @returns {string}
*/
floor: x => `floor(${x})`,
/**
* Generates SQL statement that produces the rounded value of a given number
* @param {string} x
* @param {string} p precision
* @returns {string}
*/
round: (x, p) => `round(${x}${p ? `,${p}` : ''})`,
// Date and Time Functions
/**
* Generates SQL statement that produces the year of a given timestamp
* @param {string} x
* @returns {string}
*/
year: x => `cast( strftime('%Y',${x}) as Integer )`,
/**
* Generates SQL statement that produces the month of a given timestamp
* @param {string} x
* @returns {string}
*/
month: x => `cast( strftime('%m',${x}) as Integer )`,
/**
* Generates SQL statement that produces the day of a given timestamp
* @param {string} x
* @returns {string}
*/
day: x => `cast( strftime('%d',${x}) as Integer )`,
/**
* Generates SQL statement that produces the hours of a given timestamp
* @param {string} x
* @returns {string}
*/
hour: x => `cast( strftime('%H',${x}) as Integer )`,
/**
* Generates SQL statement that produces the minutes of a given timestamp
* @param {string} x
* @returns {string}
*/
minute: x => `cast( strftime('%M',${x}) as Integer )`,
/**
* Generates SQL statement that produces the seconds of a given timestamp
* @param {string} x
* @returns {string}
*/
second: x => `cast( strftime('%S',${x}) as Integer )`,
/**
* Generates SQL statement that produces the fractional seconds of a given timestamp
* @param {string} x
* @returns {string}
*/
fractionalseconds: x => `cast( strftime('%f0000',${x}) as Integer )`,
/**
* maximum date time value
* @returns {string}
*/
maxdatetime: () => '9999-12-31 23:59:59.999',
/**
* minimum date time value
* @returns {string}
*/
mindatetime: () => '0001-01-01 00:00:00.000',

@@ -58,2 +205,7 @@

// which can be divided by 60 back to minutes
/**
* Generates SQL statement that produces the offset in minutes of a given date time offset string
* @param {string} x
* @returns {string}
*/
totaloffsetminutes: x => `case

@@ -72,2 +224,7 @@ when substr(${x}, length(${x})) = 'z' then 0

// while the odata specification states that the seconds has to be fractional which only julianday allows
/**
* Generates SQL statement that produces an OData compliant duration string like: P12DT23H59M59.999999999999S
* @param {string} x
* @returns {string}
*/
totalseconds: x => `(

@@ -98,3 +255,15 @@ (

// Time functions
/**
* Generates SQL statement that calculates the difference in 100nanoseconds between two timestamps
* @param {string} x left timestamp
* @param {string} y right timestamp
* @returns {string}
*/
nano100_between: (x, y) => `(julianday(${y}) - julianday(${x})) * 864000000000`,
/**
* Generates SQL statement that calculates the difference in seconds between two timestamps
* @param {string} x left timestamp
* @param {string} y right timestamp
* @returns {string}
*/
seconds_between: (x, y) => `(julianday(${y}) - julianday(${x})) * 86400`,

@@ -105,2 +274,8 @@ // Calculates the difference in full days using julian day

// Which on the day resolution are included as the individual days therefor ignoring them to match HANA
/**
* Generates SQL statement that calculates the difference in days between two timestamps
* @param {string} x left timestamp
* @param {string} y right timestamp
* @returns {string}
*/
days_between: (x, y) => `(

@@ -132,2 +307,8 @@ cast ( julianday(${y}) as Integer ) - cast ( julianday(${x}) as Integer )

*/
/**
* Generates SQL statement that calculates the difference in months between two timestamps
* @param {string} x left timestamp
* @param {string} y right timestamp
* @returns {string}
*/
months_between: (x, y) => `

@@ -150,2 +331,8 @@ (

)`,
/**
* Generates SQL statement that calculates the difference in years between two timestamps
* @param {string} x left timestamp
* @param {string} y right timestamp
* @returns {string}
*/
years_between(x, y) {

@@ -152,0 +339,0 @@ return `floor(${this.months_between(x, y)} / 12)`

@@ -17,7 +17,21 @@ const cds = require('@sap/cds/lib')

class CQN2SQLRenderer {
/**
* Creates a new CQN2SQL instance for processing a query
* @constructor
* @param {import('@sap/cds/apis/services').ContextProperties} context the cds.context of the request
*/
constructor(context) {
/**
* @type {import('@sap/cds/apis/services').ContextProperties}
*/
this.context = cds.context || context
// REVISIT: find a way to make CQN2SQLRenderer work in SQLService as well
/** @type {CQN2SQLRenderer|unknown} */
this.class = new.target // for IntelliSense
this.class._init() // is a noop for subsequent calls
}
/**
* Initializes the class one first creation to link types to data converters
*/
static _init() {

@@ -41,5 +55,15 @@ const _add_mixins = (aspect, mixins) => {

/**
* Renders incoming query into SQL and generates binding values
* @param {import('./infer/cqn').Query} q CQN query to be rendered
* @param {unknown[]|undefined} vars Values to be used for params
* @returns {CQN2SQLRenderer|unknown}
*/
render(q, vars) {
const cmd = q.cmd || Object.keys(q)[0] // SELECT, INSERT, ...
/**
* @type {string} the rendered SQL string
*/
this.sql = '' // to have it as first property for debugging
/** @type {unknown[]} */
this.values = [] // prepare values, filled in by subroutines

@@ -56,2 +80,7 @@ this[cmd]((this.cqn = q)) // actual sql rendering happens here

/**
* Links the incoming query with the current service model
* @param {import('./infer/cqn').Query} q
* @returns {import('./infer/cqn').Query}
*/
infer(q) {

@@ -63,2 +92,6 @@ return q.target ? q : cds_infer(q)

/**
* Renders a CREATE query into generic SQL
* @param {import('./infer/cqn').CREATE} q
*/
CREATE(q) {

@@ -78,2 +111,7 @@ const { target } = q,

/**
* Renders a column clause for the given elements
* @param {import('./infer/cqn').elements} elements
* @returns {string} SQL
*/
CREATE_elements(elements) {

@@ -90,2 +128,7 @@ let sql = ''

/**
* Renders a column definition for the given element
* @param {import('./infer/cqn').element} element
* @returns {string} SQL
*/
CREATE_element(element) {

@@ -96,2 +139,7 @@ const type = this.type4(element)

/**
* Renders the SQL type definition for the given element
* @param {import('./infer/cqn').element} element
* @returns {string}
*/
type4(element) {

@@ -105,2 +153,5 @@ if (!element._type) element = cds.builtin.types[element.type] || element

/** @callback converter */
/** @type {Object<string,import('@sap/cds/apis/csn').Definition>} */
static TypeMap = {

@@ -131,2 +182,6 @@ // Utilizing cds.linked inheritance

/**
* Renders a DROP query into generic SQL
* @param {import('./infer/cqn').DROP} q
*/
DROP(q) {

@@ -140,2 +195,6 @@ const { target } = q

/**
* Renders a SELECT statement into generic SQL
* @param {import('./infer/cqn').SELECT} q
*/
SELECT(q) {

@@ -158,12 +217,30 @@ let { from, expand, where, groupBy, having, orderBy, limit, one, distinct, localized } = q.SELECT

else if ((x = limit)) sql += ` LIMIT ${this.limit(x)}`
if (expand) sql = this.SELECT_expand(q, sql)
// Expand cannot work without an inferred query
if (expand) {
if (!q.elements) cds.error`Query was not inferred and includes expand. For which the metadata is missing.`
sql = this.SELECT_expand(q, sql)
}
return (this.sql = sql)
}
/**
* Renders a column clause into generic SQL
* @param {import('./infer/cqn').SELECT} param0
* @returns {string} SQL
*/
SELECT_columns({ SELECT }) {
// REVISIT: We don't have to run x.as through this.column_name(), do we?
if (!SELECT.columns) return '*'
return SELECT.columns.map(x => this.column_expr(x) + (typeof x.as === 'string' ? ' as ' + this.quote(x.as) : ''))
return SELECT.columns.map(x => {
if (x === '*') return x
return this.column_expr(x) + (typeof x.as === 'string' ? ' as ' + this.quote(x.as) : '')
})
}
/**
* Renders a JSON select around the provided SQL statement
* @param {import('./infer/cqn').SELECT} param0
* @param {string} sql
* @returns {string} SQL
*/
SELECT_expand({ SELECT, elements }, sql) {

@@ -197,2 +274,7 @@ if (!SELECT.columns) return sql

/**
* Renders a SELECT column expression into generic SQL
* @param {import('./infer/cqn').col} x
* @returns {string} SQL
*/
column_expr(x) {

@@ -208,2 +290,7 @@ if (x.func && !x.as) x.as = x.func

/**
* Renders a FROM clause into generic SQL
* @param {import('./infer/cqn').source} from
* @returns {string} SQL
*/
from(from) {

@@ -224,2 +311,7 @@ const { ref, as } = from,

/**
* Renders a WHERE clause into generic SQL
* @param {import('./infer/cqn').predicate} xpr
* @returns {string} SQL
*/
where(xpr) {

@@ -229,2 +321,7 @@ return this.xpr({ xpr })

/**
* Renders a HAVING clause into generic SQL
* @param {import('./infer/cqn').predicate} xpr
* @returns {string} SQL
*/
having(xpr) {

@@ -234,2 +331,7 @@ return this.xpr({ xpr })

/**
* Renders a groupBy clause into generic SQL
* @param {import('./infer/cqn').expr[]} clause
* @returns {string[] | string} SQL
*/
groupBy(clause) {

@@ -239,2 +341,8 @@ return clause.map(c => this.expr(c))

/**
* Renders an orderBy clause into generic SQL
* @param {import('./infer/cqn').ordering_term[]} orderBy
* @param {boolean | undefined} localized
* @returns {string[] | string} SQL
*/
orderBy(orderBy, localized) {

@@ -251,2 +359,8 @@ return orderBy.map(

/**
* Renders an limit clause into generic SQL
* @param {import('./infer/cqn').limit} param0
* @returns {string} SQL
* @throws {Error} When no rows are defined
*/
limit({ rows, offset }) {

@@ -259,2 +373,7 @@ if (!rows) throw new Error('Rows parameter is missing in SELECT.limit(rows, offset)')

/**
* Renders an INSERT query into generic SQL
* @param {import('./infer/cqn').INSERT} q
* @returns {string} SQL
*/
INSERT(q) {

@@ -273,2 +392,7 @@ const { INSERT } = q

/**
* Renders an INSERT query with entries property
* @param {import('./infer/cqn').INSERT} q
* @returns {string} SQL
*/
INSERT_entries(q) {

@@ -285,2 +409,4 @@ const { INSERT } = q

: ObjectKeys(INSERT.entries[0])
/** @type {string[]} */
this.columns = columns.filter(elements ? c => !elements[c]?.['@cds.extension'] : () => true).map(c => this.quote(c))

@@ -318,2 +444,7 @@

/**
* Renders an INSERT query with rows property
* @param {import('./infer/cqn').INSERT} q
* @returns {string} SQL
*/
INSERT_rows(q) {

@@ -347,2 +478,7 @@ const { INSERT } = q

/**
* Renders an INSERT query with values property
* @param {import('./infer/cqn').INSERT} q
* @returns {string} SQL
*/
INSERT_values(q) {

@@ -353,2 +489,7 @@ let { columns, values } = q.INSERT

/**
* Renders an INSERT query from SELECT query
* @param {import('./infer/cqn').INSERT} q
* @returns {string} SQL
*/
INSERT_select(q) {

@@ -369,2 +510,8 @@ const { INSERT } = q

/**
* Wraps the provided SQL expression for output processing
* @param {import('./infer/cqn').element} element
* @param {string} expr
* @returns {string} SQL
*/
output_converter4(element, expr) {

@@ -375,4 +522,6 @@ const fn = element?.[this.class._convertOutput]

/** @type {import('./converters').Converters} */
static InputConverters = {} // subclasses to override
/** @type {import('./converters').Converters} */
static OutputConverters = {} // subclasses to override

@@ -384,2 +533,7 @@

/**
* Renders an UPSERT query into generic SQL
* @param {import('./infer/cqn').UPDATE} q
* @returns {string} SQL
*/
UPSERT(q) {

@@ -406,2 +560,7 @@ let { UPSERT } = q,

/**
* Renders an UPDATE query into generic SQL
* @param {import('./infer/cqn').UPDATE} q
* @returns {string} SQL
*/
UPDATE(q) {

@@ -445,2 +604,7 @@ const {

/**
* Renders a DELETE query into generic SQL
* @param {import('./infer/cqn').DELETE} param0
* @returns {string} SQL
*/
DELETE({ DELETE: { from, where } }) {

@@ -454,2 +618,7 @@ let sql = `DELETE FROM ${this.from(from)}`

/**
* Renders a STREAM query into generic SQL
* @param {import('./infer/cqn').STREAM} q
* @returns {string} SQL
*/
STREAM(q) {

@@ -476,2 +645,8 @@ let { from, into, where, column, data } = q.STREAM

/**
* Renders an expression object into generic SQL
* @param {import('./infer/cqn').expr} x
* @returns {string} SQL
* @throws {Error} When an unknown un supported expression is provided
*/
expr(x) {

@@ -490,2 +665,7 @@ const wrap = x.cast ? sql => `cast(${sql} as ${this.type4(x.cast)})` : sql => sql

/**
* Renders an list of expression objects into generic SQL
* @param {import('./infer/cqn').xpr} param0
* @returns {string} SQL
*/
xpr({ xpr }) {

@@ -502,2 +682,9 @@ return xpr

/**
* Renders an operation into generic SQL
* @param {string} x The current operator string
* @param {Number} i Current index of the operator inside the xpr
* @param {import('./infer/cqn').predicate[]} xpr The parent xpr in which the operator is used
* @returns {string} The correct operator string
*/
operator(x, i, xpr) {

@@ -509,2 +696,8 @@ if (x === '=' && xpr[i + 1]?.val === null) return 'is'

/**
* Renders an argument place holder into the SQL for prepared statements
* @param {import('./infer/cqn').ref} param0
* @returns {string} SQL
* @throws {Error} When an unsupported ref definition is provided
*/
param({ ref }) {

@@ -515,2 +708,7 @@ if (ref.length > 1) throw cds.error`Unsupported nested ref parameter: ${ref}`

/**
* Renders a ref into generic SQL
* @param {import('./infer/cqn').ref} param0
* @returns {string} SQL
*/
ref({ ref }) {

@@ -520,2 +718,7 @@ return ref.map(r => this.quote(r)).join('.')

/**
* Renders a value into the correct SQL syntax of a placeholder for a prepared statement
* @param {import('./infer/cqn').val} param0
* @returns {string} SQL
*/
val({ val }) {

@@ -543,2 +746,7 @@ switch (typeof val) {

static Functions = require('./cql-functions')
/**
* Renders a function call into mapped SQL definitions from the Functions definition
* @param {import('./infer/cqn').func} param0
* @returns {string} SQL
*/
func({ func, args }) {

@@ -549,2 +757,7 @@ args = (args || []).map(e => (e === '*' ? e : { __proto__: e, toString: (x = e) => this.expr(x) }))

/**
* Renders a list into generic SQL
* @param {import('./infer/cqn').list} param0
* @returns {string} SQL
*/
list({ list }) {

@@ -554,2 +767,7 @@ return `(${list.map(e => this.expr(e))})`

/**
* Renders a Regular Expression into its string representation
* @param {RegExp} o
* @returns {string} SQL
*/
regex(o) {

@@ -559,6 +777,16 @@ if (is_regexp(o)) return o.source

/**
* Renders the object as a JSON string in generic SQL
* @param {object} o
* @returns {string} SQL
*/
json(o) {
return JSON.stringify(o)
return this.string(JSON.stringify(o))
}
/**
* Renders a javascript string into a generic SQL string
* @param {string} s
* @returns {string} SQL
*/
string(s) {

@@ -568,6 +796,18 @@ return `'${s.replace(/'/g, "''")}'`

/**
* Calculates the effect column name
* @param {import('./infer/cqn').col} col
* @returns {string} explicit/implicit column alias
*/
column_name(col) {
if (col === '*')
cds.error`Query was not inferred and includes '*' in the columns. For which there is no column name available.`
return (typeof col.as === 'string' && col.as) || ('val' in col && col.val + '') || col.ref[col.ref.length - 1]
}
/**
* Calculates the Database name of the given name
* @param {string|import('./infer/cqn').ref} name
* @returns {string} Database name
*/
name(name) {

@@ -577,10 +817,23 @@ return (name.id || name).replace(/\./g, '_')

/** @type {unknown} */
static ReservedWords = {}
/**
* Ensures that the given identifier is properly quoted when required by the database
* @param {string} s
* @returns {string} SQL
*/
quote(s) {
if (typeof s !== 'string') return '"' + s + '"'
if (s.includes('"')) return '"' + s.replace(/"/g, '""') + '"'
if (s.toUpperCase() in this.class.ReservedWords || /^\d|[$' @./\\]/.test(s)) return '"' + s + '"'
if (s.toUpperCase() in this.class.ReservedWords || /^\d|[$' ?@./\\]/.test(s)) return '"' + s + '"'
return s
}
/**
* Convers the columns array into an array of SQL expressions that extract the correct value from inserted JSON data
* @param {object[]} columns
* @param {import('./infer/cqn').elements} elements
* @param {Boolean} isUpdate
* @returns {string[]} Array of SQL expressions for processing input JSON data
*/
managed(columns, elements, isUpdate = false) {

@@ -631,2 +884,7 @@ const annotation = isUpdate ? '@cds.on.update' : '@cds.on.insert'

/**
* Returns the default value
* @param {string} defaultValue
* @returns {string}
*/
defaultValue(defaultValue = this.context.timestamp.toISOString()) {

@@ -648,2 +906,9 @@ return typeof defaultValue === 'string' ? this.string(defaultValue) : defaultValue

const _empty = a => !a || a.length === 0
module.exports = Object.assign((q, m) => new CQN2SQLRenderer().render(cqn4sql(q, m), m), { class: CQN2SQLRenderer })
/**
* @param {import('@sap/cds/apis/cqn').Query} q
* @param {import('@sap/cds/apis/csn').CSN} m
*/
module.exports = (q, m) => new CQN2SQLRenderer().render(cqn4sql(q, m), m)
module.exports.class = CQN2SQLRenderer
module.exports.classDefinition = CQN2SQLRenderer // class is a reserved typescript word

@@ -7,2 +7,13 @@ const cds = require('@sap/cds')

/**
* @callback nextCallback
* @param {Error|undefined} error
* @returns {Promise<unknown>}
*/
/**
* @param {import('@sap/cds/apis/services').Request} req
* @param {nextCallback} next
* @returns {Promise<number>}
*/
async function onDeep(req, next) {

@@ -140,2 +151,6 @@ const { query } = req

/**
* @param {import('@sap/cds/apis/cqn').Query} query
* @param {import('@sap/cds/apis/csn').Definition} target
*/
const getExpandForDeep = (query, target) => {

@@ -146,2 +161,3 @@ const from = query.DELETE?.from || query.UPDATE?.entity

/** @type {import("@sap/cds/apis/ql").SELECT<unknown>} */
const cqn = SELECT.from(from)

@@ -156,2 +172,8 @@ if (where) cqn.SELECT.where = where

/**
* @param {import('@sap/cds/apis/cqn').Query} query
* @param {unknown[]} dbData
* @param {import('@sap/cds/apis/csn').Definition} target
* @returns
*/
const getDeepQueries = (query, dbData, target) => {

@@ -181,2 +203,7 @@ let queryData

/**
* @param {unknown[]} diff
* @param {import('@sap/cds/apis/csn').Definition} target
* @returns {import('@sap/cds/apis/cqn').Query[]}
*/
const _getDeepQueries = (diff, target) => {

@@ -183,0 +210,0 @@ const queries = []

@@ -44,2 +44,12 @@ const cds = require('@sap/cds')

/**
* @callback nextCallback
* @param {Error|undefined} error
* @returns {Promise<unknown>}
*/
/**
* @param {import('@sap/cds/apis/services').Request} req
* @param {nextCallback} next
*/
module.exports = async function fill_in_keys(req, next) {

@@ -46,0 +56,0 @@ // REVISIT dummy handler until we have input processing

129

lib/infer/index.js

@@ -7,3 +7,2 @@ 'use strict'

const { pseudos } = require('./pseudos')
// REVISIT: we should always return cds.linked elements
const cdsTypes = cds.linked({

@@ -22,7 +21,6 @@ definitions: {

for (const each in cdsTypes) cdsTypes[`cds.${each}`] = cdsTypes[each]
/**
* @param {CQN|CQL} originalQuery
* @param {CSN} [model]
* @returns {InferredCQN} = q with .target and .elements
* @param {import('@sap/cds/apis/cqn').Query|string} originalQuery
* @param {import('@sap/cds/apis/csn').CSN} [model]
* @returns {import('./cqn').Query} = q with .target and .elements
*/

@@ -45,2 +43,6 @@ function infer(originalQuery, model = cds.context?.model || cds.model) {

inferred.STREAM
// cache for already processed calculated elements
const alreadySeenCalcElements = new Set()
const sources = inferTarget(_.from || _.into || _.entity, {})

@@ -140,7 +142,7 @@ const joinTree = new JoinTree(sources)

*
* @param {Object} arg - The argument object that will be augmented with additional properties.
* @param {object} arg - The argument object that will be augmented with additional properties.
* It must contain a 'ref' property, which is an array representing the steps to be processed.
* Optionally, it can also contain an 'xpr' property, which is also processed recursively.
*
* @param {Object} $baseLink - Optional parameter. It represents the environment in which the first 'ref' step should be
* @param {object} $baseLink - Optional parameter. It represents the environment in which the first 'ref' step should be
* resolved. It's needed for infix filter / expand columns. It must contain a 'definition'

@@ -184,3 +186,3 @@ * property, which is an object representing the base environment.

}
arg.$refLinks.push({ definition: e, target: e._target || e })
arg.$refLinks.push({ definition: e, target: definition })
// filter paths are flattened

@@ -196,3 +198,3 @@ // REVISIT: too much augmentation -> better remove flatName..

const recent = arg.$refLinks[i - 1]
const { elements } = recent.target
const { elements } = recent.definition._target || recent.definition
const e = elements[id]

@@ -225,2 +227,7 @@ if (!e) throw new Error(`"${id}" not found in the elements of "${arg.$refLinks[i - 1].definition.name}"`)

})
const { definition, target } = arg.$refLinks[arg.$refLinks.length - 1]
if (definition.value) {
// nested calculated element
attachRefLinksToArg(definition.value, { definition: definition.parent, target }, true)
}
}

@@ -237,3 +244,3 @@

*
* @returns {Object} The `$combinedElements` dictionary, which maps element names to an array of objects
* @returns {object} The `$combinedElements` dictionary, which maps element names to an array of objects
* containing the index and table alias where the element can be found.

@@ -275,5 +282,5 @@ */

*
* @param {Object} $combinedElements The `$combinedElements` dictionary of the query, which maps element names
* @param {object} $combinedElements The `$combinedElements` dictionary of the query, which maps element names
* to an array of objects containing the index and table alias where the element can be found.
* @returns {Object} The inferred `elements` dictionary of the query, which maps element names to their corresponding definitions.
* @returns {object} The inferred `elements` dictionary of the query, which maps element names to their corresponding definitions.
*/

@@ -456,3 +463,3 @@ function inferQueryElements($combinedElements) {

function inferQueryElement(column, insertIntoQueryElements = true, $baseLink = null, context) {
const { inExists, inExpr, inNestedProjection } = context || {}
const { inExists, inExpr, inNestedProjection, inCalcElement } = context || {}
if (column.param) return // parameter references are only resolved into values on execution e.g. :val, :1 or ?

@@ -499,3 +506,3 @@ if (column.args) column.args.forEach(arg => inferQueryElement(arg, false, $baseLink, context)) // e.g. function in expression

const element = elements[id]
if (!inExists && !inNestedProjection && element.target) {
if (!inExists && !inNestedProjection && !inCalcElement && element.target) {
// only fk access in infix filter

@@ -514,3 +521,4 @@ const nextStep = column.ref[1]?.id || column.ref[1]

}
column.$refLinks.push({ definition: elements[id], target })
const resolvableIn = definition.target ? definition._target : target
column.$refLinks.push({ definition: elements[id], target: resolvableIn })
} else {

@@ -550,4 +558,5 @@ stepNotFoundInPredecessor(id, definition.name)

const target = definition._target || column.$refLinks[i - 1].target
if (element) {
const $refLink = { definition: elements[id], target: column.$refLinks[i - 1].target }
const $refLink = { definition: elements[id], target }
column.$refLinks.push($refLink)

@@ -558,3 +567,3 @@ } else if (firstStepIsSelf) {

// `$user.some.unknown.element` -> no error
column.$refLinks.push({ definition: {}, target: column.$refLinks[i - 1].target })
column.$refLinks.push({ definition: {}, target })
} else if (id === '$dummy') {

@@ -644,3 +653,4 @@ // `some.known.element.$dummy` -> no error; used by cds.ql to simulate joins

throw new Error(`Duplicate definition of element “${elementName}”`)
queryElements[elementName] = getCopyWithAnnos(column, leafArt)
const element = getCopyWithAnnos(column, leafArt)
queryElements[elementName] = element
}

@@ -662,8 +672,16 @@ }

}
const virtual = (column.$refLinks[column.$refLinks.length - 1].definition.virtual || !isPersisted) && !inExpr
const leafArt = column.$refLinks[column.$refLinks.length - 1].definition
const virtual = (leafArt.virtual || !isPersisted) && !inExpr
// check if we need to merge the column `ref` into the join tree of the query
if (!inExists && !virtual && isColumnJoinRelevant(column, firstStepIsSelf)) {
if (!inExists && !virtual && !inCalcElement && isColumnJoinRelevant(column, firstStepIsSelf)) {
if (originalQuery.UPDATE)
throw cds.error(
'Path expressions for UPDATE statements are not supported. Use “where exists” with infix filters instead.',
)
Object.defineProperty(column, 'isJoinRelevant', { value: true })
joinTree.mergeColumn(column)
joinTree.mergeColumn(column, $baseLink)
}
if (leafArt.value && !leafArt.value.stored) {
resolveCalculatedElement(leafArt, column)
}

@@ -673,5 +691,5 @@ /**

*
* @param {Object} col - The column object with properties: `inline` and `$refLinks`.
* @param {object} col - The column object with properties: `inline` and `$refLinks`.
* @param {string} [namePrefix=col.as || col.flatName] - Prefix for naming new columns. Defaults to `col.as` or `col.flatName`.
* @returns {Object} - An object with resolved and processed inline column definitions.
* @returns {object} - An object with resolved and processed inline column definitions.
*

@@ -730,4 +748,4 @@ * Procedure:

*
* @param {Object} col - The column object with properties: `expand` and `$refLinks`.
* @returns {Object} - A `cds.struct` object with expanded column definitions.
* @param {object} col - The column object with properties: `expand` and `$refLinks`.
* @returns {object} - A `cds.struct` object with expanded column definitions.
*

@@ -798,3 +816,55 @@ * Procedure:

}
function resolveCalculatedElement(calcElement) {
if (alreadySeenCalcElements.has(calcElement)) return
else alreadySeenCalcElements.add(calcElement)
const { ref, xpr, func } = calcElement.value
if (ref || xpr) {
attachRefLinksToArg(calcElement.value, { definition: calcElement.parent, target: calcElement.parent }, true)
// column is now fully linked, now we need to find out if we need to merge it into the join tree
// for that, we calculate all paths from a calc element and merge them into the join tree
mergePathsIntoJoinTree(calcElement.value)
}
if (func) calcElement.value.args?.forEach(arg => inferQueryElement(arg, false)) // {func}.args are optional
function mergePathsIntoJoinTree(e, basePath = null) {
basePath = basePath || { $refLinks: [], ref: [] }
if (e.ref) {
e.$refLinks.forEach((link, i) => {
const { definition } = link
if (!definition.value) {
basePath.$refLinks.push(link)
basePath.ref.push(e.ref[i])
}
})
const leafOfCalculatedElementRef = e.$refLinks[e.$refLinks.length - 1].definition
if (leafOfCalculatedElementRef.value) mergePathsIntoJoinTree(leafOfCalculatedElementRef.value, basePath)
mergePathIfNecessary(basePath, e)
} else if (e.xpr) {
e.xpr.forEach(step => {
if (step.ref) {
const subPath = { $refLinks: [...basePath.$refLinks], ref: [...basePath.ref] }
step.$refLinks.forEach((link, i) => {
const { definition } = link
if (definition.value) {
mergePathsIntoJoinTree(definition.value)
} else {
subPath.$refLinks.push(link)
subPath.ref.push(step.ref[i])
}
})
mergePathIfNecessary(subPath, step)
}
})
}
function mergePathIfNecessary(p, step) {
const calcElementIsJoinRelevant = isColumnJoinRelevant(p)
if (calcElementIsJoinRelevant) {
if (!calcElement.value.isColumnJoinRelevant) Object.defineProperty(step, 'isJoinRelevant', { value: true })
joinTree.mergeColumn(p)
}
}
}
}
/**

@@ -850,6 +920,12 @@ * Checks whether or not the `ref` of the given column is join relevant.

function inferElementsFromWildCard() {
const exclude = _.excluding ? x => _.excluding.includes(x) : () => false
if (Object.keys(queryElements).length === 0 && aliases.length === 1) {
// only one query source and no overwritten columns
Object.entries(sources[aliases[0]].elements).forEach(([name, element]) => {
if (element.type !== 'cds.LargeBinary') queryElements[name] = element
if (!exclude(name) && element.type !== 'cds.LargeBinary') queryElements[name] = element
if (element.value) {
// we might have join relevant calculated elements
resolveCalculatedElement(element)
}
})

@@ -859,3 +935,2 @@ return

const exclude = _.excluding ? x => _.excluding.includes(x) : () => false
const ambiguousElements = {}

@@ -862,0 +937,0 @@ Object.entries($combinedElements).forEach(([name, tableAliases]) => {

'use strict'
// REVISIT: define following unknown types
/**
* @typedef {unknown} $refLink
*/
/**
* @typedef {unknown} parent
*/
/**
* @typedef {unknown} where
*/
/**
* @typedef {unknown} children
*/
/**
* @typedef {unknown} queryArtifact
*/
/**
* @typedef {string} alias
*/
/**
* @typedef {Map<alias,Root>} _roots
*/
/**
* @typedef {Object.<string, unknown>} sources
*/
/**
* A class representing a Node in the join tree.
*
* @property {$refLink} - A reference link to this node.
* @property {parent} - The parent Node of this node.
* @property {where} - An optional condition to be applied to this node.
* @property {children} - A Map of children nodes belonging to this node.
*/
class Node {
/**
* @param {$refLink} $refLink
* @param {parent} parent
* @param {where} where
*/
constructor($refLink, parent, where = null) {
/** @type {$refLink} - A reference link to this node. */
this.$refLink = $refLink
/** @type {parent} - The parent Node of this node. */
this.parent = parent
/** @type {where} - An optional condition to be applied to this node. */
this.where = where
/** @type {children} - A Map of children nodes belonging to this node. */
this.children = new Map()

@@ -22,14 +60,16 @@ }

* A class representing the root of the join tree.
*
* @property {queryArtifact} - The artifact used to make the query.
* @property {alias} - The alias of the artifact.
* @property {parent} - The parent Node of this root, null for the root Node.
* @property {children} - A Map of children nodes belonging to this root.
*/
class Root {
/**
* @param {[alias, queryArtifact]} querySource
*/
constructor(querySource) {
const [alias, queryArtifact] = querySource
/** @type {queryArtifact} - The artifact used to make the query. */
this.queryArtifact = queryArtifact
/** @type {alias} - The alias of the artifact. */
this.alias = alias
/** @type {parent} - The parent Node of this root, null for the root Node. */
this.parent = null
/** @type {children} - A Map of children nodes belonging to this root. */
this.children = new Map()

@@ -41,10 +81,12 @@ }

* A class representing a Join Tree.
*
* @property {_roots} - A Map of root nodes.
* @property {isInitial} - A boolean indicating if the join tree is in its initial state.
* @property {_queryAliases} - A Map of query aliases, which is used during the association to join translation.
*/
class JoinTree {
/**
*
* @param {sources} sources
*/
constructor(sources) {
/** @type {_roots} - A Map of root nodes. */
this._roots = new Map()
/** @type {boolean} - A boolean indicating if the join tree is in its initial state. */
this.isInitial = true

@@ -58,2 +100,3 @@ /**

* table alias entry, is the capitalized version of the alias.
* @type {Map<string, string>}
*/

@@ -88,2 +131,3 @@ this._queryAliases = new Map()

* @param {string} alias - The original alias name.
* @param {unknown[]} outerQueries - An array of outer queries.
* @returns {string} - The next unambiguous table alias.

@@ -114,3 +158,3 @@ */

*
* @param {Object} col - The column object to be merged into the existing join tree. This object should have the properties $refLinks and ref.
* @param {object} col - The column object to be merged into the existing join tree. This object should have the properties $refLinks and ref.
* @returns {boolean} - Always returns true, indicating the column has been successfully merged into the join tree.

@@ -153,4 +197,5 @@ */

if (child.$refLink.definition.isAssociation) {
if (child.where) {
// always join relevant
if (child.where || col.inline) {
// filter is always join relevant
// if the column ends up in an `inline` -> each assoc step is join relevant
child.$refLink.onlyForeignKeyAccess = false

@@ -157,0 +202,0 @@ } else {

@@ -15,2 +15,6 @@ const iterator = Symbol.iterator

module.exports = class InsertResult {
/**
* @param {import('@sap/cds/apis/cqn').INSERT} query
* @param {unknown[]} results
*/
constructor(query, results) {

@@ -21,3 +25,3 @@ this.query = query

/*
/**
* Lazy access to auto-generated keys.

@@ -75,4 +79,5 @@ */

/*
/**
* the number of inserted (root) entries or the number of affectedRows in case of INSERT into SELECT
* @return {number}
*/

@@ -85,4 +90,5 @@ get affectedRows() {

/*
/**
* for checks such as res > 2
* @return {number}
*/

@@ -93,5 +99,16 @@ valueOf() {

/**
* The last id of the auto incremented key column
* @param {unknown[]} result
* @returns {number}
*/
insertedRowId4(result) {
return result.lastID
}
/**
* Number of affected rows
* @param {unknown[]} result
* @returns {number}
*/
affectedRows4(result) {

@@ -98,0 +115,0 @@ return result.changes

@@ -7,2 +7,15 @@ const cds = require('@sap/cds/lib'),

/**
* @callback next
* @param {Error} param0
* @returns {Promise<unknown>}
*/
/**
* @callback Handler
* @param {import('@sap/cds/apis/services').Request} param0
* @param {next} param1
* @returns {Promise<unknown>}
*/
class SQLService extends DatabaseService {

@@ -25,2 +38,3 @@ init() {

/** @type {Handler} */
async transformStreamFromCQN({ query }, next) {

@@ -34,2 +48,3 @@ if (!query._streaming) return next()

/** @type {Handler} */
async transformStreamIntoCQN({ query, data, target }, next) {

@@ -62,7 +77,7 @@ let col, type, etag

/** Handler for SELECT */
/**
* Handler for SELECT
* @type {Handler}
*/
async onSELECT({ query, data }) {
// REVISIT: disable this for queries like (SELECT 1)
// Will return multiple rows with objects inside
query.SELECT.expand = 'root'
const { sql, values, cqn } = this.cqn2sql(query, data)

@@ -77,2 +92,6 @@ let ps = await this.prepare(sql)

/**
* Handler for INSERT
* @type {Handler}
*/
async onINSERT({ query, data }) {

@@ -86,2 +105,6 @@ const { sql, entries, cqn } = this.cqn2sql(query, data)

/**
* Handler for UPSERT
* @type {Handler}
*/
async onUPSERT({ query, data }) {

@@ -95,9 +118,21 @@ const { sql, entries } = this.cqn2sql(query, data)

/** Handler for UPDATE */
/**
* Handler for UPDATE
* @type {Handler}
*/
async onUPDATE(req) {
if (!req.query.UPDATE.data && !req.query.UPDATE.with) return 0
// noop if not a touch for @cds.on.update
if (
!req.query.UPDATE.data &&
!req.query.UPDATE.with &&
!Object.values(req.target?.elements || {}).some(e => e['@cds.on.update'])
)
return 0
return this.onSIMPLE(req)
}
/** Handler for Stream */
/**
* Handler for Stream
* @type {Handler}
*/
async onSTREAM(req) {

@@ -121,3 +156,6 @@ const { sql, values, entries } = this.cqn2sql(req.query)

/** Handler for CREATE, DROP, UPDATE, DELETE, with simple CQN */
/**
* Handler for CREATE, DROP, UPDATE, DELETE, with simple CQN
* @type {Handler}
*/
async onSIMPLE({ query, data }) {

@@ -129,3 +167,6 @@ const { sql, values } = this.cqn2sql(query, data)

/** Handler for BEGIN, COMMIT, ROLLBACK, which don't have any CQN */
/**
* Handler for BEGIN, COMMIT, ROLLBACK, which don't have any CQN
* @type {Handler}
*/
async onEVENT({ event }) {

@@ -136,3 +177,6 @@ DEBUG?.(event) // in the other cases above DEBUG happens in cqn2sql

/** Handler for SQL statements which don't have any CQN */
/**
* Handler for SQL statements which don't have any CQN
* @type {Handler}
*/
async onPlainSQL({ query, data }, next) {

@@ -148,8 +192,16 @@ if (typeof query === 'string') {

/** Override in subclasses to detect more statements to be called with ps.all() */
/**
* Override in subclasses to detect more statements to be called with ps.all()
* @param {string} sql
*/
hasResults(sql) {
return /^(SELECT|WITH|CALL|PRAGMA table_info)/.test(sql)
return /^(SELECT|WITH|CALL|PRAGMA table_info)/i.test(sql)
}
/** Derives and executes a query to fill in `$count` for given query */
/**
* Derives and executes a query to fill in `$count` for given query
* @param {import('@sap/cds/apis/cqn').SELECT} query - SELECT CQN
* @param {unknown[]} ret - Results of the original query
* @returns {Promise<number>}
*/
async count(query, ret) {

@@ -182,11 +234,25 @@ if (ret) {

static CQN2SQL = require('./cqn2sql').class
constructor() {
super(...arguments)
/** @param {unknown[]} args */
constructor(...args) {
super(...args)
/** @type {unknown} */
this.class = new.target // for IntelliSense
}
/**
* @param {import('@sap/cds/apis/cqn').Query} q
* @param {unknown} values
* @returns {typeof SQLService.CQN2SQL}
*/
cqn2sql(q, values) {
const cqn = this.cqn4sql(q)
// REVISIT: disable this for queries like (SELECT 1)
// Will return multiple rows with objects inside
// Only enable expand when the query is inferred
if (cqn.SELECT && cqn.elements) cqn.SELECT.expand = cqn.SELECT.expand ?? 'root'
const cmd = cqn.cmd || Object.keys(cqn)[0]
if (cmd in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 }) {
if (cmd in { INSERT: 1, DELETE: 1, UPSERT: 1, UPDATE: 1 } || cqn.STREAM?.into) {
let resolvedCqn = resolveView(cqn, this.model, this)

@@ -201,2 +267,7 @@ if (resolvedCqn && resolvedCqn[cmd]._transitions?.[0].target) {

}
/**
* @param {import('@sap/cds/apis/cqn').Query} q
* @returns {import('./infer/cqn').Query}
*/
cqn4sql(q) {

@@ -220,5 +291,7 @@ // REVISIT: move this check to cqn4sql?

* Used to execute simple SQL statement like BEGIN, COMMIT, ROLLBACK
* @param {string} sql
* @returns {Promise<unknown>} The result of the query
*/
// eslint-disable-next-line no-unused-vars
async exec(sql) {
sql
throw '2b overridden by subclass'

@@ -228,24 +301,34 @@ }

/** Interface of prepared statement objects as returned by {@link SQLService#prepare} */
/**
* Interface of prepared statement objects as returned by {@link SQLService#prepare}
* @class
* @interface
*/
class PreparedStatement {
// eslint-disable-line no-unused-vars
/**
* Executes a prepared DML query, i.e., INSERT, UPDATE, DELETE, CREATE, DROP
* @param {[]|{}} binding_params
* @param {unknown|unknown[]} binding_params
*/
async run(/*binding_params*/) {} // eslint-disable-line no-unused-vars
async run(binding_params) {
binding_params
return 0
}
/**
* Executes a prepared SELECT query and returns a single/first row only
* @param {[]|{}} binding_params
* @param {unknown|unknown[]} binding_params
* @returns {Promise<unknown>}
*/
async get(/*binding_params*/) {
async get(binding_params) {
binding_params
return {}
} // eslint-disable-line no-unused-vars
}
/**
* Executes a prepared SELECT query and returns an array of all rows
* @param {[]|{}} binding_params
* @param {unknown|unknown[]} binding_params
* @returns {Promise<unknown[]>}
*/
async all(/*binding_params*/) {
async all(binding_params) {
binding_params
return [{}]
} // eslint-disable-line no-unused-vars
}
}

@@ -303,2 +386,3 @@ SQLService.prototype.PreparedStatement = PreparedStatement

module.exports = Object.assign(SQLService, { _target_name4 })
Object.assign(SQLService, { _target_name4 })
module.exports = SQLService
{
"name": "@cap-js/db-service",
"version": "1.0.1",
"version": "1.1.0",
"description": "CDS base database service",
"homepage": "https://cap.cloud.sap/",
"homepage": "https://github.com/cap-js/cds-dbs/tree/main/db-service#cds-base-database-service",
"repository": {
"type": "git",
"url": "https://github.com/cap-js/cds-dbs"
},
"bugs": {
"url": "https://github.com/cap-js/cds-dbs/issues"
},
"keywords": [

@@ -12,2 +19,3 @@ "CAP",

"main": "index.js",
"types": "./dist/index.d.ts",
"files": [

@@ -23,10 +31,14 @@ "lib",

"prettier": "npx prettier --write .",
"test": "npx jest --silent",
"test": "npm run build && npx jest --silent",
"build": "tsc && find lib/ -type f -name '*.d.ts' -exec cp '{}' 'dist/{}' ';' && cp ts.eslintrc.cjs dist/.eslintrc.cjs && npx eslint ./dist --ext .d.ts",
"lint": "npx eslint . && npx prettier --check . "
},
"dependencies": {},
"peerDependencies": {
"@sap/cds": ">=7"
"@sap/cds": ">=7.1.1"
},
"license": "SEE LICENSE"
"license": "SEE LICENSE",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.2.0",
"typescript": "^5.1.6"
}
}

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc