@datagouv/components-next 1.0.2-dev.93 → 1.0.2-dev.95

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,4 +1,4 @@
1
- import { c as Ke } from "./main-DEpfIocP.js";
1
+ import { c as Ke } from "./main-D4WQMky0.js";
2
2
  import We from "vue";
3
3
  function Fe(I, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.93",
3
+ "version": "1.0.2-dev.95",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -13,6 +13,6 @@ export function useMetrics() {
13
13
  getDatasetMetrics: (datasetId: string) => getDatasetMetrics(datasetId, config.metricsApiUrl!),
14
14
  getDataserviceMetrics: (dataserviceId: string) => getDataserviceMetrics(dataserviceId, config.metricsApiUrl!),
15
15
  getReuseMetrics: (reuseId: string) => getReuseMetrics(reuseId, config.metricsApiUrl!),
16
- createDatasetsForOrganizationMetricsUrl: (organizationId: string) => createDatasetsForOrganizationMetricsUrl(organizationId, config.metricsApiUrl!, config.apiBase),
16
+ createDatasetsForOrganizationMetricsUrl: (organizationId: string) => createDatasetsForOrganizationMetricsUrl(organizationId, config.metricsApiUrl!, config.apiBase, config.$fetch),
17
17
  }
18
18
  }
package/src/config.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { inject, type Component, type InjectionKey } from 'vue'
2
2
  import type { UseFetchFunction } from './functions/api.types'
3
- import type { FetchOptions } from 'ofetch'
3
+ import type { $Fetch, FetchOptions } from 'ofetch'
4
4
 
5
5
  export type PluginConfig = {
6
6
  name: string // Name of the application (ex: data.gouv.fr)
@@ -25,6 +25,16 @@ export type PluginConfig = {
25
25
  tabularAllowRemote?: boolean
26
26
  tabularApiDataserviceId?: string
27
27
  customUseFetch?: UseFetchFunction | null
28
+ /**
29
+ * Imperative configured fetch (auth, headers, error handling): the single source of truth for
30
+ * requests. The default `useFetch` uses it as its transport, and imperative helpers (metrics CSV
31
+ * export, …) call it directly — so they never need a `?? ofetch` fallback.
32
+ * Optional for consumers: when omitted, the plugin defaults it to an `ofetch` instance built from
33
+ * the `onRequest`/`onResponse` hooks below (see the `datagouv` plugin install). A consumer can
34
+ * instead provide its own (e.g. a Bearer-authenticated `$fetch`) and skip the hooks entirely.
35
+ */
36
+ $fetch?: $Fetch | null
37
+ /** Auth/headers/error hooks. Folded into the default `$fetch` when no `$fetch` is provided. */
28
38
  onRequest?: FetchOptions['onRequest']
29
39
  onRequestError?: FetchOptions['onRequestError']
30
40
  onResponse?: FetchOptions['onResponse']
@@ -38,8 +48,12 @@ export type PluginConfig = {
38
48
 
39
49
  export const configKey = Symbol() as InjectionKey<PluginConfig>
40
50
 
41
- export function useComponentsConfig(): PluginConfig {
51
+ // After the `datagouv` plugin install, `$fetch` is always set (defaulted to an ofetch instance), so
52
+ // consumers of the config can rely on it without a fallback.
53
+ export type ResolvedPluginConfig = PluginConfig & { $fetch: $Fetch }
54
+
55
+ export function useComponentsConfig(): ResolvedPluginConfig {
42
56
  const config = inject(configKey)
43
57
  if (!config) throw new Error('Calling `useComponentsConfig` outside @datagouv/components')
44
- return config
58
+ return config as ResolvedPluginConfig
45
59
  }
@@ -1,7 +1,6 @@
1
1
  import { ref, toValue, watchEffect, onMounted, type ComputedRef, type MaybeRefOrGetter, type Ref } from 'vue'
2
2
  import { ofetch } from 'ofetch'
3
3
  import { useComponentsConfig } from '../config'
4
- import { useTranslation } from '../composables/useTranslation'
5
4
  import type { AsyncData, AsyncDataRequestStatus, UseFetchOptions } from './api.types'
6
5
 
7
6
  function deepToValue(obj: MaybeRefOrGetter<Record<string, unknown> | undefined>): Record<string, unknown> | undefined {
@@ -18,8 +17,6 @@ export async function useFetch<DataT, ErrorT = never>(
18
17
  ): Promise<AsyncData<DataT, ErrorT>> {
19
18
  const config = useComponentsConfig()
20
19
 
21
- const { locale } = useTranslation()
22
-
23
20
  if (config.customUseFetch) {
24
21
  return await config.customUseFetch(url, options)
25
22
  }
@@ -37,38 +34,12 @@ export async function useFetch<DataT, ErrorT = never>(
37
34
  status.value = 'pending'
38
35
  try {
39
36
  data.value = isRaw
37
+ // Raw targets another data.gouv service (the Tabular API in TabularExplorer) via its own
38
+ // absolute URL, so it must stay bare ofetch: no datagouv apiBase, no datagouv auth attached.
40
39
  ? await ofetch<DataT | null>(urlValue, { params: params ?? query })
41
- : await ofetch<DataT | null>(urlValue, {
42
- baseURL: config.apiBase,
43
- params: params ?? query,
44
- onRequest(param) {
45
- if (config.onRequest) {
46
- if (Array.isArray(config.onRequest)) {
47
- config.onRequest.forEach(r => r(param))
48
- }
49
- else {
50
- config.onRequest(param)
51
- }
52
- }
53
- const { options } = param
54
- options.headers.set('Content-Type', 'application/json')
55
- options.headers.set('Accept', 'application/json')
56
- options.credentials = 'include'
57
- if (config.devApiKey) {
58
- options.headers.set('X-API-KEY', config.devApiKey)
59
- }
60
-
61
- if (locale) {
62
- if (!options.params) {
63
- options.params = {}
64
- }
65
- options.params['lang'] = locale
66
- }
67
- },
68
- onRequestError: config.onRequestError,
69
- onResponse: config.onResponse,
70
- onResponseError: config.onResponseError,
71
- })
40
+ // The configured `$fetch` carries baseURL + auth + headers (see the `datagouv` plugin install
41
+ // for the default one). We only forward the URL and params here.
42
+ : await config.$fetch<DataT | null>(urlValue, { baseURL: config.apiBase, params: params ?? query })
72
43
  status.value = 'success'
73
44
  }
74
45
  catch (e) {
@@ -1,5 +1,5 @@
1
1
  import { escapeCsvValue } from './helpers'
2
- import { ofetch } from 'ofetch'
2
+ import { ofetch, type $Fetch } from 'ofetch'
3
3
  import type { DatasetV2 } from '../types/datasets'
4
4
  import type { PaginatedArray } from '../types/api'
5
5
 
@@ -119,14 +119,16 @@ export async function getDatasetMetrics(datasetId: string, metricsApi: string):
119
119
  }
120
120
  }
121
121
 
122
- export async function createDatasetsForOrganizationMetricsUrl(organizationId: string, metricsApi: string, apiBase: string) {
122
+ export async function createDatasetsForOrganizationMetricsUrl(organizationId: string, metricsApi: string, apiBase: string, apiFetch: $Fetch) {
123
123
  let data = 'dataset_title,dataset_id,month,monthly_visit,monthly_download_resource\n'
124
124
 
125
- // fetch datasets info from organization datasets
125
+ // fetch datasets info from organization datasets through the configured fetch, so it carries the
126
+ // consumer's auth (cookie for cdata via `$api`, Bearer for verticals) instead of a hardcoded
127
+ // `credentials: 'include'`, which breaks CORS cross-origin on the verticals.
126
128
  const datasets: Record<string, Record<string, string>> = {}
127
129
  let datasetsUrl: string | null = `/api/2/datasets/?organization=${organizationId}&page_size=200`
128
130
  while (datasetsUrl) {
129
- const body: PaginatedArray<DatasetV2> = await ofetch(datasetsUrl, { baseURL: apiBase, credentials: 'include' })
131
+ const body: PaginatedArray<DatasetV2> = await apiFetch(datasetsUrl, { baseURL: apiBase })
130
132
  datasetsUrl = body.next_page
131
133
  for (const row of body.data) {
132
134
  datasets[row.id] = { title: row.title }
package/src/main.ts CHANGED
@@ -104,6 +104,8 @@ import InfiniteLoader from './components/InfiniteLoader.vue'
104
104
  import TabularExplorer from './components/TabularExplorer/TabularExplorer.vue'
105
105
  import type { UseFetchFunction } from './functions/api.types'
106
106
  import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
107
+ import { ofetch } from 'ofetch'
108
+ import { useTranslation } from './composables/useTranslation'
107
109
 
108
110
  export { Toaster, toast } from 'vue-sonner'
109
111
 
@@ -278,6 +280,31 @@ export {
278
280
  // Vue Plugin
279
281
  const datagouv: Plugin<PluginConfig> = {
280
282
  async install(app: App, options) {
283
+ // Default `$fetch` to an ofetch instance carrying the datagouv API specifics + the consumer's
284
+ // auth hooks, so everything downstream (the default `useFetch`, imperative helpers) can rely on
285
+ // a single configured fetch. A consumer that provides its own `$fetch` keeps full control.
286
+ if (!options.$fetch) {
287
+ options.$fetch = ofetch.create({
288
+ baseURL: options.apiBase,
289
+ onRequest(context) {
290
+ if (options.onRequest) {
291
+ if (Array.isArray(options.onRequest)) options.onRequest.forEach(hook => hook(context))
292
+ else options.onRequest(context)
293
+ }
294
+ context.options.headers.set('Content-Type', 'application/json')
295
+ context.options.headers.set('Accept', 'application/json')
296
+ if (options.devApiKey) context.options.headers.set('X-API-KEY', options.devApiKey)
297
+ const { locale } = useTranslation()
298
+ if (locale) {
299
+ context.options.params ??= {}
300
+ context.options.params['lang'] = locale
301
+ }
302
+ },
303
+ onRequestError: options.onRequestError,
304
+ onResponse: options.onResponse,
305
+ onResponseError: options.onResponseError,
306
+ })
307
+ }
281
308
  app.provide(configKey, options)
282
309
  if (!options.textClamp) {
283
310
  const textClamp = await import('vue3-text-clamp')