@adobe/aio-cli-plugin-app-storage 1.2.0 → 1.3.0-pre.2026-02-20.sha-df98265c

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 (37) hide show
  1. package/README.md +722 -1
  2. package/package.json +25 -5
  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/org/stats.js +111 -0
  23. package/src/commands/app/db/ping.js +77 -0
  24. package/src/commands/app/db/provision.js +190 -0
  25. package/src/commands/app/db/stats.js +101 -0
  26. package/src/commands/app/db/status.js +159 -0
  27. package/src/commands/app/state/delete.js +3 -3
  28. package/src/commands/app/state/get.js +2 -2
  29. package/src/commands/app/state/list.js +3 -3
  30. package/src/commands/app/state/put.js +4 -4
  31. package/src/commands/app/state/stats.js +2 -2
  32. package/src/constants/db.js +32 -0
  33. package/src/constants/global.js +14 -0
  34. package/src/{constants.js → constants/state.js} +3 -0
  35. package/src/utils/inputValidation.js +74 -0
  36. package/src/utils/output.js +35 -0
  37. package/oclif.manifest.json +0 -316
@@ -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']
@@ -0,0 +1,170 @@
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
+ import { prettyJson } from '../../../../utils/output.js'
18
+
19
+ // Regular expression to match the -s/--spec or -k/--key flags in <flag>=<value> format
20
+ // if match.groups.spec is defined, it means the flag was -s or --spec
21
+ // if match.groups.key is defined, it means the flag was -k or --key
22
+ const specFlagMatch = /^((?<spec>-s|--spec)|(?<key>-k|--key))=(?<val>.+)/
23
+
24
+ export class Create extends DBBaseCommand {
25
+ getOrderedSpecs () {
26
+ // Key/spec order matters when creating an index and both key and spec can be specified,
27
+ // so we need to parse argv to obtain the proper order since using this.flags loses the order between the two
28
+ const args = this.argv.slice(1) // Ignore the first element (collection name)
29
+ const fullSpec = []
30
+ let specOption = false
31
+ args.forEach((arg) => {
32
+ if (specOption === 'key') {
33
+ // Previous arg was -k or --key
34
+ fullSpec.push(arg)
35
+ specOption = false
36
+ } else if (specOption === 'spec') {
37
+ // Previous arg was -s or --spec
38
+ fullSpec.push(asObject(arg))
39
+ specOption = false
40
+ } else if (arg === '-k' || arg === '--key') {
41
+ // Next arg is a key
42
+ specOption = 'key'
43
+ } else if (arg === '-s' || arg === '--spec') {
44
+ // Next arg is a spec
45
+ specOption = 'spec'
46
+ } else {
47
+ // Check if the arg is in the form of -s=<val>/--spec=<val> or -k=<val>/--key=<val>
48
+ const specMatch = arg.match(specFlagMatch)
49
+ if (specMatch?.groups?.spec) {
50
+ // Spec is a JSON object
51
+ fullSpec.push(asObject(specMatch.groups.val))
52
+ } else if (specMatch?.groups?.key) {
53
+ // Key is a string
54
+ fullSpec.push(specMatch.groups.val)
55
+ }
56
+ specOption = false
57
+ }
58
+ })
59
+
60
+ return fullSpec
61
+ }
62
+
63
+ async run () {
64
+ const { collection } = this.args
65
+ const { name, unique } = this.flags
66
+
67
+ try {
68
+ const fullSpec = this.getOrderedSpecs()
69
+ const prettySpec = prettyJson(fullSpec)
70
+
71
+ this.log(chalk.blue(`Creating index ${name ? `'${name}' ` : ''}on collection '${collection}'...`))
72
+ this.log(chalk.dim(` Specification:\n${prettySpec}`))
73
+ if (unique) this.log(chalk.dim(` Unique index: ${unique}`))
74
+
75
+ const client = await this.db.connect()
76
+ const coll = await client.collection(collection)
77
+
78
+ // Build options
79
+ const options = {}
80
+ if (name) options.name = name
81
+ if (unique) options.unique = unique
82
+
83
+ const result = await coll.createIndex(fullSpec, options)
84
+
85
+ this.debugLogger?.info?.('Index created successfully:', result)
86
+
87
+ const response = {
88
+ collection,
89
+ indexName: result,
90
+ specification: fullSpec,
91
+ status: 'created',
92
+ namespace: this.rtNamespace,
93
+ timestamp: new Date().toISOString()
94
+ }
95
+
96
+ if (Object.keys(options).length > 0) response.options = options
97
+
98
+ this.log(chalk.green(`Index '${result}' created successfully in the '${collection}' collection`))
99
+ this.log(chalk.dim(` Specification:\n${prettySpec}`))
100
+ if (unique) this.log(chalk.dim(` Unique: ${unique}`))
101
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
102
+ this.log(chalk.dim(` Created: ${new Date().toLocaleString()}`))
103
+
104
+ return response
105
+ } catch (error) {
106
+ this.debugLogger?.error?.('Error creating index:', error)
107
+
108
+ this.log(chalk.red('Failed to create index'))
109
+ this.log(chalk.dim(` Collection: ${collection}`))
110
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
111
+
112
+ this.error(`Failed to create index on collection '${collection}': ${error.message}`)
113
+ }
114
+ }
115
+ }
116
+
117
+ Create.description = 'Create a new index on a collection in the database'
118
+
119
+ Create.examples = [
120
+ '$ aio app db index create users --spec \'{"name":1, "age":-1}\'',
121
+ '$ aio app db index create users -s \'{"name":1, "age":-1}\' --name "name_age_index"',
122
+ '$ aio app db index create students -s \'{"name":1}\' --key grade --unique',
123
+ '$ aio app db index create reviews -k sku -k rating',
124
+ '$ aio app db index create products -s \'{"name":"text", "category":"text"}\' --json',
125
+ '$ aio app db index create books -s \'{"author":1}\' -k year',
126
+ '$ aio app db idx create orders --spec \'{"customerId":1}\' --spec \'{"orderDate":-1}\' --name "customer_order_index" --unique'
127
+ ]
128
+
129
+ Create.args = {
130
+ collection: Args.string({
131
+ name: 'collection',
132
+ description: 'The name of the collection to create the index on',
133
+ required: true,
134
+ parse: input => isNonEmptyString(input, 'Collection name')
135
+ })
136
+ }
137
+
138
+ Create.flags = {
139
+ ...DBBaseCommand.flags,
140
+ spec: Flags.string({
141
+ char: 's',
142
+ helpGroup: 'Requires at least one of the index definition',
143
+ description: 'Index specification as a JSON object (e.g., \'{"name":1, "age":-1}\')',
144
+ multiple: true,
145
+ atLeastOne: ['key', 'spec'],
146
+ parse: input => asObject(input, 'Index specification')
147
+ }),
148
+ key: Flags.string({
149
+ char: 'k',
150
+ helpGroup: 'Requires at least one of the index definition',
151
+ description: 'Index key to use with default specification',
152
+ multiple: true,
153
+ atLeastOne: ['key', 'spec'],
154
+ // Note: untrimmed whitespace input is allowed for index keys
155
+ parse: input => isNonEmptyString(input, 'Index key')
156
+ }),
157
+ name: Flags.string({
158
+ char: 'n',
159
+ description: 'A name that uniquely identifies the index',
160
+ // Note: untrimmed whitespace input is allowed for index names
161
+ parse: input => isNonEmptyString(input, 'Index name')
162
+ }),
163
+ unique: Flags.boolean({
164
+ char: 'u',
165
+ description: 'Creates a unique index so that the collection will not accept insertion or update of documents where the index key value matches an existing value in the index',
166
+ default: false
167
+ })
168
+ }
169
+
170
+ Create.aliases = ['app:db:idx:create']
@@ -0,0 +1,87 @@
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 } 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 Drop extends DBBaseCommand {
20
+ async run () {
21
+ const { collection, indexName } = this.args
22
+
23
+ try {
24
+ this.log(chalk.blue(`Dropping index '${indexName}' from collection '${collection}'...`))
25
+
26
+ const client = await this.db.connect()
27
+ const coll = await client.collection(collection)
28
+
29
+ const result = await coll.dropIndex(indexName)
30
+
31
+ this.debugLogger?.info?.('Index dropped successfully:', result)
32
+
33
+ const response = {
34
+ collection,
35
+ indexName,
36
+ status: 'dropped',
37
+ namespace: this.rtNamespace,
38
+ timestamp: new Date().toISOString(),
39
+ result
40
+ }
41
+
42
+ this.log(chalk.green(`Index '${indexName}' dropped successfully`))
43
+ this.log(chalk.dim(` Details:\n${prettyJson(result)}`))
44
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
45
+ this.log(chalk.dim(` Dropped: ${new Date().toLocaleString()}`))
46
+
47
+ return response
48
+ } catch (error) {
49
+ this.debugLogger?.error?.('Error dropping index:', error)
50
+
51
+ this.log(chalk.red('Failed to drop index'))
52
+ this.log(chalk.dim(` Collection: ${collection}`))
53
+ this.log(chalk.dim(` Index: ${indexName}`))
54
+ this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
55
+ this.log(chalk.dim(` Error: ${error.message}`))
56
+
57
+ this.error(`Failed to drop index '${indexName}' from collection '${collection}': ${error.message}`)
58
+ }
59
+ }
60
+ }
61
+
62
+ Drop.description = 'Drop an index from a collection in the database'
63
+
64
+ Drop.examples = [
65
+ '$ aio app db index drop users name_age_index',
66
+ '$ aio app db index drop products category_1 --json',
67
+ '$ aio app db idx drop orders orderDate_index'
68
+ ]
69
+
70
+ Drop.args = {
71
+ collection: Args.string({
72
+ name: 'collection',
73
+ description: 'The name of the collection to drop the index from',
74
+ required: true,
75
+ parse: input => isNonEmptyString(input, 'Collection name')
76
+ }),
77
+ indexName: Args.string({
78
+ name: 'indexName',
79
+ description: 'The name of the index to drop',
80
+ required: true,
81
+ parse: input => isNonEmptyString(input, 'Index name')
82
+ })
83
+ }
84
+
85
+ Drop.flags = DBBaseCommand.flags
86
+
87
+ Drop.aliases = ['app:db:idx:drop']