appwrite-utils-cli 1.7.7 → 1.7.9
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/SELECTION_DIALOGS.md +146 -0
- package/dist/cli/commands/databaseCommands.js +89 -23
- package/dist/config/services/ConfigLoaderService.d.ts +7 -0
- package/dist/config/services/ConfigLoaderService.js +47 -1
- package/dist/functions/deployments.js +5 -23
- package/dist/functions/methods.js +4 -2
- package/dist/functions/pathResolution.d.ts +37 -0
- package/dist/functions/pathResolution.js +185 -0
- package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
- package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
- package/dist/functions/templates/hono-typescript/README.md +286 -0
- package/dist/functions/templates/hono-typescript/package.json +26 -0
- package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
- package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
- package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
- package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/dist/functions/templates/typescript-node/README.md +32 -0
- package/dist/functions/templates/typescript-node/package.json +25 -0
- package/dist/functions/templates/typescript-node/src/context.ts +103 -0
- package/dist/functions/templates/typescript-node/src/index.ts +29 -0
- package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
- package/dist/functions/templates/uv/README.md +31 -0
- package/dist/functions/templates/uv/pyproject.toml +30 -0
- package/dist/functions/templates/uv/src/__init__.py +0 -0
- package/dist/functions/templates/uv/src/context.py +125 -0
- package/dist/functions/templates/uv/src/index.py +46 -0
- package/dist/main.js +175 -4
- package/dist/migrations/appwriteToX.d.ts +27 -2
- package/dist/migrations/appwriteToX.js +293 -69
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/migrations/yaml/generateImportSchemas.js +23 -8
- package/dist/shared/schemaGenerator.js +25 -12
- package/dist/shared/selectionDialogs.d.ts +214 -0
- package/dist/shared/selectionDialogs.js +540 -0
- package/dist/utils/configDiscovery.d.ts +4 -4
- package/dist/utils/configDiscovery.js +66 -30
- package/dist/utils/yamlConverter.d.ts +1 -0
- package/dist/utils/yamlConverter.js +26 -3
- package/dist/utilsController.d.ts +7 -1
- package/dist/utilsController.js +198 -17
- package/package.json +4 -2
- package/scripts/copy-templates.ts +23 -0
- package/src/cli/commands/databaseCommands.ts +133 -34
- package/src/config/services/ConfigLoaderService.ts +62 -1
- package/src/functions/deployments.ts +10 -35
- package/src/functions/methods.ts +4 -2
- package/src/functions/pathResolution.ts +227 -0
- package/src/main.ts +276 -34
- package/src/migrations/appwriteToX.ts +385 -90
- package/src/migrations/yaml/generateImportSchemas.ts +26 -8
- package/src/shared/schemaGenerator.ts +29 -12
- package/src/shared/selectionDialogs.ts +745 -0
- package/src/utils/configDiscovery.ts +83 -39
- package/src/utils/yamlConverter.ts +29 -3
- package/src/utilsController.ts +250 -22
- package/dist/utils/schemaStrings.d.ts +0 -14
- package/dist/utils/schemaStrings.js +0 -428
- package/dist/utils/sessionPreservationExample.d.ts +0 -1666
- package/dist/utils/sessionPreservationExample.js +0 -101
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { existsSync, statSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, isAbsolute } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { MessageFormatter } from '../shared/messageFormatter.js';
|
|
5
|
+
import { logger } from '../shared/logging.js';
|
|
6
|
+
/**
|
|
7
|
+
* Expands tilde (~) in paths to the user's home directory
|
|
8
|
+
* @param pathStr - Path string that may contain ~
|
|
9
|
+
* @returns Expanded path with home directory
|
|
10
|
+
*/
|
|
11
|
+
export function expandTildePath(pathStr) {
|
|
12
|
+
if (!pathStr)
|
|
13
|
+
return pathStr;
|
|
14
|
+
if (pathStr.startsWith('~/') || pathStr === '~') {
|
|
15
|
+
const expandedPath = pathStr.replace(/^~(?=$|\/|\\)/, homedir());
|
|
16
|
+
logger.debug('Expanded tilde path', { original: pathStr, expanded: expandedPath });
|
|
17
|
+
return expandedPath;
|
|
18
|
+
}
|
|
19
|
+
return pathStr;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalizes function name to standard format (lowercase, dashes instead of spaces)
|
|
23
|
+
* @param name - Function name to normalize
|
|
24
|
+
* @returns Normalized function name
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeFunctionName(name) {
|
|
27
|
+
if (!name)
|
|
28
|
+
return name;
|
|
29
|
+
const normalized = name.toLowerCase().replace(/\s+/g, '-');
|
|
30
|
+
if (normalized !== name) {
|
|
31
|
+
logger.debug('Normalized function name', { original: name, normalized });
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validates that a directory exists and contains function markers
|
|
37
|
+
* @param dirPath - Directory path to validate
|
|
38
|
+
* @returns True if directory is a valid function directory
|
|
39
|
+
*/
|
|
40
|
+
export function validateFunctionDirectory(dirPath) {
|
|
41
|
+
try {
|
|
42
|
+
// Check if directory exists
|
|
43
|
+
if (!existsSync(dirPath)) {
|
|
44
|
+
logger.debug('Directory does not exist', { dirPath });
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Check if it's actually a directory
|
|
48
|
+
const stats = statSync(dirPath);
|
|
49
|
+
if (!stats.isDirectory()) {
|
|
50
|
+
logger.debug('Path is not a directory', { dirPath });
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
// Check for function markers
|
|
54
|
+
const contents = readdirSync(dirPath);
|
|
55
|
+
const hasPackageJson = contents.includes('package.json');
|
|
56
|
+
const hasPyprojectToml = contents.includes('pyproject.toml');
|
|
57
|
+
const hasSrcDir = contents.includes('src');
|
|
58
|
+
const isValid = hasPackageJson || hasPyprojectToml || hasSrcDir;
|
|
59
|
+
logger.debug('Function directory validation', {
|
|
60
|
+
dirPath,
|
|
61
|
+
isValid,
|
|
62
|
+
markers: {
|
|
63
|
+
hasPackageJson,
|
|
64
|
+
hasPyprojectToml,
|
|
65
|
+
hasSrcDir
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return isValid;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
logger.debug('Error validating function directory', {
|
|
72
|
+
dirPath,
|
|
73
|
+
error: error instanceof Error ? error.message : String(error)
|
|
74
|
+
});
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Helper function to search for function in standard locations
|
|
80
|
+
* @param configDirPath - Directory where config file is located
|
|
81
|
+
* @param normalizedName - Normalized function name
|
|
82
|
+
* @returns First valid function directory path or undefined
|
|
83
|
+
*/
|
|
84
|
+
export function findFunctionInStandardLocations(configDirPath, normalizedName) {
|
|
85
|
+
const searchPaths = [
|
|
86
|
+
// Same directory as config
|
|
87
|
+
join(configDirPath, 'functions', normalizedName),
|
|
88
|
+
// Parent directory of config
|
|
89
|
+
join(configDirPath, '..', 'functions', normalizedName),
|
|
90
|
+
// Current working directory
|
|
91
|
+
join(process.cwd(), 'functions', normalizedName),
|
|
92
|
+
];
|
|
93
|
+
logger.debug('Searching for function in standard locations', {
|
|
94
|
+
normalizedName,
|
|
95
|
+
configDirPath,
|
|
96
|
+
searchPaths
|
|
97
|
+
});
|
|
98
|
+
for (const searchPath of searchPaths) {
|
|
99
|
+
const resolvedPath = resolve(searchPath);
|
|
100
|
+
logger.debug('Checking search path', { searchPath, resolvedPath });
|
|
101
|
+
if (validateFunctionDirectory(resolvedPath)) {
|
|
102
|
+
logger.debug('Found function in standard location', { resolvedPath });
|
|
103
|
+
return resolvedPath;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
logger.debug('Function not found in any standard location', { normalizedName });
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolves the absolute path to a function directory
|
|
111
|
+
* Handles multiple resolution strategies with proper priority
|
|
112
|
+
*
|
|
113
|
+
* @param functionName - Name of the function
|
|
114
|
+
* @param configDirPath - Directory where config file is located
|
|
115
|
+
* @param dirPath - Optional explicit dirPath from config
|
|
116
|
+
* @param explicitPath - Optional path passed as parameter (highest priority)
|
|
117
|
+
* @returns Absolute path to the function directory
|
|
118
|
+
* @throws Error if function directory cannot be found or is invalid
|
|
119
|
+
*/
|
|
120
|
+
export function resolveFunctionDirectory(functionName, configDirPath, dirPath, explicitPath) {
|
|
121
|
+
logger.debug('Resolving function directory', {
|
|
122
|
+
functionName,
|
|
123
|
+
configDirPath,
|
|
124
|
+
dirPath,
|
|
125
|
+
explicitPath
|
|
126
|
+
});
|
|
127
|
+
const normalizedName = normalizeFunctionName(functionName);
|
|
128
|
+
// Priority 1: Explicit path parameter (highest priority)
|
|
129
|
+
if (explicitPath) {
|
|
130
|
+
logger.debug('Using explicit path parameter');
|
|
131
|
+
const expandedPath = expandTildePath(explicitPath);
|
|
132
|
+
const resolvedPath = isAbsolute(expandedPath)
|
|
133
|
+
? expandedPath
|
|
134
|
+
: resolve(process.cwd(), expandedPath);
|
|
135
|
+
if (!validateFunctionDirectory(resolvedPath)) {
|
|
136
|
+
const errorMsg = `Explicit path is not a valid function directory: ${resolvedPath}`;
|
|
137
|
+
logger.error(errorMsg);
|
|
138
|
+
MessageFormatter.error('Invalid function directory', errorMsg, { prefix: 'Path Resolution' });
|
|
139
|
+
throw new Error(errorMsg);
|
|
140
|
+
}
|
|
141
|
+
logger.debug('Resolved using explicit path', { resolvedPath });
|
|
142
|
+
MessageFormatter.debug(`Resolved function directory using explicit path: ${resolvedPath}`, undefined, { prefix: 'Path Resolution' });
|
|
143
|
+
return resolvedPath;
|
|
144
|
+
}
|
|
145
|
+
// Priority 2: dirPath from config (relative to config location)
|
|
146
|
+
if (dirPath) {
|
|
147
|
+
logger.debug('Using dirPath from config');
|
|
148
|
+
const expandedPath = expandTildePath(dirPath);
|
|
149
|
+
const resolvedPath = isAbsolute(expandedPath)
|
|
150
|
+
? expandedPath
|
|
151
|
+
: resolve(configDirPath, expandedPath);
|
|
152
|
+
if (!validateFunctionDirectory(resolvedPath)) {
|
|
153
|
+
const errorMsg = `Config dirPath is not a valid function directory: ${resolvedPath}`;
|
|
154
|
+
logger.error(errorMsg);
|
|
155
|
+
MessageFormatter.error('Invalid function directory', errorMsg, { prefix: 'Path Resolution' });
|
|
156
|
+
throw new Error(errorMsg);
|
|
157
|
+
}
|
|
158
|
+
logger.debug('Resolved using config dirPath', { resolvedPath });
|
|
159
|
+
MessageFormatter.debug(`Resolved function directory using config dirPath: ${resolvedPath}`, undefined, { prefix: 'Path Resolution' });
|
|
160
|
+
return resolvedPath;
|
|
161
|
+
}
|
|
162
|
+
// Priority 3: Search standard locations
|
|
163
|
+
logger.debug('Searching standard locations for function');
|
|
164
|
+
const foundPath = findFunctionInStandardLocations(configDirPath, normalizedName);
|
|
165
|
+
if (foundPath) {
|
|
166
|
+
logger.debug('Resolved using standard location search', { foundPath });
|
|
167
|
+
MessageFormatter.debug(`Found function directory in standard location: ${foundPath}`, undefined, { prefix: 'Path Resolution' });
|
|
168
|
+
return foundPath;
|
|
169
|
+
}
|
|
170
|
+
// Priority 4: Not found - throw error
|
|
171
|
+
const searchedLocations = [
|
|
172
|
+
join(configDirPath, 'functions', normalizedName),
|
|
173
|
+
join(configDirPath, '..', 'functions', normalizedName),
|
|
174
|
+
join(process.cwd(), 'functions', normalizedName),
|
|
175
|
+
];
|
|
176
|
+
const errorMsg = `Function directory not found for '${functionName}' (normalized: '${normalizedName}'). ` +
|
|
177
|
+
`Searched locations:\n${searchedLocations.map(p => ` - ${p}`).join('\n')}`;
|
|
178
|
+
logger.error('Function directory not found', {
|
|
179
|
+
functionName,
|
|
180
|
+
normalizedName,
|
|
181
|
+
searchedLocations
|
|
182
|
+
});
|
|
183
|
+
MessageFormatter.error('Function directory not found', errorMsg, { prefix: 'Path Resolution' });
|
|
184
|
+
throw new Error(errorMsg);
|
|
185
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Count Documents in Collection Function
|
|
2
|
+
|
|
3
|
+
A utility function that accurately counts documents in an Appwrite collection, even when there are more than 5,000 documents.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Handles collections with any number of documents
|
|
7
|
+
- Supports filtering using Appwrite queries
|
|
8
|
+
- Uses efficient binary search algorithm for large collections
|
|
9
|
+
- Provides detailed logging during the counting process
|
|
10
|
+
|
|
11
|
+
## Structure
|
|
12
|
+
- `src/main.ts`: Main function implementation with counting logic
|
|
13
|
+
- `src/request.ts`: Request validation schema using Zod
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
Send a POST request with:
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"databaseId": "your-database-id",
|
|
20
|
+
"collectionId": "your-collection-id",
|
|
21
|
+
"queries": [Query.orderDesc("$createdAt"), Query.contains("name", "John")] // Or put the string array from after this, they are the same
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Response
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"success": true,
|
|
29
|
+
"count": 12345
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
1. Install dependencies: `npm|yarn|bun install`
|
|
35
|
+
2. Build: `npm|yarn|bun run build`
|
|
36
|
+
3. Deploy: Function will be built automatically during deployment
|
|
37
|
+
|
|
38
|
+
## Deployment
|
|
39
|
+
Make sure it's inside `appwriteConfig.ts` functions array, and if you want to build it FIRST, before Appwrite (using your system), you can
|
|
40
|
+
add the `predeployCommands` to the function in `appwriteConfig.ts`.
|
|
41
|
+
|
|
42
|
+
## Example Config
|
|
43
|
+
```typescript
|
|
44
|
+
{
|
|
45
|
+
$id: 'count-docs',
|
|
46
|
+
name: 'Count Documents',
|
|
47
|
+
runtime: 'node-18.0',
|
|
48
|
+
path: 'functions/count-docs',
|
|
49
|
+
entrypoint: './main.js',
|
|
50
|
+
execute: ['any'],
|
|
51
|
+
predeployCommands: ['npm install', 'npm run build'],
|
|
52
|
+
deployDir: './dist'
|
|
53
|
+
}
|
|
54
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{functionName}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Appwrite function to count documents in a collection",
|
|
5
|
+
"main": "src/main.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/main.js",
|
|
10
|
+
"dev": "tsx src/main.ts"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"node-appwrite": "^13.0.0",
|
|
14
|
+
"appwrite-utils": "latest",
|
|
15
|
+
"zod": "^3.23.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^20.0.0",
|
|
19
|
+
"typescript": "^5.0.0",
|
|
20
|
+
"tsx": "^4.0.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Client, Databases, Query } from "node-appwrite";
|
|
2
|
+
import { AppwriteRequest, type AppwriteResponse } from "appwrite-utils";
|
|
3
|
+
import { requestSchema } from "./request.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Main function to handle document counting requests.
|
|
7
|
+
* @param {Object} params - The function parameters.
|
|
8
|
+
* @param {Object} params.req - The request object.
|
|
9
|
+
* @param {Object} params.res - The response object.
|
|
10
|
+
* @param {Function} params.log - Logging function.
|
|
11
|
+
* @param {Function} params.error - Error logging function.
|
|
12
|
+
* @returns {Promise<Object>} JSON response with count or error message.
|
|
13
|
+
*/
|
|
14
|
+
export default async ({
|
|
15
|
+
req,
|
|
16
|
+
res,
|
|
17
|
+
log,
|
|
18
|
+
error,
|
|
19
|
+
}: {
|
|
20
|
+
req: AppwriteRequest;
|
|
21
|
+
res: AppwriteResponse;
|
|
22
|
+
log: (message: string) => void;
|
|
23
|
+
error: (message: string) => void;
|
|
24
|
+
}) => {
|
|
25
|
+
// Initialize Appwrite client
|
|
26
|
+
const client = new Client()
|
|
27
|
+
.setEndpoint(process.env["APPWRITE_FUNCTION_ENDPOINT"]!)
|
|
28
|
+
.setProject(process.env["APPWRITE_FUNCTION_PROJECT_ID"]!)
|
|
29
|
+
.setKey(req.headers["x-appwrite-key"] || "");
|
|
30
|
+
|
|
31
|
+
const databases = new Databases(client);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (req.method === "POST") {
|
|
35
|
+
// Parse request body
|
|
36
|
+
const body = requestSchema.safeParse(
|
|
37
|
+
typeof req.body === "string" ? JSON.parse(req.body) : req.body
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (!body.success) {
|
|
41
|
+
return res.json({ success: false, error: body.error }, 400);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { databaseId, collectionId, queries = [] } = body.data;
|
|
45
|
+
|
|
46
|
+
log(`Queries: ${JSON.stringify(queries)}`);
|
|
47
|
+
|
|
48
|
+
// Count documents in the specified collection
|
|
49
|
+
const count = await countAllDocuments(
|
|
50
|
+
log,
|
|
51
|
+
databases,
|
|
52
|
+
databaseId,
|
|
53
|
+
collectionId,
|
|
54
|
+
queries
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Return successful response with document count
|
|
58
|
+
return res.json({
|
|
59
|
+
success: true,
|
|
60
|
+
count: count,
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
// Return error for non-POST requests
|
|
64
|
+
return res.json({ success: false, error: "Method not allowed" }, 405);
|
|
65
|
+
}
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// Log and return any errors
|
|
68
|
+
error(`Error processing request: ${err}`);
|
|
69
|
+
return res.json({ success: false, error: (err as Error).message }, 500);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Counts all documents in a collection, handling large collections efficiently.
|
|
75
|
+
* @param {Function} log - Logging function.
|
|
76
|
+
* @param {Databases} databases - Appwrite Databases instance.
|
|
77
|
+
* @param {string} databaseId - ID of the database.
|
|
78
|
+
* @param {string} collectionId - ID of the collection.
|
|
79
|
+
* @param {string[]} queries - Array of query strings to filter documents.
|
|
80
|
+
* @param {number} batchSize - Size of batches for processing (default: 1000).
|
|
81
|
+
* @returns {Promise<number>} Total count of documents.
|
|
82
|
+
*/
|
|
83
|
+
async function countAllDocuments(
|
|
84
|
+
log: any,
|
|
85
|
+
databases: Databases,
|
|
86
|
+
databaseId: string,
|
|
87
|
+
collectionId: string,
|
|
88
|
+
queries: string[] = [],
|
|
89
|
+
batchSize: number = 1000
|
|
90
|
+
): Promise<number> {
|
|
91
|
+
// Filter out limit and offset queries
|
|
92
|
+
const initialQueries = queries.filter(
|
|
93
|
+
(q) => !(q.includes("limit") || q.includes("offset"))
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// Initial query to check if total is less than 5000
|
|
97
|
+
const initialResponse = await databases.listDocuments(
|
|
98
|
+
databaseId,
|
|
99
|
+
collectionId,
|
|
100
|
+
[...initialQueries, Query.limit(1)]
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (initialResponse.total < 5000) {
|
|
104
|
+
log(`Total documents (from initial response): ${initialResponse.total}`);
|
|
105
|
+
return initialResponse.total;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If total is 5000 or more, we need to count manually
|
|
109
|
+
let bound = 5000;
|
|
110
|
+
|
|
111
|
+
// Exponential search to find an upper bound
|
|
112
|
+
while (true) {
|
|
113
|
+
log(`Querying for offset ${bound}`);
|
|
114
|
+
try {
|
|
115
|
+
const response = await databases.listDocuments(databaseId, collectionId, [
|
|
116
|
+
...initialQueries,
|
|
117
|
+
Query.limit(1),
|
|
118
|
+
Query.offset(bound),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
if (response.documents.length === 0) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
bound *= 2;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Binary search to find the exact count
|
|
131
|
+
let low = Math.floor(bound / 2);
|
|
132
|
+
let high = bound;
|
|
133
|
+
let lastValidCount = low;
|
|
134
|
+
|
|
135
|
+
while (low <= high) {
|
|
136
|
+
const mid = Math.floor((low + high) / 2);
|
|
137
|
+
log(`Binary search: Querying for offset ${mid}`);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const response = await databases.listDocuments(databaseId, collectionId, [
|
|
141
|
+
...initialQueries,
|
|
142
|
+
Query.limit(1),
|
|
143
|
+
Query.offset(mid),
|
|
144
|
+
]);
|
|
145
|
+
|
|
146
|
+
if (response.documents.length > 0) {
|
|
147
|
+
lastValidCount = mid + 1; // +1 because offset is 0-based
|
|
148
|
+
low = mid + 1;
|
|
149
|
+
} else {
|
|
150
|
+
high = mid - 1;
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
high = mid - 1;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
log(`Total documents: ${lastValidCount}`);
|
|
158
|
+
return lastValidCount;
|
|
159
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"checkJs": false,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"rootDir": "./src",
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"strictNullChecks": true,
|
|
15
|
+
"strictFunctionTypes": true,
|
|
16
|
+
"noImplicitThis": true,
|
|
17
|
+
"noImplicitReturns": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"moduleDetection": "force",
|
|
20
|
+
"resolveJsonModule": true,
|
|
21
|
+
"isolatedModules": true,
|
|
22
|
+
"verbatimModuleSyntax": false,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"forceConsistentCasingInFileNames": true
|
|
25
|
+
},
|
|
26
|
+
"include": ["src/**/*"],
|
|
27
|
+
"exclude": ["node_modules", "dist"]
|
|
28
|
+
}
|