@adobe/aio-cli-plugin-app-storage 1.0.3 → 1.2.0-pre.2025-11-18.sha-9c4079ac

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.
Files changed (36) hide show
  1. package/README.md +676 -12
  2. package/package.json +33 -6
  3. package/src/BaseCommand.js +11 -58
  4. package/src/DBBaseCommand.js +112 -0
  5. package/src/StateBaseCommand.js +87 -0
  6. package/src/commands/app/add/db.js +20 -0
  7. package/src/commands/app/db/collection/create.js +119 -0
  8. package/src/commands/app/db/collection/drop.js +90 -0
  9. package/src/commands/app/db/collection/list.js +100 -0
  10. package/src/commands/app/db/collection/rename.js +98 -0
  11. package/src/commands/app/db/collection/stats.js +94 -0
  12. package/src/commands/app/db/delete.js +89 -0
  13. package/src/commands/app/db/document/count.js +96 -0
  14. package/src/commands/app/db/document/delete.js +95 -0
  15. package/src/commands/app/db/document/find.js +133 -0
  16. package/src/commands/app/db/document/insert.js +147 -0
  17. package/src/commands/app/db/document/replace.js +122 -0
  18. package/src/commands/app/db/document/update.js +144 -0
  19. package/src/commands/app/db/index/create.js +170 -0
  20. package/src/commands/app/db/index/drop.js +87 -0
  21. package/src/commands/app/db/index/list.js +82 -0
  22. package/src/commands/app/db/ping.js +77 -0
  23. package/src/commands/app/db/provision.js +190 -0
  24. package/src/commands/app/db/stats.js +87 -0
  25. package/src/commands/app/db/status.js +159 -0
  26. package/src/commands/app/state/delete.js +3 -3
  27. package/src/commands/app/state/get.js +2 -2
  28. package/src/commands/app/state/list.js +3 -3
  29. package/src/commands/app/state/put.js +4 -4
  30. package/src/commands/app/state/stats.js +2 -2
  31. package/src/constants/db.js +32 -0
  32. package/src/constants/global.js +14 -0
  33. package/src/{constants.js → constants/state.js} +3 -0
  34. package/src/utils/inputValidation.js +74 -0
  35. package/src/utils/output.js +35 -0
  36. package/oclif.manifest.json +0 -306
@@ -0,0 +1,133 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License")
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { DBBaseCommand } from '../../../../DBBaseCommand.js'
14
+ import { Args, Flags } from '@oclif/core'
15
+ import { asObject, isNonEmptyString } from '../../../../utils/inputValidation.js'
16
+ import chalk from 'chalk'
17
+ import { prettyJson } from '../../../../utils/output.js'
18
+
19
+ export class Find extends DBBaseCommand {
20
+ async run () {
21
+ const { collection, filter } = this.args
22
+ const { limit, skip, sort, projection } = this.flags
23
+
24
+ try {
25
+ this.log(chalk.blue(`Finding documents in collection '${collection}'...`))
26
+ this.log(chalk.dim(` Filter:\n${prettyJson(filter)}`))
27
+
28
+ // Prepare options for find
29
+ const options = { limit }
30
+ this.log(chalk.dim(` Limit: ${limit}`))
31
+ if (skip !== undefined) {
32
+ this.log(chalk.dim(` Skip: ${skip}`))
33
+ options.skip = skip
34
+ }
35
+ if (sort !== undefined) {
36
+ this.log(chalk.dim(` Sort:\n${prettyJson(sort)}`))
37
+ options.sort = sort
38
+ }
39
+ if (projection !== undefined) {
40
+ this.log(chalk.dim(` Projection:\n${prettyJson(projection)}`))
41
+ options.projection = projection
42
+ }
43
+
44
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}\n`))
45
+
46
+ const client = await this.db.connect()
47
+ const coll = client.collection(collection)
48
+ const results = await coll.findArray(filter, options)
49
+ const timestamp = new Date().toISOString()
50
+ const response = {
51
+ collection,
52
+ filter,
53
+ options,
54
+ results,
55
+ namespace: this.rtNamespace,
56
+ timestamp
57
+ }
58
+
59
+ this.debugLogger?.info?.('Find results:', results)
60
+ if (results?.length > 0) {
61
+ this.log(chalk.green(`Retrieved ${results.length} document(s) from collection '${collection}'`))
62
+ this.log(chalk.dim(` Searched: ${timestamp}`))
63
+ this.log(chalk.dim(` Results:\n${prettyJson(results)}`))
64
+ } else {
65
+ this.log(chalk.green(`No documents matching the filter criteria found in collection '${collection}'.`))
66
+ this.log(chalk.dim(` Searched: ${timestamp}`))
67
+ }
68
+
69
+ return response
70
+ } catch (error) {
71
+ this.debugLogger?.error?.('Error finding documents:', error)
72
+
73
+ const errorMessage = `Failed to find documents in collection '${collection}': ${error.message}`
74
+
75
+ this.log(chalk.red('Failed to find documents'))
76
+ this.log(chalk.dim(` Collection: ${collection}`))
77
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
78
+ this.error(errorMessage)
79
+ }
80
+ }
81
+ }
82
+
83
+ Find.description = 'Find documents in a collection based on filter criteria.'
84
+
85
+ Find.examples = [
86
+ '$ aio app db document find users \'{}\'',
87
+ '$ aio app db document find products \'{"category": "Computer Accessories"}\' --json',
88
+ '$ aio app db document find products \'{"name": {"$regex": "Speakers$"}}\' --sort \'{"price": -1}\' --limit 10 --skip 5 --projection \'{"name": 1, "price": 1}\'',
89
+ '$ aio app db doc find orders \'{"status": "pending"}\' --sort \'{"orderDate": -1}\''
90
+ ]
91
+
92
+ Find.args = {
93
+ collection: Args.string({
94
+ name: 'collection',
95
+ description: 'The name of the collection',
96
+ required: true,
97
+ parse: input => isNonEmptyString(input, 'Collection name')
98
+ }),
99
+ filter: Args.string({
100
+ name: 'filter',
101
+ description: 'Filter criteria for the documents to find (JSON string, e.g. \'{"status": "active"}\')',
102
+ required: true,
103
+ parse: input => asObject(input, 'Filter')
104
+ })
105
+ }
106
+
107
+ Find.flags = {
108
+ ...DBBaseCommand.flags,
109
+ limit: Flags.integer({
110
+ char: 'l',
111
+ description: 'Limit the number of documents returned, max: 100',
112
+ default: 20,
113
+ max: 100,
114
+ min: 0
115
+ }),
116
+ skip: Flags.integer({
117
+ char: 's',
118
+ description: 'Skip the first N documents',
119
+ min: 0
120
+ }),
121
+ sort: Flags.string({
122
+ char: 'o',
123
+ description: 'Sort specification as a JSON object (e.g. \'{"field": 1}\')',
124
+ parse: input => asObject(input, 'Sort')
125
+ }),
126
+ projection: Flags.string({
127
+ char: 'p',
128
+ description: 'Projection specification as a JSON object (e.g. \'{"field1": 1, "field2": 0}\')',
129
+ parse: input => asObject(input, 'Projection')
130
+ })
131
+ }
132
+
133
+ Find.aliases = ['app:db:doc:find']
@@ -0,0 +1,147 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { DBBaseCommand } from '../../../../DBBaseCommand.js'
14
+ import { Args, Flags } from '@oclif/core'
15
+ import chalk from 'chalk'
16
+ import { isNonEmptyString } from '../../../../utils/inputValidation.js'
17
+ import { prettyJson } from '../../../../utils/output.js'
18
+
19
+ export class Insert extends DBBaseCommand {
20
+ async run () {
21
+ const { collection, documents } = this.args
22
+ const { bypassDocumentValidation } = this.flags
23
+
24
+ try {
25
+ this.log(chalk.blue(`Inserting ${documents.length} documents into collection '${collection}'...`))
26
+
27
+ // Build options object
28
+ const insertOptions = {}
29
+ if (bypassDocumentValidation) {
30
+ insertOptions.bypassDocumentValidation = true
31
+ this.log(chalk.dim(' Bypassing document validation'))
32
+ }
33
+
34
+ const client = await this.db.connect()
35
+ const coll = await client.collection(collection)
36
+
37
+ // Perform the insert operation
38
+ const result = await coll.insertMany(documents, insertOptions)
39
+
40
+ this.debugLogger?.info?.('Documents inserted successfully:', result)
41
+
42
+ const response = {
43
+ collection,
44
+ status: 'inserted',
45
+ namespace: this.rtNamespace,
46
+ timestamp: new Date().toISOString(),
47
+ result
48
+ }
49
+
50
+ if (bypassDocumentValidation) {
51
+ response.options = insertOptions
52
+ }
53
+
54
+ this.log(chalk.green(`Successfully inserted ${result.insertedCount} documents into collection '${collection}'`))
55
+ this.log(chalk.dim(` Collection: ${collection}`))
56
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
57
+
58
+ if (result.insertedIds && Object.keys(result.insertedIds).length > 0) {
59
+ this.log(chalk.dim(` Inserted IDs: ${JSON.stringify(result.insertedIds)}`))
60
+ }
61
+
62
+ this.log(chalk.dim(` Details:\n${prettyJson(result)}`))
63
+
64
+ this.log(chalk.dim(` Inserted: ${new Date().toLocaleString()}`))
65
+
66
+ return response
67
+ } catch (error) {
68
+ this.debugLogger?.error?.('Error inserting documents:', error)
69
+
70
+ const errorMessage = `Failed to insert documents into collection '${collection}': ${error.message}`
71
+
72
+ this.log(chalk.red('Failed to insert documents'))
73
+ this.log(chalk.dim(` Collection: ${collection}`))
74
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
75
+ this.log(chalk.dim(` Error: ${error.message}`))
76
+
77
+ this.error(errorMessage)
78
+ }
79
+ }
80
+ }
81
+
82
+ Insert.description = 'Insert one or more documents into a collection'
83
+
84
+ Insert.examples = [
85
+ '$ aio app db document insert users \'{"name": "John", "age": 30}\'',
86
+ '$ aio app db document insert products \'[{"id": 1, "name": "Product A"}, {"id": 2, "name": "Product B"}]\' --json',
87
+ '$ aio app db document insert temp \'{"data": "test"}\' --bypassDocumentValidation',
88
+ '$ aio app db doc insert bulk \'[{"field": "foo"}, {"field": "bar"}]\' --bypassDocumentValidation --json'
89
+ ]
90
+
91
+ Insert.args = {
92
+ collection: Args.string({
93
+ name: 'collection',
94
+ description: 'The name of the collection to insert documents into',
95
+ required: true,
96
+ parse: input => isNonEmptyString(input, 'Collection name')
97
+ }),
98
+ documents: Args.string({
99
+ name: 'documents',
100
+ description: 'JSON object or array of documents to insert',
101
+ required: true,
102
+ parse: input => {
103
+ if (typeof input !== 'string' || input.trim().length === 0) {
104
+ throw new Error('Documents: Must be a JSON string representing an object or non-empty array')
105
+ }
106
+
107
+ let result
108
+ try {
109
+ result = JSON.parse(input)
110
+ } catch (e) {
111
+ throw new Error(`Documents: JSON parse error: ${e.message}`)
112
+ }
113
+
114
+ const isSingleDoc = !Array.isArray(result)
115
+ if (isSingleDoc) {
116
+ result = [result]
117
+ }
118
+
119
+ if (result.length === 0) {
120
+ throw new Error('Documents: Cannot be empty')
121
+ }
122
+
123
+ // Validate each document is an object
124
+ for (let i = 0; i < result.length; i++) {
125
+ if (typeof result[i] !== 'object' || result[i] === null || Array.isArray(result[i])) {
126
+ if (isSingleDoc) {
127
+ throw new Error('Documents: Must be a JSON string representing an object or non-empty array')
128
+ }
129
+ throw new Error(`Documents: Element at index ${i} must be an object`)
130
+ }
131
+ }
132
+
133
+ return result
134
+ }
135
+ })
136
+ }
137
+
138
+ Insert.flags = {
139
+ ...DBBaseCommand.flags,
140
+ bypassDocumentValidation: Flags.boolean({
141
+ char: 'b',
142
+ description: 'Bypass schema validation if present',
143
+ default: false
144
+ })
145
+ }
146
+
147
+ Insert.aliases = ['app:db:doc:insert']
@@ -0,0 +1,122 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { DBBaseCommand } from '../../../../DBBaseCommand.js'
14
+ import { Args, Flags } from '@oclif/core'
15
+ import chalk from 'chalk'
16
+ import { asObject, isNonEmptyString } from '../../../../utils/inputValidation.js'
17
+
18
+ export class Replace extends DBBaseCommand {
19
+ async run () {
20
+ const { collection, filter, replacement } = this.args
21
+ const { upsert } = this.flags
22
+
23
+ try {
24
+ this.log(chalk.blue(`Replacing document in collection '${collection}'...`))
25
+
26
+ if (upsert) {
27
+ this.log(chalk.dim(' Upsert enabled: Will create document if not found'))
28
+ }
29
+
30
+ const client = await this.db.connect()
31
+ const coll = client.collection(collection)
32
+
33
+ // Build options
34
+ const options = {}
35
+ if (upsert) {
36
+ options.upsert = true
37
+ }
38
+
39
+ // Replace the document
40
+ const result = await coll.replaceOne(filter, replacement, options)
41
+
42
+ this.debugLogger?.info?.('Document replaced successfully:', result)
43
+
44
+ const response = {
45
+ collection,
46
+ filter,
47
+ replacement,
48
+ namespace: this.rtNamespace,
49
+ timestamp: new Date().toISOString(),
50
+ result
51
+ }
52
+
53
+ if (result.matchedCount > 0) {
54
+ this.log(chalk.green(`Document replaced successfully in collection '${collection}'`))
55
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
56
+ } else if (upsert && result.upsertedId) {
57
+ this.log(chalk.green(`Document created (upserted) in collection '${collection}'`))
58
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
59
+ this.log(chalk.dim(` Upserted ID: ${result.upsertedId}`))
60
+ this.log(chalk.dim(` Upserted count: ${result.upsertedCount}`))
61
+ } else {
62
+ this.log(chalk.yellow(`No document found in collection '${collection}' matching the filter`))
63
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
64
+ }
65
+
66
+ this.log(chalk.dim(` Replaced: ${new Date().toLocaleString()}`))
67
+
68
+ return response
69
+ } catch (error) {
70
+ this.debugLogger?.error?.('Error replacing document:', error)
71
+
72
+ const errorMessage = `Failed to replace document in collection '${collection}': ${error.message}`
73
+
74
+ this.log(chalk.red('Failed to replace document'))
75
+ this.log(chalk.dim(` Collection: ${collection}`))
76
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
77
+
78
+ this.error(errorMessage)
79
+ }
80
+ }
81
+ }
82
+
83
+ Replace.description = 'Replace a single document in a collection'
84
+
85
+ Replace.examples = [
86
+ '$ aio app db document replace users \'{"name": "John"}\' \'{"name": "John Doe", "age": 30, "status": "active"}\'',
87
+ '$ aio app db document replace products \'{"id": "123"}\' \'{"id": "123", "name": "New Product", "price": 99.99}\' --json',
88
+ '$ aio app db document replace posts \'{"slug": "hello-world"}\' \'{"title": "Hello World", "content": "Updated content", "status": "published"}\' --upsert',
89
+ '$ aio app db doc replace users \'{"email": "john@example.com"}\' \'{"email": "john@example.com", "name": "John", "verified": true}\' --upsert --json'
90
+ ]
91
+
92
+ Replace.args = {
93
+ collection: Args.string({
94
+ name: 'collection',
95
+ description: 'The name of the collection',
96
+ required: true,
97
+ parse: input => isNonEmptyString(input, 'Collection name')
98
+ }),
99
+ filter: Args.string({
100
+ name: 'filter',
101
+ description: 'The filter document (JSON string)',
102
+ required: true,
103
+ parse: input => asObject(input, 'Filter')
104
+ }),
105
+ replacement: Args.string({
106
+ name: 'replacement',
107
+ description: 'The replacement document (JSON string)',
108
+ required: true,
109
+ parse: input => asObject(input, 'Replacement')
110
+ })
111
+ }
112
+
113
+ Replace.flags = {
114
+ ...DBBaseCommand.flags,
115
+ upsert: Flags.boolean({
116
+ char: 'u',
117
+ description: 'If no document is found, create a new one',
118
+ default: false
119
+ })
120
+ }
121
+
122
+ Replace.aliases = ['app:db:doc:replace']
@@ -0,0 +1,144 @@
1
+ /*
2
+ Copyright 2025 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { DBBaseCommand } from '../../../../DBBaseCommand.js'
14
+ import { Args, Flags } from '@oclif/core'
15
+ import chalk from 'chalk'
16
+ import { asObject, isNonEmptyString } from '../../../../utils/inputValidation.js'
17
+
18
+ export class Update extends DBBaseCommand {
19
+ async run () {
20
+ const { collection, filter, update } = this.args
21
+ const { many, upsert } = this.flags
22
+ const docPlural = many ? '(s)' : ''
23
+
24
+ try {
25
+ this.log(chalk.blue(`Updating document${docPlural} in collection '${collection}'...`))
26
+ this.log(chalk.dim(` Filter: ${JSON.stringify(filter)}`))
27
+ this.log(chalk.dim(` Update: ${JSON.stringify(update)}`))
28
+
29
+ if (upsert) {
30
+ this.log(chalk.dim(' Upsert enabled: Will create document if not found'))
31
+ }
32
+
33
+ const client = await this.db.connect()
34
+ const coll = client.collection(collection)
35
+
36
+ // Build options
37
+ const options = {}
38
+ if (upsert) {
39
+ options.upsert = true
40
+ }
41
+
42
+ // Update the document
43
+ const result = await (many ? coll.updateMany(filter, update, options) : coll.updateOne(filter, update, options))
44
+
45
+ this.debugLogger?.info?.(`Document${docPlural} updated successfully:`, result)
46
+
47
+ const response = {
48
+ collection,
49
+ filter,
50
+ update,
51
+ namespace: this.rtNamespace,
52
+ timestamp: new Date().toISOString(),
53
+ result
54
+ }
55
+ const optionOutput = { ...options }
56
+ if (many) {
57
+ optionOutput.many = true
58
+ }
59
+ if (Object.keys(optionOutput).length > 0) {
60
+ response.options = optionOutput
61
+ }
62
+
63
+ if (result.matchedCount > 0) {
64
+ if (result.modifiedCount > 0) {
65
+ this.log(chalk.green(`Document${docPlural} updated successfully in collection '${collection}'`))
66
+ this.log(chalk.dim(` Collection: ${collection}`))
67
+ this.log(chalk.dim(` Matched Count: ${result.matchedCount}`))
68
+ this.log(chalk.dim(` Modified Count: ${result.modifiedCount}`))
69
+ } else {
70
+ this.log(chalk.green(`${result.matchedCount} matching document${docPlural} found in collection '${collection}', but no update was necessary`))
71
+ }
72
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
73
+ } else if (result.upsertedId) {
74
+ this.log(chalk.green(`Document created (upserted) in collection '${collection}'`))
75
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
76
+ this.log(chalk.dim(` Upserted ID: ${result.upsertedId}`))
77
+ this.log(chalk.dim(` Upserted count: ${result.upsertedCount}`))
78
+ } else {
79
+ this.log(chalk.yellow(`No document found in collection '${collection}' matching the filter`))
80
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
81
+ }
82
+
83
+ this.log(chalk.dim(` Updated: ${new Date().toLocaleString()}`))
84
+
85
+ return response
86
+ } catch (error) {
87
+ this.debugLogger?.error?.(`Error updating document${docPlural}:`, error)
88
+
89
+ const errorMessage = `Failed to update document${docPlural} in collection '${collection}': ${error.message}`
90
+
91
+ this.log(chalk.red(`Failed to update document${docPlural}`))
92
+ this.log(chalk.dim(` Collection: ${collection}`))
93
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
94
+
95
+ this.error(errorMessage)
96
+ }
97
+ }
98
+ }
99
+
100
+ Update.description = 'Update document(s) in a collection'
101
+
102
+ Update.examples = [
103
+ '$ aio app db document update users \'{"name": "John"}\' \'{"$set": {"age": 31}}\'',
104
+ '$ aio app db document update products \'{"id": "123"}\' \'{"$inc": {"stock": -1}}\' --json',
105
+ '$ aio app db document update posts \'{"slug": "hello-world"}\' \'{"$set": {"status": "published"}}\' --many',
106
+ '$ aio app db doc update users \'{"email": "john@example.com"}\' \'{"$set": {"lastLogin": "2024-01-01"}}\' --upsert'
107
+ ]
108
+
109
+ Update.args = {
110
+ collection: Args.string({
111
+ name: 'collection',
112
+ description: 'The name of the collection',
113
+ required: true,
114
+ parse: input => isNonEmptyString(input, 'Collection name')
115
+ }),
116
+ filter: Args.string({
117
+ name: 'filter',
118
+ description: 'The filter document (JSON string)',
119
+ required: true,
120
+ parse: input => asObject(input, 'Filter')
121
+ }),
122
+ update: Args.string({
123
+ name: 'update',
124
+ description: 'The update document (JSON string)',
125
+ required: true,
126
+ parse: input => asObject(input, 'Update')
127
+ })
128
+ }
129
+
130
+ Update.flags = {
131
+ ...DBBaseCommand.flags,
132
+ upsert: Flags.boolean({
133
+ char: 'u',
134
+ description: 'If no document is found, create a new one',
135
+ default: false
136
+ }),
137
+ many: Flags.boolean({
138
+ char: 'm',
139
+ description: 'Update all documents matching the filter. Without this option, only the first matching document is updated.',
140
+ default: false
141
+ })
142
+ }
143
+
144
+ Update.aliases = ['app:db:doc:update']