s3db.js 11.3.2 → 12.0.1
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 +102 -8
- package/dist/s3db.cjs.js +36945 -15510
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +66 -1
- package/dist/s3db.es.js +36914 -15534
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +58 -0
- package/mcp/tools/documentation.js +434 -0
- package/mcp/tools/index.js +4 -0
- package/package.json +35 -15
- package/src/behaviors/user-managed.js +13 -6
- package/src/client.class.js +79 -49
- package/src/concerns/base62.js +85 -0
- package/src/concerns/dictionary-encoding.js +294 -0
- package/src/concerns/geo-encoding.js +256 -0
- package/src/concerns/high-performance-inserter.js +34 -30
- package/src/concerns/ip.js +325 -0
- package/src/concerns/metadata-encoding.js +345 -66
- package/src/concerns/money.js +193 -0
- package/src/concerns/partition-queue.js +7 -4
- package/src/concerns/plugin-storage.js +97 -47
- package/src/database.class.js +76 -74
- package/src/errors.js +0 -4
- package/src/plugins/api/auth/api-key-auth.js +88 -0
- package/src/plugins/api/auth/basic-auth.js +154 -0
- package/src/plugins/api/auth/index.js +112 -0
- package/src/plugins/api/auth/jwt-auth.js +169 -0
- package/src/plugins/api/index.js +544 -0
- package/src/plugins/api/middlewares/index.js +15 -0
- package/src/plugins/api/middlewares/validator.js +185 -0
- package/src/plugins/api/routes/auth-routes.js +241 -0
- package/src/plugins/api/routes/resource-routes.js +304 -0
- package/src/plugins/api/server.js +354 -0
- package/src/plugins/api/utils/error-handler.js +147 -0
- package/src/plugins/api/utils/openapi-generator.js +1240 -0
- package/src/plugins/api/utils/response-formatter.js +218 -0
- package/src/plugins/backup/streaming-exporter.js +132 -0
- package/src/plugins/backup.plugin.js +103 -50
- package/src/plugins/cache/s3-cache.class.js +95 -47
- package/src/plugins/cache.plugin.js +107 -9
- package/src/plugins/concerns/plugin-dependencies.js +313 -0
- package/src/plugins/concerns/prometheus-formatter.js +255 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
- package/src/plugins/consumers/sqs-consumer.js +4 -0
- package/src/plugins/costs.plugin.js +255 -39
- package/src/plugins/eventual-consistency/helpers.js +15 -1
- package/src/plugins/geo.plugin.js +873 -0
- package/src/plugins/importer/index.js +1020 -0
- package/src/plugins/index.js +11 -0
- package/src/plugins/metrics.plugin.js +163 -4
- package/src/plugins/queue-consumer.plugin.js +6 -27
- package/src/plugins/relation.errors.js +139 -0
- package/src/plugins/relation.plugin.js +1242 -0
- package/src/plugins/replicator.plugin.js +2 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
- package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
- package/src/plugins/replicators/index.js +28 -3
- package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
- package/src/plugins/replicators/mysql-replicator.class.js +558 -0
- package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
- package/src/plugins/replicators/postgres-replicator.class.js +182 -7
- package/src/plugins/replicators/s3db-replicator.class.js +1 -12
- package/src/plugins/replicators/schema-sync.helper.js +601 -0
- package/src/plugins/replicators/sqs-replicator.class.js +11 -9
- package/src/plugins/replicators/turso-replicator.class.js +416 -0
- package/src/plugins/replicators/webhook-replicator.class.js +612 -0
- package/src/plugins/state-machine.plugin.js +122 -68
- package/src/plugins/tfstate/README.md +745 -0
- package/src/plugins/tfstate/base-driver.js +80 -0
- package/src/plugins/tfstate/errors.js +112 -0
- package/src/plugins/tfstate/filesystem-driver.js +129 -0
- package/src/plugins/tfstate/index.js +2660 -0
- package/src/plugins/tfstate/s3-driver.js +192 -0
- package/src/plugins/ttl.plugin.js +536 -0
- package/src/resource.class.js +315 -36
- package/src/s3db.d.ts +66 -1
- package/src/schema.class.js +366 -32
- package/SECURITY.md +0 -76
- package/src/partition-drivers/base-partition-driver.js +0 -106
- package/src/partition-drivers/index.js +0 -66
- package/src/partition-drivers/memory-partition-driver.js +0 -289
- package/src/partition-drivers/sqs-partition-driver.js +0 -337
- package/src/partition-drivers/sync-partition-driver.js +0 -38
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import tryFn from "#src/concerns/try-fn.js";
|
|
2
|
+
import requirePluginDependency from "#src/plugins/concerns/plugin-dependencies.js";
|
|
3
|
+
import BaseReplicator from './base-replicator.class.js';
|
|
4
|
+
import { ReplicationError } from '../replicator.errors.js';
|
|
5
|
+
import {
|
|
6
|
+
generateSQLiteCreateTable,
|
|
7
|
+
generateSQLiteAlterTable
|
|
8
|
+
} from './schema-sync.helper.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Turso Replicator - Replicate data to Turso (SQLite edge database)
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ REQUIRED DEPENDENCY: You must install the Turso client library:
|
|
14
|
+
* ```bash
|
|
15
|
+
* pnpm add @libsql/client
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Configuration:
|
|
19
|
+
* @param {string} url - Turso database URL (required) - e.g., 'libsql://your-db.turso.io'
|
|
20
|
+
* @param {string} authToken - Turso authentication token (required)
|
|
21
|
+
* @param {Object} schemaSync - Schema synchronization configuration
|
|
22
|
+
* @param {boolean} schemaSync.enabled - Enable automatic schema management (default: false)
|
|
23
|
+
* @param {string} schemaSync.strategy - Sync strategy: 'alter' | 'drop-create' | 'validate-only' (default: 'alter')
|
|
24
|
+
* @param {string} schemaSync.onMismatch - Action on schema mismatch: 'error' | 'warn' | 'ignore' (default: 'error')
|
|
25
|
+
* @param {boolean} schemaSync.autoCreateTable - Auto-create table if not exists (default: true)
|
|
26
|
+
* @param {boolean} schemaSync.autoCreateColumns - Auto-add missing columns (default: true, only with strategy: 'alter')
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* new TursoReplicator({
|
|
30
|
+
* url: 'libsql://my-db-user.turso.io',
|
|
31
|
+
* authToken: process.env.TURSO_AUTH_TOKEN,
|
|
32
|
+
* schemaSync: {
|
|
33
|
+
* enabled: true,
|
|
34
|
+
* strategy: 'alter',
|
|
35
|
+
* onMismatch: 'error'
|
|
36
|
+
* }
|
|
37
|
+
* }, {
|
|
38
|
+
* users: [{ actions: ['insert', 'update'], table: 'users_table' }],
|
|
39
|
+
* orders: 'orders_table'
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* See docs/plugins/replicator.md for comprehensive configuration documentation.
|
|
43
|
+
*/
|
|
44
|
+
class TursoReplicator extends BaseReplicator {
|
|
45
|
+
constructor(config = {}, resources = {}) {
|
|
46
|
+
super(config);
|
|
47
|
+
this.url = config.url;
|
|
48
|
+
this.authToken = config.authToken;
|
|
49
|
+
this.client = null;
|
|
50
|
+
|
|
51
|
+
// Schema sync configuration
|
|
52
|
+
this.schemaSync = {
|
|
53
|
+
enabled: config.schemaSync?.enabled || false,
|
|
54
|
+
strategy: config.schemaSync?.strategy || 'alter',
|
|
55
|
+
onMismatch: config.schemaSync?.onMismatch || 'error',
|
|
56
|
+
autoCreateTable: config.schemaSync?.autoCreateTable !== false,
|
|
57
|
+
autoCreateColumns: config.schemaSync?.autoCreateColumns !== false
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Parse resources configuration
|
|
61
|
+
this.resources = this.parseResourcesConfig(resources);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
parseResourcesConfig(resources) {
|
|
65
|
+
const parsed = {};
|
|
66
|
+
|
|
67
|
+
for (const [resourceName, config] of Object.entries(resources)) {
|
|
68
|
+
if (typeof config === 'string') {
|
|
69
|
+
parsed[resourceName] = [{
|
|
70
|
+
table: config,
|
|
71
|
+
actions: ['insert']
|
|
72
|
+
}];
|
|
73
|
+
} else if (Array.isArray(config)) {
|
|
74
|
+
parsed[resourceName] = config.map(item => {
|
|
75
|
+
if (typeof item === 'string') {
|
|
76
|
+
return { table: item, actions: ['insert'] };
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
table: item.table,
|
|
80
|
+
actions: item.actions || ['insert']
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
} else if (typeof config === 'object') {
|
|
84
|
+
parsed[resourceName] = [{
|
|
85
|
+
table: config.table,
|
|
86
|
+
actions: config.actions || ['insert']
|
|
87
|
+
}];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return parsed;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
validateConfig() {
|
|
95
|
+
const errors = [];
|
|
96
|
+
if (!this.url) errors.push('URL is required');
|
|
97
|
+
if (!this.authToken) errors.push('Auth token is required');
|
|
98
|
+
if (Object.keys(this.resources).length === 0) {
|
|
99
|
+
errors.push('At least one resource must be configured');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const [resourceName, tables] of Object.entries(this.resources)) {
|
|
103
|
+
for (const tableConfig of tables) {
|
|
104
|
+
if (!tableConfig.table) {
|
|
105
|
+
errors.push(`Table name is required for resource '${resourceName}'`);
|
|
106
|
+
}
|
|
107
|
+
if (!Array.isArray(tableConfig.actions) || tableConfig.actions.length === 0) {
|
|
108
|
+
errors.push(`Actions array is required for resource '${resourceName}'`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return { isValid: errors.length === 0, errors };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async initialize(database) {
|
|
117
|
+
await super.initialize(database);
|
|
118
|
+
|
|
119
|
+
// Validate plugin dependencies are installed
|
|
120
|
+
await requirePluginDependency('turso-replicator');
|
|
121
|
+
|
|
122
|
+
const [ok, err, sdk] = await tryFn(() => import('@libsql/client'));
|
|
123
|
+
if (!ok) {
|
|
124
|
+
throw new ReplicationError('Failed to import Turso SDK', {
|
|
125
|
+
operation: 'initialize',
|
|
126
|
+
replicatorClass: 'TursoReplicator',
|
|
127
|
+
original: err,
|
|
128
|
+
suggestion: 'Install @libsql/client: pnpm add @libsql/client'
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { createClient } = sdk;
|
|
133
|
+
this.client = createClient({
|
|
134
|
+
url: this.url,
|
|
135
|
+
authToken: this.authToken
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Test connection
|
|
139
|
+
const [okTest, errTest] = await tryFn(async () => {
|
|
140
|
+
await this.client.execute('SELECT 1');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
if (!okTest) {
|
|
144
|
+
throw new ReplicationError('Failed to connect to Turso database', {
|
|
145
|
+
operation: 'initialize',
|
|
146
|
+
replicatorClass: 'TursoReplicator',
|
|
147
|
+
url: this.url,
|
|
148
|
+
original: errTest,
|
|
149
|
+
suggestion: 'Check Turso URL and auth token'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Sync schemas if enabled
|
|
154
|
+
if (this.schemaSync.enabled) {
|
|
155
|
+
await this.syncSchemas(database);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.emit('connected', {
|
|
159
|
+
replicator: 'TursoReplicator',
|
|
160
|
+
url: this.url
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Sync table schemas based on S3DB resource definitions
|
|
166
|
+
*/
|
|
167
|
+
async syncSchemas(database) {
|
|
168
|
+
for (const [resourceName, tableConfigs] of Object.entries(this.resources)) {
|
|
169
|
+
const [okRes, errRes, resource] = await tryFn(async () => {
|
|
170
|
+
return await database.getResource(resourceName);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (!okRes) {
|
|
174
|
+
if (this.config.verbose) {
|
|
175
|
+
console.warn(`[TursoReplicator] Could not get resource ${resourceName} for schema sync: ${errRes.message}`);
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const attributes = resource.config.versions[resource.config.currentVersion]?.attributes || {};
|
|
181
|
+
|
|
182
|
+
for (const tableConfig of tableConfigs) {
|
|
183
|
+
const tableName = tableConfig.table;
|
|
184
|
+
|
|
185
|
+
const [okSync, errSync] = await tryFn(async () => {
|
|
186
|
+
await this.syncTableSchema(tableName, attributes);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!okSync) {
|
|
190
|
+
const message = `Schema sync failed for table ${tableName}: ${errSync.message}`;
|
|
191
|
+
|
|
192
|
+
if (this.schemaSync.onMismatch === 'error') {
|
|
193
|
+
throw new Error(message);
|
|
194
|
+
} else if (this.schemaSync.onMismatch === 'warn') {
|
|
195
|
+
console.warn(`[TursoReplicator] ${message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.emit('schema_sync_completed', {
|
|
202
|
+
replicator: this.name,
|
|
203
|
+
resources: Object.keys(this.resources)
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Sync a single table schema
|
|
209
|
+
*/
|
|
210
|
+
async syncTableSchema(tableName, attributes) {
|
|
211
|
+
// Check if table exists
|
|
212
|
+
const [okCheck, errCheck, result] = await tryFn(async () => {
|
|
213
|
+
return await this.client.execute({
|
|
214
|
+
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
215
|
+
args: [tableName]
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const tableExists = okCheck && result.rows.length > 0;
|
|
220
|
+
|
|
221
|
+
if (!tableExists) {
|
|
222
|
+
if (!this.schemaSync.autoCreateTable) {
|
|
223
|
+
throw new Error(`Table ${tableName} does not exist and autoCreateTable is disabled`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (this.schemaSync.strategy === 'validate-only') {
|
|
227
|
+
throw new Error(`Table ${tableName} does not exist (validate-only mode)`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Create table
|
|
231
|
+
const createSQL = generateSQLiteCreateTable(tableName, attributes);
|
|
232
|
+
|
|
233
|
+
if (this.config.verbose) {
|
|
234
|
+
console.log(`[TursoReplicator] Creating table ${tableName}:\n${createSQL}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
await this.client.execute(createSQL);
|
|
238
|
+
|
|
239
|
+
this.emit('table_created', {
|
|
240
|
+
replicator: this.name,
|
|
241
|
+
tableName,
|
|
242
|
+
attributes: Object.keys(attributes)
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Table exists - check for schema changes
|
|
249
|
+
if (this.schemaSync.strategy === 'drop-create') {
|
|
250
|
+
if (this.config.verbose) {
|
|
251
|
+
console.warn(`[TursoReplicator] Dropping and recreating table ${tableName}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
await this.client.execute(`DROP TABLE IF EXISTS ${tableName}`);
|
|
255
|
+
const createSQL = generateSQLiteCreateTable(tableName, attributes);
|
|
256
|
+
await this.client.execute(createSQL);
|
|
257
|
+
|
|
258
|
+
this.emit('table_recreated', {
|
|
259
|
+
replicator: this.name,
|
|
260
|
+
tableName,
|
|
261
|
+
attributes: Object.keys(attributes)
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (this.schemaSync.strategy === 'alter' && this.schemaSync.autoCreateColumns) {
|
|
268
|
+
// Get existing columns
|
|
269
|
+
const [okPragma, errPragma, pragmaResult] = await tryFn(async () => {
|
|
270
|
+
return await this.client.execute(`PRAGMA table_info(${tableName})`);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
if (okPragma) {
|
|
274
|
+
const existingSchema = {};
|
|
275
|
+
for (const row of pragmaResult.rows) {
|
|
276
|
+
existingSchema[row.name] = { type: row.type };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const alterStatements = generateSQLiteAlterTable(tableName, attributes, existingSchema);
|
|
280
|
+
|
|
281
|
+
if (alterStatements.length > 0) {
|
|
282
|
+
if (this.config.verbose) {
|
|
283
|
+
console.log(`[TursoReplicator] Altering table ${tableName}:`, alterStatements);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
for (const stmt of alterStatements) {
|
|
287
|
+
await this.client.execute(stmt);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.emit('table_altered', {
|
|
291
|
+
replicator: this.name,
|
|
292
|
+
tableName,
|
|
293
|
+
addedColumns: alterStatements.length
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
shouldReplicateResource(resourceName) {
|
|
301
|
+
return this.resources.hasOwnProperty(resourceName);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
shouldReplicateAction(resourceName, operation) {
|
|
305
|
+
if (!this.resources[resourceName]) return false;
|
|
306
|
+
|
|
307
|
+
return this.resources[resourceName].some(tableConfig =>
|
|
308
|
+
tableConfig.actions.includes(operation)
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
getTablesForResource(resourceName, operation) {
|
|
313
|
+
if (!this.resources[resourceName]) return [];
|
|
314
|
+
|
|
315
|
+
return this.resources[resourceName]
|
|
316
|
+
.filter(tableConfig => tableConfig.actions.includes(operation))
|
|
317
|
+
.map(tableConfig => tableConfig.table);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async replicate(resourceName, operation, data, id, beforeData = null) {
|
|
321
|
+
if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
|
|
322
|
+
return { skipped: true, reason: 'resource_not_included' };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!this.shouldReplicateAction(resourceName, operation)) {
|
|
326
|
+
return { skipped: true, reason: 'action_not_included' };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const tables = this.getTablesForResource(resourceName, operation);
|
|
330
|
+
if (tables.length === 0) {
|
|
331
|
+
return { skipped: true, reason: 'no_tables_for_action' };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const results = [];
|
|
335
|
+
const errors = [];
|
|
336
|
+
|
|
337
|
+
for (const table of tables) {
|
|
338
|
+
const [okTable, errTable] = await tryFn(async () => {
|
|
339
|
+
if (operation === 'insert') {
|
|
340
|
+
const cleanData = this._cleanInternalFields(data);
|
|
341
|
+
const keys = Object.keys(cleanData);
|
|
342
|
+
const values = keys.map(k => cleanData[k]);
|
|
343
|
+
const placeholders = keys.map((_, i) => `?`).join(', ');
|
|
344
|
+
const sql = `INSERT OR IGNORE INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`;
|
|
345
|
+
await this.client.execute({ sql, args: values });
|
|
346
|
+
} else if (operation === 'update') {
|
|
347
|
+
const cleanData = this._cleanInternalFields(data);
|
|
348
|
+
const keys = Object.keys(cleanData).filter(k => k !== 'id');
|
|
349
|
+
const setClause = keys.map(k => `${k}=?`).join(', ');
|
|
350
|
+
const values = keys.map(k => cleanData[k]);
|
|
351
|
+
values.push(id);
|
|
352
|
+
const sql = `UPDATE ${table} SET ${setClause} WHERE id=?`;
|
|
353
|
+
await this.client.execute({ sql, args: values });
|
|
354
|
+
} else if (operation === 'delete') {
|
|
355
|
+
const sql = `DELETE FROM ${table} WHERE id=?`;
|
|
356
|
+
await this.client.execute({ sql, args: [id] });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
results.push({ table, success: true });
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
if (!okTable) {
|
|
363
|
+
errors.push({ table, error: errTable.message });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const success = errors.length === 0;
|
|
368
|
+
|
|
369
|
+
this.emit('replicated', {
|
|
370
|
+
replicator: this.name,
|
|
371
|
+
resourceName,
|
|
372
|
+
operation,
|
|
373
|
+
id,
|
|
374
|
+
tables,
|
|
375
|
+
results,
|
|
376
|
+
errors,
|
|
377
|
+
success
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return { success, results, errors, tables };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
_cleanInternalFields(data) {
|
|
384
|
+
if (!data || typeof data !== 'object') return data;
|
|
385
|
+
|
|
386
|
+
const cleanData = { ...data };
|
|
387
|
+
|
|
388
|
+
Object.keys(cleanData).forEach(key => {
|
|
389
|
+
if (key.startsWith('$') || key.startsWith('_')) {
|
|
390
|
+
delete cleanData[key];
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
return cleanData;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async cleanup() {
|
|
398
|
+
if (this.client) {
|
|
399
|
+
this.client.close();
|
|
400
|
+
this.client = null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async getStatus() {
|
|
405
|
+
const baseStatus = await super.getStatus();
|
|
406
|
+
return {
|
|
407
|
+
...baseStatus,
|
|
408
|
+
connected: !!this.client,
|
|
409
|
+
url: this.url,
|
|
410
|
+
resources: Object.keys(this.resources),
|
|
411
|
+
schemaSync: this.schemaSync
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export default TursoReplicator;
|