@delfinjs/core 1.0.0 → 1.0.1

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.
@@ -15,6 +15,6 @@ function e(n) {
15
15
  return { ...o, ...d, ..._ };
16
16
  }
17
17
  export {
18
- e as a,
19
- t as g
18
+ t as a,
19
+ e as g
20
20
  };
@@ -1,8 +1,8 @@
1
1
  import { parse as s, serialize as u } from "cookie-es";
2
2
  import { destr as c } from "destr";
3
- import { g as f } from "../appConfig-hHCWS_gL.js";
3
+ import { a as f } from "../appConfig-DxhVfL1J.js";
4
4
  const r = {};
5
- function U() {
5
+ function g() {
6
6
  if (typeof document > "u") return !1;
7
7
  const e = s(document.cookie), n = e.accessToken, t = e.userData;
8
8
  if (n)
@@ -76,7 +76,7 @@ function _() {
76
76
  }
77
77
  export {
78
78
  i as getShellUrl,
79
- U as isAuthenticated,
79
+ g as isAuthenticated,
80
80
  _ as receiveAuthFromUrl,
81
81
  y as redirectToLogin
82
82
  };
@@ -10,10 +10,10 @@ const s = (t) => !!(t == null || t === "" || Array.isArray(t) && t.length === 0
10
10
  return r;
11
11
  };
12
12
  export {
13
- y as a,
14
- i as b,
13
+ i as a,
14
+ y as b,
15
15
  o as c,
16
- c as d,
17
- n as e,
16
+ n as d,
17
+ c as e,
18
18
  s as i
19
19
  };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { formatCurrency as a, formatDate as o, formatNumber as t, hexToRgb as i, loadAllMicroUIConfigs as l, loadMicroUIConfig as p, rgbToHex as d } from "./utils/index.js";
2
- import { e as n, i as f, b as m, a as g, c, d as x } from "./helpers-CbBec0n2.js";
3
- import { a as A, g as V } from "./appConfig-hHCWS_gL.js";
2
+ import { d as n, i as f, a as m, b as g, c, e as x } from "./helpers-DMOWsSP3.js";
3
+ import { g as A, a as V } from "./appConfig-DxhVfL1J.js";
4
4
  import { decrypt as C, encrypt as u, hasCredential as b, parseCredentials as T, parseJwt as U } from "./crypto/index.js";
5
5
  import { MFEventTypes as N, eventBus as w } from "./eventBus/index.js";
6
6
  import { createAppApi as F, getApiBaseUrl as M } from "./api/index.js";
@@ -1,5 +1,5 @@
1
- import { e as y, i as _, b as C, a as O, c as U, d as h } from "../helpers-CbBec0n2.js";
2
- import { a as x, g as N } from "../appConfig-hHCWS_gL.js";
1
+ import { d as y, i as _, a as C, b as O, c as U, e as h } from "../helpers-DMOWsSP3.js";
2
+ import { g as x, a as N } from "../appConfig-DxhVfL1J.js";
3
3
  const l = (n) => {
4
4
  const e = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
5
5
  n = n.replace(e, (r, a, o, i) => a + a + o + o + i + i);
@@ -1,4 +1,4 @@
1
- import { a as n, b as s, i } from "../helpers-CbBec0n2.js";
1
+ import { i, b as n, a as s } from "../helpers-DMOWsSP3.js";
2
2
  const d = (r) => n(r) || s(r) || r === !1 ? "Bu alan zorunludur" : !!String(r).trim().length || "Bu alan zorunludur", l = (r) => {
3
3
  if (i(r)) return !0;
4
4
  const t = /^(?:[^<>()[\]\\.,;:\s@"]+(?:\.[^<>()[\]\\.,;:\s@"]+)*|".+")@(?:\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]|(?:[a-z\-\d]+\.)+[a-z]{2,})$/i;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delfinjs/core",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Framework-agnostic shared utilities, event bus, API client, auth helpers, validators, and types for Delfin micro-frontends",
@@ -17,13 +17,10 @@
17
17
  "./types": "./src/types/index.js"
18
18
  },
19
19
  "files": [
20
+ "src",
20
21
  "dist",
21
22
  "README.md"
22
23
  ],
23
- "scripts": {
24
- "build": "vite build",
25
- "prepublishOnly": "npm run build"
26
- },
27
24
  "dependencies": {
28
25
  "cookie-es": "^1.2.2",
29
26
  "crypto-js": "^4.2.0",
@@ -41,5 +38,8 @@
41
38
  "event-bus",
42
39
  "api-client"
43
40
  ],
44
- "license": "UNLICENSED"
45
- }
41
+ "license": "UNLICENSED",
42
+ "scripts": {
43
+ "build": "vite build"
44
+ }
45
+ }
@@ -0,0 +1,99 @@
1
+ import { ofetch } from 'ofetch'
2
+ import { eventBus, MFEventTypes } from '../eventBus/index.js'
3
+ import { getAppConfig } from '../utils/appConfig.js'
4
+
5
+ /**
6
+ * Resolve the API base URL from runtime config or environment.
7
+ * @returns {string}
8
+ */
9
+ export function getApiBaseUrl() {
10
+ if (typeof window !== 'undefined' && window.__APP_CONFIG__?.VITE_API_BASE_URL) {
11
+ return window.__APP_CONFIG__.VITE_API_BASE_URL
12
+ }
13
+ return (typeof import.meta !== 'undefined' && import.meta.env?.VITE_API_BASE_URL) || '/api'
14
+ }
15
+
16
+ /**
17
+ * Framework-agnostic API client factory.
18
+ *
19
+ * @param {Object} options
20
+ * @param {string} [options.baseURL] - API base URL (defaults to runtime config)
21
+ * @param {string} [options.appSlug] - Micro-UI slug for auto token refresh via event bus
22
+ * @param {Record<string,string>} [options.headers] - Extra default headers
23
+ * @param {() => string|null} [options.getToken] - Callback to retrieve the current auth token
24
+ * @returns {{ (url: string, opts?: any): Promise<any>, setToken: (t: string) => void }}
25
+ */
26
+ export function createAppApi(options = {}) {
27
+ const { baseURL, appSlug, headers: customHeaders, getToken } = typeof options === 'string'
28
+ ? { baseURL: options, appSlug: undefined, headers: undefined, getToken: undefined }
29
+ : options
30
+
31
+ let _token = null
32
+ let _refreshPromise = null
33
+
34
+ function requestTokenRefresh() {
35
+ if (_refreshPromise) return _refreshPromise
36
+
37
+ _refreshPromise = new Promise(resolve => {
38
+ const timeout = setTimeout(() => {
39
+ unsubscribe()
40
+ _refreshPromise = null
41
+ resolve(null)
42
+ }, 10_000)
43
+
44
+ const unsubscribe = eventBus.on(
45
+ MFEventTypes.AUTH_MICROUI_TOKEN_REFRESH_RESPONSE,
46
+ ({ slug, token }) => {
47
+ if (slug === appSlug) {
48
+ clearTimeout(timeout)
49
+ unsubscribe()
50
+ _token = token
51
+ _refreshPromise = null
52
+ resolve(token)
53
+ }
54
+ },
55
+ )
56
+
57
+ eventBus.emit(MFEventTypes.AUTH_MICROUI_TOKEN_REFRESH_REQUEST, { slug: appSlug })
58
+ })
59
+
60
+ return _refreshPromise
61
+ }
62
+
63
+ const baseApi = ofetch.create({
64
+ baseURL: baseURL || getApiBaseUrl(),
65
+ async onRequest({ options: reqOpts }) {
66
+ const bearerToken = _token || getToken?.() || null
67
+ reqOpts.headers = {
68
+ ...(customHeaders || {}),
69
+ ...(reqOpts.headers || {}),
70
+ ...(bearerToken ? { Authorization: `Bearer ${bearerToken}` } : {}),
71
+ }
72
+ },
73
+ })
74
+
75
+ const api = async (url, opts) => {
76
+ try {
77
+ return await baseApi(url, opts)
78
+ } catch (error) {
79
+ if (error?.response?.status === 401 && appSlug) {
80
+ const newToken = await requestTokenRefresh()
81
+ if (newToken) {
82
+ return baseApi(url, {
83
+ ...opts,
84
+ headers: {
85
+ ...(opts?.headers || {}),
86
+ Authorization: `Bearer ${newToken}`,
87
+ },
88
+ })
89
+ }
90
+ }
91
+ throw error
92
+ }
93
+ }
94
+
95
+ /** Manually set the bearer token (e.g. received from the host). */
96
+ api.setToken = token => { _token = token }
97
+
98
+ return api
99
+ }
@@ -0,0 +1,127 @@
1
+ import { parse, serialize } from 'cookie-es'
2
+ import { destr } from 'destr'
3
+ import { getAppConfig } from '../utils/appConfig.js'
4
+
5
+ /**
6
+ * Check if user is authenticated by reading cookies.
7
+ * @returns {boolean}
8
+ */
9
+ export function isAuthenticated() {
10
+ if (typeof document === 'undefined') return false
11
+
12
+ const cookies = parse(document.cookie)
13
+ const accessToken = cookies.accessToken
14
+ const userData = cookies.userData
15
+
16
+ if (accessToken) {
17
+ try {
18
+ const token = destr(decodeURIComponent(accessToken))
19
+ return !!token && token !== 'null' && token !== 'undefined'
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+
25
+ if (userData) {
26
+ try {
27
+ const user = destr(decodeURIComponent(userData))
28
+ return !!user && typeof user === 'object' && user.id
29
+ } catch {
30
+ return false
31
+ }
32
+ }
33
+
34
+ return false
35
+ }
36
+
37
+ /**
38
+ * Determine the shell (host) application URL.
39
+ * @returns {string}
40
+ */
41
+ export function getShellUrl() {
42
+ const runtimeHost = getAppConfig('VITE_DELFIN_UI_BASE_URL')
43
+ if (runtimeHost) return runtimeHost.replace(/\/+$/, '')
44
+
45
+ if (typeof window !== 'undefined' && window.location.hostname === 'localhost' && window.location.port !== '3000') {
46
+ return 'http://localhost:3000'
47
+ }
48
+
49
+ return typeof window !== 'undefined' ? window.location.origin : ''
50
+ }
51
+
52
+ function isCrossOrigin() {
53
+ const shellUrl = getShellUrl()
54
+ try {
55
+ return typeof window !== 'undefined' && window.location.origin !== new URL(shellUrl).origin
56
+ } catch {
57
+ return false
58
+ }
59
+ }
60
+
61
+ function getAppSlug() {
62
+ if (typeof window !== 'undefined' && window.__APP_CONFIG__?.VITE_APP_SLUG)
63
+ return window.__APP_CONFIG__.VITE_APP_SLUG
64
+ return typeof import.meta !== 'undefined' ? import.meta.env?.VITE_APP_SLUG : undefined
65
+ }
66
+
67
+ function cleanAuthParam(url) {
68
+ try {
69
+ const u = new URL(url)
70
+ u.searchParams.delete('_auth')
71
+ return u.toString()
72
+ } catch {
73
+ return url
74
+ }
75
+ }
76
+
77
+ function redirectToAuthBridge() {
78
+ const shellUrl = getShellUrl()
79
+ const slug = getAppSlug()
80
+ const currentUrl = cleanAuthParam(window.location.href)
81
+ const slugParam = slug ? `&slug=${encodeURIComponent(slug)}` : ''
82
+ window.location.href = `${shellUrl}/auth-bridge?redirect=${encodeURIComponent(currentUrl)}${slugParam}`
83
+ }
84
+
85
+ /**
86
+ * Redirect to the shell login page.
87
+ */
88
+ export function redirectToLogin() {
89
+ if (typeof window === 'undefined') return
90
+
91
+ if (isCrossOrigin()) {
92
+ redirectToAuthBridge()
93
+ return
94
+ }
95
+
96
+ const shellUrl = getShellUrl()
97
+ window.location.href = `${shellUrl}/login?redirect=${encodeURIComponent(window.location.href)}`
98
+ }
99
+
100
+ /**
101
+ * Receive auth cookies from the `_auth` query parameter (set by auth-bridge redirect).
102
+ * Call once at app startup before any route guard runs.
103
+ * @returns {boolean} true if auth data was received and stored
104
+ */
105
+ export function receiveAuthFromUrl() {
106
+ if (typeof window === 'undefined') return false
107
+
108
+ const params = new URLSearchParams(window.location.search)
109
+ const encoded = params.get('_auth')
110
+ if (!encoded) return false
111
+
112
+ try {
113
+ const authCookies = JSON.parse(atob(decodeURIComponent(encoded)))
114
+
115
+ for (const [name, value] of Object.entries(authCookies)) {
116
+ document.cookie = serialize(name, value, {
117
+ path: '/',
118
+ maxAge: 60 * 60 * 24 * 30,
119
+ })
120
+ }
121
+
122
+ window.history.replaceState(null, '', cleanAuthParam(window.location.href))
123
+ return true
124
+ } catch {
125
+ return false
126
+ }
127
+ }
@@ -0,0 +1,78 @@
1
+ import CryptoJS from 'crypto-js'
2
+
3
+ /**
4
+ * AES-256-CBC encrypt (OpenSSL-compatible, matches .NET CryptoJsCompatDecryptorHelper).
5
+ * @param {string} plainText
6
+ * @param {string} passphrase
7
+ * @returns {string} base64 ciphertext
8
+ */
9
+ export function encrypt(plainText, passphrase) {
10
+ return CryptoJS.AES.encrypt(plainText, passphrase).toString()
11
+ }
12
+
13
+ /**
14
+ * AES-256-CBC decrypt (OpenSSL-compatible).
15
+ * @param {string} encryptedBase64
16
+ * @param {string} passphrase
17
+ * @returns {string} plaintext
18
+ */
19
+ export function decrypt(encryptedBase64, passphrase) {
20
+ const bytes = CryptoJS.AES.decrypt(encryptedBase64, passphrase)
21
+ return bytes.toString(CryptoJS.enc.Utf8)
22
+ }
23
+
24
+ /**
25
+ * Parse a JWT token and return its payload.
26
+ * @param {string} token
27
+ * @returns {Record<string, any>}
28
+ */
29
+ export function parseJwt(token) {
30
+ const base64Url = token.split('.')[1]
31
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
32
+ const jsonPayload = decodeURIComponent(
33
+ atob(base64)
34
+ .split('')
35
+ .map(c => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
36
+ .join(''),
37
+ )
38
+
39
+ return JSON.parse(jsonPayload)
40
+ }
41
+
42
+ /**
43
+ * Parse JWT credentials claim into a Set of active credential names.
44
+ * Format: "CredName###1" where 1 = active.
45
+ * @param {string} token
46
+ * @returns {Set<string>}
47
+ */
48
+ export function parseCredentials(token) {
49
+ if (!token) return new Set()
50
+
51
+ try {
52
+ const payload = parseJwt(token)
53
+ const raw = payload.credentials
54
+ if (!Array.isArray(raw)) return new Set()
55
+
56
+ const active = new Set()
57
+ for (const entry of raw) {
58
+ const [name, value] = entry.split('###')
59
+ if (value === '1' && name) active.add(name)
60
+ }
61
+ return active
62
+ } catch (err) {
63
+ console.error('[credentials] Failed to parse JWT credentials:', err)
64
+ return new Set()
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check whether a required credential is satisfied.
70
+ * Returns true if no credential is required.
71
+ * @param {Set<string>} credentials
72
+ * @param {string|undefined} required
73
+ * @returns {boolean}
74
+ */
75
+ export function hasCredential(credentials, required) {
76
+ if (!required) return true
77
+ return credentials.has(required)
78
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Microfrontend Event Types
3
+ */
4
+ export const MFEventTypes = {
5
+ AUTH_LOGIN: 'mf:auth:login',
6
+ AUTH_LOGOUT: 'mf:auth:logout',
7
+ AUTH_TOKEN_REFRESH: 'mf:auth:token-refresh',
8
+ AUTH_USER_UPDATE: 'mf:auth:user-update',
9
+ AUTH_MICROUI_TOKEN_REFRESH_REQUEST: 'mf:auth:microui-token-refresh-request',
10
+ AUTH_MICROUI_TOKEN_REFRESH_RESPONSE: 'mf:auth:microui-token-refresh-response',
11
+
12
+ NAV_ROUTE_CHANGE: 'mf:nav:route-change',
13
+ NAV_BREADCRUMB_UPDATE: 'mf:nav:breadcrumb-update',
14
+
15
+ THEME_CHANGE: 'mf:theme:change',
16
+ LOCALE_CHANGE: 'mf:locale:change',
17
+
18
+ LOADING_START: 'mf:loading:start',
19
+ LOADING_END: 'mf:loading:end',
20
+
21
+ ERROR_GLOBAL: 'mf:error:global',
22
+ ERROR_AUTH: 'mf:error:auth',
23
+
24
+ APP_MOUNTED: 'mf:app:mounted',
25
+ APP_UNMOUNTED: 'mf:app:unmounted',
26
+ APP_READY: 'mf:app:ready',
27
+ }
28
+
29
+ class EventBus {
30
+ constructor() {
31
+ this.events = new Map()
32
+ }
33
+
34
+ /**
35
+ * Subscribe to an event.
36
+ * @param {string} event
37
+ * @param {Function} callback
38
+ * @returns {Function} unsubscribe
39
+ */
40
+ on(event, callback) {
41
+ if (!this.events.has(event)) {
42
+ this.events.set(event, new Set())
43
+ }
44
+ this.events.get(event).add(callback)
45
+ return () => this.off(event, callback)
46
+ }
47
+
48
+ /**
49
+ * Subscribe once.
50
+ * @param {string} event
51
+ * @param {Function} callback
52
+ */
53
+ once(event, callback) {
54
+ const wrapper = (...args) => {
55
+ callback(...args)
56
+ this.off(event, wrapper)
57
+ }
58
+ this.on(event, wrapper)
59
+ }
60
+
61
+ /**
62
+ * Unsubscribe.
63
+ * @param {string} event
64
+ * @param {Function} callback
65
+ */
66
+ off(event, callback) {
67
+ if (this.events.has(event)) {
68
+ this.events.get(event).delete(callback)
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Emit an event.
74
+ * @param {string} event
75
+ * @param {*} data
76
+ */
77
+ emit(event, data) {
78
+ if (this.events.has(event)) {
79
+ this.events.get(event).forEach(callback => {
80
+ try {
81
+ callback(data)
82
+ } catch (error) {
83
+ console.error(`[EventBus] Error in handler for ${event}:`, error)
84
+ }
85
+ })
86
+ }
87
+
88
+ if (typeof window !== 'undefined') {
89
+ window.dispatchEvent(new CustomEvent(event, { detail: data }))
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Clear subscriptions.
95
+ * @param {string} [event] - If omitted, clears all.
96
+ */
97
+ clear(event) {
98
+ if (event) {
99
+ this.events.delete(event)
100
+ } else {
101
+ this.events.clear()
102
+ }
103
+ }
104
+ }
105
+
106
+ /** Singleton EventBus instance */
107
+ export const eventBus = new EventBus()
108
+
109
+ if (typeof window !== 'undefined') {
110
+ Object.values(MFEventTypes).forEach(eventType => {
111
+ window.addEventListener(eventType, event => {
112
+ if (!event._fromEventBus) {
113
+ eventBus.events.get(eventType)?.forEach(callback => {
114
+ callback(event.detail)
115
+ })
116
+ }
117
+ })
118
+ })
119
+ }
package/src/index.js ADDED
@@ -0,0 +1,65 @@
1
+ // Utils
2
+ export {
3
+ hexToRgb,
4
+ rgbToHex,
5
+ formatDate,
6
+ formatCurrency,
7
+ formatNumber,
8
+ isEmpty,
9
+ isNullOrUndefined,
10
+ isEmptyArray,
11
+ isObject,
12
+ isToday,
13
+ deepClone,
14
+ getAppConfig,
15
+ getAllAppConfig,
16
+ loadMicroUIConfig,
17
+ loadAllMicroUIConfigs,
18
+ } from './utils/index.js'
19
+
20
+ // Crypto & Credentials
21
+ export {
22
+ encrypt,
23
+ decrypt,
24
+ parseJwt,
25
+ parseCredentials,
26
+ hasCredential,
27
+ } from './crypto/index.js'
28
+
29
+ // Event Bus
30
+ export { eventBus, MFEventTypes } from './eventBus/index.js'
31
+
32
+ // API Client
33
+ export { createAppApi, getApiBaseUrl } from './api/index.js'
34
+
35
+ // Auth Helpers
36
+ export {
37
+ isAuthenticated,
38
+ getShellUrl,
39
+ redirectToLogin,
40
+ receiveAuthFromUrl,
41
+ } from './auth/index.js'
42
+
43
+ // Validators
44
+ export {
45
+ requiredValidator,
46
+ emailValidator,
47
+ passwordValidator,
48
+ confirmedValidator,
49
+ betweenValidator,
50
+ integerValidator,
51
+ regexValidator,
52
+ alphaValidator,
53
+ urlValidator,
54
+ lengthValidator,
55
+ alphaDashValidator,
56
+ } from './validators/index.js'
57
+
58
+ // Types & Enums
59
+ export {
60
+ AppContentLayoutNav,
61
+ ContentWidth,
62
+ NavbarType,
63
+ FooterType,
64
+ Skins,
65
+ } from './types/index.js'
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @typedef {Object} ThemeConfig
3
+ * @property {string} theme - 'light' | 'dark' | 'system'
4
+ * @property {boolean} isVerticalNavCollapsed
5
+ * @property {string} contentWidth - 'boxed' | 'fluid'
6
+ * @property {string} contentLayoutNav - 'vertical' | 'horizontal'
7
+ * @property {boolean} isVerticalNavSemiDark
8
+ * @property {boolean} isAppRTL
9
+ * @property {string} navbarType - 'sticky' | 'static' | 'hidden'
10
+ * @property {string} footerType - 'sticky' | 'static' | 'hidden'
11
+ * @property {boolean} navbarBlur
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} UserData
16
+ * @property {string|number} id
17
+ * @property {string} fullName
18
+ * @property {string} username
19
+ * @property {string} email
20
+ * @property {string} avatar
21
+ * @property {string} role
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object} AbilityRule
26
+ * @property {string} action
27
+ * @property {string} subject
28
+ */
29
+
30
+ /**
31
+ * @typedef {Object} NavItem
32
+ * @property {string} title
33
+ * @property {string} [to]
34
+ * @property {Object} [icon]
35
+ * @property {NavItem[]} [children]
36
+ * @property {string} [heading]
37
+ */
38
+
39
+ /**
40
+ * @typedef {Object} MFAppConfig
41
+ * @property {string} name
42
+ * @property {string} url
43
+ * @property {string} scope
44
+ * @property {string} module
45
+ */
46
+
47
+ export const AppContentLayoutNav = {
48
+ Vertical: 'vertical',
49
+ Horizontal: 'horizontal',
50
+ }
51
+
52
+ export const ContentWidth = {
53
+ Boxed: 'boxed',
54
+ Fluid: 'fluid',
55
+ }
56
+
57
+ export const NavbarType = {
58
+ Sticky: 'sticky',
59
+ Static: 'static',
60
+ Hidden: 'hidden',
61
+ }
62
+
63
+ export const FooterType = {
64
+ Sticky: 'sticky',
65
+ Static: 'static',
66
+ Hidden: 'hidden',
67
+ }
68
+
69
+ export const Skins = {
70
+ Default: 'default',
71
+ Bordered: 'bordered',
72
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Runtime configuration loader.
3
+ *
4
+ * Host app (Docker/K8s): reads from window.__APP_CONFIG__
5
+ * Micro-UI (loaded in host): reads from window.__MICROUI_CONFIG__[slug]
6
+ * Development (local): falls back to import.meta.env
7
+ *
8
+ * @param {string} key - Config key (e.g., 'VITE_API_BASE_URL')
9
+ * @param {string} [appSlug] - Optional micro-UI slug for namespaced config
10
+ * @returns {string|undefined}
11
+ */
12
+ export function getAppConfig(key, appSlug) {
13
+ if (typeof window !== 'undefined') {
14
+ if (appSlug && window.__MICROUI_CONFIG__?.[appSlug]?.[key] !== undefined)
15
+ return window.__MICROUI_CONFIG__[appSlug][key]
16
+
17
+ if (window.__APP_CONFIG__?.[key] !== undefined)
18
+ return window.__APP_CONFIG__[key]
19
+ }
20
+
21
+ return typeof import.meta !== 'undefined' ? import.meta.env?.[key] : undefined
22
+ }
23
+
24
+ /**
25
+ * Get all app configuration merged (build-time + host + micro-UI).
26
+ * @param {string} [appSlug]
27
+ * @returns {Record<string, string>}
28
+ */
29
+ export function getAllAppConfig(appSlug) {
30
+ const buildConfig = (typeof import.meta !== 'undefined' && import.meta.env) || {}
31
+ const hostConfig = (typeof window !== 'undefined' && window.__APP_CONFIG__) || {}
32
+ const microConfig = (typeof window !== 'undefined' && appSlug && window.__MICROUI_CONFIG__?.[appSlug]) || {}
33
+
34
+ return { ...buildConfig, ...hostConfig, ...microConfig }
35
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Convert hex color to RGB string
3
+ * @param {string} hex - Hex color string (e.g., '#ff0000' or 'ff0000')
4
+ * @returns {string|null} RGB string (e.g., '255, 0, 0')
5
+ */
6
+ export const hexToRgb = hex => {
7
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i
8
+ hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b)
9
+
10
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
11
+
12
+ return result
13
+ ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`
14
+ : null
15
+ }
16
+
17
+ /**
18
+ * Convert RGB values to hex color string
19
+ * @param {number} r - Red (0-255)
20
+ * @param {number} g - Green (0-255)
21
+ * @param {number} b - Blue (0-255)
22
+ * @returns {string} Hex color string (e.g., '#ff0000')
23
+ */
24
+ export const rgbToHex = (r, g, b) => {
25
+ const toHex = c => {
26
+ const hex = c.toString(16)
27
+ return hex.length === 1 ? '0' + hex : hex
28
+ }
29
+
30
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`
31
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Format date to locale string
3
+ * @param {Date|string|number} date
4
+ * @param {string} [locale='en-US']
5
+ * @param {Intl.DateTimeFormatOptions} [options={}]
6
+ * @returns {string}
7
+ */
8
+ export const formatDate = (date, locale = 'en-US', options = {}) => {
9
+ const defaultOptions = {
10
+ year: 'numeric',
11
+ month: 'short',
12
+ day: 'numeric',
13
+ ...options,
14
+ }
15
+
16
+ return new Intl.DateTimeFormat(locale, defaultOptions).format(new Date(date))
17
+ }
18
+
19
+ /**
20
+ * Format number as currency
21
+ * @param {number} value
22
+ * @param {string} [currency='USD']
23
+ * @param {string} [locale='en-US']
24
+ * @returns {string}
25
+ */
26
+ export const formatCurrency = (value, currency = 'USD', locale = 'en-US') => {
27
+ return new Intl.NumberFormat(locale, {
28
+ style: 'currency',
29
+ currency,
30
+ }).format(value)
31
+ }
32
+
33
+ /**
34
+ * Format number with locale
35
+ * @param {number} value
36
+ * @param {string} [locale='en-US']
37
+ * @param {Intl.NumberFormatOptions} [options={}]
38
+ * @returns {string}
39
+ */
40
+ export const formatNumber = (value, locale = 'en-US', options = {}) => {
41
+ return new Intl.NumberFormat(locale, options).format(value)
42
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Check if value is empty (null, undefined, empty string, empty array, empty object)
3
+ * @param {*} value
4
+ * @returns {boolean}
5
+ */
6
+ export const isEmpty = value => {
7
+ if (value === null || value === undefined || value === '') return true
8
+ if (Array.isArray(value) && value.length === 0) return true
9
+ if (typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 0) return true
10
+ return false
11
+ }
12
+
13
+ /** @param {*} value */
14
+ export const isNullOrUndefined = value => value === null || value === undefined
15
+
16
+ /** @param {*} arr */
17
+ export const isEmptyArray = arr => Array.isArray(arr) && arr.length === 0
18
+
19
+ /**
20
+ * Check if value is a plain object (not array, not null)
21
+ * @param {*} value
22
+ * @returns {boolean}
23
+ */
24
+ export const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value)
25
+
26
+ /**
27
+ * Check if a Date is today
28
+ * @param {Date} date
29
+ * @returns {boolean}
30
+ */
31
+ export const isToday = date => {
32
+ const today = new Date()
33
+ return (
34
+ date.getDate() === today.getDate() &&
35
+ date.getMonth() === today.getMonth() &&
36
+ date.getFullYear() === today.getFullYear()
37
+ )
38
+ }
39
+
40
+ /**
41
+ * Deep clone an object
42
+ * @param {*} obj
43
+ * @returns {*}
44
+ */
45
+ export const deepClone = obj => {
46
+ if (obj === null || typeof obj !== 'object') return obj
47
+ if (Array.isArray(obj)) return obj.map(deepClone)
48
+
49
+ const cloned = {}
50
+ for (const key in obj) {
51
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
52
+ cloned[key] = deepClone(obj[key])
53
+ }
54
+ }
55
+ return cloned
56
+ }
@@ -0,0 +1,5 @@
1
+ export { hexToRgb, rgbToHex } from './colorConverter.js'
2
+ export { formatDate, formatCurrency, formatNumber } from './formatters.js'
3
+ export { isEmpty, isNullOrUndefined, isEmptyArray, isObject, isToday, deepClone } from './helpers.js'
4
+ export { getAppConfig, getAllAppConfig } from './appConfig.js'
5
+ export { loadMicroUIConfig, loadAllMicroUIConfigs } from './microuiConfigLoader.js'
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Fetches each micro-UI's /config.js from its container origin,
3
+ * parses the JSON object from it, and stores it under
4
+ * window.__MICROUI_CONFIG__[slug].
5
+ */
6
+
7
+ if (typeof window !== 'undefined' && !window.__MICROUI_CONFIG__) {
8
+ window.__MICROUI_CONFIG__ = {}
9
+ }
10
+
11
+ function extractOrigin(remoteUrl) {
12
+ try {
13
+ return new URL(remoteUrl).origin
14
+ } catch {
15
+ return null
16
+ }
17
+ }
18
+
19
+ function parseConfigJs(text) {
20
+ const start = text.indexOf('{')
21
+ const end = text.lastIndexOf('}')
22
+ if (start === -1 || end === -1) return null
23
+
24
+ try {
25
+ return JSON.parse(text.slice(start, end + 1))
26
+ } catch {
27
+ return null
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Load a single micro-UI's runtime config from its container.
33
+ * @param {string} slug
34
+ * @param {string} remoteUrl - The remoteEntry.js URL (used to derive origin)
35
+ * @returns {Promise<Record<string, string>|null>}
36
+ */
37
+ export async function loadMicroUIConfig(slug, remoteUrl) {
38
+ const origin = extractOrigin(remoteUrl)
39
+ if (!origin) return null
40
+
41
+ try {
42
+ const response = await fetch(`${origin}/config.js`, { cache: 'no-cache' })
43
+ if (!response.ok) return null
44
+
45
+ const text = await response.text()
46
+ const config = parseConfigJs(text)
47
+ if (config && typeof window !== 'undefined') {
48
+ window.__MICROUI_CONFIG__[slug] = config
49
+ }
50
+
51
+ return config
52
+ } catch {
53
+ console.warn(`[MicroUIConfig] Failed to load config for ${slug} from ${origin}/config.js`)
54
+ return null
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Load runtime configs for all registered micro-UIs in parallel.
60
+ * @param {Record<string, { remoteUrl: string }>} microuiConfig
61
+ * @returns {Promise<void>}
62
+ */
63
+ export async function loadAllMicroUIConfigs(microuiConfig) {
64
+ const entries = Object.entries(microuiConfig)
65
+ if (entries.length === 0) return
66
+
67
+ await Promise.allSettled(
68
+ entries.map(([slug, config]) => loadMicroUIConfig(slug, config.remoteUrl)),
69
+ )
70
+ }
@@ -0,0 +1,68 @@
1
+ import { isEmpty, isNullOrUndefined, isEmptyArray } from '../utils/helpers.js'
2
+
3
+ export const requiredValidator = value => {
4
+ if (isNullOrUndefined(value) || isEmptyArray(value) || value === false)
5
+ return 'Bu alan zorunludur'
6
+
7
+ return !!String(value).trim().length || 'Bu alan zorunludur'
8
+ }
9
+
10
+ export const emailValidator = value => {
11
+ if (isEmpty(value)) return true
12
+ const re = /^(?:[^<>()[\]\\.,;:\s@"]+(?:\.[^<>()[\]\\.,;:\s@"]+)*|".+")@(?:\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]|(?:[a-z\-\d]+\.)+[a-z]{2,})$/i
13
+ if (Array.isArray(value))
14
+ return value.every(val => re.test(String(val))) || 'Gecerli bir e-posta adresi girin'
15
+
16
+ return re.test(String(value)) || 'Gecerli bir e-posta adresi girin'
17
+ }
18
+
19
+ export const passwordValidator = password => {
20
+ const regExp = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%&*()]).{8,}/
21
+ return regExp.test(password) || 'Alan en az bir buyuk harf, kucuk harf, ozel karakter ve rakam icermeli ve en az 8 karakter uzunlugunda olmalidir'
22
+ }
23
+
24
+ export const confirmedValidator = (value, target) =>
25
+ value === target || 'Sifreler eslesmedi'
26
+
27
+ export const betweenValidator = (value, min, max) => {
28
+ const n = Number(value)
29
+ return (Number(min) <= n && Number(max) >= n) || `${min} ve ${max} arasi bir deger girin`
30
+ }
31
+
32
+ export const integerValidator = value => {
33
+ if (isEmpty(value)) return true
34
+ if (Array.isArray(value))
35
+ return value.every(val => /^-?\d+$/.test(String(val))) || 'Bu alan tam sayi olmalidir'
36
+
37
+ return /^-?\d+$/.test(String(value)) || 'Bu alan tam sayi olmalidir'
38
+ }
39
+
40
+ export const regexValidator = (value, regex) => {
41
+ if (isEmpty(value)) return true
42
+ let re = regex
43
+ if (typeof re === 'string') re = new RegExp(re)
44
+ if (Array.isArray(value)) return value.every(val => regexValidator(val, re))
45
+
46
+ return re.test(String(value)) || 'Gecersiz format'
47
+ }
48
+
49
+ export const alphaValidator = value => {
50
+ if (isEmpty(value)) return true
51
+ return /^[A-Z]*$/i.test(String(value)) || 'Sadece harf karakterleri kullanilaabilir'
52
+ }
53
+
54
+ export const urlValidator = value => {
55
+ if (isEmpty(value)) return true
56
+ const re = /^https?:\/\/[^\s$.?#].\S*$/
57
+ return re.test(String(value)) || 'Gecersiz URL'
58
+ }
59
+
60
+ export const lengthValidator = (value, length) => {
61
+ if (isEmpty(value)) return true
62
+ return String(value).length === length || `Karakter uzunlugu ${length} karakter olmalidir`
63
+ }
64
+
65
+ export const alphaDashValidator = value => {
66
+ if (isEmpty(value)) return true
67
+ return /^[\w-]*$/.test(String(value)) || 'Gecersiz karakter'
68
+ }