@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.
- package/README.md +722 -1
- package/package.json +25 -5
- package/src/BaseCommand.js +11 -58
- package/src/DBBaseCommand.js +112 -0
- package/src/StateBaseCommand.js +87 -0
- package/src/commands/app/add/db.js +20 -0
- package/src/commands/app/db/collection/create.js +119 -0
- package/src/commands/app/db/collection/drop.js +90 -0
- package/src/commands/app/db/collection/list.js +100 -0
- package/src/commands/app/db/collection/rename.js +98 -0
- package/src/commands/app/db/collection/stats.js +94 -0
- package/src/commands/app/db/delete.js +89 -0
- package/src/commands/app/db/document/count.js +96 -0
- package/src/commands/app/db/document/delete.js +95 -0
- package/src/commands/app/db/document/find.js +133 -0
- package/src/commands/app/db/document/insert.js +147 -0
- package/src/commands/app/db/document/replace.js +122 -0
- package/src/commands/app/db/document/update.js +144 -0
- package/src/commands/app/db/index/create.js +170 -0
- package/src/commands/app/db/index/drop.js +87 -0
- package/src/commands/app/db/index/list.js +82 -0
- package/src/commands/app/db/org/stats.js +111 -0
- package/src/commands/app/db/ping.js +77 -0
- package/src/commands/app/db/provision.js +190 -0
- package/src/commands/app/db/stats.js +101 -0
- package/src/commands/app/db/status.js +159 -0
- package/src/commands/app/state/delete.js +3 -3
- package/src/commands/app/state/get.js +2 -2
- package/src/commands/app/state/list.js +3 -3
- package/src/commands/app/state/put.js +4 -4
- package/src/commands/app/state/stats.js +2 -2
- package/src/constants/db.js +32 -0
- package/src/constants/global.js +14 -0
- package/src/{constants.js → constants/state.js} +3 -0
- package/src/utils/inputValidation.js +74 -0
- package/src/utils/output.js +35 -0
- package/oclif.manifest.json +0 -316
|
@@ -0,0 +1,98 @@
|
|
|
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 RenameCollection extends DBBaseCommand {
|
|
20
|
+
async run () {
|
|
21
|
+
const { currentName, newName } = this.args
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
this.log(chalk.blue(`Renaming collection '${currentName}' to '${newName}'...`))
|
|
25
|
+
|
|
26
|
+
const client = await this.db.connect()
|
|
27
|
+
|
|
28
|
+
// Get the collection object
|
|
29
|
+
const collection = client.collection(currentName)
|
|
30
|
+
|
|
31
|
+
// Rename the collection
|
|
32
|
+
const result = await collection.renameCollection(newName)
|
|
33
|
+
|
|
34
|
+
this.debugLogger?.info?.('Collection renamed successfully:', result)
|
|
35
|
+
|
|
36
|
+
const response = {
|
|
37
|
+
currentName,
|
|
38
|
+
newName,
|
|
39
|
+
status: 'renamed',
|
|
40
|
+
namespace: this.rtNamespace,
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.log(chalk.green(`Collection '${currentName}' renamed to '${newName}' successfully`))
|
|
46
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
47
|
+
|
|
48
|
+
if (result && typeof result === 'object' && Object.keys(result).length > 0) {
|
|
49
|
+
this.log(chalk.dim(` Details:\n${prettyJson(result)}`))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.log(chalk.dim(` Renamed: ${new Date().toLocaleString()}`))
|
|
53
|
+
|
|
54
|
+
return response
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.debugLogger?.error?.('Error renaming collection:', error)
|
|
57
|
+
|
|
58
|
+
const errorMessage = `Failed to rename collection '${currentName}': ${error.message}`
|
|
59
|
+
|
|
60
|
+
this.log(chalk.red('Failed to rename collection'))
|
|
61
|
+
this.log(chalk.dim(` Current: ${currentName}`))
|
|
62
|
+
this.log(chalk.dim(` New: ${newName}`))
|
|
63
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
64
|
+
this.log(chalk.dim(` Error: ${error.message}`))
|
|
65
|
+
|
|
66
|
+
this.error(errorMessage)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
RenameCollection.description = 'Rename a collection in the database'
|
|
72
|
+
|
|
73
|
+
RenameCollection.examples = [
|
|
74
|
+
'$ aio app db collection rename users customers',
|
|
75
|
+
'$ aio app db collection rename old_products new_products --json',
|
|
76
|
+
'$ aio app db col rename inventory stock'
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
RenameCollection.args = {
|
|
80
|
+
currentName: Args.string({
|
|
81
|
+
name: 'currentName',
|
|
82
|
+
description: 'The current name of the collection to rename',
|
|
83
|
+
required: true,
|
|
84
|
+
parse: input => isNonEmptyString(input, 'Current collection name')
|
|
85
|
+
}),
|
|
86
|
+
newName: Args.string({
|
|
87
|
+
name: 'newName',
|
|
88
|
+
description: 'The new name for the collection',
|
|
89
|
+
required: true,
|
|
90
|
+
parse: input => isNonEmptyString(input, 'New collection name')
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
RenameCollection.flags = {
|
|
95
|
+
...DBBaseCommand.flags
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
RenameCollection.aliases = ['app:db:col:rename']
|
|
@@ -0,0 +1,94 @@
|
|
|
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 StatsCollection extends DBBaseCommand {
|
|
20
|
+
async run () {
|
|
21
|
+
const { collection } = this.args
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
this.log(chalk.blue(`Getting stats for collection '${collection}'...`))
|
|
25
|
+
|
|
26
|
+
const client = await this.db.connect()
|
|
27
|
+
|
|
28
|
+
// Get the collection object
|
|
29
|
+
const coll = client.collection(collection)
|
|
30
|
+
|
|
31
|
+
// Get collection-level statistics
|
|
32
|
+
const stats = await coll.stats()
|
|
33
|
+
|
|
34
|
+
this.debugLogger?.info?.('Collection stats retrieved successfully:', stats)
|
|
35
|
+
|
|
36
|
+
const response = {
|
|
37
|
+
collection,
|
|
38
|
+
stats,
|
|
39
|
+
namespace: this.rtNamespace,
|
|
40
|
+
timestamp: new Date().toISOString()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.log(chalk.green(`Stats for collection '${collection}':`))
|
|
44
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
45
|
+
|
|
46
|
+
// Display stats in a formatted way
|
|
47
|
+
Object.entries(stats).forEach(([key, value]) => {
|
|
48
|
+
if (typeof value === 'object' && value !== null) {
|
|
49
|
+
this.log(chalk.dim(` ${key}:\n${prettyJson(value)}`))
|
|
50
|
+
} else {
|
|
51
|
+
this.log(chalk.dim(` ${key}: ${value}`))
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
this.log(chalk.dim(` Retrieved: ${new Date().toLocaleString()}`))
|
|
56
|
+
|
|
57
|
+
return response
|
|
58
|
+
} catch (error) {
|
|
59
|
+
this.debugLogger?.error?.('Error getting collection stats:', error)
|
|
60
|
+
|
|
61
|
+
const errorMessage = `Failed to get stats for collection '${collection}': ${error.message}`
|
|
62
|
+
|
|
63
|
+
this.log(chalk.red('Failed to get collection stats'))
|
|
64
|
+
this.log(chalk.dim(` Collection: ${collection}`))
|
|
65
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
66
|
+
this.log(chalk.dim(` Error: ${error.message}`))
|
|
67
|
+
|
|
68
|
+
this.error(errorMessage)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
StatsCollection.description = 'Get statistics for a collection in the database'
|
|
74
|
+
|
|
75
|
+
StatsCollection.examples = [
|
|
76
|
+
'$ aio app db collection stats users',
|
|
77
|
+
'$ aio app db collection stats products --json',
|
|
78
|
+
'$ aio app db col stats inventory'
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
StatsCollection.args = {
|
|
82
|
+
collection: Args.string({
|
|
83
|
+
name: 'collection',
|
|
84
|
+
description: 'The name of the collection to get stats for',
|
|
85
|
+
required: true,
|
|
86
|
+
parse: input => isNonEmptyString(input, 'Collection name')
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
StatsCollection.flags = {
|
|
91
|
+
...DBBaseCommand.flags
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
StatsCollection.aliases = ['app:db:col:stats']
|
|
@@ -0,0 +1,89 @@
|
|
|
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 chalk from 'chalk'
|
|
15
|
+
import { Flags } from '@oclif/core'
|
|
16
|
+
import { DB_STATUS } from '../../../constants/db.js'
|
|
17
|
+
import { isProductionNamespace } from '../../../utils/inputValidation.js'
|
|
18
|
+
|
|
19
|
+
export class DeleteDb extends DBBaseCommand {
|
|
20
|
+
async run () {
|
|
21
|
+
const namespace = this.rtNamespace
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Check if the namespace is a production namespace
|
|
25
|
+
if (isProductionNamespace(namespace)) {
|
|
26
|
+
this.error('A production database may not be deleted directly. Please contact the App Builder team to have this database deleted.')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line node/no-unsupported-features/es-syntax
|
|
30
|
+
const { input } = await import('@inquirer/prompts')
|
|
31
|
+
|
|
32
|
+
if (!this.flags.force) {
|
|
33
|
+
process.stderr.write(chalk.red('❌ CAUTION, This action cannot be reverted and all stored data will be lost.') + '\n')
|
|
34
|
+
|
|
35
|
+
const res = await input({
|
|
36
|
+
message: chalk.yellow(`confirm deletion by typing: '${namespace}'`)
|
|
37
|
+
})
|
|
38
|
+
if (res !== namespace) {
|
|
39
|
+
return this.error('confirmation did not match, aborted')
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.log(chalk.blue(`Proceeding to delete the database for the namespace: '${namespace}'...`))
|
|
44
|
+
|
|
45
|
+
const deleteResult = await this.db.deleteDatabase()
|
|
46
|
+
this.debugLogger?.info?.('Delete request result:', deleteResult)
|
|
47
|
+
|
|
48
|
+
const deleteStatus = deleteResult?.status?.toUpperCase() || DB_STATUS.UNKNOWN
|
|
49
|
+
|
|
50
|
+
if (deleteStatus === DB_STATUS.DELETED) {
|
|
51
|
+
this.log(chalk.green('Database deleted successfully'))
|
|
52
|
+
this.log(chalk.dim('Check database status: aio app db status'))
|
|
53
|
+
} else {
|
|
54
|
+
this.warn(`Delete request returned status '${deleteStatus}'`)
|
|
55
|
+
this.warn('If the issue persists, please contact the App Builder team.')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = {
|
|
59
|
+
status: deleteStatus,
|
|
60
|
+
namespace,
|
|
61
|
+
timestamp: new Date().toISOString(),
|
|
62
|
+
details: deleteResult
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result
|
|
66
|
+
} catch (error) {
|
|
67
|
+
this.debugLogger?.error?.('Delete command error:', error)
|
|
68
|
+
this.error(`Database deletion failed: ${error.message}`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
DeleteDb.description = 'Delete the database for your App Builder application (non-production only)'
|
|
74
|
+
|
|
75
|
+
DeleteDb.examples = [
|
|
76
|
+
'$ aio app db delete',
|
|
77
|
+
'$ aio app db delete --force',
|
|
78
|
+
'$ aio app db delete --json'
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
DeleteDb.flags = {
|
|
82
|
+
...DBBaseCommand.flags,
|
|
83
|
+
force: Flags.boolean({
|
|
84
|
+
description: '[use with caution!] force delete, skips confirmation safety prompt',
|
|
85
|
+
default: false
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
DeleteDb.args = {}
|
|
@@ -0,0 +1,96 @@
|
|
|
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 { asObject, isNonEmptyString } from '../../../../utils/inputValidation.js'
|
|
17
|
+
|
|
18
|
+
export class Count extends DBBaseCommand {
|
|
19
|
+
async run () {
|
|
20
|
+
const { collection, query } = this.args
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
this.log(chalk.blue(`Counting documents in collection '${collection}'...`))
|
|
24
|
+
|
|
25
|
+
if (query) {
|
|
26
|
+
this.log(chalk.dim(` Using query filter: ${JSON.stringify(query)}`))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const client = await this.db.connect()
|
|
30
|
+
const coll = client.collection(collection)
|
|
31
|
+
|
|
32
|
+
// Count documents
|
|
33
|
+
const count = await coll.countDocuments(query || {}, {})
|
|
34
|
+
|
|
35
|
+
this.debugLogger?.info?.('Document count:', count)
|
|
36
|
+
|
|
37
|
+
const response = {
|
|
38
|
+
collection,
|
|
39
|
+
query: query || {},
|
|
40
|
+
count,
|
|
41
|
+
namespace: this.rtNamespace,
|
|
42
|
+
timestamp: new Date().toISOString()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.log(chalk.green(`Found ${count} document(s) in collection '${collection}'`))
|
|
46
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
47
|
+
|
|
48
|
+
if (query && Object.keys(query).length > 0) {
|
|
49
|
+
this.log(chalk.dim(' Query filter applied: Yes'))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.log(chalk.dim(` Counted: ${new Date().toLocaleString()}`))
|
|
53
|
+
|
|
54
|
+
return response
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.debugLogger?.error?.('Error counting documents:', error)
|
|
57
|
+
|
|
58
|
+
const errorMessage = `Failed to count documents in collection '${collection}': ${error.message}`
|
|
59
|
+
|
|
60
|
+
this.log(chalk.red('Failed to count documents'))
|
|
61
|
+
this.log(chalk.dim(` Collection: ${collection}`))
|
|
62
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
63
|
+
this.error(errorMessage)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Count.description = 'Count documents in a collection'
|
|
69
|
+
|
|
70
|
+
Count.examples = [
|
|
71
|
+
'$ aio app db document countDocuments users',
|
|
72
|
+
'$ aio app db document countDocuments users \'{"age": {"$gte": 21}}\'',
|
|
73
|
+
'$ aio app db document countDocuments products \'{"category": "electronics"}\' --json',
|
|
74
|
+
'$ aio app db doc count orders \'{"status": "shipped"}\''
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
Count.args = {
|
|
78
|
+
collection: Args.string({
|
|
79
|
+
name: 'collection',
|
|
80
|
+
description: 'The name of the collection',
|
|
81
|
+
required: true,
|
|
82
|
+
parse: input => isNonEmptyString(input, 'Collection name')
|
|
83
|
+
}),
|
|
84
|
+
query: Args.string({
|
|
85
|
+
name: 'query',
|
|
86
|
+
description: 'The query filter document (JSON string). If not provided, counts all documents.',
|
|
87
|
+
required: false,
|
|
88
|
+
parse: input => asObject(input, 'Query')
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Count.flags = {
|
|
93
|
+
...DBBaseCommand.flags
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
Count.aliases = ['app:db:doc:count']
|
|
@@ -0,0 +1,95 @@
|
|
|
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 { asObject, isNonEmptyString } from '../../../../utils/inputValidation.js'
|
|
17
|
+
|
|
18
|
+
export class Delete extends DBBaseCommand {
|
|
19
|
+
async run () {
|
|
20
|
+
const { collection, filter } = this.args
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
this.log(chalk.blue(`Deleting document from collection '${collection}'...`))
|
|
24
|
+
|
|
25
|
+
const client = await this.db.connect()
|
|
26
|
+
const coll = client.collection(collection)
|
|
27
|
+
|
|
28
|
+
// Delete the document
|
|
29
|
+
const result = await coll.deleteOne(filter)
|
|
30
|
+
|
|
31
|
+
this.debugLogger?.info?.('Document deleted successfully:', result)
|
|
32
|
+
|
|
33
|
+
const response = {
|
|
34
|
+
collection,
|
|
35
|
+
filter,
|
|
36
|
+
deletedCount: result.deletedCount,
|
|
37
|
+
acknowledged: result.acknowledged,
|
|
38
|
+
namespace: this.rtNamespace,
|
|
39
|
+
timestamp: new Date().toISOString(),
|
|
40
|
+
result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (result.deletedCount > 0) {
|
|
44
|
+
this.log(chalk.green(`Document deleted successfully from collection '${collection}'`))
|
|
45
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
46
|
+
} else {
|
|
47
|
+
this.log(chalk.yellow(`No document found in collection '${collection}' matching the filter`))
|
|
48
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.log(chalk.dim(` Deleted: ${new Date().toLocaleString()}`))
|
|
52
|
+
|
|
53
|
+
return response
|
|
54
|
+
} catch (error) {
|
|
55
|
+
this.debugLogger?.error?.('Error deleting document:', error)
|
|
56
|
+
|
|
57
|
+
const errorMessage = `Failed to delete document from collection '${collection}': ${error.message}`
|
|
58
|
+
|
|
59
|
+
this.log(chalk.red('Failed to delete document'))
|
|
60
|
+
this.log(chalk.dim(` Collection: ${collection}`))
|
|
61
|
+
this.log(chalk.dim(` Namespace: ${this.rtNamespace}`))
|
|
62
|
+
|
|
63
|
+
this.error(errorMessage)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Delete.description = 'Delete a single document from a collection'
|
|
69
|
+
|
|
70
|
+
Delete.examples = [
|
|
71
|
+
'$ aio app db document delete users \'{"name": "John"}\'',
|
|
72
|
+
'$ aio app db document delete products \'{"id": "123"}\' --json',
|
|
73
|
+
'$ aio app db doc delete posts \'{"status": "draft"}\''
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
Delete.args = {
|
|
77
|
+
collection: Args.string({
|
|
78
|
+
name: 'collection',
|
|
79
|
+
description: 'The name of the collection',
|
|
80
|
+
required: true,
|
|
81
|
+
parse: input => isNonEmptyString(input, 'Collection name')
|
|
82
|
+
}),
|
|
83
|
+
filter: Args.string({
|
|
84
|
+
name: 'filter',
|
|
85
|
+
description: 'The filter document (JSON string)',
|
|
86
|
+
required: true,
|
|
87
|
+
parse: input => asObject(input, 'Filter')
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Delete.flags = {
|
|
92
|
+
...DBBaseCommand.flags
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Delete.aliases = ['app:db:doc:delete']
|
|
@@ -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']
|