@mapomodule/core 2.0.0-beta.1 → 2.0.0-beta.10

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 (76) hide show
  1. package/README.md +31 -21
  2. package/dist/index.d.mts +14 -1
  3. package/dist/index.d.ts +6 -1
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.mjs +4 -21
  6. package/dist/module.d.mts +11 -2
  7. package/dist/module.d.ts +10 -2
  8. package/dist/module.d.ts.map +1 -0
  9. package/dist/module.json +1 -1
  10. package/dist/module.mjs +73 -15
  11. package/dist/runtime/__tests__/plugins/00.fetch.real.spec.d.ts +2 -0
  12. package/dist/runtime/__tests__/plugins/00.fetch.real.spec.d.ts.map +1 -0
  13. package/dist/runtime/__tests__/plugins/00.fetch.spec.d.ts +2 -0
  14. package/dist/runtime/__tests__/plugins/00.fetch.spec.d.ts.map +1 -0
  15. package/dist/runtime/__tests__/plugins/01.init.server.spec.d.ts +2 -0
  16. package/dist/runtime/__tests__/plugins/01.init.server.spec.d.ts.map +1 -0
  17. package/dist/runtime/__tests__/utils/fetchError.spec.d.ts +2 -0
  18. package/dist/runtime/__tests__/utils/fetchError.spec.d.ts.map +1 -0
  19. package/dist/runtime/api/crud.d.ts +23 -0
  20. package/dist/runtime/api/crud.d.ts.map +1 -0
  21. package/dist/runtime/api/crud.js +80 -0
  22. package/dist/runtime/api/multipart.d.ts +15 -0
  23. package/dist/runtime/api/multipart.d.ts.map +1 -0
  24. package/dist/runtime/api/multipart.js +31 -0
  25. package/dist/runtime/auth/useCanAccessRoute.d.ts +12 -0
  26. package/dist/runtime/auth/useCanAccessRoute.d.ts.map +1 -0
  27. package/dist/runtime/auth/useCanAccessRoute.js +28 -0
  28. package/dist/runtime/auth/useMapoAuth.d.ts +28 -0
  29. package/dist/runtime/auth/useMapoAuth.d.ts.map +1 -0
  30. package/dist/runtime/auth/useMapoAuth.js +38 -0
  31. package/dist/runtime/composables/useMapo.d.ts +48 -0
  32. package/dist/runtime/composables/useMapo.d.ts.map +1 -0
  33. package/dist/runtime/composables/useMapo.js +15 -0
  34. package/dist/runtime/middleware/auth.d.ts +10 -0
  35. package/dist/runtime/middleware/auth.d.ts.map +1 -0
  36. package/dist/runtime/middleware/auth.js +15 -0
  37. package/dist/runtime/middleware/permissions.d.ts +20 -0
  38. package/dist/runtime/middleware/permissions.d.ts.map +1 -0
  39. package/dist/runtime/middleware/permissions.js +27 -0
  40. package/dist/runtime/middleware/roles.d.ts +12 -0
  41. package/dist/runtime/middleware/roles.d.ts.map +1 -0
  42. package/dist/runtime/middleware/roles.js +13 -0
  43. package/dist/runtime/page-meta.nuxt.d.ts +23 -0
  44. package/dist/runtime/plugins/00.fetch.d.ts +21 -0
  45. package/dist/runtime/plugins/00.fetch.d.ts.map +1 -0
  46. package/dist/runtime/plugins/00.fetch.js +61 -0
  47. package/dist/runtime/plugins/01.init.server.d.ts +15 -0
  48. package/dist/runtime/plugins/01.init.server.d.ts.map +1 -0
  49. package/dist/runtime/plugins/01.init.server.js +35 -0
  50. package/dist/runtime/types.d.ts +75 -0
  51. package/dist/runtime/types.d.ts.map +1 -0
  52. package/dist/runtime/types.js +10 -0
  53. package/dist/runtime/types.nuxt.d.ts +18 -0
  54. package/dist/runtime/utils/fetchError.d.ts +29 -0
  55. package/dist/runtime/utils/fetchError.d.ts.map +1 -0
  56. package/dist/runtime/utils/fetchError.js +12 -0
  57. package/dist/runtime/utils/useMapoFetch.d.ts +30 -0
  58. package/dist/runtime/utils/useMapoFetch.d.ts.map +1 -0
  59. package/dist/runtime/utils/useMapoFetch.js +8 -0
  60. package/dist/types.d.mts +10 -10
  61. package/dist/types.d.ts +13 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/package.json +8 -8
  64. package/src/index.ts +5 -0
  65. package/src/module.ts +13 -1
  66. package/src/runtime/__tests__/plugins/00.fetch.real.spec.ts +124 -0
  67. package/src/runtime/__tests__/plugins/00.fetch.spec.ts +3 -1
  68. package/src/runtime/__tests__/plugins/01.init.server.spec.ts +94 -0
  69. package/src/runtime/__tests__/utils/fetchError.spec.ts +97 -0
  70. package/src/runtime/api/crud.ts +8 -0
  71. package/src/runtime/plugins/00.fetch.ts +13 -6
  72. package/src/runtime/plugins/01.init.server.ts +10 -6
  73. package/src/runtime/types.ts +14 -0
  74. package/src/runtime/utils/fetchError.ts +50 -0
  75. package/dist/index.d.cts +0 -1
  76. package/dist/module.d.cts +0 -2
@@ -0,0 +1,15 @@
1
+ import { useSnackStore } from "@mapomodule/store/runtime/stores/snack";
2
+ import { useConfirmStore } from "@mapomodule/store/runtime/stores/confirm";
3
+ import { useMapoAuth } from "../auth/useMapoAuth.js";
4
+ import { useCrud } from "../api/crud.js";
5
+ export function useMapo(options = {}) {
6
+ return {
7
+ $api: {
8
+ crud: (endpoint) => useCrud(endpoint)
9
+ },
10
+ $auth: useMapoAuth(options),
11
+ $snack: useSnackStore(),
12
+ $confirm: useConfirmStore(),
13
+ $options: options
14
+ };
15
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Route middleware that protects authenticated pages.
3
+ *
4
+ * If the user is not authenticated, it redirects to the configured login page
5
+ * and appends the current route (`to.fullPath`) as a `redirect` query param,
6
+ * so the app can return the user to the original destination after login.
7
+ */
8
+ declare const _default: import("nuxt/app").RouteMiddleware;
9
+ export default _default;
10
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/runtime/middleware/auth.ts"],"names":[],"mappings":"AAOA;;;;;;GAMG;;AACH,wBASG"}
@@ -0,0 +1,15 @@
1
+ import {
2
+ defineNuxtRouteMiddleware,
3
+ navigateTo,
4
+ useRuntimeConfig
5
+ } from "nuxt/app";
6
+ import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
7
+ export default defineNuxtRouteMiddleware((to) => {
8
+ const loginUrl = useRuntimeConfig().public.mapoCore.loginUrl;
9
+ const auth = useAuthStore();
10
+ if (!auth.isAuthenticated) {
11
+ return navigateTo(
12
+ `${loginUrl}?redirect=${encodeURIComponent(to.fullPath)}`
13
+ );
14
+ }
15
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Route middleware that enforces page-level permissions from `to.meta.permissions`.
3
+ *
4
+ * Supported formats:
5
+ * - `string[]`: all listed raw permission codenames are required.
6
+ * - `{ model: string }`: permissions are derived from codenames ending with
7
+ * `_<model>` (for example, `view_article`, `change_article`).
8
+ *
9
+ * Behavior:
10
+ * - If no permissions are declared, access is allowed.
11
+ * - Superusers always pass; for model-based permissions, full CRUD actions are
12
+ * exposed in `auth.pagePermissions[routeName]`.
13
+ * - For regular users, access is denied with HTTP 403 when required permissions
14
+ * are missing (or `view` is missing for model-based permissions).
15
+ * - For allowed model-based routes, resolved actions are stored in
16
+ * `auth.pagePermissions[routeName]`.
17
+ */
18
+ declare const _default: import("nuxt/app").RouteMiddleware;
19
+ export default _default;
20
+ //# sourceMappingURL=permissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/runtime/middleware/permissions.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;GAgBG;;AACH,wBA6BG"}
@@ -0,0 +1,27 @@
1
+ import { defineNuxtRouteMiddleware, createError } from "nuxt/app";
2
+ import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
3
+ export default defineNuxtRouteMiddleware((to) => {
4
+ const auth = useAuthStore();
5
+ const perms = to.meta.permissions;
6
+ if (!perms) return;
7
+ if (auth.info?.is_superuser) {
8
+ if (!Array.isArray(perms)) {
9
+ auth.pagePermissions[String(to.name)] = [
10
+ "view",
11
+ "add",
12
+ "change",
13
+ "delete"
14
+ ];
15
+ }
16
+ return;
17
+ }
18
+ if (Array.isArray(perms)) {
19
+ if (!perms.every((p) => auth.rawPermissions.includes(p)))
20
+ throw createError({ statusCode: 403, message: "Forbidden" });
21
+ } else {
22
+ const actions = auth.rawPermissions.filter((p) => p.endsWith(`_${perms.model}`)).map((p) => p.replace(`_${perms.model}`, ""));
23
+ if (!actions.includes("view"))
24
+ throw createError({ statusCode: 403, message: "Forbidden" });
25
+ auth.pagePermissions[String(to.name)] = actions;
26
+ }
27
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Route middleware that enforces role-based access from `to.meta.roles`.
3
+ *
4
+ * Access rules:
5
+ * - If no roles are configured on the route, access is allowed.
6
+ * - Superusers always bypass role checks.
7
+ * - For other users, at least one required role must match a user group.
8
+ * - If no match is found, navigation is blocked with HTTP 403.
9
+ */
10
+ declare const _default: import("nuxt/app").RouteMiddleware;
11
+ export default _default;
12
+ //# sourceMappingURL=roles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"roles.d.ts","sourceRoot":"","sources":["../../../src/runtime/middleware/roles.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;;AACH,wBAUG"}
@@ -0,0 +1,13 @@
1
+ import { defineNuxtRouteMiddleware, createError } from "nuxt/app";
2
+ import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
3
+ export default defineNuxtRouteMiddleware((to) => {
4
+ const auth = useAuthStore();
5
+ const required = to.meta.roles;
6
+ if (!required?.length) return;
7
+ if (auth.info?.is_superuser) return;
8
+ const userGroups = auth.info?.groups ?? [];
9
+ const hasRole = required.some((r) => userGroups.includes(r));
10
+ if (!hasRole) {
11
+ throw createError({ statusCode: 403, message: "Forbidden" });
12
+ }
13
+ });
@@ -0,0 +1,23 @@
1
+ declare module "#app" {
2
+ interface PageMeta {
3
+ /**
4
+ * Permissions required to access this route. Checked by the 'permissions' middleware and sidebar visibility.
5
+ * - `{ model: string }` — derives `view_<model>` from a Django model name and populates `authStore.pagePermissions`
6
+ * - `string[]` — raw Django permission codenames (e.g. `['view_article', 'change_article']`)
7
+ */
8
+ permissions?: { model: string } | string[];
9
+ /** Required role/group names. Checked by the 'roles' middleware. */
10
+ roles?: string[];
11
+ }
12
+ }
13
+
14
+ declare module "vue-router" {
15
+ interface RouteMeta {
16
+ permissions?: { model: string } | string[];
17
+ roles?: string[];
18
+ /** Route middleware names applied to this route. Read by useCanAccessRoute for sidebar visibility. */
19
+ middleware?: string | string[];
20
+ }
21
+ }
22
+
23
+ export {};
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Registers the global Mapo fetch client and loading state.
3
+ *
4
+ * The plugin provides:
5
+ * - `$mapoFetch`: a shared `$fetch` instance with centralized request/response hooks.
6
+ * - `$mapoFetchLoading`: a computed boolean based on a global pending-request counter.
7
+ *
8
+ * Error handling behavior:
9
+ * - `401` (except logout calls): reset auth state and, on client, redirect to login
10
+ * with the current route encoded in a `redirect` query parameter.
11
+ * - `403`: show a global "Permission denied" error snackbar.
12
+ */
13
+ declare const _default: import("nuxt/app").Plugin<{
14
+ mapoFetch: import("nitropack/types").$Fetch<unknown, import("nitropack/types").NitroFetchRequest>;
15
+ mapoFetchLoading: import("vue").ComputedRef<boolean>;
16
+ }> & import("nuxt/app").ObjectPlugin<{
17
+ mapoFetch: import("nitropack/types").$Fetch<unknown, import("nitropack/types").NitroFetchRequest>;
18
+ mapoFetchLoading: import("vue").ComputedRef<boolean>;
19
+ }>;
20
+ export default _default;
21
+ //# sourceMappingURL=00.fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"00.fetch.d.ts","sourceRoot":"","sources":["../../../src/runtime/plugins/00.fetch.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;GAWG;;;;;;;;AACH,wBAmEG"}
@@ -0,0 +1,61 @@
1
+ import { ref, computed } from "vue";
2
+ import {
3
+ defineNuxtPlugin,
4
+ navigateTo,
5
+ useRequestHeaders,
6
+ useRoute,
7
+ useRuntimeConfig
8
+ } from "nuxt/app";
9
+ import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
10
+ import { useSnackStore } from "@mapomodule/store/runtime/stores/snack";
11
+ export default defineNuxtPlugin({
12
+ name: "mapo-core:fetch",
13
+ enforce: "pre",
14
+ setup() {
15
+ const rc = useRuntimeConfig().public.mapoCore;
16
+ const logoutUrl = rc.logoutUrl;
17
+ const loginUrl = rc.loginUrl;
18
+ let redirectInFlight = false;
19
+ const handle = (status, requestUrl) => {
20
+ if (status === 401 && !requestUrl.includes(logoutUrl) && !redirectInFlight) {
21
+ redirectInFlight = true;
22
+ const auth = useAuthStore();
23
+ auth.reset();
24
+ if (import.meta.client) {
25
+ const route = useRoute();
26
+ navigateTo(
27
+ `${loginUrl}?redirect=${encodeURIComponent(route.fullPath)}`
28
+ );
29
+ }
30
+ }
31
+ if (status === 403) {
32
+ useSnackStore().show("Permission denied", "error");
33
+ }
34
+ };
35
+ const pending = ref(0);
36
+ const mapoFetchLoading = computed(() => pending.value > 0);
37
+ const forwardedCookie = import.meta.server ? useRequestHeaders(["cookie"]).cookie : void 0;
38
+ const mapoFetch = $fetch.create({
39
+ onRequest({ options }) {
40
+ pending.value++;
41
+ if (forwardedCookie) {
42
+ const headers = new Headers(
43
+ options.headers
44
+ );
45
+ if (!headers.has("cookie")) headers.set("cookie", forwardedCookie);
46
+ options.headers = headers;
47
+ }
48
+ },
49
+ onResponse({ response, request }) {
50
+ pending.value--;
51
+ handle(response.status, String(request));
52
+ },
53
+ onRequestError() {
54
+ pending.value--;
55
+ }
56
+ });
57
+ return {
58
+ provide: { mapoFetch, mapoFetchLoading }
59
+ };
60
+ }
61
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Performs server-side bootstrap of Mapo runtime stores.
3
+ *
4
+ * On each SSR request, this plugin:
5
+ * - Activates the request Pinia instance and initializes auth/sidebar stores.
6
+ * - Restores sidebar UI state from cookies.
7
+ * - If the HttpOnly session cookie is present, calls the configured `userInfoApi`
8
+ * (forwarding request cookies) to restore the authenticated user in the auth store.
9
+ *
10
+ * On session restore failure it resets auth state, and clears the session cookie only
11
+ * when the backend rejected the session (401/403) — never on a transport error.
12
+ */
13
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
14
+ export default _default;
15
+ //# sourceMappingURL=01.init.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"01.init.server.d.ts","sourceRoot":"","sources":["../../../src/runtime/plugins/01.init.server.ts"],"names":[],"mappings":"AAcA;;;;;;;;;;;GAWG;;AACH,wBAuCG"}
@@ -0,0 +1,35 @@
1
+ import {
2
+ defineNuxtPlugin,
3
+ useRuntimeConfig,
4
+ useCookie,
5
+ useRequestHeaders
6
+ } from "nuxt/app";
7
+ import { setActivePinia } from "pinia";
8
+ import { useAuthStore } from "@mapomodule/store/runtime/stores/auth";
9
+ import { useSidebarStore } from "@mapomodule/store/runtime/stores/sidebar";
10
+ import { CoreCookieEnum } from "../types.js";
11
+ export default defineNuxtPlugin({
12
+ name: "mapo-core:init",
13
+ async setup(nuxtApp) {
14
+ const runtimeConfig = useRuntimeConfig().public.mapoCore;
15
+ const userInfoApi = runtimeConfig?.userInfoApi ?? "/api/profiles/me/";
16
+ const pinia = nuxtApp.$pinia;
17
+ setActivePinia(pinia);
18
+ const authStore = useAuthStore(pinia);
19
+ const sidebarStore = useSidebarStore(pinia);
20
+ sidebarStore.hydrateFromCookies();
21
+ const sessionCookie = useCookie(CoreCookieEnum.Session);
22
+ if (!sessionCookie.value) return;
23
+ try {
24
+ const headers = useRequestHeaders(["cookie"]);
25
+ const user = await nuxtApp.$mapoFetch(userInfoApi, { method: "GET", headers });
26
+ authStore.setUser(user);
27
+ } catch (e) {
28
+ console.error(`[mapo] Cannot restore session from cookie:
29
+ ${e}`);
30
+ authStore.reset();
31
+ const status = e?.status ?? e?.response?.status;
32
+ if (status === 401 || status === 403) sessionCookie.value = null;
33
+ }
34
+ }
35
+ });
@@ -0,0 +1,75 @@
1
+ export declare enum MultipartPolicyEnum {
2
+ Auto = "auto",
3
+ Force = "force",
4
+ Disable = "disable"
5
+ }
6
+ export type MultipartPolicy = `${MultipartPolicyEnum}`;
7
+ export declare enum CoreCookieEnum {
8
+ Session = "__mapo_session"
9
+ }
10
+ export interface ListParams {
11
+ page?: number;
12
+ limit?: number;
13
+ search?: string;
14
+ ordering?: string;
15
+ [key: string]: unknown;
16
+ }
17
+ export interface PaginatedResponse<T> {
18
+ results: T[];
19
+ count: number;
20
+ next?: string | null;
21
+ previous?: string | null;
22
+ }
23
+ export type ApiResponse<T> = T;
24
+ export interface CrudConfig {
25
+ baseURL?: string;
26
+ headers?: Record<string, string>;
27
+ [key: string]: unknown;
28
+ }
29
+ export interface CrudOptions {
30
+ multipart?: MultipartPolicy;
31
+ }
32
+ /**
33
+ * What an endpoint returns from OPTIONS. Deliberately loose: backends extend it
34
+ * (Camomilla adds `lang_info`), and clients read only the keys they know.
35
+ */
36
+ export interface ResourceMetadata {
37
+ name?: string;
38
+ description?: string;
39
+ renders?: string[];
40
+ parses?: string[];
41
+ actions?: Record<string, unknown>;
42
+ [key: string]: unknown;
43
+ }
44
+ export interface CrudRepository<T> {
45
+ list(params?: ListParams, config?: CrudConfig): Promise<PaginatedResponse<T>>;
46
+ detail(id: string | number, config?: CrudConfig): Promise<T>;
47
+ create(data: Partial<T>, config?: CrudConfig, opts?: CrudOptions): Promise<T>;
48
+ update(id: string | number, data: Partial<T>, config?: CrudConfig, opts?: CrudOptions): Promise<T>;
49
+ partialUpdate(id: string | number, diff: Partial<T>, config?: CrudConfig, opts?: CrudOptions): Promise<T>;
50
+ delete(id: string | number, config?: CrudConfig): Promise<void>;
51
+ options(config?: CrudConfig): Promise<ResourceMetadata>;
52
+ updateOrCreate(data: Partial<T> & {
53
+ id?: string | number;
54
+ }, config?: CrudConfig, opts?: CrudOptions): Promise<T>;
55
+ updateOrder(startId: string | number, endId: string | number, config?: CrudConfig): Promise<void>;
56
+ }
57
+ export interface Credentials {
58
+ username: string;
59
+ password: string;
60
+ }
61
+ export interface AuthToken {
62
+ token: string;
63
+ [key: string]: unknown;
64
+ }
65
+ /**
66
+ * Shape of `runtimeConfig.public.mapoCore` as written by the module setup.
67
+ * All entries are guaranteed by the module `defaults`.
68
+ */
69
+ export interface MapoCoreRuntimeConfig {
70
+ authLoginUrl: string;
71
+ userInfoApi: string;
72
+ logoutUrl: string;
73
+ loginUrl: string;
74
+ }
75
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runtime/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export var MultipartPolicyEnum = /* @__PURE__ */ ((MultipartPolicyEnum2) => {
2
+ MultipartPolicyEnum2["Auto"] = "auto";
3
+ MultipartPolicyEnum2["Force"] = "force";
4
+ MultipartPolicyEnum2["Disable"] = "disable";
5
+ return MultipartPolicyEnum2;
6
+ })(MultipartPolicyEnum || {});
7
+ export var CoreCookieEnum = /* @__PURE__ */ ((CoreCookieEnum2) => {
8
+ CoreCookieEnum2["Session"] = "__mapo_session";
9
+ return CoreCookieEnum2;
10
+ })(CoreCookieEnum || {});
@@ -0,0 +1,18 @@
1
+ import type { $Fetch } from "ofetch";
2
+
3
+ declare module "nuxt/app" {
4
+ interface NuxtApp {
5
+ $mapoFetch: $Fetch;
6
+ }
7
+ }
8
+
9
+ declare module "vue-router" {
10
+ interface RouteMeta {
11
+ permissions?: { model: string } | string[];
12
+ roles?: string[];
13
+ /** Route middleware names applied to this route. Read by useCanAccessRoute for sidebar visibility. */
14
+ middleware?: string | string[];
15
+ }
16
+ }
17
+
18
+ export {};
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Helpers to read the backend payload out of a failed `$mapoFetch` call.
3
+ *
4
+ * `ofetch` throws a `FetchError` that exposes the parsed response body as
5
+ * `error.data` (and `error.response._data`) — NOT as `error.response.data`.
6
+ * Reading the wrong path silently yields `undefined`, which turns every
7
+ * backend validation message into a generic "something went wrong".
8
+ * Use these helpers instead of destructuring the error by hand.
9
+ */
10
+ /**
11
+ * Parsed response body of a failed request, or `undefined` when the request
12
+ * failed before a response was received (network error, abort…).
13
+ *
14
+ * @example
15
+ * try { await crud.create(model) }
16
+ * catch (err) {
17
+ * const data = getErrorData<{ title?: string[] }>(err)
18
+ * errors.value = data ?? {}
19
+ * }
20
+ */
21
+ export declare function getErrorData<T = Record<string, unknown>>(error: unknown): T | undefined;
22
+ /** HTTP status of a failed request, or `0` when no response was received. */
23
+ export declare function getErrorStatus(error: unknown): number;
24
+ /**
25
+ * `detail` message returned by the backend (DRF convention), when present.
26
+ * Falls back to `undefined` so callers can substitute a translated default.
27
+ */
28
+ export declare function getErrorDetail(error: unknown): string | undefined;
29
+ //# sourceMappingURL=fetchError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchError.d.ts","sourceRoot":"","sources":["../../../src/runtime/utils/fetchError.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtD,KAAK,EAAE,OAAO,GACb,CAAC,GAAG,SAAS,CAGf;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAGjE"}
@@ -0,0 +1,12 @@
1
+ export function getErrorData(error) {
2
+ const e = error;
3
+ return e?.data ?? e?.response?._data;
4
+ }
5
+ export function getErrorStatus(error) {
6
+ const e = error;
7
+ return e?.status ?? e?.statusCode ?? e?.response?.status ?? 0;
8
+ }
9
+ export function getErrorDetail(error) {
10
+ const data = getErrorData(error);
11
+ return typeof data?.detail === "string" ? data.detail : void 0;
12
+ }
@@ -0,0 +1,30 @@
1
+ import type { $Fetch } from "ofetch";
2
+ import type { ComputedRef } from "vue";
3
+ export interface UseMapoFetchReturn {
4
+ /** The auth-aware `$mapoFetch` instance (same one provided by the plugin). */
5
+ fetch: $Fetch;
6
+ /**
7
+ * `true` while at least one `$mapoFetch` request is in flight — globally,
8
+ * across the entire app. Incremented by the plugin-level `onRequest` hook
9
+ * and decremented by `onResponse` / `onResponseError`, so it stays accurate
10
+ * even for raw `useNuxtApp().$mapoFetch` calls outside this composable.
11
+ */
12
+ loading: ComputedRef<boolean>;
13
+ }
14
+ /**
15
+ * Returns the global `$mapoFetch` instance together with a centralized
16
+ * `loading` indicator that tracks **all** in-flight `$mapoFetch` requests,
17
+ * not just the ones made through this composable.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const { fetch, loading } = useMapoFetch()
22
+ * await fetch('/api/articles/', { method: 'POST', body: payload })
23
+ * ```
24
+ *
25
+ * ```vue
26
+ * <UButton :loading="loading" @click="submit">Save</UButton>
27
+ * ```
28
+ */
29
+ export declare function useMapoFetch(): UseMapoFetchReturn;
30
+ //# sourceMappingURL=useMapoFetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMapoFetch.d.ts","sourceRoot":"","sources":["../../../src/runtime/utils/useMapoFetch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IACjC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,IAAI,kBAAkB,CASjD"}
@@ -0,0 +1,8 @@
1
+ import { useNuxtApp } from "nuxt/app";
2
+ export function useMapoFetch() {
3
+ const nuxt = useNuxtApp();
4
+ return {
5
+ fetch: nuxt.$mapoFetch,
6
+ loading: nuxt.$mapoFetchLoading
7
+ };
8
+ }
package/dist/types.d.mts CHANGED
@@ -1,13 +1,13 @@
1
- import type { ModuleHooks, ModuleRuntimeHooks, ModuleRuntimeConfig, ModulePublicRuntimeConfig } from './module.mjs'
1
+ import type { NuxtModule } from '@nuxt/schema'
2
2
 
3
- declare module '#app' {
4
- interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
5
- }
3
+ import type { default as Module } from './module.mjs'
6
4
 
7
- declare module '@nuxt/schema' {
8
- interface NuxtHooks extends ModuleHooks {}
9
- interface RuntimeConfig extends ModuleRuntimeConfig {}
10
- interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}
11
- }
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
12
6
 
13
- export * from "./module.mjs"
7
+ export { type ApiResponse, type AuthToken, type CoreCookieEnum, type Credentials, type CrudConfig, type CrudOptions, type CrudRepository, type ListParams, type MultipartPolicy, type MultipartPolicyEnum, type PaginatedResponse } from '../dist/runtime/types.js'
8
+
9
+ export { type MapoApiLayer, type MapoFacade } from '../dist/runtime/composables/useMapo.js'
10
+
11
+ export { type RoutePermissions } from '../dist/runtime/auth/useCanAccessRoute.js'
12
+
13
+ export { type MapoOptions, default } from './module.mjs'
@@ -0,0 +1,13 @@
1
+ export interface MapoOptions {
2
+ authLoginUrl?: string;
3
+ userInfoApi?: string;
4
+ logoutUrl?: string;
5
+ loginUrl?: string;
6
+ }
7
+ export declare const MAPO_DEFAULTS: {
8
+ authLoginUrl: string;
9
+ userInfoApi: string;
10
+ logoutUrl: string;
11
+ loginUrl: string;
12
+ };
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa;;;;;CAKO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mapomodule/core",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.10",
4
4
  "description": "Mapo core: API layer, auth, composables, middleware",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,26 +40,26 @@
40
40
  "scripts": {
41
41
  "dev": "nuxt-module-build build --stub --watch",
42
42
  "build": "nuxt-module-build build && tsc -p tsconfig.build-types.json",
43
- "prepare": "nuxt-module-build build --stub",
44
43
  "dev:prepare": "nuxt-module-build build --stub",
45
44
  "typecheck": "tsc --noEmit",
46
45
  "lint": "eslint src"
47
46
  },
48
47
  "dependencies": {
49
- "@mapomodule/store": "2.0.0-beta.1",
50
- "@mapomodule/utils": "2.0.0-beta.1"
48
+ "@mapomodule/store": "2.0.0-beta.6",
49
+ "@mapomodule/utils": "2.0.0-beta.6"
51
50
  },
52
51
  "peerDependencies": {
53
- "nuxt": ">=4.0.0"
52
+ "nuxt": ">=4.0.0",
53
+ "pinia": "^3.0.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@nuxt/kit": "4.4.2",
57
57
  "@nuxt/module-builder": "^1.0.2",
58
58
  "@nuxt/schema": "4.4.2",
59
- "nuxt": "^4.4.2",
59
+ "nuxt": "^4.5.1",
60
60
  "ofetch": "^1.5.1",
61
61
  "pinia": "^3.0.4",
62
- "vue": "^3.5.32",
63
- "vue-router": "^5.0.4"
62
+ "vue": "^3.5.40",
63
+ "vue-router": "^5.2.0"
64
64
  }
65
65
  }
package/src/index.ts CHANGED
@@ -2,3 +2,8 @@ export type * from "./module";
2
2
  export { MultipartPolicyEnum, CoreCookieEnum } from "./runtime/types";
3
3
  export { useCanAccessRoute } from "./runtime/auth/useCanAccessRoute";
4
4
  export { useMapoFetch } from "./runtime/utils/useMapoFetch";
5
+ export {
6
+ getErrorData,
7
+ getErrorStatus,
8
+ getErrorDetail,
9
+ } from "./runtime/utils/fetchError";
package/src/module.ts CHANGED
@@ -40,6 +40,16 @@ export default defineNuxtModule<MapoOptions>({
40
40
  async setup(options, nuxt) {
41
41
  const resolver = createResolver(import.meta.url);
42
42
 
43
+ // In consuming apps this runtime lives inside node_modules, where Nuxt
44
+ // skips auto-import injection by default (workspace apps are unaffected:
45
+ // pnpm symlinks resolve outside node_modules). Opt back in and transpile
46
+ // the runtime for the SSR build.
47
+ nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
48
+ nuxt.options.imports.transform ??= {};
49
+ (nuxt.options.imports.transform.include ??= []).push(
50
+ /@mapomodule[\\/]core[\\/]/,
51
+ );
52
+
43
53
  addTypeTemplate({
44
54
  filename: "types/mapo-core-page-meta.d.ts",
45
55
  getContents: () =>
@@ -50,7 +60,9 @@ export default defineNuxtModule<MapoOptions>({
50
60
  });
51
61
 
52
62
  if (!hasNuxtModule("@mapomodule/store")) {
53
- await installModule("@mapomodule/store");
63
+ // Resolve from core's own node_modules: with pnpm's isolated layout the
64
+ // consuming app cannot resolve the bare "@mapomodule/store" specifier.
65
+ await installModule(await resolver.resolvePath("@mapomodule/store"));
54
66
  }
55
67
 
56
68
  // `defaults` guarantees these are set at runtime, but MapoOptions keeps