@tumbaland/frontend-core 1.1.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/dist/apiClient.d.ts +33 -0
- package/dist/apiClient.js +59 -0
- package/dist/authService.d.ts +35 -0
- package/dist/authService.js +162 -0
- package/dist/groupService.d.ts +19 -0
- package/dist/groupService.js +63 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +4 -0
- package/dist/test/setup.d.ts +9 -0
- package/dist/test/setup.js +28 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js +1 -0
- package/package.json +28 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ApiClientOptions {
|
|
2
|
+
/** Read fresh at call time (config may not be resolved yet at module load). */
|
|
3
|
+
baseUrl: () => string;
|
|
4
|
+
/** Called on a 401 response, e.g. to redirect to the login page. */
|
|
5
|
+
onUnauthorized?: () => void;
|
|
6
|
+
}
|
|
7
|
+
export interface ApiRequestOptions extends RequestInit {
|
|
8
|
+
/** Skip the onUnauthorized callback for this call (e.g. an auth-check that expects 401 as a normal "not logged in" result, not a hard redirect). */
|
|
9
|
+
skipAuthRedirect?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class ApiError extends Error {
|
|
12
|
+
status: number;
|
|
13
|
+
body: unknown;
|
|
14
|
+
constructor(status: number, message: string, body?: unknown);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generic authenticated JSON fetch wrapper — base URL handling, credentials,
|
|
18
|
+
* correlation/session headers, structured errors, and an opt-in 401 →
|
|
19
|
+
* redirect-to-auth hook, per TECHNICAL_REVIEW.md P1.3. `authService` and
|
|
20
|
+
* `groupService` don't route through this: they use different auth
|
|
21
|
+
* transports (cookie vs. Bearer token) that predate this client. New
|
|
22
|
+
* call sites (the many per-front album/finance/etc. service files that
|
|
23
|
+
* still hand-roll fetch) can adopt this incrementally.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createApiClient(options: ApiClientOptions): {
|
|
26
|
+
request: <T = unknown>(path: string, init?: ApiRequestOptions) => Promise<T>;
|
|
27
|
+
get: <T = unknown>(path: string, init?: ApiRequestOptions) => Promise<T>;
|
|
28
|
+
post: <T = unknown>(path: string, body?: unknown, init?: ApiRequestOptions) => Promise<T>;
|
|
29
|
+
put: <T = unknown>(path: string, body?: unknown, init?: ApiRequestOptions) => Promise<T>;
|
|
30
|
+
patch: <T = unknown>(path: string, body?: unknown, init?: ApiRequestOptions) => Promise<T>;
|
|
31
|
+
delete: <T = unknown>(path: string, init?: ApiRequestOptions) => Promise<T>;
|
|
32
|
+
};
|
|
33
|
+
export type ApiClient = ReturnType<typeof createApiClient>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getCorrelationId, getSessionId } from '@tumbaland/components';
|
|
2
|
+
export class ApiError extends Error {
|
|
3
|
+
constructor(status, message, body) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = 'ApiError';
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.body = body;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generic authenticated JSON fetch wrapper — base URL handling, credentials,
|
|
12
|
+
* correlation/session headers, structured errors, and an opt-in 401 →
|
|
13
|
+
* redirect-to-auth hook, per TECHNICAL_REVIEW.md P1.3. `authService` and
|
|
14
|
+
* `groupService` don't route through this: they use different auth
|
|
15
|
+
* transports (cookie vs. Bearer token) that predate this client. New
|
|
16
|
+
* call sites (the many per-front album/finance/etc. service files that
|
|
17
|
+
* still hand-roll fetch) can adopt this incrementally.
|
|
18
|
+
*/
|
|
19
|
+
export function createApiClient(options) {
|
|
20
|
+
async function request(path, init = {}) {
|
|
21
|
+
const { skipAuthRedirect, headers, ...rest } = init;
|
|
22
|
+
const response = await fetch(`${options.baseUrl()}${path}`, {
|
|
23
|
+
credentials: 'include',
|
|
24
|
+
...rest,
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
'x-correlation-id': getCorrelationId(),
|
|
28
|
+
'x-session-id': getSessionId(),
|
|
29
|
+
...headers
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (response.status === 401 && !skipAuthRedirect) {
|
|
33
|
+
options.onUnauthorized?.();
|
|
34
|
+
}
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
let body;
|
|
37
|
+
try {
|
|
38
|
+
body = await response.json();
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// response wasn't JSON — leave body undefined
|
|
42
|
+
}
|
|
43
|
+
const message = body?.message ?? `HTTP error! status: ${response.status}`;
|
|
44
|
+
throw new ApiError(response.status, message, body);
|
|
45
|
+
}
|
|
46
|
+
if (response.status === 204) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
return response.json();
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
request,
|
|
53
|
+
get: (path, init) => request(path, { ...init, method: 'GET' }),
|
|
54
|
+
post: (path, body, init) => request(path, { ...init, method: 'POST', body: body !== undefined ? JSON.stringify(body) : undefined }),
|
|
55
|
+
put: (path, body, init) => request(path, { ...init, method: 'PUT', body: body !== undefined ? JSON.stringify(body) : undefined }),
|
|
56
|
+
patch: (path, body, init) => request(path, { ...init, method: 'PATCH', body: body !== undefined ? JSON.stringify(body) : undefined }),
|
|
57
|
+
delete: (path, init) => request(path, { ...init, method: 'DELETE' })
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AuthResponse, User } from './types';
|
|
2
|
+
export interface AuthServiceConfig {
|
|
3
|
+
/** Read fresh at call time (config may not be resolved yet at module load). */
|
|
4
|
+
getAuthServiceUrl: () => string;
|
|
5
|
+
getAuthFrontUrl: () => string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Builds a per-front auth service instance. Each of the 7 frontends that
|
|
9
|
+
* previously hand-rolled their own (six divergent copies, per
|
|
10
|
+
* TECHNICAL_REVIEW.md P1.3) now just wire this up with their own config
|
|
11
|
+
* accessor:
|
|
12
|
+
*
|
|
13
|
+
* export const authService = createAuthService({
|
|
14
|
+
* getAuthServiceUrl: () => getGlobalConfig().AUTH_SERVICE_URL!,
|
|
15
|
+
* getAuthFrontUrl: () => getGlobalConfig().AUTH_FRONT_URL!
|
|
16
|
+
* });
|
|
17
|
+
*/
|
|
18
|
+
export declare function createAuthService(config: AuthServiceConfig): {
|
|
19
|
+
/** Sync the JWT from cookie to localStorage. Call once on app start. */
|
|
20
|
+
init(): void;
|
|
21
|
+
getToken(): string | null;
|
|
22
|
+
syncToken(): void;
|
|
23
|
+
clearAuthCache(): void;
|
|
24
|
+
/** Verifies the session against auth-service. Cached for 5 minutes. */
|
|
25
|
+
checkAuth(): Promise<AuthResponse>;
|
|
26
|
+
redirectToLogin(returnUrl?: string): void;
|
|
27
|
+
logout(): Promise<boolean>;
|
|
28
|
+
getCurrentUser(): Promise<User | null>;
|
|
29
|
+
/** Full profile (e.g. firstName/lastName) — only auth-service's /auth/profile has this. */
|
|
30
|
+
getProfile(): Promise<User | null>;
|
|
31
|
+
/** Decodes the local JWT directly — no network call, but can be stale. */
|
|
32
|
+
getCurrentUserFromToken(): User | null;
|
|
33
|
+
isAuthenticated(): Promise<boolean>;
|
|
34
|
+
};
|
|
35
|
+
export type AuthService = ReturnType<typeof createAuthService>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { getCorrelationId, getSessionId } from '@tumbaland/components';
|
|
2
|
+
const AUTH_CACHE_TTL = 5 * 60 * 1000;
|
|
3
|
+
function authHeaders() {
|
|
4
|
+
return {
|
|
5
|
+
'Content-Type': 'application/json',
|
|
6
|
+
'x-correlation-id': getCorrelationId(),
|
|
7
|
+
'x-session-id': getSessionId()
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function getJwtTokenFromCookie(cookieName = 'access_token') {
|
|
11
|
+
const match = document.cookie.match(new RegExp('(^| )' + cookieName + '=([^;]+)'));
|
|
12
|
+
return match ? match[2] : null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Builds a per-front auth service instance. Each of the 7 frontends that
|
|
16
|
+
* previously hand-rolled their own (six divergent copies, per
|
|
17
|
+
* TECHNICAL_REVIEW.md P1.3) now just wire this up with their own config
|
|
18
|
+
* accessor:
|
|
19
|
+
*
|
|
20
|
+
* export const authService = createAuthService({
|
|
21
|
+
* getAuthServiceUrl: () => getGlobalConfig().AUTH_SERVICE_URL!,
|
|
22
|
+
* getAuthFrontUrl: () => getGlobalConfig().AUTH_FRONT_URL!
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
export function createAuthService(config) {
|
|
26
|
+
let authCache = null;
|
|
27
|
+
let authCacheTime = 0;
|
|
28
|
+
function syncJwtToken() {
|
|
29
|
+
const token = getJwtTokenFromCookie();
|
|
30
|
+
if (token && localStorage.getItem('authToken') !== token) {
|
|
31
|
+
localStorage.setItem('authToken', token);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
/** Sync the JWT from cookie to localStorage. Call once on app start. */
|
|
36
|
+
init() {
|
|
37
|
+
syncJwtToken();
|
|
38
|
+
},
|
|
39
|
+
getToken() {
|
|
40
|
+
return localStorage.getItem('authToken');
|
|
41
|
+
},
|
|
42
|
+
syncToken() {
|
|
43
|
+
syncJwtToken();
|
|
44
|
+
},
|
|
45
|
+
clearAuthCache() {
|
|
46
|
+
authCache = null;
|
|
47
|
+
authCacheTime = 0;
|
|
48
|
+
},
|
|
49
|
+
/** Verifies the session against auth-service. Cached for 5 minutes. */
|
|
50
|
+
async checkAuth() {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
if (authCache && now - authCacheTime < AUTH_CACHE_TTL) {
|
|
53
|
+
return authCache;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(`${config.getAuthServiceUrl()}/auth/verify`, {
|
|
57
|
+
credentials: 'include',
|
|
58
|
+
headers: authHeaders()
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
const result = {
|
|
65
|
+
authenticated: data.success && data.authenticated,
|
|
66
|
+
user: data.user || null
|
|
67
|
+
};
|
|
68
|
+
authCache = result;
|
|
69
|
+
authCacheTime = now;
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
console.error('Auth check failed:', error);
|
|
74
|
+
const result = { authenticated: false, user: null };
|
|
75
|
+
authCache = result;
|
|
76
|
+
authCacheTime = now;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
redirectToLogin(returnUrl) {
|
|
81
|
+
const currentUrl = returnUrl || window.location.href;
|
|
82
|
+
window.location.href = `${config.getAuthFrontUrl()}?redirect_uri=${encodeURIComponent(currentUrl)}`;
|
|
83
|
+
},
|
|
84
|
+
async logout() {
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(`${config.getAuthServiceUrl()}/auth/logout`, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
credentials: 'include',
|
|
89
|
+
headers: authHeaders()
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
93
|
+
}
|
|
94
|
+
const data = await response.json();
|
|
95
|
+
if (data.success) {
|
|
96
|
+
localStorage.removeItem('authToken');
|
|
97
|
+
authCache = null;
|
|
98
|
+
authCacheTime = 0;
|
|
99
|
+
window.location.reload();
|
|
100
|
+
}
|
|
101
|
+
return data.success;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Logout failed:', error);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
async getCurrentUser() {
|
|
109
|
+
const result = await this.checkAuth();
|
|
110
|
+
return result.user;
|
|
111
|
+
},
|
|
112
|
+
/** Full profile (e.g. firstName/lastName) — only auth-service's /auth/profile has this. */
|
|
113
|
+
async getProfile() {
|
|
114
|
+
try {
|
|
115
|
+
const response = await fetch(`${config.getAuthServiceUrl()}/auth/profile`, {
|
|
116
|
+
credentials: 'include',
|
|
117
|
+
headers: authHeaders()
|
|
118
|
+
});
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
const errorText = await response.text();
|
|
121
|
+
console.error('Profile fetch error:', response.status, errorText);
|
|
122
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
123
|
+
}
|
|
124
|
+
const data = await response.json();
|
|
125
|
+
return data.success ? data.user : null;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error('Get profile failed:', error);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
/** Decodes the local JWT directly — no network call, but can be stale. */
|
|
133
|
+
getCurrentUserFromToken() {
|
|
134
|
+
try {
|
|
135
|
+
const token = this.getToken();
|
|
136
|
+
if (!token)
|
|
137
|
+
return null;
|
|
138
|
+
const payload = token.split('.')[1];
|
|
139
|
+
if (!payload)
|
|
140
|
+
return null;
|
|
141
|
+
const decoded = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
|
|
142
|
+
if (decoded.exp && Date.now() >= decoded.exp * 1000) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
id: decoded.sub || decoded.id,
|
|
147
|
+
email: decoded.email,
|
|
148
|
+
name: decoded.name || decoded.preferred_username,
|
|
149
|
+
picture: decoded.picture
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('Failed to decode JWT token:', error);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
async isAuthenticated() {
|
|
158
|
+
const result = await this.checkAuth();
|
|
159
|
+
return result.authenticated;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Group } from './types';
|
|
2
|
+
export interface GroupServiceConfig {
|
|
3
|
+
/** Read fresh at call time (config may not be resolved yet at module load). */
|
|
4
|
+
getGroupApiUrl: () => string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Builds a per-front group service instance, consolidating the
|
|
8
|
+
* getUserGroups/getUserGroupIds pair that was duplicated across
|
|
9
|
+
* album/finance/relationship-front (TECHNICAL_REVIEW.md P1.3).
|
|
10
|
+
*
|
|
11
|
+
* export const { getUserGroups, getUserGroupIds } = createGroupService({
|
|
12
|
+
* getGroupApiUrl: () => getGlobalConfig().GROUP_API_URL!
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export declare function createGroupService(config: GroupServiceConfig): {
|
|
16
|
+
getUserGroups(): Promise<Group[]>;
|
|
17
|
+
getUserGroupIds(): Promise<string[]>;
|
|
18
|
+
};
|
|
19
|
+
export type GroupService = ReturnType<typeof createGroupService>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getCorrelationId, getSessionId } from '@tumbaland/components';
|
|
2
|
+
function getAuthToken() {
|
|
3
|
+
return localStorage.getItem('authToken') || '';
|
|
4
|
+
}
|
|
5
|
+
function groupHeaders() {
|
|
6
|
+
return {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
Authorization: `Bearer ${getAuthToken()}`,
|
|
9
|
+
'x-correlation-id': getCorrelationId(),
|
|
10
|
+
'x-session-id': getSessionId()
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Builds a per-front group service instance, consolidating the
|
|
15
|
+
* getUserGroups/getUserGroupIds pair that was duplicated across
|
|
16
|
+
* album/finance/relationship-front (TECHNICAL_REVIEW.md P1.3).
|
|
17
|
+
*
|
|
18
|
+
* export const { getUserGroups, getUserGroupIds } = createGroupService({
|
|
19
|
+
* getGroupApiUrl: () => getGlobalConfig().GROUP_API_URL!
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
export function createGroupService(config) {
|
|
23
|
+
return {
|
|
24
|
+
async getUserGroups() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await fetch(`${config.getGroupApiUrl()}/api/groups/mine`, {
|
|
27
|
+
headers: groupHeaders()
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
throw new Error(`Failed to fetch groups: ${response.statusText}`);
|
|
31
|
+
}
|
|
32
|
+
const result = await response.json();
|
|
33
|
+
if (!result.success) {
|
|
34
|
+
throw new Error(result.message || 'Failed to fetch groups');
|
|
35
|
+
}
|
|
36
|
+
return result.data;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Error fetching user groups:', error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
async getUserGroupIds() {
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`${config.getGroupApiUrl()}/api/groups/mine/ids`, {
|
|
46
|
+
headers: groupHeaders()
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`Failed to fetch group IDs: ${response.statusText}`);
|
|
50
|
+
}
|
|
51
|
+
const result = await response.json();
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
throw new Error(result.message || 'Failed to fetch group IDs');
|
|
54
|
+
}
|
|
55
|
+
return result.data;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error('Error fetching user group IDs:', error);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createApiClient, ApiError } from './apiClient';
|
|
2
|
+
export type { ApiClientOptions, ApiRequestOptions } from './apiClient';
|
|
3
|
+
export { createAuthService } from './authService';
|
|
4
|
+
export type { AuthServiceConfig, AuthService } from './authService';
|
|
5
|
+
export { createGroupService } from './groupService';
|
|
6
|
+
export type { GroupServiceConfig, GroupService } from './groupService';
|
|
7
|
+
export type { User, AuthResponse, Group, ApiResponse } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This jsdom environment doesn't provide a working localStorage (window.localStorage
|
|
3
|
+
// is undefined here) — polyfill it with a simple in-memory Storage.
|
|
4
|
+
class MemoryStorage {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.store = new Map();
|
|
7
|
+
}
|
|
8
|
+
get length() {
|
|
9
|
+
return this.store.size;
|
|
10
|
+
}
|
|
11
|
+
clear() {
|
|
12
|
+
this.store.clear();
|
|
13
|
+
}
|
|
14
|
+
getItem(key) {
|
|
15
|
+
return this.store.has(key) ? this.store.get(key) : null;
|
|
16
|
+
}
|
|
17
|
+
key(index) {
|
|
18
|
+
return Array.from(this.store.keys())[index] ?? null;
|
|
19
|
+
}
|
|
20
|
+
removeItem(key) {
|
|
21
|
+
this.store.delete(key);
|
|
22
|
+
}
|
|
23
|
+
setItem(key, value) {
|
|
24
|
+
this.store.set(key, String(value));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(globalThis, 'localStorage', { value: new MemoryStorage(), configurable: true });
|
|
28
|
+
Object.defineProperty(window, 'localStorage', { value: globalThis.localStorage, configurable: true });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface User {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name: string;
|
|
5
|
+
firstName?: string;
|
|
6
|
+
lastName?: string;
|
|
7
|
+
picture?: string;
|
|
8
|
+
roles?: string[];
|
|
9
|
+
isApproved?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface AuthResponse {
|
|
12
|
+
authenticated: boolean;
|
|
13
|
+
user: User | null;
|
|
14
|
+
}
|
|
15
|
+
export interface Group {
|
|
16
|
+
_id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
memberCount?: number;
|
|
20
|
+
isOwner?: boolean;
|
|
21
|
+
createdAt?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ApiResponse<T> {
|
|
24
|
+
success: boolean;
|
|
25
|
+
data: T;
|
|
26
|
+
message?: string;
|
|
27
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tumbaland/frontend-core",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Shared frontend auth/group/API-client logic for Tumbaland frontends",
|
|
5
|
+
"author": "Tumbaland",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"release": "standard-version && npm run build && npm publish --access public",
|
|
15
|
+
"release:beta": "standard-version --prerelease beta && npm run build && npm publish --access public --tag beta",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"typecheck": "tsc --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@tumbaland/components": "*"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^26.1.0",
|
|
25
|
+
"standard-version": "^9.5.0",
|
|
26
|
+
"typescript": "^6.0.3"
|
|
27
|
+
}
|
|
28
|
+
}
|