dobo 1.2.3 → 1.2.5

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/README.md CHANGED
@@ -18,6 +18,22 @@ $ npm install dobo
18
18
 
19
19
  Now open your ```<bajo-data-dir>/config/.plugins``` and put ```dobo``` in it
20
20
 
21
+ ## Documentations
22
+
23
+ - [Query Language](docs/query-language.md)
24
+
25
+ ## Database Drivers
26
+
27
+ - Memory (Built-in)
28
+ - [SQL/Knex](https://github.com/ardhi/dobo-knex)
29
+ - [CouchDB](https://github.com/ardhi/dobo-couchdb)
30
+ - [Elasticsearch](https://github.com/ardhi/dobo-elasticsearch)
31
+ - [MongoDB](https://github.com/ardhi/dobo-mongodb)
32
+ - [Redis](https://github.com/ardhi/dobo-redis)
33
+ - [REST Proxy](https://github.com/ardhi/dobo-restproxy)
34
+
35
+ More will come soon
36
+
21
37
  ## License
22
38
 
23
39
  [MIT](LICENSE)
@@ -0,0 +1,152 @@
1
+ # Query Language
2
+
3
+ Since *dobo* use [Ghost QL](https://github.com/TryGhost/NQL) for its query language, you have the option to pick one of these syntaxes:
4
+
5
+ - [NQL](https://github.com/TryGhost/NQL/tree/main/packages/nql)
6
+ - [Mongo QL](https://www.mongodb.com/docs/manual/tutorial/query-documents/)
7
+
8
+ Any NQL statements will be converted to MongoDB-like QL object first, this then passed down to the database's native QL provided by its driver.
9
+
10
+ Examples from [NQL test suite](https://github.com/TryGhost/NQL/blob/main/packages/nql-lang/test/parser.test.js):
11
+
12
+ ## Equals
13
+
14
+ count:5
15
+ ```javascript
16
+ { count: 5 }
17
+ ```
18
+
19
+ slug:getting-started
20
+ ```javascript
21
+ { slug: 'getting-started' }
22
+ ```
23
+
24
+ author:'Joe Bloggs'
25
+ ```javascript
26
+ { author: 'Joe Bloggs' }
27
+ ```
28
+
29
+ ## Not Equals
30
+ count:-5
31
+ ```javascript
32
+ { count: { $ne: 5 } }
33
+ ```
34
+
35
+ author:-'Joe Bloggs'
36
+ ```javascript
37
+ { author: { $ne: 'Joe Bloggs' } }
38
+ ```
39
+
40
+ ## Less/Greater Than or Equals
41
+
42
+ count:>5
43
+ ```javascript
44
+ { count: { $gt: 5 } }
45
+ ```
46
+ tag:<getting-started
47
+ ```javascript
48
+ { tag: { $lt: 'getting-started' } }
49
+ ```
50
+ count:>=5
51
+ ```javascript
52
+ { count: { $gte: 5 } }
53
+ ```
54
+ author:<=\'Joe Bloggs\'
55
+ ```javascript
56
+ { author: { $lte: 'Joe Bloggs' } }
57
+ ```
58
+
59
+ ## IN or NOT IN, single or multiple value
60
+
61
+ count:[5]
62
+ ```javascript
63
+ { count: { $in: [5] } }
64
+ ```
65
+
66
+ tag:-[getting-started]
67
+ ```javascript
68
+ { tag: { $nin: ['getting-started'] } }
69
+ ```
70
+
71
+ author:-['Joe Bloggs', 'John O\\'Nolan', 'Hello World']
72
+ ```javascript
73
+ { author: { $nin: ['Joe Bloggs', 'John O\'Nolan', 'Hello World'] } }
74
+ ```
75
+
76
+ ## CONTAINS, STARTSWITH and ENDSWITH with and without NOT
77
+
78
+ email:~'gmail.com'
79
+ ```javascript
80
+ { email: { $regex: /gmail\.com/i } }
81
+ ```
82
+
83
+ email:-~'gmail.com'
84
+ ```javascript
85
+ { email: { $not: /gmail\.com/i } }
86
+ ```
87
+
88
+ email:~^'gmail.com'
89
+ ```javascript
90
+ { email: { $regex: /^gmail\.com/i } }
91
+ ```
92
+
93
+ email:-~^'gmail.com'
94
+ ```javascript
95
+ { email: { $not: /^gmail\.com/i } }
96
+ ```
97
+
98
+ email:~$'gmail.com'
99
+ ```javascript
100
+ { email: { $regex: /gmail\.com$/i } }
101
+ ```
102
+
103
+ email:-~$'gmail.com'
104
+ ```javascript
105
+ { email: { $not: /gmail\.com$/i } }
106
+ ```
107
+
108
+ ## NULL and Boolean Value
109
+
110
+ image:null
111
+ ```javascript
112
+ { image: null }
113
+ ```
114
+
115
+ featured:false
116
+ ```javascript
117
+ { featured: false } }
118
+ ```
119
+
120
+ featured:-true
121
+ ```javascript
122
+ { featured: { $ne: true } }
123
+ ```
124
+
125
+ ## Logical Operators
126
+
127
+ page:false+status:published
128
+ ```javascript
129
+ { $and: [{ page: false }, { status: 'published' }] }
130
+ ```
131
+
132
+ page:true,featured:true
133
+ ```javascript
134
+ { $or: [{ page: true }, { featured: true }] }
135
+ ```
136
+
137
+ ## Groups/ungroups
138
+
139
+ (page:false,(status:published+featured:true))
140
+ ```javascript
141
+ {
142
+ $or: [
143
+ { page: false },
144
+ {
145
+ $and: [
146
+ {status: 'published'},
147
+ {featured: true}
148
+ ]
149
+ }
150
+ ]
151
+ }
152
+ ```
package/index.js CHANGED
@@ -327,10 +327,11 @@ async function factory (pkgName) {
327
327
 
328
328
  getInfo = (name) => {
329
329
  const { breakNsPath } = this.app.bajo
330
- const { find, map } = this.lib._
330
+ const { find, map, isEmpty } = this.lib._
331
331
  const schema = this.getSchema(name)
332
332
  const conn = this.getConnection(schema.connection)
333
- const { ns, path: type } = breakNsPath(conn.type)
333
+ let { ns, path: type } = breakNsPath(conn.type)
334
+ if (isEmpty(type)) type = conn.type
334
335
  const driver = find(this.drivers, { type, ns, driver: conn.driver })
335
336
  const instance = find(this.app[driver.ns].instances, { name: schema.connection })
336
337
  const opts = conn.type === 'mssql' ? { includeTriggerModifications: true } : undefined
@@ -5,9 +5,10 @@ async function defSanitizer (item) {
5
5
  async function collectConnections ({ item, index, options }) {
6
6
  const conn = item
7
7
  const { importModule, breakNsPath } = this.app.bajo
8
- const { has, find } = this.lib._
8
+ const { has, find, isEmpty } = this.lib._
9
9
  if (!has(conn, 'type')) this.fatal('mustValidDbType')
10
- const { ns, path: type } = breakNsPath(conn.type)
10
+ let { ns, path: type } = breakNsPath(conn.type)
11
+ if (isEmpty(type)) type = conn.type
11
12
  const driver = find(this.drivers, { ns, type })
12
13
  if (!driver) this.fatal('unsupportedDbType%s', conn.type)
13
14
  let file = `${ns}:/${this.name}/lib/${type}/conn-sanitizer.js`
@@ -23,7 +23,7 @@ async function collectDrivers () {
23
23
  const [type, provider] = t.split('@')
24
24
  const exists = find(me.drivers, { type, ns })
25
25
  if (exists) this.fatal('dbTypeAlreadySupportedByDriver%s%s', type, info.driver)
26
- const driver = pick(find(me.app[ns].drivers, { name: type }) ?? {}, ['dialect', 'idField', 'lowerCaseColl', 'returning'])
26
+ const driver = pick(find(me.app[ns].drivers, { name: type }) ?? {}, ['dialect', 'idField', 'lowerCaseModel', 'returning'])
27
27
  const ext = {
28
28
  type,
29
29
  ns,
@@ -23,7 +23,7 @@ async function sanitizeFeature (item) {
23
23
  async function sanitizeIndexes (item) {
24
24
  const { generateId } = this.app.bajo
25
25
  for (const idx of item.indexes) {
26
- if (!idx.name) idx.name = `${(item.collName ?? item.name).toUpperCase()}_${generateId()}`
26
+ if (!idx.name) idx.name = `${(item.modelName ?? item.name).toUpperCase()}_${generateId()}`
27
27
  if (!(typeof idx.fields === 'string' || Array.isArray(idx.fields))) this.fatal('onlyAcceptFields%s%s', 'indexes', item.name)
28
28
  }
29
29
  }
@@ -53,9 +53,10 @@ async function sanitizeSchema (items) {
53
53
  if (!conn) fatal.call(this, 'Unknown connection \'%s@%s\'', item.name, item.connection)
54
54
  item.fullText = item.fullText ?? { fields: [] }
55
55
  item.indexes = item.indexes ?? []
56
- const { ns, path: type } = breakNsPath(conn.type)
56
+ let { ns, path: type } = breakNsPath(conn.type)
57
+ if (isEmpty(type)) type = conn.type
57
58
  const driver = find(this.drivers, { type, ns, driver: conn.driver })
58
- if (driver.lowerCaseColl) item.name = item.name.toLowerCase()
59
+ if (driver.lowerCaseModel) item.name = item.name.toLowerCase()
59
60
  let file = `${ns}:/${this.name}/lib/${type}/prop-sanitizer.js`
60
61
  let propSanitizer = await importModule(file)
61
62
  if (!propSanitizer) propSanitizer = genericPropSanitizer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Database ORM/ODM for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -6,12 +6,12 @@ async function clear (name, options = {}) {
6
6
 
7
7
  await this.modelExists(name, true)
8
8
  const { noHook } = options
9
- const { handler, schema } = await resolveMethod.call(this, name, 'model-clear', options)
9
+ const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-clear', options)
10
10
  if (!noHook) {
11
11
  await runHook(`${this.name}:beforeModelClear`, schema, options)
12
12
  await runHook(`${this.name}.${camelCase(name)}:beforeModelClear`, options)
13
13
  }
14
- const resp = await handler.call(this, { schema, options })
14
+ const resp = await handler.call(this.app[driver.ns], { schema, options })
15
15
  if (!noHook) {
16
16
  await runHook(`${this.name}.${camelCase(name)}:afterModelClear`, options, resp)
17
17
  await runHook(`${this.name}:afterModelClear`, schema, options, resp)
@@ -4,12 +4,12 @@ async function create (name, options = {}) {
4
4
  const { runHook } = this.app.bajo
5
5
  const { camelCase } = this.lib._
6
6
 
7
- const { handler, schema } = await resolveMethod.call(this, name, 'model-create', options)
7
+ const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-create', options)
8
8
  if (!options.noHook) {
9
9
  await runHook(`${this.name}:beforeModelCreate`, schema, options)
10
10
  await runHook(`${this.name}.${camelCase(name)}:beforeModelCreate`, options)
11
11
  }
12
- await handler.call(this, { schema, options })
12
+ await handler.call(this.app[driver.ns], { schema, options })
13
13
  if (!options.noHook) {
14
14
  await runHook(`${this.name}.${camelCase(name)}:afterModelCreate`, options)
15
15
  await runHook(`${this.name}:afterModelCreate`, schema, options)
@@ -3,13 +3,13 @@ import resolveMethod from '../../lib/resolve-method.js'
3
3
  async function drop (name, options = {}) {
4
4
  const { runHook } = this.app.bajo
5
5
  const { camelCase } = this.lib._
6
- const { handler, schema } = await resolveMethod.call(this, name, 'model-drop', options)
6
+ const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-drop', options)
7
7
 
8
8
  if (!options.noHook) {
9
9
  await runHook(`${this.name}:beforeModelDrop`, schema, options)
10
10
  await runHook(`${this.name}.${camelCase(name)}:beforeModelDrop`, options)
11
11
  }
12
- await handler.call(this, { schema, options })
12
+ await handler.call(this.app[driver.ns], { schema, options })
13
13
  if (!options.noHook) {
14
14
  await runHook(`${this.name}.${camelCase(name)}:afterModelDrop`, options)
15
15
  await runHook(`${this.name}:afterModelDrop`, schema, options)
@@ -6,12 +6,12 @@ async function exists (name, thrown, options = {}) {
6
6
  if (cache[name]) return cache[name]
7
7
  const { runHook } = this.app.bajo
8
8
  const { camelCase } = this.lib._
9
- const { handler, schema } = await resolveMethod.call(this, name, 'model-exists', options)
9
+ const { handler, schema, driver } = await resolveMethod.call(this, name, 'model-exists', options)
10
10
  if (!options.noHook) {
11
11
  await runHook(`${this.name}:beforeModelExists`, schema, options)
12
12
  await runHook(`${this.name}.${camelCase(name)}:beforeModelExists`, options)
13
13
  }
14
- const exist = await handler.call(this, { schema, options })
14
+ const exist = await handler.call(this.app[driver.ns], { schema, options })
15
15
  if (!options.noHook) {
16
16
  await runHook(`${this.name}.${camelCase(name)}:afterModelExists`, exist, options)
17
17
  await runHook(`${this.name}:afterModelExists`, schema, exist, options)
@@ -6,7 +6,7 @@ async function findOne (name, filter = {}, opts = {}) {
6
6
  const { isSet } = this.lib.aneka
7
7
  const { runHook } = this.app.bajo
8
8
  const { get, set } = this.cache ?? {}
9
- const { cloneDeep, camelCase, omit } = this.lib._
9
+ const { cloneDeep, camelCase, omit, pick } = this.lib._
10
10
  delete opts.record
11
11
  const options = cloneDeep(omit(opts, ['req', 'reply']))
12
12
  options.req = opts.req
@@ -37,12 +37,13 @@ async function findOne (name, filter = {}, opts = {}) {
37
37
  }
38
38
  filter.limit = 1
39
39
  filter.page = 1
40
- const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
40
+ let record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
41
41
  delete options.record
42
42
  record.data = record.data[0]
43
43
 
44
44
  if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
45
45
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
46
+ record = pick(record, ['data'])
46
47
  if (!noHook) {
47
48
  await runHook(`${this.name}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
48
49
  await runHook(`${this.name}:afterRecordFindOne`, name, filter, options, record)