digitaltwin-core 0.13.3 → 0.14.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/package.json +101 -106
- package/dist/auth/apisix_parser.d.ts +0 -146
- package/dist/auth/apisix_parser.d.ts.map +0 -1
- package/dist/auth/apisix_parser.js +0 -185
- package/dist/auth/apisix_parser.js.map +0 -1
- package/dist/auth/auth_config.d.ts +0 -126
- package/dist/auth/auth_config.d.ts.map +0 -1
- package/dist/auth/auth_config.js +0 -169
- package/dist/auth/auth_config.js.map +0 -1
- package/dist/auth/index.d.ts +0 -5
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -4
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/types.d.ts +0 -100
- package/dist/auth/types.d.ts.map +0 -1
- package/dist/auth/types.js +0 -2
- package/dist/auth/types.js.map +0 -1
- package/dist/auth/user_service.d.ts +0 -86
- package/dist/auth/user_service.d.ts.map +0 -1
- package/dist/auth/user_service.js +0 -237
- package/dist/auth/user_service.js.map +0 -1
- package/dist/components/assets_manager.d.ts +0 -662
- package/dist/components/assets_manager.d.ts.map +0 -1
- package/dist/components/assets_manager.js +0 -1529
- package/dist/components/assets_manager.js.map +0 -1
- package/dist/components/async_upload.d.ts +0 -20
- package/dist/components/async_upload.d.ts.map +0 -1
- package/dist/components/async_upload.js +0 -10
- package/dist/components/async_upload.js.map +0 -1
- package/dist/components/collector.d.ts +0 -203
- package/dist/components/collector.d.ts.map +0 -1
- package/dist/components/collector.js +0 -202
- package/dist/components/collector.js.map +0 -1
- package/dist/components/custom_table_manager.d.ts +0 -503
- package/dist/components/custom_table_manager.d.ts.map +0 -1
- package/dist/components/custom_table_manager.js +0 -1052
- package/dist/components/custom_table_manager.js.map +0 -1
- package/dist/components/global_assets_handler.d.ts +0 -63
- package/dist/components/global_assets_handler.d.ts.map +0 -1
- package/dist/components/global_assets_handler.js +0 -127
- package/dist/components/global_assets_handler.js.map +0 -1
- package/dist/components/handler.d.ts +0 -104
- package/dist/components/handler.d.ts.map +0 -1
- package/dist/components/handler.js +0 -110
- package/dist/components/handler.js.map +0 -1
- package/dist/components/harvester.d.ts +0 -182
- package/dist/components/harvester.d.ts.map +0 -1
- package/dist/components/harvester.js +0 -393
- package/dist/components/harvester.js.map +0 -1
- package/dist/components/index.d.ts +0 -11
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/index.js +0 -9
- package/dist/components/index.js.map +0 -1
- package/dist/components/interfaces.d.ts +0 -126
- package/dist/components/interfaces.d.ts.map +0 -1
- package/dist/components/interfaces.js +0 -8
- package/dist/components/interfaces.js.map +0 -1
- package/dist/components/map_manager.d.ts +0 -61
- package/dist/components/map_manager.d.ts.map +0 -1
- package/dist/components/map_manager.js +0 -242
- package/dist/components/map_manager.js.map +0 -1
- package/dist/components/tileset_manager.d.ts +0 -125
- package/dist/components/tileset_manager.d.ts.map +0 -1
- package/dist/components/tileset_manager.js +0 -618
- package/dist/components/tileset_manager.js.map +0 -1
- package/dist/components/types.d.ts +0 -226
- package/dist/components/types.d.ts.map +0 -1
- package/dist/components/types.js +0 -8
- package/dist/components/types.js.map +0 -1
- package/dist/database/adapters/knex_database_adapter.d.ts +0 -92
- package/dist/database/adapters/knex_database_adapter.d.ts.map +0 -1
- package/dist/database/adapters/knex_database_adapter.js +0 -647
- package/dist/database/adapters/knex_database_adapter.js.map +0 -1
- package/dist/database/database_adapter.d.ts +0 -251
- package/dist/database/database_adapter.d.ts.map +0 -1
- package/dist/database/database_adapter.js +0 -46
- package/dist/database/database_adapter.js.map +0 -1
- package/dist/engine/digital_twin_engine.d.ts +0 -253
- package/dist/engine/digital_twin_engine.d.ts.map +0 -1
- package/dist/engine/digital_twin_engine.js +0 -790
- package/dist/engine/digital_twin_engine.js.map +0 -1
- package/dist/engine/endpoints.d.ts +0 -47
- package/dist/engine/endpoints.d.ts.map +0 -1
- package/dist/engine/endpoints.js +0 -56
- package/dist/engine/endpoints.js.map +0 -1
- package/dist/engine/events.d.ts +0 -93
- package/dist/engine/events.d.ts.map +0 -1
- package/dist/engine/events.js +0 -71
- package/dist/engine/events.js.map +0 -1
- package/dist/engine/initializer.d.ts +0 -62
- package/dist/engine/initializer.d.ts.map +0 -1
- package/dist/engine/initializer.js +0 -108
- package/dist/engine/initializer.js.map +0 -1
- package/dist/engine/queue_manager.d.ts +0 -87
- package/dist/engine/queue_manager.d.ts.map +0 -1
- package/dist/engine/queue_manager.js +0 -196
- package/dist/engine/queue_manager.js.map +0 -1
- package/dist/engine/scheduler.d.ts +0 -30
- package/dist/engine/scheduler.d.ts.map +0 -1
- package/dist/engine/scheduler.js +0 -370
- package/dist/engine/scheduler.js.map +0 -1
- package/dist/engine/upload_processor.d.ts +0 -36
- package/dist/engine/upload_processor.d.ts.map +0 -1
- package/dist/engine/upload_processor.js +0 -101
- package/dist/engine/upload_processor.js.map +0 -1
- package/dist/env/env.d.ts +0 -134
- package/dist/env/env.d.ts.map +0 -1
- package/dist/env/env.js +0 -177
- package/dist/env/env.js.map +0 -1
- package/dist/index.d.ts +0 -49
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -57
- package/dist/index.js.map +0 -1
- package/dist/openapi/generator.d.ts +0 -93
- package/dist/openapi/generator.d.ts.map +0 -1
- package/dist/openapi/generator.js +0 -293
- package/dist/openapi/generator.js.map +0 -1
- package/dist/openapi/index.d.ts +0 -9
- package/dist/openapi/index.d.ts.map +0 -1
- package/dist/openapi/index.js +0 -9
- package/dist/openapi/index.js.map +0 -1
- package/dist/openapi/types.d.ts +0 -182
- package/dist/openapi/types.d.ts.map +0 -1
- package/dist/openapi/types.js +0 -16
- package/dist/openapi/types.js.map +0 -1
- package/dist/storage/adapters/local_storage_service.d.ts +0 -51
- package/dist/storage/adapters/local_storage_service.d.ts.map +0 -1
- package/dist/storage/adapters/local_storage_service.js +0 -110
- package/dist/storage/adapters/local_storage_service.js.map +0 -1
- package/dist/storage/adapters/ovh_storage_service.d.ts +0 -61
- package/dist/storage/adapters/ovh_storage_service.d.ts.map +0 -1
- package/dist/storage/adapters/ovh_storage_service.js +0 -172
- package/dist/storage/adapters/ovh_storage_service.js.map +0 -1
- package/dist/storage/storage_factory.d.ts +0 -14
- package/dist/storage/storage_factory.d.ts.map +0 -1
- package/dist/storage/storage_factory.js +0 -36
- package/dist/storage/storage_factory.js.map +0 -1
- package/dist/storage/storage_service.d.ts +0 -163
- package/dist/storage/storage_service.d.ts.map +0 -1
- package/dist/storage/storage_service.js +0 -54
- package/dist/storage/storage_service.js.map +0 -1
- package/dist/types/data_record.d.ts +0 -123
- package/dist/types/data_record.d.ts.map +0 -1
- package/dist/types/data_record.js +0 -8
- package/dist/types/data_record.js.map +0 -1
- package/dist/utils/http_responses.d.ts +0 -155
- package/dist/utils/http_responses.d.ts.map +0 -1
- package/dist/utils/http_responses.js +0 -190
- package/dist/utils/http_responses.js.map +0 -1
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -6
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -74
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -92
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/map_to_data_record.d.ts +0 -10
- package/dist/utils/map_to_data_record.d.ts.map +0 -1
- package/dist/utils/map_to_data_record.js +0 -36
- package/dist/utils/map_to_data_record.js.map +0 -1
- package/dist/utils/servable_endpoint.d.ts +0 -63
- package/dist/utils/servable_endpoint.d.ts.map +0 -1
- package/dist/utils/servable_endpoint.js +0 -67
- package/dist/utils/servable_endpoint.js.map +0 -1
- package/dist/utils/zip_utils.d.ts +0 -66
- package/dist/utils/zip_utils.d.ts.map +0 -1
- package/dist/utils/zip_utils.js +0 -169
- package/dist/utils/zip_utils.js.map +0 -1
|
@@ -1,647 +0,0 @@
|
|
|
1
|
-
import knex from 'knex';
|
|
2
|
-
import { DatabaseAdapter } from '../database_adapter.js';
|
|
3
|
-
import { mapToDataRecord } from '../../utils/map_to_data_record.js';
|
|
4
|
-
/**
|
|
5
|
-
* Knex-based implementation with extended querying capabilities.
|
|
6
|
-
*/
|
|
7
|
-
export class KnexDatabaseAdapter extends DatabaseAdapter {
|
|
8
|
-
#knex;
|
|
9
|
-
#storage;
|
|
10
|
-
constructor(config, storage) {
|
|
11
|
-
super();
|
|
12
|
-
this.#knex = knex(config);
|
|
13
|
-
this.#storage = storage;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Create a KnexDatabaseAdapter for PostgreSQL with simplified configuration
|
|
17
|
-
*/
|
|
18
|
-
static forPostgreSQL(pgConfig, storage, _tableName = 'data_index') {
|
|
19
|
-
const knexConfig = {
|
|
20
|
-
client: 'pg',
|
|
21
|
-
connection: {
|
|
22
|
-
host: pgConfig.host,
|
|
23
|
-
port: pgConfig.port || 5432,
|
|
24
|
-
user: pgConfig.user,
|
|
25
|
-
password: pgConfig.password,
|
|
26
|
-
database: pgConfig.database,
|
|
27
|
-
ssl: pgConfig.ssl || false
|
|
28
|
-
},
|
|
29
|
-
pool: {
|
|
30
|
-
min: 2,
|
|
31
|
-
max: 15,
|
|
32
|
-
acquireTimeoutMillis: 30000,
|
|
33
|
-
createTimeoutMillis: 30000,
|
|
34
|
-
destroyTimeoutMillis: 5000,
|
|
35
|
-
idleTimeoutMillis: 30000,
|
|
36
|
-
reapIntervalMillis: 1000
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
return new KnexDatabaseAdapter(knexConfig, storage);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Create a KnexDatabaseAdapter for SQLite with simplified configuration
|
|
43
|
-
*/
|
|
44
|
-
static forSQLite(sqliteConfig, storage, _tableName = 'data_index') {
|
|
45
|
-
const client = sqliteConfig.client || 'sqlite3';
|
|
46
|
-
const knexConfig = {
|
|
47
|
-
client,
|
|
48
|
-
connection: {
|
|
49
|
-
filename: sqliteConfig.filename
|
|
50
|
-
},
|
|
51
|
-
pool: {
|
|
52
|
-
min: 1,
|
|
53
|
-
max: 5,
|
|
54
|
-
acquireTimeoutMillis: sqliteConfig.busyTimeout || 30000,
|
|
55
|
-
afterCreate: (conn, cb) => {
|
|
56
|
-
if (sqliteConfig.enableForeignKeys !== false) {
|
|
57
|
-
// Both sqlite3 and better-sqlite3 support PRAGMA
|
|
58
|
-
if (client === 'better-sqlite3') {
|
|
59
|
-
conn.pragma('foreign_keys = ON');
|
|
60
|
-
conn.pragma('journal_mode = WAL');
|
|
61
|
-
conn.pragma('synchronous = NORMAL');
|
|
62
|
-
conn.pragma('cache_size = 10000');
|
|
63
|
-
cb();
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
conn.run('PRAGMA foreign_keys = ON', () => {
|
|
67
|
-
conn.run('PRAGMA journal_mode = WAL', () => {
|
|
68
|
-
conn.run('PRAGMA synchronous = NORMAL', () => {
|
|
69
|
-
conn.run('PRAGMA cache_size = 10000', cb);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
cb();
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
useNullAsDefault: true
|
|
81
|
-
};
|
|
82
|
-
return new KnexDatabaseAdapter(knexConfig, storage);
|
|
83
|
-
}
|
|
84
|
-
// ========== Basic methods ==========
|
|
85
|
-
async save(meta) {
|
|
86
|
-
const insertData = {
|
|
87
|
-
id: meta.id,
|
|
88
|
-
name: meta.name,
|
|
89
|
-
type: meta.type,
|
|
90
|
-
url: meta.url,
|
|
91
|
-
date: meta.date.toISOString()
|
|
92
|
-
};
|
|
93
|
-
// Add asset-specific fields if present (for AssetMetadataRow)
|
|
94
|
-
if ('description' in meta)
|
|
95
|
-
insertData.description = meta.description;
|
|
96
|
-
if ('source' in meta)
|
|
97
|
-
insertData.source = meta.source;
|
|
98
|
-
if ('owner_id' in meta)
|
|
99
|
-
insertData.owner_id = meta.owner_id;
|
|
100
|
-
if ('filename' in meta)
|
|
101
|
-
insertData.filename = meta.filename;
|
|
102
|
-
if ('is_public' in meta)
|
|
103
|
-
insertData.is_public = meta.is_public;
|
|
104
|
-
// TilesetManager support (public URL)
|
|
105
|
-
if ('tileset_url' in meta)
|
|
106
|
-
insertData.tileset_url = meta.tileset_url;
|
|
107
|
-
// Async upload support
|
|
108
|
-
if ('upload_status' in meta)
|
|
109
|
-
insertData.upload_status = meta.upload_status;
|
|
110
|
-
if ('upload_error' in meta)
|
|
111
|
-
insertData.upload_error = meta.upload_error;
|
|
112
|
-
if ('upload_job_id' in meta)
|
|
113
|
-
insertData.upload_job_id = meta.upload_job_id;
|
|
114
|
-
// Insert and get the auto-generated ID
|
|
115
|
-
const [insertedId] = await this.#knex(meta.name).insert(insertData).returning('id');
|
|
116
|
-
// Handle different return formats (PostgreSQL returns object, SQLite returns number)
|
|
117
|
-
const newId = typeof insertedId === 'object' ? insertedId.id : insertedId;
|
|
118
|
-
// Return record with the generated ID
|
|
119
|
-
return mapToDataRecord({ ...meta, id: newId }, this.#storage);
|
|
120
|
-
}
|
|
121
|
-
async delete(id, name) {
|
|
122
|
-
await this.#knex(name).where({ id }).delete();
|
|
123
|
-
}
|
|
124
|
-
async getById(id, name) {
|
|
125
|
-
const row = await this.#knex(name).where({ id }).first();
|
|
126
|
-
return row ? mapToDataRecord(row, this.#storage) : undefined;
|
|
127
|
-
}
|
|
128
|
-
async getLatestByName(name) {
|
|
129
|
-
const row = await this.#knex(name).select('*').orderBy('date', 'desc').limit(1).first();
|
|
130
|
-
return row ? mapToDataRecord(row, this.#storage) : undefined;
|
|
131
|
-
}
|
|
132
|
-
async doesTableExists(name) {
|
|
133
|
-
return this.#knex.schema.hasTable(name);
|
|
134
|
-
}
|
|
135
|
-
async createTable(name) {
|
|
136
|
-
const tableExists = await this.#knex.schema.hasTable(name);
|
|
137
|
-
if (!tableExists) {
|
|
138
|
-
await this.#knex.schema.createTable(name, table => {
|
|
139
|
-
table.increments('id').primary();
|
|
140
|
-
table.string('name').notNullable();
|
|
141
|
-
table.string('type').notNullable();
|
|
142
|
-
table.string('url').notNullable();
|
|
143
|
-
table.datetime('date').notNullable();
|
|
144
|
-
// Asset-specific fields (optional, for AssetsManager components)
|
|
145
|
-
table.text('description').nullable();
|
|
146
|
-
table.string('source').nullable();
|
|
147
|
-
table.integer('owner_id').unsigned().nullable();
|
|
148
|
-
table.string('filename').nullable();
|
|
149
|
-
table.boolean('is_public').defaultTo(true).notNullable();
|
|
150
|
-
// TilesetManager support (public URL for Cesium)
|
|
151
|
-
table.text('tileset_url').nullable();
|
|
152
|
-
// Async upload support (for large file processing)
|
|
153
|
-
table.string('upload_status', 20).nullable(); // pending, processing, completed, failed
|
|
154
|
-
table.text('upload_error').nullable();
|
|
155
|
-
table.string('upload_job_id', 100).nullable(); // BullMQ job ID for status tracking
|
|
156
|
-
// Foreign key constraint to users table (if it exists)
|
|
157
|
-
// Note: This will only work if users table exists first
|
|
158
|
-
try {
|
|
159
|
-
table.foreign('owner_id').references('id').inTable('users').onDelete('SET NULL');
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
// Ignore foreign key creation if users table doesn't exist yet
|
|
163
|
-
// This allows backward compatibility for non-authenticated assets
|
|
164
|
-
}
|
|
165
|
-
// Optimized indexes for most frequent queries
|
|
166
|
-
table.index('name', `${name}_idx_name`);
|
|
167
|
-
table.index('date', `${name}_idx_date`);
|
|
168
|
-
table.index(['name', 'date'], `${name}_idx_name_date`);
|
|
169
|
-
table.index(['date', 'name'], `${name}_idx_date_name`); // For date range queries
|
|
170
|
-
table.index('owner_id', `${name}_idx_owner_id`); // For asset filtering and foreign key
|
|
171
|
-
table.index('is_public', `${name}_idx_is_public`); // For visibility filtering
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async createTableWithColumns(name, columns) {
|
|
176
|
-
const tableExists = await this.#knex.schema.hasTable(name);
|
|
177
|
-
if (!tableExists) {
|
|
178
|
-
await this.#knex.schema.createTable(name, table => {
|
|
179
|
-
// Standard columns for CustomTableManager
|
|
180
|
-
table.increments('id').primary();
|
|
181
|
-
table.datetime('created_at').defaultTo(this.#knex.fn.now()).notNullable();
|
|
182
|
-
table.datetime('updated_at').defaultTo(this.#knex.fn.now()).notNullable();
|
|
183
|
-
// Custom columns from StoreConfiguration
|
|
184
|
-
for (const [columnName, columnType] of Object.entries(columns)) {
|
|
185
|
-
// Parse SQL type and apply it to the table
|
|
186
|
-
this.#addColumnToTable(table, columnName, columnType);
|
|
187
|
-
}
|
|
188
|
-
// Indexes for performance
|
|
189
|
-
table.index('created_at', `${name}_idx_created_at`);
|
|
190
|
-
table.index('updated_at', `${name}_idx_updated_at`);
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Helper method to add a column to a Knex table based on SQL type string
|
|
196
|
-
* @private
|
|
197
|
-
*/
|
|
198
|
-
#addColumnToTable(table, columnName, sqlType) {
|
|
199
|
-
const lowerType = sqlType.toLowerCase();
|
|
200
|
-
if (lowerType.includes('text')) {
|
|
201
|
-
const col = table.text(columnName);
|
|
202
|
-
if (lowerType.includes('not null'))
|
|
203
|
-
col.notNullable();
|
|
204
|
-
else
|
|
205
|
-
col.nullable();
|
|
206
|
-
}
|
|
207
|
-
else if (lowerType.includes('integer')) {
|
|
208
|
-
const col = table.integer(columnName);
|
|
209
|
-
if (lowerType.includes('not null'))
|
|
210
|
-
col.notNullable();
|
|
211
|
-
else
|
|
212
|
-
col.nullable();
|
|
213
|
-
}
|
|
214
|
-
else if (lowerType.includes('boolean')) {
|
|
215
|
-
const col = table.boolean(columnName);
|
|
216
|
-
if (lowerType.includes('not null'))
|
|
217
|
-
col.notNullable();
|
|
218
|
-
else
|
|
219
|
-
col.nullable();
|
|
220
|
-
if (lowerType.includes('default true'))
|
|
221
|
-
col.defaultTo(true);
|
|
222
|
-
else if (lowerType.includes('default false'))
|
|
223
|
-
col.defaultTo(false);
|
|
224
|
-
}
|
|
225
|
-
else if (lowerType.includes('timestamp') || lowerType.includes('datetime')) {
|
|
226
|
-
const col = table.datetime(columnName);
|
|
227
|
-
if (lowerType.includes('not null'))
|
|
228
|
-
col.notNullable();
|
|
229
|
-
else
|
|
230
|
-
col.nullable();
|
|
231
|
-
if (lowerType.includes('default current_timestamp'))
|
|
232
|
-
col.defaultTo(this.#knex.fn.now());
|
|
233
|
-
}
|
|
234
|
-
else if (lowerType.includes('real') || lowerType.includes('decimal') || lowerType.includes('float')) {
|
|
235
|
-
const col = table.decimal(columnName);
|
|
236
|
-
if (lowerType.includes('not null'))
|
|
237
|
-
col.notNullable();
|
|
238
|
-
else
|
|
239
|
-
col.nullable();
|
|
240
|
-
}
|
|
241
|
-
else if (lowerType.includes('varchar')) {
|
|
242
|
-
// Extract length from varchar(255)
|
|
243
|
-
const match = lowerType.match(/varchar\((\d+)\)/);
|
|
244
|
-
const length = match ? parseInt(match[1]) : 255;
|
|
245
|
-
const col = table.string(columnName, length);
|
|
246
|
-
if (lowerType.includes('not null'))
|
|
247
|
-
col.notNullable();
|
|
248
|
-
else
|
|
249
|
-
col.nullable();
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
// Default to string for unknown types
|
|
253
|
-
const col = table.string(columnName);
|
|
254
|
-
if (lowerType.includes('not null'))
|
|
255
|
-
col.notNullable();
|
|
256
|
-
else
|
|
257
|
-
col.nullable();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
/**
|
|
261
|
-
* Migrate existing table schema to match expected schema.
|
|
262
|
-
*
|
|
263
|
-
* Automatically adds missing columns and indexes for asset tables.
|
|
264
|
-
* Only performs safe operations (adding columns with defaults or nullable).
|
|
265
|
-
*
|
|
266
|
-
* @param {string} name - Table name to migrate
|
|
267
|
-
* @returns {Promise<string[]>} Array of migration messages describing what was done
|
|
268
|
-
*/
|
|
269
|
-
async migrateTableSchema(name) {
|
|
270
|
-
const tableExists = await this.#knex.schema.hasTable(name);
|
|
271
|
-
if (!tableExists) {
|
|
272
|
-
return []; // Table doesn't exist, nothing to migrate
|
|
273
|
-
}
|
|
274
|
-
const migrations = [];
|
|
275
|
-
// Define expected columns for asset tables (those created by createTable)
|
|
276
|
-
const expectedColumns = {
|
|
277
|
-
is_public: {
|
|
278
|
-
exists: await this.#knex.schema.hasColumn(name, 'is_public'),
|
|
279
|
-
add: async () => {
|
|
280
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
281
|
-
table.boolean('is_public').defaultTo(true).notNullable();
|
|
282
|
-
});
|
|
283
|
-
migrations.push(`Added column 'is_public' (BOOLEAN DEFAULT true NOT NULL)`);
|
|
284
|
-
}
|
|
285
|
-
},
|
|
286
|
-
tileset_url: {
|
|
287
|
-
exists: await this.#knex.schema.hasColumn(name, 'tileset_url'),
|
|
288
|
-
add: async () => {
|
|
289
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
290
|
-
table.text('tileset_url').nullable();
|
|
291
|
-
});
|
|
292
|
-
migrations.push(`Added column 'tileset_url' (TEXT nullable)`);
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
upload_status: {
|
|
296
|
-
exists: await this.#knex.schema.hasColumn(name, 'upload_status'),
|
|
297
|
-
add: async () => {
|
|
298
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
299
|
-
table.string('upload_status', 20).nullable().defaultTo(null);
|
|
300
|
-
});
|
|
301
|
-
migrations.push(`Added column 'upload_status' (VARCHAR(20) nullable)`);
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
|
-
upload_error: {
|
|
305
|
-
exists: await this.#knex.schema.hasColumn(name, 'upload_error'),
|
|
306
|
-
add: async () => {
|
|
307
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
308
|
-
table.text('upload_error').nullable();
|
|
309
|
-
});
|
|
310
|
-
migrations.push(`Added column 'upload_error' (TEXT nullable)`);
|
|
311
|
-
}
|
|
312
|
-
},
|
|
313
|
-
upload_job_id: {
|
|
314
|
-
exists: await this.#knex.schema.hasColumn(name, 'upload_job_id'),
|
|
315
|
-
add: async () => {
|
|
316
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
317
|
-
table.string('upload_job_id', 100).nullable();
|
|
318
|
-
});
|
|
319
|
-
migrations.push(`Added column 'upload_job_id' (VARCHAR(100) nullable)`);
|
|
320
|
-
}
|
|
321
|
-
},
|
|
322
|
-
created_at: {
|
|
323
|
-
exists: await this.#knex.schema.hasColumn(name, 'created_at'),
|
|
324
|
-
add: async () => {
|
|
325
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
326
|
-
table.datetime('created_at').defaultTo(this.#knex.fn.now()).nullable();
|
|
327
|
-
});
|
|
328
|
-
migrations.push(`Added column 'created_at' (DATETIME nullable)`);
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
updated_at: {
|
|
332
|
-
exists: await this.#knex.schema.hasColumn(name, 'updated_at'),
|
|
333
|
-
add: async () => {
|
|
334
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
335
|
-
table.datetime('updated_at').defaultTo(this.#knex.fn.now()).nullable();
|
|
336
|
-
});
|
|
337
|
-
migrations.push(`Added column 'updated_at' (DATETIME nullable)`);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
// Expected indexes
|
|
342
|
-
const expectedIndexes = {
|
|
343
|
-
[`${name}_idx_is_public`]: {
|
|
344
|
-
exists: await this.#hasIndex(name, `${name}_idx_is_public`),
|
|
345
|
-
add: async () => {
|
|
346
|
-
await this.#knex.schema.alterTable(name, table => {
|
|
347
|
-
table.index('is_public', `${name}_idx_is_public`);
|
|
348
|
-
});
|
|
349
|
-
migrations.push(`Added index '${name}_idx_is_public'`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
// Add missing columns
|
|
354
|
-
for (const [_columnName, config] of Object.entries(expectedColumns)) {
|
|
355
|
-
if (!config.exists) {
|
|
356
|
-
await config.add();
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
// Add missing indexes
|
|
360
|
-
for (const [_indexName, config] of Object.entries(expectedIndexes)) {
|
|
361
|
-
if (!config.exists) {
|
|
362
|
-
await config.add();
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return migrations;
|
|
366
|
-
}
|
|
367
|
-
/**
|
|
368
|
-
* Check if an index exists on a table
|
|
369
|
-
* @private
|
|
370
|
-
*/
|
|
371
|
-
async #hasIndex(tableName, indexName) {
|
|
372
|
-
try {
|
|
373
|
-
// PostgreSQL
|
|
374
|
-
if (this.#knex.client.config.client === 'pg') {
|
|
375
|
-
const result = await this.#knex.raw(`SELECT 1 FROM pg_indexes WHERE tablename = ? AND indexname = ?`, [
|
|
376
|
-
tableName,
|
|
377
|
-
indexName
|
|
378
|
-
]);
|
|
379
|
-
return result.rows.length > 0;
|
|
380
|
-
}
|
|
381
|
-
// SQLite - query sqlite_master
|
|
382
|
-
if (this.#knex.client.config.client === 'sqlite3' || this.#knex.client.config.client === 'better-sqlite3') {
|
|
383
|
-
const result = await this.#knex.raw(`SELECT name FROM sqlite_master WHERE type='index' AND name=?`, [
|
|
384
|
-
indexName
|
|
385
|
-
]);
|
|
386
|
-
return result.length > 0;
|
|
387
|
-
}
|
|
388
|
-
// Unknown database, assume index doesn't exist
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
catch {
|
|
392
|
-
// If query fails, assume index doesn't exist
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
// ========== Extended methods ==========
|
|
397
|
-
async getFirstByName(name) {
|
|
398
|
-
const row = await this.#knex(name).orderBy('date', 'asc').first();
|
|
399
|
-
return row ? mapToDataRecord(row, this.#storage) : undefined;
|
|
400
|
-
}
|
|
401
|
-
async getByDateRange(name, startDate, endDate, limit) {
|
|
402
|
-
let query = this.#knex(name).select('*').where('date', '>=', startDate.toISOString());
|
|
403
|
-
if (endDate) {
|
|
404
|
-
query = query.where('date', '<', endDate.toISOString());
|
|
405
|
-
}
|
|
406
|
-
query = query.orderBy('date', 'asc');
|
|
407
|
-
if (limit) {
|
|
408
|
-
query = query.limit(limit);
|
|
409
|
-
}
|
|
410
|
-
const rows = await query;
|
|
411
|
-
return rows.map(row => mapToDataRecord(row, this.#storage));
|
|
412
|
-
}
|
|
413
|
-
async getAfterDate(name, afterDate, limit) {
|
|
414
|
-
let query = this.#knex(name).where('date', '>', afterDate.toISOString()).orderBy('date', 'asc');
|
|
415
|
-
if (limit) {
|
|
416
|
-
query = query.limit(limit);
|
|
417
|
-
}
|
|
418
|
-
const rows = await query;
|
|
419
|
-
return rows.map(row => mapToDataRecord(row, this.#storage));
|
|
420
|
-
}
|
|
421
|
-
async getLatestBefore(name, beforeDate) {
|
|
422
|
-
const row = await this.#knex(name).where('date', '<', beforeDate.toISOString()).orderBy('date', 'desc').first();
|
|
423
|
-
return row ? mapToDataRecord(row, this.#storage) : undefined;
|
|
424
|
-
}
|
|
425
|
-
async getLatestRecordsBefore(name, beforeDate, limit) {
|
|
426
|
-
const rows = await this.#knex(name)
|
|
427
|
-
.where('date', '<', beforeDate.toISOString())
|
|
428
|
-
.orderBy('date', 'desc')
|
|
429
|
-
.limit(limit);
|
|
430
|
-
return rows.map(row => mapToDataRecord(row, this.#storage));
|
|
431
|
-
}
|
|
432
|
-
async hasRecordsAfterDate(name, afterDate) {
|
|
433
|
-
const result = await this.#knex(name)
|
|
434
|
-
.where('date', '>', afterDate.toISOString())
|
|
435
|
-
.select(this.#knex.raw('1'))
|
|
436
|
-
.limit(1)
|
|
437
|
-
.first();
|
|
438
|
-
return !!result;
|
|
439
|
-
}
|
|
440
|
-
async countByDateRange(name, startDate, endDate) {
|
|
441
|
-
let query = this.#knex(name).where('date', '>=', startDate.toISOString());
|
|
442
|
-
if (endDate) {
|
|
443
|
-
query = query.where('date', '<', endDate.toISOString());
|
|
444
|
-
}
|
|
445
|
-
const result = await query.count('* as count').first();
|
|
446
|
-
return Number(result?.count) || 0;
|
|
447
|
-
}
|
|
448
|
-
// ========== Batch operations for performance ==========
|
|
449
|
-
async saveBatch(metadataList) {
|
|
450
|
-
if (metadataList.length === 0)
|
|
451
|
-
return [];
|
|
452
|
-
// Group by table name for efficient batch inserts
|
|
453
|
-
const groupedByTable = new Map();
|
|
454
|
-
for (const meta of metadataList) {
|
|
455
|
-
const group = groupedByTable.get(meta.name);
|
|
456
|
-
if (group) {
|
|
457
|
-
group.push(meta);
|
|
458
|
-
}
|
|
459
|
-
else {
|
|
460
|
-
groupedByTable.set(meta.name, [meta]);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const results = [];
|
|
464
|
-
// Process each table in a transaction for consistency
|
|
465
|
-
for (const [tableName, metas] of groupedByTable) {
|
|
466
|
-
const insertData = metas.map(meta => {
|
|
467
|
-
const data = {
|
|
468
|
-
name: meta.name,
|
|
469
|
-
type: meta.type,
|
|
470
|
-
url: meta.url,
|
|
471
|
-
date: meta.date.toISOString()
|
|
472
|
-
};
|
|
473
|
-
// Only include ID if it's explicitly set (for updates)
|
|
474
|
-
if (meta.id !== undefined) {
|
|
475
|
-
data.id = meta.id;
|
|
476
|
-
}
|
|
477
|
-
// Add asset-specific fields if present
|
|
478
|
-
if ('description' in meta)
|
|
479
|
-
data.description = meta.description;
|
|
480
|
-
if ('source' in meta)
|
|
481
|
-
data.source = meta.source;
|
|
482
|
-
if ('owner_id' in meta)
|
|
483
|
-
data.owner_id = meta.owner_id;
|
|
484
|
-
if ('filename' in meta)
|
|
485
|
-
data.filename = meta.filename;
|
|
486
|
-
return data;
|
|
487
|
-
});
|
|
488
|
-
await this.#knex(tableName).insert(insertData);
|
|
489
|
-
// Convert to DataRecords
|
|
490
|
-
for (const meta of metas) {
|
|
491
|
-
results.push(mapToDataRecord(meta, this.#storage));
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
return results;
|
|
495
|
-
}
|
|
496
|
-
async deleteBatch(deleteRequests) {
|
|
497
|
-
if (deleteRequests.length === 0)
|
|
498
|
-
return;
|
|
499
|
-
// Group by table name for efficient batch deletes
|
|
500
|
-
const groupedByTable = new Map();
|
|
501
|
-
for (const req of deleteRequests) {
|
|
502
|
-
const group = groupedByTable.get(req.name);
|
|
503
|
-
if (group) {
|
|
504
|
-
group.push(req.id);
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
groupedByTable.set(req.name, [req.id]);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
// Process each table
|
|
511
|
-
for (const [tableName, ids] of groupedByTable) {
|
|
512
|
-
await this.#knex(tableName).whereIn('id', ids).delete();
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
async getByIdsBatch(requests) {
|
|
516
|
-
if (requests.length === 0)
|
|
517
|
-
return [];
|
|
518
|
-
const results = [];
|
|
519
|
-
// Group by table name for efficient queries
|
|
520
|
-
const groupedByTable = new Map();
|
|
521
|
-
for (const req of requests) {
|
|
522
|
-
const group = groupedByTable.get(req.name);
|
|
523
|
-
if (group) {
|
|
524
|
-
group.push(req.id);
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
groupedByTable.set(req.name, [req.id]);
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// Query each table
|
|
531
|
-
for (const [tableName, ids] of groupedByTable) {
|
|
532
|
-
const rows = await this.#knex(tableName).whereIn('id', ids);
|
|
533
|
-
for (const row of rows) {
|
|
534
|
-
results.push(mapToDataRecord(row, this.#storage));
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
return results;
|
|
538
|
-
}
|
|
539
|
-
// ========== Optimized query for assets manager ==========
|
|
540
|
-
async getAllAssetsPaginated(name, offset = 0, limit = 100) {
|
|
541
|
-
// Get total count efficiently
|
|
542
|
-
const countResult = await this.#knex(name).count('* as count').first();
|
|
543
|
-
const total = Number(countResult?.count) || 0;
|
|
544
|
-
// Get paginated results
|
|
545
|
-
const rows = await this.#knex(name).select('*').orderBy('date', 'desc').offset(offset).limit(limit);
|
|
546
|
-
const records = rows.map(row => mapToDataRecord(row, this.#storage));
|
|
547
|
-
return { records, total };
|
|
548
|
-
}
|
|
549
|
-
// ========== Methods for CustomTableManager ==========
|
|
550
|
-
async findByConditions(tableName, conditions) {
|
|
551
|
-
let query = this.#knex(tableName).select('*');
|
|
552
|
-
// Apply each condition
|
|
553
|
-
for (const [column, value] of Object.entries(conditions)) {
|
|
554
|
-
if (value === null) {
|
|
555
|
-
query = query.whereNull(column);
|
|
556
|
-
}
|
|
557
|
-
else if (value === undefined) {
|
|
558
|
-
// Skip undefined values
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
else {
|
|
562
|
-
query = query.where(column, value);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
// Check if table has 'date' column, otherwise use 'created_at'
|
|
566
|
-
const hasDateColumn = await this.#knex.schema.hasColumn(tableName, 'date');
|
|
567
|
-
const sortColumn = hasDateColumn ? 'date' : 'created_at';
|
|
568
|
-
const rows = await query.orderBy(sortColumn, 'desc');
|
|
569
|
-
return rows.map(row => mapToDataRecord(row, this.#storage));
|
|
570
|
-
}
|
|
571
|
-
async updateById(tableName, id, data) {
|
|
572
|
-
// Create a clean update object with updated_at timestamp
|
|
573
|
-
const updateData = {
|
|
574
|
-
...data,
|
|
575
|
-
updated_at: new Date()
|
|
576
|
-
};
|
|
577
|
-
// Remove system fields that shouldn't be updated
|
|
578
|
-
delete updateData.id;
|
|
579
|
-
delete updateData.created_at;
|
|
580
|
-
delete updateData.date;
|
|
581
|
-
// Serialize file_index to JSON string if present (stored as TEXT in DB)
|
|
582
|
-
if ('file_index' in updateData && updateData.file_index) {
|
|
583
|
-
updateData.file_index =
|
|
584
|
-
typeof updateData.file_index === 'string'
|
|
585
|
-
? updateData.file_index
|
|
586
|
-
: JSON.stringify(updateData.file_index);
|
|
587
|
-
}
|
|
588
|
-
const rowsAffected = await this.#knex(tableName).where({ id }).update(updateData);
|
|
589
|
-
if (rowsAffected === 0) {
|
|
590
|
-
throw new Error(`No record found with ID ${id} in table ${tableName}`);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
async close() {
|
|
594
|
-
await this.#knex.destroy();
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Find records for custom tables (returns raw database rows, not DataRecords)
|
|
598
|
-
* This bypasses mapToDataRecord() which assumes standard table structure
|
|
599
|
-
*/
|
|
600
|
-
async findCustomTableRecords(tableName, conditions = {}) {
|
|
601
|
-
let query = this.#knex(tableName).select('*');
|
|
602
|
-
// Apply each condition
|
|
603
|
-
for (const [column, value] of Object.entries(conditions)) {
|
|
604
|
-
if (value === null) {
|
|
605
|
-
query = query.whereNull(column);
|
|
606
|
-
}
|
|
607
|
-
else if (value === undefined) {
|
|
608
|
-
// Skip undefined values
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
query = query.where(column, value);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
// Always sort by created_at for custom tables
|
|
616
|
-
const rows = await query.orderBy('created_at', 'desc');
|
|
617
|
-
return rows;
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Get a single custom table record by ID (returns raw database row, not DataRecord)
|
|
621
|
-
*/
|
|
622
|
-
async getCustomTableRecordById(tableName, id) {
|
|
623
|
-
const row = await this.#knex(tableName).where({ id }).first();
|
|
624
|
-
return row || null;
|
|
625
|
-
}
|
|
626
|
-
/**
|
|
627
|
-
* Insert a record into a custom table (returns the new record ID)
|
|
628
|
-
*/
|
|
629
|
-
async insertCustomTableRecord(tableName, data) {
|
|
630
|
-
const now = new Date();
|
|
631
|
-
const insertData = {
|
|
632
|
-
...data,
|
|
633
|
-
created_at: now,
|
|
634
|
-
updated_at: now
|
|
635
|
-
};
|
|
636
|
-
const result = await this.#knex(tableName).insert(insertData).returning('id');
|
|
637
|
-
const insertedId = result[0];
|
|
638
|
-
return typeof insertedId === 'object' ? insertedId.id : insertedId;
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Get the underlying Knex instance for advanced operations
|
|
642
|
-
*/
|
|
643
|
-
getKnex() {
|
|
644
|
-
return this.#knex;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
//# sourceMappingURL=knex_database_adapter.js.map
|