@lowdefy/server-dev 7.0.0 → 7.1.0
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/lib/docs/dataSets/checkSharedCollectionIds.js +20 -9
- package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +53 -5
- package/lib/docs/dataSets/describeTargetKey.js +29 -0
- package/lib/docs/dataSets/groupDataSetByCollection.js +19 -11
- package/lib/docs/dataSets/openDataSession.js +32 -18
- package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +150 -0
- package/lib/docs/dataSets/readDataSet.js +34 -19
- package/lib/docs/dataSets/readDataSet.test.mjs +56 -25
- package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
- package/lib/docs/dataSets/sweepOrphanDatabases.js +7 -4
- package/lib/docs/dataSets/test/app/lowdefy.yaml +22 -0
- package/lib/docs/dataSets/test/app/tests/data/shop.yaml +4 -0
- package/lib/docs/devToolDefinitions.js +3 -3
- package/lib/docs/getBrowser.js +1 -1
- package/lib/docs/getBuildStatus.js +29 -3
- package/lib/docs/getBuildStatus.test.mjs +99 -3
- package/lib/docs/getOverview.js +2 -2
- package/lib/docs/rails/findExternalBlocks.js +3 -3
- package/lib/docs/runJourneyDataSet.test.mjs +1 -1
- package/lib/docs/withDataSession.js +2 -2
- package/lib/server/applyDataSetRedirect.js +14 -5
- package/lib/server/applyDataSetRedirect.test.mjs +119 -13
- package/lib/server/applyDataSetRedirectTenantReach.test.mjs +4 -1
- package/lib/server/applyDataSetRedirectWalled.test.mjs +4 -1
- package/lib/server/createLowdefyContext.js +3 -3
- package/lib/server/createLowdefyContext.test.mjs +6 -3
- package/lib/server/mutantDataSetWrappers.test.mjs +4 -1
- package/manager/getContext.mjs +2 -2
- package/manager/processes/initialBuild.mjs +3 -1
- package/manager/processes/initialBuild.test.mjs +3 -0
- package/manager/processes/lowdefyBuild.mjs +4 -3
- package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +10 -0
- package/package.json +37 -37
- package/package.original.json +37 -37
- package/lib/docs/dataSets/findCollectionCollisions.js +0 -52
|
@@ -124,17 +124,27 @@ function createContext() {
|
|
|
124
124
|
return { readConfigFile: async (filePath) => artifacts[filePath] ?? null };
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
127
|
+
// A session as openDataSession opens it for a build whose one target is the MONGODB_URI secret. The
|
|
128
|
+
// gate numbers a target it meets for the first time, so each test takes a fresh one.
|
|
129
|
+
function createSession(overrides = {}) {
|
|
130
|
+
return {
|
|
131
|
+
id: 'abc',
|
|
132
|
+
name: 'staging-sample',
|
|
133
|
+
state: 'open',
|
|
134
|
+
databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
|
|
135
|
+
prefix: 'ld_000000000001',
|
|
136
|
+
databaseName: 'ld_000000000001_0',
|
|
137
|
+
databases: new Map([[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_000000000001_0']]),
|
|
138
|
+
databaseCount: 1,
|
|
139
|
+
...overrides,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const session = createSession();
|
|
134
144
|
|
|
135
145
|
function redirectedContext(options = {}) {
|
|
136
146
|
const context = createContext();
|
|
137
|
-
applyDataSetRedirect({ context, session: options.session ??
|
|
147
|
+
applyDataSetRedirect({ context, session: options.session ?? createSession(), connections });
|
|
138
148
|
return context;
|
|
139
149
|
}
|
|
140
150
|
|
|
@@ -164,12 +174,12 @@ test('applyDataSetRedirect points a module connection, written under its module
|
|
|
164
174
|
});
|
|
165
175
|
});
|
|
166
176
|
|
|
167
|
-
test('applyDataSetRedirect points a plugin type that declares redirect at the session database', async () => {
|
|
177
|
+
test('applyDataSetRedirect points a plugin type that declares redirect at the session database of its target', async () => {
|
|
168
178
|
const context = redirectedContext();
|
|
169
179
|
const artifact = await context.readConfigFile('connections/folders.json');
|
|
170
180
|
expect(artifact.properties).toEqual({
|
|
171
181
|
databaseUri: session.databaseUri,
|
|
172
|
-
databaseName:
|
|
182
|
+
databaseName: 'ld_000000000001_1',
|
|
173
183
|
});
|
|
174
184
|
});
|
|
175
185
|
|
|
@@ -249,7 +259,7 @@ test('applyDataSetRedirect never mutates the artifact it read', async () => {
|
|
|
249
259
|
});
|
|
250
260
|
|
|
251
261
|
test('applyDataSetRedirect throws on a connection read after the session has closed', async () => {
|
|
252
|
-
const context = redirectedContext({ session: {
|
|
262
|
+
const context = redirectedContext({ session: createSession({ state: 'closed' }) });
|
|
253
263
|
await expect(context.readConfigFile('connections/tickets.json')).rejects.toThrow(
|
|
254
264
|
'Data session abc has ended.'
|
|
255
265
|
);
|
|
@@ -259,6 +269,102 @@ test('applyDataSetRedirect throws on a connection read after the session has clo
|
|
|
259
269
|
expect(await context.readConfigFile('pages/home.json')).toBe(artifacts['pages/home.json']);
|
|
260
270
|
});
|
|
261
271
|
|
|
272
|
+
describe('database targets', () => {
|
|
273
|
+
const targetArtifacts = {
|
|
274
|
+
'connections/app_users.json': {
|
|
275
|
+
connectionId: 'app_users',
|
|
276
|
+
type: 'MongoDBCollection',
|
|
277
|
+
properties: { databaseUri: { _secret: 'MONGODB_URI' }, collection: 'users' },
|
|
278
|
+
},
|
|
279
|
+
'connections/app_users_again.json': {
|
|
280
|
+
connectionId: 'app_users_again',
|
|
281
|
+
type: 'MongoDBCollection',
|
|
282
|
+
properties: { databaseUri: { _secret: 'MONGODB_URI', '~r': 'r_1' }, collection: 'users' },
|
|
283
|
+
},
|
|
284
|
+
'connections/auth_users.json': {
|
|
285
|
+
connectionId: 'auth_users',
|
|
286
|
+
type: 'MongoDBCollection',
|
|
287
|
+
properties: { databaseUri: { _secret: 'AUTH_MONGODB_URI' }, collection: 'users' },
|
|
288
|
+
},
|
|
289
|
+
'connections/reports_users.json': {
|
|
290
|
+
connectionId: 'reports_users',
|
|
291
|
+
type: 'MongoDBCollection',
|
|
292
|
+
properties: {
|
|
293
|
+
databaseUri: { _secret: 'MONGODB_URI' },
|
|
294
|
+
databaseName: 'reports',
|
|
295
|
+
collection: 'users',
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
'connections/computed_users.json': {
|
|
299
|
+
connectionId: 'computed_users',
|
|
300
|
+
type: 'MongoDBCollection',
|
|
301
|
+
properties: {
|
|
302
|
+
databaseUri: { _if: { test: true, then: { _secret: 'A' }, else: { _secret: 'B' } } },
|
|
303
|
+
collection: 'users',
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
async function readDatabaseName({ session: targetSession, connectionId }) {
|
|
309
|
+
const context = { readConfigFile: async (filePath) => targetArtifacts[filePath] ?? null };
|
|
310
|
+
applyDataSetRedirect({ context, session: targetSession, connections });
|
|
311
|
+
const artifact = await context.readConfigFile(`connections/${connectionId}.json`);
|
|
312
|
+
return artifact.properties.databaseName;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
test('connections on one target share its session database', async () => {
|
|
316
|
+
const targetSession = createSession();
|
|
317
|
+
expect(await readDatabaseName({ session: targetSession, connectionId: 'app_users' })).toEqual(
|
|
318
|
+
'ld_000000000001_0'
|
|
319
|
+
);
|
|
320
|
+
expect(
|
|
321
|
+
await readDatabaseName({ session: targetSession, connectionId: 'app_users_again' })
|
|
322
|
+
).toEqual('ld_000000000001_0');
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('a different databaseUri secret or databaseName reads a session database of its own', async () => {
|
|
326
|
+
const targetSession = createSession();
|
|
327
|
+
const names = [];
|
|
328
|
+
for (const connectionId of ['app_users', 'auth_users', 'reports_users']) {
|
|
329
|
+
names.push(await readDatabaseName({ session: targetSession, connectionId }));
|
|
330
|
+
}
|
|
331
|
+
expect(names).toEqual(['ld_000000000001_0', 'ld_000000000001_1', 'ld_000000000001_2']);
|
|
332
|
+
// The number holds for the rest of the session.
|
|
333
|
+
expect(await readDatabaseName({ session: targetSession, connectionId: 'auth_users' })).toEqual(
|
|
334
|
+
'ld_000000000001_1'
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('a target the session numbered when it opened keeps that database', async () => {
|
|
339
|
+
const targetSession = createSession({
|
|
340
|
+
databases: new Map([
|
|
341
|
+
[JSON.stringify(['secret:AUTH_MONGODB_URI', null]), 'ld_000000000001_0'],
|
|
342
|
+
[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_000000000001_1'],
|
|
343
|
+
]),
|
|
344
|
+
databaseCount: 2,
|
|
345
|
+
});
|
|
346
|
+
expect(await readDatabaseName({ session: targetSession, connectionId: 'auth_users' })).toEqual(
|
|
347
|
+
'ld_000000000001_0'
|
|
348
|
+
);
|
|
349
|
+
expect(await readDatabaseName({ session: targetSession, connectionId: 'app_users' })).toEqual(
|
|
350
|
+
'ld_000000000001_1'
|
|
351
|
+
);
|
|
352
|
+
expect(
|
|
353
|
+
await readDatabaseName({ session: targetSession, connectionId: 'reports_users' })
|
|
354
|
+
).toEqual('ld_000000000001_2');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('a connection whose target is computed per request reads the session first database', async () => {
|
|
358
|
+
const targetSession = createSession({ databases: new Map(), databaseCount: 1 });
|
|
359
|
+
expect(
|
|
360
|
+
await readDatabaseName({ session: targetSession, connectionId: 'computed_users' })
|
|
361
|
+
).toEqual('ld_000000000001_0');
|
|
362
|
+
expect(await readDatabaseName({ session: targetSession, connectionId: 'auth_users' })).toEqual(
|
|
363
|
+
'ld_000000000001_1'
|
|
364
|
+
);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
262
368
|
describe('a request through callRequest under a data session', () => {
|
|
263
369
|
const resolver = jest.fn(async () => ({ ok: true }));
|
|
264
370
|
resolver.schema = {};
|
|
@@ -309,7 +415,7 @@ describe('a request through callRequest under a data session', () => {
|
|
|
309
415
|
steps: {},
|
|
310
416
|
user: null,
|
|
311
417
|
};
|
|
312
|
-
applyDataSetRedirect({ context, session, connections: requestConnections });
|
|
418
|
+
applyDataSetRedirect({ context, session: createSession(), connections: requestConnections });
|
|
313
419
|
return context;
|
|
314
420
|
}
|
|
315
421
|
|
|
@@ -333,7 +439,7 @@ describe('a request through callRequest under a data session', () => {
|
|
|
333
439
|
expect(resolver).toHaveBeenCalledTimes(1);
|
|
334
440
|
expect(resolver.mock.calls[0][0].connection).toEqual({
|
|
335
441
|
databaseUri: session.databaseUri,
|
|
336
|
-
databaseName:
|
|
442
|
+
databaseName: 'ld_000000000001_1',
|
|
337
443
|
collection: 'rows',
|
|
338
444
|
});
|
|
339
445
|
});
|
|
@@ -35,7 +35,10 @@ const session = {
|
|
|
35
35
|
name: 'staging-sample',
|
|
36
36
|
state: 'open',
|
|
37
37
|
databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
|
|
38
|
-
|
|
38
|
+
prefix: 'ld_000000000001',
|
|
39
|
+
databaseName: 'ld_000000000001_0',
|
|
40
|
+
databases: new Map([[JSON.stringify([`literal:${walledUri}`, null]), 'ld_000000000001_0']]),
|
|
41
|
+
databaseCount: 1,
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
async function WalledRead({ connection, walled }) {
|
|
@@ -62,7 +62,10 @@ const session = {
|
|
|
62
62
|
name: 'staging-sample',
|
|
63
63
|
state: 'open',
|
|
64
64
|
databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
|
|
65
|
-
|
|
65
|
+
prefix: 'ld_000000000001',
|
|
66
|
+
databaseName: 'ld_000000000001_0',
|
|
67
|
+
databases: new Map([[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_000000000001_0']]),
|
|
68
|
+
databaseCount: 1,
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
function createContext({ sessionState = 'open' } = {}) {
|
|
@@ -81,10 +81,10 @@ function isMcpPath(path) {
|
|
|
81
81
|
// outside a browser, e.g. run_request), resolved the same way the headless
|
|
82
82
|
// renderer's cookie user is. A `dataSession` option runs a call the dev
|
|
83
83
|
// server makes itself (run_request, run_endpoint on a data set) on that data
|
|
84
|
-
// session's
|
|
84
|
+
// session's databases, as a journey's cookie does for its browser requests.
|
|
85
85
|
async function createLowdefyContext({ c, user, dataSession: callDataSession }) {
|
|
86
|
-
// A journey on a data set: this request, and only this one, reads the session's
|
|
87
|
-
// before anything else, so a request that outlived its session never reaches
|
|
86
|
+
// A journey on a data set: this request, and only this one, reads the session's databases.
|
|
87
|
+
// Read before anything else, so a request that outlived its session never reaches any database.
|
|
88
88
|
const dataSession = callDataSession ?? readDataSession(c.req.header('cookie'));
|
|
89
89
|
if (!type.isNone(dataSession?.ended)) {
|
|
90
90
|
throw createDataSessionEndedError({ id: dataSession.ended });
|
|
@@ -252,7 +252,10 @@ describe('data sessions', () => {
|
|
|
252
252
|
name: 'staging-sample',
|
|
253
253
|
state,
|
|
254
254
|
databaseUri: 'mongodb://memory/',
|
|
255
|
-
|
|
255
|
+
prefix: 'ld_aaaaaaaaaaaa',
|
|
256
|
+
databaseName: 'ld_aaaaaaaaaaaa_0',
|
|
257
|
+
databases: new Map([[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_aaaaaaaaaaaa_0']]),
|
|
258
|
+
databaseCount: 1,
|
|
256
259
|
work: new Set(),
|
|
257
260
|
};
|
|
258
261
|
dataSessionRegistry.set(session.id, session);
|
|
@@ -279,7 +282,7 @@ describe('data sessions', () => {
|
|
|
279
282
|
const context = await createLowdefyContext({ c: createHonoContext({ headers: { cookie } }) });
|
|
280
283
|
expect((await context.readConfigFile('connections/tickets.json')).properties).toEqual({
|
|
281
284
|
databaseUri: 'mongodb://memory/',
|
|
282
|
-
databaseName: '
|
|
285
|
+
databaseName: 'ld_aaaaaaaaaaaa_0',
|
|
283
286
|
collection: 'tickets',
|
|
284
287
|
});
|
|
285
288
|
expect(context.dataSet).toEqual('staging-sample');
|
|
@@ -322,7 +325,7 @@ describe('data sessions', () => {
|
|
|
322
325
|
const context = await createLowdefyContext({ c: createHonoContext(), dataSession: session });
|
|
323
326
|
expect((await context.readConfigFile('connections/tickets.json')).properties).toEqual({
|
|
324
327
|
databaseUri: 'mongodb://memory/',
|
|
325
|
-
databaseName: '
|
|
328
|
+
databaseName: 'ld_aaaaaaaaaaaa_0',
|
|
326
329
|
collection: 'tickets',
|
|
327
330
|
});
|
|
328
331
|
expect(context.dataSet).toEqual('staging-sample');
|
|
@@ -33,7 +33,10 @@ const session = {
|
|
|
33
33
|
name: 'shop',
|
|
34
34
|
state: 'open',
|
|
35
35
|
databaseUri: 'mongodb://127.0.0.1:27999/?replicaSet=rs',
|
|
36
|
-
|
|
36
|
+
prefix: 'ld_0123456789ab',
|
|
37
|
+
databaseName: 'ld_0123456789ab_0',
|
|
38
|
+
databases: new Map([[JSON.stringify(['secret:MONGODB_URI', null]), 'ld_0123456789ab_0']]),
|
|
39
|
+
databaseCount: 1,
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
function setup() {
|
package/manager/getContext.mjs
CHANGED
|
@@ -89,10 +89,10 @@ async function getContext() {
|
|
|
89
89
|
|
|
90
90
|
// Wrap lowdefyBuild to capture and store the shallow build result
|
|
91
91
|
const buildFn = lowdefyBuild(context);
|
|
92
|
-
context.lowdefyBuild = async () => {
|
|
92
|
+
context.lowdefyBuild = async (buildOptions) => {
|
|
93
93
|
let result;
|
|
94
94
|
try {
|
|
95
|
-
result = await buildFn();
|
|
95
|
+
result = await buildFn(buildOptions);
|
|
96
96
|
} catch (error) {
|
|
97
97
|
context.lastBuildFailed = true;
|
|
98
98
|
throw error;
|
|
@@ -20,7 +20,9 @@ import { pluginChecksNeedRebuild } from '@lowdefy/node-utils';
|
|
|
20
20
|
function initialBuild(context) {
|
|
21
21
|
return async () => {
|
|
22
22
|
context.readDotEnv();
|
|
23
|
-
|
|
23
|
+
// This build holds its warnings for plugins it could not check: the install
|
|
24
|
+
// below adds them, and the build after it warns for any still missing.
|
|
25
|
+
await context.lowdefyBuild({ pluginInstallFollows: true });
|
|
24
26
|
await context.checkMockUserWarning();
|
|
25
27
|
await context.installPlugins();
|
|
26
28
|
// The build lists the plugins it used but could not check, since they were
|
|
@@ -66,6 +66,9 @@ test('initialBuild builds again after installing plugins when plugin checks are
|
|
|
66
66
|
});
|
|
67
67
|
await initialBuild(context)();
|
|
68
68
|
expect(context.lowdefyBuild).toHaveBeenCalledTimes(2);
|
|
69
|
+
// The first build holds its pending warnings; the build after the install
|
|
70
|
+
// gives them.
|
|
71
|
+
expect(context.lowdefyBuild.mock.calls).toEqual([[{ pluginInstallFollows: true }], []]);
|
|
69
72
|
expect(calls).toEqual([
|
|
70
73
|
'readDotEnv',
|
|
71
74
|
'lowdefyBuild',
|
|
@@ -26,7 +26,7 @@ function formatDuration(ms) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
function lowdefyBuild({ directories, logger, options }) {
|
|
29
|
-
async function build() {
|
|
29
|
+
async function build({ pluginInstallFollows = false } = {}) {
|
|
30
30
|
logger.info({ spin: 'start' }, 'Building config...');
|
|
31
31
|
const startTime = Date.now();
|
|
32
32
|
|
|
@@ -46,6 +46,7 @@ function lowdefyBuild({ directories, logger, options }) {
|
|
|
46
46
|
customTypesMap,
|
|
47
47
|
directories: { ...directories, build: directories.buildStaging },
|
|
48
48
|
logger,
|
|
49
|
+
pluginInstallFollows,
|
|
49
50
|
refResolver: options.refResolver,
|
|
50
51
|
skipPluginChecks: options.skipPluginChecks,
|
|
51
52
|
stage: 'dev',
|
|
@@ -84,8 +85,8 @@ function lowdefyBuild({ directories, logger, options }) {
|
|
|
84
85
|
// Watchers, restarts and plugin installs each start builds, and every build
|
|
85
86
|
// writes the same staging directory, so a build waits for the one before it.
|
|
86
87
|
let previousBuild = Promise.resolve();
|
|
87
|
-
return () => {
|
|
88
|
-
const nextBuild = previousBuild.then(build);
|
|
88
|
+
return (buildOptions) => {
|
|
89
|
+
const nextBuild = previousBuild.then(() => build(buildOptions));
|
|
89
90
|
previousBuild = nextBuild.catch(() => {});
|
|
90
91
|
return nextBuild;
|
|
91
92
|
};
|
|
@@ -60,3 +60,13 @@ test('lowdefyBuild passes skipPluginChecks undefined to shallowBuild when the op
|
|
|
60
60
|
expect(args.skipPluginChecks).toBeUndefined();
|
|
61
61
|
expect(args.stage).toEqual('dev');
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
test('lowdefyBuild passes pluginInstallFollows to shallowBuild', async () => {
|
|
65
|
+
await lowdefyBuild(createContext({}))({ pluginInstallFollows: true });
|
|
66
|
+
expect(mockShallowBuild.mock.calls.at(-1)[0].pluginInstallFollows).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('lowdefyBuild passes pluginInstallFollows false when the build is not given it', async () => {
|
|
70
|
+
await lowdefyBuild(createContext({}))();
|
|
71
|
+
expect(mockShallowBuild.mock.calls.at(-1)[0].pluginInstallFollows).toBe(false);
|
|
72
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/server-dev",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -45,42 +45,42 @@
|
|
|
45
45
|
"@hono/mcp": "0.3.2",
|
|
46
46
|
"@hono/node-server": "2.0.10",
|
|
47
47
|
"@hono/vite-dev-server": "0.26.0",
|
|
48
|
-
"@lowdefy/actions-core": "7.
|
|
49
|
-
"@lowdefy/api": "7.
|
|
50
|
-
"@lowdefy/block-utils": "7.
|
|
51
|
-
"@lowdefy/blocks-aggrid": "7.
|
|
52
|
-
"@lowdefy/blocks-antd": "7.
|
|
53
|
-
"@lowdefy/blocks-antd-x": "7.
|
|
54
|
-
"@lowdefy/blocks-basic": "7.
|
|
55
|
-
"@lowdefy/blocks-captcha": "7.
|
|
56
|
-
"@lowdefy/blocks-echarts": "7.
|
|
57
|
-
"@lowdefy/blocks-loaders": "7.
|
|
58
|
-
"@lowdefy/blocks-markdown": "7.
|
|
59
|
-
"@lowdefy/blocks-table": "7.
|
|
60
|
-
"@lowdefy/blocks-tiptap": "7.
|
|
61
|
-
"@lowdefy/build": "7.
|
|
62
|
-
"@lowdefy/client": "7.
|
|
63
|
-
"@lowdefy/connection-axios-http": "7.
|
|
64
|
-
"@lowdefy/connection-mongodb": "7.
|
|
65
|
-
"@lowdefy/docs-content": "7.
|
|
66
|
-
"@lowdefy/e2e-utils": "7.
|
|
67
|
-
"@lowdefy/engine": "7.
|
|
68
|
-
"@lowdefy/errors": "7.
|
|
69
|
-
"@lowdefy/helpers": "7.
|
|
70
|
-
"@lowdefy/layout": "7.
|
|
71
|
-
"@lowdefy/logger": "7.
|
|
72
|
-
"@lowdefy/node-utils": "7.
|
|
73
|
-
"@lowdefy/operators-change-case": "7.
|
|
74
|
-
"@lowdefy/operators-cron": "7.
|
|
75
|
-
"@lowdefy/operators-dayjs": "7.
|
|
76
|
-
"@lowdefy/operators-diff": "7.
|
|
77
|
-
"@lowdefy/operators-js": "7.
|
|
78
|
-
"@lowdefy/operators-mql": "7.
|
|
79
|
-
"@lowdefy/operators-nunjucks": "7.
|
|
80
|
-
"@lowdefy/operators-uuid": "7.
|
|
81
|
-
"@lowdefy/operators-yaml": "7.
|
|
82
|
-
"@lowdefy/plugin-better-auth": "7.
|
|
83
|
-
"@lowdefy/websockets-core": "7.
|
|
48
|
+
"@lowdefy/actions-core": "7.1.0",
|
|
49
|
+
"@lowdefy/api": "7.1.0",
|
|
50
|
+
"@lowdefy/block-utils": "7.1.0",
|
|
51
|
+
"@lowdefy/blocks-aggrid": "7.1.0",
|
|
52
|
+
"@lowdefy/blocks-antd": "7.1.0",
|
|
53
|
+
"@lowdefy/blocks-antd-x": "7.1.0",
|
|
54
|
+
"@lowdefy/blocks-basic": "7.1.0",
|
|
55
|
+
"@lowdefy/blocks-captcha": "7.1.0",
|
|
56
|
+
"@lowdefy/blocks-echarts": "7.1.0",
|
|
57
|
+
"@lowdefy/blocks-loaders": "7.1.0",
|
|
58
|
+
"@lowdefy/blocks-markdown": "7.1.0",
|
|
59
|
+
"@lowdefy/blocks-table": "7.1.0",
|
|
60
|
+
"@lowdefy/blocks-tiptap": "7.1.0",
|
|
61
|
+
"@lowdefy/build": "7.1.0",
|
|
62
|
+
"@lowdefy/client": "7.1.0",
|
|
63
|
+
"@lowdefy/connection-axios-http": "7.1.0",
|
|
64
|
+
"@lowdefy/connection-mongodb": "7.1.0",
|
|
65
|
+
"@lowdefy/docs-content": "7.1.0",
|
|
66
|
+
"@lowdefy/e2e-utils": "7.1.0",
|
|
67
|
+
"@lowdefy/engine": "7.1.0",
|
|
68
|
+
"@lowdefy/errors": "7.1.0",
|
|
69
|
+
"@lowdefy/helpers": "7.1.0",
|
|
70
|
+
"@lowdefy/layout": "7.1.0",
|
|
71
|
+
"@lowdefy/logger": "7.1.0",
|
|
72
|
+
"@lowdefy/node-utils": "7.1.0",
|
|
73
|
+
"@lowdefy/operators-change-case": "7.1.0",
|
|
74
|
+
"@lowdefy/operators-cron": "7.1.0",
|
|
75
|
+
"@lowdefy/operators-dayjs": "7.1.0",
|
|
76
|
+
"@lowdefy/operators-diff": "7.1.0",
|
|
77
|
+
"@lowdefy/operators-js": "7.1.0",
|
|
78
|
+
"@lowdefy/operators-mql": "7.1.0",
|
|
79
|
+
"@lowdefy/operators-nunjucks": "7.1.0",
|
|
80
|
+
"@lowdefy/operators-uuid": "7.1.0",
|
|
81
|
+
"@lowdefy/operators-yaml": "7.1.0",
|
|
82
|
+
"@lowdefy/plugin-better-auth": "7.1.0",
|
|
83
|
+
"@lowdefy/websockets-core": "7.1.0",
|
|
84
84
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
85
85
|
"@tailwindcss/postcss": "4.3.3",
|
|
86
86
|
"@vitejs/plugin-react": "6.0.2",
|
package/package.original.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/server-dev",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -53,42 +53,42 @@
|
|
|
53
53
|
"@hono/mcp": "0.3.2",
|
|
54
54
|
"@hono/node-server": "2.0.10",
|
|
55
55
|
"@hono/vite-dev-server": "0.26.0",
|
|
56
|
-
"@lowdefy/actions-core": "7.
|
|
57
|
-
"@lowdefy/api": "7.
|
|
58
|
-
"@lowdefy/block-utils": "7.
|
|
59
|
-
"@lowdefy/blocks-aggrid": "7.
|
|
60
|
-
"@lowdefy/blocks-antd": "7.
|
|
61
|
-
"@lowdefy/blocks-antd-x": "7.
|
|
62
|
-
"@lowdefy/blocks-basic": "7.
|
|
63
|
-
"@lowdefy/blocks-captcha": "7.
|
|
64
|
-
"@lowdefy/blocks-echarts": "7.
|
|
65
|
-
"@lowdefy/blocks-loaders": "7.
|
|
66
|
-
"@lowdefy/blocks-markdown": "7.
|
|
67
|
-
"@lowdefy/blocks-table": "7.
|
|
68
|
-
"@lowdefy/blocks-tiptap": "7.
|
|
69
|
-
"@lowdefy/build": "7.
|
|
70
|
-
"@lowdefy/client": "7.
|
|
71
|
-
"@lowdefy/connection-axios-http": "7.
|
|
72
|
-
"@lowdefy/connection-mongodb": "7.
|
|
73
|
-
"@lowdefy/docs-content": "7.
|
|
74
|
-
"@lowdefy/e2e-utils": "7.
|
|
75
|
-
"@lowdefy/engine": "7.
|
|
76
|
-
"@lowdefy/errors": "7.
|
|
77
|
-
"@lowdefy/helpers": "7.
|
|
78
|
-
"@lowdefy/layout": "7.
|
|
79
|
-
"@lowdefy/logger": "7.
|
|
80
|
-
"@lowdefy/node-utils": "7.
|
|
81
|
-
"@lowdefy/operators-change-case": "7.
|
|
82
|
-
"@lowdefy/operators-cron": "7.
|
|
83
|
-
"@lowdefy/operators-dayjs": "7.
|
|
84
|
-
"@lowdefy/operators-diff": "7.
|
|
85
|
-
"@lowdefy/operators-js": "7.
|
|
86
|
-
"@lowdefy/operators-mql": "7.
|
|
87
|
-
"@lowdefy/operators-nunjucks": "7.
|
|
88
|
-
"@lowdefy/operators-uuid": "7.
|
|
89
|
-
"@lowdefy/operators-yaml": "7.
|
|
90
|
-
"@lowdefy/plugin-better-auth": "7.
|
|
91
|
-
"@lowdefy/websockets-core": "7.
|
|
56
|
+
"@lowdefy/actions-core": "7.1.0",
|
|
57
|
+
"@lowdefy/api": "7.1.0",
|
|
58
|
+
"@lowdefy/block-utils": "7.1.0",
|
|
59
|
+
"@lowdefy/blocks-aggrid": "7.1.0",
|
|
60
|
+
"@lowdefy/blocks-antd": "7.1.0",
|
|
61
|
+
"@lowdefy/blocks-antd-x": "7.1.0",
|
|
62
|
+
"@lowdefy/blocks-basic": "7.1.0",
|
|
63
|
+
"@lowdefy/blocks-captcha": "7.1.0",
|
|
64
|
+
"@lowdefy/blocks-echarts": "7.1.0",
|
|
65
|
+
"@lowdefy/blocks-loaders": "7.1.0",
|
|
66
|
+
"@lowdefy/blocks-markdown": "7.1.0",
|
|
67
|
+
"@lowdefy/blocks-table": "7.1.0",
|
|
68
|
+
"@lowdefy/blocks-tiptap": "7.1.0",
|
|
69
|
+
"@lowdefy/build": "7.1.0",
|
|
70
|
+
"@lowdefy/client": "7.1.0",
|
|
71
|
+
"@lowdefy/connection-axios-http": "7.1.0",
|
|
72
|
+
"@lowdefy/connection-mongodb": "7.1.0",
|
|
73
|
+
"@lowdefy/docs-content": "7.1.0",
|
|
74
|
+
"@lowdefy/e2e-utils": "7.1.0",
|
|
75
|
+
"@lowdefy/engine": "7.1.0",
|
|
76
|
+
"@lowdefy/errors": "7.1.0",
|
|
77
|
+
"@lowdefy/helpers": "7.1.0",
|
|
78
|
+
"@lowdefy/layout": "7.1.0",
|
|
79
|
+
"@lowdefy/logger": "7.1.0",
|
|
80
|
+
"@lowdefy/node-utils": "7.1.0",
|
|
81
|
+
"@lowdefy/operators-change-case": "7.1.0",
|
|
82
|
+
"@lowdefy/operators-cron": "7.1.0",
|
|
83
|
+
"@lowdefy/operators-dayjs": "7.1.0",
|
|
84
|
+
"@lowdefy/operators-diff": "7.1.0",
|
|
85
|
+
"@lowdefy/operators-js": "7.1.0",
|
|
86
|
+
"@lowdefy/operators-mql": "7.1.0",
|
|
87
|
+
"@lowdefy/operators-nunjucks": "7.1.0",
|
|
88
|
+
"@lowdefy/operators-uuid": "7.1.0",
|
|
89
|
+
"@lowdefy/operators-yaml": "7.1.0",
|
|
90
|
+
"@lowdefy/plugin-better-auth": "7.1.0",
|
|
91
|
+
"@lowdefy/websockets-core": "7.1.0",
|
|
92
92
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
93
93
|
"@tailwindcss/postcss": "4.3.3",
|
|
94
94
|
"@vitejs/plugin-react": "6.0.2",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { type } from '@lowdefy/helpers';
|
|
18
|
-
|
|
19
|
-
import describeDatabaseTarget from './describeDatabaseTarget.js';
|
|
20
|
-
|
|
21
|
-
// Under a data session every redirected connection reads one database, so two connections that
|
|
22
|
-
// name one literal collection in different databases (a different databaseName or databaseUri
|
|
23
|
-
// secret) would merge into one collection. Connections whose collection or database is computed per
|
|
24
|
-
// request cannot be checked, and connections with no databaseUri are not redirected at all.
|
|
25
|
-
function findCollectionCollisions({ artifacts }) {
|
|
26
|
-
const byCollection = {};
|
|
27
|
-
Object.entries(artifacts).forEach(([connectionId, artifact]) => {
|
|
28
|
-
const collection = artifact.properties?.collection;
|
|
29
|
-
if (!type.isString(collection) || type.isUndefined(artifact.properties?.databaseUri)) return;
|
|
30
|
-
const target = describeDatabaseTarget({ properties: artifact.properties });
|
|
31
|
-
if (type.isNone(target)) return;
|
|
32
|
-
byCollection[collection] = byCollection[collection] ?? [];
|
|
33
|
-
byCollection[collection].push({ connectionId, target });
|
|
34
|
-
});
|
|
35
|
-
const collisions = [];
|
|
36
|
-
Object.entries(byCollection).forEach(([collection, entries]) => {
|
|
37
|
-
const sorted = entries.sort((a, b) => a.connectionId.localeCompare(b.connectionId));
|
|
38
|
-
for (let i = 0; i < sorted.length; i += 1) {
|
|
39
|
-
for (let j = i + 1; j < sorted.length; j += 1) {
|
|
40
|
-
const a = sorted[i];
|
|
41
|
-
const b = sorted[j];
|
|
42
|
-
if (a.target.databaseName === b.target.databaseName && a.target.uri === b.target.uri) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
collisions.push({ collection, connectionIds: [a.connectionId, b.connectionId] });
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
return collisions;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default findCollectionCollisions;
|