@hostlink/nuxt-light 1.74.1 → 1.76.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/README.md CHANGED
@@ -55,6 +55,114 @@ export default defineNuxtConfig({
55
55
 
56
56
  That's it! You can now use nuxt-light in your Nuxt app ✨
57
57
 
58
+ ## API clients
59
+
60
+ `nuxt-light` supports multiple Light API endpoints. The client named `auth` is
61
+ always the default: when a component or composable does not specify a client,
62
+ the request is sent to `auth`.
63
+
64
+ Configure named clients in `nuxt.config.ts`:
65
+
66
+ ```ts
67
+ export default defineNuxtConfig({
68
+ runtimeConfig: {
69
+ public: {
70
+ light: {
71
+ clients: {
72
+ auth: {
73
+ baseURL: 'https://auth.example.com/graphql',
74
+ },
75
+ business: {
76
+ baseURL: 'https://business.example.com/graphql',
77
+ },
78
+ },
79
+ },
80
+ },
81
+ },
82
+ modules: [
83
+ '@hostlink/nuxt-light',
84
+ ],
85
+ })
86
+ ```
87
+
88
+ A client can also be configured with a URL string:
89
+
90
+ ```ts
91
+ clients: {
92
+ auth: '/auth-api/',
93
+ business: '/business-api/',
94
+ }
95
+ ```
96
+
97
+ ### Backwards-compatible single endpoint
98
+
99
+ The existing `public.apiBase` setting is still supported. When no named
100
+ `auth` client is configured, `apiBase` is registered as the `auth` client:
101
+
102
+ ```ts
103
+ export default defineNuxtConfig({
104
+ runtimeConfig: {
105
+ public: {
106
+ apiBase: '/api/',
107
+ },
108
+ },
109
+ })
110
+ ```
111
+
112
+ If neither setting is provided, the default Auth API URL is `/api/`.
113
+
114
+ ### Selecting a client
115
+
116
+ Use `useLightClient()` without an argument to get the Auth API client:
117
+
118
+ ```ts
119
+ const auth = useLightClient()
120
+ const business = useLightClient('business')
121
+
122
+ await auth.auth.login(username, password)
123
+ await business.query({
124
+ app: {
125
+ orders: {
126
+ order_id: true,
127
+ },
128
+ },
129
+ })
130
+ ```
131
+
132
+ The standard composables also default to `auth`:
133
+
134
+ ```ts
135
+ await q(query) // auth
136
+ await q(query, 'business') // business
137
+
138
+ model('User') // auth
139
+ model('Order', 'business') // business
140
+
141
+ collect('User', fields) // auth
142
+ collect('Order', fields, 'business') // business
143
+
144
+ await m('updateUser', args, fields) // auth
145
+ await m('updateOrder', args, fields, 'business') // business
146
+ ```
147
+
148
+ `<L-Table>` uses `auth` unless its `client` prop is provided:
149
+
150
+ ```vue
151
+ <template>
152
+ <!-- Uses the default auth client -->
153
+ <L-Table model-name="User" />
154
+
155
+ <!-- Uses the business client -->
156
+ <L-Table
157
+ client="business"
158
+ model-name="Order"
159
+ />
160
+ </template>
161
+ ```
162
+
163
+ An unknown client name throws an explicit configuration error instead of
164
+ silently sending the request to another endpoint.
165
+
58
166
  ## Development
59
167
 
60
168
  ```bash
@@ -119,4 +227,4 @@ nuxt.config.ts
119
227
  ]
120
228
  }
121
229
  }
122
- ```
230
+ ```
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.74.1",
4
+ "version": "1.76.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -220,7 +220,6 @@ const module$1 = defineNuxtModule({
220
220
  });
221
221
  addImports({ name: "isGranted", from: "@hostlink/light" });
222
222
  addImports({ name: "getGrantedRights", from: "@hostlink/light" });
223
- addImports({ name: "query", as: "q", from: "@hostlink/light" });
224
223
  addImports({ name: "mutation", from: "@hostlink/light" });
225
224
  addImports({ name: "query", from: "@hostlink/light" });
226
225
  await addComponentsDir({
@@ -27,6 +27,7 @@ export type LTableColumn = QTableColumn & {
27
27
  autoWidth?: boolean;
28
28
  };
29
29
  export type LTableProps = QTableProps & {
30
+ client?: string;
30
31
  columns?: Array<LTableColumn>;
31
32
  actions?: Array<'view' | 'edit' | 'delete' | 'update'>;
32
33
  sortBy?: string;
@@ -127,6 +128,7 @@ declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
127
128
  rowsPerPageLabel: string;
128
129
  rowsPerPageOptions: readonly any[];
129
130
  selection: "none" | "single" | "multiple";
131
+ client: string;
130
132
  searchable: boolean;
131
133
  canExpandRow: (row: any) => boolean;
132
134
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -5,8 +5,9 @@ import { ref, computed, onMounted, useSlots, watch, useAttrs } from "vue";
5
5
  import useLight from "../../composables/useLight";
6
6
  import { useLightTableDefaults } from "../../composables/useLightProps";
7
7
  import model from "../../composables/model";
8
+ import useLightClient from "../../composables/useLightClient";
8
9
  import { toQuery } from "@hostlink/light";
9
- import { list, navigateTo } from "#imports";
10
+ import { navigateTo } from "#imports";
10
11
  import { useI18n } from "vue-i18n";
11
12
  import { useStorage, useSessionStorage } from "@vueuse/core";
12
13
  import { useRoute } from "#imports";
@@ -98,6 +99,7 @@ const props = defineProps({
98
99
  "onUpdate:selected": { type: Function, required: false },
99
100
  "onUpdate:expanded": { type: Function, required: false },
100
101
  onVirtualScroll: { type: Function, required: false },
102
+ client: { type: String, required: false, default: "auth" },
101
103
  actions: { type: Array, required: false, default: () => [] },
102
104
  sortBy: { type: String, required: false },
103
105
  modelName: { type: null, required: false },
@@ -138,7 +140,7 @@ if (!saveFilters.value) {
138
140
  defineOptions({ inheritAttrs: false });
139
141
  const light = useLight();
140
142
  const vAttrs = useAttrs();
141
- const L_KEYS = ["actions", "sortBy", "modelName", "searchable", "onRequestData", "addComponent", "addComponentProps", "name", "searchStyle", "canExpandRow"];
143
+ const L_KEYS = ["client", "actions", "sortBy", "modelName", "searchable", "onRequestData", "addComponent", "addComponentProps", "name", "searchStyle", "canExpandRow"];
142
144
  const base = useLightTableDefaults(props, vAttrs, L_KEYS);
143
145
  const pagination = ref(props.pagination);
144
146
  if (props.rowsPerPageOptions[0] == 0) {
@@ -195,6 +197,17 @@ const resolveColumnOptions = async (col) => {
195
197
  } catch {
196
198
  }
197
199
  };
200
+ const autoWidthSelectStyle = (col) => {
201
+ if (!col.autoWidth) return {};
202
+ const options = localSearchOptions.value[col.name] ?? [];
203
+ const labelWidth = (option) => {
204
+ const label = String(option.label ?? option.value ?? "");
205
+ return Array.from(label).reduce((width2, character) => width2 + (character.charCodeAt(0) > 255 ? 2 : 1), 0);
206
+ };
207
+ const longestLabel = Math.max(0, ...options.map(labelWidth));
208
+ const width = Math.min(36, Math.max(12, longestLabel + 4));
209
+ return { width: `${width}ch`, minWidth: `${width}ch` };
210
+ };
198
211
  onMounted(async () => {
199
212
  await Promise.all(
200
213
  (props.columns ?? []).map((col) => resolveColumnOptions(col))
@@ -331,7 +344,7 @@ const onLocalRequest = async (p) => {
331
344
  }
332
345
  loading.value = true;
333
346
  try {
334
- let l = list(model2, localFields);
347
+ let l = useLightClient(props.client).list(model2, localFields);
335
348
  l = l.filters(localFilters);
336
349
  if (sort) {
337
350
  l = l.sort(sort);
@@ -420,7 +433,7 @@ const ss = computed(() => Object.keys(slots));
420
433
  const onDelete = async (id) => {
421
434
  if (modelName.value == null) return;
422
435
  try {
423
- await model(modelName.value).delete(id);
436
+ await model(modelName.value, props.client).delete(id);
424
437
  } catch (e) {
425
438
  $q.notify({
426
439
  message: e.message,
@@ -700,7 +713,7 @@ const hasFilters = computed(() => {
700
713
  <q-td v-if="selection != 'none'" auto-width />
701
714
  <q-td v-if="hasRowExpand" auto-width />
702
715
  <q-td v-if="hasActions" auto-width />
703
- <q-td v-for="col in props.cols" :key="col.name" :style="col.style">
716
+ <q-td v-for="col in props.cols" :key="col.name" :auto-width="col.autoWidth ?? false" :style="col.style">
704
717
 
705
718
  <template v-if="col.searchable">
706
719
 
@@ -724,7 +737,7 @@ const hasFilters = computed(() => {
724
737
  :options="localSearchOptions[col.name]" emit-value map-options
725
738
  :multiple="col.searchMultiple" use-input input-debounce="0" @filter="(val, update) => {
726
739
  searchSelectFilter(val, update, col.name);
727
- }" :style="col.searchStyle" :placeholder="col.searchPlaceholder" />
740
+ }" :style="[autoWidthSelectStyle(col), col.searchStyle]" :placeholder="col.searchPlaceholder" />
728
741
  </template>
729
742
 
730
743
  <template v-if="col.searchType == 'date'">
@@ -27,6 +27,7 @@ export type LTableColumn = QTableColumn & {
27
27
  autoWidth?: boolean;
28
28
  };
29
29
  export type LTableProps = QTableProps & {
30
+ client?: string;
30
31
  columns?: Array<LTableColumn>;
31
32
  actions?: Array<'view' | 'edit' | 'delete' | 'update'>;
32
33
  sortBy?: string;
@@ -127,6 +128,7 @@ declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
127
128
  rowsPerPageLabel: string;
128
129
  rowsPerPageOptions: readonly any[];
129
130
  selection: "none" | "single" | "multiple";
131
+ client: string;
130
132
  searchable: boolean;
131
133
  canExpandRow: (row: any) => boolean;
132
134
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -1,2 +1,2 @@
1
- declare const _default: (name: string, fields: Object) => any;
1
+ declare const _default: (name: string, fields: Object, client?: string) => any;
2
2
  export default _default;
@@ -1,5 +1,4 @@
1
- import { getApiClient } from "@hostlink/light";
2
- export default (name, fields) => {
3
- const client = getApiClient();
4
- return client.collect(name, fields);
1
+ import useLightClient from "./useLightClient.js";
2
+ export default (name, fields, client = "auth") => {
3
+ return useLightClient(client).collect(name, fields);
5
4
  };
@@ -1,3 +1,3 @@
1
1
  export default function (operation: string, args?: {
2
2
  [key: string]: any;
3
- }, fields?: any): Promise<any>;
3
+ }, fields?: any, client?: string): Promise<any>;
@@ -1,4 +1,4 @@
1
- import { mutation } from "@hostlink/light";
1
+ import useLightClient from "./useLightClient.js";
2
2
  const removeUndefinedValues = (obj) => {
3
3
  for (const key in obj) {
4
4
  if (obj[key] instanceof File) {
@@ -13,7 +13,7 @@ const removeUndefinedValues = (obj) => {
13
13
  }
14
14
  return obj;
15
15
  };
16
- export default function(operation, args, fields = []) {
16
+ export default function(operation, args, fields = [], client = "auth") {
17
17
  if (args) {
18
18
  args = removeUndefinedValues(args);
19
19
  }
@@ -29,7 +29,7 @@ export default function(operation, args, fields = []) {
29
29
  if (Object.keys(q[operation]).length === 0) {
30
30
  q[operation] = true;
31
31
  }
32
- return mutation(q).then((res) => {
32
+ return useLightClient(client).mutation(q).then((res) => {
33
33
  return res[operation];
34
34
  });
35
35
  }
@@ -1,5 +1,5 @@
1
1
  import type { LTableColumn } from "../components/L/Table.vue.js";
2
- declare const _default: (name: string) => Omit<{
2
+ declare const _default: (name: string, client?: string) => Omit<{
3
3
  field: (f: string) => import("@hostlink/light").Field | null;
4
4
  $fields: Record<string, import("@hostlink/light").Field>;
5
5
  setDataPath(path: string): string;
@@ -1,7 +1,7 @@
1
- import { getModel } from "@hostlink/light";
1
+ import useLightClient from "./useLightClient.js";
2
2
  import defu from "defu";
3
- export default (name) => {
4
- const m = getModel(name);
3
+ export default (name, client = "auth") => {
4
+ const m = useLightClient(client).model(name);
5
5
  return defu(m, {
6
6
  columns(fields) {
7
7
  let columns = [];
@@ -0,0 +1,2 @@
1
+ import type { GraphQLQuery } from '@hostlink/light';
2
+ export default function q(query: GraphQLQuery, client?: string): Promise<any>;
@@ -0,0 +1,4 @@
1
+ import useLightClient from "./useLightClient.js";
2
+ export default function q(query, client = "auth") {
3
+ return useLightClient(client).query(query);
4
+ }
@@ -0,0 +1,4 @@
1
+ import type { LightClient } from '@hostlink/light';
2
+ export declare function registerLightClient(name: string, client: LightClient): void;
3
+ export declare function clearLightClients(): void;
4
+ export default function useLightClient(name?: string): LightClient;
@@ -0,0 +1,12 @@
1
+ const clients = /* @__PURE__ */ new Map();
2
+ export function registerLightClient(name, client) {
3
+ clients.set(name, client);
4
+ }
5
+ export function clearLightClients() {
6
+ clients.clear();
7
+ }
8
+ export default function useLightClient(name = "auth") {
9
+ const client = clients.get(name);
10
+ if (!client) throw new Error(`Light client "${name}" is not configured`);
11
+ return client;
12
+ }
@@ -12,10 +12,22 @@ import { createLightPlugin } from "./formkit/index.js";
12
12
  import { plugin, defaultConfig } from "@formkit/vue";
13
13
  import getApiBase from "./composables/getApiBase.js";
14
14
  import useLight from "./composables/useLight.js";
15
+ import useLightClient, { clearLightClients, registerLightClient } from "./composables/useLightClient.js";
15
16
  import { zhTW } from "@formkit/i18n";
16
17
  export default defineNuxtPlugin((nuxtApp) => {
17
- const client = createClient(getApiBase());
18
- setApiClient(client);
18
+ const runtimeConfig = useRuntimeConfig();
19
+ const lightConfig = runtimeConfig.public.light;
20
+ const configuredClients = lightConfig?.clients ?? {};
21
+ clearLightClients();
22
+ for (const [name, config] of Object.entries(configuredClients)) {
23
+ const baseURL = typeof config === "string" ? config : config.baseURL;
24
+ if (!baseURL) throw new Error(`Light client "${name}" requires a baseURL`);
25
+ registerLightClient(name, createClient(baseURL));
26
+ }
27
+ if (!configuredClients.auth) {
28
+ registerLightClient("auth", createClient(getApiBase()));
29
+ }
30
+ setApiClient(useLightClient());
19
31
  defineModel("Permission", {}).setDataPath("app.listPermission");
20
32
  defineModel("SystemValue", {}).setDataPath("app.listSystemValue");
21
33
  defineModel("Config", {}).setDataPath("app.listConfig");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.74.1",
3
+ "version": "1.76.0",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  "@formkit/inputs": "^2.1.2",
39
39
  "@formkit/validation": "^2.1.2",
40
40
  "@formkit/vue": "^2.1.2",
41
- "@hostlink/light": "^3.2.7",
41
+ "@hostlink/light": "^3.3.1",
42
42
  "@nuxt/module-builder": "^1.0.1",
43
43
  "@quasar/extras": "^2.0.2",
44
44
  "@quasar/quasar-ui-qmarkdown": "^3.0.1",