streamby-core 0.27.2 → 0.27.4

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.
@@ -1,12 +1,7 @@
1
1
  import { MongoClient, Document } from 'mongodb';
2
- import { FieldDefinition, NodeSchema } from '../../types';
2
+ import { NodeSchema } from '../../types';
3
3
  export declare const ensureCollectionsExist: (client: MongoClient) => Promise<void>;
4
- export declare const createNoSQLExportCollection: (connection: MongoClient, projectId: string, exportName: string, fields: FieldDefinition[]) => Promise<{
5
- collectionName: string;
6
- exportId: string;
7
- }>;
8
- export declare const createNoSQLRawExportCollection: (connection: MongoClient, projectId: string, exportName: string, method: string, nodeSchema?: NodeSchema) => Promise<{
9
- collectionName: string;
4
+ export declare const createNoSQLExportCollection: (connection: MongoClient, projectId: string, exportName: string, method: string, nodeSchema?: NodeSchema) => Promise<{
10
5
  exportId: string;
11
6
  }>;
12
7
  export declare const nosqlAdapter: {
@@ -1,45 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.nosqlAdapter = exports.createNoSQLRawExportCollection = exports.createNoSQLExportCollection = exports.ensureCollectionsExist = void 0;
3
+ exports.nosqlAdapter = exports.createNoSQLExportCollection = exports.ensureCollectionsExist = void 0;
4
4
  const mongodb_1 = require("mongodb");
5
5
  const ensureCollectionsExist = async (client) => {
6
6
  const db = client.db();
7
- // Ensure 'projects' collection exists and define its schema implicitly
8
- // Schema: { _id: ObjectId, name: string, description: string, dbType: string, createdAt: Date, updatedAt: Date, members: [{ userId: string, archived: boolean }] }
9
- const projectsCollection = db.collection('projects');
10
- await projectsCollection.findOne({}); // Attempt to find one to ensure collection is created if it doesn't exist
7
+ await db.collection('projects').findOne({});
11
8
  console.log('✅ "projects" collection ensured to exist.');
12
- // Ensure 'exports' collection exists and define its schema implicitly
13
- // Schema: { _id: ObjectId, projectId: ObjectId, status: string, filePath: string, createdAt: Date, updatedAt: Date }
14
- const exportsCollection = db.collection('exports');
15
- await exportsCollection.findOne({}); // Attempt to find one to ensure collection is created if it doesn't exist
9
+ await db.collection('exports').findOne({});
16
10
  console.log('✅ "exports" collection ensured to exist.');
11
+ await db.collection('records').findOne({});
12
+ console.log('✅ "records" collection ensured to exist.');
13
+ await db.collection('_tables').findOne({});
14
+ console.log('✅ "_tables" collection ensured to exist.');
17
15
  };
18
16
  exports.ensureCollectionsExist = ensureCollectionsExist;
19
- const createNoSQLExportCollection = async (connection, projectId, exportName, fields) => {
17
+ const createNoSQLExportCollection = async (connection, projectId, exportName, method, nodeSchema) => {
20
18
  const db = connection.db();
21
- const slug = exportName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
22
- const collectionName = `export_${projectId}_${slug}`;
23
- // Create the collection implicitly by inserting the metadata document
24
- const metadataDocument = {
25
- __metadata: {
26
- fields,
27
- createdAt: new Date(),
28
- updatedAt: new Date(),
29
- },
30
- };
31
- await db.collection(collectionName).insertOne(metadataDocument);
32
- console.log(`✅ Collection '${collectionName}' created with metadata.`);
33
- return { collectionName, exportId: collectionName };
19
+ const result = await db.collection('exports').insertOne({
20
+ projectId,
21
+ name: exportName,
22
+ method,
23
+ nodeSchema,
24
+ createdAt: new Date(),
25
+ updatedAt: new Date(),
26
+ });
27
+ return { exportId: result.insertedId.toHexString() };
34
28
  };
35
29
  exports.createNoSQLExportCollection = createNoSQLExportCollection;
36
- const createNoSQLRawExportCollection = async (connection, projectId, exportName, method, nodeSchema) => {
37
- const db = connection.db();
38
- const collectionName = projectId;
39
- const result = await db.collection(collectionName).insertOne({ nodeSchema, name: exportName, method, createdAt: new Date(), updatedAt: new Date() });
40
- return { collectionName, exportId: result.insertedId.toHexString() };
41
- };
42
- exports.createNoSQLRawExportCollection = createNoSQLRawExportCollection;
43
30
  exports.nosqlAdapter = {
44
31
  find: async (connection, tableName, filter) => {
45
32
  const db = connection.db();
@@ -1,11 +1,5 @@
1
1
  import { Pool } from 'pg';
2
- import { FieldDefinition } from '../../types';
3
- export declare const createSQLExportTable: (connection: Pool, projectId: string, exportName: string, fields: FieldDefinition[]) => Promise<{
4
- collectionName: string;
5
- exportId: string;
6
- }>;
7
- export declare const createSQLRawExportTable: (connection: Pool, projectId: string, exportName: string, nodeSchema?: any) => Promise<{
8
- collectionName: string;
2
+ export declare const createSQLExportTable: (connection: Pool, projectId: string, exportName: string, nodeSchema?: any) => Promise<{
9
3
  exportId: string;
10
4
  }>;
11
5
  export declare const sqlAdapter: {
@@ -1,51 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sqlAdapter = exports.createSQLRawExportTable = exports.createSQLExportTable = void 0;
3
+ exports.sqlAdapter = exports.createSQLExportTable = void 0;
4
4
  const crypto_1 = require("crypto");
5
- const createSQLExportTable = async (connection, projectId, exportName, fields) => {
6
- const slug = exportName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
7
- const tableName = `export_${projectId}_${slug}`;
8
- const columns = fields.map(field => `"${field.name}" ${field.type}`).join(',');
9
- const createTableQuery = `
10
- CREATE TABLE IF NOT EXISTS "${tableName}" (
11
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
12
- "projectId" UUID NOT NULL,
13
- __metadata JSONB NOT NULL,
14
- ${columns ? `, ${columns}` : ''}
15
- );
16
- `;
17
- await connection.query(createTableQuery);
18
- console.log(`✅ Table '${tableName}' created with metadata.`);
19
- const metadata = {
20
- fields,
21
- createdAt: new Date(),
22
- updatedAt: new Date(),
23
- };
24
- // Insert metadata as the first row, or a dedicated metadata row
25
- // For simplicity, we'll just create the table with the metadata column.
26
- // Actual metadata insertion for validation would happen on data inserts.
27
- return { collectionName: tableName, exportId: tableName };
28
- };
29
- exports.createSQLExportTable = createSQLExportTable;
30
- const createSQLRawExportTable = async (connection, projectId, exportName, nodeSchema) => {
31
- const tableName = projectId;
5
+ function serializeForPg(v) {
6
+ if (Array.isArray(v) || (v !== null && typeof v === 'object' && !(v instanceof Date))) {
7
+ return JSON.stringify(v);
8
+ }
9
+ return v;
10
+ }
11
+ const createSQLExportTable = async (connection, projectId, exportName, nodeSchema) => {
32
12
  const exportId = (0, crypto_1.randomUUID)();
33
- const createTableQuery = `
34
- CREATE TABLE IF NOT EXISTS "${tableName}" (
35
- id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
36
- node_schema JSONB,
37
- name TEXT,
38
- method TEXT,
39
- created_at TIMESTAMPTZ DEFAULT NOW(),
40
- updated_at TIMESTAMPTZ DEFAULT NOW()
41
- );
42
- `;
43
- await connection.query(createTableQuery);
44
- await connection.query(`INSERT INTO "${tableName}" (id, node_schema, name, method) VALUES ($1, $2, $3, $4)`, [exportId, nodeSchema ? JSON.stringify(nodeSchema) : null, exportName, 'GET']);
45
- console.log(`✅ Raw table '${tableName}' created.`);
46
- return { collectionName: tableName, exportId };
13
+ await connection.query(`INSERT INTO "exports" (id, "projectId", node_schema, name, method, created_at, updated_at)
14
+ VALUES ($1, $2, $3, $4, 'GET', NOW(), NOW())`, [exportId, projectId, nodeSchema ? JSON.stringify(nodeSchema) : null, exportName]);
15
+ return { exportId };
47
16
  };
48
- exports.createSQLRawExportTable = createSQLRawExportTable;
17
+ exports.createSQLExportTable = createSQLExportTable;
49
18
  exports.sqlAdapter = {
50
19
  find: async (connection, tableName, filter, schema) => {
51
20
  const keys = Object.keys(filter);
@@ -69,7 +38,7 @@ exports.sqlAdapter = {
69
38
  },
70
39
  create: async (connection, tableName, data, schema) => {
71
40
  const keys = Object.keys(data).map(key => `"${key}"`).join(', ');
72
- const values = Object.values(data);
41
+ const values = Object.values(data).map(serializeForPg);
73
42
  const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
74
43
  const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
75
44
  const result = await connection.query(`INSERT INTO ${fullTableName} (${keys}) VALUES (${placeholders}) RETURNING *`, values);
@@ -78,7 +47,7 @@ exports.sqlAdapter = {
78
47
  update: async (connection, tableName, filter, data, schema) => {
79
48
  const dataKeys = Object.keys(data);
80
49
  const filterKeys = Object.keys(filter);
81
- const values = [...Object.values(data), ...Object.values(filter)];
50
+ const values = [...Object.values(data).map(serializeForPg), ...Object.values(filter)];
82
51
  const setClause = dataKeys.map((key, i) => `"${key}" = $${i + 1}`).join(', ');
83
52
  const whereClause = filterKeys.map((key, i) => `"${key}" = $${dataKeys.length + i + 1}`).join(' AND ');
84
53
  const fullTableName = schema ? `"${schema}"."${tableName}"` : `"${tableName}"`;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getExportRoute = getExportRoute;
4
4
  function getExportRoute(req, res, next) {
5
- if (req.path.includes('/get-export/')) {
5
+ if (req.path.includes('/export/')) {
6
6
  return next();
7
7
  }
8
8
  return next();
@@ -84,12 +84,11 @@ function exportRouter(config) {
84
84
  };
85
85
  }
86
86
  else if (exportMetadata.type === 'json') {
87
- const rawData = await db.collection(exportMetadata.collectionName).findOne({ _id: new mongodb_1.ObjectId(exportId) });
87
+ const rawData = await db.collection('exports').findOne({ _id: new mongodb_1.ObjectId(exportId) });
88
88
  data = {
89
89
  json: rawData?.json,
90
90
  name: rawData?.name,
91
91
  method: rawData?.method,
92
- collectionName: rawData?.collectionName,
93
92
  createdAt: rawData?.createdAt,
94
93
  updatedAt: rawData?.updatedAt,
95
94
  type: exportMetadata.type,
@@ -98,7 +97,7 @@ function exportRouter(config) {
98
97
  };
99
98
  }
100
99
  else if (exportMetadata.type === 'externalApi') {
101
- const ProjectModel = (0, manager_1.getModel)('projects', 'nosql');
100
+ const ProjectModel = (0, manager_1.getModel)('projects');
102
101
  const currentProject = await ProjectModel.findOne({ _id: projectId });
103
102
  if (!currentProject) {
104
103
  return res.status(404).json({ message: 'Project not found.' });
@@ -144,14 +143,13 @@ function exportRouter(config) {
144
143
  };
145
144
  }
146
145
  else {
147
- const rawData = await db.collection(exportMetadata.collectionName).findOne({ _id: new mongodb_1.ObjectId(exportId) });
146
+ const rawData = await db.collection('exports').findOne({ _id: new mongodb_1.ObjectId(exportId) });
148
147
  data = {
149
148
  json: rawData?.json,
150
149
  name: rawData?.name,
151
150
  method: rawData?.method,
152
151
  createdAt: rawData?.createdAt,
153
152
  updatedAt: rawData?.updatedAt,
154
- collectionName: rawData?.collectionName,
155
153
  type: exportMetadata.type,
156
154
  credentialId: exportMetadata.credentialId,
157
155
  description: rawData?.description,
@@ -160,7 +158,33 @@ function exportRouter(config) {
160
158
  }
161
159
  }
162
160
  else if (project.dbType === 'sql') {
163
- // ... SQL implementation needed
161
+ const pool = connection.client;
162
+ if (exportMetadata.nodeSchema) {
163
+ data = {
164
+ name: exportMetadata.name,
165
+ description: exportMetadata.description,
166
+ nodeSchema: exportMetadata.nodeSchema,
167
+ useConnections: exportMetadata.useConnections,
168
+ useCredentials: exportMetadata.useCredentials,
169
+ type: exportMetadata.type,
170
+ };
171
+ }
172
+ else {
173
+ const result = await pool.query(`SELECT * FROM "exports" WHERE id = $1`, [exportId]);
174
+ const rawData = result.rows[0];
175
+ if (rawData) {
176
+ data = {
177
+ json: rawData.json,
178
+ name: rawData.name,
179
+ method: rawData.method,
180
+ createdAt: rawData.created_at,
181
+ updatedAt: rawData.updated_at,
182
+ type: exportMetadata.type,
183
+ fields: exportMetadata.fields,
184
+ description: rawData.description,
185
+ };
186
+ }
187
+ }
164
188
  }
165
189
  if (!data) {
166
190
  return res.status(404).json({ message: 'Export data not found' });
@@ -253,14 +277,14 @@ function exportRouter(config) {
253
277
  if (!exportMetadata) {
254
278
  return res.status(404).json({ message: 'Export not found in this project' });
255
279
  }
256
- await (0, export_1.deleteExport)(config, projectId, exportId, project.dbType, exportMetadata.name);
280
+ await (0, export_1.deleteExport)(config, projectId, exportId, project.dbType);
257
281
  res.status(200).json({ message: 'Export deleted successfully' });
258
282
  }
259
283
  catch (err) {
260
284
  res.status(500).json({ message: 'Failed to delete export', details: err.message });
261
285
  }
262
286
  });
263
- router.get('/:projectId/get-export/:exportName', async (req, res) => {
287
+ router.get('/:projectId/export/:exportName', async (req, res) => {
264
288
  try {
265
289
  const { projectId, exportName } = req.params;
266
290
  const project = await Project.findOne({ _id: projectId });
@@ -318,64 +342,61 @@ function exportRouter(config) {
318
342
  let data;
319
343
  if (exportMetadata.nodeSchema) {
320
344
  data = await (0, pipeline_1.executePipeline)(exportMetadata.nodeSchema, project, config);
345
+ return res.json(data ?? null);
321
346
  }
322
- else {
323
- const targetDb = config.databases?.find(db => db.type === project.dbType);
324
- if (!targetDb) {
325
- return res.status(500).json({ message: `Database connection not found for type ${project.dbType}` });
347
+ // Legacy path: raw JSON or external API export
348
+ const targetDb = config.databases?.find(db => db.type === project.dbType);
349
+ if (!targetDb) {
350
+ return res.status(500).json({ message: `Database connection not found for type ${project.dbType}` });
351
+ }
352
+ const connection = (0, connectionManager_1.getConnection)(targetDb.id);
353
+ if (project.dbType === 'nosql') {
354
+ const db = connection.client.db();
355
+ if (exportMetadata.type === 'json') {
356
+ const rawData = await db.collection('exports').findOne({ _id: new mongodb_1.ObjectId(exportMetadata.id.toString()) });
357
+ data = rawData ? rawData.json : null;
326
358
  }
327
- const connection = (0, connectionManager_1.getConnection)(targetDb.id);
328
- if (project.dbType === 'nosql') {
329
- const db = connection.client.db();
330
- if (exportMetadata.type === 'json') {
331
- const rawData = await db.collection(exportMetadata.collectionName).findOne({ _id: new mongodb_1.ObjectId(exportMetadata.id) });
332
- data = rawData ? rawData.json : null;
333
- }
334
- else if (exportMetadata.type === 'externalApi') {
335
- let headers = {};
336
- if (exportMetadata.credentialId) {
337
- if (!(0, encryption_1.isEncryptionKeySet)()) {
338
- throw new Error('Encryption key is not set. Cannot use encrypted credentials.');
339
- }
340
- const credential = project.credentials?.find((cred) => cred.id === exportMetadata.credentialId);
341
- if (!credential) {
342
- throw new Error(`Credential with ID ${exportMetadata.credentialId} not found.`);
343
- }
344
- const decryptedValue = (0, encryption_1.decrypt)(credential.encryptedValue);
345
- const authPrefix = exportMetadata.prefix ? `${exportMetadata.prefix} ` : '';
346
- headers = {
347
- 'Authorization': `${authPrefix}${decryptedValue}`,
348
- 'Content-Type': 'application/json',
349
- };
350
- }
351
- try {
352
- const response = await fetch(exportMetadata.apiUrl, { headers });
353
- if (!response.ok) {
354
- throw new Error(`Failed to fetch from external API: ${response.statusText}`);
355
- }
356
- data = await response.json();
359
+ else if (exportMetadata.type === 'externalApi') {
360
+ let headers = {};
361
+ if (exportMetadata.credentialId) {
362
+ if (!(0, encryption_1.isEncryptionKeySet)()) {
363
+ throw new Error('Encryption key is not set. Cannot use encrypted credentials.');
357
364
  }
358
- catch (error) {
359
- throw new Error(`Error fetching from external API: ${error.message}`);
365
+ const credential = project.credentials?.find((cred) => cred.id === exportMetadata.credentialId);
366
+ if (!credential) {
367
+ throw new Error(`Credential with ID ${exportMetadata.credentialId} not found.`);
360
368
  }
361
- if (exportMetadata.fields && exportMetadata.fields.length > 0) {
362
- data = data.map((item) => {
363
- return exportMetadata.fields.reduce((filtered, field) => {
364
- filtered[field.name] = item[field.name];
365
- return filtered;
366
- }, {});
367
- });
369
+ const decryptedValue = (0, encryption_1.decrypt)(credential.encryptedValue);
370
+ const authPrefix = exportMetadata.prefix ? `${exportMetadata.prefix} ` : '';
371
+ headers = {
372
+ 'Authorization': `${authPrefix}${decryptedValue}`,
373
+ 'Content-Type': 'application/json',
374
+ };
375
+ }
376
+ try {
377
+ const response = await fetch(exportMetadata.apiUrl, { headers });
378
+ if (!response.ok) {
379
+ throw new Error(`Failed to fetch from external API: ${response.statusText}`);
368
380
  }
381
+ data = await response.json();
382
+ }
383
+ catch (error) {
384
+ throw new Error(`Error fetching from external API: ${error.message}`);
385
+ }
386
+ if (exportMetadata.fields && exportMetadata.fields.length > 0) {
387
+ data = data.map((item) => {
388
+ return exportMetadata.fields.reduce((filtered, field) => {
389
+ filtered[field.name] = item[field.name];
390
+ return filtered;
391
+ }, {});
392
+ });
369
393
  }
370
394
  }
371
- else if (project.dbType === 'sql') {
372
- return res.status(501).json({ message: 'SQL public exports not yet implemented' });
373
- }
374
- } // end legacy else block
375
- if (data === null || data === undefined) {
376
- return res.status(404).json({ message: 'Export data not found' });
377
395
  }
378
- res.json(data);
396
+ else if (project.dbType === 'sql') {
397
+ return res.status(501).json({ message: 'SQL public exports not yet implemented' });
398
+ }
399
+ res.json(data ?? null);
379
400
  }
380
401
  catch (err) {
381
402
  res.status(500).json({ message: 'Failed to fetch public export data', details: err.message });
@@ -129,6 +129,11 @@ class Model {
129
129
  archived: member.archived,
130
130
  }));
131
131
  }
132
+ // Track which connection found this record so update() routes correctly
133
+ try {
134
+ Object.defineProperty(transformedResult, '__foundByConnectionId', { value: connectionId, enumerable: false, writable: true });
135
+ }
136
+ catch { }
132
137
  return transformedResult;
133
138
  }
134
139
  }
@@ -144,8 +149,13 @@ class Model {
144
149
  }
145
150
  }
146
151
  const result = await nosql_1.nosqlAdapter.findOne(connection, this.tableName, processedFilter);
147
- if (result)
152
+ if (result) {
153
+ try {
154
+ Object.defineProperty(result, '__foundByConnectionId', { value: connectionId, enumerable: false, writable: true });
155
+ }
156
+ catch { }
148
157
  return result;
158
+ }
149
159
  }
150
160
  }
151
161
  return null;
@@ -219,14 +229,17 @@ class Model {
219
229
  if (!existingProject) {
220
230
  return null; // Project not found
221
231
  }
222
- const targetDbType = existingProject.dbType; // Get the actual dbType of the project
223
- // Find the connection that matches the targetDbType (only among active connections)
224
232
  const activeConnectionIds = this.connectionIds.filter(id => (0, connectionManager_1.getConnectedIds)().includes(id));
225
- const clientEntry = activeConnectionIds
226
- .map(id => (0, connectionManager_1.getConnection)(id))
227
- .find(entry => entry.type === targetDbType);
233
+ // Use the connection where findOne actually found the record, not project.dbType.
234
+ // project.dbType is the DATA db type (where the project's collections live), not the
235
+ // STORAGE db type (where the project document itself lives). Using dbType here would
236
+ // route SQL-stored projects with dbType:'nosql' to MongoDB, causing update failures.
237
+ const storageConnectionId = existingProject.__foundByConnectionId;
238
+ const clientEntry = (storageConnectionId && activeConnectionIds.includes(storageConnectionId))
239
+ ? (0, connectionManager_1.getConnection)(storageConnectionId)
240
+ : activeConnectionIds.map(id => (0, connectionManager_1.getConnection)(id)).find(entry => entry.type === existingProject.dbType);
228
241
  if (!clientEntry) {
229
- throw new Error(`No active connection found for database type: ${targetDbType}`);
242
+ throw new Error(`No active connection found for database type: ${existingProject.dbType}`);
230
243
  }
231
244
  const connection = clientEntry.client;
232
245
  const dbType = clientEntry.type; // This will be the targetDbType
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.authenticate = void 0;
4
4
  exports.checkRole = checkRole;
5
5
  const authenticate = (config) => async (req, res, next) => {
6
- if (req.path.includes('/get-export/')) {
6
+ if (req.path.includes('/export/')) {
7
7
  return next();
8
8
  }
9
9
  try {
@@ -10,7 +10,7 @@ async function addCredential(config, projectId, newCredential) {
10
10
  if (!(0, encryption_1.isEncryptionKeySet)()) {
11
11
  throw new Error('Encryption key is not set. Cannot add credentials.');
12
12
  }
13
- const ProjectModel = (0, manager_1.getModel)('projects', 'nosql');
13
+ const ProjectModel = (0, manager_1.getModel)('projects');
14
14
  const project = await ProjectModel.findOne({ _id: projectId });
15
15
  if (!project) {
16
16
  throw new Error('Project not found.');
@@ -31,7 +31,7 @@ async function updateCredential(config, projectId, credentialId, updates) {
31
31
  if (!(0, encryption_1.isEncryptionKeySet)()) {
32
32
  throw new Error('Encryption key is not set. Cannot update credentials.');
33
33
  }
34
- const ProjectModel = (0, manager_1.getModel)('projects', 'nosql');
34
+ const ProjectModel = (0, manager_1.getModel)('projects');
35
35
  const project = await ProjectModel.findOne({ _id: projectId });
36
36
  if (!project) {
37
37
  throw new Error('Project not found.');
@@ -57,7 +57,7 @@ async function updateCredential(config, projectId, credentialId, updates) {
57
57
  return updatedProject;
58
58
  }
59
59
  async function deleteCredential(config, projectId, credentialId) {
60
- const ProjectModel = (0, manager_1.getModel)('projects', 'nosql');
60
+ const ProjectModel = (0, manager_1.getModel)('projects');
61
61
  const project = await ProjectModel.findOne({ _id: projectId });
62
62
  if (!project) {
63
63
  throw new Error('Project not found.');
@@ -172,10 +172,16 @@ async function deleteRecord(connectionString, dbType, tableName, recordId) {
172
172
  }
173
173
  // ─── Internal (pool-based) variants ──────────────────────────────────────────
174
174
  // These operate on already-connected Pool / MongoClient from connectionManager.
175
+ //
176
+ // nosql storage pattern: all user records go into a single 'records' collection
177
+ // with _projectId and _tableName metadata fields. Table membership is tracked in
178
+ // a separate '_tables' collection. SQL keeps the legacy db_{projectId}_{tableName}
179
+ // table-per-collection pattern which is appropriate for relational databases.
175
180
  function quoteSqlTable(tableName) {
176
181
  const parts = tableName.split('.');
177
182
  return parts.length === 2 ? `"${parts[0]}"."${parts[1]}"` : `"${tableName}"`;
178
183
  }
184
+ const RECORD_META_PROJECTION = { _projectId: 0, _tableName: 0 };
179
185
  async function listTablesInternal(client, dbType, projectId) {
180
186
  const prefix = projectId ? `db_${projectId}_` : null;
181
187
  if (dbType === 'sql') {
@@ -190,40 +196,54 @@ async function listTablesInternal(client, dbType, projectId) {
190
196
  return prefix ? full.slice(prefix.length) : full;
191
197
  });
192
198
  }
193
- const cols = await client.db().listCollections().toArray();
194
- const names = cols.map(c => c.name).sort();
195
- if (!prefix)
196
- return names;
197
- return names.filter(n => n.startsWith(prefix)).map(n => n.slice(prefix.length));
199
+ const db = client.db();
200
+ if (!projectId) {
201
+ const cols = await db.listCollections().toArray();
202
+ return cols.map(c => c.name).sort();
203
+ }
204
+ const tables = await db.collection('_tables').find({ _projectId: projectId }).toArray();
205
+ return tables.map((t) => t.tableName).sort();
198
206
  }
199
207
  async function queryRecordsInternal(client, dbType, tableName, limit = 50, offset = 0, projectId) {
200
- const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
201
208
  if (dbType === 'sql') {
209
+ const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
202
210
  const result = await client.query(`SELECT * FROM ${quoteSqlTable(fullName)} LIMIT $1 OFFSET $2`, [limit, offset]);
203
211
  return result.rows;
204
212
  }
205
- return client.db().collection(fullName).find().limit(limit).skip(offset).toArray();
213
+ const db = client.db();
214
+ const filter = {};
215
+ if (projectId)
216
+ filter._projectId = projectId;
217
+ if (tableName)
218
+ filter._tableName = tableName;
219
+ return db.collection('records').find(filter, { projection: RECORD_META_PROJECTION }).limit(limit).skip(offset).toArray();
206
220
  }
207
221
  async function queryRecordByIdInternal(client, dbType, tableName, recordId, projectId) {
208
- const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
209
222
  if (dbType === 'sql') {
223
+ const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
210
224
  const result = await client.query(`SELECT * FROM ${quoteSqlTable(fullName)} WHERE id = $1 LIMIT 1`, [recordId]);
211
225
  return result.rows[0] ?? null;
212
226
  }
213
227
  const db = client.db();
214
- let doc = await db.collection(fullName).findOne({ _id: recordId });
228
+ const meta = {};
229
+ if (projectId)
230
+ meta._projectId = projectId;
231
+ if (tableName)
232
+ meta._tableName = tableName;
233
+ const opts = { projection: RECORD_META_PROJECTION };
234
+ let doc = await db.collection('records').findOne({ ...meta, _id: recordId }, opts);
215
235
  if (!doc) {
216
236
  try {
217
237
  const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
218
- doc = await db.collection(fullName).findOne({ _id: new ObjectId(recordId) });
238
+ doc = await db.collection('records').findOne({ ...meta, _id: new ObjectId(recordId) }, opts);
219
239
  }
220
240
  catch { /* invalid ObjectId format — not found */ }
221
241
  }
222
242
  return doc;
223
243
  }
224
244
  async function createTableOrCollectionInternal(client, dbType, schema, projectId) {
225
- const fullName = projectId ? `db_${projectId}_${schema.tableName}` : schema.tableName;
226
245
  if (dbType === 'sql') {
246
+ const fullName = projectId ? `db_${projectId}_${schema.tableName}` : schema.tableName;
227
247
  const columnDdl = schema.columns.map(col => {
228
248
  const nullable = col.nullable === false ? 'NOT NULL' : '';
229
249
  const pk = col.primaryKey ? 'PRIMARY KEY' : '';
@@ -232,11 +252,16 @@ async function createTableOrCollectionInternal(client, dbType, schema, projectId
232
252
  await client.query(`CREATE TABLE IF NOT EXISTS ${quoteSqlTable(fullName)} (${columnDdl})`);
233
253
  return;
234
254
  }
235
- await client.db().createCollection(fullName);
255
+ const db = client.db();
256
+ await db.collection('_tables').insertOne({
257
+ _projectId: projectId ?? null,
258
+ tableName: schema.tableName,
259
+ createdAt: new Date(),
260
+ });
236
261
  }
237
262
  async function insertRecordInternal(client, dbType, tableName, record, projectId) {
238
- const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
239
263
  if (dbType === 'sql') {
264
+ const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
240
265
  const keys = Object.keys(record);
241
266
  const values = Object.values(record);
242
267
  const cols = keys.map(k => `"${k}"`).join(', ');
@@ -244,12 +269,14 @@ async function insertRecordInternal(client, dbType, tableName, record, projectId
244
269
  const result = await client.query(`INSERT INTO ${quoteSqlTable(fullName)} (${cols}) VALUES (${placeholders}) RETURNING *`, values);
245
270
  return result.rows[0];
246
271
  }
247
- const result = await client.db().collection(fullName).insertOne(record);
272
+ const db = client.db();
273
+ const doc = { _projectId: projectId ?? null, _tableName: tableName, ...record };
274
+ const result = await db.collection('records').insertOne(doc);
248
275
  return { ...record, _id: result.insertedId };
249
276
  }
250
277
  async function updateRecordInternal(client, dbType, tableName, recordId, updates, projectId) {
251
- const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
252
278
  if (dbType === 'sql') {
279
+ const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
253
280
  const keys = Object.keys(updates).filter(k => k !== 'id');
254
281
  if (!keys.length)
255
282
  return null;
@@ -258,31 +285,41 @@ async function updateRecordInternal(client, dbType, tableName, recordId, updates
258
285
  return result.rows[0] ?? null;
259
286
  }
260
287
  const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
261
- const { _id, ...fields } = updates;
262
- let filter;
288
+ const { _id, _projectId: _p, _tableName: _t, ...fields } = updates;
289
+ const meta = {};
290
+ if (projectId)
291
+ meta._projectId = projectId;
292
+ if (tableName)
293
+ meta._tableName = tableName;
294
+ let idFilter;
263
295
  try {
264
- filter = { _id: new ObjectId(recordId) };
296
+ idFilter = new ObjectId(recordId);
265
297
  }
266
298
  catch {
267
- filter = { _id: recordId };
299
+ idFilter = recordId;
268
300
  }
269
- return client.db().collection(fullName).findOneAndUpdate(filter, { $set: fields }, { returnDocument: 'after' });
301
+ return client.db().collection('records').findOneAndUpdate({ ...meta, _id: idFilter }, { $set: fields }, { returnDocument: 'after', projection: RECORD_META_PROJECTION });
270
302
  }
271
303
  async function deleteRecordInternal(client, dbType, tableName, recordId, projectId) {
272
- const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
273
304
  if (dbType === 'sql') {
305
+ const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
274
306
  const result = await client.query(`DELETE FROM ${quoteSqlTable(fullName)} WHERE id = $1`, [recordId]);
275
307
  return (result.rowCount ?? 0) > 0;
276
308
  }
277
309
  const { ObjectId } = await Promise.resolve().then(() => __importStar(require('mongodb')));
278
- let filter;
310
+ const meta = {};
311
+ if (projectId)
312
+ meta._projectId = projectId;
313
+ if (tableName)
314
+ meta._tableName = tableName;
315
+ let idFilter;
279
316
  try {
280
- filter = { _id: new ObjectId(recordId) };
317
+ idFilter = new ObjectId(recordId);
281
318
  }
282
319
  catch {
283
- filter = { _id: recordId };
320
+ idFilter = recordId;
284
321
  }
285
- const result = await client.db().collection(fullName).deleteOne(filter);
322
+ const result = await client.db().collection('records').deleteOne({ ...meta, _id: idFilter });
286
323
  return result.deletedCount > 0;
287
324
  }
288
325
  async function deleteTableOrCollection(connectionString, dbType, tableName) {
@@ -295,10 +332,17 @@ async function deleteTableOrCollection(connectionString, dbType, tableName) {
295
332
  });
296
333
  }
297
334
  async function deleteTableOrCollectionInternal(client, dbType, tableName, projectId) {
298
- const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
299
335
  if (dbType === 'sql') {
336
+ const fullName = projectId ? `db_${projectId}_${tableName}` : tableName;
300
337
  await client.query(`DROP TABLE IF EXISTS ${quoteSqlTable(fullName)}`);
301
338
  return;
302
339
  }
303
- await client.db().dropCollection(fullName);
340
+ const db = client.db();
341
+ const meta = {};
342
+ if (projectId)
343
+ meta._projectId = projectId;
344
+ if (tableName)
345
+ meta._tableName = tableName;
346
+ await db.collection('records').deleteMany(meta);
347
+ await db.collection('_tables').deleteOne({ _projectId: projectId ?? null, tableName });
304
348
  }
@@ -5,7 +5,7 @@ interface ExportResult {
5
5
  }
6
6
  export declare function createExport(config: StreamByConfig, projectId: string, description: string, exportName: string, dbType: DatabaseType, exportType: 'json' | 'externalApi', isPrivate?: boolean, allowedOrigin?: string[], nodeSchema?: NodeSchema, useConnections?: boolean, useCredentials?: boolean, storageDbId?: string): Promise<ExportResult>;
7
7
  export declare function updateExport(config: StreamByConfig, projectId: string, exportId: string, description: string, exportName: string, dbType: DatabaseType, exportType: 'json' | 'externalApi', isPrivate?: boolean, allowedOrigin?: string[], devMode?: boolean, devPorts?: number[], nodeSchema?: NodeSchema, useConnections?: boolean, useCredentials?: boolean): Promise<ExportResult>;
8
- export declare function deleteExport(config: StreamByConfig, projectId: string, exportId: string, dbType: DatabaseType, exportName: string): Promise<{
8
+ export declare function deleteExport(config: StreamByConfig, projectId: string, exportId: string, dbType: DatabaseType): Promise<{
9
9
  message: string;
10
10
  }>;
11
11
  export {};
@@ -20,22 +20,19 @@ async function createExport(config, projectId, description, exportName, dbType,
20
20
  const resolvedDbType = targetDb.type;
21
21
  let exportId;
22
22
  if (resolvedDbType === 'nosql') {
23
- const result = await (0, nosql_1.createNoSQLRawExportCollection)(connection.client, projectId, exportName, 'GET', nodeSchema);
23
+ const result = await (0, nosql_1.createNoSQLExportCollection)(connection.client, projectId, exportName, 'GET', nodeSchema);
24
24
  exportId = result.exportId;
25
25
  }
26
26
  else if (resolvedDbType === 'sql') {
27
- const result = await (0, sql_1.createSQLRawExportTable)(connection.client, projectId, exportName, nodeSchema);
27
+ const result = await (0, sql_1.createSQLExportTable)(connection.client, projectId, exportName, nodeSchema);
28
28
  exportId = result.exportId;
29
29
  }
30
30
  else {
31
31
  throw new Error('Unsupported database type');
32
32
  }
33
- // Route the project document update to the correct DB based on where the project lives
34
- const Project = (0, manager_1.getModel)('projects');
35
- const project = await Project.findOne({ _id: projectId });
36
- const isSqlProject = project?.dbType === 'sql';
33
+ const now = new Date();
37
34
  const exportEntry = {
38
- id: isSqlProject ? exportId : new mongodb_1.ObjectId(exportId),
35
+ id: exportId, // Always a plain string; works for both UUID (SQL export) and ObjectId hex (nosql export)
39
36
  name: exportName,
40
37
  type: exportType,
41
38
  method: 'GET',
@@ -46,8 +43,10 @@ async function createExport(config, projectId, description, exportName, dbType,
46
43
  useCredentials,
47
44
  description,
48
45
  storageDbId: targetDb.id,
46
+ createdAt: now,
47
+ updatedAt: now,
49
48
  };
50
- const ProjectModel = isSqlProject ? (0, manager_1.getModel)('projects', 'sql') : (0, manager_1.getModel)('projects', 'nosql');
49
+ const ProjectModel = (0, manager_1.getModel)('projects');
51
50
  const updated = await ProjectModel.update({ _id: projectId }, { $push: { exports: exportEntry } });
52
51
  if (!updated) {
53
52
  throw new Error(`Failed to save export entry to project ${projectId}. The project may not exist or the database update failed.`);
@@ -72,7 +71,7 @@ async function updateExport(config, projectId, exportId, description, exportName
72
71
  const connection = (0, connectionManager_1.getConnection)(targetDb.id);
73
72
  if (targetDb.type === 'nosql') {
74
73
  const db = connection.client.db();
75
- await db.collection(projectId).updateOne({ _id: new mongodb_1.ObjectId(exportId) }, { $set: { nodeSchema, description, updatedAt: new Date() } });
74
+ await db.collection('exports').updateOne({ _id: new mongodb_1.ObjectId(exportId) }, { $set: { nodeSchema, description, updatedAt: new Date() } });
76
75
  }
77
76
  // SQL raw export table update would go here if needed in future
78
77
  // Update the export entry in the project document
@@ -86,12 +85,12 @@ async function updateExport(config, projectId, exportId, description, exportName
86
85
  await SqlProject.update({ _id: projectId }, { exports: updatedExports });
87
86
  }
88
87
  else {
89
- const NoSQLProject = (0, manager_1.getModel)('projects', 'nosql');
88
+ const NoSQLProject = (0, manager_1.getModel)('projects');
90
89
  await NoSQLProject.update({ _id: new mongodb_1.ObjectId(projectId), 'exports.id': { $in: [new mongodb_1.ObjectId(exportId), exportId] } }, { $set: { 'exports.$.name': exportName, 'exports.$.private': isPrivate, 'exports.$.allowedOrigin': allowedOrigin, 'exports.$.devMode': devMode, 'exports.$.devPorts': devPorts, 'exports.$.nodeSchema': nodeSchema, 'exports.$.useConnections': useConnections, 'exports.$.useCredentials': useCredentials, 'exports.$.description': description } });
91
90
  }
92
91
  return { exportId, message: `${exportType} export updated successfully` };
93
92
  }
94
- async function deleteExport(config, projectId, exportId, dbType, exportName) {
93
+ async function deleteExport(config, projectId, exportId, dbType) {
95
94
  // Resolve the project to find where its raw export data lives
96
95
  const Project = (0, manager_1.getModel)('projects');
97
96
  const project = await Project.findOne({ _id: projectId });
@@ -108,15 +107,15 @@ async function deleteExport(config, projectId, exportId, dbType, exportName) {
108
107
  const connection = (0, connectionManager_1.getConnection)(targetDb.id);
109
108
  if (targetDb.type === 'nosql') {
110
109
  const db = connection.client.db();
111
- await db.collection(projectId).deleteOne({ _id: new mongodb_1.ObjectId(exportId) });
110
+ await db.collection('exports').deleteOne({ _id: new mongodb_1.ObjectId(exportId) });
112
111
  }
113
- // SQL raw export table row deletion would go here if needed in future
112
+ // SQL export row deletion would go here if needed in future
114
113
  if (isSqlProject) {
115
114
  const SqlProject = (0, manager_1.getModel)('projects', 'sql');
116
115
  await SqlProject.update({ _id: projectId }, { $pull: { exports: { id: exportId } } });
117
116
  }
118
117
  else {
119
- const NoSQLProject = (0, manager_1.getModel)('projects', 'nosql');
118
+ const NoSQLProject = (0, manager_1.getModel)('projects');
120
119
  await NoSQLProject.update({ _id: new mongodb_1.ObjectId(projectId) }, { $pull: { exports: { id: new mongodb_1.ObjectId(exportId) } } });
121
120
  }
122
121
  return { message: 'Export deleted successfully' };
@@ -91,9 +91,10 @@ async function executePipeline(nodeSchema, project, config) {
91
91
  continue;
92
92
  const connectionId = node.data?.connectionId;
93
93
  const recordId = node.data?.recordId;
94
+ const projectIdentifier = project._id?.toString() ?? project.id;
94
95
  const fetchData = async (client, type) => recordId
95
- ? (0, dbConnection_1.queryRecordByIdInternal)(client, type, tableName, recordId, project.id)
96
- : (0, dbConnection_1.queryRecordsInternal)(client, type, tableName, 500, 0, project.id);
96
+ ? (0, dbConnection_1.queryRecordByIdInternal)(client, type, tableName, recordId, projectIdentifier)
97
+ : (0, dbConnection_1.queryRecordsInternal)(client, type, tableName, 500, 0, projectIdentifier);
97
98
  if (connectionId) {
98
99
  const { client, type } = (0, connectionManager_1.getConnection)(connectionId);
99
100
  dataResults.push(await fetchData(client, type));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamby-core",
3
- "version": "0.27.2",
3
+ "version": "0.27.4",
4
4
  "description": "StreamBy middleware framework for media storage management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",