dobo 2.2.4 → 2.3.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.
@@ -0,0 +1,30 @@
1
+ import crypto from 'crypto'
2
+
3
+ async function unique (opts = {}) {
4
+ const { omit } = this.app.lib._
5
+ opts.fieldName = opts.fieldName ?? 'id'
6
+ opts.fields = opts.fields ?? []
7
+ return {
8
+ properties: [{
9
+ name: opts.fieldName,
10
+ type: 'string',
11
+ maxLength: 32,
12
+ required: true,
13
+ index: 'primary'
14
+ }],
15
+ hooks: [{
16
+ name: 'beforeCreateRecord',
17
+ level: 1000,
18
+ handler: async function (body, options) {
19
+ if (opts.fields.length === 0) opts.fields = omit(this.properties.map(prop => prop.name), [opts.fieldName])
20
+ const item = {}
21
+ for (const f of opts.fields) {
22
+ item[f] = body[f]
23
+ }
24
+ body[opts.fieldName] = crypto.createHash('md5').update(JSON.stringify(item)).digest('hex')
25
+ }
26
+ }]
27
+ }
28
+ }
29
+
30
+ export default unique
@@ -65,8 +65,12 @@ async function findAllProps (model, inputs = [], indexes = [], isExtender) {
65
65
  * @param {Object} options - Options to the feature
66
66
  * @returns {Array} New properties found in feature
67
67
  */
68
- async function applyFeature (model, feature, options, indexes) {
69
- const { isArray } = this.app.lib._
68
+ async function applyFeature (model, feature, options, indexes, isExtender) {
69
+ const { isArray, findIndex } = this.app.lib._
70
+ if (feature.name === 'dobo:unique' && options.fieldName === 'id') {
71
+ const idx = findIndex(model.properties, { name: 'id' })
72
+ if (idx > -1) model.properties.pullAt(idx)
73
+ }
70
74
  const item = await feature.handler(options)
71
75
  if (item.rules) model.rules.push(...item.rules)
72
76
  if (!isArray(item.properties)) item.properties = [item.properties]
@@ -88,14 +92,14 @@ async function applyFeature (model, feature, options, indexes) {
88
92
  * @param {Object} model - Model
89
93
  * @param {Array} [inputs] - Array of properties
90
94
  */
91
- async function findAllFeats (model, inputs = [], indexes = []) {
95
+ async function findAllFeats (model, inputs = [], indexes = [], isExtender) {
92
96
  const { isString, omit } = this.app.lib._
93
97
  for (let feat of inputs) {
94
98
  if (isString(feat)) feat = { name: feat }
95
99
  const featName = feat.name.indexOf(':') === -1 ? `dobo:${feat.name}` : feat.name
96
100
  const feature = this.app.dobo.getFeature(featName)
97
101
  if (!feature) this.fatal('invalidFeature%s%s', model.name, featName)
98
- await applyFeature.call(this, model, feature, omit(feat, 'name'), indexes)
102
+ await applyFeature.call(this, model, feature, omit(feat, 'name'), indexes, isExtender)
99
103
  }
100
104
  }
101
105
 
@@ -307,7 +311,6 @@ async function collectModels () {
307
311
  const idProp = schema.properties.find(p => p.name === 'id')
308
312
  if (!this.constructor.idTypes.includes(idProp.type)) this.fatal('invalidIdType%s%s', schema.name, this.constructor.idTypes.join(', '))
309
313
  if (idProp.type === 'string' && !has(idProp, 'maxLength')) idProp.maxLength = 50
310
- // schema.properties = without(schema.properties, undefined)
311
314
  const model = new DoboModel(plugin, schema)
312
315
  me.models.push(model)
313
316
  }
@@ -1,6 +1,6 @@
1
- import crypto from 'crypto'
2
1
  import { ulid } from 'ulid'
3
2
  import { v4 as uuidv4, v7 as uuidv7 } from 'uuid'
3
+ import crypto from 'crypto'
4
4
 
5
5
  const defIdField = {
6
6
  name: '_id',
@@ -48,6 +48,7 @@ async function driverFactory () {
48
48
  * @param {Object} conn - Connection object
49
49
  */
50
50
  async sanitizeConnection (conn) {
51
+ conn.proto = conn.proto ?? 'http' // used by driver that use url based connection
51
52
  conn.memory = false
52
53
  }
53
54
 
@@ -155,42 +156,47 @@ async function driverFactory () {
155
156
 
156
157
  async _createRecord (model, body = {}, options = {}) {
157
158
  const { isSet, generateId } = this.app.lib.aneka
158
- const { pick, isFunction } = this.app.lib._
159
- const prop = model.properties.find(p => p.name === 'id')
160
- if (!isSet(body.id) && prop.type === 'string') {
161
- if (options.checksumId) {
162
- if (options.checksumId === true) options.checksumId = Object.keys(body)
163
- const checksum = pick(body, options.checksumId)
164
- body.id = crypto.createHash('md5').update(JSON.stringify(checksum)).digest('hex')
165
- } else if (this.idGenerator) {
166
- if (['uuid', 'uuidv4'].includes(this.idGenerator)) body.id = uuidv4()
167
- else if (['uuidv7'].includes(this.idGenerator)) body.id = uuidv7()
168
- else if (this.idGenerator === 'generateId') body.id = generateId()
169
- else if (isFunction(this.idGenerator)) body.id = await this.idGenerator(model, body, options)
170
- }
171
- if (!body.id) body.id = ulid()
172
- body.id = body.id.slice(0, prop.maxLength)
173
- }
174
- if (!this.uniqueIndexSupport) await this._checkUnique(model, body, options)
159
+ const { isFunction } = this.app.lib._
175
160
  for (const prop of model.properties) {
176
161
  if (!isSet(body[prop.name]) && isSet(prop.default)) {
177
162
  if (isFunction(prop.default)) body[prop.name] = await prop.default.call(model)
178
- else if (typeof prop.default === 'string') {
179
- if (['now()', 'current_timestamp()'].includes(prop.default.toLowerCase()) && ['datetime', 'date', 'timestamp'].includes(prop.type)) {
163
+ else if (typeof prop.default !== 'string') body[prop.name] = prop.default
164
+ else {
165
+ if (['now'].includes(prop.default) && prop.type === 'datetime') {
180
166
  body[prop.name] = new Date()
181
- } else if (['uuid()', 'uuidv4()'].includes(prop.default.toLowerCase()) && prop.type === 'string') {
167
+ } else if (['uuid', 'uuidv4'].includes(prop.default) && prop.type === 'string') {
182
168
  body[prop.name] = uuidv4().slice(0, prop.maxLength)
183
- } else if (['uuidv7()'].includes(prop.default.toLowerCase()) && prop.type === 'string') {
169
+ } else if (prop.default === 'uuidv7' && prop.type === 'string') {
184
170
  body[prop.name] = uuidv7().slice(0, prop.maxLength)
185
- } else if (['ulid()'].includes(prop.default.toLowerCase()) && prop.type === 'string') {
171
+ } else if (prop.default === 'ulid' && prop.type === 'string') {
186
172
  body[prop.name] = ulid().slice(0, prop.maxLength)
187
- } else if (prop.default.toLowerCase() === 'generateId' && prop.type === 'string') {
173
+ } else if (prop.default === 'generateid' && prop.type === 'string') {
188
174
  body[prop.name] = generateId()
175
+ } else if (prop.default.startsWith('md5:') && prop.type === 'string') {
176
+ const [, field] = prop.default.split(':')
177
+ const fields = field.split(',')
178
+ if (model.properties.filter(item => fields.includes(item.name)).length === fields.length) {
179
+ const values = fields.map(f => body[f])
180
+ body[prop.name] = crypto.createHash('md5').update(values.join(':')).digest('hex')
181
+ }
182
+ } else {
183
+ body[prop.name] = prop.default
189
184
  }
190
185
  }
191
- body[prop.name] = isFunction(prop.default) ? await prop.default.call(model) : prop.default
192
186
  }
193
187
  }
188
+ const prop = model.properties.find(p => p.name === 'id')
189
+ if (!isSet(body.id) && prop.type === 'string') {
190
+ if (this.idGenerator) {
191
+ if (['uuid', 'uuidv4'].includes(this.idGenerator)) body.id = uuidv4()
192
+ else if (['uuidv7'].includes(this.idGenerator)) body.id = uuidv7()
193
+ else if (this.idGenerator === 'generateId') body.id = generateId()
194
+ else if (isFunction(this.idGenerator)) body.id = await this.idGenerator(model, body, options)
195
+ }
196
+ if (!body.id) body.id = ulid()
197
+ body.id = body.id.slice(0, prop.maxLength)
198
+ }
199
+ if (!this.uniqueIndexSupport) await this._checkUnique(model, body, options)
194
200
  if (isSet(body.id)) {
195
201
  const resp = await this.getRecord(model, body.id, { noHook: true })
196
202
  if (!isEmpty(resp.data)) throw this.plugin.error('recordExists%s%s', body.id, model.name)
@@ -13,13 +13,28 @@ export async function execHook (name, ...args) {
13
13
  }
14
14
 
15
15
  export async function execModelHook (name, ...args) {
16
- const { last } = this.app.lib._
16
+ const { last, orderBy } = this.app.lib._
17
17
  const { noModelHook } = last(args)
18
18
  const results = []
19
19
  if (!noModelHook) {
20
- const hooks = this.hooks.filter(hook => hook.name === name)
20
+ const hooks = orderBy(this.hooks.filter(hook => hook.name === name), ['level'])
21
21
  for (const hook of hooks) {
22
- await hook.handler.call(this, ...args)
22
+ if (hook.noWait) hook.handler.call(this, ...args)
23
+ else await hook.handler.call(this, ...args)
24
+ }
25
+ }
26
+ return results
27
+ }
28
+
29
+ export async function execDynHook (name, ...args) {
30
+ const { last, orderBy } = this.app.lib._
31
+ const opts = last(args)
32
+ const results = []
33
+ if (!opts.noDynHook) {
34
+ const hooks = orderBy((opts.dynHooks ?? []).filter(hook => hook.name === name), ['level'])
35
+ for (const hook of hooks) {
36
+ if (hook.noWait) hook.handler.call(this, ...args)
37
+ else await hook.handler.call(this, ...args)
23
38
  }
24
39
  }
25
40
  return results
@@ -142,6 +157,7 @@ export async function handleAttachmentUpload (id, trigger, options = {}) {
142
157
 
143
158
  export async function getSingleRef (record = {}, options = {}) {
144
159
  const { isSet } = this.app.lib.aneka
160
+ const { get } = this.app.lib._
145
161
  const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
146
162
  const refs = {}
147
163
  options.refs = options.refs ?? []
@@ -151,6 +167,7 @@ export async function getSingleRef (record = {}, options = {}) {
151
167
  if (!((typeof options.refs === 'string' && ['*', 'all'].includes(options.refs)) || options.refs.includes(key))) continue
152
168
  const ref = prop.ref[key]
153
169
  if (ref.fields.length === 0) continue
170
+ if (get(record, `_ref.${key}`)) continue
154
171
  const rModel = this.app.dobo.getModel(ref.model)
155
172
  const query = {}
156
173
  query[ref.propName] = record[prop.name]
@@ -172,7 +189,7 @@ export async function getSingleRef (record = {}, options = {}) {
172
189
 
173
190
  export async function getMultiRefs (records = [], options = {}) {
174
191
  const { isSet } = this.app.lib.aneka
175
- const { uniq, map } = this.app.lib._
192
+ const { uniq, map, get } = this.app.lib._
176
193
  const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
177
194
  options.refs = options.refs ?? []
178
195
  if (props.length > 0) {
@@ -182,6 +199,7 @@ export async function getMultiRefs (records = [], options = {}) {
182
199
  const ref = prop.ref[key]
183
200
  if (ref.fields.length === 0) continue
184
201
  if (ref.type !== '1:1') continue
202
+ if (get(records, `0._ref.${key}`)) continue
185
203
  const rModel = this.app.dobo.getModel(ref.model)
186
204
  const matches = uniq(map(records, r => {
187
205
  let v = r[prop.name]
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook } from './_util.js'
2
2
  const action = 'clearRecord'
3
3
 
4
4
  async function clearRecord (...args) {
@@ -8,7 +8,9 @@ async function clearRecord (...args) {
8
8
  const { options } = await getFilterAndOptions.call(this, null, opts, action)
9
9
  await execHook.call(this, 'beforeClearRecord', options)
10
10
  await execModelHook.call(this, 'beforeClearRecord', options)
11
+ await execDynHook.call(this, 'beforeClearRecord', options)
11
12
  const result = (await this.driver._clearRecord(this, options)) ?? {}
13
+ await execDynHook.call(this, 'beforeClearRecord', result, options)
12
14
  await execModelHook.call(this, 'afterClearRecord', result, options)
13
15
  await execHook.call(this, 'afterClearRecord', result, options)
14
16
  return dataOnly ? result.data : result
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook } from './_util.js'
2
2
  const action = 'countRecord'
3
3
 
4
4
  async function countRecord (...args) {
@@ -8,7 +8,9 @@ async function countRecord (...args) {
8
8
  const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
9
9
  await execHook.call(this, 'beforeCountRecord', options)
10
10
  await execModelHook.call(this, 'beforeCountRecord', filter, options)
11
+ await execDynHook.call(this, 'beforeCountRecord', filter, options)
11
12
  const result = (await this.driver._countRecord(this, filter, options)) ?? {}
13
+ await execDynHook.call(this, 'afterCountRecord', filter, result, options)
12
14
  await execModelHook.call(this, 'afterCountRecord', filter, result, options)
13
15
  await execHook.call(this, 'afterCountRecord', filter, result, options)
14
16
  return dataOnly ? result.data : result
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook } from './_util.js'
2
2
  const action = 'createAggregate'
3
3
 
4
4
  async function createAggregate (...args) {
@@ -8,7 +8,9 @@ async function createAggregate (...args) {
8
8
  const { filter, options } = await getFilterAndOptions.call(this, _filter, opts, action)
9
9
  await execHook.call(this, 'beforeCreateAggregate', filter, params, options)
10
10
  await execModelHook.call(this, 'beforeCreateAggregate', filter, params, options)
11
+ await execDynHook.call(this, 'beforeCreateAggregate', filter, params, options)
11
12
  const result = (await this.driver._createAggregate(this, filter, params, options)) ?? {}
13
+ await execDynHook.call(this, 'afterCreateAggregate', filter, params, result, options)
12
14
  await execModelHook.call(this, 'afterCreateAggregate', filter, params, result, options)
13
15
  await execHook.call(this, 'afterCreateAggregate', filter, params, result, options)
14
16
  return dataOnly ? result.data : result
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook } from './_util.js'
2
2
  const action = 'createHistogram'
3
3
 
4
4
  async function createHistogram (...args) {
@@ -8,7 +8,9 @@ async function createHistogram (...args) {
8
8
  const { filter, options } = await getFilterAndOptions.call(this, _filter, opts, action)
9
9
  await execHook.call(this, 'beforeCreateHistogram', filter, params, options)
10
10
  await execModelHook.call(this, 'beforeCreateHistogram', filter, params, options)
11
+ await execDynHook.call(this, 'beforeCreateHistogram', filter, params, options)
11
12
  const result = (await this.driver._createHistogram(this, filter, params, options)) ?? {}
13
+ await execDynHook.call(this, 'afterCreateHistogram', filter, params, result, options)
12
14
  await execModelHook.call(this, 'afterCreateHistogram', filter, params, result, options)
13
15
  await execHook.call(this, 'afterCreateHistogram', filter, params, result, options)
14
16
  return dataOnly ? result.data : result
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execValidation, execModelHook, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
2
2
 
3
3
  export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
4
4
  const action = 'createRecord'
@@ -16,6 +16,7 @@ async function createRecord (...args) {
16
16
  const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, onlyTypes })
17
17
  await execHook.call(this, 'beforeCreateRecord', input, options)
18
18
  await execModelHook.call(this, 'beforeCreateRecord', input, options)
19
+ await execDynHook.call(this, 'beforeCreateRecord', input, options)
19
20
  if (!noValidation) await execValidation.call(this, input, options)
20
21
  let result = options.record ?? (await this.driver._createRecord(this, input, options)) ?? {}
21
22
  if (noResult) {
@@ -26,6 +27,7 @@ async function createRecord (...args) {
26
27
  if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
27
28
  if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
28
29
  await handleReq.call(this, result.data.id, 'created', options)
30
+ await execDynHook.call(this, 'afterCreateRecord', input, result, options)
29
31
  await execModelHook.call(this, 'afterCreateRecord', input, result, options)
30
32
  await execHook.call(this, 'afterCreateRecord', input, result, options)
31
33
  await runHook('cache:clear', this, 'create', body, result)
@@ -1,4 +1,4 @@
1
- import { getMultiRefs, execHook, execModelHook, getFilterAndOptions } from './_util.js'
1
+ import { getMultiRefs, execHook, execModelHook, execDynHook, getFilterAndOptions } from './_util.js'
2
2
  const action = 'findAllRecord'
3
3
 
4
4
  async function native (filter, options, dataOnly) {
@@ -9,6 +9,7 @@ async function native (filter, options, dataOnly) {
9
9
  if (!this.cacheable) noCache = true
10
10
  await execHook.call(this, 'beforeFindAllRecord', filter, options)
11
11
  await execModelHook.call(this, 'beforeFindAllRecord', filter, options)
12
+ await execDynHook.call(this, 'beforeFindAllRecord', filter, options)
12
13
  if (get && !noCache && !options.record) {
13
14
  const cachedResult = await get({ model: this.name, filter, options })
14
15
  if (cachedResult) {
@@ -24,6 +25,7 @@ async function native (filter, options, dataOnly) {
24
25
  }
25
26
  }
26
27
  if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
28
+ await execDynHook.call(this, 'beforeCreateRecord', filter, result, options)
27
29
  await execModelHook.call(this, 'afterFindAllRecord', filter, result, options)
28
30
  await execHook.call(this, 'afterFindAllRecord', filter, result, options)
29
31
  if (set && !noCache) await set({ model: this.name, filter, options, result })
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, getMultiRefs } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, getMultiRefs } from './_util.js'
2
2
  const action = 'findRecord'
3
3
 
4
4
  /**
@@ -87,6 +87,7 @@ async function findRecord (...args) {
87
87
  }
88
88
  await execHook.call(this, 'beforeFindRecord', filter, options)
89
89
  await execModelHook.call(this, 'beforeFindRecord', filter, options)
90
+ await execDynHook.call(this, 'beforeFindRecord', filter, options)
90
91
  const result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
91
92
  if (!noResultSanitizer) {
92
93
  for (const idx in result.data) {
@@ -94,6 +95,7 @@ async function findRecord (...args) {
94
95
  }
95
96
  }
96
97
  if (isSet(options.refs)) await getMultiRefs.call(this, result.data, options)
98
+ await execDynHook.call(this, 'afterFindRecord', filter, result, options)
97
99
  await execModelHook.call(this, 'afterFindRecord', filter, result, options)
98
100
  await execHook.call(this, 'afterFindRecord', filter, result, options)
99
101
  if (!noCache) await runHook('cache:setByFilter', this, filter, result)
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, getSingleRef } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, getSingleRef } from './_util.js'
2
2
  const action = 'getRecord'
3
3
 
4
4
  /**
@@ -55,6 +55,7 @@ async function getRecord (...args) {
55
55
  id = this.sanitizeId(id)
56
56
  await execHook.call(this, 'beforeGetRecord', id, options)
57
57
  await execModelHook.call(this, 'beforeGetRecord', id, options)
58
+ await execDynHook.call(this, 'beforeGetRecord', id, options)
58
59
  if (!noCache) {
59
60
  try {
60
61
  await runHook('cache:getById', this, id)
@@ -70,7 +71,8 @@ async function getRecord (...args) {
70
71
  if (isEmpty(result.data) && !options.throwNotFound) return dataOnly ? undefined : { data: undefined }
71
72
  if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
72
73
  if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
73
- await execModelHook.call(this, 'afterGetRecord', id, result.data, options)
74
+ await execDynHook.call(this, 'afterGetRecord', id, result, options)
75
+ await execModelHook.call(this, 'afterGetRecord', id, result, options)
74
76
  await execHook.call(this, 'afterGetRecord', id, result, options)
75
77
  if (!noCache) await runHook('cache:setById', this, id, result)
76
78
  return dataOnly ? result.data : result
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
2
2
  const action = 'removeRecord'
3
3
 
4
4
  /**
@@ -42,6 +42,7 @@ async function removeRecord (...args) {
42
42
  id = this.sanitizeId(id)
43
43
  await execHook.call(this, 'beforeRemoveRecord', id, options)
44
44
  await execModelHook.call(this, 'beforeRemoveRecord', id, options)
45
+ await execDynHook.call(this, 'beforeRemoveRecord', id, options)
45
46
  const result = options.record ?? (await this.driver._removeRecord(this, id, options)) ?? {}
46
47
  if (noResult) {
47
48
  await runHook('cache:clear', this, 'remove', id)
@@ -50,6 +51,7 @@ async function removeRecord (...args) {
50
51
  if (!noResultSanitizer) result.oldData = await this.sanitizeRecord(result.oldData, options)
51
52
  if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
52
53
  await handleReq.call(this, result.oldData.id, 'removed', options)
54
+ await execDynHook.call(this, 'afterRemoveRecord', id, result, options)
53
55
  await execModelHook.call(this, 'afterRemoveRecord', id, result, options)
54
56
  await execHook.call(this, 'afterRemoveRecord', id, result, options)
55
57
  await runHook('cache:clear', this, 'remove', id, result)
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execValidation, execModelHook, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
2
2
  import { onlyTypes } from './create-record.js'
3
3
  const action = 'updateRecord'
4
4
 
@@ -60,6 +60,7 @@ async function updateRecord (...args) {
60
60
  delete input.id
61
61
  await execHook.call(this, 'beforeUpdateRecord', id, input, options)
62
62
  await execModelHook.call(this, 'beforeUpdateRecord', id, input, options)
63
+ await execDynHook.call(this, 'beforeUpdateRecord', id, input, options)
63
64
  if (!noValidation) await execValidation.call(this, input, options)
64
65
  const result = options.record ?? (await this.driver._updateRecord(this, id, input, options)) ?? {}
65
66
  if (noResult) {
@@ -72,6 +73,7 @@ async function updateRecord (...args) {
72
73
  }
73
74
  if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
74
75
  await handleReq.call(this, result.data.id, 'updated', options)
76
+ await execDynHook.call(this, 'afterUpdateRecord', id, input, result, options)
75
77
  await execModelHook.call(this, 'afterUpdateRecord', id, input, result, options)
76
78
  await execHook.call(this, 'afterUpdateRecord', id, input, result, options)
77
79
  await runHook('cache:clear', this, 'update', id, body, result)
@@ -1,4 +1,4 @@
1
- import { getFilterAndOptions, execHook, execModelHook, execValidation, getSingleRef, handleReq } from './_util.js'
1
+ import { getFilterAndOptions, execHook, execModelHook, execDynHook, execValidation, getSingleRef, handleReq } from './_util.js'
2
2
  const action = 'upsertRecord'
3
3
 
4
4
  async function native (body = {}, opts = {}) {
@@ -13,6 +13,7 @@ async function native (body = {}, opts = {}) {
13
13
  if (!noValidation) input = await execValidation.call(this, input, options)
14
14
  await execHook.call(this, 'beforeUpsertRecord', input, options)
15
15
  await execModelHook.call(this, 'beforeUpsertRecord', input, options)
16
+ await execDynHook.call(this, 'beforeUpsertRecord', input, options)
16
17
  const result = options.record ?? (await this.driver._upsertRecord(this, input, options)) ?? {}
17
18
  if (noResult) {
18
19
  await runHook('cache:clear', this, 'upsert', body)
@@ -21,6 +22,7 @@ async function native (body = {}, opts = {}) {
21
22
  if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
22
23
  if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
23
24
  await handleReq.call(this, result.data.id, 'upserted', options)
25
+ await execDynHook.call(this, 'afterUpsertRecord', input, result, options)
24
26
  await execModelHook.call(this, 'afterUpsertRecord', input, result, options)
25
27
  await execHook.call(this, 'afterUpsertRecord', input, result, options)
26
28
  await runHook('cache:clear', this, 'upsert', body, result)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "2.2.4",
3
+ "version": "2.3.0",
4
4
  "description": "DBMS for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-01-24
4
+
5
+ - [2.3.0] Add ```dynHooks``` to model's options
6
+ - [2.3.0] Add ```dobo:unique``` feature
7
+ - [2.3.0] Add ```noWait``` handler for model's hook
8
+
9
+ ## 2026-01-21
10
+
11
+ - [2.2.5] Add ```proto``` to ```connection.options```
12
+
3
13
  ## 2026-01-19
4
14
 
5
15
  - [2.2.4] Bug fix on route ```dobo:/attachment/...```
@@ -1,13 +0,0 @@
1
- async function intId (opts = {}) {
2
- return {
3
- properties: [{
4
- name: 'id',
5
- type: 'integer',
6
- required: true,
7
- index: 'primary',
8
- unsigned: true
9
- }]
10
- }
11
- }
12
-
13
- export default intId