@sap/cds 9.0.4 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,31 @@
4
4
  - The format is based on [Keep a Changelog](https://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## Version 9.1.0 - 2025-06-30
8
+
9
+ ### Added
10
+
11
+ - CDS config schema validations for `cds.requires.auth.tenants`, `cds.cdsc`, `cds.query`, `cds.log`, `cds.server`
12
+ - Queue option `targetPrefix` to prefix `target` value of `cds.outbox.Messages` entries for microservice isolation
13
+ - Basic support for CRUD for hierarchy entities
14
+
15
+ ### Changed
16
+
17
+ - Reduced the amount of SELECT nesting the OData adapter does for `$apply` queries.
18
+ - Better error messages for unresolved parent associations in hierarchy requests
19
+ - Enabled updated behavior of `draftActivate` to move updates to fields of draft enabled entities with type `cds.LargeBinary` from draft to active table on the database level, with feature flag `cds.env.fiori.move_media_data_in_db`.
20
+
21
+ ### Fixed
22
+
23
+ - Copies of `cds.context` with `locale`
24
+ - Support for relative paths in `@odata.bind`
25
+ - `cds build` on Windows OS - fixed cli tar usage for resources.tgz
26
+ - Actions and functions with scalar return types use same `@odata.context` calculation as other return types, fixing e.g. `cds.odata.contextAbsoluteUrl` not being respected
27
+ - Improve content-type and content-length handling in OData adapter
28
+ - Parsing incorrect function parameters
29
+ - `cds deploy --dry` no longer tries to load a DB adapter, so that it works w/o one installed.
30
+ - Fix `@mandatory` for actions and functions
31
+
7
32
  ## Version 9.0.4 - 2025-06-18
8
33
 
9
34
  ### Fixed
@@ -41,16 +41,14 @@ const { Draft } = cds.linked(`
41
41
  }
42
42
  `).definitions
43
43
 
44
-
45
- function DraftEntity4 (active, name = active.name+'.drafts') {
46
-
47
- const draft = Object.create (active, {
44
+ function DraftEntity4(active, name = active.name + '.drafts') {
45
+ const draft = Object.create(active, {
48
46
  name: { value: name }, // REVISIT: lots of things break if we do that!
49
47
  elements: { value: { ...active.elements, ...Draft.elements }, enumerable: true },
50
48
  actives: { value: active },
51
49
  query: { value: undefined }, // to not inherit that from active
52
50
  // drafts: { value: undefined }, // to not inherit that from active -> doesn't work yet as the coding in lean-draft.js uses .drafts to identify both active and draft entities
53
- isDraft: { value: true },
51
+ isDraft: { value: true }
54
52
  })
55
53
 
56
54
  // for quoted names, we need to overwrite the cds.persistence.name of the derived, draft entity
@@ -60,7 +58,6 @@ function DraftEntity4 (active, name = active.name+'.drafts') {
60
58
  return draft
61
59
  }
62
60
 
63
-
64
61
  module.exports = function cds_compile_for_lean_drafts(csn) {
65
62
  function _redirect(assoc, target) {
66
63
  assoc.target = target.name
@@ -153,7 +150,8 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
153
150
  key === '@mandatory' ||
154
151
  key === '@Common.FieldControl' && newEl[key]?.['#'] === 'Mandatory' ||
155
152
  // key === '@Core.Immutable': Not allowed via UI anyway -> okay to cleanse them in PATCH
156
- key.startsWith('@assert') ||
153
+ // REVISIT: Remove feature flag dependency: If active, validation errors will be degraded to messages and stored in draft admin data
154
+ (!active._service?.entities.DraftAdministrativeData.elements.DraftMessages && key.startsWith('@assert')) ||
157
155
  key.startsWith('@PersonalData')
158
156
  )
159
157
  newEl[key] = undefined
@@ -162,6 +160,23 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
162
160
  draft.elements[each] = newEl
163
161
  }
164
162
 
163
+ // For list-report hierarchies, there must not be deep deletes w.r.t. recursive compositions
164
+ // Therefore, they are degraded to associations.
165
+ // Note: For object-page hiearchies, deep delete on draft recursive compositions is still needed.
166
+ if (draft.elements.LimitedDescendantCount && draft['@Common.DraftRoot.ActivationAction']) {
167
+ for (const c in draft.compositions) {
168
+ const comp = draft.compositions[c]
169
+ if (comp.target === draft.name) {
170
+ // modify comp to assoc
171
+ comp.type = 'cds.Association'
172
+ comp.isComposition = false
173
+ comp.is = function(kind) { return kind === 'Association' }
174
+ delete draft.compositions[c]
175
+ draft.associations[c] = comp
176
+ }
177
+ }
178
+ }
179
+
165
180
  return draft
166
181
  }
167
182
 
@@ -173,6 +188,13 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
173
188
  def.elements.HasActiveEntity.virtual = true
174
189
  if (def.elements.DraftAdministrativeData_DraftUUID) def.elements.DraftAdministrativeData_DraftUUID.virtual = true
175
190
  def.elements.DraftAdministrativeData.virtual = true
191
+ if (def.elements.LimitedDescendantCount) {
192
+ // for hierarchies: make sure recursive compositions are not part of the draft tree
193
+ for (const c in def.compositions) {
194
+ const comp = def.compositions[c]
195
+ if (comp.target === def.name) comp['@odata.draft.ignore'] = true
196
+ }
197
+ }
176
198
  // will insert drafts entities, so that others can use `.drafts` even without incoming draft requests
177
199
  addDraftEntity(def, csn)
178
200
  }
@@ -20,6 +20,9 @@ const deploy = module.exports = function cds_deploy (model, options, csvs) {
20
20
  if (o.mocked) deploy.include_external_entities_in(model)
21
21
  else deploy.exclude_external_entities_in(model)
22
22
 
23
+ // dry deployment (output schema only)
24
+ if (o.dry) return await deploy.schema ({model, options:{}}, model, o)
25
+
23
26
  // prepare db
24
27
  if (!db.run) db = await cds.connect.to(db)
25
28
  if (!cds.db) cds.db = cds.services.db = db
@@ -34,10 +37,9 @@ const deploy = module.exports = function cds_deploy (model, options, csvs) {
34
37
 
35
38
  // deploy schema and initial data...
36
39
  try {
37
- const _run = fn => o.dry ? fn(db) : db.run(fn)
38
- await _run (async tx => {
40
+ await db.run (async tx => {
39
41
  let any = await deploy.schema (tx, model, o)
40
- if ((any || csvs) && !o.dry) await deploy.data (tx, model, o, csvs, file => LOG?.(GREY, ' > init from', local(file), RESET))
42
+ if (any || csvs) await deploy.data (tx, model, o, csvs, file => LOG?.(GREY, ' > init from', local(file), RESET))
41
43
  })
42
44
  LOG?.('/> successfully deployed to', descr, '\n')
43
45
  } catch (e) {
@@ -157,7 +157,7 @@ const _queue = {
157
157
  storeLastError: true,
158
158
  timeout: '1h',
159
159
  legacyLocking: true,
160
- ignoredContext: ['user', 'http', 'model', 'timestamp']
160
+ ignoredContext: ['user', 'http', 'model', 'timestamp', '_locale', '_features']
161
161
  },
162
162
  // legacy
163
163
  "in-memory-outbox": "in-memory-queue",
@@ -2,17 +2,6 @@ const production = process.env.NODE_ENV === 'production'
2
2
 
3
3
  module.exports = {
4
4
 
5
- /**
6
- * For our own tests to replace hard-coded checks for CDS_ENV === 'better-sqlite'
7
- * which don't work anymore with cds8 where that is the default.
8
- */
9
- get _better_sqlite() {
10
- if (process.env.CDS_ENV === 'better-sqlite') return true
11
- let conf = this.requires.db || this.requires.kinds.sql
12
- if (conf?.impl === '@cap-js/sqlite') return true
13
- else return false
14
- },
15
-
16
5
  production,
17
6
 
18
7
  requires: require('./cds-requires'),
@@ -81,23 +81,23 @@ module.exports = {
81
81
  folders: {
82
82
  type: 'object',
83
83
  default: {},
84
- description: 'Only set folders if you don\'t want to use the defaults \'app/\', \'db/\', \'srv/\'.',
84
+ description: 'Overrides for default folders. Only override if you don\'t want to use the defaults \'app/\', \'db/\', \'srv/\'.',
85
85
  additionalProperties: true,
86
86
  properties: {
87
87
  app: {
88
88
  type: 'string',
89
89
  format: 'uri-reference',
90
- description: 'Add a custom path for the app property, which becomes \'cds.roots\'.'
90
+ description: 'Applications, e.g. UI5 or Fiori apps'
91
91
  },
92
92
  db: {
93
93
  type: 'string',
94
94
  format: 'uri-reference',
95
- description: 'Add a custom path for the db property, which becomes \'cds.roots\'.'
95
+ description: 'Database models and migrations'
96
96
  },
97
97
  srv: {
98
98
  type: 'string',
99
99
  format: 'uri-reference',
100
- description: 'Add a custom path for the srv property, which becomes \'cds.roots\'.'
100
+ description: 'Services'
101
101
  }
102
102
  },
103
103
  patternProperties: {
@@ -168,7 +168,7 @@ module.exports = {
168
168
  properties: {
169
169
  kind: {
170
170
  type: 'string',
171
- description: 'Define the kind of strategy.',
171
+ description: 'Authentication kind.',
172
172
  anyOf: [
173
173
  {
174
174
  $ref: '#/$defs/authType'
@@ -181,6 +181,27 @@ module.exports = {
181
181
  users: {
182
182
  $ref: '#/$defs/mockUsers'
183
183
  },
184
+ tenants: {
185
+ type: 'object',
186
+ description: 'List of tenants with their respective features.',
187
+ additionalProperties: true,
188
+ patternProperties: {
189
+ '.+': {
190
+ type: 'object',
191
+ additionalProperties: true,
192
+ properties: {
193
+ features: {
194
+ type: 'array',
195
+ description: 'List of feature toggles for this tenant.',
196
+ uniqueItems: true,
197
+ items: {
198
+ type: 'string'
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ },
184
205
  credentials: {
185
206
  type: 'object',
186
207
  description: 'You can explicitly configure credentials, but this is overruled by VCAP_SERVICES if a matching entry is found therein.',
@@ -645,6 +666,193 @@ module.exports = {
645
666
  ]
646
667
  }
647
668
  }
669
+ },
670
+ cdsc: {
671
+ type: 'object',
672
+ default: {},
673
+ description: 'CDS compiler configuration options.',
674
+ additionalProperties: true,
675
+ properties: {
676
+ moduleLookupDirectories: {
677
+ type: 'array',
678
+ description: 'List of directories to search for modules.',
679
+ uniqueItems: true,
680
+ items: {
681
+ type: 'string',
682
+ format: 'uri-reference'
683
+ }
684
+ }
685
+ }
686
+ },
687
+ query: {
688
+ type: 'object',
689
+ default: {},
690
+ description: 'Query configuration options.',
691
+ additionalProperties: true,
692
+ properties: {
693
+ limit: {
694
+ type: 'object',
695
+ description: 'Default limit for queries.',
696
+ additionalProperties: true,
697
+ properties: {
698
+ default: {
699
+ type: 'integer',
700
+ description: 'Default number of results returned by a query.',
701
+ minimum: 1
702
+ },
703
+ max: {
704
+ type: 'integer',
705
+ description: 'Maximum number of results returned by a query.',
706
+ minimum: 1
707
+ },
708
+ reliablePaging: {
709
+ type: 'boolean',
710
+ description: 'Enable reliable paging for queries.'
711
+ }
712
+ }
713
+ }
714
+ }
715
+ },
716
+ log: {
717
+ type: 'object',
718
+ default: {},
719
+ description: 'Logging configuration options.',
720
+ additionalProperties: true,
721
+ properties: {
722
+ format: {
723
+ type: 'string',
724
+ description: 'Log format, either \'plain\' or \'json\'.',
725
+ enum: [
726
+ 'plain',
727
+ 'json'
728
+ ]
729
+ },
730
+ levels: {
731
+ type: 'object',
732
+ description: 'Log levels for different components.',
733
+ additionalProperties: {
734
+ type: 'string'
735
+ },
736
+ properties: {
737
+ cds: {
738
+ type: 'string',
739
+ description: 'Server and common output.'
740
+ },
741
+ cli: {
742
+ type: 'string',
743
+ description: 'CLI output.'
744
+ },
745
+ build: {
746
+ type: 'string',
747
+ description: 'CDS build output.'
748
+ },
749
+ app: {
750
+ type: 'string',
751
+ description: 'Application Service.'
752
+ },
753
+ db: {
754
+ type: 'string',
755
+ description: 'Databases.'
756
+ },
757
+ sql: {
758
+ type: 'string',
759
+ description: 'SQL output.'
760
+ },
761
+ messaging: {
762
+ type: 'string',
763
+ description: 'Messaging Service.'
764
+ },
765
+ remote: {
766
+ type: 'string',
767
+ description: 'Remote Service.'
768
+ },
769
+ 'audit-log': {
770
+ type: 'string',
771
+ description: 'AuditLog Service.'
772
+ },
773
+ odata: {
774
+ type: 'string',
775
+ description: 'OData Protocol Adapter.'
776
+ },
777
+ rest: {
778
+ type: 'string',
779
+ description: 'REST Protocol Adapter.'
780
+ },
781
+ graphql: {
782
+ type: 'string',
783
+ description: 'GraphQL Protocol Adapter.'
784
+ },
785
+ auth: {
786
+ type: 'string',
787
+ description: 'Authentication.'
788
+ },
789
+ deploy: {
790
+ type: 'string',
791
+ description: 'Database Deployment.'
792
+ },
793
+ mtx: {
794
+ type: 'string',
795
+ description: 'Multitenancy and Extensibility.'
796
+ }
797
+ }
798
+ },
799
+ mask_headers: {
800
+ type: 'array',
801
+ description: 'List of header patterns to mask in logs.',
802
+ uniqueItems: true,
803
+ items: {
804
+ type: 'string'
805
+ },
806
+ default: [
807
+ '/authorization/i',
808
+ '/cookie/i',
809
+ '/cert/i',
810
+ '/ssl/i'
811
+ ]
812
+ },
813
+ als_custom_fields: {
814
+ type: 'object',
815
+ description:
816
+ `Custom fields for Application Logging Service (ALS) in Kibana's error rendering.
817
+ Key is the index in the log message array.`,
818
+ additionalProperties: {
819
+ type: 'integer'
820
+ }
821
+ },
822
+ cls_custom_fields: {
823
+ type: 'array',
824
+ description:
825
+ `Custom fields for CLS in Kibana's error rendering.
826
+ Each entry is a field name.`,
827
+ uniqueItems: true,
828
+ items: {
829
+ type: 'string'
830
+ }
831
+ }
832
+ }
833
+ },
834
+ server: {
835
+ type: 'object',
836
+ default: {},
837
+ description: 'Server configuration options.',
838
+ additionalProperties: true,
839
+ properties: {
840
+ port: {
841
+ type: 'integer',
842
+ description: 'Port number for the server to listen on.',
843
+ minimum: 1,
844
+ maximum: 65535
845
+ },
846
+ cors: {
847
+ type: 'boolean',
848
+ description: 'Enable CORS support.',
849
+ default: true
850
+ },
851
+ index: {
852
+ type: 'boolean',
853
+ description: 'Serve the default index page.'
854
+ }
855
+ }
648
856
  }
649
857
  }
650
858
  },
@@ -909,4 +1117,4 @@ module.exports = {
909
1117
  }
910
1118
  }
911
1119
  }
912
- }
1120
+ }
@@ -110,7 +110,7 @@ class Request extends require('./event') {
110
110
  delete err.stack
111
111
  throw err
112
112
  }
113
- let e = this.error(...args)
113
+ let e = this._errors.add(4, ...args)
114
114
  if (!('stack' in e)) Error.captureStackTrace (e = Object.assign(new Error,e), this.reject)
115
115
  if (!('message' in e)) e.message = String (e.code || e.status)
116
116
  throw e
@@ -242,8 +242,7 @@ class action extends struct {
242
242
  super.validate (data, path, ctx, this.params || {})
243
243
  }
244
244
 
245
- // REVISIT: why is e['@mandatory'] not checked here?
246
- _is_mandatory(e) { return e.notNull && !e.default } // params
245
+ _is_mandatory(e) { return e.notNull && !e.default || e._is_mandatory() } // params
247
246
  }
248
247
 
249
248
  /** Managed associations are struct-like, with foreign keys as elements to validate. */
@@ -1,6 +1,6 @@
1
1
  try {
2
2
  const xssec = require('@sap/xssec')
3
- module.exports = xssec // use v3 compat api // REVISIT: why ???
3
+ module.exports = xssec
4
4
  } catch (e) {
5
5
  if (e.code === 'MODULE_NOT_FOUND') e.message = `Cannot find '@sap/xssec'. Make sure to install it with 'npm i @sap/xssec'\n` + e.message
6
6
  throw e
@@ -2,7 +2,7 @@
2
2
  this.singular4 = (dn,stripped) => {
3
3
  let n = dn.name || dn; if (stripped) n = n.match(last)[0]
4
4
  return dn['@singular'] || (
5
- /species|news$/i.test(n) ? n :
5
+ /(species|news)$/i.test(n) ? n :
6
6
  /ess$/.test(n) ? n : // Address
7
7
  /ees$/.test(n) ? n.slice(0, -1) : // Employees --> Employee
8
8
  /[sz]es$/.test(n) ? n.slice(0, -2) :
@@ -15,7 +15,7 @@ this.singular4 = (dn,stripped) => {
15
15
  this.plural4 = (dn,stripped) => {
16
16
  let n = dn.name || dn; if (stripped) n = n.match(last)[0]
17
17
  return dn['@plural'] || (
18
- /analysis|status|species|sheep|news$/i.test(n) ? n :
18
+ /(analysis|status|species|sheep|news)$/i.test(n) ? n :
19
19
  /[^aeiou]y$/.test(n) ? n.slice(0,-1) + 'ies' :
20
20
  /(s|x|z|ch|sh)$/.test(n) ? n + 'es' :
21
21
  n + 's'
package/lib/utils/tar.js CHANGED
@@ -1,3 +1,4 @@
1
+ const { PassThrough } = require('stream')
1
2
  const child_process = require('child_process')
2
3
  const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
3
4
  Error.captureStackTrace(spawn,spawn)
@@ -8,26 +9,36 @@ const spawn = /\btar\b/.test(process.env.DEBUG) ? (cmd, args, options) => {
8
9
  const cds = require('../index'), { fs, path, mkdirp, exists, rimraf } = cds.utils
9
10
  const _resolve = (...x) => path.resolve (cds.root,...x)
10
11
 
11
- // tar does not work properly on Windows (by npm/jest tests) w/o this change
12
+ // ======= ONLY_FOR_WINDOWS ======
13
+ // This section contains logic relevant for Windows OS.
14
+
15
+ // tar does not work properly on Windows w/o this change
12
16
  const win = path => {
13
17
  if (!path) return path
14
18
  if (typeof path === 'string') return path.replace('C:', '//localhost/c$').replace(/\\+/g, '/')
15
19
  if (Array.isArray(path)) return path.map(el => win(el))
16
20
  }
17
21
 
18
- async function copyDir(src, dest) {
22
+ // spawn tar on Windows, using the cli version
23
+ const winSpawnDir = (dir, args) => {
24
+ if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
25
+ else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
26
+ }
27
+
28
+ // copy a directory recursively on Windows, using fs.promises
29
+ async function winCopyDir(src, dest) {
19
30
  if ((await fs.promises.stat(src)).isDirectory()) {
20
31
  const entries = await fs.promises.readdir(src)
21
- return Promise.all(entries.map(async each => copyDir(path.join(src, each), path.join(dest, each))))
32
+ return Promise.all(entries.map(async each => winCopyDir(path.join(src, each), path.join(dest, each))))
22
33
  } else {
23
34
  await fs.promises.mkdir(path.dirname(dest), { recursive: true })
24
35
  return fs.promises.copyFile(src, dest)
25
36
  }
26
37
  }
27
38
 
28
- // Copy resources containing files and folders to temp dir on Windows and pack temp dir.
29
- // cli tar has a size limit on Windows.
30
- const createTemp = async (root, resources) => {
39
+ // copy resources containing files and folders to temp dir on Windows
40
+ // cli tar has a size limit on Windows
41
+ const winCreateTemp = async (root, resources) => {
31
42
  // Asynchronously copies the entire content from src to dest.
32
43
  const temp = await fs.promises.mkdtemp(`${fs.realpathSync(require('os').tmpdir())}${path.sep}tar-`)
33
44
  for (let resource of resources) {
@@ -43,7 +54,7 @@ const createTemp = async (root, resources) => {
43
54
  await fs.promises.cp(resource, destination, { recursive: true })
44
55
  } else {
45
56
  // node < 16
46
- await copyDir(resource, destination)
57
+ await winCopyDir(resource, destination)
47
58
  }
48
59
  }
49
60
  }
@@ -51,6 +62,43 @@ const createTemp = async (root, resources) => {
51
62
  return temp
52
63
  }
53
64
 
65
+ // spawn tar on Windows, using a temp dir, which is copied from the original dir
66
+ // cli tar has a size limit on Windows
67
+ const winSpawnTempDir = (dir, args) => {
68
+ // Synchronous trick: use a PassThrough as placeholder
69
+ const stdout = new PassThrough()
70
+ const stderr = new PassThrough()
71
+ const c = {
72
+ stdout,
73
+ stderr,
74
+ on: (...a) => { stdout.on(...a); stderr.on(...a); return c },
75
+ once: (...a) => { stdout.once(...a); stderr.once(...a); return c },
76
+ kill: () => {},
77
+ }
78
+
79
+ // async copy, then swap streams/events
80
+ winCreateTemp(dir, args.shift()).then(tempPath => {
81
+ const real = winSpawnDir(tempPath, args)
82
+ real.stdout.pipe(stdout)
83
+ real.stderr && real.stderr.pipe(stderr)
84
+ const cleanup = () => exists(tempPath) && rimraf(tempPath)
85
+ real.on('close', (...ev) => {
86
+ stdout.emit('close', ...ev)
87
+ stderr.emit('close', ...ev)
88
+ cleanup()
89
+ })
90
+ real.on('error', (...ev) => {
91
+ stdout.emit('error', ...ev)
92
+ stderr.emit('error', ...ev)
93
+ cleanup()
94
+ })
95
+ c.kill = (...ev) => real.kill(...ev)
96
+ })
97
+ return c
98
+ }
99
+
100
+ // ====== END ONLY_FOR_WINDOWS ======
101
+
54
102
  const tarInfo = async (info) => {
55
103
  let cmd, param
56
104
  if (info === 'version') {
@@ -118,19 +166,12 @@ exports.create = (dir='.', ...args) => {
118
166
  if (typeof dir === 'string') dir = _resolve(dir)
119
167
  if (Array.isArray(dir)) [ dir, ...args ] = [ cds.root, dir, ...args ]
120
168
 
121
- let c, temp
169
+ let c
122
170
  args = args.filter(el => el)
123
- if (process.platform === 'win32') {
124
- const spawnDir = (dir, args) => {
125
- if (args.some(arg => arg === '-f')) return spawn ('tar', ['c', '-C', win(dir), ...win(args)])
126
- else return spawn ('tar', ['cf', '-', '-C', win(dir), ...win(args)])
127
- }
171
+ if (process.platform === 'win32') {
128
172
  args.push('.')
129
- if (Array.isArray(args[0])) {
130
- c = createTemp(dir, args.shift()) .then (t => spawnDir(t,args))
131
- } else {
132
- c = spawnDir(dir, args)
133
- }
173
+ if (Array.isArray(args[0])) c = winSpawnTempDir(dir, args)
174
+ else c = winSpawnDir(dir, args)
134
175
  } else {
135
176
  if (Array.isArray(args[0])) {
136
177
  args.push (...args.shift().map (f => path.isAbsolute(f) ? path.relative(dir,f) : f))
@@ -153,11 +194,7 @@ exports.create = (dir='.', ...args) => {
153
194
  c.stdout.on('data', d => data.push(d))
154
195
  c.stderr.on('data', d => stderr += d)
155
196
  c.on('close', code => code ? reject(new Error(stderr)) : resolve(Buffer.concat(data)))
156
- c.on('error', reject)
157
- if (process.platform === 'win32') {
158
- c.on('close', () => temp && exists(temp) && rimraf(temp))
159
- c.on('error', () => temp && exists(temp) && rimraf(temp))
160
- }
197
+ c.on('error', reject)
161
198
  },
162
199
 
163
200
  /**
@@ -78,9 +78,7 @@ module.exports = cds.service.impl(function () {
78
78
  if (await _targetEntityDoesNotExist(req)) req.reject(404) // REVISIT: add a reasonable error message
79
79
  }
80
80
 
81
- // flag to trigger read after write in legacy odata adapter
82
- if (req.constructor.name in { ODataRequest: 1 }) req._.readAfterWrite = true
83
- if (req.protocol?.match(/odata/)) req._.readAfterWrite = true //> REVISIT for noah
81
+ if (req.protocol?.match(/odata/)) req._.readAfterWrite = true
84
82
 
85
83
  return req.data
86
84
  })
@@ -251,7 +251,7 @@ async function validate_input(req) {
251
251
  }
252
252
 
253
253
  const errs = cds.validate(req.data, req.target, assertOptions)
254
- if (errs) return req._errors.push(...errs)
254
+ if (errs) return errs.forEach(err => req.error(err))
255
255
 
256
256
  // -------------------------------------------------
257
257
  // REVISIT: is the below still needed?
@@ -332,7 +332,7 @@ function validate_action(req) {
332
332
  protocol: req.protocol
333
333
  }
334
334
  let errs = cds.validate(data, operation, assertOptions)
335
- if (errs) return req._errors.push(...errs)
335
+ if (errs) return errs.forEach(err => req.error(err))
336
336
 
337
337
  // REVISIT: we still need the following because cds.validate doesn't check for @mandatory params (both flat and nested)
338
338
  const errors = []
@@ -76,12 +76,6 @@ function handle_temporal_data(req) {
76
76
  _getDateFromQueryOptions(_queryOptions['sap-valid-to'] ?? normalizeTimestamp('9999-12-31T23:59:59.9999999Z'))
77
77
  )
78
78
  }
79
-
80
- // REVISIT: needed without okra
81
- if (req.constructor.name !== 'ODataRequest') {
82
- req._['VALID-FROM'] = _['VALID-FROM']
83
- req._['VALID-TO'] = _['VALID-TO']
84
- }
85
79
  }
86
80
  handle_temporal_data._initial = true
87
81