@antelopejs/dms-database 0.1.2 → 0.2.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 (30) hide show
  1. package/README.md +1 -1
  2. package/frontend-vue/app/build/composables/useDataBrowserGrid.ts +4 -38
  3. package/frontend-vue/app/build/composables/useDatabaseHealth.ts +7 -4
  4. package/frontend-vue/app/build/composables/useDatabaseSchemas.ts +1 -2
  5. package/frontend-vue/app/build/composables/useDiagramPersistence.ts +49 -16
  6. package/frontend-vue/app/build/composables/useQueryStore.ts +59 -27
  7. package/frontend-vue/app/build/composables/useTableCounts.ts +50 -0
  8. package/frontend-vue/app/build/query/runs.ts +7 -27
  9. package/frontend-vue/app/build/schemas/useSchemasListing.ts +10 -33
  10. package/frontend-vue/app/components/ConnectionStatus.vue +14 -11
  11. package/frontend-vue/app/components/DataBrowserSidebar.vue +20 -42
  12. package/frontend-vue/app/components/DataBrowserTabBar.vue +1 -1
  13. package/frontend-vue/app/components/DataGrid.vue +43 -89
  14. package/frontend-vue/app/components/InstancePicker.vue +63 -55
  15. package/frontend-vue/app/components/QueryConsole.vue +47 -37
  16. package/frontend-vue/app/components/QueryLibrary.vue +20 -17
  17. package/frontend-vue/app/components/RowDrawer.vue +15 -18
  18. package/frontend-vue/app/components/SchemaDiagram.vue +7 -7
  19. package/frontend-vue/app/components/TableInspector.vue +116 -84
  20. package/frontend-vue/package.json +2 -1
  21. package/frontend-vue/pnpm-lock.yaml +883 -2587
  22. package/frontend-vue/tests/fixtures/inertia.ts +8 -0
  23. package/frontend-vue/tests/fixtures/serverRender.ts +219 -0
  24. package/frontend-vue/tests/fixtures/vueI18n.ts +4 -0
  25. package/frontend-vue/tests/helpers.test.ts +1 -0
  26. package/frontend-vue/tests/locales.test.ts +24 -0
  27. package/frontend-vue/tests/onDemandReads.test.ts +106 -0
  28. package/frontend-vue/tests/serverRender.test.ts +235 -0
  29. package/frontend-vue/vitest.config.ts +24 -0
  30. package/package.json +4 -4
@@ -68,7 +68,6 @@ const BROWSE_LIST = '/api/database/browse/list'
68
68
  const BROWSE_COUNT = '/api/database/browse/count'
69
69
 
70
70
  const { t, n } = useI18n()
71
- const { $authFetch } = useAuthFetch()
72
71
  const { copy } = useClipboard()
73
72
  const toast = useToast()
74
73
 
@@ -222,26 +221,23 @@ async function follow(target: TableRow) {
222
221
  }
223
222
 
224
223
  // --- rows: in every instance at once; the data browser shows each one's ---
225
- const total = ref<number | null>(null)
226
-
227
- async function loadTotal(key: string) {
228
- total.value = null
229
- if (!schemaId.value || !tableName.value) return
230
- try {
231
- const result = await $authFetch<{ total: number }>(BROWSE_COUNT, {
232
- query: {
233
- filter_schema: `is:${schemaId.value}`,
234
- filter_table: `is:${tableName.value}`,
235
- filter_instance: `is:${CROSS_INSTANCE_VALUE}`,
236
- },
237
- })
238
- if (key === tableKey.value) total.value = result.total
239
- } catch {
240
- // The total the list gave, if any, stays.
241
- }
242
- }
243
-
244
- watch(tableKey, loadTotal, { immediate: true })
224
+ // Read with the table, on the server too when the link opens the drawer; no
225
+ // figure shows while another table's loads, nor once it failed.
226
+ const rowTotal = useApiData<{ total: number }>(
227
+ () => (schemaId.value && tableName.value ? BROWSE_COUNT : null),
228
+ {
229
+ query: () => ({
230
+ filter_schema: `is:${schemaId.value}`,
231
+ filter_table: `is:${tableName.value}`,
232
+ filter_instance: `is:${CROSS_INSTANCE_VALUE}`,
233
+ }),
234
+ },
235
+ )
236
+ const total = computed(() =>
237
+ rowTotal.status.value === 'success'
238
+ ? (rowTotal.data.value?.total ?? null)
239
+ : null,
240
+ )
245
241
 
246
242
  const rowsFact = computed(() => {
247
243
  const rows = total.value
@@ -283,20 +279,94 @@ const tabs = computed(() => [
283
279
  ])
284
280
 
285
281
  // --- sample row: the latest row, or the next one on demand ---
286
- const sample = ref<Record<string, unknown> | null>(null)
287
- const sampleOffset = ref(0)
288
- const sampleTotal = ref(0)
289
- const sampleLoading = ref(false)
290
- const sampleFailed = ref(false)
291
-
292
282
  interface SamplePage {
293
283
  results: Record<string, unknown>[]
294
284
  total: number
295
285
  }
296
286
 
297
- // The instance the sample is read from: the default one, or the first named
298
- // instance holding rows when the default one holds none.
299
- const sampleInstance = ref<string | null>(null)
287
+ /**
288
+ * The row asked for: its offset in an instance. No instance at offset 0 reads
289
+ * the default one, or the first named instance holding rows when the default
290
+ * one holds none.
291
+ */
292
+ interface SampleRequest {
293
+ instance: string | null
294
+ offset: number
295
+ }
296
+
297
+ /** A sample read, with the table and the instance it was read from. */
298
+ interface SampleRead {
299
+ table: string
300
+ instance: string | null
301
+ page: SamplePage
302
+ }
303
+
304
+ const requested = shallowRef<SampleRequest>({ instance: null, offset: 0 })
305
+
306
+ type ReadSample = (instance: string | null) => Promise<SamplePage>
307
+
308
+ async function firstFilledInstance(
309
+ read: ReadSample,
310
+ ): Promise<{ instance: string | null; page: SamplePage }> {
311
+ const page = await read(null)
312
+ if (page.total > 0) return { instance: null, page }
313
+ for (const instance of schema.value?.instances ?? []) {
314
+ const named = await read(instance)
315
+ if (named.total > 0) return { instance, page: named }
316
+ }
317
+ return { instance: null, page }
318
+ }
319
+
320
+ // Read once the Sample tab shows, in the browser only.
321
+ const sampleRead = useApiData<SampleRead>(
322
+ () =>
323
+ schemaId.value && tableName.value
324
+ ? `dms-database:sample:${JSON.stringify([
325
+ tableKey.value,
326
+ requested.value.instance,
327
+ requested.value.offset,
328
+ ])}`
329
+ : null,
330
+ async ({ $authFetch, signal }) => {
331
+ const table = tableKey.value
332
+ const { instance, offset } = requested.value
333
+ const read: ReadSample = (from) =>
334
+ $authFetch<SamplePage>(BROWSE_LIST, {
335
+ query: {
336
+ filter_schema: `is:${schemaId.value}`,
337
+ filter_table: `is:${tableName.value}`,
338
+ offset,
339
+ limit: 1,
340
+ ...(from ? { filter_instance: `is:${from}` } : {}),
341
+ },
342
+ signal,
343
+ })
344
+ const found =
345
+ instance === null && offset === 0
346
+ ? await firstFilledInstance(read)
347
+ : { instance, page: await read(instance) }
348
+ return { table, ...found }
349
+ },
350
+ {
351
+ immediate: false,
352
+ server: false,
353
+ watch: false,
354
+ refreshOnPageRefresh: false,
355
+ },
356
+ )
357
+
358
+ // Another table (a relation followed) shows none of the previous one's row.
359
+ const sampleShown = computed(() => {
360
+ const value = sampleRead.data.value
361
+ return value?.table === tableKey.value ? value : null
362
+ })
363
+ const sample = computed(() => sampleShown.value?.page.results?.[0] ?? null)
364
+ const sampleTotal = computed(() => sampleShown.value?.page.total ?? 0)
365
+ const sampleOffset = computed(() => requested.value.offset)
366
+ const sampleLoading = computed(() => sampleRead.pending.value)
367
+ const sampleFailed = computed(() => sampleRead.status.value === 'error')
368
+ // The instance the sample is read from.
369
+ const sampleInstance = computed(() => sampleShown.value?.instance ?? null)
300
370
 
301
371
  const sampleInstanceLabel = computed(() =>
302
372
  sampleInstance.value === null
@@ -306,72 +376,34 @@ const sampleInstanceLabel = computed(() =>
306
376
  }),
307
377
  )
308
378
 
309
- function fetchSample(instance: string | null) {
310
- const query: Record<string, string | number> = {
311
- filter_schema: `is:${schemaId.value}`,
312
- filter_table: `is:${tableName.value}`,
313
- offset: sampleOffset.value,
314
- limit: 1,
315
- }
316
- if (instance) query.filter_instance = `is:${instance}`
317
- return $authFetch<SamplePage>(BROWSE_LIST, { query })
318
- }
319
-
320
- async function firstFilledInstance(): Promise<SamplePage> {
321
- const page = await fetchSample(null)
322
- if (page.total > 0) return page
323
- for (const instance of schema.value?.instances ?? []) {
324
- const named = await fetchSample(instance)
325
- if (named.total > 0) {
326
- sampleInstance.value = instance
327
- return named
328
- }
329
- }
330
- return page
331
- }
332
-
333
- async function loadSample() {
334
- if (!schemaId.value || !tableName.value) return
335
- sampleLoading.value = true
336
- sampleFailed.value = false
337
- try {
338
- const res =
339
- sampleInstance.value === null && sampleOffset.value === 0
340
- ? await firstFilledInstance()
341
- : await fetchSample(sampleInstance.value)
342
- sample.value = res.results?.[0] ?? null
343
- sampleTotal.value = res.total ?? 0
344
- } catch {
345
- sampleFailed.value = true
346
- sample.value = null
347
- } finally {
348
- sampleLoading.value = false
349
- }
379
+ function loadSample() {
380
+ if (schemaId.value && tableName.value) void sampleRead.execute()
350
381
  }
351
382
 
352
383
  function nextSample() {
353
- sampleOffset.value =
354
- sampleTotal.value > 0 ? (sampleOffset.value + 1) % sampleTotal.value : 0
384
+ const total = sampleTotal.value
385
+ requested.value = {
386
+ instance: sampleInstance.value,
387
+ offset: total > 0 ? (requested.value.offset + 1) % total : 0,
388
+ }
355
389
  loadSample()
356
390
  }
357
391
 
358
392
  // Another table (a relation followed) starts over from its latest row.
359
393
  watch(tableKey, () => {
360
- sample.value = null
361
- sampleOffset.value = 0
362
- sampleTotal.value = 0
363
- sampleInstance.value = null
364
- sampleFailed.value = false
394
+ requested.value = { instance: null, offset: 0 }
365
395
  if (tab.value === 'sample' && !loading.value) loadSample()
366
396
  })
367
397
 
368
398
  // The sample waits for the schemas: they name the instances to look in.
369
- watch(
370
- () => tab.value === 'sample' && !loading.value,
371
- (ready) => {
372
- if (ready && !sample.value && !sampleLoading.value) loadSample()
373
- },
374
- { immediate: true },
399
+ onMounted(() =>
400
+ watch(
401
+ () => tab.value === 'sample' && !loading.value,
402
+ (ready) => {
403
+ if (ready && !sample.value && !sampleLoading.value) loadSample()
404
+ },
405
+ { immediate: true },
406
+ ),
375
407
  )
376
408
 
377
409
  // The `_instance` tag the store adds to rows of named instances is not a
@@ -6,7 +6,7 @@
6
6
  "main": "./dms.frontend.ts",
7
7
  "packageManager": "pnpm@10.6.5",
8
8
  "engines": {
9
- "@antelopejs/dms-frontend": ">=0.5.6 <0.6.0"
9
+ "@antelopejs/dms-frontend": ">=0.6.0 <0.7.0"
10
10
  },
11
11
  "scripts": {
12
12
  "dev": "pnpm --dir ../playground frontend:dev",
@@ -36,6 +36,7 @@
36
36
  "@intlify/message-compiler": "^11",
37
37
  "@nuxt/ui": "4.10.0",
38
38
  "@typescript-eslint/parser": "^8.0.0",
39
+ "@vitejs/plugin-vue": "^6.0.10",
39
40
  "eslint": "^9.0.0",
40
41
  "eslint-plugin-vue": "^10.0.0",
41
42
  "prettier": "^3.5.3",