@octabits-io/nuxt-ui-kit 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.
@@ -0,0 +1,453 @@
1
+ import { UserManager, UserManagerSettings, UserProfile } from "oidc-client-ts";
2
+ import { ComputedRef, Ref } from "vue";
3
+ import { Treaty } from "@elysiajs/eden";
4
+ import { Elysia } from "elysia";
5
+ //#region src/auth/oidc.d.ts
6
+ /** Issuer + client id, resolved at first use (client-side runtime config). */
7
+ interface OidcClientConfig {
8
+ issuerUrl: string;
9
+ clientId: string;
10
+ }
11
+ interface UserManagerFactoryOptions {
12
+ /**
13
+ * Resolve the issuer/client pair lazily — typically from a runtime-injected
14
+ * config object (e.g. a K8s-entrypoint `window.__APP_CONFIG__`) falling back
15
+ * to the app's build-time config.
16
+ */
17
+ getConfig: () => OidcClientConfig;
18
+ /** OAuth scopes requested on the initial signin. */
19
+ scope: string;
20
+ /**
21
+ * Scopes sent on the refresh-token grant when the IdP restricts them to a
22
+ * subset of the signin scopes (see `ZITADEL_REFRESH_TOKEN_ALLOWED_SCOPE`).
23
+ */
24
+ refreshTokenAllowedScope?: string;
25
+ /** Path the IdP redirects back to after signin. Default `/auth/callback`. */
26
+ redirectPath?: string;
27
+ /** Path the IdP redirects to after logout. Default `/login`. */
28
+ postLogoutRedirectPath?: string;
29
+ /** Default `true`. */
30
+ automaticSilentRenew?: boolean;
31
+ /** Raw oidc-client-ts settings escape hatch, applied last. */
32
+ settings?: Partial<UserManagerSettings>;
33
+ /** Called when issuer/client resolve empty. Default `console.error`. */
34
+ onMissingConfig?: (message: string) => void;
35
+ }
36
+ /**
37
+ * Lazily-created `UserManager` singleton bound to `window.localStorage`.
38
+ * Call the returned getter from plugins/stores/composables — the manager is
39
+ * constructed on first call (client-side only; requires `window`).
40
+ */
41
+ declare function createUserManagerFactory(options: UserManagerFactoryOptions): () => UserManager;
42
+ type StaleKeyStorage = Pick<Storage, 'length' | 'key' | 'removeItem'>;
43
+ /**
44
+ * Remove any `oidc.user:` storage keys that don't belong to the current
45
+ * authority+clientId — leftovers from environment switches would otherwise
46
+ * shadow or bloat the session storage.
47
+ */
48
+ declare function removeStaleOidcKeys(authority: string, clientId: string, storage?: StaleKeyStorage): void;
49
+ /** Refresh tokens fail unrecoverably with these OIDC error codes — user must re-auth. */
50
+ declare function isUnrecoverableRenewError(message: string): boolean;
51
+ interface LoginRedirectorOptions {
52
+ getUserManager: () => UserManager;
53
+ /** Fallback login page for when `signinRedirect` itself fails. Default `/login`. */
54
+ loginPath?: string;
55
+ /**
56
+ * Paths where redirecting to login would loop (the login/callback pages
57
+ * themselves). Default: `loginPath` and anything under `/auth/`.
58
+ */
59
+ isAuthRoute?: (path: string) => boolean;
60
+ /** Default `console.error`. */
61
+ log?: (message: string, detail?: unknown) => void;
62
+ }
63
+ /**
64
+ * Build a `redirectToLogin()` that starts an OIDC signin redirect carrying the
65
+ * current path as returnUrl state, with a plain `/login?redirect=` navigation
66
+ * fallback when the IdP redirect cannot even be started. No-ops on auth routes.
67
+ */
68
+ declare function createLoginRedirector(options: LoginRedirectorOptions): () => Promise<void>;
69
+ /**
70
+ * A user-facing session event the app should surface (toast/banner). The kit
71
+ * classifies; presentation and copy stay in the app.
72
+ *
73
+ * - `renew-failed` — silent renew failed recoverably (next renew may succeed).
74
+ * - `session-expired` — the session is gone; a login redirect follows.
75
+ */
76
+ type SessionNotice = {
77
+ kind: 'renew-failed';
78
+ error: Error;
79
+ } | {
80
+ kind: 'session-expired';
81
+ error?: Error;
82
+ };
83
+ interface SessionLifecycleHandlers {
84
+ /** Start re-authentication (typically from {@link createLoginRedirector}). */
85
+ redirectToLogin: () => void | Promise<void>;
86
+ /** Surface a notice to the user (toast). Optional — omit for headless use. */
87
+ notify?: (notice: SessionNotice) => void;
88
+ /** Clear app-side session state (e.g. reset the auth store's user). */
89
+ onSessionLost?: () => void;
90
+ /** Default `console.warn`. */
91
+ log?: (message: string, detail?: unknown) => void;
92
+ }
93
+ /**
94
+ * Wire oidc-client-ts session events to app callbacks:
95
+ *
96
+ * - silent-renew error → `notify` (`renew-failed`, or `session-expired` when the
97
+ * error is unrecoverable — see {@link isUnrecoverableRenewError}); an
98
+ * unrecoverable error also triggers the login redirect
99
+ * - access token expired without renewal → `notify(session-expired)` +
100
+ * `onSessionLost` + login redirect
101
+ * - back-channel signout at the IdP → `onSessionLost` + login redirect (no notice
102
+ * — the user initiated it elsewhere)
103
+ *
104
+ * Returns a detach function.
105
+ */
106
+ declare function attachSessionLifecycleHandlers(userManager: UserManager, handlers: SessionLifecycleHandlers): () => void;
107
+ //#endregion
108
+ //#region src/auth/zitadel.d.ts
109
+ /**
110
+ * Zitadel scope presets for {@link createUserManagerFactory}.
111
+ *
112
+ * The URN scopes request the resource-owner (organization) claim and the
113
+ * project role grants; `offline_access` requests a refresh token.
114
+ */
115
+ declare const ZITADEL_ORG_PROJECT_SCOPE = "openid profile email urn:zitadel:iam:user:resourceowner urn:zitadel:iam:org:project:roles offline_access";
116
+ /**
117
+ * Zitadel only accepts standard OIDC scopes on the refresh-token grant —
118
+ * sending `offline_access` or the `urn:zitadel:*` scopes returns
119
+ * `invalid_scope` even though they were granted at the initial auth. The
120
+ * URN-based claims still need to land in the refreshed access token; that
121
+ * depends on "Assert Roles on Authentication" being enabled at the Zitadel
122
+ * project level.
123
+ */
124
+ declare const ZITADEL_REFRESH_TOKEN_ALLOWED_SCOPE = "openid profile email";
125
+ //#endregion
126
+ //#region src/auth/bypass.d.ts
127
+ type BypassStorage = Pick<Storage, 'getItem' | 'setItem'>;
128
+ interface AuthBypassProfile {
129
+ sub: string;
130
+ email: string;
131
+ name: string;
132
+ }
133
+ interface SeedAuthBypassOptions {
134
+ /** The shared secret the API accepts as a Bearer token. Falsy → no-op. */
135
+ bypassSecret: string | null | undefined;
136
+ issuerUrl: string;
137
+ clientId: string;
138
+ /**
139
+ * MUST be the build-time production flag (e.g. `import.meta.env.PROD`).
140
+ * When `true` the seed is refused unconditionally — a leaked runtime env var
141
+ * cannot enable the bypass in a production build.
142
+ */
143
+ isProductionBuild: boolean;
144
+ /** Identity baked into the fake session. */
145
+ profile?: AuthBypassProfile;
146
+ /** Fake session lifetime. Default 86400 (24h). */
147
+ sessionTtlSeconds?: number;
148
+ storage?: BypassStorage;
149
+ /** Default `console.warn`. */
150
+ warn?: (message: string) => void;
151
+ }
152
+ /**
153
+ * Dev/E2E auth bypass: seed storage with a fake oidc-client-ts user whose
154
+ * `access_token` is the bypass secret, so the app considers the session
155
+ * authenticated and the API client sends the secret as Bearer.
156
+ *
157
+ * Call from a plugin that runs before the OIDC plugin. Skips seeding when a
158
+ * valid (non-expired) session already exists; overwrites corrupt entries.
159
+ * Returns whether a session was seeded.
160
+ */
161
+ declare function seedAuthBypassSession(options: SeedAuthBypassOptions): boolean;
162
+ //#endregion
163
+ //#region src/auth/session.d.ts
164
+ /** Default mapped shape of the authenticated user. */
165
+ interface AuthSessionUser {
166
+ id: string;
167
+ email: string;
168
+ name: string | null;
169
+ picture: string | null;
170
+ }
171
+ interface AuthSessionCoreOptions<TUser> {
172
+ getUserManager: () => UserManager;
173
+ /** Map OIDC profile claims to the app's user shape. */
174
+ mapUser: (profile: UserProfile) => TUser;
175
+ /** Default `console.warn`. */
176
+ log?: (message: string, detail?: unknown) => void;
177
+ }
178
+ interface AuthSessionCore<TUser> {
179
+ user: Ref<TUser | null>;
180
+ initialized: Ref<boolean>;
181
+ loading: Ref<boolean>;
182
+ isAuthenticated: ComputedRef<boolean>;
183
+ /** Restore the session from storage, silently renewing an expired token. */
184
+ checkAuth: () => Promise<void>;
185
+ /** Start the signin redirect, carrying `returnUrl` as state. */
186
+ login: (returnUrl?: string) => Promise<void>;
187
+ /** Complete the signin redirect; returns the returnUrl state (default `/`). */
188
+ handleCallback: () => Promise<string>;
189
+ /** Clear the local session and redirect to the IdP's logout endpoint. */
190
+ logout: () => Promise<void>;
191
+ }
192
+ declare function defaultAuthUserMapper(profile: UserProfile): AuthSessionUser;
193
+ /**
194
+ * Reactive OIDC session state + actions — the setup body of an auth store.
195
+ * Wrap it in the app's own store so naming and registration stay app-owned:
196
+ *
197
+ * ```ts
198
+ * export const useAuthStore = defineStore('auth', () =>
199
+ * createAuthSessionCore({ getUserManager, mapUser: defaultAuthUserMapper }),
200
+ * )
201
+ * ```
202
+ */
203
+ declare function createAuthSessionCore<TUser = AuthSessionUser>(options: AuthSessionCoreOptions<TUser>): AuthSessionCore<TUser>;
204
+ //#endregion
205
+ //#region src/auth/guard.d.ts
206
+ /** The route fields the guard needs — structurally satisfied by a Nuxt route. */
207
+ interface GuardRoute {
208
+ path: string;
209
+ fullPath: string;
210
+ }
211
+ interface AuthGuardOptions<TRoute extends GuardRoute = GuardRoute> {
212
+ /**
213
+ * Ensure the session is restored and report whether the user is
214
+ * authenticated — typically `checkAuth()` once, then `isAuthenticated`.
215
+ */
216
+ ensureAuthenticated: () => Promise<boolean> | boolean;
217
+ /** Routes reachable without auth. Default: `/login` and `/auth/*`. */
218
+ isPublicRoute?: (to: TRoute) => boolean;
219
+ /** Build the login redirect target. Default `/login?redirect=<returnTo>`. */
220
+ loginRedirect?: (returnTo: string) => string;
221
+ /**
222
+ * Per-app policy hook that runs once the user is authenticated — tenant/org
223
+ * validation, role gates, acceptance gates. Return a path to redirect to,
224
+ * or nothing to let the navigation through.
225
+ */
226
+ afterAuthenticated?: (to: TRoute) => Promise<string | undefined | void> | string | undefined | void;
227
+ }
228
+ /**
229
+ * Build the body of a global auth route-middleware. The returned handler
230
+ * yields a redirect target path or `undefined` to allow navigation; the app's
231
+ * middleware maps that onto its router:
232
+ *
233
+ * ```ts
234
+ * export default defineNuxtRouteMiddleware(async (to) => {
235
+ * const target = await guard(to)
236
+ * if (target) return navigateTo(target)
237
+ * })
238
+ * ```
239
+ */
240
+ declare function createAuthGuard<TRoute extends GuardRoute = GuardRoute>(options: AuthGuardOptions<TRoute>): (to: TRoute) => Promise<string | undefined>;
241
+ //#endregion
242
+ //#region src/api/client.d.ts
243
+ type AnyElysia = Elysia<any, any, any, any, any, any, any>;
244
+ interface ResolveApiBaseUrlOptions {
245
+ /**
246
+ * The explicitly configured URL, first-match-wins — e.g.
247
+ * `__APP_CONFIG__.API_URL || runtimeConfig.public.apiUrl`. Falsy → fallback.
248
+ */
249
+ configuredUrl: string | null | undefined;
250
+ /** Build-time production flag (`import.meta.env.PROD`). */
251
+ isProductionBuild: boolean;
252
+ /** Dev fallback becomes `http://localhost:<port>`. */
253
+ devFallbackPort: number;
254
+ /** Production fallback origin. Default `window.location.origin`. */
255
+ origin?: string;
256
+ }
257
+ /**
258
+ * Resolve the API base URL: configured value, else the page origin in
259
+ * production builds (same-host ingress), else a localhost dev port.
260
+ */
261
+ declare function resolveApiBaseUrl(options: ResolveApiBaseUrlOptions): string;
262
+ /**
263
+ * Bearer-token provider backed by the OIDC session: resolves to the current
264
+ * access token, or `null` when there is no non-expired session.
265
+ */
266
+ declare function createAccessTokenProvider(getUserManager: () => UserManager): () => Promise<string | null>;
267
+ interface TreatyClientFactoryOptions {
268
+ /** Resolve (and memoize, if desired) the base URL at first client use. */
269
+ getBaseUrl: () => string;
270
+ /** Bearer token per request; `null` sends no Authorization header. */
271
+ getAccessToken: () => Promise<string | null>;
272
+ /**
273
+ * Eden Treaty's auto-Date parsing on responses. Default `false`: with the
274
+ * default `true`, any `YYYY-MM-DD` string in a response is silently
275
+ * converted to a `Date` object, which then JSON-serializes back as a full
276
+ * ISO datetime on the next request — breaking server-side "plain ISO date
277
+ * string" validation. Keep the wire contract string-typed unless the API
278
+ * genuinely round-trips Date objects.
279
+ */
280
+ parseDate?: boolean;
281
+ /** Extra Treaty config (fetcher, onRequest, …), applied last. */
282
+ treatyConfig?: Omit<Treaty.Config, 'headers' | 'parseDate'>;
283
+ }
284
+ /**
285
+ * Lazily-created Eden Treaty client singleton with OIDC bearer injection.
286
+ *
287
+ * ```ts
288
+ * const getClient = createTreatyClientFactory<App>({ getBaseUrl, getAccessToken })
289
+ * export function useApi() {
290
+ * const client = getClient()
291
+ * return { api: client.api, client }
292
+ * }
293
+ * ```
294
+ */
295
+ declare function createTreatyClientFactory<App extends AnyElysia>(options: TreatyClientFactoryOptions): () => Treaty.Create<App>;
296
+ //#endregion
297
+ //#region src/org/orgStore.d.ts
298
+ type OrgStorage = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
299
+ /** Result seam for the org fetch — mirrors an Eden Treaty `{ data, error }`. */
300
+ type FetchOrganizationsResult<TOrg> = {
301
+ items: TOrg[];
302
+ error?: undefined;
303
+ } | {
304
+ items?: undefined;
305
+ error: unknown;
306
+ };
307
+ interface OrgStoreCoreOptions<TOrg> {
308
+ /**
309
+ * Fetch the orgs the current user is granted. Return `{ items }` on
310
+ * success or `{ error }` to surface a fetch failure (`fetchError`).
311
+ */
312
+ fetchOrganizations: () => Promise<FetchOrganizationsResult<TOrg>>;
313
+ /** The org's URL/selection identity. */
314
+ getSlug: (org: TOrg) => string;
315
+ /** Storage key persisting the current selection. Default `currentOrgSlug`. */
316
+ persistenceKey?: string;
317
+ storage?: OrgStorage;
318
+ }
319
+ interface OrgStoreCore<TOrg> {
320
+ organizations: Ref<TOrg[]>;
321
+ currentSlug: Ref<string | null>;
322
+ currentOrganization: ComputedRef<TOrg | null>;
323
+ loading: Ref<boolean>;
324
+ fetchError: Ref<unknown>;
325
+ /** Fetch grants; revokes the current selection if access was lost. */
326
+ fetchOrganizations: () => Promise<void>;
327
+ /** Select an org by slug (or clear with `null`); persists the choice. */
328
+ setCurrent: (slug: string | null) => void;
329
+ /** Restore the persisted selection (call once at app start). */
330
+ loadPersisted: () => void;
331
+ }
332
+ /**
333
+ * Reactive granted-organizations state + switching — the setup body of an
334
+ * org/tenant store. Wrap it in the app's own store (and alias names there):
335
+ *
336
+ * ```ts
337
+ * export const useTenantStore = defineStore('tenant', () => {
338
+ * const core = createOrgStoreCore<Tenant>({ fetchOrganizations, getSlug: t => t.slug })
339
+ * return { ...core, fetchTenants: core.fetchOrganizations }
340
+ * })
341
+ * ```
342
+ */
343
+ declare function createOrgStoreCore<TOrg>(options: OrgStoreCoreOptions<TOrg>): OrgStoreCore<TOrg>;
344
+ //#endregion
345
+ //#region src/composables/useConfirm.d.ts
346
+ interface ConfirmOptions {
347
+ title: string;
348
+ message?: string;
349
+ confirmText?: string;
350
+ cancelText?: string;
351
+ /** Renders the confirm button in the error color. */
352
+ dangerous?: boolean;
353
+ }
354
+ /** Promise-based confirmation: `if (await confirm({ title, dangerous: true })) …` */
355
+ declare function useConfirm(): {
356
+ confirm: (options: ConfirmOptions) => Promise<boolean>;
357
+ };
358
+ /** State + handlers for the dialog renderer component. */
359
+ declare function useConfirmState(): {
360
+ isOpen: import("vue").Ref<boolean, boolean>;
361
+ options: import("vue").Ref<{
362
+ title: string;
363
+ message?: string;
364
+ confirmText?: string;
365
+ cancelText?: string;
366
+ dangerous?: boolean;
367
+ }, ConfirmOptions | {
368
+ title: string;
369
+ message?: string;
370
+ confirmText?: string;
371
+ cancelText?: string;
372
+ dangerous?: boolean;
373
+ }>;
374
+ handleConfirm: () => void;
375
+ handleCancel: () => void;
376
+ };
377
+ //#endregion
378
+ //#region src/composables/apiErrorMessenger.d.ts
379
+ /** `{ key, message }` API error body (an OctError over the wire). */
380
+ interface ApiErrorLike {
381
+ key: string;
382
+ message: string;
383
+ }
384
+ /** `validation_error` body carrying per-field failures. */
385
+ interface ValidationApiErrorLike extends ApiErrorLike {
386
+ key: 'validation_error';
387
+ fields: {
388
+ path: string;
389
+ message: string;
390
+ }[];
391
+ }
392
+ interface ApiErrorMessengerOptions {
393
+ /** Translate a key (assumed to exist). */
394
+ t: (key: string) => string;
395
+ /** Does a translation exist for this key? */
396
+ te: (key: string) => boolean;
397
+ /** Default `console.error`. Pass `() => {}` to silence. */
398
+ log?: (message: string, error: unknown) => void;
399
+ }
400
+ /**
401
+ * Map API error bodies to user-facing i18n strings using a fixed key
402
+ * convention the consumer's locale files fulfil:
403
+ *
404
+ * - `errors.<key>` — one entry per API error key (fallback: the raw
405
+ * server `message`, and `errors.internal_server_error` for non-API errors)
406
+ * - `validation.fields.<path>` — display names for validated fields
407
+ * - `validation.messages.<snake_cased_message>` — validation message texts
408
+ *
409
+ * Framework-free: pass `t`/`te` from your i18n instance (the app-side
410
+ * composable is typically `const { t, te } = useI18n()` + this factory).
411
+ * Eden Treaty error envelopes (`{ value }`) are unwrapped automatically.
412
+ */
413
+ declare function createApiErrorMessenger(options: ApiErrorMessengerOptions): {
414
+ getErrorMessage: (error: unknown) => string;
415
+ isValidationError: (error: unknown) => error is ValidationApiErrorLike;
416
+ };
417
+ //#endregion
418
+ //#region src/composables/useDirtyTracking.d.ts
419
+ /**
420
+ * Form change-detection over a reactive state object via JSON deep-compare:
421
+ * `isDirty` flips when any field differs from the snapshot; `resetInitial()`
422
+ * re-snapshots after load/save (optionally assigning new values first);
423
+ * `getDirtyFields()` yields a minimal PATCH payload.
424
+ */
425
+ declare function useDirtyTracking<T extends Record<string, unknown>>(state: T): {
426
+ isDirty: import("vue").ComputedRef<boolean>;
427
+ getDirtyFields: () => Partial<T>;
428
+ resetInitial: (values?: Partial<T>) => void;
429
+ };
430
+ //#endregion
431
+ //#region src/composables/usePagination.d.ts
432
+ /**
433
+ * Offset-based table pagination: `page`/`itemsPerPage`/`total` state with a
434
+ * derived `offset` and ready-to-spread `queryParams { limit, offset }`.
435
+ * `onPaginationChange` fires whenever page or page size changes (refetch hook).
436
+ */
437
+ declare function usePagination(options?: {
438
+ defaultLimit?: number;
439
+ onPaginationChange?: () => void;
440
+ }): {
441
+ page: import("vue").Ref<number, number>;
442
+ itemsPerPage: import("vue").Ref<number, number>;
443
+ total: import("vue").Ref<number, number>;
444
+ offset: import("vue").ComputedRef<number>;
445
+ queryParams: import("vue").ComputedRef<{
446
+ limit: number;
447
+ offset: number;
448
+ }>;
449
+ setTotal: (value: number) => void;
450
+ resetPagination: () => void;
451
+ };
452
+ //#endregion
453
+ export { type ApiErrorLike, type ApiErrorMessengerOptions, type AuthBypassProfile, type AuthGuardOptions, type AuthSessionCore, type AuthSessionCoreOptions, type AuthSessionUser, type ConfirmOptions, type FetchOrganizationsResult, type GuardRoute, type LoginRedirectorOptions, type OidcClientConfig, type OrgStoreCore, type OrgStoreCoreOptions, type ResolveApiBaseUrlOptions, type SeedAuthBypassOptions, type SessionLifecycleHandlers, type SessionNotice, type TreatyClientFactoryOptions, type UserManagerFactoryOptions, type ValidationApiErrorLike, ZITADEL_ORG_PROJECT_SCOPE, ZITADEL_REFRESH_TOKEN_ALLOWED_SCOPE, attachSessionLifecycleHandlers, createAccessTokenProvider, createApiErrorMessenger, createAuthGuard, createAuthSessionCore, createLoginRedirector, createOrgStoreCore, createTreatyClientFactory, createUserManagerFactory, defaultAuthUserMapper, isUnrecoverableRenewError, removeStaleOidcKeys, resolveApiBaseUrl, seedAuthBypassSession, useConfirm, useConfirmState, useDirtyTracking, usePagination };