@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.
Files changed (35) hide show
  1. package/lib/docs/dataSets/checkSharedCollectionIds.js +20 -9
  2. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +53 -5
  3. package/lib/docs/dataSets/describeTargetKey.js +29 -0
  4. package/lib/docs/dataSets/groupDataSetByCollection.js +19 -11
  5. package/lib/docs/dataSets/openDataSession.js +32 -18
  6. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +150 -0
  7. package/lib/docs/dataSets/readDataSet.js +34 -19
  8. package/lib/docs/dataSets/readDataSet.test.mjs +56 -25
  9. package/lib/docs/dataSets/sessionDatabaseName.js +32 -0
  10. package/lib/docs/dataSets/sweepOrphanDatabases.js +7 -4
  11. package/lib/docs/dataSets/test/app/lowdefy.yaml +22 -0
  12. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +4 -0
  13. package/lib/docs/devToolDefinitions.js +3 -3
  14. package/lib/docs/getBrowser.js +1 -1
  15. package/lib/docs/getBuildStatus.js +29 -3
  16. package/lib/docs/getBuildStatus.test.mjs +99 -3
  17. package/lib/docs/getOverview.js +2 -2
  18. package/lib/docs/rails/findExternalBlocks.js +3 -3
  19. package/lib/docs/runJourneyDataSet.test.mjs +1 -1
  20. package/lib/docs/withDataSession.js +2 -2
  21. package/lib/server/applyDataSetRedirect.js +14 -5
  22. package/lib/server/applyDataSetRedirect.test.mjs +119 -13
  23. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +4 -1
  24. package/lib/server/applyDataSetRedirectWalled.test.mjs +4 -1
  25. package/lib/server/createLowdefyContext.js +3 -3
  26. package/lib/server/createLowdefyContext.test.mjs +6 -3
  27. package/lib/server/mutantDataSetWrappers.test.mjs +4 -1
  28. package/manager/getContext.mjs +2 -2
  29. package/manager/processes/initialBuild.mjs +3 -1
  30. package/manager/processes/initialBuild.test.mjs +3 -0
  31. package/manager/processes/lowdefyBuild.mjs +4 -3
  32. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +10 -0
  33. package/package.json +37 -37
  34. package/package.original.json +37 -37
  35. 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
- const session = {
128
- id: 'abc',
129
- name: 'staging-sample',
130
- state: 'open',
131
- databaseUri: 'mongodb://127.0.0.1:5555/?replicaSet=testset',
132
- databaseName: 'ld_000000000001',
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 ?? session, connections });
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: session.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: { ...session, state: 'closed' } });
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: session.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
- databaseName: 'ld_000000000001',
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
- databaseName: 'ld_000000000001',
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 database, as a journey's cookie does for its browser requests.
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 database. Read
87
- // before anything else, so a request that outlived its session never reaches either database.
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
- databaseName: 'ld_aaaaaaaaaaaa',
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: 'ld_aaaaaaaaaaaa',
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: 'ld_aaaaaaaaaaaa',
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
- databaseName: 'ld_0123456789ab',
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() {
@@ -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
- await context.lowdefyBuild();
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.0.0",
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.0.0",
49
- "@lowdefy/api": "7.0.0",
50
- "@lowdefy/block-utils": "7.0.0",
51
- "@lowdefy/blocks-aggrid": "7.0.0",
52
- "@lowdefy/blocks-antd": "7.0.0",
53
- "@lowdefy/blocks-antd-x": "7.0.0",
54
- "@lowdefy/blocks-basic": "7.0.0",
55
- "@lowdefy/blocks-captcha": "7.0.0",
56
- "@lowdefy/blocks-echarts": "7.0.0",
57
- "@lowdefy/blocks-loaders": "7.0.0",
58
- "@lowdefy/blocks-markdown": "7.0.0",
59
- "@lowdefy/blocks-table": "7.0.0",
60
- "@lowdefy/blocks-tiptap": "7.0.0",
61
- "@lowdefy/build": "7.0.0",
62
- "@lowdefy/client": "7.0.0",
63
- "@lowdefy/connection-axios-http": "7.0.0",
64
- "@lowdefy/connection-mongodb": "7.0.0",
65
- "@lowdefy/docs-content": "7.0.0",
66
- "@lowdefy/e2e-utils": "7.0.0",
67
- "@lowdefy/engine": "7.0.0",
68
- "@lowdefy/errors": "7.0.0",
69
- "@lowdefy/helpers": "7.0.0",
70
- "@lowdefy/layout": "7.0.0",
71
- "@lowdefy/logger": "7.0.0",
72
- "@lowdefy/node-utils": "7.0.0",
73
- "@lowdefy/operators-change-case": "7.0.0",
74
- "@lowdefy/operators-cron": "7.0.0",
75
- "@lowdefy/operators-dayjs": "7.0.0",
76
- "@lowdefy/operators-diff": "7.0.0",
77
- "@lowdefy/operators-js": "7.0.0",
78
- "@lowdefy/operators-mql": "7.0.0",
79
- "@lowdefy/operators-nunjucks": "7.0.0",
80
- "@lowdefy/operators-uuid": "7.0.0",
81
- "@lowdefy/operators-yaml": "7.0.0",
82
- "@lowdefy/plugin-better-auth": "7.0.0",
83
- "@lowdefy/websockets-core": "7.0.0",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/server-dev",
3
- "version": "7.0.0",
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.0.0",
57
- "@lowdefy/api": "7.0.0",
58
- "@lowdefy/block-utils": "7.0.0",
59
- "@lowdefy/blocks-aggrid": "7.0.0",
60
- "@lowdefy/blocks-antd": "7.0.0",
61
- "@lowdefy/blocks-antd-x": "7.0.0",
62
- "@lowdefy/blocks-basic": "7.0.0",
63
- "@lowdefy/blocks-captcha": "7.0.0",
64
- "@lowdefy/blocks-echarts": "7.0.0",
65
- "@lowdefy/blocks-loaders": "7.0.0",
66
- "@lowdefy/blocks-markdown": "7.0.0",
67
- "@lowdefy/blocks-table": "7.0.0",
68
- "@lowdefy/blocks-tiptap": "7.0.0",
69
- "@lowdefy/build": "7.0.0",
70
- "@lowdefy/client": "7.0.0",
71
- "@lowdefy/connection-axios-http": "7.0.0",
72
- "@lowdefy/connection-mongodb": "7.0.0",
73
- "@lowdefy/docs-content": "7.0.0",
74
- "@lowdefy/e2e-utils": "7.0.0",
75
- "@lowdefy/engine": "7.0.0",
76
- "@lowdefy/errors": "7.0.0",
77
- "@lowdefy/helpers": "7.0.0",
78
- "@lowdefy/layout": "7.0.0",
79
- "@lowdefy/logger": "7.0.0",
80
- "@lowdefy/node-utils": "7.0.0",
81
- "@lowdefy/operators-change-case": "7.0.0",
82
- "@lowdefy/operators-cron": "7.0.0",
83
- "@lowdefy/operators-dayjs": "7.0.0",
84
- "@lowdefy/operators-diff": "7.0.0",
85
- "@lowdefy/operators-js": "7.0.0",
86
- "@lowdefy/operators-mql": "7.0.0",
87
- "@lowdefy/operators-nunjucks": "7.0.0",
88
- "@lowdefy/operators-uuid": "7.0.0",
89
- "@lowdefy/operators-yaml": "7.0.0",
90
- "@lowdefy/plugin-better-auth": "7.0.0",
91
- "@lowdefy/websockets-core": "7.0.0",
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;