@onchaindb/sdk 0.4.0 → 0.4.2
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/.DS_Store +0 -0
- package/.claude/settings.local.json +8 -0
- package/.gitignore +5 -0
- package/.idea/.gitignore +5 -0
- package/.idea/compiler.xml +6 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/jsLinters/eslint.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +7 -0
- package/.idea/sdk.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/workspace.xml +257 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -3
- package/dist/client.js.map +1 -1
- package/dist/database.d.ts +0 -20
- package/dist/database.d.ts.map +1 -1
- package/dist/database.js +0 -40
- package/dist/database.js.map +1 -1
- package/dist/query-sdk/tests/setup.d.ts +16 -0
- package/dist/query-sdk/tests/setup.d.ts.map +1 -0
- package/dist/query-sdk/tests/setup.js +49 -0
- package/dist/query-sdk/tests/setup.js.map +1 -0
- package/examples/basic-usage.ts +136 -0
- package/examples/blob-upload-example.ts +140 -0
- package/examples/collection-schema-example.ts +304 -0
- package/examples/server-side-joins.ts +201 -0
- package/examples/tweet-self-joins-example.ts +352 -0
- package/package-lock.json +3823 -0
- package/package.json +1 -1
- package/skills.md +1096 -0
- package/src/.env +1 -0
- package/src/batch.d.ts +121 -0
- package/src/batch.js +205 -0
- package/src/batch.ts +257 -0
- package/src/client.ts +1856 -0
- package/src/database.d.ts +268 -0
- package/src/database.js +294 -0
- package/src/database.ts +695 -0
- package/src/index.d.ts +160 -0
- package/src/index.js +186 -0
- package/src/index.ts +253 -0
- package/src/query-sdk/ConditionBuilder.ts +103 -0
- package/src/query-sdk/FieldConditionBuilder.ts +2 -0
- package/src/query-sdk/NestedBuilders.ts +186 -0
- package/src/query-sdk/OnChainDB.ts +294 -0
- package/src/query-sdk/QueryBuilder.ts +1191 -0
- package/src/query-sdk/QueryResult.ts +375 -0
- package/src/query-sdk/README.md +866 -0
- package/src/query-sdk/SelectionBuilder.ts +94 -0
- package/src/query-sdk/adapters/HttpClientAdapter.ts +249 -0
- package/src/query-sdk/dist/ConditionBuilder.d.ts +22 -0
- package/src/query-sdk/dist/ConditionBuilder.js +90 -0
- package/src/query-sdk/dist/FieldConditionBuilder.d.ts +1 -0
- package/src/query-sdk/dist/FieldConditionBuilder.js +6 -0
- package/src/query-sdk/dist/NestedBuilders.d.ts +43 -0
- package/src/query-sdk/dist/NestedBuilders.js +144 -0
- package/src/query-sdk/dist/OnChainDB.d.ts +19 -0
- package/src/query-sdk/dist/OnChainDB.js +123 -0
- package/src/query-sdk/dist/QueryBuilder.d.ts +70 -0
- package/src/query-sdk/dist/QueryBuilder.js +295 -0
- package/src/query-sdk/dist/QueryResult.d.ts +52 -0
- package/src/query-sdk/dist/QueryResult.js +293 -0
- package/src/query-sdk/dist/SelectionBuilder.d.ts +20 -0
- package/src/query-sdk/dist/SelectionBuilder.js +80 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +27 -0
- package/src/query-sdk/dist/adapters/HttpClientAdapter.js +170 -0
- package/src/query-sdk/dist/index.d.ts +36 -0
- package/src/query-sdk/dist/index.js +27 -0
- package/src/query-sdk/dist/operators.d.ts +56 -0
- package/src/query-sdk/dist/operators.js +289 -0
- package/src/query-sdk/dist/tests/setup.d.ts +15 -0
- package/src/query-sdk/dist/tests/setup.js +46 -0
- package/src/query-sdk/index.ts +59 -0
- package/src/query-sdk/jest.config.js +25 -0
- package/src/query-sdk/operators.ts +335 -0
- package/src/query-sdk/package.json +46 -0
- package/src/query-sdk/tests/FieldConditionBuilder.test.ts +84 -0
- package/src/query-sdk/tests/LogicalOperator.test.ts +85 -0
- package/src/query-sdk/tests/NestedBuilders.test.ts +321 -0
- package/src/query-sdk/tests/QueryBuilder.test.ts +348 -0
- package/src/query-sdk/tests/QueryResult.test.ts +464 -0
- package/src/query-sdk/tests/aggregations.test.ts +653 -0
- package/src/query-sdk/tests/comprehensive.test.ts +279 -0
- package/src/query-sdk/tests/integration.test.ts +608 -0
- package/src/query-sdk/tests/operators.test.ts +327 -0
- package/src/query-sdk/tests/setup.ts +59 -0
- package/src/query-sdk/tests/unit.test.ts +794 -0
- package/src/query-sdk/tsconfig.json +26 -0
- package/src/query-sdk/yarn.lock +3092 -0
- package/src/types.d.ts +131 -0
- package/src/types.js +46 -0
- package/src/types.ts +534 -0
- package/src/x402/index.ts +12 -0
- package/src/x402/types.ts +250 -0
- package/src/x402/utils.ts +332 -0
- package/tsconfig.json +20 -0
- package/yarn.lock +2309 -0
package/src/database.ts
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
// Database Management Client for OnChainDB SDK
|
|
2
|
+
// Provides comprehensive collection and index management capabilities
|
|
3
|
+
|
|
4
|
+
import { AxiosInstance, AxiosError } from 'axios';
|
|
5
|
+
import { OnChainDBError } from './types';
|
|
6
|
+
import { buildPaymentPayload, encodePaymentHeader } from './x402/utils';
|
|
7
|
+
import type { X402PaymentRequirement } from './x402/types';
|
|
8
|
+
|
|
9
|
+
// Database Management Types
|
|
10
|
+
export interface Collection {
|
|
11
|
+
name: string;
|
|
12
|
+
schema?: CollectionSchema;
|
|
13
|
+
indexes?: Index[];
|
|
14
|
+
metadata?: CollectionMetadata;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CollectionSchema {
|
|
18
|
+
fields: { [fieldName: string]: FieldDefinition };
|
|
19
|
+
required?: string[];
|
|
20
|
+
relationships?: Relationship[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FieldDefinition {
|
|
24
|
+
type: 'string' | 'number' | 'boolean' | 'date' | 'object' | 'array';
|
|
25
|
+
index?: boolean;
|
|
26
|
+
unique?: boolean;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
default?: any;
|
|
29
|
+
validation?: FieldValidation;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FieldValidation {
|
|
33
|
+
min?: number;
|
|
34
|
+
max?: number;
|
|
35
|
+
pattern?: string;
|
|
36
|
+
enum?: any[];
|
|
37
|
+
custom?: (value: any) => boolean | string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Relationship {
|
|
41
|
+
type: 'one-to-one' | 'one-to-many' | 'many-to-many';
|
|
42
|
+
collection: string;
|
|
43
|
+
localField: string;
|
|
44
|
+
foreignField: string;
|
|
45
|
+
cascade?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface Index {
|
|
49
|
+
name: string;
|
|
50
|
+
collection: string;
|
|
51
|
+
field_name: string;
|
|
52
|
+
index_type: 'btree' | 'hash' | 'fulltext' | 'composite' | 'price';
|
|
53
|
+
fields?: string[]; // For composite indexes
|
|
54
|
+
options?: IndexOptions;
|
|
55
|
+
price_config?: PriceConfig; // For price-based payment splits
|
|
56
|
+
status?: IndexStatus;
|
|
57
|
+
statistics?: IndexStatistics;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface PriceConfig {
|
|
61
|
+
app_owner_percentage: number; // e.g., 0.80 for 80%
|
|
62
|
+
platform_percentage: number; // e.g., 0.20 for 20%
|
|
63
|
+
app_owner_address: string; // Wallet to receive app owner share
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface IndexOptions {
|
|
67
|
+
unique?: boolean;
|
|
68
|
+
sparse?: boolean;
|
|
69
|
+
background?: boolean;
|
|
70
|
+
partialFilter?: any;
|
|
71
|
+
textIndexVersion?: number;
|
|
72
|
+
weights?: { [field: string]: number };
|
|
73
|
+
defaultLanguage?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface IndexStatus {
|
|
77
|
+
building: boolean;
|
|
78
|
+
progress?: number;
|
|
79
|
+
error?: string;
|
|
80
|
+
created_at?: string;
|
|
81
|
+
completed_at?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface IndexStatistics {
|
|
85
|
+
size: number;
|
|
86
|
+
usage_count: number;
|
|
87
|
+
last_used?: string;
|
|
88
|
+
efficiency_score?: number;
|
|
89
|
+
memory_usage?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CollectionMetadata {
|
|
93
|
+
created_at: string;
|
|
94
|
+
updated_at: string;
|
|
95
|
+
document_count: number;
|
|
96
|
+
storage_size: number;
|
|
97
|
+
avg_document_size: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface DatabaseStats {
|
|
101
|
+
total_collections: number;
|
|
102
|
+
total_indexes: number;
|
|
103
|
+
total_documents: number;
|
|
104
|
+
storage_size: number;
|
|
105
|
+
index_size: number;
|
|
106
|
+
active_connections: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface QueryPlan {
|
|
110
|
+
query: any;
|
|
111
|
+
indexes_used: string[];
|
|
112
|
+
estimated_cost: number;
|
|
113
|
+
execution_time_estimate: number;
|
|
114
|
+
optimization_hints: string[];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface BatchOperation {
|
|
118
|
+
operation: 'create' | 'update' | 'delete';
|
|
119
|
+
collection: string;
|
|
120
|
+
data: any;
|
|
121
|
+
conditions?: any;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface BatchResult {
|
|
125
|
+
success: boolean;
|
|
126
|
+
operation: string;
|
|
127
|
+
collection: string;
|
|
128
|
+
result?: any;
|
|
129
|
+
error?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Materialized View Types
|
|
133
|
+
export interface MaterializedView {
|
|
134
|
+
name: string;
|
|
135
|
+
source_collections: string[];
|
|
136
|
+
query: any;
|
|
137
|
+
created_at?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface ViewInfo {
|
|
141
|
+
name: string;
|
|
142
|
+
source_collections: string[];
|
|
143
|
+
created_at: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface ListViewsResponse {
|
|
147
|
+
views: ViewInfo[];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Comprehensive database management client for OnChainDB
|
|
152
|
+
*
|
|
153
|
+
* Provides full CRUD operations for collections, indexes, and schemas
|
|
154
|
+
* with advanced features like query planning, batch operations, and statistics
|
|
155
|
+
*/
|
|
156
|
+
export class DatabaseManager {
|
|
157
|
+
constructor(
|
|
158
|
+
private httpClient: AxiosInstance,
|
|
159
|
+
private serverUrl: string,
|
|
160
|
+
private appId: string,
|
|
161
|
+
private apiKey?: string
|
|
162
|
+
) {}
|
|
163
|
+
|
|
164
|
+
// ==================== Index Management ====================
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Create a new index on a collection with x402 payment support
|
|
168
|
+
*
|
|
169
|
+
* Index creation is currently FREE but this method handles 402 responses
|
|
170
|
+
* in case payment is required in the future.
|
|
171
|
+
*
|
|
172
|
+
* @param indexDefinition - Index definition without status/statistics
|
|
173
|
+
* @param paymentOptions - Optional payment configuration (for future use)
|
|
174
|
+
* @returns Created index
|
|
175
|
+
*/
|
|
176
|
+
async createIndex(
|
|
177
|
+
indexDefinition: Omit<Index, 'status' | 'statistics'>,
|
|
178
|
+
paymentOptions?: {
|
|
179
|
+
userWallet?: any;
|
|
180
|
+
}
|
|
181
|
+
): Promise<Index> {
|
|
182
|
+
const endpoint = `/api/apps/${this.appId}/indexes`;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// First attempt - server will return 402 if payment required
|
|
186
|
+
const response = await this.httpClient.post(
|
|
187
|
+
endpoint,
|
|
188
|
+
indexDefinition,
|
|
189
|
+
{ headers: this.getHeaders() }
|
|
190
|
+
);
|
|
191
|
+
return response.data;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const axiosError = error as AxiosError;
|
|
194
|
+
|
|
195
|
+
// Handle 402 Payment Required (for future use when payment is enabled)
|
|
196
|
+
if (axiosError.response?.status === 402) {
|
|
197
|
+
if (!paymentOptions?.userWallet) {
|
|
198
|
+
throw new OnChainDBError(
|
|
199
|
+
'Payment required but no wallet provided. Pass userWallet in paymentOptions.',
|
|
200
|
+
'PAYMENT_REQUIRED'
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log('[x402] Payment required for index creation');
|
|
205
|
+
|
|
206
|
+
// Parse payment requirement from response
|
|
207
|
+
const paymentData = axiosError.response.data as any;
|
|
208
|
+
const accepts = paymentData?.accepts;
|
|
209
|
+
|
|
210
|
+
if (!accepts || accepts.length === 0) {
|
|
211
|
+
throw new OnChainDBError('Invalid 402 response: no payment options', 'PAYMENT_ERROR');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Use first accepted payment option (native TIA)
|
|
215
|
+
const requirement: X402PaymentRequirement = accepts[0];
|
|
216
|
+
const amountUtia = parseInt(requirement.maxAmountRequired || '0', 10);
|
|
217
|
+
const brokerAddress = requirement.payTo;
|
|
218
|
+
|
|
219
|
+
console.log(`[x402] Index creation cost: ${amountUtia} utia to ${brokerAddress}`);
|
|
220
|
+
|
|
221
|
+
// Execute payment via wallet
|
|
222
|
+
const paymentResult = await paymentOptions.userWallet.signAndBroadcast(
|
|
223
|
+
brokerAddress,
|
|
224
|
+
`${amountUtia}utia`,
|
|
225
|
+
`OnChainDB index creation - ${indexDefinition.field_name}`
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (!paymentResult.success) {
|
|
229
|
+
throw new OnChainDBError(`Payment failed: ${paymentResult.error}`, 'PAYMENT_ERROR');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log(`[x402] Payment successful: ${paymentResult.txHash}`);
|
|
233
|
+
|
|
234
|
+
// Build X-PAYMENT header and retry
|
|
235
|
+
const x402Payload = buildPaymentPayload(requirement, {
|
|
236
|
+
txHash: paymentResult.txHash,
|
|
237
|
+
network: requirement.network,
|
|
238
|
+
sender: paymentResult.sender || '',
|
|
239
|
+
chainType: 'cosmos',
|
|
240
|
+
paymentMethod: 'native'
|
|
241
|
+
});
|
|
242
|
+
const encodedPayment = encodePaymentHeader(x402Payload);
|
|
243
|
+
|
|
244
|
+
const retryResponse = await this.httpClient.post(
|
|
245
|
+
endpoint,
|
|
246
|
+
indexDefinition,
|
|
247
|
+
{
|
|
248
|
+
headers: {
|
|
249
|
+
...this.getHeaders(),
|
|
250
|
+
'X-PAYMENT': encodedPayment
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
return retryResponse.data;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Re-throw other errors
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Create multiple indexes in a batch operation
|
|
265
|
+
*/
|
|
266
|
+
async createIndexes(
|
|
267
|
+
collection: string,
|
|
268
|
+
indexes: Omit<Index, 'collection' | 'status' | 'statistics'>[]
|
|
269
|
+
): Promise<Index[]> {
|
|
270
|
+
// Since batch endpoint doesn't exist, create indexes individually
|
|
271
|
+
const results: Index[] = [];
|
|
272
|
+
|
|
273
|
+
for (const indexDef of indexes) {
|
|
274
|
+
const result = await this.createIndex({ ...indexDef, collection });
|
|
275
|
+
results.push(result);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return results;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* List all indexes for a collection or entire application
|
|
283
|
+
*/
|
|
284
|
+
async listIndexes(collection?: string, includeStats?: boolean): Promise<Index[]> {
|
|
285
|
+
const endpoint = collection
|
|
286
|
+
? `/api/apps/${this.appId}/collections/${collection}/indexes`
|
|
287
|
+
: `/api/apps/${this.appId}/indexes`;
|
|
288
|
+
|
|
289
|
+
const params = includeStats ? { include_stats: true } : {};
|
|
290
|
+
|
|
291
|
+
const response = await this.httpClient.get(endpoint, {
|
|
292
|
+
params,
|
|
293
|
+
headers: this.getHeaders()
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return response.data;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get detailed information about a specific index
|
|
301
|
+
*/
|
|
302
|
+
async getIndex(collection: string, indexName: string): Promise<Index> {
|
|
303
|
+
const response = await this.httpClient.get(
|
|
304
|
+
`/api/apps/${this.appId}/collections/${collection}/indexes/${indexName}`,
|
|
305
|
+
{ headers: this.getHeaders() }
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
return response.data;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check the status of index building operations
|
|
313
|
+
*/
|
|
314
|
+
async getIndexStatus(collection: string, indexName: string): Promise<IndexStatus> {
|
|
315
|
+
const response = await this.httpClient.get(
|
|
316
|
+
`/api/apps/${this.appId}/collections/${collection}/indexes/${indexName}/status`,
|
|
317
|
+
{ headers: this.getHeaders() }
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
return response.data;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Get index performance statistics
|
|
325
|
+
*/
|
|
326
|
+
async getIndexStatistics(collection: string, indexName: string): Promise<IndexStatistics> {
|
|
327
|
+
const response = await this.httpClient.get(
|
|
328
|
+
`/api/apps/${this.appId}/collections/${collection}/indexes/${indexName}/statistics`,
|
|
329
|
+
{ headers: this.getHeaders() }
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return response.data;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Rebuild an existing index
|
|
337
|
+
*/
|
|
338
|
+
async rebuildIndex(
|
|
339
|
+
collection: string,
|
|
340
|
+
indexName: string,
|
|
341
|
+
options?: { background?: boolean }
|
|
342
|
+
): Promise<{ success: boolean; message: string }> {
|
|
343
|
+
const response = await this.httpClient.post(
|
|
344
|
+
`/api/apps/${this.appId}/collections/${collection}/indexes/${indexName}/rebuild`,
|
|
345
|
+
options || {},
|
|
346
|
+
{ headers: this.getHeaders() }
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
return response.data;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Drop/delete an index
|
|
354
|
+
*/
|
|
355
|
+
async dropIndex(collection: string, indexName: string): Promise<{ success: boolean; message: string }> {
|
|
356
|
+
const response = await this.httpClient.delete(
|
|
357
|
+
`/api/apps/${this.appId}/collections/${collection}/indexes/${indexName}`,
|
|
358
|
+
{ headers: this.getHeaders() }
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
return response.data;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Create a Price index for on-chain commerce
|
|
366
|
+
*
|
|
367
|
+
* Price indexes enable automatic payment splits when writing to collections.
|
|
368
|
+
* Perfect for order/purchase collections where you want to charge based on item price.
|
|
369
|
+
*
|
|
370
|
+
* @param collection - Collection name (e.g., 'orders')
|
|
371
|
+
* @param fieldName - Price field name (e.g., 'total_price')
|
|
372
|
+
* @param config - Optional payment split configuration
|
|
373
|
+
* @returns Created index
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* ```typescript
|
|
377
|
+
* // Create price index on orders collection
|
|
378
|
+
* await db.createPriceIndex('orders', 'total_price', {
|
|
379
|
+
* app_owner_percentage: 0.80, // 80% to merchant
|
|
380
|
+
* platform_percentage: 0.20, // 20% to platform
|
|
381
|
+
* app_owner_address: 'celestia1abc...'
|
|
382
|
+
* });
|
|
383
|
+
*
|
|
384
|
+
* // Now when writing orders, price field triggers automatic split:
|
|
385
|
+
* await client.store({
|
|
386
|
+
* collection: 'orders',
|
|
387
|
+
* data: {
|
|
388
|
+
* order_id: '123',
|
|
389
|
+
* total_price: 100 // 80 TIA to merchant, 20 TIA to platform
|
|
390
|
+
* }
|
|
391
|
+
* });
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
async createPriceIndex(
|
|
395
|
+
collection: string,
|
|
396
|
+
fieldName: string,
|
|
397
|
+
config?: Partial<PriceConfig>
|
|
398
|
+
): Promise<Index> {
|
|
399
|
+
const priceConfig: PriceConfig = {
|
|
400
|
+
app_owner_percentage: config?.app_owner_percentage ?? 0.80,
|
|
401
|
+
platform_percentage: config?.platform_percentage ?? 0.20,
|
|
402
|
+
app_owner_address: config?.app_owner_address ?? ''
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
return this.createIndex({
|
|
406
|
+
name: `${collection}_${fieldName}_price_index`,
|
|
407
|
+
collection,
|
|
408
|
+
field_name: fieldName,
|
|
409
|
+
index_type: 'price',
|
|
410
|
+
price_config: priceConfig
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ==================== Schema Management ====================
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Validate data against collection schema
|
|
418
|
+
*/
|
|
419
|
+
async validateSchema(collection: string, data: any): Promise<{ valid: boolean; errors?: string[] }> {
|
|
420
|
+
const response = await this.httpClient.post(
|
|
421
|
+
`/api/apps/${this.appId}/collections/${collection}/validate`,
|
|
422
|
+
{ data },
|
|
423
|
+
{ headers: this.getHeaders() }
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
return response.data;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Migrate collection data to new schema
|
|
431
|
+
*/
|
|
432
|
+
async migrateSchema(
|
|
433
|
+
collection: string,
|
|
434
|
+
newSchema: CollectionSchema,
|
|
435
|
+
options?: { dryRun?: boolean; batchSize?: number }
|
|
436
|
+
): Promise<{ success: boolean; migrated: number; errors?: any[] }> {
|
|
437
|
+
const payload = { schema: newSchema, ...options };
|
|
438
|
+
|
|
439
|
+
const response = await this.httpClient.post(
|
|
440
|
+
`/api/apps/${this.appId}/collections/${collection}/migrate`,
|
|
441
|
+
payload,
|
|
442
|
+
{ headers: this.getHeaders() }
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
return response.data;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ==================== Query Planning and Optimization ====================
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Explain query execution plan
|
|
452
|
+
*/
|
|
453
|
+
async explainQuery(collection: string, query: any): Promise<QueryPlan> {
|
|
454
|
+
const response = await this.httpClient.post(
|
|
455
|
+
`/api/apps/${this.appId}/collections/${collection}/explain`,
|
|
456
|
+
{ query },
|
|
457
|
+
{ headers: this.getHeaders() }
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
return response.data;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Get optimization suggestions for query performance
|
|
465
|
+
*/
|
|
466
|
+
async getOptimizationSuggestions(collection: string): Promise<{
|
|
467
|
+
missing_indexes: string[];
|
|
468
|
+
unused_indexes: string[];
|
|
469
|
+
slow_queries: any[];
|
|
470
|
+
recommendations: string[];
|
|
471
|
+
}> {
|
|
472
|
+
const response = await this.httpClient.get(
|
|
473
|
+
`/api/apps/${this.appId}/collections/${collection}/optimize`,
|
|
474
|
+
{ headers: this.getHeaders() }
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
return response.data;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// ==================== Batch Operations ====================
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Execute multiple database operations in a single transaction
|
|
484
|
+
*/
|
|
485
|
+
async executeBatch(operations: BatchOperation[]): Promise<BatchResult[]> {
|
|
486
|
+
const response = await this.httpClient.post(
|
|
487
|
+
`/api/apps/${this.appId}/batch`,
|
|
488
|
+
{ operations },
|
|
489
|
+
{ headers: this.getHeaders() }
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
return response.data;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ==================== Materialized Views Management ====================
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Create a materialized view
|
|
499
|
+
*
|
|
500
|
+
* Materialized views are pre-computed query results that update automatically
|
|
501
|
+
* when source data changes. Great for complex aggregations and joins.
|
|
502
|
+
*
|
|
503
|
+
* @param name - Unique name for the view
|
|
504
|
+
* @param sourceCollections - Collections this view depends on
|
|
505
|
+
* @param query - Query definition for the view
|
|
506
|
+
* @returns Created view definition
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* ```typescript
|
|
510
|
+
* // Create a view for top-selling products
|
|
511
|
+
* await db.createView('topSellers', ['products', 'orders'], {
|
|
512
|
+
* select: ['id', 'name', 'price', 'salesCount'],
|
|
513
|
+
* where: { status: 'active' },
|
|
514
|
+
* orderBy: { salesCount: 'desc' },
|
|
515
|
+
* limit: 100
|
|
516
|
+
* });
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
async createView(
|
|
520
|
+
name: string,
|
|
521
|
+
sourceCollections: string[],
|
|
522
|
+
query: any
|
|
523
|
+
): Promise<MaterializedView> {
|
|
524
|
+
const payload = {
|
|
525
|
+
name,
|
|
526
|
+
source_collections: sourceCollections,
|
|
527
|
+
query
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const response = await this.httpClient.post(
|
|
531
|
+
`/apps/${this.appId}/views`,
|
|
532
|
+
payload,
|
|
533
|
+
{ headers: this.getHeaders() }
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
return response.data;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* List all materialized views for the app
|
|
541
|
+
*/
|
|
542
|
+
async listViews(): Promise<ViewInfo[]> {
|
|
543
|
+
const response = await this.httpClient.get(
|
|
544
|
+
`/apps/${this.appId}/views`,
|
|
545
|
+
{ headers: this.getHeaders() }
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
return response.data.views || response.data;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Get a specific materialized view
|
|
553
|
+
*/
|
|
554
|
+
async getView(name: string): Promise<MaterializedView> {
|
|
555
|
+
const response = await this.httpClient.get(
|
|
556
|
+
`/apps/${this.appId}/views/${name}`,
|
|
557
|
+
{ headers: this.getHeaders() }
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
return response.data;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Delete a materialized view
|
|
565
|
+
*/
|
|
566
|
+
async deleteView(name: string): Promise<{ success: boolean; message: string }> {
|
|
567
|
+
const response = await this.httpClient.delete(
|
|
568
|
+
`/apps/${this.appId}/views/${name}`,
|
|
569
|
+
{ headers: this.getHeaders() }
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
return response.data;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Refresh/rebuild a materialized view
|
|
577
|
+
*/
|
|
578
|
+
async refreshView(name: string): Promise<{ success: boolean; message: string }> {
|
|
579
|
+
const response = await this.httpClient.post(
|
|
580
|
+
`/apps/${this.appId}/views/${name}/refresh`,
|
|
581
|
+
{},
|
|
582
|
+
{ headers: this.getHeaders() }
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
return response.data;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// ==================== Statistics and Monitoring ====================
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Get overall database statistics
|
|
592
|
+
*/
|
|
593
|
+
async getDatabaseStats(): Promise<DatabaseStats> {
|
|
594
|
+
const response = await this.httpClient.get(
|
|
595
|
+
`/api/apps/${this.appId}/stats`,
|
|
596
|
+
{ headers: this.getHeaders() }
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
return response.data;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Get collection-specific statistics
|
|
604
|
+
*/
|
|
605
|
+
async getCollectionStats(collection: string): Promise<CollectionMetadata> {
|
|
606
|
+
const response = await this.httpClient.get(
|
|
607
|
+
`/api/apps/${this.appId}/collections/${collection}/stats`,
|
|
608
|
+
{ headers: this.getHeaders() }
|
|
609
|
+
);
|
|
610
|
+
|
|
611
|
+
return response.data;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// ==================== Utility Methods ====================
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Test database connection and basic functionality
|
|
618
|
+
*/
|
|
619
|
+
async healthCheck(): Promise<{
|
|
620
|
+
healthy: boolean;
|
|
621
|
+
response_time: number;
|
|
622
|
+
version: string;
|
|
623
|
+
features: string[];
|
|
624
|
+
}> {
|
|
625
|
+
const startTime = Date.now();
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
const response = await this.httpClient.get(
|
|
629
|
+
`/api/apps/${this.appId}/health`,
|
|
630
|
+
{ headers: this.getHeaders() }
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
const responseTime = Date.now() - startTime;
|
|
634
|
+
|
|
635
|
+
return {
|
|
636
|
+
healthy: true,
|
|
637
|
+
response_time: responseTime,
|
|
638
|
+
version: response.data.version || '1.0.0',
|
|
639
|
+
features: response.data.features || []
|
|
640
|
+
};
|
|
641
|
+
} catch (error) {
|
|
642
|
+
return {
|
|
643
|
+
healthy: false,
|
|
644
|
+
response_time: Date.now() - startTime,
|
|
645
|
+
version: 'unknown',
|
|
646
|
+
features: []
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Get API headers including authentication
|
|
653
|
+
*/
|
|
654
|
+
private getHeaders(): { [key: string]: string } {
|
|
655
|
+
const headers: { [key: string]: string } = {
|
|
656
|
+
'Content-Type': 'application/json'
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
if (this.apiKey) {
|
|
660
|
+
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return headers;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Set API key for authenticated operations
|
|
668
|
+
*/
|
|
669
|
+
setApiKey(apiKey: string): void {
|
|
670
|
+
this.apiKey = apiKey;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Update server configuration
|
|
675
|
+
*/
|
|
676
|
+
updateConfig(httpClient: AxiosInstance, serverUrl: string, appId: string): void {
|
|
677
|
+
this.httpClient = httpClient;
|
|
678
|
+
this.serverUrl = serverUrl;
|
|
679
|
+
this.appId = appId;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Factory function to create a DatabaseManager instance
|
|
685
|
+
*/
|
|
686
|
+
export function createDatabaseManager(
|
|
687
|
+
httpClient: AxiosInstance,
|
|
688
|
+
serverUrl: string,
|
|
689
|
+
appId: string,
|
|
690
|
+
apiKey?: string
|
|
691
|
+
): DatabaseManager {
|
|
692
|
+
return new DatabaseManager(httpClient, serverUrl, appId, apiKey);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Types are available as imports, no need to re-export
|